#define COUNTED (1u << 2)
#define SHOWN (1u << 3)
#define TREECHANGE (1u << 4)
+#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */
static const char rev_list_usage[] =
- "git-rev-list [OPTION] commit-id <commit-id>\n"
- " --max-count=nr\n"
- " --max-age=epoch\n"
- " --min-age=epoch\n"
- " --parents\n"
- " --bisect\n"
- " --objects\n"
- " --unpacked\n"
- " --header\n"
- " --pretty\n"
- " --no-merges\n"
- " --merge-order [ --show-breaks ]\n"
- " --topo-order";
+"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"
+" --merge-order [ --show-breaks ]\n"
+" --topo-order\n"
+" formatting output:\n"
+" --parents\n"
+" --objects\n"
+" --unpacked\n"
+" --header | --pretty\n"
+" --abbrev=nr | --no-abbrev\n"
+" special purpose:\n"
+" --bisect"
+;
static int dense = 1;
static int unpacked = 0;
static int tree_objects = 0;
static int blob_objects = 0;
static int verbose_header = 0;
+static int abbrev = DEFAULT_ABBREV;
static int show_parents = 0;
static int hdr_termination = 0;
static const char *commit_prefix = "";
static int topo_order = 0;
static int no_merges = 0;
static const char **paths = NULL;
+static int remove_empty_trees = 0;
static void show_commit(struct commit *commit)
{
if (show_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
- printf(" %s", sha1_to_hex(parents->item->object.sha1));
+ 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 (commit_format == CMIT_FMT_ONELINE)
putchar(' ');
if (verbose_header) {
static char pretty_header[16384];
- pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header));
+ pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
printf("%s%c", pretty_header, hdr_termination);
}
fflush(stdout);
stop_traversal=1;
return CONTINUE;
}
- if (max_count != -1 && !max_count--)
- return STOP;
if (no_merges && (commit->parents && commit->parents->next))
return CONTINUE;
if (paths && dense) {
return CONTINUE;
}
+ if (max_count != -1 && !max_count--)
+ return STOP;
+
show_commit(commit);
return CONTINUE;
if (commit->object.flags & (UNINTERESTING | COUNTED))
break;
- nr++;
+ if (!paths || (commit->object.flags & TREECHANGE))
+ nr++;
commit->object.flags |= COUNTED;
p = commit->parents;
entry = p;
}
}
}
+
return nr;
}
nr = 0;
p = list;
while (p) {
- nr++;
+ if (!paths || (p->item->object.flags & TREECHANGE))
+ nr++;
p = p->next;
}
closest = 0;
best = list;
- p = list;
- while (p) {
- int distance = count_distance(p);
+ for (p = list; p; p = p->next) {
+ int distance;
+
+ if (paths && !(p->item->object.flags & TREECHANGE))
+ continue;
+
+ distance = count_distance(p);
clear_distance(list);
if (nr - distance < distance)
distance = nr - distance;
best = p;
closest = distance;
}
- p = p->next;
}
if (best)
best->next = NULL;
}
}
-static int is_different = 0;
+#define TREE_SAME 0
+#define TREE_NEW 1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
const char *base, const char *path)
{
- is_different = 1;
+ int diff = TREE_DIFFERENT;
+
+ /*
+ * Is it an add of a new file? It means that
+ * the old tree didn't have it at all, so we
+ * will turn "TREE_SAME" -> "TREE_NEW", but
+ * leave any "TREE_DIFFERENT" alone (and if
+ * it already was "TREE_NEW", we'll keep it
+ * "TREE_NEW" of course).
+ */
+ if (addremove == '+') {
+ diff = tree_difference;
+ if (diff != TREE_SAME)
+ return;
+ diff = TREE_NEW;
+ }
+ tree_difference = diff;
}
static void file_change(struct diff_options *options,
const unsigned char *new_sha1,
const char *base, const char *path)
{
- is_different = 1;
+ tree_difference = TREE_DIFFERENT;
}
static struct diff_options diff_opt = {
.change = file_change,
};
-static int same_tree(struct tree *t1, struct tree *t2)
+static int compare_tree(struct tree *t1, struct tree *t2)
{
- is_different = 0;
+ if (!t1)
+ return TREE_NEW;
+ if (!t2)
+ return TREE_DIFFERENT;
+ tree_difference = TREE_SAME;
if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
- return 0;
- return !is_different;
+ return TREE_DIFFERENT;
+ return tree_difference;
}
static int same_tree_as_empty(struct tree *t1)
empty.buf = "";
empty.size = 0;
- is_different = 0;
+ tree_difference = 0;
retval = diff_tree(&empty, &real, "", &diff_opt);
free(tree);
- return retval >= 0 && !is_different;
+ return retval >= 0 && !tree_difference;
}
-static struct commit *try_to_simplify_merge(struct commit *commit, struct commit_list *parent)
+static void try_to_simplify_commit(struct commit *commit)
{
+ struct commit_list **pp, *parent;
+
if (!commit->tree)
- return NULL;
+ return;
- while (parent) {
+ if (!commit->parents) {
+ if (!same_tree_as_empty(commit->tree))
+ commit->object.flags |= TREECHANGE;
+ return;
+ }
+
+ pp = &commit->parents;
+ while ((parent = *pp) != NULL) {
struct commit *p = parent->item;
- parent = parent->next;
+
+ if (p->object.flags & UNINTERESTING) {
+ pp = &parent->next;
+ continue;
+ }
+
parse_commit(p);
- if (!p->tree)
+ switch (compare_tree(p->tree, commit->tree)) {
+ case TREE_SAME:
+ parent->next = NULL;
+ commit->parents = parent;
+ return;
+
+ case TREE_NEW:
+ if (remove_empty_trees && same_tree_as_empty(p->tree)) {
+ *pp = parent->next;
+ continue;
+ }
+ /* fallthrough */
+ case TREE_DIFFERENT:
+ pp = &parent->next;
continue;
- if (same_tree(commit->tree, p->tree))
- return p;
+ }
+ die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- return NULL;
+ commit->object.flags |= TREECHANGE;
}
static void add_parents_to_list(struct commit *commit, struct commit_list **list)
}
/*
- * Ok, the commit wasn't uninteresting. If it
- * is a merge, try to find the parent that has
- * no differences in the path set if one exists.
+ * Ok, the commit wasn't uninteresting. Try to
+ * simplify the commit history and find the parent
+ * that has no differences in the path set if one exists.
*/
- if (paths && parent && parent->next) {
- struct commit *preferred;
-
- preferred = try_to_simplify_merge(commit, parent);
- if (preferred) {
- parent->item = preferred;
- parent->next = NULL;
- }
- }
+ if (paths)
+ try_to_simplify_commit(commit);
+ parent = commit->parents;
while (parent) {
struct commit *p = parent->item;
}
}
-static void compress_list(struct commit_list *list)
-{
- while (list) {
- struct commit *commit = list->item;
- struct commit_list *parent = commit->parents;
- list = list->next;
-
- if (!parent) {
- if (!same_tree_as_empty(commit->tree))
- commit->object.flags |= TREECHANGE;
- continue;
- }
-
- /*
- * Exactly one parent? Check if it leaves the tree
- * unchanged
- */
- if (!parent->next) {
- struct tree *t1 = commit->tree;
- struct tree *t2 = parent->item->tree;
- if (!t1 || !t2 || same_tree(t1, t2))
- continue;
- }
- commit->object.flags |= TREECHANGE;
- }
-}
-
static struct commit_list *limit_list(struct commit_list *list)
{
struct commit_list *newlist = NULL;
}
if (tree_objects)
mark_edges_uninteresting(newlist);
- if (paths && dense)
- compress_list(newlist);
if (bisect_list)
newlist = find_bisection(newlist);
return newlist;
add_object(obj, &pending_objects, name);
}
-static struct commit *get_commit_reference(const char *name, unsigned int flags)
+static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
{
- unsigned char sha1[20];
struct object *object;
- if (get_sha1(name, sha1))
- return NULL;
object = parse_object(sha1);
if (!object)
die("bad object %s", name);
static int include_one_commit(const char *path, const unsigned char *sha1)
{
- struct commit *com = get_commit_reference(path, 0);
+ struct commit *com = get_commit_reference(path, sha1, 0);
handle_one_commit(com, global_lst);
return 0;
}
const char *arg = argv[i];
char *dotdot;
struct commit *commit;
+ unsigned char sha1[20];
+ /* accept -<digit>, like traditilnal "head" */
+ if ((*arg == '-') && isdigit(arg[1])) {
+ max_count = atoi(arg + 1);
+ continue;
+ }
+ if (!strcmp(arg, "-n")) {
+ if (++i >= argc)
+ die("-n requires an argument");
+ max_count = atoi(argv[i]);
+ continue;
+ }
+ if (!strncmp(arg,"-n",2)) {
+ max_count = atoi(arg + 2);
+ continue;
+ }
if (!strncmp(arg, "--max-count=", 12)) {
max_count = atoi(arg + 12);
continue;
verbose_header = 1;
continue;
}
+ if (!strcmp(arg, "--no-abbrev")) {
+ abbrev = 0;
+ continue;
+ }
+ if (!strncmp(arg, "--abbrev=", 9)) {
+ abbrev = strtoul(arg + 9, NULL, 10);
+ if (abbrev && abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ else if (40 < abbrev)
+ abbrev = 40;
+ continue;
+ }
if (!strncmp(arg, "--pretty", 8)) {
commit_format = get_commit_format(arg+8);
verbose_header = 1;
dense = 0;
continue;
}
+ if (!strcmp(arg, "--remove-empty")) {
+ remove_empty_trees = 1;
+ continue;
+ }
if (!strcmp(arg, "--")) {
i++;
break;
flags = 0;
dotdot = strstr(arg, "..");
if (dotdot) {
+ unsigned char from_sha1[20];
char *next = dotdot + 2;
- struct commit *exclude = NULL;
- struct commit *include = NULL;
*dotdot = 0;
if (!*next)
next = "HEAD";
- exclude = get_commit_reference(arg, UNINTERESTING);
- include = get_commit_reference(next, 0);
- if (exclude && include) {
+ if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+ struct commit *exclude;
+ struct commit *include;
+
+ exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
+ include = get_commit_reference(next, sha1, 0);
+ if (!exclude || !include)
+ die("Invalid revision range %s..%s", arg, next);
limited = 1;
handle_one_commit(exclude, &list);
handle_one_commit(include, &list);
arg++;
limited = 1;
}
- commit = get_commit_reference(arg, flags);
- if (!commit)
+ if (get_sha1(arg, sha1) < 0) {
+ struct stat st;
+ if (lstat(arg, &st) < 0)
+ die("'%s': %s", arg, strerror(errno));
break;
+ }
+ commit = get_commit_reference(arg, sha1, flags);
handle_one_commit(commit, &list);
}
- if (!list)
+ if (!list &&
+ (!(tag_objects||tree_objects||blob_objects) && !pending_objects))
usage(rev_list_usage);
paths = get_pathspec(prefix, argv + i);