All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC/PATCH] notes: rework subcommands and parse options
@ 2010-02-25  8:24 Stephen Boyd
  2010-02-25 18:26 ` Tim Henigan
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Stephen Boyd @ 2010-02-25  8:24 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johan Herland, Thomas Rast

Running 'git notes copy -h' is not very helfpul right now. It lists
the options for all the git notes subcommands and is rather confusing.
Fix this by splitting cmd_notes() into separate functions for each
subcommand (besides append and edit since they're very similar) and
only providing a usage message for the subcommand.

This has an added benefit of reducing the code complexity while making
it safer and easier to read. The downside is we get some code bloat
from similar setup and teardown needed for notes and options parsing.
We also get a bit stricter in options parsing by only allowing
the ref option to come before the subcommand.

Cc: Johan Herland <johan@herland.net>
Cc: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Stephen Boyd <bebarino@gmail.com>
---

This is based on tr/notes-display in pu.

I think this will be a better approach and probably easier to
maintain in the long run. Maybe some more common code can be combined?

 builtin-notes.c |  543 ++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 379 insertions(+), 164 deletions(-)

diff --git a/builtin-notes.c b/builtin-notes.c
index 2e45be9..a8a8bef 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -30,6 +30,46 @@ static const char * const git_notes_usage[] = {
 	NULL
 };
 
+static const char * const git_notes_list_usage[] = {
+	"git notes [list [<object>]]",
+	NULL
+};
+
+static const char * const git_notes_add_usage[] = {
+	"git notes add [<options>] [<object>]",
+	NULL
+};
+
+static const char * const git_notes_copy_usage[] = {
+	"git notes copy [<options>] <from-object> <to-object>",
+	NULL
+};
+
+static const char * const git_notes_append_usage[] = {
+	"git notes append [<options>] [<object>]",
+	NULL
+};
+
+static const char * const git_notes_edit_usage[] = {
+	"git notes edit [<object>]",
+	NULL
+};
+
+static const char * const git_notes_show_usage[] = {
+	"git notes show [<object>]",
+	NULL
+};
+
+static const char * const git_notes_remove_usage[] = {
+	"git notes remove [<object>]",
+	NULL
+};
+
+static const char * const git_notes_prune_usage[] = {
+	"git notes prune",
+	NULL
+};
+
 static const char note_template[] =
 	"\n"
 	"#\n"
@@ -279,7 +319,6 @@ 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"))
@@ -434,219 +473,395 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 	return ret;
 }
 
-int cmd_notes(int argc, const char **argv, const char *prefix)
+static struct notes_tree *init_notes_check(const char *subcommand)
 {
 	struct notes_tree *t;
-	unsigned char object[20], from_obj[20], new_note[20];
-	const unsigned char *note;
-	const char *object_ref;
-	char logmsg[100];
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
 
-	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 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;
+	if (prefixcmp(t->ref, "refs/notes/"))
+		die("Refusing to %s notes in %s (outside of refs/notes/)",
+		    subcommand, t->ref);
+	return t;
+}
+
+static int list(int argc, const char **argv, const char *prefix)
+{
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20];
+	const unsigned char *note;
+	int retval = -1;
 	struct option options[] = {
-		OPT_GROUP("Notes options"),
-		OPT_CALLBACK('m', "message", &msg, "MSG",
-			     "note contents as a string", parse_msg_arg),
-		OPT_CALLBACK('F', "file", &msg, "FILE",
-			     "note contents in a file", parse_file_arg),
-		OPT_CALLBACK('c', "reedit-message", &msg, "OBJECT",
-			   "reuse and edit specified note object", parse_reedit_arg),
-		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
-			   "reuse specified note object", parse_reuse_arg),
-		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()
 	};
 
-	git_config(git_default_config, NULL);
-
-	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
+	if (argc)
+		argc = parse_options(argc, argv, prefix, options,
+				     git_notes_list_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"))
-		add = 1;
-	else if (argc && !strcmp(argv[0], "copy"))
-		copy = 1;
-	else if (argc && !strcmp(argv[0], "append"))
-		append = 1;
-	else if (argc && !strcmp(argv[0], "edit"))
-		edit = 1;
-	else if (argc && !strcmp(argv[0], "show"))
-		show = 1;
-	else if (argc && !strcmp(argv[0], "remove"))
-		remove = 1;
-	else if (argc && !strcmp(argv[0], "prune"))
-		prune = 1;
-	else if (!argc) {
-		list = 1; /* Default to 'list' if no other subcommand given */
-		i = 0;
+	if (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_list_usage, options);
 	}
 
-	if (list + add + copy + append + edit + show + remove + prune != 1)
-		usage_with_options(git_notes_usage, options);
+	object_ref = argc ? argv[0] : "HEAD";
 
-	if (msg.given && !(add || append || edit)) {
-		error("cannot use -m/-F/-c/-C options with %s subcommand.",
-		      argv[0]);
-		usage_with_options(git_notes_usage, options);
-	}
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
 
-	if (msg.given && edit) {
-		fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
-			"for the 'edit' subcommand.\n"
-			"Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
-	}
+	t = init_notes_check("list");
+	note = get_note(t, object);
 
-	if (force && !(add || copy)) {
-		error("cannot use -f option with %s subcommand.", argv[0]);
-		usage_with_options(git_notes_usage, options);
-	}
+	if (note) {
+		puts(sha1_to_hex(note));
+		retval = 0;
+	} else if (argc == 0)
+		retval = for_each_note(t, 0, list_each_note, NULL);
 
-	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);
-	}
+	free_notes(t);
+	return retval;
+}
 
-	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);
-		}
-		from_ref = argv[i++];
-		if (get_sha1(from_ref, from_obj))
-			die("Failed to resolve '%s' as a valid ref.", from_ref);
-	}
+static int add(int argc, const char **argv, const char *prefix)
+{
+	int force = 0;
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20], new_note[20];
+	char logmsg[100];
+	const unsigned char *note;
+	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	struct option options[] = {
+		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+			"note contents as a string", PARSE_OPT_NONEG,
+			parse_msg_arg},
+		{ OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+			"note contents in a file", PARSE_OPT_NONEG,
+			parse_file_arg},
+		{ OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+			"reuse and edit specified note object", PARSE_OPT_NONEG,
+			parse_reedit_arg},
+		{ OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+			"reuse specified note object", PARSE_OPT_NONEG,
+			parse_reuse_arg},
+		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_END()
+	};
 
-	given_object = argc > i;
-	object_ref = given_object ? argv[i++] : "HEAD";
+	argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
+			     0);
 
-	if (argc > i || (prune && given_object)) {
+	if (1 < argc) {
 		error("too many parameters");
-		usage_with_options(git_notes_usage, options);
+		usage_with_options(git_notes_add_usage, options);
 	}
 
+	object_ref = argc ? argv[0] : "HEAD";
+
 	if (get_sha1(object_ref, object))
 		die("Failed to resolve '%s' as a valid ref.", object_ref);
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	t = init_notes_check("add");
+	note = get_note(t, object);
 
-	if (prefixcmp(t->ref, "refs/notes/"))
-		die("Refusing to %s notes in %s (outside of refs/notes/)",
-		    argv[0], t->ref);
+	if (note) {
+		if (!force)
+			return	error("Cannot add notes. Found existing notes "
+				      "for object %s. Use '-f' to overwrite "
+				      "existing notes", sha1_to_hex(object));
+		fprintf(stderr, "Overwriting existing notes for object %s\n",
+			sha1_to_hex(object));
+	}
 
-	note = get_note(t, object);
+	create_note(object, &msg, 0, note, new_note);
 
-	/* list command */
+	if (is_null_sha1(new_note))
+		remove_note(t, object);
+	else
+		add_note(t, object, new_note, combine_notes_overwrite);
 
-	if (list) {
-		if (given_object) {
-			if (note) {
-				puts(sha1_to_hex(note));
-				goto end;
-			}
+	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
+		 is_null_sha1(new_note) ? "removed" : "added", "add");
+	commit_notes(t, logmsg);
+	free_notes(t);
+	strbuf_release(&(msg.buf));
+	return 0;
+}
+
+static int copy(int argc, const char **argv, const char *prefix)
+{
+	int force = 0, from_stdin = 0;
+	const unsigned char *from_note, *note;
+	const char *object_ref;
+	unsigned char object[20], from_obj[20], new_note[20];
+	char logmsg[100];
+	struct notes_tree *t;
+	const char *rewrite_cmd = NULL;
+	struct option options[] = {
+		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies "
+			   "--stdin)"),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,
+			     0);
+
+	if (from_stdin || rewrite_cmd) {
+		if (argc) {
+			error("too many parameters");
+			usage_with_options(git_notes_copy_usage, options);
 		} else {
-			retval = for_each_note(t, 0, list_each_note, NULL);
-			goto end;
+			return notes_copy_from_stdin(force, rewrite_cmd);
 		}
 	}
 
-	/* show command */
-
-	if ((list || show) && !note) {
-		error("No note found for object %s.", sha1_to_hex(object));
-		retval = 1;
-		goto end;
-	} else if (show) {
-		const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
-		retval = execv_git_cmd(show_args);
-		goto end;
+	if (2 < argc) {
+		error("too few parameters");
+		usage_with_options(git_notes_copy_usage, options);
 	}
 
-	/* add/append/edit/remove/prune command */
+	if (get_sha1(argv[0], from_obj))
+		die("Failed to resolve '%s' as a valid ref.", argv[0]);
 
-	if ((add || copy) && note) {
-		if (!force) {
-			error("Cannot %s notes. Found existing notes for object"
-			      " %s. Use '-f' to overwrite existing notes",
-			      argv[0], sha1_to_hex(object));
-			retval = 1;
-			goto end;
-		}
+	object_ref = 1 < argc ? argv[1] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check("copy");
+	note = get_note(t, object);
+
+	if (note) {
+		if (!force)
+			return error("Cannot copy notes. Found existing notes "
+				     "for object %s. Use '-f' to overwrite "
+				     "existing notes", sha1_to_hex(object));
 		fprintf(stderr, "Overwriting existing notes for object %s\n",
 			sha1_to_hex(object));
 	}
 
-	if (remove) {
-		msg.given = 1;
-		msg.use_editor = 0;
-		strbuf_reset(&(msg.buf));
+	from_note = get_note(t, from_obj);
+	if (!from_note)
+		return error("Missing notes on source object %s. Cannot copy.",
+			     sha1_to_hex(from_obj));
+	hashcpy(new_note, from_note);
+
+	if (is_null_sha1(new_note))
+		remove_note(t, object);
+	else
+		add_note(t, object, new_note, combine_notes_overwrite);
+
+	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
+		 is_null_sha1(new_note) ? "removed" : "added", "copy");
+	commit_notes(t, logmsg);
+	free_notes(t);
+	return 0;
+}
+
+static int append_edit(int argc, const char **argv, const char *prefix)
+{
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20], new_note[20];
+	const unsigned char *note;
+	char logmsg[100];
+	const char * const *usage;
+	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	struct option options[] = {
+		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+			"note contents as a string", PARSE_OPT_NONEG,
+			parse_msg_arg},
+		{ OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+			"note contents in a file", PARSE_OPT_NONEG,
+			parse_file_arg},
+		{ OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+			"reuse and edit specified note object", PARSE_OPT_NONEG,
+			parse_reedit_arg},
+		{ OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+			"reuse specified note object", PARSE_OPT_NONEG,
+			parse_reuse_arg},
+		OPT_END()
+	};
+	int edit = !strcmp(argv[0], "edit");
+
+	usage = edit ? git_notes_edit_usage : git_notes_append_usage;
+	argc = parse_options(argc, argv, prefix, options, usage,
+			     PARSE_OPT_KEEP_ARGV0);
+
+	if (2 < argc) {
+		error("too many parameters");
+		usage_with_options(usage, options);
 	}
 
-	if (prune) {
-		hashclr(new_note);
-		prune_notes(t);
-		goto commit;
-	} else if (copy) {
-		const unsigned char *from_note = get_note(t, from_obj);
-		if (!from_note) {
-			error("Missing notes on source object %s. Cannot copy.",
-			      sha1_to_hex(from_obj));
-			retval = 1;
-			goto end;
-		}
-		hashcpy(new_note, from_note);
-	} else
-		create_note(object, &msg, append, note, new_note);
+	if (msg.given && edit)
+		fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
+			"for the 'edit' subcommand.\n"
+			"Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
+
+	object_ref = 1 < argc ? argv[1] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check(argv[0]);
+	note = get_note(t, object);
+
+	create_note(object, &msg, !edit, note, new_note);
 
 	if (is_null_sha1(new_note))
 		remove_note(t, object);
 	else
 		add_note(t, object, new_note, combine_notes_overwrite);
 
-commit:
 	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
 		 is_null_sha1(new_note) ? "removed" : "added", argv[0]);
 	commit_notes(t, logmsg);
-
-end:
 	free_notes(t);
 	strbuf_release(&(msg.buf));
+	return 0;
+}
+
+static int show(int argc, const char **argv, const char *prefix)
+{
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20];
+	const unsigned char *note;
+	int retval;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, git_notes_show_usage,
+			     0);
+
+	if (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_show_usage, options);
+	}
+
+	object_ref = argc ? argv[0] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check("show");
+	note = get_note(t, object);
+
+	if (!note)
+		retval = error("No note found for object %s.",
+				sha1_to_hex(object));
+	else {
+		const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
+		retval = execv_git_cmd(show_args);
+	}
+	free_notes(t);
 	return retval;
 }
+
+static int remove_cmd(int argc, const char **argv, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20];
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_notes_remove_usage, 0);
+
+	if (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_remove_usage, options);
+	}
+
+	object_ref = argc ? argv[0] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check("remove");
+
+	fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object));
+	remove_note(t, object);
+
+	commit_notes(t, "Notes removed by 'git notes remove'");
+	free_notes(t);
+	return 0;
+}
+
+static int prune(int argc, const char **argv, const char *prefix)
+{
+	struct notes_tree *t;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,
+			     0);
+
+	if (argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_prune_usage, options);
+	}
+
+	t = init_notes_check("prune");
+
+	prune_notes(t);
+	commit_notes(t, "Notes removed by 'git notes prune'");
+	free_notes(t);
+	return 0;
+}
+
+int cmd_notes(int argc, const char **argv, const char *prefix)
+{
+	int result;
+	const char *override_notes_ref = NULL;
+	struct option options[] = {
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
+		OPT_END()
+	};
+
+	git_config(git_default_config, NULL);
+	argc = parse_options(argc, argv, prefix, options, git_notes_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+
+	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 < 1 || !strcmp(argv[0], "list"))
+		result = list(argc, argv, prefix);
+	else if (!strcmp(argv[0], "add"))
+		result = add(argc, argv, prefix);
+	else if (!strcmp(argv[0], "copy"))
+		result = copy(argc, argv, prefix);
+	else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
+		result = append_edit(argc, argv, prefix);
+	else if (!strcmp(argv[0], "show"))
+		result = show(argc, argv, prefix);
+	else if (!strcmp(argv[0], "remove"))
+		result = remove_cmd(argc, argv, prefix);
+	else if (!strcmp(argv[0], "prune"))
+		result = prune(argc, argv, prefix);
+	else {
+		result = error("Unknown subcommand: %s", argv[0]);
+	}
+
+	return result ? 1 : 0;
+}
-- 
1.7.0.90.g251a4

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [RFC/PATCH] notes: rework subcommands and parse options
  2010-02-25  8:24 [RFC/PATCH] notes: rework subcommands and parse options Stephen Boyd
@ 2010-02-25 18:26 ` Tim Henigan
  2010-02-26 11:22 ` Johan Herland
  2010-02-27  8:59 ` [PATCH] " Stephen Boyd
  2 siblings, 0 replies; 4+ messages in thread
