mirror of
https://github.com/git/git.git
synced 2024-05-27 21:36:09 +02:00
Merge branch 'tg/rerere'
Fixes to "git rerere" corner cases, especially when conflict markers cannot be parsed in the file. * tg/rerere: rerere: recalculate conflict ID when unresolved conflict is committed rerere: teach rerere to handle nested conflicts rerere: return strbuf from handle path rerere: factor out handle_conflict function rerere: only return whether a path has conflicts or not rerere: fix crash with files rerere can't handle rerere: add documentation for conflict normalization rerere: mark strings for translation rerere: wrap paths in output in sq rerere: lowercase error messages rerere: unify error messages when read_cache fails
This commit is contained in:
commit
39006893f9
|
@ -0,0 +1,182 @@
|
||||||
|
Rerere
|
||||||
|
======
|
||||||
|
|
||||||
|
This document describes the rerere logic.
|
||||||
|
|
||||||
|
Conflict normalization
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
To ensure recorded conflict resolutions can be looked up in the rerere
|
||||||
|
database, even when branches are merged in a different order,
|
||||||
|
different branches are merged that result in the same conflict, or
|
||||||
|
when different conflict style settings are used, rerere normalizes the
|
||||||
|
conflicts before writing them to the rerere database.
|
||||||
|
|
||||||
|
Different conflict styles and branch names are normalized by stripping
|
||||||
|
the labels from the conflict markers, and removing the common ancestor
|
||||||
|
version from the `diff3` conflict style. Branches that are merged
|
||||||
|
in different order are normalized by sorting the conflict hunks. More
|
||||||
|
on each of those steps in the following sections.
|
||||||
|
|
||||||
|
Once these two normalization operations are applied, a conflict ID is
|
||||||
|
calculated based on the normalized conflict, which is later used by
|
||||||
|
rerere to look up the conflict in the rerere database.
|
||||||
|
|
||||||
|
Removing the common ancestor version
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Say we have three branches AB, AC and AC2. The common ancestor of
|
||||||
|
these branches has a file with a line containing the string "A" (for
|
||||||
|
brevity this is called "line A" in the rest of the document). In
|
||||||
|
branch AB this line is changed to "B", in AC, this line is changed to
|
||||||
|
"C", and branch AC2 is forked off of AC, after the line was changed to
|
||||||
|
"C".
|
||||||
|
|
||||||
|
Forking a branch ABAC off of branch AB and then merging AC into it, we
|
||||||
|
get a conflict like the following:
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
B
|
||||||
|
=======
|
||||||
|
C
|
||||||
|
>>>>>>> AC
|
||||||
|
|
||||||
|
Doing the analogous with AC2 (forking a branch ABAC2 off of branch AB
|
||||||
|
and then merging branch AC2 into it), using the diff3 conflict style,
|
||||||
|
we get a conflict like the following:
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
B
|
||||||
|
||||||| merged common ancestors
|
||||||
|
A
|
||||||
|
=======
|
||||||
|
C
|
||||||
|
>>>>>>> AC2
|
||||||
|
|
||||||
|
By resolving this conflict, to leave line D, the user declares:
|
||||||
|
|
||||||
|
After examining what branches AB and AC did, I believe that making
|
||||||
|
line A into line D is the best thing to do that is compatible with
|
||||||
|
what AB and AC wanted to do.
|
||||||
|
|
||||||
|
As branch AC2 refers to the same commit as AC, the above implies that
|
||||||
|
this is also compatible what AB and AC2 wanted to do.
|
||||||
|
|
||||||
|
By extension, this means that rerere should recognize that the above
|
||||||
|
conflicts are the same. To do this, the labels on the conflict
|
||||||
|
markers are stripped, and the common ancestor version is removed. The above
|
||||||
|
examples would both result in the following normalized conflict:
|
||||||
|
|
||||||
|
<<<<<<<
|
||||||
|
B
|
||||||
|
=======
|
||||||
|
C
|
||||||
|
>>>>>>>
|
||||||
|
|
||||||
|
Sorting hunks
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
As before, lets imagine that a common ancestor had a file with line A
|
||||||
|
its early part, and line X in its late part. And then four branches
|
||||||
|
are forked that do these things:
|
||||||
|
|
||||||
|
- AB: changes A to B
|
||||||
|
- AC: changes A to C
|
||||||
|
- XY: changes X to Y
|
||||||
|
- XZ: changes X to Z
|
||||||
|
|
||||||
|
Now, forking a branch ABAC off of branch AB and then merging AC into
|
||||||
|
it, and forking a branch ACAB off of branch AC and then merging AB
|
||||||
|
into it, would yield the conflict in a different order. The former
|
||||||
|
would say "A became B or C, what now?" while the latter would say "A
|
||||||
|
became C or B, what now?"
|
||||||
|
|
||||||
|
As a reminder, the act of merging AC into ABAC and resolving the
|
||||||
|
conflict to leave line D means that the user declares:
|
||||||
|
|
||||||
|
After examining what branches AB and AC did, I believe that
|
||||||
|
making line A into line D is the best thing to do that is
|
||||||
|
compatible with what AB and AC wanted to do.
|
||||||
|
|
||||||
|
So the conflict we would see when merging AB into ACAB should be
|
||||||
|
resolved the same way---it is the resolution that is in line with that
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
Imagine that similarly previously a branch XYXZ was forked from XY,
|
||||||
|
and XZ was merged into it, and resolved "X became Y or Z" into "X
|
||||||
|
became W".
|
||||||
|
|
||||||
|
Now, if a branch ABXY was forked from AB and then merged XY, then ABXY
|
||||||
|
would have line B in its early part and line Y in its later part.
|
||||||
|
Such a merge would be quite clean. We can construct 4 combinations
|
||||||
|
using these four branches ((AB, AC) x (XY, XZ)).
|
||||||
|
|
||||||
|
Merging ABXY and ACXZ would make "an early A became B or C, a late X
|
||||||
|
became Y or Z" conflict, while merging ACXY and ABXZ would make "an
|
||||||
|
early A became C or B, a late X became Y or Z". We can see there are
|
||||||
|
4 combinations of ("B or C", "C or B") x ("X or Y", "Y or X").
|
||||||
|
|
||||||
|
By sorting, the conflict is given its canonical name, namely, "an
|
||||||
|
early part became B or C, a late part becames X or Y", and whenever
|
||||||
|
any of these four patterns appear, and we can get to the same conflict
|
||||||
|
and resolution that we saw earlier.
|
||||||
|
|
||||||
|
Without the sorting, we'd have to somehow find a previous resolution
|
||||||
|
from combinatorial explosion.
|
||||||
|
|
||||||
|
Conflict ID calculation
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Once the conflict normalization is done, the conflict ID is calculated
|
||||||
|
as the sha1 hash of the conflict hunks appended to each other,
|
||||||
|
separated by <NUL> characters. The conflict markers are stripped out
|
||||||
|
before the sha1 is calculated. So in the example above, where we
|
||||||
|
merge branch AC which changes line A to line C, into branch AB, which
|
||||||
|
changes line A to line C, the conflict ID would be
|
||||||
|
SHA1('B<NUL>C<NUL>').
|
||||||
|
|
||||||
|
If there are multiple conflicts in one file, the sha1 is calculated
|
||||||
|
the same way with all hunks appended to each other, in the order in
|
||||||
|
which they appear in the file, separated by a <NUL> character.
|
||||||
|
|
||||||
|
Nested conflicts
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Nested conflicts are handled very similarly to "simple" conflicts.
|
||||||
|
Similar to simple conflicts, the conflict is first normalized by
|
||||||
|
stripping the labels from conflict markers, stripping the common ancestor
|
||||||
|
version, and the sorting the conflict hunks, both for the outer and the
|
||||||
|
inner conflict. This is done recursively, so any number of nested
|
||||||
|
conflicts can be handled.
|
||||||
|
|
||||||
|
The only difference is in how the conflict ID is calculated. For the
|
||||||
|
inner conflict, the conflict markers themselves are not stripped out
|
||||||
|
before calculating the sha1.
|
||||||
|
|
||||||
|
Say we have the following conflict for example:
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
1
|
||||||
|
=======
|
||||||
|
<<<<<<< HEAD
|
||||||
|
3
|
||||||
|
=======
|
||||||
|
2
|
||||||
|
>>>>>>> branch-2
|
||||||
|
>>>>>>> branch-3~
|
||||||
|
|
||||||
|
After stripping out the labels of the conflict markers, and sorting
|
||||||
|
the hunks, the conflict would look as follows:
|
||||||
|
|
||||||
|
<<<<<<<
|
||||||
|
1
|
||||||
|
=======
|
||||||
|
<<<<<<<
|
||||||
|
2
|
||||||
|
=======
|
||||||
|
3
|
||||||
|
>>>>>>>
|
||||||
|
>>>>>>>
|
||||||
|
|
||||||
|
and finally the conflict ID would be calculated as:
|
||||||
|
`sha1('1<NUL><<<<<<<\n3\n=======\n2\n>>>>>>><NUL>')`
|
|
@ -75,7 +75,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
|
||||||
if (!strcmp(argv[0], "forget")) {
|
if (!strcmp(argv[0], "forget")) {
|
||||||
struct pathspec pathspec;
|
struct pathspec pathspec;
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
warning("'git rerere forget' without paths is deprecated");
|
warning(_("'git rerere forget' without paths is deprecated"));
|
||||||
parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
|
parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
|
||||||
prefix, argv + 1);
|
prefix, argv + 1);
|
||||||
return rerere_forget(&pathspec);
|
return rerere_forget(&pathspec);
|
||||||
|
@ -107,7 +107,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
|
||||||
const char *path = merge_rr.items[i].string;
|
const char *path = merge_rr.items[i].string;
|
||||||
const struct rerere_id *id = merge_rr.items[i].util;
|
const struct rerere_id *id = merge_rr.items[i].util;
|
||||||
if (diff_two(rerere_path(id, "preimage"), path, path, path))
|
if (diff_two(rerere_path(id, "preimage"), path, path, path))
|
||||||
die("unable to generate diff for %s", rerere_path(id, NULL));
|
die(_("unable to generate diff for '%s'"), rerere_path(id, NULL));
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
usage_with_options(rerere_usage, options);
|
usage_with_options(rerere_usage, options);
|
||||||
|
|
259
rerere.c
259
rerere.c
|
@ -213,7 +213,7 @@ static void read_rr(struct string_list *rr)
|
||||||
|
|
||||||
/* There has to be the hash, tab, path and then NUL */
|
/* There has to be the hash, tab, path and then NUL */
|
||||||
if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
|
if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
|
||||||
die("corrupt MERGE_RR");
|
die(_("corrupt MERGE_RR"));
|
||||||
|
|
||||||
if (buf.buf[40] != '.') {
|
if (buf.buf[40] != '.') {
|
||||||
variant = 0;
|
variant = 0;
|
||||||
|
@ -222,10 +222,10 @@ static void read_rr(struct string_list *rr)
|
||||||
errno = 0;
|
errno = 0;
|
||||||
variant = strtol(buf.buf + 41, &path, 10);
|
variant = strtol(buf.buf + 41, &path, 10);
|
||||||
if (errno)
|
if (errno)
|
||||||
die("corrupt MERGE_RR");
|
die(_("corrupt MERGE_RR"));
|
||||||
}
|
}
|
||||||
if (*(path++) != '\t')
|
if (*(path++) != '\t')
|
||||||
die("corrupt MERGE_RR");
|
die(_("corrupt MERGE_RR"));
|
||||||
buf.buf[40] = '\0';
|
buf.buf[40] = '\0';
|
||||||
id = new_rerere_id_hex(buf.buf);
|
id = new_rerere_id_hex(buf.buf);
|
||||||
id->variant = variant;
|
id->variant = variant;
|
||||||
|
@ -260,12 +260,12 @@ static int write_rr(struct string_list *rr, int out_fd)
|
||||||
rr->items[i].string, 0);
|
rr->items[i].string, 0);
|
||||||
|
|
||||||
if (write_in_full(out_fd, buf.buf, buf.len) < 0)
|
if (write_in_full(out_fd, buf.buf, buf.len) < 0)
|
||||||
die("unable to write rerere record");
|
die(_("unable to write rerere record"));
|
||||||
|
|
||||||
strbuf_release(&buf);
|
strbuf_release(&buf);
|
||||||
}
|
}
|
||||||
if (commit_lock_file(&write_lock) != 0)
|
if (commit_lock_file(&write_lock) != 0)
|
||||||
die("unable to write rerere record");
|
die(_("unable to write rerere record"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,38 +303,6 @@ static void rerere_io_putstr(const char *str, struct rerere_io *io)
|
||||||
ferr_puts(str, io->output, &io->wrerror);
|
ferr_puts(str, io->output, &io->wrerror);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Write a conflict marker to io->output (if defined).
|
|
||||||
*/
|
|
||||||
static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
|
|
||||||
{
|
|
||||||
char buf[64];
|
|
||||||
|
|
||||||
while (size) {
|
|
||||||
if (size <= sizeof(buf) - 2) {
|
|
||||||
memset(buf, ch, size);
|
|
||||||
buf[size] = '\n';
|
|
||||||
buf[size + 1] = '\0';
|
|
||||||
size = 0;
|
|
||||||
} else {
|
|
||||||
int sz = sizeof(buf) - 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure we will not write everything out
|
|
||||||
* in this round by leaving at least 1 byte
|
|
||||||
* for the next round, giving the next round
|
|
||||||
* a chance to add the terminating LF. Yuck.
|
|
||||||
*/
|
|
||||||
if (size <= sz)
|
|
||||||
sz -= (sz - size) + 1;
|
|
||||||
memset(buf, ch, sz);
|
|
||||||
buf[sz] = '\0';
|
|
||||||
size -= sz;
|
|
||||||
}
|
|
||||||
rerere_io_putstr(buf, io);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
|
static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
|
||||||
{
|
{
|
||||||
if (io->output)
|
if (io->output)
|
||||||
|
@ -385,6 +353,71 @@ static int is_cmarker(char *buf, int marker_char, int marker_size)
|
||||||
return isspace(*buf);
|
return isspace(*buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rerere_strbuf_putconflict(struct strbuf *buf, int ch, size_t size)
|
||||||
|
{
|
||||||
|
strbuf_addchars(buf, ch, size);
|
||||||
|
strbuf_addch(buf, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_conflict(struct strbuf *out, struct rerere_io *io,
|
||||||
|
int marker_size, git_SHA_CTX *ctx)
|
||||||
|
{
|
||||||
|
enum {
|
||||||
|
RR_SIDE_1 = 0, RR_SIDE_2, RR_ORIGINAL
|
||||||
|
} hunk = RR_SIDE_1;
|
||||||
|
struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
|
||||||
|
struct strbuf buf = STRBUF_INIT, conflict = STRBUF_INIT;
|
||||||
|
int has_conflicts = -1;
|
||||||
|
|
||||||
|
while (!io->getline(&buf, io)) {
|
||||||
|
if (is_cmarker(buf.buf, '<', marker_size)) {
|
||||||
|
if (handle_conflict(&conflict, io, marker_size, NULL) < 0)
|
||||||
|
break;
|
||||||
|
if (hunk == RR_SIDE_1)
|
||||||
|
strbuf_addbuf(&one, &conflict);
|
||||||
|
else
|
||||||
|
strbuf_addbuf(&two, &conflict);
|
||||||
|
strbuf_release(&conflict);
|
||||||
|
} else if (is_cmarker(buf.buf, '|', marker_size)) {
|
||||||
|
if (hunk != RR_SIDE_1)
|
||||||
|
break;
|
||||||
|
hunk = RR_ORIGINAL;
|
||||||
|
} else if (is_cmarker(buf.buf, '=', marker_size)) {
|
||||||
|
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
||||||
|
break;
|
||||||
|
hunk = RR_SIDE_2;
|
||||||
|
} else if (is_cmarker(buf.buf, '>', marker_size)) {
|
||||||
|
if (hunk != RR_SIDE_2)
|
||||||
|
break;
|
||||||
|
if (strbuf_cmp(&one, &two) > 0)
|
||||||
|
strbuf_swap(&one, &two);
|
||||||
|
has_conflicts = 1;
|
||||||
|
rerere_strbuf_putconflict(out, '<', marker_size);
|
||||||
|
strbuf_addbuf(out, &one);
|
||||||
|
rerere_strbuf_putconflict(out, '=', marker_size);
|
||||||
|
strbuf_addbuf(out, &two);
|
||||||
|
rerere_strbuf_putconflict(out, '>', marker_size);
|
||||||
|
if (ctx) {
|
||||||
|
git_SHA1_Update(ctx, one.buf ? one.buf : "",
|
||||||
|
one.len + 1);
|
||||||
|
git_SHA1_Update(ctx, two.buf ? two.buf : "",
|
||||||
|
two.len + 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (hunk == RR_SIDE_1)
|
||||||
|
strbuf_addbuf(&one, &buf);
|
||||||
|
else if (hunk == RR_ORIGINAL)
|
||||||
|
; /* discard */
|
||||||
|
else if (hunk == RR_SIDE_2)
|
||||||
|
strbuf_addbuf(&two, &buf);
|
||||||
|
}
|
||||||
|
strbuf_release(&one);
|
||||||
|
strbuf_release(&two);
|
||||||
|
strbuf_release(&buf);
|
||||||
|
|
||||||
|
return has_conflicts;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read contents a file with conflicts, normalize the conflicts
|
* Read contents a file with conflicts, normalize the conflicts
|
||||||
* by (1) discarding the common ancestor version in diff3-style,
|
* by (1) discarding the common ancestor version in diff3-style,
|
||||||
|
@ -394,80 +427,35 @@ static int is_cmarker(char *buf, int marker_char, int marker_size)
|
||||||
* one side of the conflict, NUL, the other side of the conflict,
|
* one side of the conflict, NUL, the other side of the conflict,
|
||||||
* and NUL concatenated together.
|
* and NUL concatenated together.
|
||||||
*
|
*
|
||||||
* Return the number of conflict hunks found.
|
* Return 1 if conflict hunks are found, 0 if there are no conflict
|
||||||
*
|
* hunks and -1 if an error occured.
|
||||||
* NEEDSWORK: the logic and theory of operation behind this conflict
|
|
||||||
* normalization may deserve to be documented somewhere, perhaps in
|
|
||||||
* Documentation/technical/rerere.txt.
|
|
||||||
*/
|
*/
|
||||||
static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
|
static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
|
||||||
{
|
{
|
||||||
git_SHA_CTX ctx;
|
git_SHA_CTX ctx;
|
||||||
int hunk_no = 0;
|
struct strbuf buf = STRBUF_INIT, out = STRBUF_INIT;
|
||||||
enum {
|
int has_conflicts = 0;
|
||||||
RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL
|
|
||||||
} hunk = RR_CONTEXT;
|
|
||||||
struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
|
|
||||||
struct strbuf buf = STRBUF_INIT;
|
|
||||||
|
|
||||||
if (sha1)
|
if (sha1)
|
||||||
git_SHA1_Init(&ctx);
|
git_SHA1_Init(&ctx);
|
||||||
|
|
||||||
while (!io->getline(&buf, io)) {
|
while (!io->getline(&buf, io)) {
|
||||||
if (is_cmarker(buf.buf, '<', marker_size)) {
|
if (is_cmarker(buf.buf, '<', marker_size)) {
|
||||||
if (hunk != RR_CONTEXT)
|
has_conflicts = handle_conflict(&out, io, marker_size,
|
||||||
goto bad;
|
sha1 ? &ctx : NULL);
|
||||||
hunk = RR_SIDE_1;
|
if (has_conflicts < 0)
|
||||||
} else if (is_cmarker(buf.buf, '|', marker_size)) {
|
break;
|
||||||
if (hunk != RR_SIDE_1)
|
rerere_io_putmem(out.buf, out.len, io);
|
||||||
goto bad;
|
strbuf_reset(&out);
|
||||||
hunk = RR_ORIGINAL;
|
} else
|
||||||
} else if (is_cmarker(buf.buf, '=', marker_size)) {
|
|
||||||
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
|
||||||
goto bad;
|
|
||||||
hunk = RR_SIDE_2;
|
|
||||||
} else if (is_cmarker(buf.buf, '>', marker_size)) {
|
|
||||||
if (hunk != RR_SIDE_2)
|
|
||||||
goto bad;
|
|
||||||
if (strbuf_cmp(&one, &two) > 0)
|
|
||||||
strbuf_swap(&one, &two);
|
|
||||||
hunk_no++;
|
|
||||||
hunk = RR_CONTEXT;
|
|
||||||
rerere_io_putconflict('<', marker_size, io);
|
|
||||||
rerere_io_putmem(one.buf, one.len, io);
|
|
||||||
rerere_io_putconflict('=', marker_size, io);
|
|
||||||
rerere_io_putmem(two.buf, two.len, io);
|
|
||||||
rerere_io_putconflict('>', marker_size, io);
|
|
||||||
if (sha1) {
|
|
||||||
git_SHA1_Update(&ctx, one.buf ? one.buf : "",
|
|
||||||
one.len + 1);
|
|
||||||
git_SHA1_Update(&ctx, two.buf ? two.buf : "",
|
|
||||||
two.len + 1);
|
|
||||||
}
|
|
||||||
strbuf_reset(&one);
|
|
||||||
strbuf_reset(&two);
|
|
||||||
} else if (hunk == RR_SIDE_1)
|
|
||||||
strbuf_addbuf(&one, &buf);
|
|
||||||
else if (hunk == RR_ORIGINAL)
|
|
||||||
; /* discard */
|
|
||||||
else if (hunk == RR_SIDE_2)
|
|
||||||
strbuf_addbuf(&two, &buf);
|
|
||||||
else
|
|
||||||
rerere_io_putstr(buf.buf, io);
|
rerere_io_putstr(buf.buf, io);
|
||||||
continue;
|
|
||||||
bad:
|
|
||||||
hunk = 99; /* force error exit */
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
strbuf_release(&one);
|
|
||||||
strbuf_release(&two);
|
|
||||||
strbuf_release(&buf);
|
strbuf_release(&buf);
|
||||||
|
strbuf_release(&out);
|
||||||
|
|
||||||
if (sha1)
|
if (sha1)
|
||||||
git_SHA1_Final(sha1, &ctx);
|
git_SHA1_Final(sha1, &ctx);
|
||||||
if (hunk != RR_CONTEXT)
|
|
||||||
return -1;
|
return has_conflicts;
|
||||||
return hunk_no;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -476,7 +464,7 @@ static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_siz
|
||||||
*/
|
*/
|
||||||
static int handle_file(const char *path, unsigned char *sha1, const char *output)
|
static int handle_file(const char *path, unsigned char *sha1, const char *output)
|
||||||
{
|
{
|
||||||
int hunk_no = 0;
|
int has_conflicts = 0;
|
||||||
struct rerere_io_file io;
|
struct rerere_io_file io;
|
||||||
int marker_size = ll_merge_marker_size(path);
|
int marker_size = ll_merge_marker_size(path);
|
||||||
|
|
||||||
|
@ -485,34 +473,34 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output
|
||||||
io.input = fopen(path, "r");
|
io.input = fopen(path, "r");
|
||||||
io.io.wrerror = 0;
|
io.io.wrerror = 0;
|
||||||
if (!io.input)
|
if (!io.input)
|
||||||
return error_errno("Could not open %s", path);
|
return error_errno(_("could not open '%s'"), path);
|
||||||
|
|
||||||
if (output) {
|
if (output) {
|
||||||
io.io.output = fopen(output, "w");
|
io.io.output = fopen(output, "w");
|
||||||
if (!io.io.output) {
|
if (!io.io.output) {
|
||||||
error_errno("Could not write %s", output);
|
error_errno(_("could not write '%s'"), output);
|
||||||
fclose(io.input);
|
fclose(io.input);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
|
has_conflicts = handle_path(sha1, (struct rerere_io *)&io, marker_size);
|
||||||
|
|
||||||
fclose(io.input);
|
fclose(io.input);
|
||||||
if (io.io.wrerror)
|
if (io.io.wrerror)
|
||||||
error("There were errors while writing %s (%s)",
|
error(_("there were errors while writing '%s' (%s)"),
|
||||||
path, strerror(io.io.wrerror));
|
path, strerror(io.io.wrerror));
|
||||||
if (io.io.output && fclose(io.io.output))
|
if (io.io.output && fclose(io.io.output))
|
||||||
io.io.wrerror = error_errno("Failed to flush %s", path);
|
io.io.wrerror = error_errno(_("failed to flush '%s'"), path);
|
||||||
|
|
||||||
if (hunk_no < 0) {
|
if (has_conflicts < 0) {
|
||||||
if (output)
|
if (output)
|
||||||
unlink_or_warn(output);
|
unlink_or_warn(output);
|
||||||
return error("Could not parse conflict hunks in %s", path);
|
return error(_("could not parse conflict hunks in '%s'"), path);
|
||||||
}
|
}
|
||||||
if (io.io.wrerror)
|
if (io.io.wrerror)
|
||||||
return -1;
|
return -1;
|
||||||
return hunk_no;
|
return has_conflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -569,7 +557,7 @@ static int find_conflict(struct string_list *conflict)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
if (read_cache() < 0)
|
if (read_cache() < 0)
|
||||||
return error("Could not read index");
|
return error(_("index file corrupt"));
|
||||||
|
|
||||||
for (i = 0; i < active_nr;) {
|
for (i = 0; i < active_nr;) {
|
||||||
int conflict_type;
|
int conflict_type;
|
||||||
|
@ -602,7 +590,7 @@ int rerere_remaining(struct string_list *merge_rr)
|
||||||
if (setup_rerere(merge_rr, RERERE_READONLY))
|
if (setup_rerere(merge_rr, RERERE_READONLY))
|
||||||
return 0;
|
return 0;
|
||||||
if (read_cache() < 0)
|
if (read_cache() < 0)
|
||||||
return error("Could not read index");
|
return error(_("index file corrupt"));
|
||||||
|
|
||||||
for (i = 0; i < active_nr;) {
|
for (i = 0; i < active_nr;) {
|
||||||
int conflict_type;
|
int conflict_type;
|
||||||
|
@ -685,17 +673,17 @@ static int merge(const struct rerere_id *id, const char *path)
|
||||||
* Mark that "postimage" was used to help gc.
|
* Mark that "postimage" was used to help gc.
|
||||||
*/
|
*/
|
||||||
if (utime(rerere_path(id, "postimage"), NULL) < 0)
|
if (utime(rerere_path(id, "postimage"), NULL) < 0)
|
||||||
warning_errno("failed utime() on %s",
|
warning_errno(_("failed utime() on '%s'"),
|
||||||
rerere_path(id, "postimage"));
|
rerere_path(id, "postimage"));
|
||||||
|
|
||||||
/* Update "path" with the resolution */
|
/* Update "path" with the resolution */
|
||||||
f = fopen(path, "w");
|
f = fopen(path, "w");
|
||||||
if (!f)
|
if (!f)
|
||||||
return error_errno("Could not open %s", path);
|
return error_errno(_("could not open '%s'"), path);
|
||||||
if (fwrite(result.ptr, result.size, 1, f) != 1)
|
if (fwrite(result.ptr, result.size, 1, f) != 1)
|
||||||
error_errno("Could not write %s", path);
|
error_errno(_("could not write '%s'"), path);
|
||||||
if (fclose(f))
|
if (fclose(f))
|
||||||
return error_errno("Writing %s failed", path);
|
return error_errno(_("writing '%s' failed"), path);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
free(cur.ptr);
|
free(cur.ptr);
|
||||||
|
@ -715,13 +703,13 @@ static void update_paths(struct string_list *update)
|
||||||
struct string_list_item *item = &update->items[i];
|
struct string_list_item *item = &update->items[i];
|
||||||
if (add_file_to_cache(item->string, 0))
|
if (add_file_to_cache(item->string, 0))
|
||||||
exit(128);
|
exit(128);
|
||||||
fprintf(stderr, "Staged '%s' using previous resolution.\n",
|
fprintf_ln(stderr, _("Staged '%s' using previous resolution."),
|
||||||
item->string);
|
item->string);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (write_locked_index(&the_index, &index_lock,
|
if (write_locked_index(&the_index, &index_lock,
|
||||||
COMMIT_LOCK | SKIP_IF_UNCHANGED))
|
COMMIT_LOCK | SKIP_IF_UNCHANGED))
|
||||||
die("Unable to write new index file");
|
die(_("unable to write new index file"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void remove_variant(struct rerere_id *id)
|
static void remove_variant(struct rerere_id *id)
|
||||||
|
@ -753,7 +741,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
|
||||||
if (!handle_file(path, NULL, NULL)) {
|
if (!handle_file(path, NULL, NULL)) {
|
||||||
copy_file(rerere_path(id, "postimage"), path, 0666);
|
copy_file(rerere_path(id, "postimage"), path, 0666);
|
||||||
id->collection->status[variant] |= RR_HAS_POSTIMAGE;
|
id->collection->status[variant] |= RR_HAS_POSTIMAGE;
|
||||||
fprintf(stderr, "Recorded resolution for '%s'.\n", path);
|
fprintf_ln(stderr, _("Recorded resolution for '%s'."), path);
|
||||||
free_rerere_id(rr_item);
|
free_rerere_id(rr_item);
|
||||||
rr_item->util = NULL;
|
rr_item->util = NULL;
|
||||||
return;
|
return;
|
||||||
|
@ -787,9 +775,9 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
|
||||||
if (rerere_autoupdate)
|
if (rerere_autoupdate)
|
||||||
string_list_insert(update, path);
|
string_list_insert(update, path);
|
||||||
else
|
else
|
||||||
fprintf(stderr,
|
fprintf_ln(stderr,
|
||||||
"Resolved '%s' using previous resolution.\n",
|
_("Resolved '%s' using previous resolution."),
|
||||||
path);
|
path);
|
||||||
free_rerere_id(rr_item);
|
free_rerere_id(rr_item);
|
||||||
rr_item->util = NULL;
|
rr_item->util = NULL;
|
||||||
return;
|
return;
|
||||||
|
@ -803,11 +791,11 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
|
||||||
if (id->collection->status[variant] & RR_HAS_POSTIMAGE) {
|
if (id->collection->status[variant] & RR_HAS_POSTIMAGE) {
|
||||||
const char *path = rerere_path(id, "postimage");
|
const char *path = rerere_path(id, "postimage");
|
||||||
if (unlink(path))
|
if (unlink(path))
|
||||||
die_errno("cannot unlink stray '%s'", path);
|
die_errno(_("cannot unlink stray '%s'"), path);
|
||||||
id->collection->status[variant] &= ~RR_HAS_POSTIMAGE;
|
id->collection->status[variant] &= ~RR_HAS_POSTIMAGE;
|
||||||
}
|
}
|
||||||
id->collection->status[variant] |= RR_HAS_PREIMAGE;
|
id->collection->status[variant] |= RR_HAS_PREIMAGE;
|
||||||
fprintf(stderr, "Recorded preimage for '%s'\n", path);
|
fprintf_ln(stderr, _("Recorded preimage for '%s'"), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_plain_rerere(struct string_list *rr, int fd)
|
static int do_plain_rerere(struct string_list *rr, int fd)
|
||||||
|
@ -830,15 +818,16 @@ static int do_plain_rerere(struct string_list *rr, int fd)
|
||||||
const char *path = conflict.items[i].string;
|
const char *path = conflict.items[i].string;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (string_list_has_string(rr, path))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ask handle_file() to scan and assign a
|
* Ask handle_file() to scan and assign a
|
||||||
* conflict ID. No need to write anything out
|
* conflict ID. No need to write anything out
|
||||||
* yet.
|
* yet.
|
||||||
*/
|
*/
|
||||||
ret = handle_file(path, sha1, NULL);
|
ret = handle_file(path, sha1, NULL);
|
||||||
|
if (ret != 0 && string_list_has_string(rr, path)) {
|
||||||
|
remove_variant(string_list_lookup(rr, path)->util);
|
||||||
|
string_list_remove(rr, path, 1);
|
||||||
|
}
|
||||||
if (ret < 1)
|
if (ret < 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -879,7 +868,7 @@ static int is_rerere_enabled(void)
|
||||||
return rr_cache_exists;
|
return rr_cache_exists;
|
||||||
|
|
||||||
if (!rr_cache_exists && mkdir_in_gitdir(git_path_rr_cache()))
|
if (!rr_cache_exists && mkdir_in_gitdir(git_path_rr_cache()))
|
||||||
die("Could not create directory %s", git_path_rr_cache());
|
die(_("could not create directory '%s'"), git_path_rr_cache());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,7 +947,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
|
||||||
mmfile_t mmfile[3] = {{NULL}};
|
mmfile_t mmfile[3] = {{NULL}};
|
||||||
mmbuffer_t result = {NULL, 0};
|
mmbuffer_t result = {NULL, 0};
|
||||||
const struct cache_entry *ce;
|
const struct cache_entry *ce;
|
||||||
int pos, len, i, hunk_no;
|
int pos, len, i, has_conflicts;
|
||||||
struct rerere_io_mem io;
|
struct rerere_io_mem io;
|
||||||
int marker_size = ll_merge_marker_size(path);
|
int marker_size = ll_merge_marker_size(path);
|
||||||
|
|
||||||
|
@ -1012,11 +1001,11 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
|
||||||
* Grab the conflict ID and optionally write the original
|
* Grab the conflict ID and optionally write the original
|
||||||
* contents with conflict markers out.
|
* contents with conflict markers out.
|
||||||
*/
|
*/
|
||||||
hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
|
has_conflicts = handle_path(sha1, (struct rerere_io *)&io, marker_size);
|
||||||
strbuf_release(&io.input);
|
strbuf_release(&io.input);
|
||||||
if (io.io.output)
|
if (io.io.output)
|
||||||
fclose(io.io.output);
|
fclose(io.io.output);
|
||||||
return hunk_no;
|
return has_conflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||||
|
@ -1033,7 +1022,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||||
*/
|
*/
|
||||||
ret = handle_cache(path, sha1, NULL);
|
ret = handle_cache(path, sha1, NULL);
|
||||||
if (ret < 1)
|
if (ret < 1)
|
||||||
return error("Could not parse conflict hunks in '%s'", path);
|
return error(_("could not parse conflict hunks in '%s'"), path);
|
||||||
|
|
||||||
/* Nuke the recorded resolution for the conflict */
|
/* Nuke the recorded resolution for the conflict */
|
||||||
id = new_rerere_id(sha1);
|
id = new_rerere_id(sha1);
|
||||||
|
@ -1051,7 +1040,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||||
handle_cache(path, sha1, rerere_path(id, "thisimage"));
|
handle_cache(path, sha1, rerere_path(id, "thisimage"));
|
||||||
if (read_mmfile(&cur, rerere_path(id, "thisimage"))) {
|
if (read_mmfile(&cur, rerere_path(id, "thisimage"))) {
|
||||||
free(cur.ptr);
|
free(cur.ptr);
|
||||||
error("Failed to update conflicted state in '%s'", path);
|
error(_("failed to update conflicted state in '%s'"), path);
|
||||||
goto fail_exit;
|
goto fail_exit;
|
||||||
}
|
}
|
||||||
cleanly_resolved = !try_merge(id, path, &cur, &result);
|
cleanly_resolved = !try_merge(id, path, &cur, &result);
|
||||||
|
@ -1062,16 +1051,16 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id->collection->status_nr <= id->variant) {
|
if (id->collection->status_nr <= id->variant) {
|
||||||
error("no remembered resolution for '%s'", path);
|
error(_("no remembered resolution for '%s'"), path);
|
||||||
goto fail_exit;
|
goto fail_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
filename = rerere_path(id, "postimage");
|
filename = rerere_path(id, "postimage");
|
||||||
if (unlink(filename)) {
|
if (unlink(filename)) {
|
||||||
if (errno == ENOENT)
|
if (errno == ENOENT)
|
||||||
error("no remembered resolution for %s", path);
|
error(_("no remembered resolution for '%s'"), path);
|
||||||
else
|
else
|
||||||
error_errno("cannot unlink %s", filename);
|
error_errno(_("cannot unlink '%s'"), filename);
|
||||||
goto fail_exit;
|
goto fail_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1081,7 +1070,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||||
* the postimage.
|
* the postimage.
|
||||||
*/
|
*/
|
||||||
handle_cache(path, sha1, rerere_path(id, "preimage"));
|
handle_cache(path, sha1, rerere_path(id, "preimage"));
|
||||||
fprintf(stderr, "Updated preimage for '%s'\n", path);
|
fprintf_ln(stderr, _("Updated preimage for '%s'"), path);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* And remember that we can record resolution for this
|
* And remember that we can record resolution for this
|
||||||
|
@ -1090,7 +1079,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||||
item = string_list_insert(rr, path);
|
item = string_list_insert(rr, path);
|
||||||
free_rerere_id(item);
|
free_rerere_id(item);
|
||||||
item->util = id;
|
item->util = id;
|
||||||
fprintf(stderr, "Forgot resolution for %s\n", path);
|
fprintf(stderr, _("Forgot resolution for '%s'\n"), path);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail_exit:
|
fail_exit:
|
||||||
|
@ -1105,7 +1094,7 @@ int rerere_forget(struct pathspec *pathspec)
|
||||||
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
||||||
|
|
||||||
if (read_cache() < 0)
|
if (read_cache() < 0)
|
||||||
return error("Could not read index");
|
return error(_("index file corrupt"));
|
||||||
|
|
||||||
fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
|
fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
|
@ -1193,7 +1182,7 @@ void rerere_gc(struct string_list *rr)
|
||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
dir = opendir(git_path("rr-cache"));
|
dir = opendir(git_path("rr-cache"));
|
||||||
if (!dir)
|
if (!dir)
|
||||||
die_errno("unable to open rr-cache directory");
|
die_errno(_("unable to open rr-cache directory"));
|
||||||
/* Collect stale conflict IDs ... */
|
/* Collect stale conflict IDs ... */
|
||||||
while ((e = readdir(dir))) {
|
while ((e = readdir(dir))) {
|
||||||
struct rerere_dir *rr_dir;
|
struct rerere_dir *rr_dir;
|
||||||
|
|
|
@ -577,4 +577,69 @@ test_expect_success 'multiple identical conflicts' '
|
||||||
count_pre_post 0 0
|
count_pre_post 0 0
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'rerere with unexpected conflict markers does not crash' '
|
||||||
|
git reset --hard &&
|
||||||
|
|
||||||
|
git checkout -b branch-1 master &&
|
||||||
|
echo "bar" >test &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -m two &&
|
||||||
|
|
||||||
|
git reset --hard &&
|
||||||
|
git checkout -b branch-2 master &&
|
||||||
|
echo "foo" >test &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -a -m one &&
|
||||||
|
|
||||||
|
test_must_fail git merge branch-1 &&
|
||||||
|
echo "<<<<<<< a" >test &&
|
||||||
|
git rerere &&
|
||||||
|
|
||||||
|
git rerere clear
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'rerere with inner conflict markers' '
|
||||||
|
git reset --hard &&
|
||||||
|
|
||||||
|
git checkout -b A master &&
|
||||||
|
echo "bar" >test &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -m two &&
|
||||||
|
echo "baz" >test &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -m three &&
|
||||||
|
|
||||||
|
git reset --hard &&
|
||||||
|
git checkout -b B master &&
|
||||||
|
echo "foo" >test &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -a -m one &&
|
||||||
|
|
||||||
|
test_must_fail git merge A~ &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -m "will solve conflicts later" &&
|
||||||
|
test_must_fail git merge A &&
|
||||||
|
|
||||||
|
echo "resolved" >test &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -m "solved conflict" &&
|
||||||
|
|
||||||
|
echo "resolved" >expect &&
|
||||||
|
|
||||||
|
git reset --hard HEAD~~ &&
|
||||||
|
test_must_fail git merge A~ &&
|
||||||
|
git add test &&
|
||||||
|
git commit -q -m "will solve conflicts later" &&
|
||||||
|
test_must_fail git merge A &&
|
||||||
|
cat test >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
git add test &&
|
||||||
|
git commit -m "rerere solved conflict" &&
|
||||||
|
git reset --hard HEAD~ &&
|
||||||
|
test_must_fail git merge A &&
|
||||||
|
cat test >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in New Issue