git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 3/4] branch: add --dry-run option to branch

From
Glen Choo <chooglen@google.com>
Date
Nov 22, 2021, 22:32 UTC
Message-ID
<20211122223252.19922-4-chooglen@google.com>
In-Reply-To
<20211122223252.19922-1-chooglen@google.com>

When running "git branch --recurse-submodules topic", it would be useful to know whether or not 'topic' is a valid branch for all repositories. Currently there is no way to test this without actually creating the branch.

Add a --dry-run option to branch creation that can check whether or not a branch name and start point would be valid for a repository without creating a branch. Refactor cmd_branch() to make the chosen action more obvious.

Incidentally, fix an incorrect usage string that combined the 'list' usage of git branch (-l) with the 'create' usage; this string has been incorrect since its inception, a8dfd5eac4 (Make builtin-branch.c use parse_options., 2007-10-07).

Signed-off-by: Glen Choo <chooglen@google.com>
---
The --dry-run option is motivated mainly by --recurse-submodules. To my
knowledge, there isn't a strong existing demand, but this might be
mildly useful to some users.
 Documentation/git-branch.txt |  8 ++++++-
 branch.c                     |  6 ++---
 branch.h                     | 22 ++++++++++++++++++
 builtin/branch.c             | 44 ++++++++++++++++++++++++++----------
 t/t3200-branch.sh            | 13 +++++++++++
 5 files changed, 77 insertions(+), 16 deletions(-)
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 5449767121..8cdc33c097 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track | --no-track] [-f] [--dry-run | -n] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,6 +205,12 @@ This option is only applicable in non-verbose mode.
 --no-abbrev::
 	Display the full sha1s in the output listing rather than abbreviating them.
 
+-n::
+--dry-run::
+	Can only be used when creating a branch. If the branch creation
+	would fail, show the relevant error message. If the branch
+	creation would succeed, show nothing.
+
 -t::
 --track::
 	When creating a new branch, set up `branch.<name>.remote` and
diff --git a/branch.c b/branch.c
index f8b755513f..528cb2d639 100644
--- a/branch.c
+++ b/branch.c
@@ -206,9 +206,9 @@ N_("\n"
 "will track its remote counterpart, you may want to use\n"
 "\"git push -u\" to set the upstream config as you push.");
 
-static void validate_branch_start(struct repository *r, const char *start_name,
-				  enum branch_track track,
-				  struct object_id *oid, char **full_ref)
+void validate_branch_start(struct repository *r, const char *start_name,
+			   enum branch_track track, struct object_id *oid,
+			   char **full_ref)
 {
 	struct commit *commit;
 	int explicit_tracking = 0;
diff --git a/branch.h b/branch.h
index 75cefcdcbd..d8e5ff4e28 100644
--- a/branch.h
+++ b/branch.h
@@ -3,6 +3,7 @@
 
 struct repository;
 struct strbuf;
+struct object_id;
 
 enum branch_track {
 	BRANCH_TRACK_UNSPECIFIED = -1,
@@ -17,6 +18,27 @@ extern enum branch_track git_branch_track;
 
 /* Functions for acting on the information about branches. */
 
+/*
+ * Validates whether a ref is a valid starting point for a branch, where:
+ *
+ *   - r is the repository to validate the branch for
+ *
+ *   - start_name is the ref that we would like to test
+ *
+ *   - track is the tracking mode of the new branch. If tracking is
+ *     explicitly requested, start_name must be a branch (because
+ *     otherwise start_name cannot be tracked)
+ *
+ *   - oid is an out parameter containing the object_id of start_name
+ *
+ *   - full_ref is an out parameter containing the 'full' form of
+ *     start_name e.g. refs/heads/main instead of main
+ *
+ */
+void validate_branch_start(struct repository *r, const char *start_name,
+			   enum branch_track track, struct object_id *oid,
+			   char **full_ref);
+
 /*
  * This sets the branch.<new_ref>.{remote,merge} config settings so that
  * branch 'new_ref' tracks 'orig_ref'. This is called when branches are
diff --git a/builtin/branch.c b/builtin/branch.c
index eb5c117a6e..5d4b9c82b4 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -27,7 +27,8 @@
 
 static const char * const builtin_branch_usage[] = {
 	N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
-	N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
+	N_("git branch [<options>] [-l] [<pattern>...]"),
+	N_("git branch [<options>] [-f] [--dry-run | -n] <branch-name> [<start-point>]"),
 	N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
 	N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
 	N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
@@ -616,14 +617,14 @@ static int edit_branch_description(const char *branch_name)
 
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
-	int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
-	int show_current = 0;
-	int reflog = 0, edit_description = 0;
-	int quiet = 0, unset_upstream = 0;
+	/* possible actions */
+	int delete = 0, rename = 0, copy = 0, force = 0, list = 0, create = 0,
+	    unset_upstream = 0, show_current = 0, edit_description = 0;
+	/* possible options */
+	int reflog = 0, quiet = 0, dry_run = 0, icase = 0;
 	const char *new_upstream = NULL;
 	enum branch_track track;
 	struct ref_filter filter;
-	int icase = 0;
 	static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 	struct ref_format format = REF_FORMAT_INIT;
 
@@ -670,6 +671,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 			N_("print only branches of the object"), parse_opt_object_name),
 		OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
 		OPT_STRING(  0 , "format", &format.format, N_("format"), N_("format to use for the output")),
+		OPT__DRY_RUN(&dry_run, N_("show whether the branch would be created")),
 		OPT_END(),
 	};
 
