threads / rfc / 21698

RFC patch, 3 partsrerere: keep a list of resolved files in MERGE_RR

Subject: [PATCH/RFC 1/3] rerere: keep a list of resolved files in MERGE_RR

## tl;dr

7 messages between Nov 21, 2009 and Nov 24, 2009. Diffs are folded; open one to read it.

replies: 6people: 3as markdown or json

Johannes Sixt· Nov 21, 2009, 18:58 UTC · lore

[PATCH/RFC 0/3] git rerere unresolve file

When you commit an incorrect conflict resolution, git rerere gets in the way: rerere clear only clears cache entries of not yet resolved conflicts. But there is no other way to remove an incorrect resolution short of purging the whole rr-cache.

This series implements 'git rerere unresolve' that removes a recorded resolution.

This is RFC for two reasons:
- It changes the contents of MERGE_RR in a way that *may* interfer in unwanted 
ways with older versions that do not understand unresolve.
- rerere unresolve also restores the conflict regions. I'm not sure whether 
this is good because there is 'git checkout --conflict=merge file' that does 
it in a better way. See the commit message of patch 3.

If rerere unresolve does *not* restore conflict regions (and record the result as preimage right away) we are facing a problem: When the resolution is committed, the postimage has differences from the preimage that are not related to the conflict. This may inhibit reusing the resolution later when the file has new changes.

Johannes Sixt (3):
  rerere: keep a list of resolved files in MERGE_RR
  rerere: make recording of the preimage reusable
  git rerere unresolve file...
 Documentation/git-rerere.txt |   10 +++-
 builtin-rerere.c             |   13 +++-
 rerere.c                     |  153 ++++++++++++++++++++++++++++++++++-------
 rerere.h                     |    4 +-
 4 files changed, 148 insertions(+), 32 deletions(-)
Johannes Sixt· Nov 21, 2009, 19:00 UTC · re: Johannes Sixt · lore

Previously, MERGE_RR entries that rerere detected as resolved were removed from MERGE_RR so that it contained only entries for files that still had conflicts.

This changes the "database" format to also keep the entries about files for which resolutions have been recorded. The purpose is to allow that resolved conflicts can be unresolved. To do that it is necessary to have a mapping from the file name to the conflict hash.

The new format of MERGE_RR is:
1. Entries about unresolved conflicts.
2. An all-zeros SHA1 as boundary marker.
3. Entries about resolved conflicts.
Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 builtin-rerere.c |    4 +++-
 rerere.c         |   49 ++++++++++++++++++++++++++++++++++++++++---------
 rerere.h         |    2 +-
 3 files changed, 44 insertions(+), 11 deletions(-)
Show changes to 3 files +44 −11

builtin-rerere.c, rerere.c, rerere.h

diff --git a/builtin-rerere.c b/builtin-rerere.c
index adfb7b5..275827d 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -101,12 +101,13 @@ static int diff_two(const char *file1, const char *label1,
 int cmd_rerere(int argc, const char **argv, const char *prefix)
 {
 	struct string_list merge_rr = { NULL, 0, 0, 1 };
+	struct string_list merge_rr_done = { NULL, 0, 0, 1 };
 	int i, fd;
 
 	if (argc < 2)
 		return rerere();
 
-	fd = setup_rerere(&merge_rr);
+	fd = setup_rerere(&merge_rr, &merge_rr_done);
 	if (fd < 0)
 		return 0;
 
@@ -132,5 +133,6 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
 		usage(git_rerere_usage);
 
 	string_list_clear(&merge_rr, 1);
+	string_list_clear(&merge_rr_done, 1);
 	return 0;
 }
diff --git a/rerere.c b/rerere.c
index 29f95f6..84d0bb7 100644
--- a/rerere.c
+++ b/rerere.c
@@ -23,8 +23,20 @@ int has_rerere_resolution(const char *hex)
 	return !stat(rerere_path(hex, "postimage"), &st);
 }
 
