Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
git/builtin-rev-list.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
372 lines (330 sloc)
8.33 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |