Browse Source
* jh/notes-merge: (23 commits) Provide 'git merge --abort' as a synonym to 'git reset --merge' cmd_merge(): Parse options before checking MERGE_HEAD Provide 'git notes get-ref' to easily retrieve current notes ref git notes merge: Add testcases for merging notes trees at different fanouts git notes merge: Add another auto-resolving strategy: "cat_sort_uniq" git notes merge: --commit should fail if underlying notes ref has moved git notes merge: List conflicting notes in notes merge commit message git notes merge: Manual conflict resolution, part 2/2 git notes merge: Manual conflict resolution, part 1/2 Documentation: Preliminary docs on 'git notes merge' git notes merge: Add automatic conflict resolvers (ours, theirs, union) git notes merge: Handle real, non-conflicting notes merges builtin/notes.c: Refactor creation of notes commits. git notes merge: Initial implementation handling trivial merges only builtin/notes.c: Split notes ref DWIMmery into a separate function notes.c: Use two newlines (instead of one) when concatenating notes (trivial) t3303: Indent with tabs instead of spaces for consistency notes.h/c: Propagate combine_notes_fn return value to add_note() and beyond notes.h/c: Allow combine_notes functions to remove notes notes.c: Reorder functions in preparation for next commit ... Conflicts: builtin.hmaint
Junio C Hamano
14 years ago
20 changed files with 3794 additions and 162 deletions
@ -0,0 +1,737 @@
@@ -0,0 +1,737 @@
|
||||
#include "cache.h" |
||||
#include "commit.h" |
||||
#include "refs.h" |
||||
#include "diff.h" |
||||
#include "diffcore.h" |
||||
#include "xdiff-interface.h" |
||||
#include "ll-merge.h" |
||||
#include "dir.h" |
||||
#include "notes.h" |
||||
#include "notes-merge.h" |
||||
#include "strbuf.h" |
||||
|
||||
struct notes_merge_pair { |
||||
unsigned char obj[20], base[20], local[20], remote[20]; |
||||
}; |
||||
|
||||
void init_notes_merge_options(struct notes_merge_options *o) |
||||
{ |
||||
memset(o, 0, sizeof(struct notes_merge_options)); |
||||
strbuf_init(&(o->commit_msg), 0); |
||||
o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT; |
||||
} |
||||
|
||||
#define OUTPUT(o, v, ...) \ |
||||
do { \ |
||||
if ((o)->verbosity >= (v)) { \ |
||||
printf(__VA_ARGS__); \ |
||||
puts(""); \ |
||||
} \ |
||||
} while (0) |
||||
|
||||
static int path_to_sha1(const char *path, unsigned char *sha1) |
||||
{ |
||||
char hex_sha1[40]; |
||||
int i = 0; |
||||
while (*path && i < 40) { |
||||
if (*path != '/') |
||||
hex_sha1[i++] = *path; |
||||
path++; |
||||
} |
||||
if (*path || i != 40) |
||||
return -1; |
||||
return get_sha1_hex(hex_sha1, sha1); |
||||
} |
||||
|
||||
static int verify_notes_filepair(struct diff_filepair *p, unsigned char *sha1) |
||||
{ |
||||
switch (p->status) { |
||||
case DIFF_STATUS_MODIFIED: |
||||
assert(p->one->mode == p->two->mode); |
||||
assert(!is_null_sha1(p->one->sha1)); |
||||
assert(!is_null_sha1(p->two->sha1)); |
||||
break; |
||||
case DIFF_STATUS_ADDED: |
||||
assert(is_null_sha1(p->one->sha1)); |
||||
break; |
||||
case DIFF_STATUS_DELETED: |
||||
assert(is_null_sha1(p->two->sha1)); |
||||
break; |
||||
default: |
||||
return -1; |
||||
} |
||||
assert(!strcmp(p->one->path, p->two->path)); |
||||
return path_to_sha1(p->one->path, sha1); |
||||
} |
||||
|
||||
static struct notes_merge_pair *find_notes_merge_pair_pos( |
||||
struct notes_merge_pair *list, int len, unsigned char *obj, |
||||
int insert_new, int *occupied) |
||||
{ |
||||
/* |
||||
* Both diff_tree_remote() and diff_tree_local() tend to process |
||||
* merge_pairs in ascending order. Therefore, cache last returned |
||||
* index, and search sequentially from there until the appropriate |
||||
* position is found. |
||||
* |
||||
* Since inserts only happen from diff_tree_remote() (which mainly |
||||
* _appends_), we don't care that inserting into the middle of the |
||||
* list is expensive (using memmove()). |
||||
*/ |
||||
static int last_index; |
||||
int i = last_index < len ? last_index : len - 1; |
||||
int prev_cmp = 0, cmp = -1; |
||||
while (i >= 0 && i < len) { |
||||
cmp = hashcmp(obj, list[i].obj); |
||||
if (!cmp) /* obj belongs @ i */ |
||||
break; |
||||
else if (cmp < 0 && prev_cmp <= 0) /* obj belongs < i */ |
||||
i--; |
||||
else if (cmp < 0) /* obj belongs between i-1 and i */ |
||||
break; |
||||
else if (cmp > 0 && prev_cmp >= 0) /* obj belongs > i */ |
||||
i++; |
||||
else /* if (cmp > 0) */ { /* obj belongs between i and i+1 */ |
||||
i++; |
||||
break; |
||||
} |
||||
prev_cmp = cmp; |
||||
} |
||||
if (i < 0) |
||||
i = 0; |
||||
/* obj belongs at, or immediately preceding, index i (0 <= i <= len) */ |
||||
|
||||
if (!cmp) |
||||
*occupied = 1; |
||||
else { |
||||
*occupied = 0; |
||||
if (insert_new && i < len) { |
||||
memmove(list + i + 1, list + i, |
||||
(len - i) * sizeof(struct notes_merge_pair)); |
||||
memset(list + i, 0, sizeof(struct notes_merge_pair)); |
||||
} |
||||
} |
||||
last_index = i; |
||||
return list + i; |
||||
} |
||||
|
||||
static unsigned char uninitialized[20] = |
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" \ |
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; |
||||
|
||||
static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, |
||||
const unsigned char *base, |
||||
const unsigned char *remote, |
||||
int *num_changes) |
||||
{ |
||||
struct diff_options opt; |
||||
struct notes_merge_pair *changes; |
||||
int i, len = 0; |
||||
|
||||
trace_printf("\tdiff_tree_remote(base = %.7s, remote = %.7s)\n", |
||||
sha1_to_hex(base), sha1_to_hex(remote)); |
||||
|
||||
diff_setup(&opt); |
||||
DIFF_OPT_SET(&opt, RECURSIVE); |
||||
opt.output_format = DIFF_FORMAT_NO_OUTPUT; |
||||
if (diff_setup_done(&opt) < 0) |
||||
die("diff_setup_done failed"); |
||||
diff_tree_sha1(base, remote, "", &opt); |
||||
diffcore_std(&opt); |
||||
|
||||
changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair)); |
||||
|
||||
for (i = 0; i < diff_queued_diff.nr; i++) { |
||||
struct diff_filepair *p = diff_queued_diff.queue[i]; |
||||
struct notes_merge_pair *mp; |
||||
int occupied; |
||||
unsigned char obj[20]; |
||||
|
||||
if (verify_notes_filepair(p, obj)) { |
||||
trace_printf("\t\tCannot merge entry '%s' (%c): " |
||||
"%.7s -> %.7s. Skipping!\n", p->one->path, |
||||
p->status, sha1_to_hex(p->one->sha1), |
||||
sha1_to_hex(p->two->sha1)); |
||||
continue; |
||||
} |
||||
mp = find_notes_merge_pair_pos(changes, len, obj, 1, &occupied); |
||||
if (occupied) { |
||||
/* We've found an addition/deletion pair */ |
||||
assert(!hashcmp(mp->obj, obj)); |
||||
if (is_null_sha1(p->one->sha1)) { /* addition */ |
||||
assert(is_null_sha1(mp->remote)); |
||||
hashcpy(mp->remote, p->two->sha1); |
||||
} else if (is_null_sha1(p->two->sha1)) { /* deletion */ |
||||
assert(is_null_sha1(mp->base)); |
||||
hashcpy(mp->base, p->one->sha1); |
||||
} else |
||||
assert(!"Invalid existing change recorded"); |
||||
} else { |
||||
hashcpy(mp->obj, obj); |
||||
hashcpy(mp->base, p->one->sha1); |
||||
hashcpy(mp->local, uninitialized); |
||||
hashcpy(mp->remote, p->two->sha1); |
||||
len++; |
||||
} |
||||
trace_printf("\t\tStored remote change for %s: %.7s -> %.7s\n", |
||||
sha1_to_hex(mp->obj), sha1_to_hex(mp->base), |
||||
sha1_to_hex(mp->remote)); |
||||
} |
||||
diff_flush(&opt); |
||||
diff_tree_release_paths(&opt); |
||||
|
||||
*num_changes = len; |
||||
return changes; |
||||
} |
||||
|
||||
static void diff_tree_local(struct notes_merge_options *o, |
||||
struct notes_merge_pair *changes, int len, |
||||
const unsigned char *base, |
||||
const unsigned char *local) |
||||
{ |
||||
struct diff_options opt; |
||||
int i; |
||||
|
||||
trace_printf("\tdiff_tree_local(len = %i, base = %.7s, local = %.7s)\n", |
||||
len, sha1_to_hex(base), sha1_to_hex(local)); |
||||
|
||||
diff_setup(&opt); |
||||
DIFF_OPT_SET(&opt, RECURSIVE); |
||||
opt.output_format = DIFF_FORMAT_NO_OUTPUT; |
||||
if (diff_setup_done(&opt) < 0) |
||||
die("diff_setup_done failed"); |
||||
diff_tree_sha1(base, local, "", &opt); |
||||
diffcore_std(&opt); |
||||
|
||||
for (i = 0; i < diff_queued_diff.nr; i++) { |
||||
struct diff_filepair *p = diff_queued_diff.queue[i]; |
||||
struct notes_merge_pair *mp; |
||||
int match; |
||||
unsigned char obj[20]; |
||||
|
||||
if (verify_notes_filepair(p, obj)) { |
||||
trace_printf("\t\tCannot merge entry '%s' (%c): " |
||||
"%.7s -> %.7s. Skipping!\n", p->one->path, |
||||
p->status, sha1_to_hex(p->one->sha1), |
||||
sha1_to_hex(p->two->sha1)); |
||||
continue; |
||||
} |
||||
mp = find_notes_merge_pair_pos(changes, len, obj, 0, &match); |
||||
if (!match) { |
||||
trace_printf("\t\tIgnoring local-only change for %s: " |
||||
"%.7s -> %.7s\n", sha1_to_hex(obj), |
||||
sha1_to_hex(p->one->sha1), |
||||
sha1_to_hex(p->two->sha1)); |
||||
continue; |
||||
} |
||||
|
||||
assert(!hashcmp(mp->obj, obj)); |
||||
if (is_null_sha1(p->two->sha1)) { /* deletion */ |
||||
/* |
||||
* Either this is a true deletion (1), or it is part |
||||
* of an A/D pair (2), or D/A pair (3): |
||||
* |
||||
* (1) mp->local is uninitialized; set it to null_sha1 |
||||
* (2) mp->local is not uninitialized; don't touch it |
||||
* (3) mp->local is uninitialized; set it to null_sha1 |
||||
* (will be overwritten by following addition) |
||||
*/ |
||||
if (!hashcmp(mp->local, uninitialized)) |
||||
hashclr(mp->local); |
||||
} else if (is_null_sha1(p->one->sha1)) { /* addition */ |
||||
/* |
||||
* Either this is a true addition (1), or it is part |
||||
* of an A/D pair (2), or D/A pair (3): |
||||
* |
||||
* (1) mp->local is uninitialized; set to p->two->sha1 |
||||
* (2) mp->local is uninitialized; set to p->two->sha1 |
||||
* (3) mp->local is null_sha1; set to p->two->sha1 |
||||
*/ |
||||
assert(is_null_sha1(mp->local) || |
||||
!hashcmp(mp->local, uninitialized)); |
||||
hashcpy(mp->local, p->two->sha1); |
||||
} else { /* modification */ |
||||
/* |
||||
* This is a true modification. p->one->sha1 shall |
||||
* match mp->base, and mp->local shall be uninitialized. |
||||
* Set mp->local to p->two->sha1. |
||||
*/ |
||||
assert(!hashcmp(p->one->sha1, mp->base)); |
||||
assert(!hashcmp(mp->local, uninitialized)); |
||||
hashcpy(mp->local, p->two->sha1); |
||||
} |
||||
trace_printf("\t\tStored local change for %s: %.7s -> %.7s\n", |
||||
sha1_to_hex(mp->obj), sha1_to_hex(mp->base), |
||||
sha1_to_hex(mp->local)); |
||||
} |
||||
diff_flush(&opt); |
||||
diff_tree_release_paths(&opt); |
||||
} |
||||
|
||||
static void check_notes_merge_worktree(struct notes_merge_options *o) |
||||
{ |
||||
if (!o->has_worktree) { |
||||
/* |
||||
* Must establish NOTES_MERGE_WORKTREE. |
||||
* Abort if NOTES_MERGE_WORKTREE already exists |
||||
*/ |
||||
if (file_exists(git_path(NOTES_MERGE_WORKTREE))) { |
||||
if (advice_resolve_conflict) |
||||
die("You have not concluded your previous " |
||||
"notes merge (%s exists).\nPlease, use " |
||||
"'git notes merge --commit' or 'git notes " |
||||
"merge --abort' to commit/abort the " |
||||
"previous merge before you start a new " |
||||
"notes merge.", git_path("NOTES_MERGE_*")); |
||||
else |
||||
die("You have not concluded your notes merge " |
||||
"(%s exists).", git_path("NOTES_MERGE_*")); |
||||
} |
||||
|
||||
if (safe_create_leading_directories(git_path( |
||||
NOTES_MERGE_WORKTREE "/.test"))) |
||||
die_errno("unable to create directory %s", |
||||
git_path(NOTES_MERGE_WORKTREE)); |
||||
o->has_worktree = 1; |
||||
} else if (!file_exists(git_path(NOTES_MERGE_WORKTREE))) |
||||
/* NOTES_MERGE_WORKTREE should already be established */ |
||||
die("missing '%s'. This should not happen", |
||||
git_path(NOTES_MERGE_WORKTREE)); |
||||
} |
||||
|
||||
static void write_buf_to_worktree(const unsigned char *obj, |
||||
const char *buf, unsigned long size) |
||||
{ |
||||
int fd; |
||||
char *path = git_path(NOTES_MERGE_WORKTREE "/%s", sha1_to_hex(obj)); |
||||
if (safe_create_leading_directories(path)) |
||||
die_errno("unable to create directory for '%s'", path); |
||||
if (file_exists(path)) |
||||
die("found existing file at '%s'", path); |
||||
|
||||
fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0666); |
||||
if (fd < 0) |
||||
die_errno("failed to open '%s'", path); |
||||
|
||||
while (size > 0) { |
||||
long ret = write_in_full(fd, buf, size); |
||||
if (ret < 0) { |
||||
/* Ignore epipe */ |
||||
if (errno == EPIPE) |
||||
break; |
||||
die_errno("notes-merge"); |
||||
} else if (!ret) { |
||||
die("notes-merge: disk full?"); |
||||
} |
||||
size -= ret; |
||||
buf += ret; |
||||
} |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
static void write_note_to_worktree(const unsigned char *obj, |
||||
const unsigned char *note) |
||||
{ |
||||
enum object_type type; |
||||
unsigned long size; |
||||
void *buf = read_sha1_file(note, &type, &size); |
||||
|
||||
if (!buf) |
||||
die("cannot read note %s for object %s", |
||||
sha1_to_hex(note), sha1_to_hex(obj)); |
||||
if (type != OBJ_BLOB) |
||||
die("blob expected in note %s for object %s", |
||||
sha1_to_hex(note), sha1_to_hex(obj)); |
||||
write_buf_to_worktree(obj, buf, size); |
||||
free(buf); |
||||
} |
||||
|
||||
static int ll_merge_in_worktree(struct notes_merge_options *o, |
||||
struct notes_merge_pair *p) |
||||
{ |
||||
mmbuffer_t result_buf; |
||||
mmfile_t base, local, remote; |
||||
int status; |
||||
|
||||
read_mmblob(&base, p->base); |
||||
read_mmblob(&local, p->local); |
||||
read_mmblob(&remote, p->remote); |
||||
|
||||
status = ll_merge(&result_buf, sha1_to_hex(p->obj), &base, NULL, |
||||
&local, o->local_ref, &remote, o->remote_ref, 0); |
||||
|
||||
free(base.ptr); |
||||
free(local.ptr); |
||||
free(remote.ptr); |
||||
|
||||
if ((status < 0) || !result_buf.ptr) |
||||
die("Failed to execute internal merge"); |
||||
|
||||
write_buf_to_worktree(p->obj, result_buf.ptr, result_buf.size); |
||||
free(result_buf.ptr); |
||||
|
||||
return status; |
||||
} |
||||
|
||||
static int merge_one_change_manual(struct notes_merge_options *o, |
||||
struct notes_merge_pair *p, |
||||
struct notes_tree *t) |
||||
{ |
||||
const char *lref = o->local_ref ? o->local_ref : "local version"; |
||||
const char *rref = o->remote_ref ? o->remote_ref : "remote version"; |
||||
|
||||
trace_printf("\t\t\tmerge_one_change_manual(obj = %.7s, base = %.7s, " |
||||
"local = %.7s, remote = %.7s)\n", |
||||
sha1_to_hex(p->obj), sha1_to_hex(p->base), |
||||
sha1_to_hex(p->local), sha1_to_hex(p->remote)); |
||||
|
||||
/* add "Conflicts:" section to commit message first time through */ |
||||
if (!o->has_worktree) |
||||
strbuf_addstr(&(o->commit_msg), "\n\nConflicts:\n"); |
||||
|
||||
strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj)); |
||||
|
||||
OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj)); |
||||
check_notes_merge_worktree(o); |
||||
if (is_null_sha1(p->local)) { |
||||
/* D/F conflict, checkout p->remote */ |
||||
assert(!is_null_sha1(p->remote)); |
||||
OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s " |
||||
"deleted in %s and modified in %s. Version from %s " |
||||
"left in tree.", sha1_to_hex(p->obj), lref, rref, rref); |
||||
write_note_to_worktree(p->obj, p->remote); |
||||
} else if (is_null_sha1(p->remote)) { |
||||
/* D/F conflict, checkout p->local */ |
||||
assert(!is_null_sha1(p->local)); |
||||
OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s " |
||||
"deleted in %s and modified in %s. Version from %s " |
||||
"left in tree.", sha1_to_hex(p->obj), rref, lref, lref); |
||||
write_note_to_worktree(p->obj, p->local); |
||||
} else { |
||||
/* "regular" conflict, checkout result of ll_merge() */ |
||||
const char *reason = "content"; |
||||
if (is_null_sha1(p->base)) |
||||
reason = "add/add"; |
||||
assert(!is_null_sha1(p->local)); |
||||
assert(!is_null_sha1(p->remote)); |
||||
OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for " |
||||
"object %s", reason, sha1_to_hex(p->obj)); |
||||
ll_merge_in_worktree(o, p); |
||||
} |
||||
|
||||
trace_printf("\t\t\tremoving from partial merge result\n"); |
||||
remove_note(t, p->obj); |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
static int merge_one_change(struct notes_merge_options *o, |
||||
struct notes_merge_pair *p, struct notes_tree *t) |
||||
{ |
||||
/* |
||||
* Return 0 if change is successfully resolved (stored in notes_tree). |
||||
* Return 1 is change results in a conflict (NOT stored in notes_tree, |
||||
* but instead written to NOTES_MERGE_WORKTREE with conflict markers). |
||||
*/ |
||||
switch (o->strategy) { |
||||
case NOTES_MERGE_RESOLVE_MANUAL: |
||||
return merge_one_change_manual(o, p, t); |
||||
case NOTES_MERGE_RESOLVE_OURS: |
||||
OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj)); |
||||
/* nothing to do */ |
||||
return 0; |
||||
case NOTES_MERGE_RESOLVE_THEIRS: |
||||
OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj)); |
||||
if (add_note(t, p->obj, p->remote, combine_notes_overwrite)) |
||||
die("BUG: combine_notes_overwrite failed"); |
||||
return 0; |
||||
case NOTES_MERGE_RESOLVE_UNION: |
||||
OUTPUT(o, 2, "Concatenating local and remote notes for %s", |
||||
sha1_to_hex(p->obj)); |
||||
if (add_note(t, p->obj, p->remote, combine_notes_concatenate)) |
||||
die("failed to concatenate notes " |
||||
"(combine_notes_concatenate)"); |
||||
return 0; |
||||
case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ: |
||||
OUTPUT(o, 2, "Concatenating unique lines in local and remote " |
||||
"notes for %s", sha1_to_hex(p->obj)); |
||||
if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq)) |
||||
die("failed to concatenate notes " |
||||
"(combine_notes_cat_sort_uniq)"); |
||||
return 0; |
||||
} |
||||
die("Unknown strategy (%i).", o->strategy); |
||||
} |
||||
|
||||
static int merge_changes(struct notes_merge_options *o, |
||||
struct notes_merge_pair *changes, int *num_changes, |
||||
struct notes_tree *t) |
||||
{ |
||||
int i, conflicts = 0; |
||||
|
||||
trace_printf("\tmerge_changes(num_changes = %i)\n", *num_changes); |
||||
for (i = 0; i < *num_changes; i++) { |
||||
struct notes_merge_pair *p = changes + i; |
||||
trace_printf("\t\t%.7s: %.7s -> %.7s/%.7s\n", |
||||
sha1_to_hex(p->obj), sha1_to_hex(p->base), |
||||
sha1_to_hex(p->local), sha1_to_hex(p->remote)); |
||||
|
||||
if (!hashcmp(p->base, p->remote)) { |
||||
/* no remote change; nothing to do */ |
||||
trace_printf("\t\t\tskipping (no remote change)\n"); |
||||
} else if (!hashcmp(p->local, p->remote)) { |
||||
/* same change in local and remote; nothing to do */ |
||||
trace_printf("\t\t\tskipping (local == remote)\n"); |
||||
} else if (!hashcmp(p->local, uninitialized) || |
||||
!hashcmp(p->local, p->base)) { |
||||
/* no local change; adopt remote change */ |
||||
trace_printf("\t\t\tno local change, adopted remote\n"); |
||||
if (add_note(t, p->obj, p->remote, |
||||
combine_notes_overwrite)) |
||||
die("BUG: combine_notes_overwrite failed"); |
||||
} else { |
||||
/* need file-level merge between local and remote */ |
||||
trace_printf("\t\t\tneed content-level merge\n"); |
||||
conflicts += merge_one_change(o, p, t); |
||||
} |
||||
} |
||||
|
||||
return conflicts; |
||||
} |
||||
|
||||
static int merge_from_diffs(struct notes_merge_options *o, |
||||
const unsigned char *base, |
||||
const unsigned char *local, |
||||
const unsigned char *remote, struct notes_tree *t) |
||||
{ |
||||
struct notes_merge_pair *changes; |
||||
int num_changes, conflicts; |
||||
|
||||
trace_printf("\tmerge_from_diffs(base = %.7s, local = %.7s, " |
||||
"remote = %.7s)\n", sha1_to_hex(base), sha1_to_hex(local), |
||||
sha1_to_hex(remote)); |
||||
|
||||
changes = diff_tree_remote(o, base, remote, &num_changes); |
||||
diff_tree_local(o, changes, num_changes, base, local); |
||||
|
||||
conflicts = merge_changes(o, changes, &num_changes, t); |
||||
free(changes); |
||||
|
||||
OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree", |
||||
conflicts, t->dirty ? "dirty" : "clean"); |
||||
|
||||
return conflicts ? -1 : 1; |
||||
} |
||||
|
||||
void create_notes_commit(struct notes_tree *t, struct commit_list *parents, |
||||
const char *msg, unsigned char *result_sha1) |
||||
{ |
||||
unsigned char tree_sha1[20]; |
||||
|
||||
assert(t->initialized); |
||||
|
||||
if (write_notes_tree(t, tree_sha1)) |
||||
die("Failed to write notes tree to database"); |
||||
|
||||
if (!parents) { |
||||
/* Deduce parent commit from t->ref */ |
||||
unsigned char parent_sha1[20]; |
||||
if (!read_ref(t->ref, parent_sha1)) { |
||||
struct commit *parent = lookup_commit(parent_sha1); |
||||
if (!parent || parse_commit(parent)) |
||||
die("Failed to find/parse commit %s", t->ref); |
||||
commit_list_insert(parent, &parents); |
||||
} |
||||
/* else: t->ref points to nothing, assume root/orphan commit */ |
||||
} |
||||
|
||||
if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL)) |
||||
die("Failed to commit notes tree to database"); |
||||
} |
||||
|
||||
int notes_merge(struct notes_merge_options *o, |
||||
struct notes_tree *local_tree, |
||||
unsigned char *result_sha1) |
||||
{ |
||||
unsigned char local_sha1[20], remote_sha1[20]; |
||||
struct commit *local, *remote; |
||||
struct commit_list *bases = NULL; |
||||
const unsigned char *base_sha1, *base_tree_sha1; |
||||
int result = 0; |
||||
|
||||
assert(o->local_ref && o->remote_ref); |
||||
assert(!strcmp(o->local_ref, local_tree->ref)); |
||||
hashclr(result_sha1); |
||||
|
||||
trace_printf("notes_merge(o->local_ref = %s, o->remote_ref = %s)\n", |
||||
o->local_ref, o->remote_ref); |
||||
|
||||
/* Dereference o->local_ref into local_sha1 */ |
||||
if (!resolve_ref(o->local_ref, local_sha1, 0, NULL)) |
||||
die("Failed to resolve local notes ref '%s'", o->local_ref); |
||||
else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1)) |
||||
local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */ |
||||
else if (!(local = lookup_commit_reference(local_sha1))) |
||||
die("Could not parse local commit %s (%s)", |
||||
sha1_to_hex(local_sha1), o->local_ref); |
||||
trace_printf("\tlocal commit: %.7s\n", sha1_to_hex(local_sha1)); |
||||
|
||||
/* Dereference o->remote_ref into remote_sha1 */ |
||||
if (get_sha1(o->remote_ref, remote_sha1)) { |
||||
/* |
||||
* Failed to get remote_sha1. If o->remote_ref looks like an |
||||
* unborn ref, perform the merge using an empty notes tree. |
||||
*/ |
||||
if (!check_ref_format(o->remote_ref)) { |
||||
hashclr(remote_sha1); |
||||
remote = NULL; |
||||
} else { |
||||
die("Failed to resolve remote notes ref '%s'", |
||||
o->remote_ref); |
||||
} |
||||
} else if (!(remote = lookup_commit_reference(remote_sha1))) { |
||||
die("Could not parse remote commit %s (%s)", |
||||
sha1_to_hex(remote_sha1), o->remote_ref); |
||||
} |
||||
trace_printf("\tremote commit: %.7s\n", sha1_to_hex(remote_sha1)); |
||||
|
||||
if (!local && !remote) |
||||
die("Cannot merge empty notes ref (%s) into empty notes ref " |
||||
"(%s)", o->remote_ref, o->local_ref); |
||||
if (!local) { |
||||
/* result == remote commit */ |
||||
hashcpy(result_sha1, remote_sha1); |
||||
goto found_result; |
||||
} |
||||
if (!remote) { |
||||
/* result == local commit */ |
||||
hashcpy(result_sha1, local_sha1); |
||||
goto found_result; |
||||
} |
||||
assert(local && remote); |
||||
|
||||
/* Find merge bases */ |
||||
bases = get_merge_bases(local, remote, 1); |
||||
if (!bases) { |
||||
base_sha1 = null_sha1; |
||||
base_tree_sha1 = (unsigned char *)EMPTY_TREE_SHA1_BIN; |
||||
OUTPUT(o, 4, "No merge base found; doing history-less merge"); |
||||
} else if (!bases->next) { |
||||
base_sha1 = bases->item->object.sha1; |
||||
base_tree_sha1 = bases->item->tree->object.sha1; |
||||
OUTPUT(o, 4, "One merge base found (%.7s)", |
||||
sha1_to_hex(base_sha1)); |
||||
} else { |
||||
/* TODO: How to handle multiple merge-bases? */ |
||||
base_sha1 = bases->item->object.sha1; |
||||
base_tree_sha1 = bases->item->tree->object.sha1; |
||||
OUTPUT(o, 3, "Multiple merge bases found. Using the first " |
||||
"(%.7s)", sha1_to_hex(base_sha1)); |
||||
} |
||||
|
||||
OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with " |
||||
"merge-base %.7s", sha1_to_hex(remote->object.sha1), |
||||
sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1)); |
||||
|
||||
if (!hashcmp(remote->object.sha1, base_sha1)) { |
||||
/* Already merged; result == local commit */ |
||||
OUTPUT(o, 2, "Already up-to-date!"); |
||||
hashcpy(result_sha1, local->object.sha1); |
||||
goto found_result; |
||||
} |
||||
if (!hashcmp(local->object.sha1, base_sha1)) { |
||||
/* Fast-forward; result == remote commit */ |
||||
OUTPUT(o, 2, "Fast-forward"); |
||||
hashcpy(result_sha1, remote->object.sha1); |
||||
goto found_result; |
||||
} |
||||
|
||||
result = merge_from_diffs(o, base_tree_sha1, local->tree->object.sha1, |
||||
remote->tree->object.sha1, local_tree); |
||||
|
||||
if (result != 0) { /* non-trivial merge (with or without conflicts) */ |
||||
/* Commit (partial) result */ |
||||
struct commit_list *parents = NULL; |
||||
commit_list_insert(remote, &parents); /* LIFO order */ |
||||
commit_list_insert(local, &parents); |
||||
create_notes_commit(local_tree, parents, o->commit_msg.buf, |
||||
result_sha1); |
||||
} |
||||
|
||||
found_result: |
||||
free_commit_list(bases); |
||||
strbuf_release(&(o->commit_msg)); |
||||
trace_printf("notes_merge(): result = %i, result_sha1 = %.7s\n", |
||||
result, sha1_to_hex(result_sha1)); |
||||
return result; |
||||
} |
||||
|
||||
int notes_merge_commit(struct notes_merge_options *o, |
||||
struct notes_tree *partial_tree, |
||||
struct commit *partial_commit, |
||||
unsigned char *result_sha1) |
||||
{ |
||||
/* |
||||
* Iterate through files in .git/NOTES_MERGE_WORKTREE and add all |
||||
* found notes to 'partial_tree'. Write the updates notes tree to |
||||
* the DB, and commit the resulting tree object while reusing the |
||||
* commit message and parents from 'partial_commit'. |
||||
* Finally store the new commit object SHA1 into 'result_sha1'. |
||||
*/ |
||||
struct dir_struct dir; |
||||
const char *path = git_path(NOTES_MERGE_WORKTREE "/"); |
||||
int path_len = strlen(path), i; |
||||
const char *msg = strstr(partial_commit->buffer, "\n\n"); |
||||
|
||||
OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s", |
||||
path_len - 1, path); |
||||
|
||||
if (!msg || msg[2] == '\0') |
||||
die("partial notes commit has empty message"); |
||||
msg += 2; |
||||
|
||||
memset(&dir, 0, sizeof(dir)); |
||||
read_directory(&dir, path, path_len, NULL); |
||||
for (i = 0; i < dir.nr; i++) { |
||||
struct dir_entry *ent = dir.entries[i]; |
||||
struct stat st; |
||||
const char *relpath = ent->name + path_len; |
||||
unsigned char obj_sha1[20], blob_sha1[20]; |
||||
|
||||
if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) { |
||||
OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name); |
||||
continue; |
||||
} |
||||
|
||||
/* write file as blob, and add to partial_tree */ |
||||
if (stat(ent->name, &st)) |
||||
die_errno("Failed to stat '%s'", ent->name); |
||||
if (index_path(blob_sha1, ent->name, &st, 1)) |
||||
die("Failed to write blob object from '%s'", ent->name); |
||||
if (add_note(partial_tree, obj_sha1, blob_sha1, NULL)) |
||||
die("Failed to add resolved note '%s' to notes tree", |
||||
ent->name); |
||||
OUTPUT(o, 4, "Added resolved note for object %s: %s", |
||||
sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1)); |
||||
} |
||||
|
||||
create_notes_commit(partial_tree, partial_commit->parents, msg, |
||||
result_sha1); |
||||
OUTPUT(o, 4, "Finalized notes merge commit: %s", |
||||
sha1_to_hex(result_sha1)); |
||||
return 0; |
||||
} |
||||
|
||||
int notes_merge_abort(struct notes_merge_options *o) |
||||
{ |
||||
/* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */ |
||||
struct strbuf buf = STRBUF_INIT; |
||||
int ret; |
||||
|
||||
strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE)); |
||||
OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf); |
||||
ret = remove_dir_recursively(&buf, 0); |
||||
strbuf_release(&buf); |
||||
return ret; |
||||
} |
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
#ifndef NOTES_MERGE_H |
||||
#define NOTES_MERGE_H |
||||
|
||||
#define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE" |
||||
|
||||
enum notes_merge_verbosity { |
||||
NOTES_MERGE_VERBOSITY_DEFAULT = 2, |
||||
NOTES_MERGE_VERBOSITY_MAX = 5 |
||||
}; |
||||
|
||||
struct notes_merge_options { |
||||
const char *local_ref; |
||||
const char *remote_ref; |
||||
struct strbuf commit_msg; |
||||
int verbosity; |
||||
enum { |
||||
NOTES_MERGE_RESOLVE_MANUAL = 0, |
||||
NOTES_MERGE_RESOLVE_OURS, |
||||
NOTES_MERGE_RESOLVE_THEIRS, |
||||
NOTES_MERGE_RESOLVE_UNION, |
||||
NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ |
||||
} strategy; |
||||
unsigned has_worktree:1; |
||||
}; |
||||
|
||||
void init_notes_merge_options(struct notes_merge_options *o); |
||||
|
||||
/* |
||||
* Create new notes commit from the given notes tree |
||||
* |
||||
* Properties of the created commit: |
||||
* - tree: the result of converting t to a tree object with write_notes_tree(). |
||||
* - parents: the given parents OR (if NULL) the commit referenced by t->ref. |
||||
* - author/committer: the default determined by commmit_tree(). |
||||
* - commit message: msg |
||||
* |
||||
* The resulting commit SHA1 is stored in result_sha1. |
||||
*/ |
||||
void create_notes_commit(struct notes_tree *t, struct commit_list *parents, |
||||
const char *msg, unsigned char *result_sha1); |
||||
|
||||
/* |
||||
* Merge notes from o->remote_ref into o->local_ref |
||||
* |
||||
* The given notes_tree 'local_tree' must be the notes_tree referenced by the |
||||
* o->local_ref. This is the notes_tree in which the object-level merge is |
||||
* performed. |
||||
* |
||||
* The commits given by the two refs are merged, producing one of the following |
||||
* outcomes: |
||||
* |
||||
* 1. The merge trivially results in an existing commit (e.g. fast-forward or |
||||
* already-up-to-date). 'local_tree' is untouched, the SHA1 of the result |
||||
* is written into 'result_sha1' and 0 is returned. |
||||
* 2. The merge successfully completes, producing a merge commit. local_tree |
||||
* contains the updated notes tree, the SHA1 of the resulting commit is |
||||
* written into 'result_sha1', and 1 is returned. |
||||
* 3. The merge results in conflicts. This is similar to #2 in that the |
||||
* partial merge result (i.e. merge result minus the unmerged entries) |
||||
* are stored in 'local_tree', and the SHA1 or the resulting commit |
||||
* (to be amended when the conflicts have been resolved) is written into |
||||
* 'result_sha1'. The unmerged entries are written into the |
||||
* .git/NOTES_MERGE_WORKTREE directory with conflict markers. |
||||
* -1 is returned. |
||||
* |
||||
* Both o->local_ref and o->remote_ref must be given (non-NULL), but either ref |
||||
* (although not both) may refer to a non-existing notes ref, in which case |
||||
* that notes ref is interpreted as an empty notes tree, and the merge |
||||
* trivially results in what the other ref points to. |
||||
*/ |
||||
int notes_merge(struct notes_merge_options *o, |
||||
struct notes_tree *local_tree, |
||||
unsigned char *result_sha1); |
||||
|
||||
/* |
||||
* Finalize conflict resolution from an earlier notes_merge() |
||||
* |
||||
* The given notes tree 'partial_tree' must be the notes_tree corresponding to |
||||
* the given 'partial_commit', the partial result commit created by a previous |
||||
* call to notes_merge(). |
||||
* |
||||
* This function will add the (now resolved) notes in .git/NOTES_MERGE_WORKTREE |
||||
* to 'partial_tree', and create a final notes merge commit, the SHA1 of which |
||||
* will be stored in 'result_sha1'. |
||||
*/ |
||||
int notes_merge_commit(struct notes_merge_options *o, |
||||
struct notes_tree *partial_tree, |
||||
struct commit *partial_commit, |
||||
unsigned char *result_sha1); |
||||
|
||||
/* |
||||
* Abort conflict resolution from an earlier notes_merge() |
||||
* |
||||
* Removes the notes merge worktree in .git/NOTES_MERGE_WORKTREE. |
||||
*/ |
||||
int notes_merge_abort(struct notes_merge_options *o); |
||||
|
||||
#endif |
@ -0,0 +1,368 @@
@@ -0,0 +1,368 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2010 Johan Herland |
||||
# |
||||
|
||||
test_description='Test merging of notes trees' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
test_expect_success setup ' |
||||
test_commit 1st && |
||||
test_commit 2nd && |
||||
test_commit 3rd && |
||||
test_commit 4th && |
||||
test_commit 5th && |
||||
# Create notes on 4 first commits |
||||
git config core.notesRef refs/notes/x && |
||||
git notes add -m "Notes on 1st commit" 1st && |
||||
git notes add -m "Notes on 2nd commit" 2nd && |
||||
git notes add -m "Notes on 3rd commit" 3rd && |
||||
git notes add -m "Notes on 4th commit" 4th |
||||
' |
||||
|
||||
commit_sha1=$(git rev-parse 1st^{commit}) |
||||
commit_sha2=$(git rev-parse 2nd^{commit}) |
||||
commit_sha3=$(git rev-parse 3rd^{commit}) |
||||
commit_sha4=$(git rev-parse 4th^{commit}) |
||||
commit_sha5=$(git rev-parse 5th^{commit}) |
||||
|
||||
verify_notes () { |
||||
notes_ref="$1" |
||||
git -c core.notesRef="refs/notes/$notes_ref" notes | |
||||
sort >"output_notes_$notes_ref" && |
||||
test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" && |
||||
git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \ |
||||
>"output_log_$notes_ref" && |
||||
test_cmp "expect_log_$notes_ref" "output_log_$notes_ref" |
||||
} |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
5e93d24084d32e1cb61f7070505b9d2530cca987 $commit_sha4 |
||||
8366731eeee53787d2bdf8fc1eff7d94757e8da0 $commit_sha3 |
||||
eede89064cd42441590d6afec6c37b321ada3389 $commit_sha2 |
||||
daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
Notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
Notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
Notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
Notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'verify initial notes (x)' ' |
||||
verify_notes x |
||||
' |
||||
|
||||
cp expect_notes_x expect_notes_y |
||||
cp expect_log_x expect_log_y |
||||
|
||||
test_expect_success 'fail to merge empty notes ref into empty notes ref (z => y)' ' |
||||
test_must_fail git -c "core.notesRef=refs/notes/y" notes merge z |
||||
' |
||||
|
||||
test_expect_success 'fail to merge into various non-notes refs' ' |
||||
test_must_fail git -c "core.notesRef=refs/notes" notes merge x && |
||||
test_must_fail git -c "core.notesRef=refs/notes/" notes merge x && |
||||
mkdir -p .git/refs/notes/dir && |
||||
test_must_fail git -c "core.notesRef=refs/notes/dir" notes merge x && |
||||
test_must_fail git -c "core.notesRef=refs/notes/dir/" notes merge x && |
||||
test_must_fail git -c "core.notesRef=refs/heads/master" notes merge x && |
||||
test_must_fail git -c "core.notesRef=refs/notes/y:" notes merge x && |
||||
test_must_fail git -c "core.notesRef=refs/notes/y:foo" notes merge x && |
||||
test_must_fail git -c "core.notesRef=refs/notes/foo^{bar" notes merge x |
||||
' |
||||
|
||||
test_expect_success 'fail to merge various non-note-trees' ' |
||||
git config core.notesRef refs/notes/y && |
||||
test_must_fail git notes merge refs/notes && |
||||
test_must_fail git notes merge refs/notes/ && |
||||
test_must_fail git notes merge refs/notes/dir && |
||||
test_must_fail git notes merge refs/notes/dir/ && |
||||
test_must_fail git notes merge refs/heads/master && |
||||
test_must_fail git notes merge x: && |
||||
test_must_fail git notes merge x:foo && |
||||
test_must_fail git notes merge foo^{bar |
||||
' |
||||
|
||||
test_expect_success 'merge notes into empty notes ref (x => y)' ' |
||||
git config core.notesRef refs/notes/y && |
||||
git notes merge x && |
||||
verify_notes y && |
||||
# x and y should point to the same notes commit |
||||
test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
test_expect_success 'merge empty notes ref (z => y)' ' |
||||
git notes merge z && |
||||
# y should not change (still == x) |
||||
test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
test_expect_success 'change notes on other notes ref (y)' ' |
||||
# Not touching notes to 1st commit |
||||
git notes remove 2nd && |
||||
git notes append -m "More notes on 3rd commit" 3rd && |
||||
git notes add -f -m "New notes on 4th commit" 4th && |
||||
git notes add -m "Notes on 5th commit" 5th |
||||
' |
||||
|
||||
test_expect_success 'merge previous notes commit (y^ => y) => No-op' ' |
||||
pre_state="$(git rev-parse refs/notes/y)" && |
||||
git notes merge y^ && |
||||
# y should not move |
||||
test "$pre_state" = "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_y |
||||
0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5 |
||||
dec2502dac3ea161543f71930044deff93fa945c $commit_sha4 |
||||
4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3 |
||||
daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_y <<EOF |
||||
$commit_sha5 5th |
||||
Notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
New notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
Notes on 3rd commit |
||||
|
||||
More notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
|
||||
$commit_sha1 1st |
||||
Notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'verify changed notes on other notes ref (y)' ' |
||||
verify_notes y |
||||
' |
||||
|
||||
test_expect_success 'verify unchanged notes on original notes ref (x)' ' |
||||
verify_notes x |
||||
' |
||||
|
||||
test_expect_success 'merge original notes (x) into changed notes (y) => No-op' ' |
||||
git notes merge -vvv x && |
||||
verify_notes y && |
||||
verify_notes x |
||||
' |
||||
|
||||
cp expect_notes_y expect_notes_x |
||||
cp expect_log_y expect_log_x |
||||
|
||||
test_expect_success 'merge changed (y) into original (x) => Fast-forward' ' |
||||
git config core.notesRef refs/notes/x && |
||||
git notes merge y && |
||||
verify_notes x && |
||||
verify_notes y && |
||||
# x and y should point to same the notes commit |
||||
test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
test_expect_success 'merge empty notes ref (z => y)' ' |
||||
# Prepare empty (but valid) notes ref (z) |
||||
git config core.notesRef refs/notes/z && |
||||
git notes add -m "foo" && |
||||
git notes remove && |
||||
git notes >output_notes_z && |
||||
test_cmp /dev/null output_notes_z && |
||||
# Do the merge (z => y) |
||||
git config core.notesRef refs/notes/y && |
||||
git notes merge z && |
||||
verify_notes y && |
||||
# y should no longer point to the same notes commit as x |
||||
test "$(git rev-parse refs/notes/x)" != "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_y |
||||
0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5 |
||||
dec2502dac3ea161543f71930044deff93fa945c $commit_sha4 |
||||
4069cdb399fd45463ec6eef8e051a16a03592d91 $commit_sha3 |
||||
d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2 |
||||
43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_y <<EOF |
||||
$commit_sha5 5th |
||||
Notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
New notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
Notes on 3rd commit |
||||
|
||||
More notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
New notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
Notes on 1st commit |
||||
|
||||
More notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'change notes on other notes ref (y)' ' |
||||
# Append to 1st commit notes |
||||
git notes append -m "More notes on 1st commit" 1st && |
||||
# Add new notes to 2nd commit |
||||
git notes add -m "New notes on 2nd commit" 2nd && |
||||
verify_notes y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5 |
||||
1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4 |
||||
daa55ffad6cb99bf64226532147ffcaf5ce8bdd1 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha5 5th |
||||
Notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
New notes on 4th commit |
||||
|
||||
More notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
|
||||
$commit_sha1 1st |
||||
Notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'change notes on notes ref (x)' ' |
||||
git config core.notesRef refs/notes/x && |
||||
git notes remove 3rd && |
||||
git notes append -m "More notes on 4th commit" 4th && |
||||
verify_notes x |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5 |
||||
1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4 |
||||
d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2 |
||||
43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha5 5th |
||||
Notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
New notes on 4th commit |
||||
|
||||
More notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
New notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
Notes on 1st commit |
||||
|
||||
More notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge y into x => Non-conflicting 3-way merge' ' |
||||
git notes merge y && |
||||
verify_notes x && |
||||
verify_notes y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_w |
||||
05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3 |
||||
d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_w <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
|
||||
$commit_sha3 3rd |
||||
New notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
New notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'create notes on new, separate notes ref (w)' ' |
||||
git config core.notesRef refs/notes/w && |
||||
# Add same note as refs/notes/y on 2nd commit |
||||
git notes add -m "New notes on 2nd commit" 2nd && |
||||
# Add new note on 3rd commit (non-conflicting) |
||||
git notes add -m "New notes on 3rd commit" 3rd && |
||||
# Verify state of notes on new, separate notes ref (w) |
||||
verify_notes w |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
0f2efbd00262f2fd41dfae33df8765618eeacd99 $commit_sha5 |
||||
1f257a3a90328557c452f0817d6cc50c89d315d4 $commit_sha4 |
||||
05a4927951bcef347f51486575b878b2b60137f2 $commit_sha3 |
||||
d000d30e6ddcfce3a8122c403226a2ce2fd04d9d $commit_sha2 |
||||
43add6bd0c8c0bc871ac7991e0f5573cfba27804 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha5 5th |
||||
Notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
New notes on 4th commit |
||||
|
||||
More notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
New notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
New notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
Notes on 1st commit |
||||
|
||||
More notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge w into x => Non-conflicting history-less merge' ' |
||||
git config core.notesRef refs/notes/x && |
||||
git notes merge w && |
||||
# Verify new state of notes on other notes ref (x) |
||||
verify_notes x && |
||||
# Also verify that nothing changed on other notes refs (y and w) |
||||
verify_notes y && |
||||
verify_notes w |
||||
' |
||||
|
||||
test_done |
@ -0,0 +1,647 @@
@@ -0,0 +1,647 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2010 Johan Herland |
||||
# |
||||
|
||||
test_description='Test notes merging with auto-resolving strategies' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
# Set up a notes merge scenario with all kinds of potential conflicts |
||||
test_expect_success 'setup commits' ' |
||||
test_commit 1st && |
||||
test_commit 2nd && |
||||
test_commit 3rd && |
||||
test_commit 4th && |
||||
test_commit 5th && |
||||
test_commit 6th && |
||||
test_commit 7th && |
||||
test_commit 8th && |
||||
test_commit 9th && |
||||
test_commit 10th && |
||||
test_commit 11th && |
||||
test_commit 12th && |
||||
test_commit 13th && |
||||
test_commit 14th && |
||||
test_commit 15th |
||||
' |
||||
|
||||
commit_sha1=$(git rev-parse 1st^{commit}) |
||||
commit_sha2=$(git rev-parse 2nd^{commit}) |
||||
commit_sha3=$(git rev-parse 3rd^{commit}) |
||||
commit_sha4=$(git rev-parse 4th^{commit}) |
||||
commit_sha5=$(git rev-parse 5th^{commit}) |
||||
commit_sha6=$(git rev-parse 6th^{commit}) |
||||
commit_sha7=$(git rev-parse 7th^{commit}) |
||||
commit_sha8=$(git rev-parse 8th^{commit}) |
||||
commit_sha9=$(git rev-parse 9th^{commit}) |
||||
commit_sha10=$(git rev-parse 10th^{commit}) |
||||
commit_sha11=$(git rev-parse 11th^{commit}) |
||||
commit_sha12=$(git rev-parse 12th^{commit}) |
||||
commit_sha13=$(git rev-parse 13th^{commit}) |
||||
commit_sha14=$(git rev-parse 14th^{commit}) |
||||
commit_sha15=$(git rev-parse 15th^{commit}) |
||||
|
||||
verify_notes () { |
||||
notes_ref="$1" |
||||
suffix="$2" |
||||
git -c core.notesRef="refs/notes/$notes_ref" notes | |
||||
sort >"output_notes_$suffix" && |
||||
test_cmp "expect_notes_$suffix" "output_notes_$suffix" && |
||||
git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \ |
||||
>"output_log_$suffix" && |
||||
test_cmp "expect_log_$suffix" "output_log_$suffix" |
||||
} |
||||
|
||||
test_expect_success 'setup merge base (x)' ' |
||||
git config core.notesRef refs/notes/x && |
||||
git notes add -m "x notes on 6th commit" 6th && |
||||
git notes add -m "x notes on 7th commit" 7th && |
||||
git notes add -m "x notes on 8th commit" 8th && |
||||
git notes add -m "x notes on 9th commit" 9th && |
||||
git notes add -m "x notes on 10th commit" 10th && |
||||
git notes add -m "x notes on 11th commit" 11th && |
||||
git notes add -m "x notes on 12th commit" 12th && |
||||
git notes add -m "x notes on 13th commit" 13th && |
||||
git notes add -m "x notes on 14th commit" 14th && |
||||
git notes add -m "x notes on 15th commit" 15th |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
457a85d6c814ea208550f15fcc48f804ac8dc023 $commit_sha15 |
||||
b0c95b954301d69da2bc3723f4cb1680d355937c $commit_sha14 |
||||
5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13 |
||||
dd161bc149470fd890dd4ab52a4cbd79bbd18c36 $commit_sha12 |
||||
7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9 |
||||
a3daf8a1e4e5dc3409a303ad8481d57bfea7f5d6 $commit_sha8 |
||||
897003322b53bc6ca098e9324ee508362347e734 $commit_sha7 |
||||
11d97fdebfa5ceee540a3da07bce6fa0222bc082 $commit_sha6 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha15 15th |
||||
x notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
x notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
x notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
x notes on 12th commit |
||||
|
||||
$commit_sha11 11th |
||||
x notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
x notes on 9th commit |
||||
|
||||
$commit_sha8 8th |
||||
x notes on 8th commit |
||||
|
||||
$commit_sha7 7th |
||||
x notes on 7th commit |
||||
|
||||
$commit_sha6 6th |
||||
x notes on 6th commit |
||||
|
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'verify state of merge base (x)' 'verify_notes x x' |
||||
|
||||
test_expect_success 'setup local branch (y)' ' |
||||
git update-ref refs/notes/y refs/notes/x && |
||||
git config core.notesRef refs/notes/y && |
||||
git notes add -f -m "y notes on 3rd commit" 3rd && |
||||
git notes add -f -m "y notes on 4th commit" 4th && |
||||
git notes add -f -m "y notes on 5th commit" 5th && |
||||
git notes remove 6th && |
||||
git notes remove 7th && |
||||
git notes remove 8th && |
||||
git notes add -f -m "y notes on 12th commit" 12th && |
||||
git notes add -f -m "y notes on 13th commit" 13th && |
||||
git notes add -f -m "y notes on 14th commit" 14th && |
||||
git notes add -f -m "y notes on 15th commit" 15th |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_y |
||||
68b8630d25516028bed862719855b3d6768d7833 $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13 |
||||
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12 |
||||
7abbc45126d680336fb24294f013a7cdfa3ed545 $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
20c613c835011c48a5abe29170a2402ca6354910 $commit_sha9 |
||||
154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
EOF |
||||
|
||||
cat >expect_log_y <<EOF |
||||
$commit_sha15 15th |
||||
y notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
y notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
y notes on 12th commit |
||||
|
||||
$commit_sha11 11th |
||||
x notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
x notes on 9th commit |
||||
|
||||
$commit_sha8 8th |
||||
|
||||
$commit_sha7 7th |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
y notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'verify state of local branch (y)' 'verify_notes y y' |
||||
|
||||
test_expect_success 'setup remote branch (z)' ' |
||||
git update-ref refs/notes/z refs/notes/x && |
||||
git config core.notesRef refs/notes/z && |
||||
git notes add -f -m "z notes on 2nd commit" 2nd && |
||||
git notes add -f -m "y notes on 4th commit" 4th && |
||||
git notes add -f -m "z notes on 5th commit" 5th && |
||||
git notes remove 6th && |
||||
git notes add -f -m "z notes on 8th commit" 8th && |
||||
git notes remove 9th && |
||||
git notes add -f -m "z notes on 11th commit" 11th && |
||||
git notes remove 12th && |
||||
git notes add -f -m "y notes on 14th commit" 14th && |
||||
git notes add -f -m "z notes on 15th commit" 15th |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_z |
||||
9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
5d30216a129eeffa97d9694ffe8c74317a560315 $commit_sha13 |
||||
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8 |
||||
897003322b53bc6ca098e9324ee508362347e734 $commit_sha7 |
||||
99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_z <<EOF |
||||
$commit_sha15 15th |
||||
z notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
x notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
|
||||
$commit_sha11 11th |
||||
z notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
|
||||
$commit_sha8 8th |
||||
z notes on 8th commit |
||||
|
||||
$commit_sha7 7th |
||||
x notes on 7th commit |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
z notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'verify state of remote branch (z)' 'verify_notes z z' |
||||
|
||||
# At this point, before merging z into y, we have the following status: |
||||
# |
||||
# commit | base/x | local/y | remote/z | diff from x to y/z | result |
||||
# -------|---------|---------|----------|----------------------------|------- |
||||
# 1st | [none] | [none] | [none] | unchanged / unchanged | [none] |
||||
# 2nd | [none] | [none] | 283b482 | unchanged / added | 283b482 |
||||
# 3rd | [none] | 5772f42 | [none] | added / unchanged | 5772f42 |
||||
# 4th | [none] | e2bfd06 | e2bfd06 | added / added (same) | e2bfd06 |
||||
# 5th | [none] | 154508c | 99fc34a | added / added (diff) | ??? |
||||
# 6th | 11d97fd | [none] | [none] | removed / removed | [none] |
||||
# 7th | 8970033 | [none] | 8970033 | removed / unchanged | [none] |
||||
# 8th | a3daf8a | [none] | 851e163 | removed / changed | ??? |
||||
# 9th | 20c613c | 20c613c | [none] | unchanged / removed | [none] |
||||
# 10th | b8d03e1 | b8d03e1 | b8d03e1 | unchanged / unchanged | b8d03e1 |
||||
# 11th | 7abbc45 | 7abbc45 | 7e3c535 | unchanged / changed | 7e3c535 |
||||
# 12th | dd161bc | a66055f | [none] | changed / removed | ??? |
||||
# 13th | 5d30216 | 3a631fd | 5d30216 | changed / unchanged | 3a631fd |
||||
# 14th | b0c95b9 | 5de7ea7 | 5de7ea7 | changed / changed (same) | 5de7ea7 |
||||
# 15th | 457a85d | 68b8630 | 9b4b2c6 | changed / changed (diff) | ??? |
||||
|
||||
test_expect_success 'merge z into y with invalid strategy => Fail/No changes' ' |
||||
git config core.notesRef refs/notes/y && |
||||
test_must_fail git notes merge --strategy=foo z && |
||||
# Verify no changes (y) |
||||
verify_notes y y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_ours |
||||
68b8630d25516028bed862719855b3d6768d7833 $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13 |
||||
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12 |
||||
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
154508c7a0bcad82b6fe4b472bc4c26b3bf0825b $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_ours <<EOF |
||||
$commit_sha15 15th |
||||
y notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
y notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
y notes on 12th commit |
||||
|
||||
$commit_sha11 11th |
||||
z notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
|
||||
$commit_sha8 8th |
||||
|
||||
$commit_sha7 7th |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
y notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge z into y with "ours" strategy => Non-conflicting 3-way merge' ' |
||||
git notes merge --strategy=ours z && |
||||
verify_notes y ours |
||||
' |
||||
|
||||
test_expect_success 'reset to pre-merge state (y)' ' |
||||
git update-ref refs/notes/y refs/notes/y^1 && |
||||
# Verify pre-merge state |
||||
verify_notes y y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_theirs |
||||
9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13 |
||||
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8 |
||||
99fc34adfc400b95c67b013115e37e31aa9a6d23 $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_theirs <<EOF |
||||
$commit_sha15 15th |
||||
z notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
y notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
|
||||
$commit_sha11 11th |
||||
z notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
|
||||
$commit_sha8 8th |
||||
z notes on 8th commit |
||||
|
||||
$commit_sha7 7th |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
z notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge z into y with "theirs" strategy => Non-conflicting 3-way merge' ' |
||||
git notes merge --strategy=theirs z && |
||||
verify_notes y theirs |
||||
' |
||||
|
||||
test_expect_success 'reset to pre-merge state (y)' ' |
||||
git update-ref refs/notes/y refs/notes/y^1 && |
||||
# Verify pre-merge state |
||||
verify_notes y y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_union |
||||
7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13 |
||||
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12 |
||||
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8 |
||||
6c841cc36ea496027290967ca96bd2bef54dbb47 $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_union <<EOF |
||||
$commit_sha15 15th |
||||
y notes on 15th commit |
||||
|
||||
z notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
y notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
y notes on 12th commit |
||||
|
||||
$commit_sha11 11th |
||||
z notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
|
||||
$commit_sha8 8th |
||||
z notes on 8th commit |
||||
|
||||
$commit_sha7 7th |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
y notes on 5th commit |
||||
|
||||
z notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge z into y with "union" strategy => Non-conflicting 3-way merge' ' |
||||
git notes merge --strategy=union z && |
||||
verify_notes y union |
||||
' |
||||
|
||||
test_expect_success 'reset to pre-merge state (y)' ' |
||||
git update-ref refs/notes/y refs/notes/y^1 && |
||||
# Verify pre-merge state |
||||
verify_notes y y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_union2 |
||||
d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13 |
||||
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12 |
||||
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8 |
||||
357b6ca14c7afd59b7f8b8aaaa6b8b723771135b $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_union2 <<EOF |
||||
$commit_sha15 15th |
||||
z notes on 15th commit |
||||
|
||||
y notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
y notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
y notes on 12th commit |
||||
|
||||
$commit_sha11 11th |
||||
z notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
|
||||
$commit_sha8 8th |
||||
z notes on 8th commit |
||||
|
||||
$commit_sha7 7th |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
z notes on 5th commit |
||||
|
||||
y notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge y into z with "union" strategy => Non-conflicting 3-way merge' ' |
||||
git config core.notesRef refs/notes/z && |
||||
git notes merge --strategy=union y && |
||||
verify_notes z union2 |
||||
' |
||||
|
||||
test_expect_success 'reset to pre-merge state (z)' ' |
||||
git update-ref refs/notes/z refs/notes/z^1 && |
||||
# Verify pre-merge state |
||||
verify_notes z z |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_cat_sort_uniq |
||||
6be90240b5f54594203e25d9f2f64b7567175aee $commit_sha15 |
||||
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 |
||||
3a631fdb6f41b05b55d8f4baf20728ba8f6fccbc $commit_sha13 |
||||
a66055fa82f7a03fe0c02a6aba3287a85abf7c62 $commit_sha12 |
||||
7e3c53503a3db8dd996cb62e37c66e070b44b54d $commit_sha11 |
||||
b8d03e173f67f6505a76f6e00cf93440200dd9be $commit_sha10 |
||||
851e1638784a884c7dd26c5d41f3340f6387413a $commit_sha8 |
||||
660311d7f78dc53db12ac373a43fca7465381a7e $commit_sha5 |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_cat_sort_uniq <<EOF |
||||
$commit_sha15 15th |
||||
y notes on 15th commit |
||||
z notes on 15th commit |
||||
|
||||
$commit_sha14 14th |
||||
y notes on 14th commit |
||||
|
||||
$commit_sha13 13th |
||||
y notes on 13th commit |
||||
|
||||
$commit_sha12 12th |
||||
y notes on 12th commit |
||||
|
||||
$commit_sha11 11th |
||||
z notes on 11th commit |
||||
|
||||
$commit_sha10 10th |
||||
x notes on 10th commit |
||||
|
||||
$commit_sha9 9th |
||||
|
||||
$commit_sha8 8th |
||||
z notes on 8th commit |
||||
|
||||
$commit_sha7 7th |
||||
|
||||
$commit_sha6 6th |
||||
|
||||
$commit_sha5 5th |
||||
y notes on 5th commit |
||||
z notes on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflicting 3-way merge' ' |
||||
git notes merge --strategy=cat_sort_uniq y && |
||||
verify_notes z cat_sort_uniq |
||||
' |
||||
|
||||
test_done |
@ -0,0 +1,556 @@
@@ -0,0 +1,556 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2010 Johan Herland |
||||
# |
||||
|
||||
test_description='Test notes merging with manual conflict resolution' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
# Set up a notes merge scenario with different kinds of conflicts |
||||
test_expect_success 'setup commits' ' |
||||
test_commit 1st && |
||||
test_commit 2nd && |
||||
test_commit 3rd && |
||||
test_commit 4th && |
||||
test_commit 5th |
||||
' |
||||
|
||||
commit_sha1=$(git rev-parse 1st^{commit}) |
||||
commit_sha2=$(git rev-parse 2nd^{commit}) |
||||
commit_sha3=$(git rev-parse 3rd^{commit}) |
||||
commit_sha4=$(git rev-parse 4th^{commit}) |
||||
commit_sha5=$(git rev-parse 5th^{commit}) |
||||
|
||||
verify_notes () { |
||||
notes_ref="$1" |
||||
git -c core.notesRef="refs/notes/$notes_ref" notes | |
||||
sort >"output_notes_$notes_ref" && |
||||
test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" && |
||||
git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \ |
||||
>"output_log_$notes_ref" && |
||||
test_cmp "expect_log_$notes_ref" "output_log_$notes_ref" |
||||
} |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4 |
||||
e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3 |
||||
ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
x notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
x notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
x notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'setup merge base (x)' ' |
||||
git config core.notesRef refs/notes/x && |
||||
git notes add -m "x notes on 2nd commit" 2nd && |
||||
git notes add -m "x notes on 3rd commit" 3rd && |
||||
git notes add -m "x notes on 4th commit" 4th && |
||||
verify_notes x |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_y |
||||
e2bfd06a37dd2031684a59a6e2b033e212239c78 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
b0a6021ec006d07e80e9b20ec9b444cbd9d560d3 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_y <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
y notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
|
||||
$commit_sha1 1st |
||||
y notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'setup local branch (y)' ' |
||||
git update-ref refs/notes/y refs/notes/x && |
||||
git config core.notesRef refs/notes/y && |
||||
git notes add -f -m "y notes on 1st commit" 1st && |
||||
git notes remove 2nd && |
||||
git notes add -f -m "y notes on 3rd commit" 3rd && |
||||
git notes add -f -m "y notes on 4th commit" 4th && |
||||
verify_notes y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_z |
||||
cff59c793c20bb49a4e01bc06fb06bad642e0d54 $commit_sha4 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_z <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
z notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
z notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'setup remote branch (z)' ' |
||||
git update-ref refs/notes/z refs/notes/x && |
||||
git config core.notesRef refs/notes/z && |
||||
git notes add -f -m "z notes on 1st commit" 1st && |
||||
git notes add -f -m "z notes on 2nd commit" 2nd && |
||||
git notes remove 3rd && |
||||
git notes add -f -m "z notes on 4th commit" 4th && |
||||
verify_notes z |
||||
' |
||||
|
||||
# At this point, before merging z into y, we have the following status: |
||||
# |
||||
# commit | base/x | local/y | remote/z | diff from x to y/z |
||||
# -------|---------|---------|----------|--------------------------- |
||||
# 1st | [none] | b0a6021 | 0a81da8 | added / added (diff) |
||||
# 2nd | ceefa67 | [none] | 283b482 | removed / changed |
||||
# 3rd | e5388c1 | 5772f42 | [none] | changed / removed |
||||
# 4th | 6e8e3fe | e2bfd06 | cff59c7 | changed / changed (diff) |
||||
# 5th | [none] | [none] | [none] | [none] |
||||
|
||||
cat <<EOF | sort >expect_conflicts |
||||
$commit_sha1 |
||||
$commit_sha2 |
||||
$commit_sha3 |
||||
$commit_sha4 |
||||
EOF |
||||
|
||||
cat >expect_conflict_$commit_sha1 <<EOF |
||||
<<<<<<< refs/notes/m |
||||
y notes on 1st commit |
||||
======= |
||||
z notes on 1st commit |
||||
>>>>>>> refs/notes/z |
||||
EOF |
||||
|
||||
cat >expect_conflict_$commit_sha2 <<EOF |
||||
z notes on 2nd commit |
||||
EOF |
||||
|
||||
cat >expect_conflict_$commit_sha3 <<EOF |
||||
y notes on 3rd commit |
||||
EOF |
||||
|
||||
cat >expect_conflict_$commit_sha4 <<EOF |
||||
<<<<<<< refs/notes/m |
||||
y notes on 4th commit |
||||
======= |
||||
z notes on 4th commit |
||||
>>>>>>> refs/notes/z |
||||
EOF |
||||
|
||||
cp expect_notes_y expect_notes_m |
||||
cp expect_log_y expect_log_m |
||||
|
||||
git rev-parse refs/notes/y > pre_merge_y |
||||
git rev-parse refs/notes/z > pre_merge_z |
||||
|
||||
test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' |
||||
git update-ref refs/notes/m refs/notes/y && |
||||
git config core.notesRef refs/notes/m && |
||||
test_must_fail git notes merge z >output && |
||||
# Output should point to where to resolve conflicts |
||||
grep -q "\\.git/NOTES_MERGE_WORKTREE" output && |
||||
# Inspect merge conflicts |
||||
ls .git/NOTES_MERGE_WORKTREE >output_conflicts && |
||||
test_cmp expect_conflicts output_conflicts && |
||||
( for f in $(cat expect_conflicts); do |
||||
test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" || |
||||
exit 1 |
||||
done ) && |
||||
# Verify that current notes tree (pre-merge) has not changed (m == y) |
||||
verify_notes y && |
||||
verify_notes m && |
||||
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_z |
||||
00494adecf2d9635a02fa431308d67993f853968 $commit_sha4 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
0a81da8956346e19bcb27a906f04af327e03e31b $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_z <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
z notes on 4th commit |
||||
|
||||
More z notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
z notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'change notes in z' ' |
||||
git notes --ref z append -m "More z notes on 4th commit" 4th && |
||||
verify_notes z |
||||
' |
||||
|
||||
test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' ' |
||||
test -d .git/NOTES_MERGE_WORKTREE && |
||||
test_must_fail git notes merge z >output 2>&1 && |
||||
# Output should indicate what is wrong |
||||
grep -q "\\.git/NOTES_MERGE_\\* exists" output |
||||
' |
||||
|
||||
# Setup non-conflicting merge between x and new notes ref w |
||||
|
||||
cat <<EOF | sort >expect_notes_w |
||||
ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2 |
||||
f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_w <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
x notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
w notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'setup unrelated notes ref (w)' ' |
||||
git config core.notesRef refs/notes/w && |
||||
git notes add -m "w notes on 1st commit" 1st && |
||||
git notes add -m "x notes on 2nd commit" 2nd && |
||||
verify_notes w |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_w |
||||
6e8e3febca3c2bb896704335cc4d0c34cb2f8715 $commit_sha4 |
||||
e5388c10860456ee60673025345fe2e153eb8cf8 $commit_sha3 |
||||
ceefa674873670e7ecd131814d909723cce2b669 $commit_sha2 |
||||
f75d1df88cbfe4258d49852f26cfc83f2ad4494b $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_w <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
x notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
x notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
x notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
w notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' ' |
||||
test -d .git/NOTES_MERGE_WORKTREE && |
||||
git notes merge x && |
||||
verify_notes w && |
||||
# Verify that other notes refs has not changed (x and y) |
||||
verify_notes x && |
||||
verify_notes y |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_m |
||||
021faa20e931fb48986ffc6282b4bb05553ac946 $commit_sha4 |
||||
5772f42408c0dd6f097a7ca2d24de0e78d1c46b1 $commit_sha3 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 5th |
||||
|
||||
$commit_sha4 4th |
||||
y and z notes on 4th commit |
||||
|
||||
$commit_sha3 3rd |
||||
y notes on 3rd commit |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
y and z notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'finalize conflicting merge (z => m)' ' |
||||
# Resolve conflicts and finalize merge |
||||
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF && |
||||
y and z notes on 1st commit |
||||
EOF |
||||
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF && |
||||
y and z notes on 4th commit |
||||
EOF |
||||
git notes merge --commit && |
||||
# No .git/NOTES_MERGE_* files left |
||||
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && |
||||
test_cmp /dev/null output && |
||||
# Merge commit has pre-merge y and pre-merge z as parents |
||||
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && |
||||
test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && |
||||
# Merge commit mentions the notes refs merged |
||||
git log -1 --format=%B refs/notes/m > merge_commit_msg && |
||||
grep -q refs/notes/m merge_commit_msg && |
||||
grep -q refs/notes/z merge_commit_msg && |
||||
# Merge commit mentions conflicting notes |
||||
grep -q "Conflicts" merge_commit_msg && |
||||
( for sha1 in $(cat expect_conflicts); do |
||||
grep -q "$sha1" merge_commit_msg || |
||||
exit 1 |
||||
done ) && |
||||
# Verify contents of merge result |
||||
verify_notes m && |
||||
# Verify that other notes refs has not changed (w, x, y and z) |
||||
verify_notes w && |
||||
verify_notes x && |
||||
verify_notes y && |
||||
verify_notes z |
||||
' |
||||
|
||||
cat >expect_conflict_$commit_sha4 <<EOF |
||||
<<<<<<< refs/notes/m |
||||
y notes on 4th commit |
||||
======= |
||||
z notes on 4th commit |
||||
|
||||
More z notes on 4th commit |
||||
>>>>>>> refs/notes/z |
||||
EOF |
||||
|
||||
cp expect_notes_y expect_notes_m |
||||
cp expect_log_y expect_log_m |
||||
|
||||
git rev-parse refs/notes/y > pre_merge_y |
||||
git rev-parse refs/notes/z > pre_merge_z |
||||
|
||||
test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' |
||||
git update-ref refs/notes/m refs/notes/y && |
||||
git config core.notesRef refs/notes/m && |
||||
test_must_fail git notes merge z >output && |
||||
# Output should point to where to resolve conflicts |
||||
grep -q "\\.git/NOTES_MERGE_WORKTREE" output && |
||||
# Inspect merge conflicts |
||||
ls .git/NOTES_MERGE_WORKTREE >output_conflicts && |
||||
test_cmp expect_conflicts output_conflicts && |
||||
( for f in $(cat expect_conflicts); do |
||||
test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" || |
||||
exit 1 |
||||
done ) && |
||||
# Verify that current notes tree (pre-merge) has not changed (m == y) |
||||
verify_notes y && |
||||
verify_notes m && |
||||
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" |
||||
' |
||||
|
||||
test_expect_success 'abort notes merge' ' |
||||
git notes merge --abort && |
||||
# No .git/NOTES_MERGE_* files left |
||||
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && |
||||
test_cmp /dev/null output && |
||||
# m has not moved (still == y) |
||||
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" |
||||
# Verify that other notes refs has not changed (w, x, y and z) |
||||
verify_notes w && |
||||
verify_notes x && |
||||
verify_notes y && |
||||
verify_notes z |
||||
' |
||||
|
||||
git rev-parse refs/notes/y > pre_merge_y |
||||
git rev-parse refs/notes/z > pre_merge_z |
||||
|
||||
test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' |
||||
test_must_fail git notes merge z >output && |
||||
# Output should point to where to resolve conflicts |
||||
grep -q "\\.git/NOTES_MERGE_WORKTREE" output && |
||||
# Inspect merge conflicts |
||||
ls .git/NOTES_MERGE_WORKTREE >output_conflicts && |
||||
test_cmp expect_conflicts output_conflicts && |
||||
( for f in $(cat expect_conflicts); do |
||||
test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" || |
||||
exit 1 |
||||
done ) && |
||||
# Verify that current notes tree (pre-merge) has not changed (m == y) |
||||
verify_notes y && |
||||
verify_notes m && |
||||
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_m |
||||
304dfb4325cf243025b9957486eb605a9b51c199 $commit_sha5 |
||||
283b48219aee9a4105f6cab337e789065c82c2b9 $commit_sha2 |
||||
0a59e787e6d688aa6309e56e8c1b89431a0fc1c1 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 5th |
||||
new note on 5th commit |
||||
|
||||
$commit_sha4 4th |
||||
|
||||
$commit_sha3 3rd |
||||
|
||||
$commit_sha2 2nd |
||||
z notes on 2nd commit |
||||
|
||||
$commit_sha1 1st |
||||
y and z notes on 1st commit |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'add + remove notes in finalized merge (z => m)' ' |
||||
# Resolve one conflict |
||||
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF && |
||||
y and z notes on 1st commit |
||||
EOF |
||||
# Remove another conflict |
||||
rm .git/NOTES_MERGE_WORKTREE/$commit_sha4 && |
||||
# Remove a D/F conflict |
||||
rm .git/NOTES_MERGE_WORKTREE/$commit_sha3 && |
||||
# Add a new note |
||||
echo "new note on 5th commit" > .git/NOTES_MERGE_WORKTREE/$commit_sha5 && |
||||
# Finalize merge |
||||
git notes merge --commit && |
||||
# No .git/NOTES_MERGE_* files left |
||||
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && |
||||
test_cmp /dev/null output && |
||||
# Merge commit has pre-merge y and pre-merge z as parents |
||||
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && |
||||
test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && |
||||
# Merge commit mentions the notes refs merged |
||||
git log -1 --format=%B refs/notes/m > merge_commit_msg && |
||||
grep -q refs/notes/m merge_commit_msg && |
||||
grep -q refs/notes/z merge_commit_msg && |
||||
# Merge commit mentions conflicting notes |
||||
grep -q "Conflicts" merge_commit_msg && |
||||
( for sha1 in $(cat expect_conflicts); do |
||||
grep -q "$sha1" merge_commit_msg || |
||||
exit 1 |
||||
done ) && |
||||
# Verify contents of merge result |
||||
verify_notes m && |
||||
# Verify that other notes refs has not changed (w, x, y and z) |
||||
verify_notes w && |
||||
verify_notes x && |
||||
verify_notes y && |
||||
verify_notes z |
||||
' |
||||
|
||||
cp expect_notes_y expect_notes_m |
||||
cp expect_log_y expect_log_m |
||||
|
||||
test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' |
||||
git update-ref refs/notes/m refs/notes/y && |
||||
test_must_fail git notes merge z >output && |
||||
# Output should point to where to resolve conflicts |
||||
grep -q "\\.git/NOTES_MERGE_WORKTREE" output && |
||||
# Inspect merge conflicts |
||||
ls .git/NOTES_MERGE_WORKTREE >output_conflicts && |
||||
test_cmp expect_conflicts output_conflicts && |
||||
( for f in $(cat expect_conflicts); do |
||||
test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" || |
||||
exit 1 |
||||
done ) && |
||||
# Verify that current notes tree (pre-merge) has not changed (m == y) |
||||
verify_notes y && |
||||
verify_notes m && |
||||
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" |
||||
' |
||||
|
||||
cp expect_notes_w expect_notes_m |
||||
cp expect_log_w expect_log_m |
||||
|
||||
test_expect_success 'reset notes ref m to somewhere else (w)' ' |
||||
git update-ref refs/notes/m refs/notes/w && |
||||
verify_notes m && |
||||
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" |
||||
' |
||||
|
||||
test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' ' |
||||
# Resolve conflicts |
||||
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF && |
||||
y and z notes on 1st commit |
||||
EOF |
||||
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha4 <<EOF && |
||||
y and z notes on 4th commit |
||||
EOF |
||||
# Fail to finalize merge |
||||
test_must_fail git notes merge --commit >output 2>&1 && |
||||
# .git/NOTES_MERGE_* must remain |
||||
test -f .git/NOTES_MERGE_PARTIAL && |
||||
test -f .git/NOTES_MERGE_REF && |
||||
test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 && |
||||
test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 && |
||||
test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 && |
||||
test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 && |
||||
# Refs are unchanged |
||||
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" |
||||
test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" |
||||
test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" |
||||
# Mention refs/notes/m, and its current and expected value in output |
||||
grep -q "refs/notes/m" output && |
||||
grep -q "$(git rev-parse refs/notes/m)" output && |
||||
grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output && |
||||
# Verify that other notes refs has not changed (w, x, y and z) |
||||
verify_notes w && |
||||
verify_notes x && |
||||
verify_notes y && |
||||
verify_notes z |
||||
' |
||||
|
||||
test_expect_success 'resolve situation by aborting the notes merge' ' |
||||
git notes merge --abort && |
||||
# No .git/NOTES_MERGE_* files left |
||||
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && |
||||
test_cmp /dev/null output && |
||||
# m has not moved (still == w) |
||||
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" |
||||
# Verify that other notes refs has not changed (w, x, y and z) |
||||
verify_notes w && |
||||
verify_notes x && |
||||
verify_notes y && |
||||
verify_notes z |
||||
' |
||||
|
||||
test_done |
@ -0,0 +1,436 @@
@@ -0,0 +1,436 @@
|
||||
#!/bin/sh |
||||
# |
||||
# Copyright (c) 2010 Johan Herland |
||||
# |
||||
|
||||
test_description='Test notes merging at various fanout levels' |
||||
|
||||
. ./test-lib.sh |
||||
|
||||
verify_notes () { |
||||
notes_ref="$1" |
||||
commit="$2" |
||||
if test -f "expect_notes_$notes_ref" |
||||
then |
||||
git -c core.notesRef="refs/notes/$notes_ref" notes | |
||||
sort >"output_notes_$notes_ref" && |
||||
test_cmp "expect_notes_$notes_ref" "output_notes_$notes_ref" || |
||||
return 1 |
||||
fi && |
||||
git -c core.notesRef="refs/notes/$notes_ref" log --format="%H %s%n%N" \ |
||||
"$commit" >"output_log_$notes_ref" && |
||||
test_cmp "expect_log_$notes_ref" "output_log_$notes_ref" |
||||
} |
||||
|
||||
verify_fanout () { |
||||
notes_ref="$1" |
||||
# Expect entire notes tree to have a fanout == 1 |
||||
git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null && |
||||
git ls-tree -r --name-only "refs/notes/$notes_ref" | |
||||
while read path |
||||
do |
||||
case "$path" in |
||||
??/??????????????????????????????????????) |
||||
: true |
||||
;; |
||||
*) |
||||
echo "Invalid path \"$path\"" && |
||||
return 1 |
||||
;; |
||||
esac |
||||
done |
||||
} |
||||
|
||||
verify_no_fanout () { |
||||
notes_ref="$1" |
||||
# Expect entire notes tree to have a fanout == 0 |
||||
git rev-parse --quiet --verify "refs/notes/$notes_ref" >/dev/null && |
||||
git ls-tree -r --name-only "refs/notes/$notes_ref" | |
||||
while read path |
||||
do |
||||
case "$path" in |
||||
????????????????????????????????????????) |
||||
: true |
||||
;; |
||||
*) |
||||
echo "Invalid path \"$path\"" && |
||||
return 1 |
||||
;; |
||||
esac |
||||
done |
||||
} |
||||
|
||||
# Set up a notes merge scenario with different kinds of conflicts |
||||
test_expect_success 'setup a few initial commits with notes (notes ref: x)' ' |
||||
git config core.notesRef refs/notes/x && |
||||
for i in 1 2 3 4 5 |
||||
do |
||||
test_commit "commit$i" >/dev/null && |
||||
git notes add -m "notes for commit$i" || return 1 |
||||
done |
||||
' |
||||
|
||||
commit_sha1=$(git rev-parse commit1^{commit}) |
||||
commit_sha2=$(git rev-parse commit2^{commit}) |
||||
commit_sha3=$(git rev-parse commit3^{commit}) |
||||
commit_sha4=$(git rev-parse commit4^{commit}) |
||||
commit_sha5=$(git rev-parse commit5^{commit}) |
||||
|
||||
cat <<EOF | sort >expect_notes_x |
||||
aed91155c7a72c2188e781fdf40e0f3761b299db $commit_sha5 |
||||
99fab268f9d7ee7b011e091a436c78def8eeee69 $commit_sha4 |
||||
953c20ae26c7aa0b428c20693fe38bc687f9d1a9 $commit_sha3 |
||||
6358796131b8916eaa2dde6902642942a1cb37e1 $commit_sha2 |
||||
b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_x <<EOF |
||||
$commit_sha5 commit5 |
||||
notes for commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
notes for commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'sanity check (x)' ' |
||||
verify_notes x commit5 && |
||||
verify_no_fanout x |
||||
' |
||||
|
||||
num=300 |
||||
|
||||
cp expect_log_x expect_log_y |
||||
|
||||
test_expect_success 'Add a few hundred commits w/notes to trigger fanout (x -> y)' ' |
||||
git update-ref refs/notes/y refs/notes/x && |
||||
git config core.notesRef refs/notes/y && |
||||
i=5 && |
||||
while test $i -lt $num |
||||
do |
||||
i=$(($i + 1)) && |
||||
test_commit "commit$i" >/dev/null && |
||||
git notes add -m "notes for commit$i" || return 1 |
||||
done && |
||||
test "$(git rev-parse refs/notes/y)" != "$(git rev-parse refs/notes/x)" && |
||||
# Expected number of commits and notes |
||||
test $(git rev-list HEAD | wc -l) = $num && |
||||
test $(git notes list | wc -l) = $num && |
||||
# 5 first notes unchanged |
||||
verify_notes y commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree has fanout (y)' 'verify_fanout y' |
||||
|
||||
test_expect_success 'No-op merge (already included) (x => y)' ' |
||||
git update-ref refs/notes/m refs/notes/y && |
||||
git config core.notesRef refs/notes/m && |
||||
git notes merge x && |
||||
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
test_expect_success 'Fast-forward merge (y => x)' ' |
||||
git update-ref refs/notes/m refs/notes/x && |
||||
git notes merge y && |
||||
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)" |
||||
' |
||||
|
||||
cat <<EOF | sort >expect_notes_z |
||||
9f506ee70e20379d7f78204c77b334f43d77410d $commit_sha3 |
||||
23a47d6ea7d589895faf800752054818e1e7627b $commit_sha2 |
||||
b02d459c32f0e68f2fe0981033bb34f38776ba47 $commit_sha1 |
||||
EOF |
||||
|
||||
cat >expect_log_z <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
notes for commit3 |
||||
|
||||
appended notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
new notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'change some of the initial 5 notes (x -> z)' ' |
||||
git update-ref refs/notes/z refs/notes/x && |
||||
git config core.notesRef refs/notes/z && |
||||
git notes add -f -m "new notes for commit2" commit2 && |
||||
git notes append -m "appended notes for commit3" commit3 && |
||||
git notes remove commit4 && |
||||
git notes remove commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree has no fanout (z)' 'verify_no_fanout z' |
||||
|
||||
cp expect_log_z expect_log_m |
||||
|
||||
test_expect_success 'successful merge without conflicts (y => z)' ' |
||||
git update-ref refs/notes/m refs/notes/z && |
||||
git config core.notesRef refs/notes/m && |
||||
git notes merge y && |
||||
verify_notes m commit5 && |
||||
# x/y/z unchanged |
||||
verify_notes x commit5 && |
||||
verify_notes y commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
cat >expect_log_w <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
other notes for commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
other notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
other notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'introduce conflicting changes (y -> w)' ' |
||||
git update-ref refs/notes/w refs/notes/y && |
||||
git config core.notesRef refs/notes/w && |
||||
git notes add -f -m "other notes for commit1" commit1 && |
||||
git notes add -f -m "other notes for commit3" commit3 && |
||||
git notes add -f -m "other notes for commit4" commit4 && |
||||
git notes remove commit5 && |
||||
verify_notes w commit5 |
||||
' |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
other notes for commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
other notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
new notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
other notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'successful merge using "ours" strategy (z => w)' ' |
||||
git update-ref refs/notes/m refs/notes/w && |
||||
git config core.notesRef refs/notes/m && |
||||
git notes merge -s ours z && |
||||
verify_notes m commit5 && |
||||
# w/x/y/z unchanged |
||||
verify_notes w commit5 && |
||||
verify_notes x commit5 && |
||||
verify_notes y commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
notes for commit3 |
||||
|
||||
appended notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
new notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
other notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'successful merge using "theirs" strategy (z => w)' ' |
||||
git update-ref refs/notes/m refs/notes/w && |
||||
git notes merge -s theirs z && |
||||
verify_notes m commit5 && |
||||
# w/x/y/z unchanged |
||||
verify_notes w commit5 && |
||||
verify_notes x commit5 && |
||||
verify_notes y commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
other notes for commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
other notes for commit3 |
||||
|
||||
notes for commit3 |
||||
|
||||
appended notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
new notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
other notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'successful merge using "union" strategy (z => w)' ' |
||||
git update-ref refs/notes/m refs/notes/w && |
||||
git notes merge -s union z && |
||||
verify_notes m commit5 && |
||||
# w/x/y/z unchanged |
||||
verify_notes w commit5 && |
||||
verify_notes x commit5 && |
||||
verify_notes y commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
other notes for commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
appended notes for commit3 |
||||
notes for commit3 |
||||
other notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
new notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
other notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'successful merge using "cat_sort_uniq" strategy (z => w)' ' |
||||
git update-ref refs/notes/m refs/notes/w && |
||||
git notes merge -s cat_sort_uniq z && |
||||
verify_notes m commit5 && |
||||
# w/x/y/z unchanged |
||||
verify_notes w commit5 && |
||||
verify_notes x commit5 && |
||||
verify_notes y commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
# We're merging z into w. Here are the conflicts we expect: |
||||
# |
||||
# commit | x -> w | x -> z | conflict? |
||||
# -------|-----------|-----------|---------- |
||||
# 1 | changed | unchanged | no, use w |
||||
# 2 | unchanged | changed | no, use z |
||||
# 3 | changed | changed | yes (w, then z in conflict markers) |
||||
# 4 | changed | deleted | yes (w) |
||||
# 5 | deleted | deleted | no, deleted |
||||
|
||||
test_expect_success 'fails to merge using "manual" strategy (z => w)' ' |
||||
git update-ref refs/notes/m refs/notes/w && |
||||
test_must_fail git notes merge z |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
cat <<EOF | sort >expect_conflicts |
||||
$commit_sha3 |
||||
$commit_sha4 |
||||
EOF |
||||
|
||||
cat >expect_conflict_$commit_sha3 <<EOF |
||||
<<<<<<< refs/notes/m |
||||
other notes for commit3 |
||||
======= |
||||
notes for commit3 |
||||
|
||||
appended notes for commit3 |
||||
>>>>>>> refs/notes/z |
||||
EOF |
||||
|
||||
cat >expect_conflict_$commit_sha4 <<EOF |
||||
other notes for commit4 |
||||
EOF |
||||
|
||||
test_expect_success 'verify conflict entries (with no fanout)' ' |
||||
ls .git/NOTES_MERGE_WORKTREE >output_conflicts && |
||||
test_cmp expect_conflicts output_conflicts && |
||||
( for f in $(cat expect_conflicts); do |
||||
test_cmp "expect_conflict_$f" ".git/NOTES_MERGE_WORKTREE/$f" || |
||||
exit 1 |
||||
done ) && |
||||
# Verify that current notes tree (pre-merge) has not changed (m == w) |
||||
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" |
||||
' |
||||
|
||||
cat >expect_log_m <<EOF |
||||
$commit_sha5 commit5 |
||||
|
||||
$commit_sha4 commit4 |
||||
other notes for commit4 |
||||
|
||||
$commit_sha3 commit3 |
||||
other notes for commit3 |
||||
|
||||
appended notes for commit3 |
||||
|
||||
$commit_sha2 commit2 |
||||
new notes for commit2 |
||||
|
||||
$commit_sha1 commit1 |
||||
other notes for commit1 |
||||
|
||||
EOF |
||||
|
||||
test_expect_success 'resolve and finalize merge (z => w)' ' |
||||
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha3 <<EOF && |
||||
other notes for commit3 |
||||
|
||||
appended notes for commit3 |
||||
EOF |
||||
git notes merge --commit && |
||||
verify_notes m commit5 && |
||||
# w/x/y/z unchanged |
||||
verify_notes w commit5 && |
||||
verify_notes x commit5 && |
||||
verify_notes y commit5 && |
||||
verify_notes z commit5 |
||||
' |
||||
|
||||
test_expect_success 'notes tree still has fanout after merge (m)' 'verify_fanout m' |
||||
|
||||
test_done |
@ -0,0 +1,313 @@
@@ -0,0 +1,313 @@
|
||||
#!/bin/sh |
||||
|
||||
test_description='test aborting in-progress merges |
||||
|
||||
Set up repo with conflicting and non-conflicting branches: |
||||
|
||||
There are three files foo/bar/baz, and the following graph illustrates the |
||||
content of these files in each commit: |
||||
|
||||
# foo/bar/baz --- foo/bar/bazz <-- master |
||||
# \ |
||||
# --- foo/barf/bazf <-- conflict_branch |
||||
# \ |
||||
# --- foo/bart/baz <-- clean_branch |
||||
|
||||
Next, test git merge --abort with the following variables: |
||||
- before/after successful merge (should fail when not in merge context) |
||||
- with/without conflicts |
||||
- clean/dirty index before merge |
||||
- clean/dirty worktree before merge |
||||
- dirty index before merge matches contents on remote branch |
||||
- changed/unchanged worktree after merge |
||||
- changed/unchanged index after merge |
||||
' |
||||
. ./test-lib.sh |
||||
|
||||
test_expect_success 'setup' ' |
||||
# Create the above repo |
||||
echo foo > foo && |
||||
echo bar > bar && |
||||
echo baz > baz && |
||||
git add foo bar baz && |
||||
git commit -m initial && |
||||
echo bazz > baz && |
||||
git commit -a -m "second" && |
||||
git checkout -b conflict_branch HEAD^ && |
||||
echo barf > bar && |
||||
echo bazf > baz && |
||||
git commit -a -m "conflict" && |
||||
git checkout -b clean_branch HEAD^ && |
||||
echo bart > bar && |
||||
git commit -a -m "clean" && |
||||
git checkout master |
||||
' |
||||
|
||||
pre_merge_head="$(git rev-parse HEAD)" |
||||
|
||||
test_expect_success 'fails without MERGE_HEAD (unstarted merge)' ' |
||||
test_must_fail git merge --abort 2>output && |
||||
grep -q MERGE_HEAD output && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" |
||||
' |
||||
|
||||
test_expect_success 'fails without MERGE_HEAD (completed merge)' ' |
||||
git merge clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
# Merge successfully completed |
||||
post_merge_head="$(git rev-parse HEAD)" && |
||||
test_must_fail git merge --abort 2>output && |
||||
grep -q MERGE_HEAD output && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$post_merge_head" = "$(git rev-parse HEAD)" |
||||
' |
||||
|
||||
test_expect_success 'Forget previous merge' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Abort after --no-commit' ' |
||||
# Redo merge, but stop before creating merge commit |
||||
git merge --no-commit clean_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort non-conflicting merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
test -z "$(git diff --staged)" |
||||
' |
||||
|
||||
test_expect_success 'Abort after conflicts' ' |
||||
# Create conflicting merge |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort conflicting merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
test -z "$(git diff --staged)" |
||||
' |
||||
|
||||
test_expect_success 'Clean merge with dirty index fails' ' |
||||
echo xyzzy >> foo && |
||||
git add foo && |
||||
git diff --staged > expect && |
||||
test_must_fail git merge clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Conflicting merge with dirty index fails' ' |
||||
test_must_fail git merge conflict_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Reset index (but preserve worktree changes)' ' |
||||
git reset "$pre_merge_head" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort clean merge with non-conflicting dirty worktree' ' |
||||
git merge --no-commit clean_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort conflicting merge with non-conflicting dirty worktree' ' |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Fail clean merge with conflicting dirty worktree' ' |
||||
echo xyzzy >> bar && |
||||
git diff > expect && |
||||
test_must_fail git merge --no-commit clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Fail conflicting merge with conflicting dirty worktree' ' |
||||
test_must_fail git merge conflict_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Fail clean merge with matching dirty worktree' ' |
||||
echo bart > bar && |
||||
git diff > expect && |
||||
test_must_fail git merge --no-commit clean_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort clean merge with matching dirty index' ' |
||||
git add bar && |
||||
git diff --staged > expect && |
||||
git merge --no-commit clean_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
### When aborting the merge, git will discard all staged changes, |
||||
### including those that were staged pre-merge. In other words, |
||||
### --abort will LOSE any staged changes (the staged changes that |
||||
### are lost must match the merge result, or the merge would not |
||||
### have been allowed to start). Change expectations accordingly: |
||||
rm expect && |
||||
touch expect && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual && |
||||
test -z "$(git diff)" |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Fail conflicting merge with matching dirty worktree' ' |
||||
echo barf > bar && |
||||
git diff > expect && |
||||
test_must_fail git merge conflict_branch && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
test -z "$(git diff --staged)" && |
||||
git diff > actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'Abort conflicting merge with matching dirty index' ' |
||||
git add bar && |
||||
git diff --staged > expect && |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
### When aborting the merge, git will discard all staged changes, |
||||
### including those that were staged pre-merge. In other words, |
||||
### --abort will LOSE any staged changes (the staged changes that |
||||
### are lost must match the merge result, or the merge would not |
||||
### have been allowed to start). Change expectations accordingly: |
||||
rm expect && |
||||
touch expect && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff --staged > actual && |
||||
test_cmp expect actual && |
||||
test -z "$(git diff)" |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Abort merge with pre- and post-merge worktree changes' ' |
||||
# Pre-merge worktree changes |
||||
echo xyzzy > foo && |
||||
echo barf > bar && |
||||
git add bar && |
||||
git diff > expect && |
||||
git diff --staged > expect-staged && |
||||
# Perform merge |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Post-merge worktree changes |
||||
echo yzxxz > foo && |
||||
echo blech > baz && |
||||
### When aborting the merge, git will discard staged changes (bar) |
||||
### and unmerged changes (baz). Other changes that are neither |
||||
### staged nor marked as unmerged (foo), will be preserved. For |
||||
### these changed, git cannot tell pre-merge changes apart from |
||||
### post-merge changes, so the post-merge changes will be |
||||
### preserved. Change expectations accordingly: |
||||
git diff -- foo > expect && |
||||
rm expect-staged && |
||||
touch expect-staged && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff > actual && |
||||
test_cmp expect actual && |
||||
git diff --staged > actual-staged && |
||||
test_cmp expect-staged actual-staged |
||||
' |
||||
|
||||
test_expect_success 'Reset worktree changes' ' |
||||
git reset --hard "$pre_merge_head" |
||||
' |
||||
|
||||
test_expect_success 'Abort merge with pre- and post-merge index changes' ' |
||||
# Pre-merge worktree changes |
||||
echo xyzzy > foo && |
||||
echo barf > bar && |
||||
git add bar && |
||||
git diff > expect && |
||||
git diff --staged > expect-staged && |
||||
# Perform merge |
||||
test_must_fail git merge conflict_branch && |
||||
test -f .git/MERGE_HEAD && |
||||
# Post-merge worktree changes |
||||
echo yzxxz > foo && |
||||
echo blech > baz && |
||||
git add foo bar && |
||||
### When aborting the merge, git will discard all staged changes |
||||
### (foo, bar and baz), and no changes will be preserved. Whether |
||||
### the changes were staged pre- or post-merge does not matter |
||||
### (except for not preventing starting the merge). |
||||
### Change expectations accordingly: |
||||
rm expect expect-staged && |
||||
touch expect && |
||||
touch expect-staged && |
||||
# Abort merge |
||||
git merge --abort && |
||||
test ! -f .git/MERGE_HEAD && |
||||
test "$pre_merge_head" = "$(git rev-parse HEAD)" && |
||||
git diff > actual && |
||||
test_cmp expect actual && |
||||
git diff --staged > actual-staged && |
||||
test_cmp expect-staged actual-staged |
||||
' |
||||
|
||||
test_done |
Loading…
Reference in new issue