From: Phil Hord <phil.hord@xxxxxxxxx> 'git tag -d' accepts one or more tag refs to delete, but each deletion is done by calling `delete_ref` on each argv. This is painfully slow when removing from packed refs. Use delete_refs instead so all the removals can be done inside a single transaction with a single write. I have a repo with 24,000 tags, most of which are not useful to any developers. Having this many refs slows down many operations that would otherwise be very fast. Removing these tags when they've been accidentally fetched again takes about 30 minutes using delete_ref. git tag -l feature/* | xargs git tag -d Removing the same tags using delete_refs takes less than 5 seconds. Signed-off-by: Phil Hord <phil.hord@xxxxxxxxx> --- builtin/tag.c | 52 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/builtin/tag.c b/builtin/tag.c index e0a4c25382..f652af83e7 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -72,10 +72,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data); + const struct object_id *oid, void *cb_data); static int for_each_tag_name(const char **argv, each_tag_name_fn fn, - const void *cb_data) + void *cb_data) { const char **p; struct strbuf ref = STRBUF_INIT; @@ -97,18 +97,50 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn, return had_error; } -static int delete_tag(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data) +struct tag_args { + char *oid_abbrev; + char *refname; +}; + +static int make_string_list(const char *name, const char *ref, + const struct object_id *oid, void *cb_data) { - if (delete_ref(NULL, ref, oid, 0)) - return 1; - printf(_("Deleted tag '%s' (was %s)\n"), name, - find_unique_abbrev(oid, DEFAULT_ABBREV)); + struct string_list *ref_list = cb_data; + struct tag_args *info = xmalloc(sizeof(struct tag_args)); + + string_list_append(ref_list, ref); + + info->oid_abbrev = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV)); + info->refname = xstrdup(name); + ref_list->items[ref_list->nr - 1].util = info; return 0; } +static int delete_tags(const char **argv) +{ + int result; + struct string_list ref_list = STRING_LIST_INIT_DUP; + struct string_list_item *ref_list_item; + + result = for_each_tag_name(argv, make_string_list, (void *) &ref_list); + if (!result) + result = delete_refs(NULL, &ref_list, REF_NO_DEREF); + + for_each_string_list_item(ref_list_item, &ref_list) { + struct tag_args * info = ref_list_item->util; + if (!result) + printf(_("Deleted tag '%s' (was %s)\n"), info->refname, + info->oid_abbrev); + free(info->oid_abbrev); + free(info->refname); + free(info); + } + string_list_clear(&ref_list, 0); + return result; +} + static int verify_tag(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data) + const struct object_id *oid, void *cb_data) { int flags; const struct ref_format *format = cb_data; @@ -511,7 +543,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (filter.merge_commit) die(_("--merged and --no-merged options are only allowed in list mode")); if (cmdmode == 'd') - return for_each_tag_name(argv, delete_tag, NULL); + return delete_tags(argv); if (cmdmode == 'v') { if (format.format && verify_ref_format(&format)) usage_with_options(git_tag_usage, options); -- 2.23.0.rc1.174.g4cc1b04b4c.dirty