{"thread":{"id":"41223","subject":"[PATCH 1/7] diff-no-index: do not take a redundant prefix argument","startedAt":"2016-01-20T11:06:01Z","lastAt":"2016-01-22T17:17:26Z","messageCount":20,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano","Jeff King","Duy Nguyen"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"276433","messageId":"1453287968-26000-1-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":null,"subject":"[PATCH 0/7] Diff rename, manual correction, round 2","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:01Z","receivedAt":"2016-01-20T11:06:01Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Git is amazing. It can detect file renames without you telling it. But\neven the best fails sometimes. And when it fails, we have no way to\ncorrect it. Not so amazing..\n\nRound 1 was three years ago. The idea back then [1] was to let the\nuser say \"rename this path to this path\". Jeff quickly pointed out it\ndidn't help merge. And it should help merge to reduce merge conflicts.\nFor merge to take advantage of rename correction, the user needs to\nspell out \"rename this blob to this blob\" instead. Then I went away\ndoing something and dropped it.\n\nRound 2. I still want to keep \"rename path to path\", at least on the\nuser interface level. For git-merge, I do exactly what Jeff wrote back\nthen, traversing the whole notes tree to collect rename hints. But now\nI cache the result, so while traversing is expensive, we don't have to\npay often.\n\nThe first two patches are preparation. Patch 3 adds --rename-file\nwhere you can tell Git to \"rename this path to that path\" by writing a\nfile with lines like this\n\n    this path => that path\n\nI will need to support quoting, but that can come later. Patch 5 is\nsimilar.\n\nPatch 4 adds --rename-notes, where you can now store the above file in\na notes tree. The note will be used when its associate commit A is\ndiff'ed against A^.\n\nPatch 6 introduces a new syntax to that file, \"blob SHA-1 => SHA-1\".\nThis is the basis for patch 7, where we traverse the whole notes tree,\nconvert all non-blob lines into blob ones. Then we simply tell\ngit-merge to use that as a rename instruction file.\n\nExpect round 3 in 2019 (hopefully not)\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/202654\n\nNguyễn Thái Ngọc Duy (7):\n  diff-no-index: do not take a redundant prefix argument\n  diff.c: take \"prefix\" argument in diff_opt_parse()\n  diff: add --rename-file\n  log: add --rename-notes to correct renames per commit\n  merge: add --rename-file\n  diffcore-rename: allow to say \"rename this blob to that blob\"\n  merge: add --rename-notes\n\n Documentation/diff-options.txt   |   7 +++\n Documentation/pretty-options.txt |   5 ++\n builtin/am.c                     |   2 +-\n builtin/diff.c                   |   2 +-\n builtin/merge.c                  | 123 +++++++++++++++++++++++++++++++++++++++\n diff-no-index.c                  |   7 ++-\n diff.c                           |  13 ++++-\n diff.h                           |   5 +-\n diffcore-rename.c                | 104 ++++++++++++++++++++++++++++++++-\n log-tree.c                       |  32 ++++++++++\n merge-recursive.c                |   1 +\n merge-recursive.h                |   1 +\n revision.c                       |  12 +++-\n revision.h                       |   1 +\n t/t4001-diff-rename.sh           |  82 ++++++++++++++++++++++++++\n 15 files changed, 386 insertions(+), 11 deletions(-)\n\n-- \n2.7.0.125.g9eec362\n"},{"id":"276432","messageId":"1453287968-26000-2-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/7] diff-no-index: do not take a redundant prefix argument","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:02Z","receivedAt":"2016-01-20T11:06:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Prefix is already set up in \"revs\". The same prefix should be used for\nall options parsing. So kill the last argument. This patch does not\nactually change anything because the only caller does use the same\nprefix for init_revisions() and diff_no_index().\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/diff.c  | 2 +-\n diff-no-index.c | 4 ++--\n diff.h          | 2 +-\n 3 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex ed0acca..52c98a9 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -341,7 +341,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)\n \t}\n \tif (no_index)\n \t\t/* If this is a no-index diff, just run it and exit there. */\n-\t\tdiff_no_index(&rev, argc, argv, prefix);\n+\t\tdiff_no_index(&rev, argc, argv);\n \n \t/* Otherwise, we are doing the usual \"git\" diff */\n \trev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;\ndiff --git a/diff-no-index.c b/diff-no-index.c\nindex 8e0fd27..491e842 100644\n--- a/diff-no-index.c\n+++ b/diff-no-index.c\n@@ -237,12 +237,12 @@ static void fixup_paths(const char **path, struct strbuf *replacement)\n }\n \n void diff_no_index(struct rev_info *revs,\n-\t\t   int argc, const char **argv,\n-\t\t   const char *prefix)\n+\t\t   int argc, const char **argv)\n {\n \tint i, prefixlen;\n \tconst char *paths[2];\n \tstruct strbuf replacement = STRBUF_INIT;\n+\tconst char *prefix = revs->prefix;\n \n \tdiff_setup(&revs->diffopt);\n \tfor (i = 1; i < argc - 2; ) {\ndiff --git a/diff.h b/diff.h\nindex f7208ad..f61ee54 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -345,7 +345,7 @@ extern int diff_flush_patch_id(struct diff_options *, unsigned char *);\n \n extern int diff_result_code(struct diff_options *, int);\n \n-extern void diff_no_index(struct rev_info *, int, const char **, const char *);\n+extern void diff_no_index(struct rev_info *, int, const char **);\n \n extern int index_differs_from(const char *def, int diff_flags);\n \n-- \n2.7.0.125.g9eec362\n"},{"id":"276434","messageId":"1453287968-26000-3-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/7] diff.c: take \"prefix\" argument in diff_opt_parse()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:03Z","receivedAt":"2016-01-20T11:06:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This will be important later when diff_opt_parse() accepts paths as\narguments. Paths must be prefixed before access because setup code\nmoves cwd but does not (and cannot) update command line options.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/am.c    | 2 +-\n diff-no-index.c | 3 ++-\n diff.c          | 3 ++-\n diff.h          | 2 +-\n revision.c      | 2 +-\n 5 files changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 9fb42fd..f009b6c 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1657,7 +1657,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \n \t\tinit_revisions(&rev_info, NULL);\n \t\trev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;\n-\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);\n+\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);\n \t\tadd_pending_sha1(&rev_info, \"HEAD\", our_tree, 0);\n \t\tdiff_setup_done(&rev_info.diffopt);\n \t\trun_diff_index(&rev_info, 1);\ndiff --git a/diff-no-index.c b/diff-no-index.c\nindex 491e842..03daadb 100644\n--- a/diff-no-index.c\n+++ b/diff-no-index.c\n@@ -252,7 +252,8 @@ void diff_no_index(struct rev_info *revs,\n \t\telse if (!strcmp(argv[i], \"--\"))\n \t\t\ti++;\n \t\telse {\n-\t\t\tj = diff_opt_parse(&revs->diffopt, argv + i, argc - i);\n+\t\t\tj = diff_opt_parse(&revs->diffopt, argv + i, argc - i,\n+\t\t\t\t\t   revs->prefix);\n \t\t\tif (j <= 0)\n \t\t\t\tdie(\"invalid diff option/value: %s\", argv[i]);\n \t\t\ti += j;\ndiff --git a/diff.c b/diff.c\nindex 80eb0c2..8d38fe8 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3693,7 +3693,8 @@ static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)\n \treturn 1;\n }\n \n-int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n+int diff_opt_parse(struct diff_options *options,\n+\t\t   const char **av, int ac, const char *prefix)\n {\n \tconst char *arg = av[0];\n \tconst char *optarg;\ndiff --git a/diff.h b/diff.h\nindex f61ee54..76b5536 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -268,7 +268,7 @@ extern int parse_long_opt(const char *opt, const char **argv,\n extern int git_diff_basic_config(const char *var, const char *value, void *cb);\n extern int git_diff_ui_config(const char *var, const char *value, void *cb);\n extern void diff_setup(struct diff_options *);\n-extern int diff_opt_parse(struct diff_options *, const char **, int);\n+extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);\n extern void diff_setup_done(struct diff_options *);\n \n #define DIFF_DETECT_RENAME\t1\ndiff --git a/revision.c b/revision.c\nindex 0a282f5..14daefb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2049,7 +2049,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--ignore-missing\")) {\n \t\trevs->ignore_missing = 1;\n \t} else {\n-\t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc);\n+\t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);\n \t\tif (!opts)\n \t\t\tunkv[(*unkc)++] = arg;\n \t\treturn opts;\n-- \n2.7.0.125.g9eec362\n"},{"id":"276435","messageId":"1453287968-26000-4-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/7] diff: add --rename-file","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:04Z","receivedAt":"2016-01-20T11:06:04Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Git's heuristics to detect renames or copies works most of the time.\nThis option can be used to correct the result when it goes wrong.\nMatching pairs get max rename score and override even exact rename\ndetection.\n\nNote that --rename-file does not try to break existing diff pairs. So\nif you have \"abc => def\" in your file, but they are already paired up\n(e.g. \"abc => abc\" and \"def => def\") and not broken down by -B, then\nnothing happens.\n\nAn assumption is made in this patch, that the rename file only contains\na couple rename pairs, not thousands of them. Looping through all\nrename source and destination for each rename line will not affect\nperformance and we can keep the code simple.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/diff-options.txt |  7 +++++\n diff.c                         | 10 +++++++\n diff.h                         |  1 +\n diffcore-rename.c              | 64 ++++++++++++++++++++++++++++++++++++++++--\n t/t4001-diff-rename.sh         | 33 ++++++++++++++++++++++\n 5 files changed, 113 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt\nindex 306b7e3..7ae04a0 100644\n--- a/Documentation/diff-options.txt\n+++ b/Documentation/diff-options.txt\n@@ -380,6 +380,13 @@ endif::git-log[]\n \tprojects, so use it with caution.  Giving more than one\n \t`-C` option has the same effect.\n \n+--rename-file=<path>::\n+\tThe given file contains explicit rename pairs that override\n+\tautomatic detected renames. Each line contains a rename pair\n+\tin the following format:\n++\n+<source path> <space> \"=>\" <space> <destination path>\n+\n -D::\n --irreversible-delete::\n \tOmit the preimage for deletes, i.e. print only the header but not\ndiff --git a/diff.c b/diff.c\nindex 8d38fe8..36cf08b 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3773,6 +3773,16 @@ int diff_opt_parse(struct diff_options *options,\n \t\tDIFF_OPT_SET(options, RENAME_EMPTY);\n \telse if (!strcmp(arg, \"--no-rename-empty\"))\n \t\tDIFF_OPT_CLR(options, RENAME_EMPTY);\n+\telse if (skip_prefix(arg, \"--rename-file=\", &arg)) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tconst char *path = arg;\n+\n+\t\tif (prefix)\n+\t\t\tpath = prefix_filename(prefix, strlen(prefix), path);\n+\t\tif (strbuf_read_file(&sb, path, 0) == -1)\n+\t\t\tdie(_(\"unable to read %s\"), path);\n+\t\toptions->manual_renames = strbuf_detach(&sb, NULL); /* leak */\n+\t}\n \telse if (!strcmp(arg, \"--relative\"))\n \t\tDIFF_OPT_SET(options, RELATIVE_NAME);\n \telse if (skip_prefix(arg, \"--relative=\", &arg)) {\ndiff --git a/diff.h b/diff.h\nindex 76b5536..37179ba 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -176,6 +176,7 @@ struct diff_options {\n \tdiff_prefix_fn_t output_prefix;\n \tint output_prefix_length;\n \tvoid *output_prefix_data;\n+\tconst char *manual_renames;\n \n \tint diff_path_counter;\n };\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex af1fe08..79beec8 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -346,8 +346,11 @@ static int find_exact_renames(struct diff_options *options)\n \t\tinsert_file_table(&file_table, i, rename_src[i].p->one);\n \n \t/* Walk the destinations and find best source match */\n-\tfor (i = 0; i < rename_dst_nr; i++)\n+\tfor (i = 0; i < rename_dst_nr; i++) {\n+\t\tif (rename_dst[i].pair)\n+\t\t\tcontinue; /* dealt with exact match already. */\n \t\trenames += find_identical_files(&file_table, i, options);\n+\t}\n \n \t/* Free the hash data structure and entries */\n \thashmap_free(&file_table, 1);\n@@ -355,6 +358,61 @@ static int find_exact_renames(struct diff_options *options)\n \treturn renames;\n }\n \n+static int manual_rename(const char *src, int srclen,\n+\t\t\t const char *dst, int dstlen)\n+{\n+\tint src_index, dst_index;\n+\n+\tfor (src_index = 0; src_index < rename_src_nr; src_index++) {\n+\t\tconst char *path = rename_src[src_index].p->one->path;\n+\t\tif (strlen(path) == srclen && !strncmp(path, src, srclen))\n+\t\t\tbreak;\n+\t}\n+\tif (src_index == rename_src_nr)\n+\t\treturn 0;\n+\n+\tfor (dst_index = 0; dst_index < rename_dst_nr; dst_index++) {\n+\t\tconst char *path = rename_dst[dst_index].two->path;\n+\t\tif (strlen(path) == dstlen && !strncmp(path, dst, dstlen))\n+\t\t\tbreak;\n+\t}\n+\tif (dst_index == rename_dst_nr)\n+\t\treturn 0;\n+\n+\trecord_rename_pair(dst_index, src_index, MAX_SCORE);\n+\treturn 1;\n+}\n+\n+static int find_manual_renames(struct diff_options *options)\n+{\n+\tint renames = 0;\n+\tconst char *p, *end;\n+\n+\tif (!options->manual_renames)\n+\t\treturn 0;\n+\n+\tp = options->manual_renames;\n+\tend = p + strlen(p);\n+\twhile (p < end) {\n+\t\tconst char *line_end = strchr(p, '\\n');\n+\t\tconst char *arrow = strstr(p, \" => \");\n+\t\tconst char *src = p, *dst;\n+\n+\t\tif (!line_end)\n+\t\t\tline_end = end;\n+\t\tp = line_end + 1;\n+\n+\t\tif (!arrow || arrow >= line_end)\n+\t\t\tcontinue;\n+\n+\t\tdst = arrow + strlen(\" => \");\n+\t\trenames += manual_rename(src, arrow - src,\n+\t\t\t\t\t dst, line_end - dst);\n+\t}\n+\n+\treturn renames;\n+}\n+\n #define NUM_CANDIDATE_PER_DST 4\n static void record_if_better(struct diff_score m[], struct diff_score *o)\n {\n@@ -500,11 +558,13 @@ void diffcore_rename(struct diff_options *options)\n \tif (rename_dst_nr == 0 || rename_src_nr == 0)\n \t\tgoto cleanup; /* nothing to do */\n \n+\trename_count = find_manual_renames(options);\n+\n \t/*\n \t * We really want to cull the candidates list early\n \t * with cheap tests in order to avoid doing deltas.\n \t */\n-\trename_count = find_exact_renames(options);\n+\trename_count += find_exact_renames(options);\n \n \t/* Did we only want exact renames? */\n \tif (minimum_score == MAX_SCORE)\ndiff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh\nindex 2f327b7..ab9a666 100755\n--- a/t/t4001-diff-rename.sh\n+++ b/t/t4001-diff-rename.sh\n@@ -156,4 +156,37 @@ test_expect_success 'rename pretty print common prefix and suffix overlap' '\n \ttest_i18ngrep \" d/f/{ => f}/e \" output\n '\n \n+test_expect_success 'manual rename correction' '\n+\ttest_create_repo correct-rename &&\n+\t(\n+\t\tcd correct-rename &&\n+\t\techo one > old-one &&\n+\t\techo two > old-two &&\n+\t\tgit add old-one old-two &&\n+\t\tgit commit -m old &&\n+\t\tgit rm old-one old-two &&\n+\t\techo one > new-one &&\n+\t\techo two > new-two &&\n+\t\tgit add new-one new-two &&\n+\t\tgit commit -m new &&\n+\t\tgit diff -M --summary HEAD^ | grep rename >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\t rename old-one => new-one (100%)\n+\t\t rename old-two => new-two (100%)\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\n+\t\tcat >correction <<-\\EOF &&\n+\t\told-one => new-two\n+\t\told-two => new-one\n+\t\tEOF\n+\t\tgit diff -M --rename-file=correction --summary HEAD^ | grep rename >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\t rename old-two => new-one (100%)\n+\t\t rename old-one => new-two (100%)\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_done\n-- \n2.7.0.125.g9eec362\n"},{"id":"276436","messageId":"1453287968-26000-5-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/7] log: add --rename-notes to correct renames per commit","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:05Z","receivedAt":"2016-01-20T11:06:05Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"For simplicity, the note of commit A implies rename correction between\nA^ and A. If parents are manipulated (e.g. \"git log --reflog\") then\nthe rename output may surprise people.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/pretty-options.txt |  5 +++++\n log-tree.c                       | 32 ++++++++++++++++++++++++++++++++\n revision.c                       | 10 ++++++++++\n revision.h                       |  1 +\n t/t4001-diff-rename.sh           | 24 ++++++++++++++++++++++++\n 5 files changed, 72 insertions(+)\n\ndiff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt\nindex 4b659ac..15a2971 100644\n--- a/Documentation/pretty-options.txt\n+++ b/Documentation/pretty-options.txt\n@@ -75,6 +75,11 @@ being displayed. Examples: \"--notes=foo\" will show only notes from\n --[no-]standard-notes::\n \tThese options are deprecated. Use the above --notes/--no-notes\n \toptions instead.\n+\n+--rename-notes=<ref>::\n+\tGet per-commit rename instructions from notes. See option\n+\t`--rename-file` for more information. If both `--rename-notes`\n+\tand `--rename-file` are specified, the last one takes effect.\n endif::git-rev-list[]\n \n --show-signature::\ndiff --git a/log-tree.c b/log-tree.c\nindex f70a30e..e5766a6 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -788,6 +788,36 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)\n \treturn !opt->loginfo;\n }\n \n+static void populate_rename_notes(struct rev_info *opt, const struct object_id *oid)\n+{\n+\tstatic char *last_note;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tconst unsigned char *sha1;\n+\n+\tif (!opt->rename_notes)\n+\t\treturn;\n+\n+\t/*\n+\t * \"--rename-notes=abc --rename-file=def\" is specified in this\n+\t * order, --rename-file wins.\n+\t */\n+\tif (opt->diffopt.manual_renames != NULL &&\n+\t    opt->diffopt.manual_renames != last_note)\n+\t\treturn;\n+\n+\tfree(last_note);\n+\topt->diffopt.manual_renames = NULL;\n+\n+\tsha1 = get_note(opt->rename_notes, oid->hash);\n+\tif (!sha1)\n+\t\treturn;\n+\n+\tlast_note = read_sha1_file(sha1, &type, &size);\n+\tif (type == OBJ_BLOB)\n+\t\topt->diffopt.manual_renames = last_note;\n+}\n+\n /*\n  * Show the diff of a commit.\n  *\n@@ -805,6 +835,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n \tparse_commit_or_die(commit);\n \toid = &commit->tree->object.oid;\n \n+\tpopulate_rename_notes(opt, &commit->object.oid);\n+\n \t/* Root commit? */\n \tparents = get_saved_parents(opt, commit);\n \tif (!parents) {\ndiff --git a/revision.c b/revision.c\nindex 14daefb..20346c1 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1958,6 +1958,16 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->notes_opt.use_default_notes = 1;\n \t} else if (!strcmp(arg, \"--no-standard-notes\")) {\n \t\trevs->notes_opt.use_default_notes = 0;\n+\t} else if (skip_prefix(arg, \"--rename-notes=\", &optarg)) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tstruct notes_tree *nt;\n+\n+\t\tstrbuf_addstr(&buf, optarg);\n+\t\texpand_notes_ref(&buf);\n+\t\trevs->rename_notes = nt = xcalloc(1, sizeof(*nt));\n+\t\tinit_notes(nt, buf.buf, NULL, 0);\n+\t\tstrbuf_release(&buf);\n+\t\trevs->diffopt.manual_renames = NULL;\n \t} else if (!strcmp(arg, \"--oneline\")) {\n \t\trevs->verbose_header = 1;\n \t\tget_commit_format(\"oneline\", revs);\ndiff --git a/revision.h b/revision.h\nindex 23857c0..db2f225 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -189,6 +189,7 @@ struct rev_info {\n \t/* diff info for patches and for paths limiting */\n \tstruct diff_options diffopt;\n \tstruct diff_options pruning;\n+\tstruct notes_tree *rename_notes;\n \n \tstruct reflog_walk_info *reflog_info;\n \tstruct decoration children;\ndiff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh\nindex ab9a666..21d9378 100755\n--- a/t/t4001-diff-rename.sh\n+++ b/t/t4001-diff-rename.sh\n@@ -189,4 +189,28 @@ test_expect_success 'manual rename correction' '\n \t)\n '\n \n+test_expect_success 'rename correction from notes' '\n+\t(\n+\t\tcd correct-rename &&\n+\t\tgit show --summary -M HEAD | grep rename >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\t rename old-one => new-one (100%)\n+\t\t rename old-two => new-two (100%)\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\n+\t\tcat >correction <<-\\EOF &&\n+\t\told-one => new-two\n+\t\told-two => new-one\n+\t\tEOF\n+\t\tgit notes --ref=rename add -F correction HEAD &&\n+\t\tgit show --summary -M --rename-notes=rename HEAD | grep rename >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\t rename old-two => new-one (100%)\n+\t\t rename old-one => new-two (100%)\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_done\n-- \n2.7.0.125.g9eec362\n"},{"id":"276437","messageId":"1453287968-26000-6-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 5/7] merge: add --rename-file","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:06Z","receivedAt":"2016-01-20T11:06:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"For one-time amateurs, --rename-file could be used to specify what path\nshould be renamed to what path between heads and the merge base, when\nthey hit big merge conflicts because diff fails to recognize renames\ncorrectly.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/merge.c   | 9 +++++++++\n merge-recursive.c | 1 +\n merge-recursive.h | 1 +\n 3 files changed, 11 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 15bf95b..95a6c26 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -67,6 +67,8 @@ static int abort_current_merge;\n static int show_progress = -1;\n static int default_to_upstream = 1;\n static const char *sign_commit;\n+static const char *rename_file;\n+static struct strbuf manual_renames = STRBUF_INIT;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n@@ -225,6 +227,7 @@ static struct option builtin_merge_options[] = {\n \t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, N_(\"key-id\"),\n \t  N_(\"GPG sign commit\"), PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \tOPT_BOOL(0, \"overwrite-ignore\", &overwrite_ignore, N_(\"update ignored files (default)\")),\n+\tOPT_FILENAME(0, \"rename-file\", &rename_file, N_(\"--rename-file to diff\")),\n \tOPT_END()\n };\n \n@@ -664,6 +667,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\to.renormalize = option_renormalize;\n \t\to.show_rename_progress =\n \t\t\tshow_progress == -1 ? isatty(2) : show_progress;\n+\t\tif (manual_renames.len)\n+\t\t\to.manual_renames = manual_renames.buf;\n \n \t\tfor (x = 0; x < xopts_nr; x++)\n \t\t\tif (parse_merge_opt(&o, xopts[x]))\n@@ -1255,6 +1260,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_merge_usage,\n \t\t\tbuiltin_merge_options);\n \n+\tif (rename_file &&\n+\t    strbuf_read_file(&manual_renames, rename_file, 0) == -1)\n+\t\tdie(_(\"unable to read %s\"), rename_file);\n+\n \tif (!head_commit) {\n \t\tstruct commit *remote_head;\n \t\t/*\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8eabde2..ec7e044 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -492,6 +492,7 @@ static struct string_list *get_renames(struct merge_options *o,\n \topts.rename_score = o->rename_score;\n \topts.show_rename_progress = o->show_rename_progress;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\topts.manual_renames = o->manual_renames;\n \tdiff_setup_done(&opts);\n \tdiff_tree_sha1(o_tree->object.oid.hash, tree->object.oid.hash, \"\", &opts);\n \tdiffcore_std(&opts);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 9e090a3..898b169 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -27,6 +27,7 @@ struct merge_options {\n \tstruct string_list current_file_set;\n \tstruct string_list current_directory_set;\n \tstruct string_list df_conflict_file_set;\n+\tconst char *manual_renames;\n };\n \n /* merge_trees() but with recursive ancestor consolidation */\n-- \n2.7.0.125.g9eec362\n"},{"id":"276438","messageId":"1453287968-26000-7-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 6/7] diffcore-rename: allow to say \"rename this blob to that blob\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:07Z","receivedAt":"2016-01-20T11:06:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This will be needed for git-merge, where we do a diff between two\ntrees far away. We need to translate paths to blobs so that merge can\nstll use it.\n\nThis patch has another potential use: to reduce processing at rename\ndetection. We can cache the rename output in a big file with these \"blob\nto blob\" lines. The next diff will be fast because we only need to\ncompare SHA-1 instead of doing deltas. The naive find_manual_renames()\nwill have to be rewritten to dealwith large cache file though.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n diffcore-rename.c      | 40 ++++++++++++++++++++++++++++++++++++++++\n t/t4001-diff-rename.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 65 insertions(+)\n\ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 79beec8..a06c06a 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -358,6 +358,38 @@ static int find_exact_renames(struct diff_options *options)\n \treturn renames;\n }\n \n+static int manual_rename_blob(const char *src, int srclen,\n+\t\t\t      const char *dst, int dstlen)\n+{\n+\tstruct object_id oid;\n+\tint src_index, dst_index;\n+\n+\tif (srclen != GIT_SHA1_HEXSZ || get_oid_hex(src, &oid))\n+\t\treturn 0;\n+\n+\tfor (src_index = 0; src_index < rename_src_nr; src_index++) {\n+\t\tconst unsigned char *sha1 = rename_src[src_index].p->one->sha1;\n+\t\tif (hashcmp(oid.hash, sha1))\n+\t\t\tbreak;\n+\t}\n+\tif (src_index == rename_src_nr)\n+\t\treturn 0;\n+\n+\tif (dstlen != GIT_SHA1_HEXSZ || get_oid_hex(dst, &oid))\n+\t\treturn 0;\n+\n+\tfor (dst_index = 0; dst_index < rename_dst_nr; dst_index++) {\n+\t\tconst unsigned char *sha1 = rename_dst[dst_index].two->sha1;\n+\t\tif (hashcmp(oid.hash, sha1))\n+\t\t\tbreak;\n+\t}\n+\tif (dst_index == rename_dst_nr)\n+\t\treturn 0;\n+\n+\trecord_rename_pair(dst_index, src_index, MAX_SCORE);\n+\treturn 1;\n+}\n+\n static int manual_rename(const char *src, int srclen,\n \t\t\t const char *dst, int dstlen)\n {\n@@ -406,6 +438,14 @@ static int find_manual_renames(struct diff_options *options)\n \t\t\tcontinue;\n \n \t\tdst = arrow + strlen(\" => \");\n+\n+\t\tif (skip_prefix(src, \"blob \", &src)) {\n+\t\t\trenames +=\n+\t\t\t\tmanual_rename_blob(src, arrow - src,\n+\t\t\t\t\t\t   dst, line_end - dst);\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\trenames += manual_rename(src, arrow - src,\n \t\t\t\t\t dst, line_end - dst);\n \t}\ndiff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh\nindex 21d9378..4d5c667 100755\n--- a/t/t4001-diff-rename.sh\n+++ b/t/t4001-diff-rename.sh\n@@ -189,6 +189,31 @@ test_expect_success 'manual rename correction' '\n \t)\n '\n \n+test_expect_success 'manual rename correction with blobs' '\n+\t(\n+\t\tcd correct-rename &&\n+\t\tgit diff -M --summary HEAD^ | grep rename >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\t rename old-one => new-one (100%)\n+\t\t rename old-two => new-two (100%)\n+\t\tEOF\n+\t\ttest_cmp expected actual &&\n+\n+\t\tONE=`echo one | git hash-object --stdin` &&\n+\t\tTWO=`echo two | git hash-object --stdin` &&\n+\t\tcat >correction <<-EOF &&\n+\t\tblob $ONE => $TWO\n+\t\tblob $TWO => $ONE\n+\t\tEOF\n+\t\tgit diff -M --rename-file=correction --summary HEAD^ | grep rename >actual &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\t rename old-two => new-one (100%)\n+\t\t rename old-one => new-two (100%)\n+\t\tEOF\n+\t\ttest_cmp expected actual\n+\t)\n+'\n+\n test_expect_success 'rename correction from notes' '\n \t(\n \t\tcd correct-rename &&\n-- \n2.7.0.125.g9eec362\n"},{"id":"276439","messageId":"1453287968-26000-8-git-send-email-pclouds@gmail.com","threadId":"41223","inReplyTo":"1453287968-26000-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 7/7] merge: add --rename-notes","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-20T11:06:08Z","receivedAt":"2016-01-20T11:06:08Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"For merge professionals, --rename-file is as bad a nightmare as\nresolving conflicts because they may have to create a rename file for\nevery merge. --rename-notes takes advantage of rename notes to avoid that.\n\nBecause rename notes are between commit A and A^, not between A and its\nmerge base, we need to convert \"rename path A to path B\" to \"rename blob\nA' to blob B'\" and hope that mere base still has that blob A'.\n\nMerging notes this way is expensive. So it's cached in $GIT_DIR/rename-cache.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/merge.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 114 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 95a6c26..6ad2e52 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -30,6 +30,7 @@\n #include \"fmt-merge-msg.h\"\n #include \"gpg-interface.h\"\n #include \"sequencer.h\"\n+#include \"notes.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -69,6 +70,7 @@ static int default_to_upstream = 1;\n static const char *sign_commit;\n static const char *rename_file;\n static struct strbuf manual_renames = STRBUF_INIT;\n+static const char *rename_note_ref;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n@@ -228,6 +230,7 @@ static struct option builtin_merge_options[] = {\n \t  N_(\"GPG sign commit\"), PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \tOPT_BOOL(0, \"overwrite-ignore\", &overwrite_ignore, N_(\"update ignored files (default)\")),\n \tOPT_FILENAME(0, \"rename-file\", &rename_file, N_(\"--rename-file to diff\")),\n+\tOPT_STRING(0, \"rename-notes\", &rename_note_ref, N_(\"note-ref\"), N_(\"--rename-notes to diff\")),\n \tOPT_END()\n };\n \n@@ -1161,6 +1164,102 @@ static struct commit_list *collect_parents(struct commit *head_commit,\n \treturn remoteheads;\n }\n \n+static int merge_rename_note(const unsigned char *object_hash,\n+\t\t\t     const unsigned char *note_hash,\n+\t\t\t     char *note_path,\n+\t\t\t     void *cb_data)\n+{\n+\tstruct strbuf *cache = cb_data;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *p, *end;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tchar *note;\n+\n+\tnote = read_sha1_file(note_hash, &type, &size);\n+\tif (type != OBJ_BLOB) {\n+\t\tfree(note);\n+\t\treturn 0;\n+\t}\n+\n+\tp = note;\n+\tend = p + strlen(p);\n+\twhile (p < end) {\n+\t\tconst char *line_end = strchr(p, '\\n');\n+\t\tconst char *arrow = strstr(p, \" => \");\n+\t\tconst char *src = p, *dst;\n+\t\tstruct object_id src_oid;\n+\t\tstruct object_id dst_oid;\n+\n+\t\tif (!line_end)\n+\t\t\tline_end = end;\n+\t\tp = line_end + 1;\n+\n+\t\tif (!arrow || arrow >= line_end)\n+\t\t\tcontinue;\n+\n+\t\tif (starts_with(src, \"blob \") && src + 4 < arrow) {\n+\t\t\tstrbuf_addf(cache, \"%.*s\\n\",\n+\t\t\t\t    (int)(line_end - src), src);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s:%.*s\", sha1_to_hex(object_hash),\n+\t\t\t    (int)(arrow - src), src);\n+\t\tif (get_sha1(sb.buf, src_oid.hash))\n+\t\t\tcontinue;\n+\n+\t\tdst = arrow + strlen(\" => \");\n+\t\tstrbuf_reset(&sb);\n+\t\tstrbuf_addf(&sb, \"%s:%.*s\", sha1_to_hex(object_hash),\n+\t\t\t    (int)(line_end - dst), dst);\n+\t\tif (get_sha1(sb.buf, dst_oid.hash))\n+\t\t\tcontinue;\n+\n+\t\tstrbuf_addf(cache, \"blob %s => %s\\n\",\n+\t\t\t    oid_to_hex(&src_oid),\n+\t\t\t    oid_to_hex(&dst_oid));\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Traverse through the given notes tree, convert all \"path to path\"\n+ * rename lines into \"blob to blob\" and return it. If cache_file is\n+ * non-NULL, return it's content if still valid. Otherwise save the\n+ * new content in it.\n+ */\n+static void merge_rename_notes(struct strbuf *cache,\n+\t\t\t       struct notes_tree *t, const char *cache_file)\n+{\n+\tstruct object_id notes_oid;\n+\n+\tif (cache_file) {\n+\t\tstruct object_id cache_oid;\n+\n+\t\tstrbuf_reset(cache);\n+\t\tif (!resolve_ref_unsafe(t->ref, RESOLVE_REF_READING,\n+\t\t\t\t\tnotes_oid.hash, NULL))\n+\t\t\treturn;\n+\n+\t\tif (strbuf_read_file(cache, cache_file, 0) > GIT_SHA1_HEXSZ + 1 &&\n+\t\t    cache->buf[GIT_SHA1_HEXSZ] == '\\n' &&\n+\t\t    !get_oid_hex(cache->buf, &cache_oid) &&\n+\t\t    !oidcmp(&notes_oid, &cache_oid)) {\n+\t\t\tstrbuf_remove(cache, 0, GIT_SHA1_HEXSZ + 1);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tstrbuf_reset(cache);\n+\tfor_each_note(t, 0, merge_rename_note, cache);\n+\tif (cache_file && cache->len)\n+\t\twrite_file(cache_file, \"%s\\n%s\",\n+\t\t\t   oid_to_hex(&notes_oid), cache->buf);\n+}\n+\n int cmd_merge(int argc, const char **argv, const char *prefix)\n {\n \tunsigned char result_tree[20];\n@@ -1260,10 +1359,25 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_merge_usage,\n \t\t\tbuiltin_merge_options);\n \n+\tif (rename_file && rename_note_ref)\n+\t\tdie(_(\"--rename-file and --rename-notes are incompatible\"));\n+\n \tif (rename_file &&\n \t    strbuf_read_file(&manual_renames, rename_file, 0) == -1)\n \t\tdie(_(\"unable to read %s\"), rename_file);\n \n+\tif (rename_note_ref) {\n+\t\tstruct notes_tree rename_notes;\n+\t\tstruct strbuf ref = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&ref, rename_note_ref);\n+\t\texpand_notes_ref(&ref);\n+\t\tinit_notes(&rename_notes, ref.buf, NULL, 0);\n+\t\tstrbuf_release(&ref);\n+\t\tmerge_rename_notes(&manual_renames, &rename_notes,\n+\t\t\t\t   git_path(\"GIT_RENAME_CACHE\"));\n+\t}\n+\n \tif (!head_commit) {\n \t\tstruct commit *remote_head;\n \t\t/*\n-- \n2.7.0.125.g9eec362\n"},{"id":"276445","messageId":"xmqqr3hc57at.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"1453287968-26000-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 2/7] diff.c: take \"prefix\" argument in diff_opt_parse()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-20T20:23:38Z","receivedAt":"2016-01-20T20:23:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> This will be important later when diff_opt_parse() accepts paths as\n> arguments. Paths must be prefixed before access because setup code\n> moves cwd but does not (and cannot) update command line options.\n\nThe above sounds like a sensible thing to do (note: I didn't read\nthe patch or remainder of the series), but makes me wonder how the\nexisting --orderfile option works without this support.  Perhaps it\nis not working and needs to be updated to take advantage of this\nchange, too?\n\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  builtin/am.c    | 2 +-\n>  diff-no-index.c | 3 ++-\n>  diff.c          | 3 ++-\n>  diff.h          | 2 +-\n>  revision.c      | 2 +-\n>  5 files changed, 7 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/am.c b/builtin/am.c\n> index 9fb42fd..f009b6c 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -1657,7 +1657,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n>  \n>  \t\tinit_revisions(&rev_info, NULL);\n>  \t\trev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;\n> -\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);\n> +\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);\n>  \t\tadd_pending_sha1(&rev_info, \"HEAD\", our_tree, 0);\n>  \t\tdiff_setup_done(&rev_info.diffopt);\n>  \t\trun_diff_index(&rev_info, 1);\n> diff --git a/diff-no-index.c b/diff-no-index.c\n> index 491e842..03daadb 100644\n> --- a/diff-no-index.c\n> +++ b/diff-no-index.c\n> @@ -252,7 +252,8 @@ void diff_no_index(struct rev_info *revs,\n>  \t\telse if (!strcmp(argv[i], \"--\"))\n>  \t\t\ti++;\n>  \t\telse {\n> -\t\t\tj = diff_opt_parse(&revs->diffopt, argv + i, argc - i);\n> +\t\t\tj = diff_opt_parse(&revs->diffopt, argv + i, argc - i,\n> +\t\t\t\t\t   revs->prefix);\n>  \t\t\tif (j <= 0)\n>  \t\t\t\tdie(\"invalid diff option/value: %s\", argv[i]);\n>  \t\t\ti += j;\n> diff --git a/diff.c b/diff.c\n> index 80eb0c2..8d38fe8 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -3693,7 +3693,8 @@ static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)\n>  \treturn 1;\n>  }\n>  \n> -int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n> +int diff_opt_parse(struct diff_options *options,\n> +\t\t   const char **av, int ac, const char *prefix)\n>  {\n>  \tconst char *arg = av[0];\n>  \tconst char *optarg;\n> diff --git a/diff.h b/diff.h\n> index f61ee54..76b5536 100644\n> --- a/diff.h\n> +++ b/diff.h\n> @@ -268,7 +268,7 @@ extern int parse_long_opt(const char *opt, const char **argv,\n>  extern int git_diff_basic_config(const char *var, const char *value, void *cb);\n>  extern int git_diff_ui_config(const char *var, const char *value, void *cb);\n>  extern void diff_setup(struct diff_options *);\n> -extern int diff_opt_parse(struct diff_options *, const char **, int);\n> +extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);\n>  extern void diff_setup_done(struct diff_options *);\n>  \n>  #define DIFF_DETECT_RENAME\t1\n> diff --git a/revision.c b/revision.c\n> index 0a282f5..14daefb 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -2049,7 +2049,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n>  \t} else if (!strcmp(arg, \"--ignore-missing\")) {\n>  \t\trevs->ignore_missing = 1;\n>  \t} else {\n> -\t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc);\n> +\t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);\n>  \t\tif (!opts)\n>  \t\t\tunkv[(*unkc)++] = arg;\n>  \t\treturn opts;\n"},{"id":"276447","messageId":"20160120202946.GA6092@sigill.intra.peff.net","threadId":"41223","inReplyTo":"xmqqr3hc57at.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 2/7] diff.c: take \"prefix\" argument in diff_opt_parse()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-01-20T20:29:46Z","receivedAt":"2016-01-20T20:29:46Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Jan 20, 2016 at 12:23:38PM -0800, Junio C Hamano wrote:\n\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n> \n> > This will be important later when diff_opt_parse() accepts paths as\n> > arguments. Paths must be prefixed before access because setup code\n> > moves cwd but does not (and cannot) update command line options.\n> \n> The above sounds like a sensible thing to do (note: I didn't read\n> the patch or remainder of the series), but makes me wonder how the\n> existing --orderfile option works without this support.  Perhaps it\n> is not working and needs to be updated to take advantage of this\n> change, too?\n\nYeah, I think it simply does not work.\n\n  $ >main-order\n  $ mkdir subdir && >subdir/sub-order\n  $ cd subdir\n  $ git show -Osub-order\n  fatal: failed to read orderfile 'sub-order': No such file or directory\n  $ git show -Omain-order\n  [shows diff]\n\n-Peff\n"},{"id":"276459","messageId":"xmqqh9i753by.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"20160120202946.GA6092@sigill.intra.peff.net","subject":"Re: [PATCH 2/7] diff.c: take \"prefix\" argument in diff_opt_parse()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-20T21:49:21Z","receivedAt":"2016-01-20T21:49:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Jan 20, 2016 at 12:23:38PM -0800, Junio C Hamano wrote:\n>\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>> \n>> > This will be important later when diff_opt_parse() accepts paths as\n>> > arguments. Paths must be prefixed before access because setup code\n>> > moves cwd but does not (and cannot) update command line options.\n>> \n>> The above sounds like a sensible thing to do (note: I didn't read\n>> the patch or remainder of the series), but makes me wonder how the\n>> existing --orderfile option works without this support.  Perhaps it\n>> is not working and needs to be updated to take advantage of this\n>> change, too?\n>\n> Yeah, I think it simply does not work.\n>\n>   $ >main-order\n>   $ mkdir subdir && >subdir/sub-order\n>   $ cd subdir\n>   $ git show -Osub-order\n>   fatal: failed to read orderfile 'sub-order': No such file or directory\n>   $ git show -Omain-order\n>   [shows diff]\n\nGood.\n\nThen 2/7 can be rerolled and advertised as \"make -O to work from\nsubdirectories\", and can gradulate regardless of the remainder of\nthe series.  Even if the rest needs rerolls to get it right (or\ntakes until 2019 to mature ;-), we will have one less change to\nre-review in the process as we can push these early and obviously\ncorrect part out separately.\n"},{"id":"276464","messageId":"xmqq4me750rq.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"1453287968-26000-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 3/7] diff: add --rename-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-20T22:44:41Z","receivedAt":"2016-01-20T22:44:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> Git's heuristics to detect renames or copies works most of the time.\n> This option can be used to correct the result when it goes wrong.\n> Matching pairs get max rename score and override even exact rename\n> detection.\n>\n> Note that --rename-file does not try to break existing diff pairs. So\n> if you have \"abc => def\" in your file, but they are already paired up\n> (e.g. \"abc => abc\" and \"def => def\") and not broken down by -B, then\n> nothing happens.\n>\n> An assumption is made in this patch, that the rename file only contains\n> a couple rename pairs, not thousands of them. Looping through all\n> rename source and destination for each rename line will not affect\n> performance and we can keep the code simple.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\n> +--rename-file=<path>::\n> +\tThe given file contains explicit rename pairs that override\n> +\tautomatic detected renames. Each line contains a rename pair\n> +\tin the following format:\n> ++\n> +<source path> <space> \"=>\" <space> <destination path>\n> +\n\nObviously this needs path-quoting support before it can get anywhere\nnear 'next', but I found the basic idea outlined in the change in\ndiffcore-rename.c in this patch OK.  The manual one comes even\nbefore the \"look for identical ones\" step, which is logically the\nright thing to do.\n\nOne small nit is that using MAX_SCORE may have unintended fallouts.\nThe manually specified one is chosen among rename candidates not\nbecause it has the highest similarity score---it in fact may not\nhave a good similarity score at all, but we pair the paths because\nthe user tells us to (and the user knows better than we do).\n\nI am just wondering if it is easy to make the similarity score shown\nat the beginning of diff displayed differently.  For manually paired\npaths, we can and should keep \"rename from OLDPATH\" and \"rename to\nNEWPATH\" entries, but I think \"similarity index 100%\" should ideally\nbe not shown for them, and if we have to have the line (e.g. perhaps\nexisting tools expect a line that begins with \"similarity index\" to\nexist), show something like \"similarity index N/A\" that is clearly\ndifferent from \"100%\".\n"},{"id":"276465","messageId":"xmqqzivz3m3a.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"1453287968-26000-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 3/7] diff: add --rename-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-20T22:47:05Z","receivedAt":"2016-01-20T22:47:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> Git's heuristics to detect renames or copies works most of the time.\n> This option can be used to correct the result when it goes wrong.\n> Matching pairs get max rename score and override even exact rename\n> detection.\n>\n> Note that --rename-file does not try to break existing diff pairs. So\n> if you have \"abc => def\" in your file, but they are already paired up\n> (e.g. \"abc => abc\" and \"def => def\") and not broken down by -B, then\n> nothing happens.\n\nBy the way, how would this interact with \"diff -R\"?\n\nDo you need to spell --rename-file in reverse before you can run the\ndiff in reverse?\n"},{"id":"276468","messageId":"xmqqoacf3k3v.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"1453287968-26000-5-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 4/7] log: add --rename-notes to correct renames per commit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-20T23:29:56Z","receivedAt":"2016-01-20T23:29:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> For simplicity, the note of commit A implies rename correction between\n> A^ and A. If parents are manipulated (e.g. \"git log --reflog\") then\n> the rename output may surprise people.\n\nI do not think \"git log --reflog\" attempts to show changes to bring\nthe tree of @{2} into the shape of @{1}, even though for traversal\npurposes it pretends as if @{1}'s parent is @{2}.  So I am not sure\nwhat you are trying to say in the above sentence.\n\nA path limited \"git log -- path1/ path2/...\" also manipulates the\ncommit->parents for traversal purposes, but I think the patch is\nstill produced against the true parents (there is a call to\nget_saved_parents() in log_tree_diff() that shows the change for a\ngiven commit), and in that context, commit A that has notes about\nthe change to bring the tree of commit A^ to its tree still makes\nsense.\n\nI'd be more worried about \"git log -m -p\"--the diff against the\nsecond and subsequent parents would not want to use the notes that\ndescribes the change between the first parent and the resulting\nmerge.\n\n\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Documentation/pretty-options.txt |  5 +++++\n>  log-tree.c                       | 32 ++++++++++++++++++++++++++++++++\n>  revision.c                       | 10 ++++++++++\n>  revision.h                       |  1 +\n>  t/t4001-diff-rename.sh           | 24 ++++++++++++++++++++++++\n>  5 files changed, 72 insertions(+)\n>\n> diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt\n> index 4b659ac..15a2971 100644\n> --- a/Documentation/pretty-options.txt\n> +++ b/Documentation/pretty-options.txt\n> @@ -75,6 +75,11 @@ being displayed. Examples: \"--notes=foo\" will show only notes from\n>  --[no-]standard-notes::\n>  \tThese options are deprecated. Use the above --notes/--no-notes\n>  \toptions instead.\n> +\n> +--rename-notes=<ref>::\n> +\tGet per-commit rename instructions from notes. See option\n> +\t`--rename-file` for more information. If both `--rename-notes`\n> +\tand `--rename-file` are specified, the last one takes effect.\n>  endif::git-rev-list[]\n>  \n>  --show-signature::\n> diff --git a/log-tree.c b/log-tree.c\n> index f70a30e..e5766a6 100644\n> --- a/log-tree.c\n> +++ b/log-tree.c\n> @@ -788,6 +788,36 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)\n>  \treturn !opt->loginfo;\n>  }\n>  \n> +static void populate_rename_notes(struct rev_info *opt, const struct object_id *oid)\n> +{\n> +\tstatic char *last_note;\n> +\tenum object_type type;\n> +\tunsigned long size;\n> +\tconst unsigned char *sha1;\n> +\n> +\tif (!opt->rename_notes)\n> +\t\treturn;\n> +\n> +\t/*\n> +\t * \"--rename-notes=abc --rename-file=def\" is specified in this\n> +\t * order, --rename-file wins.\n> +\t */\n> +\tif (opt->diffopt.manual_renames != NULL &&\n> +\t    opt->diffopt.manual_renames != last_note)\n> +\t\treturn;\n> +\n> +\tfree(last_note);\n> +\topt->diffopt.manual_renames = NULL;\n> +\n> +\tsha1 = get_note(opt->rename_notes, oid->hash);\n> +\tif (!sha1)\n> +\t\treturn;\n> +\n> +\tlast_note = read_sha1_file(sha1, &type, &size);\n> +\tif (type == OBJ_BLOB)\n> +\t\topt->diffopt.manual_renames = last_note;\n> +}\n> +\n>  /*\n>   * Show the diff of a commit.\n>   *\n> @@ -805,6 +835,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n>  \tparse_commit_or_die(commit);\n>  \toid = &commit->tree->object.oid;\n>  \n> +\tpopulate_rename_notes(opt, &commit->object.oid);\n> +\n>  \t/* Root commit? */\n>  \tparents = get_saved_parents(opt, commit);\n>  \tif (!parents) {\n> diff --git a/revision.c b/revision.c\n> index 14daefb..20346c1 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -1958,6 +1958,16 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n>  \t\trevs->notes_opt.use_default_notes = 1;\n>  \t} else if (!strcmp(arg, \"--no-standard-notes\")) {\n>  \t\trevs->notes_opt.use_default_notes = 0;\n> +\t} else if (skip_prefix(arg, \"--rename-notes=\", &optarg)) {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tstruct notes_tree *nt;\n> +\n> +\t\tstrbuf_addstr(&buf, optarg);\n> +\t\texpand_notes_ref(&buf);\n> +\t\trevs->rename_notes = nt = xcalloc(1, sizeof(*nt));\n> +\t\tinit_notes(nt, buf.buf, NULL, 0);\n> +\t\tstrbuf_release(&buf);\n> +\t\trevs->diffopt.manual_renames = NULL;\n>  \t} else if (!strcmp(arg, \"--oneline\")) {\n>  \t\trevs->verbose_header = 1;\n>  \t\tget_commit_format(\"oneline\", revs);\n> diff --git a/revision.h b/revision.h\n> index 23857c0..db2f225 100644\n> --- a/revision.h\n> +++ b/revision.h\n> @@ -189,6 +189,7 @@ struct rev_info {\n>  \t/* diff info for patches and for paths limiting */\n>  \tstruct diff_options diffopt;\n>  \tstruct diff_options pruning;\n> +\tstruct notes_tree *rename_notes;\n>  \n>  \tstruct reflog_walk_info *reflog_info;\n>  \tstruct decoration children;\n> diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh\n> index ab9a666..21d9378 100755\n> --- a/t/t4001-diff-rename.sh\n> +++ b/t/t4001-diff-rename.sh\n> @@ -189,4 +189,28 @@ test_expect_success 'manual rename correction' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'rename correction from notes' '\n> +\t(\n> +\t\tcd correct-rename &&\n> +\t\tgit show --summary -M HEAD | grep rename >actual &&\n> +\t\tcat >expected <<-\\EOF &&\n> +\t\t rename old-one => new-one (100%)\n> +\t\t rename old-two => new-two (100%)\n> +\t\tEOF\n> +\t\ttest_cmp expected actual &&\n> +\n> +\t\tcat >correction <<-\\EOF &&\n> +\t\told-one => new-two\n> +\t\told-two => new-one\n> +\t\tEOF\n> +\t\tgit notes --ref=rename add -F correction HEAD &&\n> +\t\tgit show --summary -M --rename-notes=rename HEAD | grep rename >actual &&\n> +\t\tcat >expected <<-\\EOF &&\n> +\t\t rename old-two => new-one (100%)\n> +\t\t rename old-one => new-two (100%)\n> +\t\tEOF\n> +\t\ttest_cmp expected actual\n> +\t)\n> +'\n> +\n>  test_done\n"},{"id":"276496","messageId":"20160121114844.GA19318@lanh","threadId":"41223","inReplyTo":"xmqqh9i753by.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 2/7] diff.c: take \"prefix\" argument in diff_opt_parse()","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-21T11:48:44Z","receivedAt":"2016-01-21T11:48:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Wed, Jan 20, 2016 at 01:49:21PM -0800, Junio C Hamano wrote:\n> Jeff King <peff@peff.net> writes:\n> \n> > On Wed, Jan 20, 2016 at 12:23:38PM -0800, Junio C Hamano wrote:\n> >\n> >> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n> >> \n> >> > This will be important later when diff_opt_parse() accepts paths as\n> >> > arguments. Paths must be prefixed before access because setup code\n> >> > moves cwd but does not (and cannot) update command line options.\n> >> \n> >> The above sounds like a sensible thing to do (note: I didn't read\n> >> the patch or remainder of the series), but makes me wonder how the\n> >> existing --orderfile option works without this support.  Perhaps it\n> >> is not working and needs to be updated to take advantage of this\n> >> change, too?\n> >\n> > Yeah, I think it simply does not work.\n> >\n> >   $ >main-order\n> >   $ mkdir subdir && >subdir/sub-order\n> >   $ cd subdir\n> >   $ git show -Osub-order\n> >   fatal: failed to read orderfile 'sub-order': No such file or directory\n> >   $ git show -Omain-order\n> >   [shows diff]\n> \n> Good.\n> \n> Then 2/7 can be rerolled and advertised as \"make -O to work from\n> subdirectories\", and can gradulate regardless of the remainder of\n> the series.  Even if the rest needs rerolls to get it right (or\n> takes until 2019 to mature ;-), we will have one less change to\n> re-review in the process as we can push these early and obviously\n> correct part out separately.\n> \n\nI didn't know there was already an option that takes a path. I read\nthrough the function and found another one. So here's the standalone\npatch that fixes both.\n\n-- 8< --\nSubject: [PATCH] diff: make -O and --output work in subdirectory\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n builtin/am.c          |  2 +-\n diff-no-index.c       |  3 ++-\n diff.c                | 14 ++++++++++----\n diff.h                |  2 +-\n revision.c            |  2 +-\n t/t4056-diff-order.sh |  6 ++++++\n 6 files changed, 21 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 9fb42fd..f009b6c 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1657,7 +1657,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \n \t\tinit_revisions(&rev_info, NULL);\n \t\trev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;\n-\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);\n+\t\tdiff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);\n \t\tadd_pending_sha1(&rev_info, \"HEAD\", our_tree, 0);\n \t\tdiff_setup_done(&rev_info.diffopt);\n \t\trun_diff_index(&rev_info, 1);\ndiff --git a/diff-no-index.c b/diff-no-index.c\nindex 8e0fd27..aa81a15 100644\n--- a/diff-no-index.c\n+++ b/diff-no-index.c\n@@ -252,7 +252,8 @@ void diff_no_index(struct rev_info *revs,\n \t\telse if (!strcmp(argv[i], \"--\"))\n \t\t\ti++;\n \t\telse {\n-\t\t\tj = diff_opt_parse(&revs->diffopt, argv + i, argc - i);\n+\t\t\tj = diff_opt_parse(&revs->diffopt, argv + i, argc - i,\n+\t\t\t\t\t   revs->prefix);\n \t\t\tif (j <= 0)\n \t\t\t\tdie(\"invalid diff option/value: %s\", argv[i]);\n \t\t\ti += j;\ndiff --git a/diff.c b/diff.c\nindex 80eb0c2..2136b69 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3693,12 +3693,16 @@ static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)\n \treturn 1;\n }\n \n-int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n+int diff_opt_parse(struct diff_options *options,\n+\t\t   const char **av, int ac, const char *prefix)\n {\n \tconst char *arg = av[0];\n \tconst char *optarg;\n \tint argcount;\n \n+\tif (!prefix)\n+\t\tprefix = \"\";\n+\n \t/* Output format options */\n \tif (!strcmp(arg, \"-p\") || !strcmp(arg, \"-u\") || !strcmp(arg, \"--patch\")\n \t    || opt_arg(arg, 'U', \"unified\", &options->context))\n@@ -3915,7 +3919,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n \telse if (!strcmp(arg, \"--pickaxe-regex\"))\n \t\toptions->pickaxe_opts |= DIFF_PICKAXE_REGEX;\n \telse if ((argcount = short_opt('O', av, &optarg))) {\n-\t\toptions->orderfile = optarg;\n+\t\tconst char *path = prefix_filename(prefix, strlen(prefix), optarg);\n+\t\toptions->orderfile = xstrdup(path);\n \t\treturn argcount;\n \t}\n \telse if ((argcount = parse_long_opt(\"diff-filter\", av, &optarg))) {\n@@ -3954,9 +3959,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n \telse if (!strcmp(arg, \"--no-function-context\"))\n \t\tDIFF_OPT_CLR(options, FUNCCONTEXT);\n \telse if ((argcount = parse_long_opt(\"output\", av, &optarg))) {\n-\t\toptions->file = fopen(optarg, \"w\");\n+\t\tconst char *path = prefix_filename(prefix, strlen(prefix), optarg);\n+\t\toptions->file = fopen(path, \"w\");\n \t\tif (!options->file)\n-\t\t\tdie_errno(\"Could not open '%s'\", optarg);\n+\t\t\tdie_errno(\"Could not open '%s'\", path);\n \t\toptions->close_file = 1;\n \t\treturn argcount;\n \t} else\ndiff --git a/diff.h b/diff.h\nindex 893f446..4537e38 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -268,7 +268,7 @@ extern int parse_long_opt(const char *opt, const char **argv,\n extern int git_diff_basic_config(const char *var, const char *value, void *cb);\n extern int git_diff_ui_config(const char *var, const char *value, void *cb);\n extern void diff_setup(struct diff_options *);\n-extern int diff_opt_parse(struct diff_options *, const char **, int);\n+extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);\n extern void diff_setup_done(struct diff_options *);\n \n #define DIFF_DETECT_RENAME\t1\ndiff --git a/revision.c b/revision.c\nindex 0a282f5..14daefb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2049,7 +2049,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--ignore-missing\")) {\n \t\trevs->ignore_missing = 1;\n \t} else {\n-\t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc);\n+\t\tint opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);\n \t\tif (!opts)\n \t\t\tunkv[(*unkc)++] = arg;\n \t\treturn opts;\ndiff --git a/t/t4056-diff-order.sh b/t/t4056-diff-order.sh\nindex c0460bb..43dd474 100755\n--- a/t/t4056-diff-order.sh\n+++ b/t/t4056-diff-order.sh\n@@ -68,6 +68,12 @@ test_expect_success POSIXPERM,SANITY 'unreadable orderfile' '\n \ttest_must_fail git diff -Ounreadable_file --name-only HEAD^..HEAD\n '\n \n+test_expect_success \"orderfile using option from subdir with --output\" '\n+\tmkdir subdir &&\n+\tgit -C subdir diff -O../order_file_1 --output ../actual --name-only HEAD^..HEAD &&\n+\ttest_cmp expect_1 actual\n+'\n+\n for i in 1 2\n do\n \ttest_expect_success \"orderfile using option ($i)\" '\n-- \n2.7.0.125.g9eec362\n\n-- 8< --\n"},{"id":"276500","messageId":"xmqqtwm6yg30.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"1453287968-26000-8-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 7/7] merge: add --rename-notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-21T17:53:23Z","receivedAt":"2016-01-21T17:53:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> +/*\n> + * Traverse through the given notes tree, convert all \"path to path\"\n> + * rename lines into \"blob to blob\" and return it. If cache_file is\n> + * non-NULL, return it's content if still valid. Otherwise save the\n> + * new content in it.\n> + */\n> +static void merge_rename_notes(struct strbuf *cache,\n> +\t\t\t       struct notes_tree *t, const char *cache_file)\n> +{\n> +\tstruct object_id notes_oid;\n> +\n> +\tif (cache_file) {\n> +\t...\n> +\t}\n> +\n> +\tstrbuf_reset(cache);\n> +\tfor_each_note(t, 0, merge_rename_note, cache);\n\nThis uses _all_ merge notes attached to _any_ commit in the history,\nwithout even checking if the commit is involved in the current merge\nbeing done?  How would that be useful?\n\nI also suspect that the data structure to keep track renames by\nusing notes needs a better design.  You seem to have a note per\ncommit and one note records a set of \"this goes to that\", and\nthat is the reason why you need to discriminately read everything\nunder the sun.\n\nI think the index into the notes tree for the purpose of this use\ncase should not be \"which commit records this set of renames?\",\nbut by \"what is the destination blob of possible rename\noperations?\".  IOW, if a path that held blob X was moved to\nanother path that holds blob Z in commit A, and if a path that\nheld blob Y was moved to another path that holds blob Z in commit\nB, attach a note to blob Z that records \"moved from X in A\" and\n\"moved from Y in B\".\n\nThen, after diffcore-rename has enumerated the potential rename\ndestinations, look these destination blobs up in the notes.  You\nsee a path with blob Z created, and you know if you have removed\npath that held X or Y in the potential rename source set, you\nfound a manually recorded rename.  If you have ancestry\ninformation when you do it, you could even reject \"move from X to\nZ\" when you know commit A is not involved in the ancestry path\ninvolved in the merge, but that is optional (as you may be\ncomparing two states that may not be related with each other).\n\nFor the purpose of helping \"git log\", a notes tree reorganized in\nsuch a way would be useful.  Again, when you find a potential\nrename destination that has blob Z as the result of the change,\nyou read the note attached to the blob to learn that it may have\ncome from a path that held blob X (if we are dealing with commit\nA), or it may have come from a path that held blob Y (if we are\ndealing with commit B).  So you can add a new field in diffopt\nstructure and allow the caller somewhere in the callchain\n(log_tree_diff(), perhaps?) to pass which resulting commit it\nwants the recorded renames to be used from.  When it is time for\n\"git log -M -p\" to show commit A, diffcore-rename down in the\ncallchain will get the diff_queue that contains \"diff A^..A\",\namong which there is a path that used to have blob Z that goes\naway, reads the notes for blob Z and notice that commit A moved a\npath with blob X that is going away was renamed to it, discarding\nthe other record for blob Z that talks about the change in commit\nB that is not relevant for the purpose of showing commit A.\n\nHmm?\n\n> @@ -1260,10 +1359,25 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(builtin_merge_usage,\n>  \t\t\tbuiltin_merge_options);\n>  \n> +\tif (rename_file && rename_note_ref)\n> +\t\tdie(_(\"--rename-file and --rename-notes are incompatible\"));\n> +\n>  \tif (rename_file &&\n>  \t    strbuf_read_file(&manual_renames, rename_file, 0) == -1)\n>  \t\tdie(_(\"unable to read %s\"), rename_file);\n>  \n> +\tif (rename_note_ref) {\n> +\t\tstruct notes_tree rename_notes;\n> +\t\tstruct strbuf ref = STRBUF_INIT;\n> +\n> +\t\tstrbuf_addstr(&ref, rename_note_ref);\n> +\t\texpand_notes_ref(&ref);\n> +\t\tinit_notes(&rename_notes, ref.buf, NULL, 0);\n> +\t\tstrbuf_release(&ref);\n> +\t\tmerge_rename_notes(&manual_renames, &rename_notes,\n> +\t\t\t\t   git_path(\"GIT_RENAME_CACHE\"));\n> +\t}\n> +\n\nThese new hunks must move way below the codepath and be run only\nafter we are certain that we need to do a real merge.  The merge\nlogic that follows this line tries to handle light-weight special\ncases (like an obvious fast-forward and already up-to-date cases)\nas early as possible to avoid doing unnecessary things.\n\nThis patch (and the earlier one adds strbuf-read-file of the\nrename-file) breaks the design by doing these new heavyweight\noperations whose result may not be used at all too early, I\nthink.\n\nIf you go the route of reorganizing the rename notes around the\ndestination blobs, then you wouldn't even be reading everything\nhere very high in the callchain (you would be doing so only after\ndiffcore-rename decides that rename notes may be worth reading),\nand that would fix this.\n\n>  \tif (!head_commit) {\n>  \t\tstruct commit *remote_head;\n>  \t\t/*\n\nThanks.\n"},{"id":"276523","messageId":"xmqqh9i6wn9q.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"20160121114844.GA19318@lanh","subject":"Re: [PATCH 2/7] diff.c: take \"prefix\" argument in diff_opt_parse()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-21T23:01:05Z","receivedAt":"2016-01-21T23:01:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks.\n"},{"id":"276526","messageId":"CACsJy8BZtr_2e-LyGpdQ52R1TBDL7x3ZZQPu+G3-tphBfrmAaw@mail.gmail.com","threadId":"41223","inReplyTo":"xmqqoacf3k3v.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 4/7] log: add --rename-notes to correct renames per commit","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-22T01:00:40Z","receivedAt":"2016-01-22T01:00:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Thu, Jan 21, 2016 at 6:29 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>\n>> For simplicity, the note of commit A implies rename correction between\n>> A^ and A. If parents are manipulated (e.g. \"git log --reflog\") then\n>> the rename output may surprise people.\n>\n> I do not think \"git log --reflog\" attempts to show changes to bring\n> the tree of @{2} into the shape of @{1}, even though for traversal\n> purposes it pretends as if @{1}'s parent is @{2}.  So I am not sure\n> what you are trying to say in the above sentence.\n\nHazy memory about hierarchy manipulation by reflog, maybe I'm wrong.\n\n> A path limited \"git log -- path1/ path2/...\" also manipulates the\n> commit->parents for traversal purposes, but I think the patch is\n> still produced against the true parents (there is a call to\n> get_saved_parents() in log_tree_diff() that shows the change for a\n> given commit), and in that context, commit A that has notes about\n> the change to bring the tree of commit A^ to its tree still makes\n> sense.\n>\n> I'd be more worried about \"git log -m -p\"--the diff against the\n> second and subsequent parents would not want to use the notes that\n> describes the change between the first parent and the resulting\n> merge.\n\nI \"fix\" this by providing the tools and let the user go wild. The line\n\"A => B\" says rename A to B for a diff between any source or target.\nBut they can also say rename A to B only if diff source is X, or diff\ntarget is Y, or both. The syntax I'm having for that is something like\nthis\n\n.source X # X can be anything that resolves to a tree SHA-1 with get_sha1()\n.target Y\nA => B\n-- \nDuy\n"},{"id":"276529","messageId":"CACsJy8D3-i4UpavWZz0QU6oJE3AK06ugjPPTMOtqUA+Sa7==qQ@mail.gmail.com","threadId":"41223","inReplyTo":"xmqqtwm6yg30.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH 7/7] merge: add --rename-notes","fromName":"Duy Nguyen","fromEmail":"pclouds@gmail.com","sentAt":"2016-01-22T03:35:56Z","receivedAt":"2016-01-22T03:35:56Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Jan 22, 2016 at 12:53 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> This uses _all_ merge notes attached to _any_ commit in the history,\n> without even checking if the commit is involved in the current merge\n> being done?  How would that be useful?\n>\n> I also suspect that the data structure to keep track renames by\n> using notes needs a better design.  You seem to have a note per\n> commit and one note records a set of \"this goes to that\", and\n> that is the reason why you need to discriminately read everything\n> under the sun.\n>\n> I think the index into the notes tree for the purpose of this use\n> case should not be \"which commit records this set of renames?\",\n> but by \"what is the destination blob of possible rename\n> operations?\".  IOW, if a path that held blob X was moved to\n> another path that holds blob Z in commit A, and if a path that\n> held blob Y was moved to another path that holds blob Z in commit\n> B, attach a note to blob Z that records \"moved from X in A\" and\n> \"moved from Y in B\".\n\nThis is exactly the output after merge_rename_notes() is done. A\nbunch of \"rename this blob to that blob\" pairs.\n\nThe problem with indexing notes tree by blob is how the user will\nmanage it. From the user perspective, I think it's natural to\nthink \"I have made this commit that renames this path to that\npath. But Git does not recognize it. I need to correct it by\nadding a note to the commit\".\n\nThe notes tree saves exactly that. Reading and updating can be\ndone with existing tools. Exchanging git notes is something we\nhave been avoiding looking at, but per-commit notes would make it\neasier to exchange too, I think.\n\nThe information in that form may not be the best way to be\nconsumed by Git. But that is what cache is for. We can either\ngenerate a secondary notes tree, indexed by blob, or naively a\nbig \"blob to blob\" rename file that I did in this patch. But it\ndoes not have to be visible to the user.\n\nIf notes-indexed-by-blob is exposed to the user, I think we need some\nmore helper tools to edit and view them because blob SHA-1 is not\nsomething the user deals with, normally. It feels not as easy as\nediting a simple text file, to me.\n-- \nDuy\n"},{"id":"276551","messageId":"xmqq60ylwn2x.fsf@gitster.mtv.corp.google.com","threadId":"41223","inReplyTo":"CACsJy8D3-i4UpavWZz0QU6oJE3AK06ugjPPTMOtqUA+Sa7==qQ@mail.gmail.com","subject":"Re: [PATCH 7/7] merge: add --rename-notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-22T17:17:26Z","receivedAt":"2016-01-22T17:17:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Duy Nguyen <pclouds@gmail.com> writes:\n\n> The problem with indexing notes tree by blob is how the user will\n> manage it.\n\nI do not think they have to be incompatible goals.  Allowing the\nuser to specify \"at this commit, this path was moved to that path\"\nmay be a good thing.  But at the same time, using a better data\nstructure at runtime is also important.\n\nUnless I am reading your \"cache\" code wrong, it is always reading a\nflat list of unsorted \"<blob> to <blob>\" entries describing\neverything that happened in the entire history of the project into\ncore, and then forcing the low-level code to find the entries\nrelevant to the change in the commit it is looking at at runtime.\n\nThe way the \"cache\" code stores and forces the runtime to use the\ndata makes it proportional to the size of the history of the project\n(here I am assuming uniform \"rename density\" across history, the\nlonger the history of a project, the more renames there would be) to\nfind the manually defined renames just to show a single change, no?\n\nWhat was disappointing is that the way the rename notes is used\nduring 'log -M' traversal is an example of a good design.  When\nshowing a single commit, the cost the code pays to get to the data\nfor a single commit out of the rename notes tree does not grow\nproportional to the size of the history.\n"}]}