From: Tim Henigan @ 2010-02-25 18:26 UTC (permalink / raw)
  To: Stephen Boyd; +Cc: git, Junio C Hamano, Johan Herland, Thomas Rast

On Thu, Feb 25, 2010 at 3:24 AM, Stephen Boyd <bebarino@gmail.com> wrote:
>
> +       else {
> +               result = error("Unknown subcommand: %s", argv[0]);
> +       }

In the case where an unknown subcommand was given, shouldn't the usage
instructions be printed?  i.e. add the following after the error()?

    usage_with_options(git_notes_usage, options);

-Tim

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [RFC/PATCH] notes: rework subcommands and parse options
  2010-02-25  8:24 [RFC/PATCH] notes: rework subcommands and parse options Stephen Boyd
  2010-02-25 18:26 ` Tim Henigan
@ 2010-02-26 11:22 ` Johan Herland
  2010-02-27  8:59 ` [PATCH] " Stephen Boyd
  2 siblings, 0 replies; 4+ messages in thread
From: Johan Herland @ 2010-02-26 11:22 UTC (permalink / raw)
  To: Stephen Boyd; +Cc: git, Junio C Hamano, Thomas Rast

On Thursday 25 February 2010, Stephen Boyd wrote:
> Running 'git notes copy -h' is not very helfpul right now. It lists
> the options for all the git notes subcommands and is rather confusing.
> Fix this by splitting cmd_notes() into separate functions for each
> subcommand (besides append and edit since they're very similar) and
> only providing a usage message for the subcommand.
> 
> This has an added benefit of reducing the code complexity while making
> it safer and easier to read. The downside is we get some code bloat
> from similar setup and teardown needed for notes and options parsing.
> We also get a bit stricter in options parsing by only allowing
> the ref option to come before the subcommand.
> 
> Cc: Johan Herland <johan@herland.net>
> Cc: Thomas Rast <trast@student.ethz.ch>
> Signed-off-by: Stephen Boyd <bebarino@gmail.com>
> ---
> 
> This is based on tr/notes-display in pu.
> 
> I think this will be a better approach and probably easier to
> maintain in the long run. Maybe some more common code can be combined?

I like the intent of this, but the implementation is not there yet:

(your patch edited for clarity while revieweing)

> +static int list(int argc, const char **argv, const char *prefix)

[...]

> +	object_ref = argc ? argv[0] : "HEAD";
> 
> +	if (get_sha1(object_ref, object))
> +		die("Failed to resolve '%s' as a valid ref.", object_ref);
> 
> +	t = init_notes_check("list");
> +	note = get_note(t, object);
> 
> +	if (note) {
> +		puts(sha1_to_hex(note));
> +		retval = 0;
> +	} else if (argc == 0)
> +		retval = for_each_note(t, 0, list_each_note, NULL);

This changes the semantics when calling "git notes list" (in the case where
HEAD has notes) from displaying all notes, to displaying HEAD's notes.

IMO, "git notes list" without further parameters should always show a list
of all notes regardless of whether HEAD has notes or not.

My patch below fixes this, and adds a test verifying those semantics (i.e.
the test succeeds before applying your patch, fails after applying it, and
succeeds again after squashing in my additional patch below).

> +static int add(int argc, const char **argv, const char *prefix)

[...]

> +	if (note) {
> +		if (!force)
> +			return	error("Cannot add notes. Found existing notes "
> +				      "for object %s. Use '-f' to overwrite "
> +				      "existing notes", sha1_to_hex(object));

Missing cleanup code before the "return":

	free_notes(t);
	strbuf_release(&(msg.buf));

Also s/<tab>/<space>/ between "return" and "error"

> +static int copy(int argc, const char **argv, const char *prefix)

[...]

> +	if (2 < argc) {
> +		error("too few parameters");

s/few/many/

[...]

> +	if (note) {
> +		if (!force)
> +			return error("Cannot copy notes. Found existing notes "
> +				     "for object %s. Use '-f' to overwrite "
> +				     "existing notes", sha1_to_hex(object));

Missing cleanup code before the "return":

	free_notes(t);

>  		fprintf(stderr, "Overwriting existing notes for object %s\n",
>  			sha1_to_hex(object));
>  	}
> 
> +	from_note = get_note(t, from_obj);
> +	if (!from_note)
> +		return error("Missing notes on source object %s. Cannot copy.",
> +			     sha1_to_hex(from_obj));

Missing cleanup code before the "return":

	free_notes(t);

> +	hashcpy(new_note, from_note);
> +
> +	if (is_null_sha1(new_note))
> +		remove_note(t, object);
> +	else
> +		add_note(t, object, new_note, combine_notes_overwrite);
> +
> +	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
> +		 is_null_sha1(new_note) ? "removed" : "added", "copy");
> +	commit_notes(t, logmsg);

Can get rid of "new_note" and "logmsg" in this block. Also is_null_sha1()
should never happen. Hence, simply replace with:

	add_note(t, object, from_note, combine_notes_overwrite);
	commit_notes(t, "Notes added by 'git notes copy'");

> +static int show(int argc, const char **argv, const char *prefix)

[...]

> +	if (!note)
> +		retval = error("No note found for object %s.",
> +				sha1_to_hex(object));

Minor whitespace issue (replace last TAB with 7 spaces)

> +int cmd_notes(int argc, const char **argv, const char *prefix)
> +{
> +	int result;
> +	const char *override_notes_ref = NULL;
> +	struct option options[] = {
> +		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
> +			   "use notes from <notes_ref>"),
> +		OPT_END()
> +	};
> +	git_config(git_default_config, NULL);
> +	argc = parse_options(argc, argv, prefix, options, git_notes_usage,
> +			     PARSE_OPT_STOP_AT_NON_OPTION);

Here, you move --ref from being a "git notes <subcommand>" option to a
"git notes" option (i.e. given _before_ the <subcommand>). There is
nothing wrong with this per se, but it needs to be reflected in
git_notes_usage[] (see my patch below for a proposal).

[...]

> +	if (argc < 1 || !strcmp(argv[0], "list"))
> +		result = list(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "add"))
> +		result = add(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "copy"))
> +		result = copy(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
> +		result = append_edit(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "show"))
> +		result = show(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "remove"))
> +		result = remove_cmd(argc, argv, prefix);
> +	else if (!strcmp(argv[0], "prune"))
> +		result = prune(argc, argv, prefix);
> +	else {
> +		result = error("Unknown subcommand: %s", argv[0]);

As Tim Henigan already noted, you need

	usage_with_options(git_notes_usage, options);

If you squash in the following patch (fixes all of my above complaints),
you can add

	Acked-by: Johan Herland <johan@herland.net>

to your commit message. :)

---
 builtin/notes.c  |   77 ++++++++++++++++++++++++++---------------------------
 t/t3301-notes.sh |   11 +++++++
 2 files changed, 49 insertions(+), 39 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index d9dd1f2..e471b28 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -19,14 +19,14 @@
 #include "string-list.h"
 
 static const char * const git_notes_usage[] = {
-	"git notes [list [<object>]]",
-	"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
-	"git notes copy [-f] <from-object> <to-object>",
-	"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
-	"git notes edit [<object>]",
-	"git notes show [<object>]",
-	"git notes remove [<object>]",
-	"git notes prune",
+	"git notes [--ref <notes_ref>] [list [<object>]]",
+	"git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+	"git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>",
+	"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+	"git notes [--ref <notes_ref>] edit [<object>]",
+	"git notes [--ref <notes_ref>] show [<object>]",
+	"git notes [--ref <notes_ref>] remove [<object>]",
+	"git notes [--ref <notes_ref>] prune",
 	NULL
 };
 
@@ -478,7 +478,6 @@ static struct notes_tree *init_notes_check(const char *subcommand)
 
 static int list(int argc, const char **argv, const char *prefix)
 {
-	const char *object_ref;
 	struct notes_tree *t;
 	unsigned char object[20];
 	const unsigned char *note;
@@ -496,18 +495,18 @@ static int list(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_list_usage, options);
 	}
 
-	object_ref = argc ? argv[0] : "HEAD";
-
-	if (get_sha1(object_ref, object))
-		die("Failed to resolve '%s' as a valid ref.", object_ref);
-
 	t = init_notes_check("list");
-	note = get_note(t, object);
-
-	if (note) {
-		puts(sha1_to_hex(note));
-		retval = 0;
-	} else if (argc == 0)
+	if (argc) {
+		if (get_sha1(argv[0], object))
+			die("Failed to resolve '%s' as a valid ref.", argv[0]);
+		note = get_note(t, object);
+		if (note) {
+			puts(sha1_to_hex(note));
+			retval = 0;
+		} else
+			retval = error("No note found for object %s.",
+				       sha1_to_hex(object));
+	} else
 		retval = for_each_note(t, 0, list_each_note, NULL);
 
 	free_notes(t);
@@ -557,10 +556,13 @@ static int add(int argc, const char **argv, const char *prefix)
 	note = get_note(t, object);
 
 	if (note) {
-		if (!force)
-			return	error("Cannot add notes. Found existing notes "
-				      "for object %s. Use '-f' to overwrite "
-				      "existing notes", sha1_to_hex(object));
+		if (!force) {
+			free_notes(t);
+			strbuf_release(&(msg.buf));
+			return error("Cannot add notes. Found existing notes "
+				     "for object %s. Use '-f' to overwrite "
+				     "existing notes", sha1_to_hex(object));
+		}
 		fprintf(stderr, "Overwriting existing notes for object %s\n",
 			sha1_to_hex(object));
 	}
@@ -585,8 +587,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 	int force = 0, from_stdin = 0;
 	const unsigned char *from_note, *note;
 	const char *object_ref;
-	unsigned char object[20], from_obj[20], new_note[20];
-	char logmsg[100];
+	unsigned char object[20], from_obj[20];
 	struct notes_tree *t;
 	const char *rewrite_cmd = NULL;
 	struct option options[] = {
@@ -611,7 +612,7 @@ static int copy(int argc, const char **argv, const char *prefix)
 	}
 
 	if (2 < argc) {
-		error("too few parameters");
+		error("too many parameters");
 		usage_with_options(git_notes_copy_usage, options);
 	}
 
@@ -627,28 +628,25 @@ static int copy(int argc, const char **argv, const char *prefix)
 	note = get_note(t, object);
 
 	if (note) {
-		if (!force)
+		if (!force) {
+			free_notes(t);
 			return error("Cannot copy notes. Found existing notes "
 				     "for object %s. Use '-f' to overwrite "
 				     "existing notes", sha1_to_hex(object));
+		}
 		fprintf(stderr, "Overwriting existing notes for object %s\n",
 			sha1_to_hex(object));
 	}
 
 	from_note = get_note(t, from_obj);
-	if (!from_note)
+	if (!from_note) {
+		free_notes(t);
 		return error("Missing notes on source object %s. Cannot copy.",
 			     sha1_to_hex(from_obj));
-	hashcpy(new_note, from_note);
-
-	if (is_null_sha1(new_note))
-		remove_note(t, object);
-	else
-		add_note(t, object, new_note, combine_notes_overwrite);
+	}
 
-	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
-		 is_null_sha1(new_note) ? "removed" : "added", "copy");
-	commit_notes(t, logmsg);
+	add_note(t, object, from_note, combine_notes_overwrite);
+	commit_notes(t, "Notes added by 'git notes copy'");
 	free_notes(t);
 	return 0;
 }
@@ -745,7 +743,7 @@ static int show(int argc, const char **argv, const char *prefix)
 
 	if (!note)
 		retval = error("No note found for object %s.",
-				sha1_to_hex(object));
+			       sha1_to_hex(object));
 	else {
 		const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
 		retval = execv_git_cmd(show_args);
@@ -852,6 +850,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		result = prune(argc, argv, prefix);
 	else {
 		result = error("Unknown subcommand: %s", argv[0]);
+		usage_with_options(git_notes_usage, options);
 	}
 
 	return result ? 1 : 0;
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index a4a0b1d..1d6cd45 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -378,6 +378,17 @@ test_expect_success 'append to existing note with "git notes append"' '
 	test_cmp expect output
 '
 
+cat > expect_list << EOF
+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
+4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d
+EOF
+
+test_expect_success '"git notes list" does not expand to "git notes list HEAD"' '
+	git notes list > output &&
+	test_cmp expect_list output
+'
+
 test_expect_success 'appending empty string does not change existing note' '
 	git notes append -m "" &&
 	git notes show > output &&
-- 
1.7.0.207.gac4ec


Have fun! :)

...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH] notes: rework subcommands and parse options
  2010-02-25  8:24 [RFC/PATCH] notes: rework subcommands and parse options Stephen Boyd
  2010-02-25 18:26 ` Tim Henigan
  2010-02-26 11:22 ` Johan Herland
@ 2010-02-27  8:59 ` Stephen Boyd
  2 siblings, 0 replies; 4+ messages in thread
