{"thread":{"id":"5898","subject":"[PATCH] git-pickaxe: blame rewritten.","startedAt":"2006-10-12T08:52:52Z","lastAt":"2006-10-13T22:50:56Z","messageCount":18,"participants":["Junio C Hamano","Linus Torvalds","Luben Tuikov","Jakub Narebski","Petr Baudis"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"28658","messageId":"7v7iz5rk4b.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":null,"subject":"[PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-12T08:52:52Z","receivedAt":"2006-10-12T08:52:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Currently it does what git-blame does, but only faster.\n\nMore importantly, its internal structure is designed to support\ncontent movement (aka cut-and-paste) more easily by allowing\nmore than one paths to be taken from the same commit.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\nI really hate to do this immediately after writing obituary for\nannotate, but I had a solid 24-hour to work on git, which is a\nrare opportunity for me these days, so here it is.\n\nFor 160+ *.c files in git.git repository, running git-blame and\ngit-pickaxe with \"-n -f\" options for all of them takes the\nfollowing:\n\n *** blame ***\n 141.43user 1.41system 2:22.94elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k\n 0inputs+0outputs (0major+438667minor)pagefaults 0swaps\n *** pickaxe ***\n 78.99user 2.22system 1:21.22elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k\n 0inputs+0outputs (0major+397307minor)pagefaults 0swaps\n\nI have given only cursory check to its output (some files are\nblamed slightly differently from how git-blame does), but it\nappears that there is no major breakage.  You can see for\nexample try annotating builtin-apply.c starting from v1.4.0;\nthere are two differences, which pickaxe assigns blame to older\ncommits and both of them seem to be sensible.  This exercise\nalso revealed some mistakes I made earlier (number of same\npatches were cherry-picked in separate branches and then later\nmerged: \"gitk master --not e4c9327a 15b4d577 -- pack-objects.c\"\nwould show an example of such a breakage.\n\n Documentation/git-pickaxe.txt |  104 +++++\n Documentation/git.txt         |    3 +\n Makefile                      |    1 +\n builtin-pickaxe.c             |  952 +++++++++++++++++++++++++++++++++++++++++\n builtin.h                     |    1 +\n git.c                         |    1 +\n t/t8003-pickaxe.sh            |    9 +\n 7 files changed, 1071 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt\nnew file mode 100644\nindex 0000000..7f30cdf\n--- /dev/null\n+++ b/Documentation/git-pickaxe.txt\n@@ -0,0 +1,104 @@\n+git-pickaxe(1)\n+==============\n+\n+NAME\n+----\n+git-pickaxe - Show what revision and author last modified each line of a file\n+\n+SYNOPSIS\n+--------\n+'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [--] <file> [<rev>]\n+\n+DESCRIPTION\n+-----------\n+\n+Annotates each line in the given file with information from the revision which\n+last modified the line. Optionally, start annotating from the given revision.\n+\n+Also it can limit the range of lines annotated.\n+\n+This report doesn't tell you anything about lines which have been deleted or\n+replaced; you need to use a tool such as gitlink:git-diff[1] or the \"pickaxe\"\n+interface briefly mentioned in the following paragraph.\n+\n+Apart from supporting file annotation, git also supports searching the\n+development history for when a code snippet occured in a change. This makes it\n+possible to track when a code snippet was added to a file, moved or copied\n+between files, and eventually deleted or replaced. It works by searching for\n+a text string in the diff. A small example:\n+\n+-----------------------------------------------------------------------------\n+$ git log --pretty=oneline -S'blame_usage'\n+5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S <ancestry-file>\n+ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output\n+-----------------------------------------------------------------------------\n+\n+OPTIONS\n+-------\n+-c, --compatibility::\n+\tUse the same output mode as gitlink:git-annotate[1] (Default: off).\n+\n+-L n,m::\n+\tAnnotate only the specified line range (lines count from 1).\n+\n+-l, --long::\n+\tShow long rev (Default: off).\n+\n+-t, --time::\n+\tShow raw timestamp (Default: off).\n+\n+-S, --rev-file <revs-file>::\n+\tUse revs from revs-file instead of calling gitlink:git-rev-list[1].\n+\n+-f, --show-name::\n+\tShow filename in the original commit.  By default\n+\tfilename is shown if there is any line that came from a\n+\tfile with different name, due to rename detection.\n+\n+-n, --show-number::\n+\tShow line number in the original commit (Default: off).\n+\n+-p, --porcelain::\n+\tShow in a format designed for machine consumption.\n+\n+-h, --help::\n+\tShow help message.\n+\n+\n+THE PORCELAIN FORMAT\n+--------------------\n+\n+In this format, each line is output after a header; the\n+header at the minumum has the first line which has:\n+\n+- 40-byte SHA-1 of the commit the line is attributed to;\n+- the line number of the line in the original file;\n+- the line number of the line in the final file;\n+- on a line that starts a group of line from a different\n+  commit than the previous one, the number of lines in this\n+  group.  On subsequent lines this field is absent.\n+\n+This header line is followed by the following information\n+at least once for each commit:\n+\n+- author name (\"author\"), email (\"author-mail\"), time\n+  (\"author-time\"), and timezone (\"author-tz\"); similarly\n+  for committer.\n+- filename in the commit the line is attributed to.\n+- the first line of the commit log message (\"summary\").\n+\n+The contents of the actual line is output after the above\n+header, prefixed by a TAB. This is to allow adding more\n+header elements later.\n+\n+SEE ALSO\n+--------\n+gitlink:git-blame[1]\n+\n+AUTHOR\n+------\n+Written by Junio C Hamano <junkio@cox.net>\n+\n+GIT\n+---\n+Part of the gitlink:git[7] suite\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 3af6fc6..7074e32 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -430,6 +430,9 @@ gitlink:git-annotate[1]::\n gitlink:git-blame[1]::\n \tBlame file lines on commits.\n \n+gitlink:git-pickaxe[1]::\n+\tFind out where each line in a file came from.\n+\n gitlink:git-check-ref-format[1]::\n \tMake sure ref name is well formed.\n \ndiff --git a/Makefile b/Makefile\nindex 2c7c338..bd99550 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -288,6 +288,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-mv.o \\\n \tbuiltin-name-rev.o \\\n \tbuiltin-pack-objects.o \\\n+\tbuiltin-pickaxe.o \\\n \tbuiltin-prune.o \\\n \tbuiltin-prune-packed.o \\\n \tbuiltin-push.o \\\ndiff --git a/builtin-pickaxe.c b/builtin-pickaxe.c\nnew file mode 100644\nindex 0000000..0e26308\n--- /dev/null\n+++ b/builtin-pickaxe.c\n@@ -0,0 +1,952 @@\n+/*\n+ * Pickaxe\n+ *\n+ * Copyright (c) 2006, Junio C Hamano\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"blob.h\"\n+#include \"commit.h\"\n+#include \"tree-walk.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n+#include \"xdiff-interface.h\"\n+\n+#include <time.h>\n+#include <sys/time.h>\n+\n+static char pickaxe_usage[] =\n+\"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [--] file [commit]\\n\"\n+\"  -c, --compatibility Use the same output mode as git-annotate (Default: off)\\n\"\n+\"  -l, --long          Show long commit SHA1 (Default: off)\\n\"\n+\"  -t, --time          Show raw timestamp (Default: off)\\n\"\n+\"  -f, --show-name     Show original filename (Default: auto)\\n\"\n+\"  -n, --show-number   Show original linenumber (Default: off)\\n\"\n+\"  -p, --porcelain     Show in a format designed for machine consumption\\n\"\n+\"  -L n,m              Process only line range n,m, counting from 1\\n\"\n+\"  -S revs-file        Use revisions from revs-file instead of calling git-rev-list\\n\";\n+\n+static int longest_file;\n+static int longest_author;\n+static int max_orig_digits;\n+static int max_digits;\n+\n+#define DEBUG 0\n+/*\n+ * One blob in a commit\n+ */\n+struct origin {\n+\tstruct commit *commit;\n+\tunsigned char blob_sha1[20];\n+\tchar metainfo_given;\n+\tchar path[FLEX_ARRAY];\n+};\n+\n+struct blame_entry {\n+\t/* the first line of this group in the final image;\n+\t * internally all line numbers are 0 based.\n+\t */\n+\tint lno;\n+\n+\t/* how many lines this group has */\n+\tint num_lines;\n+\n+\t/* the commit that introduced this group into the final image */\n+\tstruct origin *suspect;\n+\n+\t/* true if the suspect is truly guilty; false while we have not\n+\t * checked if the group came from one of its parents.\n+\t */\n+\tchar guilty;\n+\n+\t/* the line number of the first line of this group in the\n+\t * suspect's file; internally all line numbers are 0 based.\n+\t */\n+\tint s_lno;\n+};\n+\n+struct scoreboard {\n+\t/* the final commit (i.e. where we started digging from) */\n+\tstruct commit *final;\n+\n+\tconst char *path;\n+\n+\t/* the contents in the final; pointed into by buf pointers of\n+\t * blame_entries\n+\t */\n+\tconst char *final_buf;\n+\tunsigned long final_buf_size;\n+\n+\t/* list of blames */\n+\tstruct blame_entry **entries;\n+\tint num_entries;\n+};\n+\n+static int blame_entry_sort(const void *a_, const void *b_)\n+{\n+\tint a = (*(struct blame_entry **)a_)->lno;\n+\tint b = (*(struct blame_entry **)b_)->lno;\n+\treturn a - b;\n+}\n+\n+static void coalesce(struct scoreboard *sb)\n+{\n+\tint i, o, num;\n+\tstruct blame_entry **ent;\n+\n+\tqsort(sb->entries, sb->num_entries, sizeof(struct blame_entry *),\n+\t      blame_entry_sort);\n+\tent = sb->entries;\n+\tnum = sb->num_entries;\n+\n+\t/*\n+\t * Look at i, and see if it is adjacent to the last one at (o-1);\n+\t * if so, extend the last one into a larger group and drop the\n+\t * current one at i.  Copy the entry to remove gaps made by the\n+\t * coalescing process as we go.\n+\t */\n+\tfor (o = 0, i = 0; i < num; i++) {\n+\t\tif (o &&\n+\t\t    ent[o-1]->suspect == ent[i]->suspect &&\n+\t\t    ent[o-1]->s_lno + ent[o-1]->num_lines == ent[i]->s_lno) {\n+\t\t\tent[o-1]->num_lines += ent[i]->num_lines;\n+\t\t\tfree(ent[i]);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (o != i)\n+\t\t\tent[o] = ent[i];\n+\t\to++;\n+\t}\n+\tsb->num_entries = o;\n+}\n+\n+static void free_origin(struct origin *o)\n+{\n+\tfree(o);\n+}\n+\n+static struct origin *find_origin(struct scoreboard *sb,\n+\t\t\t\t  struct commit *commit,\n+\t\t\t\t  const char *path)\n+{\n+\tint i, num;\n+\tstruct blame_entry **ent;\n+\tstruct origin *o;\n+\tunsigned mode;\n+\tchar type[10];\n+\n+\tent = sb->entries;\n+\tnum = sb->num_entries;\n+\tfor (i = 0; i < num; i++) {\n+\t\tif (ent[i]->suspect->commit == commit &&\n+\t\t    !strcmp(ent[i]->suspect->path, path))\n+\t\t\treturn ent[i]->suspect;\n+\t}\n+\n+\to = xcalloc(1, sizeof(*o) + strlen(path) + 1);\n+\to->commit = commit;\n+\tstrcpy(o->path, path);\n+\tif (get_tree_entry(commit->object.sha1, path, o->blob_sha1, &mode))\n+\t\tgoto err_out;\n+\tif (sha1_object_info(o->blob_sha1, type, NULL) ||\n+\t    strcmp(type, blob_type))\n+\t\tgoto err_out;\n+\treturn o;\n+ err_out:\n+\tfree_origin(o);\n+\treturn NULL;\n+}\n+\n+static struct origin *find_rename(struct scoreboard *sb,\n+\t\t\t\t  struct commit *parent,\n+\t\t\t\t  struct origin *origin)\n+{\n+\tstruct origin *porigin = NULL;\n+\tstruct diff_options diff_opts;\n+\tint i;\n+\tconst char *paths[1];\n+\n+\tdiff_setup(&diff_opts);\n+\tdiff_opts.recursive = 1;\n+\tdiff_opts.detect_rename = DIFF_DETECT_RENAME;\n+\tdiff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\tpaths[0] = NULL;\n+\tdiff_tree_setup_paths(paths, &diff_opts);\n+\tif (diff_setup_done(&diff_opts) < 0)\n+\t\tdie(\"diff-setup\");\n+\tdiff_tree_sha1(origin->commit->tree->object.sha1,\n+\t\t       parent->tree->object.sha1,\n+\t\t       \"\", &diff_opts);\n+\tdiffcore_std(&diff_opts);\n+\n+\tfor (i = 0; i < diff_queued_diff.nr; i++) {\n+\t\tstruct diff_filepair *p = diff_queued_diff.queue[i];\n+\t\tif (p->status == 'R' && !strcmp(p->one->path, origin->path)) {\n+\t\t\tporigin = find_origin(sb, parent, p->two->path);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tdiff_flush(&diff_opts);\n+\treturn porigin;\n+}\n+\n+struct chunk {\n+\t/* line number in postimage; up to but not including this\n+\t * line is the same as preimage\n+\t */\n+\tint same;\n+\n+\t/* preimage line number after this chunk */\n+\tint p_next;\n+\n+\t/* postimage line number after this chunk */\n+\tint t_next;\n+};\n+\n+struct patch {\n+\tstruct chunk *chunks;\n+\tint num;\n+};\n+\n+struct blame_diff_state {\n+\tstruct xdiff_emit_state xm;\n+\tstruct patch *ret;\n+};\n+\n+static void process_u0_diff(void *state_, char *line, unsigned long len)\n+{\n+\tstruct blame_diff_state *state = state_;\n+\tstruct chunk *chunk;\n+\tint off1, off2, len1, len2;\n+\n+if(DEBUG) fprintf(stderr, \"%.*s\", (int) len, line);\n+\n+\tif (len < 4 || line[0] != '@' || line[1] != '@')\n+\t\treturn;\n+\n+\tstate->ret->num++;\n+\tstate->ret->chunks = xrealloc(state->ret->chunks,\n+\t\t\t\t      sizeof(struct chunk) * state->ret->num);\n+\tchunk = &state->ret->chunks[state->ret->num - 1];\n+\n+\tif (parse_hunk_header(line, len,\n+\t\t\t      &off1, &len1,\n+\t\t\t      &off2, &len2)) {\n+\t\tstate->ret->num--;\n+\t\treturn;\n+\t}\n+\n+\t/* Line numbers in patch output are one based. */\n+\toff1--;\n+\toff2--;\n+\n+\tchunk->same = len2 ? off2 : (off2 + 1);\n+\n+\tchunk->p_next = off1 + (len1 ? len1 : 1);\n+\tchunk->t_next = chunk->same + len2;\n+}\n+\n+static struct patch *get_patch(struct origin *parent,\n+\t\t\t       struct origin *origin)\n+{\n+\tstruct blame_diff_state state;\n+\txpparam_t xpp;\n+\txdemitconf_t xecfg;\n+\tmmfile_t file_p, file_o;\n+\txdemitcb_t ecb;\n+\tchar type[10];\n+\n+if(DEBUG) fprintf(stderr, \"get patch %.8s %.8s\\n\",\n+\t\tsha1_to_hex(parent->commit->object.sha1),\n+\t\tsha1_to_hex(origin->commit->object.sha1));\n+\n+\tfile_p.ptr = read_sha1_file(parent->blob_sha1, type,\n+\t\t\t\t    (unsigned long *) &file_p.size);\n+\tfile_o.ptr = read_sha1_file(origin->blob_sha1, type,\n+\t\t\t\t    (unsigned long *) &file_o.size);\n+\tif (!file_p.ptr || !file_o.ptr)\n+\t\treturn NULL;\n+\n+\txpp.flags = XDF_NEED_MINIMAL;\n+\txecfg.ctxlen = 0;\n+\txecfg.flags = 0;\n+\tecb.outf = xdiff_outf;\n+\tecb.priv = &state;\n+\tmemset(&state, 0, sizeof(state));\n+\tstate.xm.consume = process_u0_diff;\n+\tstate.ret = xmalloc(sizeof(struct patch));\n+\tstate.ret->chunks = NULL;\n+\tstate.ret->num = 0;\n+\n+\txdl_diff(&file_p, &file_o, &xpp, &xecfg, &ecb);\n+\treturn state.ret;\n+}\n+\n+static void free_patch(struct patch *p)\n+{\n+\tfree(p->chunks);\n+\tfree(p);\n+}\n+\n+static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)\n+{\n+\tsb->entries = xrealloc(sb->entries,\n+\t\t\t       sizeof(struct blame_entry *) *\n+\t\t\t       (sb->num_entries + 1));\n+\tsb->entries[sb->num_entries++] = e;\n+}\n+\n+static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,\n+\t\t\t  int tlno, int plno, int same,\n+\t\t\t  struct origin *parent)\n+{\n+\t/* it is known that lines between tlno to same\n+\t * came from parent, and e has an overlap with that range.\n+\t * it also is known that parent's line plno corresponds to\n+\t * e's line tlno.\n+\t *\n+\t *                <---- e ----->\n+\t *                   <------>\n+\t *                   <------------>\n+\t *             <------------>\n+\t *             <------------------>\n+\t *\n+\t * Potentially we need to split e into three parts; before\n+\t * this chunk, the chunk to be blamed for parent, and after\n+\t * that portion.\n+\t *\n+\t * When the function splits the blame_entry, it pushes the part\n+\t * that needs to be re-processed at the end of the queue.\n+\t */\n+\tint chunk_end_lno;\n+\tstruct blame_entry split[3], *new_entry;\n+\tmemset(split, 0, sizeof(split));\n+\n+\tsplit[1].suspect = parent;\n+\tif (e->s_lno < tlno) {\n+\t\t/* there is a pre-chunk part not blamed on parent */\n+\t\tsplit[0].suspect = e->suspect;\n+\t\tsplit[0].lno = e->lno;\n+\t\tsplit[0].s_lno = e->s_lno;\n+\t\tsplit[0].num_lines = tlno - e->s_lno;\n+\t\tsplit[1].lno = e->lno + tlno - e->s_lno;\n+\t\tsplit[1].s_lno = plno;\n+\t}\n+\telse {\n+\t\tsplit[1].lno = e->lno;\n+\t\tsplit[1].s_lno = plno + (e->s_lno - tlno);\n+\t}\n+\n+\tif (same < e->s_lno + e->num_lines) {\n+\t\t/* there is a post-chunk part not blamed on parent */\n+\t\tsplit[2].suspect = e->suspect;\n+\t\tsplit[2].lno = e->lno + (same - e->s_lno);\n+\t\tsplit[2].s_lno = e->s_lno + (same - e->s_lno);\n+\t\tsplit[2].num_lines = e->s_lno + e->num_lines - same;\n+\t\tchunk_end_lno = split[2].lno;\n+\t}\n+\telse\n+\t\tchunk_end_lno = e->lno + e->num_lines;\n+\tsplit[1].num_lines = chunk_end_lno - split[1].lno;\n+\n+\tif (split[0].suspect && split[2].suspect) {\n+\t\t/* we need to split e into two and add another for parent */\n+\t\tmemcpy(e, &(split[0]), sizeof(struct blame_entry));\n+\n+\t\tnew_entry = xmalloc(sizeof(*new_entry));\n+\t\tmemcpy(new_entry, &(split[2]), sizeof(struct blame_entry));\n+\t\tadd_blame_entry(sb, new_entry);\n+\n+\t\tnew_entry = xmalloc(sizeof(*new_entry));\n+\t\tmemcpy(new_entry, &(split[1]), sizeof(struct blame_entry));\n+\t\tadd_blame_entry(sb, new_entry);\n+\t}\n+\telse if (!split[0].suspect && !split[2].suspect)\n+\t\t/* parent covers the entire area */\n+\t\tmemcpy(e, &(split[1]), sizeof(struct blame_entry));\n+\telse if (split[0].suspect) {\n+\t\tmemcpy(e, &(split[0]), sizeof(struct blame_entry));\n+\n+\t\tnew_entry = xmalloc(sizeof(*new_entry));\n+\t\tmemcpy(new_entry, &(split[1]), sizeof(struct blame_entry));\n+\t\tadd_blame_entry(sb, new_entry);\n+\t}\n+\telse {\n+\t\tnew_entry = xmalloc(sizeof(*new_entry));\n+\t\tmemcpy(new_entry, &(split[1]), sizeof(struct blame_entry));\n+\t\tadd_blame_entry(sb, new_entry);\n+\n+\t\tmemcpy(e, &(split[2]), sizeof(struct blame_entry));\n+\t}\n+}\n+\n+static void blame_chunk(struct scoreboard *sb,\n+\t\t\tint tlno, int plno, int same,\n+\t\t\tstruct origin *target, struct origin *parent)\n+{\n+\tint i;\n+\tfor (i = 0; i < sb->num_entries; i++) {\n+\t\tstruct blame_entry *e = sb->entries[i];\n+\t\tif (e->guilty || e->suspect != target)\n+\t\t\tcontinue;\n+\t\tif (same <= e->s_lno)\n+\t\t\tcontinue;\n+\t\tif (tlno < e->s_lno + e->num_lines)\n+\t\t\tblame_overlap(sb, e, tlno, plno, same, parent);\n+\t}\n+}\n+\n+static int pass_blame_to_parent(struct scoreboard *sb,\n+\t\t\t\tstruct origin *target,\n+\t\t\t\tstruct origin *parent)\n+{\n+\tint i;\n+\tstruct patch *patch;\n+\tint plno, tlno, last_in_target = -1;\n+\n+\tfor (i = 0; i < sb->num_entries; i++) {\n+\t\tstruct blame_entry *e = sb->entries[i];\n+\t\tif (e->guilty || e->suspect != target)\n+\t\t\tcontinue;\n+\t\tif (last_in_target < e->s_lno + e->num_lines)\n+\t\t\tlast_in_target = e->s_lno + e->num_lines;\n+\t}\n+\tpatch = get_patch(parent, target);\n+\tplno = tlno = 0;\n+\tfor (i = 0; i < patch->num; i++) {\n+\t\tstruct chunk *chunk = &patch->chunks[i];\n+\n+if(DEBUG)\tfprintf(stderr,\n+\t\t\t\"plno = %d, tlno = %d, \"\n+\t\t\t\"same as parent up to %d, resync %d and %d\\n\",\n+\t\t\tplno, tlno,\n+\t\t\tchunk->same, chunk->p_next, chunk->t_next);\n+\t\tblame_chunk(sb, tlno, plno, chunk->same, target, parent);\n+\t\tplno = chunk->p_next;\n+\t\ttlno = chunk->t_next;\n+\t}\n+\t/* rest (i.e. anything above tlno) are the same as parent */\n+\tblame_chunk(sb, tlno, plno, last_in_target, target, parent);\n+\n+\tfree_patch(patch);\n+\treturn 0;\n+}\n+\n+#define MAXPARENT 16\n+static void pass_blame(struct scoreboard *sb, struct origin *origin)\n+{\n+\tint i, parent_ix;\n+\tstruct commit *commit = origin->commit;\n+\tstruct commit_list *parent;\n+\tstruct origin *parent_origin[MAXPARENT], *porigin;\n+\n+\tif (parse_commit(commit))\n+\t\texit(1);\n+\n+\tmemset(parent_origin, 0, sizeof(parent_origin));\n+\tfor (parent_ix = 0, parent = commit->parents;\n+\t     parent_ix < MAXPARENT && parent;\n+\t     parent = parent->next, parent_ix++) {\n+\t\tif (parse_commit(parent->item))\n+\t\t\tcontinue;\n+\t\tporigin = find_origin(sb, parent->item, origin->path);\n+\t\tif (!porigin)\n+\t\t\tporigin = find_rename(sb, parent->item, origin);\n+\t\tif (!porigin)\n+\t\t\tcontinue;\n+\t\tif (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {\n+\t\t\tfor (i = 0; i < sb->num_entries; i++) {\n+\t\t\t\tif (sb->entries[i]->suspect == origin)\n+\t\t\t\t\tsb->entries[i]->suspect = porigin;\n+\t\t\t}\n+\t\t\t/* now everything blamed for origin is blamed for\n+\t\t\t * porigin, we do not need to keep it anymore.\n+\t\t\t * Do not free porigin (or the ones we got from\n+\t\t\t * earlier round); they may still be used elsewhere.\n+\t\t\t */\n+\t\t\tfree_origin(origin);\n+\t\t\treturn;\n+\t\t}\n+\t\tparent_origin[parent_ix] = porigin;\n+\t}\n+\n+\tfor (parent_ix = 0, parent = commit->parents;\n+\t     parent_ix < MAXPARENT && parent;\n+\t     parent = parent->next, parent_ix++) {\n+\t\tstruct origin *porigin = parent_origin[parent_ix];\n+\t\tif (!porigin)\n+\t\t\tcontinue;\n+\t\tif (pass_blame_to_parent(sb, origin, porigin))\n+\t\t\tbreak;\n+\t}\n+\n+\t/* NEEDSWORK:\n+\t * Optionally run \"ciff\" to find copies from parents' files here\n+\t */\n+\n+\t/* Take responsibility for the remaining entries */\n+\tfor (i = 0; i < sb->num_entries; i++)\n+\t\tif (!sb->entries[i]->guilty &&\n+\t\t    sb->entries[i]->suspect == origin)\n+\t\t\tsb->entries[i]->guilty = 1;\n+}\n+\n+static void assign_blame(struct scoreboard *sb)\n+{\n+\twhile (1) {\n+\t\tint i;\n+\t\tstruct origin *suspect = NULL;\n+\n+\t\t/* find one suspect to break down */\n+\t\tfor (i = 0; !suspect && i < sb->num_entries; i++) {\n+\t\t\tif (!sb->entries[i]->guilty)\n+\t\t\t\tsuspect = sb->entries[i]->suspect;\n+\t\t}\n+\t\tif (!suspect)\n+\t\t\treturn; /* all done */\n+\n+\t\tpass_blame(sb, suspect);\n+\t}\n+}\n+\n+static const char *format_time(unsigned long time, const char *tz_str,\n+\t\t\t       int show_raw_time)\n+{\n+\tstatic char time_buf[128];\n+\ttime_t t = time;\n+\tint minutes, tz;\n+\tstruct tm *tm;\n+\n+\tif (show_raw_time) {\n+\t\tsprintf(time_buf, \"%lu %s\", time, tz_str);\n+\t\treturn time_buf;\n+\t}\n+\n+\ttz = atoi(tz_str);\n+\tminutes = tz < 0 ? -tz : tz;\n+\tminutes = (minutes / 100)*60 + (minutes % 100);\n+\tminutes = tz < 0 ? -minutes : minutes;\n+\tt = time + minutes * 60;\n+\ttm = gmtime(&t);\n+\n+\tstrftime(time_buf, sizeof(time_buf), \"%Y-%m-%d %H:%M:%S \", tm);\n+\tstrcat(time_buf, tz_str);\n+\treturn time_buf;\n+}\n+\n+struct commit_info\n+{\n+\tchar *author;\n+\tchar *author_mail;\n+\tunsigned long author_time;\n+\tchar *author_tz;\n+\n+\t/* filled only when asked for details */\n+\tchar *committer;\n+\tchar *committer_mail;\n+\tunsigned long committer_time;\n+\tchar *committer_tz;\n+\n+\tchar *summary;\n+};\n+\n+static void get_ac_line(const char *inbuf, const char *what,\n+\t\t\tint bufsz, char *person, char **mail,\n+\t\t\tunsigned long *time, char **tz)\n+{\n+\tint len;\n+\tchar *tmp, *endp;\n+\n+\ttmp = strstr(inbuf, what);\n+\tif (!tmp)\n+\t\tgoto error_out;\n+\ttmp += strlen(what);\n+\tendp = strchr(tmp, '\\n');\n+\tif (!endp)\n+\t\tlen = strlen(tmp);\n+\telse\n+\t\tlen = endp - tmp;\n+\tif (bufsz <= len) {\n+\terror_out:\n+\t\t/* Ugh */\n+\t\tperson = *mail = *tz = \"(unknown)\";\n+\t\t*time = 0;\n+\t\treturn;\n+\t}\n+\tmemcpy(person, tmp, len);\n+\n+\ttmp = person;\n+\ttmp += len;\n+\t*tmp = 0;\n+\twhile (*tmp != ' ')\n+\t\ttmp--;\n+\t*tz = tmp+1;\n+\n+\t*tmp = 0;\n+\twhile (*tmp != ' ')\n+\t\ttmp--;\n+\t*time = strtoul(tmp, NULL, 10);\n+\n+\t*tmp = 0;\n+\twhile (*tmp != ' ')\n+\t\ttmp--;\n+\t*mail = tmp + 1;\n+\t*tmp = 0;\n+}\n+\n+static void get_commit_info(struct commit *commit,\n+\t\t\t    struct commit_info *ret,\n+\t\t\t    int detailed)\n+{\n+\tint len;\n+\tchar *tmp, *endp;\n+\tstatic char author_buf[1024];\n+\tstatic char committer_buf[1024];\n+\tstatic char summary_buf[1024];\n+\n+\tret->author = author_buf;\n+\tget_ac_line(commit->buffer, \"\\nauthor \",\n+\t\t    sizeof(author_buf), author_buf, &ret->author_mail,\n+\t\t    &ret->author_time, &ret->author_tz);\n+\n+\tif (!detailed)\n+\t\treturn;\n+\n+\tret->committer = committer_buf;\n+\tget_ac_line(commit->buffer, \"\\ncommitter \",\n+\t\t    sizeof(committer_buf), committer_buf, &ret->committer_mail,\n+\t\t    &ret->committer_time, &ret->committer_tz);\n+\n+\tret->summary = summary_buf;\n+\ttmp = strstr(commit->buffer, \"\\n\\n\");\n+\tif (!tmp) {\n+\terror_out:\n+\t\tsprintf(summary_buf, \"(%s)\", sha1_to_hex(commit->object.sha1));\n+\t\treturn;\n+\t}\n+\ttmp += 2;\n+\tendp = strchr(tmp, '\\n');\n+\tif (!endp)\n+\t\tgoto error_out;\n+\tlen = endp - tmp;\n+\tif (len >= sizeof(summary_buf))\n+\t\tgoto error_out;\n+\tmemcpy(summary_buf, tmp, len);\n+\tsummary_buf[len] = 0;\n+}\n+\n+static const char *nth_line(const char *buf, unsigned long len, int lno)\n+{\n+\twhile (lno) {\n+\t\tif (*buf == '\\n')\n+\t\t\tlno--;\n+\t\tbuf++;\n+\t\tif (!--len)\n+\t\t\tdie(\"internal error: buffer ran out\");\n+\t}\n+\treturn buf;\n+}\n+\n+#define OUTPUT_ANNOTATE_COMPAT\t001\n+#define OUTPUT_LONG_OBJECT_NAME\t002\n+#define OUTPUT_RAW_TIMESTAMP\t004\n+#define OUTPUT_PORCELAIN\t010\n+#define OUTPUT_SHOW_NAME\t020\n+#define OUTPUT_SHOW_NUMBER\t040\n+\n+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)\n+{\n+\tint cnt;\n+\tconst char *cp;\n+\tstruct origin *suspect = ent->suspect;\n+\tchar hex[41];\n+\n+\tstrcpy(hex, sha1_to_hex(suspect->commit->object.sha1));\n+\tprintf(\"%s%c%d %d %d\\n\",\n+\t       hex,\n+\t       ent->guilty ? ' ' : '*', // purely for debugging\n+\t       ent->s_lno + 1,\n+\t       ent->lno + 1,\n+\t       ent->num_lines);\n+\tif (!ent->suspect->metainfo_given) {\n+\t\tstruct commit_info ci;\n+\t\tsuspect->metainfo_given = 1;\n+\t\tget_commit_info(suspect->commit, &ci, 1);\n+\t\tprintf(\"author %s\\n\", ci.author);\n+\t\tprintf(\"author-mail %s\\n\", ci.author_mail);\n+\t\tprintf(\"author-time %lu\\n\", ci.author_time);\n+\t\tprintf(\"author-tz %s\\n\", ci.author_tz);\n+\t\tprintf(\"committer %s\\n\", ci.committer);\n+\t\tprintf(\"committer-mail %s\\n\", ci.committer_mail);\n+\t\tprintf(\"committer-time %lu\\n\", ci.committer_time);\n+\t\tprintf(\"committer-tz %s\\n\", ci.committer_tz);\n+\t\tprintf(\"filename %s\\n\", suspect->path);\n+\t\tprintf(\"summary %s\\n\", ci.summary);\n+\t}\n+\tcp = nth_line(sb->final_buf, sb->final_buf_size, ent->lno);\n+\tfor (cnt = 0; cnt < ent->num_lines; cnt++) {\n+\t\tchar ch;\n+\t\tif (cnt)\n+\t\t\tprintf(\"%s %d %d\\n\", hex,\n+\t\t\t       ent->s_lno + 1 + cnt,\n+\t\t\t       ent->lno + 1 + cnt);\n+\t\tputchar('\\t');\n+\t\tdo {\n+\t\t\tch = *cp++;\n+\t\t\tputchar(ch);\n+\t\t} while (ch != '\\n' &&\n+\t\t\t cp < sb->final_buf + sb->final_buf_size);\n+\t}\n+}\n+\n+static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)\n+{\n+\tint cnt;\n+\tconst char *cp;\n+\tstruct origin *suspect = ent->suspect;\n+\tstruct commit_info ci;\n+\tchar hex[41];\n+\tint show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);\n+\n+\tget_commit_info(suspect->commit, &ci, 1);\n+\tstrcpy(hex, sha1_to_hex(suspect->commit->object.sha1));\n+\n+\tcp = nth_line(sb->final_buf, sb->final_buf_size, ent->lno);\n+\tfor (cnt = 0; cnt < ent->num_lines; cnt++) {\n+\t\tchar ch;\n+\n+\t\tprintf(\"%.*s\", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);\n+\t\tif (opt & OUTPUT_ANNOTATE_COMPAT)\n+\t\t\tprintf(\"\\t(%10s\\t%10s\\t%d)\", ci.author,\n+\t\t\t       format_time(ci.author_time, ci.author_tz,\n+\t\t\t\t\t   show_raw_time),\n+\t\t\t       ent->lno + 1 + cnt);\n+\t\telse {\n+\t\t\tif (opt & OUTPUT_SHOW_NAME)\n+\t\t\t\tprintf(\" %-*.*s\", longest_file, longest_file,\n+\t\t\t\t       suspect->path);\n+\t\t\tif (opt & OUTPUT_SHOW_NUMBER)\n+\t\t\t\tprintf(\" %*d\", max_orig_digits,\n+\t\t\t\t       ent->s_lno + 1 + cnt);\n+\t\t\tprintf(\" (%-*.*s %10s %*d) \",\n+\t\t\t       longest_author, longest_author, ci.author,\n+\t\t\t       format_time(ci.author_time, ci.author_tz,\n+\t\t\t\t\t   show_raw_time),\n+\t\t\t       max_digits, ent->lno + 1 + cnt);\n+\t\t}\n+\t\tdo {\n+\t\t\tch = *cp++;\n+\t\t\tputchar(ch);\n+\t\t} while (ch != '\\n' &&\n+\t\t\t cp < sb->final_buf + sb->final_buf_size);\n+\t}\n+}\n+\n+static void output(struct scoreboard *sb, int option)\n+{\n+\tint i, num;\n+\tstruct blame_entry **ent;\n+\tent = sb->entries;\n+\tnum = sb->num_entries;\n+\tfor (i = 0; i < num; i++) {\n+\t\tif (option & OUTPUT_PORCELAIN)\n+\t\t\temit_porcelain(sb, ent[i]);\n+\t\telse\n+\t\t\temit_other(sb, ent[i], option);\n+\t}\n+}\n+\n+static int count_lines(const char *buf, unsigned long len)\n+{\n+\tint num = 0;\n+\tif (len && buf[len-1] != '\\n')\n+\t\tnum++; /* incomplete line at the end */\n+\twhile (len--) {\n+\t\tif (*buf++ == '\\n')\n+\t\t\tnum++;\n+\t}\n+\treturn num;\n+}\n+\n+static int read_ancestry(const char *graft_file)\n+{\n+\tFILE *fp = fopen(graft_file, \"r\");\n+\tchar buf[1024];\n+\tif (!fp)\n+\t\treturn -1;\n+\twhile (fgets(buf, sizeof(buf), fp)) {\n+\t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n+\t\tint len = strlen(buf);\n+\t\tstruct commit_graft *graft = read_graft_line(buf, len);\n+\t\tregister_commit_graft(graft, 0);\n+\t}\n+\tfclose(fp);\n+\treturn 0;\n+}\n+\n+static int lineno_width(int lines)\n+{\n+        int i, width;\n+\n+        for (width = 1, i = 10; i <= lines + 1; width++)\n+                i *= 10;\n+        return width;\n+}\n+\n+static void find_alignment(struct scoreboard *sb, int *option)\n+{\n+\tint i;\n+\tint longest_src_lines = 0;\n+\tint longest_dst_lines = 0;\n+\n+\tfor (i = 0; i < sb->num_entries; i++) {\n+\t\tstruct blame_entry *e = sb->entries[i];\n+\t\tstruct origin *suspect = e->suspect;\n+\t\tstruct commit_info ci;\n+\t\tint num;\n+\n+\t\tif (!suspect->metainfo_given) {\n+\t\t\tsuspect->metainfo_given = 1;\n+\t\t\tget_commit_info(suspect->commit, &ci, 1);\n+\t\t\tif (strcmp(suspect->path, sb->path))\n+\t\t\t\t*option |= OUTPUT_SHOW_NAME;\n+\t\t\tnum = strlen(suspect->path);\n+\t\t\tif (longest_file < num)\n+\t\t\t\tlongest_file = num;\n+\t\t\tnum = strlen(ci.author);\n+\t\t\tif (longest_author < num)\n+\t\t\t\tlongest_author = num;\n+\t\t}\n+\t\tnum = e->s_lno + e->num_lines;\n+\t\tif (longest_src_lines < num)\n+\t\t\tlongest_src_lines = num;\n+\t\tnum = e->lno + e->num_lines;\n+\t\tif (longest_dst_lines < num)\n+\t\t\tlongest_dst_lines = num;\n+\t}\n+\tmax_orig_digits = lineno_width(longest_src_lines);\n+\tmax_digits = lineno_width(longest_dst_lines);\n+}\n+\n+int cmd_pickaxe(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *path = argv[1];\n+\tunsigned char sha1[20];\n+\tstruct scoreboard sb;\n+\tstruct origin *o;\n+\tstruct blame_entry *ent;\n+\tint i, seen_dashdash;\n+\tlong bottom, top, lno;\n+\tint output_option = 0;\n+\tconst char *revs_file = NULL;\n+\tconst char *final_commit_name = \"HEAD\";\n+\tchar type[10];\n+\n+\tbottom = top = 0;\n+\tseen_dashdash = 0;\n+\tfor (i = 1; i < argc; i++) {\n+\t\tconst char *arg = argv[i];\n+\t\tif (*arg != '-')\n+\t\t\tbreak;\n+\t\telse if (!strcmp(\"-c\", arg))\n+\t\t\toutput_option |= OUTPUT_ANNOTATE_COMPAT;\n+\t\telse if (!strcmp(\"-t\", arg))\n+\t\t\toutput_option |= OUTPUT_RAW_TIMESTAMP;\n+\t\telse if (!strcmp(\"-l\", arg))\n+\t\t\toutput_option |= OUTPUT_LONG_OBJECT_NAME;\n+\t\telse if (!strcmp(\"-S\", arg) && ++i < argc)\n+\t\t\trevs_file = argv[i];\n+\t\telse if (!strcmp(\"-L\", arg) && ++i < argc) {\n+\t\t\tchar *term;\n+\t\t\targ = argv[i];\n+\t\t\tif (bottom || top)\n+\t\t\t\tdie(\"More than one '-L n,m' option given\");\n+\t\t\tbottom = strtol(arg, &term, 10);\n+\t\t\tif (*term == ',') {\n+\t\t\t\ttop = strtol(term + 1, &term, 10);\n+\t\t\t\tif (*term)\n+\t\t\t\t\tusage(pickaxe_usage);\n+\t\t\t}\n+\t\t\tif (bottom && top && top < bottom) {\n+\t\t\t\tunsigned long tmp;\n+\t\t\t\ttmp = top; top = bottom; bottom = tmp;\n+\t\t\t}\n+\t\t}\n+\t\telse if (!strcmp(\"-f\", arg) ||\n+\t\t\t !strcmp(\"--show-name\", arg))\n+\t\t\toutput_option |= OUTPUT_SHOW_NAME;\n+\t\telse if (!strcmp(\"-n\", arg) ||\n+\t\t\t !strcmp(\"--show-number\", arg))\n+\t\t\toutput_option |= OUTPUT_SHOW_NUMBER;\n+\t\telse if (!strcmp(\"--porcelain\", arg))\n+\t\t\toutput_option |= OUTPUT_PORCELAIN;\n+\t\telse if (!strcmp(\"--\", arg)) {\n+\t\t\tseen_dashdash = 1;\n+\t\t\ti++;\n+\t\t\tbreak;\n+\t\t}\n+\t\telse\n+\t\t\tusage(pickaxe_usage);\n+\t}\n+\n+\t/* argv[i] is filename, argv[i+1] if exists is the commit */\n+\tif (i >= argc)\n+\t\tusage(pickaxe_usage);\n+\tpath = argv[i++];\n+\tif (!seen_dashdash) {\n+\t\tstruct stat st;\n+\t\tif (lstat(path, &st))\n+\t\t\tdie(\"cannot stat path %s: %s\", path, strerror(errno));\n+\t}\n+\n+\tif (i == argc - 1)\n+\t\tfinal_commit_name = argv[i];\n+\telse if (i != argc)\n+\t\tusage(pickaxe_usage);\n+\n+\tmemset(&sb, 0, sizeof(sb));\n+\tif (get_sha1(final_commit_name, sha1) ||\n+\t    !(sb.final = lookup_commit_reference(sha1)))\n+\t\tdie(\"no such commit %s\", final_commit_name);\n+\to = find_origin(&sb, sb.final, path);\n+\tif (!o)\n+\t\tdie(\"no such path %s in %s\", path, final_commit_name);\n+\n+\tsb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size);\n+\tlno = count_lines(sb.final_buf, sb.final_buf_size);\n+\n+\tif (bottom < 1)\n+\t\tbottom = 1;\n+\tif (top < 1)\n+\t\ttop = lno;\n+\tbottom--;\n+\tif (lno < top)\n+\t\tdie(\"file %s has only %lu lines\", path, lno);\n+\n+\tent = xcalloc(1, sizeof(*ent));\n+\tent->lno = bottom;\n+\tent->num_lines = top - bottom;\n+\tent->suspect = o;\n+\tent->s_lno = bottom;\n+\n+\tsb.entries = xcalloc(10, sizeof(struct blame_entry *));\n+\tsb.entries[0] = ent;\n+\tsb.num_entries = 1;\n+\tsb.path = path;\n+\n+\tif (revs_file && read_ancestry(revs_file))\n+\t\tdie(\"reading graft file %s failed: %s\",\n+\t\t    revs_file, strerror(errno));\n+\n+\tassign_blame(&sb);\n+\n+\tcoalesce(&sb);\n+\n+\tif (!(output_option & OUTPUT_PORCELAIN))\n+\t\tfind_alignment(&sb, &output_option);\n+\n+\toutput(&sb, output_option);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex f9fa9ff..7451ce6 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -39,6 +39,7 @@ extern int cmd_mailsplit(int argc, const\n extern int cmd_mv(int argc, const char **argv, const char *prefix);\n extern int cmd_name_rev(int argc, const char **argv, const char *prefix);\n extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);\n+extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);\n extern int cmd_prune(int argc, const char **argv, const char *prefix);\n extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);\n extern int cmd_push(int argc, const char **argv, const char *prefix);\ndiff --git a/git.c b/git.c\nindex e089b53..6164380 100644\n--- a/git.c\n+++ b/git.c\n@@ -245,6 +245,7 @@ static void handle_internal_command(int \n \t\t{ \"mv\", cmd_mv, RUN_SETUP },\n \t\t{ \"name-rev\", cmd_name_rev, RUN_SETUP },\n \t\t{ \"pack-objects\", cmd_pack_objects, RUN_SETUP },\n+\t\t{ \"pickaxe\", cmd_pickaxe, RUN_SETUP },\n \t\t{ \"prune\", cmd_prune, RUN_SETUP },\n \t\t{ \"prune-packed\", cmd_prune_packed, RUN_SETUP },\n \t\t{ \"push\", cmd_push, RUN_SETUP },\ndiff --git a/t/t8003-pickaxe.sh b/t/t8003-pickaxe.sh\nnew file mode 100755\nindex 0000000..d09d1c9\n--- /dev/null\n+++ b/t/t8003-pickaxe.sh\n@@ -0,0 +1,9 @@\n+#!/bin/sh\n+\n+test_description='git-pickaxe'\n+. ./test-lib.sh\n+\n+PROG='git pickaxe -c'\n+. ../annotate-tests.sh\n+\n+test_done\n-- \n1.4.3.rc2.gdce3\n"},{"id":"28670","messageId":"Pine.LNX.4.64.0610120854440.3952@g5.osdl.org","threadId":"5898","inReplyTo":"7v7iz5rk4b.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-10-12T15:58:54Z","receivedAt":"2006-10-12T15:58:54Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Thu, 12 Oct 2006, Junio C Hamano wrote:\n> +\n> +SYNOPSIS\n> +--------\n> +'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [--] <file> [<rev>]\n\nBtw, could we please get rid of this horrible command line syntax.\n\nPretty much _every_ other git command takes the form\n\n\tgit cmd [<rev>] [--] <path>\n\nbut for some reason annotate and blame (and now pickaxe) do it the wrong \nway around, and do\n\n\tgit cmd [--] <path> [<rev>]\n\nwhich is just irritating to somebody who has grown very used to being able \nto specify revisions first.\n\n(I'd actually also like to have a range-modifier, so that I could do\n\n\tgit annotate --since=2.weeks.ago v2.6.18.. <path>\n\nthat didn't go back beyond a certain point, so the command line syntax \nactually _does_ matter - even if we don't support that now, having the \nregular command line syntax means that we -could- support it some day).\n\n\t\tLinus\n"},{"id":"28678","messageId":"7viripnyfh.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"Pine.LNX.4.64.0610120854440.3952@g5.osdl.org","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-12T19:09:38Z","receivedAt":"2006-10-12T19:09:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@osdl.org> writes:\n\n> Btw, could we please get rid of this horrible command line syntax.\n>\n> Pretty much _every_ other git command takes the form\n>\n> \tgit cmd [<rev>] [--] <path>\n>\n> but for some reason annotate and blame (and now pickaxe) do it the wrong \n> way around, and do\n>\n> \tgit cmd [--] <path> [<rev>]\n>\n> which is just irritating to somebody who has grown very used to being able \n> to specify revisions first.\n\nSide note: I do not intend to keep it named pickaxe -- only\nwhile it is in \"pu\".\n\nI think we could support all of them to retain backward\ncompatibility:\n\n\tgit blame [-options] <path> [<rev>]\t\t(*1*)\n\tgit blame [-options] -- <path> [<rev>]\t\t(*2*)\n\tgit blame [-options] [<rev>] [--] <path>\t(*3*)\n\n(*1*) Only for path that does not start with a '-'; we should\n      tighten the input to make sure <path> lstat()'s fine\n      (which we currently do with pickaxe), <path> cannot be\n      interpreted as a valud rev, and when <rev> is given, <rev>\n      does not lstat() Ok, to avoid ambiguity.  Other cases we\n      should require the newer format with explicit -- (*3*).\n\n(*2*) Backward compatible canoncal format.  The above comment\n      for validation does not apply, as (*3*) never has more\n      than one path for 'annotate/blame/pickaxe'\n\n(*3*) The canonical format for ohter git commands.  Without\n      an explicit --, <rev> should not lstat() Ok, and <path>\n      should, to avoid ambiguity.\n\n> (I'd actually also like to have a range-modifier, so that I could do\n>\n> \tgit annotate --since=2.weeks.ago v2.6.18.. <path>\n>\n> that didn't go back beyond a certain point,...\n\nI am not sure about revision bottom (v.2.6.18..) offhand, but\nthe age limit (--since=2.weeks) should be trivial.\n\nInside pass_blame() while we iterate over the parents of the\nsuspect we are looking at, you can skip the parent if it is\nolder than the age limit, or an ancestor of revision bottom,\nlike this:\n\n--- l/builtin-pickaxe.c\n+++ k/builtin-pickaxe.c\n@@ -450,6 +450,12 @@ static void pass_blame(struct scoreboard\n \t     parent = parent->next, parent_ix++) {\n \t\tif (parse_commit(parent->item))\n \t\t\tcontinue;\n+\n+\t\tif (parent is older than age limit)\n+\t\t\tcontinue;\n+\t\tif (parent is an ancestor of revision bottom)\n+\t\t\tcontinue;\n+\n \t\tporigin = find_origin(sb, parent->item, origin->path);\n \t\tif (!porigin)\n \t\t\tporigin = find_rename(sb, parent->item, origin);\n\n\nI think we can get away by checking if the parent _is_ the\nrevision bottom (or one of the bottoms, if you say \"--not\nv2.6.18 v2.6.17.13\") instead of doing \"is it an ancestor\" check,\nin practice.  But that is not correct when a merge is involved.\n"},{"id":"28677","messageId":"20061012193107.44651.qmail@web31805.mail.mud.yahoo.com","threadId":"5898","inReplyTo":"7v7iz5rk4b.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Luben Tuikov","fromEmail":"ltuikov@yahoo.com","sentAt":"2006-10-12T19:31:07Z","receivedAt":"2006-10-12T19:31:07Z","isPatch":true,"sender":{"key":"ltuikov@yahoo.com","avatar":null},"body":"--- Junio C Hamano <junkio@cox.net> wrote:\n> Currently it does what git-blame does, but only faster.\n> \n> More importantly, its internal structure is designed to support\n> content movement (aka cut-and-paste) more easily by allowing\n> more than one paths to be taken from the same commit.\n\nGood work.\n \n> I have given only cursory check to its output (some files are\n> blamed slightly differently from how git-blame does), but it\n\nDetails?  How differently?\n\n> appears that there is no major breakage.  You can see for\n> example try annotating builtin-apply.c starting from v1.4.0;\n> there are two differences, which pickaxe assigns blame to older\n> commits and both of them seem to be sensible.\n\nRenames are naturally supported?\n\n> diff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt\n> new file mode 100644\n> index 0000000..7f30cdf\n> --- /dev/null\n> +++ b/Documentation/git-pickaxe.txt\n> @@ -0,0 +1,104 @@\n> +git-pickaxe(1)\n> +==============\n> +\n> +NAME\n> +----\n> +git-pickaxe - Show what revision and author last modified each line of a file\n[...]\n> +THE PORCELAIN FORMAT\n> +--------------------\n> +\n\nLet's quantify the output:\n---cut---\nThe porcelain format is as follows:\n\n<SHA-1> <orig line> <line> [<num lines>\nauthor <name>\nauthor-mail <email format>\nauthor-time <time>\nauthor-tz <TZ>\ncommitter <name>\ncommitter-mail <email format>\ncommitter-time <time>\ncommitter-tz <TZ>\nfilename <string>\nsummary <string>]\n<TAB><line data>\n\nWhere\n  <SHA-1> is the SHA-1 of the commit which introduces this line.\n  <orig line> it the line number where this line is introduced.\n  <line> is the line number of the final file (at SHA-1 commit)\n\nThen, if <SHA-1> is different from the previous line's SHA-1 (if no\nprevious then always different), a header follows.  It starts by the\nnumber of lines that this <SHA-1> commit introduces, then on a new\nline, information about about the commit is printed for the following\nseveal lines, then newline, TAB, and the line data.\n\nIf, OTOH, <SHA-1> is the same as the previous line's <SHA-1> then the\nheader is not printed as indicated by brackets. Just newline,\nTAB, and the line data.\n---cut----\n\nThis kind of makes it slightly clearer.\n\nJunio, is it possible to also print the \"previous\" commit?\nI mean, is it tenable to print the commit such that\na \"git-diff C B -- A:file\" will give a diff of the block of lines\nwe're looking at?\n\nPicture:\n\nAnnotate A:file\nC B line 1\nX D line 2\nC B line 3\nF G line 4\n...\n\nCurrently we do not print \"C, X, F\", but only \"B, D, G\".\nSo in effect, <orig line> is the line of, wlg, \"C:file\"\nwhere line 1 was introduced (by commit B). I.e.\nthe \"parent\" commit of that commit, which doesn't always\nexist. (e.g. if the commit added the file)\n\nIs this tenable?  If it is not or if it is going to make\nit slow or ambiguous, lets forget about it.\n\n     Luben\n"},{"id":"28680","messageId":"20061012201007.2464.qmail@web31803.mail.mud.yahoo.com","threadId":"5898","inReplyTo":"7viripnyfh.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Luben Tuikov","fromEmail":"ltuikov@yahoo.com","sentAt":"2006-10-12T20:10:07Z","receivedAt":"2006-10-12T20:10:07Z","isPatch":true,"sender":{"key":"ltuikov@yahoo.com","avatar":null},"body":"--- Junio C Hamano <junkio@cox.net> wrote:\n> Side note: I do not intend to keep it named pickaxe -- only\n> while it is in \"pu\".\n\nYes, I was going to mention something about that, but hesitated\nand didn't.\n\nWhat name is a good name?  Reuse and old one?  \"blame\", \"annotate\"?\n\n   Luben\n"},{"id":"28698","messageId":"egmdkv$k33$1@sea.gmane.org","threadId":"5898","inReplyTo":"7v7iz5rk4b.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2006-10-12T21:55:49Z","receivedAt":"2006-10-12T21:55:49Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Junio C Hamano wrote:\n\n> Currently it does what git-blame does, but only faster.\n> \n> More importantly, its internal structure is designed to support\n> content movement (aka cut-and-paste) more easily by allowing\n> more than one paths to be taken from the same commit.\n> \n> Signed-off-by: Junio C Hamano <junkio@cox.net>\n> ---\n> \n> I really hate to do this immediately after writing obituary for\n> annotate, but I had a solid 24-hour to work on git, which is a\n> rare opportunity for me these days, so here it is.\n\nWhy not reuse git-annotate name? git-pickaxe doesn't do pickaxe...\n-- \nJakub Narebski\nWarsaw, Poland\nShadeHawk on #git\n"},{"id":"28693","messageId":"7vmz81kwh3.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"20061012193107.44651.qmail@web31805.mail.mud.yahoo.com","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-12T22:20:08Z","receivedAt":"2006-10-12T22:20:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luben Tuikov <ltuikov@yahoo.com> writes:\n\n> Junio, is it possible to also print the \"previous\" commit?\n> I mean, is it tenable to print the commit such that\n> a \"git-diff C B -- A:file\" will give a diff of the block of lines\n> we're looking at?\n\nThere is no single \"previous\" in general.  Which side of the\nmerge would you take?\n"},{"id":"28700","messageId":"20061012224727.GU20017@pasky.or.cz","threadId":"5898","inReplyTo":"egmdkv$k33$1@sea.gmane.org","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Petr Baudis","fromEmail":"pasky@suse.cz","sentAt":"2006-10-12T22:47:27Z","receivedAt":"2006-10-12T22:47:27Z","isPatch":true,"sender":{"key":"pasky@ucw.cz","avatar":"https://avatars.githubusercontent.com/u/18439?v=4"},"body":"Dear diary, on Thu, Oct 12, 2006 at 11:55:49PM CEST, I got a letter\nwhere Jakub Narebski <jnareb@gmail.com> said that...\n> Junio C Hamano wrote:\n> \n> > Currently it does what git-blame does, but only faster.\n> > \n> > More importantly, its internal structure is designed to support\n> > content movement (aka cut-and-paste) more easily by allowing\n> > more than one paths to be taken from the same commit.\n> > \n> > Signed-off-by: Junio C Hamano <junkio@cox.net>\n> > ---\n> > \n> > I really hate to do this immediately after writing obituary for\n> > annotate, but I had a solid 24-hour to work on git, which is a\n> > rare opportunity for me these days, so here it is.\n> \n> Why not reuse git-annotate name? git-pickaxe doesn't do pickaxe...\n\nI agree that git-pickaxe is wrong, and luckily Junio does too,\napparently.\n\nBut please, let's not go right back to the git-annotate / git-blame\nsituation. It's just confusing to have two tools that do the same thing,\nperhaps subtly differently. If it's gonna replace git-blame, it should\neither do that right away or live as git-blame2 for some time, but not\nplay any confusing games with the names.\n\n-- \n\t\t\t\tPetr \"Pasky\" Baudis\nStuff: http://pasky.or.cz/\n#!/bin/perl -sp0777i<X+d*lMLa^*lN%0]dsXx++lMlN/dsM0<j]dsj\n$/=unpack('H*',$_);$_=`echo 16dio\\U$k\"SK$/SM$n\\EsN0p[lN*1\nlK[d2%Sa2/d0$^Ixp\"|dc`;s/\\W//g;$_=pack('H*',/((..)*)$/)\n"},{"id":"28704","messageId":"7v8xjlksyz.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"20061012224727.GU20017@pasky.or.cz","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-12T23:35:48Z","receivedAt":"2006-10-12T23:35:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Petr Baudis <pasky@suse.cz> writes:\n\n> But please, let's not go right back to the git-annotate / git-blame\n> situation. It's just confusing to have two tools that do the same thing,\n> perhaps subtly differently. If it's gonna replace git-blame, it should\n> either do that right away or live as git-blame2 for some time, but not\n> play any confusing games with the names.\n\nActually the plan is to make it do _true_ pickaxe, although it\nwill most likely end up either in dustbin or replace blame.\n"},{"id":"28705","messageId":"7vzmc1jcz0.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"20061012193107.44651.qmail@web31805.mail.mud.yahoo.com","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-13T00:06:43Z","receivedAt":"2006-10-13T00:06:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luben Tuikov <ltuikov@yahoo.com> writes:\n\n> Renames are naturally supported?\n\nDidn't I say you can see it yourself by doing this and that?  \n\nA short answer is yes.  I'll do a re-write-up of algorithm\noverview and directions to enhance it in a separate message when\nI have time.\n\nOne interesting difference is that it can do partial file (line\nrange).  With the \"parameter order\" fix Linus asked, I can do:\n\n$ git-pickaxe -L 551,554 master commit.c\n\nwhich would run a lot faster than:\n\n$ git-pickaxe -L 551,560 master commit.c\n\nThis is a made-up example, but you would get the idea.  In\nshort, it does not have to dig deeper outside the range\noriginally you specified.\n\n> ---cut---\n> The porcelain format is as follows:\n>\n> <SHA-1> <orig line> <line> [<num lines>\n\nThis is misleading.  <num lines> is always shown for the group\nhead, even if there was another group earlier from the same\ncommit (otherwise the Porcelain has to buffer the chunk, which\nyou did not like).  Second and subsequent lines in the same\ngroup do not have <num lines>.\n\n> author <name>\n> author-mail <email format>\n>...\n> committer-tz <TZ>\n> filename <string>\n> summary <string>]\n> <TAB><line data>\n>\n> Where\n>   <SHA-1> is the SHA-1 of the commit which introduces this line.\n>   <orig line> it the line number where this line is introduced.\n>   <line> is the line number of the final file (at SHA-1 commit)\n>\n> Then, if <SHA-1> is different from the previous line's SHA-1 (if no\n> previous then always different), a header follows.  It starts by the\n> number of lines that this <SHA-1> commit introduces,...\n\nSo this description is wrong; <num lines> is not part of the\n\"extra\".\n\n>...\n> ---cut----\n>\n> This kind of makes it slightly clearer.\n\nI deliberately left it vague so that we can add things later to\nthe header.  Porcelains should ignore the fields that they do\nnot understand, and should not expect these fields listed above\ncome in the order the above list shows.\n\nAlso I deliberately left it vague so that Porcelains can get the\nheader for the same SHA-1 more than once.  This is needed when\nwe add \"ciff\" to pick more than one paths from the same commit.\nIn such a case, most likely we are better off not to repeat\nheader fields from author...committer-tz and summary but we\nwould need filename.  The expectation to the Porcelains is:\n\n   Read one line, which begins with commit object name and two\n   or three numbers; if it has three numbers, it is the\n   beginning of a group.  Any such \"object name\" line can be\n   followed by a header.  Read subsequent lines until you get a\n   line that begins with a <TAB>.\n\n   - do not get upset if you see a header field that you do not\n     understand; you are reading from newer version of blame.\n\n   - do not get upset if you see a header for the commit you\n     have already seen.  Some of the header fields can be\n     updated from the one you have seen the last time, so use\n     the updated value from the new header.\n\n   Lines that begin with a <TAB> is data from the file being\n   annotated.  The object name and headers you saw before this\n   line annotate it.\n\nSo Porcelains need to keep one set of these header fields per\ncommit object.\n"},{"id":"28709","messageId":"20061013010402.99837.qmail@web31814.mail.mud.yahoo.com","threadId":"5898","inReplyTo":"7vzmc1jcz0.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Luben Tuikov","fromEmail":"ltuikov@yahoo.com","sentAt":"2006-10-13T01:04:02Z","receivedAt":"2006-10-13T01:04:02Z","isPatch":true,"sender":{"key":"ltuikov@yahoo.com","avatar":null},"body":"--- Junio C Hamano <junkio@cox.net> wrote:\n> > ---cut---\n> > The porcelain format is as follows:\n> >\n> > <SHA-1> <orig line> <line> [<num lines>\n> \n> This is misleading.  <num lines> is always shown for the group\n> head, even if there was another group earlier from the same\n> commit (otherwise the Porcelain has to buffer the chunk, which\n\nCompletely understood and always agreed upon.\n\n> you did not like).  Second and subsequent lines in the same\n> group do not have <num lines>.\n\nCompletely understood and always agreed upon.\n\n> \n> > author <name>\n> > author-mail <email format>\n> >...\n> > committer-tz <TZ>\n> > filename <string>\n> > summary <string>]\n> > <TAB><line data>\n> >\n> > Where\n> >   <SHA-1> is the SHA-1 of the commit which introduces this line.\n> >   <orig line> it the line number where this line is introduced.\n> >   <line> is the line number of the final file (at SHA-1 commit)\n> >\n> > Then, if <SHA-1> is different from the previous line's SHA-1 (if no\n> > previous then always different), a header follows.  It starts by the\n> > number of lines that this <SHA-1> commit introduces,...\n> \n> So this description is wrong; <num lines> is not part of the\n> \"extra\".\n\nSo, it is possible to print\n\n<SHA-1> <orig lineno> <this lineno> <num lines>\nTAB<data line>\n\n?\n\n> I deliberately left it vague so that we can add things later to\n> the header.  Porcelains should ignore the fields that they do\n> not understand, and should not expect these fields listed above\n> come in the order the above list shows.\n\nIf the format changes, then porcelains should change their parsing\nalgorithms.\n\n> Also I deliberately left it vague so that Porcelains can get the\n> header for the same SHA-1 more than once.  This is needed when\n\nFor different blocks separated by at least one different commit, yes.\nBut the opposite should not be true.\n\nThat is, you start a group, only when the previous commit differs\nfrom this commit.\n\nIt would be prudent to print a <num lines> iff a group is started.\n\n> we add \"ciff\" to pick more than one paths from the same commit.\n> In such a case, most likely we are better off not to repeat\n> header fields from author...committer-tz and summary but we\n> would need filename.  The expectation to the Porcelains is:\n> \n>    Read one line, which begins with commit object name and two\n>    or three numbers; if it has three numbers, it is the\n>    beginning of a group.\n\nAnd if it doesn't have three numbers?\n\n    Luben\n"},{"id":"28711","messageId":"7vmz81dm3v.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"20061013010402.99837.qmail@web31814.mail.mud.yahoo.com","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-13T01:45:56Z","receivedAt":"2006-10-13T01:45:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luben Tuikov <ltuikov@yahoo.com> writes:\n\n> That is, you start a group, only when the previous commit differs\n> from this commit.\n>\n> It would be prudent to print a <num lines> iff a group is started.\n\nYes.  I think the current code does that.\n\n>> we add \"ciff\" to pick more than one paths from the same commit.\n>> In such a case, most likely we are better off not to repeat\n>> header fields from author...committer-tz and summary but we\n>> would need filename.  The expectation to the Porcelains is:\n>> \n>>    Read one line, which begins with commit object name and two\n>>    or three numbers; if it has three numbers, it is the\n>>    beginning of a group.\n>\n> And if it doesn't have three numbers?\n\nIt can still have header if we wanted to say some more details\nabout the line.  What's the problem?\n"},{"id":"28714","messageId":"7vbqogeq20.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"Pine.LNX.4.64.0610120854440.3952@g5.osdl.org","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-13T05:35:19Z","receivedAt":"2006-10-13T05:35:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@osdl.org> writes:\n\n> On Thu, 12 Oct 2006, Junio C Hamano wrote:\n>> +\n>> +SYNOPSIS\n>> +--------\n>> +'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [--] <file> [<rev>]\n>\n> Btw, could we please get rid of this horrible command line syntax.\n>\n> Pretty much _every_ other git command takes the form\n>\n> \tgit cmd [<rev>] [--] <path>\n>\n> but for some reason annotate and blame (and now pickaxe) do it the wrong \n> way around, and do\n>\n> \tgit cmd [--] <path> [<rev>]\n>\n> which is just irritating to somebody who has grown very used to being able \n> to specify revisions first.\n>\n> (I'd actually also like to have a range-modifier, so that I could do\n>\n> \tgit annotate --since=2.weeks.ago v2.6.18.. <path>\n>\n> that didn't go back beyond a certain point, so the command line syntax \n> actually _does_ matter - even if we don't support that now, having the \n> regular command line syntax means that we -could- support it some day).\n>\n> \t\tLinus\n\nSo here it is.  I did not think \"-n 20\" would make sense for\nthis application so the code does not do anything about it.\n\nThis will be parked on \"pu\" for now.\n\n-- >8 --\n[PATCH] git-pickaxe: minimally use revision machinery.\n\nThis rewrites pickaxe argument parser to use revision machinery,\nso that you can say something like:\n\n\tgit pickaxe --since=2.weeks master commit.c\n\tgit pickaxe -L 20,40 v1.4.0..master -- commit.c\n\nThe traditional parameter order (path first then optional rev)\nis supported but cannot be mixed with revision limiting\nparameters.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n Documentation/git-pickaxe.txt |    2 +-\n builtin-pickaxe.c             |  221 ++++++++++++++++++++++++++++++++---------\n 2 files changed, 173 insertions(+), 50 deletions(-)\n\ndiff --git a/Documentation/git-pickaxe.txt b/Documentation/git-pickaxe.txt\nindex 7f30cdf..7685bd0 100644\n--- a/Documentation/git-pickaxe.txt\n+++ b/Documentation/git-pickaxe.txt\n@@ -7,7 +7,7 @@ git-pickaxe - Show what revision and aut\n \n SYNOPSIS\n --------\n-'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [--] <file> [<rev>]\n+'git-pickaxe' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [<rev>] [--] <file>\n \n DESCRIPTION\n -----------\ndiff --git a/builtin-pickaxe.c b/builtin-pickaxe.c\nindex 0e26308..0aa4565 100644\n--- a/builtin-pickaxe.c\n+++ b/builtin-pickaxe.c\n@@ -11,13 +11,14 @@ #include \"commit.h\"\n #include \"tree-walk.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"revision.h\"\n #include \"xdiff-interface.h\"\n \n #include <time.h>\n #include <sys/time.h>\n \n static char pickaxe_usage[] =\n-\"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [--] file [commit]\\n\"\n+\"git-pickaxe [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [commit] [--] file\\n\"\n \"  -c, --compatibility Use the same output mode as git-annotate (Default: off)\\n\"\n \"  -l, --long          Show long commit SHA1 (Default: off)\\n\"\n \"  -t, --time          Show raw timestamp (Default: off)\\n\"\n@@ -109,7 +110,8 @@ static void coalesce(struct scoreboard *\n \tfor (o = 0, i = 0; i < num; i++) {\n \t\tif (o &&\n \t\t    ent[o-1]->suspect == ent[i]->suspect &&\n-\t\t    ent[o-1]->s_lno + ent[o-1]->num_lines == ent[i]->s_lno) {\n+\t\t    ent[o-1]->s_lno + ent[o-1]->num_lines == ent[i]->s_lno &&\n+\t\t    ent[o-1]->guilty == ent[i]->guilty) {\n \t\t\tent[o-1]->num_lines += ent[i]->num_lines;\n \t\t\tfree(ent[i]);\n \t\t\tcontinue;\n@@ -220,7 +222,8 @@ static void process_u0_diff(void *state_\n \tstruct chunk *chunk;\n \tint off1, off2, len1, len2;\n \n-if(DEBUG) fprintf(stderr, \"%.*s\", (int) len, line);\n+\tif (DEBUG)\n+\t\tfprintf(stderr, \"%.*s\", (int) len, line);\n \n \tif (len < 4 || line[0] != '@' || line[1] != '@')\n \t\treturn;\n@@ -256,17 +259,23 @@ static struct patch *get_patch(struct or\n \tmmfile_t file_p, file_o;\n \txdemitcb_t ecb;\n \tchar type[10];\n-\n-if(DEBUG) fprintf(stderr, \"get patch %.8s %.8s\\n\",\n-\t\tsha1_to_hex(parent->commit->object.sha1),\n-\t\tsha1_to_hex(origin->commit->object.sha1));\n-\n-\tfile_p.ptr = read_sha1_file(parent->blob_sha1, type,\n-\t\t\t\t    (unsigned long *) &file_p.size);\n-\tfile_o.ptr = read_sha1_file(origin->blob_sha1, type,\n-\t\t\t\t    (unsigned long *) &file_o.size);\n-\tif (!file_p.ptr || !file_o.ptr)\n+\tchar *blob_p, *blob_o;\n+\n+\tif (DEBUG) fprintf(stderr, \"get patch %.8s %.8s\\n\",\n+\t\t\t   sha1_to_hex(parent->commit->object.sha1),\n+\t\t\t   sha1_to_hex(origin->commit->object.sha1));\n+\n+\tblob_p = read_sha1_file(parent->blob_sha1, type,\n+\t\t\t\t(unsigned long *) &file_p.size);\n+\tblob_o = read_sha1_file(origin->blob_sha1, type,\n+\t\t\t\t(unsigned long *) &file_o.size);\n+\tfile_p.ptr = blob_p;\n+\tfile_o.ptr = blob_o;\n+\tif (!file_p.ptr || !file_o.ptr) {\n+\t\tfree(blob_p);\n+\t\tfree(blob_o);\n \t\treturn NULL;\n+\t}\n \n \txpp.flags = XDF_NEED_MINIMAL;\n \txecfg.ctxlen = 0;\n@@ -280,6 +289,8 @@ if(DEBUG) fprintf(stderr, \"get patch %.8\n \tstate.ret->num = 0;\n \n \txdl_diff(&file_p, &file_o, &xpp, &xecfg, &ecb);\n+\tfree(blob_p);\n+\tfree(blob_o);\n \treturn state.ret;\n }\n \n@@ -417,11 +428,12 @@ static int pass_blame_to_parent(struct s\n \tfor (i = 0; i < patch->num; i++) {\n \t\tstruct chunk *chunk = &patch->chunks[i];\n \n-if(DEBUG)\tfprintf(stderr,\n-\t\t\t\"plno = %d, tlno = %d, \"\n-\t\t\t\"same as parent up to %d, resync %d and %d\\n\",\n-\t\t\tplno, tlno,\n-\t\t\tchunk->same, chunk->p_next, chunk->t_next);\n+\t\tif (DEBUG)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t\"plno = %d, tlno = %d, \"\n+\t\t\t\t\"same as parent up to %d, resync %d and %d\\n\",\n+\t\t\t\tplno, tlno,\n+\t\t\t\tchunk->same, chunk->p_next, chunk->t_next);\n \t\tblame_chunk(sb, tlno, plno, chunk->same, target, parent);\n \t\tplno = chunk->p_next;\n \t\ttlno = chunk->t_next;\n@@ -434,7 +446,8 @@ if(DEBUG)\tfprintf(stderr,\n }\n \n #define MAXPARENT 16\n-static void pass_blame(struct scoreboard *sb, struct origin *origin)\n+static void pass_blame(struct scoreboard *sb, struct origin *origin,\n+\t\t       struct rev_info *revs)\n {\n \tint i, parent_ix;\n \tstruct commit *commit = origin->commit;\n@@ -448,8 +461,15 @@ static void pass_blame(struct scoreboard\n \tfor (parent_ix = 0, parent = commit->parents;\n \t     parent_ix < MAXPARENT && parent;\n \t     parent = parent->next, parent_ix++) {\n-\t\tif (parse_commit(parent->item))\n+\t\tstruct commit *p = parent->item;\n+\n+\t\tif (parse_commit(p))\n+\t\t\tcontinue;\n+\t\tif (p->object.flags & UNINTERESTING)\n \t\t\tcontinue;\n+\t\tif (revs->max_age != -1 && p->date  < revs->max_age)\n+\t\t\tcontinue;\n+\n \t\tporigin = find_origin(sb, parent->item, origin->path);\n \t\tif (!porigin)\n \t\t\tporigin = find_rename(sb, parent->item, origin);\n@@ -485,14 +505,9 @@ static void pass_blame(struct scoreboard\n \t * Optionally run \"ciff\" to find copies from parents' files here\n \t */\n \n-\t/* Take responsibility for the remaining entries */\n-\tfor (i = 0; i < sb->num_entries; i++)\n-\t\tif (!sb->entries[i]->guilty &&\n-\t\t    sb->entries[i]->suspect == origin)\n-\t\t\tsb->entries[i]->guilty = 1;\n }\n \n-static void assign_blame(struct scoreboard *sb)\n+static void assign_blame(struct scoreboard *sb, struct rev_info *revs)\n {\n \twhile (1) {\n \t\tint i;\n@@ -506,7 +521,13 @@ static void assign_blame(struct scoreboa\n \t\tif (!suspect)\n \t\t\treturn; /* all done */\n \n-\t\tpass_blame(sb, suspect);\n+\t\tpass_blame(sb, suspect, revs);\n+\n+\t\t/* Take responsibility for the remaining entries */\n+\t\tfor (i = 0; i < sb->num_entries; i++)\n+\t\t\tif (!sb->entries[i]->guilty &&\n+\t\t\t    sb->entries[i]->suspect == suspect)\n+\t\t\t\tsb->entries[i]->guilty = 1;\n \t}\n }\n \n@@ -829,23 +850,29 @@ static void find_alignment(struct scoreb\n \tmax_digits = lineno_width(longest_dst_lines);\n }\n \n+static int has_path_in_work_tree(const char *path)\n+{\n+\tstruct stat st;\n+\treturn !lstat(path, &st);\n+}\n+\n int cmd_pickaxe(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *path = argv[1];\n-\tunsigned char sha1[20];\n+\tstruct rev_info revs;\n+\tconst char *path;\n \tstruct scoreboard sb;\n \tstruct origin *o;\n \tstruct blame_entry *ent;\n-\tint i, seen_dashdash;\n+\tint i, seen_dashdash, unk;\n \tlong bottom, top, lno;\n \tint output_option = 0;\n \tconst char *revs_file = NULL;\n-\tconst char *final_commit_name = \"HEAD\";\n+\tconst char *final_commit_name = NULL;\n \tchar type[10];\n \n \tbottom = top = 0;\n \tseen_dashdash = 0;\n-\tfor (i = 1; i < argc; i++) {\n+\tfor (unk = i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (*arg != '-')\n \t\t\tbreak;\n@@ -887,28 +914,121 @@ int cmd_pickaxe(int argc, const char **a\n \t\t\tbreak;\n \t\t}\n \t\telse\n+\t\t\targv[unk++] = arg;\n+\t}\n+\n+\t/* We have collected options unknown to us in argv[1..unk]\n+\t * which are to be passed to revision machinery if we are\n+\t * going to do the \"bottom\" procesing.\n+\t *\n+\t * The remaining are:\n+\t *\n+\t * (1) if seen_dashdash, its either\n+\t *     \"-options -- <path>\" or\n+\t *     \"-options -- <path> <rev>\".\n+\t *     but the latter is allowed only if there is no\n+\t *     options that we passed to revision machinery.\n+\t *\n+\t * (2) otherwise, we may have \"--\" somewhere later and\n+\t *     might be looking at the first one of multiple 'rev'\n+\t *     parameters (e.g. \" master ^next ^maint -- path\").\n+\t *     See if there is a dashdash first, and give the\n+\t *     arguments before that to revision machinery.\n+\t *     After that there must be one 'path'.\n+\t *\n+\t * (3) otherwise, its one of the three:\n+\t *     \"-options <path> <rev>\"\n+\t *     \"-options <rev> <path>\"\n+\t *     \"-options <path>\"\n+\t *     but again the first one is allowed only if\n+\t *     there is no options that we passed to revision\n+\t *     machinery.\n+\t */\n+\n+\tif (seen_dashdash) {\n+\t\t/* (1) */\n+\t\tif (argc <= i)\n+\t\t\tusage(pickaxe_usage);\n+\t\tpath = argv[i];\n+\t\tif (i + 1 == argc - 1) {\n+\t\t\tif (unk != 1)\n+\t\t\t\tusage(pickaxe_usage);\n+\t\t\targv[unk++] = argv[i + 1];\n+\t\t}\n+\t\telse if (i + 1 != argc)\n+\t\t\t/* garbage at end */\n \t\t\tusage(pickaxe_usage);\n \t}\n+\telse {\n+\t\tint j;\n+\t\tfor (j = i; !seen_dashdash && j < argc; j++)\n+\t\t\tif (!strcmp(argv[j], \"--\"))\n+\t\t\t\tseen_dashdash = j;\n+\t\tif (seen_dashdash) {\n+\t\t\tif (seen_dashdash + 1 != argc - 1)\n+\t\t\t\tusage(pickaxe_usage);\n+\t\t\tpath = argv[seen_dashdash + 1];\n+\t\t\tfor (j = i; j < seen_dashdash; j++)\n+\t\t\t\targv[unk++] = argv[j];\n+\t\t}\n+\t\telse {\n+\t\t\t/* (3) */\n+\t\t\tpath = argv[i];\n+\t\t\tif (i + 1 == argc - 1) {\n+\t\t\t\tfinal_commit_name = argv[i + 1];\n+\n+\t\t\t\t/* if (unk == 1) we could be getting\n+\t\t\t\t * old-style\n+\t\t\t\t */\n+\t\t\t\tif (unk == 1 && !has_path_in_work_tree(path)) {\n+\t\t\t\t\tpath = argv[i + 1];\n+\t\t\t\t\tfinal_commit_name = argv[i];\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\telse if (i != argc - 1)\n+\t\t\t\tusage(pickaxe_usage); /* garbage at end */\n \n-\t/* argv[i] is filename, argv[i+1] if exists is the commit */\n-\tif (i >= argc)\n-\t\tusage(pickaxe_usage);\n-\tpath = argv[i++];\n-\tif (!seen_dashdash) {\n-\t\tstruct stat st;\n-\t\tif (lstat(path, &st))\n-\t\t\tdie(\"cannot stat path %s: %s\", path, strerror(errno));\n+\t\t\tif (!has_path_in_work_tree(path))\n+\t\t\t\tdie(\"cannot stat path %s: %s\",\n+\t\t\t\t    path, strerror(errno));\n+\t\t}\n \t}\n \n-\tif (i == argc - 1)\n-\t\tfinal_commit_name = argv[i];\n-\telse if (i != argc)\n-\t\tusage(pickaxe_usage);\n+\tif (final_commit_name)\n+\t\targv[unk++] = final_commit_name;\n \n+\t/* Now we got rev and path.  We do not want the path pruning\n+\t * but we may want \"bottom\" processing.\n+\t */\n+\targv[unk] = NULL;\n+\n+\tinit_revisions(&revs, NULL);\n+\tsetup_revisions(unk, argv, &revs, \"HEAD\");\n \tmemset(&sb, 0, sizeof(sb));\n-\tif (get_sha1(final_commit_name, sha1) ||\n-\t    !(sb.final = lookup_commit_reference(sha1)))\n-\t\tdie(\"no such commit %s\", final_commit_name);\n+\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\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+\t/* If we have bottom, this will mark the ancestors of the\n+\t * bottom commits we would reach while traversing as\n+\t * uninteresting.\n+\t */\n+\tprepare_revision_walk(&revs);\n+\n \to = find_origin(&sb, sb.final, path);\n \tif (!o)\n \t\tdie(\"no such path %s in %s\", path, final_commit_name);\n@@ -939,7 +1059,7 @@ int cmd_pickaxe(int argc, const char **a\n \t\tdie(\"reading graft file %s failed: %s\",\n \t\t    revs_file, strerror(errno));\n \n-\tassign_blame(&sb);\n+\tassign_blame(&sb, &revs);\n \n \tcoalesce(&sb);\n \n@@ -947,6 +1067,9 @@ int cmd_pickaxe(int argc, const char **a\n \t\tfind_alignment(&sb, &output_option);\n \n \toutput(&sb, output_option);\n-\n+\tfree((void *)sb.final_buf);\n+\tfor (i = 0; i < sb.num_entries; i++)\n+\t\tfree(sb.entries[i]);\n+\tfree(sb.entries);\n \treturn 0;\n }\n-- \n1.4.3.rc2.g51ca\n"},{"id":"28743","messageId":"7vd58wc5zs.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"7viripnyfh.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-13T20:31:35Z","receivedAt":"2006-10-13T20:31:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n>> (I'd actually also like to have a range-modifier, so that I could do\n>>\n>> \tgit annotate --since=2.weeks.ago v2.6.18.. <path>\n>>\n>> that didn't go back beyond a certain point,...\n>\n> I am not sure about revision bottom (v.2.6.18..) offhand, but\n> the age limit (--since=2.weeks) should be trivial.\n>\n> Inside pass_blame() while we iterate over the parents of the\n> suspect we are looking at, you can skip the parent if it is\n> older than the age limit, or an ancestor of revision bottom,\n> like this:\n>\n> --- l/builtin-pickaxe.c\n> +++ k/builtin-pickaxe.c\n> @@ -450,6 +450,12 @@ static void pass_blame(struct scoreboard\n>  \t     parent = parent->next, parent_ix++) {\n>  \t\tif (parse_commit(parent->item))\n>  \t\t\tcontinue;\n> +\n> +\t\tif (parent is older than age limit)\n> +\t\t\tcontinue;\n> +\t\tif (parent is an ancestor of revision bottom)\n> +\t\t\tcontinue;\n> +\n>  \t\tporigin = find_origin(sb, parent->item, origin->path);\n>  \t\tif (!porigin)\n>  \t\t\tporigin = find_rename(sb, parent->item, origin);\n>\n\nThis is not quite right.  The above code tries to avoid passing\nblames to the boundary revisions, so \"v2.6.18..\" makes every old\nline to be blamed on one revision after v2.6.18, which might be\ntechnically correct but not what we want.\n\nInstead, we should pass blame, and then prevent boundary\nrevisions to pass blame further down.  The code in \"pu\" today\nhas seen slight restructure of this part and this change should\nbe easier to implement there, something along the lines of...\n\n--- l/builtin-pickaxe.c\n+++ k/builtin-pickaxe.c\n@@ -450,8 +450,7 @@ static int pass_blame_to_parent(struct s\n }\n \n #define MAXPARENT 16\n-static void pass_blame(struct scoreboard *sb, struct origin *origin,\n-\t\t       struct rev_info *revs)\n+static void pass_blame(struct scoreboard *sb, struct origin *origin)\n {\n \tint i, parent_ix;\n \tstruct commit *commit = origin->commit;\n@@ -469,10 +468,6 @@ static void pass_blame(struct scoreboard\n \n \t\tif (parse_commit(p))\n \t\t\tcontinue;\n-\t\tif (p->object.flags & UNINTERESTING)\n-\t\t\tcontinue;\n-\t\tif (revs->max_age != -1 && p->date  < revs->max_age)\n-\t\t\tcontinue;\n \n \t\tporigin = find_origin(sb, parent->item, origin->path);\n \t\tif (!porigin)\n@@ -516,6 +511,7 @@ static void assign_blame(struct scoreboa\n \twhile (1) {\n \t\tint i;\n \t\tstruct origin *suspect = NULL;\n+\t\tstruct commit *commit;\n \n \t\t/* find one suspect to break down */\n \t\tfor (i = 0; !suspect && i < sb->num_entries; i++) {\n@@ -525,7 +521,10 @@ static void assign_blame(struct scoreboa\n \t\tif (!suspect)\n \t\t\treturn; /* all done */\n \n-\t\tpass_blame(sb, suspect, revs);\n+\t\tcommit = suspect->commit;\n+\t\tif (!(commit->object.flags & UNINTERESTING) &&\n+\t\t    !(revs->max_age != -1 && commit->date  < revs->max_age))\n+\t\t\tpass_blame(sb, suspect, revs);\n \n \t\t/* Take responsibility for the remaining entries */\n \t\tfor (i = 0; i < sb->num_entries; i++)\n"},{"id":"28744","messageId":"20061013205418.5087.qmail@web31803.mail.mud.yahoo.com","threadId":"5898","inReplyTo":"7vmz81kwh3.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Luben Tuikov","fromEmail":"ltuikov@yahoo.com","sentAt":"2006-10-13T20:54:18Z","receivedAt":"2006-10-13T20:54:18Z","isPatch":true,"sender":{"key":"ltuikov@yahoo.com","avatar":null},"body":"--- Junio C Hamano <junkio@cox.net> wrote:\n> Luben Tuikov <ltuikov@yahoo.com> writes:\n> \n> > Junio, is it possible to also print the \"previous\" commit?\n> > I mean, is it tenable to print the commit such that\n> > a \"git-diff C B -- A:file\" will give a diff of the block of lines\n> > we're looking at?\n> \n> There is no single \"previous\" in general.  Which side of the\n> merge would you take?\n\nThe parent commit.\n\nFor example,\nAnnotation/blame of A:File\nC 1 line 1\n  2 line 2\nD 3 line 3\nB 4 line 4\n\nThe parent commit of C, such that,\ngit-diff parent(C) C -- A:File\nwill give me the diff which introduced\nthe \"first block\", or more generally,\nall lines annotated with C.\n\nThen when I click on 1 or 2, I'd like to\nsee Annotation/blame of parent(C):File,\non the line number where the C \"block\" was\nintroduced.\n\n   Luben\n"},{"id":"28747","messageId":"7vy7rjc2vg.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"20061013205418.5087.qmail@web31803.mail.mud.yahoo.com","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-13T21:38:59Z","receivedAt":"2006-10-13T21:38:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luben Tuikov <ltuikov@yahoo.com> writes:\n\n> --- Junio C Hamano <junkio@cox.net> wrote:\n>> Luben Tuikov <ltuikov@yahoo.com> writes:\n>> \n>> > Junio, is it possible to also print the \"previous\" commit?\n>> > I mean, is it tenable to print the commit such that\n>> > a \"git-diff C B -- A:file\" will give a diff of the block of lines\n>> > we're looking at?\n>> \n>> There is no single \"previous\" in general.  Which side of the\n>> merge would you take?\n>\n> The parent commit.\n\nThere is no single \"the parent commit\" in general.  Which side\nof the merge would you take?\n\nAlso remeber, when we blame a line to a revision (unless we do\nnot limit the blame with v2.6.18.. and --since=2.weeks which\nonly git-pickaxe can do), the line is known to have been\nintroduced by _that_ commit.  If there were a corresponding line\nin \"the parent commit\" for that line, we would not have assigned\nthe blame to the commit, but the blame would have been passed\ndown to \"the parent commit\" already.\n"},{"id":"28749","messageId":"20061013215942.36919.qmail@web31806.mail.mud.yahoo.com","threadId":"5898","inReplyTo":"7vy7rjc2vg.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Luben Tuikov","fromEmail":"ltuikov@yahoo.com","sentAt":"2006-10-13T21:59:42Z","receivedAt":"2006-10-13T21:59:42Z","isPatch":true,"sender":{"key":"ltuikov@yahoo.com","avatar":null},"body":"--- Junio C Hamano <junkio@cox.net> wrote:\n> Luben Tuikov <ltuikov@yahoo.com> writes:\n> \n> > --- Junio C Hamano <junkio@cox.net> wrote:\n> >> Luben Tuikov <ltuikov@yahoo.com> writes:\n> >> \n> >> > Junio, is it possible to also print the \"previous\" commit?\n> >> > I mean, is it tenable to print the commit such that\n> >> > a \"git-diff C B -- A:file\" will give a diff of the block of lines\n> >> > we're looking at?\n> >> \n> >> There is no single \"previous\" in general.  Which side of the\n> >> merge would you take?\n> >\n> > The parent commit.\n> \n> There is no single \"the parent commit\" in general.  Which side\n> of the merge would you take?\n\nYes, I realise that...\n\nI guess I'm trying to get to a successful implementation of\nthe intention of commit 65910395c08e3dc4be685a9a9f60adfa61c89aa5\n(later reverted for a good reason).\n\nIt is ok if this is not possible.  After all, the absolutely\nunambiguous way is blame->commit->blame->commit->..., etc,\ndue to multiple parenting.\n\n> Also remeber, when we blame a line to a revision (unless we do\n> not limit the blame with v2.6.18.. and --since=2.weeks which\n> only git-pickaxe can do), the line is known to have been\n> introduced by _that_ commit.\n\nThat is what we want.\n\n(fully agree with your previous comment that we limit _after_\n placing blame on a commit...)\n\n> If there were a corresponding line\n> in \"the parent commit\" for that line, we would not have assigned\n> the blame to the commit, but the blame would have been passed\n> down to \"the parent commit\" already.\n\nIndeed.\n\n   Luben\n"},{"id":"28751","messageId":"7v8xjjbzjj.fsf@assigned-by-dhcp.cox.net","threadId":"5898","inReplyTo":"20061013215942.36919.qmail@web31806.mail.mud.yahoo.com","subject":"Re: [PATCH] git-pickaxe: blame rewritten.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-10-13T22:50:56Z","receivedAt":"2006-10-13T22:50:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Luben Tuikov <ltuikov@yahoo.com> writes:\n\n>> If there were a corresponding line\n>> in \"the parent commit\" for that line, we would not have assigned\n>> the blame to the commit, but the blame would have been passed\n>> down to \"the parent commit\" already.\n>\n> Indeed.\n\nSo is the topic retracted?\n\nActually, I kind of know what you want.  However, I just do not\nthink blame is the right place to do that.\n\nSuppose you have three lines with this history (for simplicity\nthe ancestry is linear, time flows from left to right):\n\n        Revisions\t0 1 2 3 4 5\n        line 1          a a a A A A\n        line 2          b b x y B B\n        line 3          c C C C C C\n\nand we dig from commit 5.  The three lines will get blamed to\ncommit 3, 4 and 1 respectively.\n\n\tlast change\tlineno\tdata\n\t3\t\t1\tA\n        4\t\t2\tB\n        1\t\t3\tC\n\nYou click lineno #2 and what you will see is this:\n\t\n        Revisions\t0 1 2 3 4\n        line 1          a a a A A\n        line 2          b b x y B\n        line 3          c C C C C\n\n\tlast change\tlineno\tdata\n\t3\t\t1\tA\n        4\t\t2\tB\n        1\t\t3\tC\n\nIn such a case, we already know the current commit we are\nlooking at (commit 4) is what introduced 'B', so there is no\nmore digging down from that line, but we would like to peel that\n'B' to reveal what was behind it (in this case, 'y').  Clicking\nline 1 would take you to commit 3 and that happens to show the\nline after the line that had 'A' had 'y', but clicking line 3\nwould take you to commit 1 and you would miss changes that had\n'x' or 'y' on the line before the line that had 'C'.  So \"click\nsurrounding ones to see how things changed\" is not quite the\nright answer to the question \"Ok, we know 'B' appeared in the\ncommit that got blamed.  What, if anything, was there instead\nbefore that?\"\n\nYou would need to diff commit 4 and commit 3 (after all, we know\n4 introduced 'B' on line 2 so we know rev 3 is already different\nand does not have 'B' there) and guess what's on that line, and\nif you have more than one parent for commit 4 you would need to\ndo that for each parent and show that in some human readable\nway.  This will be expensive but is a useful thing to do \"on\ndemand\".\n\nI think \"on demand\" is really the key word here.  We can spend\nextra cycles to find that out when we know we are interested in\nwhat, if any, was there where we have 'B' today.  Spending that\nextra cycle to all lines in the blamed file will end up wasted\nif we are using this for \"iteratively dig deeper\" in gitweb.\nThat is why I do not think blame is the right place to do this.\n\nWhat you can do to improve gitweb is to change the URL that each\nlineno on the blame page has, so that when it is pointing at the\ncurrent commit (i.e. the commit we are looking at is the one\nthat was blamed for the line), have it run an equivalent of\n\"git-diff-tree -m -p commit\", and guess what line that line that\ndid not exist in any of the parent (in our example, line 2 that\nhad 'B') is in the parent, and show 'git-blame' for that file in\nthat parent around that line.\n"}]}