{"thread":{"id":"40355","subject":"[PATCH 1/7] rerere: split conflict ID further","startedAt":"2015-09-14T23:57:23Z","lastAt":"2016-04-06T23:05:35Z","messageCount":32,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"269974","messageId":"1442275050-30497-1-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":null,"subject":"[PATCH 0/7] saving and replaying multiple variants with rerere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:23Z","receivedAt":"2015-09-14T23:57:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is a continuation of jc/rerere topic that is now in 'next'.\nThe old topic is purely about refactoring and code cleaning and this\nseries builds on top of it.\n\nWe record a previously seen conflict and its resolution in a\nsubdirectory of $GIT_DIR/rr-cache, and the name of the subdirectory\nis computed by hashing the \"shape\" of the conflicted hunk.  When a\nsimilar-looking conflict is encountered later in the working tree,\n\"git rerere\" tries to replay the recorded resolution by running a\nthree-way merge to apply the change to go from the previous conflict\nto the previous resolution to the current conflicted state.\n\nDepending on what appears in the context around the conflicted hunk,\nhowever, this three-way merge itself can conflict and fail to apply\nto a seemingly identical conflict.  You _could_ argue that you could\nmerge with reduced context lines to work around the problem, but\nthat is a risky proposition.\n\nAs a safer approach, these patches allow us to keep more than one\npairs of <preimage,postimage> (i.e. conflicted state and its\nresolution) in the same \"shape\".  If two paths have identical\nlooking conflict and one's resolution does not cleanly replay to the\nother's conflicted state, we now save the resolutions of these two\npaths as different \"variants\" so that they can be applied when we\nsee these conflicts again.\n\nI think the basic \"record and replay\" part is done with this round,\nbut I haven't really reviewed the part that deals with garbage\ncollection of old rerere database entries, which probably needs more\nwork (and could use help, hints hints...).\n\nJunio C Hamano (7):\n  rerere: split conflict ID further\n  rerere: scan $GIT_DIR/rr-cache/$ID when instantiating a rerere_id\n  rerere: handle leftover rr-cache/$ID directory and postimage files\n  rerere: delay the recording of preimage\n  rerere: allow multiple variants to exist\n  t4200: rerere a merge with two identical conflicts\n  rerere: do use multiple variants\n\n rerere.c          | 270 +++++++++++++++++++++++++++++++++++++++++++++---------\n rerere.h          |   4 +-\n t/t4200-rerere.sh |  92 ++++++++++++++++++-\n 3 files changed, 321 insertions(+), 45 deletions(-)\n\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269973","messageId":"1442275050-30497-2-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 1/7] rerere: split conflict ID further","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:24Z","receivedAt":"2015-09-14T23:57:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The plan is to keep assigning the backward compatible conflict ID\nbased on the hash of the (normalized) text of conflicts, keep using\nthat conflict ID as the directory name under $GIT_DIR/rr-cache/, but\nallow each conflicted path to use a separate \"variant\" to record\nresolutions, i.e. having more than one <preimage,postimage> pairs\nunder $GIT_DIR/rr-cache/$ID/ directory.  As the first step in that\ndirection, separate the shared \"conflict ID\" out of the rerere_id\nstructure.\n\nThe plan is to keep information per $ID in rerere_dir, that can be\nshared among rerere_id that is per conflicted path.\n\nWhen we are done with rerere(), which can be directly called from\nother programs like \"git apply\", \"git commit\" and \"git merge\", the\nshared rerere_dir structures can be freed entirely, so they are not\nreference-counted and they are not freed when we release rerere_id's\nthat reference them.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----\n rerere.h |  3 ++-\n 2 files changed, 58 insertions(+), 6 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 9bef24f..a5d8a06 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -8,6 +8,7 @@\n #include \"ll-merge.h\"\n #include \"attr.h\"\n #include \"pathspec.h\"\n+#include \"sha1-lookup.h\"\n \n #define RESOLVED 0\n #define PUNTED 1\n@@ -22,6 +23,23 @@ static int rerere_autoupdate;\n \n static char *merge_rr_path;\n \n+static int rerere_dir_nr;\n+static int rerere_dir_alloc;\n+\n+static struct rerere_dir {\n+\tunsigned char sha1[20];\n+} **rerere_dir;\n+\n+static void free_rerere_dirs(void)\n+{\n+\tint i;\n+\tfor (i = 0; i < rerere_dir_nr; i++)\n+\t\tfree(rerere_dir[i]);\n+\tfree(rerere_dir);\n+\trerere_dir_nr = rerere_dir_alloc = 0;\n+\trerere_dir = NULL;\n+}\n+\n static void free_rerere_id(struct string_list_item *item)\n {\n \tfree(item->util);\n@@ -29,7 +47,7 @@ static void free_rerere_id(struct string_list_item *item)\n \n static const char *rerere_id_hex(const struct rerere_id *id)\n {\n-\treturn id->hex;\n+\treturn sha1_to_hex(id->collection->sha1);\n }\n \n const char *rerere_path(const struct rerere_id *id, const char *file)\n@@ -40,6 +58,37 @@ const char *rerere_path(const struct rerere_id *id, const char *file)\n \treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n }\n \n+static const unsigned char *rerere_dir_sha1(size_t i, void *table)\n+{\n+\tstruct rerere_dir **rr_dir = table;\n+\treturn rr_dir[i]->sha1;\n+}\n+\n+static struct rerere_dir *find_rerere_dir(const char *hex)\n+{\n+\tunsigned char sha1[20];\n+\tstruct rerere_dir *rr_dir;\n+\tint pos;\n+\n+\tif (get_sha1_hex(hex, sha1))\n+\t\treturn NULL; /* BUG */\n+\tpos = sha1_pos(sha1, rerere_dir, rerere_dir_nr, rerere_dir_sha1);\n+\tif (pos < 0) {\n+\t\trr_dir = xmalloc(sizeof(*rr_dir));\n+\t\thashcpy(rr_dir->sha1, sha1);\n+\t\tpos = -1 - pos;\n+\n+\t\t/* Make sure the array is big enough ... */\n+\t\tALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc);\n+\t\t/* ... and add it in. */\n+\t\trerere_dir_nr++;\n+\t\tmemmove(rerere_dir + pos + 1, rerere_dir + pos,\n+\t\t\t(rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));\n+\t\trerere_dir[pos] = rr_dir;\n+\t}\n+\treturn rerere_dir[pos];\n+}\n+\n static int has_rerere_resolution(const struct rerere_id *id)\n {\n \tstruct stat st;\n@@ -50,7 +99,7 @@ static int has_rerere_resolution(const struct rerere_id *id)\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n-\tstrcpy(id->hex, hex);\n+\tid->collection = find_rerere_dir(hex);\n \treturn id;\n }\n \n@@ -810,12 +859,14 @@ int setup_rerere(struct string_list *merge_rr, int flags)\n int rerere(int flags)\n {\n \tstruct string_list merge_rr = STRING_LIST_INIT_DUP;\n-\tint fd;\n+\tint fd, status;\n \n \tfd = setup_rerere(&merge_rr, flags);\n \tif (fd < 0)\n \t\treturn 0;\n-\treturn do_plain_rerere(&merge_rr, fd);\n+\tstatus = do_plain_rerere(&merge_rr, fd);\n+\tfree_rerere_dirs();\n+\treturn status;\n }\n \n static int rerere_forget_one_path(const char *path, struct string_list *rr)\n@@ -900,7 +951,7 @@ int rerere_forget(struct pathspec *pathspec)\n static struct rerere_id *dirname_to_id(const char *name)\n {\n \tstatic struct rerere_id id;\n-\tstrcpy(id.hex, name);\n+\tid.collection = find_rerere_dir(name);\n \treturn &id;\n }\n \ndiff --git a/rerere.h b/rerere.h\nindex ce545d0..faf5a23 100644\n--- a/rerere.h\n+++ b/rerere.h\n@@ -15,8 +15,9 @@ struct pathspec;\n  */\n extern void *RERERE_RESOLVED;\n \n+struct rerere_dir;\n struct rerere_id {\n-\tchar hex[41];\n+\tstruct rerere_dir *collection;\n };\n \n extern int setup_rerere(struct string_list *, int);\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269975","messageId":"1442275050-30497-3-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 2/7] rerere: scan $GIT_DIR/rr-cache/$ID when instantiating a rerere_id","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:25Z","receivedAt":"2015-09-14T23:57:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This will help fixing bootstrap corner-case issues, e.g. having an\nempty $GIT_DIR/rr-cache/$ID directory would fail to record a\npreimage, in later changes in this series.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 32 +++++++++++++++++++++++++++++---\n 1 file changed, 29 insertions(+), 3 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex a5d8a06..fbdade8 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -26,8 +26,11 @@ static char *merge_rr_path;\n static int rerere_dir_nr;\n static int rerere_dir_alloc;\n \n+#define RR_HAS_POSTIMAGE 1\n+#define RR_HAS_PREIMAGE 2\n static struct rerere_dir {\n \tunsigned char sha1[20];\n+\tunsigned char status;\n } **rerere_dir;\n \n static void free_rerere_dirs(void)\n@@ -58,6 +61,27 @@ const char *rerere_path(const struct rerere_id *id, const char *file)\n \treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n }\n \n+static int is_rr_file(const char *name, const char *filename)\n+{\n+\treturn !strcmp(name, filename);\n+}\n+\n+static void scan_rerere_dir(struct rerere_dir *rr_dir)\n+{\n+\tstruct dirent *de;\n+\tDIR *dir = opendir(git_path(\"rr-cache/%s\", sha1_to_hex(rr_dir->sha1)));\n+\n+\tif (!dir)\n+\t\treturn;\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tif (is_rr_file(de->d_name, \"postimage\"))\n+\t\t\trr_dir->status |= RR_HAS_POSTIMAGE;\n+\t\telse if (is_rr_file(de->d_name, \"preimage\"))\n+\t\t\trr_dir->status |= RR_HAS_PREIMAGE;\n+\t}\n+\tclosedir(dir);\n+}\n+\n static const unsigned char *rerere_dir_sha1(size_t i, void *table)\n {\n \tstruct rerere_dir **rr_dir = table;\n@@ -76,6 +100,7 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \tif (pos < 0) {\n \t\trr_dir = xmalloc(sizeof(*rr_dir));\n \t\thashcpy(rr_dir->sha1, sha1);\n+\t\trr_dir->status = 0;\n \t\tpos = -1 - pos;\n \n \t\t/* Make sure the array is big enough ... */\n@@ -85,15 +110,14 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \t\tmemmove(rerere_dir + pos + 1, rerere_dir + pos,\n \t\t\t(rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));\n \t\trerere_dir[pos] = rr_dir;\n+\t\tscan_rerere_dir(rr_dir);\n \t}\n \treturn rerere_dir[pos];\n }\n \n static int has_rerere_resolution(const struct rerere_id *id)\n {\n-\tstruct stat st;\n-\n-\treturn !stat(rerere_path(id, \"postimage\"), &st);\n+\treturn (id->collection->status & RR_HAS_POSTIMAGE);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n@@ -737,6 +761,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t} else if (!handle_file(path, NULL, NULL)) {\n \t\t/* The user has resolved it. */\n \t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n+\t\tid->collection->status |= RR_HAS_POSTIMAGE;\n \t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n \t} else {\n \t\treturn;\n@@ -797,6 +822,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\t * normalized contents to the \"preimage\" file.\n \t\t */\n \t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\tid->collection->status |= RR_HAS_PREIMAGE;\n \t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n \t}\n \n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269977","messageId":"1442275050-30497-4-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 3/7] rerere: handle leftover rr-cache/$ID directory and postimage files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:26Z","receivedAt":"2015-09-14T23:57:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"If by some accident there is only $GIT_DIR/rr-cache/$ID directory\nexisted, we wouldn't have recorded a preimage for a conflict that\nis newly encountered, which would mean after a manual resolution,\nwe wouldn't have recorded it by storing the postimage, because the\nlogic used to be \"if there is no rr-cache/$ID directory, then we are\nthe first so record the preimage\".  Instead, record preimage if we\ndo not have one.\n\nIn addition, if there is only $GIT_DIR/rr-cache/$ID/postimage\nwithout corresponding preimage, we would have tried to call into\nmerge() and punted.\n\nThese would have been a situation frustratingly hard to recover\nfrom.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c          | 42 +++++++++++++++++++++++++-----------------\n t/t4200-rerere.sh | 18 +++++++++++++++++-\n 2 files changed, 42 insertions(+), 18 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex fbdade8..a1e2963 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -117,7 +117,9 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \n static int has_rerere_resolution(const struct rerere_id *id)\n {\n-\treturn (id->collection->status & RR_HAS_POSTIMAGE);\n+\tconst int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;\n+\n+\treturn ((id->collection->status & both) == both);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n@@ -806,24 +808,30 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\tstring_list_insert(rr, path)->util = id;\n \n \t\t/*\n-\t\t * If the directory does not exist, create\n-\t\t * it.  mkdir_in_gitdir() will fail with\n-\t\t * EEXIST if there already is one.\n-\t\t *\n-\t\t * NEEDSWORK: make sure \"gc\" does not remove\n-\t\t * preimage without removing the directory.\n+\t\t * Ensure that the directory exists.\n+\t\t * mkdir_in_gitdir() will fail with EEXIST if there\n+\t\t * already is one.\n \t\t */\n-\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)))\n-\t\t\tcontinue;\n+\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)) &&\n+\t\t    errno != EEXIST)\n+\t\t\tcontinue; /* NEEDSWORK: perhaps we should die? */\n \n-\t\t/*\n-\t\t * We are the first to encounter this\n-\t\t * conflict.  Ask handle_file() to write the\n-\t\t * normalized contents to the \"preimage\" file.\n-\t\t */\n-\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tid->collection->status |= RR_HAS_PREIMAGE;\n-\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\tif (id->collection->status & RR_HAS_PREIMAGE) {\n+\t\t\t;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * We are the first to encounter this\n+\t\t\t * conflict.  Ask handle_file() to write the\n+\t\t\t * normalized contents to the \"preimage\" file.\n+\t\t\t *\n+\t\t\t * NEEDSWORK: what should happen if we had a\n+\t\t\t * leftover postimage that is totally\n+\t\t\t * unrelated?  Perhaps we should unlink it?\n+\t\t\t */\n+\t\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\t}\n \t}\n \n \tfor (i = 0; i < rr->nr; i++)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex ed9c91e..52a8e09 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -184,12 +184,28 @@ test_expect_success 'rerere updates postimage timestamp' '\n '\n \n test_expect_success 'rerere clear' '\n-\trm $rr/postimage &&\n+\tcp $rr/preimage .git/pre-saved &&\n+\tmv $rr/postimage .git/post-saved &&\n \techo \"$sha1\ta1\" | perl -pe \"y/\\012/\\000/\" >.git/MERGE_RR &&\n \tgit rerere clear &&\n \t! test -d $rr\n '\n \n+test_expect_success 'leftover directory' '\n+\tgit reset --hard &&\n+\tmkdir -p $rr &&\n+\ttest_must_fail git merge first &&\n+\ttest -f $rr/preimage\n+'\n+\n+test_expect_success 'missing preimage' '\n+\tgit reset --hard &&\n+\tmkdir -p $rr &&\n+\tcp .git/post-saved $rr/postimage &&\n+\ttest_must_fail git merge first &&\n+\ttest -f $rr/preimage\n+'\n+\n test_expect_success 'set up for garbage collection tests' '\n \tmkdir -p $rr &&\n \techo Hello >$rr/preimage &&\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269978","messageId":"1442275050-30497-5-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 4/7] rerere: delay the recording of preimage","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:27Z","receivedAt":"2015-09-14T23:57:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We record the preimage only when there is no directory to record the\nconflict we encountered, i.e. when $GIT_DIR/rr-cache/$ID does not\nexist.  As the plan is to allow multiple <preimage,postimage> pairs\nas variants for the same conflict ID eventually, this logic needs to\ngo.\n\nAs the first step in that direction, stop the \"did we create the\ndirectory?  Then we record the preimage\" logic.  Instead, we record\nif a preimage does not exist when we saw a conflict in a path.  Also\nmake sure that we create a new preimage under $ID with existing\nstale postimage, which most likely is totally unrelated to the\nresolution of this new conflict.\n\nIn later patches, we will further update this logic to be \"do we\nhave <preimage,postimage> pair that cleanly resolve the current\nconflicts?  If not, record a new preimage as a new variant\", but\nthat does not happen at this stage yet.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 52 +++++++++++++++++++++++++---------------------------\n 1 file changed, 25 insertions(+), 27 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex a1e2963..c0482b8 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -122,6 +122,11 @@ static int has_rerere_resolution(const struct rerere_id *id)\n \treturn ((id->collection->status & both) == both);\n }\n \n+static int has_rerere_preimage(const struct rerere_id *id)\n+{\n+\treturn (id->collection->status & RR_HAS_PREIMAGE);\n+}\n+\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n@@ -749,8 +754,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \tconst char *path = rr_item->string;\n \tconst struct rerere_id *id = rr_item->util;\n \n-\t/* Is there a recorded resolution we could attempt to apply? */\n-\tif (has_rerere_resolution(id)) {\n+\tif (!has_rerere_preimage(id)) {\n+\t\t/*\n+\t\t * We are the first to encounter this conflict.  Ask\n+\t\t * handle_file() to write the normalized contents to\n+\t\t * the \"preimage\" file.\n+\t\t */\n+\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\tif (id->collection->status & RR_HAS_POSTIMAGE) {\n+\t\t\tconst char *path = rerere_path(id, \"postimage\");\n+\t\t\tif (unlink(path))\n+\t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n+\t\t\tid->collection->status &= ~RR_HAS_PREIMAGE;\n+\t\t}\n+\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\treturn;\n+\t} else if (has_rerere_resolution(id)) {\n+\t\t/* Is there a recorded resolution we could attempt to apply? */\n \t\tif (merge(id, path))\n \t\t\treturn; /* failed to replay */\n \n@@ -807,31 +828,8 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\tid = new_rerere_id(sha1);\n \t\tstring_list_insert(rr, path)->util = id;\n \n-\t\t/*\n-\t\t * Ensure that the directory exists.\n-\t\t * mkdir_in_gitdir() will fail with EEXIST if there\n-\t\t * already is one.\n-\t\t */\n-\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)) &&\n-\t\t    errno != EEXIST)\n-\t\t\tcontinue; /* NEEDSWORK: perhaps we should die? */\n-\n-\t\tif (id->collection->status & RR_HAS_PREIMAGE) {\n-\t\t\t;\n-\t\t} else {\n-\t\t\t/*\n-\t\t\t * We are the first to encounter this\n-\t\t\t * conflict.  Ask handle_file() to write the\n-\t\t\t * normalized contents to the \"preimage\" file.\n-\t\t\t *\n-\t\t\t * NEEDSWORK: what should happen if we had a\n-\t\t\t * leftover postimage that is totally\n-\t\t\t * unrelated?  Perhaps we should unlink it?\n-\t\t\t */\n-\t\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\t\tid->collection->status |= RR_HAS_PREIMAGE;\n-\t\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n-\t\t}\n+\t\t/* Ensure that the directory exists. */\n+\t\tmkdir_in_gitdir(rerere_path(id, NULL));\n \t}\n \n \tfor (i = 0; i < rr->nr; i++)\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269980","messageId":"1442275050-30497-6-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 5/7] rerere: allow multiple variants to exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:28Z","receivedAt":"2015-09-14T23:57:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The shape of the conflict in a path determines the conflict ID.  The\npreimage and postimage pair that was recorded for the conflict ID\npreviously may or may not replay well for the conflict we just saw.\n\nCurrently, we punt when the previous resolution does not cleanly\nreplay, but ideally we should then be able to record the currently\nconflicted path by assigning a new 'variant', and then record the\nresolution the user is going to make.\n\nIntroduce a mechanism to have more than one variant for a given\nconflict ID; we do not actually assign any variant other than 0th\nvariant yet at this step.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----------\n rerere.h |   1 +\n 2 files changed, 106 insertions(+), 22 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex c0482b8..f674461 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -30,14 +30,17 @@ static int rerere_dir_alloc;\n #define RR_HAS_PREIMAGE 2\n static struct rerere_dir {\n \tunsigned char sha1[20];\n-\tunsigned char status;\n+\tint status_alloc, status_nr;\n+\tunsigned char *status;\n } **rerere_dir;\n \n static void free_rerere_dirs(void)\n {\n \tint i;\n-\tfor (i = 0; i < rerere_dir_nr; i++)\n+\tfor (i = 0; i < rerere_dir_nr; i++) {\n+\t\tfree(rerere_dir[i]->status);\n \t\tfree(rerere_dir[i]);\n+\t}\n \tfree(rerere_dir);\n \trerere_dir_nr = rerere_dir_alloc = 0;\n \trerere_dir = NULL;\n@@ -53,17 +56,59 @@ static const char *rerere_id_hex(const struct rerere_id *id)\n \treturn sha1_to_hex(id->collection->sha1);\n }\n \n+static void fit_variant(struct rerere_dir *rr_dir, int variant)\n+{\n+\tvariant++;\n+\tALLOC_GROW(rr_dir->status, variant, rr_dir->status_alloc);\n+\tif (rr_dir->status_nr < variant) {\n+\t\tmemset(rr_dir->status + rr_dir->status_nr,\n+\t\t       '\\0', variant - rr_dir->status_nr);\n+\t\trr_dir->status_nr = variant;\n+\t}\n+}\n+\n+static void assign_variant(struct rerere_id *id)\n+{\n+\tint variant;\n+\tstruct rerere_dir *rr_dir = id->collection;\n+\n+\tvariant = id->variant;\n+\tif (variant < 0) {\n+\t\tvariant = 0; /* for now */\n+\t}\n+\tfit_variant(rr_dir, variant);\n+\tid->variant = variant;\n+}\n+\n const char *rerere_path(const struct rerere_id *id, const char *file)\n {\n \tif (!file)\n \t\treturn git_path(\"rr-cache/%s\", rerere_id_hex(id));\n \n-\treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n+\tif (id->variant <= 0)\n+\t\treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n+\n+\treturn git_path(\"rr-cache/%s/%s.%d\",\n+\t\t\trerere_id_hex(id), file, id->variant);\n }\n \n-static int is_rr_file(const char *name, const char *filename)\n+static int is_rr_file(const char *name, const char *filename, int *variant)\n {\n-\treturn !strcmp(name, filename);\n+\tconst char *suffix;\n+\tchar *ep;\n+\n+\tif (!strcmp(name, filename)) {\n+\t\t*variant = 0;\n+\t\treturn 1;\n+\t}\n+\tif (!skip_prefix(name, filename, &suffix) || *suffix != '.')\n+\t\treturn 0;\n+\n+\terrno = 0;\n+\t*variant = strtol(suffix + 1, &ep, 10);\n+\tif (errno || *ep)\n+\t\treturn 0;\n+\treturn 1;\n }\n \n static void scan_rerere_dir(struct rerere_dir *rr_dir)\n@@ -74,10 +119,15 @@ static void scan_rerere_dir(struct rerere_dir *rr_dir)\n \tif (!dir)\n \t\treturn;\n \twhile ((de = readdir(dir)) != NULL) {\n-\t\tif (is_rr_file(de->d_name, \"postimage\"))\n-\t\t\trr_dir->status |= RR_HAS_POSTIMAGE;\n-\t\telse if (is_rr_file(de->d_name, \"preimage\"))\n-\t\t\trr_dir->status |= RR_HAS_PREIMAGE;\n+\t\tint variant;\n+\n+\t\tif (is_rr_file(de->d_name, \"postimage\", &variant)) {\n+\t\t\tfit_variant(rr_dir, variant);\n+\t\t\trr_dir->status[variant] |= RR_HAS_POSTIMAGE;\n+\t\t} else if (is_rr_file(de->d_name, \"preimage\", &variant)) {\n+\t\t\tfit_variant(rr_dir, variant);\n+\t\t\trr_dir->status[variant] |= RR_HAS_PREIMAGE;\n+\t\t}\n \t}\n \tclosedir(dir);\n }\n@@ -100,7 +150,9 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \tif (pos < 0) {\n \t\trr_dir = xmalloc(sizeof(*rr_dir));\n \t\thashcpy(rr_dir->sha1, sha1);\n-\t\trr_dir->status = 0;\n+\t\trr_dir->status = NULL;\n+\t\trr_dir->status_nr = 0;\n+\t\trr_dir->status_alloc = 0;\n \t\tpos = -1 - pos;\n \n \t\t/* Make sure the array is big enough ... */\n@@ -118,19 +170,27 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n static int has_rerere_resolution(const struct rerere_id *id)\n {\n \tconst int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;\n+\tint variant = id->variant;\n \n-\treturn ((id->collection->status & both) == both);\n+\tif (variant < 0)\n+\t\treturn 0;\n+\treturn ((id->collection->status[variant] & both) == both);\n }\n \n static int has_rerere_preimage(const struct rerere_id *id)\n {\n-\treturn (id->collection->status & RR_HAS_PREIMAGE);\n+\tint variant = id->variant;\n+\n+\tif (variant < 0)\n+\t\treturn 0;\n+\treturn (id->collection->status[variant] & RR_HAS_PREIMAGE);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n \tid->collection = find_rerere_dir(hex);\n+\tid->variant = -1; /* not known yet */\n \treturn id;\n }\n \n@@ -157,16 +217,26 @@ static void read_rr(struct string_list *rr)\n \t\tchar *path;\n \t\tunsigned char sha1[20];\n \t\tstruct rerere_id *id;\n+\t\tint variant;\n \n \t\t/* There has to be the hash, tab, path and then NUL */\n \t\tif (buf.len < 42 || get_sha1_hex(buf.buf, sha1))\n \t\t\tdie(\"corrupt MERGE_RR\");\n \n-\t\tif (buf.buf[40] != '\\t')\n+\t\tif (buf.buf[40] != '.') {\n+\t\t\tvariant = 0;\n+\t\t\tpath = buf.buf + 40;\n+\t\t} else {\n+\t\t\terrno = 0;\n+\t\t\tvariant = strtol(buf.buf + 41, &path, 10);\n+\t\t\tif (errno)\n+\t\t\t\tdie(\"corrupt MERGE_RR\");\n+\t\t}\n+\t\tif (*(path++) != '\\t')\n \t\t\tdie(\"corrupt MERGE_RR\");\n \t\tbuf.buf[40] = '\\0';\n-\t\tpath = buf.buf + 41;\n \t\tid = new_rerere_id_hex(buf.buf);\n+\t\tid->variant = variant;\n \t\tstring_list_insert(rr, path)->util = id;\n \t}\n \tstrbuf_release(&buf);\n@@ -187,9 +257,16 @@ static int write_rr(struct string_list *rr, int out_fd)\n \t\tid = rr->items[i].util;\n \t\tif (!id)\n \t\t\tcontinue;\n-\t\tstrbuf_addf(&buf, \"%s\\t%s%c\",\n-\t\t\t    rerere_id_hex(id),\n-\t\t\t    rr->items[i].string, 0);\n+\t\tassert(id->variant >= 0);\n+\t\tif (0 < id->variant)\n+\t\t\tstrbuf_addf(&buf, \"%s.%d\\t%s%c\",\n+\t\t\t\t    rerere_id_hex(id), id->variant,\n+\t\t\t\t    rr->items[i].string, 0);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s\\t%s%c\",\n+\t\t\t\t    rerere_id_hex(id),\n+\t\t\t\t    rr->items[i].string, 0);\n+\n \t\tif (write_in_full(out_fd, buf.buf, buf.len) != buf.len)\n \t\t\tdie(\"unable to write rerere record\");\n \n@@ -752,7 +829,12 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t\t       struct string_list *update)\n {\n \tconst char *path = rr_item->string;\n-\tconst struct rerere_id *id = rr_item->util;\n+\tstruct rerere_id *id = rr_item->util;\n+\tint variant;\n+\n+\tif (id->variant < 0)\n+\t\tassign_variant(id);\n+\tvariant = id->variant;\n \n \tif (!has_rerere_preimage(id)) {\n \t\t/*\n@@ -761,13 +843,13 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t * the \"preimage\" file.\n \t\t */\n \t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tif (id->collection->status & RR_HAS_POSTIMAGE) {\n+\t\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n \t\t\tconst char *path = rerere_path(id, \"postimage\");\n \t\t\tif (unlink(path))\n \t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n-\t\t\tid->collection->status &= ~RR_HAS_PREIMAGE;\n+\t\t\tid->collection->status[variant] &= ~RR_HAS_PREIMAGE;\n \t\t}\n-\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n \t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n \t\treturn;\n \t} else if (has_rerere_resolution(id)) {\n@@ -784,7 +866,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t} else if (!handle_file(path, NULL, NULL)) {\n \t\t/* The user has resolved it. */\n \t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n-\t\tid->collection->status |= RR_HAS_POSTIMAGE;\n+\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n \t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n \t} else {\n \t\treturn;\n@@ -919,6 +1001,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)\n \n \t/* Nuke the recorded resolution for the conflict */\n \tid = new_rerere_id(sha1);\n+\tid->variant = 0; /* for now */\n \tfilename = rerere_path(id, \"postimage\");\n \tif (unlink(filename))\n \t\treturn (errno == ENOENT\ndiff --git a/rerere.h b/rerere.h\nindex faf5a23..64d38d9 100644\n--- a/rerere.h\n+++ b/rerere.h\n@@ -18,6 +18,7 @@ extern void *RERERE_RESOLVED;\n struct rerere_dir;\n struct rerere_id {\n \tstruct rerere_dir *collection;\n+\tint variant;\n };\n \n extern int setup_rerere(struct string_list *, int);\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269979","messageId":"1442275050-30497-7-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 6/7] t4200: rerere a merge with two identical conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:29Z","receivedAt":"2015-09-14T23:57:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When the context of multiple identical conflicts are different, two\nseemingly the same conflict resolution cannot be safely applied.\n\nIn such a case, at least we should be able to record these two\nresolutions separately in the rerere database, and reuse them when\nwe see the same conflict later.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t4200-rerere.sh | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 74 insertions(+)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 52a8e09..8d52854 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -407,4 +407,78 @@ test_expect_success 'rerere -h' '\n \ttest_i18ngrep [Uu]sage help\n '\n \n+concat_insert () {\n+\tlast=$1\n+\tshift\n+\tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n+}\n+\n+test_expect_failure 'multiple identical conflicts' '\n+\tgit reset --hard &&\n+\n+\ttest_seq 1 6 >early &&\n+\t>late &&\n+\ttest_seq 11 15 >short &&\n+\ttest_seq 111 120 >long &&\n+\tconcat_insert short >file1 &&\n+\tconcat_insert long >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m base &&\n+\tgit tag base &&\n+\tgit checkout -b six.1 &&\n+\tconcat_insert short 6.1 >file1 &&\n+\tconcat_insert long 6.1 >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m 6.1 &&\n+\tgit checkout -b six.2 HEAD^ &&\n+\tconcat_insert short 6.2 >file1 &&\n+\tconcat_insert long 6.2 >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m 6.2 &&\n+\n+\t# At this point, six.1 and six.2\n+\t# - derive from common ancestor that has two files\n+\t#   1...6 7 11..15 (file1) and 1...6 7 111..120 (file2)\n+\t# - six.1 replaces these 7s with 6.1\n+\t# - six.2 replaces these 7s with 6.2\n+\n+\ttest_must_fail git merge six.1 &&\n+\n+\t# Check that rerere knows that file1 and file2 have conflicts\n+\n+\tprintf \"%s\\n\" file1 file2 >expect &&\n+\tgit ls-files -u | sed -e \"s/^.*\t//\" | sort -u >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tgit rerere status | sort >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n+\tconcat_insert short 6.1 6.2 >file1 &&\n+\tconcat_insert long 6.1 6.2 >file2 &&\n+\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# We resolved file1 and file2\n+\tgit rerere &&\n+\t>expect &&\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Now we should be able to resolve them both\n+\tgit reset --hard &&\n+\ttest_must_fail git merge six.1 &&\n+\tgit rerere &&\n+\n+\t>expect &&\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tconcat_insert short 6.1 6.2 >file1.expect &&\n+\tconcat_insert long 6.1 6.2 >file2.expect &&\n+\ttest_cmp file1.expect file1 &&\n+\ttest_cmp file2.expect file2\n+'\n+\n test_done\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"269976","messageId":"1442275050-30497-8-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH 7/7] rerere: do use multiple variants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-14T23:57:30Z","receivedAt":"2015-09-14T23:57:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This enables the multiple-variant support for real.  Multiple\nconflicts of the same shape can have differences in contexts where\nthey appear, interfering the replaying of recorded resolution of one\nconflict to another, and in such a case, their resolutions are\nrecorded as different variants under the same conflict ID.\n\nWe still need to adjust garbage collection codepaths for this\nchange, but the basic \"replay\" functionality is functional with\nthis change.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c          | 94 +++++++++++++++++++++++++++++++++----------------------\n t/t4200-rerere.sh |  2 +-\n 2 files changed, 57 insertions(+), 39 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex f674461..cefc865 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -74,7 +74,9 @@ static void assign_variant(struct rerere_id *id)\n \n \tvariant = id->variant;\n \tif (variant < 0) {\n-\t\tvariant = 0; /* for now */\n+\t\tfor (variant = 0; variant < rr_dir->status_nr; variant++)\n+\t\t\tif (!rr_dir->status[variant])\n+\t\t\t\tbreak;\n \t}\n \tfit_variant(rr_dir, variant);\n \tid->variant = variant;\n@@ -177,15 +179,6 @@ static int has_rerere_resolution(const struct rerere_id *id)\n \treturn ((id->collection->status[variant] & both) == both);\n }\n \n-static int has_rerere_preimage(const struct rerere_id *id)\n-{\n-\tint variant = id->variant;\n-\n-\tif (variant < 0)\n-\t\treturn 0;\n-\treturn (id->collection->status[variant] & RR_HAS_PREIMAGE);\n-}\n-\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n@@ -818,6 +811,13 @@ static void update_paths(struct string_list *update)\n \t\trollback_lock_file(&index_lock);\n }\n \n+static void remove_variant(struct rerere_id *id)\n+{\n+\tunlink_or_warn(rerere_path(id, \"postimage\"));\n+\tunlink_or_warn(rerere_path(id, \"preimage\"));\n+\tid->collection->status[id->variant] = 0;\n+}\n+\n /*\n  * The path indicated by rr_item may still have conflict for which we\n  * have a recorded resolution, in which case replay it and optionally\n@@ -830,32 +830,42 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n {\n \tconst char *path = rr_item->string;\n \tstruct rerere_id *id = rr_item->util;\n+\tstruct rerere_dir *rr_dir = id->collection;\n \tint variant;\n \n-\tif (id->variant < 0)\n-\t\tassign_variant(id);\n \tvariant = id->variant;\n \n-\tif (!has_rerere_preimage(id)) {\n+\t/* Has the user resolved it already? */\n+\tif (variant >= 0) {\n+\t\tif (!handle_file(path, NULL, NULL)) {\n+\t\t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n+\t\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n+\t\t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n+\t\t\tfree_rerere_id(rr_item);\n+\t\t\trr_item->util = NULL;\n+\t\t\treturn;\n+\t\t}\n \t\t/*\n-\t\t * We are the first to encounter this conflict.  Ask\n-\t\t * handle_file() to write the normalized contents to\n-\t\t * the \"preimage\" file.\n+\t\t * There may be other variants that can cleanly\n+\t\t * replay.  Try them and update the variant number for\n+\t\t * this one.\n \t\t */\n-\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n-\t\t\tconst char *path = rerere_path(id, \"postimage\");\n-\t\t\tif (unlink(path))\n-\t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n-\t\t\tid->collection->status[variant] &= ~RR_HAS_PREIMAGE;\n-\t\t}\n-\t\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n-\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n-\t\treturn;\n-\t} else if (has_rerere_resolution(id)) {\n-\t\t/* Is there a recorded resolution we could attempt to apply? */\n-\t\tif (merge(id, path))\n-\t\t\treturn; /* failed to replay */\n+\t}\n+\n+\t/* Does any existing resolution apply cleanly? */\n+\tfor (variant = 0; variant < rr_dir->status_nr; variant++) {\n+\t\tconst int both = RR_HAS_PREIMAGE | RR_HAS_POSTIMAGE;\n+\t\tstruct rerere_id vid = *id;\n+\n+\t\tif ((rr_dir->status[variant] & both) != both)\n+\t\t\tcontinue;\n+\n+\t\tvid.variant = variant;\n+\t\tif (merge(&vid, path))\n+\t\t\tcontinue; /* failed to replay */\n+\n+\t\tif (0 <= id->variant && id->variant != variant)\n+\t\t\tremove_variant(id);\n \n \t\tif (rerere_autoupdate)\n \t\t\tstring_list_insert(update, path);\n@@ -863,16 +873,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t\tfprintf(stderr,\n \t\t\t\t\"Resolved '%s' using previous resolution.\\n\",\n \t\t\t\tpath);\n-\t} else if (!handle_file(path, NULL, NULL)) {\n-\t\t/* The user has resolved it. */\n-\t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n-\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n-\t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n-\t} else {\n+\t\tfree_rerere_id(rr_item);\n+\t\trr_item->util = NULL;\n \t\treturn;\n \t}\n-\tfree_rerere_id(rr_item);\n-\trr_item->util = NULL;\n+\n+\t/* None of the existing one applies; we need a new variant */\n+\tassign_variant(id);\n+\n+\tvariant = id->variant;\n+\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n+\t\tconst char *path = rerere_path(id, \"postimage\");\n+\t\tif (unlink(path))\n+\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n+\t\tid->collection->status[variant] &= ~RR_HAS_PREIMAGE;\n+\t}\n+\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n+\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n }\n \n static int do_plain_rerere(struct string_list *rr, int fd)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 8d52854..5649829 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -413,7 +413,7 @@ concat_insert () {\n \tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n }\n \n-test_expect_failure 'multiple identical conflicts' '\n+test_expect_success 'multiple identical conflicts' '\n \tgit reset --hard &&\n \n \ttest_seq 1 6 >early &&\n-- \n2.6.0-rc2-164-gdcd5d00\n"},{"id":"281991","messageId":"1459204942-26601-1-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1442275050-30497-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 00/11] saving and replaying multiple variants with rerere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:11Z","receivedAt":"2016-03-28T22:42:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Take two after several months.  That is what you get when you end up\nrunning a busy project, instead of staying to be an individual\ncontributor ;-).\n\nThe previous round starts at the message this is a response to and\nis found at http://thread.gmane.org/gmane.comp.version-control.git/277878\n\nThe previous round completed the basic \"record and replay\", while\nleaving \"gc\" and \"forget\" as known-to-be-stale bits.  This round\nattempts to complete them. \n\nJunio C Hamano (11):\n  rerere: split conflict ID further\n  rerere: scan $GIT_DIR/rr-cache/$ID when instantiating a rerere_id\n  rerere: handle leftover rr-cache/$ID directory and postimage files\n  rerere: delay the recording of preimage\n  rerere: allow multiple variants to exist\n  t4200: rerere a merge with two identical conflicts\n  rerere: do use multiple variants\n  rerere: gc and clear\n  rerere: move code related to \"forget\" together\n  rerere: split code to call ll_merge() further\n  rerere: adjust 'forget' to multi-variant world order\n\n rerere.c          | 626 +++++++++++++++++++++++++++++++++++++-----------------\n rerere.h          |   4 +-\n t/t4200-rerere.sh | 159 +++++++++++++-\n 3 files changed, 593 insertions(+), 196 deletions(-)\n\n-- \n2.8.0-215-g046a488\n"},{"id":"281995","messageId":"1459204942-26601-2-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 01/11] rerere: split conflict ID further","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:12Z","receivedAt":"2016-03-28T22:42:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The plan is to keep assigning the backward compatible conflict ID\nbased on the hash of the (normalized) text of conflicts, keep using\nthat conflict ID as the directory name under $GIT_DIR/rr-cache/, but\nallow each conflicted path to use a separate \"variant\" to record\nresolutions, i.e. having more than one <preimage,postimage> pairs\nunder $GIT_DIR/rr-cache/$ID/ directory.  As the first step in that\ndirection, separate the shared \"conflict ID\" out of the rerere_id\nstructure.\n\nThe plan is to keep information per $ID in rerere_dir, that can be\nshared among rerere_id that is per conflicted path.\n\nWhen we are done with rerere(), which can be directly called from\nother programs like \"git apply\", \"git commit\" and \"git merge\", the\nshared rerere_dir structures can be freed entirely, so they are not\nreference-counted and they are not freed when we release rerere_id's\nthat reference them.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * This is designed to be applied to a slightly newer codebase than\n   the previous round, and a few removed lines that used to call\n   strcpy() in the preimage shows up as using xsnprintf() in the\n   patch.  We no longer use a simple string anyway, so the postimages\n   are the same between rerolls.\n\n rerere.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----\n rerere.h |  3 ++-\n 2 files changed, 58 insertions(+), 6 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 3d0fa8f..a5d8a06 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -8,6 +8,7 @@\n #include \"ll-merge.h\"\n #include \"attr.h\"\n #include \"pathspec.h\"\n+#include \"sha1-lookup.h\"\n \n #define RESOLVED 0\n #define PUNTED 1\n@@ -22,6 +23,23 @@ static int rerere_autoupdate;\n \n static char *merge_rr_path;\n \n+static int rerere_dir_nr;\n+static int rerere_dir_alloc;\n+\n+static struct rerere_dir {\n+\tunsigned char sha1[20];\n+} **rerere_dir;\n+\n+static void free_rerere_dirs(void)\n+{\n+\tint i;\n+\tfor (i = 0; i < rerere_dir_nr; i++)\n+\t\tfree(rerere_dir[i]);\n+\tfree(rerere_dir);\n+\trerere_dir_nr = rerere_dir_alloc = 0;\n+\trerere_dir = NULL;\n+}\n+\n static void free_rerere_id(struct string_list_item *item)\n {\n \tfree(item->util);\n@@ -29,7 +47,7 @@ static void free_rerere_id(struct string_list_item *item)\n \n static const char *rerere_id_hex(const struct rerere_id *id)\n {\n-\treturn id->hex;\n+\treturn sha1_to_hex(id->collection->sha1);\n }\n \n const char *rerere_path(const struct rerere_id *id, const char *file)\n@@ -40,6 +58,37 @@ const char *rerere_path(const struct rerere_id *id, const char *file)\n \treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n }\n \n+static const unsigned char *rerere_dir_sha1(size_t i, void *table)\n+{\n+\tstruct rerere_dir **rr_dir = table;\n+\treturn rr_dir[i]->sha1;\n+}\n+\n+static struct rerere_dir *find_rerere_dir(const char *hex)\n+{\n+\tunsigned char sha1[20];\n+\tstruct rerere_dir *rr_dir;\n+\tint pos;\n+\n+\tif (get_sha1_hex(hex, sha1))\n+\t\treturn NULL; /* BUG */\n+\tpos = sha1_pos(sha1, rerere_dir, rerere_dir_nr, rerere_dir_sha1);\n+\tif (pos < 0) {\n+\t\trr_dir = xmalloc(sizeof(*rr_dir));\n+\t\thashcpy(rr_dir->sha1, sha1);\n+\t\tpos = -1 - pos;\n+\n+\t\t/* Make sure the array is big enough ... */\n+\t\tALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc);\n+\t\t/* ... and add it in. */\n+\t\trerere_dir_nr++;\n+\t\tmemmove(rerere_dir + pos + 1, rerere_dir + pos,\n+\t\t\t(rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));\n+\t\trerere_dir[pos] = rr_dir;\n+\t}\n+\treturn rerere_dir[pos];\n+}\n+\n static int has_rerere_resolution(const struct rerere_id *id)\n {\n \tstruct stat st;\n@@ -50,7 +99,7 @@ static int has_rerere_resolution(const struct rerere_id *id)\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n-\txsnprintf(id->hex, sizeof(id->hex), \"%s\", hex);\n+\tid->collection = find_rerere_dir(hex);\n \treturn id;\n }\n \n@@ -810,12 +859,14 @@ int setup_rerere(struct string_list *merge_rr, int flags)\n int rerere(int flags)\n {\n \tstruct string_list merge_rr = STRING_LIST_INIT_DUP;\n-\tint fd;\n+\tint fd, status;\n \n \tfd = setup_rerere(&merge_rr, flags);\n \tif (fd < 0)\n \t\treturn 0;\n-\treturn do_plain_rerere(&merge_rr, fd);\n+\tstatus = do_plain_rerere(&merge_rr, fd);\n+\tfree_rerere_dirs();\n+\treturn status;\n }\n \n static int rerere_forget_one_path(const char *path, struct string_list *rr)\n@@ -900,7 +951,7 @@ int rerere_forget(struct pathspec *pathspec)\n static struct rerere_id *dirname_to_id(const char *name)\n {\n \tstatic struct rerere_id id;\n-\txsnprintf(id.hex, sizeof(id.hex), \"%s\", name);\n+\tid.collection = find_rerere_dir(name);\n \treturn &id;\n }\n \ndiff --git a/rerere.h b/rerere.h\nindex ce545d0..faf5a23 100644\n--- a/rerere.h\n+++ b/rerere.h\n@@ -15,8 +15,9 @@ struct pathspec;\n  */\n extern void *RERERE_RESOLVED;\n \n+struct rerere_dir;\n struct rerere_id {\n-\tchar hex[41];\n+\tstruct rerere_dir *collection;\n };\n \n extern int setup_rerere(struct string_list *, int);\n-- \n2.8.0-215-g046a488\n"},{"id":"281992","messageId":"1459204942-26601-3-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 02/11] rerere: scan $GIT_DIR/rr-cache/$ID when instantiating a rerere_id","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:13Z","receivedAt":"2016-03-28T22:42:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This will help fixing bootstrap corner-case issues, e.g. having an\nempty $GIT_DIR/rr-cache/$ID directory would fail to record a\npreimage, in later changes in this series.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * Unchanged.\n\n rerere.c | 32 +++++++++++++++++++++++++++++---\n 1 file changed, 29 insertions(+), 3 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex a5d8a06..fbdade8 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -26,8 +26,11 @@ static char *merge_rr_path;\n static int rerere_dir_nr;\n static int rerere_dir_alloc;\n \n+#define RR_HAS_POSTIMAGE 1\n+#define RR_HAS_PREIMAGE 2\n static struct rerere_dir {\n \tunsigned char sha1[20];\n+\tunsigned char status;\n } **rerere_dir;\n \n static void free_rerere_dirs(void)\n@@ -58,6 +61,27 @@ const char *rerere_path(const struct rerere_id *id, const char *file)\n \treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n }\n \n+static int is_rr_file(const char *name, const char *filename)\n+{\n+\treturn !strcmp(name, filename);\n+}\n+\n+static void scan_rerere_dir(struct rerere_dir *rr_dir)\n+{\n+\tstruct dirent *de;\n+\tDIR *dir = opendir(git_path(\"rr-cache/%s\", sha1_to_hex(rr_dir->sha1)));\n+\n+\tif (!dir)\n+\t\treturn;\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tif (is_rr_file(de->d_name, \"postimage\"))\n+\t\t\trr_dir->status |= RR_HAS_POSTIMAGE;\n+\t\telse if (is_rr_file(de->d_name, \"preimage\"))\n+\t\t\trr_dir->status |= RR_HAS_PREIMAGE;\n+\t}\n+\tclosedir(dir);\n+}\n+\n static const unsigned char *rerere_dir_sha1(size_t i, void *table)\n {\n \tstruct rerere_dir **rr_dir = table;\n@@ -76,6 +100,7 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \tif (pos < 0) {\n \t\trr_dir = xmalloc(sizeof(*rr_dir));\n \t\thashcpy(rr_dir->sha1, sha1);\n+\t\trr_dir->status = 0;\n \t\tpos = -1 - pos;\n \n \t\t/* Make sure the array is big enough ... */\n@@ -85,15 +110,14 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \t\tmemmove(rerere_dir + pos + 1, rerere_dir + pos,\n \t\t\t(rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));\n \t\trerere_dir[pos] = rr_dir;\n+\t\tscan_rerere_dir(rr_dir);\n \t}\n \treturn rerere_dir[pos];\n }\n \n static int has_rerere_resolution(const struct rerere_id *id)\n {\n-\tstruct stat st;\n-\n-\treturn !stat(rerere_path(id, \"postimage\"), &st);\n+\treturn (id->collection->status & RR_HAS_POSTIMAGE);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n@@ -737,6 +761,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t} else if (!handle_file(path, NULL, NULL)) {\n \t\t/* The user has resolved it. */\n \t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n+\t\tid->collection->status |= RR_HAS_POSTIMAGE;\n \t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n \t} else {\n \t\treturn;\n@@ -797,6 +822,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\t * normalized contents to the \"preimage\" file.\n \t\t */\n \t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\tid->collection->status |= RR_HAS_PREIMAGE;\n \t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n \t}\n \n-- \n2.8.0-215-g046a488\n"},{"id":"281996","messageId":"1459204942-26601-4-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 03/11] rerere: handle leftover rr-cache/$ID directory and postimage files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:14Z","receivedAt":"2016-03-28T22:42:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"If by some accident there is only $GIT_DIR/rr-cache/$ID directory\nexisted, we wouldn't have recorded a preimage for a conflict that\nis newly encountered, which would mean after a manual resolution,\nwe wouldn't have recorded it by storing the postimage, because the\nlogic used to be \"if there is no rr-cache/$ID directory, then we are\nthe first so record the preimage\".  Instead, record preimage if we\ndo not have one.\n\nIn addition, if there is only $GIT_DIR/rr-cache/$ID/postimage\nwithout corresponding preimage, we would have tried to call into\nmerge() and punted.\n\nThese would have been a situation frustratingly hard to recover\nfrom.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * A test that saved away both postimage and preimage has been\n   updated to only save postimage, as we do not look at the other\n   one.\n\n rerere.c          | 42 +++++++++++++++++++++++++-----------------\n t/t4200-rerere.sh | 17 ++++++++++++++++-\n 2 files changed, 41 insertions(+), 18 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex fbdade8..a1e2963 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -117,7 +117,9 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \n static int has_rerere_resolution(const struct rerere_id *id)\n {\n-\treturn (id->collection->status & RR_HAS_POSTIMAGE);\n+\tconst int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;\n+\n+\treturn ((id->collection->status & both) == both);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n@@ -806,24 +808,30 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\tstring_list_insert(rr, path)->util = id;\n \n \t\t/*\n-\t\t * If the directory does not exist, create\n-\t\t * it.  mkdir_in_gitdir() will fail with\n-\t\t * EEXIST if there already is one.\n-\t\t *\n-\t\t * NEEDSWORK: make sure \"gc\" does not remove\n-\t\t * preimage without removing the directory.\n+\t\t * Ensure that the directory exists.\n+\t\t * mkdir_in_gitdir() will fail with EEXIST if there\n+\t\t * already is one.\n \t\t */\n-\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)))\n-\t\t\tcontinue;\n+\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)) &&\n+\t\t    errno != EEXIST)\n+\t\t\tcontinue; /* NEEDSWORK: perhaps we should die? */\n \n-\t\t/*\n-\t\t * We are the first to encounter this\n-\t\t * conflict.  Ask handle_file() to write the\n-\t\t * normalized contents to the \"preimage\" file.\n-\t\t */\n-\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tid->collection->status |= RR_HAS_PREIMAGE;\n-\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\tif (id->collection->status & RR_HAS_PREIMAGE) {\n+\t\t\t;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * We are the first to encounter this\n+\t\t\t * conflict.  Ask handle_file() to write the\n+\t\t\t * normalized contents to the \"preimage\" file.\n+\t\t\t *\n+\t\t\t * NEEDSWORK: what should happen if we had a\n+\t\t\t * leftover postimage that is totally\n+\t\t\t * unrelated?  Perhaps we should unlink it?\n+\t\t\t */\n+\t\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\t}\n \t}\n \n \tfor (i = 0; i < rr->nr; i++)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex ed9c91e..c428011 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -184,12 +184,27 @@ test_expect_success 'rerere updates postimage timestamp' '\n '\n \n test_expect_success 'rerere clear' '\n-\trm $rr/postimage &&\n+\tmv $rr/postimage .git/post-saved &&\n \techo \"$sha1\ta1\" | perl -pe \"y/\\012/\\000/\" >.git/MERGE_RR &&\n \tgit rerere clear &&\n \t! test -d $rr\n '\n \n+test_expect_success 'leftover directory' '\n+\tgit reset --hard &&\n+\tmkdir -p $rr &&\n+\ttest_must_fail git merge first &&\n+\ttest -f $rr/preimage\n+'\n+\n+test_expect_success 'missing preimage' '\n+\tgit reset --hard &&\n+\tmkdir -p $rr &&\n+\tcp .git/post-saved $rr/postimage &&\n+\ttest_must_fail git merge first &&\n+\ttest -f $rr/preimage\n+'\n+\n test_expect_success 'set up for garbage collection tests' '\n \tmkdir -p $rr &&\n \techo Hello >$rr/preimage &&\n-- \n2.8.0-215-g046a488\n"},{"id":"281993","messageId":"1459204942-26601-5-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 04/11] rerere: delay the recording of preimage","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:15Z","receivedAt":"2016-03-28T22:42:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We record the preimage only when there is no directory to record the\nconflict we encountered, i.e. when $GIT_DIR/rr-cache/$ID does not\nexist.  As the plan is to allow multiple <preimage,postimage> pairs\nas variants for the same conflict ID eventually, this logic needs to\ngo.\n\nAs the first step in that direction, stop the \"did we create the\ndirectory?  Then we record the preimage\" logic.  Instead, we record\nif a preimage does not exist when we saw a conflict in a path.  Also\nmake sure that we remove a stale postimage, which most likely is\ntotally unrelated to the resolution of this new conflict, when we\ncreate a new preimage under $ID when $GIT_DIR/rr-cache/$ID already\nexists.\n\nIn later patches, we will further update this logic to be \"do we\nhave <preimage,postimage> pair that cleanly resolve the current\nconflicts?  If not, record a new preimage as a new variant\", but\nthat does not happen at this stage yet.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * The previous one had a handful of confusions between which of\n   RR_HAS_{PRE,POST}IMAGE bit to clear, which was pointed out by\n   J6t.  The corrections to them appear here and a few patches\n   that follow this one in this round.\n\n rerere.c | 52 +++++++++++++++++++++++++---------------------------\n 1 file changed, 25 insertions(+), 27 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex a1e2963..33b1348 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -122,6 +122,11 @@ static int has_rerere_resolution(const struct rerere_id *id)\n \treturn ((id->collection->status & both) == both);\n }\n \n+static int has_rerere_preimage(const struct rerere_id *id)\n+{\n+\treturn (id->collection->status & RR_HAS_PREIMAGE);\n+}\n+\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n@@ -749,8 +754,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \tconst char *path = rr_item->string;\n \tconst struct rerere_id *id = rr_item->util;\n \n-\t/* Is there a recorded resolution we could attempt to apply? */\n-\tif (has_rerere_resolution(id)) {\n+\tif (!has_rerere_preimage(id)) {\n+\t\t/*\n+\t\t * We are the first to encounter this conflict.  Ask\n+\t\t * handle_file() to write the normalized contents to\n+\t\t * the \"preimage\" file.\n+\t\t */\n+\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\tif (id->collection->status & RR_HAS_POSTIMAGE) {\n+\t\t\tconst char *path = rerere_path(id, \"postimage\");\n+\t\t\tif (unlink(path))\n+\t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n+\t\t\tid->collection->status &= ~RR_HAS_POSTIMAGE;\n+\t\t}\n+\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\treturn;\n+\t} else if (has_rerere_resolution(id)) {\n+\t\t/* Is there a recorded resolution we could attempt to apply? */\n \t\tif (merge(id, path))\n \t\t\treturn; /* failed to replay */\n \n@@ -807,31 +828,8 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\tid = new_rerere_id(sha1);\n \t\tstring_list_insert(rr, path)->util = id;\n \n-\t\t/*\n-\t\t * Ensure that the directory exists.\n-\t\t * mkdir_in_gitdir() will fail with EEXIST if there\n-\t\t * already is one.\n-\t\t */\n-\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)) &&\n-\t\t    errno != EEXIST)\n-\t\t\tcontinue; /* NEEDSWORK: perhaps we should die? */\n-\n-\t\tif (id->collection->status & RR_HAS_PREIMAGE) {\n-\t\t\t;\n-\t\t} else {\n-\t\t\t/*\n-\t\t\t * We are the first to encounter this\n-\t\t\t * conflict.  Ask handle_file() to write the\n-\t\t\t * normalized contents to the \"preimage\" file.\n-\t\t\t *\n-\t\t\t * NEEDSWORK: what should happen if we had a\n-\t\t\t * leftover postimage that is totally\n-\t\t\t * unrelated?  Perhaps we should unlink it?\n-\t\t\t */\n-\t\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\t\tid->collection->status |= RR_HAS_PREIMAGE;\n-\t\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n-\t\t}\n+\t\t/* Ensure that the directory exists. */\n+\t\tmkdir_in_gitdir(rerere_path(id, NULL));\n \t}\n \n \tfor (i = 0; i < rr->nr; i++)\n-- \n2.8.0-215-g046a488\n"},{"id":"282004","messageId":"1459204942-26601-6-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 05/11] rerere: allow multiple variants to exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:16Z","receivedAt":"2016-03-28T22:42:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The shape of the conflict in a path determines the conflict ID.  The\npreimage and postimage pair that was recorded for the conflict ID\npreviously may or may not replay well for the conflict we just saw.\n\nCurrently, we punt when the previous resolution does not cleanly\nreplay, but ideally we should then be able to record the currently\nconflicted path by assigning a new 'variant', and then record the\nresolution the user is going to make.\n\nIntroduce a mechanism to have more than one variant for a given\nconflict ID; we do not actually assign any variant other than 0th\nvariant yet at this step.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----------\n rerere.h |   1 +\n 2 files changed, 106 insertions(+), 22 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 33b1348..0cf857b 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -30,14 +30,17 @@ static int rerere_dir_alloc;\n #define RR_HAS_PREIMAGE 2\n static struct rerere_dir {\n \tunsigned char sha1[20];\n-\tunsigned char status;\n+\tint status_alloc, status_nr;\n+\tunsigned char *status;\n } **rerere_dir;\n \n static void free_rerere_dirs(void)\n {\n \tint i;\n-\tfor (i = 0; i < rerere_dir_nr; i++)\n+\tfor (i = 0; i < rerere_dir_nr; i++) {\n+\t\tfree(rerere_dir[i]->status);\n \t\tfree(rerere_dir[i]);\n+\t}\n \tfree(rerere_dir);\n \trerere_dir_nr = rerere_dir_alloc = 0;\n \trerere_dir = NULL;\n@@ -53,17 +56,59 @@ static const char *rerere_id_hex(const struct rerere_id *id)\n \treturn sha1_to_hex(id->collection->sha1);\n }\n \n+static void fit_variant(struct rerere_dir *rr_dir, int variant)\n+{\n+\tvariant++;\n+\tALLOC_GROW(rr_dir->status, variant, rr_dir->status_alloc);\n+\tif (rr_dir->status_nr < variant) {\n+\t\tmemset(rr_dir->status + rr_dir->status_nr,\n+\t\t       '\\0', variant - rr_dir->status_nr);\n+\t\trr_dir->status_nr = variant;\n+\t}\n+}\n+\n+static void assign_variant(struct rerere_id *id)\n+{\n+\tint variant;\n+\tstruct rerere_dir *rr_dir = id->collection;\n+\n+\tvariant = id->variant;\n+\tif (variant < 0) {\n+\t\tvariant = 0; /* for now */\n+\t}\n+\tfit_variant(rr_dir, variant);\n+\tid->variant = variant;\n+}\n+\n const char *rerere_path(const struct rerere_id *id, const char *file)\n {\n \tif (!file)\n \t\treturn git_path(\"rr-cache/%s\", rerere_id_hex(id));\n \n-\treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n+\tif (id->variant <= 0)\n+\t\treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n+\n+\treturn git_path(\"rr-cache/%s/%s.%d\",\n+\t\t\trerere_id_hex(id), file, id->variant);\n }\n \n-static int is_rr_file(const char *name, const char *filename)\n+static int is_rr_file(const char *name, const char *filename, int *variant)\n {\n-\treturn !strcmp(name, filename);\n+\tconst char *suffix;\n+\tchar *ep;\n+\n+\tif (!strcmp(name, filename)) {\n+\t\t*variant = 0;\n+\t\treturn 1;\n+\t}\n+\tif (!skip_prefix(name, filename, &suffix) || *suffix != '.')\n+\t\treturn 0;\n+\n+\terrno = 0;\n+\t*variant = strtol(suffix + 1, &ep, 10);\n+\tif (errno || *ep)\n+\t\treturn 0;\n+\treturn 1;\n }\n \n static void scan_rerere_dir(struct rerere_dir *rr_dir)\n@@ -74,10 +119,15 @@ static void scan_rerere_dir(struct rerere_dir *rr_dir)\n \tif (!dir)\n \t\treturn;\n \twhile ((de = readdir(dir)) != NULL) {\n-\t\tif (is_rr_file(de->d_name, \"postimage\"))\n-\t\t\trr_dir->status |= RR_HAS_POSTIMAGE;\n-\t\telse if (is_rr_file(de->d_name, \"preimage\"))\n-\t\t\trr_dir->status |= RR_HAS_PREIMAGE;\n+\t\tint variant;\n+\n+\t\tif (is_rr_file(de->d_name, \"postimage\", &variant)) {\n+\t\t\tfit_variant(rr_dir, variant);\n+\t\t\trr_dir->status[variant] |= RR_HAS_POSTIMAGE;\n+\t\t} else if (is_rr_file(de->d_name, \"preimage\", &variant)) {\n+\t\t\tfit_variant(rr_dir, variant);\n+\t\t\trr_dir->status[variant] |= RR_HAS_PREIMAGE;\n+\t\t}\n \t}\n \tclosedir(dir);\n }\n@@ -100,7 +150,9 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \tif (pos < 0) {\n \t\trr_dir = xmalloc(sizeof(*rr_dir));\n \t\thashcpy(rr_dir->sha1, sha1);\n-\t\trr_dir->status = 0;\n+\t\trr_dir->status = NULL;\n+\t\trr_dir->status_nr = 0;\n+\t\trr_dir->status_alloc = 0;\n \t\tpos = -1 - pos;\n \n \t\t/* Make sure the array is big enough ... */\n@@ -118,19 +170,27 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n static int has_rerere_resolution(const struct rerere_id *id)\n {\n \tconst int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;\n+\tint variant = id->variant;\n \n-\treturn ((id->collection->status & both) == both);\n+\tif (variant < 0)\n+\t\treturn 0;\n+\treturn ((id->collection->status[variant] & both) == both);\n }\n \n static int has_rerere_preimage(const struct rerere_id *id)\n {\n-\treturn (id->collection->status & RR_HAS_PREIMAGE);\n+\tint variant = id->variant;\n+\n+\tif (variant < 0)\n+\t\treturn 0;\n+\treturn (id->collection->status[variant] & RR_HAS_PREIMAGE);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n \tid->collection = find_rerere_dir(hex);\n+\tid->variant = -1; /* not known yet */\n \treturn id;\n }\n \n@@ -157,16 +217,26 @@ static void read_rr(struct string_list *rr)\n \t\tchar *path;\n \t\tunsigned char sha1[20];\n \t\tstruct rerere_id *id;\n+\t\tint variant;\n \n \t\t/* There has to be the hash, tab, path and then NUL */\n \t\tif (buf.len < 42 || get_sha1_hex(buf.buf, sha1))\n \t\t\tdie(\"corrupt MERGE_RR\");\n \n-\t\tif (buf.buf[40] != '\\t')\n+\t\tif (buf.buf[40] != '.') {\n+\t\t\tvariant = 0;\n+\t\t\tpath = buf.buf + 40;\n+\t\t} else {\n+\t\t\terrno = 0;\n+\t\t\tvariant = strtol(buf.buf + 41, &path, 10);\n+\t\t\tif (errno)\n+\t\t\t\tdie(\"corrupt MERGE_RR\");\n+\t\t}\n+\t\tif (*(path++) != '\\t')\n \t\t\tdie(\"corrupt MERGE_RR\");\n \t\tbuf.buf[40] = '\\0';\n-\t\tpath = buf.buf + 41;\n \t\tid = new_rerere_id_hex(buf.buf);\n+\t\tid->variant = variant;\n \t\tstring_list_insert(rr, path)->util = id;\n \t}\n \tstrbuf_release(&buf);\n@@ -187,9 +257,16 @@ static int write_rr(struct string_list *rr, int out_fd)\n \t\tid = rr->items[i].util;\n \t\tif (!id)\n \t\t\tcontinue;\n-\t\tstrbuf_addf(&buf, \"%s\\t%s%c\",\n-\t\t\t    rerere_id_hex(id),\n-\t\t\t    rr->items[i].string, 0);\n+\t\tassert(id->variant >= 0);\n+\t\tif (0 < id->variant)\n+\t\t\tstrbuf_addf(&buf, \"%s.%d\\t%s%c\",\n+\t\t\t\t    rerere_id_hex(id), id->variant,\n+\t\t\t\t    rr->items[i].string, 0);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s\\t%s%c\",\n+\t\t\t\t    rerere_id_hex(id),\n+\t\t\t\t    rr->items[i].string, 0);\n+\n \t\tif (write_in_full(out_fd, buf.buf, buf.len) != buf.len)\n \t\t\tdie(\"unable to write rerere record\");\n \n@@ -752,7 +829,12 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t\t       struct string_list *update)\n {\n \tconst char *path = rr_item->string;\n-\tconst struct rerere_id *id = rr_item->util;\n+\tstruct rerere_id *id = rr_item->util;\n+\tint variant;\n+\n+\tif (id->variant < 0)\n+\t\tassign_variant(id);\n+\tvariant = id->variant;\n \n \tif (!has_rerere_preimage(id)) {\n \t\t/*\n@@ -761,13 +843,13 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t * the \"preimage\" file.\n \t\t */\n \t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tif (id->collection->status & RR_HAS_POSTIMAGE) {\n+\t\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n \t\t\tconst char *path = rerere_path(id, \"postimage\");\n \t\t\tif (unlink(path))\n \t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n-\t\t\tid->collection->status &= ~RR_HAS_POSTIMAGE;\n+\t\t\tid->collection->status[variant] &= ~RR_HAS_POSTIMAGE;\n \t\t}\n-\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n \t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n \t\treturn;\n \t} else if (has_rerere_resolution(id)) {\n@@ -784,7 +866,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t} else if (!handle_file(path, NULL, NULL)) {\n \t\t/* The user has resolved it. */\n \t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n-\t\tid->collection->status |= RR_HAS_POSTIMAGE;\n+\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n \t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n \t} else {\n \t\treturn;\n@@ -919,6 +1001,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)\n \n \t/* Nuke the recorded resolution for the conflict */\n \tid = new_rerere_id(sha1);\n+\tid->variant = 0; /* for now */\n \tfilename = rerere_path(id, \"postimage\");\n \tif (unlink(filename))\n \t\treturn (errno == ENOENT\ndiff --git a/rerere.h b/rerere.h\nindex faf5a23..64d38d9 100644\n--- a/rerere.h\n+++ b/rerere.h\n@@ -18,6 +18,7 @@ extern void *RERERE_RESOLVED;\n struct rerere_dir;\n struct rerere_id {\n \tstruct rerere_dir *collection;\n+\tint variant;\n };\n \n extern int setup_rerere(struct string_list *, int);\n-- \n2.8.0-215-g046a488\n"},{"id":"281998","messageId":"1459204942-26601-7-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 06/11] t4200: rerere a merge with two identical conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:17Z","receivedAt":"2016-03-28T22:42:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When the context of multiple identical conflicts are different, two\nseemingly the same conflict resolution cannot be safely applied.\n\nIn such a case, at least we should be able to record these two\nresolutions separately in the rerere database, and reuse them when\nwe see the same conflict later.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * Unchanged\n\n t/t4200-rerere.sh | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 74 insertions(+)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex c428011..6fcc6d4 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -406,4 +406,78 @@ test_expect_success 'rerere -h' '\n \ttest_i18ngrep [Uu]sage help\n '\n \n+concat_insert () {\n+\tlast=$1\n+\tshift\n+\tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n+}\n+\n+test_expect_failure 'multiple identical conflicts' '\n+\tgit reset --hard &&\n+\n+\ttest_seq 1 6 >early &&\n+\t>late &&\n+\ttest_seq 11 15 >short &&\n+\ttest_seq 111 120 >long &&\n+\tconcat_insert short >file1 &&\n+\tconcat_insert long >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m base &&\n+\tgit tag base &&\n+\tgit checkout -b six.1 &&\n+\tconcat_insert short 6.1 >file1 &&\n+\tconcat_insert long 6.1 >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m 6.1 &&\n+\tgit checkout -b six.2 HEAD^ &&\n+\tconcat_insert short 6.2 >file1 &&\n+\tconcat_insert long 6.2 >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m 6.2 &&\n+\n+\t# At this point, six.1 and six.2\n+\t# - derive from common ancestor that has two files\n+\t#   1...6 7 11..15 (file1) and 1...6 7 111..120 (file2)\n+\t# - six.1 replaces these 7s with 6.1\n+\t# - six.2 replaces these 7s with 6.2\n+\n+\ttest_must_fail git merge six.1 &&\n+\n+\t# Check that rerere knows that file1 and file2 have conflicts\n+\n+\tprintf \"%s\\n\" file1 file2 >expect &&\n+\tgit ls-files -u | sed -e \"s/^.*\t//\" | sort -u >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tgit rerere status | sort >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n+\tconcat_insert short 6.1 6.2 >file1 &&\n+\tconcat_insert long 6.1 6.2 >file2 &&\n+\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# We resolved file1 and file2\n+\tgit rerere &&\n+\t>expect &&\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Now we should be able to resolve them both\n+\tgit reset --hard &&\n+\ttest_must_fail git merge six.1 &&\n+\tgit rerere &&\n+\n+\t>expect &&\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tconcat_insert short 6.1 6.2 >file1.expect &&\n+\tconcat_insert long 6.1 6.2 >file2.expect &&\n+\ttest_cmp file1.expect file1 &&\n+\ttest_cmp file2.expect file2\n+'\n+\n test_done\n-- \n2.8.0-215-g046a488\n"},{"id":"281999","messageId":"1459204942-26601-8-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 07/11] rerere: do use multiple variants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:18Z","receivedAt":"2016-03-28T22:42:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This enables the multiple-variant support for real.  Multiple\nconflicts of the same shape can have differences in contexts where\nthey appear, interfering the replaying of recorded resolution of one\nconflict to another, and in such a case, their resolutions are\nrecorded as different variants under the same conflict ID.\n\nWe still need to adjust garbage collection codepaths for this\nchange, but the basic \"replay\" functionality is functional with\nthis change.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * Mostly unchanged except for fixing the same glitch as 04/11.\n\n rerere.c          | 98 ++++++++++++++++++++++++++++++++++---------------------\n t/t4200-rerere.sh |  2 +-\n 2 files changed, 61 insertions(+), 39 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 0cf857b..353227c 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -74,7 +74,9 @@ static void assign_variant(struct rerere_id *id)\n \n \tvariant = id->variant;\n \tif (variant < 0) {\n-\t\tvariant = 0; /* for now */\n+\t\tfor (variant = 0; variant < rr_dir->status_nr; variant++)\n+\t\t\tif (!rr_dir->status[variant])\n+\t\t\t\tbreak;\n \t}\n \tfit_variant(rr_dir, variant);\n \tid->variant = variant;\n@@ -177,15 +179,6 @@ static int has_rerere_resolution(const struct rerere_id *id)\n \treturn ((id->collection->status[variant] & both) == both);\n }\n \n-static int has_rerere_preimage(const struct rerere_id *id)\n-{\n-\tint variant = id->variant;\n-\n-\tif (variant < 0)\n-\t\treturn 0;\n-\treturn (id->collection->status[variant] & RR_HAS_PREIMAGE);\n-}\n-\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n@@ -818,6 +811,13 @@ static void update_paths(struct string_list *update)\n \t\trollback_lock_file(&index_lock);\n }\n \n+static void remove_variant(struct rerere_id *id)\n+{\n+\tunlink_or_warn(rerere_path(id, \"postimage\"));\n+\tunlink_or_warn(rerere_path(id, \"preimage\"));\n+\tid->collection->status[id->variant] = 0;\n+}\n+\n /*\n  * The path indicated by rr_item may still have conflict for which we\n  * have a recorded resolution, in which case replay it and optionally\n@@ -830,32 +830,46 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n {\n \tconst char *path = rr_item->string;\n \tstruct rerere_id *id = rr_item->util;\n+\tstruct rerere_dir *rr_dir = id->collection;\n \tint variant;\n \n-\tif (id->variant < 0)\n-\t\tassign_variant(id);\n \tvariant = id->variant;\n \n-\tif (!has_rerere_preimage(id)) {\n+\t/* Has the user resolved it already? */\n+\tif (variant >= 0) {\n+\t\tif (!handle_file(path, NULL, NULL)) {\n+\t\t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n+\t\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n+\t\t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n+\t\t\tfree_rerere_id(rr_item);\n+\t\t\trr_item->util = NULL;\n+\t\t\treturn;\n+\t\t}\n \t\t/*\n-\t\t * We are the first to encounter this conflict.  Ask\n-\t\t * handle_file() to write the normalized contents to\n-\t\t * the \"preimage\" file.\n+\t\t * There may be other variants that can cleanly\n+\t\t * replay.  Try them and update the variant number for\n+\t\t * this one.\n \t\t */\n-\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n-\t\t\tconst char *path = rerere_path(id, \"postimage\");\n-\t\t\tif (unlink(path))\n-\t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n-\t\t\tid->collection->status[variant] &= ~RR_HAS_POSTIMAGE;\n-\t\t}\n-\t\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n-\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n-\t\treturn;\n-\t} else if (has_rerere_resolution(id)) {\n-\t\t/* Is there a recorded resolution we could attempt to apply? */\n-\t\tif (merge(id, path))\n-\t\t\treturn; /* failed to replay */\n+\t}\n+\n+\t/* Does any existing resolution apply cleanly? */\n+\tfor (variant = 0; variant < rr_dir->status_nr; variant++) {\n+\t\tconst int both = RR_HAS_PREIMAGE | RR_HAS_POSTIMAGE;\n+\t\tstruct rerere_id vid = *id;\n+\n+\t\tif ((rr_dir->status[variant] & both) != both)\n+\t\t\tcontinue;\n+\n+\t\tvid.variant = variant;\n+\t\tif (merge(&vid, path))\n+\t\t\tcontinue; /* failed to replay */\n+\n+\t\t/*\n+\t\t * If there already is a different variant that applies\n+\t\t * cleanly, there is no point maintaining our own variant.\n+\t\t */\n+\t\tif (0 <= id->variant && id->variant != variant)\n+\t\t\tremove_variant(id);\n \n \t\tif (rerere_autoupdate)\n \t\t\tstring_list_insert(update, path);\n@@ -863,16 +877,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t\tfprintf(stderr,\n \t\t\t\t\"Resolved '%s' using previous resolution.\\n\",\n \t\t\t\tpath);\n-\t} else if (!handle_file(path, NULL, NULL)) {\n-\t\t/* The user has resolved it. */\n-\t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n-\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n-\t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n-\t} else {\n+\t\tfree_rerere_id(rr_item);\n+\t\trr_item->util = NULL;\n \t\treturn;\n \t}\n-\tfree_rerere_id(rr_item);\n-\trr_item->util = NULL;\n+\n+\t/* None of the existing one applies; we need a new variant */\n+\tassign_variant(id);\n+\n+\tvariant = id->variant;\n+\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n+\t\tconst char *path = rerere_path(id, \"postimage\");\n+\t\tif (unlink(path))\n+\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n+\t\tid->collection->status[variant] &= ~RR_HAS_POSTIMAGE;\n+\t}\n+\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n+\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n }\n \n static int do_plain_rerere(struct string_list *rr, int fd)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 6fcc6d4..b1bda20 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -412,7 +412,7 @@ concat_insert () {\n \tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n }\n \n-test_expect_failure 'multiple identical conflicts' '\n+test_expect_success 'multiple identical conflicts' '\n \tgit reset --hard &&\n \n \ttest_seq 1 6 >early &&\n-- \n2.8.0-215-g046a488\n"},{"id":"282002","messageId":"1459204942-26601-9-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 08/11] rerere: gc and clear","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:19Z","receivedAt":"2016-03-28T22:42:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adjust \"git rerere gc\" and \"git rerere clear\" to the new world order\nwith rerere database with multiple variants for the same shape of\nconflicts.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * New in v2; a preview appeared on $gmane/288454\n\n rerere.c          | 87 ++++++++++++++++++++++++++++++-------------------------\n t/t4200-rerere.sh | 82 ++++++++++++++++++++++++++++++++++++++++++++++-----\n 2 files changed, 123 insertions(+), 46 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 353227c..3d4dd33 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1081,29 +1081,16 @@ int rerere_forget(struct pathspec *pathspec)\n  * Garbage collection support\n  */\n \n-/*\n- * Note that this is not reentrant but is used only one-at-a-time\n- * so it does not matter right now.\n- */\n-static struct rerere_id *dirname_to_id(const char *name)\n-{\n-\tstatic struct rerere_id id;\n-\tid.collection = find_rerere_dir(name);\n-\treturn &id;\n-}\n-\n-static time_t rerere_created_at(const char *dir_name)\n+static time_t rerere_created_at(struct rerere_id *id)\n {\n \tstruct stat st;\n-\tstruct rerere_id *id = dirname_to_id(dir_name);\n \n \treturn stat(rerere_path(id, \"preimage\"), &st) ? (time_t) 0 : st.st_mtime;\n }\n \n-static time_t rerere_last_used_at(const char *dir_name)\n+static time_t rerere_last_used_at(struct rerere_id *id)\n {\n \tstruct stat st;\n-\tstruct rerere_id *id = dirname_to_id(dir_name);\n \n \treturn stat(rerere_path(id, \"postimage\"), &st) ? (time_t) 0 : st.st_mtime;\n }\n@@ -1113,15 +1100,28 @@ static time_t rerere_last_used_at(const char *dir_name)\n  */\n static void unlink_rr_item(struct rerere_id *id)\n {\n-\tunlink(rerere_path(id, \"thisimage\"));\n-\tunlink(rerere_path(id, \"preimage\"));\n-\tunlink(rerere_path(id, \"postimage\"));\n-\t/*\n-\t * NEEDSWORK: what if this rmdir() fails?  Wouldn't we then\n-\t * assume that we already have preimage recorded in\n-\t * do_plain_rerere()?\n-\t */\n-\trmdir(rerere_path(id, NULL));\n+\tunlink_or_warn(rerere_path(id, \"thisimage\"));\n+\tremove_variant(id);\n+\tid->collection->status[id->variant] = 0;\n+}\n+\n+static void prune_one(struct rerere_id *id, time_t now,\n+\t\t      int cutoff_resolve, int cutoff_noresolve)\n+{\n+\ttime_t then;\n+\tint cutoff;\n+\n+\tthen = rerere_last_used_at(id);\n+\tif (then)\n+\t\tcutoff = cutoff_resolve;\n+\telse {\n+\t\tthen = rerere_created_at(id);\n+\t\tif (!then)\n+\t\t\treturn;\n+\t\tcutoff = cutoff_noresolve;\n+\t}\n+\tif (then < now - cutoff * 86400)\n+\t\tunlink_rr_item(id);\n }\n \n void rerere_gc(struct string_list *rr)\n@@ -1129,8 +1129,8 @@ void rerere_gc(struct string_list *rr)\n \tstruct string_list to_remove = STRING_LIST_INIT_DUP;\n \tDIR *dir;\n \tstruct dirent *e;\n-\tint i, cutoff;\n-\ttime_t now = time(NULL), then;\n+\tint i;\n+\ttime_t now = time(NULL);\n \tint cutoff_noresolve = 15;\n \tint cutoff_resolve = 60;\n \n@@ -1142,25 +1142,32 @@ void rerere_gc(struct string_list *rr)\n \t\tdie_errno(\"unable to open rr-cache directory\");\n \t/* Collect stale conflict IDs ... */\n \twhile ((e = readdir(dir))) {\n+\t\tstruct rerere_dir *rr_dir;\n+\t\tstruct rerere_id id;\n+\t\tint now_empty;\n+\n \t\tif (is_dot_or_dotdot(e->d_name))\n \t\t\tcontinue;\n-\n-\t\tthen = rerere_last_used_at(e->d_name);\n-\t\tif (then) {\n-\t\t\tcutoff = cutoff_resolve;\n-\t\t} else {\n-\t\t\tthen = rerere_created_at(e->d_name);\n-\t\t\tif (!then)\n-\t\t\t\tcontinue;\n-\t\t\tcutoff = cutoff_noresolve;\n+\t\trr_dir = find_rerere_dir(e->d_name);\n+\t\tif (!rr_dir)\n+\t\t\tcontinue; /* or should we remove e->d_name? */\n+\n+\t\tnow_empty = 1;\n+\t\tfor (id.variant = 0, id.collection = rr_dir;\n+\t\t     id.variant < id.collection->status_nr;\n+\t\t     id.variant++) {\n+\t\t\tprune_one(&id, now, cutoff_resolve, cutoff_noresolve);\n+\t\t\tif (id.collection->status[id.variant])\n+\t\t\t\tnow_empty = 0;\n \t\t}\n-\t\tif (then < now - cutoff * 86400)\n+\t\tif (now_empty)\n \t\t\tstring_list_append(&to_remove, e->d_name);\n \t}\n \tclosedir(dir);\n-\t/* ... and then remove them one-by-one */\n+\n+\t/* ... and then remove the empty directories */\n \tfor (i = 0; i < to_remove.nr; i++)\n-\t\tunlink_rr_item(dirname_to_id(to_remove.items[i].string));\n+\t\trmdir(git_path(\"rr-cache/%s\", to_remove.items[i].string));\n \tstring_list_clear(&to_remove, 0);\n }\n \n@@ -1177,8 +1184,10 @@ void rerere_clear(struct string_list *merge_rr)\n \n \tfor (i = 0; i < merge_rr->nr; i++) {\n \t\tstruct rerere_id *id = merge_rr->items[i].util;\n-\t\tif (!has_rerere_resolution(id))\n+\t\tif (!has_rerere_resolution(id)) {\n \t\t\tunlink_rr_item(id);\n+\t\t\trmdir(rerere_path(id, NULL));\n+\t\t}\n \t}\n \tunlink_or_warn(git_path(\"MERGE_RR\"));\n }\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex b1bda20..647ea6f 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -412,6 +412,39 @@ concat_insert () {\n \tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n }\n \n+count_pre_post () {\n+\tfind .git/rr-cache/ -type f -name \"preimage*\" >actual &&\n+\ttest_line_count = \"$1\" actual &&\n+\tfind .git/rr-cache/ -type f -name \"postimage*\" >actual &&\n+\ttest_line_count = \"$2\" actual\n+}\n+\n+test_expect_success 'rerere gc' '\n+\tfind .git/rr-cache -type f >original &&\n+\txargs test-chmtime -172800 <original &&\n+\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\tfind .git/rr-cache -type f >actual &&\n+\ttest_cmp original actual &&\n+\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n+\tfind .git/rr-cache -type f >actual &&\n+\ttest_cmp original actual &&\n+\n+\tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n+\tfind .git/rr-cache -type f >actual &&\n+\t>expect &&\n+\ttest_cmp expect actual\n+'\n+\n+merge_conflict_resolve () {\n+\tgit reset --hard &&\n+\ttest_must_fail git merge six.1 &&\n+\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n+\tconcat_insert short 6.1 6.2 >file1 &&\n+\tconcat_insert long 6.1 6.2 >file2\n+}\n+\n test_expect_success 'multiple identical conflicts' '\n \tgit reset --hard &&\n \n@@ -441,10 +474,9 @@ test_expect_success 'multiple identical conflicts' '\n \t# - six.1 replaces these 7s with 6.1\n \t# - six.2 replaces these 7s with 6.2\n \n-\ttest_must_fail git merge six.1 &&\n+\tmerge_conflict_resolve &&\n \n \t# Check that rerere knows that file1 and file2 have conflicts\n-\n \tprintf \"%s\\n\" file1 file2 >expect &&\n \tgit ls-files -u | sed -e \"s/^.*\t//\" | sort -u >actual &&\n \ttest_cmp expect actual &&\n@@ -452,19 +484,43 @@ test_expect_success 'multiple identical conflicts' '\n \tgit rerere status | sort >actual &&\n \ttest_cmp expect actual &&\n \n-\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n-\tconcat_insert short 6.1 6.2 >file1 &&\n-\tconcat_insert long 6.1 6.2 >file2 &&\n-\n \tgit rerere remaining >actual &&\n \ttest_cmp expect actual &&\n \n+\tcount_pre_post 2 0 &&\n+\n+\t# Pretend that the conflicts were made quite some time ago\n+\tfind .git/rr-cache/ -type f | xargs test-chmtime -172800 &&\n+\n+\t# Unresolved entries have not expired yet\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\tcount_pre_post 2 0 &&\n+\n+\t# Unresolved entries have expired\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=1 rerere gc &&\n+\tcount_pre_post 0 0 &&\n+\n+\t# Recreate the conflicted state\n+\tmerge_conflict_resolve &&\n+\tcount_pre_post 2 0 &&\n+\n+\t# Clear it\n+\tgit rerere clear &&\n+\tcount_pre_post 0 0 &&\n+\n+\t# Recreate the conflicted state\n+\tmerge_conflict_resolve &&\n+\tcount_pre_post 2 0 &&\n+\n \t# We resolved file1 and file2\n \tgit rerere &&\n \t>expect &&\n \tgit rerere remaining >actual &&\n \ttest_cmp expect actual &&\n \n+\t# We must have recorded both of them\n+\tcount_pre_post 2 2 &&\n+\n \t# Now we should be able to resolve them both\n \tgit reset --hard &&\n \ttest_must_fail git merge six.1 &&\n@@ -477,7 +533,19 @@ test_expect_success 'multiple identical conflicts' '\n \tconcat_insert short 6.1 6.2 >file1.expect &&\n \tconcat_insert long 6.1 6.2 >file2.expect &&\n \ttest_cmp file1.expect file1 &&\n-\ttest_cmp file2.expect file2\n+\ttest_cmp file2.expect file2 &&\n+\n+\t# Pretend again\n+\tfind .git/rr-cache/ -type f | xargs test-chmtime -172800 &&\n+\n+\t# Resolved entries have not expired yet\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\n+\tcount_pre_post 2 2 &&\n+\n+\t# Resolved entries have expired\n+\tgit -c gc.rerereresolved=1 -c gc.rerereunresolved=5 rerere gc &&\n+\tcount_pre_post 0 0\n '\n \n test_done\n-- \n2.8.0-215-g046a488\n"},{"id":"282001","messageId":"1459204942-26601-10-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 09/11] rerere: move code related to \"forget\" together","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:20Z","receivedAt":"2016-03-28T22:42:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"rerere forget\" is the only user of handle_cache() helper, which in\nturn is the only user of rerere_io that reads from an in-core buffer\nwhose getline method is implemented as rerere_mem_getline().  Gather\nthem together.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * New in v2.\n\n rerere.c | 194 +++++++++++++++++++++++++++++++--------------------------------\n 1 file changed, 97 insertions(+), 97 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 3d4dd33..2b870e0 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -517,103 +517,6 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output\n }\n \n /*\n- * Subclass of rerere_io that reads from an in-core buffer that is a\n- * strbuf\n- */\n-struct rerere_io_mem {\n-\tstruct rerere_io io;\n-\tstruct strbuf input;\n-};\n-\n-/*\n- * ... and its getline() method implementation\n- */\n-static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)\n-{\n-\tstruct rerere_io_mem *io = (struct rerere_io_mem *)io_;\n-\tchar *ep;\n-\tsize_t len;\n-\n-\tstrbuf_release(sb);\n-\tif (!io->input.len)\n-\t\treturn -1;\n-\tep = memchr(io->input.buf, '\\n', io->input.len);\n-\tif (!ep)\n-\t\tep = io->input.buf + io->input.len;\n-\telse if (*ep == '\\n')\n-\t\tep++;\n-\tlen = ep - io->input.buf;\n-\tstrbuf_add(sb, io->input.buf, len);\n-\tstrbuf_remove(&io->input, 0, len);\n-\treturn 0;\n-}\n-\n-static int handle_cache(const char *path, unsigned char *sha1, const char *output)\n-{\n-\tmmfile_t mmfile[3] = {{NULL}};\n-\tmmbuffer_t result = {NULL, 0};\n-\tconst struct cache_entry *ce;\n-\tint pos, len, i, hunk_no;\n-\tstruct rerere_io_mem io;\n-\tint marker_size = ll_merge_marker_size(path);\n-\n-\t/*\n-\t * Reproduce the conflicted merge in-core\n-\t */\n-\tlen = strlen(path);\n-\tpos = cache_name_pos(path, len);\n-\tif (0 <= pos)\n-\t\treturn -1;\n-\tpos = -pos - 1;\n-\n-\twhile (pos < active_nr) {\n-\t\tenum object_type type;\n-\t\tunsigned long size;\n-\n-\t\tce = active_cache[pos++];\n-\t\tif (ce_namelen(ce) != len || memcmp(ce->name, path, len))\n-\t\t\tbreak;\n-\t\ti = ce_stage(ce) - 1;\n-\t\tif (!mmfile[i].ptr) {\n-\t\t\tmmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);\n-\t\t\tmmfile[i].size = size;\n-\t\t}\n-\t}\n-\tfor (i = 0; i < 3; i++)\n-\t\tif (!mmfile[i].ptr && !mmfile[i].size)\n-\t\t\tmmfile[i].ptr = xstrdup(\"\");\n-\n-\t/*\n-\t * NEEDSWORK: handle conflicts from merges with\n-\t * merge.renormalize set, too\n-\t */\n-\tll_merge(&result, path, &mmfile[0], NULL,\n-\t\t &mmfile[1], \"ours\",\n-\t\t &mmfile[2], \"theirs\", NULL);\n-\tfor (i = 0; i < 3; i++)\n-\t\tfree(mmfile[i].ptr);\n-\n-\tmemset(&io, 0, sizeof(io));\n-\tio.io.getline = rerere_mem_getline;\n-\tif (output)\n-\t\tio.io.output = fopen(output, \"w\");\n-\telse\n-\t\tio.io.output = NULL;\n-\tstrbuf_init(&io.input, 0);\n-\tstrbuf_attach(&io.input, result.ptr, result.size, result.size);\n-\n-\t/*\n-\t * Grab the conflict ID and optionally write the original\n-\t * contents with conflict markers out.\n-\t */\n-\thunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);\n-\tstrbuf_release(&io.input);\n-\tif (io.io.output)\n-\t\tfclose(io.io.output);\n-\treturn hunk_no;\n-}\n-\n-/*\n  * Look at a cache entry at \"i\" and see if it is not conflicting,\n  * conflicting and we are willing to handle, or conflicting and\n  * we are unable to handle, and return the determination in *type.\n@@ -1005,6 +908,103 @@ int rerere(int flags)\n \treturn status;\n }\n \n+/*\n+ * Subclass of rerere_io that reads from an in-core buffer that is a\n+ * strbuf\n+ */\n+struct rerere_io_mem {\n+\tstruct rerere_io io;\n+\tstruct strbuf input;\n+};\n+\n+/*\n+ * ... and its getline() method implementation\n+ */\n+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)\n+{\n+\tstruct rerere_io_mem *io = (struct rerere_io_mem *)io_;\n+\tchar *ep;\n+\tsize_t len;\n+\n+\tstrbuf_release(sb);\n+\tif (!io->input.len)\n+\t\treturn -1;\n+\tep = memchr(io->input.buf, '\\n', io->input.len);\n+\tif (!ep)\n+\t\tep = io->input.buf + io->input.len;\n+\telse if (*ep == '\\n')\n+\t\tep++;\n+\tlen = ep - io->input.buf;\n+\tstrbuf_add(sb, io->input.buf, len);\n+\tstrbuf_remove(&io->input, 0, len);\n+\treturn 0;\n+}\n+\n+static int handle_cache(const char *path, unsigned char *sha1, const char *output)\n+{\n+\tmmfile_t mmfile[3] = {{NULL}};\n+\tmmbuffer_t result = {NULL, 0};\n+\tconst struct cache_entry *ce;\n+\tint pos, len, i, hunk_no;\n+\tstruct rerere_io_mem io;\n+\tint marker_size = ll_merge_marker_size(path);\n+\n+\t/*\n+\t * Reproduce the conflicted merge in-core\n+\t */\n+\tlen = strlen(path);\n+\tpos = cache_name_pos(path, len);\n+\tif (0 <= pos)\n+\t\treturn -1;\n+\tpos = -pos - 1;\n+\n+\twhile (pos < active_nr) {\n+\t\tenum object_type type;\n+\t\tunsigned long size;\n+\n+\t\tce = active_cache[pos++];\n+\t\tif (ce_namelen(ce) != len || memcmp(ce->name, path, len))\n+\t\t\tbreak;\n+\t\ti = ce_stage(ce) - 1;\n+\t\tif (!mmfile[i].ptr) {\n+\t\t\tmmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);\n+\t\t\tmmfile[i].size = size;\n+\t\t}\n+\t}\n+\tfor (i = 0; i < 3; i++)\n+\t\tif (!mmfile[i].ptr && !mmfile[i].size)\n+\t\t\tmmfile[i].ptr = xstrdup(\"\");\n+\n+\t/*\n+\t * NEEDSWORK: handle conflicts from merges with\n+\t * merge.renormalize set, too?\n+\t */\n+\tll_merge(&result, path, &mmfile[0], NULL,\n+\t\t &mmfile[1], \"ours\",\n+\t\t &mmfile[2], \"theirs\", NULL);\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfile[i].ptr);\n+\n+\tmemset(&io, 0, sizeof(io));\n+\tio.io.getline = rerere_mem_getline;\n+\tif (output)\n+\t\tio.io.output = fopen(output, \"w\");\n+\telse\n+\t\tio.io.output = NULL;\n+\tstrbuf_init(&io.input, 0);\n+\tstrbuf_attach(&io.input, result.ptr, result.size, result.size);\n+\n+\t/*\n+\t * Grab the conflict ID and optionally write the original\n+\t * contents with conflict markers out.\n+\t */\n+\thunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);\n+\tstrbuf_release(&io.input);\n+\tif (io.io.output)\n+\t\tfclose(io.io.output);\n+\treturn hunk_no;\n+}\n+\n static int rerere_forget_one_path(const char *path, struct string_list *rr)\n {\n \tconst char *filename;\n-- \n2.8.0-215-g046a488\n"},{"id":"282003","messageId":"1459204942-26601-11-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 10/11] rerere: split code to call ll_merge() further","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:21Z","receivedAt":"2016-03-28T22:42:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The merge() helper function is given an existing rerere ID (i.e. the\nname of the .git/rr-cache/* subdirectory, and the variant number)\nthat identifies one <preimage, postimage> pair, try to see if the\nconflicted state in the given path can be resolved by using the pair,\nand if this succeeds, then update the conflicted path with the\nresult in the working tree.\n\nTo implement rerere_forget() in the multiple variant world, we'd\nneed a helper to do the \"see if a <preimage, postimage> pair cleanly\nresolves a conflicted state we have in-core\" part, without actually\ntouching any file in the working tree, in order to identify which\nvariant(s) to remove.  Split the logic to do so into a separate\nhelper function try_merge() out of merge().\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * New in v2.\n\n rerere.c | 47 +++++++++++++++++++++++++++++++----------------\n 1 file changed, 31 insertions(+), 16 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 2b870e0..e636d4b 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -622,6 +622,33 @@ int rerere_remaining(struct string_list *merge_rr)\n }\n \n /*\n+ * Try using the given conflict resolution \"ID\" to see\n+ * if that recorded conflict resolves cleanly what we\n+ * got in the \"cur\".\n+ */\n+static int try_merge(const struct rerere_id *id, const char *path,\n+\t\t     mmfile_t *cur, mmbuffer_t *result)\n+{\n+\tint ret;\n+\tmmfile_t base = {NULL, 0}, other = {NULL, 0};\n+\n+\tif (read_mmfile(&base, rerere_path(id, \"preimage\")) ||\n+\t    read_mmfile(&other, rerere_path(id, \"postimage\")))\n+\t\tret = 1;\n+\telse\n+\t\t/*\n+\t\t * A three-way merge. Note that this honors user-customizable\n+\t\t * low-level merge driver settings.\n+\t\t */\n+\t\tret = ll_merge(result, path, &base, NULL, cur, \"\", &other, \"\", NULL);\n+\n+\tfree(base.ptr);\n+\tfree(other.ptr);\n+\n+\treturn ret;\n+}\n+\n+/*\n  * Find the conflict identified by \"id\"; the change between its\n  * \"preimage\" (i.e. a previous contents with conflict markers) and its\n  * \"postimage\" (i.e. the corresponding contents with conflicts\n@@ -635,30 +662,20 @@ static int merge(const struct rerere_id *id, const char *path)\n {\n \tFILE *f;\n \tint ret;\n-\tmmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};\n+\tmmfile_t cur = {NULL, 0};\n \tmmbuffer_t result = {NULL, 0};\n \n \t/*\n \t * Normalize the conflicts in path and write it out to\n \t * \"thisimage\" temporary file.\n \t */\n-\tif (handle_file(path, NULL, rerere_path(id, \"thisimage\")) < 0) {\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n-\n-\tif (read_mmfile(&cur, rerere_path(id, \"thisimage\")) ||\n-\t    read_mmfile(&base, rerere_path(id, \"preimage\")) ||\n-\t    read_mmfile(&other, rerere_path(id, \"postimage\"))) {\n+\tif ((handle_file(path, NULL, rerere_path(id, \"thisimage\")) < 0) ||\n+\t    read_mmfile(&cur, rerere_path(id, \"thisimage\"))) {\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n-\t/*\n-\t * A three-way merge. Note that this honors user-customizable\n-\t * low-level merge driver settings.\n-\t */\n-\tret = ll_merge(&result, path, &base, NULL, &cur, \"\", &other, \"\", NULL);\n+\tret = try_merge(id, path, &cur, &result);\n \tif (ret)\n \t\tgoto out;\n \n@@ -684,8 +701,6 @@ static int merge(const struct rerere_id *id, const char *path)\n \n out:\n \tfree(cur.ptr);\n-\tfree(base.ptr);\n-\tfree(other.ptr);\n \tfree(result.ptr);\n \n \treturn ret;\n-- \n2.8.0-215-g046a488\n"},{"id":"282000","messageId":"1459204942-26601-12-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 11/11] rerere: adjust 'forget' to multi-variant world order","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-03-28T22:42:22Z","receivedAt":"2016-03-28T22:42:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Because conflicts with the same contents inside conflict blocks\nenclosed by \"<<<<<<<\" and \">>>>>>>\" can now have multiple variants\nto help three-way merge to adjust to the differences outside the\nconflict blocks, \"rerere forget $path\" needs to be taught that there\nmay be multiple recorded resolutions that share the same conflict\nhash (which groups the conflicts with \"the same contents inside\nconflict blocks\"), among which there are some that would not be\nrelevant to the conflict we are looking at.  These \"other variants\"\nthat happen to share the same conflict hash should not be cleared,\nand the variant that would apply to the current conflict may not be\nthe zero-th one (which is the only one that is cleared by the\ncurrent code).\n\nAfter finding the conflict hash, iterate over the existing variants\nand try to resolve the conflict using each of them to find the one\nthat \"cleanly\" resolves the current conflict.  That is the one we\nwant to forget and record the preimage for, so that the user can\nrecord the corrected resolution.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * New in v2.  I _think_ we should refactor the code further in\n   order to avoid rewriting \"thisimage\", \"thisimage.1\", etc. on the\n   filesystem (instead, teach handle_path() to output to an in-core\n   buffer and keep it around during this loop), and doing the same\n   handle_cache() over and over again, but for now this should do.\n\n rerere.c | 28 +++++++++++++++++++++++++++-\n 1 file changed, 27 insertions(+), 1 deletion(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex e636d4b..1693866 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1038,7 +1038,33 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)\n \n \t/* Nuke the recorded resolution for the conflict */\n \tid = new_rerere_id(sha1);\n-\tid->variant = 0; /* for now */\n+\n+\tfor (id->variant = 0;\n+\t     id->variant < id->collection->status_nr;\n+\t     id->variant++) {\n+\t\tmmfile_t cur = { NULL, 0 };\n+\t\tmmbuffer_t result = {NULL, 0};\n+\t\tint cleanly_resolved;\n+\n+\t\tif (!has_rerere_resolution(id))\n+\t\t\tcontinue;\n+\n+\t\thandle_cache(path, sha1, rerere_path(id, \"thisimage\"));\n+\t\tif (read_mmfile(&cur, rerere_path(id, \"thisimage\"))) {\n+\t\t\tfree(cur.ptr);\n+\t\t\treturn error(\"Failed to update conflicted state in '%s'\",\n+\t\t\t\t     path);\n+\t\t}\n+\t\tcleanly_resolved = !try_merge(id, path, &cur, &result);\n+\t\tfree(result.ptr);\n+\t\tfree(cur.ptr);\n+\t\tif (cleanly_resolved)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (id->collection->status_nr <= id->variant)\n+\t\treturn error(\"no remembered resolution for '%s'\", path);\n+\n \tfilename = rerere_path(id, \"postimage\");\n \tif (unlink(filename))\n \t\treturn (errno == ENOENT\n-- \n2.8.0-215-g046a488\n"},{"id":"282811","messageId":"1459983935-25267-1-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459204942-26601-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 00/11] saving and replaying multiple variants with rerere","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:24Z","receivedAt":"2016-04-06T23:05:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This adds only finishing touches compared to the previous one, which\nstarts at the message this is a response to and is found at\nhttp://thread.gmane.org/gmane.comp.version-control.git/290068.\n\nNamely, the last patch is different in that it not just adjusts\n'forget', but also has a new test for 'rerere forget'.\n\nJunio C Hamano (11):\n  rerere: split conflict ID further\n  rerere: scan $GIT_DIR/rr-cache/$ID when instantiating a rerere_id\n  rerere: handle leftover rr-cache/$ID directory and postimage files\n  rerere: delay the recording of preimage\n  rerere: allow multiple variants to exist\n  t4200: rerere a merge with two identical conflicts\n  rerere: do use multiple variants\n  rerere: gc and clear\n  rerere: move code related to \"forget\" together\n  rerere: split code to call ll_merge() further\n  rerere: adjust 'forget' to multi-variant world order\n\n rerere.c          | 626 +++++++++++++++++++++++++++++++++++++-----------------\n rerere.h          |   4 +-\n t/t4200-rerere.sh | 170 ++++++++++++++-\n 3 files changed, 604 insertions(+), 196 deletions(-)\n\n-- \n2.8.1-273-ga2cd0f9\n\n\n t/t4200-rerere.sh | 13 ++++++++++++-\n 1 file changed, 12 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 647ea6f..1a080e7 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -477,6 +477,7 @@ test_expect_success 'multiple identical conflicts' '\n \tmerge_conflict_resolve &&\n \n \t# Check that rerere knows that file1 and file2 have conflicts\n+\n \tprintf \"%s\\n\" file1 file2 >expect &&\n \tgit ls-files -u | sed -e \"s/^.*\t//\" | sort -u >actual &&\n \ttest_cmp expect actual &&\n@@ -535,7 +536,17 @@ test_expect_success 'multiple identical conflicts' '\n \ttest_cmp file1.expect file1 &&\n \ttest_cmp file2.expect file2 &&\n \n-\t# Pretend again\n+\t# Forget resolution for file2\n+\tgit rerere forget file2 &&\n+\techo file2 >expect &&\n+\tgit rerere status >actual &&\n+\ttest_cmp expect actual &&\n+\tcount_pre_post 2 1 &&\n+\n+\t# file2 already has correct resolution, so record it again\n+\tgit rerere &&\n+\n+\t# Pretend that the resolutions are old again\n \tfind .git/rr-cache/ -type f | xargs test-chmtime -172800 &&\n \n \t# Resolved entries have not expired yet\n"},{"id":"282815","messageId":"1459983935-25267-2-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 01/11] rerere: split conflict ID further","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:25Z","receivedAt":"2016-04-06T23:05:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The plan is to keep assigning the backward compatible conflict ID\nbased on the hash of the (normalized) text of conflicts, keep using\nthat conflict ID as the directory name under $GIT_DIR/rr-cache/, but\nallow each conflicted path to use a separate \"variant\" to record\nresolutions, i.e. having more than one <preimage,postimage> pairs\nunder $GIT_DIR/rr-cache/$ID/ directory.  As the first step in that\ndirection, separate the shared \"conflict ID\" out of the rerere_id\nstructure.\n\nThe plan is to keep information per $ID in rerere_dir, that can be\nshared among rerere_id that is per conflicted path.\n\nWhen we are done with rerere(), which can be directly called from\nother programs like \"git apply\", \"git commit\" and \"git merge\", the\nshared rerere_dir structures can be freed entirely, so they are not\nreference-counted and they are not freed when we release rerere_id's\nthat reference them.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----\n rerere.h |  3 ++-\n 2 files changed, 58 insertions(+), 6 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 3d0fa8f..a5d8a06 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -8,6 +8,7 @@\n #include \"ll-merge.h\"\n #include \"attr.h\"\n #include \"pathspec.h\"\n+#include \"sha1-lookup.h\"\n \n #define RESOLVED 0\n #define PUNTED 1\n@@ -22,6 +23,23 @@ static int rerere_autoupdate;\n \n static char *merge_rr_path;\n \n+static int rerere_dir_nr;\n+static int rerere_dir_alloc;\n+\n+static struct rerere_dir {\n+\tunsigned char sha1[20];\n+} **rerere_dir;\n+\n+static void free_rerere_dirs(void)\n+{\n+\tint i;\n+\tfor (i = 0; i < rerere_dir_nr; i++)\n+\t\tfree(rerere_dir[i]);\n+\tfree(rerere_dir);\n+\trerere_dir_nr = rerere_dir_alloc = 0;\n+\trerere_dir = NULL;\n+}\n+\n static void free_rerere_id(struct string_list_item *item)\n {\n \tfree(item->util);\n@@ -29,7 +47,7 @@ static void free_rerere_id(struct string_list_item *item)\n \n static const char *rerere_id_hex(const struct rerere_id *id)\n {\n-\treturn id->hex;\n+\treturn sha1_to_hex(id->collection->sha1);\n }\n \n const char *rerere_path(const struct rerere_id *id, const char *file)\n@@ -40,6 +58,37 @@ const char *rerere_path(const struct rerere_id *id, const char *file)\n \treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n }\n \n+static const unsigned char *rerere_dir_sha1(size_t i, void *table)\n+{\n+\tstruct rerere_dir **rr_dir = table;\n+\treturn rr_dir[i]->sha1;\n+}\n+\n+static struct rerere_dir *find_rerere_dir(const char *hex)\n+{\n+\tunsigned char sha1[20];\n+\tstruct rerere_dir *rr_dir;\n+\tint pos;\n+\n+\tif (get_sha1_hex(hex, sha1))\n+\t\treturn NULL; /* BUG */\n+\tpos = sha1_pos(sha1, rerere_dir, rerere_dir_nr, rerere_dir_sha1);\n+\tif (pos < 0) {\n+\t\trr_dir = xmalloc(sizeof(*rr_dir));\n+\t\thashcpy(rr_dir->sha1, sha1);\n+\t\tpos = -1 - pos;\n+\n+\t\t/* Make sure the array is big enough ... */\n+\t\tALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc);\n+\t\t/* ... and add it in. */\n+\t\trerere_dir_nr++;\n+\t\tmemmove(rerere_dir + pos + 1, rerere_dir + pos,\n+\t\t\t(rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));\n+\t\trerere_dir[pos] = rr_dir;\n+\t}\n+\treturn rerere_dir[pos];\n+}\n+\n static int has_rerere_resolution(const struct rerere_id *id)\n {\n \tstruct stat st;\n@@ -50,7 +99,7 @@ static int has_rerere_resolution(const struct rerere_id *id)\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n-\txsnprintf(id->hex, sizeof(id->hex), \"%s\", hex);\n+\tid->collection = find_rerere_dir(hex);\n \treturn id;\n }\n \n@@ -810,12 +859,14 @@ int setup_rerere(struct string_list *merge_rr, int flags)\n int rerere(int flags)\n {\n \tstruct string_list merge_rr = STRING_LIST_INIT_DUP;\n-\tint fd;\n+\tint fd, status;\n \n \tfd = setup_rerere(&merge_rr, flags);\n \tif (fd < 0)\n \t\treturn 0;\n-\treturn do_plain_rerere(&merge_rr, fd);\n+\tstatus = do_plain_rerere(&merge_rr, fd);\n+\tfree_rerere_dirs();\n+\treturn status;\n }\n \n static int rerere_forget_one_path(const char *path, struct string_list *rr)\n@@ -900,7 +951,7 @@ int rerere_forget(struct pathspec *pathspec)\n static struct rerere_id *dirname_to_id(const char *name)\n {\n \tstatic struct rerere_id id;\n-\txsnprintf(id.hex, sizeof(id.hex), \"%s\", name);\n+\tid.collection = find_rerere_dir(name);\n \treturn &id;\n }\n \ndiff --git a/rerere.h b/rerere.h\nindex ce545d0..faf5a23 100644\n--- a/rerere.h\n+++ b/rerere.h\n@@ -15,8 +15,9 @@ struct pathspec;\n  */\n extern void *RERERE_RESOLVED;\n \n+struct rerere_dir;\n struct rerere_id {\n-\tchar hex[41];\n+\tstruct rerere_dir *collection;\n };\n \n extern int setup_rerere(struct string_list *, int);\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282812","messageId":"1459983935-25267-3-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 02/11] rerere: scan $GIT_DIR/rr-cache/$ID when instantiating a rerere_id","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:26Z","receivedAt":"2016-04-06T23:05:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This will help fixing bootstrap corner-case issues, e.g. having an\nempty $GIT_DIR/rr-cache/$ID directory would fail to record a\npreimage, in later changes in this series.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 32 +++++++++++++++++++++++++++++---\n 1 file changed, 29 insertions(+), 3 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex a5d8a06..fbdade8 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -26,8 +26,11 @@ static char *merge_rr_path;\n static int rerere_dir_nr;\n static int rerere_dir_alloc;\n \n+#define RR_HAS_POSTIMAGE 1\n+#define RR_HAS_PREIMAGE 2\n static struct rerere_dir {\n \tunsigned char sha1[20];\n+\tunsigned char status;\n } **rerere_dir;\n \n static void free_rerere_dirs(void)\n@@ -58,6 +61,27 @@ const char *rerere_path(const struct rerere_id *id, const char *file)\n \treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n }\n \n+static int is_rr_file(const char *name, const char *filename)\n+{\n+\treturn !strcmp(name, filename);\n+}\n+\n+static void scan_rerere_dir(struct rerere_dir *rr_dir)\n+{\n+\tstruct dirent *de;\n+\tDIR *dir = opendir(git_path(\"rr-cache/%s\", sha1_to_hex(rr_dir->sha1)));\n+\n+\tif (!dir)\n+\t\treturn;\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tif (is_rr_file(de->d_name, \"postimage\"))\n+\t\t\trr_dir->status |= RR_HAS_POSTIMAGE;\n+\t\telse if (is_rr_file(de->d_name, \"preimage\"))\n+\t\t\trr_dir->status |= RR_HAS_PREIMAGE;\n+\t}\n+\tclosedir(dir);\n+}\n+\n static const unsigned char *rerere_dir_sha1(size_t i, void *table)\n {\n \tstruct rerere_dir **rr_dir = table;\n@@ -76,6 +100,7 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \tif (pos < 0) {\n \t\trr_dir = xmalloc(sizeof(*rr_dir));\n \t\thashcpy(rr_dir->sha1, sha1);\n+\t\trr_dir->status = 0;\n \t\tpos = -1 - pos;\n \n \t\t/* Make sure the array is big enough ... */\n@@ -85,15 +110,14 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \t\tmemmove(rerere_dir + pos + 1, rerere_dir + pos,\n \t\t\t(rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));\n \t\trerere_dir[pos] = rr_dir;\n+\t\tscan_rerere_dir(rr_dir);\n \t}\n \treturn rerere_dir[pos];\n }\n \n static int has_rerere_resolution(const struct rerere_id *id)\n {\n-\tstruct stat st;\n-\n-\treturn !stat(rerere_path(id, \"postimage\"), &st);\n+\treturn (id->collection->status & RR_HAS_POSTIMAGE);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n@@ -737,6 +761,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t} else if (!handle_file(path, NULL, NULL)) {\n \t\t/* The user has resolved it. */\n \t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n+\t\tid->collection->status |= RR_HAS_POSTIMAGE;\n \t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n \t} else {\n \t\treturn;\n@@ -797,6 +822,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\t * normalized contents to the \"preimage\" file.\n \t\t */\n \t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\tid->collection->status |= RR_HAS_PREIMAGE;\n \t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n \t}\n \n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282820","messageId":"1459983935-25267-4-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 03/11] rerere: handle leftover rr-cache/$ID directory and postimage files","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:27Z","receivedAt":"2016-04-06T23:05:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"If by some accident there is only $GIT_DIR/rr-cache/$ID directory\nexisted, we wouldn't have recorded a preimage for a conflict that\nis newly encountered, which would mean after a manual resolution,\nwe wouldn't have recorded it by storing the postimage, because the\nlogic used to be \"if there is no rr-cache/$ID directory, then we are\nthe first so record the preimage\".  Instead, record preimage if we\ndo not have one.\n\nIn addition, if there is only $GIT_DIR/rr-cache/$ID/postimage\nwithout corresponding preimage, we would have tried to call into\nmerge() and punted.\n\nThese would have been a situation frustratingly hard to recover\nfrom.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c          | 42 +++++++++++++++++++++++++-----------------\n t/t4200-rerere.sh | 17 ++++++++++++++++-\n 2 files changed, 41 insertions(+), 18 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex fbdade8..a1e2963 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -117,7 +117,9 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \n static int has_rerere_resolution(const struct rerere_id *id)\n {\n-\treturn (id->collection->status & RR_HAS_POSTIMAGE);\n+\tconst int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;\n+\n+\treturn ((id->collection->status & both) == both);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n@@ -806,24 +808,30 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\tstring_list_insert(rr, path)->util = id;\n \n \t\t/*\n-\t\t * If the directory does not exist, create\n-\t\t * it.  mkdir_in_gitdir() will fail with\n-\t\t * EEXIST if there already is one.\n-\t\t *\n-\t\t * NEEDSWORK: make sure \"gc\" does not remove\n-\t\t * preimage without removing the directory.\n+\t\t * Ensure that the directory exists.\n+\t\t * mkdir_in_gitdir() will fail with EEXIST if there\n+\t\t * already is one.\n \t\t */\n-\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)))\n-\t\t\tcontinue;\n+\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)) &&\n+\t\t    errno != EEXIST)\n+\t\t\tcontinue; /* NEEDSWORK: perhaps we should die? */\n \n-\t\t/*\n-\t\t * We are the first to encounter this\n-\t\t * conflict.  Ask handle_file() to write the\n-\t\t * normalized contents to the \"preimage\" file.\n-\t\t */\n-\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tid->collection->status |= RR_HAS_PREIMAGE;\n-\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\tif (id->collection->status & RR_HAS_PREIMAGE) {\n+\t\t\t;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * We are the first to encounter this\n+\t\t\t * conflict.  Ask handle_file() to write the\n+\t\t\t * normalized contents to the \"preimage\" file.\n+\t\t\t *\n+\t\t\t * NEEDSWORK: what should happen if we had a\n+\t\t\t * leftover postimage that is totally\n+\t\t\t * unrelated?  Perhaps we should unlink it?\n+\t\t\t */\n+\t\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\t}\n \t}\n \n \tfor (i = 0; i < rr->nr; i++)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex ed9c91e..c428011 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -184,12 +184,27 @@ test_expect_success 'rerere updates postimage timestamp' '\n '\n \n test_expect_success 'rerere clear' '\n-\trm $rr/postimage &&\n+\tmv $rr/postimage .git/post-saved &&\n \techo \"$sha1\ta1\" | perl -pe \"y/\\012/\\000/\" >.git/MERGE_RR &&\n \tgit rerere clear &&\n \t! test -d $rr\n '\n \n+test_expect_success 'leftover directory' '\n+\tgit reset --hard &&\n+\tmkdir -p $rr &&\n+\ttest_must_fail git merge first &&\n+\ttest -f $rr/preimage\n+'\n+\n+test_expect_success 'missing preimage' '\n+\tgit reset --hard &&\n+\tmkdir -p $rr &&\n+\tcp .git/post-saved $rr/postimage &&\n+\ttest_must_fail git merge first &&\n+\ttest -f $rr/preimage\n+'\n+\n test_expect_success 'set up for garbage collection tests' '\n \tmkdir -p $rr &&\n \techo Hello >$rr/preimage &&\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282822","messageId":"1459983935-25267-5-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 04/11] rerere: delay the recording of preimage","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:28Z","receivedAt":"2016-04-06T23:05:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We record the preimage only when there is no directory to record the\nconflict we encountered, i.e. when $GIT_DIR/rr-cache/$ID does not\nexist.  As the plan is to allow multiple <preimage,postimage> pairs\nas variants for the same conflict ID eventually, this logic needs to\ngo.\n\nAs the first step in that direction, stop the \"did we create the\ndirectory?  Then we record the preimage\" logic.  Instead, we record\nif a preimage does not exist when we saw a conflict in a path.  Also\nmake sure that we remove a stale postimage, which most likely is\ntotally unrelated to the resolution of this new conflict, when we\ncreate a new preimage under $ID when $GIT_DIR/rr-cache/$ID already\nexists.\n\nIn later patches, we will further update this logic to be \"do we\nhave <preimage,postimage> pair that cleanly resolve the current\nconflicts?  If not, record a new preimage as a new variant\", but\nthat does not happen at this stage yet.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 52 +++++++++++++++++++++++++---------------------------\n 1 file changed, 25 insertions(+), 27 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex a1e2963..33b1348 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -122,6 +122,11 @@ static int has_rerere_resolution(const struct rerere_id *id)\n \treturn ((id->collection->status & both) == both);\n }\n \n+static int has_rerere_preimage(const struct rerere_id *id)\n+{\n+\treturn (id->collection->status & RR_HAS_PREIMAGE);\n+}\n+\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n@@ -749,8 +754,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \tconst char *path = rr_item->string;\n \tconst struct rerere_id *id = rr_item->util;\n \n-\t/* Is there a recorded resolution we could attempt to apply? */\n-\tif (has_rerere_resolution(id)) {\n+\tif (!has_rerere_preimage(id)) {\n+\t\t/*\n+\t\t * We are the first to encounter this conflict.  Ask\n+\t\t * handle_file() to write the normalized contents to\n+\t\t * the \"preimage\" file.\n+\t\t */\n+\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\t\tif (id->collection->status & RR_HAS_POSTIMAGE) {\n+\t\t\tconst char *path = rerere_path(id, \"postimage\");\n+\t\t\tif (unlink(path))\n+\t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n+\t\t\tid->collection->status &= ~RR_HAS_POSTIMAGE;\n+\t\t}\n+\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n+\t\treturn;\n+\t} else if (has_rerere_resolution(id)) {\n+\t\t/* Is there a recorded resolution we could attempt to apply? */\n \t\tif (merge(id, path))\n \t\t\treturn; /* failed to replay */\n \n@@ -807,31 +828,8 @@ static int do_plain_rerere(struct string_list *rr, int fd)\n \t\tid = new_rerere_id(sha1);\n \t\tstring_list_insert(rr, path)->util = id;\n \n-\t\t/*\n-\t\t * Ensure that the directory exists.\n-\t\t * mkdir_in_gitdir() will fail with EEXIST if there\n-\t\t * already is one.\n-\t\t */\n-\t\tif (mkdir_in_gitdir(rerere_path(id, NULL)) &&\n-\t\t    errno != EEXIST)\n-\t\t\tcontinue; /* NEEDSWORK: perhaps we should die? */\n-\n-\t\tif (id->collection->status & RR_HAS_PREIMAGE) {\n-\t\t\t;\n-\t\t} else {\n-\t\t\t/*\n-\t\t\t * We are the first to encounter this\n-\t\t\t * conflict.  Ask handle_file() to write the\n-\t\t\t * normalized contents to the \"preimage\" file.\n-\t\t\t *\n-\t\t\t * NEEDSWORK: what should happen if we had a\n-\t\t\t * leftover postimage that is totally\n-\t\t\t * unrelated?  Perhaps we should unlink it?\n-\t\t\t */\n-\t\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\t\tid->collection->status |= RR_HAS_PREIMAGE;\n-\t\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n-\t\t}\n+\t\t/* Ensure that the directory exists. */\n+\t\tmkdir_in_gitdir(rerere_path(id, NULL));\n \t}\n \n \tfor (i = 0; i < rr->nr; i++)\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282813","messageId":"1459983935-25267-6-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 05/11] rerere: allow multiple variants to exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:29Z","receivedAt":"2016-04-06T23:05:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The shape of the conflict in a path determines the conflict ID.  The\npreimage and postimage pair that was recorded for the conflict ID\npreviously may or may not replay well for the conflict we just saw.\n\nCurrently, we punt when the previous resolution does not cleanly\nreplay, but ideally we should then be able to record the currently\nconflicted path by assigning a new 'variant', and then record the\nresolution the user is going to make.\n\nIntroduce a mechanism to have more than one variant for a given\nconflict ID; we do not actually assign any variant other than 0th\nvariant yet at this step.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----------\n rerere.h |   1 +\n 2 files changed, 106 insertions(+), 22 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 33b1348..0cf857b 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -30,14 +30,17 @@ static int rerere_dir_alloc;\n #define RR_HAS_PREIMAGE 2\n static struct rerere_dir {\n \tunsigned char sha1[20];\n-\tunsigned char status;\n+\tint status_alloc, status_nr;\n+\tunsigned char *status;\n } **rerere_dir;\n \n static void free_rerere_dirs(void)\n {\n \tint i;\n-\tfor (i = 0; i < rerere_dir_nr; i++)\n+\tfor (i = 0; i < rerere_dir_nr; i++) {\n+\t\tfree(rerere_dir[i]->status);\n \t\tfree(rerere_dir[i]);\n+\t}\n \tfree(rerere_dir);\n \trerere_dir_nr = rerere_dir_alloc = 0;\n \trerere_dir = NULL;\n@@ -53,17 +56,59 @@ static const char *rerere_id_hex(const struct rerere_id *id)\n \treturn sha1_to_hex(id->collection->sha1);\n }\n \n+static void fit_variant(struct rerere_dir *rr_dir, int variant)\n+{\n+\tvariant++;\n+\tALLOC_GROW(rr_dir->status, variant, rr_dir->status_alloc);\n+\tif (rr_dir->status_nr < variant) {\n+\t\tmemset(rr_dir->status + rr_dir->status_nr,\n+\t\t       '\\0', variant - rr_dir->status_nr);\n+\t\trr_dir->status_nr = variant;\n+\t}\n+}\n+\n+static void assign_variant(struct rerere_id *id)\n+{\n+\tint variant;\n+\tstruct rerere_dir *rr_dir = id->collection;\n+\n+\tvariant = id->variant;\n+\tif (variant < 0) {\n+\t\tvariant = 0; /* for now */\n+\t}\n+\tfit_variant(rr_dir, variant);\n+\tid->variant = variant;\n+}\n+\n const char *rerere_path(const struct rerere_id *id, const char *file)\n {\n \tif (!file)\n \t\treturn git_path(\"rr-cache/%s\", rerere_id_hex(id));\n \n-\treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n+\tif (id->variant <= 0)\n+\t\treturn git_path(\"rr-cache/%s/%s\", rerere_id_hex(id), file);\n+\n+\treturn git_path(\"rr-cache/%s/%s.%d\",\n+\t\t\trerere_id_hex(id), file, id->variant);\n }\n \n-static int is_rr_file(const char *name, const char *filename)\n+static int is_rr_file(const char *name, const char *filename, int *variant)\n {\n-\treturn !strcmp(name, filename);\n+\tconst char *suffix;\n+\tchar *ep;\n+\n+\tif (!strcmp(name, filename)) {\n+\t\t*variant = 0;\n+\t\treturn 1;\n+\t}\n+\tif (!skip_prefix(name, filename, &suffix) || *suffix != '.')\n+\t\treturn 0;\n+\n+\terrno = 0;\n+\t*variant = strtol(suffix + 1, &ep, 10);\n+\tif (errno || *ep)\n+\t\treturn 0;\n+\treturn 1;\n }\n \n static void scan_rerere_dir(struct rerere_dir *rr_dir)\n@@ -74,10 +119,15 @@ static void scan_rerere_dir(struct rerere_dir *rr_dir)\n \tif (!dir)\n \t\treturn;\n \twhile ((de = readdir(dir)) != NULL) {\n-\t\tif (is_rr_file(de->d_name, \"postimage\"))\n-\t\t\trr_dir->status |= RR_HAS_POSTIMAGE;\n-\t\telse if (is_rr_file(de->d_name, \"preimage\"))\n-\t\t\trr_dir->status |= RR_HAS_PREIMAGE;\n+\t\tint variant;\n+\n+\t\tif (is_rr_file(de->d_name, \"postimage\", &variant)) {\n+\t\t\tfit_variant(rr_dir, variant);\n+\t\t\trr_dir->status[variant] |= RR_HAS_POSTIMAGE;\n+\t\t} else if (is_rr_file(de->d_name, \"preimage\", &variant)) {\n+\t\t\tfit_variant(rr_dir, variant);\n+\t\t\trr_dir->status[variant] |= RR_HAS_PREIMAGE;\n+\t\t}\n \t}\n \tclosedir(dir);\n }\n@@ -100,7 +150,9 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n \tif (pos < 0) {\n \t\trr_dir = xmalloc(sizeof(*rr_dir));\n \t\thashcpy(rr_dir->sha1, sha1);\n-\t\trr_dir->status = 0;\n+\t\trr_dir->status = NULL;\n+\t\trr_dir->status_nr = 0;\n+\t\trr_dir->status_alloc = 0;\n \t\tpos = -1 - pos;\n \n \t\t/* Make sure the array is big enough ... */\n@@ -118,19 +170,27 @@ static struct rerere_dir *find_rerere_dir(const char *hex)\n static int has_rerere_resolution(const struct rerere_id *id)\n {\n \tconst int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;\n+\tint variant = id->variant;\n \n-\treturn ((id->collection->status & both) == both);\n+\tif (variant < 0)\n+\t\treturn 0;\n+\treturn ((id->collection->status[variant] & both) == both);\n }\n \n static int has_rerere_preimage(const struct rerere_id *id)\n {\n-\treturn (id->collection->status & RR_HAS_PREIMAGE);\n+\tint variant = id->variant;\n+\n+\tif (variant < 0)\n+\t\treturn 0;\n+\treturn (id->collection->status[variant] & RR_HAS_PREIMAGE);\n }\n \n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n \tid->collection = find_rerere_dir(hex);\n+\tid->variant = -1; /* not known yet */\n \treturn id;\n }\n \n@@ -157,16 +217,26 @@ static void read_rr(struct string_list *rr)\n \t\tchar *path;\n \t\tunsigned char sha1[20];\n \t\tstruct rerere_id *id;\n+\t\tint variant;\n \n \t\t/* There has to be the hash, tab, path and then NUL */\n \t\tif (buf.len < 42 || get_sha1_hex(buf.buf, sha1))\n \t\t\tdie(\"corrupt MERGE_RR\");\n \n-\t\tif (buf.buf[40] != '\\t')\n+\t\tif (buf.buf[40] != '.') {\n+\t\t\tvariant = 0;\n+\t\t\tpath = buf.buf + 40;\n+\t\t} else {\n+\t\t\terrno = 0;\n+\t\t\tvariant = strtol(buf.buf + 41, &path, 10);\n+\t\t\tif (errno)\n+\t\t\t\tdie(\"corrupt MERGE_RR\");\n+\t\t}\n+\t\tif (*(path++) != '\\t')\n \t\t\tdie(\"corrupt MERGE_RR\");\n \t\tbuf.buf[40] = '\\0';\n-\t\tpath = buf.buf + 41;\n \t\tid = new_rerere_id_hex(buf.buf);\n+\t\tid->variant = variant;\n \t\tstring_list_insert(rr, path)->util = id;\n \t}\n \tstrbuf_release(&buf);\n@@ -187,9 +257,16 @@ static int write_rr(struct string_list *rr, int out_fd)\n \t\tid = rr->items[i].util;\n \t\tif (!id)\n \t\t\tcontinue;\n-\t\tstrbuf_addf(&buf, \"%s\\t%s%c\",\n-\t\t\t    rerere_id_hex(id),\n-\t\t\t    rr->items[i].string, 0);\n+\t\tassert(id->variant >= 0);\n+\t\tif (0 < id->variant)\n+\t\t\tstrbuf_addf(&buf, \"%s.%d\\t%s%c\",\n+\t\t\t\t    rerere_id_hex(id), id->variant,\n+\t\t\t\t    rr->items[i].string, 0);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s\\t%s%c\",\n+\t\t\t\t    rerere_id_hex(id),\n+\t\t\t\t    rr->items[i].string, 0);\n+\n \t\tif (write_in_full(out_fd, buf.buf, buf.len) != buf.len)\n \t\t\tdie(\"unable to write rerere record\");\n \n@@ -752,7 +829,12 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t\t       struct string_list *update)\n {\n \tconst char *path = rr_item->string;\n-\tconst struct rerere_id *id = rr_item->util;\n+\tstruct rerere_id *id = rr_item->util;\n+\tint variant;\n+\n+\tif (id->variant < 0)\n+\t\tassign_variant(id);\n+\tvariant = id->variant;\n \n \tif (!has_rerere_preimage(id)) {\n \t\t/*\n@@ -761,13 +843,13 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t * the \"preimage\" file.\n \t\t */\n \t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tif (id->collection->status & RR_HAS_POSTIMAGE) {\n+\t\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n \t\t\tconst char *path = rerere_path(id, \"postimage\");\n \t\t\tif (unlink(path))\n \t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n-\t\t\tid->collection->status &= ~RR_HAS_POSTIMAGE;\n+\t\t\tid->collection->status[variant] &= ~RR_HAS_POSTIMAGE;\n \t\t}\n-\t\tid->collection->status |= RR_HAS_PREIMAGE;\n+\t\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n \t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n \t\treturn;\n \t} else if (has_rerere_resolution(id)) {\n@@ -784,7 +866,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t} else if (!handle_file(path, NULL, NULL)) {\n \t\t/* The user has resolved it. */\n \t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n-\t\tid->collection->status |= RR_HAS_POSTIMAGE;\n+\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n \t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n \t} else {\n \t\treturn;\n@@ -919,6 +1001,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)\n \n \t/* Nuke the recorded resolution for the conflict */\n \tid = new_rerere_id(sha1);\n+\tid->variant = 0; /* for now */\n \tfilename = rerere_path(id, \"postimage\");\n \tif (unlink(filename))\n \t\treturn (errno == ENOENT\ndiff --git a/rerere.h b/rerere.h\nindex faf5a23..64d38d9 100644\n--- a/rerere.h\n+++ b/rerere.h\n@@ -18,6 +18,7 @@ extern void *RERERE_RESOLVED;\n struct rerere_dir;\n struct rerere_id {\n \tstruct rerere_dir *collection;\n+\tint variant;\n };\n \n extern int setup_rerere(struct string_list *, int);\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282814","messageId":"1459983935-25267-7-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 06/11] t4200: rerere a merge with two identical conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:30Z","receivedAt":"2016-04-06T23:05:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When the context of multiple identical conflicts are different, two\nseemingly the same conflict resolution cannot be safely applied.\n\nIn such a case, at least we should be able to record these two\nresolutions separately in the rerere database, and reuse them when\nwe see the same conflict later.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t4200-rerere.sh | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 74 insertions(+)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex c428011..6fcc6d4 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -406,4 +406,78 @@ test_expect_success 'rerere -h' '\n \ttest_i18ngrep [Uu]sage help\n '\n \n+concat_insert () {\n+\tlast=$1\n+\tshift\n+\tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n+}\n+\n+test_expect_failure 'multiple identical conflicts' '\n+\tgit reset --hard &&\n+\n+\ttest_seq 1 6 >early &&\n+\t>late &&\n+\ttest_seq 11 15 >short &&\n+\ttest_seq 111 120 >long &&\n+\tconcat_insert short >file1 &&\n+\tconcat_insert long >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m base &&\n+\tgit tag base &&\n+\tgit checkout -b six.1 &&\n+\tconcat_insert short 6.1 >file1 &&\n+\tconcat_insert long 6.1 >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m 6.1 &&\n+\tgit checkout -b six.2 HEAD^ &&\n+\tconcat_insert short 6.2 >file1 &&\n+\tconcat_insert long 6.2 >file2 &&\n+\tgit add file1 file2 &&\n+\tgit commit -m 6.2 &&\n+\n+\t# At this point, six.1 and six.2\n+\t# - derive from common ancestor that has two files\n+\t#   1...6 7 11..15 (file1) and 1...6 7 111..120 (file2)\n+\t# - six.1 replaces these 7s with 6.1\n+\t# - six.2 replaces these 7s with 6.2\n+\n+\ttest_must_fail git merge six.1 &&\n+\n+\t# Check that rerere knows that file1 and file2 have conflicts\n+\n+\tprintf \"%s\\n\" file1 file2 >expect &&\n+\tgit ls-files -u | sed -e \"s/^.*\t//\" | sort -u >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tgit rerere status | sort >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n+\tconcat_insert short 6.1 6.2 >file1 &&\n+\tconcat_insert long 6.1 6.2 >file2 &&\n+\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# We resolved file1 and file2\n+\tgit rerere &&\n+\t>expect &&\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# Now we should be able to resolve them both\n+\tgit reset --hard &&\n+\ttest_must_fail git merge six.1 &&\n+\tgit rerere &&\n+\n+\t>expect &&\n+\tgit rerere remaining >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tconcat_insert short 6.1 6.2 >file1.expect &&\n+\tconcat_insert long 6.1 6.2 >file2.expect &&\n+\ttest_cmp file1.expect file1 &&\n+\ttest_cmp file2.expect file2\n+'\n+\n test_done\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282819","messageId":"1459983935-25267-8-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 07/11] rerere: do use multiple variants","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:31Z","receivedAt":"2016-04-06T23:05:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This enables the multiple-variant support for real.  Multiple\nconflicts of the same shape can have differences in contexts where\nthey appear, interfering the replaying of recorded resolution of one\nconflict to another, and in such a case, their resolutions are\nrecorded as different variants under the same conflict ID.\n\nWe still need to adjust garbage collection codepaths for this\nchange, but the basic \"replay\" functionality is functional with\nthis change.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c          | 98 ++++++++++++++++++++++++++++++++++---------------------\n t/t4200-rerere.sh |  2 +-\n 2 files changed, 61 insertions(+), 39 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 0cf857b..353227c 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -74,7 +74,9 @@ static void assign_variant(struct rerere_id *id)\n \n \tvariant = id->variant;\n \tif (variant < 0) {\n-\t\tvariant = 0; /* for now */\n+\t\tfor (variant = 0; variant < rr_dir->status_nr; variant++)\n+\t\t\tif (!rr_dir->status[variant])\n+\t\t\t\tbreak;\n \t}\n \tfit_variant(rr_dir, variant);\n \tid->variant = variant;\n@@ -177,15 +179,6 @@ static int has_rerere_resolution(const struct rerere_id *id)\n \treturn ((id->collection->status[variant] & both) == both);\n }\n \n-static int has_rerere_preimage(const struct rerere_id *id)\n-{\n-\tint variant = id->variant;\n-\n-\tif (variant < 0)\n-\t\treturn 0;\n-\treturn (id->collection->status[variant] & RR_HAS_PREIMAGE);\n-}\n-\n static struct rerere_id *new_rerere_id_hex(char *hex)\n {\n \tstruct rerere_id *id = xmalloc(sizeof(*id));\n@@ -818,6 +811,13 @@ static void update_paths(struct string_list *update)\n \t\trollback_lock_file(&index_lock);\n }\n \n+static void remove_variant(struct rerere_id *id)\n+{\n+\tunlink_or_warn(rerere_path(id, \"postimage\"));\n+\tunlink_or_warn(rerere_path(id, \"preimage\"));\n+\tid->collection->status[id->variant] = 0;\n+}\n+\n /*\n  * The path indicated by rr_item may still have conflict for which we\n  * have a recorded resolution, in which case replay it and optionally\n@@ -830,32 +830,46 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n {\n \tconst char *path = rr_item->string;\n \tstruct rerere_id *id = rr_item->util;\n+\tstruct rerere_dir *rr_dir = id->collection;\n \tint variant;\n \n-\tif (id->variant < 0)\n-\t\tassign_variant(id);\n \tvariant = id->variant;\n \n-\tif (!has_rerere_preimage(id)) {\n+\t/* Has the user resolved it already? */\n+\tif (variant >= 0) {\n+\t\tif (!handle_file(path, NULL, NULL)) {\n+\t\t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n+\t\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n+\t\t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n+\t\t\tfree_rerere_id(rr_item);\n+\t\t\trr_item->util = NULL;\n+\t\t\treturn;\n+\t\t}\n \t\t/*\n-\t\t * We are the first to encounter this conflict.  Ask\n-\t\t * handle_file() to write the normalized contents to\n-\t\t * the \"preimage\" file.\n+\t\t * There may be other variants that can cleanly\n+\t\t * replay.  Try them and update the variant number for\n+\t\t * this one.\n \t\t */\n-\t\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n-\t\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n-\t\t\tconst char *path = rerere_path(id, \"postimage\");\n-\t\t\tif (unlink(path))\n-\t\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n-\t\t\tid->collection->status[variant] &= ~RR_HAS_POSTIMAGE;\n-\t\t}\n-\t\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n-\t\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n-\t\treturn;\n-\t} else if (has_rerere_resolution(id)) {\n-\t\t/* Is there a recorded resolution we could attempt to apply? */\n-\t\tif (merge(id, path))\n-\t\t\treturn; /* failed to replay */\n+\t}\n+\n+\t/* Does any existing resolution apply cleanly? */\n+\tfor (variant = 0; variant < rr_dir->status_nr; variant++) {\n+\t\tconst int both = RR_HAS_PREIMAGE | RR_HAS_POSTIMAGE;\n+\t\tstruct rerere_id vid = *id;\n+\n+\t\tif ((rr_dir->status[variant] & both) != both)\n+\t\t\tcontinue;\n+\n+\t\tvid.variant = variant;\n+\t\tif (merge(&vid, path))\n+\t\t\tcontinue; /* failed to replay */\n+\n+\t\t/*\n+\t\t * If there already is a different variant that applies\n+\t\t * cleanly, there is no point maintaining our own variant.\n+\t\t */\n+\t\tif (0 <= id->variant && id->variant != variant)\n+\t\t\tremove_variant(id);\n \n \t\tif (rerere_autoupdate)\n \t\t\tstring_list_insert(update, path);\n@@ -863,16 +877,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,\n \t\t\tfprintf(stderr,\n \t\t\t\t\"Resolved '%s' using previous resolution.\\n\",\n \t\t\t\tpath);\n-\t} else if (!handle_file(path, NULL, NULL)) {\n-\t\t/* The user has resolved it. */\n-\t\tcopy_file(rerere_path(id, \"postimage\"), path, 0666);\n-\t\tid->collection->status[variant] |= RR_HAS_POSTIMAGE;\n-\t\tfprintf(stderr, \"Recorded resolution for '%s'.\\n\", path);\n-\t} else {\n+\t\tfree_rerere_id(rr_item);\n+\t\trr_item->util = NULL;\n \t\treturn;\n \t}\n-\tfree_rerere_id(rr_item);\n-\trr_item->util = NULL;\n+\n+\t/* None of the existing one applies; we need a new variant */\n+\tassign_variant(id);\n+\n+\tvariant = id->variant;\n+\thandle_file(path, NULL, rerere_path(id, \"preimage\"));\n+\tif (id->collection->status[variant] & RR_HAS_POSTIMAGE) {\n+\t\tconst char *path = rerere_path(id, \"postimage\");\n+\t\tif (unlink(path))\n+\t\t\tdie_errno(\"cannot unlink stray '%s'\", path);\n+\t\tid->collection->status[variant] &= ~RR_HAS_POSTIMAGE;\n+\t}\n+\tid->collection->status[variant] |= RR_HAS_PREIMAGE;\n+\tfprintf(stderr, \"Recorded preimage for '%s'\\n\", path);\n }\n \n static int do_plain_rerere(struct string_list *rr, int fd)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 6fcc6d4..b1bda20 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -412,7 +412,7 @@ concat_insert () {\n \tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n }\n \n-test_expect_failure 'multiple identical conflicts' '\n+test_expect_success 'multiple identical conflicts' '\n \tgit reset --hard &&\n \n \ttest_seq 1 6 >early &&\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282821","messageId":"1459983935-25267-9-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 08/11] rerere: gc and clear","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:32Z","receivedAt":"2016-04-06T23:05:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adjust \"git rerere gc\" and \"git rerere clear\" to the new world order\nwith rerere database with multiple variants for the same shape of\nconflicts.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c          | 87 ++++++++++++++++++++++++++++++-------------------------\n t/t4200-rerere.sh | 81 +++++++++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 123 insertions(+), 45 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 353227c..3d4dd33 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1081,29 +1081,16 @@ int rerere_forget(struct pathspec *pathspec)\n  * Garbage collection support\n  */\n \n-/*\n- * Note that this is not reentrant but is used only one-at-a-time\n- * so it does not matter right now.\n- */\n-static struct rerere_id *dirname_to_id(const char *name)\n-{\n-\tstatic struct rerere_id id;\n-\tid.collection = find_rerere_dir(name);\n-\treturn &id;\n-}\n-\n-static time_t rerere_created_at(const char *dir_name)\n+static time_t rerere_created_at(struct rerere_id *id)\n {\n \tstruct stat st;\n-\tstruct rerere_id *id = dirname_to_id(dir_name);\n \n \treturn stat(rerere_path(id, \"preimage\"), &st) ? (time_t) 0 : st.st_mtime;\n }\n \n-static time_t rerere_last_used_at(const char *dir_name)\n+static time_t rerere_last_used_at(struct rerere_id *id)\n {\n \tstruct stat st;\n-\tstruct rerere_id *id = dirname_to_id(dir_name);\n \n \treturn stat(rerere_path(id, \"postimage\"), &st) ? (time_t) 0 : st.st_mtime;\n }\n@@ -1113,15 +1100,28 @@ static time_t rerere_last_used_at(const char *dir_name)\n  */\n static void unlink_rr_item(struct rerere_id *id)\n {\n-\tunlink(rerere_path(id, \"thisimage\"));\n-\tunlink(rerere_path(id, \"preimage\"));\n-\tunlink(rerere_path(id, \"postimage\"));\n-\t/*\n-\t * NEEDSWORK: what if this rmdir() fails?  Wouldn't we then\n-\t * assume that we already have preimage recorded in\n-\t * do_plain_rerere()?\n-\t */\n-\trmdir(rerere_path(id, NULL));\n+\tunlink_or_warn(rerere_path(id, \"thisimage\"));\n+\tremove_variant(id);\n+\tid->collection->status[id->variant] = 0;\n+}\n+\n+static void prune_one(struct rerere_id *id, time_t now,\n+\t\t      int cutoff_resolve, int cutoff_noresolve)\n+{\n+\ttime_t then;\n+\tint cutoff;\n+\n+\tthen = rerere_last_used_at(id);\n+\tif (then)\n+\t\tcutoff = cutoff_resolve;\n+\telse {\n+\t\tthen = rerere_created_at(id);\n+\t\tif (!then)\n+\t\t\treturn;\n+\t\tcutoff = cutoff_noresolve;\n+\t}\n+\tif (then < now - cutoff * 86400)\n+\t\tunlink_rr_item(id);\n }\n \n void rerere_gc(struct string_list *rr)\n@@ -1129,8 +1129,8 @@ void rerere_gc(struct string_list *rr)\n \tstruct string_list to_remove = STRING_LIST_INIT_DUP;\n \tDIR *dir;\n \tstruct dirent *e;\n-\tint i, cutoff;\n-\ttime_t now = time(NULL), then;\n+\tint i;\n+\ttime_t now = time(NULL);\n \tint cutoff_noresolve = 15;\n \tint cutoff_resolve = 60;\n \n@@ -1142,25 +1142,32 @@ void rerere_gc(struct string_list *rr)\n \t\tdie_errno(\"unable to open rr-cache directory\");\n \t/* Collect stale conflict IDs ... */\n \twhile ((e = readdir(dir))) {\n+\t\tstruct rerere_dir *rr_dir;\n+\t\tstruct rerere_id id;\n+\t\tint now_empty;\n+\n \t\tif (is_dot_or_dotdot(e->d_name))\n \t\t\tcontinue;\n-\n-\t\tthen = rerere_last_used_at(e->d_name);\n-\t\tif (then) {\n-\t\t\tcutoff = cutoff_resolve;\n-\t\t} else {\n-\t\t\tthen = rerere_created_at(e->d_name);\n-\t\t\tif (!then)\n-\t\t\t\tcontinue;\n-\t\t\tcutoff = cutoff_noresolve;\n+\t\trr_dir = find_rerere_dir(e->d_name);\n+\t\tif (!rr_dir)\n+\t\t\tcontinue; /* or should we remove e->d_name? */\n+\n+\t\tnow_empty = 1;\n+\t\tfor (id.variant = 0, id.collection = rr_dir;\n+\t\t     id.variant < id.collection->status_nr;\n+\t\t     id.variant++) {\n+\t\t\tprune_one(&id, now, cutoff_resolve, cutoff_noresolve);\n+\t\t\tif (id.collection->status[id.variant])\n+\t\t\t\tnow_empty = 0;\n \t\t}\n-\t\tif (then < now - cutoff * 86400)\n+\t\tif (now_empty)\n \t\t\tstring_list_append(&to_remove, e->d_name);\n \t}\n \tclosedir(dir);\n-\t/* ... and then remove them one-by-one */\n+\n+\t/* ... and then remove the empty directories */\n \tfor (i = 0; i < to_remove.nr; i++)\n-\t\tunlink_rr_item(dirname_to_id(to_remove.items[i].string));\n+\t\trmdir(git_path(\"rr-cache/%s\", to_remove.items[i].string));\n \tstring_list_clear(&to_remove, 0);\n }\n \n@@ -1177,8 +1184,10 @@ void rerere_clear(struct string_list *merge_rr)\n \n \tfor (i = 0; i < merge_rr->nr; i++) {\n \t\tstruct rerere_id *id = merge_rr->items[i].util;\n-\t\tif (!has_rerere_resolution(id))\n+\t\tif (!has_rerere_resolution(id)) {\n \t\t\tunlink_rr_item(id);\n+\t\t\trmdir(rerere_path(id, NULL));\n+\t\t}\n \t}\n \tunlink_or_warn(git_path(\"MERGE_RR\"));\n }\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex b1bda20..a85fc7d 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -412,6 +412,39 @@ concat_insert () {\n \tcat early && printf \"%s\\n\" \"$@\" && cat late \"$last\"\n }\n \n+count_pre_post () {\n+\tfind .git/rr-cache/ -type f -name \"preimage*\" >actual &&\n+\ttest_line_count = \"$1\" actual &&\n+\tfind .git/rr-cache/ -type f -name \"postimage*\" >actual &&\n+\ttest_line_count = \"$2\" actual\n+}\n+\n+test_expect_success 'rerere gc' '\n+\tfind .git/rr-cache -type f >original &&\n+\txargs test-chmtime -172800 <original &&\n+\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\tfind .git/rr-cache -type f >actual &&\n+\ttest_cmp original actual &&\n+\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n+\tfind .git/rr-cache -type f >actual &&\n+\ttest_cmp original actual &&\n+\n+\tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n+\tfind .git/rr-cache -type f >actual &&\n+\t>expect &&\n+\ttest_cmp expect actual\n+'\n+\n+merge_conflict_resolve () {\n+\tgit reset --hard &&\n+\ttest_must_fail git merge six.1 &&\n+\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n+\tconcat_insert short 6.1 6.2 >file1 &&\n+\tconcat_insert long 6.1 6.2 >file2\n+}\n+\n test_expect_success 'multiple identical conflicts' '\n \tgit reset --hard &&\n \n@@ -441,7 +474,7 @@ test_expect_success 'multiple identical conflicts' '\n \t# - six.1 replaces these 7s with 6.1\n \t# - six.2 replaces these 7s with 6.2\n \n-\ttest_must_fail git merge six.1 &&\n+\tmerge_conflict_resolve &&\n \n \t# Check that rerere knows that file1 and file2 have conflicts\n \n@@ -452,19 +485,43 @@ test_expect_success 'multiple identical conflicts' '\n \tgit rerere status | sort >actual &&\n \ttest_cmp expect actual &&\n \n-\t# Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)\n-\tconcat_insert short 6.1 6.2 >file1 &&\n-\tconcat_insert long 6.1 6.2 >file2 &&\n-\n \tgit rerere remaining >actual &&\n \ttest_cmp expect actual &&\n \n+\tcount_pre_post 2 0 &&\n+\n+\t# Pretend that the conflicts were made quite some time ago\n+\tfind .git/rr-cache/ -type f | xargs test-chmtime -172800 &&\n+\n+\t# Unresolved entries have not expired yet\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\tcount_pre_post 2 0 &&\n+\n+\t# Unresolved entries have expired\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=1 rerere gc &&\n+\tcount_pre_post 0 0 &&\n+\n+\t# Recreate the conflicted state\n+\tmerge_conflict_resolve &&\n+\tcount_pre_post 2 0 &&\n+\n+\t# Clear it\n+\tgit rerere clear &&\n+\tcount_pre_post 0 0 &&\n+\n+\t# Recreate the conflicted state\n+\tmerge_conflict_resolve &&\n+\tcount_pre_post 2 0 &&\n+\n \t# We resolved file1 and file2\n \tgit rerere &&\n \t>expect &&\n \tgit rerere remaining >actual &&\n \ttest_cmp expect actual &&\n \n+\t# We must have recorded both of them\n+\tcount_pre_post 2 2 &&\n+\n \t# Now we should be able to resolve them both\n \tgit reset --hard &&\n \ttest_must_fail git merge six.1 &&\n@@ -477,7 +534,19 @@ test_expect_success 'multiple identical conflicts' '\n \tconcat_insert short 6.1 6.2 >file1.expect &&\n \tconcat_insert long 6.1 6.2 >file2.expect &&\n \ttest_cmp file1.expect file1 &&\n-\ttest_cmp file2.expect file2\n+\ttest_cmp file2.expect file2 &&\n+\n+\t# Pretend that the resolutions are old again\n+\tfind .git/rr-cache/ -type f | xargs test-chmtime -172800 &&\n+\n+\t# Resolved entries have not expired yet\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\n+\tcount_pre_post 2 2 &&\n+\n+\t# Resolved entries have expired\n+\tgit -c gc.rerereresolved=1 -c gc.rerereunresolved=5 rerere gc &&\n+\tcount_pre_post 0 0\n '\n \n test_done\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282816","messageId":"1459983935-25267-10-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 09/11] rerere: move code related to \"forget\" together","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:33Z","receivedAt":"2016-04-06T23:05:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"rerere forget\" is the only user of handle_cache() helper, which in\nturn is the only user of rerere_io that reads from an in-core buffer\nwhose getline method is implemented as rerere_mem_getline().  Gather\nthem together.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 194 +++++++++++++++++++++++++++++++--------------------------------\n 1 file changed, 97 insertions(+), 97 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 3d4dd33..2b870e0 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -517,103 +517,6 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output\n }\n \n /*\n- * Subclass of rerere_io that reads from an in-core buffer that is a\n- * strbuf\n- */\n-struct rerere_io_mem {\n-\tstruct rerere_io io;\n-\tstruct strbuf input;\n-};\n-\n-/*\n- * ... and its getline() method implementation\n- */\n-static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)\n-{\n-\tstruct rerere_io_mem *io = (struct rerere_io_mem *)io_;\n-\tchar *ep;\n-\tsize_t len;\n-\n-\tstrbuf_release(sb);\n-\tif (!io->input.len)\n-\t\treturn -1;\n-\tep = memchr(io->input.buf, '\\n', io->input.len);\n-\tif (!ep)\n-\t\tep = io->input.buf + io->input.len;\n-\telse if (*ep == '\\n')\n-\t\tep++;\n-\tlen = ep - io->input.buf;\n-\tstrbuf_add(sb, io->input.buf, len);\n-\tstrbuf_remove(&io->input, 0, len);\n-\treturn 0;\n-}\n-\n-static int handle_cache(const char *path, unsigned char *sha1, const char *output)\n-{\n-\tmmfile_t mmfile[3] = {{NULL}};\n-\tmmbuffer_t result = {NULL, 0};\n-\tconst struct cache_entry *ce;\n-\tint pos, len, i, hunk_no;\n-\tstruct rerere_io_mem io;\n-\tint marker_size = ll_merge_marker_size(path);\n-\n-\t/*\n-\t * Reproduce the conflicted merge in-core\n-\t */\n-\tlen = strlen(path);\n-\tpos = cache_name_pos(path, len);\n-\tif (0 <= pos)\n-\t\treturn -1;\n-\tpos = -pos - 1;\n-\n-\twhile (pos < active_nr) {\n-\t\tenum object_type type;\n-\t\tunsigned long size;\n-\n-\t\tce = active_cache[pos++];\n-\t\tif (ce_namelen(ce) != len || memcmp(ce->name, path, len))\n-\t\t\tbreak;\n-\t\ti = ce_stage(ce) - 1;\n-\t\tif (!mmfile[i].ptr) {\n-\t\t\tmmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);\n-\t\t\tmmfile[i].size = size;\n-\t\t}\n-\t}\n-\tfor (i = 0; i < 3; i++)\n-\t\tif (!mmfile[i].ptr && !mmfile[i].size)\n-\t\t\tmmfile[i].ptr = xstrdup(\"\");\n-\n-\t/*\n-\t * NEEDSWORK: handle conflicts from merges with\n-\t * merge.renormalize set, too\n-\t */\n-\tll_merge(&result, path, &mmfile[0], NULL,\n-\t\t &mmfile[1], \"ours\",\n-\t\t &mmfile[2], \"theirs\", NULL);\n-\tfor (i = 0; i < 3; i++)\n-\t\tfree(mmfile[i].ptr);\n-\n-\tmemset(&io, 0, sizeof(io));\n-\tio.io.getline = rerere_mem_getline;\n-\tif (output)\n-\t\tio.io.output = fopen(output, \"w\");\n-\telse\n-\t\tio.io.output = NULL;\n-\tstrbuf_init(&io.input, 0);\n-\tstrbuf_attach(&io.input, result.ptr, result.size, result.size);\n-\n-\t/*\n-\t * Grab the conflict ID and optionally write the original\n-\t * contents with conflict markers out.\n-\t */\n-\thunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);\n-\tstrbuf_release(&io.input);\n-\tif (io.io.output)\n-\t\tfclose(io.io.output);\n-\treturn hunk_no;\n-}\n-\n-/*\n  * Look at a cache entry at \"i\" and see if it is not conflicting,\n  * conflicting and we are willing to handle, or conflicting and\n  * we are unable to handle, and return the determination in *type.\n@@ -1005,6 +908,103 @@ int rerere(int flags)\n \treturn status;\n }\n \n+/*\n+ * Subclass of rerere_io that reads from an in-core buffer that is a\n+ * strbuf\n+ */\n+struct rerere_io_mem {\n+\tstruct rerere_io io;\n+\tstruct strbuf input;\n+};\n+\n+/*\n+ * ... and its getline() method implementation\n+ */\n+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)\n+{\n+\tstruct rerere_io_mem *io = (struct rerere_io_mem *)io_;\n+\tchar *ep;\n+\tsize_t len;\n+\n+\tstrbuf_release(sb);\n+\tif (!io->input.len)\n+\t\treturn -1;\n+\tep = memchr(io->input.buf, '\\n', io->input.len);\n+\tif (!ep)\n+\t\tep = io->input.buf + io->input.len;\n+\telse if (*ep == '\\n')\n+\t\tep++;\n+\tlen = ep - io->input.buf;\n+\tstrbuf_add(sb, io->input.buf, len);\n+\tstrbuf_remove(&io->input, 0, len);\n+\treturn 0;\n+}\n+\n+static int handle_cache(const char *path, unsigned char *sha1, const char *output)\n+{\n+\tmmfile_t mmfile[3] = {{NULL}};\n+\tmmbuffer_t result = {NULL, 0};\n+\tconst struct cache_entry *ce;\n+\tint pos, len, i, hunk_no;\n+\tstruct rerere_io_mem io;\n+\tint marker_size = ll_merge_marker_size(path);\n+\n+\t/*\n+\t * Reproduce the conflicted merge in-core\n+\t */\n+\tlen = strlen(path);\n+\tpos = cache_name_pos(path, len);\n+\tif (0 <= pos)\n+\t\treturn -1;\n+\tpos = -pos - 1;\n+\n+\twhile (pos < active_nr) {\n+\t\tenum object_type type;\n+\t\tunsigned long size;\n+\n+\t\tce = active_cache[pos++];\n+\t\tif (ce_namelen(ce) != len || memcmp(ce->name, path, len))\n+\t\t\tbreak;\n+\t\ti = ce_stage(ce) - 1;\n+\t\tif (!mmfile[i].ptr) {\n+\t\t\tmmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);\n+\t\t\tmmfile[i].size = size;\n+\t\t}\n+\t}\n+\tfor (i = 0; i < 3; i++)\n+\t\tif (!mmfile[i].ptr && !mmfile[i].size)\n+\t\t\tmmfile[i].ptr = xstrdup(\"\");\n+\n+\t/*\n+\t * NEEDSWORK: handle conflicts from merges with\n+\t * merge.renormalize set, too?\n+\t */\n+\tll_merge(&result, path, &mmfile[0], NULL,\n+\t\t &mmfile[1], \"ours\",\n+\t\t &mmfile[2], \"theirs\", NULL);\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfile[i].ptr);\n+\n+\tmemset(&io, 0, sizeof(io));\n+\tio.io.getline = rerere_mem_getline;\n+\tif (output)\n+\t\tio.io.output = fopen(output, \"w\");\n+\telse\n+\t\tio.io.output = NULL;\n+\tstrbuf_init(&io.input, 0);\n+\tstrbuf_attach(&io.input, result.ptr, result.size, result.size);\n+\n+\t/*\n+\t * Grab the conflict ID and optionally write the original\n+\t * contents with conflict markers out.\n+\t */\n+\thunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);\n+\tstrbuf_release(&io.input);\n+\tif (io.io.output)\n+\t\tfclose(io.io.output);\n+\treturn hunk_no;\n+}\n+\n static int rerere_forget_one_path(const char *path, struct string_list *rr)\n {\n \tconst char *filename;\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282817","messageId":"1459983935-25267-11-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 10/11] rerere: split code to call ll_merge() further","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:34Z","receivedAt":"2016-04-06T23:05:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The merge() helper function is given an existing rerere ID (i.e. the\nname of the .git/rr-cache/* subdirectory, and the variant number)\nthat identifies one <preimage, postimage> pair, try to see if the\nconflicted state in the given path can be resolved by using the pair,\nand if this succeeds, then update the conflicted path with the\nresult in the working tree.\n\nTo implement rerere_forget() in the multiple variant world, we'd\nneed a helper to do the \"see if a <preimage, postimage> pair cleanly\nresolves a conflicted state we have in-core\" part, without actually\ntouching any file in the working tree, in order to identify which\nvariant(s) to remove.  Split the logic to do so into a separate\nhelper function try_merge() out of merge().\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 47 +++++++++++++++++++++++++++++++----------------\n 1 file changed, 31 insertions(+), 16 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 2b870e0..e636d4b 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -622,6 +622,33 @@ int rerere_remaining(struct string_list *merge_rr)\n }\n \n /*\n+ * Try using the given conflict resolution \"ID\" to see\n+ * if that recorded conflict resolves cleanly what we\n+ * got in the \"cur\".\n+ */\n+static int try_merge(const struct rerere_id *id, const char *path,\n+\t\t     mmfile_t *cur, mmbuffer_t *result)\n+{\n+\tint ret;\n+\tmmfile_t base = {NULL, 0}, other = {NULL, 0};\n+\n+\tif (read_mmfile(&base, rerere_path(id, \"preimage\")) ||\n+\t    read_mmfile(&other, rerere_path(id, \"postimage\")))\n+\t\tret = 1;\n+\telse\n+\t\t/*\n+\t\t * A three-way merge. Note that this honors user-customizable\n+\t\t * low-level merge driver settings.\n+\t\t */\n+\t\tret = ll_merge(result, path, &base, NULL, cur, \"\", &other, \"\", NULL);\n+\n+\tfree(base.ptr);\n+\tfree(other.ptr);\n+\n+\treturn ret;\n+}\n+\n+/*\n  * Find the conflict identified by \"id\"; the change between its\n  * \"preimage\" (i.e. a previous contents with conflict markers) and its\n  * \"postimage\" (i.e. the corresponding contents with conflicts\n@@ -635,30 +662,20 @@ static int merge(const struct rerere_id *id, const char *path)\n {\n \tFILE *f;\n \tint ret;\n-\tmmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};\n+\tmmfile_t cur = {NULL, 0};\n \tmmbuffer_t result = {NULL, 0};\n \n \t/*\n \t * Normalize the conflicts in path and write it out to\n \t * \"thisimage\" temporary file.\n \t */\n-\tif (handle_file(path, NULL, rerere_path(id, \"thisimage\")) < 0) {\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n-\n-\tif (read_mmfile(&cur, rerere_path(id, \"thisimage\")) ||\n-\t    read_mmfile(&base, rerere_path(id, \"preimage\")) ||\n-\t    read_mmfile(&other, rerere_path(id, \"postimage\"))) {\n+\tif ((handle_file(path, NULL, rerere_path(id, \"thisimage\")) < 0) ||\n+\t    read_mmfile(&cur, rerere_path(id, \"thisimage\"))) {\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n-\t/*\n-\t * A three-way merge. Note that this honors user-customizable\n-\t * low-level merge driver settings.\n-\t */\n-\tret = ll_merge(&result, path, &base, NULL, &cur, \"\", &other, \"\", NULL);\n+\tret = try_merge(id, path, &cur, &result);\n \tif (ret)\n \t\tgoto out;\n \n@@ -684,8 +701,6 @@ static int merge(const struct rerere_id *id, const char *path)\n \n out:\n \tfree(cur.ptr);\n-\tfree(base.ptr);\n-\tfree(other.ptr);\n \tfree(result.ptr);\n \n \treturn ret;\n-- \n2.8.1-273-ga2cd0f9\n"},{"id":"282818","messageId":"1459983935-25267-12-git-send-email-gitster@pobox.com","threadId":"40355","inReplyTo":"1459983935-25267-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 11/11] rerere: adjust 'forget' to multi-variant world order","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-06T23:05:35Z","receivedAt":"2016-04-06T23:05:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Because conflicts with the same contents inside conflict blocks\nenclosed by \"<<<<<<<\" and \">>>>>>>\" can now have multiple variants\nto help three-way merge to adjust to the differences outside the\nconflict blocks, \"rerere forget $path\" needs to be taught that there\nmay be multiple recorded resolutions that share the same conflict\nhash (which groups the conflicts with \"the same contents inside\nconflict blocks\"), among which there are some that would not be\nrelevant to the conflict we are looking at.  These \"other variants\"\nthat happen to share the same conflict hash should not be cleared,\nand the variant that would apply to the current conflict may not be\nthe zero-th one (which is the only one that is cleared by the\ncurrent code).\n\nAfter finding the conflict hash, iterate over the existing variants\nand try to resolve the conflict using each of them to find the one\nthat \"cleanly\" resolves the current conflict.  That is the one we\nwant to forget and record the preimage for, so that the user can\nrecord the corrected resolution.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c          | 28 +++++++++++++++++++++++++++-\n t/t4200-rerere.sh | 10 ++++++++++\n 2 files changed, 37 insertions(+), 1 deletion(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex e636d4b..1693866 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1038,7 +1038,33 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)\n \n \t/* Nuke the recorded resolution for the conflict */\n \tid = new_rerere_id(sha1);\n-\tid->variant = 0; /* for now */\n+\n+\tfor (id->variant = 0;\n+\t     id->variant < id->collection->status_nr;\n+\t     id->variant++) {\n+\t\tmmfile_t cur = { NULL, 0 };\n+\t\tmmbuffer_t result = {NULL, 0};\n+\t\tint cleanly_resolved;\n+\n+\t\tif (!has_rerere_resolution(id))\n+\t\t\tcontinue;\n+\n+\t\thandle_cache(path, sha1, rerere_path(id, \"thisimage\"));\n+\t\tif (read_mmfile(&cur, rerere_path(id, \"thisimage\"))) {\n+\t\t\tfree(cur.ptr);\n+\t\t\treturn error(\"Failed to update conflicted state in '%s'\",\n+\t\t\t\t     path);\n+\t\t}\n+\t\tcleanly_resolved = !try_merge(id, path, &cur, &result);\n+\t\tfree(result.ptr);\n+\t\tfree(cur.ptr);\n+\t\tif (cleanly_resolved)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (id->collection->status_nr <= id->variant)\n+\t\treturn error(\"no remembered resolution for '%s'\", path);\n+\n \tfilename = rerere_path(id, \"postimage\");\n \tif (unlink(filename))\n \t\treturn (errno == ENOENT\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex a85fc7d..1a080e7 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -536,6 +536,16 @@ test_expect_success 'multiple identical conflicts' '\n \ttest_cmp file1.expect file1 &&\n \ttest_cmp file2.expect file2 &&\n \n+\t# Forget resolution for file2\n+\tgit rerere forget file2 &&\n+\techo file2 >expect &&\n+\tgit rerere status >actual &&\n+\ttest_cmp expect actual &&\n+\tcount_pre_post 2 1 &&\n+\n+\t# file2 already has correct resolution, so record it again\n+\tgit rerere &&\n+\n \t# Pretend that the resolutions are old again\n \tfind .git/rr-cache/ -type f | xargs test-chmtime -172800 &&\n \n-- \n2.8.1-273-ga2cd0f9\n"}]}