notes: implement helpers needed for note copying during rewrite

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Thomas Rast 2010-03-12 18:04:32 +01:00 committed by Junio C Hamano
parent 160baa0d9c
commit 6956f858f6
8 changed files with 385 additions and 8 deletions

View File

@ -1305,6 +1305,36 @@ The effective value of "core.notesRef" (possibly overridden by
GIT_NOTES_REF) is also implicitly added to the list of refs to be GIT_NOTES_REF) is also implicitly added to the list of refs to be
displayed. displayed.
notes.rewrite.<command>::
When rewriting commits with <command> (currently `amend` or
`rebase`) and this variable is set to `true`, git
automatically copies your notes from the original to the
rewritten commit. Defaults to `true`, but see
"notes.rewriteRef" below.
+
This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
environment variable, which must be a colon separated list of refs or
globs.
notes.rewriteMode::
When copying notes during a rewrite (see the
"notes.rewrite.<command>" option), determines what to do if
the target commit already has a note. Must be one of
`overwrite`, `concatenate`, or `ignore`. Defaults to
`concatenate`.
+
This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
environment variable.
notes.rewriteRef::
When copying notes during a rewrite, specifies the (fully
qualified) ref whose notes should be copied. The ref may be a
glob, in which case notes in all matching refs will be copied.
You may also specify this configuration several times.
+
Does not have a default value; you must configure this variable to
enable note rewriting.
pack.window:: pack.window::
The size of the window used by linkgit:git-pack-objects[1] when no The size of the window used by linkgit:git-pack-objects[1] when no
window size is given on the command line. Defaults to 10. window size is given on the command line. Defaults to 10.

View File

@ -35,6 +35,10 @@ This command always manipulates the notes specified in "core.notesRef"
To change which notes are shown by 'git-log', see the To change which notes are shown by 'git-log', see the
"notes.displayRef" configuration. "notes.displayRef" configuration.
See the description of "notes.rewrite.<command>" in
linkgit:git-config[1] for a way of carrying your notes across commands
that rewrite commits.
SUBCOMMANDS SUBCOMMANDS
----------- -----------

View File

