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 <branch>)... [<pattern>...]"),
43
NULL
44
};
45
701
return 0;
702
}
703
704
+struct stacked_branch_data {
705
+ struct strset *deletable_branch_names;
706
+ struct strset *protected_branch_names;
707
+ struct strset *visited_branch_names;
708
+};
709
+
710
+static int collect_stacked_branch_bases(const struct reference *ref,
711
+ void *cb_data)
712
+{
713
+ struct stacked_branch_data *data = cb_data;
714
+ const char *branch_name;
715
+
716
+ if (!skip_prefix(ref->name, "refs/heads/", &branch_name))
717
+ BUG("expected local branch ref, got '%s'", ref->name);
718
+ if (strset_contains(data->deletable_branch_names, branch_name))
719
+ return 0;
720
+
721
+ while (strset_add(data->visited_branch_names, branch_name)) {
722
+ struct branch *branch = branch_get(branch_name);
723
+ const char *upstream_refname = branch_get_upstream(branch, NULL);
724
+ const char *upstream_branch_name;
725
+
726
+ if (!upstream_refname ||
727
+ !skip_prefix(upstream_refname, "refs/heads/",
728
+ &upstream_branch_name) ||
729
+ !strset_contains(data->deletable_branch_names,
730
+ upstream_branch_name))
731
+ break;
732
+
733
+ strset_add(data->protected_branch_names, upstream_branch_name);
734
+ branch_name = upstream_branch_name;
735
+ }
736
+
737
+ return 0;
738
+}
739
+
740
+static void protect_stacked_branch_bases(struct ref_store *refs,
741
+ struct strset *deletable_branch_names)
742
+{
743
+ struct strset protected_branch_names = STRSET_INIT;
744
+ struct strset visited_branch_names = STRSET_INIT;
745
+ struct stacked_branch_data data = {
746
+ .deletable_branch_names = deletable_branch_names,
747
+ .protected_branch_names = &protected_branch_names,
748
+ .visited_branch_names = &visited_branch_names,
749
+ };
750
+ struct refs_for_each_ref_options opts = {
751
+ .prefix = "refs/heads/",
752
+ };
753
+ struct hashmap_iter iter;
754
+ struct strmap_entry *entry;
755
+
756
+ refs_for_each_ref_ext(refs, collect_stacked_branch_bases, &data, &opts);
757
+
758
+ strset_for_each_entry(&protected_branch_names, &iter, entry)
759
+ strset_remove(deletable_branch_names, entry->key);
760
+
761
+ strset_clear(&visited_branch_names);
762
+ strset_clear(&protected_branch_names);
763
+}
764
+
765
+static int branch_pushes_to_upstream(struct branch *branch,
766
+ const char *upstream)
767
+{
768
+ struct remote *remote = remote_get(remote_for_branch(branch, NULL));
769
+ char *tracking = NULL;
770
+ int ret = 0;
771
+
772
+ if (remote)
773
+ tracking = apply_refspecs(&remote->fetch, branch->refname);
774
+ if (tracking && !strcmp(tracking, upstream))
775
+ ret = 1;
776
+
777
+ free(tracking);
778
+ return ret;
779
+}
780
+
781
+static int delete_merged_branches(const struct strvec *upstreams,
782
+ const char **argv, unsigned int flags)
783
+{
784
+ struct ref_store *refs = get_main_ref_store(the_repository);
785
+ struct ref_filter filter = REF_FILTER_INIT;
786
+ struct ref_array candidates = { 0 };
787
+ struct strset deletable_branch_names = STRSET_INIT;
788
+ struct strvec branches_to_delete = STRVEC_INIT;
789
+ struct hashmap_iter iter;
790
+ struct strmap_entry *entry;
791
+ int ret = 0;
792
+
793
+ for (size_t i = 0; i < upstreams->nr; i++)
794
+ if (ref_filter_forked_add(&filter, upstreams->v[i]) < 0)
795
+ die(_("'%s' is not a valid branch or pattern"),
796
+ upstreams->v[i]);
797
+
798
+ filter.kind = FILTER_REFS_BRANCHES;
799
+ filter.name_patterns = argv;
800
+ filter_refs(&candidates, &filter, filter.kind);
801
+
802
+ for (int i = 0; i < candidates.nr; i++) {
803
+ const char *branch_refname = candidates.items[i]->refname;
804
+ const char *branch_name;
805
+ struct branch *branch;
806
+ const char *upstream_refname;
807
+
808
+ if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
809
+ BUG("filter returned non-branch ref '%s'", branch_refname);
810
+ if (branch_checked_out(branch_refname))
811
+ continue;
812
+
813
+ branch = branch_get(branch_name);
814
+ upstream_refname = branch_get_upstream(branch, NULL);
815
+ if (!upstream_refname || !refs_ref_exists(refs, upstream_refname))
816
+ continue;
817
+ if (branch_pushes_to_upstream(branch, upstream_refname))
818
+ continue;
819
+ if (check_branch_commit(branch_name, branch_name,
820
+ &candidates.items[i]->objectname, NULL,
821
+ FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
822
+ continue;
823
+
824
+ strset_add(&deletable_branch_names, branch_name);
825
+ }
826
+
827
+ protect_stacked_branch_bases(refs, &deletable_branch_names);
828
+
829
+ strset_for_each_entry(&deletable_branch_names, &iter, entry)
830
+ strvec_push(&branches_to_delete, entry->key);
831
+
832
+ if (branches_to_delete.nr)
833
+ ret = delete_branches(branches_to_delete.nr, branches_to_delete.v,
834
+ FILTER_REFS_BRANCHES,
835
+ DELETE_BRANCH_SKIP_UNMERGED |
836
+ DELETE_BRANCH_NO_HEAD_FALLBACK |
837
+ flags);
838
+
839
+ strvec_clear(&branches_to_delete);
840
+ strset_clear(&deletable_branch_names);
841
+ ref_array_clear(&candidates);
842
+ ref_filter_clear(&filter);
843
+ return ret;
844
+}
845
+
846
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
847
848
static int edit_branch_description(const char *branch_name)
884
/* possible actions */
885
int delete = 0, rename = 0, copy = 0, list = 0,
886
unset_upstream = 0, show_current = 0, edit_description = 0;
887
+ struct strvec delete_merged = STRVEC_INIT;
888
const char *new_upstream = NULL;
889
int noncreate_actions = 0;
890
/* possible options */
938
OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
939
OPT_BOOL(0, "edit-description", &edit_description,
940
N_("edit the description for the branch")),
941
+ OPT_CALLBACK_F(0, "delete-merged", &delete_merged, N_("branch"),
942
+ N_("delete merged branches whose upstream matches <branch> (repeatable)"),
943
+ PARSE_OPT_NONEG, parse_opt_strvec),
944
OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
945
OPT_MERGED(&filter, N_("print only branches that are merged")),
946
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
988
0);
989
990
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
843
- !show_current && !unset_upstream && argc == 0)
991
+ !show_current && !unset_upstream && !delete_merged.nr &&
992
+ argc == 0)
993
list = 1;
994
995
if (filter.with_commit || filter.no_commit ||
999
1000
noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
1001
!!show_current + !!list + !!edit_description +
853
- !!unset_upstream;
1002
+ !!unset_upstream + !!delete_merged.nr;
1003
if (noncreate_actions > 1)
1004
usage_with_options(builtin_branch_usage, options);
1005
1041
(delete > 1 ? DELETE_BRANCH_FORCE : 0) |
1042
(quiet ? DELETE_BRANCH_QUIET : 0));
1043
goto out;
1044
+ } else if (delete_merged.nr) {
1045
+ ret = delete_merged_branches(&delete_merged, argv,
1046
+ quiet ? DELETE_BRANCH_QUIET : 0);
1047
+ goto out;
1048
} else if (show_current) {
1049
print_current_branch_name();
1050
ret = 0;
1204
ret = 0;
1205
1206
out:
1207
+ strvec_clear(&delete_merged);
1208
string_list_clear(&sorting_options, 0);
1209
return ret;
1210
}