From: Stephen Boyd @ 2010-02-27  8:59 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johan Herland, Thomas Rast

Running 'git notes copy -h' is not very helfpul right now. It lists
the options for all the git notes subcommands and is rather confusing.
Fix this by splitting cmd_notes() into separate functions for each
subcommand (besides append and edit since they're very similar) and
only providing a usage message for the subcommand.

This has an added benefit of reducing the code complexity while making
it safer and easier to read. The downside is we get some code bloat
from similar setup and teardown needed for notes and options parsing.
We also get a bit stricter in options parsing by only allowing
the ref option to come before the subcommand.

Acked-by: Johan Herland <johan@herland.net>
Cc: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Stephen Boyd <bebarino@gmail.com>
---

Based on latest version of tr/notes-display in pu.

Thanks to Tim Henigan for the suggestion and Johan Herland for the
suggestions/cleanup.

 builtin-notes.c  |  549 +++++++++++++++++++++++++++++++++++++----------------
 t/t3301-notes.sh |   11 +
 2 files changed, 394 insertions(+), 166 deletions(-)

diff --git a/builtin-notes.c b/builtin-notes.c
index 2e45be9..3b84392 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -19,13 +19,54 @@
 #include "string-list.h"
 
 static const char * const git_notes_usage[] = {
+	"git notes [--ref <notes_ref>] [list [<object>]]",
+	"git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+	"git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>",
+	"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+	"git notes [--ref <notes_ref>] edit [<object>]",
+	"git notes [--ref <notes_ref>] show [<object>]",
+	"git notes [--ref <notes_ref>] remove [<object>]",
+	"git notes [--ref <notes_ref>] prune",
+	NULL
+};
+
+static const char * const git_notes_list_usage[] = {
 	"git notes [list [<object>]]",
-	"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
-	"git notes copy [-f] <from-object> <to-object>",
-	"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
+	NULL
+};
+
+static const char * const git_notes_add_usage[] = {
+	"git notes add [<options>] [<object>]",
+	NULL
+};
+
+static const char * const git_notes_copy_usage[] = {
+	"git notes copy [<options>] <from-object> <to-object>",
+	"git notes copy --stdin [<from-object> <to-object>]...",
+	NULL
+};
+
+static const char * const git_notes_append_usage[] = {
+	"git notes append [<options>] [<object>]",
+	NULL
+};
+
+static const char * const git_notes_edit_usage[] = {
 	"git notes edit [<object>]",
+	NULL
+};
+
+static const char * const git_notes_show_usage[] = {
 	"git notes show [<object>]",
+	NULL
+};
+
+static const char * const git_notes_remove_usage[] = {
 	"git notes remove [<object>]",
+	NULL
+};
+
+static const char * const git_notes_prune_usage[] = {
 	"git notes prune",
 	NULL
 };
@@ -279,7 +320,6 @@ 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"))
@@ -434,219 +474,396 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 	return ret;
 }
 
-int cmd_notes(int argc, const char **argv, const char *prefix)
+static struct notes_tree *init_notes_check(const char *subcommand)
 {
 	struct notes_tree *t;
-	unsigned char object[20], from_obj[20], new_note[20];
-	const unsigned char *note;
-	const char *object_ref;
-	char logmsg[100];
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
 
-	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 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;
+	if (prefixcmp(t->ref, "refs/notes/"))
+		die("Refusing to %s notes in %s (outside of refs/notes/)",
+		    subcommand, t->ref);
+	return t;
+}
+
+static int list(int argc, const char **argv, const char *prefix)
+{
+	struct notes_tree *t;
+	unsigned char object[20];
+	const unsigned char *note;
+	int retval = -1;
 	struct option options[] = {
-		OPT_GROUP("Notes options"),
-		OPT_CALLBACK('m', "message", &msg, "MSG",
-			     "note contents as a string", parse_msg_arg),
-		OPT_CALLBACK('F', "file", &msg, "FILE",
-			     "note contents in a file", parse_file_arg),
-		OPT_CALLBACK('c', "reedit-message", &msg, "OBJECT",
-			   "reuse and edit specified note object", parse_reedit_arg),
-		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
-			   "reuse specified note object", parse_reuse_arg),
-		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()
 	};
 
