Merge branch 'jc/rerere'

* jc/rerere:
  rerere.autoupdate
  t4200: fix rerere test
  rerere: remove dubious "tail_optimization"
  git-rerere: detect unparsable conflicts
  rerere: rerere_created_at() and has_resolution() abstraction
maint
Junio C Hamano 2008-07-07 02:17:28 -07:00
commit 86d7244321
3 changed files with 95 additions and 44 deletions

View File

@ -658,6 +658,11 @@ gc.rerereunresolved::
kept for this many days when 'git-rerere gc' is run. kept for this many days when 'git-rerere gc' is run.
The default is 15 days. See linkgit:git-rerere[1]. The default is 15 days. See linkgit:git-rerere[1].


rerere.autoupdate::
When set to true, `git-rerere` updates the index with the
resulting contents after it cleanly resolves conflicts using
previously recorded resolution. Defaults to false.

rerere.enabled:: rerere.enabled::
Activate recording of resolved conflicts, so that identical Activate recording of resolved conflicts, so that identical
conflict hunks can be resolved automatically, should they conflict hunks can be resolved automatically, should they

View File

@ -16,6 +16,9 @@ static int cutoff_resolve = 60;
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
static int rerere_enabled = -1; static int rerere_enabled = -1;


/* automatically update cleanly resolved paths to the index */
static int rerere_autoupdate;

static char *merge_rr_path; static char *merge_rr_path;


static const char *rr_path(const char *name, const char *file) static const char *rr_path(const char *name, const char *file)
@ -23,6 +26,18 @@ static const char *rr_path(const char *name, const char *file)
return git_path("rr-cache/%s/%s", name, file); return git_path("rr-cache/%s/%s", name, file);
} }


