21
#include "branch.h"
22
#include "path.h"
23
#include "string-list.h"
24
+#include "strmap.h"
25
#include "column.h"
26
#include "utf8.h"
27
#include "ref-filter.h"
39
N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
40
N_("git branch [<options>] [-r | -a] [--points-at]"),
41
N_("git branch [<options>] [-r | -a] [--format]"),
42
+ N_("git branch [<options>] (--delete-merged <pattern>)... "
43
+ "[<branch-pattern>...]"),
44
NULL
45
};
46
703
return 0;
704
}
705
706
+struct stacked_branch_data {
707
+ struct strset *deletable_branch_names;
708
+ struct strset *protected_branch_names;
709
+};
710
+
711
+static int collect_stacked_branch_base(const struct reference *ref,
712
+ void *cb_data)
713
+{
714
+ struct stacked_branch_data *data = cb_data;
715
+ const char *branch_name;
716
+ struct branch *branch;
717
+ const char *upstream_refname;
718
+ const char *upstream_branch_name;
719
+
720
+ if (!skip_prefix(ref->name, "refs/heads/", &branch_name))
721
+ BUG("expected local branch ref, got '%s'", ref->name);
722
+ if (strset_contains(data->deletable_branch_names, branch_name))
723
+ return 0;
724
+
725
+ branch = branch_get(branch_name);
726
+ upstream_refname = branch_get_upstream(branch, NULL);
727
+ if (!upstream_refname ||
728
+ !skip_prefix(upstream_refname, "refs/heads/",
729
+ &upstream_branch_name) ||
730
+ !strset_contains(data->deletable_branch_names,
731
+ upstream_branch_name))
732
+ return 0;
733
+
734
+ strset_add(data->protected_branch_names, upstream_branch_name);
735
+ return 0;
736
+}
737
+
738
+static void protect_stacked_branch_bases(struct ref_store *refs,
739
+ struct strset *deletable_branch_names,
740
+ struct strset *protected_branch_names)
741
+{
742
+ struct stacked_branch_data data = {
743
+ .deletable_branch_names = deletable_branch_names,
744
+ .protected_branch_names = protected_branch_names,
745
+ };
746
+ struct refs_for_each_ref_options opts = {
747
+ .prefix = "refs/heads/",
748
+ };
749
+ struct hashmap_iter iter;
750
+ struct strmap_entry *entry;
751
+
752
+ refs_for_each_ref_ext(refs, collect_stacked_branch_base, &data, &opts);
753
+
754
+ strset_for_each_entry(protected_branch_names, &iter, entry)
755
+ strset_remove(deletable_branch_names, entry->key);
756
+}
757
+
758
+static void clear_deleted_upstreams(struct strset *protected_branch_names,
759
+ struct strset *deletable_branch_names)
760
+{
761
+ struct strbuf key = STRBUF_INIT;
762
+ struct hashmap_iter iter;
763
+ struct strmap_entry *entry;
764
+
765
+ strset_for_each_entry(protected_branch_names, &iter, entry) {
766
+ struct branch *branch = branch_get(entry->key);
767
+ const char *upstream_refname = branch_get_upstream(branch, NULL);
768
+ const char *upstream_branch_name;
769
+
770
+ if (!upstream_refname ||
771
+ !skip_prefix(upstream_refname, "refs/heads/",
772
+ &upstream_branch_name) ||
773
+ !strset_contains(deletable_branch_names,
774
+ upstream_branch_name))
775
+ continue;
776
+
777
+ strbuf_addf(&key, "branch.%s.merge", branch->name);
778
+ repo_config_set_gently(the_repository, key.buf, NULL);
779
+ strbuf_reset(&key);
780
+ strbuf_addf(&key, "branch.%s.remote", branch->name);
781
+ repo_config_set_gently(the_repository, key.buf, NULL);
782
+ strbuf_reset(&key);
783
+ }
784
+
785
+ strbuf_release(&key);
786
+}
787
+
788
+static int branch_pushes_to_upstream(struct branch *branch,
789
+ const char *upstream)
790
+{
791
+ struct remote *remote = remote_get(remote_for_branch(branch, NULL));
792
+ char *push_refname = NULL;
793
+ char *tracking = NULL;
794
+ int ret = 0;
795
+
796
+ if (!remote)
797
+ return 0;
798
+ if (remote->push.nr)
799
+ push_refname = apply_refspecs(&remote->push, branch->refname);
800
+ else
801
+ push_refname = xstrdup(branch->refname);
802
+ if (push_refname)
803
+ tracking = apply_refspecs(&remote->fetch, push_refname);
804
+ if (tracking && !strcmp(tracking, upstream))
805
+ ret = 1;
806
+
807
+ free(push_refname);
808
+ free(tracking);
809
+ return ret;
810
+}
811
+
812
+static int delete_merged_branches(const struct strvec *upstreams,
813
+ const char **argv, unsigned int flags)
814
+{
815
+ struct ref_store *refs = get_main_ref_store(the_repository);
816
+ struct ref_filter filter = REF_FILTER_INIT;
817
+ struct ref_array candidates = { 0 };
818
+ struct strset deletable_branch_names = STRSET_INIT;
819
+ struct strset protected_branch_names = STRSET_INIT;
820
+ struct strvec branches_to_delete = STRVEC_INIT;
821
+ struct hashmap_iter iter;
822
+ struct strmap_entry *entry;
823
+ int ret = 0;
824
+
825
+ for (size_t i = 0; i < upstreams->nr; i++)
826
+ if (ref_filter_forked_add(&filter, upstreams->v[i]) < 0)
827
+ die(_("'%s' is not a valid branch or pattern"),
828
+ upstreams->v[i]);
829
+
830
+ filter.kind = FILTER_REFS_BRANCHES;
831
+ filter.name_patterns = argv;
832
+ filter_refs(&candidates, &filter, filter.kind);
833
+
834
+ for (int i = 0; i < candidates.nr; i++) {
835
+ const char *branch_refname = candidates.items[i]->refname;
836
+ const char *branch_name;
837
+ struct branch *branch;
838
+ const char *upstream_refname;
839
+
840
+ if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
841
+ BUG("filter returned non-branch ref '%s'", branch_refname);
842
+ if (branch_checked_out(branch_refname))
843
+ continue;
844
+
845
+ branch = branch_get(branch_name);
846
+ upstream_refname = branch_get_upstream(branch, NULL);
847
+ if (!upstream_refname || !refs_ref_exists(refs, upstream_refname))
848
+ continue;
849
+ if (branch_pushes_to_upstream(branch, upstream_refname))
850
+ continue;
851
+ if (check_branch_commit(branch_name, branch_name,
852
+ &candidates.items[i]->objectname, NULL,
853
+ FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
854
+ continue;
855
+
856
+ strset_add(&deletable_branch_names, branch_name);
857
+ }
858
+
859
+ protect_stacked_branch_bases(refs, &deletable_branch_names,
860
+ &protected_branch_names);
861
+
862
+ strset_for_each_entry(&deletable_branch_names, &iter, entry)
863
+ strvec_push(&branches_to_delete, entry->key);
864
+
865
+ if (branches_to_delete.nr)
866
+ ret = delete_branches(branches_to_delete.nr, branches_to_delete.v,
867
+ FILTER_REFS_BRANCHES,
868
+ DELETE_BRANCH_SKIP_UNMERGED |
869
+ DELETE_BRANCH_NO_HEAD_FALLBACK |
870
+ flags);
871
+
872
+ if (!ret)
873
+ clear_deleted_upstreams(&protected_branch_names,
874
+ &deletable_branch_names);
875
+
876
+ strvec_clear(&branches_to_delete);
877
+ strset_clear(&protected_branch_names);
878
+ strset_clear(&deletable_branch_names);
879
+ ref_array_clear(&candidates);
880
+ ref_filter_clear(&filter);
881
+ return ret;
882
+}
883
+
884
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
885
886
static int edit_branch_description(const char *branch_name)
945
/* possible actions */
946
int delete = 0, rename = 0, copy = 0, list = 0,
947
unset_upstream = 0, show_current = 0, edit_description = 0;
948
+ struct strvec delete_merged = STRVEC_INIT;
949
const char *new_upstream = NULL;
950
int noncreate_actions = 0;
951
/* possible options */
999
OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
1000
OPT_BOOL(0, "edit-description", &edit_description,
1001
N_("edit the description for the branch")),
1002
+ OPT_CALLBACK_F(0, "delete-merged", &delete_merged, N_("pattern"),
1003
+ N_("delete merged branches whose upstream matches <pattern> (repeatable)"),
1004
+ PARSE_OPT_NONEG, parse_opt_strvec),
1005
OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
1006
OPT_MERGED(&filter, N_("print only branches that are merged")),
1007
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
1049
0);
1050
1051
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
867
- !show_current && !unset_upstream && argc == 0)
1052
+ !show_current && !unset_upstream && !delete_merged.nr &&
1053
+ argc == 0)
1054
list = 1;
1055
1056
if (filter.with_commit || filter.no_commit ||
1060
1061
noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
1062
!!show_current + !!list + !!edit_description +
877
- !!unset_upstream;
1063
+ !!unset_upstream + !!delete_merged.nr;
1064
if (noncreate_actions > 1)
1065
usage_with_options(builtin_branch_usage, options);
1066
1102
(delete > 1 ? DELETE_BRANCH_FORCE : 0) |
1103
(quiet ? DELETE_BRANCH_QUIET : 0));
1104
goto out;
1105
+ } else if (delete_merged.nr) {
1106
+ ret = delete_merged_branches(&delete_merged, argv,
1107
+ quiet ? DELETE_BRANCH_QUIET : 0);
1108
+ goto out;
1109
} else if (show_current) {
1110
print_current_branch_name();
1111
ret = 0;
1277
ret = 0;
1278
1279
out:
1280
+ strvec_clear(&delete_merged);
1281
string_list_clear(&sorting_options, 0);
1282
return ret;
1283
}