@@ -705,10 +707,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	    filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
 		list = 1;
 
-	if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
-	    list + edit_description + unset_upstream > 1)
+	create = 1 - (!!delete + !!rename + !!copy + !!new_upstream +
+		      !!show_current + !!list + !!edit_description +
+		      !!unset_upstream);
+	if (create < 0)
 		usage_with_options(builtin_branch_usage, options);
 
+	if (dry_run && !create)
+		die(_("--dry-run can only be used when creating branches"));
+
 	if (filter.abbrev == -1)
 		filter.abbrev = DEFAULT_ABBREV;
 	filter.ignore_case = icase;
@@ -844,7 +851,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		strbuf_addf(&buf, "branch.%s.merge", branch->name);
 		git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
 		strbuf_release(&buf);
-	} else if (argc > 0 && argc <= 2) {
+	} else if (create && argc > 0 && argc <= 2) {
+		const char *branch_name = argv[0];
+		const char *start_name = (argc == 2) ? argv[1] : head;
+
 		if (filter.kind != FILTER_REFS_BRANCHES)
 			die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n"
 				  "Did you mean to use: -a|-r --list <pattern>?"));
@@ -852,10 +862,20 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		if (track == BRANCH_TRACK_OVERRIDE)
 			die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
 
-		create_branch(the_repository,
-			      argv[0], (argc == 2) ? argv[1] : head,
-			      force, 0, reflog, quiet, track);
+		if (dry_run) {
+			struct strbuf buf = STRBUF_INIT;
+			char *unused_full_ref;
+			struct object_id unused_oid;
 
+			validate_new_branchname(branch_name, &buf, force);
+			validate_branch_start(the_repository, start_name, track,
+					      &unused_oid, &unused_full_ref);
+			strbuf_release(&buf);
+			FREE_AND_NULL(unused_full_ref);
+			return 0;
+		}
+		create_branch(the_repository, branch_name, start_name, force, 0,
+			      reflog, quiet, track);
 	} else
 		usage_with_options(builtin_branch_usage, options);
 
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 6bf95a1707..653891736a 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -59,6 +59,19 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git branch --dry-run abc should fail when abc exists' '
+	test_must_fail git branch --dry-run abc
+'
+
+test_expect_success 'git branch --dry-run --force abc should succeed when abc exists' '
+	git branch --dry-run --force abc
+'
+
+test_expect_success 'git branch --dry-run def should not create a branch' '
+	git branch --dry-run def &&
+	test_must_fail git rev-parse def
+'
+
 test_expect_success 'git branch a/b/c should create a branch' '
 	git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
 '
-- 
2.33.GIT
Previous: Glen ChooNext: Ævar Arnfjörð Bjarmason
Message 9 of 110 in “implement branch --recurse-submodules”
  1. 0/4 implement branch --recurse-submodulesGlen Choo, Nov 22, 2021
  2. 1/4 submodule-config: add submodules_of_tree() helperGlen Choo, Nov 22, 2021
  3. Jonathan TanNov 23, 2021
  4. Glen ChooNov 23, 2021
  5. Ævar Arnfjörð BjarmasonNov 23, 2021
  6. Glen ChooNov 23, 2021
  7. Junio C HamanoNov 23, 2021
  8. 2/4 branch: refactor out branch validation from create_branch()Glen Choo, Nov 22, 2021
  9. 3/4 branch: add --dry-run option to branchGlen Choo, Nov 22, 2021
  10. Ævar Arnfjörð BjarmasonNov 23, 2021
  11. Glen ChooNov 23, 2021
  12. Jonathan TanNov 23, 2021
  13. Glen ChooNov 24, 2021
  14. 4/4 branch: add --recurse-submodules option for branch creationGlen Choo, Nov 22, 2021
  15. Ævar Arnfjörð BjarmasonNov 23, 2021
  16. Glen ChooNov 23, 2021
  17. Philippe BlainNov 23, 2021
  18. Glen ChooNov 23, 2021
  19. Jonathan TanNov 24, 2021
  20. Glen ChooNov 24, 2021
  21. Jonathan TanNov 29, 2021
  22. 0/3 implement branch --recurse-submodulesGlen Choo, Dec 6, 2021
  23. 1/3 branch: move --set-upstream-to behavior to setup_tracking()Glen Choo, Dec 6, 2021
  24. Junio C HamanoDec 6, 2021
  25. Glen ChooDec 8, 2021
  26. Junio C HamanoDec 6, 2021
  27. Glen ChooDec 8, 2021
  28. 2/3 builtin/branch: clean up action-picking logic in cmd_branch()Glen Choo, Dec 6, 2021
  29. 3/3 branch: add --recurse-submodules option for branch creationGlen Choo, Dec 6, 2021
  30. 0/5 implement branch --recurse-submodulesGlen Choo, Dec 9, 2021
  31. 1/5 branch: move --set-upstream-to behavior to setup_tracking()Glen Choo, Dec 9, 2021
  32. Jonathan TanDec 9, 2021
  33. Glen ChooDec 9, 2021
  34. 2/5 branch: remove forward declaration of validate_branch_start()Glen Choo, Dec 9, 2021
  35. 3/5 builtin/branch: clean up action-picking logic in cmd_branch()Glen Choo, Dec 9, 2021
  36. Jonathan TanDec 9, 2021
  37. Glen ChooDec 9, 2021
  38. 4/5 branch: add --recurse-submodules option for branch creationGlen Choo, Dec 9, 2021
  39. Philippe BlainDec 11, 2021
  40. Glen ChooDec 14, 2021
  41. 5/5 branch.c: replace questionable exit() codesGlen Choo, Dec 9, 2021
  42. Ævar Arnfjörð BjarmasonDec 10, 2021
  43. Glen ChooDec 10, 2021
  44. Junio C HamanoDec 13, 2021
  45. Ævar Arnfjörð BjarmasonDec 13, 2021
  46. Junio C HamanoDec 13, 2021
  47. Jonathan TanDec 9, 2021
  48. Glen ChooDec 9, 2021
  49. Jonathan TanDec 13, 2021
  50. Glen ChooDec 14, 2021
  51. 0/5 implement branch --recurse-submodulesGlen Choo, Dec 16, 2021
  52. 1/5 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()Glen Choo, Dec 16, 2021
  53. 2/5 branch: make create_branch() always create a branchGlen Choo, Dec 16, 2021
  54. 3/5 branch: add a dry_run parameter to create_branch()Glen Choo, Dec 16, 2021
  55. 4/5 builtin/branch: clean up action-picking logic in cmd_branch()Glen Choo, Dec 16, 2021
  56. 5/5 branch: add --recurse-submodules option for branch creationGlen Choo, Dec 16, 2021
  57. 0/5 implement branch --recurse-submodulesGlen Choo, Dec 16, 2021
  58. 1/5 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()Glen Choo, Dec 16, 2021
  59. 2/5 branch: make create_branch() always create a branchGlen Choo, Dec 16, 2021
  60. 3/5 branch: add a dry_run parameter to create_branch()Glen Choo, Dec 16, 2021
  61. 4/5 builtin/branch: clean up action-picking logic in cmd_branch()Glen Choo, Dec 16, 2021
  62. 5/5 branch: add --recurse-submodules option for branch creationGlen Choo, Dec 16, 2021
  63. Junio C HamanoDec 17, 2021
  64. Junio C HamanoDec 17, 2021
  65. Glen ChooDec 20, 2021
  66. Junio C HamanoDec 20, 2021
  67. Glen ChooDec 20, 2021
  68. 0/5 implement branch --recurse-submodulesGlen Choo, Dec 20, 2021
  69. 1/5 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()Glen Choo, Dec 20, 2021
  70. Jonathan TanJan 11, 2022
  71. Glen ChooJan 11, 2022
  72. Jonathan TanJan 11, 2022
  73. 2/5 branch: make create_branch() always create a branchGlen Choo, Dec 20, 2021
  74. Jonathan TanJan 11, 2022
  75. Glen ChooJan 11, 2022
  76. 3/5 branch: add a dry_run parameter to create_branch()Glen Choo, Dec 20, 2021
  77. 4/5 builtin/branch: clean up action-picking logic in cmd_branch()Glen Choo, Dec 20, 2021
  78. 5/5 branch: add --recurse-submodules option for branch creationGlen Choo, Dec 20, 2021
  79. Junio C HamanoDec 26, 2021
  80. Jonathan TanJan 11, 2022
  81. Glen ChooJan 11, 2022
  82. Jonathan TanJan 11, 2022
  83. Glen ChooJan 11, 2022
  84. Glen ChooDec 20, 2021
  85. Junio C HamanoDec 21, 2021
  86. Glen ChooDec 21, 2021
  87. 0/6 implement branch --recurse-submodulesGlen Choo, Jan 24, 2022
  88. 2/6 branch: make create_branch() always create a branchGlen Choo, Jan 24, 2022
  89. 1/6 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()Glen Choo, Jan 24, 2022
  90. 3/6 branch: add a dry_run parameter to create_branch()Glen Choo, Jan 24, 2022
  91. 4/6 builtin/branch: consolidate action-picking logic in cmd_branch()Glen Choo, Jan 24, 2022
  92. 5/6 branch: add --recurse-submodules option for branch creationGlen Choo, Jan 24, 2022
  93. Jonathan TanJan 27, 2022
  94. Glen ChooJan 27, 2022
  95. Glen ChooJan 27, 2022
  96. 6/6 branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaksGlen Choo, Jan 24, 2022
  97. Junio C HamanoJan 27, 2022
  98. Glen ChooJan 28, 2022
  99. 0/6 implement branch --recurse-submodulesGlen Choo, Jan 29, 2022
  100. 1/6 branch: move --set-upstream-to behavior to dwim_and_setup_tracking()Glen Choo, Jan 29, 2022
  101. 2/6 branch: make create_branch() always create a branchGlen Choo, Jan 29, 2022
  102. Junio C HamanoFeb 1, 2022
  103. 3/6 branch: add a dry_run parameter to create_branch()Glen Choo, Jan 29, 2022
  104. 4/6 builtin/branch: consolidate action-picking logic in cmd_branch()Glen Choo, Jan 29, 2022
  105. 5/6 branch: add --recurse-submodules option for branch creationGlen Choo, Jan 29, 2022
  106. Glen ChooFeb 4, 2022
  107. Junio C HamanoFeb 4, 2022
  108. Glen ChooFeb 4, 2022
  109. 6/6 branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaksGlen Choo, Jan 29, 2022
  110. Jonathan TanFeb 1, 2022

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.