summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2010-03-24 16:26:43 -0700
committerJunio C Hamano <gitster@pobox.com>2010-03-24 16:26:43 -0700
commita86ed83cce0fc0b9a9fe4e17ac1bf4f719028d20 (patch)
treeb601185c28e5ea4f4a624debe01427465147650c
parentb6a7a06aa6f083f21b0fca2558c737324eda1602 (diff)
parent66d681998411e8e5034080d5267a5e0f6cdc0c17 (diff)
downloadgit-a86ed83cce0fc0b9a9fe4e17ac1bf4f719028d20.tar.gz
Merge branch 'tr/notes-display'
* tr/notes-display: git-notes(1): add a section about the meaning of history notes: track whether notes_trees were changed at all notes: add shorthand --ref to override GIT_NOTES_REF commit --amend: copy notes to the new commit rebase: support automatic notes copying notes: implement helpers needed for note copying during rewrite notes: implement 'git notes copy --stdin' rebase -i: invoke post-rewrite hook rebase: invoke post-rewrite hook commit --amend: invoke post-rewrite hook Documentation: document post-rewrite hook Support showing notes from more than one notes tree test-lib: unset GIT_NOTES_REF to stop it from influencing tests Conflicts: git-am.sh refs.c
-rw-r--r--Documentation/config.txt53
-rw-r--r--Documentation/git-notes.txt46
-rw-r--r--Documentation/githooks.txt38
-rw-r--r--Documentation/pretty-options.txt11
-rw-r--r--builtin.h17
-rw-r--r--builtin/commit.c45
-rw-r--r--builtin/log.c5
-rw-r--r--builtin/notes.c197
-rw-r--r--cache.h3
-rwxr-xr-xgit-am.sh13
-rwxr-xr-xgit-rebase--interactive.sh52
-rwxr-xr-xgit-rebase.sh6
-rw-r--r--notes.c190
-rw-r--r--notes.h65
-rw-r--r--pretty.c6
-rw-r--r--refs.c4
-rw-r--r--refs.h5
-rw-r--r--revision.c21
-rw-r--r--revision.h5
-rwxr-xr-xt/t3301-notes.sh377
-rwxr-xr-xt/t3400-rebase.sh17
-rwxr-xr-xt/t3404-rebase-interactive.sh24
-rwxr-xr-xt/t5407-post-rewrite-hook.sh183
-rwxr-xr-xt/t7501-commit.sh12
-rw-r--r--t/test-lib.sh4
25 files changed, 1366 insertions, 33 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 1dbded0fdc..06b2f827b4 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -519,10 +519,12 @@ check that makes sure that existing object files will not get overwritten.
core.notesRef::
When showing commit messages, also show notes which are stored in
the given ref. This ref is expected to contain files named
- after the full SHA-1 of the commit they annotate.
+ after the full SHA-1 of the commit they annotate. The ref
+ must be fully qualified.
+
If such a file exists in the given ref, the referenced blob is read, and
-appended to the commit message, separated by a "Notes:" line. If the
+appended to the commit message, separated by a "Notes (<refname>):"
+line (shortened to "Notes:" in the case of "refs/notes/commits"). If the
given ref itself does not exist, it is not an error, but means that no
notes should be printed.
+
@@ -1334,6 +1336,53 @@ mergetool.keepTemporaries::
mergetool.prompt::
Prompt before each invocation of the merge resolution program.
+notes.displayRef::
+ The (fully qualified) refname from which to show notes when
+ showing commit messages. The value of this variable can be set
+ to a glob, in which case notes from all matching refs will be
+ shown. You may also specify this configuration variable
+ several times. A warning will be issued for refs that do not
+ exist, but a glob that does not match any refs is silently
+ ignored.
++
+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+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::
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.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index bef2f3942e..4e5113b837 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
[verse]
'git notes' [list [<object>]]
'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
'git notes' edit [<object>]
'git notes' show [<object>]
@@ -27,12 +27,17 @@ A typical use of notes is to extend a commit message without having
to change the commit itself. Such commit notes can be shown by `git log`
along with the original commit message. To discern these notes from the
message stored in the commit object, the notes are indented like the
-message, after an unindented line saying "Notes:".
+message, after an unindented line saying "Notes (<refname>):" (or
+"Notes:" for the default setting).
-To disable notes, you have to set the config variable core.notesRef to
-the empty string. Alternatively, you can set it to a different ref,
-something like "refs/notes/bugzilla". This setting can be overridden
-by the environment variable "GIT_NOTES_REF".
+This command always manipulates the notes specified in "core.notesRef"
+(see linkgit:git-config[1]), which can be overridden by GIT_NOTES_REF.
+To change which notes are shown by 'git-log', see the
+"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
@@ -55,6 +60,16 @@ copy::
object has none (use -f to overwrite existing notes to the
second object). This subcommand is equivalent to:
`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>. (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
append::
Append to the notes of an existing object (defaults to HEAD).
@@ -101,6 +116,25 @@ OPTIONS
Like '-C', but with '-c' the editor is invoked, so that
the user can further edit the note message.
+--ref <ref>::
+ Manipulate the notes tree in <ref>. This overrides both
+ GIT_NOTES_REF and the "core.notesRef" configuration. The ref
+ is taken to be in `refs/notes/` if it is not qualified.
+
+
+NOTES
+-----
+
+Every notes change creates a new commit at the specified notes ref.
+You can therefore inspect the history of the notes by invoking, e.g.,
+`git log -p notes/commits`.
+
+Currently the commit message only records which operation triggered
+the update, and the commit authorship is determined according to the
+usual rules (see linkgit:git-commit[1]). These details may change in
+the future.
+
+
Author
------
Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c035a7..7183aa9abb 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,44 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
exiting with non-zero status from this script causes the 'git gc --auto'
to abort.
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call
+it!). Its first argument denotes the command it was invoked by:
+currently one of `amend` or `rebase`. Further command-dependent
+arguments may be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+ <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent. If it is empty, the
+preceding SP is also omitted. Currently, no commands pass any
+'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:
+
+rebase::
+ For the 'squash' and 'fixup' operation, all commits that were
+ squashed are listed as being rewritten to the squashed commit.
+ This means that there will be several lines sharing the same
+ 'new-sha1'.
++
+The commits are guaranteed to be listed in the order that they were
+processed by rebase.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index aa96caeab2..af6d2b995a 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -30,9 +30,18 @@ people using 80-column terminals.
defaults to UTF-8.
--no-notes::
---show-notes::
+--show-notes[=<ref>]::
Show the notes (see linkgit:git-notes[1]) that annotate the
commit, when showing the commit log message. This is the default
for `git log`, `git show` and `git whatchanged` commands when
there is no `--pretty`, `--format` nor `--oneline` option is
given on the command line.
++
+With an optional argument, add this ref to the list of notes. The ref
+is taken to be in `refs/notes/` if it is not qualified.
+
+--[no-]standard-notes::
+ Enable or disable populating the notes ref list from the
+ 'core.notesRef' and 'notes.displayRef' variables (or
+ corresponding environment overrides). Enabled by default.
+ See linkgit:git-config[1].
diff --git a/builtin.h b/builtin.h
index cdf98477a6..464588b299 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,23 @@ extern int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret,
const char *author);
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 cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/builtin/commit.c b/builtin/commit.c
index f4c73442cf..8dd104ee0b 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -66,6 +66,7 @@ static char *edit_message, *use_message;
static char *author_name, *author_email, *author_date;
static int all, edit_flag, also, interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
static char *untracked_files_arg, *force_date;
/*
* The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static struct option builtin_commit_options[] = {
OPT_BOOLEAN('z', "null", &null_termination,
"terminate entries with NUL"),
OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+ OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
return git_status_config(k, v, s);
}
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+ const unsigned char *newsha1)
+{
+ /* oldsha1 SP newsha1 LF NUL */
+ static char buf[2*40 + 3];
+ struct child_process proc;
+ const char *argv[3];
+ int code;
+ size_t n;
+
+ if (access(git_path(post_rewrite_hook), X_OK) < 0)
+ return 0;
+
+ argv[0] = git_path(post_rewrite_hook);
+ argv[1] = "amend";
+ argv[2] = NULL;
+
+ memset(&proc, 0, sizeof(proc));
+ proc.argv = argv;
+ proc.in = -1;
+ proc.stdout_to_stderr = 1;
+
+ code = start_command(&proc);
+ if (code)
+ return code;
+ n = snprintf(buf, sizeof(buf), "%s %s\n",
+ sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+ write_in_full(proc.in, buf, n);
+ close(proc.in);
+ return finish_command(&proc);
+}
+
int cmd_commit(int argc, const char **argv, const char *prefix)
{
struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
rerere(0);
run_hook(get_index_file(), "post-commit", NULL);
+ if (amend && !no_post_rewrite) {
+ struct notes_rewrite_cfg *cfg;
+ cfg = init_copy_notes_for_rewrite("amend");
+ if (cfg) {
+ copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+ finish_copy_notes_for_rewrite(cfg);
+ }
+ run_rewrite_hook(head_sha1, commit_sha1);
+ }
if (!quiet)
print_summary(prefix, commit_sha1);
diff --git a/builtin/log.c b/builtin/log.c
index fade77200f..a8dd8c989c 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -60,6 +60,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
if (!rev->show_notes_given && !rev->pretty_given)
rev->show_notes = 1;
+ if (rev->show_notes)
+ init_display_notes(&rev->notes_opt);
if (rev->diffopt.pickaxe || rev->diffopt.filter)
rev->always_show_header = 0;
@@ -1105,6 +1107,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
DIFF_OPT_SET(&rev.diffopt, BINARY);
+ if (rev.show_notes)
+ init_display_notes(&rev.notes_opt);
+
if (!use_stdout)
output_directory = set_outdir(prefix, output_directory);
diff --git a/builtin/notes.c b/builtin/notes.c
index feb710ac4a..4543d11311 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -16,6 +16,7 @@
#include "exec_cmd.h"
#include "run-command.h"
#include "parse-options.h"
+#include "string-list.h"
static const char * const git_notes_usage[] = {
"git notes [list [<object>]]",
@@ -239,6 +240,8 @@ int commit_notes(struct notes_tree *t, const char *msg)
t = &default_notes_tree;
if (!t->initialized || !t->ref || !*t->ref)
die("Cannot commit uninitialized/unreferenced notes tree");
+ if (!t->dirty)
+ return 0; /* don't have to commit an unchanged tree */
/* Prepare commit message and reflog message */
strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
@@ -269,6 +272,161 @@ int commit_notes(struct notes_tree *t, const char *msg)
return 0;
}
+
+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 notes_rewrite_cfg *c = NULL;
+ struct notes_tree *t;
+ 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);
+ t = &default_notes_tree;
+ }
+
+ while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ unsigned char from_obj[20], to_obj[20];
+ struct strbuf **split;
+ int err;
+
+ split = strbuf_split(&buf, ' ');
+ if (!split[0] || !split[1])
+ die("Malformed input line: '%s'.", buf.buf);
+ strbuf_rtrim(split[0]);
+ strbuf_rtrim(split[1]);
+ if (get_sha1(split[0]->buf, from_obj))
+ die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+ if (get_sha1(split[1]->buf, to_obj))
+ die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+ 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) {
+ error("Failed to copy notes from '%s' to '%s'",
+ split[0]->buf, split[1]->buf);
+ ret = 1;
+ }
+
+ strbuf_list_free(split);
+ }
+
+ if (!rewrite_cmd) {
+ commit_notes(t, "Notes added by 'git notes copy'");
+ free_notes(t);
+ } else {
+ finish_copy_notes_for_rewrite(c);
+ }
+ return ret;
+}
+
int cmd_notes(int argc, const char **argv, const char *prefix)
{
struct notes_tree *t;
@@ -278,9 +436,11 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
char logmsg[100];
int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
- remove = 0, prune = 0, force = 0;
+ remove = 0, prune = 0, force = 0, from_stdin = 0;
int given_object = 0, i = 1, retval = 0;
struct msg_arg msg = { 0, 0, STRBUF_INIT };
+ const char *rewrite_cmd = NULL;
+ const char *override_notes_ref = NULL;
struct option options[] = {
OPT_GROUP("Notes contents options"),
{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
@@ -297,6 +457,11 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
parse_reuse_arg},
OPT_GROUP("Other options"),
OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+ OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+ OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+ "use notes from <notes_ref>"),
+ OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+ "load rewriting config for <command> (implies --stdin)"),
OPT_END()
};
@@ -304,6 +469,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
+ if (override_notes_ref) {
+ struct strbuf sb = STRBUF_INIT;
+ if (!prefixcmp(override_notes_ref, "refs/notes/"))
+ /* we're happy */;
+ else if (!prefixcmp(override_notes_ref, "notes/"))
+ strbuf_addstr(&sb, "refs/");
+ else
+ strbuf_addstr(&sb, "refs/notes/");
+ strbuf_addstr(&sb, override_notes_ref);
+ setenv("GIT_NOTES_REF", sb.buf, 1);
+ strbuf_release(&sb);
+ }
+
if (argc && !strcmp(argv[0], "list"))
list = 1;
else if (argc && !strcmp(argv[0], "add"))
@@ -345,8 +523,25 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
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) {
+ error("cannot use --stdin with %s subcommand.", argv[0]);
+ usage_with_options(git_notes_usage, options);
+ }
+
if (copy) {
const char *from_ref;
+ if (from_stdin || rewrite_cmd) {
+ if (argc > 1) {
+ error("too many parameters");
+ usage_with_options(git_notes_usage, options);
+ } else {
+ return notes_copy_from_stdin(force, rewrite_cmd);
+ }
+ }
if (argc < 3) {
error("too few parameters");
usage_with_options(git_notes_usage, options);
diff --git a/cache.h b/cache.h
index f62db0bd7e..2928107bb1 100644
--- a/cache.h
+++ b/cache.h
@@ -387,6 +387,9 @@ static inline enum object_type object_type(unsigned int mode)
#define ATTRIBUTE_MACRO_PREFIX "[attr]"
#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+#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"
/*
* Repository-local GIT_* environment variables
diff --git a/git-am.sh b/git-am.sh
index 50a292a7da..1056075545 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -593,6 +593,7 @@ do
echo "Patch is empty. Was it split wrong?"
stop_here $this
}
+ rm -f "$dotest/original-commit"
if test -f "$dotest/rebasing" &&
commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
-e q "$dotest/$msgnum") &&
@@ -600,6 +601,7 @@ do
then
git cat-file commit "$commit" |
sed -e '1,/^$/d' >"$dotest/msg-clean"
+ echo "$commit" > "$dotest/original-commit"
else
{
sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -783,6 +785,10 @@ do
git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
stop_here $this
+ if test -f "$dotest/original-commit"; then
+ echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+ fi
+
if test -x "$GIT_DIR"/hooks/post-applypatch
then
"$GIT_DIR"/hooks/post-applypatch
@@ -791,5 +797,12 @@ do
go_next
done
+if test -s "$dotest"/rewritten; then
+ git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+ if test -x "$GIT_DIR"/hooks/post-rewrite; then
+ "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+ fi
+fi
+
rm -fr "$dotest"
git gc --auto
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3e4fd1456f..415ae72dbc 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
# command is processed, this file is deleted.
AMEND="$DOTEST"/amend
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s. The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
PRESERVE_MERGES=
STRATEGY=
ONTO=
@@ -198,6 +205,7 @@ make_patch () {
}
die_with_patch () {
+ echo "$1" > "$DOTEST"/stopped-sha
make_patch "$1"
git rerere
die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
fi
+ echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
;;
*)
output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
die_with_patch $1 ""
}
+flush_rewritten_pending() {
+ test -s "$REWRITTEN_PENDING" || return
+ newsha1="$(git rev-parse HEAD^0)"
+ sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+ rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+ oldsha1="$(git rev-parse $1)"
+ echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+ case "$(peek_next_command)" in
+ squash|s|fixup|f)
+ ;;
+ *)
+ flush_rewritten_pending
+ ;;
+ esac
+}
+
do_next () {
rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
mark_action_done
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
+ record_in_rewritten $sha1
;;
reword|r)
comment_for_reflog reword
@@ -445,7 +475,8 @@ do_next () {
mark_action_done
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
- git commit --amend
+ git commit --amend --no-post-rewrite
+ record_in_rewritten $sha1
;;
edit|e)
comment_for_reflog edit
@@ -453,6 +484,7 @@ do_next () {
mark_action_done
pick_one $sha1 ||
die_with_patch $sha1 "Could not apply $sha1... $rest"
+ echo "$1" > "$DOTEST"/stopped-sha
make_patch $sha1
git rev-parse --verify HEAD > "$AMEND"
warn "Stopped at $sha1... $rest"
@@ -509,6 +541,7 @@ do_next () {
rm -f "$SQUASH_MSG" "$FIXUP_MSG"
;;
esac
+ record_in_rewritten $sha1
;;
*)
warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,15 @@ do_next () {
test ! -f "$DOTEST"/verbose ||
git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
} &&
+ {
+ git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+ true # we don't care if this copying failed
+ } &&
+ if test -x "$GIT_DIR"/hooks/post-rewrite &&
+ test -s "$REWRITTEN_LIST"; then
+ "$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+ true # we don't care if this hook failed
+ fi &&
rm -rf "$DOTEST" &&
git gc --auto &&
warn "Successfully rebased and updated $HEADNAME."
@@ -571,7 +613,12 @@ skip_unnecessary_picks () {
esac
echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
- mv -f "$TODO".new "$TODO" ||
+ mv -f "$TODO".new "$TODO" &&
+ case "$(peek_next_command)" in
+ squash|s|fixup|f)
+ record_in_rewritten "$ONTO"
+ ;;
+ esac ||
die "Could not skip unnecessary pick commands"
}
@@ -685,6 +732,7 @@ first and then run 'git rebase --continue' again."
test -n "$amend" && git reset --soft $amend
die "Could not commit staged changes."
}
+ record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
fi
require_clean_work_tree
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7b1d..e0eb9568f3 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
then
printf "Committed: %0${prec}d " $msgnum
fi
+ echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
else
if test -z "$GIT_QUIET"
then
@@ -151,6 +152,11 @@ move_to_original_branch () {
finish_rb_merge () {
move_to_original_branch
+ git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+ if test -x "$GIT_DIR"/hooks/post-rewrite &&
+ test -s "$dotest"/rewritten; then
+ "$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+ fi
rm -r "$dotest"
say All done.
}
diff --git a/notes.c b/notes.c
index 07941b7235..e425e19827 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
#include "utf8.h"
#include "strbuf.h"
#include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
/*
* Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
struct notes_tree default_notes_tree;
+static struct string_list display_notes_refs;
+static struct notes_tree **display_notes_trees;
+
static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
struct int_node *node, unsigned int n);
@@ -828,6 +833,83 @@ int combine_notes_ignore(unsigned char *cur_sha1,
return 0;
}
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+ int flag, void *cb)
+{
+ struct string_list *refs = cb;
+ if (!unsorted_string_list_has_string(refs, path))
+ string_list_append(path, refs);
+ return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+ if (has_glob_specials(glob)) {
+ for_each_glob_ref(string_list_add_one_ref, glob, list);
+ } else {
+ unsigned char sha1[20];
+ if (get_sha1(glob, sha1))
+ warning("notes ref %s is invalid", glob);
+ if (!unsorted_string_list_has_string(list, glob))
+ string_list_append(glob, list);
+ }
+}
+
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+ const char *globs)
+{
+ struct strbuf globbuf = STRBUF_INIT;
+ struct strbuf **split;
+ int i;
+
+ strbuf_addstr(&globbuf, globs);
+ split = strbuf_split(&globbuf, ':');
+
+ for (i = 0; split[i]; i++) {
+ if (!split[i]->len)
+ continue;
+ if (split[i]->buf[split[i]->len-1] == ':')
+ strbuf_setlen(split[i], split[i]->len-1);
+ string_list_add_refs_by_glob(list, split[i]->buf);
+ }
+
+ strbuf_list_free(split);
+ strbuf_release(&globbuf);
+}
+
+static int string_list_add_refs_from_list(struct string_list_item *item,
+ void *cb)
+{
+ struct string_list *list = cb;
+ string_list_add_refs_by_glob(list, item->string);
+ return 0;
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+ int *load_refs = cb;
+
+ if (*load_refs && !strcmp(k, "notes.displayref")) {
+ if (!v)
+ config_error_nonbool(k);
+ string_list_add_refs_by_glob(&display_notes_refs, v);
+ }
+
+ return 0;
+}
+
+static const char *default_notes_ref(void)
+{
+ const char *notes_ref = NULL;
+ if (!notes_ref)
+ notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+ if (!notes_ref)
+ notes_ref = notes_ref_name; /* value of core.notesRef config */
+ if (!notes_ref)
+ notes_ref = GIT_NOTES_DEFAULT_REF;
+ return notes_ref;
+}
+
void init_notes(struct notes_tree *t, const char *notes_ref,
combine_notes_fn combine_notes, int flags)
{
@@ -840,11 +922,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
assert(!t->initialized);
if (!notes_ref)
- notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
- if (!notes_ref)
- notes_ref = notes_ref_name; /* value of core.notesRef config */
- if (!notes_ref)
- notes_ref = GIT_NOTES_DEFAULT_REF;
+ notes_ref = default_notes_ref();
if (!combine_notes)
combine_notes = combine_notes_concatenate;
@@ -855,6 +933,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
t->ref = notes_ref ? xstrdup(notes_ref) : NULL;
t->combine_notes = combine_notes;
t->initialized = 1;
+ t->dirty = 0;
if (flags & NOTES_INIT_EMPTY || !notes_ref ||
read_ref(notes_ref, object_sha1))
@@ -868,6 +947,63 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
load_subtree(t, &root_tree, t->root, 0);
}
+struct load_notes_cb_data {
+ int counter;
+ struct notes_tree **trees;
+};
+
+static int load_one_display_note_ref(struct string_list_item *item,
+ void *cb_data)
+{
+ struct load_notes_cb_data *c = cb_data;
+ struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+ init_notes(t, item->string, combine_notes_ignore, 0);
+ c->trees[c->counter++] = t;
+ return 0;
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+ struct notes_tree **trees;
+ struct load_notes_cb_data cb_data;
+ trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
+ cb_data.counter = 0;
+ cb_data.trees = trees;
+ for_each_string_list(load_one_display_note_ref, refs, &cb_data);
+ trees[cb_data.counter] = NULL;
+ return trees;
+}
+
+void init_display_notes(struct display_notes_opt *opt)
+{
+ char *display_ref_env;
+ int load_config_refs = 0;
+ display_notes_refs.strdup_strings = 1;
+
+ assert(!display_notes_trees);
+
+ if (!opt || !opt->suppress_default_notes) {
+ string_list_append(default_notes_ref(), &display_notes_refs);
+ display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
+ if (display_ref_env) {
+ string_list_add_refs_from_colon_sep(&display_notes_refs,
+ display_ref_env);
+ load_config_refs = 0;
+ } else
+ load_config_refs = 1;
+ }
+
+ git_config(notes_display_config, &load_config_refs);
+
+ if (opt && opt->extra_notes_refs)
+ for_each_string_list(string_list_add_refs_from_list,
+ opt->extra_notes_refs,
+ &display_notes_refs);
+
+ display_notes_trees = load_notes_trees(&display_notes_refs);
+ string_list_clear(&display_notes_refs, 0);
+}
+
void add_note(struct notes_tree *t, const unsigned char *object_sha1,
const unsigned char *note_sha1, combine_notes_fn combine_notes)
{
@@ -876,6 +1012,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
if (!t)
t = &default_notes_tree;
assert(t->initialized);
+ t->dirty = 1;
if (!combine_notes)
combine_notes = t->combine_notes;
l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
@@ -891,6 +1028,7 @@ void remove_note(struct notes_tree *t, const unsigned char *object_sha1)
if (!t)
t = &default_notes_tree;
assert(t->initialized);
+ t->dirty = 1;
hashcpy(l.key_sha1, object_sha1);
hashclr(l.val_sha1);
note_tree_remove(t, t->root, 0, &l);
@@ -1016,8 +1154,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
if (msglen && msg[msglen - 1] == '\n')
msglen--;
- if (flags & NOTES_SHOW_HEADER)
- strbuf_addstr(sb, "\nNotes:\n");
+ if (flags & NOTES_SHOW_HEADER) {
+ const char *ref = t->ref;
+ if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
+ strbuf_addstr(sb, "\nNotes:\n");
+ } else {
+ if (!prefixcmp(ref, "refs/"))
+ ref += 5;
+ if (!prefixcmp(ref, "notes/"))
+ ref += 6;
+ strbuf_addf(sb, "\nNotes (%s):\n", ref);
+ }
+ }
for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
linelen = strchrnul(msg_p, '\n') - msg_p;
@@ -1030,3 +1178,31 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
free(msg);
}
+
+void format_display_notes(const unsigned char *object_sha1,
+ struct strbuf *sb, const char *output_encoding, int flags)
+{
+ int i;
+ assert(display_notes_trees);
+ for (i = 0; display_notes_trees[i]; i++)
+ format_note(display_notes_trees[i], object_sha1, sb,
+ output_encoding, flags);
+}
+
+int copy_note(struct notes_tree *t,
+ const unsigned char *from_obj, const unsigned char *to_obj,
+ int force, combine_notes_fn combine_fn)
+{
+ const unsigned char *note = get_note(t, from_obj);
+ const unsigned char *existing_note = get_note(t, to_obj);
+
+ if (!force && existing_note)
+ return 1;
+
+ if (note)
+ add_note(t, to_obj, note, combine_fn);
+ else if (existing_note)
+ add_note(t, to_obj, null_sha1, combine_fn);
+
+ return 0;
+}
diff --git a/notes.h b/notes.h
index bad03ccab7..ee65bd1a24 100644
--- a/notes.h
+++ b/notes.h
@@ -40,6 +40,7 @@ extern struct notes_tree {
char *ref;
combine_notes_fn *combine_notes;
int initialized;
+ int dirty;
} default_notes_tree;
/*
@@ -100,6 +101,15 @@ const unsigned char *get_note(struct notes_tree *t,
const unsigned char *object_sha1);
/*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+ const unsigned char *from_obj, const unsigned char *to_obj,
+ int force, combine_notes_fn combine_fn);
+
+/*
* Flags controlling behaviour of for_each_note()
*
* Default behaviour of for_each_note() is to traverse every single note object
@@ -198,4 +208,59 @@ void free_notes(struct notes_tree *t);
void format_note(struct notes_tree *t, const unsigned char *object_sha1,
struct strbuf *sb, const char *output_encoding, int flags);
+
+struct string_list;
+
+struct display_notes_opt {
+ int suppress_default_notes:1;
+ struct string_list *extra_notes_refs;
+};
+
+/*
+ * Load the notes machinery for displaying several notes trees.
+ *
+ * If 'opt' is not NULL, then it specifies additional settings for the
+ * displaying:
+ *
+ * - suppress_default_notes indicates that the notes from
+ * core.notesRef and notes.displayRef should not be loaded.
+ *
+ * - extra_notes_refs may contain a list of globs (in the same style
+ * as notes.displayRef) where notes should be loaded from.
+ */
+void init_display_notes(struct display_notes_opt *opt);
+
+/*
+ * Append notes for the given 'object_sha1' from all trees set up by
+ * init_display_notes() to 'sb'. The 'flags' are a bitwise
+ * combination of
+ *
+ * - NOTES_SHOW_HEADER: add a 'Notes (refname):' header
+ *
+ * - NOTES_INDENT: indent the notes by 4 places
+ *
+ * You *must* call init_display_notes() before using this function.
+ */
+void format_display_notes(const unsigned char *object_sha1,
+ struct strbuf *sb, const char *output_encoding, int flags);
+
+/*
+ * Load the notes tree from each ref listed in 'refs'. The output is
+ * an array of notes_tree*, terminated by a NULL.
+ */
+struct notes_tree **load_notes_trees(struct string_list *refs);
+
+/*
+ * Add all refs that match 'glob' to the 'list'.
+ */
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+
+/*
+ * Add all refs from a colon-separated glob list 'globs' to the end of
+ * 'list'. Empty components are ignored. This helper is used to
+ * parse GIT_NOTES_DISPLAY_REF style environment variables.
+ */
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+ const char *globs);
+
#endif
diff --git a/pretty.c b/pretty.c
index f999485a54..6ba3da89b7 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
}
return 0; /* unknown %g placeholder */
case 'N':
- format_note(NULL, commit->object.sha1, sb,
+ format_display_notes(commit->object.sha1, sb,
git_log_output_encoding ? git_log_output_encoding
: git_commit_encoding, 0);
return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
strbuf_addch(sb, '\n');
if (context->show_notes)
- format_note(NULL, commit->object.sha1, sb, encoding,
- NOTES_SHOW_HEADER | NOTES_INDENT);
+ format_display_notes(commit->object.sha1, sb, encoding,
+ NOTES_SHOW_HEADER | NOTES_INDENT);
free(reencoded);
}
diff --git a/refs.c b/refs.c
index a7518b6f0d..0f24c8d5d9 100644
--- a/refs.c
+++ b/refs.c
@@ -698,7 +698,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
{
struct strbuf real_pattern = STRBUF_INIT;
struct ref_filter filter;
- const char *has_glob_specials;
int ret;
if (!prefix && prefixcmp(pattern, "refs/"))
@@ -707,8 +706,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
strbuf_addstr(&real_pattern, prefix);
strbuf_addstr(&real_pattern, pattern);
- has_glob_specials = strpbrk(pattern, "?*[");
- if (!has_glob_specials) {
+ if (!has_glob_specials(pattern)) {
/* Append implied '/' '*' if not present. */
if (real_pattern.buf[real_pattern.len - 1] != '/')
strbuf_addch(&real_pattern, '/');
diff --git a/refs.h b/refs.h
index f7648b9bd3..4a18b083f5 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,11 @@ extern int for_each_replace_ref(each_ref_fn, void *);
extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
+static inline const char *has_glob_specials(const char *pattern)
+{
+ return strpbrk(pattern, "?*[");
+}
+
/* can be used to learn about broken ref and symref */
extern int for_each_rawref(each_ref_fn, void *);
diff --git a/revision.c b/revision.c
index 0471cd3f7e..f4b8b38315 100644
--- a/revision.c
+++ b/revision.c
@@ -12,6 +12,7 @@
#include "patch-ids.h"
#include "decorate.h"
#include "log-tree.h"
+#include "string-list.h"
volatile show_early_output_fn_t show_early_output;
@@ -1191,9 +1192,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--show-notes")) {
revs->show_notes = 1;
revs->show_notes_given = 1;
+ } else if (!prefixcmp(arg, "--show-notes=")) {
+ struct strbuf buf = STRBUF_INIT;
+ revs->show_notes = 1;
+ revs->show_notes_given = 1;
+ if (!revs->notes_opt.extra_notes_refs)
+ revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
+ if (!prefixcmp(arg+13, "refs/"))
+ /* happy */;
+ else if (!prefixcmp(arg+13, "notes/"))
+ strbuf_addstr(&buf, "refs/");
+ else
+ strbuf_addstr(&buf, "refs/notes/");
+ strbuf_addstr(&buf, arg+13);
+ string_list_append(strbuf_detach(&buf, NULL),
+ revs->notes_opt.extra_notes_refs);
} else if (!strcmp(arg, "--no-notes")) {
revs->show_notes = 0;
revs->show_notes_given = 1;
+ } else if (!strcmp(arg, "--standard-notes")) {
+ revs->show_notes_given = 1;
+ revs->notes_opt.suppress_default_notes = 0;
+ } else if (!strcmp(arg, "--no-standard-notes")) {
+ revs->notes_opt.suppress_default_notes = 1;
} else if (!strcmp(arg, "--oneline")) {
revs->verbose_header = 1;
get_commit_format("oneline", revs);
diff --git a/revision.h b/revision.h
index ceae4cae74..568f1c98de 100644
--- a/revision.h
+++ b/revision.h
@@ -3,6 +3,7 @@
#include "parse-options.h"
#include "grep.h"
+#include "notes.h"
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
@@ -20,6 +21,7 @@
struct rev_info;
struct log_info;
+struct string_list;
struct rev_info {
/* Starting list */
@@ -126,6 +128,9 @@ struct rev_info {
struct reflog_walk_info *reflog_info;
struct decoration children;
struct decoration merge_simplification;
+
+ /* notes-specific options: which refs to show */
+ struct display_notes_opt notes_opt;
};
#define REV_TREE_SAME 0
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 37b96871c5..a4a0b1d6c5 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -416,7 +416,7 @@ Date: Thu Apr 7 15:18:13 2005 -0700
6th
-Notes:
+Notes (other):
other note
EOF
@@ -449,7 +449,139 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
test_cmp expect-not-other output
'
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:18:13 2005 -0700
+
+ 6th
+
+Notes:
+ order test
+
+Notes (other):
+ other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:17:13 2005 -0700
+
+ 5th
+
+Notes:
+ replacement for deleted note
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+ GIT_NOTES_REF=refs/notes/commits git notes add \
+ -m"replacement for deleted note" HEAD^ &&
+ GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" &&
+ git config --unset core.notesRef &&
+ git config notes.displayRef "refs/notes/*" &&
+ git log -2 > output &&
+ test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+ git config core.notesRef refs/notes/commits &&
+ git config notes.displayRef refs/notes/other &&
+ git log -2 > output &&
+ test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+ git config --unset core.notesRef &&
+ git config notes.displayRef refs/notes/commits &&
+ git config --add notes.displayRef refs/notes/other &&
+ git log -2 > output &&
+ test_cmp expect-both output
+'
+
+cat > expect-both-reversed << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:18:13 2005 -0700
+
+ 6th
+
+Notes (other):
+ other note
+
+Notes:
+ order test
+EOF
+
+test_expect_success 'notes.displayRef respects order' '
+ git config core.notesRef refs/notes/other &&
+ git config --unset-all notes.displayRef &&
+ git config notes.displayRef refs/notes/commits &&
+ git log -1 > output &&
+ test_cmp expect-both-reversed output
+'
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
+ git config --unset-all core.notesRef &&
+ git config --unset-all notes.displayRef &&
+ GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \
+ git log -2 > output &&
+ test_cmp expect-both output
+'
+
+cat > expect-none << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:18:13 2005 -0700
+
+ 6th
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:17:13 2005 -0700
+
+ 5th
+EOF
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+ git config notes.displayRef "refs/notes/*" &&
+ GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&
+ test_cmp expect-none output
+'
+
+test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' '
+ GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log --show-notes=* -2 > output &&
+ test_cmp expect-both output
+'
+
+cat > expect-commits << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:18:13 2005 -0700
+
+ 6th
+
+Notes:
+ order test
+EOF
+
+test_expect_success '--no-standard-notes' '
+ git log --no-standard-notes --show-notes=commits -1 > output &&
+ test_cmp expect-commits output
+'
+
+test_expect_success '--standard-notes' '
+ git log --no-standard-notes --show-notes=commits \
+ --standard-notes -2 > output &&
+ test_cmp expect-both output
+'
+
+test_expect_success '--show-notes=ref accumulates' '
+ git log --show-notes=other --show-notes=commits \
+ --no-standard-notes -1 > output &&
+ test_cmp expect-both-reversed output
+'
+
test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+ git config core.notesRef refs/notes/other &&
echo "Note on a tree" > expect
git notes add -m "Note on a tree" HEAD: &&
git notes show HEAD: > actual &&
@@ -473,7 +605,7 @@ Date: Thu Apr 7 15:19:13 2005 -0700
7th
-Notes:
+Notes (other):
other note
EOF
@@ -504,7 +636,7 @@ Date: Thu Apr 7 15:21:13 2005 -0700
9th
-Notes:
+Notes (other):
yet another note
EOF
@@ -534,7 +666,7 @@ Date: Thu Apr 7 15:21:13 2005 -0700
9th
-Notes:
+Notes (other):
yet another note
$whitespace
yet another note
@@ -553,7 +685,7 @@ Date: Thu Apr 7 15:22:13 2005 -0700
10th
-Notes:
+Notes (other):
other note
EOF
@@ -570,7 +702,7 @@ Date: Thu Apr 7 15:22:13 2005 -0700
10th
-Notes:
+Notes (other):
other note
$whitespace
yet another note
@@ -589,7 +721,7 @@ Date: Thu Apr 7 15:23:13 2005 -0700
11th
-Notes:
+Notes (other):
other note
$whitespace
yet another note
@@ -620,7 +752,7 @@ Date: Thu Apr 7 15:23:13 2005 -0700
11th
-Notes:
+Notes (other):
yet another note
$whitespace
yet another note
@@ -645,4 +777,233 @@ test_expect_success 'cannot copy note from object without notes' '
test_must_fail git notes copy HEAD^ HEAD
'
+cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:25:13 2005 -0700
+
+ 13th
+
+Notes (other):
+ yet another note
+$whitespace
+ yet another note
+
+commit 7038787dfe22a14c3867ce816dbba39845359719
+Author: A U Thor <author@example.com>
+Date: Thu Apr 7 15:24:13 2005 -0700
+
+ 12th
+
+Notes (other):
+ other note
+$whitespace
+ yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+ (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+ echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+ git notes copy --stdin &&
+ git log -2 > output &&
+ test_cmp expect output &&
+ test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+ 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
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2d66..dbf7dfba9b 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
git diff --exit-code file-with-cr:CR HEAD:CR
'
+test_expect_success 'rebase can copy notes' '
+ git config notes.rewrite.rebase true &&
+ git config notes.rewriteRef "refs/notes/*" &&
+ test_commit n1 &&
+ test_commit n2 &&
+ test_commit n3 &&
+ git notes add -m"a note" n3 &&
+ git rebase --onto n1 n2 &&
+ test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+ git reset --hard n3 &&
+ git rebase -m --onto n1 n2 &&
+ test "a note" = "$(git notes show HEAD)"
+'
+
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e3513709e..19668c2c92 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,28 @@ test_expect_success 'reword' '
git show HEAD~2 | grep "C changed"
'
+test_expect_success 'rebase -i can copy notes' '
+ git config notes.rewrite.rebase true &&
+ git config notes.rewriteRef "refs/notes/*" &&
+ test_commit n1 &&
+ test_commit n2 &&
+ test_commit n3 &&
+ git notes add -m"a note" n3 &&
+ git rebase --onto n1 n2 &&
+ test "a note" = "$(git notes show HEAD)"
+'
+
+cat >expect <<EOF
+an earlier note
+a note
+EOF
+
+test_expect_success 'rebase -i can copy notes over a fixup' '
+ git reset --hard n3 &&
+ git notes add -m"an earlier note" n2 &&
+ GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
+ git notes show > output &&
+ test_cmp expect output
+'
+
test_done
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000000..f0f91f149d
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,183 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit A foo A &&
+ test_commit B foo B &&
+ test_commit C foo C &&
+ test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+ rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+ test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+ test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+ clear_hook_input &&
+ echo "D new message" > newmsg &&
+ oldsha=$(git rev-parse HEAD^0) &&
+ git commit -Fnewmsg --amend &&
+ echo amend > expected.args &&
+ echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+ verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+ clear_hook_input &&
+ echo "D new message again" > newmsg &&
+ git commit --no-post-rewrite -Fnewmsg --amend &&
+ test ! -f post-rewrite.args &&
+ test ! -f post-rewrite.data
+'
+
+test_expect_success 'git rebase' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_must_fail git rebase --onto A B &&
+ echo C > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_must_fail git rebase --onto A B &&
+ test_must_fail git rebase --skip &&
+ echo D > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase -m' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_must_fail git rebase -m --onto A B &&
+ echo C > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_must_fail git rebase --onto A B &&
+ test_must_fail git rebase --skip &&
+ echo D > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+# Helper to work around the lack of one-shot exporting for
+# test_must_fail (as it is a shell function)
+test_fail_interactive_rebase () {
+ (
+ FAKE_LINES="$1" &&
+ shift &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i "$@"
+ )
+}
+
+test_expect_success 'git rebase -i (unchanged)' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_fail_interactive_rebase "1 2" --onto A B &&
+ echo C > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_fail_interactive_rebase "2" --onto A B &&
+ echo D > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+ git reset --hard D &&
+ clear_hook_input &&
+ test_fail_interactive_rebase "1 squash 2" --onto A B &&
+ echo C > foo &&
+ git add foo &&
+ git rebase --continue &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_expect_success 'git rebase -i (fixup without conflict)' '
+ git reset --hard D &&
+ clear_hook_input &&
+ FAKE_LINES="1 fixup 2" git rebase -i B &&
+ echo rebase >expected.args &&
+ cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+ verify_hook_input
+'
+
+test_done
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901d47..8297cb4f1e 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
'
+test_expect_success 'amend can copy notes' '
+
+ git config notes.rewrite.amend true &&
+ git config notes.rewriteRef "refs/notes/*" &&
+ test_commit foo &&
+ git notes add -m"a note" &&
+ test_tick &&
+ git commit --amend -m"new foo" &&
+ test "$(git notes show)" = "a note"
+
+'
+
test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index a0e396a952..c582964b0d 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -54,6 +54,10 @@ unset GIT_OBJECT_DIRECTORY
unset GIT_CEILING_DIRECTORIES
unset SHA1_FILE_DIRECTORIES
unset SHA1_FILE_DIRECTORY
+unset GIT_NOTES_REF
+unset GIT_NOTES_DISPLAY_REF
+unset GIT_NOTES_REWRITE_REF
+unset GIT_NOTES_REWRITE_MODE
GIT_MERGE_VERBOSITY=5
export GIT_MERGE_VERBOSITY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME