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:
Junio C Hamano 2010-01-20 14:44:31 -08:00
Родитель 030b1a77f7 dea4562bf5
Коммит 6751e0471d
14 изменённых файлов: 652 добавлений и 47 удалений

Просмотреть файл

@ -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);

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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
Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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")

176
resolve-undo.c Normal file
Просмотреть файл

@ -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);
}
}

16
resolve-undo.h Normal file
Просмотреть файл

@ -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

143
t/t2030-unresolve-info.sh Executable file
Просмотреть файл

@ -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