Code

Merge branch 'jc/blame' (early part) into HEAD
authorJunio C Hamano <gitster@pobox.com>
Tue, 8 Jul 2008 22:25:44 +0000 (15:25 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 8 Jul 2008 22:25:44 +0000 (15:25 -0700)
* 'jc/blame' (early part):
  git-blame --reverse
  builtin-blame.c: allow more than 16 parents
  builtin-blame.c: move prepare_final() into a separate function.
  rev-list --children
  revision traversal: --children option

Conflicts:

Documentation/rev-list-options.txt
revision.c

Documentation/rev-list-options.txt
builtin-blame.c
builtin-rev-list.c
revision.c
revision.h

index 37dd1d61ea36d7afb1e1b16cc46bee0d357ad1c8..b6f5d87e723bec4f00a3929274c43bfd478cc083 100644 (file)
@@ -45,6 +45,10 @@ endif::git-rev-list[]
 
        Print the parents of the commit.
 
+--children::
+
+       Print the children of the commit.
+
 ifdef::git-rev-list[]
 --timestamp::
        Print the raw commit timestamp.
index b451f6c64dde8ce6358bb5c8dfccc8bad181a6bb..cf41511c798330a7e0ec02db5785747ad2b662a5 100644 (file)
@@ -43,6 +43,7 @@ static int max_orig_digits;
 static int max_digits;
 static int max_score_digits;
 static int show_root;
+static int reverse;
 static int blank_boundary;
 static int incremental;
 static int cmd_is_annotate;
@@ -91,7 +92,7 @@ struct origin {
  * Given an origin, prepare mmfile_t structure to be used by the
  * diff machinery
  */
-static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+static void fill_origin_blob(struct origin *o, mmfile_t *file)
 {
        if (!o->file.ptr) {
                enum object_type type;
@@ -106,7 +107,6 @@ static char *fill_origin_blob(struct origin *o, mmfile_t *file)
        }
        else
                *file = o->file;
-       return file->ptr;
 }
 
 /*
@@ -178,7 +178,7 @@ struct blame_entry {
 struct scoreboard {
        /* the final commit (i.e. where we started digging from) */
        struct commit *final;
-
+       struct rev_info *revs;
        const char *path;
 
        /*
@@ -1192,18 +1192,48 @@ static void pass_whole_blame(struct scoreboard *sb,
        }
 }
 
-#define MAXPARENT 16
+/*
+ * We pass blame from the current commit to its parents.  We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+{
+       if (!reverse)
+               return commit->parents;
+       return lookup_decoration(&revs->children, &commit->object);
+}
+
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+{
+       int cnt;
+       struct commit_list *l = first_scapegoat(revs, commit);
+       for (cnt = 0; l; l = l->next)
+               cnt++;
+       return cnt;
+}
+
+#define MAXSG 16
 
 static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
 {
-       int i, pass;
+       struct rev_info *revs = sb->revs;
+       int i, pass, num_sg;
        struct commit *commit = origin->commit;
-       struct commit_list *parent;
-       struct origin *parent_origin[MAXPARENT], *porigin;
-
-       memset(parent_origin, 0, sizeof(parent_origin));
+       struct commit_list *sg;
+       struct origin *sg_buf[MAXSG];
+       struct origin *porigin, **sg_origin = sg_buf;
+
+       num_sg = num_scapegoats(revs, commit);
+       if (!num_sg)
+               goto finish;
+       else if (num_sg < ARRAY_SIZE(sg_buf))
+               memset(sg_buf, 0, sizeof(sg_buf));
+       else
+               sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
 
-       /* The first pass looks for unrenamed path to optimize for
+       /*
+        * The first pass looks for unrenamed path to optimize for
         * common cases, then we look for renames in the second pass.
         */
        for (pass = 0; pass < 2; pass++) {
@@ -1211,13 +1241,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                                       struct commit *, struct origin *);
                find = pass ? find_rename : find_origin;
 
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct commit *p = parent->item;
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct commit *p = sg->item;
                        int j, same;
 
-                       if (parent_origin[i])
+                       if (sg_origin[i])
                                continue;
                        if (parse_commit(p))
                                continue;
@@ -1230,24 +1260,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                                goto finish;
                        }
                        for (j = same = 0; j < i; j++)
-                               if (parent_origin[j] &&
-                                   !hashcmp(parent_origin[j]->blob_sha1,
+                               if (sg_origin[j] &&
+                                   !hashcmp(sg_origin[j]->blob_sha1,
                                             porigin->blob_sha1)) {
                                        same = 1;
                                        break;
                                }
                        if (!same)
-                               parent_origin[i] = porigin;
+                               sg_origin[i] = porigin;
                        else
                                origin_decref(porigin);
                }
        }
 
        num_commits++;
-       for (i = 0, parent = commit->parents;
-            i < MAXPARENT && parent;
-            parent = parent->next, i++) {
-               struct origin *porigin = parent_origin[i];
+       for (i = 0, sg = first_scapegoat(revs, commit);
+            i < num_sg && sg;
+            sg = sg->next, i++) {
+               struct origin *porigin = sg_origin[i];
                if (!porigin)
                        continue;
                if (pass_blame_to_parent(sb, origin, porigin))
@@ -1258,10 +1288,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
         * Optionally find moves in parents' files.
         */
        if (opt & PICKAXE_BLAME_MOVE)
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct origin *porigin = parent_origin[i];
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
                        if (!porigin)
                                continue;
                        if (find_move_in_parent(sb, origin, porigin))
@@ -1272,23 +1302,25 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
         * Optionally find copies from parents' files.
         */
        if (opt & PICKAXE_BLAME_COPY)
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct origin *porigin = parent_origin[i];
-                       if (find_copy_in_parent(sb, origin, parent->item,
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
+                       if (find_copy_in_parent(sb, origin, sg->item,
                                                porigin, opt))
                                goto finish;
                }
 
  finish:
-       for (i = 0; i < MAXPARENT; i++) {
-               if (parent_origin[i]) {
-                       drop_origin_blob(parent_origin[i]);
-                       origin_decref(parent_origin[i]);
+       for (i = 0; i < num_sg; i++) {
+               if (sg_origin[i]) {
+                       drop_origin_blob(sg_origin[i]);
+                       origin_decref(sg_origin[i]);
                }
        }
        drop_origin_blob(origin);
+       if (sg_buf != sg_origin)
+               free(sg_origin);
 }
 
 /*
@@ -1487,8 +1519,10 @@ static void found_guilty_entry(struct blame_entry *ent)
  * is still unknown, pick one blame_entry, and allow its current
  * suspect to pass blames to its parents.
  */
-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+static void assign_blame(struct scoreboard *sb, int opt)
 {
+       struct rev_info *revs = sb->revs;
+
        while (1) {
                struct blame_entry *ent;
                struct commit *commit;
@@ -1509,8 +1543,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
                commit = suspect->commit;
                if (!commit->object.parsed)
                        parse_commit(commit);
-               if (!(commit->object.flags & UNINTERESTING) &&
-                   !(revs->max_age != -1 && commit->date < revs->max_age))
+               if (reverse ||
+                   (!(commit->object.flags & UNINTERESTING) &&
+                    !(revs->max_age != -1 && commit->date < revs->max_age)))
                        pass_blame(sb, suspect, opt);
                else {
                        commit->object.flags |= UNINTERESTING;
@@ -2006,6 +2041,10 @@ static int git_blame_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
 static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
 {
        struct commit *commit;
@@ -2122,6 +2161,64 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        return commit;
 }
 
+static const char *prepare_final(struct scoreboard *sb)
+{
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+
+       /*
+        * There must be one and only one positive commit in the
+        * revs->pending array.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (obj->flags & UNINTERESTING)
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig from %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       return final_commit_name;
+}
+
+static const char *prepare_initial(struct scoreboard *sb)
+{
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+
+       /*
+        * There must be one and only one negative commit, and it must be
+        * the boundary.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (!(obj->flags & UNINTERESTING))
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig down to %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       if (!final_commit_name)
+               die("No commit to dig down to?");
+       return final_commit_name;
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -2154,6 +2251,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                        blank_boundary = 1;
                else if (!strcmp("--root", arg))
                        show_root = 1;
+               else if (!strcmp("--reverse", arg)) {
+                       argv[unk++] = "--children";
+                       reverse = 1;
+               }
                else if (!strcmp(arg, "--show-stats"))
                        show_stats = 1;
                else if (!strcmp("-c", arg))
@@ -2327,26 +2428,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        setup_revisions(unk, argv, &revs, NULL);
        memset(&sb, 0, sizeof(sb));
 
-       /*
-        * There must be one and only one positive commit in the
-        * revs->pending array.
-        */
-       for (i = 0; i < revs.pending.nr; i++) {
-               struct object *obj = revs.pending.objects[i].item;
-               if (obj->flags & UNINTERESTING)
-                       continue;
-               while (obj->type == OBJ_TAG)
-                       obj = deref_tag(obj, NULL, 0);
-               if (obj->type != OBJ_COMMIT)
-                       die("Non commit %s?",
-                           revs.pending.objects[i].name);
-               if (sb.final)
-                       die("More than one commit to dig from %s and %s?",
-                           revs.pending.objects[i].name,
-                           final_commit_name);
-               sb.final = (struct commit *) obj;
-               final_commit_name = revs.pending.objects[i].name;
-       }
+       sb.revs = &revs;
+       if (!reverse)
+               final_commit_name = prepare_final(&sb);
+       else if (contents_from)
+               die("--contents and --children do not blend well.");
+       else
+               final_commit_name = prepare_initial(&sb);
 
        if (!sb.final) {
                /*
@@ -2425,7 +2513,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        if (!incremental)
                setup_pager();
 
-       assign_blame(&sb, &revs, opt);
+       assign_blame(&sb, opt);
 
        if (incremental)
                return 0;
index 83a7b1349e06dbf1a355888272d9b13a7d4c22c4..11a7eae551601abcd1eddfae389b86fac462b9a7 100644 (file)
@@ -37,6 +37,7 @@ static const char rev_list_usage[] =
 "    --reverse\n"
 "  formatting output:\n"
 "    --parents\n"
+"    --children\n"
 "    --objects | --objects-edge\n"
 "    --unpacked\n"
 "    --header | --pretty\n"
@@ -90,6 +91,15 @@ static void show_commit(struct commit *commit)
                        parents = parents->next;
                }
        }
+       if (revs.children.name) {
+               struct commit_list *children;
+
+               children = lookup_decoration(&revs.children, &commit->object);
+               while (children) {
+                       printf(" %s", sha1_to_hex(children->item->object.sha1));
+                       children = children->next;
+               }
+       }
        show_decorations(commit);
        if (revs.commit_format == CMIT_FMT_ONELINE)
                putchar(' ');
index fc667552592daa3c894d0df4e97469d1809dbf27..5a1a948a41e8f2ae2df01c0a61d2e228cc79caab 100644 (file)
@@ -10,6 +10,7 @@
 #include "grep.h"
 #include "reflog-walk.h"
 #include "patch-ids.h"
+#include "decorate.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -1321,6 +1322,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->no_walk = 0;
                                continue;
                        }
+                       if (!strcmp(arg, "--children")) {
+                               revs->children.name = "children";
+                               revs->limited = 1;
+                               continue;
+                       }
 
                        opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
                        if (opts > 0) {
@@ -1406,6 +1412,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
        if (revs->reverse && revs->reflog_info)
                die("cannot combine --reverse with --walk-reflogs");
+       if (revs->rewrite_parents && revs->children.name)
+               die("cannot combine --parents and --children");
 
        /*
         * Limitations on the graph functionality
@@ -1419,6 +1427,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        return left;
 }
 
+static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child)
+{
+       struct commit_list *l = xcalloc(1, sizeof(*l));
+
+       l->item = child;
+       l->next = add_decoration(&revs->children, &parent->object, l);
+}
+
+static void set_children(struct rev_info *revs)
+{
+       struct commit_list *l;
+       for (l = revs->commits; l; l = l->next) {
+               struct commit *commit = l->item;
+               struct commit_list *p;
+
+               for (p = commit->parents; p; p = p->next)
+                       add_child(revs, p->item, commit);
+       }
+}
+
 int prepare_revision_walk(struct rev_info *revs)
 {
        int nr = revs->pending.nr;
@@ -1447,6 +1475,8 @@ int prepare_revision_walk(struct rev_info *revs)
                        return -1;
        if (revs->topo_order)
                sort_in_topological_order(&revs->commits, revs->lifo);
+       if (revs->children.name)
+               set_children(revs);
        return 0;
 }
 
@@ -1524,6 +1554,11 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
                           commit->buffer, strlen(commit->buffer));
 }
 
+static inline int want_ancestry(struct rev_info *revs)
+{
+       return (revs->rewrite_parents || revs->children.name);
+}
+
 enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
 {
        if (commit->object.flags & SHOWN)
@@ -1544,13 +1579,13 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
                /* Commit without changes? */
                if (commit->object.flags & TREESAME) {
                        /* drop merges unless we want parenthood */
-                       if (!revs->rewrite_parents)
+                       if (!want_ancestry(revs))
                                return commit_ignore;
                        /* non-merge - always ignore it */
                        if (!commit->parents || !commit->parents->next)
                                return commit_ignore;
                }
-               if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
+               if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0)
                        return commit_error;
        }
        return commit_show;
index abce5001f19a60bb15b519b26773b57c83563021..dcf08e089abc053bfecebc9b73fe870236915acd 100644 (file)
@@ -104,6 +104,7 @@ struct rev_info {
        struct diff_options pruning;
 
        struct reflog_walk_info *reflog_info;
+       struct decoration children;
 };
 
 #define REV_TREE_SAME          0