{"thread":{"id":"12970","subject":"[PATCH 0/4] Blame in reverse","startedAt":"2008-04-03T09:08:45Z","lastAt":"2008-04-03T19:58:58Z","messageCount":8,"participants":["Junio C Hamano","Johannes Sixt","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"73584","messageId":"7v4paj486a.fsf@gitster.siamese.dyndns.org","threadId":"12970","inReplyTo":null,"subject":"[PATCH 0/4] Blame in reverse","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-03T09:08:45Z","receivedAt":"2008-04-03T09:08:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is still rough, but I think it would be a good foundation to build\ninteresting archaeology tools on.\n\nUsual \"git blame\" starts from the latest revision and finds where each\nline came from (i.e. who came up with that brilliant idea, or more often\nwho introduced that bug).  This new mode starts from an old revision, and\nannotates in reverse --- up to which point each line survived, before it\ngot replaced with something else.  A line that began its life in one file\ncan be removed from the original file but migrate to another, and we will\nfind its final resting place when you use the usual -C -C option.\n\n [1/4] \"log\" and friends: --children option\n [2/4] builtin-blame.c: move prepare_final() into a separate function.\n [3/4]  builtin-blame.c: allow more than 16 parents\n [4/4] git-blame --reverse\n\n builtin-blame.c |  206 +++++++++++++++++++++++++++++++++++++++----------------\n log-tree.c      |   20 ++++++\n revision.c      |   28 ++++++++\n revision.h      |    1 +\n 4 files changed, 196 insertions(+), 59 deletions(-)\n"},{"id":"73586","messageId":"7vlk3v2tg9.fsf@gitster.siamese.dyndns.org","threadId":"12970","inReplyTo":"7v4paj486a.fsf@gitster.siamese.dyndns.org","subject":"[PATCH 1/4] \"log\" and friends: --children option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-03T09:12:06Z","receivedAt":"2008-04-03T09:12:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This adds a new --children option to the revision machinery.  In addition\nto the list of parents, child commits of each commit are computed and\nshown in the output from \"git log\" and friends.\n\nNote that this does _not_ show all children of a commit.  It only shows\nchildren on the commit ancestry you made the revision machinery traverse.\nFor example, \"git log --children master..next\" will not show side branches\nforked from any commits shown in the range that have not been merged to\n\"next\" (i.e. what are queued in 'pu' but not yet merged to 'next').\n\nThis patch was primarily done for adding the infrastructure to allow users\nof revision machinery to find out children of commits; please consider the\ncurrent output not much more than debugging aid.  If you want to use it\nfrom \"git log\" and friends, the output format needs a bit more serious\nthought.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n log-tree.c |   20 ++++++++++++++++++++\n revision.c |   28 ++++++++++++++++++++++++++++\n revision.h |    1 +\n 3 files changed, 49 insertions(+), 0 deletions(-)\n\ndiff --git a/log-tree.c b/log-tree.c\nindex 5b29639..75851d1 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -32,6 +32,24 @@ void show_decorations(struct commit *commit)\n \tputchar(')');\n }\n \n+static void show_children(struct rev_info *revs, struct commit *commit, int abbrev)\n+{\n+\tstruct commit_list *p;\n+\n+\tif (!revs->children.name)\n+\t\treturn;\n+\tp = lookup_decoration(&revs->children, &commit->object);\n+\tif (!p)\n+\t\treturn;\n+\tprintf(\"child\");\n+\twhile (p) {\n+\t\tstruct commit *child = p->item;\n+\t\tprintf(\" %s\", diff_unique_abbrev(child->object.sha1, abbrev));\n+\t\tp = p->next;\n+\t}\n+\tputchar(revs->diffopt.line_termination);\n+}\n+\n /*\n  * Search for \"^[-A-Za-z]+: [^@]+@\" pattern. It usually matches\n  * Signed-off-by: and Acked-by: lines.\n@@ -236,6 +254,7 @@ void show_log(struct rev_info *opt, const char *sep)\n \t\t\tshow_parents(commit, abbrev_commit);\n \t\tshow_decorations(commit);\n \t\tputchar(opt->diffopt.line_termination);\n+\t\tshow_children(opt, commit, abbrev_commit);\n \t\treturn;\n \t}\n \n@@ -297,6 +316,7 @@ void show_log(struct rev_info *opt, const char *sep)\n \t\t\t\treturn;\n \t\t\t}\n \t\t}\n+\t\tshow_children(opt, commit, abbrev_commit);\n \t}\n \n \tif (!commit->buffer)\ndiff --git a/revision.c b/revision.c\nindex 196fedc..e5138b7 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -9,6 +9,7 @@\n #include \"grep.h\"\n #include \"reflog-walk.h\"\n #include \"patch-ids.h\"\n+#include \"decorate.h\"\n \n volatile show_early_output_fn_t show_early_output;\n \n@@ -1309,6 +1310,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \t\t\t\trevs->no_walk = 0;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--children\")) {\n+\t\t\t\trevs->children.name = \"children\";\n+\t\t\t\trevs->limited = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \n \t\t\topts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);\n \t\t\tif (opts > 0) {\n@@ -1398,6 +1404,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n \treturn left;\n }\n \n+static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child)\n+{\n+\tstruct commit_list *l = xcalloc(1, sizeof(*l));\n+\n+\tl->item = child;\n+\tl->next = add_decoration(&revs->children, &parent->object, l);\n+}\n+\n+static void set_children(struct rev_info *revs)\n+{\n+\tstruct commit_list *l;\n+\tfor (l = revs->commits; l; l = l->next) {\n+\t\tstruct commit *commit = l->item;\n+\t\tstruct commit_list *p;\n+\n+\t\tfor (p = commit->parents; p; p = p->next)\n+\t\t\tadd_child(revs, p->item, commit);\n+\t}\n+}\n+\n int prepare_revision_walk(struct rev_info *revs)\n {\n \tint nr = revs->pending.nr;\n@@ -1426,6 +1452,8 @@ int prepare_revision_walk(struct rev_info *revs)\n \t\t\treturn -1;\n \tif (revs->topo_order)\n \t\tsort_in_topological_order(&revs->commits, revs->lifo);\n+\tif (revs->children.name)\n+\t\tset_children(revs);\n \treturn 0;\n }\n \ndiff --git a/revision.h b/revision.h\nindex c8b3b94..966116c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -98,6 +98,7 @@ struct rev_info {\n \tstruct diff_options pruning;\n \n \tstruct reflog_walk_info *reflog_info;\n+\tstruct decoration children;\n };\n \n #define REV_TREE_SAME\t\t0\n-- \n1.5.5.rc3.139.g8b2cf\n"},{"id":"73587","messageId":"7vbq4r2tdp.fsf@gitster.siamese.dyndns.org","threadId":"12970","inReplyTo":"7v4paj486a.fsf@gitster.siamese.dyndns.org","subject":"[PATCH 2/4] builtin-blame.c: move prepare_final() into a separate function.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-03T09:13:38Z","receivedAt":"2008-04-03T09:13:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"After parsing the command line, we had a rather long loop to compute the\ncommit object to start annotating from.  Move the logic to a separate\nfunction, so that later patches become easier to read.\n\nIt also makes fill_origin_blob() function to return void; the check on its\nreturn value by its callers is always done on !file->ptr, and nobody looks\nat the return value from the function (which is the same as file->ptr).\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin-blame.c |   56 ++++++++++++++++++++++++++++++++----------------------\n 1 files changed, 33 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin-blame.c b/builtin-blame.c\nindex bfd562d..996f535 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -91,7 +91,7 @@ struct origin {\n  * Given an origin, prepare mmfile_t structure to be used by the\n  * diff machinery\n  */\n-static char *fill_origin_blob(struct origin *o, mmfile_t *file)\n+static void fill_origin_blob(struct origin *o, mmfile_t *file)\n {\n \tif (!o->file.ptr) {\n \t\tenum object_type type;\n@@ -106,7 +106,6 @@ static char *fill_origin_blob(struct origin *o, mmfile_t *file)\n \t}\n \telse\n \t\t*file = o->file;\n-\treturn file->ptr;\n }\n \n /*\n@@ -2006,6 +2005,10 @@ static int git_blame_config(const char *var, const char *value)\n \treturn git_default_config(var, value);\n }\n \n+/*\n+ * Prepare a dummy commit that represents the work tree (or staged) item.\n+ * Note that annotating work tree item never works in the reverse.\n+ */\n static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)\n {\n \tstruct commit *commit;\n@@ -2122,6 +2125,33 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con\n \treturn commit;\n }\n \n+static const char *prepare_final(struct scoreboard *sb, struct rev_info *revs)\n+{\n+\tint i;\n+\tconst char *final_commit_name = NULL;\n+\n+\t/*\n+\t * There must be one and only one positive commit in the\n+\t * revs->pending array.\n+\t */\n+\tfor (i = 0; i < revs->pending.nr; i++) {\n+\t\tstruct object *obj = revs->pending.objects[i].item;\n+\t\tif (obj->flags & UNINTERESTING)\n+\t\t\tcontinue;\n+\t\twhile (obj->type == OBJ_TAG)\n+\t\t\tobj = deref_tag(obj, NULL, 0);\n+\t\tif (obj->type != OBJ_COMMIT)\n+\t\t\tdie(\"Non commit %s?\", revs->pending.objects[i].name);\n+\t\tif (sb->final)\n+\t\t\tdie(\"More than one commit to dig from %s and %s?\",\n+\t\t\t    revs->pending.objects[i].name,\n+\t\t\t    final_commit_name);\n+\t\tsb->final = (struct commit *) obj;\n+\t\tfinal_commit_name = revs->pending.objects[i].name;\n+\t}\n+\treturn final_commit_name;\n+}\n+\n int cmd_blame(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n@@ -2327,27 +2357,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tsetup_revisions(unk, argv, &revs, NULL);\n \tmemset(&sb, 0, sizeof(sb));\n \n-\t/*\n-\t * There must be one and only one positive commit in the\n-\t * revs->pending array.\n-\t */\n-\tfor (i = 0; i < revs.pending.nr; i++) {\n-\t\tstruct object *obj = revs.pending.objects[i].item;\n-\t\tif (obj->flags & UNINTERESTING)\n-\t\t\tcontinue;\n-\t\twhile (obj->type == OBJ_TAG)\n-\t\t\tobj = deref_tag(obj, NULL, 0);\n-\t\tif (obj->type != OBJ_COMMIT)\n-\t\t\tdie(\"Non commit %s?\",\n-\t\t\t    revs.pending.objects[i].name);\n-\t\tif (sb.final)\n-\t\t\tdie(\"More than one commit to dig from %s and %s?\",\n-\t\t\t    revs.pending.objects[i].name,\n-\t\t\t    final_commit_name);\n-\t\tsb.final = (struct commit *) obj;\n-\t\tfinal_commit_name = revs.pending.objects[i].name;\n-\t}\n-\n+\tfinal_commit_name = prepare_final(&sb, &revs);\n \tif (!sb.final) {\n \t\t/*\n \t\t * \"--not A B -- path\" without anything positive;\n-- \n1.5.5.rc3.139.g8b2cf\n"},{"id":"73588","messageId":"7v3aq32tbs.fsf@gitster.siamese.dyndns.org","threadId":"12970","inReplyTo":"7v4paj486a.fsf@gitster.siamese.dyndns.org","subject":"[PATCH 3/4] builtin-blame.c: allow more than 16 parents","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-03T09:14:47Z","receivedAt":"2008-04-03T09:14:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This removes the hardcoded 16 parents limit from git-blame by allowing the\nparent array to be allocated dynamically.\n\nAs the ultimate objective is not about computing blame across dodecapus,\nbut about annotating the history upside down, it also renames \"parent\" in\nthe code to \"scapegoat\".  The name of the game used to be \"pass blame to\nyour parents\", but now it is \"find a scapegoat to pass blame on\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin-blame.c |   93 ++++++++++++++++++++++++++++++++++++-------------------\n 1 files changed, 61 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin-blame.c b/builtin-blame.c\nindex 996f535..fbc441f 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -1191,18 +1191,45 @@ static void pass_whole_blame(struct scoreboard *sb,\n \t}\n }\n \n-#define MAXPARENT 16\n+/*\n+ * We pass blame from the current commit to its parents.  We keep saying\n+ * \"parent\" (and \"porigin\"), but what we mean is to find scapegoat to\n+ * exonerate ourselves.\n+ */\n+static struct commit_list *first_scapegoat(struct commit *commit)\n+{\n+\treturn commit->parents;\n+}\n+\n+static int num_scapegoats(struct commit *commit)\n+{\n+\tint cnt;\n+\tstruct commit_list *l = first_scapegoat(commit);\n+\tfor (cnt = 0; l; l = l->next)\n+\t\tcnt++;\n+\treturn cnt;\n+}\n+\n+#define MAXSG 16\n \n static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n {\n-\tint i, pass;\n+\tint i, pass, num_sg;\n \tstruct commit *commit = origin->commit;\n-\tstruct commit_list *parent;\n-\tstruct origin *parent_origin[MAXPARENT], *porigin;\n-\n-\tmemset(parent_origin, 0, sizeof(parent_origin));\n+\tstruct commit_list *sg;\n+\tstruct origin *sg_buf[MAXSG];\n+\tstruct origin *porigin, **sg_origin = sg_buf;\n+\n+\tnum_sg = num_scapegoats(commit);\n+\tif (!num_sg)\n+\t\tgoto finish;\n+\telse if (num_sg < ARRAY_SIZE(sg_buf))\n+\t\tmemset(sg_buf, 0, sizeof(sg_buf));\n+\telse\n+\t\tsg_origin = xcalloc(num_sg, sizeof(*sg_origin));\n \n-\t/* The first pass looks for unrenamed path to optimize for\n+\t/*\n+\t * The first pass looks for unrenamed path to optimize for\n \t * common cases, then we look for renames in the second pass.\n \t */\n \tfor (pass = 0; pass < 2; pass++) {\n@@ -1210,13 +1237,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t\t\t\t       struct commit *, struct origin *);\n \t\tfind = pass ? find_rename : find_origin;\n \n-\t\tfor (i = 0, parent = commit->parents;\n-\t\t     i < MAXPARENT && parent;\n-\t\t     parent = parent->next, i++) {\n-\t\t\tstruct commit *p = parent->item;\n+\t\tfor (i = 0, sg = first_scapegoat(commit);\n+\t\t     i < num_sg && sg;\n+\t\t     sg = sg->next, i++) {\n+\t\t\tstruct commit *p = sg->item;\n \t\t\tint j, same;\n \n-\t\t\tif (parent_origin[i])\n+\t\t\tif (sg_origin[i])\n \t\t\t\tcontinue;\n \t\t\tif (parse_commit(p))\n \t\t\t\tcontinue;\n@@ -1229,24 +1256,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t\t\t\tgoto finish;\n \t\t\t}\n \t\t\tfor (j = same = 0; j < i; j++)\n-\t\t\t\tif (parent_origin[j] &&\n-\t\t\t\t    !hashcmp(parent_origin[j]->blob_sha1,\n+\t\t\t\tif (sg_origin[j] &&\n+\t\t\t\t    !hashcmp(sg_origin[j]->blob_sha1,\n \t\t\t\t\t     porigin->blob_sha1)) {\n \t\t\t\t\tsame = 1;\n \t\t\t\t\tbreak;\n \t\t\t\t}\n \t\t\tif (!same)\n-\t\t\t\tparent_origin[i] = porigin;\n+\t\t\t\tsg_origin[i] = porigin;\n \t\t\telse\n \t\t\t\torigin_decref(porigin);\n \t\t}\n \t}\n \n \tnum_commits++;\n-\tfor (i = 0, parent = commit->parents;\n-\t     i < MAXPARENT && parent;\n-\t     parent = parent->next, i++) {\n-\t\tstruct origin *porigin = parent_origin[i];\n+\tfor (i = 0, sg = first_scapegoat(commit);\n+\t     i < num_sg && sg;\n+\t     sg = sg->next, i++) {\n+\t\tstruct origin *porigin = sg_origin[i];\n \t\tif (!porigin)\n \t\t\tcontinue;\n \t\tif (pass_blame_to_parent(sb, origin, porigin))\n@@ -1257,10 +1284,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t * Optionally find moves in parents' files.\n \t */\n \tif (opt & PICKAXE_BLAME_MOVE)\n-\t\tfor (i = 0, parent = commit->parents;\n-\t\t     i < MAXPARENT && parent;\n-\t\t     parent = parent->next, i++) {\n-\t\t\tstruct origin *porigin = parent_origin[i];\n+\t\tfor (i = 0, sg = first_scapegoat(commit);\n+\t\t     i < num_sg && sg;\n+\t\t     sg = sg->next, i++) {\n+\t\t\tstruct origin *porigin = sg_origin[i];\n \t\t\tif (!porigin)\n \t\t\t\tcontinue;\n \t\t\tif (find_move_in_parent(sb, origin, porigin))\n@@ -1271,23 +1298,25 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t * Optionally find copies from parents' files.\n \t */\n \tif (opt & PICKAXE_BLAME_COPY)\n-\t\tfor (i = 0, parent = commit->parents;\n-\t\t     i < MAXPARENT && parent;\n-\t\t     parent = parent->next, i++) {\n-\t\t\tstruct origin *porigin = parent_origin[i];\n-\t\t\tif (find_copy_in_parent(sb, origin, parent->item,\n+\t\tfor (i = 0, sg = first_scapegoat(commit);\n+\t\t     i < num_sg && sg;\n+\t\t     sg = sg->next, i++) {\n+\t\t\tstruct origin *porigin = sg_origin[i];\n+\t\t\tif (find_copy_in_parent(sb, origin, sg->item,\n \t\t\t\t\t\tporigin, opt))\n \t\t\t\tgoto finish;\n \t\t}\n \n  finish:\n-\tfor (i = 0; i < MAXPARENT; i++) {\n-\t\tif (parent_origin[i]) {\n-\t\t\tdrop_origin_blob(parent_origin[i]);\n-\t\t\torigin_decref(parent_origin[i]);\n+\tfor (i = 0; i < num_sg; i++) {\n+\t\tif (sg_origin[i]) {\n+\t\t\tdrop_origin_blob(sg_origin[i]);\n+\t\t\torigin_decref(sg_origin[i]);\n \t\t}\n \t}\n \tdrop_origin_blob(origin);\n+\tif (sg_buf != sg_origin)\n+\t\tfree(sg_origin);\n }\n \n /*\n-- \n1.5.5.rc3.139.g8b2cf\n"},{"id":"73589","messageId":"7vej9n1ejn.fsf@gitster.siamese.dyndns.org","threadId":"12970","inReplyTo":"7v4paj486a.fsf@gitster.siamese.dyndns.org","subject":"[PATCH 4/4] git-blame --reverse","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-03T09:19:24Z","receivedAt":"2008-04-03T09:19:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This new option allows \"git blame\" to read an old version of the file, and\nfind up to which commit each line survived (i.e. their children rewrote\nthe line and removed the original from the contents).\n\nThe previous revision machinery update to decorate each commit with its\nchildren was leading to this change.  When the --reverse option is given,\nwe read the old version and pass blame to the children of the current\nsuspect, instead of the usual order of starting from the latest and\npassing blame to parents (hence, the earlier \"scapegoat\" rename).\n\nThe standard yardstick of \"blame\" in git.git history is still the file\n\"rev-list.c\", which was refactored heavily during its existence.  For\nexample, output from:\n\n  $ git blame -C -C -w --reverse 9de48752..master -- rev-list.c\n\nbegins like this:\n\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02  1) #include \"cache...\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02  2) #include \"commi...\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02  3) #include \"tree....\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02  4) #include \"blob....\n213523f4 rev-list.c         (JC Hamano   2006-03-01  5) #include \"epoch...\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02  6)\nab57c8dd rev-list.c         (JC Hamano   2006-02-24  7) #define SEEN\nab57c8dd rev-list.c         (JC Hamano   2006-02-24  8) #define INTERES...\n213523f4 rev-list.c         (JC Hamano   2006-03-01  9) #define COUNTED...\n7e21c29b rev-list.c         (LTorvalds   2005-07-06 10) #define SHOWN  ...\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02 11)\n6c41b801 builtin-rev-list.c (JC Hamano   2008-04-02 12) static const ch...\nb1349229 rev-list.c         (LTorvalds   2005-07-26 13)    \"usage: git-...\n\nThis sample output reveals that the original first four lines survived up\nto now but were moved to builtin-rev-list.c and inclusion of \"epoch.h\" was\nremoved after 213523f4, and the line was last seen in rev-list.c.\n\nTo be useful, this mode probably needs some more tweaking so that the\ncommit that removed the line (i.e. the children of the commits listed in\nthe above sample output) is shown instead, but then there is a little\nmatter of which child of a fork point to show.\n\nFor now, you can find the diff that rewrote the fifth line above by doing:\n\n    $ git log --children 213523f4^..\n\nto find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next,\n2006-03-01), and then look at that child with:\n\n    $ git show 1025fe5\n\nYes, it is a bit awkward, but it is a good start.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin-blame.c |   81 ++++++++++++++++++++++++++++++++++++++++++++-----------\n 1 files changed, 65 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin-blame.c b/builtin-blame.c\nindex fbc441f..5c7546d 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -43,6 +43,7 @@ static int max_orig_digits;\n static int max_digits;\n static int max_score_digits;\n static int show_root;\n+static int reverse;\n static int blank_boundary;\n static int incremental;\n static int cmd_is_annotate;\n@@ -177,7 +178,7 @@ struct blame_entry {\n struct scoreboard {\n \t/* the final commit (i.e. where we started digging from) */\n \tstruct commit *final;\n-\n+\tstruct rev_info *revs;\n \tconst char *path;\n \n \t/*\n@@ -1196,15 +1197,17 @@ static void pass_whole_blame(struct scoreboard *sb,\n  * \"parent\" (and \"porigin\"), but what we mean is to find scapegoat to\n  * exonerate ourselves.\n  */\n-static struct commit_list *first_scapegoat(struct commit *commit)\n+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)\n {\n-\treturn commit->parents;\n+\tif (!reverse)\n+\t\treturn commit->parents;\n+\treturn lookup_decoration(&revs->children, &commit->object);\n }\n \n-static int num_scapegoats(struct commit *commit)\n+static int num_scapegoats(struct rev_info *revs, struct commit *commit)\n {\n \tint cnt;\n-\tstruct commit_list *l = first_scapegoat(commit);\n+\tstruct commit_list *l = first_scapegoat(revs, commit);\n \tfor (cnt = 0; l; l = l->next)\n \t\tcnt++;\n \treturn cnt;\n@@ -1214,13 +1217,14 @@ static int num_scapegoats(struct commit *commit)\n \n static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n {\n+\tstruct rev_info *revs = sb->revs;\n \tint i, pass, num_sg;\n \tstruct commit *commit = origin->commit;\n \tstruct commit_list *sg;\n \tstruct origin *sg_buf[MAXSG];\n \tstruct origin *porigin, **sg_origin = sg_buf;\n \n-\tnum_sg = num_scapegoats(commit);\n+\tnum_sg = num_scapegoats(revs, commit);\n \tif (!num_sg)\n \t\tgoto finish;\n \telse if (num_sg < ARRAY_SIZE(sg_buf))\n@@ -1237,7 +1241,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t\t\t\t       struct commit *, struct origin *);\n \t\tfind = pass ? find_rename : find_origin;\n \n-\t\tfor (i = 0, sg = first_scapegoat(commit);\n+\t\tfor (i = 0, sg = first_scapegoat(revs, commit);\n \t\t     i < num_sg && sg;\n \t\t     sg = sg->next, i++) {\n \t\t\tstruct commit *p = sg->item;\n@@ -1270,7 +1274,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t}\n \n \tnum_commits++;\n-\tfor (i = 0, sg = first_scapegoat(commit);\n+\tfor (i = 0, sg = first_scapegoat(revs, commit);\n \t     i < num_sg && sg;\n \t     sg = sg->next, i++) {\n \t\tstruct origin *porigin = sg_origin[i];\n@@ -1284,7 +1288,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t * Optionally find moves in parents' files.\n \t */\n \tif (opt & PICKAXE_BLAME_MOVE)\n-\t\tfor (i = 0, sg = first_scapegoat(commit);\n+\t\tfor (i = 0, sg = first_scapegoat(revs, commit);\n \t\t     i < num_sg && sg;\n \t\t     sg = sg->next, i++) {\n \t\t\tstruct origin *porigin = sg_origin[i];\n@@ -1298,7 +1302,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)\n \t * Optionally find copies from parents' files.\n \t */\n \tif (opt & PICKAXE_BLAME_COPY)\n-\t\tfor (i = 0, sg = first_scapegoat(commit);\n+\t\tfor (i = 0, sg = first_scapegoat(revs, commit);\n \t\t     i < num_sg && sg;\n \t\t     sg = sg->next, i++) {\n \t\t\tstruct origin *porigin = sg_origin[i];\n@@ -1515,8 +1519,10 @@ static void found_guilty_entry(struct blame_entry *ent)\n  * is still unknown, pick one blame_entry, and allow its current\n  * suspect to pass blames to its parents.\n  */\n-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)\n+static void assign_blame(struct scoreboard *sb, int opt)\n {\n+\tstruct rev_info *revs = sb->revs;\n+\n \twhile (1) {\n \t\tstruct blame_entry *ent;\n \t\tstruct commit *commit;\n@@ -1537,8 +1543,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)\n \t\tcommit = suspect->commit;\n \t\tif (!commit->object.parsed)\n \t\t\tparse_commit(commit);\n-\t\tif (!(commit->object.flags & UNINTERESTING) &&\n-\t\t    !(revs->max_age != -1 && commit->date < revs->max_age))\n+\t\tif (reverse ||\n+\t\t    (!(commit->object.flags & UNINTERESTING) &&\n+\t\t     !(revs->max_age != -1 && commit->date < revs->max_age)))\n \t\t\tpass_blame(sb, suspect, opt);\n \t\telse {\n \t\t\tcommit->object.flags |= UNINTERESTING;\n@@ -2154,10 +2161,11 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con\n \treturn commit;\n }\n \n-static const char *prepare_final(struct scoreboard *sb, struct rev_info *revs)\n+static const char *prepare_final(struct scoreboard *sb)\n {\n \tint i;\n \tconst char *final_commit_name = NULL;\n+\tstruct rev_info *revs = sb->revs;\n \n \t/*\n \t * There must be one and only one positive commit in the\n@@ -2181,6 +2189,36 @@ static const char *prepare_final(struct scoreboard *sb, struct rev_info *revs)\n \treturn final_commit_name;\n }\n \n+static const char *prepare_initial(struct scoreboard *sb)\n+{\n+\tint i;\n+\tconst char *final_commit_name = NULL;\n+\tstruct rev_info *revs = sb->revs;\n+\n+\t/*\n+\t * There must be one and only one negative commit, and it must be\n+\t * the boundary.\n+\t */\n+\tfor (i = 0; i < revs->pending.nr; i++) {\n+\t\tstruct object *obj = revs->pending.objects[i].item;\n+\t\tif (!(obj->flags & UNINTERESTING))\n+\t\t\tcontinue;\n+\t\twhile (obj->type == OBJ_TAG)\n+\t\t\tobj = deref_tag(obj, NULL, 0);\n+\t\tif (obj->type != OBJ_COMMIT)\n+\t\t\tdie(\"Non commit %s?\", revs->pending.objects[i].name);\n+\t\tif (sb->final)\n+\t\t\tdie(\"More than one commit to dig down to %s and %s?\",\n+\t\t\t    revs->pending.objects[i].name,\n+\t\t\t    final_commit_name);\n+\t\tsb->final = (struct commit *) obj;\n+\t\tfinal_commit_name = revs->pending.objects[i].name;\n+\t}\n+\tif (!final_commit_name)\n+\t\tdie(\"No commit to dig down to?\");\n+\treturn final_commit_name;\n+}\n+\n int cmd_blame(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n@@ -2213,6 +2251,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\t\tblank_boundary = 1;\n \t\telse if (!strcmp(\"--root\", arg))\n \t\t\tshow_root = 1;\n+\t\telse if (!strcmp(\"--reverse\", arg)) {\n+\t\t\targv[unk++] = \"--children\";\n+\t\t\treverse = 1;\n+\t\t}\n \t\telse if (!strcmp(arg, \"--show-stats\"))\n \t\t\tshow_stats = 1;\n \t\telse if (!strcmp(\"-c\", arg))\n@@ -2386,7 +2428,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tsetup_revisions(unk, argv, &revs, NULL);\n \tmemset(&sb, 0, sizeof(sb));\n \n-\tfinal_commit_name = prepare_final(&sb, &revs);\n+\tsb.revs = &revs;\n+\tif (!reverse)\n+\t\tfinal_commit_name = prepare_final(&sb);\n+\telse if (contents_from)\n+\t\tdie(\"--contents and --children do not blend well.\");\n+\telse\n+\t\tfinal_commit_name = prepare_initial(&sb);\n+\n \tif (!sb.final) {\n \t\t/*\n \t\t * \"--not A B -- path\" without anything positive;\n@@ -2464,7 +2513,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tif (!incremental)\n \t\tsetup_pager();\n \n-\tassign_blame(&sb, &revs, opt);\n+\tassign_blame(&sb, opt);\n \n \tif (incremental)\n \t\treturn 0;\n-- \n1.5.5.rc3.139.g8b2cf\n"},{"id":"73596","messageId":"47F4BE98.8060209@viscovery.net","threadId":"12970","inReplyTo":"7v4paj486a.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 0/4] Blame in reverse","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-04-03T11:25:12Z","receivedAt":"2008-04-03T11:25:12Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> Usual \"git blame\" starts from the latest revision and finds where each\n> line came from (i.e. who came up with that brilliant idea, or more often\n> who introduced that bug).  This new mode starts from an old revision, and\n> annotates in reverse --- up to which point each line survived, before it\n> got replaced with something else. \n\nNice!! Is the result somehow different from\n\n\t$ git rev-list --reverse HEAD > /tmp/blame.revs\n\t$ git blame -S /tmp/blame.revs foo.c\n\n?\n\n-- Hannes\n"},{"id":"73620","messageId":"7v1w5m201g.fsf@gitster.siamese.dyndns.org","threadId":"12970","inReplyTo":"47F4BE98.8060209@viscovery.net","subject":"Re: [PATCH 0/4] Blame in reverse","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-03T19:47:23Z","receivedAt":"2008-04-03T19:47:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j.sixt@viscovery.net> writes:\n\n> Junio C Hamano schrieb:\n>> Usual \"git blame\" starts from the latest revision and finds where each\n>> line came from (i.e. who came up with that brilliant idea, or more often\n>> who introduced that bug).  This new mode starts from an old revision, and\n>> annotates in reverse --- up to which point each line survived, before it\n>> got replaced with something else. \n>\n> Nice!! Is the result somehow different from\n>\n> \t$ git rev-list --reverse HEAD > /tmp/blame.revs\n> \t$ git blame -S /tmp/blame.revs foo.c\n\nDoesn't -S read from a graft-like file, where each line shows a commit and\nthen its parents?\n\nCome to think of it, I probably should have done:\n\n\tgit rev-list --children\n\nwhich is a mirror image of \"git rev-list --parents\".  Unlike the\nadditional \"child\" header in the output of the log-tree family, that would\nnot have any controversial format or UI design issues.  Also the resulting\noutput from such command can then be fed via -S to blame.\n\nHowever, as I mentioned in the message of [4/4], I think showing the\ncommit that \"introduced\" the line is not useful when blame runs in\nreverse; blame needs to have a switch to tell it which commit to show\n(either \"the guilty party\" or \"the ones the guilty party tried to pass\nblame on\") anyway, so even though feeding an upside-down topology to the\ncommand via its \"-S\" can try to mimic what it does, it does not\nreallysolve what I wanted to do.\n"},{"id":"73626","messageId":"alpine.LSU.1.00.0804032158040.4008@racer.site","threadId":"12970","inReplyTo":"7vlk3v2tg9.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 1/4] \"log\" and friends: --children option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-04-03T19:58:58Z","receivedAt":"2008-04-03T19:58:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 3 Apr 2008, Junio C Hamano wrote:\n\n> @@ -1398,6 +1404,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch\n>  \treturn left;\n>  }\n>  \n> +static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child)\n> +{\n> +\tstruct commit_list *l = xcalloc(1, sizeof(*l));\n> +\n> +\tl->item = child;\n> +\tl->next = add_decoration(&revs->children, &parent->object, l);\n> +}\n> +\n> +static void set_children(struct rev_info *revs)\n\nI'd call this add_children().\n\nOther than that, nicely done!  I did not think it was possible with as few \nchanges.\n\nCiao,\nDscho\n"}]}