Merge branch 'jk/notes-merge-config'

"git notes merge" can be told with "--strategy=<how>" option how to
automatically handle conflicts; this can now be configured by
setting notes.mergeStrategy configuration variable.

* jk/notes-merge-config:
  notes: teach git-notes about notes.<name>.mergeStrategy option
  notes: add notes.mergeStrategy option to select default strategy
  notes: add tests for --commit/--abort/--strategy exclusivity
  notes: extract parse_notes_merge_strategy to notes-utils
  notes: extract enum notes_merge_strategy to notes-utils.h
  notes: document cat_sort_uniq rewriteMode
This commit is contained in:
Junio C Hamano 2015-08-31 15:39:05 -07:00
commit 5b6211aee1
8 changed files with 187 additions and 25 deletions

View File

@ -1942,6 +1942,18 @@ mergetool.writeToTemp::
mergetool.prompt:: mergetool.prompt::
Prompt before each invocation of the merge resolution program. Prompt before each invocation of the merge resolution program.
notes.mergeStrategy::
Which merge strategy to choose by default when resolving notes
conflicts. Must be one of `manual`, `ours`, `theirs`, `union`, or
`cat_sort_uniq`. Defaults to `manual`. See "NOTES MERGE STRATEGIES"
section of linkgit:git-notes[1] for more information on each strategy.
notes.<name>.mergeStrategy::
Which merge strategy to choose when doing a notes merge into
refs/notes/<name>. This overrides the more general
"notes.mergeStrategy". See the "NOTES MERGE STRATEGIES" section in
linkgit:git-notes[1] for more information on the available strategies.
notes.displayRef:: notes.displayRef::
The (fully qualified) refname from which to show notes when The (fully qualified) refname from which to show notes when
showing commit messages. The value of this variable can be set showing commit messages. The value of this variable can be set
@ -1970,8 +1982,8 @@ notes.rewriteMode::
When copying notes during a rewrite (see the When copying notes during a rewrite (see the
"notes.rewrite.<command>" option), determines what to do if "notes.rewrite.<command>" option), determines what to do if
the target commit already has a note. Must be one of the target commit already has a note. Must be one of
`overwrite`, `concatenate`, or `ignore`. Defaults to `overwrite`, `concatenate`, `cat_sort_uniq`, or `ignore`.
`concatenate`. Defaults to `concatenate`.
+ +
This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
environment variable. environment variable.

View File

