| #include "cache.h" |
| #include "refs.h" |
| #include "tag.h" |
| #include "commit.h" |
| #include "tree.h" |
| #include "blob.h" |
| #include "tree-walk.h" |
| #include "diff.h" |
| #include "revision.h" |
| #include "builtin.h" |
| |
| /* bits #0-15 in revision.h */ |
| |
| #define COUNTED (1u<<16) |
| |
| static const char rev_list_usage[] = |
| "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n" |
| " limiting output:\n" |
| " --max-count=nr\n" |
| " --max-age=epoch\n" |
| " --min-age=epoch\n" |
| " --sparse\n" |
| " --no-merges\n" |
| " --remove-empty\n" |
| " --all\n" |
| " ordering output:\n" |
| " --topo-order\n" |
| " --date-order\n" |
| " formatting output:\n" |
| " --parents\n" |
| " --objects | --objects-edge\n" |
| " --unpacked\n" |
| " --header | --pretty\n" |
| " --abbrev=nr | --no-abbrev\n" |
| " --abbrev-commit\n" |
| " special purpose:\n" |
| " --bisect" |
| ; |
| |
| static struct rev_info revs; |
| |
| static int bisect_list = 0; |
| static int show_timestamp = 0; |
| static int hdr_termination = 0; |
| static const char *header_prefix; |
| |
| static void show_commit(struct commit *commit) |
| { |
| if (show_timestamp) |
| printf("%lu ", commit->date); |
| if (header_prefix) |
| fputs(header_prefix, stdout); |
| if (commit->object.flags & BOUNDARY) |
| putchar('-'); |
| if (revs.abbrev_commit && revs.abbrev) |
| fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), |
| stdout); |
| else |
| fputs(sha1_to_hex(commit->object.sha1), stdout); |
| if (revs.parents) { |
| struct commit_list *parents = commit->parents; |
| while (parents) { |
| struct object *o = &(parents->item->object); |
| parents = parents->next; |
| if (o->flags & TMP_MARK) |
| continue; |
| printf(" %s", sha1_to_hex(o->sha1)); |
| o->flags |= TMP_MARK; |
| } |
| /* TMP_MARK is a general purpose flag that can |
| * be used locally, but the user should clean |
| * things up after it is done with them. |
| */ |
| for (parents = commit->parents; |
| parents; |
| parents = parents->next) |
| parents->item->object.flags &= ~TMP_MARK; |
| } |
| if (revs.commit_format == CMIT_FMT_ONELINE) |
| putchar(' '); |
| else |
| putchar('\n'); |
| |
| if (revs.verbose_header) { |
| static char pretty_header[16384]; |
| pretty_print_commit(revs.commit_format, commit, ~0, |
| pretty_header, sizeof(pretty_header), |
| revs.abbrev, NULL, NULL); |
| printf("%s%c", pretty_header, hdr_termination); |
| } |
| fflush(stdout); |
| if (commit->parents) { |
| free_commit_list(commit->parents); |
| commit->parents = NULL; |
| } |
| if (commit->buffer) { |
| free(commit->buffer); |
| commit->buffer = NULL; |
| } |
| } |
| |
| static void process_blob(struct blob *blob, |
| struct object_array *p, |
| struct name_path *path, |
| const char *name) |
| { |
| struct object *obj = &blob->object; |
| |
| if (!revs.blob_objects) |
| return; |
| if (obj->flags & (UNINTERESTING | SEEN)) |
| return; |
| obj->flags |= SEEN; |
| name = strdup(name); |
| add_object(obj, p, path, name); |
| } |
| |
| static void process_tree(struct tree *tree, |
| struct object_array *p, |
| struct name_path *path, |
| const char *name) |
| { |
| struct object *obj = &tree->object; |
| struct tree_desc desc; |
| struct name_entry entry; |
| struct name_path me; |
| |
| if (!revs.tree_objects) |
| return; |
| if (obj->flags & (UNINTERESTING | SEEN)) |
| return; |
| if (parse_tree(tree) < 0) |
| die("bad tree object %s", sha1_to_hex(obj->sha1)); |
| obj->flags |= SEEN; |
| name = strdup(name); |
| add_object(obj, p, path, name); |
| me.up = path; |
| me.elem = name; |
| me.elem_len = strlen(name); |
| |
| desc.buf = tree->buffer; |
| desc.size = tree->size; |
| |
| while (tree_entry(&desc, &entry)) { |
| if (S_ISDIR(entry.mode)) |
| process_tree(lookup_tree(entry.sha1), p, &me, entry.path); |
| else |
| process_blob(lookup_blob(entry.sha1), p, &me, entry.path); |
| } |
| free(tree->buffer); |
| tree->buffer = NULL; |
| } |
| |
| static void show_commit_list(struct rev_info *revs) |
| { |
| int i; |
| struct commit *commit; |
| struct object_array objects = { 0, 0, NULL }; |
| |
| while ((commit = get_revision(revs)) != NULL) { |
| process_tree(commit->tree, &objects, NULL, ""); |
| show_commit(commit); |
| } |
| for (i = 0; i < revs->pending.nr; i++) { |
| struct object_array_entry *pending = revs->pending.objects + i; |
| struct object *obj = pending->item; |
| const char *name = pending->name; |
| if (obj->flags & (UNINTERESTING | SEEN)) |
| continue; |
| if (obj->type == TYPE_TAG) { |
| obj->flags |= SEEN; |
| add_object_array(obj, name, &objects); |
| continue; |
| } |
| if (obj->type == TYPE_TREE) { |
| process_tree((struct tree *)obj, &objects, NULL, name); |
| continue; |
| } |
| if (obj->type == TYPE_BLOB) { |
| process_blob((struct blob *)obj, &objects, NULL, name); |
| continue; |
| } |
| die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); |
| } |
| for (i = 0; i < objects.nr; i++) { |
| struct object_array_entry *p = objects.objects + i; |
| |
| /* An object with name "foo\n0000000..." can be used to |
| * confuse downstream git-pack-objects very badly. |
| */ |
| const char *ep = strchr(p->name, '\n'); |
| if (ep) { |
| printf("%s %.*s\n", sha1_to_hex(p->item->sha1), |
| (int) (ep - p->name), |
| p->name); |
| } |
| else |
| printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); |
| } |
| } |
| |
| /* |
| * This is a truly stupid algorithm, but it's only |
| * used for bisection, and we just don't care enough. |
| * |
| * We care just barely enough to avoid recursing for |
| * non-merge entries. |
| */ |
| static int count_distance(struct commit_list *entry) |
| { |
| int nr = 0; |
| |
| while (entry) { |
| struct commit *commit = entry->item; |
| struct commit_list *p; |
| |
| if (commit->object.flags & (UNINTERESTING | COUNTED)) |
| break; |
| if (!revs.prune_fn || (commit->object.flags & TREECHANGE)) |
| nr++; |
| commit->object.flags |= COUNTED; |
| p = commit->parents; |
| entry = p; |
| if (p) { |
| p = p->next; |
| while (p) { |
| nr += count_distance(p); |
| p = p->next; |
| } |
| } |
| } |
| |
| return nr; |
| } |
| |
| static void clear_distance(struct commit_list *list) |
| { |
| while (list) { |
| struct commit *commit = list->item; |
| commit->object.flags &= ~COUNTED; |
| list = list->next; |
| } |
| } |
| |
| static struct commit_list *find_bisection(struct commit_list *list) |
| { |
| int nr, closest; |
| struct commit_list *p, *best; |
| |
| nr = 0; |
| p = list; |
| while (p) { |
| if (!revs.prune_fn || (p->item->object.flags & TREECHANGE)) |
| nr++; |
| p = p->next; |
| } |
| closest = 0; |
| best = list; |
| |
| for (p = list; p; p = p->next) { |
| int distance; |
| |
| if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) |
| continue; |
| |
| distance = count_distance(p); |
| clear_distance(list); |
| if (nr - distance < distance) |
| distance = nr - distance; |
| if (distance > closest) { |
| best = p; |
| closest = distance; |
| } |
| } |
| if (best) |
| best->next = NULL; |
| return best; |
| } |
| |
| static void mark_edge_parents_uninteresting(struct commit *commit) |
| { |
| struct commit_list *parents; |
| |
| for (parents = commit->parents; parents; parents = parents->next) { |
| struct commit *parent = parents->item; |
| if (!(parent->object.flags & UNINTERESTING)) |
| continue; |
| mark_tree_uninteresting(parent->tree); |
| if (revs.edge_hint && !(parent->object.flags & SHOWN)) { |
| parent->object.flags |= SHOWN; |
| printf("-%s\n", sha1_to_hex(parent->object.sha1)); |
| } |
| } |
| } |
| |
| static void mark_edges_uninteresting(struct commit_list *list) |
| { |
| for ( ; list; list = list->next) { |
| struct commit *commit = list->item; |
| |
| if (commit->object.flags & UNINTERESTING) { |
| mark_tree_uninteresting(commit->tree); |
| continue; |
| } |
| mark_edge_parents_uninteresting(commit); |
| } |
| } |
| |
| int cmd_rev_list(int argc, const char **argv, char **envp) |
| { |
| struct commit_list *list; |
| int i; |
| |
| init_revisions(&revs); |
| revs.abbrev = 0; |
| revs.commit_format = CMIT_FMT_UNSPECIFIED; |
| argc = setup_revisions(argc, argv, &revs, NULL); |
| |
| for (i = 1 ; i < argc; i++) { |
| const char *arg = argv[i]; |
| |
| if (!strcmp(arg, "--header")) { |
| revs.verbose_header = 1; |
| continue; |
| } |
| if (!strcmp(arg, "--timestamp")) { |
| show_timestamp = 1; |
| continue; |
| } |
| if (!strcmp(arg, "--bisect")) { |
| bisect_list = 1; |
| continue; |
| } |
| usage(rev_list_usage); |
| |
| } |
| if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { |
| /* The command line has a --pretty */ |
| hdr_termination = '\n'; |
| if (revs.commit_format == CMIT_FMT_ONELINE) |
| header_prefix = ""; |
| else |
| header_prefix = "commit "; |
| } |
| else if (revs.verbose_header) |
| /* Only --header was specified */ |
| revs.commit_format = CMIT_FMT_RAW; |
| |
| list = revs.commits; |
| |
| if ((!list && |
| (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && |
| !revs.pending.nr)) || |
| revs.diff) |
| usage(rev_list_usage); |
| |
| save_commit_buffer = revs.verbose_header; |
| track_object_refs = 0; |
| if (bisect_list) |
| revs.limited = 1; |
| |
| prepare_revision_walk(&revs); |
| if (revs.tree_objects) |
| mark_edges_uninteresting(revs.commits); |
| |
| if (bisect_list) |
| revs.commits = find_bisection(revs.commits); |
| |
| show_commit_list(&revs); |
| |
| return 0; |
| } |