@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent. If it is empty, the
preceding SP is also omitted. Currently, no commands pass any preceding SP is also omitted. Currently, no commands pass any
'extra-info'. 'extra-info'.
The hook always runs after the automatic note copying (see
"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
thus has access to these notes.
The following command-specific comments apply: The following command-specific comments apply:
rebase:: rebase::

View File

@ -16,6 +16,7 @@
#include "exec_cmd.h" #include "exec_cmd.h"
#include "run-command.h" #include "run-command.h"
#include "parse-options.h" #include "parse-options.h"
#include "string-list.h"
static const char * const git_notes_usage[] = { static const char * const git_notes_usage[] = {
"git notes [list [<object>]]", "git notes [list [<object>]]",
@ -278,14 +279,121 @@ int commit_notes(struct notes_tree *t, const char *msg)
return 0; return 0;
} }
int notes_copy_from_stdin(int force)
combine_notes_fn *parse_combine_notes_fn(const char *v)
{
if (!strcasecmp(v, "overwrite"))
return combine_notes_overwrite;
else if (!strcasecmp(v, "ignore"))
return combine_notes_ignore;
else if (!strcasecmp(v, "concatenate"))
return combine_notes_concatenate;
else
return NULL;
}
static int notes_rewrite_config(const char *k, const char *v, void *cb)
{
struct notes_rewrite_cfg *c = cb;
if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
c->enabled = git_config_bool(k, v);
return 0;
} else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
if (!v)
config_error_nonbool(k);
c->combine = parse_combine_notes_fn(v);
if (!c->combine) {
error("Bad notes.rewriteMode value: '%s'", v);
return 1;
}
return 0;
} else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
/* note that a refs/ prefix is implied in the
* underlying for_each_glob_ref */
if (!prefixcmp(v, "refs/notes/"))
string_list_add_refs_by_glob(c->refs, v);
else
warning("Refusing to rewrite notes in %s"
" (outside of refs/notes/)", v);
return 0;
}
return 0;
}
struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
{
struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
c->cmd = cmd;
c->enabled = 1;
c->combine = combine_notes_concatenate;
c->refs = xcalloc(1, sizeof(struct string_list));
c->refs->strdup_strings = 1;
c->refs_from_env = 0;
c->mode_from_env = 0;
if (rewrite_mode_env) {
c->mode_from_env = 1;
c->combine = parse_combine_notes_fn(rewrite_mode_env);
if (!c->combine)
error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
" value: '%s'", rewrite_mode_env);
}
if (rewrite_refs_env) {
c->refs_from_env = 1;
string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
}
git_config(notes_rewrite_config, c);
if (!c->enabled || !c->refs->nr) {
string_list_clear(c->refs, 0);
free(c->refs);
free(c);
return NULL;
}
c->trees = load_notes_trees(c->refs);
string_list_clear(c->refs, 0);
free(c->refs);
return c;
}
int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
const unsigned char *from_obj, const unsigned char *to_obj)
{
int ret = 0;
int i;
for (i = 0; c->trees[i]; i++)
ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
return ret;
}
void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
{
int i;
for (i = 0; c->trees[i]; i++) {
commit_notes(c->trees[i], "Notes added by 'git notes copy'");
free_notes(c->trees[i]);
}
free(c->trees);
free(c);
}
int notes_copy_from_stdin(int force, const char *rewrite_cmd)
{ {
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
struct notes_rewrite_cfg *c = NULL;
struct notes_tree *t; struct notes_tree *t;
int ret = 0; int ret = 0;
if (rewrite_cmd) {
c = init_copy_notes_for_rewrite(rewrite_cmd);
if (!c)
return 0;
} else {
init_notes(NULL, NULL, NULL, 0); init_notes(NULL, NULL, NULL, 0);
t = &default_notes_tree; t = &default_notes_tree;
}
while (strbuf_getline(&buf, stdin, '\n') != EOF) { while (strbuf_getline(&buf, stdin, '\n') != EOF) {
unsigned char from_obj[20], to_obj[20]; unsigned char from_obj[20], to_obj[20];
@ -302,7 +410,11 @@ int notes_copy_from_stdin(int force)
if (get_sha1(split[1]->buf, to_obj)) if (get_sha1(split[1]->buf, to_obj))
die("Failed to resolve '%s' as a valid ref.", split[1]->buf); die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite); if (rewrite_cmd)
err = copy_note_for_rewrite(c, from_obj, to_obj);
else
err = copy_note(t, from_obj, to_obj, force,
combine_notes_overwrite);
if (err) { if (err) {
error("Failed to copy notes from '%s' to '%s'", error("Failed to copy notes from '%s' to '%s'",
@ -313,8 +425,12 @@ int notes_copy_from_stdin(int force)
strbuf_list_free(split); strbuf_list_free(split);
} }
if (!rewrite_cmd) {
commit_notes(t, "Notes added by 'git notes copy'"); commit_notes(t, "Notes added by 'git notes copy'");
free_notes(t); free_notes(t);
} else {
finish_copy_notes_for_rewrite(c);
}
return ret; return ret;
} }
@ -330,6 +446,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
remove = 0, prune = 0, force = 0, from_stdin = 0; remove = 0, prune = 0, force = 0, from_stdin = 0;
int given_object = 0, i = 1, retval = 0; int given_object = 0, i = 1, retval = 0;
struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct msg_arg msg = { 0, 0, STRBUF_INIT };
const char *rewrite_cmd = NULL;
struct option options[] = { struct option options[] = {
OPT_GROUP("Notes options"), OPT_GROUP("Notes options"),
OPT_CALLBACK('m', "message", &msg, "MSG", OPT_CALLBACK('m', "message", &msg, "MSG",
@ -342,6 +459,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
"reuse specified note object", parse_reuse_arg), "reuse specified note object", parse_reuse_arg),
OPT_BOOLEAN('f', "force", &force, "replace existing notes"), OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
"load rewriting config for <command> (implies --stdin)"),
OPT_END() OPT_END()
}; };
@ -390,6 +509,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
usage_with_options(git_notes_usage, options); usage_with_options(git_notes_usage, options);
} }
if (!copy && rewrite_cmd) {
error("cannot use --for-rewrite with %s subcommand.", argv[0]);
usage_with_options(git_notes_usage, options);
}
if (!copy && from_stdin) { if (!copy && from_stdin) {
error("cannot use --stdin with %s subcommand.", argv[0]); error("cannot use --stdin with %s subcommand.", argv[0]);
usage_with_options(git_notes_usage, options); usage_with_options(git_notes_usage, options);
@ -397,12 +520,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
if (copy) { if (copy) {
const char *from_ref; const char *from_ref;
if (from_stdin) { if (from_stdin || rewrite_cmd) {
if (argc > 1) { if (argc > 1) {
error("too many parameters"); error("too many parameters");
usage_with_options(git_notes_usage, options); usage_with_options(git_notes_usage, options);
} else { } else {
return notes_copy_from_stdin(force); return notes_copy_from_stdin(force, rewrite_cmd);
} }
} }
if (argc < 3) { if (argc < 3) {

View File

@ -20,6 +20,23 @@ extern int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret, struct commit_list *parents, unsigned char *ret,
const char *author); const char *author);
extern int commit_notes(struct notes_tree *t, const char *msg); extern int commit_notes(struct notes_tree *t, const char *msg);
struct notes_rewrite_cfg {
struct notes_tree **trees;
const char *cmd;
int enabled;
combine_notes_fn *combine;
struct string_list *refs;
int refs_from_env;
int mode_from_env;
};
combine_notes_fn *parse_combine_notes_fn(const char *v);
struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
const unsigned char *from_obj, const unsigned char *to_obj);
void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
extern int check_pager_config(const char *cmd); extern int check_pager_config(const char *cmd);
extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix);

View File

@ -386,6 +386,8 @@ static inline enum object_type object_type(unsigned int mode)
#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF" #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
extern int is_bare_repository_cfg; extern int is_bare_repository_cfg;
extern int is_bare_repository(void); extern int is_bare_repository(void);

View File

@ -810,4 +810,199 @@ test_expect_success 'git notes copy --stdin' '
test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)" test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
' '
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:26:13 2005 -0700
14th
EOF
test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
test_commit 14th &&
test_commit 15th &&
(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
git notes copy --for-rewrite=foo &&
git log -2 > output &&
test_cmp expect output
'
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
Notes (other):
yet another note
$whitespace
yet another note
commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:26:13 2005 -0700
14th
Notes (other):
other note
$whitespace
yet another note
EOF
test_expect_success 'git notes copy --for-rewrite (enabled)' '
git config notes.rewriteMode overwrite &&
git config notes.rewriteRef "refs/notes/*" &&
(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
git notes copy --for-rewrite=foo &&
git log -2 > output &&
test_cmp expect output
'
test_expect_success 'git notes copy --for-rewrite (disabled)' '
git config notes.rewrite.bar false &&
echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
git notes copy --for-rewrite=bar &&
git log -2 > output &&
test_cmp expect output
'
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
Notes (other):
a fresh note
EOF
test_expect_success 'git notes copy --for-rewrite (overwrite)' '
git notes add -f -m"a fresh note" HEAD^ &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
test_expect_success 'git notes copy --for-rewrite (ignore)' '
git config notes.rewriteMode ignore &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
Notes (other):
a fresh note
another fresh note
EOF
test_expect_success 'git notes copy --for-rewrite (append)' '
git notes add -f -m"another fresh note" HEAD^ &&
git config notes.rewriteMode concatenate &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
Notes (other):
a fresh note
another fresh note
append 1
append 2
EOF
test_expect_success 'git notes copy --for-rewrite (append two to one)' '
git notes add -f -m"append 1" HEAD^ &&
git notes add -f -m"append 2" HEAD^^ &&
(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
test_expect_success 'git notes copy --for-rewrite (append empty)' '
git notes remove HEAD^ &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
Notes (other):
replacement note 1
EOF
test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
git notes add -f -m"replacement note 1" HEAD^ &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
cat > expect << EOF
commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
Author: A U Thor <author@example.com>
Date: Thu Apr 7 15:27:13 2005 -0700
15th
Notes (other):
replacement note 2
EOF
test_expect_success 'GIT_NOTES_REWRITE_REF works' '
git config notes.rewriteMode overwrite &&
git notes add -f -m"replacement note 2" HEAD^ &&
git config --unset-all notes.rewriteRef &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
git config notes.rewriteRef refs/notes/other &&
git notes add -f -m"replacement note 3" HEAD^ &&
echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
git log -1 > output &&
test_cmp expect output
'
test_done test_done

View File

@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES
unset SHA1_FILE_DIRECTORY unset SHA1_FILE_DIRECTORY
unset GIT_NOTES_REF unset GIT_NOTES_REF
unset GIT_NOTES_DISPLAY_REF unset GIT_NOTES_DISPLAY_REF
unset GIT_NOTES_REWRITE_REF
unset GIT_NOTES_REWRITE_MODE
GIT_MERGE_VERBOSITY=5 GIT_MERGE_VERBOSITY=5
export GIT_MERGE_VERBOSITY export GIT_MERGE_VERBOSITY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME