зеркало из https://github.com/microsoft/git.git
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:
Коммит
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")) {
|
||||
struct pathspec pathspec;
|
||||
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,
|
||||
prefix, argv + 1);
|
||||
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 struct rerere_id *id = merge_rr.items[i].util;
|
||||
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
|
||||
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 */
|
||||
if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
|
||||
die("corrupt MERGE_RR");
|
||||
die(_("corrupt MERGE_RR"));
|
||||
|
||||
if (buf.buf[40] != '.') {
|
||||
variant = 0;
|
||||
|
@ -222,10 +222,10 @@ static void read_rr(struct string_list *rr)
|
|||
errno = 0;
|
||||
variant = strtol(buf.buf + 41, &path, 10);
|
||||
if (errno)
|
||||
die("corrupt MERGE_RR");
|
||||
die(_("corrupt MERGE_RR"));
|
||||
}
|
||||
if (*(path++) != '\t')
|
||||
die("corrupt MERGE_RR");
|
||||
die(_("corrupt MERGE_RR"));
|
||||
buf.buf[40] = '\0';
|
||||
id = new_rerere_id_hex(buf.buf);
|
||||
id->variant = variant;
|
||||
|
@ -260,12 +260,12 @@ static int write_rr(struct string_list *rr, int out_fd)
|
|||
rr->items[i].string, 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);
|
||||
}
|
||||
if (commit_lock_file(&write_lock) != 0)
|
||||
die("unable to write rerere record");
|
||||
die(_("unable to write rerere record"));
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
if (io->output)
|
||||
|
@ -385,6 +353,71 @@ static int is_cmarker(char *buf, int marker_char, int marker_size)
|
|||
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
|
||||
* 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,
|
||||
* and NUL concatenated together.
|
||||
*
|
||||
* Return the number of conflict hunks found.
|
||||
*
|
||||
* NEEDSWORK: the logic and theory of operation behind this conflict
|
||||
* normalization may deserve to be documented somewhere, perhaps in
|
||||
* Documentation/technical/rerere.txt.
|
||||
* Return 1 if conflict hunks are found, 0 if there are no conflict
|
||||
* hunks and -1 if an error occured.
|
||||
*/
|
||||
static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
|
||||
{
|
||||
git_SHA_CTX ctx;
|
||||
int hunk_no = 0;
|
||||
enum {
|
||||
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;
|
||||
|
||||
struct strbuf buf = STRBUF_INIT, out = STRBUF_INIT;
|
||||
int has_conflicts = 0;
|
||||
if (sha1)
|
||||
git_SHA1_Init(&ctx);
|
||||
|
||||
while (!io->getline(&buf, io)) {
|
||||
if (is_cmarker(buf.buf, '<', marker_size)) {
|
||||
if (hunk != RR_CONTEXT)
|
||||
goto bad;
|
||||
hunk = RR_SIDE_1;
|
||||
} else if (is_cmarker(buf.buf, '|', marker_size)) {
|
||||
if (hunk != RR_SIDE_1)
|
||||
goto bad;
|
||||
hunk = RR_ORIGINAL;
|
||||
} 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
|
||||
has_conflicts = handle_conflict(&out, io, marker_size,
|
||||
sha1 ? &ctx : NULL);
|
||||
if (has_conflicts < 0)
|
||||
break;
|
||||
rerere_io_putmem(out.buf, out.len, io);
|
||||
strbuf_reset(&out);
|
||||
} else
|
||||
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(&out);
|
||||
|
||||
if (sha1)
|
||||
git_SHA1_Final(sha1, &ctx);
|
||||
if (hunk != RR_CONTEXT)
|
||||
return -1;
|
||||
return hunk_no;
|
||||
|
||||
return has_conflicts;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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)
|
||||
{
|
||||
int hunk_no = 0;
|
||||
int has_conflicts = 0;
|
||||
struct rerere_io_file io;
|
||||
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.io.wrerror = 0;
|
||||
if (!io.input)
|
||||
return error_errno("Could not open %s", path);
|
||||
return error_errno(_("could not open '%s'"), path);
|
||||
|
||||
if (output) {
|
||||
io.io.output = fopen(output, "w");
|
||||
if (!io.io.output) {
|
||||
error_errno("Could not write %s", output);
|
||||
error_errno(_("could not write '%s'"), output);
|
||||
fclose(io.input);
|
||||
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);
|
||||
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));
|
||||
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)
|
||||
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)
|
||||
return -1;
|
||||
return hunk_no;
|
||||
return has_conflicts;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -569,7 +557,7 @@ static int find_conflict(struct string_list *conflict)
|
|||
{
|
||||
int i;
|
||||
if (read_cache() < 0)
|
||||
return error("Could not read index");
|
||||
return error(_("index file corrupt"));
|
||||
|
||||
for (i = 0; i < active_nr;) {
|
||||
int conflict_type;
|
||||
|
@ -602,7 +590,7 @@ int rerere_remaining(struct string_list *merge_rr)
|
|||
if (setup_rerere(merge_rr, RERERE_READONLY))
|
||||
return 0;
|
||||
if (read_cache() < 0)
|
||||
return error("Could not read index");
|
||||
return error(_("index file corrupt"));
|
||||
|
||||
for (i = 0; i < active_nr;) {
|
||||
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.
|
||||
*/
|
||||
if (utime(rerere_path(id, "postimage"), NULL) < 0)
|
||||
warning_errno("failed utime() on %s",
|
||||
warning_errno(_("failed utime() on '%s'"),
|
||||
rerere_path(id, "postimage"));
|
||||
|
||||
/* Update "path" with the resolution */
|
||||
f = fopen(path, "w");
|
||||
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)
|
||||
error_errno("Could not write %s", path);
|
||||
error_errno(_("could not write '%s'"), path);
|
||||
if (fclose(f))
|
||||
return error_errno("Writing %s failed", path);
|
||||
return error_errno(_("writing '%s' failed"), path);
|
||||
|
||||
out:
|
||||
free(cur.ptr);
|
||||
|
@ -715,13 +703,13 @@ static void update_paths(struct string_list *update)
|
|||
struct string_list_item *item = &update->items[i];
|
||||
if (add_file_to_cache(item->string, 0))
|
||||
exit(128);
|
||||
fprintf(stderr, "Staged '%s' using previous resolution.\n",
|
||||
fprintf_ln(stderr, _("Staged '%s' using previous resolution."),
|
||||
item->string);
|
||||
}
|
||||
|
||||
if (write_locked_index(&the_index, &index_lock,
|
||||
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)
|
||||
|
@ -753,7 +741,7 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
|
|||
if (!handle_file(path, NULL, NULL)) {
|
||||
copy_file(rerere_path(id, "postimage"), path, 0666);
|
||||
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);
|
||||
rr_item->util = NULL;
|
||||
return;
|
||||
|
@ -787,9 +775,9 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
|
|||
if (rerere_autoupdate)
|
||||
string_list_insert(update, path);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"Resolved '%s' using previous resolution.\n",
|
||||
path);
|
||||
fprintf_ln(stderr,
|
||||
_("Resolved '%s' using previous resolution."),
|
||||
path);
|
||||
free_rerere_id(rr_item);
|
||||
rr_item->util = NULL;
|
||||
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) {
|
||||
const char *path = rerere_path(id, "postimage");
|
||||
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_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)
|
||||
|
@ -830,15 +818,16 @@ static int do_plain_rerere(struct string_list *rr, int fd)
|
|||
const char *path = conflict.items[i].string;
|
||||
int ret;
|
||||
|
||||
if (string_list_has_string(rr, path))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Ask handle_file() to scan and assign a
|
||||
* conflict ID. No need to write anything out
|
||||
* yet.
|
||||
*/
|
||||
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)
|
||||
continue;
|
||||
|
||||
|
@ -879,7 +868,7 @@ static int is_rerere_enabled(void)
|
|||
return rr_cache_exists;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -958,7 +947,7 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu
|
|||
mmfile_t mmfile[3] = {{NULL}};
|
||||
mmbuffer_t result = {NULL, 0};
|
||||
const struct cache_entry *ce;
|
||||
int pos, len, i, hunk_no;
|
||||
int pos, len, i, has_conflicts;
|
||||
struct rerere_io_mem io;
|
||||
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
|
||||
* 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);
|
||||
if (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)
|
||||
|
@ -1033,7 +1022,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
|||
*/
|
||||
ret = handle_cache(path, sha1, NULL);
|
||||
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 */
|
||||
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"));
|
||||
if (read_mmfile(&cur, rerere_path(id, "thisimage"))) {
|
||||
free(cur.ptr);
|
||||
error("Failed to update conflicted state in '%s'", path);
|
||||
error(_("failed to update conflicted state in '%s'"), path);
|
||||
goto fail_exit;
|
||||
}
|
||||
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) {
|
||||
error("no remembered resolution for '%s'", path);
|
||||
error(_("no remembered resolution for '%s'"), path);
|
||||
goto fail_exit;
|
||||
}
|
||||
|
||||
filename = rerere_path(id, "postimage");
|
||||
if (unlink(filename)) {
|
||||
if (errno == ENOENT)
|
||||
error("no remembered resolution for %s", path);
|
||||
error(_("no remembered resolution for '%s'"), path);
|
||||
else
|
||||
error_errno("cannot unlink %s", filename);
|
||||
error_errno(_("cannot unlink '%s'"), filename);
|
||||
goto fail_exit;
|
||||
}
|
||||
|
||||
|
@ -1081,7 +1070,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
|||
* the postimage.
|
||||
*/
|
||||
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
|
||||
|
@ -1090,7 +1079,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
|||
item = string_list_insert(rr, path);
|
||||
free_rerere_id(item);
|
||||
item->util = id;
|
||||
fprintf(stderr, "Forgot resolution for %s\n", path);
|
||||
fprintf(stderr, _("Forgot resolution for '%s'\n"), path);
|
||||
return 0;
|
||||
|
||||
fail_exit:
|
||||
|
@ -1105,7 +1094,7 @@ int rerere_forget(struct pathspec *pathspec)
|
|||
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
||||
|
||||
if (read_cache() < 0)
|
||||
return error("Could not read index");
|
||||
return error(_("index file corrupt"));
|
||||
|
||||
fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
|
||||
if (fd < 0)
|
||||
|
@ -1193,7 +1182,7 @@ void rerere_gc(struct string_list *rr)
|
|||
git_config(git_default_config, NULL);
|
||||
dir = opendir(git_path("rr-cache"));
|
||||
if (!dir)
|
||||
die_errno("unable to open rr-cache directory");
|
||||
die_errno(_("unable to open rr-cache directory"));
|
||||
/* Collect stale conflict IDs ... */
|
||||
while ((e = readdir(dir))) {
|
||||
struct rerere_dir *rr_dir;
|
||||
|
|
|
@ -577,4 +577,69 @@ test_expect_success 'multiple identical conflicts' '
|
|||
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче