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.cmaint
						commit
						a86ed83cce
					
				|  | @ -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. | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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]. | ||||
|  |  | |||
							
								
								
									
										17
									
								
								builtin.h
								
								
								
								
							
							
						
						
									
										17
									
								
								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); | ||||
|  |  | |||
|  | @ -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); | ||||
|  | ||||
|  |  | |||
|  | @ -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); | ||||
|  | ||||
|  |  | |||
							
								
								
									
										197
									
								
								builtin/notes.c
								
								
								
								
							
							
						
						
									
										197
									
								
								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); | ||||
|  |  | |||
							
								
								
									
										3
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										3
									
								
								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 | ||||
|  |  | |||
							
								
								
									
										13
									
								
								git-am.sh
								
								
								
								
							
							
						
						
									
										13
									
								
								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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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
								
								
								
								
							
							
						
						
									
										190
									
								
								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; | ||||
| } | ||||
|  |  | |||
							
								
								
									
										65
									
								
								notes.h
								
								
								
								
							
							
						
						
									
										65
									
								
								notes.h
								
								
								
								
							|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										6
									
								
								pretty.c
								
								
								
								
							
							
						
						
									
										6
									
								
								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); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										4
									
								
								refs.c
								
								
								
								
							
							
						
						
									
										4
									
								
								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, '/'); | ||||
|  |  | |||
							
								
								
									
										5
									
								
								refs.h
								
								
								
								
							
							
						
						
									
										5
									
								
								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 *); | ||||
|  | ||||
|  |  | |||
							
								
								
									
										21
									
								
								revision.c
								
								
								
								
							
							
						
						
									
										21
									
								
								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); | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										377
									
								
								t/t3301-notes.sh
								
								
								
								
							
							
						
						
									
										377
									
								
								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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano