Skip to content

Commit

Permalink
git notes merge: Initial implementation handling trivial merges only
Browse files Browse the repository at this point in the history
This initial implementation of 'git notes merge' only handles the trivial
merge cases (i.e. where the merge is either a no-op, or a fast-forward).

The patch includes testcases for these trivial merge cases.

Future patches will extend the functionality of 'git notes merge'.

This patch has been improved by the following contributions:
- Stephen Boyd: Simplify argc logic
- Stephen Boyd: Use test_commit
- Ævar Arnfjörð Bjarmason: Don't use C99 comments.
- Jonathan Nieder: Add constants for common verbosity values
- Jonathan Nieder: Use trace_printf(...) instead of OUTPUT(o, 5, ...)
- Jonathan Nieder: Remove extraneous show() function
- Jonathan Nieder: Clarify handling of empty/missing notes ref in notes_merge()
- Junio C Hamano: fixup minor style issues

Thanks-to: Stephen Boyd <[email protected]>
Thanks-to: Ævar Arnfjörð Bjarmason <[email protected]>
Thanks-to: Jonathan Nieder <[email protected]>
Thanks-to: Junio C Hamano <[email protected]>
Signed-off-by: Johan Herland <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
jherland authored and gitster committed Nov 17, 2010
1 parent 8ef313e commit 75ef3f4
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,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
Expand Down Expand Up @@ -593,6 +594,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
Expand Down
54 changes: 54 additions & 0 deletions builtin/notes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>]]",
Expand All @@ -25,6 +26,7 @@ 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] <notes_ref>",
"git notes [--ref <notes_ref>] remove [<object>]",
"git notes [--ref <notes_ref>] prune [-n | -v]",
NULL
Expand Down Expand Up @@ -61,6 +63,11 @@ static const char * const git_notes_show_usage[] = {
NULL
};

static const char * const git_notes_merge_usage[] = {
"git notes merge [<options>] <notes_ref>",
NULL
};

static const char * const git_notes_remove_usage[] = {
"git notes remove [<object>]",
NULL
Expand Down Expand Up @@ -772,6 +779,51 @@ static int show(int argc, const char **argv, const char *prefix)
return retval;
}

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_merge_options o;
int verbosity = 0, result;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_END()
};

argc = parse_options(argc, argv, prefix, options,
git_notes_merge_usage, 0);

if (argc != 1) {
error("Must specify a notes ref to merge");
usage_with_options(git_notes_merge_usage, options);
}

init_notes_merge_options(&o);
o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;

o.local_ref = default_notes_ref();
strbuf_addstr(&remote_ref, argv[0]);
expand_notes_ref(&remote_ref);
o.remote_ref = remote_ref.buf;

result = notes_merge(&o, result_sha1);

strbuf_addf(&msg, "notes: Merged notes from %s into %s",
remote_ref.buf, default_notes_ref());
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 {
/* TODO: */
die("'git notes merge' cannot yet handle non-trivial merges!");
}

strbuf_release(&remote_ref);
strbuf_release(&msg);
return 0;
}

static int remove_cmd(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
Expand Down Expand Up @@ -865,6 +917,8 @@ 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"))
Expand Down
120 changes: 120 additions & 0 deletions notes-merge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "cache.h"
#include "commit.h"
#include "refs.h"
#include "notes-merge.h"

void init_notes_merge_options(struct notes_merge_options *o)
{
memset(o, 0, sizeof(struct notes_merge_options));
o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
}

#define OUTPUT(o, v, ...) \
do { \
if ((o)->verbosity >= (v)) { \
printf(__VA_ARGS__); \
puts(""); \
} \
} while (0)

int notes_merge(struct notes_merge_options *o,
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;
int result = 0;

assert(o->local_ref && o->remote_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;
OUTPUT(o, 4, "No merge base found; doing history-less merge");
} else if (!bases->next) {
base_sha1 = bases->item->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;
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;
}

/* TODO: */
result = error("notes_merge() cannot yet handle real merges.");

found_result:
free_commit_list(bases);
trace_printf("notes_merge(): result = %i, result_sha1 = %.7s\n",
result, sha1_to_hex(result_sha1));
return result;
}
36 changes: 36 additions & 0 deletions notes-merge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef NOTES_MERGE_H
#define NOTES_MERGE_H

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;
int verbosity;
};

void init_notes_merge_options(struct notes_merge_options *o);

/*
* Merge notes from o->remote_ref into o->local_ref
*
* 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). The SHA1 of the result is written into 'result_sha1'
* and 0 is returned.
* 2. The merge fails. result_sha1 is set to null_sha1, and non-zero 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,
unsigned char *result_sha1);

#endif
Loading

0 comments on commit 75ef3f4

Please sign in to comment.