@ -101,7 +101,7 @@ merge::
any) into the current notes ref (called "local"). any) into the current notes ref (called "local").
+ +
If conflicts arise and a strategy for automatically resolving If conflicts arise and a strategy for automatically resolving
conflicting notes (see the -s/--strategy option) is not given, conflicting notes (see the "NOTES MERGE STRATEGIES" section) is not given,
the "manual" resolver is used. This resolver checks out the the "manual" resolver is used. This resolver checks out the
conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`), conflicting notes in a special worktree (`.git/NOTES_MERGE_WORKTREE`),
and instructs the user to manually resolve the conflicts there. and instructs the user to manually resolve the conflicts there.
@ -183,6 +183,7 @@ OPTIONS
When merging notes, resolve notes conflicts using the given When merging notes, resolve notes conflicts using the given
strategy. The following strategies are recognized: "manual" strategy. The following strategies are recognized: "manual"
(default), "ours", "theirs", "union" and "cat_sort_uniq". (default), "ours", "theirs", "union" and "cat_sort_uniq".
This option overrides the "notes.mergeStrategy" configuration setting.
See the "NOTES MERGE STRATEGIES" section below for more See the "NOTES MERGE STRATEGIES" section below for more
information on each notes merge strategy. information on each notes merge strategy.
@ -247,6 +248,9 @@ When done, the user can either finalize the merge with
'git notes merge --commit', or abort the merge with 'git notes merge --commit', or abort the merge with
'git notes merge --abort'. 'git notes merge --abort'.
Users may select an automated merge strategy from among the following using
either -s/--strategy option or configuring notes.mergeStrategy accordingly:
"ours" automatically resolves conflicting notes in favor of the local "ours" automatically resolves conflicting notes in favor of the local
version (i.e. the current notes ref). version (i.e. the current notes ref).
@ -310,6 +314,20 @@ core.notesRef::
This setting can be overridden through the environment and This setting can be overridden through the environment and
command line. command line.
notes.mergeStrategy::
Which merge strategy to choose by default when resolving notes
conflicts. Must be one of `manual`, `ours`, `theirs`, `union`, or
`cat_sort_uniq`. Defaults to `manual`. See "NOTES MERGE STRATEGIES"
section above for more information on each strategy.
+
This setting can be overridden by passing the `--strategy` option.
notes.<name>.mergeStrategy::
Which merge strategy to choose when doing a notes merge into
refs/notes/<name>. This overrides the more general
"notes.mergeStrategy". See the "NOTES MERGE STRATEGIES" section above
for more information on each available strategy.
notes.displayRef:: notes.displayRef::
Which ref (or refs, if a glob or specified more than once), in Which ref (or refs, if a glob or specified more than once), in
addition to the default set by `core.notesRef` or addition to the default set by `core.notesRef` or
@ -331,7 +349,8 @@ environment variable.
notes.rewriteMode:: notes.rewriteMode::
When copying notes during a rewrite, what to do if the target When copying notes during a rewrite, what to do if the target
commit already has a note. Must be one of `overwrite`, commit already has a note. Must be one of `overwrite`,
`concatenate`, and `ignore`. Defaults to `concatenate`. `concatenate`, `cat_sort_uniq`, or `ignore`. Defaults to
`concatenate`.
+ +
This setting can be overridden with the `GIT_NOTES_REWRITE_MODE` This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
environment variable. environment variable.
@ -368,7 +387,7 @@ does not match any refs is silently ignored.
'GIT_NOTES_REWRITE_MODE':: 'GIT_NOTES_REWRITE_MODE'::
When copying notes during a rewrite, what to do if the target When copying notes during a rewrite, what to do if the target
commit already has a note. commit already has a note.
Must be one of `overwrite`, `concatenate`, and `ignore`. Must be one of `overwrite`, `concatenate`, `cat_sort_uniq`, or `ignore`.
This overrides the `core.rewriteMode` setting. This overrides the `core.rewriteMode` setting.
'GIT_NOTES_REWRITE_REF':: 'GIT_NOTES_REWRITE_REF'::

View File

@ -738,6 +738,19 @@ static int merge_commit(struct notes_merge_options *o)
return ret; return ret;
} }
static int git_config_get_notes_strategy(const char *key,
enum notes_merge_strategy *strategy)
{
const char *value;
if (git_config_get_string_const(key, &value))
return 1;
if (parse_notes_merge_strategy(value, strategy))
git_die_config(key, "unknown notes merge strategy %s", value);
return 0;
}
static int merge(int argc, const char **argv, const char *prefix) static int merge(int argc, const char **argv, const char *prefix)
{ {
struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
@ -796,24 +809,28 @@ static int merge(int argc, const char **argv, const char *prefix)
expand_notes_ref(&remote_ref); expand_notes_ref(&remote_ref);
o.remote_ref = remote_ref.buf; o.remote_ref = remote_ref.buf;
t = init_notes_check("merge");
if (strategy) { if (strategy) {
if (!strcmp(strategy, "manual")) if (parse_notes_merge_strategy(strategy, &o.strategy)) {
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); error("Unknown -s/--strategy: %s", strategy);
usage_with_options(git_notes_merge_usage, options); usage_with_options(git_notes_merge_usage, options);
} }
} } else {
struct strbuf merge_key = STRBUF_INIT;
const char *short_ref = NULL;
t = init_notes_check("merge"); if (!skip_prefix(o.local_ref, "refs/notes/", &short_ref))
die("BUG: local ref %s is outside of refs/notes/",
o.local_ref);
strbuf_addf(&merge_key, "notes.%s.mergeStrategy", short_ref);
if (git_config_get_notes_strategy(merge_key.buf, &o.strategy))
git_config_get_notes_strategy("notes.mergeStrategy", &o.strategy);
strbuf_release(&merge_key);
}
strbuf_addf(&msg, "notes: Merged notes from %s into %s", strbuf_addf(&msg, "notes: Merged notes from %s into %s",
remote_ref.buf, default_notes_ref()); remote_ref.buf, default_notes_ref());

View File

@ -1,6 +1,8 @@
#ifndef NOTES_MERGE_H #ifndef NOTES_MERGE_H
#define NOTES_MERGE_H #define NOTES_MERGE_H
#include "notes-utils.h"
#define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE" #define NOTES_MERGE_WORKTREE "NOTES_MERGE_WORKTREE"
enum notes_merge_verbosity { enum notes_merge_verbosity {
@ -13,13 +15,7 @@ struct notes_merge_options {
const char *remote_ref; const char *remote_ref;
struct strbuf commit_msg; struct strbuf commit_msg;
int verbosity; int verbosity;
enum { enum notes_merge_strategy strategy;
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; unsigned has_worktree:1;
}; };

View File

@ -54,6 +54,24 @@ void commit_notes(struct notes_tree *t, const char *msg)
strbuf_release(&buf); strbuf_release(&buf);
} }
int parse_notes_merge_strategy(const char *v, enum notes_merge_strategy *s)
{
if (!strcmp(v, "manual"))
*s = NOTES_MERGE_RESOLVE_MANUAL;
else if (!strcmp(v, "ours"))
*s = NOTES_MERGE_RESOLVE_OURS;
else if (!strcmp(v, "theirs"))
*s = NOTES_MERGE_RESOLVE_THEIRS;
else if (!strcmp(v, "union"))
*s = NOTES_MERGE_RESOLVE_UNION;
else if (!strcmp(v, "cat_sort_uniq"))
*s = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
else
return -1;
return 0;
}
static combine_notes_fn parse_combine_notes_fn(const char *v) static combine_notes_fn parse_combine_notes_fn(const char *v)
{ {
if (!strcasecmp(v, "overwrite")) if (!strcasecmp(v, "overwrite"))

View File

@ -19,6 +19,14 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
void commit_notes(struct notes_tree *t, const char *msg); void commit_notes(struct notes_tree *t, const char *msg);
enum notes_merge_strategy {
NOTES_MERGE_RESOLVE_MANUAL = 0,
NOTES_MERGE_RESOLVE_OURS,
NOTES_MERGE_RESOLVE_THEIRS,
NOTES_MERGE_RESOLVE_UNION,
NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ
};
struct notes_rewrite_cfg { struct notes_rewrite_cfg {
struct notes_tree **trees; struct notes_tree **trees;
const char *cmd; const char *cmd;
@ -29,6 +37,7 @@ struct notes_rewrite_cfg {
int mode_from_env; int mode_from_env;
}; };
int parse_notes_merge_strategy(const char *v, enum notes_merge_strategy *s);
struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd); struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
int copy_note_for_rewrite(struct notes_rewrite_cfg *c, int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
const unsigned char *from_obj, const unsigned char *to_obj); const unsigned char *from_obj, const unsigned char *to_obj);

View File

@ -298,6 +298,13 @@ test_expect_success 'merge z into y with invalid strategy => Fail/No changes' '
verify_notes y y verify_notes y y
' '
test_expect_success 'merge z into y with invalid configuration option => Fail/No changes' '
git config core.notesRef refs/notes/y &&
test_must_fail git -c notes.mergeStrategy="foo" notes merge z &&
# Verify no changes (y)
verify_notes y y
'
cat <<EOF | sort >expect_notes_ours cat <<EOF | sort >expect_notes_ours
68b8630d25516028bed862719855b3d6768d7833 $commit_sha15 68b8630d25516028bed862719855b3d6768d7833 $commit_sha15
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
@ -365,6 +372,28 @@ test_expect_success 'reset to pre-merge state (y)' '
verify_notes y y verify_notes y y
' '
test_expect_success 'merge z into y with "ours" configuration option => Non-conflicting 3-way merge' '
git -c notes.mergeStrategy="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 => Non-conflicting 3-way merge' '
git -c notes.y.mergeStrategy="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 cat <<EOF | sort >expect_notes_theirs
9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15 9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
@ -432,6 +461,17 @@ test_expect_success 'reset to pre-merge state (y)' '
verify_notes y y verify_notes y y
' '
test_expect_success 'merge z into y with "theirs" strategy overriding configuration option "ours" => Non-conflicting 3-way merge' '
git -c notes.mergeStrategy="ours" 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 cat <<EOF | sort >expect_notes_union
7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15 7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
@ -505,6 +545,34 @@ test_expect_success 'reset to pre-merge state (y)' '
verify_notes y 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.y.mergeStrategy="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.y.mergeStrategy="union" -c notes.mergeStrategy="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.z.mergeStrategy="union" notes merge z &&
git notes merge --abort &&
verify_notes y y
'
cat <<EOF | sort >expect_notes_union2 cat <<EOF | sort >expect_notes_union2
d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15 d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15
5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14
@ -644,4 +712,15 @@ test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflic
verify_notes z cat_sort_uniq verify_notes z cat_sort_uniq
' '
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
'
test_expect_success 'merge y into z with "cat_sort_uniq" strategy configuration option => Non-conflicting 3-way merge' '
git -c notes.mergeStrategy="cat_sort_uniq" notes merge y &&
verify_notes z cat_sort_uniq
'
test_done test_done

View File

@ -314,6 +314,18 @@ y and z notes on 1st commit
EOF EOF
test_expect_success 'do not allow mixing --commit and --abort' '
test_must_fail git notes merge --commit --abort
'
test_expect_success 'do not allow mixing --commit and --strategy' '
test_must_fail git notes merge --commit --strategy theirs
'
test_expect_success 'do not allow mixing --abort and --strategy' '
test_must_fail git notes merge --abort --strategy theirs
'
test_expect_success 'finalize conflicting merge (z => m)' ' test_expect_success 'finalize conflicting merge (z => m)' '
# Resolve conflicts and finalize merge # Resolve conflicts and finalize merge
cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF && cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF &&