Skip to content

Commit dea4562

Browse files
committed
rerere forget path: forget recorded resolution
After you find out an earlier resolution you told rerere to use was a mismerge, there is no easy way to clear it. A new subcommand "forget" can be used to tell git to forget a recorded resolution, so that you can redo the merge from scratch. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 27d6b08 commit dea4562

File tree

4 files changed

+161
-0
lines changed

4 files changed

+161
-0
lines changed

builtin-rerere.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
110110

111111
if (!strcmp(argv[1], "-h"))
112112
usage(git_rerere_usage);
113+
else if (!strcmp(argv[1], "forget"))
114+
return rerere_forget(argv + 2);
113115

114116
fd = setup_rerere(&merge_rr);
115117
if (fd < 0)

rerere.c

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include "rerere.h"
44
#include "xdiff/xdiff.h"
55
#include "xdiff-interface.h"
6+
#include "dir.h"
7+
#include "resolve-undo.h"
8+
#include "ll-merge.h"
69

710
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
811
static int rerere_enabled = -1;
@@ -223,6 +226,87 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output
223226
return hunk_no;
224227
}
225228

229+
struct rerere_io_mem {
230+
struct rerere_io io;
231+
struct strbuf input;
232+
};
233+
234+
static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
235+
{
236+
struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
237+
char *ep;
238+
size_t len;
239+
240+
strbuf_release(sb);
241+
if (!io->input.len)
242+
return -1;
243+
ep = strchrnul(io->input.buf, '\n');
244+
if (*ep == '\n')
245+
ep++;
246+
len = ep - io->input.buf;
247+
strbuf_add(sb, io->input.buf, len);
248+
strbuf_remove(&io->input, 0, len);
249+
return 0;
250+
}
251+
252+
static int handle_cache(const char *path, unsigned char *sha1, const char *output)
253+
{
254+
mmfile_t mmfile[3];
255+
mmbuffer_t result = {NULL, 0};
256+
struct cache_entry *ce;
257+
int pos, len, i, hunk_no;
258+
struct rerere_io_mem io;
259+
260+
/*
261+
* Reproduce the conflicted merge in-core
262+
*/
263+
len = strlen(path);
264+
pos = cache_name_pos(path, len);
265+
if (0 <= pos)
266+
return -1;
267+
pos = -pos - 1;
268+
269+
for (i = 0; i < 3; i++) {
270+
enum object_type type;
271+
unsigned long size;
272+
273+
mmfile[i].size = 0;
274+
mmfile[i].ptr = NULL;
275+
if (active_nr <= pos)
276+
break;
277+
ce = active_cache[pos++];
278+
if (ce_namelen(ce) != len || memcmp(ce->name, path, len)
279+
|| ce_stage(ce) != i + 1)
280+
break;
281+
mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
282+
mmfile[i].size = size;
283+
}
284+
for (i = 0; i < 3; i++) {
285+
if (!mmfile[i].ptr && !mmfile[i].size)
286+
mmfile[i].ptr = xstrdup("");
287+
}
288+
ll_merge(&result, path, &mmfile[0],
289+
&mmfile[1], "ours",
290+
&mmfile[2], "theirs", 0);
291+
for (i = 0; i < 3; i++)
292+
free(mmfile[i].ptr);
293+
294+
memset(&io, 0, sizeof(&io));
295+
io.io.getline = rerere_mem_getline;
296+
if (output)
297+
io.io.output = fopen(output, "w");
298+
else
299+
io.io.output = NULL;
300+
strbuf_init(&io.input, 0);
301+
strbuf_attach(&io.input, result.ptr, result.size, result.size);
302+
303+
hunk_no = handle_path(sha1, (struct rerere_io *)&io);
304+
strbuf_release(&io.input);
305+
if (io.io.output)
306+
fclose(io.io.output);
307+
return hunk_no;
308+
}
309+
226310
static int find_conflict(struct string_list *conflict)
227311
{
228312
int i;
@@ -434,3 +518,52 @@ int rerere(void)
434518
return 0;
435519
return do_plain_rerere(&merge_rr, fd);
436520
}
521+
522+
static int rerere_forget_one_path(const char *path, struct string_list *rr)
523+
{
524+
const char *filename;
525+
char *hex;
526+
unsigned char sha1[20];
527+
int ret;
528+
529+
ret = handle_cache(path, sha1, NULL);
530+
if (ret < 1)
531+
return error("Could not parse conflict hunks in '%s'", path);
532+
hex = xstrdup(sha1_to_hex(sha1));
533+
filename = rerere_path(hex, "postimage");
534+
if (unlink(filename))
535+
return (errno == ENOENT
536+
? error("no remembered resolution for %s", path)
537+
: error("cannot unlink %s: %s", filename, strerror(errno)));
538+
539+
handle_cache(path, sha1, rerere_path(hex, "preimage"));
540+
fprintf(stderr, "Updated preimage for '%s'\n", path);
541+
542+
543+
string_list_insert(path, rr)->util = hex;
544+
fprintf(stderr, "Forgot resolution for %s\n", path);
545+
return 0;
546+
}
547+
548+
int rerere_forget(const char **pathspec)
549+
{
550+
int i, fd;
551+
struct string_list conflict = { NULL, 0, 0, 1 };
552+
struct string_list merge_rr = { NULL, 0, 0, 1 };
553+
554+
if (read_cache() < 0)
555+
return error("Could not read index");
556+
557+
fd = setup_rerere(&merge_rr);
558+
559+
unmerge_cache(pathspec);
560+
find_conflict(&conflict);
561+
for (i = 0; i < conflict.nr; i++) {
562+
struct string_list_item *it = &conflict.items[i];
563+
if (!match_pathspec(pathspec, it->string, strlen(it->string),
564+
0, NULL))
565+
continue;
566+
rerere_forget_one_path(it->string, &merge_rr);
567+
}
568+
return write_rr(&merge_rr, fd);
569+
}

rerere.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ extern int setup_rerere(struct string_list *);
77
extern int rerere(void);
88
extern const char *rerere_path(const char *hex, const char *file);
99
extern int has_rerere_resolution(const char *hex);
10+
extern int rerere_forget(const char **);
1011

1112
#endif

t/t2030-unresolve-info.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,29 @@ test_expect_success 'unmerge with plumbing' '
115115
test $(wc -l <actual) = 3
116116
'
117117

118+
test_expect_success 'rerere and rerere --forget' '
119+
mkdir .git/rr-cache &&
120+
prime_resolve_undo &&
121+
echo record the resolution &&
122+
git rerere &&
123+
rerere_id=$(cd .git/rr-cache && echo */postimage) &&
124+
rerere_id=${rerere_id%/postimage} &&
125+
test -f .git/rr-cache/$rerere_id/postimage &&
126+
git checkout -m file &&
127+
echo resurrect the conflict &&
128+
grep "^=======" file &&
129+
echo reresolve the conflict &&
130+
git rerere &&
131+
test "z$(cat file)" = zdifferent &&
132+
echo register the resolution again &&
133+
git add file &&
134+
check_resolve_undo kept file initial:file second:file third:file &&
135+
test -z "$(git ls-files -u)" &&
136+
git rerere forget file &&
137+
! test -f .git/rr-cache/$rerere_id/postimage &&
138+
tr "\0" "\n" <.git/MERGE_RR >actual &&
139+
echo "$rerere_id file" >expect &&
140+
test_cmp expect actual
141+
'
142+
118143
test_done

0 commit comments

Comments
 (0)