git notes merge: Manual conflict resolution, part 2/2

When the notes merge conflicts in .git/NOTES_MERGE_WORKTREE have been
resolved, we need to record a new notes commit on the appropriate notes
ref with the resolved notes.

This patch implements 'git notes merge --commit' which the user should
run after resolving conflicts in the notes merge worktree. This command
finalizes the notes merge by recombining the partial notes tree from
part 1 with the now-resolved conflicts in the notes merge worktree in a
merge commit, and updating the appropriate ref to this merge commit.

In order to correctly finalize the merge, we need to keep track of three
things:

- The partial merge result from part 1, containing the auto-merged notes.
  This is now stored into a ref called .git/NOTES_MERGE_PARTIAL.
- The unmerged notes. These are already stored in
  .git/NOTES_MERGE_WORKTREE, thanks to part 1.
- The notes ref to be updated by the finalized merge result. This is now
  stored in a symref called .git/NOTES_MERGE_REF.

In addition to "git notes merge --commit", which uses the above details
to create the finalized notes merge commit, this patch also implements
"git notes merge --reset", which aborts the ongoing notes merge by simply
removing the files/directory described above.

FTR, "git notes merge --commit" reuses "git notes merge --reset" to remove
the information described above (.git/NOTES_MERGE_*) after the notes merge
have been successfully finalized.

The patch also contains documentation and testcases for the two new options.

This patch has been improved by the following contributions:
- Ævar Arnfjörð Bjarmason: Fix nonsense sentence in --commit description
- Sverre Rabbelier: Rename --reset to --abort

Thanks-to: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Thanks-to: Sverre Rabbelier <srabbelier@gmail.com>
Signed-off-by: Johan Herland <johan@herland.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Johan Herland
2010-11-09 22:49:52 +01:00
committed by Junio C Hamano
parent 809f38c8ab
commit 6abb3655ef
5 changed files with 394 additions and 4 deletions

View File

@ -27,6 +27,8 @@ static const char * const git_notes_usage[] = {
"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]",
NULL
@ -65,6 +67,8 @@ static const char * const git_notes_show_usage[] = {
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
};
@ -761,33 +765,119 @@ 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];
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.");
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, NULL, 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)"),
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 (argc != 1) {
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);
@ -820,9 +910,19 @@ static int merge(int argc, const char **argv, const char *prefix)
/* 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 */
printf("Automatic notes merge failed. Fix conflicts in %s.\n",
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);