All of lore.kernel.org
 help / color / mirror / Atom feed
From: Glen Choo <chooglen@google.com>
To: git@vger.kernel.org
Cc: "Glen Choo" <chooglen@google.com>,
	"Jonathan Tan" <jonathantanmy@google.com>,
	"Josh Steadmon" <steadmon@google.com>,
	"Emily Shaffer" <emilyshaffer@google.com>,
	"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>,
	"Philippe Blain" <levraiphilippeblain@gmail.com>
Subject: [PATCH v2 0/3] implement branch --recurse-submodules
Date: Mon,  6 Dec 2021 13:55:25 -0800	[thread overview]
Message-ID: <20211206215528.97050-1-chooglen@google.com> (raw)
In-Reply-To: <20211122223252.19922-1-chooglen@google.com>

Submodule branching RFC:
https://lore.kernel.org/git/kl6lv912uvjv.fsf@chooglen-macbookpro.roam.corp.google.com/

Original Submodule UX RFC/Discussion:
https://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/

Contributor Summit submodules Notes:
https://lore.kernel.org/git/nycvar.QRO.7.76.6.2110211148060.56@tvgsbejvaqbjf.bet/

Submodule UX overhaul updates:
https://lore.kernel.org/git/?q=Submodule+UX+overhaul+update

This series implements branch --recurse-submodules as laid out in the
Submodule branching RFC (linked above). If there are concerns about the
UX/behavior, I would appreciate feedback on the RFC thread as well :)

Thanks for the feedback, everyone. I really really appreciate it.

The biggest difference in V2 is that I took Jonathan's advice to remove
"git branch --dry-run" in favor of adding "--dry-run" to "git
submodule--helper create-branch" instead [1]. The benefit of having "git
branch --dry-run" is pretty small, and we'd have to explain to users why
"--dry-run" doesn't work in more situations [2].

Unfortunately patch 3 (formerly patch 4) is now bigger than I would
prefer. This is due to the combined effect of removing "--dry-run" and
squashing the former patch 1. I'd appreciate any feedback on how I can
structure things differently :)

Changes since v1:
* Move the functionality of "git branch --dry-run" into "git
  submodule-helper create-branch --dry-run"
* Add more fields to the submodules_of_tree() struct to reduce the
  number of allocations made by the caller [3]. Move this functionality
  to patch 3 (formerly patch 4) and drop patch 1.
* Make submodules_of_tree() ignore inactive submodules [4]
* Structure the output of the submodules a bit better by adding prefixes
  to the child process' output (instead of inconsistently indenting the
  output).
** I wasn't able to find a good way to interleave stdout/stderr
   correctly, so a less-than-desirable workaround was to route the child
   process output to stdout/stderr depending on the exit code.
** Eventually, I would like to structure the output of submodules in a
   report, as Ævar suggested [5]. But at this stage, I think that it's
   better to spend time getting user feedback on the submodules
   branching UX and it'll be easier to standardize the output when we've
   implemented more of the UX :)

[1] https://lore.kernel.org/git/20211129210140.937875-1-jonathantanmy@google.com
[2] https://lore.kernel.org/git/211123.86zgpvup6m.gmgdl@evledraar.gmail.com
[3] https://lore.kernel.org/git/211123.86r1b7uoil.gmgdl@evledraar.gmail.com
[4] https://lore.kernel.org/git/3ad3941c-de18-41bf-2e44-4238ae868d79@gmail.com
[5] https://lore.kernel.org/git/211123.86v90juovj.gmgdl@evledraar.gmail.com

Glen Choo (3):
  branch: move --set-upstream-to behavior to setup_tracking()
  builtin/branch: clean up action-picking logic in cmd_branch()
  branch: add --recurse-submodules option for branch creation

 Documentation/config/advice.txt    |   3 +
 Documentation/config/submodule.txt |   8 +
 advice.c                           |   1 +
 advice.h                           |   1 +
 branch.c                           | 322 +++++++++++++++++++++--------
 branch.h                           |  44 +++-
 builtin/branch.c                   |  66 ++++--
 builtin/checkout.c                 |   3 +-
 builtin/submodule--helper.c        |  38 ++++
 submodule-config.c                 |  35 ++++
 submodule-config.h                 |  35 ++++
 submodule.c                        |  11 +-
 submodule.h                        |   3 +
 t/t3200-branch.sh                  |  17 ++
 t/t3207-branch-submodule.sh        | 284 +++++++++++++++++++++++++
 15 files changed, 758 insertions(+), 113 deletions(-)
 create mode 100755 t/t3207-branch-submodule.sh

