Inter-revision diff: patch 5

Comparing v7 (message) to v11 (message)

--- v7
+++ v11
@@ -1,221 +1,128 @@
 From: Harald Nordgren <haraldnordgren@gmail.com>
 
-Combined with --forked or --prune-merged, --all-remotes acts on
-every configured remote, in addition to any explicit <remote>
-arguments. Used alone, it errors out.
+Setting branch.<name>.pruneMerged=false exempts that branch from
+"git branch --prune-merged". Useful for a topic branch you want
+to develop further after an initial round has been merged
+upstream.
+
+Unless --quiet is given, the skip is reported per branch so the
+user knows why their topic was preserved.
+
+Explicit deletion via "git branch -d" continues to consult the
+normal merge check and is not affected by this setting.
 
 Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
 ---
- Documentation/git-branch.adoc |  9 ++++++--
- builtin/branch.c              | 41 +++++++++++++++++++++++++----------
- t/t3200-branch.sh             | 40 ++++++++++++++++++++++++++++++++++
- 3 files changed, 76 insertions(+), 14 deletions(-)
+ Documentation/config/branch.adoc |  7 +++++++
+ Documentation/git-branch.adoc    |  5 +++--
+ builtin/branch.c                 | 14 ++++++++++++++
+ t/t3200-branch.sh                | 30 ++++++++++++++++++++++++++++++
+ 4 files changed, 54 insertions(+), 2 deletions(-)
 
+diff --git a/Documentation/config/branch.adoc b/Documentation/config/branch.adoc
+index a4db9fa5c8..6c1b5bb9cd 100644
+--- a/Documentation/config/branch.adoc
++++ b/Documentation/config/branch.adoc
+@@ -102,3 +102,10 @@ for details).
+ 	`git branch --edit-description`. Branch description is
+ 	automatically added to the `format-patch` cover letter or
+ 	`request-pull` summary.
++
++`branch.<name>.pruneMerged`::
++	If set to `false`, branch _<name>_ is exempt from
++	`git branch --prune-merged`.  Useful for a topic branch you
++	intend to develop further after an initial round has been
++	merged upstream.  Defaults to true.  Explicit deletion via
++	`git branch -d` is unaffected.
 diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
-index 87a26da0cc..6fde8f642e 100644
+index c521b5f4ca..1bd28c4e37 100644
 --- a/Documentation/git-branch.adoc
 +++ b/Documentation/git-branch.adoc
-@@ -24,8 +24,8 @@ git branch (-m|-M) [<old-branch>] <new-branch>
- git branch (-c|-C) [<old-branch>] <new-branch>
- git branch (-d|-D) [-r] <branch-name>...
- git branch --edit-description [<branch-name>]
--git branch --forked <remote>...
--git branch [-f] --prune-merged <remote>...
-+git branch --forked (<remote>... | --all-remotes)
-+git branch [-f] --prune-merged (<remote>... | --all-remotes)
- 
- DESCRIPTION
- -----------
-@@ -227,6 +227,11 @@ With `--force` (or `-f`), delete refused branches regardless. The
- currently checked-out branch in any worktree is always preserved,
- as is any branch with `branch.<name>.pruneMerged` set to `false`.
- 
-+`--all-remotes`::
-+	With `--forked` or `--prune-merged`, act on every
-+	configured remote in addition to any explicit _<remote>_
-+	arguments.
-+
- `-v`::
- `-vv`::
- `--verbose`::
+@@ -226,9 +226,10 @@ the upstream refs refreshed.
+ +
+ A branch is left alone if any of the following holds:
+ its upstream no longer resolves locally; it is checked out in any
+-worktree; or its push destination (`<branch>@{push}`) equals its
++worktree; its push destination (`<branch>@{push}`) equals its
+ upstream (`<branch>@{upstream}`), so it cannot be distinguished
+-from a freshly pulled trunk that just looks "fully merged".
++from a freshly pulled trunk that just looks "fully merged"; or
++`branch.<name>.pruneMerged` is set to `false`.
+ +
+ Branches refused by the "fully merged" safety check are listed as
+ warnings and skipped; pass them to `git branch -D` explicitly if
 diff --git a/builtin/branch.c b/builtin/branch.c
-index c48af54301..22c30164ca 100644
+index 1569f29573..187d5d1563 100644
 --- a/builtin/branch.c
 +++ b/builtin/branch.c
-@@ -715,6 +715,13 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
- 	free_worktrees(worktrees);
- }
+@@ -875,7 +875,9 @@ static int prune_merged_branches(int argc, const char **argv, int quiet)
+ 		struct branch *branch = branch_get(short_name);
+ 		const char *upstream, *push;
+ 		struct strbuf full = STRBUF_INIT;
++		struct strbuf key = STRBUF_INIT;
+ 		int skip;
++		int opt_out;
  
