{"thread":{"id":"24364","subject":"[PATCH v3 01/13] parse-options: stop when encounter a non-option","startedAt":"2010-07-11T06:18:49Z","lastAt":"2010-07-23T02:08:37Z","messageCount":39,"participants":["Bo Yang","Jakub Narebski","Junio C Hamano","Thomas Rast"],"isPatch":true,"patchVersion":3,"patchTotal":13},"messages":[{"id":"145294","messageId":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":null,"subject":"[PATCH v3 01/13] parse-options: stop when encounter a non-option","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:49Z","receivedAt":"2010-07-11T06:18:49Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"We support the syntax like:\n-L n1,m1 pathspec1 -L n2,m2 pathspec2.\n\nMake the parse-options API not stop when encounter a\nnon-option argument, report the status and go on parsing\nthe remain options.\n\nThanks-to: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n parse-options.c |    3 ++-\n parse-options.h |    3 ++-\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/parse-options.c b/parse-options.c\nindex 0fa79bc..cbb49d3 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -374,7 +374,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,\n \t\t\tif (parse_nodash_opt(ctx, arg, options) == 0)\n \t\t\t\tcontinue;\n \t\t\tif (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)\n-\t\t\t\tbreak;\n+\t\t\t\treturn PARSE_OPT_NON_OPTION;\n \t\t\tctx->out[ctx->cpidx++] = ctx->argv[0];\n \t\t\tcontinue;\n \t\t}\n@@ -456,6 +456,7 @@ int parse_options(int argc, const char **argv, const char *prefix,\n \tswitch (parse_options_step(&ctx, options, usagestr)) {\n \tcase PARSE_OPT_HELP:\n \t\texit(129);\n+\tcase PARSE_OPT_NON_OPTION:\n \tcase PARSE_OPT_DONE:\n \t\tbreak;\n \tdefault: /* PARSE_OPT_UNKNOWN */\ndiff --git a/parse-options.h b/parse-options.h\nindex 7435cdb..407697a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -161,7 +161,8 @@ extern NORETURN void usage_msg_opt(const char *msg,\n enum {\n \tPARSE_OPT_HELP = -1,\n \tPARSE_OPT_DONE,\n-\tPARSE_OPT_UNKNOWN\n+\tPARSE_OPT_NON_OPTION,\n+\tPARSE_OPT_UNKNOWN,\n };\n \n /*\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145296","messageId":"1278829141-11900-2-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 02/13] parse-options: add two helper functions","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:50Z","receivedAt":"2010-07-11T06:18:50Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"1. parse_options_current: get the current option/argument the API\n   now is dealing with;\n2. parse_options_next: make the API to deal with the next\n   option/argument.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n parse-options.c |   19 +++++++++++++++++++\n parse-options.h |    4 ++++\n 2 files changed, 23 insertions(+), 0 deletions(-)\n\ndiff --git a/parse-options.c b/parse-options.c\nindex cbb49d3..4266bde 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -439,6 +439,25 @@ unknown:\n \treturn PARSE_OPT_DONE;\n }\n \n+const char *parse_options_current(struct parse_opt_ctx_t *ctx)\n+{\n+\treturn ctx->argv[0];\n+}\n+\n+int parse_options_next(struct parse_opt_ctx_t *ctx, int retain)\n+{\n+\tif (ctx->argc <= 0)\n+\t\treturn -1;\n+\n+\tif (retain == 1)\n+\t\tctx->out[ctx->cpidx++] = ctx->argv[0];\n+\n+\tctx->argc--;\n+\tctx->argv++;\n+\n+\treturn 0;\n+}\n+\n int parse_options_end(struct parse_opt_ctx_t *ctx)\n {\n \tmemmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));\ndiff --git a/parse-options.h b/parse-options.h\nindex 407697a..4791baa 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -187,6 +187,10 @@ extern int parse_options_step(struct parse_opt_ctx_t *ctx,\n \t\t\t      const struct option *options,\n \t\t\t      const char * const usagestr[]);\n \n+extern const char *parse_options_current(struct parse_opt_ctx_t *ctx);\n+\n+extern int parse_options_next(struct parse_opt_ctx_t *ctx, int retain);\n+\n extern int parse_options_end(struct parse_opt_ctx_t *ctx);\n \n extern int parse_options_concat(struct option *dst, size_t, struct option *src);\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145297","messageId":"1278829141-11900-3-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 03/13] add the basic data structure for line level history","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:51Z","receivedAt":"2010-07-11T06:18:51Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"'struct diff_line_range' is the main data structure to store\nthe user interesting line range. There is one 'diff_line_range'\nfor each file, and there are multiple 'struct range' in each\n'diff_line_range'. In this way, we support multiple ranges.\n\nWithin 'struct range', there are multiple 'struct print_range'\nwhich represent a diff chunk.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n Makefile   |    2 +\n diffcore.h |    1 +\n line.c     |  456 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n line.h     |  122 ++++++++++++++++\n revision.c |    6 +\n revision.h |    8 +-\n 6 files changed, 593 insertions(+), 2 deletions(-)\n create mode 100644 line.c\n create mode 100644 line.h\n\ndiff --git a/Makefile b/Makefile\nindex 1fadd43..0183a21 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -496,6 +496,7 @@ LIB_H += grep.h\n LIB_H += hash.h\n LIB_H += help.h\n LIB_H += levenshtein.h\n+LIB_H += line.h\n LIB_H += list-objects.h\n LIB_H += ll-merge.h\n LIB_H += log-tree.h\n@@ -582,6 +583,7 @@ LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\n LIB_OBJS += levenshtein.o\n+LIB_OBJS += line.o\n LIB_OBJS += list-objects.o\n LIB_OBJS += ll-merge.o\n LIB_OBJS += lockfile.o\ndiff --git a/diffcore.h b/diffcore.h\nindex 491bea0..13d8e93 100644\n--- a/diffcore.h\n+++ b/diffcore.h\n@@ -23,6 +23,7 @@\n #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */\n \n struct userdiff_driver;\n+struct diff_options;\n \n struct diff_filespec {\n \tunsigned char sha1[20];\ndiff --git a/line.c b/line.c\nnew file mode 100644\nindex 0000000..8bda022\n--- /dev/null\n+++ b/line.c\n@@ -0,0 +1,456 @@\n+#include \"line.h\"\n+#include \"cache.h\"\n+#include \"tag.h\"\n+#include \"blob.h\"\n+#include \"tree.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"decorate.h\"\n+#include \"revision.h\"\n+#include \"xdiff-interface.h\"\n+#include \"strbuf.h\"\n+#include \"log-tree.h\"\n+\n+static void cleanup(struct diff_line_range *r)\n+{\n+\twhile (r) {\n+\t\tstruct diff_line_range *next = r->next;\n+\t\tDIFF_LINE_RANGE_CLEAR(r);\n+\t\tfree(r);\n+\t\tr = next;\n+\t}\n+}\n+\n+static struct object *verify_commit(struct rev_info *revs)\n+{\n+\tstruct object *commit = NULL;\n+\tconst char *name = NULL;\n+\tint i;\n+\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 (commit)\n+\t\t\tdie(\"More than one commit to dig from: %s and %s?\",\n+\t\t\t    revs->pending.objects[i].name, name);\n+\t\tcommit = obj;\n+\t\tname = revs->pending.objects[i].name;\n+\t}\n+\n+\tif (commit == NULL)\n+\t\tdie(\"No commit specified?\");\n+\n+\treturn commit;\n+}\n+\n+static void fill_blob_sha1(struct commit *commit, struct diff_line_range *r)\n+{\n+\tunsigned mode;\n+\tunsigned char sha1[20];\n+\n+\twhile (r) {\n+\t\tif (get_tree_entry(commit->object.sha1, r->spec->path,\n+\t\t\tsha1, &mode))\n+\t\t\tgoto error;\n+\t\tfill_filespec(r->spec, sha1, mode);\n+\t\tr = r->next;\n+\t}\n+\n+\treturn;\n+error:\n+\tdie(\"There is no path %s in the commit\", r->spec->path);\n+}\n+\n+static void fill_line_ends(struct diff_filespec *spec, long *lines,\n+\tunsigned long **line_ends)\n+{\n+\tint num = 0, size = 50;\n+\tlong cur = 0;\n+\tunsigned long *ends = NULL;\n+\tchar *data = NULL;\n+\n+\tif (diff_populate_filespec(spec, 0))\n+\t\tdie(\"Cannot read blob %s\", sha1_to_hex(spec->sha1));\n+\n+\tends = xmalloc(size * sizeof(int));\n+\tends[cur++] = -1;\n+\tdata = spec->data;\n+\twhile (num < spec->size) {\n+\t\tif (data[num] == '\\n' || num == spec->size - 1) {\n+\t\t\tALLOC_GROW(ends, (cur + 1), size);\n+\t\t\tends[cur++] = num;\n+\t\t}\n+\t\tnum++;\n+\t}\n+\n+\t/* shrink the array to fit the elements */\n+\tends = xrealloc(ends, cur * sizeof(int));\n+\t*lines = cur;\n+\t*line_ends = ends;\n+}\n+\n+static const char *nth_line(struct diff_filespec *spec, int line,\n+\t\tlong lines, unsigned long *line_ends)\n+{\n+\tassert(line < lines);\n+\tassert(spec && spec->data);\n+\n+\treturn (char *)spec->data + line_ends[line] + 1;\n+}\n+\n+/*\n+ * copied from blame.c, indeed, we can even to use this to test\n+ * whether line log works. :)\n+ */\n+static const char *parse_loc(const char *spec, struct diff_filespec *file,\n+\t\t\t     long lines, unsigned long *line_ends,\n+\t\t\t     long begin, long *ret)\n+{\n+\tchar *term;\n+\tconst char *line;\n+\tlong num;\n+\tint reg_error;\n+\tregex_t regexp;\n+\tregmatch_t match[1];\n+\n+\t/* Allow \"-L <something>,+20\" to mean starting at <something>\n+\t * for 20 lines, or \"-L <something>,-5\" for 5 lines ending at\n+\t * <something>.\n+\t */\n+\tif (1 < begin && (spec[0] == '+' || spec[0] == '-')) {\n+\t\tnum = strtol(spec + 1, &term, 10);\n+\t\tif (term != spec + 1) {\n+\t\t\tif (spec[0] == '-')\n+\t\t\t\tnum = 0 - num;\n+\t\t\tif (0 < num)\n+\t\t\t\t*ret = begin + num - 2;\n+\t\t\telse if (!num)\n+\t\t\t\t*ret = begin;\n+\t\t\telse\n+\t\t\t\t*ret = begin + num;\n+\t\t\treturn term;\n+\t\t}\n+\t\treturn spec;\n+\t}\n+\tnum = strtol(spec, &term, 10);\n+\tif (term != spec) {\n+\t\t*ret = num;\n+\t\treturn term;\n+\t}\n+\tif (spec[0] != '/')\n+\t\treturn spec;\n+\n+\t/* it could be a regexp of form /.../ */\n+\tfor (term = (char *) spec + 1; *term && *term != '/'; term++) {\n+\t\tif (*term == '\\\\')\n+\t\t\tterm++;\n+\t}\n+\tif (*term != '/')\n+\t\treturn spec;\n+\n+\t/* try [spec+1 .. term-1] as regexp */\n+\t*term = 0;\n+\tbegin--; /* input is in human terms */\n+\tline = nth_line(file, begin, lines, line_ends);\n+\n+\tif (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&\n+\t    !(reg_error = regexec(&regexp, line, 1, match, 0))) {\n+\t\tconst char *cp = line + match[0].rm_so;\n+\t\tconst char *nline;\n+\n+\t\twhile (begin++ < lines) {\n+\t\t\tnline = nth_line(file, begin, lines, line_ends);\n+\t\t\tif (line <= cp && cp < nline)\n+\t\t\t\tbreak;\n+\t\t\tline = nline;\n+\t\t}\n+\t\t*ret = begin;\n+\t\tregfree(&regexp);\n+\t\t*term++ = '/';\n+\t\treturn term;\n+\t}\n+\telse {\n+\t\tchar errbuf[1024];\n+\t\tregerror(reg_error, &regexp, errbuf, 1024);\n+\t\tdie(\"-L parameter '%s': %s\", spec + 1, errbuf);\n+\t}\n+}\n+\n+static void parse_range(long lines, unsigned long *line_ends,\n+\t\tstruct range *r, struct diff_filespec *spec)\n+{\n+\tconst char *term;\n+\n+\tterm = parse_loc(r->arg, spec, lines, line_ends, 1, &r->start);\n+\tif (*term == ',') {\n+\t\tterm = parse_loc(term + 1, spec, lines, line_ends,\n+\t\t\tr->start + 1, &r->end);\n+\t\tif (*term) {\n+\t\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n+\t\t}\n+\t}\n+\n+\tif (*term) {\n+\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n+\t}\n+\n+\tif (r->start > r->end) {\n+\t\tlong tmp = r->start;\n+\t\tr->start = r->end;\n+\t\tr->end = tmp;\n+\t}\n+\n+\tif (r->start < 1)\n+\t\tr->start = 1;\n+\tif (r->end >= lines)\n+\t\tr->end = lines - 1;\n+}\n+\n+static void parse_lines(struct commit *commit, struct diff_line_range *r)\n+{\n+\tint i;\n+\tstruct range *old_range = NULL;\n+\tlong lines = 0;\n+\tunsigned long *ends = NULL;\n+\n+\twhile (r) {\n+\t\tstruct diff_filespec *spec = r->spec;\n+\t\tint num = r->nr;\n+\t\tassert(spec);\n+\t\tfill_blob_sha1(commit, r);\n+\t\told_range = r->ranges;\n+\t\tr->ranges = NULL;\n+\t\tr->nr = r->alloc = 0;\n+\t\tfill_line_ends(spec, &lines, &ends);\n+\t\tfor (i = 0; i < num; i++) {\n+\t\t\tparse_range(lines, ends, old_range + i, spec);\n+\t\t\tdiff_line_range_insert(r, old_range[i].arg,\n+\t\t\t\told_range[i].start, old_range[i].end);\n+\t\t}\n+\n+\t\tfree(ends);\n+\t\tends = NULL;\n+\n+\t\tr = r->next;\n+\t\tfree(old_range);\n+\t}\n+}\n+\n+/*\n+ * Insert a new line range into a diff_line_range struct, and keep the\n+ * r->ranges sorted by their starting line number.\n+ */\n+struct range *diff_line_range_insert(struct diff_line_range *r, const char *arg,\n+\t\t\t\tint start, int end)\n+{\n+\tint i = 0;\n+\tstruct range *rs = r->ranges;\n+\tint left_extend = 0, right_extend = 0;\n+\n+\tassert(r != NULL);\n+\tassert(start <= end);\n+\n+\tif (r->nr == 0 || rs[r->nr - 1].end < start - 1) {\n+\t\tDIFF_LINE_RANGE_GROW(r);\n+\t\trs = r->ranges;\n+\t\tint num = r->nr - 1;\n+\t\trs[num].arg = arg;\n+\t\trs[num].start = start;\n+\t\trs[num].end = end;\n+\t\treturn rs + num;\n+\t}\n+\n+\tfor (; i < r->nr; i++) {\n+\t\tif (rs[i].end < start - 1)\n+\t\t\tcontinue;\n+\t\tif (rs[i].end == start - 1) {\n+\t\t\trs[i].end = end;\n+\t\t\tright_extend = 1;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tassert(rs[i].end > start - 1);\n+\t\tif (rs[i].start <= start) {\n+\t\t\tif (rs[i].end < end) {\n+\t\t\t\trs[i].end = end;\n+\t\t\t\tright_extend = 1;\n+\t\t\t}\n+\t\t\tgoto out;\n+\t\t} else if (rs[i].start <= end + 1) {\n+\t\t\trs[i].start = start;\n+\t\t\tleft_extend = 1;\n+\t\t\tif (rs[i].end < end) {\n+\t\t\t\trs[i].end = end;\n+\t\t\t\tright_extend = 1;\n+\t\t\t}\n+\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tint num = r->nr - i;\n+\t\t\tDIFF_LINE_RANGE_GROW(r);\n+\t\t\trs = r->ranges;\n+\t\t\tmemmove(rs + i + 1, rs + i, num * sizeof(struct range));\n+\t\t\trs[i].arg = arg;\n+\t\t\trs[i].start = start;\n+\t\t\trs[i].end = end;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\tassert(r->nr != i);\n+\tif (left_extend) {\n+\t\tint j = i;\n+\t\tfor (; j > -1; j--) {\n+\t\t\tif (rs[j].end >= rs[i].start - 1)\n+\t\t\t\tif (rs[j].start < rs[i].start)\n+\t\t\t\t\trs[i].start = rs[j].start;\n+\t\t}\n+\t\tmemmove(rs + j + 1, rs + i, (r->nr - i) * sizeof(struct range));\n+\t\tr->nr -= i - j - 1;\n+\t}\n+\tif (right_extend) {\n+\t\tint j = i;\n+\t\tfor (; j < r->nr; j++) {\n+\t\t\tif (rs[j].start <= rs[i].end + 1)\n+\t\t\t\tif (rs[j].end > rs[i].end)\n+\t\t\t\t\trs[i].end = rs[j].end;\n+\t\t}\n+\t\tif (j < r->nr) {\n+\t\t\tmemmove(rs + i + 1, rs + j, (r->nr - j) * sizeof(struct range));\n+\t\t}\n+\t\tr->nr -= j - i - 1;\n+\t}\n+\tassert(r->nr);\n+\n+\treturn rs + i;\n+}\n+\n+void diff_line_range_clear(struct diff_line_range *r)\n+{\n+\tint i = 0, zero = 0;\n+\n+\tfor (; i < r->nr; i++) {\n+\t\tstruct range *rg = r->ranges + i;\n+\t\tRANGE_CLEAR(rg);\n+\t}\n+\n+\tif (r->prev) {\n+\t\tzero = 0;\n+\t\tif (r->prev->count == 1) {\n+\t\t\tzero = 1;\n+\t\t}\n+\t\tfree_filespec(r->prev);\n+\t\tif (zero)\n+\t\t\tr->prev = NULL;\n+\t}\n+\tif (r->spec) {\n+\t\tzero = 0;\n+\t\tif (r->spec->count == 1) {\n+\t\t\tzero = 1;\n+\t\t}\n+\t\tfree_filespec(r->spec);\n+\t\tif (zero)\n+\t\t\tr->spec = NULL;\n+\t}\n+\n+\tr->status = '\\0';\n+\tr->alloc = r->nr = 0;\n+\n+\tif (r->ranges)\n+\t\tfree(r->ranges);\n+\tr->ranges = NULL;\n+\tr->next = NULL;\n+}\n+\n+void diff_line_range_append(struct diff_line_range *r, const char *arg)\n+{\n+\tDIFF_LINE_RANGE_GROW(r);\n+\tr->ranges[r->nr - 1].arg = arg;\n+}\n+\n+struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,\n+\tstruct diff_line_range *other)\n+{\n+\tstruct diff_line_range *one = out, *two = other;\n+\tstruct diff_line_range *pone;\n+\n+\twhile (one) {\n+\t\tstruct diff_line_range *ptwo;\n+\t\ttwo = other;\n+\t\tptwo = other;\n+\t\twhile (two) {\n+\t\t\tif (!strcmp(one->spec->path, two->spec->path)) {\n+\t\t\t\tint i = 0;\n+\t\t\t\tfor (; i < two->nr; i++) {\n+\t\t\t\t\tdiff_line_range_insert(one, NULL,\n+\t\t\t\t\t\ttwo->ranges[i].start,\n+\t\t\t\t\t\ttwo->ranges[i].end);\n+\t\t\t\t}\n+\t\t\t\tif (two == other) {\n+\t\t\t\t\tother = other->next;\n+\t\t\t\t} else {\n+\t\t\t\t\tptwo->next = two->next;\n+\t\t\t\t}\n+\t\t\t\tDIFF_LINE_RANGE_CLEAR(two);\n+\t\t\t\tfree(two);\n+\t\t\t\ttwo = NULL;\n+\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tptwo = two;\n+\t\t\ttwo = two->next;\n+\t\t}\n+\n+\t\tpone = one;\n+\t\tone = one->next;\n+\t}\n+\tpone->next = other;\n+\n+\treturn out;\n+}\n+\n+void add_line_range(struct rev_info *revs, struct commit *commit, struct diff_line_range *r)\n+{\n+\tstruct diff_line_range *ret = NULL;\n+\n+\tif (r != NULL) {\n+\t\tret = lookup_decoration(&revs->line_range, &commit->object);\n+\t\tif (ret != NULL) {\n+\t\t\tdiff_line_range_merge(ret, r);\n+\t\t} else {\n+\t\t\tadd_decoration(&revs->line_range, &commit->object, r);\n+\t\t}\n+\t\tcommit->object.flags |= RANGE_UPDATE;\n+\t}\n+}\n+\n+struct diff_line_range *lookup_line_range(struct rev_info *revs, struct commit *commit)\n+{\n+\tstruct diff_line_range *ret = NULL;\n+\n+\tret = lookup_decoration(&revs->line_range, &commit->object);\n+\treturn ret;\n+}\n+\n+void setup_line(struct rev_info *rev, struct diff_line_range *r)\n+{\n+\tstruct commit *commit = NULL;\n+\tstruct diff_options *opt = &rev->diffopt;\n+\n+\tcommit = (struct commit *)verify_commit(rev);\n+\tparse_lines(commit, r);\n+\n+\tadd_line_range(rev, commit, r);\n+\t/*\n+\t * Note we support -M/-C to detect file rename\n+\t */\n+\topt->nr_paths = 0;\n+\tdiff_tree_release_paths(opt);\n+}\n+\ndiff --git a/line.h b/line.h\nnew file mode 100644\nindex 0000000..a2f8545\n--- /dev/null\n+++ b/line.h\n@@ -0,0 +1,122 @@\n+#ifndef LINE_H\n+#define LINE_H\n+\n+#include \"diffcore.h\"\n+\n+struct rev_info;\n+struct commit;\n+struct diff_line_range;\n+struct diff_options;\n+\n+struct print_range {\n+\tint start, end;\n+\tint pstart, pend;\n+\tint line_added : 1;\n+};\n+\n+struct print_pair {\n+\tint alloc, nr;\n+\tstruct print_range *ranges;\n+};\n+\n+#define PRINT_RANGE_INIT(r) \\\n+\tdo { \\\n+\t\t(r)->line_added = 0; \\\n+\t} while(0);\n+\n+#define PRINT_PAIR_INIT(p) \\\n+\tdo { \\\n+\t\t(p)->alloc = (p)->nr = 0; \\\n+\t\t(p)->ranges = NULL; \\\n+\t} while(0);\n+\n+#define PRINT_PAIR_GROW(p) \\\n+\tdo { \\\n+\t\t(p)->nr ++; \\\n+\t\tALLOC_GROW((p)->ranges, (p)->nr, (p)->alloc); \\\n+\t} while (0);\n+\n+#define PRINT_PAIR_CLEAR(p) \\\n+\tdo { \\\n+\t\t(p)->alloc = (p)->nr = 0; \\\n+\t\tif ((p)->ranges) \\\n+\t\t\tfree((p)->ranges); \\\n+\t\t(p)->ranges = NULL; \\\n+\t} while(0);\n+\n+struct range {\n+\tconst char *arg;\t//The argument to specify this line range\n+\tlong start, end;\t//The start line number, inclusive\n+\tlong pstart, pend;\t//The end line number, inclusive\n+\tstruct print_pair pair;\n+\t\t\t//The changed lines inside this range\n+\tunsigned int diff:1;\n+};\n+\n+struct diff_line_range {\n+\tstruct diff_filespec *prev;\n+\tstruct diff_filespec *spec;\n+\tchar status;\n+\tint alloc;\n+\tint nr;\n+\tstruct range *ranges;\n+\tunsigned int\ttouch:1,\n+\t\t\tdiff:1;\n+\tstruct diff_line_range *next;\n+};\n+\n+#define RANGE_INIT(r) \\\n+\tdo { \\\n+\t\t(r)->arg = NULL; \\\n+\t\t(r)->start = (r)->end = 0; \\\n+\t\t(r)->pstart = (r)->pend = 0; \\\n+\t\tPRINT_PAIR_INIT(&((r)->pair)); \\\n+\t\t(r)->diff = 0; \\\n+\t} while (0);\n+\n+#define RANGE_CLEAR(r) \\\n+\tdo { \\\n+\t\t(r)->arg = NULL; \\\n+\t\t(r)->start = (r)->end = 0; \\\n+\t\t(r)->pstart = (r)->pend = 0; \\\n+\t\tPRINT_PAIR_CLEAR(&r->pair); \\\n+\t\t(r)->diff = 0; \\\n+\t} while(0);\n+\n+#define DIFF_LINE_RANGE_INIT(r) \\\n+\tdo { \\\n+\t\t(r)->prev = (r)->spec = NULL; \\\n+\t\t(r)->status = '\\0'; \\\n+\t\t(r)->alloc = (r)->nr = 0; \\\n+\t\t(r)->ranges = NULL; \\\n+\t\t(r)->next = NULL; \\\n+\t\t(r)->touch = 0; \\\n+\t\t(r)->diff = 0; \\\n+\t} while(0);\n+\n+#define DIFF_LINE_RANGE_GROW(r) \\\n+\tdo { \\\n+\t\t(r)->nr ++; \\\n+\t\tALLOC_GROW((r)->ranges, (r)->nr, (r)->alloc); \\\n+\t\tRANGE_INIT(((r)->ranges + (r)->nr - 1)); \\\n+\t} while(0);\n+\n+#define DIFF_LINE_RANGE_CLEAR(r) \\\n+\tdiff_line_range_clear((r));\n+\n+extern struct range *diff_line_range_insert(struct diff_line_range *r, const char *arg,\n+\t\t\t\tint start, int end);\n+extern void diff_line_range_append(struct diff_line_range *r, const char *arg);\n+\n+extern void diff_line_range_clear(struct diff_line_range *r);\n+\n+extern struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,\n+\tstruct diff_line_range *other);\n+\n+extern void setup_line(struct rev_info *rev, struct diff_line_range *r);\n+\n+extern void add_line_range(struct rev_info *revs, struct commit *commit, struct diff_line_range *r);\n+\n+extern struct diff_line_range *lookup_line_range(struct rev_info *revs, struct commit *commit);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 7e82efd..63f37ea 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1637,6 +1637,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->combine_merges)\n \t\trevs->ignore_merges = 0;\n \trevs->diffopt.abbrev = revs->abbrev;\n+\n+\tif (revs->line) {\n+\t\trevs->limited = 1;\n+\t\trevs->topo_order = 1;\n+\t}\n+\n \tif (diff_setup_done(&revs->diffopt) < 0)\n \t\tdie(\"diff_setup_done failed\");\n \ndiff --git a/revision.h b/revision.h\nindex 36fdf22..55836e0 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -14,7 +14,8 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define RANGE_UPDATE\t(1u<<9) /* for line level traverse */\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n@@ -68,7 +69,8 @@ struct rev_info {\n \t\t\tcherry_pick:1,\n \t\t\tbisect:1,\n \t\t\tancestry_path:1,\n-\t\t\tfirst_parent_only:1;\n+\t\t\tfirst_parent_only:1,\n+\t\t\tline:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n@@ -137,6 +139,8 @@ struct rev_info {\n \t/* commit counts */\n \tint count_left;\n \tint count_right;\n+\t/* line-level intersting range */\n+\tstruct decoration line_range;\n };\n \n #define REV_TREE_SAME\t\t0\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145298","messageId":"1278829141-11900-4-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 04/13] refactor parse_loc","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:52Z","receivedAt":"2010-07-11T06:18:52Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Both 'git blame -L' and 'git log -L' parse the same style\nof line number arguments, so put the 'parse_loc' function\nto line.c and export it.\nThe caller of parse_loc should provide a callback function\nwhich is used to calculate the nth line start position.\nOther parts such as regexp search, line number parsing are\nabstracted and re-used.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n builtin/blame.c |   89 +++++-------------------------------------------------\n line.c          |   35 ++++++++++++---------\n line.h          |    5 +++\n 3 files changed, 34 insertions(+), 95 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 01e62fd..17b71cd 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -21,6 +21,7 @@\n #include \"parse-options.h\"\n #include \"utf8.h\"\n #include \"userdiff.h\"\n+#include \"line.h\"\n \n static char blame_usage[] = \"git blame [options] [rev-opts] [rev] [--] file\";\n \n@@ -541,11 +542,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src)\n \tdst->score = 0;\n }\n \n-static const char *nth_line(struct scoreboard *sb, int lno)\n+static const char *nth_line(struct scoreboard *sb, long lno)\n {\n \treturn sb->final_buf + sb->lineno[lno];\n }\n \n+static const char *nth_line_cb(void *data, long lno)\n+{\n+\treturn nth_line((struct scoreboard *)data, lno);\n+}\n+\n /*\n  * It is known that lines between tlno to same came from parent, and e\n  * has an overlap with that range.  it also is known that parent's\n@@ -1907,83 +1913,6 @@ static const char *add_prefix(const char *prefix, const char *path)\n }\n \n /*\n- * Parsing of (comma separated) one item in the -L option\n- */\n-static const char *parse_loc(const char *spec,\n-\t\t\t     struct scoreboard *sb, long lno,\n-\t\t\t     long begin, long *ret)\n-{\n-\tchar *term;\n-\tconst char *line;\n-\tlong num;\n-\tint reg_error;\n-\tregex_t regexp;\n-\tregmatch_t match[1];\n-\n-\t/* Allow \"-L <something>,+20\" to mean starting at <something>\n-\t * for 20 lines, or \"-L <something>,-5\" for 5 lines ending at\n-\t * <something>.\n-\t */\n-\tif (1 < begin && (spec[0] == '+' || spec[0] == '-')) {\n-\t\tnum = strtol(spec + 1, &term, 10);\n-\t\tif (term != spec + 1) {\n-\t\t\tif (spec[0] == '-')\n-\t\t\t\tnum = 0 - num;\n-\t\t\tif (0 < num)\n-\t\t\t\t*ret = begin + num - 2;\n-\t\t\telse if (!num)\n-\t\t\t\t*ret = begin;\n-\t\t\telse\n-\t\t\t\t*ret = begin + num;\n-\t\t\treturn term;\n-\t\t}\n-\t\treturn spec;\n-\t}\n-\tnum = strtol(spec, &term, 10);\n-\tif (term != spec) {\n-\t\t*ret = num;\n-\t\treturn term;\n-\t}\n-\tif (spec[0] != '/')\n-\t\treturn spec;\n-\n-\t/* it could be a regexp of form /.../ */\n-\tfor (term = (char *) spec + 1; *term && *term != '/'; term++) {\n-\t\tif (*term == '\\\\')\n-\t\t\tterm++;\n-\t}\n-\tif (*term != '/')\n-\t\treturn spec;\n-\n-\t/* try [spec+1 .. term-1] as regexp */\n-\t*term = 0;\n-\tbegin--; /* input is in human terms */\n-\tline = nth_line(sb, begin);\n-\n-\tif (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&\n-\t    !(reg_error = regexec(&regexp, line, 1, match, 0))) {\n-\t\tconst char *cp = line + match[0].rm_so;\n-\t\tconst char *nline;\n-\n-\t\twhile (begin++ < lno) {\n-\t\t\tnline = nth_line(sb, begin);\n-\t\t\tif (line <= cp && cp < nline)\n-\t\t\t\tbreak;\n-\t\t\tline = nline;\n-\t\t}\n-\t\t*ret = begin;\n-\t\tregfree(&regexp);\n-\t\t*term++ = '/';\n-\t\treturn term;\n-\t}\n-\telse {\n-\t\tchar errbuf[1024];\n-\t\tregerror(reg_error, &regexp, errbuf, 1024);\n-\t\tdie(\"-L parameter '%s': %s\", spec + 1, errbuf);\n-\t}\n-}\n-\n-/*\n  * Parsing of -L option\n  */\n static void prepare_blame_range(struct scoreboard *sb,\n@@ -1993,9 +1922,9 @@ static void prepare_blame_range(struct scoreboard *sb,\n {\n \tconst char *term;\n \n-\tterm = parse_loc(bottomtop, sb, lno, 1, bottom);\n+\tterm = parse_loc(bottomtop, nth_line_cb, sb, lno, 1, bottom);\n \tif (*term == ',') {\n-\t\tterm = parse_loc(term + 1, sb, lno, *bottom + 1, top);\n+\t\tterm = parse_loc(term + 1, nth_line_cb, sb, lno, *bottom + 1, top);\n \t\tif (*term)\n \t\t\tusage(blame_usage);\n \t}\ndiff --git a/line.c b/line.c\nindex 8bda022..a6bcbdf 100644\n--- a/line.c\n+++ b/line.c\n@@ -94,22 +94,26 @@ static void fill_line_ends(struct diff_filespec *spec, long *lines,\n \t*line_ends = ends;\n }\n \n-static const char *nth_line(struct diff_filespec *spec, int line,\n-\t\tlong lines, unsigned long *line_ends)\n+struct nth_line_cb {\n+\tstruct diff_filespec *spec;\n+\tlong lines;\n+\tunsigned long *line_ends;\n+};\n+\n+static const char *nth_line(void *data, long line)\n {\n-\tassert(line < lines);\n-\tassert(spec && spec->data);\n+\tstruct nth_line_cb *d = data;\n+\tassert(d && line < d->lines);\n+\tassert(d->spec && d->spec->data);\n \n-\treturn (char *)spec->data + line_ends[line] + 1;\n+\treturn (char *)d->spec->data + d->line_ends[line] + 1;\n }\n \n /*\n- * copied from blame.c, indeed, we can even to use this to test\n- * whether line log works. :)\n+ * Parsing of (comma separated) one item in the -L option\n  */\n-static const char *parse_loc(const char *spec, struct diff_filespec *file,\n-\t\t\t     long lines, unsigned long *line_ends,\n-\t\t\t     long begin, long *ret)\n+const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n+\t\tvoid *data, long lines, long begin, long *ret)\n {\n \tchar *term;\n \tconst char *line;\n@@ -156,7 +160,7 @@ static const char *parse_loc(const char *spec, struct diff_filespec *file,\n \t/* try [spec+1 .. term-1] as regexp */\n \t*term = 0;\n \tbegin--; /* input is in human terms */\n-\tline = nth_line(file, begin, lines, line_ends);\n+\tline = nth_line(data, begin);\n \n \tif (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&\n \t    !(reg_error = regexec(&regexp, line, 1, match, 0))) {\n@@ -164,7 +168,7 @@ static const char *parse_loc(const char *spec, struct diff_filespec *file,\n \t\tconst char *nline;\n \n \t\twhile (begin++ < lines) {\n-\t\t\tnline = nth_line(file, begin, lines, line_ends);\n+\t\t\tnline = nth_line(data, begin);\n \t\t\tif (line <= cp && cp < nline)\n \t\t\t\tbreak;\n \t\t\tline = nline;\n@@ -185,11 +189,12 @@ static void parse_range(long lines, unsigned long *line_ends,\n \t\tstruct range *r, struct diff_filespec *spec)\n {\n \tconst char *term;\n+\tstruct nth_line_cb data = {spec, lines, line_ends};\n \n-\tterm = parse_loc(r->arg, spec, lines, line_ends, 1, &r->start);\n+\tterm = parse_loc(r->arg, nth_line, &data, lines, 1, &r->start);\n \tif (*term == ',') {\n-\t\tterm = parse_loc(term + 1, spec, lines, line_ends,\n-\t\t\tr->start + 1, &r->end);\n+\t\tterm = parse_loc(term + 1, nth_line, &data, lines,\n+\t\t\t\tr->start + 1, &r->end);\n \t\tif (*term) {\n \t\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n \t\t}\ndiff --git a/line.h b/line.h\nindex a2f8545..0eb03bd 100644\n--- a/line.h\n+++ b/line.h\n@@ -8,6 +8,8 @@ struct commit;\n struct diff_line_range;\n struct diff_options;\n \n+typedef const char *(*nth_line_fn_t)(void *data, long lno);\n+\n struct print_range {\n \tint start, end;\n \tint pstart, pend;\n@@ -119,4 +121,7 @@ extern void add_line_range(struct rev_info *revs, struct commit *commit, struct\n \n extern struct diff_line_range *lookup_line_range(struct rev_info *revs, struct commit *commit);\n \n+const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n+\t\tvoid *data, long lines, long begin, long *ret);\n+\n #endif\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145295","messageId":"1278829141-11900-5-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 05/13] parse the -L options","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:53Z","receivedAt":"2010-07-11T06:18:53Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"With the two new APIs of parse options added in the previous\ncommit, we parse the multiple '-L n,m <file>' syntax.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n builtin/log.c |  102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 files changed, 101 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 08b8722..1e90b03 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -19,6 +19,7 @@\n #include \"remote.h\"\n #include \"string-list.h\"\n #include \"parse-options.h\"\n+#include \"line.h\"\n \n /* Set a default date-time format for git log (\"log.date\" config variable) */\n static const char *default_date_mode = NULL;\n@@ -27,11 +28,24 @@ static int default_show_root = 1;\n static int decoration_style;\n static const char *fmt_patch_subject_prefix = \"PATCH\";\n static const char *fmt_pretty;\n+static const char *dashdash = \"--\";\n \n-static const char * const builtin_log_usage =\n+static char builtin_log_usage[] =\n \t\"git log [<options>] [<since>..<until>] [[--] <path>...]\\n\"\n+\t\"git log [<options>] -L n,m <path>\\n\"\n \t\"   or: git show [options] <object>...\";\n \n+static const char *log_opt_usage[] = {\n+\tbuiltin_log_usage,\n+\tNULL\n+};\n+\n+struct line_opt_callback_data {\n+\tstruct diff_line_range **range;\n+\tstruct parse_opt_ctx_t *ctx;\n+\tstruct rev_info *rev;\n+};\n+\n static int parse_decoration_style(const char *var, const char *value)\n {\n \tswitch (git_config_maybe_bool(var, value)) {\n@@ -49,12 +63,41 @@ static int parse_decoration_style(const char *var, const char *value)\n \treturn -1;\n }\n \n+static int log_line_range_callback(const struct option *option, const char *arg, int unset)\n+{\n+\tstruct line_opt_callback_data *data = option->value;\n+\tstruct diff_line_range *r = *data->range;\n+\tstruct parse_opt_ctx_t *ctx = data->ctx;\n+\tif (!arg)\n+\t\treturn -1;\n+\n+\tif (r->nr == 0 && r->next == NULL) {\n+\t\tctx->out[ctx->cpidx++] = dashdash;\n+\t}\n+\n+\tdiff_line_range_append(r, arg);\n+\tdata->rev->line = 1;\n+\treturn 0;\n+}\n+\n static void cmd_log_init(int argc, const char **argv, const char *prefix,\n \t\t\t struct rev_info *rev, struct setup_revision_opt *opt)\n {\n \tint i;\n \tint decoration_given = 0;\n \tstruct userformat_want w;\n+\tconst char *path = NULL, *pathspec = NULL;\n+\tstatic struct diff_line_range *range = NULL;\n+\tstatic struct parse_opt_ctx_t ctx;\n+\tstatic struct line_opt_callback_data line_cb = {&range, &ctx, NULL};\n+\tstatic const struct option options[] = {\n+\t\tOPT_CALLBACK('L', NULL, &line_cb, \"n,m\", \"Process only line range n,m, counting from 1\", log_line_range_callback),\n+\t\tOPT_END()\n+\t};\n+\n+\tline_cb.rev = rev;\n+\trange = xmalloc(sizeof(*range));\n+\tDIFF_LINE_RANGE_INIT(range);\n \n \trev->abbrev = DEFAULT_ABBREV;\n \trev->commit_format = CMIT_FMT_DEFAULT;\n@@ -75,6 +118,58 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,\n \t */\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage(builtin_log_usage);\n+\n+\tparse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |\n+\t\t\tPARSE_OPT_KEEP_ARGV0 | PARSE_OPT_STOP_AT_NON_OPTION);\n+\tfor (;;) {\n+\t\tswitch (parse_options_step(&ctx, options, log_opt_usage)) {\n+\t\tcase PARSE_OPT_HELP:\n+\t\t\texit(129);\n+\t\tcase PARSE_OPT_DONE:\n+\t\t\tgoto parse_done;\n+\t\tcase PARSE_OPT_NON_OPTION:\n+\t\t\tpath = parse_options_current(&ctx);\n+\t\t\tpathspec = prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n+\t\t\trange->spec = alloc_filespec(pathspec);\n+\t\t\tfree((void *)pathspec);\n+\t\t\tif (range->nr == 0) {\n+\t\t\t\tif(range->next) {\n+\t\t\t\t\tdie(\"Path %s need a -L <range> option\\n\"\n+\t\t\t\t\t\"If you want follow the history of the whole file \"\n+\t\t\t\t\t\"whether to using 'git log' without -L or using \"\n+\t\t\t\t\t\"'git log -L 1,$ <path>'\", range->spec->path);\n+\t\t\t\t} else {\n+\t\t\t\t\tparse_options_next(&ctx, 1);\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tstruct diff_line_range *r = xmalloc(sizeof(*r));\n+\t\t\tDIFF_LINE_RANGE_INIT(r);\n+\t\t\tr->next = range;\n+\t\t\trange = r;\n+\t\t\tparse_options_next(&ctx, 1);\n+\t\t\tcontinue;\n+\t\tcase PARSE_OPT_UNKNOWN:\n+\t\t\tparse_options_next(&ctx, 1);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tparse_revision_opt(rev, &ctx, options, log_opt_usage);\n+\t}\n+parse_done:\n+\targc = parse_options_end(&ctx);\n+\n+\t/* die if '-L <range>' with no pathspec follow */\n+\tif (range->nr > 0 && range->spec == NULL) {\n+\t\tdie(\"Each -L should follow a pathspec\");\n+\t}\n+\t/* clear up the last range */\n+\tif (range->nr == 0) {\n+\t\tstruct diff_line_range *r = range->next;\n+\t\tDIFF_LINE_RANGE_CLEAR(range);\n+\t\trange = r;\n+\t}\n+\n \targc = setup_revisions(argc, argv, rev, opt);\n \n \tmemset(&w, 0, sizeof(w));\n@@ -125,6 +220,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,\n \t\trev->show_decorations = 1;\n \t\tload_ref_decorations(decoration_style);\n \t}\n+\n+\t/* Test whether line level history is asked for */\n+\tif (range && range->nr > 0) {\n+\t\tsetup_line(rev, range);\n+\t}\n }\n \n /*\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145299","messageId":"1278829141-11900-6-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 06/13] export three functions from diff.c","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:54Z","receivedAt":"2010-07-11T06:18:54Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Use fill_metainfo to fill the line level diff meta data,\nemit_line to print out a line and quote_two to quote\npaths.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n diff.c |    6 +++---\n diff.h |   17 +++++++++++++++++\n 2 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 3aa695d..d685f5a 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -144,7 +144,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)\n \treturn git_color_default_config(var, value, cb);\n }\n \n-static char *quote_two(const char *one, const char *two)\n+char *quote_two(const char *one, const char *two)\n {\n \tint need_one = quote_c_style(one, NULL, NULL, 1);\n \tint need_two = quote_c_style(two, NULL, NULL, 1);\n@@ -325,7 +325,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res\n \t\tfputc('\\n', file);\n }\n \n-static void emit_line(struct diff_options *o, const char *set, const char *reset,\n+void emit_line(struct diff_options *o, const char *set, const char *reset,\n \t\t      const char *line, int len)\n {\n \temit_line_0(o, set, reset, line[0], line+1, len-1);\n@@ -2564,7 +2564,7 @@ static int similarity_index(struct diff_filepair *p)\n \treturn p->score * 100 / MAX_SCORE;\n }\n \n-static void fill_metainfo(struct strbuf *msg,\n+void fill_metainfo(struct strbuf *msg,\n \t\t\t  const char *name,\n \t\t\t  const char *other,\n \t\t\t  struct diff_filespec *one,\ndiff --git a/diff.h b/diff.h\nindex 063d10a..9676ab9 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -12,6 +12,7 @@ struct diff_queue_struct;\n struct strbuf;\n struct diff_filespec;\n struct userdiff_driver;\n+struct diff_filepair;\n \n typedef void (*change_fn_t)(struct diff_options *options,\n \t\t unsigned old_mode, unsigned new_mode,\n@@ -301,4 +302,20 @@ extern size_t fill_textconv(struct userdiff_driver *driver,\n \n extern struct userdiff_driver *get_textconv(struct diff_filespec *one);\n \n+/* some output functions line.c need */\n+extern void fill_metainfo(struct strbuf *msg,\n+\t\t\t  const char *name,\n+\t\t\t  const char *other,\n+\t\t\t  struct diff_filespec *one,\n+\t\t\t  struct diff_filespec *two,\n+\t\t\t  struct diff_options *o,\n+\t\t\t  struct diff_filepair *p,\n+\t\t\t  int *must_show_header,\n+\t\t\t  int use_color);\n+\n+extern void emit_line(struct diff_options *o, const char *set, const char *reset,\n+\t\t      const char *line, int len);\n+\n+extern char *quote_two(const char *one, const char *two);\n+\n #endif /* DIFF_H */\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145300","messageId":"1278829141-11900-7-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 07/13] add range clone functions","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:55Z","receivedAt":"2010-07-11T06:18:55Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Both single range clone and deeply clone are supported.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n line.c |   39 +++++++++++++++++++++++++++++++++++++++\n line.h |    4 ++++\n 2 files changed, 43 insertions(+), 0 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex a6bcbdf..ba96d7d 100644\n--- a/line.c\n+++ b/line.c\n@@ -378,6 +378,45 @@ void diff_line_range_append(struct diff_line_range *r, const char *arg)\n \tr->ranges[r->nr - 1].arg = arg;\n }\n \n+struct diff_line_range *diff_line_range_clone(struct diff_line_range *r)\n+{\n+\tstruct diff_line_range *ret = xmalloc(sizeof(*ret));\n+\tint i = 0;\n+\n+\tDIFF_LINE_RANGE_INIT(ret);\n+\tret->ranges = xcalloc(r->nr, sizeof(struct range));\n+\tmemcpy(ret->ranges, r->ranges, sizeof(struct range) * r->nr);\n+\n+\tret->alloc = ret->nr = r->nr;\n+\n+\tfor (; i < ret->nr; i++)\n+\t\tPRINT_PAIR_INIT(&ret->ranges[i].pair);\n+\n+\tret->spec = r->spec;\n+\tassert(ret->spec);\n+\tret->spec->count++;\n+\n+\treturn ret;\n+}\n+\n+struct diff_line_range *diff_line_range_clone_deeply(struct diff_line_range *r)\n+{\n+\tstruct diff_line_range *ret = NULL;\n+\tstruct diff_line_range *tmp = NULL, *prev = NULL;\n+\n+\tassert(r);\n+\tret = tmp = prev = diff_line_range_clone(r);\n+\tr = r->next;\n+\twhile (r) {\n+\t\ttmp = diff_line_range_clone(r);\n+\t\tprev->next = tmp;\n+\t\tprev = tmp;\n+\t\tr = r->next;\n+\t}\n+\n+\treturn ret;\n+}\n+\n struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,\n \tstruct diff_line_range *other)\n {\ndiff --git a/line.h b/line.h\nindex 0eb03bd..885e985 100644\n--- a/line.h\n+++ b/line.h\n@@ -112,6 +112,10 @@ extern void diff_line_range_append(struct diff_line_range *r, const char *arg);\n \n extern void diff_line_range_clear(struct diff_line_range *r);\n \n+extern struct diff_line_range *diff_line_range_clone(struct diff_line_range *r);\n+\n+extern struct diff_line_range *diff_line_range_clone_deeply(struct diff_line_range *r);\n+\n extern struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,\n \tstruct diff_line_range *other);\n \n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145301","messageId":"1278829141-11900-8-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 08/13] map/take range to parent","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:56Z","receivedAt":"2010-07-11T06:18:56Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"The interesting line range will be passed to all of its parents.\nFor non-merge commit, we just map_range the ranges. Generally, the\nalgorithm do:\n\n1. Run a diffcore_std to find out the pairs;\n2. Run a xdi_diff_hunks on each interesting file pair;\n3. The map_range_cb callback will be invoked for each diff hunk,\n   and in the function map_lines we will calculate the pre-image\n   range from the post-image range.\n\nFor merge commit, another take_range pass will be done except the\nabove normal map_range work. It is used to subtract each same\npart lines of the current range out. After this pass, if there is\nany line range left, this means the merge must be a non-trivial\nmerge. This is how the non-trivial merge detect work.\n\nThe algorithm that map lines from post-image to pre-image is in\nthe function map_lines. Generally, we use simple line number\ncalculation method to do the map.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n line.c     |  453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n revision.h |    5 +-\n 2 files changed, 457 insertions(+), 1 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex ba96d7d..e1af2f6 100644\n--- a/line.c\n+++ b/line.c\n@@ -498,3 +498,456 @@ void setup_line(struct rev_info *rev, struct diff_line_range *r)\n \tdiff_tree_release_paths(opt);\n }\n \n+struct take_range_cb_data {\n+\tstruct diff_line_range *interesting;\n+\tstruct diff_line_range *range;\n+\tlong plno, tlno;\n+\tint diff;\n+};\n+\n+#define SCALE_FACTOR 4\n+void map_lines(long p_start, long p_end, long t_start, long t_end,\n+\t\tlong start, long end, long *o_start, long *o_end)\n+{\n+\t/*\n+\t * xdiff always assign 'same' with the last line number of two\n+\t * same ranges. When some lines are added from scratch,\n+\t * p_start = same + 1;\n+\t * p_end = same;\n+\t * so, we can understand the following condition.\n+\t */\n+\tif (p_start > p_end) {\n+\t\t*o_start = *o_end = 0;\n+\t\treturn;\n+\t}\n+\t/* A deletion */\n+\tif (t_start > t_end) {\n+\t\t*o_start = p_start;\n+\t\t*o_end = p_end;\n+\t\treturn;\n+\t}\n+\n+\tif (start == t_start && end == t_end) {\n+\t\t*o_start = p_start;\n+\t\t*o_end = p_end;\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * A new strategy for lines mapping:\n+\t *\n+\t * When the pre-image is no more than 1/4 of the post-image, it\n+\t * take no sense to say what the real pre-image is, wan can just\n+\t * treat all the post-image lines as added from scratch.\n+\t */\n+\tif (SCALE_FACTOR * (p_end - p_start + 1) < (t_end - t_start + 1)) {\n+\t\t*o_start = *o_end = 0;\n+\t\treturn;\n+\t}\n+\n+\t*o_start = p_start + start - t_start;\n+\t*o_end = p_end - (t_end - end);\n+\n+\tif (*o_start > *o_end) {\n+\t\tint temp = *o_start;\n+\t\t*o_start = *o_end;\n+\t\t*o_end = temp;\n+\t}\n+\n+\tif (*o_start < p_start)\n+\t\t*o_start = p_start;\n+\tif (*o_end > p_end)\n+\t\t*o_end = p_end;\n+}\n+\n+static void map_range(struct take_range_cb_data *data, int same,\n+\t\tlong p_start, long p_end, long t_start, long t_end)\n+{\n+\tstruct range *ranges = data->interesting->ranges;\n+\tlong takens, takene, start, end;\n+\tint i = 0, out = 0, added = 0;\n+\tlong op_start = p_start, op_end = p_end, ot_start = t_start, ot_end = t_end;\n+\n+\tfor (; i < data->interesting->nr; i++) {\n+\t\tadded = 0;\n+\t\tif (t_start > ranges[i].end)\n+\t\t\tcontinue;\n+\t\tif (t_end < ranges[i].start)\n+\t\t\tbreak;\n+\n+\t\tif (t_start > ranges[i].start) {\n+\t\t\tstart = t_start;\n+\t\t\ttakens = p_start;\n+\t\t\tif (t_end >= ranges[i].end) {\n+\t\t\t\tend = ranges[i].end;\n+\t\t\t\ttakene = p_start + end - t_start;\n+\t\t\t} else {\n+\t\t\t\tend = t_end;\n+\t\t\t\ttakene = p_end;\n+\t\t\t\tout = 1;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tstart = ranges[i].start;\n+\t\t\ttakens = p_start + start - t_start;\n+\t\t\tif (t_end >= ranges[i].end) {\n+\t\t\t\tend = ranges[i].end;\n+\t\t\t\ttakene = p_start + end - t_start;\n+\t\t\t} else {\n+\t\t\t\tend = t_end;\n+\t\t\t\ttakene = p_end;\n+\t\t\t\tout = 1;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!same) {\n+\t\t\tstruct print_pair *pair = &ranges[i].pair;\n+\t\t\tPRINT_PAIR_GROW(pair);\n+\t\t\tstruct print_range *rr = pair->ranges + pair->nr - 1;\n+\t\t\tPRINT_RANGE_INIT(rr);\n+\t\t\trr->start = start;\n+\t\t\trr->end = end;\n+\t\t\tmap_lines(op_start, op_end, ot_start, ot_end, start, end,\n+\t\t\t\t\t&takens, &takene);\n+\t\t\tif (takens == 0 && takene == 0) {\n+\t\t\t\tadded = 1;\n+\t\t\t\trr->line_added = 1;\n+\t\t\t}\n+\t\t\trr->pstart = takens;\n+\t\t\trr->pend = takene;\n+\t\t\tdata->diff = 1;\n+\t\t\tdata->interesting->diff = 1;\n+\t\t\tranges[i].diff = 1;\n+\t\t}\n+\t\tif (added) {\n+\t\t\t/* Code movement/copy detect here */\n+\t\t} else {\n+\t\t\tstruct range *added_range = diff_line_range_insert(data->range,\n+\t\t\t\t\tNULL, takens, takene);\n+\t\t\tassert(added_range);\n+\t\t\tranges[i].pstart = added_range->start;\n+\t\t\tranges[i].pend = added_range->end;\n+\t\t}\n+\n+\t\tt_start = end + 1;\n+\t\tp_start = takene + 1;\n+\n+\t\tif (out)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+static void take_range(struct take_range_cb_data *data,\n+\t\tlong p_start, long p_end, long t_start, long t_end)\n+{\n+\tstruct range *ranges = data->interesting->ranges;\n+\tlong takens, takene, start, end;\n+\tint i = 0, out = 0, added = 0;\n+\n+\tfor (; i < data->interesting->nr; i++) {\n+\t\tadded = 0;\n+\t\tif (t_start > ranges[i].end)\n+\t\t\tcontinue;\n+\t\tif (t_end < ranges[i].start)\n+\t\t\tbreak;\n+\n+\t\tif (t_start > ranges[i].start) {\n+\t\t\tlong tmp = ranges[i].end;\n+\t\t\tranges[i].end = t_start - 1;\n+\t\t\tstart = t_start;\n+\t\t\ttakens = p_start;\n+\t\t\tif (t_end >= tmp) {\n+\t\t\t\tend = tmp;\n+\t\t\t\ttakene = p_start + end - t_start;\n+\t\t\t\tp_start = takene + 1;\n+\t\t\t\tt_start = end + 1;\n+\t\t\t} else {\n+\t\t\t\tend = t_end;\n+\t\t\t\ttakene = p_end;\n+\t\t\t\tdiff_line_range_insert(data->interesting, NULL,\n+\t\t\t\t\tt_end + 1, tmp);\n+\t\t\t\tout = 1;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tstart = ranges[i].start;\n+\t\t\ttakens = p_start + start - t_start;\n+\t\t\tif (t_end >= ranges[i].end) {\n+\t\t\t\tint num = data->interesting->nr - 1;\n+\t\t\t\tend = ranges[i].end;\n+\t\t\t\ttakene = p_start + end - t_start;\n+\t\t\t\tt_start = end + 1;\n+\t\t\t\tp_start = takene + 1;\n+\t\t\t\tmemmove(ranges + i, ranges + i + 1, (num - i) * sizeof(*ranges));\n+\t\t\t\tdata->interesting->nr = num;\n+\t\t\t\ti--;\n+\t\t\t} else {\n+\t\t\t\tend = t_end;\n+\t\t\t\ttakene = p_end;\n+\t\t\t\tranges[i].start = t_end + 1;\n+\t\t\t\tout = 1;\n+\t\t\t}\n+\t\t}\n+\n+\t\tdiff_line_range_insert(data->range, NULL, takens, takene);\n+\n+\t\tif (out)\n+\t\t\tbreak;\n+\t}\n+}\n+\n+static void take_range_cb(void *data, long same, long p_next, long t_next)\n+{\n+\tstruct take_range_cb_data *d = data;\n+\tlong p_start = d->plno + 1, t_start = d->tlno + 1;\n+\tlong p_end = p_start + same - t_start, t_end = same;\n+\n+\t/* If one file is added from scratch, this may confuse take_range */\n+\tif (t_end >= t_start)\n+\t\ttake_range(d, p_start, p_end, t_start, t_end);\n+\td->plno = p_next;\n+\td->tlno = t_next;\n+}\n+\n+static void map_range_cb(void *data, long same, long p_next, long t_next)\n+{\n+\tstruct take_range_cb_data *d = data;\n+\n+\tlong p_start = d->plno + 1;\n+\tlong t_start = d->tlno + 1;\n+\tlong p_end = same - t_start + p_start;\n+\tlong t_end = same;\n+\n+\t/* Firstly, take the unchanged lines from child */\n+\tif (t_end >= t_start)\n+\t\tmap_range(d, 1, p_start, p_end, t_start, t_end);\n+\n+\t/* find out which lines to print */\n+\tt_start = same + 1;\n+\tp_start = d->plno + t_start - d->tlno;\n+\tmap_range(d, 0, p_start, p_next, t_start, t_next);\n+\n+\td->plno = p_next;\n+\td->tlno = t_next;\n+}\n+\n+static void assign_range_to_parent(struct rev_info *rev, struct commit *c,\n+\t\tstruct commit *p, struct diff_line_range *r,\n+\t\tstruct diff_options *opt, int map)\n+{\n+\tstruct diff_line_range *rr = xmalloc(sizeof(*rr));\n+\tstruct diff_line_range *cr = rr, *prev_r = rr;\n+\tstruct tree_desc desc1, desc2;\n+\tvoid *tree1 = NULL, *tree2 = NULL;\n+\tunsigned long size1, size2;\n+\tstruct diff_queue_struct *queue;\n+\tstruct take_range_cb_data cb = {NULL, cr, 0, 0};\n+\txpparam_t xpp;\n+\txdemitconf_t xecfg;\n+\tint i, diff = 0;\n+\txdiff_emit_hunk_consume_fn fn = map ? map_range_cb : take_range_cb;\n+\n+\tDIFF_LINE_RANGE_INIT(cr);\n+\tmemset(&xpp, 0, sizeof(xpp));\n+\tmemset(&xecfg, 0, sizeof(xecfg));\n+\txpp.flags = XDF_NEED_MINIMAL;\n+\txecfg.ctxlen = xecfg.interhunkctxlen = 0;\n+\n+\t/*\n+\t * Compose up two trees, for root commit, we make up a empty tree.\n+\t */\n+\tassert(c);\n+\ttree2 = read_object_with_reference(c->tree->object.sha1, \"tree\", &size2, NULL);\n+\tif (tree2 == NULL)\n+\t\tdie(\"Unable to read tree (%s)\", sha1_to_hex(c->tree->object.sha1));\n+\tinit_tree_desc(&desc2, tree2, size2);\n+\tif (p) {\n+\t\ttree1 = read_object_with_reference(p->tree->object.sha1, \"tree\", &size1, NULL);\n+\t\tif (tree1 == NULL)\n+\t\t\tdie(\"Unable to read tree (%s)\", sha1_to_hex(p->tree->object.sha1));\n+\t\tinit_tree_desc(&desc1, tree1, size1);\n+\t} else {\n+\t\tinit_tree_desc(&desc1, \"\", 0);\n+\t}\n+\n+\tDIFF_QUEUE_CLEAR(&diff_queued_diff);\n+\tdiff_tree(&desc1, &desc2, \"\", opt);\n+\tdiffcore_std(opt);\n+\n+\tqueue = &diff_queued_diff;\n+\tfor (i = 0; i < queue->nr; i++) {\n+\t\tstruct diff_filepair *pair = queue->queue[i];\n+\t\tstruct diff_line_range *rg = r;\n+\t\tmmfile_t file_p, file_t;\n+\t\tassert(pair->two->path);\n+\t\twhile (rg) {\n+\t\t\tassert(rg->spec->path);\n+\t\t\tif (!strcmp(rg->spec->path, pair->two->path))\n+\t\t\t\tbreak;\n+\t\t\trg = rg->next;\n+\t\t}\n+\n+\t\tif (rg == NULL)\n+\t\t\tcontinue;\n+\t\trg->touch = 1;\n+\t\tif (rg->nr == 0)\n+\t\t\tcontinue;\n+\n+\t\trg->status = pair->status;\n+\t\tassert(pair->two->sha1_valid);\n+\t\tdiff_populate_filespec(pair->two, 0);\n+\t\tfile_t.ptr = pair->two->data;\n+\t\tfile_t.size = pair->two->size;\n+\n+\t\tif (rg->prev)\n+\t\t\tfree_filespec(rg->prev);\n+\t\trg->prev = pair->one;\n+\t\trg->prev->count++;\n+\t\tif (pair->one->sha1_valid) {\n+\t\t\tdiff_populate_filespec(pair->one, 0);\n+\t\t\tfile_p.ptr = pair->one->data;\n+\t\t\tfile_p.size = pair->one->size;\n+\t\t} else {\n+\t\t\tfile_p.ptr = \"\";\n+\t\t\tfile_p.size = 0;\n+\t\t}\n+\n+\t\tif (cr->nr != 0) {\n+\t\t\tstruct diff_line_range *tmp = xmalloc(sizeof(*tmp));\n+\t\t\tcr->next = tmp;\n+\t\t\tprev_r = cr;\n+\t\t\tcr = tmp;\n+\t\t} else if (cr->spec)\n+\t\t\tDIFF_LINE_RANGE_CLEAR(cr);\n+\n+\t\tDIFF_LINE_RANGE_INIT(cr);\n+\t\tif (pair->one->sha1_valid) {\n+\t\t\tcr->spec = pair->one;\n+\t\t\tcr->spec->count++;\n+\t\t}\n+\n+\t\tcb.interesting = rg;\n+\t\tcb.range = cr;\n+\t\tcb.diff = 0;\n+\t\tcb.plno = cb.tlno = 0;\n+\t\txdi_diff_hunks(&file_p, &file_t, fn, &cb, &xpp, &xecfg);\n+\t\tif (cb.diff)\n+\t\t\tdiff = 1;\n+\t\t/*\n+\t\t * The remain part is the same part.\n+\t\t * Instead of calculating the true line number of the two files,\n+\t\t * use the biggest integer.\n+\t\t */\n+\t\tif (map)\n+\t\t\tmap_range(&cb, 1, cb.plno + 1, 0x7FFFFFFF, cb.tlno + 1, 0x7FFFFFFF);\n+\t\telse\n+\t\t\ttake_range(&cb, cb.plno + 1, 0x7FFFFFFF, cb.tlno + 1, 0x7FFFFFFF);\n+\t}\n+\topt->output_format = DIFF_FORMAT_NO_OUTPUT;\n+\tdiff_flush(opt);\n+\n+\t/* Collect the untouch ranges, this comes from the files not changed\n+\t * between two commit.\n+\t */\n+\tstruct diff_line_range *rg = r;\n+\twhile (rg) {\n+\t\t/* clear the touch one to make it usable in next round */\n+\t\tif (rg->touch) {\n+\t\t\trg->touch = 0;\n+\t\t} else {\n+\t\t\tstruct diff_line_range *untouch = diff_line_range_clone(rg);\n+\t\t\tif (prev_r == rr && rr->nr == 0) {\n+\t\t\t\trr = prev_r = untouch;\n+\t\t\t} else {\n+\t\t\t\tprev_r->next = untouch;\n+\t\t\t\tprev_r = untouch;\n+\t\t\t}\n+\t\t}\n+\t\trg = rg->next;\n+\t}\n+\n+\tif (cr->nr == 0) {\n+\t\tDIFF_LINE_RANGE_CLEAR(cr);\n+\t\tfree(cr);\n+\t\tif (prev_r == cr) {\n+\t\t\trr = NULL;\n+\t\t} else {\n+\t\t\tprev_r->next = NULL;\n+\t\t}\n+\t}\n+\n+\tif (rr) {\n+\t\tassert(p);\n+\t\tadd_line_range(rev, p, rr);\n+\t}\n+\n+\t/* and the ranges of current commit c is updated */\n+\tc->object.flags &= ~RANGE_UPDATE;\n+\tif (diff)\n+\t\tc->object.flags |= NEED_PRINT;\n+\n+\tif (tree1)\n+\t\tfree(tree1);\n+\tif (tree2)\n+\t\tfree(tree2);\n+}\n+\n+static void diff_update_parent_range(struct rev_info *rev, struct commit *commit)\n+{\n+\tstruct diff_line_range *r = lookup_line_range(rev, commit);\n+\tstruct commit_list *parents = commit->parents;\n+\tstruct commit *c = NULL;\n+\tif (parents) {\n+\t\tassert(parents->next == NULL);\n+\t\tc = parents->item;\n+\t}\n+\n+\tassign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);\n+}\n+\n+static void assign_parents_range(struct rev_info *rev, struct commit *commit)\n+{\n+\tstruct commit_list *parents = commit->parents;\n+\tstruct diff_line_range *r = lookup_line_range(rev, commit);\n+\tstruct diff_line_range *copy = NULL, *range = NULL;\n+\tint nontrivial = 0;\n+\n+\t/*\n+\t * If we are in linear history, update range and flush the patch if\n+\t * necessary\n+\t */\n+\tif (parents == NULL || parents->next == NULL) {\n+\t\treturn diff_update_parent_range(rev, commit);\n+\t}\n+\n+\t/*\n+\t * Loop on the parents and assign the ranges to different\n+\t * parents, if there is any range left, this commit must\n+\t * be an evil merge.\n+\t */\n+\tcopy = diff_line_range_clone_deeply(r);\n+\tparents = commit->parents;\n+\twhile (parents) {\n+\t\tstruct commit *p = parents->item;\n+\t\tassign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);\n+\t\tassign_range_to_parent(rev, commit, p, copy, &rev->diffopt, 0);\n+\t\tparents = parents->next;\n+\t}\n+\n+\t/*\n+\t * yes, this must be an evil merge.\n+\t */\n+\trange = copy;\n+\twhile (range) {\n+\t\tif (range->nr) {\n+\t\t\tcommit->object.flags |= NEED_PRINT | EVIL_MERGE;\n+\t\t\tnontrivial = 1;\n+\t\t}\n+\t\trange = range->next;\n+\t}\n+\n+\tif (nontrivial) {\n+\t\tadd_decoration(&rev->nontrivial_merge, &commit->object, copy);\n+\t} else {\n+\t\tcleanup(copy);\n+\t}\n+}\n+\ndiff --git a/revision.h b/revision.h\nindex 55836e0..bbc4e2c 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -15,7 +15,9 @@\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n #define RANGE_UPDATE\t(1u<<9) /* for line level traverse */\n-#define ALL_REV_FLAGS\t((1u<<10)-1)\n+#define NEED_PRINT\t(1u<<10)\n+#define EVIL_MERGE\t(1u<<11)\n+#define ALL_REV_FLAGS\t((1u<<12)-1)\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n@@ -141,6 +143,7 @@ struct rev_info {\n \tint count_right;\n \t/* line-level intersting range */\n \tstruct decoration line_range;\n+\tstruct decoration nontrivial_merge;\n };\n \n #define REV_TREE_SAME\t\t0\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145303","messageId":"1278829141-11900-9-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 09/13] print the line log","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:57Z","receivedAt":"2010-07-11T06:18:57Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"'struct line_chunk' is used to make sure each file is scaned\nonly once when printing the lines. We trace the line number and\nthe start position of intermediate line in this struct.\nTwo helper functions from diff.c are used to generate the\nconsistent format of diff meta info.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n line.c |  245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 245 insertions(+), 0 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex e1af2f6..8813376 100644\n--- a/line.c\n+++ b/line.c\n@@ -951,3 +951,248 @@ static void assign_parents_range(struct rev_info *rev, struct commit *commit)\n \t}\n }\n \n+struct line_chunk {\n+\tint lone, ltwo;\n+\tconst char *one, *two;\n+\tconst char *one_end, *two_end;\n+\tstruct diff_line_range *range;\n+};\n+\n+static void flush_lines(struct diff_options *opt, const char **ptr, const char *end,\n+\t\tint slno, int elno, int *lno, const char *color, const char heading)\n+{\n+\tconst char *p = *ptr;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *reset;\n+\n+\tif (strcmp(color, \"\"))\n+\t\treset = \"\";\n+\telse\n+\t\treset = diff_get_color_opt(opt, DIFF_RESET);\n+\n+\tstrbuf_addf(&buf, \"%s%c\", color, heading);\n+\twhile (*ptr < end && *lno < slno) {\n+\t\tif (**ptr == '\\n') {\n+\t\t\t(*lno)++;\n+\t\t\tif (*lno == slno) {\n+\t\t\t\t(*ptr)++;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\t(*ptr)++;\n+\t}\n+\tassert(*ptr <= end);\n+\tp = *ptr;\n+\n+\t/*\n+\t * todo: when --graph landed on master, this should be changed\n+\t * a little.\n+\t */\n+\twhile (*ptr < end && *lno <= elno) {\n+\t\tif (**ptr == '\\n') {\n+\t\t\tfprintf(opt->file, \"%s\", buf.buf);\n+\t\t\tif (*ptr - p) {\n+\t\t\t\tfwrite(p, *ptr - p, 1, opt->file);\n+\t\t\t}\n+\t\t\tfprintf(opt->file, \"%s\\n\", reset);\n+\t\t\tp = *ptr + 1;\n+\t\t\t(*lno)++;\n+\t\t}\n+\t\t(*ptr)++;\n+\t}\n+\tif (*lno <= elno) {\n+\t\tfprintf(opt->file, \"%s\", buf.buf);\n+\t\tif (*ptr - p) {\n+\t\t\tfwrite(p, *ptr - p, 1, opt->file);\n+\t\t}\n+\t\tfprintf(opt->file, \"%s\\n\", reset);\n+\t}\n+\tstrbuf_release(&buf);\n+}\n+\n+static void diff_flush_range(struct diff_options *opt, struct line_chunk *chunk,\n+\t\tstruct range *range)\n+{\n+\tstruct print_pair *pair = &range->pair;\n+\tconst char *old = diff_get_color_opt(opt, DIFF_FILE_OLD);\n+\tconst char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);\n+\tint i, cur = range->start;\n+\n+\tfor (i = 0; i < pair->nr; i++) {\n+\t\tstruct print_range *pr = pair->ranges + i;\n+\t\tif (cur < pr->start)\n+\t\t\tflush_lines(opt, &chunk->two, chunk->two_end,\n+\t\t\t\tcur, pr->start - 1, &chunk->ltwo, \"\", ' ');\n+\n+\t\tif (!pr->line_added)\n+\t\t\tflush_lines(opt, &chunk->one, chunk->one_end,\n+\t\t\t\tpr->pstart, pr->pend, &chunk->lone, old, '-');\n+\t\tflush_lines(opt, &chunk->two, chunk->two_end,\n+\t\t\tpr->start, pr->end, &chunk->ltwo, new, '+');\n+\n+\t\tcur = pr->end + 1;\n+\t}\n+\n+\tif (cur <= range->end) {\n+\t\tflush_lines(opt, &chunk->two, chunk->two_end,\n+\t\t\tcur, range->end, &chunk->ltwo, \"\", ' ');\n+\t}\n+}\n+\n+static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk)\n+{\n+\tstruct diff_line_range *range = chunk->range;\n+\tconst char *set = diff_get_color_opt(opt, DIFF_FRAGINFO);\n+\tconst char *reset = diff_get_color_opt(opt, DIFF_RESET);\n+\tint i;\n+\n+\tfor (i = 0; i < range->nr; i++) {\n+\t\tstruct range *r = range->ranges + i;\n+\t\tlong lenp = r->pend - r->pstart + 1, pstart = r->pstart;\n+\t\tlong len = r->end - r->start + 1;\n+\t\tif (pstart == 0)\n+\t\t\tlenp = 0;\n+\n+\t\tfprintf(opt->file, \"%s@@ -%ld,%ld +%ld,%ld @@%s\\n\",\n+\t\t\tset, pstart, lenp, r->start, len, reset);\n+\n+\t\tdiff_flush_range(opt, chunk, r);\n+\t}\n+}\n+\n+static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *range)\n+{\n+\tstruct diff_options *opt = &rev->diffopt;\n+\tstruct diff_filespec *one = range->prev, *two = range->spec;\n+\tstruct diff_filepair p = {one, two, range->status, 0};\n+\tstruct strbuf header = STRBUF_INIT, meta = STRBUF_INIT;\n+\tconst char *a_prefix, *b_prefix;\n+\tconst char *name_a, *name_b, *a_one, *b_two;\n+\tconst char *lbl[2];\n+\tconst char *set = diff_get_color_opt(opt, DIFF_METAINFO);\n+\tconst char *reset = diff_get_color_opt(opt, DIFF_RESET);\n+\tstruct line_chunk chunk;\n+\tint must_show_header;\n+\n+\t/*\n+\t * the ranges that touch no different file, in this case\n+\t * the line number will not change, and of course we have\n+\t * no sensible rang->pair since there is no diff run.\n+\t */\n+\tif (one == NULL) {\n+\t\treturn;\n+\t}\n+\n+\tif (range->status == DIFF_STATUS_DELETED)\n+\t\tdie(\"We are following an nonexistent file, interesting!\");\n+\n+\tname_a  = one->path;\n+\tname_b = two->path;\n+\tfill_metainfo(&meta, name_a, name_b, one, two, opt, &p, &must_show_header,\n+\t\t\tDIFF_OPT_TST(opt, COLOR_DIFF));\n+\n+\tdiff_set_mnemonic_prefix(opt, \"a/\", \"b/\");\n+\tif (DIFF_OPT_TST(opt, REVERSE_DIFF)) {\n+\t\ta_prefix = opt->b_prefix;\n+\t\tb_prefix = opt->a_prefix;\n+\t} else {\n+\t\ta_prefix = opt->a_prefix;\n+\t\tb_prefix = opt->b_prefix;\n+\t}\n+\n+\tname_a = DIFF_FILE_VALID(one) ? name_a : name_b;\n+\tname_b = DIFF_FILE_VALID(two) ? name_b : name_a;\n+\n+\ta_one = quote_two(a_prefix, name_a + (*name_a == '/'));\n+\tb_two = quote_two(b_prefix, name_b + (*name_b == '/'));\n+\tlbl[0] = DIFF_FILE_VALID(one) ? a_one : \"/dev/null\";\n+\tlbl[1] = DIFF_FILE_VALID(two) ? b_two : \"/dev/null\";\n+\tstrbuf_addf(&header, \"%sdiff --git %s %s%s\\n\", set, a_one, b_two, reset);\n+\tif (lbl[0][0] == '/') {\n+\t\tstrbuf_addf(&header, \"%snew file mode %06o%s\\n\", set, two->mode, reset);\n+\t} else if (lbl[1][0] == '/') {\n+\t\tstrbuf_addf(&header, \"%sdeleted file mode %06o%s\\n\", set, one->mode, reset);\n+\t} else if (one->mode != two->mode) {\n+\t\t\tstrbuf_addf(&header, \"%sold mode %06o%s\\n\", set, one->mode, reset);\n+\t\t\tstrbuf_addf(&header, \"%snew mode %06o%s\\n\", set, two->mode, reset);\n+\t}\n+\n+\tfprintf(opt->file, \"%s%s\", header.buf, meta.buf);\n+\tstrbuf_release(&meta);\n+\tstrbuf_release(&header);\n+\tfprintf(opt->file, \"%s--- %s%s\\n\", set, lbl[0], reset);\n+\tfprintf(opt->file, \"%s+++ %s%s\\n\", set, lbl[1], reset);\n+\tfree((void *)a_one);\n+\tfree((void *)b_two);\n+\n+\tchunk.one = one->data;\n+\tchunk.one_end = one->data + one->size;\n+\tchunk.lone = 1;\n+\tchunk.two = two->data;\n+\tchunk.two_end = two->data + two->size;\n+\tchunk.ltwo = 1;\n+\tchunk.range = range;\n+\tdiff_flush_chunks(&rev->diffopt, &chunk);\n+}\n+\n+#define EVIL_MERGE_STR \"nontrivial merge found\"\n+static void flush_nontrivial_merge(struct rev_info *rev, struct diff_line_range *range)\n+{\n+\tstruct diff_options *opt = &rev->diffopt;\n+\tconst char *reset = diff_get_color_opt(opt, DIFF_RESET);\n+\tconst char *frag = diff_get_color_opt(opt, DIFF_FRAGINFO);\n+\tconst char *meta = diff_get_color_opt(opt, DIFF_METAINFO);\n+\tconst char *new = diff_get_color_opt(opt, DIFF_FILE_NEW);\n+\n+\tfprintf(opt->file, \"%s%s%s\\n\", meta, EVIL_MERGE_STR, reset);\n+\n+\twhile (range) {\n+\t\tif (range->nr) {\n+\t\t\tfprintf(opt->file, \"%s%s%s\\n\\n\", meta, range->spec->path, reset);\n+\t\t\tint lno = 1;\n+\t\t\tconst char *ptr = range->spec->data;\n+\t\t\tconst char *end = range->spec->data + range->spec->size;\n+\t\t\tint i = 0;\n+\t\t\tfor (; i < range->nr; i++) {\n+\t\t\t\tstruct range *r = range->ranges + i;\n+\t\t\t\tfprintf(opt->file, \"%s@@ %ld,%ld @@%s\\n\", frag, r->start,\n+\t\t\t\t\tr->end - r->start + 1, reset);\n+\t\t\t\tflush_lines(opt, &ptr, end, r->start, r->end,\n+\t\t\t\t\t&lno, new, ' ');\n+\t\t\t}\n+\t\t\tfprintf(opt->file, \"\\n\");\n+\t\t}\n+\t\trange = range->next;\n+\t}\n+}\n+\n+static void line_log_flush(struct rev_info *rev, struct commit *c)\n+{\n+\tstruct diff_line_range *range = lookup_line_range(rev, c);\n+\tstruct diff_line_range *nontrivial = lookup_decoration(&rev->nontrivial_merge, &c->object);\n+\tstruct log_info log;\n+\n+\tif (range == NULL)\n+\t\treturn;\n+\n+\tlog.commit = c;\n+\tlog.parent = NULL;\n+\trev->loginfo = &log;\n+\tshow_log(rev);\n+\trev->loginfo = NULL;\n+\t/*\n+\t * Add a new line after each commit message, of course we should\n+\t * add --graph alignment later when the patches comes to master.\n+\t */\n+\tfprintf(rev->diffopt.file, \"\\n\");\n+\n+\tif (c->object.flags & EVIL_MERGE)\n+\t\treturn flush_nontrivial_merge(rev, nontrivial);\n+\n+\twhile (range) {\n+\t\tif (range->diff)\n+\t\t\tdiff_flush_filepair(rev, range);\n+\t\trange = range->next;\n+\t}\n+}\n+\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145302","messageId":"1278829141-11900-10-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 10/13] map/print ranges along traversing the history topologically","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:58Z","receivedAt":"2010-07-11T06:18:58Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Since ranges may change in different branches, we should\nmake sure we do not pass range to parent until all the\nranges get 'combined' at the commit which is a split commit.\nSo, topological traversing is necessary.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n builtin/log.c |    5 ++++-\n line.c        |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n line.h        |    2 ++\n 3 files changed, 60 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 1e90b03..47b386d 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -615,7 +615,10 @@ int cmd_log(int argc, const char **argv, const char *prefix)\n \tmemset(&opt, 0, sizeof(opt));\n \topt.def = \"HEAD\";\n \tcmd_log_init(argc, argv, prefix, &rev, &opt);\n-\treturn cmd_log_walk(&rev);\n+\tif (rev.line)\n+\t\treturn cmd_line_log_walk(&rev);\n+\telse\n+\t\treturn cmd_log_walk(&rev);\n }\n \n /* format-patch */\ndiff --git a/line.c b/line.c\nindex 8813376..5c8f77a 100644\n--- a/line.c\n+++ b/line.c\n@@ -1196,3 +1196,57 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\n \t}\n }\n \n+int cmd_line_log_walk(struct rev_info *rev)\n+{\n+\tstruct commit *commit;\n+\tstruct commit_list *list = NULL;\n+\tstruct diff_line_range *r = NULL;\n+\n+\tif (prepare_revision_walk(rev))\n+\t\tdie(\"revision walk prepare failed\");\n+\n+\tlist = rev->commits;\n+\tif (list) {\n+\t\tlist->item->object.flags |= RANGE_UPDATE;\n+\t\tlist = list->next;\n+\t}\n+\t/* Clear the flags */\n+\twhile (list) {\n+\t\tlist->item->object.flags &= 0x0;\n+\t\tlist = list->next;\n+\t}\n+\n+\tlist = rev->commits;\n+\twhile (list) {\n+\t\tstruct commit_list *need_free = list;\n+\t\tcommit = list->item;\n+\n+\t\tif (commit->object.flags & RANGE_UPDATE) {\n+\t\t\tassign_parents_range(rev, commit);\n+\t\t}\n+\n+\t\tif (commit->object.flags & NEED_PRINT) {\n+\t\t\tline_log_flush(rev, commit);\n+\t\t}\n+\n+\t\tr = lookup_line_range(rev, commit);\n+\t\tif (r) {\n+\t\t\tcleanup(r);\n+\t\t\tr = NULL;\n+\t\t\tadd_line_range(rev, commit, r);\n+\t\t}\n+\n+\t\tr = lookup_decoration(&rev->nontrivial_merge, &commit->object);\n+\t\tif (r) {\n+\t\t\tcleanup(r);\n+\t\t\tr = NULL;\n+\t\t\tadd_decoration(&rev->nontrivial_merge, &commit->object, r);\n+\t\t}\n+\n+\t\tlist = list->next;\n+\t\tfree(need_free);\n+\t}\n+\n+\treturn 0;\n+}\n+\ndiff --git a/line.h b/line.h\nindex 885e985..b293894 100644\n--- a/line.h\n+++ b/line.h\n@@ -128,4 +128,6 @@ extern struct diff_line_range *lookup_line_range(struct rev_info *revs, struct c\n const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \t\tvoid *data, long lines, long begin, long *ret);\n \n+extern int cmd_line_log_walk(struct rev_info *rev);\n+\n #endif\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145304","messageId":"1278829141-11900-11-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 11/13] add --always-print option","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:18:59Z","receivedAt":"2010-07-11T06:18:59Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Always print the interesting ranges even if the current\ncommit does not change any line of it.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n builtin/log.c |    3 +++\n line.c        |   11 +++++++++--\n revision.h    |    3 ++-\n 3 files changed, 14 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 47b386d..3fa31dc 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -85,6 +85,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,\n {\n \tint i;\n \tint decoration_given = 0;\n+\tstatic int always_print = 0;\n \tstruct userformat_want w;\n \tconst char *path = NULL, *pathspec = NULL;\n \tstatic struct diff_line_range *range = NULL;\n@@ -92,6 +93,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,\n \tstatic struct line_opt_callback_data line_cb = {&range, &ctx, NULL};\n \tstatic const struct option options[] = {\n \t\tOPT_CALLBACK('L', NULL, &line_cb, \"n,m\", \"Process only line range n,m, counting from 1\", log_line_range_callback),\n+\t\tOPT_BOOLEAN(0, \"always-print\", &always_print, \"Always print the interesting range even if the current commit does not change any line of it\"),\n \t\tOPT_END()\n \t};\n \n@@ -224,6 +226,7 @@ parse_done:\n \t/* Test whether line level history is asked for */\n \tif (range && range->nr > 0) {\n \t\tsetup_line(rev, range);\n+\t\trev->always_print = always_print;\n \t}\n }\n \ndiff --git a/line.c b/line.c\nindex 5c8f77a..c08b510 100644\n--- a/line.c\n+++ b/line.c\n@@ -1080,6 +1080,13 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra\n \t * no sensible rang->pair since there is no diff run.\n \t */\n \tif (one == NULL) {\n+\t\tif (rev->always_print) {\n+\t\t\tchunk.two = two->data;\n+\t\t\tchunk.two_end = two->data + two->size;\n+\t\t\tchunk.ltwo = 1;\n+\t\t\tchunk.range = range;\n+\t\t\tdiff_flush_chunks(&rev->diffopt, &chunk);\n+\t\t}\n \t\treturn;\n \t}\n \n@@ -1190,7 +1197,7 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\n \t\treturn flush_nontrivial_merge(rev, nontrivial);\n \n \twhile (range) {\n-\t\tif (range->diff)\n+\t\tif (range->diff || (range->nr && rev->always_print))\n \t\t\tdiff_flush_filepair(rev, range);\n \t\trange = range->next;\n \t}\n@@ -1225,7 +1232,7 @@ int cmd_line_log_walk(struct rev_info *rev)\n \t\t\tassign_parents_range(rev, commit);\n \t\t}\n \n-\t\tif (commit->object.flags & NEED_PRINT) {\n+\t\tif (commit->object.flags & NEED_PRINT || rev->always_print) {\n \t\t\tline_log_flush(rev, commit);\n \t\t}\n \ndiff --git a/revision.h b/revision.h\nindex bbc4e2c..433da9a 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -72,7 +72,8 @@ struct rev_info {\n \t\t\tbisect:1,\n \t\t\tancestry_path:1,\n \t\t\tfirst_parent_only:1,\n-\t\t\tline:1;\n+\t\t\tline:1,\n+\t\t\talways_print:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145306","messageId":"1278829141-11900-12-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 12/13] add two test cases","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:19:00Z","receivedAt":"2010-07-11T06:19:00Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"t4301: for simple linear history only\nt4302: for history contains merge\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n t/t4301-log-line-single-history.sh |  342 ++++++++++++++++++++++++++++++++++++\n t/t4302-log-line-merge-history.sh  |  114 ++++++++++++\n 2 files changed, 456 insertions(+), 0 deletions(-)\n create mode 100755 t/t4301-log-line-single-history.sh\n create mode 100755 t/t4302-log-line-merge-history.sh\n\ndiff --git a/t/t4301-log-line-single-history.sh b/t/t4301-log-line-single-history.sh\nnew file mode 100755\nindex 0000000..9981496\n--- /dev/null\n+++ b/t/t4301-log-line-single-history.sh\n@@ -0,0 +1,342 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Bo Yang\n+#\n+\n+test_description='Test git log -L with single line of history\n+\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/diff-lib.sh\n+\n+echo >path0 'void func(){\n+\tint a = 0;\n+\tint b = 1;\n+\tint c;\n+\tc = a + b;\n+}\n+'\n+\n+echo >path1 'void output(){\n+\tprintf(\"hello world\");\n+}\n+'\n+\n+test_expect_success \\\n+    'add path0/path1 and commit.' \\\n+    'git add path0 path1 &&\n+     git commit -m \"Base commit\"'\n+\n+echo >path0 'void func(){\n+\tint a = 10;\n+\tint b = 11;\n+\tint c;\n+\tc = a + b;\n+}\n+'\n+\n+echo >path1 'void output(){\n+\tconst char *str = \"hello world!\";\n+\tprintf(\"%s\", str);\n+}\n+'\n+\n+test_expect_success \\\n+    'Change the 2,3 lines of path0 and path1.' \\\n+    'git add path0 path1 &&\n+     git commit -m \"Change 2,3 lines of path0 and path1\"'\n+\n+echo >path0 'void func(){\n+\tint a = 10;\n+\tint b = 11;\n+\tint c;\n+\tc = 10 * (a + b);\n+}\n+'\n+\n+test_expect_success \\\n+\t'Change the 5th line of path0.' \\\n+\t'git add path0 &&\n+\t git commit -m \"Change the 5th line of path0\"'\n+\n+echo >path0 'void func(){\n+\tint a = 10;\n+\tint b = 11;\n+\tprintf(\"%d\", a - b);\n+}\n+'\n+\n+test_expect_success \\\n+\t'Final change of path0.' \\\n+\t'git add path0 &&\n+\t git commit -m \"Final change of path0\"'\n+\n+test_expect_success \\\n+    'Show the line level log of path0' \\\n+    'git log --pretty=format:%s%n%b -L /func/,/^}/ path0 > current-path0'\n+\n+test_expect_success \\\n+    'Show the line level log of path1' \\\n+    'git log --pretty=format:%s%n%b -L /output/,/^}/ path1 > current-path1'\n+\n+test_expect_success \\\n+\t'Show the line level log of two files' \\\n+    'git log --pretty=format:%s%n%b -L /func/,/^}/ path0 -L /output/,/^}/ path1 > current-pathall'\n+\n+test_expect_success \\\n+\t'Test the line number argument' \\\n+\t'git log --pretty=format:%s%n%b -L 1,2 path0 > current-linenum'\n+\n+test_expect_success \\\n+\t'Test the --always-print option' \\\n+\t'git log --pretty=format:%s%n%b --always-print -L 1,2 path0 > current-always'\n+\n+cat >expected-path0 <<\\EOF\n+Final change of path0\n+\n+diff --git a/path0 b/path0\n+index 44db133..1518c15 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,6 +1,5 @@\n+ void func(){\n+ \tint a = 10;\n+ \tint b = 11;\n+-\tint c;\n+-\tc = 10 * (a + b);\n++\tprintf(\"%d\", a - b);\n+ }\n+\n+Change the 5th line of path0\n+\n+diff --git a/path0 b/path0\n+index 9ef1692..44db133 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,6 +1,6 @@\n+ void func(){\n+ \tint a = 10;\n+ \tint b = 11;\n+ \tint c;\n+-\tc = a + b;\n++\tc = 10 * (a + b);\n+ }\n+\n+Change 2,3 lines of path0 and path1\n+\n+diff --git a/path0 b/path0\n+index aabffdf..9ef1692 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,6 +1,6 @@\n+ void func(){\n+-\tint a = 0;\n+-\tint b = 1;\n++\tint a = 10;\n++\tint b = 11;\n+ \tint c;\n+ \tc = a + b;\n+ }\n+\n+Base commit\n+\n+diff --git a/path0 b/path0\n+new file mode 100644\n+index 0000000..aabffdf\n+--- /dev/null\n++++ b/path0\n+@@ -0,0 +1,6 @@\n++void func(){\n++\tint a = 0;\n++\tint b = 1;\n++\tint c;\n++\tc = a + b;\n++}\n+EOF\n+\n+cat >expected-path1 <<\\EOF\n+Change 2,3 lines of path0 and path1\n+\n+diff --git a/path1 b/path1\n+index 997d841..1d711b5 100644\n+--- a/path1\n++++ b/path1\n+@@ -1,3 +1,4 @@\n+ void output(){\n+-\tprintf(\"hello world\");\n++\tconst char *str = \"hello world!\";\n++\tprintf(\"%s\", str);\n+ }\n+\n+Base commit\n+\n+diff --git a/path1 b/path1\n+new file mode 100644\n+index 0000000..997d841\n+--- /dev/null\n++++ b/path1\n+@@ -0,0 +1,3 @@\n++void output(){\n++\tprintf(\"hello world\");\n++}\n+EOF\n+\n+cat >expected-pathall <<\\EOF\n+Final change of path0\n+\n+diff --git a/path0 b/path0\n+index 44db133..1518c15 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,6 +1,5 @@\n+ void func(){\n+ \tint a = 10;\n+ \tint b = 11;\n+-\tint c;\n+-\tc = 10 * (a + b);\n++\tprintf(\"%d\", a - b);\n+ }\n+\n+Change the 5th line of path0\n+\n+diff --git a/path0 b/path0\n+index 9ef1692..44db133 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,6 +1,6 @@\n+ void func(){\n+ \tint a = 10;\n+ \tint b = 11;\n+ \tint c;\n+-\tc = a + b;\n++\tc = 10 * (a + b);\n+ }\n+\n+Change 2,3 lines of path0 and path1\n+\n+diff --git a/path0 b/path0\n+index aabffdf..9ef1692 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,6 +1,6 @@\n+ void func(){\n+-\tint a = 0;\n+-\tint b = 1;\n++\tint a = 10;\n++\tint b = 11;\n+ \tint c;\n+ \tc = a + b;\n+ }\n+diff --git a/path1 b/path1\n+index 997d841..1d711b5 100644\n+--- a/path1\n++++ b/path1\n+@@ -1,3 +1,4 @@\n+ void output(){\n+-\tprintf(\"hello world\");\n++\tconst char *str = \"hello world!\";\n++\tprintf(\"%s\", str);\n+ }\n+\n+Base commit\n+\n+diff --git a/path0 b/path0\n+new file mode 100644\n+index 0000000..aabffdf\n+--- /dev/null\n++++ b/path0\n+@@ -0,0 +1,6 @@\n++void func(){\n++\tint a = 0;\n++\tint b = 1;\n++\tint c;\n++\tc = a + b;\n++}\n+diff --git a/path1 b/path1\n+new file mode 100644\n+index 0000000..997d841\n+--- /dev/null\n++++ b/path1\n+@@ -0,0 +1,3 @@\n++void output(){\n++\tprintf(\"hello world\");\n++}\n+EOF\n+\n+cat >expected-linenum <<\\EOF\n+Change 2,3 lines of path0 and path1\n+\n+diff --git a/path0 b/path0\n+index aabffdf..9ef1692 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,2 +1,2 @@\n+ void func(){\n+-\tint a = 0;\n++\tint a = 10;\n+\n+Base commit\n+\n+diff --git a/path0 b/path0\n+new file mode 100644\n+index 0000000..aabffdf\n+--- /dev/null\n++++ b/path0\n+@@ -0,0 +1,2 @@\n++void func(){\n++\tint a = 0;\n+EOF\n+\n+cat >expected-always <<\\EOF\n+Final change of path0\n+\n+diff --git a/path0 b/path0\n+index 44db133..1518c15 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,2 +1,2 @@\n+ void func(){\n+ \tint a = 10;\n+\n+Change the 5th line of path0\n+\n+diff --git a/path0 b/path0\n+index 9ef1692..44db133 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,2 +1,2 @@\n+ void func(){\n+ \tint a = 10;\n+\n+Change 2,3 lines of path0 and path1\n+\n+diff --git a/path0 b/path0\n+index aabffdf..9ef1692 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,2 +1,2 @@\n+ void func(){\n+-\tint a = 0;\n++\tint a = 10;\n+\n+Base commit\n+\n+diff --git a/path0 b/path0\n+new file mode 100644\n+index 0000000..aabffdf\n+--- /dev/null\n++++ b/path0\n+@@ -0,0 +1,2 @@\n++void func(){\n++\tint a = 0;\n+EOF\n+\n+test_expect_success \\\n+    'validate the output.' \\\n+    'test_cmp current-path0 expected-path0 &&\n+\t test_cmp current-path1 expected-path1 &&\n+\t test_cmp current-pathall expected-pathall &&\n+\t test_cmp current-linenum expected-linenum &&\n+\t test_cmp current-always expected-always'\n+\n+test_done\ndiff --git a/t/t4302-log-line-merge-history.sh b/t/t4302-log-line-merge-history.sh\nnew file mode 100755\nindex 0000000..02e7439\n--- /dev/null\n+++ b/t/t4302-log-line-merge-history.sh\n@@ -0,0 +1,114 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Bo Yang\n+#\n+\n+test_description='Test git log -L with merge commit\n+\n+'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/diff-lib.sh\n+\n+echo >path0 'void func(){\n+\tprintf(\"hello\");\n+}\n+'\n+\n+test_expect_success \\\n+    'Add path0 and commit.' \\\n+    'git add path0 &&\n+     git commit -m \"Base commit\"'\n+\n+echo >path0 'void func(){\n+\tprintf(\"hello earth\");\n+}\n+'\n+\n+test_expect_success \\\n+    'Change path0 in master.' \\\n+    'git add path0 &&\n+     git commit -m \"Change path0 in master\"'\n+\n+test_expect_success \\\n+\t'Make a new branch from the base commit' \\\n+\t'git checkout -b feature master^'\n+\n+echo >path0 'void func(){\n+\tprint(\"hello moon\");\n+}\n+'\n+\n+test_expect_success \\\n+    'Change path0 in feature.' \\\n+    'git add path0 &&\n+     git commit -m \"Change path0 in feature\"'\n+\n+test_expect_success \\\n+\t'Merge the master to feature' \\\n+\t'! git merge master'\n+\n+echo >path0 'void func(){\n+\tprintf(\"hello earth and moon\");\n+}\n+'\n+\n+test_expect_success \\\n+\t'Resolve the conflict' \\\n+\t'git add path0 &&\n+\t git commit -m \"Merge two branches\"'\n+\n+test_expect_success \\\n+    'Show the line level log of path0' \\\n+    'git log --pretty=format:%s%n%b -L /func/,/^}/ path0 > current'\n+\n+cat >expected <<\\EOF\n+Merge two branches\n+\n+nontrivial merge found\n+path0\n+\n+@@ 2,1 @@\n+ \tprintf(\"hello earth and moon\");\n+\n+\n+Change path0 in master\n+\n+diff --git a/path0 b/path0\n+index f628dea..bef7fa3 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,3 +1,3 @@\n+ void func(){\n+-\tprintf(\"hello\");\n++\tprintf(\"hello earth\");\n+ }\n+\n+Change path0 in feature\n+\n+diff --git a/path0 b/path0\n+index f628dea..a940ef6 100644\n+--- a/path0\n++++ b/path0\n+@@ -1,3 +1,3 @@\n+ void func(){\n+-\tprintf(\"hello\");\n++\tprint(\"hello moon\");\n+ }\n+\n+Base commit\n+\n+diff --git a/path0 b/path0\n+new file mode 100644\n+index 0000000..f628dea\n+--- /dev/null\n++++ b/path0\n+@@ -0,0 +1,3 @@\n++void func(){\n++\tprintf(\"hello\");\n++}\n+EOF\n+test_expect_success \\\n+    'validate the output.' \\\n+    'test_cmp current expected'\n+\n+test_done\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145305","messageId":"1278829141-11900-13-git-send-email-struggleyb.nku@gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v3 13/13] some document update","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-11T06:19:01Z","receivedAt":"2010-07-11T06:19:01Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n Documentation/git-log.txt |   30 ++++++++++++++++++++++++++++++\n 1 files changed, 30 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/git-log.txt b/Documentation/git-log.txt\nindex 0e6ff31..14a9703 100644\n--- a/Documentation/git-log.txt\n+++ b/Documentation/git-log.txt\n@@ -9,6 +9,7 @@ git-log - Show commit logs\n SYNOPSIS\n --------\n 'git log' [<options>] [<since>..<until>] [[\\--] <path>...]\n+'git log' [<options>] -L n,m <path>\n \n DESCRIPTION\n -----------\n@@ -19,6 +20,9 @@ command to control what is shown and how, and options applicable to\n the 'git diff-*' commands to control how the changes\n each commit introduces are shown.\n \n+With '-L' option, the command will help to trace the history of user specified\n+line ranges. It can trace multiple ranges coming from multiple files.\n+\n \n OPTIONS\n -------\n@@ -72,6 +76,32 @@ include::diff-options.txt[]\n \tto be prefixed with \"\\-- \" to separate them from options or\n \trefnames.\n \n+-L <start>,<end>::\n+\tThe line range.  <start> and <end> can take one of these forms:\n+\n+- number\n++\n+If <start> or <end> is a number, it specifies an\n+absolute line number (lines count from 1).\n++\n+\n+- /regex/\n++\n+This form will use the first line matching the given\n+POSIX regex.  If <end> is a regex, it will search\n+starting at the line given by <start>.\n++\n+\n+- +offset or -offset\n++\n+This is only valid for <end> and will specify a number\n+of lines before or after the line given by <start>.\n++\n+\n+--always-print::\n+\tAlways print the interesting range even if the current commit\n+\tdoes not change any line of the range.\n+\n \n include::rev-list-options.txt[]\n \n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145317","messageId":"m3hbk6l901.fsf@localhost.localdomain","threadId":"24364","inReplyTo":"1278829141-11900-13-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 13/13] some document update","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2010-07-11T08:27:04Z","receivedAt":"2010-07-11T08:27:04Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n>  Documentation/git-log.txt |   30 ++++++++++++++++++++++++++++++\n>  1 files changed, 30 insertions(+), 0 deletions(-)\n\n[...]\n> +-L <start>,<end>::\n> +\tThe line range.  <start> and <end> can take one of these forms:\n> +\n> +- number\n> ++\n> +If <start> or <end> is a number, it specifies an\n> +absolute line number (lines count from 1).\n> ++\n> +\n> +- /regex/\n> ++\n> +This form will use the first line matching the given\n> +POSIX regex.  If <end> is a regex, it will search\n> +starting at the line given by <start>.\n> ++\n> +\n> +- +offset or -offset\n> ++\n> +This is only valid for <end> and will specify a number\n> +of lines before or after the line given by <start>.\n> ++\n\nIf the parsing code for -L <start>,<end> is the same for git-blame and\nfor git-log, and therefore documentation is the same or nearly the\nsame for this option, wouldn't it be better to separate this\ndocumentation into separate file, e.g. line-range-option.txt, and\ninclude it both in git-blame and git-log manpages?  If there are minor\ndifferences, they can be covered by ifdefs.\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"145363","messageId":"AANLkTinO9uLByOvUMRf4Y9vcne1jNAfPnvT3Dq0l4xa9@mail.gmail.com","threadId":"24364","inReplyTo":"m3hbk6l901.fsf@localhost.localdomain","subject":"Re: [PATCH v3 13/13] some document update","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-12T14:12:57Z","receivedAt":"2010-07-12T14:12:57Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Hi Jakub,\n\nOn Sun, Jul 11, 2010 at 4:27 PM, Jakub Narebski <jnareb@gmail.com> wrote:\n>\n> If the parsing code for -L <start>,<end> is the same for git-blame and\n> for git-log, and therefore documentation is the same or nearly the\n> same for this option, wouldn't it be better to separate this\n> documentation into separate file, e.g. line-range-option.txt, and\n> include it both in git-blame and git-log manpages?  If there are minor\n> differences, they can be covered by ifdefs.\n>\n\nThanks a lot for your advice, I have revised the patch.\n\n-----------------------------------------------------------\nFrom 88ed88a53d83c2d46fa4917008efadc531ba1068 Mon Sep 17 00:00:00 2001\nFrom: Bo Yang <struggleyb.nku@gmail.com>\nDate: Sat, 26 Jun 2010 01:35:48 -0700\nSubject: [PATCH v3 revised 13/13] some document update\n\nBoth 'git log' and 'git blame' support the same\nformat of '-L' arguments, so put the argument\nformat description into a new file.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n Documentation/blame-options.txt     |   19 +------------------\n Documentation/git-log.txt           |   13 +++++++++++++\n Documentation/line-range-format.txt |   18 ++++++++++++++++++\n 3 files changed, 32 insertions(+), 18 deletions(-)\n create mode 100644 Documentation/line-range-format.txt\n\ndiff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\nindex 16e3c68..3526835 100644\n--- a/Documentation/blame-options.txt\n+++ b/Documentation/blame-options.txt\n@@ -13,24 +13,7 @@\n \tAnnotate only the given line range.  <start> and <end> can take\n \tone of these forms:\n\n-\t- number\n-+\n-If <start> or <end> is a number, it specifies an\n-absolute line number (lines count from 1).\n-+\n-\n-- /regex/\n-+\n-This form will use the first line matching the given\n-POSIX regex.  If <end> is a regex, it will search\n-starting at the line given by <start>.\n-+\n-\n-- +offset or -offset\n-+\n-This is only valid for <end> and will specify a number\n-of lines before or after the line given by <start>.\n-+\n+include::line-range-format.txt[]\n\n -l::\n \tShow long rev (Default: off).\ndiff --git a/Documentation/git-log.txt b/Documentation/git-log.txt\nindex 0e6ff31..4781f8b 100644\n--- a/Documentation/git-log.txt\n+++ b/Documentation/git-log.txt\n@@ -9,6 +9,7 @@ git-log - Show commit logs\n SYNOPSIS\n --------\n 'git log' [<options>] [<since>..<until>] [[\\--] <path>...]\n+'git log' [<options>] -L n,m <path>\n\n DESCRIPTION\n -----------\n@@ -19,6 +20,9 @@ command to control what is shown and how, and\noptions applicable to\n the 'git diff-*' commands to control how the changes\n each commit introduces are shown.\n\n+With '-L' option, the command will help to trace the history of user specified\n+line ranges. It can trace multiple ranges coming from multiple files.\n+\n\n OPTIONS\n -------\n@@ -72,6 +76,15 @@ include::diff-options.txt[]\n \tto be prefixed with \"\\-- \" to separate them from options or\n \trefnames.\n\n+-L <start>,<end>::\n+\tThe line range.  <start> and <end> can take one of these forms:\n+\n+include::line-range-format.txt[]\n+\n+--always-print::\n+\tAlways print the interesting range even if the current commit\n+\tdoes not change any line of the range.\n+\n\n include::rev-list-options.txt[]\n\ndiff --git a/Documentation/line-range-format.txt\nb/Documentation/line-range-format.txt\nnew file mode 100644\nindex 0000000..265bc23\n--- /dev/null\n+++ b/Documentation/line-range-format.txt\n@@ -0,0 +1,18 @@\n+- number\n++\n+If <start> or <end> is a number, it specifies an\n+absolute line number (lines count from 1).\n++\n+\n+- /regex/\n++\n+This form will use the first line matching the given\n+POSIX regex.  If <end> is a regex, it will search\n+starting at the line given by <start>.\n++\n+\n+- +offset or -offset\n++\n+This is only valid for <end> and will specify a number\n+of lines before or after the line given by <start>.\n++\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145364","messageId":"AANLkTim5m2vG3ZiveRcRIGLVszu9mFruTJ2B_6lsqkGH@mail.gmail.com","threadId":"24364","inReplyTo":"1278829141-11900-3-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 03/13] add the basic data structure for line level history","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-12T14:16:35Z","receivedAt":"2010-07-12T14:16:35Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Hi Junio,\n\n    I have make a revised version of this patch to fix a bug of it.\nThat's that I declare a 'unsigned long *ends;\" but allocate memory\nusing \"xmalloc(size*sizeof(int))\" for it. And this new patch fix it.\n\n-------------------------------------------------------------------------\nFrom 7608bb8d67126b6d9d5436f00e8743ac0600db6f Mon Sep 17 00:00:00 2001\nFrom: Bo Yang <struggleyb.nku@gmail.com>\nDate: Fri, 25 Jun 2010 19:37:32 -0700\nSubject: [PATCH v3 revised 03/13] add the basic data structure for\nline level history\n\n'struct diff_line_range' is the main data structure to store\nthe user interesting line range. There is one 'diff_line_range'\nfor each file, and there are multiple 'struct range' in each\n'diff_line_range'. In this way, we support multiple ranges.\n\nWithin 'struct range', there are multiple 'struct print_range'\nwhich represent a diff chunk.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n Makefile   |    2 +\n diffcore.h |    1 +\n line.c     |  456 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n line.h     |  122 ++++++++++++++++\n revision.c |    6 +\n revision.h |    8 +-\n 6 files changed, 593 insertions(+), 2 deletions(-)\n create mode 100644 line.c\n create mode 100644 line.h\n\ndiff --git a/Makefile b/Makefile\nindex 1fadd43..0183a21 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -496,6 +496,7 @@ LIB_H += grep.h\n LIB_H += hash.h\n LIB_H += help.h\n LIB_H += levenshtein.h\n+LIB_H += line.h\n LIB_H += list-objects.h\n LIB_H += ll-merge.h\n LIB_H += log-tree.h\n@@ -582,6 +583,7 @@ LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\n LIB_OBJS += levenshtein.o\n+LIB_OBJS += line.o\n LIB_OBJS += list-objects.o\n LIB_OBJS += ll-merge.o\n LIB_OBJS += lockfile.o\ndiff --git a/diffcore.h b/diffcore.h\nindex 491bea0..13d8e93 100644\n--- a/diffcore.h\n+++ b/diffcore.h\n@@ -23,6 +23,7 @@\n #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */\n\n struct userdiff_driver;\n+struct diff_options;\n\n struct diff_filespec {\n \tunsigned char sha1[20];\ndiff --git a/line.c b/line.c\nnew file mode 100644\nindex 0000000..2f82fd8\n--- /dev/null\n+++ b/line.c\n@@ -0,0 +1,456 @@\n+#include \"line.h\"\n+#include \"cache.h\"\n+#include \"tag.h\"\n+#include \"blob.h\"\n+#include \"tree.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"decorate.h\"\n+#include \"revision.h\"\n+#include \"xdiff-interface.h\"\n+#include \"strbuf.h\"\n+#include \"log-tree.h\"\n+\n+static void cleanup(struct diff_line_range *r)\n+{\n+\twhile (r) {\n+\t\tstruct diff_line_range *next = r->next;\n+\t\tDIFF_LINE_RANGE_CLEAR(r);\n+\t\tfree(r);\n+\t\tr = next;\n+\t}\n+}\n+\n+static struct object *verify_commit(struct rev_info *revs)\n+{\n+\tstruct object *commit = NULL;\n+\tconst char *name = NULL;\n+\tint i;\n+\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 (commit)\n+\t\t\tdie(\"More than one commit to dig from: %s and %s?\",\n+\t\t\t    revs->pending.objects[i].name, name);\n+\t\tcommit = obj;\n+\t\tname = revs->pending.objects[i].name;\n+\t}\n+\n+\tif (commit == NULL)\n+\t\tdie(\"No commit specified?\");\n+\n+\treturn commit;\n+}\n+\n+static void fill_blob_sha1(struct commit *commit, struct diff_line_range *r)\n+{\n+\tunsigned mode;\n+\tunsigned char sha1[20];\n+\n+\twhile (r) {\n+\t\tif (get_tree_entry(commit->object.sha1, r->spec->path,\n+\t\t\tsha1, &mode))\n+\t\t\tgoto error;\n+\t\tfill_filespec(r->spec, sha1, mode);\n+\t\tr = r->next;\n+\t}\n+\n+\treturn;\n+error:\n+\tdie(\"There is no path %s in the commit\", r->spec->path);\n+}\n+\n+static void fill_line_ends(struct diff_filespec *spec, long *lines,\n+\tunsigned long **line_ends)\n+{\n+\tint num = 0, size = 50;\n+\tlong cur = 0;\n+\tunsigned long *ends = NULL;\n+\tchar *data = NULL;\n+\n+\tif (diff_populate_filespec(spec, 0))\n+\t\tdie(\"Cannot read blob %s\", sha1_to_hex(spec->sha1));\n+\n+\tends = xmalloc(size * sizeof(*ends));\n+\tends[cur++] = -1;\n+\tdata = spec->data;\n+\twhile (num < spec->size) {\n+\t\tif (data[num] == '\\n' || num == spec->size - 1) {\n+\t\t\tALLOC_GROW(ends, (cur + 1), size);\n+\t\t\tends[cur++] = num;\n+\t\t}\n+\t\tnum++;\n+\t}\n+\n+\t/* shrink the array to fit the elements */\n+\tends = xrealloc(ends, cur * sizeof(*ends));\n+\t*lines = cur;\n+\t*line_ends = ends;\n+}\n+\n+static const char *nth_line(struct diff_filespec *spec, int line,\n+\t\tlong lines, unsigned long *line_ends)\n+{\n+\tassert(line < lines);\n+\tassert(spec && spec->data);\n+\n+\treturn (char *)spec->data + line_ends[line] + 1;\n+}\n+\n+/*\n+ * copied from blame.c, indeed, we can even to use this to test\n+ * whether line log works. :)\n+ */\n+static const char *parse_loc(const char *spec, struct diff_filespec *file,\n+\t\t\t     long lines, unsigned long *line_ends,\n+\t\t\t     long begin, long *ret)\n+{\n+\tchar *term;\n+\tconst char *line;\n+\tlong num;\n+\tint reg_error;\n+\tregex_t regexp;\n+\tregmatch_t match[1];\n+\n+\t/* Allow \"-L <something>,+20\" to mean starting at <something>\n+\t * for 20 lines, or \"-L <something>,-5\" for 5 lines ending at\n+\t * <something>.\n+\t */\n+\tif (1 < begin && (spec[0] == '+' || spec[0] == '-')) {\n+\t\tnum = strtol(spec + 1, &term, 10);\n+\t\tif (term != spec + 1) {\n+\t\t\tif (spec[0] == '-')\n+\t\t\t\tnum = 0 - num;\n+\t\t\tif (0 < num)\n+\t\t\t\t*ret = begin + num - 2;\n+\t\t\telse if (!num)\n+\t\t\t\t*ret = begin;\n+\t\t\telse\n+\t\t\t\t*ret = begin + num;\n+\t\t\treturn term;\n+\t\t}\n+\t\treturn spec;\n+\t}\n+\tnum = strtol(spec, &term, 10);\n+\tif (term != spec) {\n+\t\t*ret = num;\n+\t\treturn term;\n+\t}\n+\tif (spec[0] != '/')\n+\t\treturn spec;\n+\n+\t/* it could be a regexp of form /.../ */\n+\tfor (term = (char *) spec + 1; *term && *term != '/'; term++) {\n+\t\tif (*term == '\\\\')\n+\t\t\tterm++;\n+\t}\n+\tif (*term != '/')\n+\t\treturn spec;\n+\n+\t/* try [spec+1 .. term-1] as regexp */\n+\t*term = 0;\n+\tbegin--; /* input is in human terms */\n+\tline = nth_line(file, begin, lines, line_ends);\n+\n+\tif (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&\n+\t    !(reg_error = regexec(&regexp, line, 1, match, 0))) {\n+\t\tconst char *cp = line + match[0].rm_so;\n+\t\tconst char *nline;\n+\n+\t\twhile (begin++ < lines) {\n+\t\t\tnline = nth_line(file, begin, lines, line_ends);\n+\t\t\tif (line <= cp && cp < nline)\n+\t\t\t\tbreak;\n+\t\t\tline = nline;\n+\t\t}\n+\t\t*ret = begin;\n+\t\tregfree(&regexp);\n+\t\t*term++ = '/';\n+\t\treturn term;\n+\t}\n+\telse {\n+\t\tchar errbuf[1024];\n+\t\tregerror(reg_error, &regexp, errbuf, 1024);\n+\t\tdie(\"-L parameter '%s': %s\", spec + 1, errbuf);\n+\t}\n+}\n+\n+static void parse_range(long lines, unsigned long *line_ends,\n+\t\tstruct range *r, struct diff_filespec *spec)\n+{\n+\tconst char *term;\n+\n+\tterm = parse_loc(r->arg, spec, lines, line_ends, 1, &r->start);\n+\tif (*term == ',') {\n+\t\tterm = parse_loc(term + 1, spec, lines, line_ends,\n+\t\t\tr->start + 1, &r->end);\n+\t\tif (*term) {\n+\t\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n+\t\t}\n+\t}\n+\n+\tif (*term) {\n+\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n+\t}\n+\n+\tif (r->start > r->end) {\n+\t\tlong tmp = r->start;\n+\t\tr->start = r->end;\n+\t\tr->end = tmp;\n+\t}\n+\n+\tif (r->start < 1)\n+\t\tr->start = 1;\n+\tif (r->end >= lines)\n+\t\tr->end = lines - 1;\n+}\n+\n+static void parse_lines(struct commit *commit, struct diff_line_range *r)\n+{\n+\tint i;\n+\tstruct range *old_range = NULL;\n+\tlong lines = 0;\n+\tunsigned long *ends = NULL;\n+\n+\twhile (r) {\n+\t\tstruct diff_filespec *spec = r->spec;\n+\t\tint num = r->nr;\n+\t\tassert(spec);\n+\t\tfill_blob_sha1(commit, r);\n+\t\told_range = r->ranges;\n+\t\tr->ranges = NULL;\n+\t\tr->nr = r->alloc = 0;\n+\t\tfill_line_ends(spec, &lines, &ends);\n+\t\tfor (i = 0; i < num; i++) {\n+\t\t\tparse_range(lines, ends, old_range + i, spec);\n+\t\t\tdiff_line_range_insert(r, old_range[i].arg,\n+\t\t\t\told_range[i].start, old_range[i].end);\n+\t\t}\n+\n+\t\tfree(ends);\n+\t\tends = NULL;\n+\n+\t\tr = r->next;\n+\t\tfree(old_range);\n+\t}\n+}\n+\n+/*\n+ * Insert a new line range into a diff_line_range struct, and keep the\n+ * r->ranges sorted by their starting line number.\n+ */\n+struct range *diff_line_range_insert(struct diff_line_range *r, const\nchar *arg,\n+\t\t\t\tint start, int end)\n+{\n+\tint i = 0;\n+\tstruct range *rs = r->ranges;\n+\tint left_extend = 0, right_extend = 0;\n+\n+\tassert(r != NULL);\n+\tassert(start <= end);\n+\n+\tif (r->nr == 0 || rs[r->nr - 1].end < start - 1) {\n+\t\tDIFF_LINE_RANGE_GROW(r);\n+\t\trs = r->ranges;\n+\t\tint num = r->nr - 1;\n+\t\trs[num].arg = arg;\n+\t\trs[num].start = start;\n+\t\trs[num].end = end;\n+\t\treturn rs + num;\n+\t}\n+\n+\tfor (; i < r->nr; i++) {\n+\t\tif (rs[i].end < start - 1)\n+\t\t\tcontinue;\n+\t\tif (rs[i].end == start - 1) {\n+\t\t\trs[i].end = end;\n+\t\t\tright_extend = 1;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tassert(rs[i].end > start - 1);\n+\t\tif (rs[i].start <= start) {\n+\t\t\tif (rs[i].end < end) {\n+\t\t\t\trs[i].end = end;\n+\t\t\t\tright_extend = 1;\n+\t\t\t}\n+\t\t\tgoto out;\n+\t\t} else if (rs[i].start <= end + 1) {\n+\t\t\trs[i].start = start;\n+\t\t\tleft_extend = 1;\n+\t\t\tif (rs[i].end < end) {\n+\t\t\t\trs[i].end = end;\n+\t\t\t\tright_extend = 1;\n+\t\t\t}\n+\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tint num = r->nr - i;\n+\t\t\tDIFF_LINE_RANGE_GROW(r);\n+\t\t\trs = r->ranges;\n+\t\t\tmemmove(rs + i + 1, rs + i, num * sizeof(struct range));\n+\t\t\trs[i].arg = arg;\n+\t\t\trs[i].start = start;\n+\t\t\trs[i].end = end;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\tassert(r->nr != i);\n+\tif (left_extend) {\n+\t\tint j = i;\n+\t\tfor (; j > -1; j--) {\n+\t\t\tif (rs[j].end >= rs[i].start - 1)\n+\t\t\t\tif (rs[j].start < rs[i].start)\n+\t\t\t\t\trs[i].start = rs[j].start;\n+\t\t}\n+\t\tmemmove(rs + j + 1, rs + i, (r->nr - i) * sizeof(struct range));\n+\t\tr->nr -= i - j - 1;\n+\t}\n+\tif (right_extend) {\n+\t\tint j = i;\n+\t\tfor (; j < r->nr; j++) {\n+\t\t\tif (rs[j].start <= rs[i].end + 1)\n+\t\t\t\tif (rs[j].end > rs[i].end)\n+\t\t\t\t\trs[i].end = rs[j].end;\n+\t\t}\n+\t\tif (j < r->nr) {\n+\t\t\tmemmove(rs + i + 1, rs + j, (r->nr - j) * sizeof(struct range));\n+\t\t}\n+\t\tr->nr -= j - i - 1;\n+\t}\n+\tassert(r->nr);\n+\n+\treturn rs + i;\n+}\n+\n+void diff_line_range_clear(struct diff_line_range *r)\n+{\n+\tint i = 0, zero = 0;\n+\n+\tfor (; i < r->nr; i++) {\n+\t\tstruct range *rg = r->ranges + i;\n+\t\tRANGE_CLEAR(rg);\n+\t}\n+\n+\tif (r->prev) {\n+\t\tzero = 0;\n+\t\tif (r->prev->count == 1) {\n+\t\t\tzero = 1;\n+\t\t}\n+\t\tfree_filespec(r->prev);\n+\t\tif (zero)\n+\t\t\tr->prev = NULL;\n+\t}\n+\tif (r->spec) {\n+\t\tzero = 0;\n+\t\tif (r->spec->count == 1) {\n+\t\t\tzero = 1;\n+\t\t}\n+\t\tfree_filespec(r->spec);\n+\t\tif (zero)\n+\t\t\tr->spec = NULL;\n+\t}\n+\n+\tr->status = '\\0';\n+\tr->alloc = r->nr = 0;\n+\n+\tif (r->ranges)\n+\t\tfree(r->ranges);\n+\tr->ranges = NULL;\n+\tr->next = NULL;\n+}\n+\n+void diff_line_range_append(struct diff_line_range *r, const char *arg)\n+{\n+\tDIFF_LINE_RANGE_GROW(r);\n+\tr->ranges[r->nr - 1].arg = arg;\n+}\n+\n+struct diff_line_range *diff_line_range_merge(struct diff_line_range *out,\n+\tstruct diff_line_range *other)\n+{\n+\tstruct diff_line_range *one = out, *two = other;\n+\tstruct diff_line_range *pone;\n+\n+\twhile (one) {\n+\t\tstruct diff_line_range *ptwo;\n+\t\ttwo = other;\n+\t\tptwo = other;\n+\t\twhile (two) {\n+\t\t\tif (!strcmp(one->spec->path, two->spec->path)) {\n+\t\t\t\tint i = 0;\n+\t\t\t\tfor (; i < two->nr; i++) {\n+\t\t\t\t\tdiff_line_range_insert(one, NULL,\n+\t\t\t\t\t\ttwo->ranges[i].start,\n+\t\t\t\t\t\ttwo->ranges[i].end);\n+\t\t\t\t}\n+\t\t\t\tif (two == other) {\n+\t\t\t\t\tother = other->next;\n+\t\t\t\t} else {\n+\t\t\t\t\tptwo->next = two->next;\n+\t\t\t\t}\n+\t\t\t\tDIFF_LINE_RANGE_CLEAR(two);\n+\t\t\t\tfree(two);\n+\t\t\t\ttwo = NULL;\n+\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tptwo = two;\n+\t\t\ttwo = two->next;\n+\t\t}\n+\n+\t\tpone = one;\n+\t\tone = one->next;\n+\t}\n+\tpone->next = other;\n+\n+\treturn out;\n+}\n+\n+void add_line_range(struct rev_info *revs, struct commit *commit,\nstruct diff_line_range *r)\n+{\n+\tstruct diff_line_range *ret = NULL;\n+\n+\tif (r != NULL) {\n+\t\tret = lookup_decoration(&revs->line_range, &commit->object);\n+\t\tif (ret != NULL) {\n+\t\t\tdiff_line_range_merge(ret, r);\n+\t\t} else {\n+\t\t\tadd_decoration(&revs->line_range, &commit->object, r);\n+\t\t}\n+\t\tcommit->object.flags |= RANGE_UPDATE;\n+\t}\n+}\n+\n+struct diff_line_range *lookup_line_range(struct rev_info *revs,\nstruct commit *commit)\n+{\n+\tstruct diff_line_range *ret = NULL;\n+\n+\tret = lookup_decoration(&revs->line_range, &commit->object);\n+\treturn ret;\n+}\n+\n+void setup_line(struct rev_info *rev, struct diff_line_range *r)\n+{\n+\tstruct commit *commit = NULL;\n+\tstruct diff_options *opt = &rev->diffopt;\n+\n+\tcommit = (struct commit *)verify_commit(rev);\n+\tparse_lines(commit, r);\n+\n+\tadd_line_range(rev, commit, r);\n+\t/*\n+\t * Note we support -M/-C to detect file rename\n+\t */\n+\topt->nr_paths = 0;\n+\tdiff_tree_release_paths(opt);\n+}\n+\ndiff --git a/line.h b/line.h\nnew file mode 100644\nindex 0000000..a2f8545\n--- /dev/null\n+++ b/line.h\n@@ -0,0 +1,122 @@\n+#ifndef LINE_H\n+#define LINE_H\n+\n+#include \"diffcore.h\"\n+\n+struct rev_info;\n+struct commit;\n+struct diff_line_range;\n+struct diff_options;\n+\n+struct print_range {\n+\tint start, end;\n+\tint pstart, pend;\n+\tint line_added : 1;\n+};\n+\n+struct print_pair {\n+\tint alloc, nr;\n+\tstruct print_range *ranges;\n+};\n+\n+#define PRINT_RANGE_INIT(r) \\\n+\tdo { \\\n+\t\t(r)->line_added = 0; \\\n+\t} while(0);\n+\n+#define PRINT_PAIR_INIT(p) \\\n+\tdo { \\\n+\t\t(p)->alloc = (p)->nr = 0; \\\n+\t\t(p)->ranges = NULL; \\\n+\t} while(0);\n+\n+#define PRINT_PAIR_GROW(p) \\\n+\tdo { \\\n+\t\t(p)->nr ++; \\\n+\t\tALLOC_GROW((p)->ranges, (p)->nr, (p)->alloc); \\\n+\t} while (0);\n+\n+#define PRINT_PAIR_CLEAR(p) \\\n+\tdo { \\\n+\t\t(p)->alloc = (p)->nr = 0; \\\n+\t\tif ((p)->ranges) \\\n+\t\t\tfree((p)->ranges); \\\n+\t\t(p)->ranges = NULL; \\\n+\t} while(0);\n+\n+struct range {\n+\tconst char *arg;\t//The argument to specify this line range\n+\tlong start, end;\t//The start line number, inclusive\n+\tlong pstart, pend;\t//The end line number, inclusive\n+\tstruct print_pair pair;\n+\t\t\t//The changed lines inside this range\n+\tunsigned int diff:1;\n+};\n+\n+struct diff_line_range {\n+\tstruct diff_filespec *prev;\n+\tstruct diff_filespec *spec;\n+\tchar status;\n+\tint alloc;\n+\tint nr;\n+\tstruct range *ranges;\n+\tunsigned int\ttouch:1,\n+\t\t\tdiff:1;\n+\tstruct diff_line_range *next;\n+};\n+\n+#define RANGE_INIT(r) \\\n+\tdo { \\\n+\t\t(r)->arg = NULL; \\\n+\t\t(r)->start = (r)->end = 0; \\\n+\t\t(r)->pstart = (r)->pend = 0; \\\n+\t\tPRINT_PAIR_INIT(&((r)->pair)); \\\n+\t\t(r)->diff = 0; \\\n+\t} while (0);\n+\n+#define RANGE_CLEAR(r) \\\n+\tdo { \\\n+\t\t(r)->arg = NULL; \\\n+\t\t(r)->start = (r)->end = 0; \\\n+\t\t(r)->pstart = (r)->pend = 0; \\\n+\t\tPRINT_PAIR_CLEAR(&r->pair); \\\n+\t\t(r)->diff = 0; \\\n+\t} while(0);\n+\n+#define DIFF_LINE_RANGE_INIT(r) \\\n+\tdo { \\\n+\t\t(r)->prev = (r)->spec = NULL; \\\n+\t\t(r)->status = '\\0'; \\\n+\t\t(r)->alloc = (r)->nr = 0; \\\n+\t\t(r)->ranges = NULL; \\\n+\t\t(r)->next = NULL; \\\n+\t\t(r)->touch = 0; \\\n+\t\t(r)->diff = 0; \\\n+\t} while(0);\n+\n+#define DIFF_LINE_RANGE_GROW(r) \\\n+\tdo { \\\n+\t\t(r)->nr ++; \\\n+\t\tALLOC_GROW((r)->ranges, (r)->nr, (r)->alloc); \\\n+\t\tRANGE_INIT(((r)->ranges + (r)->nr - 1)); \\\n+\t} while(0);\n+\n+#define DIFF_LINE_RANGE_CLEAR(r) \\\n+\tdiff_line_range_clear((r));\n+\n+extern struct range *diff_line_range_insert(struct diff_line_range\n*r, const char *arg,\n+\t\t\t\tint start, int end);\n+extern void diff_line_range_append(struct diff_line_range *r, const char *arg);\n+\n+extern void diff_line_range_clear(struct diff_line_range *r);\n+\n+extern struct diff_line_range *diff_line_range_merge(struct\ndiff_line_range *out,\n+\tstruct diff_line_range *other);\n+\n+extern void setup_line(struct rev_info *rev, struct diff_line_range *r);\n+\n+extern void add_line_range(struct rev_info *revs, struct commit\n*commit, struct diff_line_range *r);\n+\n+extern struct diff_line_range *lookup_line_range(struct rev_info\n*revs, struct commit *commit);\n+\n+#endif\ndiff --git a/revision.c b/revision.c\nindex 7e82efd..63f37ea 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1637,6 +1637,12 @@ int setup_revisions(int argc, const char\n**argv, struct rev_info *revs, struct s\n \tif (revs->combine_merges)\n \t\trevs->ignore_merges = 0;\n \trevs->diffopt.abbrev = revs->abbrev;\n+\n+\tif (revs->line) {\n+\t\trevs->limited = 1;\n+\t\trevs->topo_order = 1;\n+\t}\n+\n \tif (diff_setup_done(&revs->diffopt) < 0)\n \t\tdie(\"diff_setup_done failed\");\n\ndiff --git a/revision.h b/revision.h\nindex 36fdf22..55836e0 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -14,7 +14,8 @@\n #define CHILD_SHOWN\t(1u<<6)\n #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n #define SYMMETRIC_LEFT\t(1u<<8)\n-#define ALL_REV_FLAGS\t((1u<<9)-1)\n+#define RANGE_UPDATE\t(1u<<9) /* for line level traverse */\n+#define ALL_REV_FLAGS\t((1u<<10)-1)\n\n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n@@ -68,7 +69,8 @@ struct rev_info {\n \t\t\tcherry_pick:1,\n \t\t\tbisect:1,\n \t\t\tancestry_path:1,\n-\t\t\tfirst_parent_only:1;\n+\t\t\tfirst_parent_only:1,\n+\t\t\tline:1;\n\n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n@@ -137,6 +139,8 @@ struct rev_info {\n \t/* commit counts */\n \tint count_left;\n \tint count_right;\n+\t/* line-level intersting range */\n+\tstruct decoration line_range;\n };\n\n #define REV_TREE_SAME\t\t0\n-- \n1.7.0.2.273.gc2413.dirty\n"},{"id":"145377","messageId":"7v630kd4ym.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-1-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 01/13] parse-options: stop when encounter a non-option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T16:45:21Z","receivedAt":"2010-07-12T16:45:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> Subject: Re: [PATCH v3 01/13] parse-options: stop when encounter a non-option\n>\n> We support the syntax like:\n> -L n1,m1 pathspec1 -L n2,m2 pathspec2.\n>\n> Make the parse-options API not stop when encounter a\n> non-option argument, report the status and go on parsing\n> the remain options.\n\n-ECANTPARSE\n\nRead the above again and wonder...\n\nDoes it stop, like the subject line says, or does it not stop???\n\nPerhaps you meant to say something like this...\n\n  Subject: [PATCH v3 01/13] parse-options: enhance STOP_AT_NON_OPTION\n\n  Make parse_options_step() report PARSE_OPT_NON_OPTION, instead of\n  PARSE_OPT_DONE, to the caller, when it sees a non-option argument.  \n\n  This will help implementing a nonstandard option syntax that takes more\n  than one parameters to an option, e.g.\n\n      -L n1,m1 pathspec1 -L n2,m2 pathspec2\n\n  by directly calling parse_options_step().  The parse_options() API only\n  calls parse_options_step() once, and its callers are not affected by\n  this change.\n\n  Thanks-to: Jonathan Nieder <jrnieder@gmail.com>\n  Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n\nCurrently blame and shortlog seems to use parse_options_step() but neither\nof them uses STOP_AT_NON_OPTION, so this change shouldn't break them.\n\n>\n> Thanks-to: Jonathan Nieder <jrnieder@gmail.com>\n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> ---\n>  parse-options.c |    3 ++-\n>  parse-options.h |    3 ++-\n>  2 files changed, 4 insertions(+), 2 deletions(-)\n>\n> diff --git a/parse-options.c b/parse-options.c\n> index 0fa79bc..cbb49d3 100644\n> --- a/parse-options.c\n> +++ b/parse-options.c\n> @@ -374,7 +374,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,\n>  \t\t\tif (parse_nodash_opt(ctx, arg, options) == 0)\n>  \t\t\t\tcontinue;\n>  \t\t\tif (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)\n> -\t\t\t\tbreak;\n> +\t\t\t\treturn PARSE_OPT_NON_OPTION;\n>  \t\t\tctx->out[ctx->cpidx++] = ctx->argv[0];\n>  \t\t\tcontinue;\n>  \t\t}\n> @@ -456,6 +456,7 @@ int parse_options(int argc, const char **argv, const char *prefix,\n>  \tswitch (parse_options_step(&ctx, options, usagestr)) {\n>  \tcase PARSE_OPT_HELP:\n>  \t\texit(129);\n> +\tcase PARSE_OPT_NON_OPTION:\n>  \tcase PARSE_OPT_DONE:\n>  \t\tbreak;\n>  \tdefault: /* PARSE_OPT_UNKNOWN */\n> diff --git a/parse-options.h b/parse-options.h\n> index 7435cdb..407697a 100644\n> --- a/parse-options.h\n> +++ b/parse-options.h\n> @@ -161,7 +161,8 @@ extern NORETURN void usage_msg_opt(const char *msg,\n>  enum {\n>  \tPARSE_OPT_HELP = -1,\n>  \tPARSE_OPT_DONE,\n> -\tPARSE_OPT_UNKNOWN\n> +\tPARSE_OPT_NON_OPTION,\n> +\tPARSE_OPT_UNKNOWN,\n>  };\n>  \n>  /*\n> -- \n> 1.7.0.2.273.gc2413.dirty\n"},{"id":"145378","messageId":"7vy6dgbqdc.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-2-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 02/13] parse-options: add two helper functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T16:45:51Z","receivedAt":"2010-07-12T16:45:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> +int parse_options_next(struct parse_opt_ctx_t *ctx, int retain)\n\nI think the latter is usually called \"keep\".\n\n> +{\n> +\tif (ctx->argc <= 0)\n> +\t\treturn -1;\n> +\n> +\tif (retain == 1)\n\nShould this really check for \"1\", or would\n\n\tif (keep)\n\nbe more conventional?\n"},{"id":"145380","messageId":"7vr5j8bq5x.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"AANLkTim5m2vG3ZiveRcRIGLVszu9mFruTJ2B_6lsqkGH@mail.gmail.com","subject":"Re: [PATCH v3 03/13] add the basic data structure for line level history","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T16:50:18Z","receivedAt":"2010-07-12T16:50:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> Subject: [PATCH v3 revised 03/13] add the basic data structure for line level history\n>\n> 'struct diff_line_range' is the main data structure to store\n> the user interesting line range. There is one 'diff_line_range'\n\n-ECANTPARSE. Perhaps \"to keep track of the line ranges the user is\ninterested in\"?\n\nIs it the end user, or the code, that is interested in the range recorded\nin this structure?  If you are adjusting the line range while traversing\nthe history, then it is more of the latter; i.e. \"the user originally\nstarted digging from this range, but after examining the diff that affect\nthat range by this commit, we found that the range corresponds to this\nwidened/narrowed range in the history leading to the commit, so we will\nupdate the range and use it from now on until we hit another commit that\naffects this range\".  In that case \"to keep track of the line ranges we\nare currently interested in\" would be more appropriate than \"the ranges\nthe user is interested in\".\n\n> for each file, and there are multiple 'struct range' in each\n> 'diff_line_range'. In this way, we support multiple ranges.\n>\n> Within 'struct range', there are multiple 'struct print_range'\n> which represent a diff chunk.\n\nWe call them \"hunks\" not \"chunks\", I think.\n\n> diff --git a/diffcore.h b/diffcore.h\n> index 491bea0..13d8e93 100644\n> --- a/diffcore.h\n> +++ b/diffcore.h\n> @@ -23,6 +23,7 @@\n>  #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */\n>\n>  struct userdiff_driver;\n> +struct diff_options;\n\nWhy???\n\n>  struct diff_filespec {\n>  \tunsigned char sha1[20];\n\n> diff --git a/line.c b/line.c\n> new file mode 100644\n> index 0000000..2f82fd8\n> --- /dev/null\n> +++ b/line.c\n> @@ -0,0 +1,456 @@\n> ...\n> +\n> +static struct object *verify_commit(struct rev_info *revs)\n> +{\n> +\tstruct object *commit = NULL;\n> +\tconst char *name = NULL;\n\nWhy not use \"int found = -1\" instead and point at the commit in the\npending.objects[] array you found with that variable?  Or use a variable\nof the type of a pointer to an element in the pending.objects[] array,\ninitialized to NULL.\n\nThat way, you do not have to tell stupid compilers that name is set once\ncommit is set to avoid warnings, which is the only reason you initialize\nname to NULL.\n\n> +\tint i;\n> +\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 (commit)\n> +\t\t\tdie(\"More than one commit to dig from: %s and %s?\",\n> +\t\t\t    revs->pending.objects[i].name, name);\n> +\t\tcommit = obj;\n> +\t\tname = revs->pending.objects[i].name;\n> +\t}\n> +\n> +\tif (commit == NULL)\n> +\t\tdie(\"No commit specified?\");\n> +\n> +\treturn commit;\n> +}\n> +\n> +static void fill_blob_sha1(struct commit *commit, struct diff_line_range *r)\n> +{\n> +\tunsigned mode;\n> +\tunsigned char sha1[20];\n> +\n> +\twhile (r) {\n> +\t\tif (get_tree_entry(commit->object.sha1, r->spec->path,\n> +\t\t\tsha1, &mode))\n> +\t\t\tgoto error;\n> +\t\tfill_filespec(r->spec, sha1, mode);\n> +\t\tr = r->next;\n> +\t}\n> +\n> +\treturn;\n> +error:\n> +\tdie(\"There is no path %s in the commit\", r->spec->path);\n> +}\n\nThese two look vaguely familiar...  Cut and paste without refactoring?\n\n> +static void fill_line_ends(struct diff_filespec *spec, long *lines,\n> +\tunsigned long **line_ends)\n> +{\n> +\tint num = 0, size = 50;\n> +\tlong cur = 0;\n> +\tunsigned long *ends = NULL;\n> +\tchar *data = NULL;\n> +\n> +\tif (diff_populate_filespec(spec, 0))\n> +\t\tdie(\"Cannot read blob %s\", sha1_to_hex(spec->sha1));\n> +\n> +\tends = xmalloc(size * sizeof(*ends));\n> +\tends[cur++] = -1;\n\nWasn't this an array of unsigned longs?\n\n> +static const char *nth_line(struct diff_filespec *spec, int line,\n> +\t\tlong lines, unsigned long *line_ends)\n> +{\n> +\tassert(line < lines);\n> +\tassert(spec && spec->data);\n\nWhy aren't \"line\" and \"lines\" of the same type?\n\n> +static void parse_range(long lines, unsigned long *line_ends,\n> +\t\tstruct range *r, struct diff_filespec *spec)\n> +{\n> +\tconst char *term;\n> +\n> +\tterm = parse_loc(r->arg, spec, lines, line_ends, 1, &r->start);\n> +\tif (*term == ',') {\n> +\t\tterm = parse_loc(term + 1, spec, lines, line_ends,\n> +\t\t\tr->start + 1, &r->end);\n> +\t\tif (*term) {\n> +\t\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n> +\t\t}\n\nExcess {} around a single statement.\n\n> +struct range *diff_line_range_insert(struct diff_line_range *r, const\n> char *arg,\n> +\t\t\t\tint start, int end)\n> +{\n> +\tint i = 0;\n> +\tstruct range *rs = r->ranges;\n> +\tint left_extend = 0, right_extend = 0;\n> +\n> +\tassert(r != NULL);\n> +\tassert(start <= end);\n> +\n> +\tif (r->nr == 0 || rs[r->nr - 1].end < start - 1) {\n> +\t\tDIFF_LINE_RANGE_GROW(r);\n> +\t\trs = r->ranges;\n> +\t\tint num = r->nr - 1;\n\ndecl-after-statement\n\n> ...\n> +out:\n> +\tassert(r->nr != i);\n> +\tif (left_extend) {\n\nSounds more like a \"merge\" than \"extend\" to me...\n\n> diff --git a/line.h b/line.h\n> new file mode 100644\n> index 0000000..a2f8545\n> --- /dev/null\n> +++ b/line.h\n> @@ -0,0 +1,122 @@\n> ...\n> +#define PRINT_RANGE_INIT(r) \\\n> +\tdo { \\\n> +\t\t(r)->line_added = 0; \\\n> +\t} while(0);\n\n\t} while (0)\n\n- No semicolon after a macro (the user will give it to us).\n- A SP after \"while\".\n\nDoes this even have to be \"do { ... } while (0)\"???\n\n> +\n> +#define PRINT_PAIR_INIT(p) \\\n> +\tdo { \\\n> +\t\t(p)->alloc = (p)->nr = 0; \\\n> +\t\t(p)->ranges = NULL; \\\n> +\t} while(0);\n> +\n> +#define PRINT_PAIR_GROW(p) \\\n> +\tdo { \\\n> +\t\t(p)->nr ++; \\\n\n\t(p)->nr++;\n\n> +\t\tALLOC_GROW((p)->ranges, (p)->nr, (p)->alloc); \\\n> +\t} while (0);\n> +\n> +#define PRINT_PAIR_CLEAR(p) \\\n> +\tdo { \\\n> +\t\t(p)->alloc = (p)->nr = 0; \\\n> +\t\tif ((p)->ranges) \\\n> +\t\t\tfree((p)->ranges); \\\n> +\t\t(p)->ranges = NULL; \\\n> +\t} while(0);\n> +\n> +struct range {\n> +\tconst char *arg;\t//The argument to specify this line range\n\nNo C++/C99 comments.\n\nDo you need to keep track of this string representation once you have\nparsed the user input and your main computation have started?\n\nIsn't \"range\" too generic a term?   Unless you make this as a static\ndeclaration only visible to functions where \"range\" can only mean \"line\nranges\" in their context, that is.\n\n> +#define RANGE_INIT(r) \\\n\nSame comment for the name possibly being too generic.\n\n> +extern struct range *diff_line_range_insert(struct diff_line_range\n> *r, const char *arg,\n\nLinewrapped?\n\n> diff --git a/revision.c b/revision.c\n> index 7e82efd..63f37ea 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -1637,6 +1637,12 @@ int setup_revisions(int argc, const char\n> **argv, struct rev_info *revs, struct s\n>  \tif (revs->combine_merges)\n>  \t\trevs->ignore_merges = 0;\n>  \trevs->diffopt.abbrev = revs->abbrev;\n> +\n> +\tif (revs->line) {\n\n\"line\" what?\n\n> diff --git a/revision.h b/revision.h\n> index 36fdf22..55836e0 100644\n> --- a/revision.h\n> +++ b/revision.h\n> @@ -14,7 +14,8 @@\n>  #define CHILD_SHOWN\t(1u<<6)\n>  #define ADDED\t\t(1u<<7)\t/* Parents already parsed and added? */\n>  #define SYMMETRIC_LEFT\t(1u<<8)\n> -#define ALL_REV_FLAGS\t((1u<<9)-1)\n> +#define RANGE_UPDATE\t(1u<<9) /* for line level traverse */\n> +#define ALL_REV_FLAGS\t((1u<<10)-1)\n>\n>  #define DECORATE_SHORT_REFS\t1\n>  #define DECORATE_FULL_REFS\t2\n> @@ -68,7 +69,8 @@ struct rev_info {\n>  \t\t\tcherry_pick:1,\n>  \t\t\tbisect:1,\n>  \t\t\tancestry_path:1,\n> -\t\t\tfirst_parent_only:1;\n> +\t\t\tfirst_parent_only:1,\n> +\t\t\tline:1;\n\nIs this really a traversal flag that affects how the history is walked?\n\n>  \t/* Diff flags */\n>  \tunsigned int\tdiff:1,\n> @@ -137,6 +139,8 @@ struct rev_info {\n>  \t/* commit counts */\n>  \tint count_left;\n>  \tint count_right;\n> +\t/* line-level intersting range */\n> +\tstruct decoration line_range;\n\ns/intersting//; or perhaps \"line level range that we are chasing\"\n\n>  };\n>\n>  #define REV_TREE_SAME\t\t0\n> -- \n> 1.7.0.2.273.gc2413.dirty\n"},{"id":"145388","messageId":"7vk4p0blfx.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-4-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 04/13] refactor parse_loc","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T18:32:18Z","receivedAt":"2010-07-12T18:32:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> Both 'git blame -L' and 'git log -L' parse the same style\n> of line number arguments, so put the 'parse_loc' function\n> to line.c and export it.\n\n> The caller of parse_loc should provide a callback function\n> which is used to calculate the nth line start position.\n\n\"the start position of the nth line\"?\n\n> Other parts such as regexp search, line number parsing are\n> abstracted and re-used.\n>\n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> ---\n>  builtin/blame.c |   89 +++++-------------------------------------------------\n>  line.c          |   35 ++++++++++++---------\n>  line.h          |    5 +++\n>  3 files changed, 34 insertions(+), 95 deletions(-)\n>\n> diff --git a/builtin/blame.c b/builtin/blame.c\n> index 01e62fd..17b71cd 100644\n> --- a/builtin/blame.c\n> +++ b/builtin/blame.c\n> @@ -21,6 +21,7 @@\n\nNice code reduction.  The abstraction feels right (but I didn't read it\nvery carefully).\n"},{"id":"145390","messageId":"7v1vb8bktw.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"AANLkTinO9uLByOvUMRf4Y9vcne1jNAfPnvT3Dq0l4xa9@mail.gmail.com","subject":"Re: [PATCH v3 13/13] some document update","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T18:45:31Z","receivedAt":"2010-07-12T18:45:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> ...\n> Thanks a lot for your advice, I have revised the patch.\n>\n> -----------------------------------------------------------\n>>From 88ed88a53d83c2d46fa4917008efadc531ba1068 Mon Sep 17 00:00:00 2001\n> From: Bo Yang <struggleyb.nku@gmail.com>\n> Date: Sat, 26 Jun 2010 01:35:48 -0700\n> Subject: [PATCH v3 revised 13/13] some document update\n>\n> Both 'git log' and 'git blame' support the same\n> format of '-L' arguments, so put the argument\n> format description into a new file.\n>\n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> ---\n>  Documentation/blame-options.txt     |   19 +------------------\n>  Documentation/git-log.txt           |   13 +++++++++++++\n>  Documentation/line-range-format.txt |   18 ++++++++++++++++++\n>  3 files changed, 32 insertions(+), 18 deletions(-)\n\nEvery time you do this, you seem to send a corrupt patch, like...\n\n> @@ -19,6 +20,9 @@ command to control what is shown and how, and\n> options applicable to\n> ...\n> diff --git a/Documentation/line-range-format.txt\n> b/Documentation/line-range-format.txt\n\nPlease don't.\n"},{"id":"145396","messageId":"7v4og49xmg.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-7-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 07/13] add range clone functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T21:52:07Z","receivedAt":"2010-07-12T21:52:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> Both single range clone and deeply clone are supported.\n\nPlease explain what you mean by \"deeply clone\" (no, not just a language\nnitpick that it should at least be \"deep clone\"), and document which one\nneeds to be used in what situations to help people who would want to\ndecide which of these functions to call.\n\nWhat I am trying to get at is that you might want to have only one kind of\nclone that is _semantically_ deep (i.e. the list is copied, but the\nelements are shared with refcounting), but makes a copy of the element\nthat is being updated on demand (i.e.  have an API function to allow users\nto modify, which internally does copy-on-write if it is still shared).  I\ndon't know that kind of arrangement makes sense; it depends on how the\ncloned ranges are used but I haven't looked at the callers.\n"},{"id":"145398","messageId":"7vy6dg8j1w.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-8-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 08/13] map/take range to parent","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T21:52:11Z","receivedAt":"2010-07-12T21:52:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> The interesting line range will be passed to all of its parents.\n\nThis needs too much context to understand what you are saying, doesn't it?\nWhat is \"it\" (presumably \"a commit\" but that is merely a guess from the\nuse of word \"parents\" and in git the only thing that has parents are\ncommits)?  In what context is this \"passing\" happen (presumably while\ndigging the history with specific line ranges in mind, but the Subject\nline does not even give any useful hint)?\n\n> For non-merge commit, we just map_range the ranges. Generally, the\n> algorithm do:\n\ns/do//;\n\n>\n> 1. Run a diffcore_std to find out the pairs;\n\ns/Run/Runs/; ( won't do any more language nitpicks from now on in this\nmessage.\n\n> 2. Run a xdi_diff_hunks on each interesting file pair;\n> 3. The map_range_cb callback will be invoked for each diff hunk,\n>    and in the function map_lines we will calculate the pre-image\n>    range from the post-image range.\n>\n> For merge commit, another take_range pass will be done except the\n> above normal map_range work. It is used to subtract each same\n> part lines of the current range out. After this pass, if there is\n> any line range left, this means the merge must be a non-trivial\n> merge. This is how the non-trivial merge detect work.\n>\n> The algorithm that map lines from post-image to pre-image is in\n> the function map_lines. Generally, we use simple line number\n> calculation method to do the map.\n>\n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> ---\n>  line.c     |  453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  revision.h |    5 +-\n>  2 files changed, 457 insertions(+), 1 deletions(-)\n>\n> diff --git a/line.c b/line.c\n> index ba96d7d..e1af2f6 100644\n> --- a/line.c\n> +++ b/line.c\n> @@ -498,3 +498,456 @@ void setup_line(struct rev_info *rev, struct diff_line_range *r)\n>  \tdiff_tree_release_paths(opt);\n>  }\n>  \n> +struct take_range_cb_data {\n> +\tstruct diff_line_range *interesting;\n> +\tstruct diff_line_range *range;\n> +\tlong plno, tlno;\n\nWhat does \"p\" and \"t\" stand for?\n\n> +\tint diff;\n> +};\n> +\n> +#define SCALE_FACTOR 4\n> +void map_lines(long p_start, long p_end, long t_start, long t_end,\n> +\t\tlong start, long end, long *o_start, long *o_end)\n\nWhat does \"p\", \"t\", \"o\" and \"\" stand for?\n\n> +{\n> +\t/*\n> +\t * xdiff always assign 'same' with the last line number of two\n> +\t * same ranges. When some lines are added from scratch,\n> +\t * p_start = same + 1;\n> +\t * p_end = same;\n> +\t * so, we can understand the following condition.\n\nNot me, sorry.\n\n> +\t */\n> +\tif (p_start > p_end) {\n> +\t\t*o_start = *o_end = 0;\n> +\t\treturn;\n> +\t}\n> +\t/* A deletion */\n> +\tif (t_start > t_end) {\n> +\t\t*o_start = p_start;\n> +\t\t*o_end = p_end;\n> +\t\treturn;\n> +\t}\n> +\n> +\tif (start == t_start && end == t_end) {\n> +\t\t*o_start = p_start;\n> +\t\t*o_end = p_end;\n> +\t\treturn;\n> +\t}\n> +\n> +\t/*\n> +\t * A new strategy for lines mapping:\n\nNew compared to which old one?  I think s/strategy/heuristic/ might be a\nbetter wording here.\n\n> +\t * When the pre-image is no more than 1/4 of the post-image, it\n> +\t * take no sense to say what the real pre-image is, wan can just\n> +\t * treat all the post-image lines as added from scratch.\n\nWhat kind of weird result would you get if you do not use this heuristic?\nI am guessing the rationale is something like \"If we consider too small\ncontributions from the preimage in a hunk, we end up splitting the hunk\ninto very many smaller pieces and will keep digging into older commits.\nThe result will show commits that add a line here and another line there\nwithout a real impact to the resulting logic.  It is easier to read the\nlogic if we stop traversal at a commit that adds large material relative\nto the original to a hunk.\" but it is unclear.\n\n> +\t */\n> +\tif (SCALE_FACTOR * (p_end - p_start + 1) < (t_end - t_start + 1)) {\n> +\t\t*o_start = *o_end = 0;\n> +\t\treturn;\n> +\t}\n> ...\n> +static void map_range(struct take_range_cb_data *data, int same,\n> +\t\tlong p_start, long p_end, long t_start, long t_end)\n> +{\n> ...\n> +\t\tif (added) {\n> +\t\t\t/* Code movement/copy detect here */\nThis probably will not even compile.\n> +\t\t} else {\n> ...\n\n> +static void take_range_cb(void *data, long same, long p_next, long t_next)\n> +{\n> +\tstruct take_range_cb_data *d = data;\n> +\tlong p_start = d->plno + 1, t_start = d->tlno + 1;\n> +\tlong p_end = p_start + same - t_start, t_end = same;\n> +\n> +\t/* If one file is added from scratch, this may confuse take_range */\n> +\tif (t_end >= t_start)\n> +\t\ttake_range(d, p_start, p_end, t_start, t_end);\n\nIs this a \"FIXME\" comment, or is it justifying the if statement?\n\n> +static void assign_range_to_parent(struct rev_info *rev, struct commit *c,\n> +\t\tstruct commit *p, struct diff_line_range *r,\n> +\t\tstruct diff_options *opt, int map)\n> +{\n> ...\n> +\txpp.flags = XDF_NEED_MINIMAL;\n\nDo you need minimal?\n\n> +\t\t/*\n> +\t\t * The remain part is the same part.\n> +\t\t * Instead of calculating the true line number of the two files,\n> +\t\t * use the biggest integer.\n> +\t\t */\n> +\t\tif (map)\n> +\t\t\tmap_range(&cb, 1, cb.plno + 1, 0x7FFFFFFF, cb.tlno + 1, 0x7FFFFFFF);\n> +\t\telse\n> +\t\t\ttake_range(&cb, cb.plno + 1, 0x7FFFFFFF, cb.tlno + 1, 0x7FFFFFFF);\n> +\t}\n\nDo you mean INT_MAX or something (I didn't bother to check the type you\nare using)?\n\nAs there is no good explanation of parameters to map/take_range(), it is\nhard to see if an arbitrary large number like this is correct, and if it\nwere correct, then perhaps it means the *_end paramters are not useful to\nthese functions?  I dunno.\n"},{"id":"145397","messageId":"7vsk3o8j1q.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-9-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 09/13] print the line log","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T21:52:17Z","receivedAt":"2010-07-12T21:52:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> +static void flush_lines(struct diff_options *opt, const char **ptr, const char *end,\n> +\t\tint slno, int elno, int *lno, const char *color, const char heading)\n> +{\n> +\tconst char *p = *ptr;\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tconst char *reset;\n> +\n> +\tif (strcmp(color, \"\"))\n\nDid you mean \"if (*color)\"?\n\n> +\t/*\n> +\t * todo: when --graph landed on master, this should be changed\n> +\t * a little.\n> +\t */\n\nHmm, don't we already have it?\n\n> +static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *range)\n> +{\n> +...\n> +}\n\nLooks suspiciously familiar...  Cut-and-paste without refactoring?\n"},{"id":"145399","messageId":"7vmxtw8j1k.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-10-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 10/13] map/print ranges along traversing the history topologically","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-12T21:52:23Z","receivedAt":"2010-07-12T21:52:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> Since ranges may change in different branches, we should\n> make sure we do not pass range to parent until all the\n> ranges get 'combined' at the commit which is a split commit.\n> So, topological traversing is necessary.\n\nWithout explaining what a \"split commit\" is, \"So, ... is necessary\"\ndoesn't really justify this.\n\n>\n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> ---\n>  builtin/log.c |    5 ++++-\n>  line.c        |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  line.h        |    2 ++\n>  3 files changed, 60 insertions(+), 1 deletions(-)\n>\n> diff --git a/builtin/log.c b/builtin/log.c\n> index 1e90b03..47b386d 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -615,7 +615,10 @@ int cmd_log(int argc, const char **argv, const char *prefix)\n>  \tmemset(&opt, 0, sizeof(opt));\n>  \topt.def = \"HEAD\";\n>  \tcmd_log_init(argc, argv, prefix, &rev, &opt);\n> -\treturn cmd_log_walk(&rev);\n> +\tif (rev.line)\n> +\t\treturn cmd_line_log_walk(&rev);\n> +\telse\n> +\t\treturn cmd_log_walk(&rev);\n>  }\n>  \n>  /* format-patch */\n> diff --git a/line.c b/line.c\n> index 8813376..5c8f77a 100644\n> --- a/line.c\n> +++ b/line.c\n> @@ -1196,3 +1196,57 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\n>  \t}\n>  }\n>  \n> +int cmd_line_log_walk(struct rev_info *rev)\n> +{\n> +\tstruct commit *commit;\n> +\tstruct commit_list *list = NULL;\n> +\tstruct diff_line_range *r = NULL;\n> +\n> +\tif (prepare_revision_walk(rev))\n> +\t\tdie(\"revision walk prepare failed\");\n> +\n> +\tlist = rev->commits;\n> +\tif (list) {\n> +\t\tlist->item->object.flags |= RANGE_UPDATE;\n> +\t\tlist = list->next;\n> +\t}\n> +\t/* Clear the flags */\n> +\twhile (list) {\n> +\t\tlist->item->object.flags &= 0x0;\n\nJust assign \"= 0\" instead, please.\n\nBut more importantly, why is it safe to clear all the flags?  Even if your\ntraversal is limited (i.e. prepare_revision_walk() already walked\neverything), at least wouldn't you need to keep flags like SYMMETRIC_LEFT,\nUNINTERESTING, BOUNDARY etc. depending on what the end user asked from the\ncommand line?\n\nIf you ask prepare_revison_walk()->limit_list() callchain to run a\nlimited and topo-sorted traversal, wouldn't the resulting revs->list\nhave commits in the order you need already?  Why do you need to have a\ncustom walker here?\n\nI am not suggesting to roll this logic into prepare_revision_walk() and\nlimit_list(); there are a lot more than \"we need topological order\" going\non in this function, and the log message needs to explain what they are.\n"},{"id":"145457","messageId":"7v8w5f3ysg.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-11-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 11/13] add --always-print option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-13T20:35:43Z","receivedAt":"2010-07-13T20:35:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> Subject: Re: [PATCH v3 11/13] add --always-print option\n\nPlease be always careful about your Subject: line.  Imagine the above\nappears in the shortlog output for v1.7.8..v1.7.9 among 500 other commits;\ndo you expect people to be able to tell what this commit is about?\n\n> Always print the interesting ranges even if the current\n> commit does not change any line of it.\n\nPlease explain why it is a good thing to do so.\n"},{"id":"145485","messageId":"7vbpab2de5.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"1278829141-11900-5-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-13T23:03:14Z","receivedAt":"2010-07-13T23:03:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> +static int log_line_range_callback(const struct option *option, const char *arg, int unset)\n> +{\n> +\tstruct line_opt_callback_data *data = option->value;\n> +\tstruct diff_line_range *r = *data->range;\n> +\tstruct parse_opt_ctx_t *ctx = data->ctx;\n\nNeed a blank line here for readability.\n\n> +\tif (!arg)\n> +\t\treturn -1;\n> ...\n> @@ -75,6 +118,58 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,\n>  \t */\n>  \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>  \t\tusage(builtin_log_usage);\n> +\n> +\tparse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |\n> +\t\t\tPARSE_OPT_KEEP_ARGV0 | PARSE_OPT_STOP_AT_NON_OPTION);\n> +\tfor (;;) {\n> +\t\tswitch (parse_options_step(&ctx, options, log_opt_usage)) {\n> +\t\tcase PARSE_OPT_HELP:\n> +\t\t\texit(129);\n> +\t\tcase PARSE_OPT_DONE:\n> +\t\t\tgoto parse_done;\n> +\t\tcase PARSE_OPT_NON_OPTION:\n> +\t\t\tpath = parse_options_current(&ctx);\n> +\t\t\tpathspec = prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n> +\t\t\trange->spec = alloc_filespec(pathspec);\n> +\t\t\tfree((void *)pathspec);\n> +\t\t\tif (range->nr == 0) {\n> +\t\t\t\tif(range->next) {\n> +\t\t\t\t\tdie(\"Path %s need a -L <range> option\\n\"\n> +\t\t\t\t\t\"If you want follow the history of the whole file \"\n> +\t\t\t\t\t\"whether to using 'git log' without -L or using \"\n> +\t\t\t\t\t\"'git log -L 1,$ <path>'\", range->spec->path);\n> +\t\t\t\t} else {\n> +\t\t\t\t\tparse_options_next(&ctx, 1);\n> +\t\t\t\t\tcontinue;\n\nThis loop smells bad.\n\nWhen \"-L n,m\" appears on the command line, log_line_range_callback() would\nbe called and would eat n,m (which is correct), but at that point you\nwould not just want to be prepared to accept a non-option (\"path\" in \"-L\nn,m path\"), but actually would want to force the user to give a path, no?\nIOW, isn't \"git log -L n,m -U20\" an error, unless \"-U20\" is a filename\nthat the user wants to track?\n\nI somehow suspect that futzing with STOP_AT_NON_OPTION (done in the first\ntwo patches in the series) to parse \"-L n,m path\" is a misguided design\nattempt.  Shouldn't you be instead giving a support for option callback to\ntake more than one argument to do this?\n\n> +\t\t\t\t}\n> +\t\t\t}\n> +\t\t\tstruct diff_line_range *r = xmalloc(sizeof(*r));\n\ndecl-after-statement, but at this point it may be moot as I am doubting\nthe higher-level design of this option parser now.\n"},{"id":"145751","messageId":"AANLkTilba9j1IfPbWuNUIxG-fkPjDIKAWubDneVJu4_N@mail.gmail.com","threadId":"24364","inReplyTo":"7vr5j8bq5x.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 03/13] add the basic data structure for line level history","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-18T14:35:42Z","receivedAt":"2010-07-18T14:35:42Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Hi Junio,\nOn Tue, Jul 13, 2010 at 12:50 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> diff --git a/diffcore.h b/diffcore.h\n>> index 491bea0..13d8e93 100644\n>> --- a/diffcore.h\n>> +++ b/diffcore.h\n>> @@ -23,6 +23,7 @@\n>>  #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */\n>>\n>>  struct userdiff_driver;\n>> +struct diff_options;\n>\n> Why???\n\nThe line:\nextern void diffcore_rename(struct diff_options *);\nlater in this file use it. So, forward declare it.\n\n>>\n...\n>>  #define DECORATE_SHORT_REFS  1\n>>  #define DECORATE_FULL_REFS   2\n>> @@ -68,7 +69,8 @@ struct rev_info {\n>>                       cherry_pick:1,\n>>                       bisect:1,\n>>                       ancestry_path:1,\n>> -                     first_parent_only:1;\n>> +                     first_parent_only:1,\n>> +                     line:1;\n>\n> Is this really a traversal flag that affects how the history is walked?\n\nHmm, a 'line' means topologically traverse at least. So, I added it\nhere. And I can't find a better place to put it. :)\n\nI have changed my code according your comments, thanks a lot!\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"},{"id":"145752","messageId":"AANLkTikmJmWfwdigYADX7JnrOxZBVvP9i7lDZ84yRz-B@mail.gmail.com","threadId":"24364","inReplyTo":"7v1vb8bktw.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 13/13] some document update","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-18T14:37:25Z","receivedAt":"2010-07-18T14:37:25Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Hi Junio,\n\nOn Tue, Jul 13, 2010 at 2:45 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Bo Yang <struggleyb.nku@gmail.com> writes:\n>\n>> ...\n>> Thanks a lot for your advice, I have revised the patch.\n>>\n>> -----------------------------------------------------------\n>>>From 88ed88a53d83c2d46fa4917008efadc531ba1068 Mon Sep 17 00:00:00 2001\n>> From: Bo Yang <struggleyb.nku@gmail.com>\n>> Date: Sat, 26 Jun 2010 01:35:48 -0700\n>> Subject: [PATCH v3 revised 13/13] some document update\n>>\n>> Both 'git log' and 'git blame' support the same\n>> format of '-L' arguments, so put the argument\n>> format description into a new file.\n>>\n>> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n>> ---\n>>  Documentation/blame-options.txt     |   19 +------------------\n>>  Documentation/git-log.txt           |   13 +++++++++++++\n>>  Documentation/line-range-format.txt |   18 ++++++++++++++++++\n>>  3 files changed, 32 insertions(+), 18 deletions(-)\n>\n> Every time you do this, you seem to send a corrupt patch, like...\n>\n>> @@ -19,6 +20,9 @@ command to control what is shown and how, and\n>> options applicable to\n>> ...\n>> diff --git a/Documentation/line-range-format.txt\n>> b/Documentation/line-range-format.txt\n>\n> Please don't.\n>\n\nI am sorry, I promise I won't do the same thing from now on. :)\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"},{"id":"145753","messageId":"AANLkTimNAyKZNdIIc6R5ylHuOVJho7UF0fnYC8jiaF9i@mail.gmail.com","threadId":"24364","inReplyTo":"7vbpab2de5.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-18T14:49:41Z","receivedAt":"2010-07-18T14:49:41Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Hi Junio,\n\nOn Wed, Jul 14, 2010 at 7:03 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>       if (argc == 2 && !strcmp(argv[1], \"-h\"))\n>>               usage(builtin_log_usage);\n>> +\n>> +     parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |\n>> +                     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_STOP_AT_NON_OPTION);\n>> +     for (;;) {\n>> +             switch (parse_options_step(&ctx, options, log_opt_usage)) {\n>> +             case PARSE_OPT_HELP:\n>> +                     exit(129);\n>> +             case PARSE_OPT_DONE:\n>> +                     goto parse_done;\n>> +             case PARSE_OPT_NON_OPTION:\n>> +                     path = parse_options_current(&ctx);\n>> +                     pathspec = prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n>> +                     range->spec = alloc_filespec(pathspec);\n>> +                     free((void *)pathspec);\n>> +                     if (range->nr == 0) {\n>> +                             if(range->next) {\n>> +                                     die(\"Path %s need a -L <range> option\\n\"\n>> +                                     \"If you want follow the history of the whole file \"\n>> +                                     \"whether to using 'git log' without -L or using \"\n>> +                                     \"'git log -L 1,$ <path>'\", range->spec->path);\n>> +                             } else {\n>> +                                     parse_options_next(&ctx, 1);\n>> +                                     continue;\n>\n> This loop smells bad.\n>\n> When \"-L n,m\" appears on the command line, log_line_range_callback() would\n> be called and would eat n,m (which is correct), but at that point you\n> would not just want to be prepared to accept a non-option (\"path\" in \"-L\n> n,m path\"), but actually would want to force the user to give a path, no?\n> IOW, isn't \"git log -L n,m -U20\" an error, unless \"-U20\" is a filename\n> that the user wants to track?\n>\n> I somehow suspect that futzing with STOP_AT_NON_OPTION (done in the first\n> two patches in the series) to parse \"-L n,m path\" is a misguided design\n> attempt.  Shouldn't you be instead giving a support for option callback to\n> take more than one argument to do this?\n>\n>> +                             }\n>> +                     }\n>> +                     struct diff_line_range *r = xmalloc(sizeof(*r));\n>\n> decl-after-statement, but at this point it may be moot as I am doubting\n> the higher-level design of this option parser now.\n>\n\nThe point is that, the syntax we support is:\n\n-L n1,m1 -L n2,m2 pathspec1  -L n3,m3 pathspec2\n\nThere can be multiple -L options for one pathspec, so I think the\nSTOP_AT_NON_OPTION way is better.\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"},{"id":"145807","messageId":"7vwrsrnwg0.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"AANLkTimNAyKZNdIIc6R5ylHuOVJho7UF0fnYC8jiaF9i@mail.gmail.com","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-19T18:44:15Z","receivedAt":"2010-07-19T18:44:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bo Yang <struggleyb.nku@gmail.com> writes:\n\n> The point is that, the syntax we support is:\n>\n> -L n1,m1 -L n2,m2 pathspec1  -L n3,m3 pathspec2\n\nThat itself smells like a bad design, unless done very carefully and\ndocumented clearly.\n\nFor example, what does this mean?\n\n    $ git log -L n1,m1 master\n\nAs -L wants to see at least one path before running out of the command\nline argument, we take \"master\" as the filename.  Hence, the command line\ndoes not have any revision specified and defaults to HEAD.  I.e. \"traverse\nfrom the current HEAD and show only commits that touch the line region\nn1,m1 that appears in the version of path 'master' in HEAD\".\n\nWhat about this?\n\n    $ git log -L n1,m1 master..next one two\n\nClearly the user wants to traverse revision range between master and next.\nThe -L option wants to see one path so slurps \"one\".  The traversal\nhowever is further limited by a pathspec \"two\".  Should the use of \"one\"\nas an argument to -L automatically add it also to the pathspec?  I.e.\n\"traverse from 'next' down to 'master', checking commits that touch either\npath 'one' or 'two', and show only commits that touch the line region\nn1,m1 that appears in the version of path 'one' in 'next'\"?\n\nOr perhaps master..next is the name of the file the user is interested in?\nI.e. \"Starting from branches 'one' and 'two', show only commits that touch\nthe line region n1,m1 that appears in file 'master..next'\"?  But that is a\nbroken interpretation, as \"-L range path\" cannto possibly make sense if\nyou have more than one starting point, and this interpretation gives you\ntwo (i.e. 'one' and 'two').\n\nHow would you disambiguate -Lpaths, revisions and pathspecs?  How does -Lpath\ninteract with pathspecs?\n\nWhat if the name of the file the user wants to annotate begins with a \"-\"?\nFor pathspec limiter, the users have already learned that \"--\" can be used\nto say \"everything that comes after this token is pathspec\", but that\nknowledge cannot be reused with this syntax.\n\nIt almost feels as if you want to have something more like\n\n    -L <begin>,<end>[,<path>]\n\nwhere <path> is mandatory for the first use of -L (i.e. missing ,<path>\nmeans the same path from the previous -L that has one) to make it clear\nthat this is completely different from the normal pathspec.\n"},{"id":"145828","messageId":"201007200048.18284.trast@student.ethz.ch","threadId":"24364","inReplyTo":"7vwrsrnwg0.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-07-19T22:48:17Z","receivedAt":"2010-07-19T22:48:17Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> It almost feels as if you want to have something more like\n> \n>     -L <begin>,<end>[,<path>]\n> \n> where <path> is mandatory for the first use of -L (i.e. missing ,<path>\n> means the same path from the previous -L that has one) to make it clear\n> that this is completely different from the normal pathspec.\n\nI think that would just needlessly break the analogy to git-blame.[0]\nWith the current code,\n\n  git blame -L 2,3 <path>\n  git log -L 2,3 <path>\n\nwork the same.  Multiple -L options could be retrofitted to git-blame,\nmaking\n\n  git {blame,log} -L 2,3 -L 4,5 <path>\n\nwork as expected.\n\nAs long as you only give a single path, even blame disambiguates in\nfavour of the filename:\n\n  git blame -L 2,3 master # wants a file 'master'\n\nIt only starts breaking down as soon as you put the -L further away\nfrom the filename:\n\n  git blame -L 2,3 master master # looks for the file master:master\n  git blame master -L 2,3 master # ditto\n  git log -L 2,3 master master   # errors out since the second file has no -L [1]\n  git log master -L 2,3 master   # looks for the file master:master\n\nAnd as you have noted, the -- can also cause weird effects.  Currently\n\n  git log -L 2,3 --branches      # error\n  git log -L 2,3 ./--branches    # ok\n  git log -L 2,3 -- --branches   # error\n\nThe last one is unfortunate, but fixing it while allowing a natural\npositioning of the -L would require parsing the -L after a --.  That's\njust as inconsistent, only in a different way.\n\n\n[0] It also requires special support from shell completion.\n\n[1] I agree with erroring out but I think the message wrongly\n    recommends path filtering (since that doesn't work well with\n    renames):\n\n      fatal: Path master need a -L <range> option\n      If you want follow the history of the whole file whether to using 'git log' without -L or using 'git log -L 1,$ <path>'\n\n    Bo, can you please change it to e.g.:\n\n      fatal: Path 'master' needs a -L<range> option\n      If you want to follow the history of the whole file, use 'git log -L 1,$ <path>'\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"145831","messageId":"7veiezni4m.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"201007200048.18284.trast@student.ethz.ch","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-19T23:53:29Z","receivedAt":"2010-07-19T23:53:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Junio C Hamano wrote:\n>> It almost feels as if you want to have something more like\n>> \n>>     -L <begin>,<end>[,<path>]\n>> \n>> where <path> is mandatory for the first use of -L (i.e. missing ,<path>\n>> means the same path from the previous -L that has one) to make it clear\n>> that this is completely different from the normal pathspec.\n>\n> I think that would just needlessly break the analogy to git-blame.[0]\n> With the current code,\n>\n>   git blame -L 2,3 <path>\n>   git log -L 2,3 <path>\n>\n> work the same.  Multiple -L options could be retrofitted to git-blame,\n> making\n>\n>   git {blame,log} -L 2,3 -L 4,5 <path>\n>\n> work as expected.\n\nI like the general direction, but I am not sure how far we want to take\nthat analogue with blame, though.\n\nFor example, Bo's \"log -L\" thing also works on more than one path, no?  I\nsuspect it might be be reasonable to teach \"blame\" to annotate more than\none path (with ranges) the same way.  There is no technical limitation in\nthe underlying scoreboard mechanism to prevent it from happening.\n\nVery similar to \"blame\" but quite differently from any normal \"log\"\noperation, \"log -L\" allows only one positive commit (starting point).\n\nPerhaps these argue that this new feature shouldn't be a new option of\n\"log\" at the UI level after all; rather, I wonder if this should be better\npresented as a new mode of \"blame\" (e.g. \"blame --log\", unlike showing\n\"cvs annotate\" like output, shows history like \"git log\" does).\n\nI _think_ I've already said the above at least a few times, though.\n"},{"id":"145847","messageId":"201007200951.56218.trast@student.ethz.ch","threadId":"24364","inReplyTo":"7veiezni4m.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-07-20T07:51:55Z","receivedAt":"2010-07-20T07:51:55Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n> Thomas Rast <trast@student.ethz.ch> writes:\n> > I think that would just needlessly break the analogy to git-blame.[0]\n> > With the current code,\n> >\n> >   git blame -L 2,3 <path>\n> >   git log -L 2,3 <path>\n> >\n> > work the same.\n> \n> I like the general direction, but I am not sure how far we want to take\n> that analogue with blame, though.\n> \n> For example, Bo's \"log -L\" thing also works on more than one path, no?  I\n> suspect it might be be reasonable to teach \"blame\" to annotate more than\n> one path (with ranges) the same way.  There is no technical limitation in\n> the underlying scoreboard mechanism to prevent it from happening.\n> \n> Very similar to \"blame\" but quite differently from any normal \"log\"\n> operation, \"log -L\" allows only one positive commit (starting point).\n\nAFAICT this is not a conceptual requirement, only one of the current\nimplementation/option parsing.  [Bo, how hard would it be to remove\nthis requirement?]\n\n'git log --follow', if it were ever unbroken, would have much the same\nproblem that while technically allowing more than one starting point,\nusing that is only possible if the starting filename happens to agree\non all of them.\n\n> Perhaps these argue that this new feature shouldn't be a new option of\n> \"log\" at the UI level after all; rather, I wonder if this should be better\n> presented as a new mode of \"blame\" (e.g. \"blame --log\", unlike showing\n> \"cvs annotate\" like output, shows history like \"git log\" does).\n\nThen you suddenly have a mode of git-blame that takes the normal\noptions, and another that takes all the git-log arguments that pertain\nto commit formatting.\n\n(Ok, you can't reasonably give it any of the diff-formatting options,\nbut e.g. --graph and --pretty are supposed to work.)\n\nWhat's more, we (well, I do anyway) absolutely want gitk to display\nthe same output eventually.  So that would also suddenly give you an\nexample where gitk isn't so equivalent to git-log-in-a-fancy-window\nany more.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"145856","messageId":"7v39venom7.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"201007200951.56218.trast@student.ethz.ch","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-20T15:45:36Z","receivedAt":"2010-07-20T15:45:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Junio C Hamano wrote:\n> ...\n>> I like the general direction, but I am not sure how far we want to take\n>> that analogue with blame, though.\n>> \n>> For example, Bo's \"log -L\" thing also works on more than one path, no?  I\n>> suspect it might be be reasonable to teach \"blame\" to annotate more than\n>> one path (with ranges) the same way.  There is no technical limitation in\n>> the underlying scoreboard mechanism to prevent it from happening.\n>> \n>> Very similar to \"blame\" but quite differently from any normal \"log\"\n>> operation, \"log -L\" allows only one positive commit (starting point).\n>\n> AFAICT this is not a conceptual requirement, only one of the current\n> implementation/option parsing.\n\nI think \"log -L\" conceptually is very different from the normal \"log with\npathspecs\".\n\nThe \"log with pathspecs\" is about \"I have these histories that lead to\nthese commits; show commits that touch paths that match them\".  On the\nother hand, \"log -L\" asks \"I have these lines in these files; explain how\nthey came about in the history leading to...\".  Now, the question is:\n\"leading to\" WHAT?\n\nBecause you start from \"I have _these lines_\", you are fundamentally\ndiscussing contents that appear as a set of line ranges _in a particular\nversion_, adjusting the range to match their older incarnations as\nnecessary as you dig deeper, no?\n\nI would say that this is not just _only_ the implementation and option\nparsing.  What is happening looks to me fundamentally very different.\n\nHaving said that, it is not entirely implausible to imagine that \"log -L\"\ncan further be enhanced to also dig into the future.  The request becomes\n\"I have these lines; explain how they came about, and how they went\".\n\nE.g. if you have a history of this shape where commit C is at the tip:\n\n    ---o---A---x---B---o---C\n\nyou _could_ ask \"log -p -L1,10,B:Makefile A..C\" (I am not proposing this\nsyntax at all, by the way) to browse the history between A and C, looking\nfor commits that touch the region of Makefile that appeared as lines 1..10\nin revision B.  Between B and C, some new lines might have been introduced\ninside the range and you would dig in reverse enlarging the range to show\nwhat have been added to it.\n\nIf you have that kind of \"forward digging\", then it would also not be\nimplausible to deal with a forked history like this:\n\n                 y---z---D\n                /\n    ---o---A---x---B---o---C\n\nand ask a similar \"log -p -L1,10,C:Makefile A..C D\" question.  You would\nneed to dig from C to x, finding the corresponding range in x, and then\nmap that corresponding range further while traversing through y, z and to\nD in reverse, just like you mapped and traversed from B through o to C in\nreverse that explained a part of Makefile in revision B in the earlier\nexample.\n\nAlso the implementation will have to be very different if you really\nwanted to do this properly.  I would imagine you would first build a\nsparse merge-simplified graph that consists of commits that touch Makefile\n(using \"Makefile\" as a pathspec) in the first pass, map the line ranges\nbackward and forward in the second pass, and output the result in the\nfinal pass.  Presumably you could collapse the backward traversing half of\nthe second pass and the third one (which is what Bo's patchset already\ndoes), but I don't think you can stream x-y-z-D going forward, as \"log -L\"\noutput needs to follow the usual new-to-old order as the other parts of\nthe history.\n\nIOW, that \"explain how they came and how they went\" is also very different\nfrom the normal \"log\", even if you choose to lift that restriction.\n\nAlso I am not entirely sure if it is easy to explain to the users.\n\nIf you instead choose to retain the \"start from one commit\", then the way\nyou ask the question becomes \"I have these lines in these files at this\ncommit; explain how they came about in the history\", which is exactly what\nyou ask \"blame\".  You are just using a different presentation from what\nthe default \"blame\" output gives.\n\n>> Perhaps these argue that this new feature shouldn't be a new option of\n>> \"log\" at the UI level after all; rather, I wonder if this should be better\n>> presented as a new mode of \"blame\" (e.g. \"blame --log\", unlike showing\n>> \"cvs annotate\" like output, shows history like \"git log\" does).\n>\n> Then you suddenly have a mode of git-blame that takes the normal\n> options, and another that takes all the git-log arguments that pertain\n> to commit formatting.\n\nI would consider it similar to \"blame --incremental\"; you can choose a\nvery different output format driven by a single switch to change the mode\nof operation.\n"},{"id":"145857","messageId":"AANLkTinXDRV_Ikd-hoGoWPZmn1Tp0NYHoumaqthydsuw@mail.gmail.com","threadId":"24364","inReplyTo":"201007200951.56218.trast@student.ethz.ch","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-20T15:46:04Z","receivedAt":"2010-07-20T15:46:04Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"On Tue, Jul 20, 2010 at 3:51 PM, Thomas Rast <trast@student.ethz.ch> wrote:\n> Junio C Hamano wrote:\n>> Thomas Rast <trast@student.ethz.ch> writes:\n>> > I think that would just needlessly break the analogy to git-blame.[0]\n>> > With the current code,\n>> >\n>> >   git blame -L 2,3 <path>\n>> >   git log -L 2,3 <path>\n>> >\n>> > work the same.\n>>\n>> I like the general direction, but I am not sure how far we want to take\n>> that analogue with blame, though.\n>>\n>> For example, Bo's \"log -L\" thing also works on more than one path, no?  I\n>> suspect it might be be reasonable to teach \"blame\" to annotate more than\n>> one path (with ranges) the same way.  There is no technical limitation in\n>> the underlying scoreboard mechanism to prevent it from happening.\n>>\n>> Very similar to \"blame\" but quite differently from any normal \"log\"\n>> operation, \"log -L\" allows only one positive commit (starting point).\n>\n> AFAICT this is not a conceptual requirement, only one of the current\n> implementation/option parsing.  [Bo, how hard would it be to remove\n> this requirement?]\n>\n> 'git log --follow', if it were ever unbroken, would have much the same\n> problem that while technically allowing more than one starting point,\n> using that is only possible if the starting filename happens to agree\n> on all of them.\n\nI think Junio here did not ask for 'git log -L' to support multiple\nstarting commits. [1]\nConsidering the arguments you give below, I am in support of put it in\n'git log -L' as what we do, now.\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"},{"id":"145858","messageId":"AANLkTil8C7h8T4sVfvj2IkYIUHmGLOIvKX82Lo_Zlyrw@mail.gmail.com","threadId":"24364","inReplyTo":"AANLkTinXDRV_Ikd-hoGoWPZmn1Tp0NYHoumaqthydsuw@mail.gmail.com","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-07-20T15:47:44Z","receivedAt":"2010-07-20T15:47:44Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"On Tue, Jul 20, 2010 at 11:46 PM, Bo Yang <struggleyb.nku@gmail.com> wrote:\n> On Tue, Jul 20, 2010 at 3:51 PM, Thomas Rast <trast@student.ethz.ch> wrote:\n>> Junio C Hamano wrote:\n>>> Thomas Rast <trast@student.ethz.ch> writes:\n>>> > I think that would just needlessly break the analogy to git-blame.[0]\n>>> > With the current code,\n>>> >\n>>> >   git blame -L 2,3 <path>\n>>> >   git log -L 2,3 <path>\n>>> >\n>>> > work the same.\n>>>\n>>> I like the general direction, but I am not sure how far we want to take\n>>> that analogue with blame, though.\n>>>\n>>> For example, Bo's \"log -L\" thing also works on more than one path, no?  I\n>>> suspect it might be be reasonable to teach \"blame\" to annotate more than\n>>> one path (with ranges) the same way.  There is no technical limitation in\n>>> the underlying scoreboard mechanism to prevent it from happening.\n>>>\n>>> Very similar to \"blame\" but quite differently from any normal \"log\"\n>>> operation, \"log -L\" allows only one positive commit (starting point).\n>>\n>> AFAICT this is not a conceptual requirement, only one of the current\n>> implementation/option parsing.  [Bo, how hard would it be to remove\n>> this requirement?]\n>>\n>> 'git log --follow', if it were ever unbroken, would have much the same\n>> problem that while technically allowing more than one starting point,\n>> using that is only possible if the starting filename happens to agree\n>> on all of them.\n>\n> I think Junio here did not ask for 'git log -L' to support multiple\n> starting commits. [1]\n\nForget [1]: gmane.comp.version-control.git:142900\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"},{"id":"145985","messageId":"201007221106.39623.trast@student.ethz.ch","threadId":"24364","inReplyTo":"7v39venom7.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-07-22T09:06:39Z","receivedAt":"2010-07-22T09:06:39Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Junio C Hamano wrote:\n[moved up this chunk from the end]\n> If you instead choose to retain the \"start from one commit\", then the way\n> you ask the question becomes \"I have these lines in these files at this\n> commit; explain how they came about in the history\", which is exactly what\n> you ask \"blame\".  You are just using a different presentation from what\n> the default \"blame\" output gives.\n[...]\n> > Then you suddenly have a mode of git-blame that takes the normal\n> > options, and another that takes all the git-log arguments that pertain\n> > to commit formatting.\n> \n> I would consider it similar to \"blame --incremental\"; you can choose a\n> very different output format driven by a single switch to change the mode\n> of operation.\n\nFor me (and Bo seemed to agree off-list) the main interest is in the\n*history*.  Yes, it is an important feature that the diffs are also\nlimited to the ranges you are scanning, but that is the secondary\nfeature.\n\n[If you think that is unfair, consider: if you remove the history\nfiltering or view, the line-log becomes log or blame, respectively.\nIf you just remove the diff display, it's still a new mode of history\nsimplification.]\n\nIn the rest of the mail I'll make some more points why it is more\nlog-like than blame-like, but this is the main point.\n\n\nThe comparison to \"blame --incremental\" is not valid because blame\nalways shows exactly one commit per line.  That's not history in my\nbook.  It does not even compute the history that line-log computes (it\ngives up on tracking a line once it has assigned blame for it) so it\nis not a new output format of existing data either.\n\n> I think \"log -L\" conceptually is very different from the normal \"log with\n> pathspecs\".\n> \n> The \"log with pathspecs\" is about \"I have these histories that lead to\n> these commits; show commits that touch paths that match them\".  On the\n> other hand, \"log -L\" asks \"I have these lines in these files; explain how\n> they came about in the history leading to...\".  Now, the question is:\n> \"leading to\" WHAT?\n\nIf pathspec limiting wasn't broken in the sense that it does not\nsupport --follow (properly, anyway), they would be exactly equivalent.\nWith --follow, you would mean \"I have these files, please explain how\nthey came about\", and you would expect git to track renames and\ncopies.\n\nThat the current \"I want to see some history, but only when it touched\nthis file\" works is a fortunate side effect of the fact that people\nrarely rename files (and that if they do, there are few collisions so\nsimply adding the old name usually works).\n\nIn particular, proper log --follow would have a problem exactly\nanalogous to:\n\n> Because you start from \"I have _these lines_\", you are fundamentally\n> discussing contents that appear as a set of line ranges _in a particular\n> version_, adjusting the range to match their older incarnations as\n> necessary as you dig deeper, no?\n[...]\n> E.g. if you have a history of this shape where commit C is at the tip:\n> \n>     ---o---A---x---B---o---C\n> \n> you _could_ ask \"log -p -L1,10,B:Makefile A..C\" (I am not proposing this\n> syntax at all, by the way) to browse the history between A and C, looking\n> for commits that touch the region of Makefile that appeared as lines 1..10\n> in revision B.  Between B and C, some new lines might have been introduced\n> inside the range and you would dig in reverse enlarging the range to show\n> what have been added to it.\n\n[I think this would be entirely possible to do for the line-log case,\nthough it would be too much effort for GSoC since it is also a\nhard-to-explain and probably rarely-used case.]\n\nSimilarly, the plans for simplification that I have discussed with Bo\nare roughly:\n\n* commits that take all ranges from one of their parents are simplified\n  away (even if a merge)\n\n* commits that take each range fully from a single parent should stay\n  but not show a diff\n\n* ranges that are \"split\" between parents should show some format of\n  diff\n\nCompare this to pathspec filtering:\n\n* commits that are TREESAME to one of their parents are simplified\n  away\n\n* commits that take each hunk literally from a single parent do not\n  show a diff with --cc\n\n* commits that have hunks \"mixed\" from the parents show a diff\n\n[The latter was first and served a guideline, of course.]\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"146037","messageId":"7vwrsneyqi.fsf@alter.siamese.dyndns.org","threadId":"24364","inReplyTo":"201007221106.39623.trast@student.ethz.ch","subject":"Re: [PATCH v3 05/13] parse the -L options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-07-23T02:08:37Z","receivedAt":"2010-07-23T02:08:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n>> I would consider it similar to \"blame --incremental\"; you can choose a\n>> very different output format driven by a single switch to change the mode\n>> of operation.\n>\n> For me (and Bo seemed to agree off-list) the main interest is in the\n> *history*.  Yes, it is an important feature that the diffs are also\n> limited to the ranges you are scanning, but that is the secondary\n> feature.\n>\n> [If you think that is unfair, consider:...\n\nWe are not saying very different things, I think.\n\nI guess I shouldn't have mentined \"blame --incremental\"; I was referring\nto a quick-hack experiment we did to show an early output when we pass\nblame to parent.  You can imagine that inside pass_blame_to_parent() we\ncan detect if the lines around the range we are passing the blame to\nparent were touched, we could show pretty-printed commit log and relevant\ndiff-tree output at that point, which would give you pretty much the same\nhistory we are talking about.\n\n> If pathspec limiting wasn't broken in the sense that it does not\n> support --follow (properly, anyway), they would be exactly equivalent...\n\nWell, --follow is from \"content-id\" camp, while pathspec is a revision\nlimiter that follows quite different philosophy, so they do not mix very\nwell.  \"log --follow -- $path\" is an unfortunate cross between these two\nphilosophies and spelled that way only because that was the easiest way to\ncheat in the command line parser.\n\nAs you alluded to, \"log --follow HEAD -- $path\" would mean exactly the\nsame as \"log -L<range>,HEAD:$path HEAD\" in the mock syntax I gave you\nearlier in the thread, but that does not change the fact that it is\nconceptually very different from the normal \"git log\".\n\n>> E.g. if you have a history of this shape where commit C is at the tip:\n>> \n>>     ---o---A---x---B---o---C\n>> \n>> you _could_ ask \"log -p -L1,10,B:Makefile A..C\" (I am not proposing this\n>> syntax at all, by the way) to browse the history between A and C, looking\n>> for commits that touch the region of Makefile that appeared as lines 1..10\n>> in revision B.  Between B and C, some new lines might have been introduced\n>> inside the range and you would dig in reverse enlarging the range to show\n>> what have been added to it.\n>\n> [I think this would be entirely possible to do for the line-log case,\n\nI am sure it is \"entirely possible\", even though it might be quite\nexpensive (both development wise and also the end result).\n\nA funny thing is, I do not think it is so \"hard to explain and rarely\nused\" case.  Imagine that you have this history:\n\n                 y---z---D maint\n                /\n    ---o---A---x---B---o---C master\n\nWouldn't it be nice if you can ask this question?\n\n    I have these lines in frotz.c at 'master' version, and I recently made\n    fixes that I need to backport to 'maint'.  How does the corresponding\n    region look like in 'maint', and what changes that may be necessary\n    for and interfere with my backport are there since these two branches\n    forked?\n\nIn the ugly mock syntax, you would write it like this:\n\n    log -p -L<range>,master:frotz.c master...maint\n\nIn this example, you have two positive revisions (notice three-dots for\nsymmetric difference), but you can easily imagine that this will work well\nwhen you have more than two branches you are interested in.\n\nTo produce a useful output, you would need to dig from C down to x while\nlisting the commit that touch the specified range and adjusting it, then\ndig the other way from x through y, z and D, again mapping the corresponding\nrange.\n\nI am not saying that what -L does is a bad thing, though.  All I am saying\nis that it is conceptually very different, and trying to coax it into\nexisting command line parsing rules of \"git log\" might not work well.\n\nYou could use a few rules to allow something like this which might be more\nfriendly to the eyes that are used to looking at the input to\nsetup_revisions() parser:\n\n    log -p -L<range1> <path1> -L<range2> <revs>...\n\nThe short-hand rules may look something like this:\n\n    - -L<range> with missing <path> is the same as -L<range>,<path> for\n       the last <path> we have seen used with -L (it would be cleaner to\n       use a single string given to -L i.e. \"-L<range>,<path>\" instead of\n       a rule \"-L takes one or two parameters\" that is totally odd-ball\n       from the point of view of normal POSIX command line rules, though);\n\n    - If there is no <path> anywhere in the first use of -L, we take the\n      first pathspec element as if it were <path>.\n\n    - -L<range>,<path> is the same as -L<range>,<rev>:<path> where <rev>\n       is the first positive revision given on the command line.\n\nNo matter what syntax sugarcoating is done, conceptually when -L says\n\"these lines in this path\", it has to talk about the range in one\nrevision.  -L<range>,<rev>:<path> instructs us: \"you are to follow this\nblock of lines in that path in this particular revision---figure out how\nthat migrated while you traverse the history and show its evolution\".\nThere is no room for the normal pathspec rules to take effect---as we are\noperating in the \"content-id\" domain, we would ideally want to\nautomatically even do --find-copies-harder, just like --follow and blame\nautomatically runs rename detection (that is another reason I think the\ntraversal semantics is very different from the regular \"log\", in addition\nto the need to traverse forward in addition to backward which I already\nmentioned).  We may want to error out when we have any <pathspec> (after\ntaking the second short-hand rule above into account, that is).\n\nIn short, I don't really have a very deep preference between \"log -L\" and\n\"blame -L\".\n\nBut as a very special case of allowing only one single positive rev and no\nforward-walking of the history, what \"log -L\" gives us is much closer to\n\"blame -L\".  That is where my suggestion came from.  If we were to\neventually have that \"log -p -L<range>,master:frotz.c master...maint\", not\nthe syntax but the capability, I am all for it to be part of the \"log\"\ncommand.\n"}]}