[PATCH v2 0/3] implement branch --recurse-submodules

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Submodule branching RFC:
https://lore.kernel.org/git/kl6lv912uvjv.fsf@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/

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

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

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@xxxxxxxxxx
[2] https://lore.kernel.org/git/211123.86zgpvup6m.gmgdl@xxxxxxxxxxxxxxxxxxx
[3] https://lore.kernel.org/git/211123.86r1b7uoil.gmgdl@xxxxxxxxxxxxxxxxxxx
[4] https://lore.kernel.org/git/3ad3941c-de18-41bf-2e44-4238ae868d79@xxxxxxxxx
[5] https://lore.kernel.org/git/211123.86v90juovj.gmgdl@xxxxxxxxxxxxxxxxxxx

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@xxxxxxxxxx>
     
      ## 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@xxxxxxxxxx>
     
    @@ 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@xxxxxxxxxx>
     
    @@ 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





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux