#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>... [ -- paths... ]\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"
+" --date-order\n"
" formatting output:\n"
" --parents\n"
-" --objects\n"
+" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
+" --abbrev=nr | --no-abbrev\n"
" special purpose:\n"
" --bisect"
;
static int tag_objects = 0;
static int tree_objects = 0;
static int blob_objects = 0;
+static int edge_hint = 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 show_breaks = 0;
static int stop_traversal = 0;
static int topo_order = 0;
+static int lifo = 1;
static int no_merges = 0;
static const char **paths = NULL;
+static int remove_empty_trees = 0;
+
+struct name_path {
+ struct name_path *up;
+ int elem_len;
+ const char *elem;
+};
+
+static char *path_name(struct name_path *path, const char *name)
+{
+ struct name_path *p;
+ char *n, *m;
+ int nlen = strlen(name);
+ int len = nlen + 1;
+
+ for (p = path; p; p = p->up) {
+ if (p->elem_len)
+ len += p->elem_len + 1;
+ }
+ n = xmalloc(len);
+ m = n + len - (nlen + 1);
+ strcpy(m, name);
+ for (p = path; p; p = p->up) {
+ if (p->elem_len) {
+ m -= p->elem_len + 1;
+ memcpy(m, p->elem, p->elem_len);
+ m[p->elem_len] = '/';
+ }
+ }
+ return n;
+}
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);
return CONTINUE;
}
-static struct object_list **add_object(struct object *obj, struct object_list **p, const char *name)
+static struct object_list **add_object(struct object *obj,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
{
struct object_list *entry = xmalloc(sizeof(*entry));
entry->item = obj;
entry->next = *p;
- entry->name = name;
+ entry->name = path_name(path, name);
*p = entry;
return &entry->next;
}
-static struct object_list **process_blob(struct blob *blob, struct object_list **p, const char *name)
+static struct object_list **process_blob(struct blob *blob,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
{
struct object *obj = &blob->object;
if (obj->flags & (UNINTERESTING | SEEN))
return p;
obj->flags |= SEEN;
- return add_object(obj, p, name);
+ return add_object(obj, p, path, name);
}
-static struct object_list **process_tree(struct tree *tree, struct object_list **p, const char *name)
+static struct object_list **process_tree(struct tree *tree,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
{
struct object *obj = &tree->object;
struct tree_entry_list *entry;
+ struct name_path me;
if (!tree_objects)
return p;
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
- p = add_object(obj, p, name);
+ p = add_object(obj, p, path, name);
+ me.up = path;
+ me.elem = name;
+ me.elem_len = strlen(name);
entry = tree->entries;
tree->entries = NULL;
while (entry) {
struct tree_entry_list *next = entry->next;
if (entry->directory)
- p = process_tree(entry->item.tree, p, entry->name);
+ p = process_tree(entry->item.tree, p, &me, entry->name);
else
- p = process_blob(entry->item.blob, p, entry->name);
+ p = process_blob(entry->item.blob, p, &me, entry->name);
free(entry);
entry = next;
}
while (list) {
struct commit *commit = pop_most_recent_commit(&list, SEEN);
- p = process_tree(commit->tree, p, "");
+ p = process_tree(commit->tree, p, NULL, "");
if (process_commit(commit) == STOP)
break;
}
continue;
if (obj->type == tag_type) {
obj->flags |= SEEN;
- p = add_object(obj, p, name);
+ p = add_object(obj, p, NULL, name);
continue;
}
if (obj->type == tree_type) {
- p = process_tree((struct tree *)obj, p, name);
+ p = process_tree((struct tree *)obj, p, NULL, name);
continue;
}
if (obj->type == blob_type) {
- p = process_blob((struct blob *)obj, p, name);
+ p = process_blob((struct blob *)obj, p, NULL, name);
continue;
}
die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
}
while (objects) {
- /* An object with name "foo\n0000000000000000000000000000000000000000"
- * can be used confuse downstream git-pack-objects very badly.
+ /* An object with name "foo\n0000000..." can be used to
+ * confuse downstream git-pack-objects very badly.
*/
const char *ep = strchr(objects->name, '\n');
if (ep) {
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 (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_list *parents = list->item->parents;
+ struct commit *commit = list->item;
- for ( ; parents; parents = parents->next) {
- struct commit *commit = parents->item;
- if (commit->object.flags & UNINTERESTING)
- mark_tree_uninteresting(commit->tree);
+ if (commit->object.flags & UNINTERESTING) {
+ mark_tree_uninteresting(commit->tree);
+ continue;
}
+ mark_edge_parents_uninteresting(commit);
}
}
-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;
static void add_pending_object(struct object *obj, const char *name)
{
- add_object(obj, &pending_objects, name);
+ add_object(obj, &pending_objects, NULL, name);
}
static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
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;
blob_objects = 1;
continue;
}
+ if (!strcmp(arg, "--objects-edge")) {
+ tag_objects = 1;
+ tree_objects = 1;
+ blob_objects = 1;
+ edge_hint = 1;
+ continue;
+ }
if (!strcmp(arg, "--unpacked")) {
unpacked = 1;
limited = 1;
}
if (!strcmp(arg, "--topo-order")) {
topo_order = 1;
+ lifo = 1;
+ limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--date-order")) {
+ topo_order = 1;
+ lifo = 0;
limited = 1;
continue;
}
dense = 0;
continue;
}
+ if (!strcmp(arg, "--remove-empty")) {
+ remove_empty_trees = 1;
+ continue;
+ }
if (!strcmp(arg, "--")) {
i++;
break;
arg++;
limited = 1;
}
- if (get_sha1(arg, sha1) < 0)
+ 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);
if (limited)
list = limit_list(list);
if (topo_order)
- sort_in_topological_order(&list);
+ sort_in_topological_order(&list, lifo);
show_commit_list(list);
} else {
#ifndef NO_OPENSSL