-+static int collect_remote_name(struct remote *remote, void *cb_data)
-+{
-+	struct string_list *remote_names = cb_data;
-+	string_list_insert(remote_names, remote->name);
-+	return 0;
-+}
+ 		strbuf_addf(&full, "refs/heads/%s", short_name);
+ 		skip = !!branch_checked_out(full.buf);
+@@ -890,6 +892,18 @@ static int prune_merged_branches(int argc, const char **argv, int quiet)
+ 		if (!push || !strcmp(push, upstream))
+ 			continue;
+ 
++		strbuf_addf(&key, "branch.%s.prunemerged", short_name);
++		if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
++		    !opt_out) {
++			if (!quiet)
++				fprintf(stderr,
++					_("Skipping '%s' (branch.%s.pruneMerged is false)\n"),
++					short_name, short_name);
++			strbuf_release(&key);
++			continue;
++		}
++		strbuf_release(&key);
 +
- static void parse_forked_args(int argc, const char **argv,
- 			      struct string_list *remote_names,
- 			      struct string_list *tracking_refs)
-@@ -804,7 +811,7 @@ static void collect_default_branch_refs(const struct string_list *remote_names,
+ 		strvec_push(&deletable, short_name);
  	}
- }
  
--static void collect_forked_set(int argc, const char **argv,
-+static void collect_forked_set(int argc, const char **argv, int all_remotes,
- 			       struct string_list *protected_default_refs,
- 			       struct string_list *out)
- {
-@@ -817,6 +824,8 @@ static void collect_forked_set(int argc, const char **argv,
- 	};
- 
- 	parse_forked_args(argc, argv, &remote_names, &tracking_refs);
-+	if (all_remotes)
-+		for_each_remote(collect_remote_name, &remote_names);
- 
- 	refs_for_each_branch_ref(get_main_ref_store(the_repository),
- 				 collect_forked_branch, &cb);
-@@ -830,15 +839,15 @@ static void collect_forked_set(int argc, const char **argv,
- 	string_list_clear(&tracking_refs, 0);
- }
- 
--static int list_forked_branches(int argc, const char **argv)
-+static int list_forked_branches(int argc, const char **argv, int all_remotes)
- {
- 	struct string_list out = STRING_LIST_INIT_DUP;
- 	struct string_list_item *item;
- 
--	if (!argc)
--		die(_("--forked requires at least one <remote>"));
-+	if (!argc && !all_remotes)
-+		die(_("--forked requires at least one <remote> or --all-remotes"));
- 
--	collect_forked_set(argc, argv, NULL, &out);
-+	collect_forked_set(argc, argv, all_remotes, NULL, &out);
- 	for_each_string_list_item(item, &out)
- 		puts(item->string);
- 
-@@ -846,8 +855,8 @@ static int list_forked_branches(int argc, const char **argv)
- 	return 0;
- }
- 
--static int prune_merged_branches(int argc, const char **argv, int force,
--				 int quiet)
-+static int prune_merged_branches(int argc, const char **argv,
-+				 int all_remotes, int force, int quiet)
- {
- 	struct string_list candidates = STRING_LIST_INIT_DUP;
- 	struct string_list protected_default_refs = STRING_LIST_INIT_DUP;
-@@ -856,10 +865,11 @@ static int prune_merged_branches(int argc, const char **argv, int force,
- 	int n_not_merged = 0;
- 	int ret = 0;
- 
--	if (!argc)
--		die(_("--prune-merged requires at least one <remote>"));
-+	if (!argc && !all_remotes)
-+		die(_("--prune-merged requires at least one <remote> or --all-remotes"));
- 
--	collect_forked_set(argc, argv, &protected_default_refs, &candidates);
-+	collect_forked_set(argc, argv, all_remotes, &protected_default_refs,
-+			   &candidates);
- 
- 	for_each_string_list_item(item, &candidates) {
- 		const char *short_name = item->string;
-@@ -983,6 +993,7 @@ int cmd_branch(int argc,
- 	    unset_upstream = 0, show_current = 0, edit_description = 0;
- 	int forked = 0;
- 	int prune_merged = 0;
-+	int all_remotes = 0;
- 	const char *new_upstream = NULL;
- 	int noncreate_actions = 0;
- 	/* possible options */
-@@ -1040,6 +1051,9 @@ int cmd_branch(int argc,
- 			N_("list local branches forked from the given <remote>s")),
- 		OPT_BOOL(0, "prune-merged", &prune_merged,
- 			N_("delete local branches forked from the given <remote>s that are merged into their upstream")),
-+		OPT_BOOL_F(0, "all-remotes", &all_remotes,
-+			N_("with --forked or --prune-merged, act on every configured remote"),
-+			PARSE_OPT_NONEG),
- 		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
- 		OPT_MERGED(&filter, N_("print only branches that are merged")),
- 		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
-@@ -1083,6 +1097,9 @@ int cmd_branch(int argc,
- 	argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
- 			     0);
- 
-+	if (all_remotes && !forked && !prune_merged)
-+		die(_("--all-remotes requires --forked or --prune-merged"));
-+
- 	if (!delete && !rename && !copy && !edit_description && !new_upstream &&
- 	    !show_current && !unset_upstream && !forked && !prune_merged &&
- 	    argc == 0)
-@@ -1136,10 +1153,10 @@ int cmd_branch(int argc,
- 				      quiet, 0, NULL);
- 		goto out;
- 	} else if (forked) {
--		ret = list_forked_branches(argc, argv);
-+		ret = list_forked_branches(argc, argv, all_remotes);
- 		goto out;
- 	} else if (prune_merged) {
--		ret = prune_merged_branches(argc, argv, force, quiet);
-+		ret = prune_merged_branches(argc, argv, all_remotes, force, quiet);
- 		goto out;
- 	} else if (show_current) {
- 		print_current_branch_name();
 diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
-index 23b82615f5..4bd92fe430 100755
+index ad87946081..da7e174e09 100755
 --- a/t/t3200-branch.sh
 +++ b/t/t3200-branch.sh
-@@ -1771,6 +1771,27 @@ test_expect_success '--forked requires at least one <remote>' '
- 	test_grep "at least one <remote>" err
+@@ -1990,4 +1990,34 @@ test_expect_success '--prune-merged requires at least one <branch>' '
+ 	test_grep "at least one <branch>" err
  '
  
-+test_expect_success '--forked --all-remotes covers every configured remote' '
-+	git -C forked branch --forked --all-remotes >actual &&
-+	cat >expect <<-\EOF &&
-+	local-foreign
-+	local-one
-+	local-two
-+	main
-+	EOF
-+	test_cmp expect actual
++test_expect_success '--prune-merged honours branch.<name>.pruneMerged=false' '
++	test_when_finished "rm -rf pm-optout" &&
++	git clone pm-upstream pm-optout &&
++	git -C pm-optout remote add fork ../pm-fork &&
++	test_config -C pm-optout remote.pushDefault fork &&
++	test_config -C pm-optout push.default current &&
++	git -C pm-optout branch one one-commit &&
++	git -C pm-optout branch --set-upstream-to=origin/next one &&
++	git -C pm-optout branch two two-commit &&
++	git -C pm-optout branch --set-upstream-to=origin/next two &&
++	test_config -C pm-optout branch.one.pruneMerged false &&
++
++	git -C pm-optout branch --prune-merged "origin/*" 2>err &&
++
++	git -C pm-optout rev-parse --verify refs/heads/one &&
++	test_must_fail git -C pm-optout rev-parse --verify refs/heads/two &&
++	test_grep "Skipping .one." err
 +'
 +
-+test_expect_success '--forked --all-remotes still validates explicit <remote>' '
-+	test_must_fail git -C forked branch --forked nope --all-remotes 2>err &&
-+	test_grep "neither a configured remote nor a remote-tracking branch" err
-+'
++test_expect_success 'branch -d still deletes a pruneMerged=false branch' '
++	test_when_finished "rm -rf pm-optout-d" &&
++	git clone pm-upstream pm-optout-d &&
++	git -C pm-optout-d branch one one-commit &&
++	git -C pm-optout-d branch --set-upstream-to=origin/next one &&
++	test_config -C pm-optout-d branch.one.pruneMerged false &&
 +
-+test_expect_success '--all-remotes alone is rejected' '
-+	test_must_fail git -C forked branch --all-remotes 2>err &&
-+	test_grep "requires --forked or --prune-merged" err
-+'
-+
- test_expect_success '--prune-merged: setup' '
- 	test_create_repo pm-upstream &&
- 	test_commit -C pm-upstream base &&
-@@ -1957,4 +1978,23 @@ test_expect_success 'branch -d still deletes a pruneMerged=false branch' '
- 	test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one
- '
- 
-+test_expect_success '--prune-merged --all-remotes covers every configured remote' '
-+	test_when_finished "rm -rf pm-allremotes" &&
-+	git clone pm-upstream pm-allremotes &&
-+	test_create_repo pm-other &&
-+	test_commit -C pm-other other-base &&
-+	git -C pm-other branch foreign other-base &&
-+	git -C pm-allremotes remote add other ../pm-other &&
-+	git -C pm-allremotes fetch other &&
-+	git -C pm-allremotes branch one --track origin/one &&
-+	git -C pm-allremotes branch foreign --track other/foreign &&
-+
-+	git -C pm-allremotes update-ref -d refs/remotes/origin/one &&
-+	git -C pm-allremotes update-ref -d refs/remotes/other/foreign &&
-+	git -C pm-allremotes branch --force --prune-merged --all-remotes &&
-+
-+	test_must_fail git -C pm-allremotes rev-parse --verify refs/heads/one &&
-+	test_must_fail git -C pm-allremotes rev-parse --verify refs/heads/foreign
++	git -C pm-optout-d branch -d one &&
++	test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one
 +'
 +
  test_done
 -- 
 gitgitgadget
+
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help