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
This commit is contained in:
Junio C Hamano 2010-03-24 16:26:43 -07:00
commit a86ed83cce
25 changed files with 1366 additions and 33 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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].

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.
}

190
notes.c
View file

@ -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;
}

65
notes.h
View file

@ -40,6 +40,7 @@ extern struct notes_tree {
char *ref;
combine_notes_fn *combine_notes;
int initialized;
int dirty;
} default_notes_tree;
/*
@ -99,6 +100,15 @@ void remove_note(struct notes_tree *t, const unsigned char *object_sha1);
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()
*
@ -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

View file

@ -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);
}

4
refs.c
View file

@ -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, '/');

5
refs.h
View file

@ -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 *);

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

183
t/t5407-post-rewrite-hook.sh Executable file
View file

@ -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

View file

@ -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

View file

@ -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