From: Jacob Keller <jacob.kel...@gmail.com>

Teach git-notes about a new configuration option
"notes.<localref>.merge" which selects the merge strategy for a
particular ref. This allows selection of merge strategy different for
each note reference, in addition to the default strategy for all
references.

Signed-off-by: Jacob Keller <jacob.kel...@gmail.com>
Cc: Johan Herland <jo...@herland.net>
Cc: Michael Haggerty <mhag...@alum.mit.edu>
Cc: Eric Sunshine <sunsh...@sunshineco.com>
---
 Documentation/config.txt            |   7 +++
 Documentation/git-notes.txt         |   6 ++
 builtin/notes.c                     | 115 +++++++++++++++++++++++++++++++++++-
 t/t3309-notes-merge-auto-resolve.sh |  50 ++++++++++++++++
 4 files changed, 176 insertions(+), 2 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 0fa88a29dd65..c53ec4538cd3 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1918,6 +1918,13 @@ notes.merge::
        STRATEGIES" section of linkgit:git-notes[1] for more information
        on each strategy.
 
+notes.<localref>.merge::
+       Which merge strategy to choose if the local ref for a notes merge
+       matches <localref>. Is overridden by notes.merge and takes the same
+       values. <localref> may be fully qualified or just under refs/notes/.
+       See "NOTES MERGE STRATEGIES" section in linkgit:git-notes[1] for more
+       information on each strategy.
+
 notes.displayRef::
        The (fully qualified) refname from which to show notes when
        showing commit messages.  The value of this variable can be set
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 9c4f8536182f..1e001807f9d9 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -322,6 +322,12 @@ notes.merge::
 +
 This setting can be overridden by passing the `--strategy` option.
 
+notes.<localref>.merge::
+       Which strategy to choose when merging into <localref>. Uses the same
+       values as notes.merge. <localref> may be either a fully qualified ref
+       or the shortname under "refs/notes/". See "NOTES MERGE STRATEGIES"
+       section above for more information about each strategy.
+
 notes.displayRef::
        Which ref (or refs, if a glob or specified more than once), in
        addition to the default set by `core.notesRef` or
diff --git a/builtin/notes.c b/builtin/notes.c
index de0caa00df1b..b0174d1024dc 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -92,6 +92,10 @@ static const char * const git_notes_get_ref_usage[] = {
 static const char note_template[] =
        "\nWrite/edit the notes for the following object:\n";
 
+static struct note_ref **note_refs;
+static int note_refs_alloc;
+static int note_refs_nr;
+static struct hashmap note_refs_hash;
 static enum notes_merge_strategy merge_strategy;
 
 struct note_data {
@@ -757,12 +761,87 @@ static int parse_notes_strategy(const char *arg, enum 
notes_merge_strategy *stra
        return 0;
 }
 
+struct note_refs_hash_key {
+       const char *str;
+       int len;
+};
+
+struct note_ref {
+       struct hashmap_entry ent; /* must be first */
+
+       const char *name;
+       enum notes_merge_strategy merge_strategy;
+};
+
+static int note_refs_hash_cmp(const struct note_ref *a, const struct note_ref 
*b, const struct note_refs_hash_key *key)
+{
+       if (key)
+               return strncmp(a->name, key->str, key->len) || 
a->name[key->len];
+       else
+               return strcmp(a->name, b->name);
+}
+
+static inline void init_note_refs_hash(void)
+{
+       if (!note_refs_hash.cmpfn)
+               hashmap_init(&note_refs_hash, 
(hashmap_cmp_fn)note_refs_hash_cmp, 0);
+}
+
+static struct note_ref *make_note_ref(const char *name, int len)
+{
+       struct note_ref *ret, *replaced;
+       struct note_refs_hash_key lookup;
+       struct hashmap_entry lookup_entry;
+
+       if (!len)
+               len = strlen(name);
+
+       init_note_refs_hash();
+       lookup.str = name;
+       lookup.len = len;
+       hashmap_entry_init(&lookup_entry, memhash(name, len));
+
+       if ((ret = hashmap_get(&note_refs_hash, &lookup_entry, &lookup)) != 
NULL)
+               return ret;
+
+       ret = xcalloc(1, sizeof(struct note_ref));
+       ALLOC_GROW(note_refs, note_refs_nr + 1, note_refs_alloc);
+       note_refs[note_refs_nr++] = ret;
+       ret->name = xstrndup(name, len);
+
+       hashmap_entry_init(ret, lookup_entry.hash);
+       replaced = hashmap_put(&note_refs_hash, ret);
+       assert(replaced == NULL);  /* no previous entry overwritten */
+       return ret;
+}
+
+static void set_strategy_for_ref(const char *ref)
+{
+       const char *name = ref;
+       struct note_refs_hash_key lookup;
+       struct hashmap_entry lookup_entry;
+       struct note_ref *note;
+
+       skip_prefix(ref, "refs/notes/", &name);
+
+       init_note_refs_hash();
+       lookup.str = name;
+       lookup.len = strlen(name);
+
+       hashmap_entry_init(&lookup_entry, memhash(name, lookup.len));
+
+       note = hashmap_get(&note_refs_hash, &lookup_entry, &lookup);
+       if (note != NULL)
+               merge_strategy = note->merge_strategy;
+}
+
 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;
+       struct note_ref;
        int do_merge = 0, do_commit = 0, do_abort = 0;
        int verbosity = 0, result;
        const char *strategy = NULL;
@@ -815,6 +894,8 @@ static int merge(int argc, const char **argv, const char 
*prefix)
        expand_notes_ref(&remote_ref);
        o.remote_ref = remote_ref.buf;
 
+       set_strategy_for_ref(o.local_ref);
+
        if (strategy && parse_notes_strategy(strategy, &merge_strategy)) {
                error("Unknown -s/--strategy: %s", strategy);
                usage_with_options(git_notes_merge_usage, options);
@@ -957,7 +1038,16 @@ static int get_ref(int argc, const char **argv, const 
char *prefix)
 
 static int git_notes_config(const char *var, const char *value, void *cb)
 {
-       if (!strcmp(var, "notes.merge")) {
+       const char *ref;
+       const char *subkey;
+       struct note_ref *note;
+
+       if (!starts_with(var, "notes."))
+               return git_default_config(var, value, cb);
+       ref = var + 6;
+
+       /* Handle notes.* variables */
+       if (!strcmp(ref, "merge")) {
                if (!value)
                        return config_error_nonbool(var);
                if (parse_notes_strategy(value, &merge_strategy))
@@ -966,7 +1056,28 @@ static int git_notes_config(const char *var, const char 
*value, void *cb)
                        return 0;
        }
 
-       return git_default_config(var, value, cb);
+       if (*ref == '/') {
+               warning("Config notes ref cannot begin with '/': %s", ref);
+               return 0;
+       }
+       subkey = strrchr(ref, '.');
+       if (!subkey)
+               return 0;
+
+       /* skip refs/notes prefix if provided */
+       skip_prefix(ref, "refs/notes/", &ref);
+
+       note = make_note_ref(ref, subkey - ref);
+       if (!strcmp(subkey, ".merge")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               if (parse_notes_strategy(value, &note->merge_strategy))
+                       return error("Unknown notes merge strategy: %s", value);
+               else
+                       return 0;
+       }
+
+       return 0;
 }
 
 int cmd_notes(int argc, const char **argv, const char *prefix)
diff --git a/t/t3309-notes-merge-auto-resolve.sh 
b/t/t3309-notes-merge-auto-resolve.sh
index a773b01b73db..15dd539cd141 100755
--- a/t/t3309-notes-merge-auto-resolve.sh
+++ b/t/t3309-notes-merge-auto-resolve.sh
@@ -383,6 +383,28 @@ test_expect_success 'reset to pre-merge state (y)' '
        verify_notes y y
 '
 
+test_expect_success 'merge z into y with "ours" per-ref configuration option 
=> Non-conflicting 3-way merge' '
+       git -c notes.y.merge="ours" notes merge 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
+'
+
+test_expect_success 'merge z into y with "ours" per-ref configuration option 
prefixed with "refs/notes" => Non-conflicting 3-way merge' '
+       git -c notes.refs/notes/y.merge="ours" notes merge 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
@@ -534,6 +556,34 @@ test_expect_success 'reset to pre-merge state (y)' '
        verify_notes y y
 '
 
+test_expect_success 'merge z into y with "union" strategy overriding per-ref 
configuration => Non-conflicting 3-way merge' '
+       git -c notes.refs/notes/y.merge="theirs" 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
+'
+
+test_expect_success 'merge z into y with "union" per-ref overriding general 
configuration => Non-conflicting 3-way merge' '
+       git -c notes.refs/notes/y.merge="union" -c notes.merge="theirs" notes 
merge 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
+'
+
+test_expect_success 'merge z into y with "manual" per-ref only checks specific 
ref configuration => Conflicting 3-way merge' '
+       test_must_fail git -c notes.refs/notes/z.merge="union" notes merge z &&
+       git notes merge --abort &&
+       verify_notes y y
+'
+
 cat <<EOF | sort >expect_notes_union2
 d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
-- 
2.5.0.482.gfcd5645

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to