-	git_config(git_default_config, NULL);
-
-	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
+	if (argc)
+		argc = parse_options(argc, argv, prefix, options,
+				     git_notes_list_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 (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_list_usage, options);
 	}
 
-	if (argc && !strcmp(argv[0], "list"))
-		list = 1;
-	else if (argc && !strcmp(argv[0], "add"))
-		add = 1;
-	else if (argc && !strcmp(argv[0], "copy"))
-		copy = 1;
-	else if (argc && !strcmp(argv[0], "append"))
-		append = 1;
-	else if (argc && !strcmp(argv[0], "edit"))
-		edit = 1;
-	else if (argc && !strcmp(argv[0], "show"))
-		show = 1;
-	else if (argc && !strcmp(argv[0], "remove"))
-		remove = 1;
-	else if (argc && !strcmp(argv[0], "prune"))
-		prune = 1;
-	else if (!argc) {
-		list = 1; /* Default to 'list' if no other subcommand given */
-		i = 0;
-	}
+	t = init_notes_check("list");
+	if (argc) {
+		if (get_sha1(argv[0], object))
+			die("Failed to resolve '%s' as a valid ref.", argv[0]);
+		note = get_note(t, object);
+		if (note) {
+			puts(sha1_to_hex(note));
+			retval = 0;
+		} else
+			retval = error("No note found for object %s.",
+				       sha1_to_hex(object));
+	} else
+		retval = for_each_note(t, 0, list_each_note, NULL);
 
