summaryrefslogtreecommitdiff
path: root/notes-merge.c
diff options
context:
space:
mode:
authorJohan Herland <johan@herland.net>2010-11-09 22:49:51 +0100
committerJunio C Hamano <gitster@pobox.com>2010-11-17 13:22:49 -0800
commit809f38c8abacdbeb7015fdeef03931568c7fddda (patch)
tree22123012e953120aa713d1839c175e6f512b61da /notes-merge.c
parent00f03061e27cad5497abcca2b70fa9fa04818275 (diff)
downloadgit-809f38c8abacdbeb7015fdeef03931568c7fddda.tar.gz
git notes merge: Manual conflict resolution, part 1/2
Conflicts (that are to be resolved manually) are written into a special- purpose working tree, located at .git/NOTES_MERGE_WORKTREE. Within this directory, conflicting notes entries are stored (with conflict markers produced by ll_merge()) using the SHA1 of the annotated object. The .git/NOTES_MERGE_WORKTREE directory will only contain the _conflicting_ note entries. The non-conflicting note entries (aka. the partial merge result) are stored in 'local_tree', and the SHA1 of the resulting commit is written to 'result_sha1'. The return value from notes_merge() is -1. The user is told to edit the files within the .git/NOTES_MERGE_WORKTREE directory in order to resolve the conflicts. The patch also contains documentation and testcases for the correct setup of .git/NOTES_MERGE_WORKTREE. The next part will recombine the partial notes merge result with the resolved conflicts in .git/NOTES_MERGE_WORKTREE to produce the complete merge result. This patch has been improved by the following contributions: - Jonathan Nieder: Use trace_printf(...) instead of OUTPUT(o, 5, ...) Thanks-to: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Johan Herland <johan@herland.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'notes-merge.c')
-rw-r--r--notes-merge.c166
1 files changed, 161 insertions, 5 deletions
diff --git a/notes-merge.c b/notes-merge.c
index 2c0b25b369..ada29067bd 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -3,6 +3,9 @@
#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"
@@ -263,16 +266,169 @@ static void diff_tree_local(struct notes_merge_options *o,
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 --reset' 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));
+
+ 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 was resolved (and added to notes_tree),
- * 1 if conflict
+ * 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 1;
+ 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 */
@@ -479,8 +635,8 @@ int notes_merge(struct notes_merge_options *o,
result = merge_from_diffs(o, base_tree_sha1, local->tree->object.sha1,
remote->tree->object.sha1, local_tree);
- if (result > 0) { /* successful non-trivial merge */
- /* Commit result */
+ 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);