Merge branch 'jc/nostat'
[gitweb.git] / rev-list.c
index 5fad300635de8901db000510f6fd12a0fbdcae6e..63391fc1136d4d4045db6406face4c53915cd5bf 100644 (file)
@@ -12,6 +12,7 @@
 #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"
@@ -21,6 +22,7 @@ static const char rev_list_usage[] =
 "    --min-age=epoch\n"
 "    --sparse\n"
 "    --no-merges\n"
+"    --remove-empty\n"
 "    --all\n"
 "  ordering output:\n"
 "    --merge-order [ --show-breaks ]\n"
@@ -30,6 +32,7 @@ static const char rev_list_usage[] =
 "    --objects\n"
 "    --unpacked\n"
 "    --header | --pretty\n"
+"    --abbrev=nr | --no-abbrev\n"
 "  special purpose:\n"
 "    --bisect"
 ;
@@ -41,6 +44,7 @@ static int tag_objects = 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 = "";
@@ -54,6 +58,7 @@ static int stop_traversal = 0;
 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)
 {
@@ -70,9 +75,21 @@ 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(' ');
@@ -81,7 +98,7 @@ static void show_commit(struct commit *commit)
 
        if (verbose_header) {
                static char pretty_header[16384];
-               pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header), 0);
+               pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
                printf("%s%c", pretty_header, hdr_termination);
        }
        fflush(stdout);
@@ -424,14 +441,33 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
-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,
@@ -440,7 +476,7 @@ 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 = {
@@ -449,12 +485,16 @@ 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)
@@ -474,28 +514,55 @@ 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)
@@ -531,20 +598,14 @@ 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;
 
@@ -558,33 +619,6 @@ static void add_parents_to_list(struct commit *commit, struct commit_list **list
        }
 }
 
-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;
@@ -614,8 +648,6 @@ static struct commit_list *limit_list(struct commit_list *list)
        }
        if (tree_objects)
                mark_edges_uninteresting(newlist);
-       if (paths && dense)
-               compress_list(newlist);
        if (bisect_list)
                newlist = find_bisection(newlist);
        return newlist;
@@ -732,6 +764,21 @@ int main(int argc, const char **argv)
                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;
@@ -750,6 +797,18 @@ int main(int argc, const char **argv)
                        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;
@@ -808,6 +867,10 @@ int main(int argc, const char **argv)
                        dense = 0;
                        continue;
                }
+               if (!strcmp(arg, "--remove-empty")) {
+                       remove_empty_trees = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--")) {
                        i++;
                        break;