-static void read_rr(struct string_list *rr)
+/*
+ * If MERGE_RR is read and rewritten by an old version, the section bound
+ * would not be preserved, and file names from both sections would be
+ * interleaved. We must make sure that the section marker does not end up
+ * in an arbitrary position. In particular, the safest result is that all
+ * paths are now in the first ("not done") section. Therefore, we choose
+ * a file name that sorts last (for all practical purposes).
+ */
+static const char section_mark[] =
+	"0000000000000000000000000000000000000000\t\377\377\377\377";
+
+static void read_rr(struct string_list *rr, struct string_list *rr_done)
 {
+	struct string_list *section = rr;
 	unsigned char sha1[20];
 	char buf[PATH_MAX];
 	FILE *in = fopen(merge_rr_path, "r");
@@ -43,14 +55,19 @@ static void read_rr(struct string_list *rr)
 			; /* do nothing */
 		if (i == sizeof(buf))
 			die("filename too long");
-		string_list_insert(buf, rr)->util = name;
+		if (prefixcmp(section_mark, name)) {
+			string_list_insert(buf, section)->util = name;
+		} else {
+			free(name);
+			section = rr_done;
+		}
 	}
 	fclose(in);
 }
 
 static struct lock_file write_lock;
 
-static int write_rr(struct string_list *rr, int out_fd)
+static void write_rr_section(struct string_list *rr, int out_fd)
 {
 	int i;
 	for (i = 0; i < rr->nr; i++) {
@@ -65,6 +82,17 @@ static int write_rr(struct string_list *rr, int out_fd)
 		    write_in_full(out_fd, path, length) != length)
 			die("unable to write rerere record");
 	}
+}
+
+static int write_rr(struct string_list *rr,
+		    struct string_list *rr_done, int out_fd)
+{
+	int length = strlen(section_mark)+1;
+
+	write_rr_section(rr, out_fd);
+	if (write_in_full(out_fd, section_mark, length) != length)
+		die("unable to write rerere record");
+	write_rr_section(rr_done, out_fd);
 	if (commit_lock_file(&write_lock) != 0)
 		die("unable to write rerere record");
 	return 0;
@@ -263,7 +291,8 @@ static int update_paths(struct string_list *update)
 	return status;
 }
 
-static int do_plain_rerere(struct string_list *rr, int fd)
+static int do_plain_rerere(struct string_list *rr,
+			   struct string_list *rr_done, int fd)
 {
 	struct string_list conflict = { NULL, 0, 0, 1 };
 	struct string_list update = { NULL, 0, 0, 1 };
@@ -328,13 +357,14 @@ static int do_plain_rerere(struct string_list *rr, int fd)
 		fprintf(stderr, "Recorded resolution for '%s'.\n", path);
 		copy_file(rerere_path(name, "postimage"), path, 0666);
 	mark_resolved:
+		string_list_insert(path, rr_done)->util = rr->items[i].util;
 		rr->items[i].util = NULL;
 	}
 
 	if (update.nr)
 		update_paths(&update);
 
-	return write_rr(rr, fd);
+	return write_rr(rr, rr_done, fd);
 }
 
 static int git_rerere_config(const char *var, const char *value, void *cb)
@@ -367,7 +397,7 @@ static int is_rerere_enabled(void)
 	return 1;
 }
 
