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
+ size_t i;
792
+ int ret = 0;
793
+
794
+ for (i = 0; i < upstreams->nr; i++)
795
+ if (ref_filter_forked_add(&filter, upstreams->v[i]) < 0)
796
+ die(_("'%s' is not a valid branch or pattern"),
797
+ upstreams->v[i]);
798
+
799
+ filter.kind = FILTER_REFS_BRANCHES;
800
+ filter.name_patterns = argv;
801
+ filter_refs(&candidates, &filter, filter.kind);
802
+
803
+ for (i = 0; i < (size_t)candidates.nr; i++) {
804
+ const char *branch_refname = candidates.items[i]->refname;
805
+ const char *branch_name;
806
+ struct branch *branch;
807
+ const char *upstream_refname;
808
+
809
+ if (!skip_prefix(branch_refname, "refs/heads/", &branch_name))
810
+ BUG("filter returned non-branch ref '%s'", branch_refname);
811
+ if (branch_checked_out(branch_refname))
812
+ continue;
813
+
814
+ branch = branch_get(branch_name);
815
+ upstream_refname = branch_get_upstream(branch, NULL);
816
+ if (!upstream_refname || !refs_ref_exists(refs, upstream_refname))
817
+ continue;
818
+ if (branch_pushes_to_upstream(branch, upstream_refname))
819
+ continue;
820
+ if (check_branch_commit(branch_name, branch_name,
821
+ &candidates.items[i]->objectname, NULL,
822
+ FILTER_REFS_BRANCHES, DELETE_BRANCH_SKIP_UNMERGED))
823
+ continue;
824
+
825
+ strset_add(&deletable_branch_names, branch_name);
826
+ }
827
+
828
+ protect_stacked_branch_bases(refs, &deletable_branch_names);
829
+
830
+ strset_for_each_entry(&deletable_branch_names, &iter, entry)
831
+ strvec_push(&branches_to_delete, entry->key);
832
+
833
+ if (branches_to_delete.nr)
834
+ ret = delete_branches(branches_to_delete.nr, branches_to_delete.v,
835
+ FILTER_REFS_BRANCHES,
836
+ DELETE_BRANCH_SKIP_UNMERGED |
837
+ DELETE_BRANCH_NO_HEAD_FALLBACK |
838
+ flags);
839
+
840
+ strvec_clear(&branches_to_delete);
841
+ strset_clear(&deletable_branch_names);
842
+ ref_array_clear(&candidates);
843
+ ref_filter_clear(&filter);
844
+ return ret;
845
+}
846
+
847
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
848
849
static int edit_branch_description(const char *branch_name)
885
/* possible actions */
886
int delete = 0, rename = 0, copy = 0, list = 0,
887
unset_upstream = 0, show_current = 0, edit_description = 0;
888
+ struct strvec delete_merged = STRVEC_INIT;
889
const char *new_upstream = NULL;
890
int noncreate_actions = 0;
891
/* possible options */
939
OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
940
OPT_BOOL(0, "edit-description", &edit_description,
941
N_("edit the description for the branch")),
942
+ OPT_CALLBACK_F(0, "delete-merged", &delete_merged, N_("branch"),
943
+ N_("delete merged branches whose upstream matches <branch> (repeatable)"),
944
+ PARSE_OPT_NONEG, parse_opt_strvec),
945
OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
946
OPT_MERGED(&filter, N_("print only branches that are merged")),
947
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
989
0);
990
991
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
843
- !show_current && !unset_upstream && argc == 0)
992
+ !show_current && !unset_upstream && !delete_merged.nr &&
993
+ argc == 0)
994
list = 1;
995
996
if (filter.with_commit || filter.no_commit ||
1000
1001
noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
1002
!!show_current + !!list + !!edit_description +
853
- !!unset_upstream;
1003
+ !!unset_upstream + !!delete_merged.nr;
1004
if (noncreate_actions > 1)
1005
usage_with_options(builtin_branch_usage, options);
1006
1042
(delete > 1 ? DELETE_BRANCH_FORCE : 0) |
1043
(quiet ? DELETE_BRANCH_QUIET : 0));
1044
goto out;
1045
+ } else if (delete_merged.nr) {
1046
+ ret = delete_merged_branches(&delete_merged, argv,
1047
+ quiet ? DELETE_BRANCH_QUIET : 0);
1048
+ goto out;
1049
} else if (show_current) {
1050
print_current_branch_name();
1051
ret = 0;
1205
ret = 0;
1206
1207
out:
1208
+ strvec_clear(&delete_merged);
1209
string_list_clear(&sorting_options, 0);
1210
return ret;
1211
}