#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"
" --objects\n"
" --unpacked\n"
" --header | --pretty\n"
+" --abbrev=nr | --no-abbrev\n"
" special purpose:\n"
" --bisect"
;
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);
}
}
-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;
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;
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);