-int setup_rerere(struct string_list *merge_rr)
+int setup_rerere(struct string_list *merge_rr, struct string_list *merge_rr_done)
 {
 	int fd;
 
@@ -378,17 +408,18 @@ int setup_rerere(struct string_list *merge_rr)
 	merge_rr_path = git_pathdup("MERGE_RR");
 	fd = hold_lock_file_for_update(&write_lock, merge_rr_path,
 				       LOCK_DIE_ON_ERROR);
-	read_rr(merge_rr);
+	read_rr(merge_rr, merge_rr_done);
 	return fd;
 }
 
 int rerere(void)
 {
 	struct string_list merge_rr = { NULL, 0, 0, 1 };
+	struct string_list merge_rr_done = { NULL, 0, 0, 1 };
 	int fd;
 
-	fd = setup_rerere(&merge_rr);
+	fd = setup_rerere(&merge_rr, &merge_rr_done);
 	if (fd < 0)
 		return 0;
-	return do_plain_rerere(&merge_rr, fd);
+	return do_plain_rerere(&merge_rr, &merge_rr_done, fd);
 }
diff --git a/rerere.h b/rerere.h
index 13313f3..9bb2f13 100644
--- a/rerere.h
+++ b/rerere.h
@@ -3,7 +3,7 @@
 
 #include "string-list.h"
 
-extern int setup_rerere(struct string_list *);
+extern int setup_rerere(struct string_list *, struct string_list *);
 extern int rerere(void);
 extern const char *rerere_path(const char *hex, const char *file);
 extern int has_rerere_resolution(const char *hex);
-- 
1.6.5.2.182.ge039a
Johannes Sixt· Nov 21, 2009, 19:01 UTC · re: Johannes Sixt · lore

[PATCH/RFC 2/3] rerere: make recording of the preimage reusable

This factors the code that computes the conflict hash and records the preimage into a helper function.

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 rerere.c |   30 ++++++++++++++++++++----------
 1 files changed, 20 insertions(+), 10 deletions(-)
Show changes to rerere.c +20 −10
diff --git a/rerere.c b/rerere.c
index 84d0bb7..11fef88 100644
--- a/rerere.c
+++ b/rerere.c
@@ -229,6 +229,25 @@ static int find_conflict(struct string_list *conflict)
 	return 0;
 }
 