-	if (list + add + copy + append + edit + show + remove + prune != 1)
-		usage_with_options(git_notes_usage, options);
+	free_notes(t);
+	return retval;
+}
 
-	if (msg.given && !(add || append || edit)) {
-		error("cannot use -m/-F/-c/-C options with %s subcommand.",
-		      argv[0]);
-		usage_with_options(git_notes_usage, options);
-	}
+static int add(int argc, const char **argv, const char *prefix)
+{
+	int retval = 0, force = 0;
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20], new_note[20];
+	char logmsg[100];
+	const unsigned char *note;
+	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	struct option options[] = {
+		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+			"note contents as a string", PARSE_OPT_NONEG,
+			parse_msg_arg},
+		{ OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+			"note contents in a file", PARSE_OPT_NONEG,
+			parse_file_arg},
+		{ OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+			"reuse and edit specified note object", PARSE_OPT_NONEG,
+			parse_reedit_arg},
+		{ OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+			"reuse specified note object", PARSE_OPT_NONEG,
+			parse_reuse_arg},
+		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_END()
+	};
 
-	if (msg.given && edit) {
-		fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
-			"for the 'edit' subcommand.\n"
-			"Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
-	}
+	argc = parse_options(argc, argv, prefix, options, git_notes_add_usage,
+			     0);
 