Range-diff against v1:
1:  1551dd683f < -:  ---------- submodule-config: add submodules_of_tree() helper
2:  a4984f6eef ! 1:  cc212dcd39 branch: refactor out branch validation from create_branch()
    @@ Metadata
     Author: Glen Choo <chooglen@google.com>
     
      ## Commit message ##
    -    branch: refactor out branch validation from create_branch()
    +    branch: move --set-upstream-to behavior to setup_tracking()
     
    -    In a subsequent commit, we would like to be able to validate whether or
    -    not a branch name is valid before we create it (--dry-run). This is
    -    useful for `git branch --recurse-submodules topic` because it allows Git
    -    to determine if the branch 'topic' can be created in all submodules
    -    without creating the branch 'topic'.
    +    This refactor is motivated by a desire to add a "dry_run" parameter to
    +    create_branch() that will validate whether or not a branch can be
    +    created without actually creating it - this behavior be used in a
    +    subsequent commit that adds `git branch --recurse-submodules topic`.
     
    -    A good starting point would be to refactor out the start point
    -    validation and dwim logic in create_branch() in a
    -    validate_branch_start() helper function. Once we do so, it becomes
    -    clear that create_branch() is more complex than it needs to be -
    -    create_branch() is also used to set tracking information when performing
    -    `git branch --set-upstream-to`. This made more sense when
    -    (the now unsupported) --set-upstream was first introduced in
    -    4fc5006676 (Add branch --set-upstream, 2010-01-18), because
    -    it would sometimes create a branch and sometimes update tracking
    -    information without creating a branch.
    +    Adding "dry_run" is not obvious because create_branch() is also used to
    +    set tracking information without creating a branch, i.e. when using
    +    --set-upstream-to. This appears to be a leftover from 4fc5006676 (Add
    +    branch --set-upstream, 2010-01-18), when --set-upstream would sometimes
    +    create a branch and sometimes update tracking information without
    +    creating a branch. However, we no longer support --set-upstream, so it
    +    makes more sense to set tracking information with another function, like
    +    setup_tracking(), and use create_branch() only to create branches. When
    +    this is done, it will be trivial to add "dry_run".
     
    -    Refactor out the branch validation and dwim logic from create_branch()
    -    into validate_branch_start(), make it so that create_branch() always
    -    tries to create a branch, and replace the now-incorrect create_branch()
    -    call with setup_tracking(). Since there were none, add tests for
    -    creating a branch with `--force`.
    +    Do this refactor by moving the branch validation and dwim logic from
    +    create_branch() into a new function, validate_branch_start(), and call
    +    it from setup_tracking(). Now that setup_tracking() can perform dwim and
    +    tracking setup without creating a branch, use it in `git branch
    +    --set-upstream-to` and remove unnecessary behavior from create_branch().
    +
    +    Since there were none, add tests for creating a branch with `--force`.
     
         Signed-off-by: Glen Choo <chooglen@google.com>
     
    @@ branch.c: N_("\n"
     -		   const char *name, const char *start_name,
     -		   int force, int clobber_head_ok, int reflog,
     -		   int quiet, enum branch_track track)
    ++/**
    ++ * 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. This is
    ++ *     expanded with DWIM and assigned to real_ref.
    ++ *
    ++ *   - 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
    ++ *
    ++ *   - real_ref is an out parameter containing the full, 'real' form of
    ++ *     start_name e.g. refs/heads/main instead of main
    ++ *
    ++ */
     +static void validate_branch_start(struct repository *r, const char *start_name,
     +				  enum branch_track track,
    -+				  struct object_id *oid, char **full_ref)
    ++				  struct object_id *oid, char **real_ref)
      {
      	struct commit *commit;
     -	struct object_id oid;
    @@ branch.c: void create_branch(struct repository *r,
      	}
      
     -	switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
    -+	switch (repo_dwim_ref(r, start_name, strlen(start_name), oid, full_ref,
    ++	switch (repo_dwim_ref(r, start_name, strlen(start_name), oid, real_ref,
     +			      0)) {
      	case 0:
      		/* Not branching from any existing branch */
    @@ branch.c: void create_branch(struct repository *r,
      		/* Unique completion -- good, only if it is a real branch */
     -		if (!starts_with(real_ref, "refs/heads/") &&
     -		    validate_remote_tracking_branch(real_ref)) {
    -+		if (!starts_with(*full_ref, "refs/heads/") &&
    -+		    validate_remote_tracking_branch(*full_ref)) {
    ++		if (!starts_with(*real_ref, "refs/heads/") &&
    ++		    validate_remote_tracking_branch(*real_ref)) {
      			if (explicit_tracking)
      				die(_(upstream_not_branch), start_name);
      			else
     -				FREE_AND_NULL(real_ref);
    -+				FREE_AND_NULL(*full_ref);
    ++				FREE_AND_NULL(*real_ref);
      		}
      		break;
      	default:
3:  cbcbc4f49e < -:  ---------- branch: add --dry-run option to branch
-:  ---------- > 2:  320749cc82 builtin/branch: clean up action-picking logic in cmd_branch()
4:  416a114fa9 ! 3:  c0441c6691 branch: add --recurse-submodules option for branch creation
    @@ Metadata
      ## Commit message ##
         branch: add --recurse-submodules option for branch creation
     
    -    Teach cmd_branch to accept the --recurse-submodules option when creating
    -    branches so that `git branch --recurse-submodules topic` will create the
    -    "topic" branch in the superproject and all submodules. Guard this (and
    -    future submodule branching) behavior behind a new configuration value
    -    'submodule.propagateBranches'.
    +    To improve the submodules UX, we would like to teach Git to handle
    +    branches in submodules. Start this process by teaching `git branch` the
    +    --recurse-submodules option so that `git branch --recurse-submodules
    +    topic` will create the "topic" branch in the superproject and its
    +    submodules.
    +
    +    Although this commit does not introduce breaking changes, it is
    +    incompatible with existing --recurse-submodules semantics e.g. `git
    +    checkout` does not recursively checkout the expected branches created by
    +    `git branch` yet. To ensure that the correct set of semantics is used,
    +    this commit introduces a new configuration value,
    +    `submodule.propagateBranches`, which enables submodule branching when
    +    true (defaults to false).
    +
    +    This commit includes changes that allow Git to work with submodules
    +    that are in trees (and not just the index):
    +
    +    * add a submodules_of_tree() helper that gives the relevant
    +      information of an in-tree submodule (e.g. path and oid) and
    +      initializes the repository
    +    * add is_tree_submodule_active() by adding a treeish_name parameter to
    +      is_submodule_active()
    +    * add the "submoduleNotUpdated" advice to advise users to update the
    +      submodules in their trees
    +
    +    Other changes
    +
    +    * add a "dry_run" parameter to create_branch() in order to support
    +      `git submodule--helper create-branch --dry-run`
     
         Signed-off-by: Glen Choo <chooglen@google.com>
     
    @@ Documentation/config/submodule.txt: submodule.recurse::
      	configuration value by using `git -c submodule.recurse=0`.
      
     +submodule.propagateBranches::
    -+	[EXPERIMENTAL] A boolean that enables branching support with
    -+	submodules. This allows certain commands to accept
    -+	`--recurse-submodules` (`git branch --recurse-submodules` will
    -+	create branches recursively), and certain commands that already
    -+	accept `--recurse-submodules` will now consider branches (`git
    -+	switch --recurse-submodules` will switch to the correct branch
    -+	in all submodules).
    ++	[EXPERIMENTAL] A boolean that enables branching support when
    ++	using `--recurse-submodules` or `submodule.recurse=true`.
    ++	Enabling this will allow certain commands to accept
    ++	`--recurse-submodules` and certain commands that already accept
    ++	`--recurse-submodules` will now consider branches.
    ++	Defaults to false.
     +
      submodule.fetchJobs::
      	Specifies how many submodules are fetched/cloned at the same time.
    @@ branch.c
      
      struct tracking {
      	struct refspec_item spec;
    +@@ branch.c: void setup_tracking(const char *new_ref, const char *orig_ref,
    + 
    + void create_branch(struct repository *r, const char *name,
    + 		   const char *start_name, int force, int clobber_head_ok,
    +-		   int reflog, int quiet, enum branch_track track)
    ++		   int reflog, int quiet, enum branch_track track, int dry_run)
    + {
    + 	struct object_id oid;
    + 	char *real_ref;
     @@ branch.c: void create_branch(struct repository *r, const char *name,
    + 	}
    + 
    + 	validate_branch_start(r, start_name, track, &oid, &real_ref);
    ++	if (dry_run)
    ++		goto cleanup;
    + 
    + 	if (reflog)
    + 		log_all_ref_updates = LOG_REFS_NORMAL;
    +@@ branch.c: void create_branch(struct repository *r, const char *name,
    + 	if (real_ref && track)
    + 		setup_tracking(ref.buf + 11, real_ref, track, quiet, 0);
    + 
    ++cleanup:
    + 	strbuf_release(&ref);
      	free(real_ref);
      }
      
    -+static int submodule_validate_branchname(struct repository *r, const char *name,
    -+					 const char *start_name, int force,
    -+					 int quiet, char **err_msg)
    -+{
    -+	int ret = 0;
    -+	struct child_process child = CHILD_PROCESS_INIT;
    -+	struct strbuf child_err = STRBUF_INIT;
    -+	child.git_cmd = 1;
    -+	child.err = -1;
    -+
    -+	prepare_other_repo_env(&child.env_array, r->gitdir);
    -+	strvec_pushl(&child.args, "branch", "--dry-run", NULL);
    -+	if (force)
    -+		strvec_push(&child.args, "--force");
    -+	if (quiet)
    -+		strvec_push(&child.args, "--quiet");
    -+	strvec_pushl(&child.args, name, start_name, NULL);
    -+
    -+	if ((ret = start_command(&child)))
    -+		return ret;
    -+	ret = finish_command(&child);
    -+	strbuf_read(&child_err, child.err, 0);
    -+	*err_msg = strbuf_detach(&child_err, NULL);
    -+	return ret;
    -+}
    -+
    -+static int submodule_create_branch(struct repository *r, const char *name,
    -+				   const char *start_oid,
    ++static int submodule_create_branch(struct repository *r,
    ++				   const struct submodule *submodule,
    ++				   const char *name, const char *start_oid,
     +				   const char *start_name, int force,
     +				   int reflog, int quiet,
    -+				   enum branch_track track, char **err_msg)
    ++				   enum branch_track track, int dry_run)
     +{
     +	int ret = 0;
     +	struct child_process child = CHILD_PROCESS_INIT;
     +	struct strbuf child_err = STRBUF_INIT;
    ++	struct strbuf out_buf = STRBUF_INIT;
    ++	char *out_prefix = xstrfmt("submodule '%s': ", submodule->name);
     +	child.git_cmd = 1;
     +	child.err = -1;
    ++	child.stdout_to_stderr = 1;
     +
     +	prepare_other_repo_env(&child.env_array, r->gitdir);
     +	strvec_pushl(&child.args, "submodule--helper", "create-branch", NULL);
    ++	if (dry_run)
    ++		strvec_push(&child.args, "--dry-run");
     +	if (force)
     +		strvec_push(&child.args, "--force");
     +	if (quiet)
    @@ branch.c: void create_branch(struct repository *r, const char *name,
     +		return ret;
     +	ret = finish_command(&child);
     +	strbuf_read(&child_err, child.err, 0);
    -+	*err_msg = strbuf_detach(&child_err, NULL);
    ++	strbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);
    ++
    ++	if (ret)
    ++		fprintf(stderr, "%s", out_buf.buf);
    ++	else
    ++		printf("%s", out_buf.buf);
    ++
    ++	strbuf_release(&child_err);
    ++	strbuf_release(&out_buf);
     +	return ret;
     +}
     +
    -+void create_submodule_branches(struct repository *r, const char *name,
    -+			       const char *start_name, int force, int reflog,
    -+			       int quiet, enum branch_track track)
    ++void create_branches_recursively(struct repository *r, const char *name,
    ++				 const char *start_name,
    ++				 const char *tracking_name, int force,
    ++				 int reflog, int quiet, enum branch_track track,
    ++				 int dry_run)
     +{
     +	int i = 0;
     +	char *branch_point = NULL;
    -+	struct repository *subrepos;
    -+	struct submodule *submodules;
     +	struct object_id super_oid;
    -+	struct submodule_entry_list *submodule_entry_list;
    -+	char *err_msg = NULL;
    -+
    -+	validate_branch_start(r, start_name, track, &super_oid, &branch_point);
    -+
    -+	submodule_entry_list = submodules_of_tree(r, &super_oid);
    -+	CALLOC_ARRAY(subrepos, submodule_entry_list->entry_nr);
    -+	CALLOC_ARRAY(submodules, submodule_entry_list->entry_nr);
    -+
    -+	for (i = 0; i < submodule_entry_list->entry_nr; i++) {
    -+		submodules[i] = *submodule_from_path(
    -+			r, &super_oid,
    -+			submodule_entry_list->name_entries[i].path);
    -+
    -+		if (repo_submodule_init(
    -+			    &subrepos[i], r,
    -+			    submodule_entry_list->name_entries[i].path,
    -+			    &super_oid)) {
    -+			die(_("submodule %s: unable to find submodule"),
    -+			    submodules[i].name);
    ++	struct submodule_entry_list submodule_entry_list;
    ++
    ++	/* Perform dwim on start_name to get super_oid and branch_point. */
    ++	validate_branch_start(r, start_name, BRANCH_TRACK_NEVER, &super_oid,
    ++			      &branch_point);
    ++
    ++	/*
    ++	 * If we were not given an explicit name to track, then assume we are at
    ++	 * the top level and, just like the non-recursive case, the tracking
    ++	 * name is the branch point.
    ++	 */
    ++	if (!tracking_name)
    ++		tracking_name = branch_point;
    ++
    ++	submodules_of_tree(r, &super_oid, &submodule_entry_list);
    ++	/*
    ++	 * Before creating any branches, first check that the branch can
    ++	 * be created in every submodule.
    ++	 */
    ++	for (i = 0; i < submodule_entry_list.entry_nr; i++) {
    ++		if (submodule_entry_list.entries[i].repo == NULL) {
     +			if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))
    -+				advise(_("You may try initializing the submodules using 'git checkout %s && git submodule update'"),
    ++				advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"),
     +				       start_name);
    ++			die(_("submodule '%s': unable to find submodule"),
    ++			    submodule_entry_list.entries[i].submodule->name);
     +		}
     +
    -+		if (submodule_validate_branchname(
    -+			    &subrepos[i], name,
    -+			    oid_to_hex(
    -+				    &submodule_entry_list->name_entries[i].oid),
    -+			    force, quiet, &err_msg))
    -+			die(_("submodule %s: could not create branch '%s'\n\t%s"),
    -+			    submodules[i].name, name, err_msg);
    ++		if (submodule_create_branch(
    ++			    submodule_entry_list.entries[i].repo,
    ++			    submodule_entry_list.entries[i].submodule, name,
    ++			    oid_to_hex(&submodule_entry_list.entries[i]
    ++						.name_entry->oid),
    ++			    tracking_name, force, reflog, quiet, track, 1))
    ++			die(_("submodule '%s': cannot create branch '%s'"),
    ++			    submodule_entry_list.entries[i].submodule->name,
    ++			    name);
     +	}
     +
     +	create_branch(the_repository, name, start_name, force, 0, reflog, quiet,
    -+		      track);
    -+
    -+	for (i = 0; i < submodule_entry_list->entry_nr; i++) {
    -+		printf_ln(_("submodule %s: creating branch '%s'"),
    -+			  submodules[i].name, name);
    ++		      BRANCH_TRACK_NEVER, dry_run);
    ++	if (dry_run)
    ++		return;
    ++	/*
    ++	 * NEEDSWORK If tracking was set up in the superproject but not the
    ++	 * submodule, users might expect "git branch --recurse-submodules" to
    ++	 * fail or give a warning, but this is not yet implemented because it is
    ++	 * tedious to determine whether or not tracking was set up in the
    ++	 * superproject.
    ++	 */
    ++	setup_tracking(name, tracking_name, track, quiet, 0);
    ++
    ++	for (i = 0; i < submodule_entry_list.entry_nr; i++) {
     +		if (submodule_create_branch(
    -+			    &subrepos[i], name,
    -+			    oid_to_hex(
    -+				    &submodule_entry_list->name_entries[i].oid),
    -+			    branch_point, force, reflog, quiet, track,
    -+			    &err_msg))
    -+			die(_("submodule %s: could not create branch '%s'\n\t%s"),
    -+			    submodules[i].name, name, err_msg);
    -+
    -+		repo_clear(&subrepos[i]);
    ++			    submodule_entry_list.entries[i].repo,
    ++			    submodule_entry_list.entries[i].submodule, name,
    ++			    oid_to_hex(&submodule_entry_list.entries[i]
    ++						.name_entry->oid),
    ++			    tracking_name, force, reflog, quiet, track, 0))
    ++			die(_("submodule '%s': cannot create branch '%s'"),
    ++			    submodule_entry_list.entries[i].submodule->name,
    ++			    name);
    ++		repo_clear(submodule_entry_list.entries[i].repo);
     +	}
     +}
     +
    @@ branch.c: void create_branch(struct repository *r, const char *name,
      	unlink(git_path_merge_head(r));
     
      ## branch.h ##
    -@@ branch.h: void create_branch(struct repository *r,
    - 		   int force, int clobber_head_ok,
    - 		   int reflog, int quiet, enum branch_track track);
    +@@ branch.h: void setup_tracking(const char *new_ref, const char *orig_ref,
    +  *   - track causes the new branch to be configured to merge the remote branch
    +  *     that start_name is a tracking branch for (if any).
    +  *
    ++ *   - dry_run causes the branch to be validated but not created.
    ++ *
    +  */
    +-void create_branch(struct repository *r,
    +-		   const char *name, const char *start_name,
    +-		   int force, int clobber_head_ok,
    +-		   int reflog, int quiet, enum branch_track track);
    ++void create_branch(struct repository *r, const char *name,
    ++		   const char *start_name, int force, int clobber_head_ok,
    ++		   int reflog, int quiet, enum branch_track track, int dry_run);
      
     +/*
    -+ * Creates a new branch in repository and its submodules.
    ++ * Creates a new branch in repository and its submodules (and its
    ++ * submodules, recursively). Besides these exceptions, the parameters
    ++ * function identically to create_branch():
    ++ *
    ++ * - start_name is the name of the ref, in repository r, that the new
    ++ *   branch should start from. In submodules, branches will start from
    ++ *   the respective gitlink commit ids in start_name's tree.
    ++ *
    ++ * - tracking_name is the name used of the ref that will be used to set
    ++ *   up tracking, e.g. origin/main. This is propagated to submodules so
    ++ *   that tracking information will appear as if the branch branched off
    ++ *   tracking_name instead of start_name (which is a plain commit id for
    ++ *   submodules). If omitted, start_name is used for tracking (just like
    ++ *   create_branch()).
    ++ *
     + */
    -+void create_submodule_branches(struct repository *r, const char *name,
    -+			       const char *start_name, int force, int reflog,
    -+			       int quiet, enum branch_track track);
    ++void create_branches_recursively(struct repository *r, const char *name,
    ++				 const char *start_name,
    ++				 const char *tracking_name, int force,
    ++				 int reflog, int quiet, enum branch_track track,
    ++				 int dry_run);
      /*
       * Check if 'name' can be a valid name for a branch; die otherwise.
       * Return 1 if the named branch already exists; return 0 otherwise.
    @@ builtin/branch.c: static int git_branch_config(const char *var, const char *valu
      }
      
     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)
    - 	int delete = 0, rename = 0, copy = 0, force = 0, list = 0, create = 0,
      	    unset_upstream = 0, show_current = 0, edit_description = 0;
    + 	int noncreate_actions = 0;
      	/* possible options */
    --	int reflog = 0, quiet = 0, dry_run = 0, icase = 0;
    -+	int reflog = 0, quiet = 0, dry_run = 0, icase = 0,
    -+	    recurse_submodules_explicit = 0;
    +-	int reflog = 0, quiet = 0, icase = 0;
    ++	int reflog = 0, quiet = 0, icase = 0, recurse_submodules_explicit = 0;
      	const char *new_upstream = NULL;
      	enum branch_track track;
      	struct ref_filter filter;
    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix
      		OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
     +		OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
      		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(),
    + 	};
     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)
    - 	if (create < 0)
    + 	if (noncreate_actions > 1)
      		usage_with_options(builtin_branch_usage, options);
      
    -+	if (recurse_submodules_explicit && submodule_propagate_branches &&
    -+	    !create)
    -+		die(_("--recurse-submodules can only be used to create branches"));
    - 	if (dry_run && !create)
    - 		die(_("--dry-run can only be used when creating branches"));
    - 
    ++	if (recurse_submodules_explicit) {
    ++		if (!submodule_propagate_branches)
    ++			die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
    ++		if (noncreate_actions)
    ++			die(_("--recurse-submodules can only be used to create branches"));
    ++	}
    ++
     +	recurse_submodules =
     +		(recurse_submodules || recurse_submodules_explicit) &&
     +		submodule_propagate_branches;
    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix
      		filter.abbrev = DEFAULT_ABBREV;
      	filter.ignore_case = icase;
     @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)
    - 			FREE_AND_NULL(unused_full_ref);
    - 			return 0;
    - 		}
    + 		git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
    + 		strbuf_release(&buf);
    + 	} else if (!noncreate_actions && 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>?"));
    +@@ builtin/branch.c: 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 (recurse_submodules) {
    -+			create_submodule_branches(the_repository, branch_name,
    -+						  start_name, force, reflog,
    -+						  quiet, track);
    ++			create_branches_recursively(the_repository, branch_name,
    ++						    start_name, NULL, force,
    ++						    reflog, quiet, track, 0);
     +			return 0;
     +		}
    - 		create_branch(the_repository, branch_name, start_name, force, 0,
    - 			      reflog, quiet, track);
    ++		create_branch(the_repository, branch_name, start_name, force, 0,
    ++			      reflog, quiet, track, 0);
      	} else
    + 		usage_with_options(builtin_branch_usage, options);
    + 
    +
    + ## builtin/checkout.c ##
    +@@ builtin/checkout.c: static void update_refs_for_switch(const struct checkout_opts *opts,
    + 				      opts->new_branch_force ? 1 : 0,
    + 				      opts->new_branch_log,
    + 				      opts->quiet,
    +-				      opts->track);
    ++				      opts->track,
    ++				      0);
    + 		new_branch_info->name = opts->new_branch;
    + 		setup_branch_path(new_branch_info);
    + 	}
     
      ## builtin/submodule--helper.c ##
     @@
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +static int module_create_branch(int argc, const char **argv, const char *prefix)
     +{
     +	enum branch_track track;
    -+	int quiet = 0, force = 0, reflog = 0;
    ++	int quiet = 0, force = 0, reflog = 0, dry_run = 0;
     +
     +	struct option options[] = {
     +		OPT__QUIET(&quiet, N_("print only error messages")),
    @@ builtin/submodule--helper.c: static int module_set_branch(int argc, const char *
     +		OPT_SET_INT('t', "track", &track,
     +			    N_("set up tracking mode (see git-pull(1))"),
     +			    BRANCH_TRACK_EXPLICIT),
    ++		OPT__DRY_RUN(&dry_run,
    ++			     N_("show whether the branch would be created")),
     +		OPT_END()
     +	};
     +	const char *const usage[] = {
    -+		N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] <name> <start_oid> <start_name>"),
    ++		N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"),
     +		NULL
     +	};
     +
    ++	git_config(git_default_config, NULL);
    ++	track = git_branch_track;
     +	argc = parse_options(argc, argv, prefix, options, usage, 0);
     +
     +	if (argc != 3)
     +		usage_with_options(usage, options);
     +
    -+	create_branch(the_repository, argv[0], argv[1], force, 0, reflog, quiet,
    -+		      BRANCH_TRACK_NEVER);
    -+	setup_tracking(argv[0], argv[2], track, quiet, 0);
    ++	if (!quiet && !dry_run)
    ++		printf_ln(_("creating branch '%s'"), argv[0]);
     +
    ++	create_branches_recursively(the_repository, argv[0], argv[1], argv[2],
    ++				    force, reflog, quiet, track, dry_run);
     +	return 0;
     +}
      struct add_data {
    @@ builtin/submodule--helper.c: static struct cmd_struct commands[] = {
      
      int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
     
    + ## submodule-config.c ##
    +@@
    + #include "strbuf.h"
    + #include "object-store.h"
    + #include "parse-options.h"
    ++#include "tree-walk.h"
    + 
    + /*
    +  * submodule cache lookup structure
    +@@ submodule-config.c: const struct submodule *submodule_from_path(struct repository *r,
    + 	return config_from(r->submodule_cache, treeish_name, path, lookup_path);
    + }
    + 
    ++void submodules_of_tree(struct repository *r,
    ++			const struct object_id *treeish_name,
    ++			struct submodule_entry_list *out)
    ++{
    ++	struct tree_desc tree;
    ++	struct submodule_tree_entry *st_entry;
    ++	struct name_entry *name_entry;
    ++
    ++	name_entry = xmalloc(sizeof(*name_entry));
    ++
    ++	CALLOC_ARRAY(out->entries, 0);
    ++	out->entry_nr = 0;
    ++	out->entry_alloc = 0;
    ++
    ++	fill_tree_descriptor(r, &tree, treeish_name);
    ++	while (tree_entry(&tree, name_entry)) {
    ++		if (!S_ISGITLINK(name_entry->mode) || !is_tree_submodule_active(r, treeish_name, name_entry->path)) {
    ++			continue;
    ++		}
    ++
    ++		st_entry = xmalloc(sizeof(*st_entry));
    ++		st_entry->name_entry = name_entry;
    ++		st_entry->submodule =
    ++			submodule_from_path(r, treeish_name, name_entry->path);
    ++		st_entry->repo = xmalloc(sizeof(*st_entry->repo));
    ++		if (repo_submodule_init(st_entry->repo, r, name_entry->path,
    ++					treeish_name))
    ++			FREE_AND_NULL(st_entry->repo);
    ++
    ++		ALLOC_GROW(out->entries, out->entry_nr + 1, out->entry_alloc);
    ++		out->entries[out->entry_nr++] = *st_entry;
    ++	}
    ++}
    ++
    + void submodule_free(struct repository *r)
    + {
    + 	if (r->submodule_cache)
    +
    + ## submodule-config.h ##
    +@@
    + #include "hashmap.h"
    + #include "submodule.h"
    + #include "strbuf.h"
    ++#include "tree-walk.h"
    + 
    + /**
    +  * The submodule config cache API allows to read submodule
    +@@ submodule-config.h: int check_submodule_name(const char *name);
    + void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);
    + void update_clone_config_from_gitmodules(int *max_jobs);
    + 
    ++/*
    ++ * Submodule entry that contains relevant information about a
    ++ * submodule in a tree.
    ++ */
    ++struct submodule_tree_entry {
    ++	/* The submodule's tree entry. */
    ++	struct name_entry *name_entry;
    ++	/*
    ++	 * A struct repository corresponding to the submodule. May be
    ++	 * NULL if the submodule has not been updated.
    ++	 */
    ++	struct repository *repo;
    ++	/*
    ++	 * A struct submodule containing the submodule config in the
    ++	 * tree's .gitmodules.
    ++	 */
    ++	const struct submodule *submodule;
    ++};
    ++
    ++struct submodule_entry_list {
    ++	struct submodule_tree_entry *entries;
    ++	int entry_nr;
    ++	int entry_alloc;
    ++};
    ++
    ++/**
    ++ * Given a treeish, return all submodules in the tree. This only reads
    ++ * one level of the tree, so it will not return nested submodules;
    ++ * callers that require nested submodules are expected to handle the
    ++ * recursion themselves.
    ++ */
    ++void submodules_of_tree(struct repository *r,
    ++			const struct object_id *treeish_name,
    ++			struct submodule_entry_list *ret);
    + #endif /* SUBMODULE_CONFIG_H */
    +
    + ## submodule.c ##
    +@@ submodule.c: int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
    +  * ie, the config looks like: "[submodule] active\n".
    +  * Since that is an invalid pathspec, we should inform the user.
    +  */
    +-int is_submodule_active(struct repository *repo, const char *path)
    ++int is_tree_submodule_active(struct repository *repo,
    ++			     const struct object_id *treeish_name,
    ++			     const char *path)
    + {
    + 	int ret = 0;
    + 	char *key = NULL;
    +@@ submodule.c: int is_submodule_active(struct repository *repo, const char *path)
    + 	const struct string_list *sl;
    + 	const struct submodule *module;
    + 
    +-	module = submodule_from_path(repo, null_oid(), path);
    ++	module = submodule_from_path(repo, treeish_name, path);
    + 
    + 	/* early return if there isn't a path->module mapping */
    + 	if (!module)
    +@@ submodule.c: int is_submodule_active(struct repository *repo, const char *path)
    + 	return ret;
    + }
    + 
    ++int is_submodule_active(struct repository *repo, const char *path)
    ++{
    ++	return is_tree_submodule_active(repo, null_oid(), path);
    ++}
    ++
    + int is_submodule_populated_gently(const char *path, int *return_error_code)
    + {
    + 	int ret = 0;
    +
    + ## submodule.h ##
    +@@ submodule.h: int git_default_submodule_config(const char *var, const char *value, void *cb);
    + struct option;
    + int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
    + 						     const char *arg, int unset);
    ++int is_tree_submodule_active(struct repository *repo,
    ++			     const struct object_id *treeish_name,
    ++			     const char *path);
    + int is_submodule_active(struct repository *repo, const char *path);
    + /*
    +  * Determine if a submodule has been populated at a given 'path' by checking if
    +
      ## t/t3207-branch-submodule.sh (new) ##
     @@
     +#!/bin/sh
    @@ t/t3207-branch-submodule.sh (new)
     +test_expect_success 'setup superproject and submodule' '
     +	git init super &&
     +	test_commit foo &&
    ++	git init sub-sub-upstream &&
    ++	test_commit -C sub-sub-upstream foo &&
     +	git init sub-upstream &&
    -+	test_commit -C sub-upstream foo &&
    -+	git -C super submodule add ../sub-upstream sub &&
    ++	git -C sub-upstream submodule add "$TRASH_DIRECTORY/sub-sub-upstream" sub-sub &&
    ++	git -C sub-upstream commit -m "add submodule" &&
    ++	git -C super submodule add "$TRASH_DIRECTORY/sub-upstream" sub &&
     +	git -C super commit -m "add submodule" &&
    -+	git -C super config submodule.propagateBranches true
    ++	git -C super config submodule.propagateBranches true &&
    ++	git -C super/sub submodule update --init
     +'
     +
    -+cleanup_branches() {
    ++CLEANUP_SCRIPT_PATH="$TRASH_DIRECTORY/cleanup_branches.sh"
    ++
    ++cat >"$CLEANUP_SCRIPT_PATH" <<'EOF'
    ++	#!/bin/sh
    ++
     +	super_dir="$1"
     +	shift
     +	(
    @@ t/t3207-branch-submodule.sh (new)
     +		git checkout main &&
     +		for branch_name in "$@"; do
     +			git branch -D "$branch_name"
    -+			git submodule foreach "(git checkout main && git branch -D $branch_name) || true"
    ++			git submodule foreach "$TRASH_DIRECTORY/cleanup_branches.sh . $branch_name || true"
     +		done
     +	)
    ++EOF
    ++chmod +x "$CLEANUP_SCRIPT_PATH"
    ++
    ++cleanup_branches() {
    ++	TRASH_DIRECTORY="\"$TRASH_DIRECTORY\"" "$CLEANUP_SCRIPT_PATH" "$@"
     +} >/dev/null 2>/dev/null
     +
     +# Test the argument parsing
    @@ t/t3207-branch-submodule.sh (new)
     +	(
     +		cd super &&
     +		git branch --recurse-submodules branch-a &&
    -+		git rev-parse --abbrev-ref branch-a &&
    -+		git -C sub rev-parse --abbrev-ref branch-a
    ++		git rev-parse branch-a &&
    ++		git -C sub rev-parse branch-a &&
    ++		git -C sub/sub-sub rev-parse branch-a
     +	)
     +'
     +
    -+test_expect_success '--recurse-submodules should be ignored if submodule.propagateBranches is false' '
    ++test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '
     +	test_when_finished "cleanup_branches super branch-a" &&
     +	(
     +		cd super &&
    -+		git -c submodule.propagateBranches=false branch --recurse-submodules branch-a &&
    -+		git rev-parse branch-a &&
    -+		test_must_fail git -C sub rev-parse branch-a
    ++		echo "fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled" >expected &&
    ++		test_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&
    ++		test_cmp expected actual
     +	)
     +'
     +
    @@ t/t3207-branch-submodule.sh (new)
     +		test_must_fail git branch --recurse-submodules branch-a 2>actual &&
     +		test_must_fail git rev-parse branch-a &&
     +
    -+		cat >expected <<EOF &&
    -+fatal: submodule sub: could not create branch ${SQ}branch-a${SQ}
    -+	fatal: A branch named ${SQ}branch-a${SQ} already exists.
    -+
    -+EOF
    ++		cat >expected <<-EOF &&
    ++		submodule ${SQ}sub${SQ}: fatal: A branch named ${SQ}branch-a${SQ} already exists.
    ++		fatal: submodule ${SQ}sub${SQ}: cannot create branch ${SQ}branch-a${SQ}
    ++		EOF
     +		test_cmp expected actual
     +	)
     +'
    @@ t/t3207-branch-submodule.sh (new)
     +	)
     +'
     +
    -+test_expect_success 'should create branch when submodule is in .git/modules but not .gitmodules' '
    ++test_expect_success 'should create branch when submodule is not in HEAD .gitmodules' '
     +	test_when_finished "cleanup_branches super branch-a branch-b branch-c" &&
     +	(
     +		cd super &&
     +		git branch branch-a &&
     +		git checkout -b branch-b &&
     +		git submodule add ../sub-upstream sub2 &&
    ++		git -C sub2 submodule update --init &&
     +		# branch-b now has a committed submodule not in branch-a
     +		git commit -m "add second submodule" &&
     +		git checkout branch-a &&
    @@ t/t3207-branch-submodule.sh (new)
     +	)
     +'
     +
    ++test_expect_success 'should not create branches in inactive submodules' '
    ++	test_when_finished "cleanup_branches super branch-a" &&
    ++	test_config -C super submodule.sub.active false &&
    ++	(
    ++		cd super &&
    ++		git branch --recurse-submodules branch-a &&
    ++		git rev-parse branch-a &&
    ++		test_must_fail git -C sub branch-a
    ++	)
    ++'
    ++
     +test_expect_success 'setup remote-tracking tests' '
     +	(
     +		cd super &&
    @@ t/t3207-branch-submodule.sh (new)
     +		# branch-b now has a committed submodule not in branch-a
     +		git commit -m "add second submodule"
     +	) &&
    -+	(
    -+		cd sub-upstream &&
    -+		git branch branch-a
    -+	) &&
     +	git clone --branch main --recurse-submodules super super-clone &&
     +	git -C super-clone config submodule.propagateBranches true
     +'
     +
     +test_expect_success 'should not create branch when submodule is not in .git/modules' '
    -+	# The cleanup needs to delete sub2:branch-b in particular because main does not have sub2
    ++	# The cleanup needs to delete sub2 separately because main does not have sub2
     +	test_when_finished "git -C super-clone/sub2 branch -D branch-b && \
    ++		git -C super-clone/sub2/sub-sub branch -D branch-b && \
     +		cleanup_branches super-clone branch-a branch-b" &&
     +	(
     +		cd super-clone &&
    @@ t/t3207-branch-submodule.sh (new)
     +		# This should fail because super-clone does not have sub2.
     +		test_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&
     +		cat >expected <<-EOF &&
    -+		fatal: submodule sub: unable to find submodule
    -+		You may reinitialize the submodules using ${SQ}git checkout origin/branch-b && git submodule update${SQ}
    ++		hint: You may try updating the submodules using ${SQ}git checkout origin/branch-b && git submodule update --init${SQ}
    ++		fatal: submodule ${SQ}sub2${SQ}: unable to find submodule
     +		EOF
    ++		test_cmp expected actual &&
     +		test_must_fail git rev-parse branch-b &&
     +		test_must_fail git -C sub rev-parse branch-b &&
     +		# User can fix themselves by initializing the submodule
     +		git checkout origin/branch-b &&
    -+		git submodule update &&
    ++		git submodule update --init --recursive &&
     +		git branch --recurse-submodules branch-b origin/branch-b
     +	)
     +'
    @@ t/t3207-branch-submodule.sh (new)
     +	(
     +		cd super-clone &&
     +		git branch --recurse-submodules branch-a origin/branch-a &&
    ++		test "$(git config branch.branch-a.remote)" = origin &&
    ++		test "$(git config branch.branch-a.merge)" = refs/heads/branch-a &&
    ++		# "origin/branch-a" does not exist for "sub", but it matches the refspec
    ++		# so tracking should be set up
     +		test "$(git -C sub config branch.branch-a.remote)" = origin &&
    -+		test "$(git -C sub config branch.branch-a.merge)" = refs/heads/branch-a
    ++		test "$(git -C sub config branch.branch-a.merge)" = refs/heads/branch-a &&
    ++		test "$(git -C sub/sub-sub config branch.branch-a.remote)" = origin &&
    ++		test "$(git -C sub/sub-sub config branch.branch-a.merge)" = refs/heads/branch-a
     +	)
     +'
     +
     +test_expect_success 'should not fail when unable to set up tracking in submodule' '
    -+	test_when_finished "cleanup_branches super-clone branch-b" &&
    ++	test_when_finished "cleanup_branches super-clone branch-a && \
    ++		git -C super-clone remote rename ex-origin origin" &&
     +	(
     +		cd super-clone &&
    -+		git branch --recurse-submodules branch-b origin/branch-b
    ++		git remote rename origin ex-origin &&
    ++		git branch --recurse-submodules branch-a ex-origin/branch-a &&
    ++		test "$(git config branch.branch-a.remote)" = ex-origin &&
    ++		test "$(git config branch.branch-a.merge)" = refs/heads/branch-a &&
    ++		test "$(git -C sub config branch.branch-a.remote)" = "" &&
    ++		test "$(git -C sub config branch.branch-a.merge)" = ""
     +	)
     +'
     +
-- 
2.33.GIT


  parent reply	other threads:[~2021-12-06 21:55 UTC|newest]

Thread overview: 110+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-11-22 22:32 [PATCH 0/4] implement branch --recurse-submodules Glen Choo
2021-11-22 22:32 ` [PATCH 1/4] submodule-config: add submodules_of_tree() helper Glen Choo
2021-11-23  2:12   ` Jonathan Tan
2021-11-23 19:48     ` Glen Choo
2021-11-23 10:53   ` Ævar Arnfjörð Bjarmason
2021-11-23 18:35     ` Glen Choo
2021-11-23 22:46   ` Junio C Hamano
2021-11-22 22:32 ` [PATCH 2/4] branch: refactor out branch validation from create_branch() Glen Choo
2021-11-22 22:32 ` [PATCH 3/4] branch: add --dry-run option to branch Glen Choo
2021-11-23 10:42   ` Ævar Arnfjörð Bjarmason
2021-11-23 18:42     ` Glen Choo
2021-11-23 23:10   ` Jonathan Tan
2021-11-24  0:52     ` Glen Choo
2021-11-22 22:32 ` [PATCH 4/4] branch: add --recurse-submodules option for branch creation Glen Choo
2021-11-23 10:45   ` Ævar Arnfjörð Bjarmason
2021-11-23 18:56     ` Glen Choo
2021-11-23 19:41   ` Philippe Blain
2021-11-23 23:43     ` Glen Choo
2021-11-24  1:31   ` Jonathan Tan
2021-11-24 18:18     ` Glen Choo
2021-11-29 21:01       ` Jonathan Tan
2021-12-06 21:55 ` Glen Choo [this message]
2021-12-06 21:55   ` [PATCH v2 1/3] branch: move --set-upstream-to behavior to setup_tracking() Glen Choo
2021-12-06 22:48     ` Junio C Hamano
2021-12-08 18:48       ` Glen Choo
2021-12-06 23:28     ` Junio C Hamano
2021-12-08 17:09       ` Glen Choo
2021-12-06 21:55   ` [PATCH v2 2/3] builtin/branch: clean up action-picking logic in cmd_branch() Glen Choo
2021-12-06 21:55   ` [PATCH v2 3/3] branch: add --recurse-submodules option for branch creation Glen Choo
2021-12-09 18:49   ` [PATCH v3 0/5] implement branch --recurse-submodules Glen Choo
2021-12-09 18:49     ` [PATCH v3 1/5] branch: move --set-upstream-to behavior to setup_tracking() Glen Choo
2021-12-09 21:19       ` Jonathan Tan
2021-12-09 22:16         ` Glen Choo
2021-12-09 18:49     ` [PATCH v3 2/5] branch: remove forward declaration of validate_branch_start() Glen Choo
2021-12-09 18:49     ` [PATCH v3 3/5] builtin/branch: clean up action-picking logic in cmd_branch() Glen Choo
2021-12-09 21:23       ` Jonathan Tan
2021-12-09 21:57         ` Glen Choo
2021-12-09 18:49     ` [PATCH v3 4/5] branch: add --recurse-submodules option for branch creation Glen Choo
2021-12-11 18:08       ` Philippe Blain
2021-12-14 20:08         ` Glen Choo
2021-12-09 18:49     ` [PATCH v3 5/5] branch.c: replace questionable exit() codes Glen Choo
2021-12-10  2:21       ` Ævar Arnfjörð Bjarmason
2021-12-10 17:43         ` Glen Choo
2021-12-13  9:02         ` Junio C Hamano
2021-12-13  9:19           ` Ævar Arnfjörð Bjarmason
2021-12-13 19:26             ` Junio C Hamano
2021-12-09 21:59     ` [PATCH v3 0/5] implement branch --recurse-submodules Jonathan Tan
2021-12-09 22:21       ` Glen Choo
2021-12-13 23:20         ` Jonathan Tan
2021-12-14 18:47           ` Glen Choo
2021-12-16  0:32     ` [PATCH v4 " Glen Choo
2021-12-16  0:32       ` [PATCH v4 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking() Glen Choo
2021-12-16  0:32       ` [PATCH v4 2/5] branch: make create_branch() always create a branch Glen Choo
2021-12-16  0:32       ` [PATCH v4 3/5] branch: add a dry_run parameter to create_branch() Glen Choo
2021-12-16  0:32       ` [PATCH v4 4/5] builtin/branch: clean up action-picking logic in cmd_branch() Glen Choo
2021-12-16  0:32       ` [PATCH v4 5/5] branch: add --recurse-submodules option for branch creation Glen Choo
2021-12-16 23:33       ` [PATCH v5 0/5] implement branch --recurse-submodules Glen Choo
2021-12-16 23:33         ` [PATCH v5 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking() Glen Choo
2021-12-16 23:33         ` [PATCH v5 2/5] branch: make create_branch() always create a branch Glen Choo
2021-12-16 23:33         ` [PATCH v5 3/5] branch: add a dry_run parameter to create_branch() Glen Choo
2021-12-16 23:33         ` [PATCH v5 4/5] builtin/branch: clean up action-picking logic in cmd_branch() Glen Choo
2021-12-16 23:33         ` [PATCH v5 5/5] branch: add --recurse-submodules option for branch creation Glen Choo
2021-12-17  0:34         ` [PATCH v5 0/5] implement branch --recurse-submodules Junio C Hamano
2021-12-17  0:45           ` Junio C Hamano
2021-12-20 19:09             ` Glen Choo
2021-12-20 19:50               ` Junio C Hamano
2021-12-20 20:25                 ` Glen Choo
2021-12-20 23:34         ` [PATCH v6 " Glen Choo
2021-12-20 23:34           ` [PATCH v6 1/5] branch: move --set-upstream-to behavior to dwim_and_setup_tracking() Glen Choo
2022-01-11  2:09             ` Jonathan Tan
2022-01-11 17:29               ` Glen Choo
2022-01-11 20:03                 ` Jonathan Tan
2021-12-20 23:34           ` [PATCH v6 2/5] branch: make create_branch() always create a branch Glen Choo
2022-01-11  2:19             ` Jonathan Tan
2022-01-11 17:51               ` Glen Choo
2021-12-20 23:34           ` [PATCH v6 3/5] branch: add a dry_run parameter to create_branch() Glen Choo
2021-12-20 23:34           ` [PATCH v6 4/5] builtin/branch: clean up action-picking logic in cmd_branch() Glen Choo
2021-12-20 23:34           ` [PATCH v6 5/5] branch: add --recurse-submodules option for branch creation Glen Choo
2021-12-26  4:09             ` Junio C Hamano
2022-01-11  3:28             ` Jonathan Tan
2022-01-11 18:11               ` Glen Choo
2022-01-11 20:15                 ` Jonathan Tan
2022-01-11 23:22                   ` Glen Choo
2021-12-20 23:36           ` [PATCH v6 0/5] implement branch --recurse-submodules Glen Choo
2021-12-21  1:07           ` Junio C Hamano
2021-12-21 17:51             ` Glen Choo
2022-01-24 20:44           ` [PATCH v7 0/6] " Glen Choo
2022-01-24 20:44             ` [PATCH v7 1/6] branch: move --set-upstream-to behavior to dwim_and_setup_tracking() Glen Choo
2022-01-24 20:44             ` [PATCH v7 2/6] branch: make create_branch() always create a branch Glen Choo
2022-01-24 20:44             ` [PATCH v7 3/6] branch: add a dry_run parameter to create_branch() Glen Choo
2022-01-24 20:44             ` [PATCH v7 4/6] builtin/branch: consolidate action-picking logic in cmd_branch() Glen Choo
2022-01-24 20:44             ` [PATCH v7 5/6] branch: add --recurse-submodules option for branch creation Glen Choo
2022-01-27 20:29               ` Jonathan Tan
2022-01-27 21:32                 ` Glen Choo
2022-01-27 22:42                   ` Glen Choo
2022-01-24 20:44             ` [PATCH v7 6/6] branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks Glen Choo
2022-01-27 22:15               ` Junio C Hamano
2022-01-28 19:44                 ` Glen Choo
2022-01-29  0:04             ` [PATCH v8 0/6] implement branch --recurse-submodules Glen Choo
2022-01-29  0:04               ` [PATCH v8 1/6] branch: move --set-upstream-to behavior to dwim_and_setup_tracking() Glen Choo
2022-01-29  0:04               ` [PATCH v8 2/6] branch: make create_branch() always create a branch Glen Choo
2022-02-01 22:20                 ` Junio C Hamano
2022-01-29  0:04               ` [PATCH v8 3/6] branch: add a dry_run parameter to create_branch() Glen Choo
2022-01-29  0:04               ` [PATCH v8 4/6] builtin/branch: consolidate action-picking logic in cmd_branch() Glen Choo
2022-01-29  0:04               ` [PATCH v8 5/6] branch: add --recurse-submodules option for branch creation Glen Choo
2022-02-04  1:10                 ` Glen Choo
2022-02-04 16:15                   ` Junio C Hamano
2022-02-04 18:10                     ` Glen Choo
2022-01-29  0:04               ` [PATCH v8 6/6] branch.c: use 'goto cleanup' in setup_tracking() to fix memory leaks Glen Choo
2022-02-01 17:43               ` [PATCH v8 0/6] implement branch --recurse-submodules Jonathan Tan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20211206215528.97050-1-chooglen@google.com \
    --to=chooglen@google.com \
    --cc=avarab@gmail.com \
    --cc=emilyshaffer@google.com \
    --cc=git@vger.kernel.org \
    --cc=jonathantanmy@google.com \
    --cc=levraiphilippeblain@gmail.com \
    --cc=steadmon@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.