+static int record_preimage(struct string_list *rr, const char *path, int force)
+{
+	unsigned char sha1[20];
+	char *hex;
+	int ret;
+
+	ret = handle_file(path, sha1, NULL);
+	if (ret < 1)
+		return -1;
+
+	hex = xstrdup(sha1_to_hex(sha1));
+	string_list_insert(path, rr)->util = hex;
+	if (mkdir(git_path("rr-cache/%s", hex), 0755) && !force)
+		return -1;
+
+	handle_file(path, NULL, rerere_path(hex, "preimage"));
+	return 0;
+}
+
 static int merge(const char *name, const char *path)
 {
 	int ret;
@@ -310,17 +329,8 @@ static int do_plain_rerere(struct string_list *rr,
 	for (i = 0; i < conflict.nr; i++) {
 		const char *path = conflict.items[i].string;
 		if (!string_list_has_string(rr, path)) {
-			unsigned char sha1[20];
-			char *hex;
-			int ret;
-			ret = handle_file(path, sha1, NULL);
-			if (ret < 1)
-				continue;
-			hex = xstrdup(sha1_to_hex(sha1));
-			string_list_insert(path, rr)->util = hex;
-			if (mkdir(git_path("rr-cache/%s", hex), 0755))
+			if (record_preimage(rr, path, 0))
 				continue;
-			handle_file(path, NULL, rerere_path(hex, "preimage"));
 			fprintf(stderr, "Recorded preimage for '%s'\n", path);
 		}
 	}
-- 
1.6.5.2.182.ge039a
Johannes Sixt· Nov 21, 2009, 19:02 UTC · re: Johannes Sixt · lore

[PATCH/RFC 3/3] git rerere unresolve file...

There was no way to remove a recorded resolution short of removing the entire .git/rr-cache. This gets in the way when an incorrect resolution was recorded.

This implements the subcommand 'git rerere unresolve' that selectively removes the recorded resolution of the specified files. It also reverts the resolution so that the files again have conflict markers. However, these unresolved conflict markers not necessarily reflect "ours" and "theirs" correctly because the preimage that was stored in the cache has the conflicted sides in a canonical order.

In handle_file(), the checks for the beginning and end of a conflict region have to be loosened slightly -- there can be any whitespace, including a LF, not just a blank -- because after the conflict regions are restored, there are no trailing blanks and a subsequent 'git rerere' would not recognize them anymore.

'git checkout --conflict=merge path...' can restore the conflicted file as well, but it does not remove the recorded resolution.

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 Documentation/git-rerere.txt |   10 +++++-
 builtin-rerere.c             |    9 +++--
 rerere.c                     |   74 ++++++++++++++++++++++++++++++++++++++----
 rerere.h                     |    2 +
 4 files changed, 84 insertions(+), 11 deletions(-)
Show changes to 4 files +84 −11

Documentation/git-rerere.txt, builtin-rerere.c, rerere.c, rerere.h

diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt
index 7dd515b..c9bf3f1 100644
--- a/Documentation/git-rerere.txt
+++ b/Documentation/git-rerere.txt
@@ -7,7 +7,7 @@ git-rerere - Reuse recorded resolution of conflicted merges
 
 SYNOPSIS
 --------
-'git rerere' ['clear'|'diff'|'status'|'gc']
+'git rerere' ['clear'|'diff'|'status'|'unresolve file...'|'gc']
 
 DESCRIPTION
 -----------
@@ -52,6 +52,14 @@ conflicts.  Additional arguments are passed directly to the system
 Like 'diff', but this only prints the filenames that will be tracked
 for resolutions.
 
+'unresolve'::
+
+Removes the resolution that was recorded for the specified files.
+The conflict sections are restored as well, although the direction of
+the "ours" and "theirs" sections may be inverted.
+You can use 'git checkout --conflict=merge file' to restore the
+original conflict markers in the correct direction.
+
 'gc'::
 
 This prunes records of conflicted merges that
diff --git a/builtin-rerere.c b/builtin-rerere.c
index 275827d..5d3a3a5 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -7,7 +7,7 @@
 #include "xdiff-interface.h"
 
 static const char git_rerere_usage[] =
-"git rerere [clear | status | diff | gc]";
+"git rerere [clear | status | diff | unresolve file... | gc]";
 
 /* these values are days */
 static int cutoff_noresolve = 15;
@@ -102,7 +102,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
 {
 	struct string_list merge_rr = { NULL, 0, 0, 1 };
 	struct string_list merge_rr_done = { NULL, 0, 0, 1 };
-	int i, fd;
+	int i, fd, ret = 0;
 
 	if (argc < 2)
 		return rerere();
@@ -129,10 +129,13 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
 			const char *name = (const char *)merge_rr.items[i].util;
 			diff_two(rerere_path(name, "preimage"), path, path, path);
 		}
+	else if (!strcmp(argv[1], "unresolve"))
+		ret = unresolve_rerere(&merge_rr, &merge_rr_done,
+				       get_pathspec(prefix, &argv[2]), fd);
 	else
 		usage(git_rerere_usage);
 
 	string_list_clear(&merge_rr, 1);
 	string_list_clear(&merge_rr_done, 1);
-	return 0;
+	return ret;
 }
diff --git a/rerere.c b/rerere.c
index 11fef88..b31008d 100644
--- a/rerere.c
+++ b/rerere.c
@@ -140,7 +140,7 @@ static int handle_file(const char *path,
 		git_SHA1_Init(&ctx);
 
 	while (fgets(buf, sizeof(buf), f)) {
-		if (!prefixcmp(buf, "<<<<<<< ")) {
+		if (!prefixcmp(buf, "<<<<<<<") && isspace(buf[7])) {
 			if (hunk != RR_CONTEXT)
 				goto bad;
 			hunk = RR_SIDE_1;
@@ -152,7 +152,7 @@ static int handle_file(const char *path,
 			if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
 				goto bad;
 			hunk = RR_SIDE_2;
-		} else if (!prefixcmp(buf, ">>>>>>> ")) {
+		} else if (!prefixcmp(buf, ">>>>>>>") && isspace(buf[7])) {
 			if (hunk != RR_SIDE_2)
 				goto bad;
 			if (strbuf_cmp(&one, &two) > 0)
@@ -248,23 +248,29 @@ static int record_preimage(struct string_list *rr, const char *path, int 
force)
 	return 0;
 }
 
-static int merge(const char *name, const char *path)
+#define RESOLVE 0
+#define UNRESOLVE 1
+#define UNRESOLVE_CHECK 2
+
+static int merge(const char *name, const char *path, int mode)
 {
 	int ret;
 	mmfile_t cur, base, other;
 	mmbuffer_t result = {NULL, 0};
 	xpparam_t xpp = {XDF_NEED_MINIMAL};
+	const char *basename = mode == RESOLVE ? "preimage" : "postimage";
+	const char *othername = mode == RESOLVE ? "postimage" : "preimage";
 
 	if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
 		return 1;
 
 	if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
-			read_mmfile(&base, rerere_path(name, "preimage")) ||
-			read_mmfile(&other, rerere_path(name, "postimage")))
+			read_mmfile(&base, rerere_path(name, basename)) ||
+			read_mmfile(&other, rerere_path(name, othername)))
 		return 1;
 	ret = xdl_merge(&base, &cur, "", &other, "",
 			&xpp, XDL_MERGE_ZEALOUS, &result);
-	if (!ret) {
+	if (!ret && mode != UNRESOLVE_CHECK) {
 		FILE *f = fopen(path, "w");
 		if (!f)
 			return error("Could not open %s: %s", path,
@@ -347,7 +353,7 @@ static int do_plain_rerere(struct string_list *rr,
 		const char *name = (const char *)rr->items[i].util;
 
 		if (has_rerere_resolution(name)) {
-			if (!merge(name, path)) {
+			if (!merge(name, path, RESOLVE)) {
 				if (rerere_autoupdate)
 					string_list_insert(path, &update);
 				fprintf(stderr,
@@ -433,3 +439,57 @@ int rerere(void)
 		return 0;
 	return do_plain_rerere(&merge_rr, &merge_rr_done, fd);
 }
+
+static int unresolve_check(struct string_list *rr_done, const char *path)
+{
+	struct string_list_item *item = string_list_lookup(path, rr_done);
+
+	if (!item) {
+		warning("not resolved using a previous resolution: %s", path);
+		return 0;
+	}
+	if (!has_rerere_resolution(item->util))
+		return error("no resolution found for %s", path);
+	if (merge(item->util, path, UNRESOLVE_CHECK))
+		return error("cannot revert resolution of %s", path);
+	return 0;
+}
+
+int unresolve_rerere_1(struct string_list *rr, struct string_list *rr_done,
+		       const char *path)
+{
+	struct string_list_item *item = string_list_lookup(path, rr_done);
+
+	if (!item)
+		return 0;
+	if (merge(item->util, path, UNRESOLVE))
+		return -1;
+
+	if (record_preimage(rr, path, 1))
+		return error("no conflict markers found: %s", path);
+
+	item->util = NULL;
+	unlink_or_warn(rerere_path(item->util, "postimage"));
+	return 0;
+}
+
+int unresolve_rerere(struct string_list *rr, struct string_list *rr_done,
+		     const char **pathspec, int fd)
+{
+	int i, ret = 0;
+
+	if (!pathspec)
+		return error("unresolve what?");
+
+	for (i = 0; pathspec[i]; i++) {
+		ret |= unresolve_check(rr_done, pathspec[i]);
+	}
+	if (ret)
+		return ret;
+
+	for (i = 0; !ret && pathspec[i]; i++) {
+		ret = unresolve_rerere_1(rr, rr_done, pathspec[i]);
+	}
+	ret |= write_rr(rr, rr_done, fd);
+	return ret;
+}
diff --git a/rerere.h b/rerere.h
index 9bb2f13..a0fa50f 100644
--- a/rerere.h
+++ b/rerere.h
@@ -7,5 +7,7 @@ extern int setup_rerere(struct string_list *, struct string_list *);
 extern int rerere(void);
 extern const char *rerere_path(const char *hex, const char *file);
 extern int has_rerere_resolution(const char *hex);
+extern int unresolve_rerere(struct string_list *, struct string_list *,
+	const char **, int);
 
 #endif
-- 
1.6.5.2.182.ge039a
Junio C Hamano· Nov 22, 2009, 02:53 UTC · re: Johannes Sixt · lore

Re: [PATCH/RFC 0/3] git rerere unresolve file

Johannes Sixt <j6t@kdbg.org> writes:
> ... But 
> there is no other way to remove an incorrect resolution short of purging the 
> whole rr-cache.
No, no no, no.  You do not have to.

As long as you can find out the <preimage,postimage> pair, iow, the subdirectory used by .git/rr-cache/, you can remove only "postimage" from there and redo your merge. "ls -t1 .git/rr-cache/*/thisimage | head" would be one way to manually find out which one it is.

For example, let's take this merge as an example.
commit 697eb20061dfa00838df32ac24c414dfb4a1f920
Merge: bba637b 46b77a6
Author: Junio C Hamano <gitster@pobox.com>
    Merge branch 'jk/1.7.0-status' into jch
You may not have rerere entries for this, so follow me along for a while.
 $ git checkout bba637b
 $ git merge 46b77a6

This will have a conflict. We are priming your rerere database, so we cheat (notice the last dot ".").

 $ git checkout 697eb200 -- .
 $ git rerere
Now reset it away, and redo the merge
 $ git reset --hard
 $ git checkout bba637b
 $ git merge 46b77a6
It will autoresolve the way I resolved in 697eb200
Suppose you do not like the resolution; you can do:
    $ git checkout --conflict=merge Documentation/git-commit.txt
    $ git rerere

to force updating "thisimage". The newest one is the entry you are looking for.

    $ ls -1t .git/rr-cache/*/thisimage | head -n 1
    .git/rr-cache/02aac459b0c777f92a8ca6f1e449e6760d366c20/thisimage

I can remove "postimage" from the directory, recreate conflict again, run rerere to remember it anew, resolve and have rerere remember the new resolution.

A tool support to compute the conflict hash would be part of a nice solution.

    $ git checkout --conflict=merge Documentation/git-commit.txt
    $ git rerere hash
    02aac459b0c777f92a8ca6f1e449e6760d366c20 Documentation/git-commit.txt    

Instead of giving "hash" subcommand and having the user to remove postimage from the directory, the tool can remove it, of course.

    $ git checkout --conflict=merge Documentation/git-commit.txt
    $ git rerere forget Documentation/git-commit.txt
    02aac459b0c777f92a8ca6f1e449e6760d366c20 Documentation/git-commit.txt    
    $ edit Documentation/git-commit.txt ;# come up with a better resolution 
    $ git rerere ;# record it
The attached are rough sketch of such "hash/forget" subcommands.
I however think the best user experience would go like this:
 * Run "git merge"; rerere replays an earlier resolution.
   $ git merge ...
   User decides that it is not a desirable one.
 * User fixes it and creates a better resolution in the work tree.  The
   user may fix-up the autoresolution or the user may first do "checkout
   --conflict=merge" and resolves the conflict from scratch.
   It does not matter how the updated resolution is prepared in the work
   tree.
 * Then the user tells rerere that "this is the corrected resolution",
   perhaps
   $ git rerere update Documentation/git-commit.txt
   This will
   - Internally recompute the original conflicted state, i.e. run
     "checkout --conflict=merge Documentation/git-commit.txt" in-core;
   - feed it to rerere.c::handle_file() to learn the conflict hash;
   - read the user's updated resolution from the work tree, and update
     "postimage" with it.

There is no need to change MERGE_RR at all if you did it the way outlined in the above.

My "hash" would be more-or-less a useless command. It still might be useful as a plumbing command, but it is mostly for debugging.

The "forget" subcommand may be useful, but the real implementation should be the first half of the "update", iow, recreate conflict in-core in order to compute the conflict hash, and drop existing "postimage", without replacing it from the work tree. We should leave it up to the user using "checkout --conflict" to reproduce the conflict in the work tree.

 builtin-rerere.c |    4 +++
 rerere.c         |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 rerere.h         |    2 +
 3 files changed, 64 insertions(+), 0 deletions(-)
Show changes to 3 files +64 −0

builtin-rerere.c, rerere.c, rerere.h

diff --git a/builtin-rerere.c b/builtin-rerere.c
index 343d6cd..8865313 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -108,6 +108,10 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
 
 	if (!strcmp(argv[1], "-h"))
 		usage(git_rerere_usage);
+	else if (!strcmp(argv[1], "hash"))
+		return rerere_report_hash();
+	else if (!strcmp(argv[1], "forget"))
+		return rerere_forget(argv + 2);
 
 	fd = setup_rerere(&merge_rr);
 	if (fd < 0)
diff --git a/rerere.c b/rerere.c
index 29f95f6..4899219 100644
--- a/rerere.c
+++ b/rerere.c
@@ -392,3 +392,61 @@ int rerere(void)
 		return 0;
 	return do_plain_rerere(&merge_rr, fd);
 }
+
+int rerere_forget(const char **path)
+{
+	struct string_list conflict = { NULL, 0, 0, 1 };
+	int i;
+
+	find_conflict(&conflict);
+
+	/*
+	 * Note note note.  I am being lazy here.  The loop should not
+	 * iterate over "path" like this.
+	 *
+	 * It should instead iterate over conflict entries, using "path"
+	 * as pathspec to filter, so that you can say "I am done with
+	 * all the conflict in Documentation/ area".
+	 */
+	for (i = 0; path[i]; i++) { 
+		unsigned char sha1[20];
+		const char *postimage;
+		int ret;
+
+		if (!string_list_has_string(&conflict, path[i])) {
+			error("No such conflicted path %s\n", path[i]);
+			continue;
+		}
+		ret = handle_file(path[i], sha1, NULL);
+		if (ret < 1) {
+			error("No conflict in work tree %s\n", path[i]);
+			continue;
+		}
+		postimage = rerere_path(sha1_to_hex(sha1), "postimage");
+		if (!unlink(postimage))
+			fprintf(stderr, "forgot resolution for %s\n", path[i]);
+		else if (errno == ENOENT)
+			error("no remembered resolution for %s", path[i]);
+		else
+			error("cannot unlink %s: %s", postimage, strerror(errno));
+	}
+	return 0;
+}
+
+int rerere_report_hash(void)
+{
+	struct string_list conflict = { NULL, 0, 0, 1 };
+	int i;
+
+	find_conflict(&conflict);
+	for (i = 0; i < conflict.nr; i++) {
+		const char *path = conflict.items[i].string;
+		unsigned char sha1[20];
+		int ret;
+		ret = handle_file(path, sha1, NULL);
+		if (ret < 1)
+			continue;
+		printf("%s %s\n", sha1_to_hex(sha1), path);
+	}
+	return 0;
+}
diff --git a/rerere.h b/rerere.h
index 13313f3..a10dea9 100644
--- a/rerere.h
+++ b/rerere.h
@@ -7,5 +7,7 @@ extern int setup_rerere(struct string_list *);
 extern int rerere(void);
 extern const char *rerere_path(const char *hex, const char *file);
 extern int has_rerere_resolution(const char *hex);
+extern int rerere_report_hash(void);
+extern int rerere_forget(const char **path);
 
 #endif
Johannes Sixt· Nov 22, 2009, 14:19 UTC · re: Junio C Hamano · lore

Re: [PATCH/RFC 0/3] git rerere unresolve file

On Sonntag, 22. November 2009, Junio C Hamano wrote:
Show 6 quoted lines
> Johannes Sixt <j6t@kdbg.org> writes:
> > ... But
> > there is no other way to remove an incorrect resolution short of purging
> > the whole rr-cache.
>
> No, no no, no.  You do not have to.

Oh, yeah, I know. But even for people who know how to drive plumbing commands, this:

> "ls -t1 .git/rr-cache/*/thisimage | head"
> would be one way to manually find out which one it is.

does not count as "you can" in my book. ;) It assumes that there are at most a handful conflicted files. In my case, for example, I have to fix a merge where there are ~100 conflicted files.

Show 22 quoted lines
>  * Then the user tells rerere that "this is the corrected resolution",
>    perhaps
>
>    $ git rerere update Documentation/git-commit.txt
>
>    This will
>
>    - Internally recompute the original conflicted state, i.e. run
>      "checkout --conflict=merge Documentation/git-commit.txt" in-core;
>
>    - feed it to rerere.c::handle_file() to learn the conflict hash;
>
>    - read the user's updated resolution from the work tree, and update
>      "postimage" with it.
>
> ...
>
> The "forget" subcommand may be useful, but the real implementation should
> be the first half of the "update", iow, recreate conflict in-core in order
> to compute the conflict hash, and drop existing "postimage", without
> replacing it from the work tree.  We should leave it up to the user using
> "checkout --conflict" to reproduce the conflict in the work tree.

Thank you for your elaborate feedback and the guidelines about how to move forward.

I actually think that 'forget' should be the primary subcommand. Because after the postimage was purged, the next implicit or explicit 'git rerere' will record the resolution anyway. I'll see what I can do.

-- Hannes
Nanako Shiraishi· Nov 24, 2009, 23:40 UTC · re: Johannes Sixt · lore

Re: [PATCH/RFC 0/3] git rerere unresolve file

Quoting Johannes Sixt <j6t@kdbg.org>
Show 33 quoted lines
> On Sonntag, 22. November 2009, Junio C Hamano wrote:
> [snip]
>>  * Then the user tells rerere that "this is the corrected resolution",
>>    perhaps
>>
>>    $ git rerere update Documentation/git-commit.txt
>>
>>    This will
>>
>>    - Internally recompute the original conflicted state, i.e. run
>>      "checkout --conflict=merge Documentation/git-commit.txt" in-core;
>>
>>    - feed it to rerere.c::handle_file() to learn the conflict hash;
>>
>>    - read the user's updated resolution from the work tree, and update
>>      "postimage" with it.
>>
>> ...
>>
>> The "forget" subcommand may be useful, but the real implementation should
>> be the first half of the "update", iow, recreate conflict in-core in order
>> to compute the conflict hash, and drop existing "postimage", without
>> replacing it from the work tree.  We should leave it up to the user using
>> "checkout --conflict" to reproduce the conflict in the work tree.
>
> Thank you for your elaborate feedback and the guidelines about how to move 
> forward.
>
> I actually think that 'forget' should be the primary subcommand. Because after 
> the postimage was purged, the next implicit or explicit 'git rerere' will 
> record the resolution anyway. I'll see what I can do.
>
> -- Hannes
I think 'forget' should be the primary interface to this new feature too. If I mistakenly recorded an incorrect merge in the past and I'm trying to do a better job this time, I wouldn't be confident enough to be able to say 'update' to mark the attempt I made this time, and it may take more than one attempts for me to reach a good result. Running 'forget' once will let me retry until I run 'rerere' again.
-- 
Nanako Shiraishi
http://ivory.ap.teacup.com/nanako3/

← back to recent threads