-	if (force && !(add || copy)) {
-		error("cannot use -f option with %s subcommand.", argv[0]);
-		usage_with_options(git_notes_usage, options);
+	if (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_add_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);
-	}
+	object_ref = argc ? argv[0] : "HEAD";
 
-	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);
-		}
-		from_ref = argv[i++];
-		if (get_sha1(from_ref, from_obj))
-			die("Failed to resolve '%s' as a valid ref.", from_ref);
-	}
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
 
-	given_object = argc > i;
-	object_ref = given_object ? argv[i++] : "HEAD";
+	t = init_notes_check("add");
+	note = get_note(t, object);
 
-	if (argc > i || (prune && given_object)) {
-		error("too many parameters");
-		usage_with_options(git_notes_usage, options);
+	if (note) {
+		if (!force) {
+			retval = error("Cannot add notes. Found existing notes "
+				       "for object %s. Use '-f' to overwrite "
+				       "existing notes", sha1_to_hex(object));
+			goto out;
+		}
+		fprintf(stderr, "Overwriting existing notes for object %s\n",
+			sha1_to_hex(object));
 	}
 
-	if (get_sha1(object_ref, object))
-		die("Failed to resolve '%s' as a valid ref.", object_ref);
+	create_note(object, &msg, 0, note, new_note);
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (is_null_sha1(new_note))
+		remove_note(t, object);
+	else
+		add_note(t, object, new_note, combine_notes_overwrite);
 
-	if (prefixcmp(t->ref, "refs/notes/"))
-		die("Refusing to %s notes in %s (outside of refs/notes/)",
-		    argv[0], t->ref);
+	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
+		 is_null_sha1(new_note) ? "removed" : "added", "add");
+	commit_notes(t, logmsg);
+out:
+	free_notes(t);
+	strbuf_release(&(msg.buf));
+	return retval;
+}
 
-	note = get_note(t, object);
+static int copy(int argc, const char **argv, const char *prefix)
+{
+	int retval = 0, force = 0, from_stdin = 0;
+	const unsigned char *from_note, *note;
+	const char *object_ref;
+	unsigned char object[20], from_obj[20];
+	struct notes_tree *t;
+	const char *rewrite_cmd = NULL;
+	struct option options[] = {
+		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies "
+			   "--stdin)"),
+		OPT_END()
+	};
 
-	/* list command */
+	argc = parse_options(argc, argv, prefix, options, git_notes_copy_usage,
+			     0);
 
-	if (list) {
-		if (given_object) {
-			if (note) {
-				puts(sha1_to_hex(note));
-				goto end;
-			}
+	if (from_stdin || rewrite_cmd) {
+		if (argc) {
+			error("too many parameters");
+			usage_with_options(git_notes_copy_usage, options);
 		} else {
-			retval = for_each_note(t, 0, list_each_note, NULL);
-			goto end;
+			return notes_copy_from_stdin(force, rewrite_cmd);
 		}
 	}
 
-	/* show command */
-
-	if ((list || show) && !note) {
-		error("No note found for object %s.", sha1_to_hex(object));
-		retval = 1;
-		goto end;
-	} else if (show) {
-		const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
-		retval = execv_git_cmd(show_args);
-		goto end;
+	if (2 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_copy_usage, options);
 	}
 
-	/* add/append/edit/remove/prune command */
+	if (get_sha1(argv[0], from_obj))
+		die("Failed to resolve '%s' as a valid ref.", argv[0]);
+
+	object_ref = 1 < argc ? argv[1] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
 
-	if ((add || copy) && note) {
+	t = init_notes_check("copy");
+	note = get_note(t, object);
+
+	if (note) {
 		if (!force) {
-			error("Cannot %s notes. Found existing notes for object"
-			      " %s. Use '-f' to overwrite existing notes",
-			      argv[0], sha1_to_hex(object));
-			retval = 1;
-			goto end;
+			retval = error("Cannot copy notes. Found existing "
+				       "notes for object %s. Use '-f' to "
+				       "overwrite existing notes",
+				       sha1_to_hex(object));
+			goto out;
 		}
 		fprintf(stderr, "Overwriting existing notes for object %s\n",
 			sha1_to_hex(object));
 	}
 
-	if (remove) {
-		msg.given = 1;
-		msg.use_editor = 0;
-		strbuf_reset(&(msg.buf));
+	from_note = get_note(t, from_obj);
+	if (!from_note) {
+		retval = error("Missing notes on source object %s. Cannot "
+			       "copy.", sha1_to_hex(from_obj));
+		goto out;
 	}
 
-	if (prune) {
-		hashclr(new_note);
-		prune_notes(t);
-		goto commit;
-	} else if (copy) {
-		const unsigned char *from_note = get_note(t, from_obj);
-		if (!from_note) {
-			error("Missing notes on source object %s. Cannot copy.",
-			      sha1_to_hex(from_obj));
-			retval = 1;
-			goto end;
-		}
-		hashcpy(new_note, from_note);
-	} else
-		create_note(object, &msg, append, note, new_note);
+	add_note(t, object, from_note, combine_notes_overwrite);
+	commit_notes(t, "Notes added by 'git notes copy'");
+out:
+	free_notes(t);
+	return retval;
+}
+
+static int append_edit(int argc, const char **argv, const char *prefix)
+{
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20], new_note[20];
+	const unsigned char *note;
+	char logmsg[100];
+	const char * const *usage;
+	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	struct option options[] = {
+		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
+			"note contents as a string", PARSE_OPT_NONEG,
+			parse_msg_arg},
+		{ OPTION_CALLBACK, 'F', "file", &msg, "FILE",
+			"note contents in a file", PARSE_OPT_NONEG,
+			parse_file_arg},
+		{ OPTION_CALLBACK, 'c', "reedit-message", &msg, "OBJECT",
+			"reuse and edit specified note object", PARSE_OPT_NONEG,
+			parse_reedit_arg},
+		{ OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT",
+			"reuse specified note object", PARSE_OPT_NONEG,
+			parse_reuse_arg},
+		OPT_END()
+	};
+	int edit = !strcmp(argv[0], "edit");
+
+	usage = edit ? git_notes_edit_usage : git_notes_append_usage;
+	argc = parse_options(argc, argv, prefix, options, usage,
+			     PARSE_OPT_KEEP_ARGV0);
+
+	if (2 < argc) {
+		error("too many parameters");
+		usage_with_options(usage, options);
+	}
+
+	if (msg.given && edit)
+		fprintf(stderr, "The -m/-F/-c/-C options have been deprecated "
+			"for the 'edit' subcommand.\n"
+			"Please use 'git notes add -f -m/-F/-c/-C' instead.\n");
+
+	object_ref = 1 < argc ? argv[1] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check(argv[0]);
+	note = get_note(t, object);
+
+	create_note(object, &msg, !edit, note, new_note);
 
 	if (is_null_sha1(new_note))
 		remove_note(t, object);
 	else
 		add_note(t, object, new_note, combine_notes_overwrite);
 
-commit:
 	snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
 		 is_null_sha1(new_note) ? "removed" : "added", argv[0]);
 	commit_notes(t, logmsg);
-
-end:
 	free_notes(t);
 	strbuf_release(&(msg.buf));
+	return 0;
+}
+
+static int show(int argc, const char **argv, const char *prefix)
+{
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20];
+	const unsigned char *note;
+	int retval;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, git_notes_show_usage,
+			     0);
+
+	if (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_show_usage, options);
+	}
+
+	object_ref = argc ? argv[0] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check("show");
+	note = get_note(t, object);
+
+	if (!note)
+		retval = error("No note found for object %s.",
+			       sha1_to_hex(object));
+	else {
+		const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
+		retval = execv_git_cmd(show_args);
+	}
+	free_notes(t);
 	return retval;
 }
+
+static int remove_cmd(int argc, const char **argv, const char *prefix)
+{
+	struct option options[] = {
+		OPT_END()
+	};
+	const char *object_ref;
+	struct notes_tree *t;
+	unsigned char object[20];
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_notes_remove_usage, 0);
+
+	if (1 < argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_remove_usage, options);
+	}
+
+	object_ref = argc ? argv[0] : "HEAD";
+
+	if (get_sha1(object_ref, object))
+		die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+	t = init_notes_check("remove");
+
+	fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object));
+	remove_note(t, object);
+
+	commit_notes(t, "Notes removed by 'git notes remove'");
+	free_notes(t);
+	return 0;
+}
+
+static int prune(int argc, const char **argv, const char *prefix)
+{
+	struct notes_tree *t;
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, git_notes_prune_usage,
+			     0);
+
+	if (argc) {
+		error("too many parameters");
+		usage_with_options(git_notes_prune_usage, options);
+	}
+
+	t = init_notes_check("prune");
+
+	prune_notes(t);
+	commit_notes(t, "Notes removed by 'git notes prune'");
+	free_notes(t);
+	return 0;
+}
+
+int cmd_notes(int argc, const char **argv, const char *prefix)
+{
+	int result;
+	const char *override_notes_ref = NULL;
+	struct option options[] = {
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
+		OPT_END()
+	};
+
+	git_config(git_default_config, NULL);
+	argc = parse_options(argc, argv, prefix, options, git_notes_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+
+	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 < 1 || !strcmp(argv[0], "list"))
+		result = list(argc, argv, prefix);
+	else if (!strcmp(argv[0], "add"))
+		result = add(argc, argv, prefix);
+	else if (!strcmp(argv[0], "copy"))
+		result = copy(argc, argv, prefix);
+	else if (!strcmp(argv[0], "append") || !strcmp(argv[0], "edit"))
+		result = append_edit(argc, argv, prefix);
+	else if (!strcmp(argv[0], "show"))
+		result = show(argc, argv, prefix);
+	else if (!strcmp(argv[0], "remove"))
+		result = remove_cmd(argc, argv, prefix);
+	else if (!strcmp(argv[0], "prune"))
+		result = prune(argc, argv, prefix);
+	else {
+		result = error("Unknown subcommand: %s", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
+	return result ? 1 : 0;
+}
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index aeec90a..5b26211 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -377,6 +377,17 @@ test_expect_success 'append to existing note with "git notes append"' '
 	test_cmp expect output
 '
 
+cat > expect_list << EOF
+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
+4b6ad22357cc8a1296720574b8d2fbc22fab0671 bd1753200303d0a0344be813e504253b3d98e74d
+EOF
+
+test_expect_success '"git notes list" does not expand to "git notes list HEAD"' '
+	git notes list > output &&
+	test_cmp expect_list output
+'
+
 test_expect_success 'appending empty string does not change existing note' '
 	git notes append -m "" &&
 	git notes show > output &&
-- 
1.7.0.94.gf7311

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2010-02-27  9:30 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-02-25  8:24 [RFC/PATCH] notes: rework subcommands and parse options Stephen Boyd
2010-02-25 18:26 ` Tim Henigan
2010-02-26 11:22 ` Johan Herland
2010-02-27  8:59 ` [PATCH] " Stephen Boyd

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.