]> git.ipfire.org Git - thirdparty/git.git/commitdiff
use delete_refs when deleting tags or branches
authorPhil Hord <phil.hord@gmail.com>
Thu, 21 Jan 2021 03:23:32 +0000 (19:23 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 22 Jan 2021 00:05:05 +0000 (16:05 -0800)
'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 very slow when
removing from packed refs. Use delete_refs instead so all the removals
can be done inside a single transaction with a single update.

Do the same for 'git branch -d'.

Since delete_refs performs all the packed-refs delete operations
inside a single transaction, if any of the deletes fail then all
them will be skipped. In practice, none of them should fail since
we verify the hash of each one before calling delete_refs, but some
network error or odd permissions problem could have different results
after this change.

Also, since the file-backed deletions are not performed in the same
transaction, those could succeed even when the packed-refs transaction
fails.

After deleting branches, remove the branch config only if the branch
ref was removed and was not subsequently added back in.

A manual test deleting 24,000 tags took about 30 minutes using
delete_ref.  It takes about 5 seconds using delete_refs.

Acked-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Phil Hord <phil.hord@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/branch.c
builtin/tag.c

index 9b68591addf077c359617d05e7a66445c0a1255e..7e547846cb7cc99c907b74e3b11a57a60a86ca2f 100644 (file)
@@ -202,6 +202,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        int remote_branch = 0;
        struct strbuf bname = STRBUF_INIT;
        unsigned allowed_interpret;
+       struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+       struct string_list_item *item;
+       int branch_name_pos;
 
        switch (kinds) {
        case FILTER_REFS_REMOTES:
@@ -219,6 +222,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
        default:
                die(_("cannot use -a with -d"));
        }
+       branch_name_pos = strcspn(fmt, "%");
 
        if (!force) {
                head_rev = lookup_commit_reference(the_repository, &head_oid);
@@ -265,30 +269,35 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                        goto next;
                }
 
-               if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid,
-                              REF_NO_DEREF)) {
-                       error(remote_branch
-                             ? _("Error deleting remote-tracking branch '%s'")
-                             : _("Error deleting branch '%s'"),
-                             bname.buf);
-                       ret = 1;
-                       goto next;
-               }
-               if (!quiet) {
-                       printf(remote_branch
-                              ? _("Deleted remote-tracking branch %s (was %s).\n")
-                              : _("Deleted branch %s (was %s).\n"),
-                              bname.buf,
-                              (flags & REF_ISBROKEN) ? "broken"
-                              : (flags & REF_ISSYMREF) ? target
-                              : find_unique_abbrev(&oid, DEFAULT_ABBREV));
-               }
-               delete_branch_config(bname.buf);
+               item = string_list_append(&refs_to_delete, name);
+               item->util = xstrdup((flags & REF_ISBROKEN) ? "broken"
+                                   : (flags & REF_ISSYMREF) ? target
+                                   : find_unique_abbrev(&oid, DEFAULT_ABBREV));
 
        next:
                free(target);
        }
 
+       if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+               ret = 1;
+
+       for_each_string_list_item(item, &refs_to_delete) {
+               char *describe_ref = item->util;
+               char *name = item->string;
+               if (!ref_exists(name)) {
+                       char *refname = name + branch_name_pos;
+                       if (!quiet)
+                               printf(remote_branch
+                                       ? _("Deleted remote-tracking branch %s (was %s).\n")
+                                       : _("Deleted branch %s (was %s).\n"),
+                                       name + branch_name_pos, describe_ref);
+
+                       delete_branch_config(refname);
+               }
+               free(describe_ref);
+       }
+       string_list_clear(&refs_to_delete, 0);
+
        free(name);
        strbuf_release(&bname);
 
index ecf011776dc057421ce4862fa894cb48525b2035..9212f12f349baac140bf499b23eb8a4811cf792b 100644 (file)
@@ -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,42 @@ 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)
+static int collect_tags(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;
+
+       string_list_append(ref_list, ref);
+       ref_list->items[ref_list->nr - 1].util = oiddup(oid);
        return 0;
 }
 
+static int delete_tags(const char **argv)
+{
+       int result;
+       struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+       struct string_list_item *item;
+
+       result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete);
+       if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+               result = 1;
+
+       for_each_string_list_item(item, &refs_to_delete) {
+               const char *name = item->string;
+               struct object_id *oid = item->util;
+               if (!ref_exists(name))
+                       printf(_("Deleted tag '%s' (was %s)\n"),
+                               item->string + 10,
+                               find_unique_abbrev(oid, DEFAULT_ABBREV));
+
+               free(oid);
+       }
+       string_list_clear(&refs_to_delete, 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;
@@ -512,7 +536,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        if (filter.reachable_from || filter.unreachable_from)
                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);