зеркало из https://github.com/microsoft/git.git
Merge branch 'jc/cache-unmerge'
* jc/cache-unmerge: rerere forget path: forget recorded resolution rerere: refactor rerere logic to make it independent from I/O rerere: remove silly 1024-byte line limit resolve-undo: teach "update-index --unresolve" to use resolve-undo info resolve-undo: "checkout -m path" uses resolve-undo information resolve-undo: allow plumbing to clear the information resolve-undo: basic tests resolve-undo: record resolved conflicts in a new index extension section builtin-merge.c: use standard active_cache macros Conflicts: builtin-ls-files.c builtin-merge.c builtin-rerere.c
This commit is contained in:
Коммит
6751e0471d
2
Makefile
2
Makefile
|
@ -497,6 +497,7 @@ LIB_H += reflog-walk.h
|
|||
LIB_H += refs.h
|
||||
LIB_H += remote.h
|
||||
LIB_H += rerere.h
|
||||
LIB_H += resolve-undo.h
|
||||
LIB_H += revision.h
|
||||
LIB_H += run-command.h
|
||||
LIB_H += sha1-lookup.h
|
||||
|
@ -592,6 +593,7 @@ LIB_OBJS += refs.o
|
|||
LIB_OBJS += remote.o
|
||||
LIB_OBJS += replace_object.o
|
||||
LIB_OBJS += rerere.o
|
||||
LIB_OBJS += resolve-undo.o
|
||||
LIB_OBJS += revision.o
|
||||
LIB_OBJS += run-command.o
|
||||
LIB_OBJS += server-info.o
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "blob.h"
|
||||
#include "xdiff-interface.h"
|
||||
#include "ll-merge.h"
|
||||
#include "resolve-undo.h"
|
||||
|
||||
static const char * const checkout_usage[] = {
|
||||
"git checkout [options] <branch>",
|
||||
|
@ -234,6 +235,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
|
|||
if (report_path_error(ps_matched, pathspec, 0))
|
||||
return 1;
|
||||
|
||||
/* "checkout -m path" to recreate conflicted state */
|
||||
if (opts->merge)
|
||||
unmerge_cache(pathspec);
|
||||
|
||||
/* Any unmerged paths? */
|
||||
for (pos = 0; pos < active_nr; pos++) {
|
||||
struct cache_entry *ce = active_cache[pos];
|
||||
|
@ -370,6 +375,7 @@ static int merge_working_tree(struct checkout_opts *opts,
|
|||
if (read_cache_preload(NULL) < 0)
|
||||
return error("corrupt index file");
|
||||
|
||||
resolve_undo_clear();
|
||||
if (opts->force) {
|
||||
ret = reset_tree(new->commit->tree, opts, 1);
|
||||
if (ret)
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "builtin.h"
|
||||
#include "tree.h"
|
||||
#include "parse-options.h"
|
||||
#include "resolve-undo.h"
|
||||
#include "string-list.h"
|
||||
|
||||
static int abbrev;
|
||||
static int show_deleted;
|
||||
|
@ -18,6 +20,7 @@ static int show_cached;
|
|||
static int show_others;
|
||||
static int show_stage;
|
||||
static int show_unmerged;
|
||||
static int show_resolve_undo;
|
||||
static int show_modified;
|
||||
static int show_killed;
|
||||
static int show_valid_bit;
|
||||
|
@ -38,6 +41,7 @@ static const char *tag_other = "";
|
|||
static const char *tag_killed = "";
|
||||
static const char *tag_modified = "";
|
||||
static const char *tag_skip_worktree = "";
|
||||
static const char *tag_resolve_undo = "";
|
||||
|
||||
static void show_dir_entry(const char *tag, struct dir_entry *ent)
|
||||
{
|
||||
|
@ -156,6 +160,38 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
|
|||
write_name_quoted(ce->name + offset, stdout, line_terminator);
|
||||
}
|
||||
|
||||
static int show_one_ru(struct string_list_item *item, void *cbdata)
|
||||
{
|
||||
int offset = prefix_offset;
|
||||
const char *path = item->string;
|
||||
struct resolve_undo_info *ui = item->util;
|
||||
int i, len;
|
||||
|
||||
len = strlen(path);
|
||||
if (len < prefix_len)
|
||||
return 0; /* outside of the prefix */
|
||||
if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched))
|
||||
return 0; /* uninterested */
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (!ui->mode[i])
|
||||
continue;
|
||||
printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
|
||||
abbrev
|
||||
? find_unique_abbrev(ui->sha1[i], abbrev)
|
||||
: sha1_to_hex(ui->sha1[i]),
|
||||
i + 1);
|
||||
write_name_quoted(path + offset, stdout, line_terminator);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void show_ru_info(const char *prefix)
|
||||
{
|
||||
if (!the_index.resolve_undo)
|
||||
return;
|
||||
for_each_string_list(show_one_ru, the_index.resolve_undo, NULL);
|
||||
}
|
||||
|
||||
static void show_files(struct dir_struct *dir, const char *prefix)
|
||||
{
|
||||
int i;
|
||||
|
@ -458,6 +494,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
|||
DIR_HIDE_EMPTY_DIRECTORIES),
|
||||
OPT_BOOLEAN('u', "unmerged", &show_unmerged,
|
||||
"show unmerged files in the output"),
|
||||
OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
|
||||
"show resolve-undo information"),
|
||||
{ OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
|
||||
"skip files matching pattern",
|
||||
0, option_parse_exclude },
|
||||
|
@ -498,6 +536,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
|||
tag_other = "? ";
|
||||
tag_killed = "K ";
|
||||
tag_skip_worktree = "S ";
|
||||
tag_resolve_undo = "U ";
|
||||
}
|
||||
if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
|
||||
require_work_tree = 1;
|
||||
|
@ -536,7 +575,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
|||
|
||||
/* With no flags, we default to showing the cached files */
|
||||
if (!(show_stage | show_deleted | show_others | show_unmerged |
|
||||
show_killed | show_modified))
|
||||
show_killed | show_modified | show_resolve_undo))
|
||||
show_cached = 1;
|
||||
|
||||
if (prefix)
|
||||
|
@ -551,6 +590,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
|||
overlay_tree_on_cache(with_tree, prefix);
|
||||
}
|
||||
show_files(&dir, prefix);
|
||||
if (show_resolve_undo)
|
||||
show_ru_info(prefix);
|
||||
|
||||
if (ps_matched) {
|
||||
int bad;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "rerere.h"
|
||||
#include "help.h"
|
||||
#include "merge-recursive.h"
|
||||
#include "resolve-undo.h"
|
||||
|
||||
#define DEFAULT_TWOHEAD (1<<0)
|
||||
#define DEFAULT_OCTOPUS (1<<1)
|
||||
|
@ -606,6 +607,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
|
|||
discard_cache();
|
||||
if (read_cache() < 0)
|
||||
die("failed to read the cache");
|
||||
resolve_undo_clear();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -620,11 +622,10 @@ static void count_diff_files(struct diff_queue_struct *q,
|
|||
|
||||
static int count_unmerged_entries(void)
|
||||
{
|
||||
const struct index_state *state = &the_index;
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < state->cache_nr; i++)
|
||||
if (ce_stage(state->cache[i]))
|
||||
for (i = 0; i < active_nr; i++)
|
||||
if (ce_stage(active_cache[i]))
|
||||
ret++;
|
||||
|
||||
return ret;
|
||||
|
@ -864,6 +865,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||
die("You have not concluded your merge (MERGE_HEAD exists).");
|
||||
}
|
||||
|
||||
resolve_undo_clear();
|
||||
/*
|
||||
* Check if we are _not_ on a detached HEAD, i.e. if there is a
|
||||
* current branch.
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "dir.h"
|
||||
#include "builtin.h"
|
||||
#include "parse-options.h"
|
||||
#include "resolve-undo.h"
|
||||
|
||||
static int nr_trees;
|
||||
static struct tree *trees[MAX_UNPACK_TREES];
|
||||
|
@ -124,6 +125,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
|||
die("You need to resolve your current index first");
|
||||
stage = opts.merge = 1;
|
||||
}
|
||||
resolve_undo_clear();
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
|
|
@ -120,6 +120,9 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
|
|||
if (argc < 2)
|
||||
return rerere(flags);
|
||||
|
||||
if (!strcmp(argv[1], "forget"))
|
||||
return rerere_forget(argv + 2);
|
||||
|
||||
fd = setup_rerere(&merge_rr, flags);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "tree-walk.h"
|
||||
#include "builtin.h"
|
||||
#include "refs.h"
|
||||
#include "resolve-undo.h"
|
||||
|
||||
/*
|
||||
* Default to not allowing changes to the list of files. The
|
||||
|
@ -440,7 +441,18 @@ static int unresolve_one(const char *path)
|
|||
|
||||
/* See if there is such entry in the index. */
|
||||
pos = cache_name_pos(path, namelen);
|
||||
if (pos < 0) {
|
||||
if (0 <= pos) {
|
||||
/* already merged */
|
||||
pos = unmerge_cache_entry_at(pos);
|
||||
if (pos < active_nr) {
|
||||
struct cache_entry *ce = active_cache[pos];
|
||||
if (ce_stage(ce) &&
|
||||
ce_namelen(ce) == namelen &&
|
||||
!memcmp(ce->name, path, namelen))
|
||||
return 0;
|
||||
}
|
||||
/* no resolve-undo information; fall back */
|
||||
} else {
|
||||
/* If there isn't, either it is unmerged, or
|
||||
* resolved as "removed" by mistake. We do not
|
||||
* want to do anything in the former case.
|
||||
|
@ -719,6 +731,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
|||
verbose = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--clear-resolve-undo")) {
|
||||
resolve_undo_clear();
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "-h") || !strcmp(path, "--help"))
|
||||
usage(update_index_usage);
|
||||
die("unknown option %s", path);
|
||||
|
|
4
cache.h
4
cache.h
|
@ -288,6 +288,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
|
|||
struct index_state {
|
||||
struct cache_entry **cache;
|
||||
unsigned int cache_nr, cache_alloc, cache_changed;
|
||||
struct string_list *resolve_undo;
|
||||
struct cache_tree *cache_tree;
|
||||
struct cache_time timestamp;
|
||||
void *alloc;
|
||||
|
@ -342,6 +343,9 @@ static inline void remove_name_hash(struct cache_entry *ce)
|
|||
#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
|
||||
#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
|
||||
#define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
|
||||
#define resolve_undo_clear() resolve_undo_clear_index(&the_index)
|
||||
#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at)
|
||||
#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec)
|
||||
#endif
|
||||
|
||||
enum object_type {
|
||||
|
|
18
read-cache.c
18
read-cache.c
|
@ -14,6 +14,7 @@
|
|||
#include "diffcore.h"
|
||||
#include "revision.h"
|
||||
#include "blob.h"
|
||||
#include "resolve-undo.h"
|
||||
|
||||
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
|
||||
|
||||
|
@ -28,6 +29,7 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int reall
|
|||
|
||||
#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
|
||||
#define CACHE_EXT_TREE 0x54524545 /* "TREE" */
|
||||
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUN" */
|
||||
|
||||
struct index_state the_index;
|
||||
|
||||
|
@ -456,6 +458,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
|
|||
{
|
||||
struct cache_entry *ce = istate->cache[pos];
|
||||
|
||||
record_resolve_undo(istate, ce);
|
||||
remove_name_hash(ce);
|
||||
istate->cache_changed = 1;
|
||||
istate->cache_nr--;
|
||||
|
@ -1183,6 +1186,9 @@ static int read_index_extension(struct index_state *istate,
|
|||
case CACHE_EXT_TREE:
|
||||
istate->cache_tree = cache_tree_read(data, sz);
|
||||
break;
|
||||
case CACHE_EXT_RESOLVE_UNDO:
|
||||
istate->resolve_undo = resolve_undo_read(data, sz);
|
||||
break;
|
||||
default:
|
||||
if (*ext < 'A' || 'Z' < *ext)
|
||||
return error("index uses %.4s extension, which we do not understand",
|
||||
|
@ -1362,6 +1368,7 @@ int is_index_unborn(struct index_state *istate)
|
|||
|
||||
int discard_index(struct index_state *istate)
|
||||
{
|
||||
resolve_undo_clear_index(istate);
|
||||
istate->cache_nr = 0;
|
||||
istate->cache_changed = 0;
|
||||
istate->timestamp.sec = 0;
|
||||
|
@ -1587,6 +1594,17 @@ int write_index(struct index_state *istate, int newfd)
|
|||
if (err)
|
||||
return -1;
|
||||
}
|
||||
if (istate->resolve_undo) {
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
resolve_undo_write(&sb, istate->resolve_undo);
|
||||
err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO,
|
||||
sb.len) < 0
|
||||
|| ce_write(&c, newfd, sb.buf, sb.len) < 0;
|
||||
strbuf_release(&sb);
|
||||
if (err)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ce_flush(&c, newfd) || fstat(newfd, &st))
|
||||
return -1;
|
||||
|
|
259
rerere.c
259
rerere.c
|
@ -3,6 +3,9 @@
|
|||
#include "rerere.h"
|
||||
#include "xdiff/xdiff.h"
|
||||
#include "xdiff-interface.h"
|
||||
#include "dir.h"
|
||||
#include "resolve-undo.h"
|
||||
#include "ll-merge.h"
|
||||
|
||||
/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
|
||||
static int rerere_enabled = -1;
|
||||
|
@ -83,61 +86,74 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err)
|
|||
ferr_write(s, strlen(s), fp, err);
|
||||
}
|
||||
|
||||
static int handle_file(const char *path,
|
||||
unsigned char *sha1, const char *output)
|
||||
struct rerere_io {
|
||||
int (*getline)(struct strbuf *, struct rerere_io *);
|
||||
FILE *output;
|
||||
int wrerror;
|
||||
/* some more stuff */
|
||||
};
|
||||
|
||||
static void rerere_io_putstr(const char *str, struct rerere_io *io)
|
||||
{
|
||||
if (io->output)
|
||||
ferr_puts(str, io->output, &io->wrerror);
|
||||
}
|
||||
|
||||
static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
|
||||
{
|
||||
if (io->output)
|
||||
ferr_write(mem, sz, io->output, &io->wrerror);
|
||||
}
|
||||
|
||||
struct rerere_io_file {
|
||||
struct rerere_io io;
|
||||
FILE *input;
|
||||
};
|
||||
|
||||
static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
|
||||
{
|
||||
struct rerere_io_file *io = (struct rerere_io_file *)io_;
|
||||
return strbuf_getwholeline(sb, io->input, '\n');
|
||||
}
|
||||
|
||||
static int handle_path(unsigned char *sha1, struct rerere_io *io)
|
||||
{
|
||||
git_SHA_CTX ctx;
|
||||
char buf[1024];
|
||||
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;
|
||||
FILE *f = fopen(path, "r");
|
||||
FILE *out = NULL;
|
||||
int wrerror = 0;
|
||||
|
||||
if (!f)
|
||||
return error("Could not open %s", path);
|
||||
|
||||
if (output) {
|
||||
out = fopen(output, "w");
|
||||
if (!out) {
|
||||
fclose(f);
|
||||
return error("Could not write %s", output);
|
||||
}
|
||||
}
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
if (sha1)
|
||||
git_SHA1_Init(&ctx);
|
||||
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
if (!prefixcmp(buf, "<<<<<<< ")) {
|
||||
while (!io->getline(&buf, io)) {
|
||||
if (!prefixcmp(buf.buf, "<<<<<<< ")) {
|
||||
if (hunk != RR_CONTEXT)
|
||||
goto bad;
|
||||
hunk = RR_SIDE_1;
|
||||
} else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) {
|
||||
} else if (!prefixcmp(buf.buf, "|||||||") && isspace(buf.buf[7])) {
|
||||
if (hunk != RR_SIDE_1)
|
||||
goto bad;
|
||||
hunk = RR_ORIGINAL;
|
||||
} else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
|
||||
} else if (!prefixcmp(buf.buf, "=======") && isspace(buf.buf[7])) {
|
||||
if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
|
||||
goto bad;
|
||||
hunk = RR_SIDE_2;
|
||||
} else if (!prefixcmp(buf, ">>>>>>> ")) {
|
||||
} else if (!prefixcmp(buf.buf, ">>>>>>> ")) {
|
||||
if (hunk != RR_SIDE_2)
|
||||
goto bad;
|
||||
if (strbuf_cmp(&one, &two) > 0)
|
||||
strbuf_swap(&one, &two);
|
||||
hunk_no++;
|
||||
hunk = RR_CONTEXT;
|
||||
if (out) {
|
||||
ferr_puts("<<<<<<<\n", out, &wrerror);
|
||||
ferr_write(one.buf, one.len, out, &wrerror);
|
||||
ferr_puts("=======\n", out, &wrerror);
|
||||
ferr_write(two.buf, two.len, out, &wrerror);
|
||||
ferr_puts(">>>>>>>\n", out, &wrerror);
|
||||
}
|
||||
rerere_io_putstr("<<<<<<<\n", io);
|
||||
rerere_io_putmem(one.buf, one.len, io);
|
||||
rerere_io_putstr("=======\n", io);
|
||||
rerere_io_putmem(two.buf, two.len, io);
|
||||
rerere_io_putstr(">>>>>>>\n", io);
|
||||
if (sha1) {
|
||||
git_SHA1_Update(&ctx, one.buf ? one.buf : "",
|
||||
one.len + 1);
|
||||
|
@ -147,13 +163,13 @@ static int handle_file(const char *path,
|
|||
strbuf_reset(&one);
|
||||
strbuf_reset(&two);
|
||||
} else if (hunk == RR_SIDE_1)
|
||||
strbuf_addstr(&one, buf);
|
||||
strbuf_addstr(&one, buf.buf);
|
||||
else if (hunk == RR_ORIGINAL)
|
||||
; /* discard */
|
||||
else if (hunk == RR_SIDE_2)
|
||||
strbuf_addstr(&two, buf);
|
||||
else if (out)
|
||||
ferr_puts(buf, out, &wrerror);
|
||||
strbuf_addstr(&two, buf.buf);
|
||||
else
|
||||
rerere_io_putstr(buf.buf, io);
|
||||
continue;
|
||||
bad:
|
||||
hunk = 99; /* force error exit */
|
||||
|
@ -161,26 +177,136 @@ static int handle_file(const char *path,
|
|||
}
|
||||
strbuf_release(&one);
|
||||
strbuf_release(&two);
|
||||
strbuf_release(&buf);
|
||||
|
||||
fclose(f);
|
||||
if (wrerror)
|
||||
error("There were errors while writing %s (%s)",
|
||||
path, strerror(wrerror));
|
||||
if (out && fclose(out))
|
||||
wrerror = error("Failed to flush %s: %s",
|
||||
path, strerror(errno));
|
||||
if (sha1)
|
||||
git_SHA1_Final(sha1, &ctx);
|
||||
if (hunk != RR_CONTEXT) {
|
||||
if (hunk != RR_CONTEXT)
|
||||
return -1;
|
||||
return hunk_no;
|
||||
}
|
||||
|
||||
static int handle_file(const char *path, unsigned char *sha1, const char *output)
|
||||
{
|
||||
int hunk_no = 0;
|
||||
struct rerere_io_file io;
|
||||
|
||||
memset(&io, 0, sizeof(io));
|
||||
io.io.getline = rerere_file_getline;
|
||||
io.input = fopen(path, "r");
|
||||
io.io.wrerror = 0;
|
||||
if (!io.input)
|
||||
return error("Could not open %s", path);
|
||||
|
||||
if (output) {
|
||||
io.io.output = fopen(output, "w");
|
||||
if (!io.io.output) {
|
||||
fclose(io.input);
|
||||
return error("Could not write %s", output);
|
||||
}
|
||||
}
|
||||
|
||||
hunk_no = handle_path(sha1, (struct rerere_io *)&io);
|
||||
|
||||
fclose(io.input);
|
||||
if (io.io.wrerror)
|
||||
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("Failed to flush %s: %s",
|
||||
path, strerror(errno));
|
||||
|
||||
if (hunk_no < 0) {
|
||||
if (output)
|
||||
unlink_or_warn(output);
|
||||
return error("Could not parse conflict hunks in %s", path);
|
||||
}
|
||||
if (wrerror)
|
||||
if (io.io.wrerror)
|
||||
return -1;
|
||||
return hunk_no;
|
||||
}
|
||||
|
||||
struct rerere_io_mem {
|
||||
struct rerere_io io;
|
||||
struct strbuf input;
|
||||
};
|
||||
|
||||
static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
|
||||
{
|
||||
struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
|
||||
char *ep;
|
||||
size_t len;
|
||||
|
||||
strbuf_release(sb);
|
||||
if (!io->input.len)
|
||||
return -1;
|
||||
ep = strchrnul(io->input.buf, '\n');
|
||||
if (*ep == '\n')
|
||||
ep++;
|
||||
len = ep - io->input.buf;
|
||||
strbuf_add(sb, io->input.buf, len);
|
||||
strbuf_remove(&io->input, 0, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_cache(const char *path, unsigned char *sha1, const char *output)
|
||||
{
|
||||
mmfile_t mmfile[3];
|
||||
mmbuffer_t result = {NULL, 0};
|
||||
struct cache_entry *ce;
|
||||
int pos, len, i, hunk_no;
|
||||
struct rerere_io_mem io;
|
||||
|
||||
/*
|
||||
* Reproduce the conflicted merge in-core
|
||||
*/
|
||||
len = strlen(path);
|
||||
pos = cache_name_pos(path, len);
|
||||
if (0 <= pos)
|
||||
return -1;
|
||||
pos = -pos - 1;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
enum object_type type;
|
||||
unsigned long size;
|
||||
|
||||
mmfile[i].size = 0;
|
||||
mmfile[i].ptr = NULL;
|
||||
if (active_nr <= pos)
|
||||
break;
|
||||
ce = active_cache[pos++];
|
||||
if (ce_namelen(ce) != len || memcmp(ce->name, path, len)
|
||||
|| ce_stage(ce) != i + 1)
|
||||
break;
|
||||
mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
|
||||
mmfile[i].size = size;
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (!mmfile[i].ptr && !mmfile[i].size)
|
||||
mmfile[i].ptr = xstrdup("");
|
||||
}
|
||||
ll_merge(&result, path, &mmfile[0],
|
||||
&mmfile[1], "ours",
|
||||
&mmfile[2], "theirs", 0);
|
||||
for (i = 0; i < 3; i++)
|
||||
free(mmfile[i].ptr);
|
||||
|
||||
memset(&io, 0, sizeof(&io));
|
||||
io.io.getline = rerere_mem_getline;
|
||||
if (output)
|
||||
io.io.output = fopen(output, "w");
|
||||
else
|
||||
io.io.output = NULL;
|
||||
strbuf_init(&io.input, 0);
|
||||
strbuf_attach(&io.input, result.ptr, result.size, result.size);
|
||||
|
||||
hunk_no = handle_path(sha1, (struct rerere_io *)&io);
|
||||
strbuf_release(&io.input);
|
||||
if (io.io.output)
|
||||
fclose(io.io.output);
|
||||
return hunk_no;
|
||||
}
|
||||
|
||||
static int find_conflict(struct string_list *conflict)
|
||||
{
|
||||
int i;
|
||||
|
@ -394,3 +520,52 @@ int rerere(int flags)
|
|||
return 0;
|
||||
return do_plain_rerere(&merge_rr, fd);
|
||||
}
|
||||
|
||||
static int rerere_forget_one_path(const char *path, struct string_list *rr)
|
||||
{
|
||||
const char *filename;
|
||||
char *hex;
|
||||
unsigned char sha1[20];
|
||||
int ret;
|
||||
|
||||
ret = handle_cache(path, sha1, NULL);
|
||||
if (ret < 1)
|
||||
return error("Could not parse conflict hunks in '%s'", path);
|
||||
hex = xstrdup(sha1_to_hex(sha1));
|
||||
filename = rerere_path(hex, "postimage");
|
||||
if (unlink(filename))
|
||||
return (errno == ENOENT
|
||||
? error("no remembered resolution for %s", path)
|
||||
: error("cannot unlink %s: %s", filename, strerror(errno)));
|
||||
|
||||
handle_cache(path, sha1, rerere_path(hex, "preimage"));
|
||||
fprintf(stderr, "Updated preimage for '%s'\n", path);
|
||||
|
||||
|
||||
string_list_insert(path, rr)->util = hex;
|
||||
fprintf(stderr, "Forgot resolution for %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rerere_forget(const char **pathspec)
|
||||
{
|
||||
int i, fd;
|
||||
struct string_list conflict = { NULL, 0, 0, 1 };
|
||||
struct string_list merge_rr = { NULL, 0, 0, 1 };
|
||||
|
||||
if (read_cache() < 0)
|
||||
return error("Could not read index");
|
||||
|
||||
fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
|
||||
|
||||
unmerge_cache(pathspec);
|
||||
find_conflict(&conflict);
|
||||
for (i = 0; i < conflict.nr; i++) {
|
||||
struct string_list_item *it = &conflict.items[i];
|
||||
if (!match_pathspec(pathspec, it->string, strlen(it->string),
|
||||
0, NULL))
|
||||
continue;
|
||||
rerere_forget_one_path(it->string, &merge_rr);
|
||||
}
|
||||
return write_rr(&merge_rr, fd);
|
||||
}
|
||||
|
|
1
rerere.h
1
rerere.h
|
@ -10,6 +10,7 @@ extern int setup_rerere(struct string_list *, int);
|
|||
extern int rerere(int);
|
||||
extern const char *rerere_path(const char *hex, const char *file);
|
||||
extern int has_rerere_resolution(const char *hex);
|
||||
extern int rerere_forget(const char **);
|
||||
|
||||
#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \
|
||||
"update the index with reused conflict resolution if possible")
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
#include "cache.h"
|
||||
#include "dir.h"
|
||||
#include "resolve-undo.h"
|
||||
#include "string-list.h"
|
||||
|
||||
/* The only error case is to run out of memory in string-list */
|
||||
void record_resolve_undo(struct index_state *istate, struct cache_entry *ce)
|
||||
{
|
||||
struct string_list_item *lost;
|
||||
struct resolve_undo_info *ui;
|
||||
struct string_list *resolve_undo;
|
||||
int stage = ce_stage(ce);
|
||||
|
||||
if (!stage)
|
||||
return;
|
||||
|
||||
if (!istate->resolve_undo) {
|
||||
resolve_undo = xcalloc(1, sizeof(*resolve_undo));
|
||||
resolve_undo->strdup_strings = 1;
|
||||
istate->resolve_undo = resolve_undo;
|
||||
}
|
||||
resolve_undo = istate->resolve_undo;
|
||||
lost = string_list_insert(ce->name, resolve_undo);
|
||||
if (!lost->util)
|
||||
lost->util = xcalloc(1, sizeof(*ui));
|
||||
ui = lost->util;
|
||||
hashcpy(ui->sha1[stage - 1], ce->sha1);
|
||||
ui->mode[stage - 1] = ce->ce_mode;
|
||||
}
|
||||
|
||||
static int write_one(struct string_list_item *item, void *cbdata)
|
||||
{
|
||||
struct strbuf *sb = cbdata;
|
||||
struct resolve_undo_info *ui = item->util;
|
||||
int i;
|
||||
|
||||
if (!ui)
|
||||
return 0;
|
||||
strbuf_addstr(sb, item->string);
|
||||
strbuf_addch(sb, 0);
|
||||
for (i = 0; i < 3; i++)
|
||||
strbuf_addf(sb, "%o%c", ui->mode[i], 0);
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (!ui->mode[i])
|
||||
continue;
|
||||
strbuf_add(sb, ui->sha1[i], 20);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo)
|
||||
{
|
||||
for_each_string_list(write_one, resolve_undo, sb);
|
||||
}
|
||||
|
||||
struct string_list *resolve_undo_read(void *data, unsigned long size)
|
||||
{
|
||||
struct string_list *resolve_undo;
|
||||
size_t len;
|
||||
char *endptr;
|
||||
int i;
|
||||
|
||||
resolve_undo = xcalloc(1, sizeof(*resolve_undo));
|
||||
resolve_undo->strdup_strings = 1;
|
||||
|
||||
while (size) {
|
||||
struct string_list_item *lost;
|
||||
struct resolve_undo_info *ui;
|
||||
|
||||
len = strlen(data) + 1;
|
||||
if (size <= len)
|
||||
goto error;
|
||||
lost = string_list_insert(data, resolve_undo);
|
||||
if (!lost->util)
|
||||
lost->util = xcalloc(1, sizeof(*ui));
|
||||
ui = lost->util;
|
||||
size -= len;
|
||||
data += len;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
ui->mode[i] = strtoul(data, &endptr, 8);
|
||||
if (!endptr || endptr == data || *endptr)
|
||||
goto error;
|
||||
len = (endptr + 1) - (char*)data;
|
||||
if (size <= len)
|
||||
goto error;
|
||||
size -= len;
|
||||
data += len;
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (!ui->mode[i])
|
||||
continue;
|
||||
if (size < 20)
|
||||
goto error;
|
||||
hashcpy(ui->sha1[i], data);
|
||||
size -= 20;
|
||||
data += 20;
|
||||
}
|
||||
}
|
||||
return resolve_undo;
|
||||
|
||||
error:
|
||||
string_list_clear(resolve_undo, 1);
|
||||
error("Index records invalid resolve-undo information");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void resolve_undo_clear_index(struct index_state *istate)
|
||||
{
|
||||
struct string_list *resolve_undo = istate->resolve_undo;
|
||||
if (!resolve_undo)
|
||||
return;
|
||||
string_list_clear(resolve_undo, 1);
|
||||
free(resolve_undo);
|
||||
istate->resolve_undo = NULL;
|
||||
istate->cache_changed = 1;
|
||||
}
|
||||
|
||||
int unmerge_index_entry_at(struct index_state *istate, int pos)
|
||||
{
|
||||
struct cache_entry *ce;
|
||||
struct string_list_item *item;
|
||||
struct resolve_undo_info *ru;
|
||||
int i, err = 0;
|
||||
|
||||
if (!istate->resolve_undo)
|
||||
return pos;
|
||||
|
||||
ce = istate->cache[pos];
|
||||
if (ce_stage(ce)) {
|
||||
/* already unmerged */
|
||||
while ((pos < istate->cache_nr) &&
|
||||
! strcmp(istate->cache[pos]->name, ce->name))
|
||||
pos++;
|
||||
return pos - 1; /* return the last entry processed */
|
||||
}
|
||||
item = string_list_lookup(ce->name, istate->resolve_undo);
|
||||
if (!item)
|
||||
return pos;
|
||||
ru = item->util;
|
||||
if (!ru)
|
||||
return pos;
|
||||
remove_index_entry_at(istate, pos);
|
||||
for (i = 0; i < 3; i++) {
|
||||
struct cache_entry *nce;
|
||||
if (!ru->mode[i])
|
||||
continue;
|
||||
nce = make_cache_entry(ru->mode[i], ru->sha1[i],
|
||||
ce->name, i + 1, 0);
|
||||
if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) {
|
||||
err = 1;
|
||||
error("cannot unmerge '%s'", ce->name);
|
||||
}
|
||||
}
|
||||
if (err)
|
||||
return pos;
|
||||
free(ru);
|
||||
item->util = NULL;
|
||||
return unmerge_index_entry_at(istate, pos);
|
||||
}
|
||||
|
||||
void unmerge_index(struct index_state *istate, const char **pathspec)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!istate->resolve_undo)
|
||||
return;
|
||||
|
||||
for (i = 0; i < istate->cache_nr; i++) {
|
||||
struct cache_entry *ce = istate->cache[i];
|
||||
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL))
|
||||
continue;
|
||||
i = unmerge_index_entry_at(istate, i);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef RESOLVE_UNDO_H
|
||||
#define RESOLVE_UNDO_H
|
||||
|
||||
struct resolve_undo_info {
|
||||
unsigned int mode[3];
|
||||
unsigned char sha1[3][20];
|
||||
};
|
||||
|
||||
extern void record_resolve_undo(struct index_state *, struct cache_entry *);
|
||||
extern void resolve_undo_write(struct strbuf *, struct string_list *);
|
||||
extern struct string_list *resolve_undo_read(void *, unsigned long);
|
||||
extern void resolve_undo_clear_index(struct index_state *);
|
||||
extern int unmerge_index_entry_at(struct index_state *, int);
|
||||
extern void unmerge_index(struct index_state *, const char **);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,143 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='undoing resolution'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
check_resolve_undo () {
|
||||
msg=$1
|
||||
shift
|
||||
while case $# in
|
||||
0) break ;;
|
||||
1|2|3) die "Bug in check-resolve-undo test" ;;
|
||||
esac
|
||||
do
|
||||
path=$1
|
||||
shift
|
||||
for stage in 1 2 3
|
||||
do
|
||||
sha1=$1
|
||||
shift
|
||||
case "$sha1" in
|
||||
'') continue ;;
|
||||
esac
|
||||
sha1=$(git rev-parse --verify "$sha1")
|
||||
printf "100644 %s %s\t%s\n" $sha1 $stage $path
|
||||
done
|
||||
done >"$msg.expect" &&
|
||||
git ls-files --resolve-undo >"$msg.actual" &&
|
||||
test_cmp "$msg.expect" "$msg.actual"
|
||||
}
|
||||
|
||||
prime_resolve_undo () {
|
||||
git reset --hard &&
|
||||
git checkout second^0 &&
|
||||
test_tick &&
|
||||
test_must_fail git merge third^0 &&
|
||||
echo merge does not leave anything &&
|
||||
check_resolve_undo empty &&
|
||||
echo different >file &&
|
||||
git add file &&
|
||||
echo resolving records &&
|
||||
check_resolve_undo recorded file initial:file second:file third:file
|
||||
}
|
||||
|
||||
test_expect_success setup '
|
||||
test_commit initial file first &&
|
||||
git branch side &&
|
||||
git branch another &&
|
||||
test_commit second file second &&
|
||||
git checkout side &&
|
||||
test_commit third file third &&
|
||||
git checkout another &&
|
||||
test_commit fourth file fourth &&
|
||||
git checkout master
|
||||
'
|
||||
|
||||
test_expect_success 'add records switch clears' '
|
||||
prime_resolve_undo &&
|
||||
test_tick &&
|
||||
git commit -m merged &&
|
||||
echo committing keeps &&
|
||||
check_resolve_undo kept file initial:file second:file third:file &&
|
||||
git checkout second^0 &&
|
||||
echo switching clears &&
|
||||
check_resolve_undo cleared
|
||||
'
|
||||
|
||||
test_expect_success 'rm records reset clears' '
|
||||
prime_resolve_undo &&
|
||||
test_tick &&
|
||||
git commit -m merged &&
|
||||
echo committing keeps &&
|
||||
check_resolve_undo kept file initial:file second:file third:file &&
|
||||
|
||||
echo merge clears upfront &&
|
||||
test_must_fail git merge fourth^0 &&
|
||||
check_resolve_undo nuked &&
|
||||
|
||||
git rm -f file &&
|
||||
echo resolving records &&
|
||||
check_resolve_undo recorded file initial:file HEAD:file fourth:file &&
|
||||
|
||||
git reset --hard &&
|
||||
echo resetting discards &&
|
||||
check_resolve_undo discarded
|
||||
'
|
||||
|
||||
test_expect_success 'plumbing clears' '
|
||||
prime_resolve_undo &&
|
||||
test_tick &&
|
||||
git commit -m merged &&
|
||||
echo committing keeps &&
|
||||
check_resolve_undo kept file initial:file second:file third:file &&
|
||||
|
||||
echo plumbing clear &&
|
||||
git update-index --clear-resolve-undo &&
|
||||
check_resolve_undo cleared
|
||||
'
|
||||
|
||||
test_expect_success 'add records checkout -m undoes' '
|
||||
prime_resolve_undo &&
|
||||
git diff HEAD &&
|
||||
git checkout --conflict=merge file &&
|
||||
echo checkout used the record and removed it &&
|
||||
check_resolve_undo removed &&
|
||||
echo the index and the work tree is unmerged again &&
|
||||
git diff >actual &&
|
||||
grep "^++<<<<<<<" actual
|
||||
'
|
||||
|
||||
test_expect_success 'unmerge with plumbing' '
|
||||
prime_resolve_undo &&
|
||||
git update-index --unresolve file &&
|
||||
git ls-files -u >actual &&
|
||||
test $(wc -l <actual) = 3
|
||||
'
|
||||
|
||||
test_expect_success 'rerere and rerere --forget' '
|
||||
mkdir .git/rr-cache &&
|
||||
prime_resolve_undo &&
|
||||
echo record the resolution &&
|
||||
git rerere &&
|
||||
rerere_id=$(cd .git/rr-cache && echo */postimage) &&
|
||||
rerere_id=${rerere_id%/postimage} &&
|
||||
test -f .git/rr-cache/$rerere_id/postimage &&
|
||||
git checkout -m file &&
|
||||
echo resurrect the conflict &&
|
||||
grep "^=======" file &&
|
||||
echo reresolve the conflict &&
|
||||
git rerere &&
|
||||
test "z$(cat file)" = zdifferent &&
|
||||
echo register the resolution again &&
|
||||
git add file &&
|
||||
check_resolve_undo kept file initial:file second:file third:file &&
|
||||
test -z "$(git ls-files -u)" &&
|
||||
git rerere forget file &&
|
||||
! test -f .git/rr-cache/$rerere_id/postimage &&
|
||||
tr "\0" "\n" <.git/MERGE_RR >actual &&
|
||||
echo "$rerere_id file" >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче