Skip to content

Commit 3d109dd

Browse files
committed
Merge branch 'jc/notes-batch-removal'
* jc/notes-batch-removal: show: --ignore-missing notes remove: --stdin reads from the standard input notes remove: --ignore-missing notes remove: allow removing more than one
2 parents 01f9ffb + cc243c3 commit 3d109dd

File tree

8 files changed

+151
-28
lines changed

8 files changed

+151
-28
lines changed

Documentation/git-notes.txt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ SYNOPSIS
1717
'git notes' merge [-v | -q] [-s <strategy> ] <notes_ref>
1818
'git notes' merge --commit [-v | -q]
1919
'git notes' merge --abort [-v | -q]
20-
'git notes' remove [<object>]
20+
'git notes' remove [--ignore-missing] [--stdin] [<object>...]
2121
'git notes' prune [-n | -v]
2222
'git notes' get-ref
2323

@@ -106,8 +106,9 @@ When done, the user can either finalize the merge with
106106
'git notes merge --abort'.
107107

108108
remove::
109-
Remove the notes for a given object (defaults to HEAD).
110-
This is equivalent to specifying an empty note message to
109+
Remove the notes for given objects (defaults to HEAD). When
110+
giving zero or one object from the command line, this is
111+
equivalent to specifying an empty note message to
111112
the `edit` subcommand.
112113

113114
prune::
@@ -154,6 +155,15 @@ OPTIONS
154155
'GIT_NOTES_REF' and the "core.notesRef" configuration. The ref
155156
is taken to be in `refs/notes/` if it is not qualified.
156157

158+
--ignore-missing::
159+
Do not consider it an error to request removing notes from an
160+
object that does not have notes attached to it.
161+
162+
--stdin::
163+
Also read the object names to remove notes from from the standard
164+
input (there is no reason you cannot combine this with object
165+
names from the command line).
166+
157167
-n::
158168
--dry-run::
159169
Do not remove anything; just report the object names whose notes

Documentation/git-rev-list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ SYNOPSIS
2929
[ \--tags[=<pattern>] ]
3030
[ \--remotes[=<pattern>] ]
3131
[ \--glob=<glob-pattern> ]
32+
[ \--ignore-missing ]
3233
[ \--stdin ]
3334
[ \--quiet ]
3435
[ \--topo-order ]

Documentation/rev-list-options.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
139139
is automatically prepended if missing. If pattern lacks '?', '*',
140140
or '[', '/*' at the end is implied.
141141

142+
--ignore-missing::
143+
144+
Upon seeing an invalid object name in the input, pretend as if
145+
the bad input was not given.
142146

143147
ifndef::git-rev-list[]
144148
--bisect::

builtin/notes.c

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ static const char * const git_notes_usage[] = {
2929
"git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
3030
"git notes merge --commit [-v | -q]",
3131
"git notes merge --abort [-v | -q]",
32-
"git notes [--ref <notes_ref>] remove [<object>]",
32+
"git notes [--ref <notes_ref>] remove [<object>...]",
3333
"git notes [--ref <notes_ref>] prune [-n | -v]",
3434
"git notes [--ref <notes_ref>] get-ref",
3535
NULL
@@ -953,40 +953,60 @@ static int merge(int argc, const char **argv, const char *prefix)
953953
return result < 0; /* return non-zero on conflicts */
954954
}
955955

956+
#define IGNORE_MISSING 1
957+
958+
static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag)
959+
{
960+
int status;
961+
unsigned char sha1[20];
962+
if (get_sha1(name, sha1))
963+
return error(_("Failed to resolve '%s' as a valid ref."), name);
964+
status = remove_note(t, sha1);
965+
if (status)
966+
fprintf(stderr, _("Object %s has no note\n"), name);
967+
else
968+
fprintf(stderr, _("Removing note for object %s\n"), name);
969+
return (flag & IGNORE_MISSING) ? 0 : status;
970+
}
971+
956972
static int remove_cmd(int argc, const char **argv, const char *prefix)
957973
{
974+
unsigned flag = 0;
975+
int from_stdin = 0;
958976
struct option options[] = {
977+
OPT_BIT(0, "ignore-missing", &flag,
978+
"attempt to remove non-existent note is not an error",
979+
IGNORE_MISSING),
980+
OPT_BOOLEAN(0, "stdin", &from_stdin,
981+
"read object names from the standard input"),
959982
OPT_END()
960983
};
961-
const char *object_ref;
962984
struct notes_tree *t;
963-
unsigned char object[20];
964-
int retval;
985+
int retval = 0;
965986

966987
argc = parse_options(argc, argv, prefix, options,
967988
git_notes_remove_usage, 0);
968989

969-
if (1 < argc) {
970-
error(_("too many parameters"));
971-
usage_with_options(git_notes_remove_usage, options);
972-
}
973-
974-
object_ref = argc ? argv[0] : "HEAD";
975-
976-
if (get_sha1(object_ref, object))
977-
die(_("Failed to resolve '%s' as a valid ref."), object_ref);
978-
979990
t = init_notes_check("remove");
980991

981-
retval = remove_note(t, object);
982-
if (retval)
983-
fprintf(stderr, _("Object %s has no note\n"), sha1_to_hex(object));
984-
else {
985-
fprintf(stderr, _("Removing note for object %s\n"),
986-
sha1_to_hex(object));
987-
988-
commit_notes(t, "Notes removed by 'git notes remove'");
992+
if (!argc && !from_stdin) {
993+
retval = remove_one_note(t, "HEAD", flag);
994+
} else {
995+
while (*argv) {
996+
retval |= remove_one_note(t, *argv, flag);
997+
argv++;
998+
}
989999
}
1000+
if (from_stdin) {
1001+
struct strbuf sb = STRBUF_INIT;
1002+
while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
1003+
strbuf_rtrim(&sb);
1004+
retval |= remove_one_note(t, sb.buf, flag);
1005+
}
1006+
strbuf_release(&sb);
1007+
}
1008+
if (!retval)
1009+
commit_notes(t, "Notes removed by 'git notes remove'");
9901010
free_notes(t);
9911011
return retval;
9921012
}

builtin/rev-parse.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ static int is_rev_argument(const char *arg)
4444
"--branches=",
4545
"--branches",
4646
"--header",
47+
"--ignore-missing",
4748
"--max-age=",
4849
"--max-count=",
4950
"--min-age=",

revision.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ void mark_parents_uninteresting(struct commit *commit)
133133

134134
static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
135135
{
136+
if (!obj)
137+
return;
136138
if (revs->no_walk && (obj->flags & UNINTERESTING))
137139
revs->no_walk = 0;
138140
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
174176
struct object *object;
175177

176178
object = parse_object(sha1);
177-
if (!object)
179+
if (!object) {
180+
if (revs->ignore_missing)
181+
return object;
178182
die("bad object %s", name);
183+
}
179184
object->flags |= flags;
180185
return object;
181186
}
@@ -906,6 +911,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
906911
return 0;
907912
while (1) {
908913
it = get_reference(revs, arg, sha1, 0);
914+
if (!it && revs->ignore_missing)
915+
return 0;
909916
if (it->type != OBJ_TAG)
910917
break;
911918
if (!((struct tag*)it)->tagged)
@@ -1044,6 +1051,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
10441051
a = lookup_commit_reference(from_sha1);
10451052
b = lookup_commit_reference(sha1);
10461053
if (!a || !b) {
1054+
if (revs->ignore_missing)
1055+
return 0;
10471056
die(symmetric ?
10481057
"Invalid symmetric difference expression %s...%s" :
10491058
"Invalid revision range %s..%s",
@@ -1090,7 +1099,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
10901099
arg++;
10911100
}
10921101
if (get_sha1_with_mode(arg, sha1, &mode))
1093-
return -1;
1102+
return revs->ignore_missing ? 0 : -1;
10941103
if (!cant_be_filename)
10951104
verify_non_filename(revs->prefix, arg);
10961105
object = get_reference(revs, arg, sha1, flags ^ local_flags);
@@ -1477,6 +1486,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
14771486
} else if (!strcmp(arg, "--children")) {
14781487
revs->children.name = "children";
14791488
revs->limited = 1;
1489+
} else if (!strcmp(arg, "--ignore-missing")) {
1490+
revs->ignore_missing = 1;
14801491
} else {
14811492
int opts = diff_opt_parse(&revs->diffopt, argv, argc);
14821493
if (!opts)

revision.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ struct rev_info {
3636
const char *prefix;
3737
const char *def;
3838
struct pathspec prune_data;
39-
unsigned int early_output;
39+
unsigned int early_output:1,
40+
ignore_missing:1;
4041

4142
/* Traversal flags */
4243
unsigned int dense:1,

t/t3301-notes.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,81 @@ test_expect_success 'removing non-existing note should not create new commit' '
435435
test_cmp before_commit after_commit
436436
'
437437

438+
test_expect_success 'removing more than one' '
439+
before=$(git rev-parse --verify refs/notes/commits) &&
440+
test_when_finished "git update-ref refs/notes/commits $before" &&
441+
442+
# We have only two -- add another and make sure it stays
443+
git notes add -m "extra" &&
444+
git notes list HEAD >after-removal-expect &&
445+
git notes remove HEAD^^ HEAD^^^ &&
446+
git notes list | sed -e "s/ .*//" >actual &&
447+
test_cmp after-removal-expect actual
448+
'
449+
450+
test_expect_success 'removing is atomic' '
451+
before=$(git rev-parse --verify refs/notes/commits) &&
452+
test_when_finished "git update-ref refs/notes/commits $before" &&
453+
test_must_fail git notes remove HEAD^^ HEAD^^^ HEAD^ &&
454+
after=$(git rev-parse --verify refs/notes/commits) &&
455+
test "$before" = "$after"
456+
'
457+
458+
test_expect_success 'removing with --ignore-missing' '
459+
before=$(git rev-parse --verify refs/notes/commits) &&
460+
test_when_finished "git update-ref refs/notes/commits $before" &&
461+
462+
# We have only two -- add another and make sure it stays
463+
git notes add -m "extra" &&
464+
git notes list HEAD >after-removal-expect &&
465+
git notes remove --ignore-missing HEAD^^ HEAD^^^ HEAD^ &&
466+
git notes list | sed -e "s/ .*//" >actual &&
467+
test_cmp after-removal-expect actual
468+
'
469+
470+
test_expect_success 'removing with --ignore-missing but bogus ref' '
471+
before=$(git rev-parse --verify refs/notes/commits) &&
472+
test_when_finished "git update-ref refs/notes/commits $before" &&
473+
test_must_fail git notes remove --ignore-missing HEAD^^ HEAD^^^ NO-SUCH-COMMIT &&
474+
after=$(git rev-parse --verify refs/notes/commits) &&
475+
test "$before" = "$after"
476+
'
477+
478+
test_expect_success 'remove reads from --stdin' '
479+
before=$(git rev-parse --verify refs/notes/commits) &&
480+
test_when_finished "git update-ref refs/notes/commits $before" &&
481+
482+
# We have only two -- add another and make sure it stays
483+
git notes add -m "extra" &&
484+
git notes list HEAD >after-removal-expect &&
485+
git rev-parse HEAD^^ HEAD^^^ >input &&
486+
git notes remove --stdin <input &&
487+
git notes list | sed -e "s/ .*//" >actual &&
488+
test_cmp after-removal-expect actual
489+
'
490+
491+
test_expect_success 'remove --stdin is also atomic' '
492+
before=$(git rev-parse --verify refs/notes/commits) &&
493+
test_when_finished "git update-ref refs/notes/commits $before" &&
494+
git rev-parse HEAD^^ HEAD^^^ HEAD^ >input &&
495+
test_must_fail git notes remove --stdin <input &&
496+
after=$(git rev-parse --verify refs/notes/commits) &&
497+
test "$before" = "$after"
498+
'
499+
500+
test_expect_success 'removing with --stdin --ignore-missing' '
501+
before=$(git rev-parse --verify refs/notes/commits) &&
502+
test_when_finished "git update-ref refs/notes/commits $before" &&
503+
504+
# We have only two -- add another and make sure it stays
505+
git notes add -m "extra" &&
506+
git notes list HEAD >after-removal-expect &&
507+
git rev-parse HEAD^^ HEAD^^^ HEAD^ >input &&
508+
git notes remove --ignore-missing --stdin <input &&
509+
git notes list | sed -e "s/ .*//" >actual &&
510+
test_cmp after-removal-expect actual
511+
'
512+
438513
test_expect_success 'list notes with "git notes list"' '
439514
git notes list > output &&
440515
test_cmp expect output

0 commit comments

Comments
 (0)