зеркало из https://github.com/microsoft/git.git
Merge branch 'jh/notes-merge'
* 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.h
This commit is contained in:
Коммит
657072f3ac
|
@ -13,6 +13,7 @@ SYNOPSIS
|
|||
[-s <strategy>] [-X <strategy-option>]
|
||||
[--[no-]rerere-autoupdate] [-m <msg>] <commit>...
|
||||
'git merge' <msg> HEAD <commit>...
|
||||
'git merge' --abort
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
@ -47,6 +48,14 @@ The second syntax (<msg> `HEAD` <commit>...) is supported for
|
|||
historical reasons. Do not use it from the command line or in
|
||||
new scripts. It is the same as `git merge -m <msg> <commit>...`.
|
||||
|
||||
The third syntax ("`git merge --abort`") can only be run after the
|
||||
merge has resulted in conflicts. 'git merge --abort' will abort the
|
||||
merge process and try to reconstruct the pre-merge state. However,
|
||||
if there were uncommitted changes when the merge started (and
|
||||
especially if those changes were further modified after the merge
|
||||
was started), 'git merge --abort' will in some cases be unable to
|
||||
reconstruct the original (pre-merge) changes. Therefore:
|
||||
|
||||
*Warning*: Running 'git merge' with uncommitted changes is
|
||||
discouraged: while possible, it leaves you in a state that is hard to
|
||||
back out of in the case of a conflict.
|
||||
|
@ -72,6 +81,18 @@ invocations.
|
|||
Allow the rerere mechanism to update the index with the
|
||||
result of auto-conflict resolution if possible.
|
||||
|
||||
--abort::
|
||||
Abort the current conflict resolution process, and
|
||||
try to reconstruct the pre-merge state.
|
||||
+
|
||||
If there were uncommitted worktree changes present when the merge
|
||||
started, 'git merge --abort' will in some cases be unable to
|
||||
reconstruct these changes. It is therefore recommended to always
|
||||
commit or stash your changes before running 'git merge'.
|
||||
+
|
||||
'git merge --abort' is equivalent to 'git reset --merge' when
|
||||
`MERGE_HEAD` is present.
|
||||
|
||||
<commit>...::
|
||||
Commits, usually other branch heads, to merge into our branch.
|
||||
You need at least one <commit>. Specifying more than one
|
||||
|
@ -142,7 +163,7 @@ happens:
|
|||
i.e. matching `HEAD`.
|
||||
|
||||
If you tried a merge which resulted in complex conflicts and
|
||||
want to start over, you can recover with `git reset --merge`.
|
||||
want to start over, you can recover with `git merge --abort`.
|
||||
|
||||
HOW CONFLICTS ARE PRESENTED
|
||||
---------------------------
|
||||
|
@ -213,8 +234,8 @@ After seeing a conflict, you can do two things:
|
|||
|
||||
* Decide not to merge. The only clean-ups you need are to reset
|
||||
the index file to the `HEAD` commit to reverse 2. and to clean
|
||||
up working tree changes made by 2. and 3.; `git-reset --hard` can
|
||||
be used for this.
|
||||
up working tree changes made by 2. and 3.; `git merge --abort`
|
||||
can be used for this.
|
||||
|
||||
* Resolve the conflicts. Git will mark the conflicts in
|
||||
the working tree. Edit the files into shape and
|
||||
|
|
|
@ -14,8 +14,12 @@ SYNOPSIS
|
|||
'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
|
||||
'git notes' edit [<object>]
|
||||
'git notes' show [<object>]
|
||||
'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref>
|
||||
'git notes' merge --commit [-v | -q]
|
||||
'git notes' merge --abort [-v | -q]
|
||||
'git notes' remove [<object>]
|
||||
'git notes' prune [-n | -v]
|
||||
'git notes' get-ref
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
|
@ -83,6 +87,21 @@ edit::
|
|||
show::
|
||||
Show the notes for a given object (defaults to HEAD).
|
||||
|
||||
merge::
|
||||
Merge the given notes ref into the current notes ref.
|
||||
This will try to merge the changes made by the given
|
||||
notes ref (called "remote") since the merge-base (if
|
||||
any) into the current notes ref (called "local").
|
||||
+
|
||||
If conflicts arise and a strategy for automatically resolving
|
||||
conflicting notes (see the -s/--strategy option) is not given,
|
||||
the "manual" resolver is used. This resolver checks out the
|
||||
conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),
|
||||
and instructs the user to manually resolve the conflicts there.
|
||||
When done, the user can either finalize the merge with
|
||||
'git notes merge --commit', or abort the merge with
|
||||
'git notes merge --abort'.
|
||||
|
||||
remove::
|
||||
Remove the notes for a given object (defaults to HEAD).
|
||||
This is equivalent to specifying an empty note message to
|
||||
|
@ -91,6 +110,10 @@ remove::
|
|||
prune::
|
||||
Remove all notes for non-existing/unreachable objects.
|
||||
|
||||
get-ref::
|
||||
Print the current notes ref. This provides an easy way to
|
||||
retrieve the current notes ref (e.g. from scripts).
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
-f::
|
||||
|
@ -133,9 +156,37 @@ OPTIONS
|
|||
Do not remove anything; just report the object names whose notes
|
||||
would be removed.
|
||||
|
||||
-s <strategy>::
|
||||
--strategy=<strategy>::
|
||||
When merging notes, resolve notes conflicts using the given
|
||||
strategy. The following strategies are recognized: "manual"
|
||||
(default), "ours", "theirs", "union" and "cat_sort_uniq".
|
||||
See the "NOTES MERGE STRATEGIES" section below for more
|
||||
information on each notes merge strategy.
|
||||
|
||||
--commit::
|
||||
Finalize an in-progress 'git notes merge'. Use this option
|
||||
when you have resolved the conflicts that 'git notes merge'
|
||||
stored in .git/NOTES_MERGE_WORKTREE. This amends the partial
|
||||
merge commit created by 'git notes merge' (stored in
|
||||
.git/NOTES_MERGE_PARTIAL) by adding the notes in
|
||||
.git/NOTES_MERGE_WORKTREE. The notes ref stored in the
|
||||
.git/NOTES_MERGE_REF symref is updated to the resulting commit.
|
||||
|
||||
--abort::
|
||||
Abort/reset a in-progress 'git notes merge', i.e. a notes merge
|
||||
with conflicts. This simply removes all files related to the
|
||||
notes merge.
|
||||
|
||||
-q::
|
||||
--quiet::
|
||||
When merging notes, operate quietly.
|
||||
|
||||
-v::
|
||||
--verbose::
|
||||
Report all object names whose notes are removed.
|
||||
When merging notes, be more verbose.
|
||||
When pruning notes, report all object names whose notes are
|
||||
removed.
|
||||
|
||||
|
||||
DISCUSSION
|
||||
|
@ -163,6 +214,38 @@ object, in which case the history of the notes can be read with
|
|||
`git log -p -g <refname>`.
|
||||
|
||||
|
||||
NOTES MERGE STRATEGIES
|
||||
----------------------
|
||||
|
||||
The default notes merge strategy is "manual", which checks out
|
||||
conflicting notes in a special work tree for resolving notes conflicts
|
||||
(`.git/NOTES_MERGE_WORKTREE`), and instructs the user to resolve the
|
||||
conflicts in that work tree.
|
||||
When done, the user can either finalize the merge with
|
||||
'git notes merge --commit', or abort the merge with
|
||||
'git notes merge --abort'.
|
||||
|
||||
"ours" automatically resolves conflicting notes in favor of the local
|
||||
version (i.e. the current notes ref).
|
||||
|
||||
"theirs" automatically resolves notes conflicts in favor of the remote
|
||||
version (i.e. the given notes ref being merged into the current notes
|
||||
ref).
|
||||
|
||||
"union" automatically resolves notes conflicts by concatenating the
|
||||
local and remote versions.
|
||||
|
||||
"cat_sort_uniq" is similar to "union", but in addition to concatenating
|
||||
the local and remote versions, this strategy also sorts the resulting
|
||||
lines, and removes duplicate lines from the result. This is equivalent
|
||||
to applying the "cat | sort | uniq" shell pipeline to the local and
|
||||
remote versions. This strategy is useful if the notes follow a line-based
|
||||
format where one wants to avoid duplicated lines in the merge result.
|
||||
Note that if either the local or remote version contain duplicate lines
|
||||
prior to the merge, these will also be removed by this notes merge
|
||||
strategy.
|
||||
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -525,6 +525,7 @@ LIB_H += mailmap.h
|
|||
LIB_H += merge-recursive.h
|
||||
LIB_H += notes.h
|
||||
LIB_H += notes-cache.h
|
||||
LIB_H += notes-merge.h
|
||||
LIB_H += object.h
|
||||
LIB_H += pack.h
|
||||
LIB_H += pack-refs.h
|
||||
|
@ -615,6 +616,7 @@ LIB_OBJS += merge-recursive.o
|
|||
LIB_OBJS += name-hash.o
|
||||
LIB_OBJS += notes.o
|
||||
LIB_OBJS += notes-cache.o
|
||||
LIB_OBJS += notes-merge.o
|
||||
LIB_OBJS += object.o
|
||||
LIB_OBJS += pack-check.o
|
||||
LIB_OBJS += pack-refs.o
|
||||
|
|
|
@ -16,7 +16,7 @@ extern const char git_more_info_string[];
|
|||
extern void prune_packed_objects(int);
|
||||
extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
|
||||
int merge_title, int shortlog_len);
|
||||
extern int commit_notes(struct notes_tree *t, const char *msg);
|
||||
extern void commit_notes(struct notes_tree *t, const char *msg);
|
||||
|
||||
struct notes_rewrite_cfg {
|
||||
struct notes_tree **trees;
|
||||
|
|
|
@ -57,6 +57,7 @@ static const char *branch;
|
|||
static int option_renormalize;
|
||||
static int verbosity;
|
||||
static int allow_rerere_auto;
|
||||
static int abort_current_merge;
|
||||
|
||||
static struct strategy all_strategy[] = {
|
||||
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
|
||||
|
@ -197,6 +198,8 @@ static struct option builtin_merge_options[] = {
|
|||
"message to be used for the merge commit (if any)",
|
||||
option_parse_message),
|
||||
OPT__VERBOSITY(&verbosity),
|
||||
OPT_BOOLEAN(0, "abort", &abort_current_merge,
|
||||
"abort the current in-progress merge"),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
@ -919,22 +922,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||
const char *best_strategy = NULL, *wt_strategy = NULL;
|
||||
struct commit_list **remotes = &remoteheads;
|
||||
|
||||
if (read_cache_unmerged()) {
|
||||
die_resolve_conflict("merge");
|
||||
}
|
||||
if (file_exists(git_path("MERGE_HEAD"))) {
|
||||
/*
|
||||
* There is no unmerged entry, don't advise 'git
|
||||
* add/rm <file>', just 'git commit'.
|
||||
*/
|
||||
if (advice_resolve_conflict)
|
||||
die("You have not concluded your merge (MERGE_HEAD exists).\n"
|
||||
"Please, commit your changes before you can merge.");
|
||||
else
|
||||
die("You have not concluded your merge (MERGE_HEAD exists).");
|
||||
}
|
||||
|
||||
resolve_undo_clear();
|
||||
/*
|
||||
* Check if we are _not_ on a detached HEAD, i.e. if there is a
|
||||
* current branch.
|
||||
|
@ -953,6 +940,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||
|
||||
argc = parse_options(argc, argv, prefix, builtin_merge_options,
|
||||
builtin_merge_usage, 0);
|
||||
|
||||
if (abort_current_merge) {
|
||||
int nargc = 2;
|
||||
const char *nargv[] = {"reset", "--merge", NULL};
|
||||
|
||||
if (!file_exists(git_path("MERGE_HEAD")))
|
||||
die("There is no merge to abort (MERGE_HEAD missing).");
|
||||
|
||||
/* Invoke 'git reset --merge' */
|
||||
return cmd_reset(nargc, nargv, prefix);
|
||||
}
|
||||
|
||||
if (read_cache_unmerged())
|
||||
die_resolve_conflict("merge");
|
||||
|
||||
if (file_exists(git_path("MERGE_HEAD"))) {
|
||||
/*
|
||||
* There is no unmerged entry, don't advise 'git
|
||||
* add/rm <file>', just 'git commit'.
|
||||
*/
|
||||
if (advice_resolve_conflict)
|
||||
die("You have not concluded your merge (MERGE_HEAD exists).\n"
|
||||
"Please, commit your changes before you can merge.");
|
||||
else
|
||||
die("You have not concluded your merge (MERGE_HEAD exists).");
|
||||
}
|
||||
resolve_undo_clear();
|
||||
|
||||
if (verbosity < 0)
|
||||
show_diffstat = 0;
|
||||
|
||||
|
|
268
builtin/notes.c
268
builtin/notes.c
|
@ -17,6 +17,7 @@
|
|||
#include "run-command.h"
|
||||
#include "parse-options.h"
|
||||
#include "string-list.h"
|
||||
#include "notes-merge.h"
|
||||
|
||||
static const char * const git_notes_usage[] = {
|
||||
"git notes [--ref <notes_ref>] [list [<object>]]",
|
||||
|
@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = {
|
|||
"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
|
||||
"git notes [--ref <notes_ref>] edit [<object>]",
|
||||
"git notes [--ref <notes_ref>] show [<object>]",
|
||||
"git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
|
||||
"git notes merge --commit [-v | -q]",
|
||||
"git notes merge --abort [-v | -q]",
|
||||
"git notes [--ref <notes_ref>] remove [<object>]",
|
||||
"git notes [--ref <notes_ref>] prune [-n | -v]",
|
||||
"git notes [--ref <notes_ref>] get-ref",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static const char * const git_notes_merge_usage[] = {
|
||||
"git notes merge [<options>] <notes_ref>",
|
||||
"git notes merge --commit [<options>]",
|
||||
"git notes merge --abort [<options>]",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char * const git_notes_remove_usage[] = {
|
||||
"git notes remove [<object>]",
|
||||
NULL
|
||||
|
@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static const char * const git_notes_get_ref_usage[] = {
|
||||
"git notes get-ref",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char note_template[] =
|
||||
"\n"
|
||||
"#\n"
|
||||
|
@ -83,6 +100,16 @@ struct msg_arg {
|
|||
struct strbuf buf;
|
||||
};
|
||||
|
||||
static void expand_notes_ref(struct strbuf *sb)
|
||||
{
|
||||
if (!prefixcmp(sb->buf, "refs/notes/"))
|
||||
return; /* we're happy */
|
||||
else if (!prefixcmp(sb->buf, "notes/"))
|
||||
strbuf_insert(sb, 0, "refs/", 5);
|
||||
else
|
||||
strbuf_insert(sb, 0, "refs/notes/", 11);
|
||||
}
|
||||
|
||||
static int list_each_note(const unsigned char *object_sha1,
|
||||
const unsigned char *note_sha1, char *note_path,
|
||||
void *cb_data)
|
||||
|
@ -271,18 +298,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
|
|||
return parse_reuse_arg(opt, arg, unset);
|
||||
}
|
||||
|
||||
int commit_notes(struct notes_tree *t, const char *msg)
|
||||
void commit_notes(struct notes_tree *t, const char *msg)
|
||||
{
|
||||
struct commit_list *parent;
|
||||
unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
unsigned char commit_sha1[20];
|
||||
|
||||
if (!t)
|
||||
t = &default_notes_tree;
|
||||
if (!t->initialized || !t->ref || !*t->ref)
|
||||
die("Cannot commit uninitialized/unreferenced notes tree");
|
||||
if (!t->dirty)
|
||||
return 0; /* don't have to commit an unchanged tree */
|
||||
return; /* don't have to commit an unchanged tree */
|
||||
|
||||
/* Prepare commit message and reflog message */
|
||||
strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
|
||||
|
@ -290,27 +316,10 @@ int commit_notes(struct notes_tree *t, const char *msg)
|
|||
if (buf.buf[buf.len - 1] != '\n')
|
||||
strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
|
||||
|
||||
/* Convert notes tree to tree object */
|
||||
if (write_notes_tree(t, tree_sha1))
|
||||
die("Failed to write current notes tree to database");
|
||||
|
||||
/* Create new commit for the tree object */
|
||||
if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
|
||||
parent = xmalloc(sizeof(*parent));
|
||||
parent->item = lookup_commit(prev_commit);
|
||||
parent->next = NULL;
|
||||
} else {
|
||||
hashclr(prev_commit);
|
||||
parent = NULL;
|
||||
}
|
||||
if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
|
||||
die("Failed to commit notes tree to database");
|
||||
|
||||
/* Update notes ref with new commit */
|
||||
update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
|
||||
create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
|
||||
update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
|
||||
|
||||
strbuf_release(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
combine_notes_fn parse_combine_notes_fn(const char *v)
|
||||
|
@ -321,6 +330,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v)
|
|||
return combine_notes_ignore;
|
||||
else if (!strcasecmp(v, "concatenate"))
|
||||
return combine_notes_concatenate;
|
||||
else if (!strcasecmp(v, "cat_sort_uniq"))
|
||||
return combine_notes_cat_sort_uniq;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
@ -573,8 +584,8 @@ static int add(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (is_null_sha1(new_note))
|
||||
remove_note(t, object);
|
||||
else
|
||||
add_note(t, object, new_note, combine_notes_overwrite);
|
||||
else if (add_note(t, object, new_note, combine_notes_overwrite))
|
||||
die("BUG: combine_notes_overwrite failed");
|
||||
|
||||
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
|
||||
is_null_sha1(new_note) ? "removed" : "added", "add");
|
||||
|
@ -653,7 +664,8 @@ static int copy(int argc, const char **argv, const char *prefix)
|
|||
goto out;
|
||||
}
|
||||
|
||||
add_note(t, object, from_note, combine_notes_overwrite);
|
||||
if (add_note(t, object, from_note, combine_notes_overwrite))
|
||||
die("BUG: combine_notes_overwrite failed");
|
||||
commit_notes(t, "Notes added by 'git notes copy'");
|
||||
out:
|
||||
free_notes(t);
|
||||
|
@ -712,8 +724,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (is_null_sha1(new_note))
|
||||
remove_note(t, object);
|
||||
else
|
||||
add_note(t, object, new_note, combine_notes_overwrite);
|
||||
else if (add_note(t, object, new_note, combine_notes_overwrite))
|
||||
die("BUG: combine_notes_overwrite failed");
|
||||
|
||||
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
|
||||
is_null_sha1(new_note) ? "removed" : "added", argv[0]);
|
||||
|
@ -761,6 +773,180 @@ static int show(int argc, const char **argv, const char *prefix)
|
|||
return retval;
|
||||
}
|
||||
|
||||
static int merge_abort(struct notes_merge_options *o)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call
|
||||
* notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
|
||||
*/
|
||||
|
||||
if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
|
||||
ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
|
||||
if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
|
||||
ret += error("Failed to delete ref NOTES_MERGE_REF");
|
||||
if (notes_merge_abort(o))
|
||||
ret += error("Failed to remove 'git notes merge' worktree");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int merge_commit(struct notes_merge_options *o)
|
||||
{
|
||||
struct strbuf msg = STRBUF_INIT;
|
||||
unsigned char sha1[20], parent_sha1[20];
|
||||
struct notes_tree *t;
|
||||
struct commit *partial;
|
||||
struct pretty_print_context pretty_ctx;
|
||||
|
||||
/*
|
||||
* Read partial merge result from .git/NOTES_MERGE_PARTIAL,
|
||||
* and target notes ref from .git/NOTES_MERGE_REF.
|
||||
*/
|
||||
|
||||
if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
|
||||
die("Failed to read ref NOTES_MERGE_PARTIAL");
|
||||
else if (!(partial = lookup_commit_reference(sha1)))
|
||||
die("Could not find commit from NOTES_MERGE_PARTIAL.");
|
||||
else if (parse_commit(partial))
|
||||
die("Could not parse commit from NOTES_MERGE_PARTIAL.");
|
||||
|
||||
if (partial->parents)
|
||||
hashcpy(parent_sha1, partial->parents->item->object.sha1);
|
||||
else
|
||||
hashclr(parent_sha1);
|
||||
|
||||
t = xcalloc(1, sizeof(struct notes_tree));
|
||||
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
|
||||
|
||||
o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0);
|
||||
if (!o->local_ref)
|
||||
die("Failed to resolve NOTES_MERGE_REF");
|
||||
|
||||
if (notes_merge_commit(o, t, partial, sha1))
|
||||
die("Failed to finalize notes merge");
|
||||
|
||||
/* Reuse existing commit message in reflog message */
|
||||
memset(&pretty_ctx, 0, sizeof(pretty_ctx));
|
||||
format_commit_message(partial, "%s", &msg, &pretty_ctx);
|
||||
strbuf_trim(&msg);
|
||||
strbuf_insert(&msg, 0, "notes: ", 7);
|
||||
update_ref(msg.buf, o->local_ref, sha1,
|
||||
is_null_sha1(parent_sha1) ? NULL : parent_sha1,
|
||||
0, DIE_ON_ERR);
|
||||
|
||||
free_notes(t);
|
||||
strbuf_release(&msg);
|
||||
return merge_abort(o);
|
||||
}
|
||||
|
||||
static int merge(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
|
||||
unsigned char result_sha1[20];
|
||||
struct notes_tree *t;
|
||||
struct notes_merge_options o;
|
||||
int do_merge = 0, do_commit = 0, do_abort = 0;
|
||||
int verbosity = 0, result;
|
||||
const char *strategy = NULL;
|
||||
struct option options[] = {
|
||||
OPT_GROUP("General options"),
|
||||
OPT__VERBOSITY(&verbosity),
|
||||
OPT_GROUP("Merge options"),
|
||||
OPT_STRING('s', "strategy", &strategy, "strategy",
|
||||
"resolve notes conflicts using the given strategy "
|
||||
"(manual/ours/theirs/union/cat_sort_uniq)"),
|
||||
OPT_GROUP("Committing unmerged notes"),
|
||||
{ OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
|
||||
"finalize notes merge by committing unmerged notes",
|
||||
PARSE_OPT_NOARG | PARSE_OPT_NONEG },
|
||||
OPT_GROUP("Aborting notes merge resolution"),
|
||||
{ OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
|
||||
"abort notes merge",
|
||||
PARSE_OPT_NOARG | PARSE_OPT_NONEG },
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
argc = parse_options(argc, argv, prefix, options,
|
||||
git_notes_merge_usage, 0);
|
||||
|
||||
if (strategy || do_commit + do_abort == 0)
|
||||
do_merge = 1;
|
||||
if (do_merge + do_commit + do_abort != 1) {
|
||||
error("cannot mix --commit, --abort or -s/--strategy");
|
||||
usage_with_options(git_notes_merge_usage, options);
|
||||
}
|
||||
|
||||
if (do_merge && argc != 1) {
|
||||
error("Must specify a notes ref to merge");
|
||||
usage_with_options(git_notes_merge_usage, options);
|
||||
} else if (!do_merge && argc) {
|
||||
error("too many parameters");
|
||||
usage_with_options(git_notes_merge_usage, options);
|
||||
}
|
||||
|
||||
init_notes_merge_options(&o);
|
||||
o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
|
||||
|
||||
if (do_abort)
|
||||
return merge_abort(&o);
|
||||
if (do_commit)
|
||||
return merge_commit(&o);
|
||||
|
||||
o.local_ref = default_notes_ref();
|
||||
strbuf_addstr(&remote_ref, argv[0]);
|
||||
expand_notes_ref(&remote_ref);
|
||||
o.remote_ref = remote_ref.buf;
|
||||
|
||||
if (strategy) {
|
||||
if (!strcmp(strategy, "manual"))
|
||||
o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
|
||||
else if (!strcmp(strategy, "ours"))
|
||||
o.strategy = NOTES_MERGE_RESOLVE_OURS;
|
||||
else if (!strcmp(strategy, "theirs"))
|
||||
o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
|
||||
else if (!strcmp(strategy, "union"))
|
||||
o.strategy = NOTES_MERGE_RESOLVE_UNION;
|
||||
else if (!strcmp(strategy, "cat_sort_uniq"))
|
||||
o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
|
||||
else {
|
||||
error("Unknown -s/--strategy: %s", strategy);
|
||||
usage_with_options(git_notes_merge_usage, options);
|
||||
}
|
||||
}
|
||||
|
||||
t = init_notes_check("merge");
|
||||
|
||||
strbuf_addf(&msg, "notes: Merged notes from %s into %s",
|
||||
remote_ref.buf, default_notes_ref());
|
||||
strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */
|
||||
|
||||
result = notes_merge(&o, t, result_sha1);
|
||||
|
||||
if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
|
||||
/* Update default notes ref with new commit */
|
||||
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
|
||||
0, DIE_ON_ERR);
|
||||
else { /* Merge has unresolved conflicts */
|
||||
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
|
||||
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
|
||||
0, DIE_ON_ERR);
|
||||
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
|
||||
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
|
||||
die("Failed to store link to current notes ref (%s)",
|
||||
default_notes_ref());
|
||||
printf("Automatic notes merge failed. Fix conflicts in %s and "
|
||||
"commit the result with 'git notes merge --commit', or "
|
||||
"abort the merge with 'git notes merge --abort'.\n",
|
||||
git_path(NOTES_MERGE_WORKTREE));
|
||||
}
|
||||
|
||||
free_notes(t);
|
||||
strbuf_release(&remote_ref);
|
||||
strbuf_release(&msg);
|
||||
return result < 0; /* return non-zero on conflicts */
|
||||
}
|
||||
|
||||
static int remove_cmd(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct option options[] = {
|
||||
|
@ -827,6 +1013,21 @@ static int prune(int argc, const char **argv, const char *prefix)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int get_ref(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct option options[] = { OPT_END() };
|
||||
argc = parse_options(argc, argv, prefix, options,
|
||||
git_notes_get_ref_usage, 0);
|
||||
|
||||
if (argc) {
|
||||
error("too many parameters");
|
||||
usage_with_options(git_notes_get_ref_usage, options);
|
||||
}
|
||||
|
||||
puts(default_notes_ref());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_notes(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int result;
|
||||
|
@ -843,13 +1044,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (override_notes_ref) {
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
if (!prefixcmp(override_notes_ref, "refs/notes/"))
|
||||
/* we're happy */;
|
||||
else if (!prefixcmp(override_notes_ref, "notes/"))
|
||||
strbuf_addstr(&sb, "refs/");
|
||||
else
|
||||
strbuf_addstr(&sb, "refs/notes/");
|
||||
strbuf_addstr(&sb, override_notes_ref);
|
||||
expand_notes_ref(&sb);
|
||||
setenv("GIT_NOTES_REF", sb.buf, 1);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
@ -864,10 +1060,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
|
|||
result = append_edit(argc, argv, prefix);
|
||||
else if (!strcmp(argv[0], "show"))
|
||||
result = show(argc, argv, prefix);
|
||||
else if (!strcmp(argv[0], "merge"))
|
||||
result = merge(argc, argv, prefix);
|
||||
else if (!strcmp(argv[0], "remove"))
|
||||
result = remove_cmd(argc, argv, prefix);
|
||||
else if (!strcmp(argv[0], "prune"))
|
||||
result = prune(argc, argv, prefix);
|
||||
else if (!strcmp(argv[0], "get-ref"))
|
||||
result = get_ref(argc, argv, prefix);
|
||||
else {
|
||||
result = error("Unknown subcommand: %s", argv[0]);
|
||||
usage_with_options(git_notes_usage, options);
|
||||
|
|
|
@ -89,6 +89,5 @@ int notes_cache_put(struct notes_cache *c, unsigned char key_sha1[20],
|
|||
|
||||
if (write_sha1_file(data, size, "blob", value_sha1) < 0)
|
||||
return -1;
|
||||
add_note(&c->tree, key_sha1, value_sha1, NULL);
|
||||
return 0;
|
||||
return add_note(&c->tree, key_sha1, value_sha1, NULL);
|
||||
}
|
||||
|
|
|
@ -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 @@
|
|||
#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
|
272
notes.c
272
notes.c
|
@ -149,86 +149,6 @@ static struct leaf_node *note_tree_find(struct notes_tree *t,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* To insert a leaf_node:
|
||||
* Search to the tree location appropriate for the given leaf_node's key:
|
||||
* - If location is unused (NULL), store the tweaked pointer directly there
|
||||
* - If location holds a note entry that matches the note-to-be-inserted, then
|
||||
* combine the two notes (by calling the given combine_notes function).
|
||||
* - If location holds a note entry that matches the subtree-to-be-inserted,
|
||||
* then unpack the subtree-to-be-inserted into the location.
|
||||
* - If location holds a matching subtree entry, unpack the subtree at that
|
||||
* location, and restart the insert operation from that level.
|
||||
* - Else, create a new int_node, holding both the node-at-location and the
|
||||
* node-to-be-inserted, and store the new int_node into the location.
|
||||
*/
|
||||
static void note_tree_insert(struct notes_tree *t, struct int_node *tree,
|
||||
unsigned char n, struct leaf_node *entry, unsigned char type,
|
||||
combine_notes_fn combine_notes)
|
||||
{
|
||||
struct int_node *new_node;
|
||||
struct leaf_node *l;
|
||||
void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
|
||||
|
||||
assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
|
||||
l = (struct leaf_node *) CLR_PTR_TYPE(*p);
|
||||
switch (GET_PTR_TYPE(*p)) {
|
||||
case PTR_TYPE_NULL:
|
||||
assert(!*p);
|
||||
*p = SET_PTR_TYPE(entry, type);
|
||||
return;
|
||||
case PTR_TYPE_NOTE:
|
||||
switch (type) {
|
||||
case PTR_TYPE_NOTE:
|
||||
if (!hashcmp(l->key_sha1, entry->key_sha1)) {
|
||||
/* skip concatenation if l == entry */
|
||||
if (!hashcmp(l->val_sha1, entry->val_sha1))
|
||||
return;
|
||||
|
||||
if (combine_notes(l->val_sha1, entry->val_sha1))
|
||||
die("failed to combine notes %s and %s"
|
||||
" for object %s",
|
||||
sha1_to_hex(l->val_sha1),
|
||||
sha1_to_hex(entry->val_sha1),
|
||||
sha1_to_hex(l->key_sha1));
|
||||
free(entry);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case PTR_TYPE_SUBTREE:
|
||||
if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
|
||||
entry->key_sha1)) {
|
||||
/* unpack 'entry' */
|
||||
load_subtree(t, entry, tree, n);
|
||||
free(entry);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PTR_TYPE_SUBTREE:
|
||||
if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
|
||||
/* unpack 'l' and restart insert */
|
||||
*p = NULL;
|
||||
load_subtree(t, l, tree, n);
|
||||
free(l);
|
||||
note_tree_insert(t, tree, n, entry, type,
|
||||
combine_notes);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* non-matching leaf_node */
|
||||
assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
|
||||
GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
|
||||
new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
|
||||
note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
|
||||
combine_notes);
|
||||
*p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
|
||||
note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
|
||||
}
|
||||
|
||||
/*
|
||||
* How to consolidate an int_node:
|
||||
* If there are > 1 non-NULL entries, give up and return non-zero.
|
||||
|
@ -305,6 +225,93 @@ static void note_tree_remove(struct notes_tree *t,
|
|||
i--;
|
||||
}
|
||||
|
||||
/*
|
||||
* To insert a leaf_node:
|
||||
* Search to the tree location appropriate for the given leaf_node's key:
|
||||
* - If location is unused (NULL), store the tweaked pointer directly there
|
||||
* - If location holds a note entry that matches the note-to-be-inserted, then
|
||||
* combine the two notes (by calling the given combine_notes function).
|
||||
* - If location holds a note entry that matches the subtree-to-be-inserted,
|
||||
* then unpack the subtree-to-be-inserted into the location.
|
||||
* - If location holds a matching subtree entry, unpack the subtree at that
|
||||
* location, and restart the insert operation from that level.
|
||||
* - Else, create a new int_node, holding both the node-at-location and the
|
||||
* node-to-be-inserted, and store the new int_node into the location.
|
||||
*/
|
||||
static int note_tree_insert(struct notes_tree *t, struct int_node *tree,
|
||||
unsigned char n, struct leaf_node *entry, unsigned char type,
|
||||
combine_notes_fn combine_notes)
|
||||
{
|
||||
struct int_node *new_node;
|
||||
struct leaf_node *l;
|
||||
void **p = note_tree_search(t, &tree, &n, entry->key_sha1);
|
||||
int ret = 0;
|
||||
|
||||
assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */
|
||||
l = (struct leaf_node *) CLR_PTR_TYPE(*p);
|
||||
switch (GET_PTR_TYPE(*p)) {
|
||||
case PTR_TYPE_NULL:
|
||||
assert(!*p);
|
||||
if (is_null_sha1(entry->val_sha1))
|
||||
free(entry);
|
||||
else
|
||||
*p = SET_PTR_TYPE(entry, type);
|
||||
return 0;
|
||||
case PTR_TYPE_NOTE:
|
||||
switch (type) {
|
||||
case PTR_TYPE_NOTE:
|
||||
if (!hashcmp(l->key_sha1, entry->key_sha1)) {
|
||||
/* skip concatenation if l == entry */
|
||||
if (!hashcmp(l->val_sha1, entry->val_sha1))
|
||||
return 0;
|
||||
|
||||
ret = combine_notes(l->val_sha1,
|
||||
entry->val_sha1);
|
||||
if (!ret && is_null_sha1(l->val_sha1))
|
||||
note_tree_remove(t, tree, n, entry);
|
||||
free(entry);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case PTR_TYPE_SUBTREE:
|
||||
if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,
|
||||
entry->key_sha1)) {
|
||||
/* unpack 'entry' */
|
||||
load_subtree(t, entry, tree, n);
|
||||
free(entry);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PTR_TYPE_SUBTREE:
|
||||
if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {
|
||||
/* unpack 'l' and restart insert */
|
||||
*p = NULL;
|
||||
load_subtree(t, l, tree, n);
|
||||
free(l);
|
||||
return note_tree_insert(t, tree, n, entry, type,
|
||||
combine_notes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* non-matching leaf_node */
|
||||
assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||
|
||||
GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);
|
||||
if (is_null_sha1(entry->val_sha1)) { /* skip insertion of empty note */
|
||||
free(entry);
|
||||
return 0;
|
||||
}
|
||||
new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);
|
||||
ret = note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),
|
||||
combine_notes);
|
||||
if (ret)
|
||||
return ret;
|
||||
*p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
|
||||
return note_tree_insert(t, new_node, n + 1, entry, type, combine_notes);
|
||||
}
|
||||
|
||||
/* Free the entire notes data contained in the given tree */
|
||||
static void note_tree_free(struct int_node *tree)
|
||||
{
|
||||
|
@ -445,8 +452,12 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
|
|||
l->key_sha1[19] = (unsigned char) len;
|
||||
type = PTR_TYPE_SUBTREE;
|
||||
}
|
||||
note_tree_insert(t, node, n, l, type,
|
||||
combine_notes_concatenate);
|
||||
if (note_tree_insert(t, node, n, l, type,
|
||||
combine_notes_concatenate))
|
||||
die("Failed to load %s %s into notes tree "
|
||||
"from %s",
|
||||
type == PTR_TYPE_NOTE ? "note" : "subtree",
|
||||
sha1_to_hex(l->key_sha1), t->ref);
|
||||
}
|
||||
continue;
|
||||
|
||||
|
@ -804,16 +815,17 @@ int combine_notes_concatenate(unsigned char *cur_sha1,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* we will separate the notes by a newline anyway */
|
||||
/* we will separate the notes by two newlines anyway */
|
||||
if (cur_msg[cur_len - 1] == '\n')
|
||||
cur_len--;
|
||||
|
||||
/* concatenate cur_msg and new_msg into buf */
|
||||
buf_len = cur_len + 1 + new_len;
|
||||
buf_len = cur_len + 2 + new_len;
|
||||
buf = (char *) xmalloc(buf_len);
|
||||
memcpy(buf, cur_msg, cur_len);
|
||||
buf[cur_len] = '\n';
|
||||
memcpy(buf + cur_len + 1, new_msg, new_len);
|
||||
buf[cur_len + 1] = '\n';
|
||||
memcpy(buf + cur_len + 2, new_msg, new_len);
|
||||
free(cur_msg);
|
||||
free(new_msg);
|
||||
|
||||
|
@ -836,6 +848,82 @@ int combine_notes_ignore(unsigned char *cur_sha1,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int string_list_add_note_lines(struct string_list *sort_uniq_list,
|
||||
const unsigned char *sha1)
|
||||
{
|
||||
char *data;
|
||||
unsigned long len;
|
||||
enum object_type t;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct strbuf **lines = NULL;
|
||||
int i, list_index;
|
||||
|
||||
if (is_null_sha1(sha1))
|
||||
return 0;
|
||||
|
||||
/* read_sha1_file NUL-terminates */
|
||||
data = read_sha1_file(sha1, &t, &len);
|
||||
if (t != OBJ_BLOB || !data || !len) {
|
||||
free(data);
|
||||
return t != OBJ_BLOB || !data;
|
||||
}
|
||||
|
||||
strbuf_attach(&buf, data, len, len + 1);
|
||||
lines = strbuf_split(&buf, '\n');
|
||||
|
||||
for (i = 0; lines[i]; i++) {
|
||||
if (lines[i]->buf[lines[i]->len - 1] == '\n')
|
||||
strbuf_setlen(lines[i], lines[i]->len - 1);
|
||||
if (!lines[i]->len)
|
||||
continue; /* skip empty lines */
|
||||
list_index = string_list_find_insert_index(sort_uniq_list,
|
||||
lines[i]->buf, 0);
|
||||
if (list_index < 0)
|
||||
continue; /* skip duplicate lines */
|
||||
string_list_insert_at_index(sort_uniq_list, list_index,
|
||||
lines[i]->buf);
|
||||
}
|
||||
|
||||
strbuf_list_free(lines);
|
||||
strbuf_release(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int string_list_join_lines_helper(struct string_list_item *item,
|
||||
void *cb_data)
|
||||
{
|
||||
struct strbuf *buf = cb_data;
|
||||
strbuf_addstr(buf, item->string);
|
||||
strbuf_addch(buf, '\n');
|
||||
return 0;
|
||||
}
|
||||
|
||||
int combine_notes_cat_sort_uniq(unsigned char *cur_sha1,
|
||||
const unsigned char *new_sha1)
|
||||
{
|
||||
struct string_list sort_uniq_list = { NULL, 0, 0, 1 };
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int ret = 1;
|
||||
|
||||
/* read both note blob objects into unique_lines */
|
||||
if (string_list_add_note_lines(&sort_uniq_list, cur_sha1))
|
||||
goto out;
|
||||
if (string_list_add_note_lines(&sort_uniq_list, new_sha1))
|
||||
goto out;
|
||||
|
||||
/* create a new blob object from sort_uniq_list */
|
||||
if (for_each_string_list(&sort_uniq_list,
|
||||
string_list_join_lines_helper, &buf))
|
||||
goto out;
|
||||
|
||||
ret = write_sha1_file(buf.buf, buf.len, blob_type, cur_sha1);
|
||||
|
||||
out:
|
||||
strbuf_release(&buf);
|
||||
string_list_clear(&sort_uniq_list, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
|
||||
int flag, void *cb)
|
||||
{
|
||||
|
@ -893,7 +981,7 @@ static int notes_display_config(const char *k, const char *v, void *cb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const char *default_notes_ref(void)
|
||||
const char *default_notes_ref(void)
|
||||
{
|
||||
const char *notes_ref = NULL;
|
||||
if (!notes_ref)
|
||||
|
@ -935,7 +1023,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
|
|||
return;
|
||||
if (get_tree_entry(object_sha1, "", sha1, &mode))
|
||||
die("Failed to read notes tree referenced by %s (%s)",
|
||||
notes_ref, object_sha1);
|
||||
notes_ref, sha1_to_hex(object_sha1));
|
||||
|
||||
hashclr(root_tree.key_sha1);
|
||||
hashcpy(root_tree.val_sha1, sha1);
|
||||
|
@ -989,7 +1077,7 @@ void init_display_notes(struct display_notes_opt *opt)
|
|||
string_list_clear(&display_notes_refs, 0);
|
||||
}
|
||||
|
||||
void add_note(struct notes_tree *t, const unsigned char *object_sha1,
|
||||
int add_note(struct notes_tree *t, const unsigned char *object_sha1,
|
||||
const unsigned char *note_sha1, combine_notes_fn combine_notes)
|
||||
{
|
||||
struct leaf_node *l;
|
||||
|
@ -1003,7 +1091,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
|
|||
l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
|
||||
hashcpy(l->key_sha1, object_sha1);
|
||||
hashcpy(l->val_sha1, note_sha1);
|
||||
note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
|
||||
return note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);
|
||||
}
|
||||
|
||||
int remove_note(struct notes_tree *t, const unsigned char *object_sha1)
|
||||
|
@ -1182,7 +1270,7 @@ void format_display_notes(const unsigned char *object_sha1,
|
|||
|
||||
int copy_note(struct notes_tree *t,
|
||||
const unsigned char *from_obj, const unsigned char *to_obj,
|
||||
int force, combine_notes_fn combine_fn)
|
||||
int force, combine_notes_fn combine_notes)
|
||||
{
|
||||
const unsigned char *note = get_note(t, from_obj);
|
||||
const unsigned char *existing_note = get_note(t, to_obj);
|
||||
|
@ -1191,9 +1279,9 @@ int copy_note(struct notes_tree *t,
|
|||
return 1;
|
||||
|
||||
if (note)
|
||||
add_note(t, to_obj, note, combine_fn);
|
||||
return add_note(t, to_obj, note, combine_notes);
|
||||
else if (existing_note)
|
||||
add_note(t, to_obj, null_sha1, combine_fn);
|
||||
return add_note(t, to_obj, null_sha1, combine_notes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
47
notes.h
47
notes.h
|
@ -12,7 +12,10 @@
|
|||
* resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return
|
||||
* value indicates failure.
|
||||
*
|
||||
* The two given SHA1s must both be non-NULL and different from each other.
|
||||
* The two given SHA1s shall both be non-NULL and different from each other.
|
||||
* Either of them (but not both) may be == null_sha1, which indicates an
|
||||
* empty/non-existent note. If the resulting SHA1 (cur_sha1) is == null_sha1,
|
||||
* the note will be removed from the notes tree.
|
||||
*
|
||||
* The default combine_notes function (you get this when passing NULL) is
|
||||
* combine_notes_concatenate(), which appends the contents of the new note to
|
||||
|
@ -24,6 +27,7 @@ typedef int (*combine_notes_fn)(unsigned char *cur_sha1, const unsigned char *ne
|
|||
int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);
|
||||
int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);
|
||||
int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);
|
||||
int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1);
|
||||
|
||||
/*
|
||||
* Notes tree object
|
||||
|
@ -43,6 +47,20 @@ extern struct notes_tree {
|
|||
int dirty;
|
||||
} default_notes_tree;
|
||||
|
||||
/*
|
||||
* Return the default notes ref.
|
||||
*
|
||||
* The default notes ref is the notes ref that is used when notes_ref == NULL
|
||||
* is passed to init_notes().
|
||||
*
|
||||
* This the first of the following to be defined:
|
||||
* 1. The '--ref' option to 'git notes', if given
|
||||
* 2. The $GIT_NOTES_REF environment variable, if set
|
||||
* 3. The value of the core.notesRef config variable, if set
|
||||
* 4. GIT_NOTES_DEFAULT_REF (i.e. "refs/notes/commits")
|
||||
*/
|
||||
const char *default_notes_ref(void);
|
||||
|
||||
/*
|
||||
* Flags controlling behaviour of notes tree initialization
|
||||
*
|
||||
|
@ -76,11 +94,24 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
|
|||
/*
|
||||
* Add the given note object to the given notes_tree structure
|
||||
*
|
||||
* If there already exists a note for the given object_sha1, the given
|
||||
* combine_notes function is invoked to break the tie. If not given (i.e.
|
||||
* combine_notes == NULL), the default combine_notes function for the given
|
||||
* notes_tree is used.
|
||||
*
|
||||
* Passing note_sha1 == null_sha1 indicates the addition of an
|
||||
* empty/non-existent note. This is a (potentially expensive) no-op unless
|
||||
* there already exists a note for the given object_sha1, AND combining that
|
||||
* note with the empty note (using the given combine_notes function) results
|
||||
* in a new/changed note.
|
||||
*
|
||||
* Returns zero on success; non-zero means combine_notes failed.
|
||||
*
|
||||
* IMPORTANT: The changes made by add_note() to the given notes_tree structure
|
||||
* are not persistent until a subsequent call to write_notes_tree() returns
|
||||
* zero.
|
||||
*/
|
||||
void add_note(struct notes_tree *t, const unsigned char *object_sha1,
|
||||
int add_note(struct notes_tree *t, const unsigned char *object_sha1,
|
||||
const unsigned char *note_sha1, combine_notes_fn combine_notes);
|
||||
|
||||
/*
|
||||
|
@ -105,11 +136,18 @@ const unsigned char *get_note(struct notes_tree *t,
|
|||
/*
|
||||
* Copy a note from one object to another in the given notes_tree.
|
||||
*
|
||||
* Fails if the to_obj already has a note unless 'force' is true.
|
||||
* Returns 1 if the to_obj already has a note and 'force' is false. Otherwise,
|
||||
* returns non-zero if 'force' is true, but the given combine_notes function
|
||||
* failed to combine from_obj's note with to_obj's existing note.
|
||||
* Returns zero on success.
|
||||
*
|
||||
* IMPORTANT: The changes made by copy_note() to the given notes_tree structure
|
||||
* are not persistent until a subsequent call to write_notes_tree() returns
|
||||
* zero.
|
||||
*/
|
||||
int copy_note(struct notes_tree *t,
|
||||
const unsigned char *from_obj, const unsigned char *to_obj,
|
||||
int force, combine_notes_fn combine_fn);
|
||||
int force, combine_notes_fn combine_notes);
|
||||
|
||||
/*
|
||||
* Flags controlling behaviour of for_each_note()
|
||||
|
@ -151,6 +189,7 @@ int copy_note(struct notes_tree *t,
|
|||
* notes tree) from within the callback:
|
||||
* - add_note()
|
||||
* - remove_note()
|
||||
* - copy_note()
|
||||
* - free_notes()
|
||||
*/
|
||||
typedef int each_note_fn(const unsigned char *object_sha1,
|
||||
|
|
|
@ -962,6 +962,7 @@ Date: Thu Apr 7 15:27:13 2005 -0700
|
|||
|
||||
Notes (other):
|
||||
a fresh note
|
||||
$whitespace
|
||||
another fresh note
|
||||
EOF
|
||||
|
||||
|
@ -983,8 +984,11 @@ Date: Thu Apr 7 15:27:13 2005 -0700
|
|||
|
||||
Notes (other):
|
||||
a fresh note
|
||||
$whitespace
|
||||
another fresh note
|
||||
$whitespace
|
||||
append 1
|
||||
$whitespace
|
||||
append 2
|
||||
EOF
|
||||
|
||||
|
@ -1061,4 +1065,23 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' '
|
|||
test_must_fail git notes copy one two three
|
||||
'
|
||||
|
||||
test_expect_success 'git notes get-ref (no overrides)' '
|
||||
git config --unset core.notesRef &&
|
||||
unset GIT_NOTES_REF &&
|
||||
test "$(git notes get-ref)" = "refs/notes/commits"
|
||||
'
|
||||
|
||||
test_expect_success 'git notes get-ref (core.notesRef)' '
|
||||
git config core.notesRef refs/notes/foo &&
|
||||
test "$(git notes get-ref)" = "refs/notes/foo"
|
||||
'
|
||||
|
||||
test_expect_success 'git notes get-ref (GIT_NOTES_REF)' '
|
||||
test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar"
|
||||
'
|
||||
|
||||
test_expect_success 'git notes get-ref (--ref)' '
|
||||
test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -168,15 +168,16 @@ INPUT_END
|
|||
}
|
||||
|
||||
verify_concatenated_notes () {
|
||||
git log | grep "^ " > output &&
|
||||
i=$number_of_commits &&
|
||||
while [ $i -gt 0 ]; do
|
||||
echo " commit #$i" &&
|
||||
echo " first note for commit #$i" &&
|
||||
echo " second note for commit #$i" &&
|
||||
i=$(($i-1));
|
||||
done > expect &&
|
||||
test_cmp expect output
|
||||
git log | grep "^ " > output &&
|
||||
i=$number_of_commits &&
|
||||
while [ $i -gt 0 ]; do
|
||||
echo " commit #$i" &&
|
||||
echo " first note for commit #$i" &&
|
||||
echo " " &&
|
||||
echo " second note for commit #$i" &&
|
||||
i=$(($i-1));
|
||||
done > expect &&
|
||||
test_cmp expect output
|
||||
}
|
||||
|
||||
test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""'
|
||||
|
|
|
@ -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 @@
|
|||
#!/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 @@
|
|||
#!/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 @@
|
|||
#!/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
|
|
@ -647,6 +647,7 @@ test_expect_success 'rebase -i can copy notes' '
|
|||
|
||||
cat >expect <<EOF
|
||||
an earlier note
|
||||
|
||||
a note
|
||||
EOF
|
||||
|
||||
|
|
|
@ -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
|
|
@ -255,13 +255,18 @@ EOF
|
|||
|
||||
INPUT_END
|
||||
|
||||
whitespace=" "
|
||||
|
||||
cat >expect <<EXPECT_END
|
||||
fourth commit
|
||||
pre-prefix of note for fourth commit
|
||||
$whitespace
|
||||
prefix of note for fourth commit
|
||||
$whitespace
|
||||
third note for fourth commit
|
||||
third commit
|
||||
prefix of note for third commit
|
||||
$whitespace
|
||||
third note for third commit
|
||||
second commit
|
||||
third note for second commit
|
||||
|
|
Загрузка…
Ссылка в новой задаче