{"thread":{"id":"24641","subject":"[PATCH v4 00/18] Reroll the line log series","startedAt":"2010-08-05T16:11:39Z","lastAt":"2010-08-10T15:40:39Z","messageCount":28,"participants":["Bo Yang","Thomas Rast","Junio C Hamano"],"isPatch":true,"patchVersion":4,"patchTotal":18},"messages":[{"id":"147196","messageId":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":null,"subject":"[PATCH v4 00/18] Reroll the line log series","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:39Z","receivedAt":"2010-08-05T16:11:39Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Modifications:\n1. We do the parent rewriting with a more sane way. Just like the way we deal with multiple pathspec pruning, we rewrite P to its parent P^, if P^ can take all the interesting ranges from P. Otherwise, we keep P.\n2. --graph will not override --full-line-diff any more. Now, they are two totally different options. You can use --full-line-diff with or without --graph.\n3. Bug fix, this includes:\n   * --graph outputs correct graph lines\n   * mark a commit as root one if it takes no ranges\n   * some other minor fix...\n\nBo Yang (18):\n  parse-options: enhance STOP_AT_NON_OPTION\n  parse-options: add two helper functions\n  Add the basic data structure for line level history\n  Refactor parse_loc\n  Parse the -L options\n  Export three functions from diff.c\n  Add range clone functions\n  map/take range to the parent of commits\n  Print the line log\n  Hook line history into cmd_log, ensuring a topo-ordered walk\n  Add tests for line history browser\n  Make rewrite_parents public to other part of git\n  Make graph_next_line external to other part of git\n  Add parent rewriting to line history browser\n  Add --graph prefix before line history output\n  Add --full-line-diff option\n  Add test cases for '--graph' of line level log\n  Document line history browser\n\n Documentation/blame-options.txt     |   19 +-\n Documentation/git-log.txt           |   15 +\n Documentation/line-range-format.txt |   18 +\n Makefile                            |    2 +\n builtin/blame.c                     |   89 +--\n builtin/log.c                       |  111 +++-\n diff.c                              |    6 +-\n diff.h                              |   17 +\n diffcore.h                          |    1 +\n graph.c                             |   14 +-\n graph.h                             |   10 +\n line.c                              | 1563 +++++++++++++++++++++++++++++++++++\n line.h                              |  141 ++++\n parse-options.c                     |   22 +-\n parse-options.h                     |    7 +-\n revision.c                          |   25 +-\n revision.h                          |   23 +-\n t/t4301-log-line-single-history.sh  |  619 ++++++++++++++\n t/t4302-log-line-merge-history.sh   |  163 ++++\n 19 files changed, 2736 insertions(+), 129 deletions(-)\n create mode 100644 Documentation/line-range-format.txt\n create mode 100644 line.c\n create mode 100644 line.h\n create mode 100755 t/t4301-log-line-single-history.sh\n create mode 100755 t/t4302-log-line-merge-history.sh\n\n-- \n1.7.2.20.g388bbb\n"},{"id":"147200","messageId":"1281024717-7855-2-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 01/18] parse-options: enhance STOP_AT_NON_OPTION","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:40Z","receivedAt":"2010-08-05T16:11:40Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Make parse_options_step() report PARSE_OPT_NON_OPTION instead\nof PARSE_OPT_DONE to the caller, when it sees a non-option argument.\n\nThis will help implementing a nonstandard option syntax that\ntakes more than one parameters to an option, e.g.\n\n  -L n1,m1 pathspec1 -L n2,m2 pathspec2\n\nby directly calling parse_options_step(). The parse_options() API\nonly calls parse_options_step() once, and its callers are not affected\nby this change.\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.2.20.g388bbb\n"},{"id":"147203","messageId":"1281024717-7855-3-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 02/18] parse-options: add two helper functions","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:41Z","receivedAt":"2010-08-05T16:11:41Z","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   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..e0c3641 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 keep)\n+{\n+\tif (ctx->argc <= 0)\n+\t\treturn -1;\n+\n+\tif (keep)\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..d3b1932 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 keep);\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.2.20.g388bbb\n"},{"id":"147202","messageId":"1281024717-7855-4-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 03/18] Add the basic data structure for line level history","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:42Z","receivedAt":"2010-08-05T16:11:42Z","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 keep\ntrack of the line ranges we are currently interested in. The\nuser starts digging from a line range, and after examining the\ndiff that affects that range by a commit, we can find a new\nrange that corresponds to this range. So, we will associate this\nnew range with the commit's parent commit.\n\nThere is one 'diff_line_range' for each file, and there are\nmultiple 'struct range' in each 'diff_line_range'. In this way,\nwe support multiple ranges.\n\nWithin 'struct range', there are multiple 'struct print_range'\nwhich represent a diff hunk.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n Makefile   |    2 +\n diffcore.h |    1 +\n line.c     |  460 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n line.h     |  128 +++++++++++++++++\n revision.h |    8 +-\n 5 files changed, 597 insertions(+), 2 deletions(-)\n create mode 100644 line.c\n create mode 100644 line.h\n\ndiff --git a/Makefile b/Makefile\nindex 4179186..c194e79 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..fdec93a\n--- /dev/null\n+++ b/line.c\n@@ -0,0 +1,460 @@\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+\tint found = -1;\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,\n+\t\t\t\trevs->pending.objects[found].name);\n+\t\tcommit = obj;\n+\t\tfound = i;\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++] = 0;\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, long line,\n+\t\tlong lines, unsigned long *line_ends)\n+{\n+\tassert(line < lines);\n+\tassert(spec && spec->data);\n+\n+\tif (line == 0)\n+\t\treturn (char *)spec->data;\n+\telse\n+\t\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+\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\tint start, int end)\n+{\n+\tint i = 0;\n+\tstruct range *rs = r->ranges;\n+\tint left_merge = 0, right_merge = 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\tint num = 0;\n+\t\tDIFF_LINE_RANGE_GROW(r);\n+\t\trs = r->ranges;\n+\t\tnum = 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_merge = 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_merge = 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_merge = 1;\n+\t\t\tif (rs[i].end < end) {\n+\t\t\t\trs[i].end = end;\n+\t\t\t\tright_merge = 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_merge) {\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_merge) {\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+\t\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..caf84c7\n--- /dev/null\n+++ b/line.h\n@@ -0,0 +1,128 @@\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)->start = (r)->end = 0; \\\n+\t\t(r)->pstart = (r)->pend = 0; \\\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,\n+\t\tconst char *arg, int start, int end);\n+\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(\n+\t\tstruct diff_line_range *out,\n+\t\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,\n+\t\tstruct diff_line_range *r);\n+\n+extern struct diff_line_range *lookup_line_range(struct rev_info *revs,\n+\t\tstruct commit *commit);\n+\n+#endif\ndiff --git a/revision.h b/revision.h\nindex 36fdf22..c0d5065 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_level_traverse: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 range that we are chasing */\n+\tstruct decoration line_range;\n };\n \n #define REV_TREE_SAME\t\t0\n-- \n1.7.2.20.g388bbb\n"},{"id":"147197","messageId":"1281024717-7855-5-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 04/18] Refactor parse_loc","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:43Z","receivedAt":"2010-08-05T16:11:43Z","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.\n\nThe caller of parse_loc should provide a callback function\nwhich is used to calculate the start position of the nth line.\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          |   37 +++++++++++++----------\n line.h          |    5 +++\n 3 files changed, 35 insertions(+), 96 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 fdec93a..4314cee 100644\n--- a/line.c\n+++ b/line.c\n@@ -95,25 +95,29 @@ 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, long 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 \tif (line == 0)\n-\t\treturn (char *)spec->data;\n+\t\treturn (char *)d->spec->data;\n \telse\n-\t\treturn (char *)spec->data + line_ends[line] + 1;\n+\t\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@@ -160,7 +164,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@@ -168,7 +172,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@@ -189,11 +193,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 caf84c7..8b30ada 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@@ -125,4 +127,7 @@ extern void add_line_range(struct rev_info *revs, struct commit *commit,\n extern struct diff_line_range *lookup_line_range(struct rev_info *revs,\n \t\tstruct 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.2.20.g388bbb\n"},{"id":"147198","messageId":"1281024717-7855-6-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 05/18] Parse the -L options","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:44Z","receivedAt":"2010-08-05T16:11:44Z","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 <pathspec>' syntax.\n\nNotice that users can give more than one '-L n,m' for each pathspec.\nAnd a pathspec with all its '-L' options maps to a single\ndiff_line_range structure.\n\nThis has the exactly the same semantics as 'git blame -L n,m <pathspec>'\nbecause we refactored and reused the blame code.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n builtin/log.c |  103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 files changed, 102 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 08b8722..84d781e 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,42 @@ 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+\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_level_traverse = 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, *r = 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 +119,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\tr = 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 +221,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.2.20.g388bbb\n"},{"id":"147199","messageId":"1281024717-7855-7-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 06/18] Export three functions from diff.c","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:45Z","receivedAt":"2010-08-05T16:11:45Z","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 17873f3..9efca95 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.2.20.g388bbb\n"},{"id":"147204","messageId":"1281024717-7855-8-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 07/18] Add range clone functions","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:46Z","receivedAt":"2010-08-05T16:11:46Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Since diff_line_range can form a single list through its\n'next' pointer, we provide two kind of clone.\n\ndiff_line_range_clone:\n\tused to clone only the element node and set the\n\telement's 'next' pointer to NULL.\ndiff_line_range_clone_deeply:\n\tused to clone the whole list of ranges.\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 4314cee..c9ff934 100644\n--- a/line.c\n+++ b/line.c\n@@ -382,6 +382,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 \t\tstruct diff_line_range *other)\n {\ndiff --git a/line.h b/line.h\nindex 8b30ada..7563536 100644\n--- a/line.h\n+++ b/line.h\n@@ -119,6 +119,10 @@ extern struct diff_line_range *diff_line_range_merge(\n \t\tstruct diff_line_range *out,\n \t\tstruct diff_line_range *other);\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 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,\n-- \n1.7.2.20.g388bbb\n"},{"id":"147205","messageId":"1281024717-7855-9-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 08/18] map/take range to the parent of commits","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:47Z","receivedAt":"2010-08-05T16:11:47Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"When going from a commit to its parents, we map the \"interesting\"\nrange of lines according to the change made.\nFor non-merge commit, we just run map_range on the ranges, which\nworks as follows:\n\n1. Run diffcore_std to find out the pre/postimage for each file.\n2. Run xdi_diff_hunks on each interesting set of pre/postimages.\n3. The map_range_cb callback is invoked for each hunk by the diff\n   engine, and we use it to calculate the pre-image range from the\n   post-image range in the function map_lines.\n\nFor merge commits, we run map_range once for every parent.\nSimultaneously we use a take_range pass to eliminate all ranges\nthat are identical. If any ranges remain after that, then the\nmerge is considered non-trivial.\n\nThe algorithm that maps 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     |  489 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n revision.h |    5 +-\n 2 files changed, 493 insertions(+), 1 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex c9ff934..3593b33 100644\n--- a/line.c\n+++ b/line.c\n@@ -502,3 +502,492 @@ 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;\t/* currently interesting ranges */\n+\tstruct diff_line_range *range;\n+\t\t/* the ranges corresponds to the interesting ranges of parent commit */\n+\tlong plno, tlno;\n+\t\t/* the last line number of diff hunk */\n+\tint diff;\n+\t\t/* whether there is some line changes between the current commit and its parent */\n+};\n+\n+#define SCALE_FACTOR 4\n+/*\n+ * [p_start, p_end] represents the pre-image of current diff hunk,\n+ * [t_start, t_end] represnets the post-image of the current diff hunk,\n+ * [start, end] represents the currently interesting line range in\n+ * post-image,\n+ * [o_start, o_end] represents the original line range that coresponds\n+ * to current line range.\n+ */\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 * Normally, p_start should be less than p_end, so does the\n+\t * t_start and t_end. But when the line range is added from\n+\t * scratch, p_start will be greater than p_end. When the line\n+\t * range is deleted, t_start will be greater than t_end.\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 heuristic for lines mapping:\n+\t *\n+\t * When the pre-image is no more than 1/4 of the post-image,\n+\t * there is no effective way to find out which part of pre-image\n+\t * corresponds to the currently interesting range of post-image.\n+\t * And we are in the danger of tracking totally useless lines.\n+\t * So, we just 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+/*\n+ * When same == 1:\n+ * [p_start, p_end] represents the diff hunk line range of pre-image,\n+ * [t_start, t_end] represents the diff hunk line range of post-image.\n+ * When same == 0, they represents a range of idnetical lines between\n+ * two images.\n+ *\n+ * This function find out the corresponding line ranges of currently\n+ * interesting ranges which this diff hunk touches.\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\tstruct print_range *rr = NULL;\n+\t\t\tPRINT_PAIR_GROW(pair);\n+\t\t\trr = 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, now place two dummy statements here */\n+\t\t\tint dummy = 0;\n+\t\t\tdummy = 1;\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+/*\n+ * [p_start, p_end] represents the line range of pre-image,\n+ * [t_start, t_end] represents the line range of post-image,\n+ * and they are identical lines.\n+ *\n+ * This function substracts out the identical lines between current\n+ * commit and its parent, from currently interesting ranges.\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, we should not bother to call\n+\t * take_range, since there is nothing to take\n+\t */\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 diff_line_range *rg = NULL;\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+\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/*\n+\t * Collect the untouch ranges, this comes from the files not changed\n+\t * between two commit.\n+\t */\n+\trg = 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 c0d5065..2627ec4 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 range that we are chasing */\n \tstruct decoration line_range;\n+\tstruct decoration nontrivial_merge;\n };\n \n #define REV_TREE_SAME\t\t0\n-- \n1.7.2.20.g388bbb\n"},{"id":"147213","messageId":"1281024717-7855-10-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 09/18] Print the line log","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:48Z","receivedAt":"2010-08-05T16:11:48Z","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 scanned\nonly once when printing the lines. We track the starting line\nnumber and the offsets of all lines in the range in this struct.\n\nWe use two functions from diff.c to generate meta info and hunk\nheaders in the usual format.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n line.c |  241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 241 insertions(+), 0 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex 3593b33..bd63d99 100644\n--- a/line.c\n+++ b/line.c\n@@ -991,3 +991,244 @@ 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 (*color)\n+\t\treset = diff_get_color_opt(opt, DIFF_RESET);\n+\telse\n+\t\treset = \"\";\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+\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\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\tfprintf(opt->file, \"%s%s%s\\n\\n\", meta, range->spec->path, reset);\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.2.20.g388bbb\n"},{"id":"147207","messageId":"1281024717-7855-11-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 10/18] Hook line history into cmd_log, ensuring a topo-ordered walk","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:49Z","receivedAt":"2010-08-05T16:11:49Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"To correctly track the line ranges over several branches,\nwe must make sure that we have processed all children before\nreaching the commit itself.\n\nThus we introduce a first pass in cmd_line_log that runs\nprepare_revision_walk to achieve the topological ordering.\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 revision.c    |    6 ++++++\n 4 files changed, 66 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 84d781e..0aa982b 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -616,7 +616,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_level_traverse)\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 bd63d99..c17659a 100644\n--- a/line.c\n+++ b/line.c\n@@ -1232,3 +1232,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 &= ~(RANGE_UPDATE | EVIL_MERGE | NEED_PRINT);\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 7563536..3f5c827 100644\n--- a/line.h\n+++ b/line.h\n@@ -134,4 +134,6 @@ extern struct diff_line_range *lookup_line_range(struct rev_info *revs,\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\ndiff --git a/revision.c b/revision.c\nindex 7e82efd..25c9a94 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_level_traverse) {\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 \n-- \n1.7.2.20.g388bbb\n"},{"id":"147206","messageId":"1281024717-7855-12-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 11/18] Add tests for line history browser","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:50Z","receivedAt":"2010-08-05T16:11:50Z","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 containing 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..9cf34f8\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 --full-line-diff option' \\\n+\t'git log --pretty=format:%s%n%b --full-line-diff -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.2.20.g388bbb\n"},{"id":"147214","messageId":"1281024717-7855-13-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 12/18] Make rewrite_parents public to other part of git","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:51Z","receivedAt":"2010-08-05T16:11:51Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"The function rewrite_one is used to rewrite a single\nparent of the current commit, and is used by rewrite_parents\nto rewrite all the parents.\n\nDecouple the dependence between them by making rewrite_one\na callback function that is passed to rewrite_parents. Then\nexport rewrite_parents for reuse by the line history browser.\n\nWe will use this function in line.c.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n revision.c |   13 ++++---------\n revision.h |   10 ++++++++++\n 2 files changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 25c9a94..fb08978 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1893,12 +1893,6 @@ int prepare_revision_walk(struct rev_info *revs)\n \treturn 0;\n }\n \n-enum rewrite_result {\n-\trewrite_one_ok,\n-\trewrite_one_noparents,\n-\trewrite_one_error\n-};\n-\n static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)\n {\n \tstruct commit_list *cache = NULL;\n@@ -1920,12 +1914,13 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp\n \t}\n }\n \n-static int rewrite_parents(struct rev_info *revs, struct commit *commit)\n+int rewrite_parents(struct rev_info *revs, struct commit *commit,\n+\trewrite_parent_fn_t rewrite_parent)\n {\n \tstruct commit_list **pp = &commit->parents;\n \twhile (*pp) {\n \t\tstruct commit_list *parent = *pp;\n-\t\tswitch (rewrite_one(revs, &parent->item)) {\n+\t\tswitch (rewrite_parent(revs, &parent->item)) {\n \t\tcase rewrite_one_ok:\n \t\t\tbreak;\n \t\tcase rewrite_one_noparents:\n@@ -1993,7 +1988,7 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)\n \tif (action == commit_show &&\n \t    !revs->show_all &&\n \t    revs->prune && revs->dense && want_ancestry(revs)) {\n-\t\tif (rewrite_parents(revs, commit) < 0)\n+\t\tif (rewrite_parents(revs, commit, rewrite_one) < 0)\n \t\t\treturn commit_error;\n \t}\n \treturn action;\ndiff --git a/revision.h b/revision.h\nindex 2627ec4..48222f6 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -199,4 +199,14 @@ enum commit_action {\n extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit);\n extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);\n \n+enum rewrite_result {\n+\trewrite_one_ok,\n+\trewrite_one_noparents,\n+\trewrite_one_error\n+};\n+\n+typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct commit **pp);\n+\n+extern int rewrite_parents(struct rev_info *revs, struct commit *commit,\n+\trewrite_parent_fn_t rewrite_parent);\n #endif\n-- \n1.7.2.20.g388bbb\n"},{"id":"147211","messageId":"1281024717-7855-14-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 13/18] Make graph_next_line external to other part of git","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:52Z","receivedAt":"2010-08-05T16:11:52Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"We will use it in line level log output.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n graph.c |   14 +-------------\n graph.h |   10 ++++++++++\n 2 files changed, 11 insertions(+), 13 deletions(-)\n\ndiff --git a/graph.c b/graph.c\nindex ac7c605..824e055 100644\n--- a/graph.c\n+++ b/graph.c\n@@ -4,21 +4,9 @@\n #include \"graph.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n-\n /* Internal API */\n \n /*\n- * Output the next line for a graph.\n- * This formats the next graph line into the specified strbuf.  It is not\n- * terminated with a newline.\n- *\n- * Returns 1 if the line includes the current commit, and 0 otherwise.\n- * graph_next_line() will return 1 exactly once for each time\n- * graph_update() is called.\n- */\n-static int graph_next_line(struct git_graph *graph, struct strbuf *sb);\n-\n-/*\n  * Output a padding line in the graph.\n  * This is similar to graph_next_line().  However, it is guaranteed to\n  * never print the current commit line.  Instead, if the commit line is\n@@ -1143,7 +1131,7 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf\n \t\tgraph_update_state(graph, GRAPH_PADDING);\n }\n \n-static int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n+int graph_next_line(struct git_graph *graph, struct strbuf *sb)\n {\n \tswitch (graph->state) {\n \tcase GRAPH_PADDING:\ndiff --git a/graph.h b/graph.h\nindex b82ae87..5b3f059 100644\n--- a/graph.h\n+++ b/graph.h\n@@ -32,6 +32,16 @@ void graph_update(struct git_graph *graph, struct commit *commit);\n  */\n int graph_is_commit_finished(struct git_graph const *graph);\n \n+/*\n+ * Output the next line for a graph.\n+ * This formats the next graph line into the specified strbuf.  It is not\n+ * terminated with a newline.\n+ *\n+ * Returns 1 if the line includes the current commit, and 0 otherwise.\n+ * graph_next_line() will return 1 exactly once for each time\n+ * graph_update() is called.\n+ */\n+int graph_next_line(struct git_graph *graph, struct strbuf *sb);\n \n /*\n  * graph_show_*: helper functions for printing to stdout\n-- \n1.7.2.20.g388bbb\n"},{"id":"147212","messageId":"1281024717-7855-15-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 14/18] Add parent rewriting to line history browser","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:53Z","receivedAt":"2010-08-05T16:11:53Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Walking forward through history (i.e., topologically earliest\ncommits first), we filter the parent list of every commit as\nfollows. Consider a parent P:\n - If P touches any of the interesting line ranges, we keep it.\n - If P is a merge and it takes all the interesting line ranges\n   from one of its parents, P is rewritten to this parent, else\n   we keep P.\n - Otherwise, P is rewritten to its (only) parent P^.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n line.c     |  286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----\n line.h     |    2 +\n revision.c |    3 +\n revision.h |    5 +-\n 4 files changed, 269 insertions(+), 27 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex c17659a..2e513da 100644\n--- a/line.c\n+++ b/line.c\n@@ -10,6 +10,9 @@\n #include \"xdiff-interface.h\"\n #include \"strbuf.h\"\n #include \"log-tree.h\"\n+#include \"graph.h\"\n+\n+static int limited = 0;\n \n static void cleanup(struct diff_line_range *r)\n {\n@@ -387,6 +390,7 @@ struct diff_line_range *diff_line_range_clone(struct diff_line_range *r)\n \tstruct diff_line_range *ret = xmalloc(sizeof(*ret));\n \tint i = 0;\n \n+\tassert(r);\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@@ -467,15 +471,15 @@ void add_line_range(struct rev_info *revs, struct commit *commit, struct diff_li\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+\tret = lookup_decoration(&revs->line_range, &commit->object);\n+\tif (ret != NULL && r != NULL) {\n+\t\tdiff_line_range_merge(ret, r);\n+\t} else {\n+\t\tadd_decoration(&revs->line_range, &commit->object, r);\n \t}\n+\n+\tif (r != NULL)\n+\t\tcommit->object.flags |= RANGE_UPDATE;\n }\n \n struct diff_line_range *lookup_line_range(struct rev_info *revs, struct commit *commit)\n@@ -541,8 +545,26 @@ void map_lines(long p_start, long p_end, long t_start, long t_end,\n \t\treturn;\n \t}\n \n-\tif (start == t_start && end == t_end) {\n+\tif (start == t_start && end == t_end)\n+\t{\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)\n+\t{\n \t\t*o_start = p_start;\n+\t\t*o_end = p_start + (end - start);\n+\t\tif (*o_end > p_end)\n+\t\t\t*o_end = p_end;\n+\t\treturn;\n+\t}\n+\n+\tif (end == t_end) {\n+\t\t*o_start = p_end - (end - start);\n+\t\tif (*o_start < p_start)\n+\t\t\t*o_start = p_start;\n \t\t*o_end = p_end;\n \t\treturn;\n \t}\n@@ -768,7 +790,7 @@ static void map_range_cb(void *data, long same, long p_next, long t_next)\n \td->tlno = t_next;\n }\n \n-static void assign_range_to_parent(struct rev_info *rev, struct commit *c,\n+static int 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@@ -914,20 +936,56 @@ static void assign_range_to_parent(struct rev_info *rev, struct commit *c,\n \t\t}\n \t}\n \n+\tif (!map)\n+\t\tgoto out;\n+\n \tif (rr) {\n \t\tassert(p);\n \t\tadd_line_range(rev, p, rr);\n+\t} else {\n+\t\t/*\n+\t\t * If there is no new ranges assigned to the parent,\n+\t\t * we should mark it as a 'root' commit.\n+\t\t */\n+\t\tfree(c->parents);\n+\t\tc->parents = NULL;\n+\t}\n+\n+\t/* debug output */\n+\t/*\n+\tfprintf(stderr, \"%8s..%8s:\\n\", sha1_to_hex(p->object.sha1), sha1_to_hex(c->object.sha1));\n+\twhile (r) {\n+\t\tfprintf(stderr, \"file: %s\\n\", r->spec->path);\n+\t\tint n = 0;\n+\t\tfor (; n < r->nr; n++) {\n+\t\t\tfprintf(stderr, \"%d-%d, \", r->ranges[n].start, r->ranges[n].end);\n+\t\t}\n+\t\tr = r->next;\n \t}\n+\tfprintf(stderr, \"\\n\");\n+\twhile (rr) {\n+\t\tfprintf(stderr, \"file: %s\\n\", rr->spec->path);\n+\t\tint n = 0;\n+\t\tfor (; n < rr->nr; n++) {\n+\t\t\tfprintf(stderr, \"%d-%d, \", rr->ranges[n].start, rr->ranges[n].end);\n+\t\t}\n+\t\trr = rr->next;\n+\t}\n+\tfprintf(stderr, \"\\n\");\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+out:\n \tif (tree1)\n \t\tfree(tree1);\n \tif (tree2)\n \t\tfree(tree2);\n+\n+\treturn diff;\n }\n \n static void diff_update_parent_range(struct rev_info *rev, struct commit *commit)\n@@ -943,13 +1001,21 @@ static void diff_update_parent_range(struct rev_info *rev, struct commit *commit\n \tassign_range_to_parent(rev, commit, c, r, &rev->diffopt, 1);\n }\n \n+struct commit_state {\n+\tstruct diff_line_range *range;\n+\tstruct object obj;\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+\tstruct decoration parents_state;\n+\tstruct commit_state *state = NULL;\n \tint nontrivial = 0;\n \n+\tmemset(&parents_state, 0, sizeof(parents_state));\n \t/*\n \t * If we are in linear history, update range and flush the patch if\n \t * necessary\n@@ -967,23 +1033,78 @@ static void assign_parents_range(struct rev_info *rev, struct commit *commit)\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\tint diff = 0;\n+\t\tstruct diff_line_range *origin_range = lookup_line_range(rev, p);\n+\t\tif (origin_range)\n+\t\t\torigin_range = diff_line_range_clone_deeply(origin_range);\n+\n+\t\tstate = xmalloc(sizeof(*state));\n+\t\tstate->range = origin_range;\n+\t\tstate->obj = p->object;\n+\t\tadd_decoration(&parents_state, &p->object, state);\n+\t\tdiff = assign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);\n+\t\t/* Since all the ranges comes from this parent, we can ignore others */\n+\t\tif (diff == 0) {\n+\t\t\t/* restore the state of parents before this one */\n+\t\t\tparents = commit->parents;\n+\t\t\twhile (parents->item != p) {\n+\t\t\t\tstruct commit_list *list = parents;\n+\t\t\t\tstruct diff_line_range *line_range = NULL;\n+\t\t\t\tparents = parents->next;\n+\t\t\t\tline_range = lookup_line_range(rev, list->item);\n+\t\t\t\tcleanup(line_range);\n+\t\t\t\tstate = lookup_decoration(&parents_state, &list->item->object);\n+\t\t\t\tadd_decoration(&parents_state, &list->item->object, NULL);\n+\t\t\t\tadd_line_range(rev, list->item, state->range);\n+\t\t\t\tlist->item->object = state->obj;\n+\t\t\t\tfree(state);\n+\t\t\t\tfree(list);\n+\t\t\t}\n+\n+\t\t\tcommit->parents = parents;\n+\t\t\tparents = parents->next;\n+\t\t\tcommit->parents->next = NULL;\n+\n+\t\t\t/* free the non-use commit_list */\n+\t\t\twhile (parents) {\n+\t\t\t\tstruct commit_list *list = parents;\n+\t\t\t\tparents = parents->next;\n+\t\t\t\tfree(list);\n+\t\t\t}\n+\t\t\tgoto out;\n+\t\t}\n+\t\t/* take the ranges from 'commit', try to detect nontrivial merge */\n \t\tassign_range_to_parent(rev, commit, p, copy, &rev->diffopt, 0);\n \t\tparents = parents->next;\n \t}\n \n+\tcommit->object.flags |= NONTRIVIAL_MERGE;\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\tcommit->object.flags |= EVIL_MERGE;\n \t\t\tnontrivial = 1;\n \t\t}\n \t\trange = range->next;\n \t}\n \n+out:\n+\t/* Never print out any diff for a merge commit */\n+\tcommit->object.flags &= ~NEED_PRINT;\n+\n+\tparents = commit->parents;\n+\twhile (parents) {\n+\t\tstate = lookup_decoration(&parents_state, &parents->item->object);\n+\t\tif (state) {\n+\t\t\tcleanup(state->range);\n+\t\t\tfree(state);\n+\t\t}\n+\t\tparents = parents->next;\n+\t}\n+\n \tif (nontrivial) {\n \t\tadd_decoration(&rev->nontrivial_merge, &commit->object, copy);\n \t} else {\n@@ -1179,8 +1300,26 @@ static void flush_nontrivial_merge(struct rev_info *rev, struct diff_line_range\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+\tchar *line_prefix = \"\";\n+\tstruct strbuf *msgbuf;\n+\tint evil = 0;\n+\tstruct diff_line_range *r = range;\n+\n+\tif (opt && opt->output_prefix) {\n+\t\tmsgbuf = opt->output_prefix(opt, opt->output_prefix_data);\n+\t\tline_prefix = msgbuf->buf;\n+\t}\n+\n+\twhile (r) {\n+\t\tif (r->nr)\n+\t\t\tevil = 1;\n+\t\tr = r->next;\n+\t}\n+\n+\tif (!evil)\n+\t\treturn;\n \n-\tfprintf(opt->file, \"%s%s%s\\n\", meta, EVIL_MERGE_STR, reset);\n+\tfprintf(opt->file, \"%s%s%s%s\\n\", line_prefix, meta, EVIL_MERGE_STR, reset);\n \n \twhile (range) {\n \t\tif (range->nr) {\n@@ -1188,7 +1327,8 @@ static void flush_nontrivial_merge(struct rev_info *rev, struct diff_line_range\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\tfprintf(opt->file, \"%s%s%s\\n\\n\", meta, range->spec->path, reset);\n+\t\t\tfprintf(opt->file, \"%s%s%s%s\\n\", line_prefix,\n+\t\t\t\tmeta, range->spec->path, reset);\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@@ -1207,10 +1347,14 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\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+\tstruct diff_options *opt = &rev->diffopt;\n \n-\tif (range == NULL)\n+\tif (range == NULL || ! (c->object.flags & NONTRIVIAL_MERGE ||\n+\t\t\t\t\t\t\tc->object.flags & NEED_PRINT))\n \t\treturn;\n \n+\tif (rev->graph)\n+\t\tgraph_update(rev->graph, c);\n \tlog.commit = c;\n \tlog.parent = NULL;\n \trev->loginfo = &log;\n@@ -1222,13 +1366,22 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\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+\tif (c->object.flags & NONTRIVIAL_MERGE)\n+\t\tflush_nontrivial_merge(rev, nontrivial);\n+\telse {\n+\t\twhile (range) {\n+\t\t\tif (range->diff)\n+\t\t\t\tdiff_flush_filepair(rev, range);\n+\t\t\trange = range->next;\n+\t\t}\n+\t}\n \n-\twhile (range) {\n-\t\tif (range->diff)\n-\t\t\tdiff_flush_filepair(rev, range);\n-\t\trange = range->next;\n+\twhile (rev->graph && !graph_is_commit_finished(rev->graph))\n+\t{\n+\t\tstruct strbuf sb;\n+\t\tstrbuf_init(&sb, 0);\n+\t\tgraph_next_line(rev->graph, &sb);\n+\t\tfputs(sb.buf, opt->file);\n \t}\n }\n \n@@ -1242,13 +1395,14 @@ int cmd_line_log_walk(struct rev_info *rev)\n \t\tdie(\"revision walk prepare failed\");\n \n \tlist = rev->commits;\n-\tif (list) {\n+\tif (list && !limited) {\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 &= ~(RANGE_UPDATE | EVIL_MERGE | NEED_PRINT);\n+\twhile (list && !limited) {\n+\t\tlist->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |\n+\t\t\t\t\t\tNEED_PRINT | EVIL_MERGE);\n \t\tlist = list->next;\n \t}\n \n@@ -1261,7 +1415,8 @@ 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 ||\n+\t\t\tcommit->object.flags & NONTRIVIAL_MERGE || rev->graph) {\n \t\t\tline_log_flush(rev, commit);\n \t\t}\n \n@@ -1286,3 +1441,84 @@ int cmd_line_log_walk(struct rev_info *rev)\n \treturn 0;\n }\n \n+static enum rewrite_result rewrite_one(struct rev_info *rev, struct commit **pp)\n+{\n+\tstruct diff_line_range *r = NULL;\n+\tstruct commit *p;\n+\twhile (1) {\n+\t\tp = *pp;\n+\t\tif (p->object.flags & RANGE_UPDATE)\n+\t\t\tassign_parents_range(rev, p);\n+\t\tif (p->object.flags & NEED_PRINT || p->object.flags & NONTRIVIAL_MERGE)\n+\t\t\treturn rewrite_one_ok;\n+\t\tif (!p->parents)\n+\t\t\treturn rewrite_one_noparents;\n+\n+\t\tr = lookup_line_range(rev, p);\n+\t\tif (!r)\n+\t\t\treturn rewrite_one_noparents;\n+\t\t*pp = p->parents->item;\n+\t}\n+}\n+\n+/* The rev->commits must be sorted in topologically order */\n+void limit_list_line(struct rev_info *rev)\n+{\n+\tstruct commit_list *list = rev->commits;\n+\tstruct commit_list *commits = xmalloc(sizeof(struct commit_list));\n+\tstruct commit_list *out = commits, *prev = commits;\n+\tstruct commit *c;\n+\tstruct diff_line_range *r;\n+\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 &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |\n+\t\t\t\t\t\tNEED_PRINT | EVIL_MERGE);\n+\t\tlist = list->next;\n+\t}\n+\n+\tlist = rev->commits;\n+\twhile (list) {\n+\t\tc = list->item;\n+\n+\t\tif (c->object.flags & RANGE_UPDATE)\n+\t\t\tassign_parents_range(rev, c);\n+\n+\t\tif (c->object.flags & NEED_PRINT || c->object.flags & NONTRIVIAL_MERGE) {\n+\t\t\tif (rewrite_parents(rev, c, rewrite_one))\n+\t\t\t\tdie(\"Can't rewrite parent for commit %s\",\n+\t\t\t\t\tsha1_to_hex(c->object.sha1));\n+\t\t\tcommits->item = c;\n+\t\t\tcommits->next = xmalloc(sizeof(struct commit_list));\n+\t\t\tprev = commits;\n+\t\t\tcommits = commits->next;\n+\t\t} else {\n+\t\t\tr = lookup_line_range(rev, c);\n+\t\t\tif (r) {\n+\t\t\t\tcleanup(r);\n+\t\t\t\tr = NULL;\n+\t\t\t\tadd_line_range(rev, c, r);\n+\t\t\t}\n+\t\t}\n+\n+\t\tlist = list->next;\n+\t}\n+\n+\tprev->next = NULL;\n+\tfree(commits);\n+\n+\tlist = rev->commits;\n+\twhile (list) {\n+\t\tstruct commit_list *l = list;\n+\t\tlist = list->next;\n+\t\tfree(l);\n+\t}\n+\n+\trev->commits = out;\n+\tlimited = 1;\n+}\n+\ndiff --git a/line.h b/line.h\nindex 3f5c827..a2083ec 100644\n--- a/line.h\n+++ b/line.h\n@@ -136,4 +136,6 @@ const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \n extern int cmd_line_log_walk(struct rev_info *rev);\n \n+extern void limit_list_line(struct rev_info *rev);\n+\n #endif\ndiff --git a/revision.c b/revision.c\nindex fb08978..a6527ca 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -13,6 +13,7 @@\n #include \"decorate.h\"\n #include \"log-tree.h\"\n #include \"string-list.h\"\n+#include \"line.h\"\n \n volatile show_early_output_fn_t show_early_output;\n \n@@ -1886,6 +1887,8 @@ int prepare_revision_walk(struct rev_info *revs)\n \t\t\treturn -1;\n \tif (revs->topo_order)\n \t\tsort_in_topological_order(&revs->commits, revs->lifo);\n+\tif (revs->rewrite_parents && revs->line_level_traverse)\n+\t\tlimit_list_line(revs);\n \tif (revs->simplify_merges)\n \t\tsimplify_merges(revs);\n \tif (revs->children.name)\ndiff --git a/revision.h b/revision.h\nindex 48222f6..7f7d178 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -16,8 +16,9 @@\n #define SYMMETRIC_LEFT\t(1u<<8)\n #define RANGE_UPDATE\t(1u<<9) /* for line level traverse */\n #define NEED_PRINT\t(1u<<10)\n-#define EVIL_MERGE\t(1u<<11)\n-#define ALL_REV_FLAGS\t((1u<<12)-1)\n+#define NONTRIVIAL_MERGE\t(1u<<11)\n+#define EVIL_MERGE\t(1u<<12)\n+#define ALL_REV_FLAGS\t((1u<<13)-1)\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n-- \n1.7.2.20.g388bbb\n"},{"id":"147208","messageId":"1281024717-7855-16-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 15/18] Add --graph prefix before line history output","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:54Z","receivedAt":"2010-08-05T16:11:54Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Makes the line level log output look good when used\nwith the '--graph' option.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n line.c |   66 ++++++++++++++++++++++++++++++++++++++++++++++-----------------\n 1 files changed, 48 insertions(+), 18 deletions(-)\n\ndiff --git a/line.c b/line.c\nindex 2e513da..7d80da8 100644\n--- a/line.c\n+++ b/line.c\n@@ -1125,6 +1125,13 @@ static void flush_lines(struct diff_options *opt, const char **ptr, const char *\n \tconst char *p = *ptr;\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *reset;\n+\tchar *line_prefix = \"\";\n+\tstruct strbuf *msgbuf;\n+\n+\tif (opt && opt->output_prefix) {\n+\t\tmsgbuf = opt->output_prefix(opt, opt->output_prefix_data);\n+\t\tline_prefix = msgbuf->buf;\n+\t}\n \n \tif (*color)\n \t\treset = diff_get_color_opt(opt, DIFF_RESET);\n@@ -1147,7 +1154,7 @@ static void flush_lines(struct diff_options *opt, const char **ptr, const char *\n \n \twhile (*ptr < end && *lno <= elno) {\n \t\tif (**ptr == '\\n') {\n-\t\t\tfprintf(opt->file, \"%s\", buf.buf);\n+\t\t\tfprintf(opt->file, \"%s%s\", line_prefix, buf.buf);\n \t\t\tif (*ptr - p) {\n \t\t\t\tfwrite(p, *ptr - p, 1, opt->file);\n \t\t\t}\n@@ -1158,7 +1165,7 @@ static void flush_lines(struct diff_options *opt, const char **ptr, const char *\n \t\t(*ptr)++;\n \t}\n \tif (*lno <= elno) {\n-\t\tfprintf(opt->file, \"%s\", buf.buf);\n+\t\tfprintf(opt->file, \"%s%s\", line_prefix, buf.buf);\n \t\tif (*ptr - p) {\n \t\t\tfwrite(p, *ptr - p, 1, opt->file);\n \t\t}\n@@ -1201,8 +1208,15 @@ static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk\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+\tchar *line_prefix = \"\";\n+\tstruct strbuf *msgbuf;\n \tint i;\n \n+\tif (opt && opt->output_prefix) {\n+\t\tmsgbuf = opt->output_prefix(opt, opt->output_prefix_data);\n+\t\tline_prefix = msgbuf->buf;\n+\t}\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@@ -1210,8 +1224,8 @@ static void diff_flush_chunks(struct diff_options *opt, struct line_chunk *chunk\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+\t\tfprintf(opt->file, \"%s%s@@ -%ld,%ld +%ld,%ld @@%s\\n\",\n+\t\t\tline_prefix, set, pstart, lenp, r->start, len, reset);\n \n \t\tdiff_flush_range(opt, chunk, r);\n \t}\n@@ -1230,6 +1244,13 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra\n \tconst char *reset = diff_get_color_opt(opt, DIFF_RESET);\n \tstruct line_chunk chunk;\n \tint must_show_header;\n+\tchar *line_prefix = \"\";\n+\tstruct strbuf *msgbuf;\n+\n+\tif (opt && opt->output_prefix) {\n+\t\tmsgbuf = opt->output_prefix(opt, opt->output_prefix_data);\n+\t\tline_prefix = msgbuf->buf;\n+\t}\n \n \t/*\n \t * the ranges that touch no different file, in this case\n@@ -1264,21 +1285,26 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra\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+\tstrbuf_addf(&header, \"%s%sdiff --git %s %s%s\\n\", line_prefix,\n+\t\t\tset, 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\tstrbuf_addf(&header, \"%s%snew file mode %06o%s\\n\",\n+\t\t\tline_prefix, 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\tstrbuf_addf(&header, \"%s%sdeleted file mode %06o%s\\n\",\n+\t\t\tline_prefix, 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\t\tstrbuf_addf(&header, \"%s%sold mode %06o%s\\n\",\n+\t\t\t\tline_prefix, set, one->mode, reset);\n+\t\t\tstrbuf_addf(&header, \"%s%snew mode %06o%s\\n\",\n+\t\t\t\tline_prefix, 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+\tfprintf(opt->file, \"%s%s--- %s%s\\n\", line_prefix, set, lbl[0], reset);\n+\tfprintf(opt->file, \"%s%s+++ %s%s\\n\", line_prefix, set, lbl[1], reset);\n \tfree((void *)a_one);\n \tfree((void *)b_two);\n \n@@ -1331,12 +1357,13 @@ static void flush_nontrivial_merge(struct rev_info *rev, struct diff_line_range\n \t\t\t\tmeta, range->spec->path, reset);\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\tfprintf(opt->file, \"%s%s@@ %ld,%ld @@%s\\n\",\n+\t\t\t\t\tline_prefix, 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\tfprintf(opt->file, \"%s\\n\", line_prefix);\n \t\t}\n \t\trange = range->next;\n \t}\n@@ -1348,6 +1375,8 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\n \tstruct diff_line_range *nontrivial = lookup_decoration(&rev->nontrivial_merge, &c->object);\n \tstruct log_info log;\n \tstruct diff_options *opt = &rev->diffopt;\n+\tchar *line_prefix = \"\";\n+\tstruct strbuf *msgbuf;\n \n \tif (range == NULL || ! (c->object.flags & NONTRIVIAL_MERGE ||\n \t\t\t\t\t\t\tc->object.flags & NEED_PRINT))\n@@ -1360,11 +1389,12 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\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 (opt && opt->output_prefix) {\n+\t\tmsgbuf = opt->output_prefix(opt, opt->output_prefix_data);\n+\t\tline_prefix = msgbuf->buf;\n+\t}\n+\tfprintf(rev->diffopt.file, \"%s\\n\", line_prefix);\n \n \tif (c->object.flags & NONTRIVIAL_MERGE)\n \t\tflush_nontrivial_merge(rev, nontrivial);\n-- \n1.7.2.20.g388bbb\n"},{"id":"147209","messageId":"1281024717-7855-17-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 16/18] Add --full-line-diff option","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:55Z","receivedAt":"2010-08-05T16:11:55Z","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        |   21 +++++++++++++++------\n revision.c    |    5 ++++-\n revision.h    |    3 ++-\n 4 files changed, 24 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0aa982b..9799c1c 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -86,6 +86,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 full_line_diff = 0;\n \tstruct userformat_want w;\n \tconst char *path = NULL, *pathspec = NULL;\n \tstatic struct diff_line_range *range = NULL, *r = NULL;\n@@ -93,6 +94,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, \"full-line-diff\", &full_line_diff, \"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@@ -225,6 +227,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->full_line_diff = full_line_diff;\n \t}\n }\n \ndiff --git a/line.c b/line.c\nindex 7d80da8..7ab0341 100644\n--- a/line.c\n+++ b/line.c\n@@ -1255,9 +1255,16 @@ static void diff_flush_filepair(struct rev_info *rev, struct diff_line_range *ra\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 * no sensible range->pair since there is no diff run.\n \t */\n \tif (one == NULL) {\n+\t\tif (rev->full_line_diff) {\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@@ -1378,8 +1385,9 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\n \tchar *line_prefix = \"\";\n \tstruct strbuf *msgbuf;\n \n-\tif (range == NULL || ! (c->object.flags & NONTRIVIAL_MERGE ||\n-\t\t\t\t\t\t\tc->object.flags & NEED_PRINT))\n+\tif (range == NULL || !(c->object.flags & NONTRIVIAL_MERGE ||\n+\t\t\tc->object.flags & NEED_PRINT ||\n+\t\t\trev->full_line_diff))\n \t\treturn;\n \n \tif (rev->graph)\n@@ -1400,7 +1408,7 @@ static void line_log_flush(struct rev_info *rev, struct commit *c)\n \t\tflush_nontrivial_merge(rev, nontrivial);\n \telse {\n \t\twhile (range) {\n-\t\t\tif (range->diff)\n+\t\t\tif (range->diff || (range->nr && rev->full_line_diff))\n \t\t\t\tdiff_flush_filepair(rev, range);\n \t\t\trange = range->next;\n \t\t}\n@@ -1432,7 +1440,7 @@ int cmd_line_log_walk(struct rev_info *rev)\n \t/* Clear the flags */\n \twhile (list && !limited) {\n \t\tlist->item->object.flags &= ~(RANGE_UPDATE | NONTRIVIAL_MERGE |\n-\t\t\t\t\t\tNEED_PRINT | EVIL_MERGE);\n+\t\t\t\tNEED_PRINT | EVIL_MERGE);\n \t\tlist = list->next;\n \t}\n \n@@ -1446,7 +1454,8 @@ int cmd_line_log_walk(struct rev_info *rev)\n \t\t}\n \n \t\tif (commit->object.flags & NEED_PRINT ||\n-\t\t\tcommit->object.flags & NONTRIVIAL_MERGE || rev->graph) {\n+\t\t\tcommit->object.flags & NONTRIVIAL_MERGE ||\n+\t\t\trev->full_line_diff) {\n \t\t\tline_log_flush(rev, commit);\n \t\t}\n \ndiff --git a/revision.c b/revision.c\nindex a6527ca..62fe002 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1887,7 +1887,10 @@ int prepare_revision_walk(struct rev_info *revs)\n \t\t\treturn -1;\n \tif (revs->topo_order)\n \t\tsort_in_topological_order(&revs->commits, revs->lifo);\n-\tif (revs->rewrite_parents && revs->line_level_traverse)\n+\tif (revs->full_line_diff)\n+\t\trevs->dense = 0;\n+\tif (revs->rewrite_parents && revs->line_level_traverse\n+\t\t&& !revs->full_line_diff)\n \t\tlimit_list_line(revs);\n \tif (revs->simplify_merges)\n \t\tsimplify_merges(revs);\ndiff --git a/revision.h b/revision.h\nindex 7f7d178..db901e5 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -73,7 +73,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_level_traverse:1;\n+\t\t\tline_level_traverse:1,\n+\t\t\tfull_line_diff:1;\n \n \t/* Diff flags */\n \tunsigned int\tdiff:1,\n-- \n1.7.2.20.g388bbb\n"},{"id":"147215","messageId":"1281024717-7855-18-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 17/18] Add test cases for '--graph' of line level log","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:56Z","receivedAt":"2010-08-05T16:11:56Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"t/t4301-log-line-single-history.sh:\n  test the linear line of history with '--graph' option;\n\nt/t4302-log-line-merge-history.sh:\n  test the case that there are merges in the history with\n  '--graph' option.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n t/t4301-log-line-single-history.sh |  277 ++++++++++++++++++++++++++++++++++++\n t/t4302-log-line-merge-history.sh  |   51 +++++++-\n 2 files changed, 327 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t4301-log-line-single-history.sh b/t/t4301-log-line-single-history.sh\nindex 9cf34f8..84bea11 100755\n--- a/t/t4301-log-line-single-history.sh\n+++ b/t/t4301-log-line-single-history.sh\n@@ -339,4 +339,281 @@ test_expect_success \\\n \t test_cmp current-linenum expected-linenum &&\n \t test_cmp current-always expected-always'\n \n+# Rerun all log with graph\n+test_expect_success \\\n+    'Show the line level log of path0 with --graph' \\\n+    'git log --pretty=format:%s%n%b --graph -L /func/,/^}/ path0 > current-path0-graph'\n+\n+test_expect_success \\\n+    'Show the line level log of path1 with --graph' \\\n+    'git log --pretty=format:%s%n%b --graph -L /output/,/^}/ path1 > current-path1-graph'\n+\n+test_expect_success \\\n+    'Show the line level log of two files with --graph' \\\n+    'git log --pretty=format:%s%n%b --graph -L /func/,/^}/ path0 --graph -L /output/,/^}/ path1 > current-pathall-graph'\n+\n+test_expect_success \\\n+    'Test the line number argument with --graph' \\\n+    'git log --pretty=format:%s%n%b --graph -L 1,2 path0 > current-linenum-graph'\n+\n+test_expect_success \\\n+\t'Test the --full-line-diff option with --graph option' \\\n+\t'git log --pretty=format:%s%n%b --full-line-diff --graph -L 1,2 path0 > current-always-graph'\n+\n+cat > expected-path0-graph <<\\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-graph <<\\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-graph <<\\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-graph <<\\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-graph <<\\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 path0 output.' \\\n+    'test_cmp current-path0-graph expected-path0-graph'\n+test_expect_success \\\n+\t'validate the path1 output.' \\\n+\t'test_cmp current-path1-graph expected-path1-graph'\n+test_expect_success \\\n+\t'validate the all path output.' \\\n+\t'test_cmp current-pathall-graph expected-pathall-graph'\n+test_expect_success \\\n+\t'validate graph output' \\\n+\t'test_cmp current-linenum-graph expected-linenum-graph'\n+test_expect_success \\\n+\t'validate --full-line-diff output' \\\n+\t'test_cmp current-always-graph expected-always-graph'\n+\n test_done\ndiff --git a/t/t4302-log-line-merge-history.sh b/t/t4302-log-line-merge-history.sh\nindex 02e7439..1536cc4 100755\n--- a/t/t4302-log-line-merge-history.sh\n+++ b/t/t4302-log-line-merge-history.sh\n@@ -66,7 +66,6 @@ Merge two branches\n \n nontrivial merge found\n path0\n-\n @@ 2,1 @@\n  \tprintf(\"hello earth and moon\");\n \n@@ -107,8 +106,58 @@ index 0000000..f628dea\n +\tprintf(\"hello\");\n +}\n EOF\n+\n+cat > expected-graph <<\\EOF\n+*   Merge two branches\n+|\\  \n+| | \n+| | nontrivial merge found\n+| | path0\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+| | @@ -2,1 +2,1 @@\n+| | -\tprintf(\"hello\");\n+| | +\tprintf(\"hello earth\");\n+| |   \n+* | Change path0 in feature\n+|/  \n+|   \n+|   diff --git a/path0 b/path0\n+|   index f628dea..a940ef6 100644\n+|   --- a/path0\n+|   +++ b/path0\n+|   @@ -2,1 +2,1 @@\n+|   -\tprintf(\"hello\");\n+|   +\tprint(\"hello moon\");\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 +2,1 @@\n+  +\tprintf(\"hello\");\n+EOF\n+\n+test_expect_success \\\n+    'Show the line log of the 2 line of path0 with graph' \\\n+    'git log --pretty=format:%s%n%b --graph -L 2,+1 path0 > current-graph'\n+\n test_expect_success \\\n     'validate the output.' \\\n     'test_cmp current expected'\n+test_expect_success \\\n+    'validate the graph output.' \\\n+    'test_cmp current-graph expected-graph'\n \n test_done\n-- \n1.7.2.20.g388bbb\n"},{"id":"147210","messageId":"1281024717-7855-19-git-send-email-struggleyb.nku@gmail.com","threadId":"24641","inReplyTo":"1281024717-7855-1-git-send-email-struggleyb.nku@gmail.com","subject":"[PATCH v4 18/18] Document line history browser","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-05T16:11:57Z","receivedAt":"2010-08-05T16:11:57Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Both 'git log' and 'git blame' support the same format\nof '-L' arguments, so we refactor its description into\na new file.\n\nAnd it is possible to use more than one '-L' option\nfor each path.\n\nSigned-off-by: Bo Yang <struggleyb.nku@gmail.com>\n---\n Documentation/blame-options.txt     |   19 +------------------\n Documentation/git-log.txt           |   15 +++++++++++++++\n Documentation/line-range-format.txt |   18 ++++++++++++++++++\n 3 files changed, 34 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 e970664..6f712e7 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@@ -63,6 +67,17 @@ OPTIONS\n \tNote that only message is considered, if also a diff is shown\n \tits size is not included.\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+You can also specify this option more than once before each path.\n+\n+\n+--full-line-diff::\n+\tAlways print the interesting range even if the current commit\n+\tdoes not change any line of the range.\n+\n [\\--] <path>...::\n \tShow only commits that affect any of the specified paths. To\n \tprevent confusion with options and branch names, paths may need\ndiff --git a/Documentation/line-range-format.txt b/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.2.20.g388bbb\n"},{"id":"147247","messageId":"201008052238.36892.trast@student.ethz.ch","threadId":"24641","inReplyTo":"1281024717-7855-12-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v4 11/18] Add tests for line history browser","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-08-05T20:38:36Z","receivedAt":"2010-08-05T20:38:36Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Bo Yang wrote:\n> t4301: for simple linear history only\n> t4302: for history containing merge\n> \n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> ---\n\nNote that applying this with --whitespace=fix will make the tests\nfail, as there are diffs contained which must preserve the SP TAB\nsequence of context lines.\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\nPlease split these and move them into the tests that generate them.\n\nI also think the style these days is\n\ntest_expect_success 'description' '\n\tline 1 &&\n\tline 2\n'\n\nbut please only convert them now if you see an obvious way to do it\nautomatically.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"147249","messageId":"201008052243.46738.trast@student.ethz.ch","threadId":"24641","inReplyTo":"1281024717-7855-3-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v4 02/18] parse-options: add two helper functions","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-08-05T20:43:46Z","receivedAt":"2010-08-05T20:43:46Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Bo Yang wrote:\n> 2. parse_options_next: make the API to deal with the next\n>    option/argument.\n\nNow that I'm re-reading it, this would be clearer as\n\n2. parse_options_next: skip the current argument, moving to the next\n   one.  Unless 'keep' is set, discard the skipped argument from the\n   final argument list.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"147248","messageId":"201008052309.03193.trast@student.ethz.ch","threadId":"24641","inReplyTo":"1281024717-7855-4-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v4 03/18] Add the basic data structure for line level history","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-08-05T21:09:02Z","receivedAt":"2010-08-05T21:09:02Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Bo Yang wrote:\n> 'struct diff_line_range' is the main data structure to keep\n> track of the line ranges we are currently interested in. The\n> user starts digging from a line range, and after examining the\n> diff that affects that range by a commit, we can find a new\n> range that corresponds to this range. So, we will associate this\n> new range with the commit's parent commit.\n> \n> There is one 'diff_line_range' for each file, and there are\n> multiple 'struct range' in each 'diff_line_range'. In this way,\n> we support multiple ranges.\n> \n> Within 'struct range', there are multiple 'struct print_range'\n> which represent a diff hunk.\n> \n> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n\n> diff --git a/line.c b/line.c\n\nSome error messages could be improved, e.g.\n\n> +\t\tif (obj->type != OBJ_COMMIT)\n> +\t\t\tdie(\"Non commit %s?\", revs->pending.objects[i].name);\n\n\"'%s' is not a commit\"\n\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,\n> +\t\t\t\trevs->pending.objects[found].name);\n\n\"You must specify exactly one starting commit for line history\"\n\nShowing two revisions from the command line is fairly arbitrary, what\nif the user specified three?  It also results in such oddness as\n\n  $ ./git-log next^@ -L 1,2 README\n  fatal: More than one commit to dig from: next and next?\n\nassuming the tip of 'next' is a merge.\n\n> +\tif (commit == NULL)\n> +\t\tdie(\"No commit specified?\");\n\n\"You must specify a starting commit for line history\"\n\n> +\t\tif (get_tree_entry(commit->object.sha1, r->spec->path,\n> +\t\t\tsha1, &mode))\n> +\t\t\tgoto error;\n[...]\n> +\treturn;\n> +error:\n> +\tdie(\"There is no path %s in the commit\", r->spec->path);\n\nSince die() never returns, you can move it in the place of the goto\nand make Dijkstra happy.\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\nYou immediately refactor this in the next commit, which is cute to\ntest the feature as indicated in the comment, but for a nicer series\nplease move the refactoring before this commit and just reuse the\ncode.\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\n\"-L argument must be <start>,<end>\"\n\nThough git-blame seems to imply ',$' if you do not give an end.  Any\nparticular reason why we do not want to be compatible with blame here?\n\n> +\tif (*term)\n> +\t\tdie(\"-L parameter's argument should be <start>,<end>\");\n\nSee above.\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\tint start, int end)\n\nIf I read the code correctly, it also ensures that no two ranges have\noverlapping extents, i.e., it will merge them if they overlap.\n\nWhich leads to the question: is this a requirement for the users of\nthe data structure, or just an optimization?  If it's a requirement,\nplease put this in a comment somewhere.\n\n> +\t/*\n> +\t * Note we support -M/-C to detect file rename\n> +\t */\n> +\topt->nr_paths = 0;\n\nDo we? :-)\n\nOut of curiosity: Without looking any further, I assume this disables\nthe path filtering stage that you had in early versions.  Did you\nnotice any speed hit or improvement by doing so?\n\n> diff --git a/line.h b/line.h\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\nSo 'end' is a start line number, and 'pstart' is an end line number?\n\nYou are using 'pstart' and 'pend' in other places in the header, too.\nWhat do they mean?  In line.c I inferred ptwo was \"previous two\", but\nhere it seems to be \"printing start\"?\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"147254","messageId":"201008052332.37435.trast@student.ethz.ch","threadId":"24641","inReplyTo":"1281024717-7855-9-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v4 08/18] map/take range to the parent of commits","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-08-05T21:32:37Z","receivedAt":"2010-08-05T21:32:37Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Bo Yang wrote:\n> The algorithm that maps 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> +#define SCALE_FACTOR 4\n> +/*\n> + * [p_start, p_end] represents the pre-image of current diff hunk,\n> + * [t_start, t_end] represnets the post-image of the current diff hunk,\n                            ^^\nTypo here ------------------/\n\n> + * [start, end] represents the currently interesting line range in\n> + * post-image,\n> + * [o_start, o_end] represents the original line range that coresponds\n> + * to current line range.\n> + */\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[...]\n> +\t/*\n> +\t * A heuristic for lines mapping:\n> +\t *\n> +\t * When the pre-image is no more than 1/4 of the post-image,\n> +\t * there is no effective way to find out which part of pre-image\n> +\t * corresponds to the currently interesting range of post-image.\n> +\t * And we are in the danger of tracking totally useless lines.\n> +\t * So, we just 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\nSo that's what SCALE_FACTOR is good for (and the comment should\nprobably say 1/SCALE_FACTOR instead).\n\nOut of curiosity, did you come up with 4 randomly or by experimentation?\n\n> +/*\n> + * When same == 1:\n> + * [p_start, p_end] represents the diff hunk line range of pre-image,\n> + * [t_start, t_end] represents the diff hunk line range of post-image.\n> + * When same == 0, they represents a range of idnetical lines between\n\n+ * When same == 0, they represent a range of identical lines between\n\n> + * two images.\n> + *\n> + * This function find out the corresponding line ranges of currently\n> + * interesting ranges which this diff hunk touches.\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\nYou took some time to comment map_lines, but not this one, sadly.\n\nI gather it works as\n\n  assign_parents_range\n  -> assign_range_to_parent once with map=1, once with map=0\n  -> either map_range_cb or take_range_cb\n  -> either map_range or take_range\n\nbut there are few comments on where the decisions should be obvious\nand where they are just heuristics.  Can you add some more comments to\nenlighten us?\n\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\nUse INT_MAX from limits.h (and besides, you're not guaranteed to have\n32 bits).\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\n> +\t\tassign_range_to_parent(rev, commit, p, r, &rev->diffopt, 1);\n\nIIUC, the latter line is\n\n  /* assign to the parent what we can */\n\nand the next one\n\n> +\t\tassign_range_to_parent(rev, commit, p, copy, &rev->diffopt, 0);\n\n  /* and remove it from our to-be-printed range */\n\nright?\n\nIf so, please rename the 'copy' variable since its purpose is not to\nbe a copy, but to hold entirely different data.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"147296","messageId":"AANLkTinZPwtdon-qCWEx5L2c-LnfrjKdBA0pdGFmFQig@mail.gmail.com","threadId":"24641","inReplyTo":"201008052238.36892.trast@student.ethz.ch","subject":"Re: [PATCH v4 11/18] Add tests for line history browser","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-06T05:28:47Z","receivedAt":"2010-08-06T05:28:47Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"On Fri, Aug 6, 2010 at 4:38 AM, Thomas Rast <trast@student.ethz.ch> wrote:\n> Bo Yang wrote:\n>> t4301: for simple linear history only\n>> t4302: for history containing merge\n>>\n>> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n>> ---\n>\n> Note that applying this with --whitespace=fix will make the tests\n> fail, as there are diffs contained which must preserve the SP TAB\n> sequence of context lines.\n\nSo, should I write the above into the commit message?\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"},{"id":"147305","messageId":"201008061104.05828.trast@student.ethz.ch","threadId":"24641","inReplyTo":"AANLkTinZPwtdon-qCWEx5L2c-LnfrjKdBA0pdGFmFQig@mail.gmail.com","subject":"Re: [PATCH v4 11/18] Add tests for line history browser","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-08-06T09:04:05Z","receivedAt":"2010-08-06T09:04:05Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Bo Yang wrote:\n> On Fri, Aug 6, 2010 at 4:38 AM, Thomas Rast <trast@student.ethz.ch> wrote:\n> > Bo Yang wrote:\n> >> t4301: for simple linear history only\n> >> t4302: for history containing merge\n> >>\n> >> Signed-off-by: Bo Yang <struggleyb.nku@gmail.com>\n> >> ---\n> >\n> > Note that applying this with --whitespace=fix will make the tests\n> > fail, as there are diffs contained which must preserve the SP TAB\n> > sequence of context lines.\n> \n> So, should I write the above into the commit message?\n\nYou can just write it after the ---, since it ceases to be relevant\nafter the email has been turned into a commit.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"147336","messageId":"7v39ur8r56.fsf@alter.siamese.dyndns.org","threadId":"24641","inReplyTo":"1281024717-7855-6-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v4 05/18] Parse the -L options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-08-06T19:42:13Z","receivedAt":"2010-08-06T19:42:13Z","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 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, *r = NULL;\n> +\tstatic struct parse_opt_ctx_t ctx;\n> +\tstatic struct line_opt_callback_data line_cb = {&range, &ctx, NULL};\n\nDo these have to be static?  cmd_log_init() may be near the top of the\ncall chain and has less reason to be reentrant, but it feels somewhat\nwrong if we are placing something that should live on stack in BSS.\n\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> +\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> + ... do the extra path thing ...\n> +\t\t\tpathspec = prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n\nPlease do not call it \"pathspec\", as this is a specific path in a commit.\n\"pathspec\" is a pattern to be matched to zero or more paths.\n\n> ...\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> +\t\tparse_revision_opt(rev, &ctx, options, log_opt_usage);\n> +\t}\n\nHmm, so the strategy is that you first run the command line through a pass\nof parse-options that is aware only of \"-L\" syntax, eat whatever it\nrecognizes, and give remainder to the setup_revisions().\n\nWhile I agree with that strategy in general, I think this implementation\nis ugly.  It may be even wrong.  For example, can a user specify a path\nthat begins with a dash with this parser?\n\nMy gut feeling is that the capturing of the (optional) second argument\ngiven to -L is better done inside your callback.\n\nNow, the current callback interface does not give you access to ctx so you\nmay need to invent a new type of \"more powerful callback API\" that gives\nyou access to the ctx as well, but if you did so, you should be able to do\nsomething like:\n\n    static int log_line_range_callback(...)\n    {\n\targ = parse_options_current(ctx);\n        ... make sure it is a line range, e.g. \"10,20\"\n        parse_options_next(ctx); /* consume it */\n        path = parse_options_current(ctx); /* peek the second position */\n        if (does it look like a path?) {\n\t\t... associate path with the range in arg\n\t\tparse_options_next(ctx); /* consume it */\n\t} else if (have we already got another range earlier?) {\n        \t... use the previous path with the range in arg\n        } else {\n        \tdie(\"-L range not followed by path\");\n\t}\n    }\n\nno?  In the above illustration, I am assuming that the \"more powerful\" one\nallows the callback to control even parsing of the first argument,\ni.e. parse-options does not call get_arg() before calling you back.\n\nAnd \"does it look like a path?\" logic could say something like \"If it is\nin the index, it is a path, even if it begins with a dash\", or \"If it is\nprefixed with ./, then it is always a path but we strip that dot-slash\nout\", and somesuch, to make the heuristic of \"do we have the optional\nsecond parameter?\" better than \"we do not allow a path that begins with a\ndash\".  After all, the callback for -L knows better than the generic\n\"parse-options\" infrastructure what to expect for the optional argument at\nthe second position.\n\nAnd if you do that, I suspect that you also can lose the \"clear up the\nlast range\" hack after the loop is done, no?\n"},{"id":"147338","messageId":"7vvd7n7cjs.fsf@alter.siamese.dyndns.org","threadId":"24641","inReplyTo":"1281024717-7855-4-git-send-email-struggleyb.nku@gmail.com","subject":"Re: [PATCH v4 03/18] Add the basic data structure for line level history","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-08-06T19:42:47Z","receivedAt":"2010-08-06T19:42:47Z","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> 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\nHmm...  I do not see anything you added to this header file that needs\nsuch a forward declaration.  Other files you added that include diffcore.h\nmay want to have that declaration, but I do not think this header file does.\n\n>  \n>  struct diff_filespec {\n>  \tunsigned char sha1[20];\n\n> diff --git a/line.h b/line.h\n> new file mode 100644\n> index 0000000..caf84c7\n> --- /dev/null\n> +++ b/line.h\n> @@ -0,0 +1,128 @@\n> ...\n> +struct print_range {\n> +\tint start, end;\n> +\tint pstart, pend;\n\nPlease describe what these fields mean.\n\n> +struct range {\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"},{"id":"147650","messageId":"AANLkTi=pzsPpC=gM3UEBAaMq7PGJYafW8SKHunVzrOyP@mail.gmail.com","threadId":"24641","inReplyTo":"7v39ur8r56.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v4 05/18] Parse the -L options","fromName":"Bo Yang","fromEmail":"struggleyb.nku@gmail.com","sentAt":"2010-08-10T15:40:39Z","receivedAt":"2010-08-10T15:40:39Z","isPatch":true,"sender":{"key":"struggleyb.nku@gmail.com","avatar":"https://avatars.githubusercontent.com/u/233030?v=4"},"body":"Hi Junio,\n\nOn Sat, Aug 7, 2010 at 3:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Bo Yang <struggleyb.nku@gmail.com> writes:\n>\n>>  static void cmd_log_init(int argc, const char **argv, const char *prefix,\n>>                        struct rev_info *rev, struct setup_revision_opt *opt)\n>>  {\n>>       int i;\n>>       int decoration_given = 0;\n>>       struct userformat_want w;\n>> +     const char *path = NULL, *pathspec = NULL;\n>> +     static struct diff_line_range *range = NULL, *r = NULL;\n>> +     static struct parse_opt_ctx_t ctx;\n>> +     static struct line_opt_callback_data line_cb = {&range, &ctx, NULL};\n>\n> Do these have to be static?  cmd_log_init() may be near the top of the\n> call chain and has less reason to be reentrant, but it feels somewhat\n> wrong if we are placing something that should live on stack in BSS.\n>\n>> +     static const struct option options[] = {\n>> +             OPT_CALLBACK('L', NULL, &line_cb, \"n,m\", \"Process only line range n,m, counting from 1\", log_line_range_callback),\n>> +             OPT_END()\n>> +     };\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>> + ... do the extra path thing ...\n>> +                     pathspec = prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n>\n> Please do not call it \"pathspec\", as this is a specific path in a commit.\n> \"pathspec\" is a pattern to be matched to zero or more paths.\n>\n>> ...\n>> +                     parse_options_next(&ctx, 1);\n>> +                     continue;\n>> +             case PARSE_OPT_UNKNOWN:\n>> +                     parse_options_next(&ctx, 1);\n>> +                     continue;\n>> +             }\n>> +             parse_revision_opt(rev, &ctx, options, log_opt_usage);\n>> +     }\n>\n> Hmm, so the strategy is that you first run the command line through a pass\n> of parse-options that is aware only of \"-L\" syntax, eat whatever it\n> recognizes, and give remainder to the setup_revisions().\n>\n> While I agree with that strategy in general, I think this implementation\n> is ugly.  It may be even wrong.  For example, can a user specify a path\n> that begins with a dash with this parser?\n>\n> My gut feeling is that the capturing of the (optional) second argument\n> given to -L is better done inside your callback.\n>\n> Now, the current callback interface does not give you access to ctx so you\n> may need to invent a new type of \"more powerful callback API\" that gives\n> you access to the ctx as well, but if you did so, you should be able to do\n> something like:\n>\n>    static int log_line_range_callback(...)\n>    {\n>        arg = parse_options_current(ctx);\n>        ... make sure it is a line range, e.g. \"10,20\"\n>        parse_options_next(ctx); /* consume it */\n>        path = parse_options_current(ctx); /* peek the second position */\n>        if (does it look like a path?) {\n>                ... associate path with the range in arg\n>                parse_options_next(ctx); /* consume it */\n>        } else if (have we already got another range earlier?) {\n>                ... use the previous path with the range in arg\n>        } else {\n>                die(\"-L range not followed by path\");\n>        }\n>    }\n>\n> no?  In the above illustration, I am assuming that the \"more powerful\" one\n> allows the callback to control even parsing of the first argument,\n> i.e. parse-options does not call get_arg() before calling you back.\n>\n> And \"does it look like a path?\" logic could say something like \"If it is\n> in the index, it is a path, even if it begins with a dash\", or \"If it is\n> prefixed with ./, then it is always a path but we strip that dot-slash\n> out\", and somesuch, to make the heuristic of \"do we have the optional\n> second parameter?\" better than \"we do not allow a path that begins with a\n> dash\".  After all, the callback for -L knows better than the generic\n> \"parse-options\" infrastructure what to expect for the optional argument at\n> the second position.\n>\n> And if you do that, I suspect that you also can lose the \"clear up the\n> last range\" hack after the loop is done, no?\n\nYes, I think so. And if I change the logic to what you suggest, it\nwill also make the later 'move/copy detect' related argument parsing\neasy. Because in move/copy detect, I should remove the 'remain path'\nbefore feed it to setup_revisions. So, I hope I can make this change\nalong with the 'move/copy detect' change together, I hope this is\nacceptable. :)\n\n-- \nRegards!\nBo\n----------------------------\nMy blog: http://blog.morebits.org\nWhy Git: http://www.whygitisbetterthanx.com/\n"}]}