static time_t rerere_created_at(const char *name)
{
struct stat st;
return stat(rr_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
}

static int has_resolution(const char *name)
{
struct stat st;
return !stat(rr_path(name, "postimage"), &st);
}

static void read_rr(struct path_list *rr) static void read_rr(struct path_list *rr)
{ {
unsigned char sha1[20]; unsigned char sha1[20];
@ -54,8 +69,12 @@ static int write_rr(struct path_list *rr, int out_fd)
{ {
int i; int i;
for (i = 0; i < rr->nr; i++) { for (i = 0; i < rr->nr; i++) {
const char *path = rr->items[i].path; const char *path;
int length = strlen(path) + 1; int length;
if (!rr->items[i].util)
continue;
path = rr->items[i].path;
length = strlen(path) + 1;
if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
write_in_full(out_fd, "\t", 1) != 1 || write_in_full(out_fd, "\t", 1) != 1 ||
write_in_full(out_fd, path, length) != length) write_in_full(out_fd, path, length) != length)
@ -98,13 +117,10 @@ static int handle_file(const char *path,
else if (!prefixcmp(buf, "=======")) else if (!prefixcmp(buf, "======="))
hunk = 2; hunk = 2;
else if (!prefixcmp(buf, ">>>>>>> ")) { else if (!prefixcmp(buf, ">>>>>>> ")) {
int cmp = strbuf_cmp(&one, &two); if (strbuf_cmp(&one, &two) > 0)

strbuf_swap(&one, &two);
hunk_no++; hunk_no++;
hunk = 0; hunk = 0;
if (cmp > 0) {
strbuf_swap(&one, &two);
}
if (out) { if (out) {
fputs("<<<<<<<\n", out); fputs("<<<<<<<\n", out);
fwrite(one.buf, one.len, 1, out); fwrite(one.buf, one.len, 1, out);
@ -135,6 +151,11 @@ static int handle_file(const char *path,
fclose(out); fclose(out);
if (sha1) if (sha1)
SHA1_Final(sha1, &ctx); SHA1_Final(sha1, &ctx);
if (hunk) {
if (output)
unlink(output);
return error("Could not parse conflict hunks in %s", path);
}
return hunk_no; return hunk_no;
} }


@ -201,33 +222,24 @@ static void unlink_rr_item(const char *name)
static void garbage_collect(struct path_list *rr) static void garbage_collect(struct path_list *rr)
{ {
struct path_list to_remove = { NULL, 0, 0, 1 }; struct path_list to_remove = { NULL, 0, 0, 1 };
char buf[1024];
DIR *dir; DIR *dir;
struct dirent *e; struct dirent *e;
int len, i, cutoff; int i, cutoff;
time_t now = time(NULL), then; time_t now = time(NULL), then;


strlcpy(buf, git_path("rr-cache"), sizeof(buf)); dir = opendir(git_path("rr-cache"));
len = strlen(buf);
dir = opendir(buf);
strcpy(buf + len++, "/");
while ((e = readdir(dir))) { while ((e = readdir(dir))) {
const char *name = e->d_name; const char *name = e->d_name;
struct stat st; if (name[0] == '.' &&
if (name[0] == '.' && (name[1] == '\0' || (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
(name[1] == '.' && name[2] == '\0')))
continue; continue;
i = snprintf(buf + len, sizeof(buf) - len, "%s", name); then = rerere_created_at(name);
strlcpy(buf + len + i, "/preimage", sizeof(buf) - len - i); if (!then)
if (stat(buf, &st))
continue; continue;
then = st.st_mtime; cutoff = (has_resolution(name)
strlcpy(buf + len + i, "/postimage", sizeof(buf) - len - i); ? cutoff_resolve : cutoff_noresolve);
cutoff = stat(buf, &st) ? cutoff_noresolve : cutoff_resolve; if (then < now - cutoff * 86400)
if (then < now - cutoff * 86400) { path_list_append(name, &to_remove);
buf[len + i] = '\0';
path_list_insert(xstrdup(name), &to_remove);
}
} }
for (i = 0; i < to_remove.nr; i++) for (i = 0; i < to_remove.nr; i++)
unlink_rr_item(to_remove.items[i].path); unlink_rr_item(to_remove.items[i].path);
@ -267,9 +279,36 @@ static int diff_two(const char *file1, const char *label1,
return 0; return 0;
} }


static struct lock_file index_lock;

static int update_paths(struct path_list *update)
{
int i;
int fd = hold_locked_index(&index_lock, 0);
int status = 0;

if (fd < 0)
return -1;

for (i = 0; i < update->nr; i++) {
struct path_list_item *item = &update->items[i];
if (add_file_to_cache(item->path, ADD_CACHE_IGNORE_ERRORS))
status = -1;
}

if (!status && active_cache_changed) {
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(&index_lock))
die("Unable to write new index file");
} else if (fd >= 0)
rollback_lock_file(&index_lock);
return status;
}

static int do_plain_rerere(struct path_list *rr, int fd) static int do_plain_rerere(struct path_list *rr, int fd)
{ {
struct path_list conflict = { NULL, 0, 0, 1 }; struct path_list conflict = { NULL, 0, 0, 1 };
struct path_list update = { NULL, 0, 0, 1 };
int i; int i;


find_conflict(&conflict); find_conflict(&conflict);
@ -306,17 +345,17 @@ static int do_plain_rerere(struct path_list *rr, int fd)
*/ */


for (i = 0; i < rr->nr; i++) { for (i = 0; i < rr->nr; i++) {
struct stat st;
int ret; int ret;
const char *path = rr->items[i].path; const char *path = rr->items[i].path;
const char *name = (const char *)rr->items[i].util; const char *name = (const char *)rr->items[i].util;


if (!stat(rr_path(name, "preimage"), &st) && if (has_resolution(name)) {
!stat(rr_path(name, "postimage"), &st)) {
if (!merge(name, path)) { if (!merge(name, path)) {
fprintf(stderr, "Resolved '%s' using " fprintf(stderr, "Resolved '%s' using "
"previous resolution.\n", path); "previous resolution.\n", path);
goto tail_optimization; if (rerere_autoupdate)
path_list_insert(path, &update);
goto mark_resolved;
} }
} }


@ -327,15 +366,13 @@ static int do_plain_rerere(struct path_list *rr, int fd)


fprintf(stderr, "Recorded resolution for '%s'.\n", path); fprintf(stderr, "Recorded resolution for '%s'.\n", path);
copy_file(rr_path(name, "postimage"), path, 0666); copy_file(rr_path(name, "postimage"), path, 0666);
tail_optimization: mark_resolved:
if (i < rr->nr - 1) rr->items[i].util = NULL;
memmove(rr->items + i,
rr->items + i + 1,
sizeof(rr->items[0]) * (rr->nr - i - 1));
rr->nr--;
i--;
} }


if (update.nr)
update_paths(&update);

return write_rr(rr, fd); return write_rr(rr, fd);
} }


@ -347,6 +384,8 @@ static int git_rerere_config(const char *var, const char *value, void *cb)
cutoff_noresolve = git_config_int(var, value); cutoff_noresolve = git_config_int(var, value);
else if (!strcmp(var, "rerere.enabled")) else if (!strcmp(var, "rerere.enabled"))
rerere_enabled = git_config_bool(var, value); rerere_enabled = git_config_bool(var, value);
else if (!strcmp(var, "rerere.autoupdate"))
rerere_autoupdate = git_config_bool(var, value);
else else
return git_default_config(var, value, cb); return git_default_config(var, value, cb);
return 0; return 0;
@ -410,11 +449,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
return do_plain_rerere(&merge_rr, fd); return do_plain_rerere(&merge_rr, fd);
else if (!strcmp(argv[1], "clear")) { else if (!strcmp(argv[1], "clear")) {
for (i = 0; i < merge_rr.nr; i++) { for (i = 0; i < merge_rr.nr; i++) {
struct stat st;
const char *name = (const char *)merge_rr.items[i].util; const char *name = (const char *)merge_rr.items[i].util;
if (!stat(git_path("rr-cache/%s", name), &st) && if (!has_resolution(name))
S_ISDIR(st.st_mode) &&
stat(rr_path(name, "postimage"), &st))
unlink_rr_item(name); unlink_rr_item(name);
} }
unlink(merge_rr_path); unlink(merge_rr_path);

View File

@ -193,9 +193,19 @@ test_expect_success 'resolution was recorded properly' '
echo Bello > file3 && echo Bello > file3 &&
git add file3 && git add file3 &&
git commit -m version2 && git commit -m version2 &&
! git merge fifth && git tag version2 &&
git diff-files -q && test_must_fail git merge fifth &&
test Cello = "$(cat file3)" test Cello = "$(cat file3)" &&
test 0 != $(git ls-files -u | wc -l)
'

test_expect_success 'rerere.autoupdate' '
git config rerere.autoupdate true
git reset --hard &&
git checkout version2 &&
test_must_fail git merge fifth &&
test 0 = $(git ls-files -u | wc -l)

' '


test_done test_done