For merge professionals, --rename-file is as bad a nightmare as resolving conflicts because they may have to create a rename file for every merge. --rename-notes takes advantage of rename notes to avoid that. Because rename notes are between commit A and A^, not between A and its merge base, we need to convert "rename path A to path B" to "rename blob A' to blob B'" and hope that mere base still has that blob A'. Merging notes this way is expensive. So it's cached in $GIT_DIR/rename-cache. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx> --- builtin/merge.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/builtin/merge.c b/builtin/merge.c index 95a6c26..6ad2e52 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -30,6 +30,7 @@ #include "fmt-merge-msg.h" #include "gpg-interface.h" #include "sequencer.h" +#include "notes.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -69,6 +70,7 @@ static int default_to_upstream = 1; static const char *sign_commit; static const char *rename_file; static struct strbuf manual_renames = STRBUF_INIT; +static const char *rename_note_ref; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -228,6 +230,7 @@ static struct option builtin_merge_options[] = { N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), OPT_FILENAME(0, "rename-file", &rename_file, N_("--rename-file to diff")), + OPT_STRING(0, "rename-notes", &rename_note_ref, N_("note-ref"), N_("--rename-notes to diff")), OPT_END() }; @@ -1161,6 +1164,102 @@ static struct commit_list *collect_parents(struct commit *head_commit, return remoteheads; } +static int merge_rename_note(const unsigned char *object_hash, + const unsigned char *note_hash, + char *note_path, + void *cb_data) +{ + struct strbuf *cache = cb_data; + struct strbuf sb = STRBUF_INIT; + const char *p, *end; + enum object_type type; + unsigned long size; + char *note; + + note = read_sha1_file(note_hash, &type, &size); + if (type != OBJ_BLOB) { + free(note); + return 0; + } + + p = note; + end = p + strlen(p); + while (p < end) { + const char *line_end = strchr(p, '\n'); + const char *arrow = strstr(p, " => "); + const char *src = p, *dst; + struct object_id src_oid; + struct object_id dst_oid; + + if (!line_end) + line_end = end; + p = line_end + 1; + + if (!arrow || arrow >= line_end) + continue; + + if (starts_with(src, "blob ") && src + 4 < arrow) { + strbuf_addf(cache, "%.*s\n", + (int)(line_end - src), src); + continue; + } + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s:%.*s", sha1_to_hex(object_hash), + (int)(arrow - src), src); + if (get_sha1(sb.buf, src_oid.hash)) + continue; + + dst = arrow + strlen(" => "); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s:%.*s", sha1_to_hex(object_hash), + (int)(line_end - dst), dst); + if (get_sha1(sb.buf, dst_oid.hash)) + continue; + + strbuf_addf(cache, "blob %s => %s\n", + oid_to_hex(&src_oid), + oid_to_hex(&dst_oid)); + } + + return 0; +} + +/* + * Traverse through the given notes tree, convert all "path to path" + * rename lines into "blob to blob" and return it. If cache_file is + * non-NULL, return it's content if still valid. Otherwise save the + * new content in it. + */ +static void merge_rename_notes(struct strbuf *cache, + struct notes_tree *t, const char *cache_file) +{ + struct object_id notes_oid; + + if (cache_file) { + struct object_id cache_oid; + + strbuf_reset(cache); + if (!resolve_ref_unsafe(t->ref, RESOLVE_REF_READING, + notes_oid.hash, NULL)) + return; + + if (strbuf_read_file(cache, cache_file, 0) > GIT_SHA1_HEXSZ + 1 && + cache->buf[GIT_SHA1_HEXSZ] == '\n' && + !get_oid_hex(cache->buf, &cache_oid) && + !oidcmp(¬es_oid, &cache_oid)) { + strbuf_remove(cache, 0, GIT_SHA1_HEXSZ + 1); + return; + } + } + + strbuf_reset(cache); + for_each_note(t, 0, merge_rename_note, cache); + if (cache_file && cache->len) + write_file(cache_file, "%s\n%s", + oid_to_hex(¬es_oid), cache->buf); +} + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; @@ -1260,10 +1359,25 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_with_options(builtin_merge_usage, builtin_merge_options); + if (rename_file && rename_note_ref) + die(_("--rename-file and --rename-notes are incompatible")); + if (rename_file && strbuf_read_file(&manual_renames, rename_file, 0) == -1) die(_("unable to read %s"), rename_file); + if (rename_note_ref) { + struct notes_tree rename_notes; + struct strbuf ref = STRBUF_INIT; + + strbuf_addstr(&ref, rename_note_ref); + expand_notes_ref(&ref); + init_notes(&rename_notes, ref.buf, NULL, 0); + strbuf_release(&ref); + merge_rename_notes(&manual_renames, &rename_notes, + git_path("GIT_RENAME_CACHE")); + } + if (!head_commit) { struct commit *remote_head; /* -- 2.7.0.125.g9eec362 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html