From c3ab1a8e4cb8a84967efcf45c5e6bee41b17f9f9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 May 2011 15:44:37 -0700 Subject: notes remove: allow removing more than one While "xargs -n1 git notes rm" is certainly a possible way to remove notes from many objects, this would create one notes "commit" per removal, which is not quite suitable for seasonal housekeeping. Allow taking more than one on the command line, and record their removal as a single atomic event if everthing goes well. Even though the old code insisted that "git notes rm" must be given only one object (or zero, in which case it would default to HEAD), this condition was not tested. Add tests to handle the new case where we feed multiple objects, and also make sure if there is a bad input, no change is recorded. Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 7 ++++--- builtin/notes.c | 47 ++++++++++++++++++++++++--------------------- t/t3301-notes.sh | 20 +++++++++++++++++++ 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 913ecd8c43..e2e1c4c88d 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -17,7 +17,7 @@ SYNOPSIS 'git notes' merge [-v | -q] [-s ] 'git notes' merge --commit [-v | -q] 'git notes' merge --abort [-v | -q] -'git notes' remove [] +'git notes' remove [...] 'git notes' prune [-n | -v] 'git notes' get-ref @@ -106,8 +106,9 @@ When done, the user can either finalize the merge with 'git notes merge --abort'. remove:: - Remove the notes for a given object (defaults to HEAD). - This is equivalent to specifying an empty note message to + Remove the notes for given objects (defaults to HEAD). When + giving zero or one object from the command line, this is + equivalent to specifying an empty note message to the `edit` subcommand. prune:: diff --git a/builtin/notes.c b/builtin/notes.c index 1fb1f73439..30cee0fd3c 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -29,7 +29,7 @@ static const char * const git_notes_usage[] = { "git notes [--ref ] merge [-v | -q] [-s ] ", "git notes merge --commit [-v | -q]", "git notes merge --abort [-v | -q]", - "git notes [--ref ] remove []", + "git notes [--ref ] remove [...]", "git notes [--ref ] prune [-n | -v]", "git notes [--ref ] get-ref", NULL @@ -953,40 +953,43 @@ static int merge(int argc, const char **argv, const char *prefix) return result < 0; /* return non-zero on conflicts */ } +static int remove_one_note(struct notes_tree *t, const char *name) +{ + int status; + unsigned char sha1[20]; + if (get_sha1(name, sha1)) + return error(_("Failed to resolve '%s' as a valid ref."), name); + status = remove_note(t, sha1); + if (status) + fprintf(stderr, _("Object %s has no note\n"), name); + else + fprintf(stderr, _("Removing note for object %s\n"), name); + return status; +} + 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]; - int retval; + int retval = 0; 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"); - retval = remove_note(t, object); - if (retval) - fprintf(stderr, _("Object %s has no note\n"), sha1_to_hex(object)); - else { - fprintf(stderr, _("Removing note for object %s\n"), - sha1_to_hex(object)); - - commit_notes(t, "Notes removed by 'git notes remove'"); + if (!argc) { + retval = remove_one_note(t, "HEAD"); + } else { + while (*argv) { + retval |= remove_one_note(t, *argv); + argv++; + } } + if (!retval) + commit_notes(t, "Notes removed by 'git notes remove'"); free_notes(t); return retval; } diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 28e17c8920..f49879e034 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -435,6 +435,26 @@ test_expect_success 'removing non-existing note should not create new commit' ' test_cmp before_commit after_commit ' +test_expect_success 'removing more than one' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git notes remove HEAD^^ HEAD^^^ && + git notes list | sed -e "s/ .*//" >actual && + test_cmp after-removal-expect actual +' + +test_expect_success 'removing is atomic' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + test_must_fail git notes remove HEAD^^ HEAD^^^ HEAD^ && + after=$(git rev-parse --verify refs/notes/commits) && + test "$before" = "$after" +' + test_expect_success 'list notes with "git notes list"' ' git notes list > output && test_cmp expect output -- cgit v1.2.1 From 2d370d2fbcf896912cc0739b806224f9a270bb5e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 May 2011 16:02:58 -0700 Subject: notes remove: --ignore-missing Depending on the application, it is not necessarily an error for an object to lack a note, especially if the only thing the caller wants to make sure is that notes are cleared for an object. By passing this option from the command line, the "git notes remove" command considers it a success if the object did not have any note to begin with. Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 6 +++++- builtin/notes.c | 14 ++++++++++---- t/t3301-notes.sh | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index e2e1c4c88d..8bbffdf648 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -17,7 +17,7 @@ SYNOPSIS 'git notes' merge [-v | -q] [-s ] 'git notes' merge --commit [-v | -q] 'git notes' merge --abort [-v | -q] -'git notes' remove [...] +'git notes' remove [--ignore-missing] [...] 'git notes' prune [-n | -v] 'git notes' get-ref @@ -155,6 +155,10 @@ OPTIONS 'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref is taken to be in `refs/notes/` if it is not qualified. +--ignore-missing:: + Do not consider it an error to request removing notes from an + object that does not have notes attached to it. + -n:: --dry-run:: Do not remove anything; just report the object names whose notes diff --git a/builtin/notes.c b/builtin/notes.c index 30cee0fd3c..541f776ebb 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -953,7 +953,9 @@ static int merge(int argc, const char **argv, const char *prefix) return result < 0; /* return non-zero on conflicts */ } -static int remove_one_note(struct notes_tree *t, const char *name) +#define MISSING_OK 1 + +static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) { int status; unsigned char sha1[20]; @@ -964,12 +966,16 @@ static int remove_one_note(struct notes_tree *t, const char *name) fprintf(stderr, _("Object %s has no note\n"), name); else fprintf(stderr, _("Removing note for object %s\n"), name); - return status; + return (flag & MISSING_OK) ? 0 : status; } static int remove_cmd(int argc, const char **argv, const char *prefix) { + unsigned flag = 0; struct option options[] = { + OPT_BIT(0, "ignore-missing", &flag, + "attempt to remove non-existent note is not an error", + MISSING_OK), OPT_END() }; struct notes_tree *t; @@ -981,10 +987,10 @@ static int remove_cmd(int argc, const char **argv, const char *prefix) t = init_notes_check("remove"); if (!argc) { - retval = remove_one_note(t, "HEAD"); + retval = remove_one_note(t, "HEAD", flag); } else { while (*argv) { - retval |= remove_one_note(t, *argv); + retval |= remove_one_note(t, *argv, flag); argv++; } } diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index f49879e034..e820a6bc70 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -455,6 +455,26 @@ test_expect_success 'removing is atomic' ' test "$before" = "$after" ' +test_expect_success 'removing with --ignore-missing' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git notes remove --ignore-missing HEAD^^ HEAD^^^ HEAD^ && + git notes list | sed -e "s/ .*//" >actual && + test_cmp after-removal-expect actual +' + +test_expect_success 'removing with --ignore-missing but bogus ref' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + test_must_fail git notes remove --ignore-missing HEAD^^ HEAD^^^ NO-SUCH-COMMIT && + after=$(git rev-parse --verify refs/notes/commits) && + test "$before" = "$after" +' + test_expect_success 'list notes with "git notes list"' ' git notes list > output && test_cmp expect output -- cgit v1.2.1 From 46538012d943156ead4024fb510a1ce5d4ff443a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 May 2011 16:44:30 -0700 Subject: notes remove: --stdin reads from the standard input Teach the command to read object names to remove from the standard input, in addition to the object names given from the command line. Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 7 ++++++- builtin/notes.c | 19 +++++++++++++++---- t/t3301-notes.sh | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 8bbffdf648..6a187f2e23 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -17,7 +17,7 @@ SYNOPSIS 'git notes' merge [-v | -q] [-s ] 'git notes' merge --commit [-v | -q] 'git notes' merge --abort [-v | -q] -'git notes' remove [--ignore-missing] [...] +'git notes' remove [--ignore-missing] [--stdin] [...] 'git notes' prune [-n | -v] 'git notes' get-ref @@ -159,6 +159,11 @@ OPTIONS Do not consider it an error to request removing notes from an object that does not have notes attached to it. +--stdin:: + Also read the object names to remove notes from from the standard + input (there is no reason you cannot combine this with object + names from the command line). + -n:: --dry-run:: Do not remove anything; just report the object names whose notes diff --git a/builtin/notes.c b/builtin/notes.c index 541f776ebb..f8e437db01 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -953,7 +953,7 @@ static int merge(int argc, const char **argv, const char *prefix) return result < 0; /* return non-zero on conflicts */ } -#define MISSING_OK 1 +#define IGNORE_MISSING 1 static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) { @@ -966,16 +966,19 @@ static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag fprintf(stderr, _("Object %s has no note\n"), name); else fprintf(stderr, _("Removing note for object %s\n"), name); - return (flag & MISSING_OK) ? 0 : status; + return (flag & IGNORE_MISSING) ? 0 : status; } static int remove_cmd(int argc, const char **argv, const char *prefix) { unsigned flag = 0; + int from_stdin = 0; struct option options[] = { OPT_BIT(0, "ignore-missing", &flag, "attempt to remove non-existent note is not an error", - MISSING_OK), + IGNORE_MISSING), + OPT_BOOLEAN(0, "stdin", &from_stdin, + "read object names from the standard input"), OPT_END() }; struct notes_tree *t; @@ -986,7 +989,7 @@ static int remove_cmd(int argc, const char **argv, const char *prefix) t = init_notes_check("remove"); - if (!argc) { + if (!argc && !from_stdin) { retval = remove_one_note(t, "HEAD", flag); } else { while (*argv) { @@ -994,6 +997,14 @@ static int remove_cmd(int argc, const char **argv, const char *prefix) argv++; } } + if (from_stdin) { + struct strbuf sb = STRBUF_INIT; + while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) { + strbuf_rtrim(&sb); + retval |= remove_one_note(t, sb.buf, flag); + } + strbuf_release(&sb); + } if (!retval) commit_notes(t, "Notes removed by 'git notes remove'"); free_notes(t); diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index e820a6bc70..16de05aff9 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -475,6 +475,41 @@ test_expect_success 'removing with --ignore-missing but bogus ref' ' test "$before" = "$after" ' +test_expect_success 'remove reads from --stdin' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + + # We have only two -- add another and make sure it stays + git notes add -m "extra" && + git notes list HEAD >after-removal-expect && + git rev-parse HEAD^^ HEAD^^^ >input && + git notes remove --stdin actual && + test_cmp after-removal-expect actual +' + +test_expect_success 'remove --stdin is also atomic' ' + before=$(git rev-parse --verify refs/notes/commits) && + test_when_finished "git update-ref refs/notes/commits $before" && + git rev-parse HEAD^^ HEAD^^^ HEAD^ >input && + test_must_fail git notes remove --stdin after-removal-expect && + git rev-parse HEAD^^ HEAD^^^ HEAD^ >input && + git notes remove --ignore-missing --stdin actual && + test_cmp after-removal-expect actual +' + test_expect_success 'list notes with "git notes list"' ' git notes list > output && test_cmp expect output -- cgit v1.2.1 From cc243c3cebd58d06bc4e064b08fa9c3fdd565250 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 18 May 2011 18:08:09 -0700 Subject: show: --ignore-missing Instead of barfing, simply ignore bad object names seen in the input. This is useful when reading from "git notes list" output that may refer to objects that have already been garbage collected. Signed-off-by: Junio C Hamano --- Documentation/git-rev-list.txt | 1 + Documentation/rev-list-options.txt | 4 ++++ builtin/rev-parse.c | 1 + revision.c | 15 +++++++++++++-- revision.h | 3 ++- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 415f4f0b30..38fafcaa6b 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -29,6 +29,7 @@ SYNOPSIS [ \--tags[=] ] [ \--remotes[=] ] [ \--glob= ] + [ \--ignore-missing ] [ \--stdin ] [ \--quiet ] [ \--topo-order ] diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 52bae31fcb..7e7ba68781 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -139,6 +139,10 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit). is automatically prepended if missing. If pattern lacks '?', '*', or '[', '/*' at the end is implied. +--ignore-missing:: + + Upon seeing an invalid object name in the input, pretend as if + the bad input was not given. ifndef::git-rev-list[] --bisect:: diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index adb1cae4f2..4c19f844a9 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -44,6 +44,7 @@ static int is_rev_argument(const char *arg) "--branches=", "--branches", "--header", + "--ignore-missing", "--max-age=", "--max-count=", "--min-age=", diff --git a/revision.c b/revision.c index a7cf79bf2e..5cb3a75f85 100644 --- a/revision.c +++ b/revision.c @@ -133,6 +133,8 @@ void mark_parents_uninteresting(struct commit *commit) static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode) { + if (!obj) + return; if (revs->no_walk && (obj->flags & UNINTERESTING)) revs->no_walk = 0; if (revs->reflog_info && obj->type == OBJ_COMMIT) { @@ -174,8 +176,11 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con struct object *object; object = parse_object(sha1); - if (!object) + if (!object) { + if (revs->ignore_missing) + return object; die("bad object %s", name); + } object->flags |= flags; return object; } @@ -906,6 +911,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) return 0; while (1) { it = get_reference(revs, arg, sha1, 0); + if (!it && revs->ignore_missing) + return 0; if (it->type != OBJ_TAG) break; if (!((struct tag*)it)->tagged) @@ -1044,6 +1051,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, a = lookup_commit_reference(from_sha1); b = lookup_commit_reference(sha1); if (!a || !b) { + if (revs->ignore_missing) + return 0; die(symmetric ? "Invalid symmetric difference expression %s...%s" : "Invalid revision range %s..%s", @@ -1090,7 +1099,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, arg++; } if (get_sha1_with_mode(arg, sha1, &mode)) - return -1; + return revs->ignore_missing ? 0 : -1; if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); @@ -1475,6 +1484,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--children")) { revs->children.name = "children"; revs->limited = 1; + } else if (!strcmp(arg, "--ignore-missing")) { + revs->ignore_missing = 1; } else { int opts = diff_opt_parse(&revs->diffopt, argv, argc); if (!opts) diff --git a/revision.h b/revision.h index bca9947977..93f338d6a7 100644 --- a/revision.h +++ b/revision.h @@ -36,7 +36,8 @@ struct rev_info { const char *prefix; const char *def; struct pathspec prune_data; - unsigned int early_output; + unsigned int early_output:1, + ignore_missing:1; /* Traversal flags */ unsigned int dense:1, -- cgit v1.2.1