summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor Blau <me@ttaylorr.com>2022-11-18 20:04:57 -0500
committerTaylor Blau <me@ttaylorr.com>2022-11-18 20:04:57 -0500
commit7a93aaab006ae57b7e20b21fa981bf3e4fab2553 (patch)
tree3b95e1d7d833a71abf6771c3efe351cf69a743db
parente06bd84551261e45fad72b2ddff646c34ca29240 (diff)
parent52469059f10483401d194f781c083b700a3880d4 (diff)
downloadgit-7a93aaab006ae57b7e20b21fa981bf3e4fab2553.tar.gz
Merge branch 'gc/submodule-clone-update-with-branches' into jch
"git clone --recurse-submodules" and "git submodule update" learns to honor the "propagete branches" option. * gc/submodule-clone-update-with-branches: clone, submodule update: create and check out branches submodule--helper: remove update_data.suboid submodule update: refactor update targets submodule: return target of submodule symref t5617: drop references to remote-tracking branches submodule--helper clone: create named branch repo-settings: add submodule_propagate_branches clone: teach --detach option
-rw-r--r--Documentation/git-clone.txt8
-rw-r--r--builtin/branch.c11
-rw-r--r--builtin/clone.c12
-rw-r--r--builtin/submodule--helper.c102
-rw-r--r--builtin/update-index.c4
-rw-r--r--cache.h1
-rw-r--r--combine-diff.c3
-rw-r--r--diff-lib.c2
-rw-r--r--dir.c2
-rw-r--r--object-file.c2
-rw-r--r--read-cache.c4
-rw-r--r--refs.c10
-rw-r--r--refs.h5
-rw-r--r--repo-settings.c13
-rw-r--r--repository.h1
-rw-r--r--submodule.c2
-rwxr-xr-xt/t5601-clone.sh22
-rwxr-xr-xt/t5617-clone-submodules.sh (renamed from t/t5617-clone-submodules-remote.sh)40
-rwxr-xr-xt/t7406-submodule-update.sh156
-rw-r--r--unpack-trees.c3
20 files changed, 357 insertions, 46 deletions
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index d6434d262d..6a4e5d31b4 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -16,7 +16,7 @@ SYNOPSIS
[--depth <depth>] [--[no-]single-branch] [--no-tags]
[--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
[--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
- [--filter=<filter> [--also-filter-submodules]] [--] <repository>
+ [--filter=<filter> [--also-filter-submodules] [--detach]] [--] <repository>
[<directory>]
DESCRIPTION
@@ -210,6 +210,12 @@ objects from the source repository into a pack in the cloned repository.
`--branch` can also take tags and detaches the HEAD at that commit
in the resulting repository.
+--detach::
+ If the cloned repository's HEAD points to a branch, point the newly
+ created HEAD to the branch's commit instead of the branch itself.
+ Additionally, in a non-bare repository, the corresponding local branch
+ will not be created.
+
-u <upload-pack>::
--upload-pack <upload-pack>::
When given, and the repository to clone from is accessed
diff --git a/builtin/branch.c b/builtin/branch.c
index 9470c980c1..0879cc655e 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -40,7 +40,6 @@ static const char * const builtin_branch_usage[] = {
static const char *head;
static struct object_id head_oid;
static int recurse_submodules = 0;
-static int submodule_propagate_branches = 0;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
@@ -106,10 +105,6 @@ static int git_branch_config(const char *var, const char *value, void *cb)
recurse_submodules = git_config_bool(var, value);
return 0;
}
- if (!strcasecmp(var, "submodule.propagateBranches")) {
- submodule_propagate_branches = git_config_bool(var, value);
- return 0;
- }
return git_color_default_config(var, value, cb);
}
@@ -713,7 +708,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
-
+ prepare_repo_settings(the_repository);
if (!delete && !rename && !copy && !edit_description && !new_upstream &&
!show_current && !unset_upstream && argc == 0)
list = 1;
@@ -729,7 +724,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_branch_usage, options);
if (recurse_submodules_explicit) {
- if (!submodule_propagate_branches)
+ if (!the_repository->settings.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"));
@@ -737,7 +732,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
recurse_submodules =
(recurse_submodules || recurse_submodules_explicit) &&
- submodule_propagate_branches;
+ the_repository->settings.submodule_propagate_branches;
if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
diff --git a/builtin/clone.c b/builtin/clone.c
index 0e4348686b..894be8eda4 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -78,6 +78,7 @@ static int option_filter_submodules = -1; /* unspecified */
static int config_filter_submodules = -1; /* unspecified */
static struct string_list server_options = STRING_LIST_INIT_NODUP;
static int option_remote_submodules;
+static int option_detach;
static const char *bundle_uri;
static int recurse_submodules_cb(const struct option *opt,
@@ -162,6 +163,8 @@ static struct option builtin_clone_options[] = {
N_("any cloned submodules will use their remote-tracking branch")),
OPT_BOOL(0, "sparse", &option_sparse_checkout,
N_("initialize sparse-checkout file to include only files at root")),
+ OPT_BOOL(0, "detach", &option_detach,
+ N_("detach HEAD and don't create a local branch")),
OPT_STRING(0, "bundle-uri", &bundle_uri,
N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
OPT_END()
@@ -613,10 +616,12 @@ static void update_remote_refs(const struct ref *refs,
}
static void update_head(const struct ref *our, const struct ref *remote,
- const char *unborn, const char *msg)
+ const char *unborn, int should_detach,
+ const char *msg)
{
const char *head;
- if (our && skip_prefix(our->name, "refs/heads/", &head)) {
+ if (our && !should_detach &&
+ skip_prefix(our->name, "refs/heads/", &head)) {
/* Local default branch link */
if (create_symref("HEAD", our->name, NULL) < 0)
die(_("unable to update HEAD"));
@@ -1362,7 +1367,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
branch_top.buf, reflog_msg.buf, transport,
!is_local);
- update_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf);
+ update_head(our_head_points_at, remote_head, unborn_head,
+ option_detach, reflog_msg.buf);
/*
* We want to show progress for recursive submodule clones iff
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c75e9e86b0..28e66a3d70 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1503,6 +1503,8 @@ struct module_clone_data {
const char *name;
const char *url;
const char *depth;
+ const char *branch;
+ const char *branch_oid;
struct list_objects_filter_options *filter_options;
unsigned int quiet: 1;
unsigned int progress: 1;
@@ -1692,6 +1694,8 @@ static int clone_submodule(const struct module_clone_data *clone_data,
strvec_push(&cp.args, clone_data->single_branch ?
"--single-branch" :
"--no-single-branch");
+ if (the_repository->settings.submodule_propagate_branches)
+ strvec_push(&cp.args, "--detach");
strvec_push(&cp.args, "--");
strvec_push(&cp.args, clone_data->url);
@@ -1704,6 +1708,21 @@ static int clone_submodule(const struct module_clone_data *clone_data,
if(run_command(&cp))
die(_("clone of '%s' into submodule path '%s' failed"),
clone_data->url, clone_data_path);
+
+ if (clone_data->branch) {
+ struct child_process branch_cp = CHILD_PROCESS_INIT;
+
+ branch_cp.git_cmd = 1;
+ prepare_other_repo_env(&branch_cp.env, sm_gitdir);
+
+ strvec_pushl(&branch_cp.args, "branch",
+ clone_data->branch, clone_data->branch_oid,
+ NULL);
+
+ if (run_command(&branch_cp))
+ die(_("could not create branch '%s' in submodule path '%s'"),
+ clone_data->branch, clone_data_path);
+ }
} else {
char *path;
@@ -1778,6 +1797,12 @@ static int module_clone(int argc, const char **argv, const char *prefix)
N_("disallow cloning into non-empty directory")),
OPT_BOOL(0, "single-branch", &clone_data.single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_STRING(0, "branch", &clone_data.branch,
+ N_("string"),
+ N_("name of branch to be created")),
+ OPT_STRING(0, "branch-oid", &clone_data.branch_oid,
+ N_("object-id"),
+ N_("commit id for new branch")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
OPT_END()
};
@@ -1785,12 +1810,14 @@ static int module_clone(int argc, const char **argv, const char *prefix)
N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
"[--reference <repository>] [--name <name>] [--depth <depth>] "
"[--single-branch] [--filter <filter-spec>] "
+ "[--branch <branch> --branch-oid <oid>]"
"--url <url> --path <path>"),
NULL
};
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
+ prepare_repo_settings(the_repository);
clone_data.dissociate = !!dissociate;
clone_data.quiet = !!quiet;
@@ -1802,6 +1829,12 @@ static int module_clone(int argc, const char **argv, const char *prefix)
usage_with_options(git_submodule_helper_usage,
module_clone_options);
+ if (!!clone_data.branch != !!clone_data.branch_oid)
+ BUG("--branch and --branch-oid must be set/unset together");
+ if ((clone_data.branch &&
+ !the_repository->settings.submodule_propagate_branches))
+ BUG("--branch is only expected with submodule.propagateBranches");
+
clone_submodule(&clone_data, &reference);
list_objects_filter_release(&filter_options);
string_list_clear(&reference, 1);
@@ -1884,8 +1917,8 @@ static void submodule_update_clone_release(struct submodule_update_clone *suc)
struct update_data {
const char *prefix;
char *displaypath;
+ const char *super_branch;
enum submodule_update_type update_default;
- struct object_id suboid;
struct string_list references;
struct submodule_update_strategy update_strategy;
struct list_objects_filter_options *filter_options;
@@ -2059,6 +2092,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
strvec_push(&child->args, suc->update_data->single_branch ?
"--single-branch" :
"--no-single-branch");
+ if (ud->super_branch) {
+ strvec_pushf(&child->args, "--branch=%s", ud->super_branch);
+ strvec_pushf(&child->args, "--branch-oid=%s",
+ oid_to_hex(&ce->oid));
+ }
cleanup:
free(displaypath);
@@ -2222,9 +2260,14 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,
static int run_update_command(const struct update_data *ud, int subforce)
{
struct child_process cp = CHILD_PROCESS_INIT;
- char *oid = oid_to_hex(&ud->oid);
+ const char *update_target;
int ret;
+ if (ud->update_strategy.type == SM_UPDATE_CHECKOUT && ud->super_branch)
+ update_target = ud->super_branch;
+ else
+ update_target = oid_to_hex(&ud->oid);
+
switch (ud->update_strategy.type) {
case SM_UPDATE_CHECKOUT:
cp.git_cmd = 1;
@@ -2252,7 +2295,7 @@ static int run_update_command(const struct update_data *ud, int subforce)
BUG("unexpected update strategy type: %d",
ud->update_strategy.type);
}
- strvec_push(&cp.args, oid);
+ strvec_push(&cp.args, update_target);
cp.dir = ud->sm_path;
prepare_submodule_repo_env(&cp.env);
@@ -2260,20 +2303,20 @@ static int run_update_command(const struct update_data *ud, int subforce)
switch (ud->update_strategy.type) {
case SM_UPDATE_CHECKOUT:
die_message(_("Unable to checkout '%s' in submodule path '%s'"),
- oid, ud->displaypath);
+ update_target, ud->displaypath);
/* No "ret" assignment, use "git checkout"'s */
break;
case SM_UPDATE_REBASE:
ret = die_message(_("Unable to rebase '%s' in submodule path '%s'"),
- oid, ud->displaypath);
+ update_target, ud->displaypath);
break;
case SM_UPDATE_MERGE:
ret = die_message(_("Unable to merge '%s' in submodule path '%s'"),
- oid, ud->displaypath);
+ update_target, ud->displaypath);
break;
case SM_UPDATE_COMMAND:
ret = die_message(_("Execution of '%s %s' failed in submodule path '%s'"),
- ud->update_strategy.command, oid, ud->displaypath);
+ ud->update_strategy.command, update_target, ud->displaypath);
break;
default:
BUG("unexpected update strategy type: %d",
@@ -2289,19 +2332,19 @@ static int run_update_command(const struct update_data *ud, int subforce)
switch (ud->update_strategy.type) {
case SM_UPDATE_CHECKOUT:
printf(_("Submodule path '%s': checked out '%s'\n"),
- ud->displaypath, oid);
+ ud->displaypath, update_target);
break;
case SM_UPDATE_REBASE:
printf(_("Submodule path '%s': rebased into '%s'\n"),
- ud->displaypath, oid);
+ ud->displaypath, update_target);
break;
case SM_UPDATE_MERGE:
printf(_("Submodule path '%s': merged in '%s'\n"),
- ud->displaypath, oid);
+ ud->displaypath, update_target);
break;
case SM_UPDATE_COMMAND:
printf(_("Submodule path '%s': '%s %s'\n"),
- ud->displaypath, ud->update_strategy.command, oid);
+ ud->displaypath, ud->update_strategy.command, update_target);
break;
default:
BUG("unexpected update strategy type: %d",
@@ -2313,7 +2356,7 @@ static int run_update_command(const struct update_data *ud, int subforce)
static int run_update_procedure(const struct update_data *ud)
{
- int subforce = is_null_oid(&ud->suboid) || ud->force;
+ int subforce = ud->just_cloned || ud->force;
if (!ud->nofetch) {
/*
@@ -2488,7 +2531,10 @@ static void update_data_to_args(const struct update_data *update_data,
static int update_submodule(struct update_data *update_data)
{
+ int submodule_up_to_date;
int ret;
+ struct object_id suboid;
+ const char *submodule_head = NULL;
ret = determine_submodule_update_strategy(the_repository,
update_data->just_cloned,
@@ -2498,9 +2544,9 @@ static int update_submodule(struct update_data *update_data)
if (ret)
return ret;
- if (update_data->just_cloned)
- oidcpy(&update_data->suboid, null_oid());
- else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+ if (!update_data->just_cloned &&
+ resolve_gitlink_ref(update_data->sm_path, "HEAD", &suboid,
+ &submodule_head))
return die_message(_("Unable to find current revision in submodule path '%s'"),
update_data->displaypath);
@@ -2527,14 +2573,26 @@ static int update_submodule(struct update_data *update_data)
update_data->sm_path);
}
- if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+ if (resolve_gitlink_ref(update_data->sm_path, remote_ref,
+ &update_data->oid, NULL))
return die_message(_("Unable to find %s revision in submodule path '%s'"),
remote_ref, update_data->sm_path);
free(remote_ref);
}
- if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) {
+ if (update_data->just_cloned)
+ submodule_up_to_date = 0;
+ else if (update_data->super_branch)
+ /* Check that the submodule's HEAD points to super_branch. */
+ submodule_up_to_date =
+ skip_prefix(submodule_head, "refs/heads/",
+ &submodule_head) &&
+ !strcmp(update_data->super_branch, submodule_head);
+ else
+ submodule_up_to_date = oideq(&update_data->oid, &suboid);
+
+ if (!submodule_up_to_date || update_data->force) {
ret = run_update_procedure(update_data);
if (ret)
return ret;
@@ -2546,7 +2604,6 @@ static int update_submodule(struct update_data *update_data)
next.prefix = NULL;
oidcpy(&next.oid, null_oid());
- oidcpy(&next.suboid, null_oid());
cp.dir = update_data->sm_path;
cp.git_cmd = 1;
@@ -2579,6 +2636,12 @@ static int update_submodules(struct update_data *update_data)
.data = &suc,
};
+ if (the_repository->settings.submodule_propagate_branches) {
+ struct branch *current_branch = branch_get(NULL);
+ if (current_branch)
+ update_data->super_branch = current_branch->name;
+ }
+
suc.update_data = update_data;
run_processes_parallel(&opts);
@@ -2688,6 +2751,7 @@ static int module_update(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, module_update_options,
git_submodule_helper_usage, 0);
+ prepare_repo_settings(the_repository);
if (opt.require_init)
opt.init = 1;
@@ -3227,7 +3291,7 @@ static void die_on_repo_without_commits(const char *path)
strbuf_addstr(&sb, path);
if (is_nonbare_repository_dir(&sb)) {
struct object_id oid;
- if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
+ if (resolve_gitlink_ref(path, "HEAD", &oid, NULL) < 0)
die(_("'%s' does not have a commit checked out"), path);
}
strbuf_release(&sb);
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 7b0c924d7d..e6305656c9 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -339,7 +339,7 @@ static int process_directory(const char *path, int len, struct stat *st)
if (S_ISGITLINK(ce->ce_mode)) {
/* Do nothing to the index if there is no HEAD! */
- if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
+ if (resolve_gitlink_ref(path, "HEAD", &oid, NULL) < 0)
return 0;
return add_one_path(ce, path, len, st);
@@ -365,7 +365,7 @@ static int process_directory(const char *path, int len, struct stat *st)
}
/* No match - should we add it as a gitlink? */
- if (!resolve_gitlink_ref(path, "HEAD", &oid))
+ if (!resolve_gitlink_ref(path, "HEAD", &oid, NULL))
return add_one_path(NULL, path, len, st);
/* Error out. */
diff --git a/cache.h b/cache.h
index 13e9c251ac..627d584a9e 100644
--- a/cache.h
+++ b/cache.h
@@ -505,6 +505,7 @@ static inline enum object_type object_type(unsigned int mode)
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
#define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX"
+#define GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT "GIT_INTERNAL_SUBMODULE_PROPAGATE_BRANCHES"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
diff --git a/combine-diff.c b/combine-diff.c
index b0ece95480..88efcaeefa 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -1060,7 +1060,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
elem->mode = canon_mode(st.st_mode);
} else if (S_ISDIR(st.st_mode)) {
struct object_id oid;
- if (resolve_gitlink_ref(elem->path, "HEAD", &oid) < 0)
+ if (resolve_gitlink_ref(elem->path, "HEAD", &oid,
+ NULL) < 0)
result = grab_blob(opt->repo, &elem->oid,
elem->mode, &result_size,
NULL, NULL);
diff --git a/diff-lib.c b/diff-lib.c
index 30a3d9a2b5..b48f155736 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -54,7 +54,7 @@ static int check_removed(const struct index_state *istate, const struct cache_en
* a directory --- the blob was removed!
*/
if (!S_ISGITLINK(ce->ce_mode) &&
- resolve_gitlink_ref(ce->name, "HEAD", &sub))
+ resolve_gitlink_ref(ce->name, "HEAD", &sub, NULL))
return 1;
}
return 0;
diff --git a/dir.c b/dir.c
index fbdb24fc81..703e9d72f2 100644
--- a/dir.c
+++ b/dir.c
@@ -3251,7 +3251,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
struct object_id submodule_head;
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
- !resolve_gitlink_ref(path->buf, "HEAD", &submodule_head)) {
+ !resolve_gitlink_ref(path->buf, "HEAD", &submodule_head, NULL)) {
/* Do not descend and nuke a nested git work tree. */
if (kept_up)
*kept_up = 1;
diff --git a/object-file.c b/object-file.c
index 957790098f..c118e62a55 100644
--- a/object-file.c
+++ b/object-file.c
@@ -2527,7 +2527,7 @@ int index_path(struct index_state *istate, struct object_id *oid,
strbuf_release(&sb);
break;
case S_IFDIR:
- return resolve_gitlink_ref(path, "HEAD", oid);
+ return resolve_gitlink_ref(path, "HEAD", oid, NULL);
default:
return error(_("%s: unsupported file type"), path);
}
diff --git a/read-cache.c b/read-cache.c
index ac9f8122e5..57293825a8 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -285,7 +285,7 @@ static int ce_compare_gitlink(const struct cache_entry *ce)
*
* If so, we consider it always to match.
*/
- if (resolve_gitlink_ref(ce->name, "HEAD", &oid) < 0)
+ if (resolve_gitlink_ref(ce->name, "HEAD", &oid, NULL) < 0)
return 0;
return !oideq(&oid, &ce->oid);
}
@@ -781,7 +781,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
namelen = strlen(path);
if (S_ISDIR(st_mode)) {
- if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
+ if (resolve_gitlink_ref(path, "HEAD", &oid, NULL) < 0)
return error(_("'%s' does not have a commit checked out"), path);
while (namelen && path[namelen-1] == '/')
namelen--;
diff --git a/refs.c b/refs.c
index 4cc3b16db9..23ddc95909 100644
--- a/refs.c
+++ b/refs.c
@@ -1898,19 +1898,21 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
}
int resolve_gitlink_ref(const char *submodule, const char *refname,
- struct object_id *oid)
+ struct object_id *oid, const char **target_out)
{
struct ref_store *refs;
int flags;
+ const char *target;
refs = get_submodule_ref_store(submodule);
if (!refs)
return -1;
-
- if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||
- is_null_oid(oid))
+ target = refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags);
+ if (!target || is_null_oid(oid))
return -1;
+ if (target_out)
+ *target_out = target;
return 0;
}
diff --git a/refs.h b/refs.h
index 3266fd8f57..ee8675d4aa 100644
--- a/refs.h
+++ b/refs.h
@@ -137,9 +137,12 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled);
* submodule (which must be non-NULL). If the resolution is
* successful, return 0 and set oid to the name of the object;
* otherwise, return a non-zero value.
+ *
+ * FIXME: Return "target" just like refs_resolve_ref_unsafe(). This will be
+ * safe to do once we merge resolve_gitlink_ref() into master.
*/
int resolve_gitlink_ref(const char *submodule, const char *refname,
- struct object_id *oid);
+ struct object_id *oid, const char **target);
/*
* Return true iff abbrev_name is a possible abbreviation for
diff --git a/repo-settings.c b/repo-settings.c
index 3021921c53..3ec0a53ea6 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -73,6 +73,19 @@ void prepare_repo_settings(struct repository *r)
r->settings.core_multi_pack_index = 1;
/*
+ * If the environment variable is set, assume that it was set by an
+ * invocation of git running in a superproject with
+ * submodule.propagateBranches set and that is recursing into this repo
+ * as a submodule. Therefore, we should ignore whatever is set in this
+ * repo's config.
+ */
+ r->settings.submodule_propagate_branches =
+ git_env_bool(GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT, -1);
+ if (r->settings.submodule_propagate_branches == -1)
+ repo_cfg_bool(r, "submodule.propagateBranches",
+ &r->settings.submodule_propagate_branches, 0);
+
+ /*
* Non-boolean config
*/
if (!repo_config_get_int(r, "index.version", &value))
diff --git a/repository.h b/repository.h
index 7ce4b8a962..9f5ce21f70 100644
--- a/repository.h
+++ b/repository.h
@@ -38,6 +38,7 @@ struct repo_settings {
int fetch_write_commit_graph;
int command_requires_full_index;
int sparse_index;
+ int submodule_propagate_branches;
struct fsmonitor_settings *fsmonitor; /* lazily loaded */
diff --git a/submodule.c b/submodule.c
index 142f5c7a75..f350932ef1 100644
--- a/submodule.c
+++ b/submodule.c
@@ -503,6 +503,8 @@ static void print_submodule_diff_summary(struct repository *r, struct rev_info *
void prepare_submodule_repo_env(struct strvec *out)
{
+ if (the_repository->settings.submodule_propagate_branches)
+ strvec_pushf(out, "%s=1", GIT_SUBMODULE_PROPAGATE_BRANCHES_ENVIRONMENT);
prepare_other_repo_env(out, DEFAULT_GIT_DIR_ENVIRONMENT);
}
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index b2524a24c2..23c8999bc5 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -310,6 +310,28 @@ test_expect_success 'clone checking out a tag' '
test_cmp fetch.expected fetch.actual
'
+test_expect_success '--detach detaches and does not create branch' '
+ test_when_finished "rm -fr dst" &&
+ git clone --detach src dst &&
+ (
+ cd dst &&
+ test_must_fail git rev-parse main &&
+ test_must_fail git symbolic-ref HEAD &&
+ test_cmp_rev HEAD refs/remotes/origin/HEAD
+ )
+'
+
+test_expect_success '--detach with --bare detaches but creates branch' '
+ test_when_finished "rm -fr dst" &&
+ git clone --bare --detach src dst &&
+ (
+ cd dst &&
+ git rev-parse main &&
+ test_must_fail git symbolic-ref HEAD &&
+ test_cmp_rev HEAD refs/heads/main
+ )
+'
+
test_expect_success 'set up ssh wrapper' '
cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \
"$TRASH_DIRECTORY/ssh$X" &&
diff --git a/t/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules.sh
index 5a4d7936a7..5767c4d318 100755
--- a/t/t5617-clone-submodules-remote.sh
+++ b/t/t5617-clone-submodules.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='Test cloning repos with submodules using remote-tracking branches'
+test_description='Test cloning repos with submodules'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
@@ -14,10 +14,17 @@ test_expect_success 'setup' '
git config --global protocol.file.allow always &&
git checkout -b main &&
test_commit commit1 &&
+ mkdir subsub &&
+ (
+ cd subsub &&
+ git init &&
+ test_commit subsubcommit1
+ ) &&
mkdir sub &&
(
cd sub &&
git init &&
+ git submodule add "file://$pwd/subsub" subsub &&
test_commit subcommit1 &&
git tag sub_when_added_to_super &&
git branch other
@@ -108,4 +115,35 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule
test_cmp_config -C super_clone3/sub false --default false remote.origin.promisor
'
+test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '
+ test_when_finished "git checkout main" &&
+
+ git checkout -b checked-out &&
+ git -C sub checkout -b not-in-clone &&
+ git -C subsub checkout -b not-in-clone &&
+ git clone --recurse-submodules \
+ --branch checked-out \
+ -c submodule.propagateBranches=true \
+ "file://$pwd/." super_clone4 &&
+
+ # Assert that each repo is pointing to "checked-out"
+ for REPO in "super_clone4" "super_clone4/sub" "super_clone4/sub/subsub"
+ do
+ HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&
+ test $HEAD_BRANCH = "refs/heads/checked-out" || return 1
+ done &&
+
+ # Assert that the submodule branches are pointing to the right revs
+ EXPECT_SUB_OID="$(git -C super_clone4 rev-parse :sub)" &&
+ ACTUAL_SUB_OID="$(git -C super_clone4/sub rev-parse refs/heads/checked-out)" &&
+ test $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&
+ EXPECT_SUBSUB_OID="$(git -C super_clone4/sub rev-parse :subsub)" &&
+ ACTUAL_SUBSUB_OID="$(git -C super_clone4/sub/subsub rev-parse refs/heads/checked-out)" &&
+ test $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&
+
+ # Assert that the submodules do not have branches from their upstream
+ test_must_fail git -C super_clone4/sub rev-parse not-in-clone &&
+ test_must_fail git -C super_clone4/sub/subsub rev-parse not-in-clone
+'
+
test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index f094e3d7f3..b749d35f78 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -1179,4 +1179,160 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=
test_cmp expect.err actual.err
'
+test_expect_success 'setup superproject with submodule.propagateBranches' '
+ git init sub1 &&
+ test_commit -C sub1 "sub1" &&
+ git init branch-super &&
+ git -C branch-super submodule add ../sub1 sub1 &&
+ git -C branch-super commit -m "super" &&
+
+ # Clone into a clean repo that we can cp around
+ git clone --recurse-submodules \
+ -c submodule.propagateBranches=true \
+ branch-super branch-super-clean &&
+ git -C branch-super-clean config submodule.propagateBranches true &&
+
+ # sub2 will not be in the clone. We will fetch the containing
+ # superproject commit and clone sub2 with "git submodule update".
+ git init sub2 &&
+ test_commit -C sub2 "sub2" &&
+ git -C branch-super submodule add ../sub2 sub2 &&
+ git -C branch-super commit -m "add sub2"
+'
+
+test_clean_submodule ()
+{
+ local negate super_dir sub_dir expect_oid actual_oid &&
+ if test "$1" = "!"
+ then
+ negate=t
+ shift
+ fi
+ super_dir="$1" &&
+ sub_dir="$2" &&
+ expect_oid="$(git -C "$super_dir" rev-parse ":$sub_dir")" &&
+ actual_oid="$(git -C "$super_dir/$sub_dir" rev-parse HEAD)" &&
+ if test -n "$negate"
+ then
+ ! test "$expect_oid" = "$actual_oid"
+ else
+ test "$expect_oid" = "$actual_oid"
+ fi
+}
+
+# Test the behavior of a newly cloned submodule
+test_expect_success 'branches - newly-cloned submodule, detached HEAD' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned fetch origin main &&
+ git -C branch-super-cloned checkout FETCH_HEAD &&
+ git -C branch-super-cloned/sub1 checkout --detach &&
+ git -C branch-super-cloned submodule update &&
+
+ # sub1 and sub2 should be in detached HEAD
+ git -C branch-super-cloned/sub1 rev-parse --verify HEAD &&
+ test_must_fail git -C branch-super-cloned/sub1 symbolic-ref HEAD &&
+ test_clean_submodule branch-super-cloned sub1 &&
+ git -C branch-super-cloned/sub2 rev-parse --verify HEAD &&
+ test_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD &&
+ test_clean_submodule branch-super-cloned sub2
+'
+
+test_expect_success 'branches - newly-cloned submodule, branch checked out' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned fetch origin main &&
+ git -C branch-super-cloned checkout FETCH_HEAD &&
+ git -C branch-super-cloned branch new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 branch new-branch &&
+ git -C branch-super-cloned submodule update &&
+
+ # Ignore sub1, we will test it later.
+ # sub2 should check out the branch
+ HEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH2 = "refs/heads/new-branch" &&
+ test_clean_submodule branch-super-cloned sub2
+'
+
+# Test the behavior of an already-cloned submodule.
+# NEEDSWORK When updating with branches, we always use the branch instead of the
+# gitlink's OID. This results in some imperfect behavior:
+#
+# - If the gitlink's OID disagrees with the branch OID, updating with branches
+# may result in a dirty worktree
+# - If the branch does not exist, the update fails.
+#
+# We will reevaluate when "git checkout --recurse-submodules" supports branches
+# For now, just test for this imperfect behavior.
+test_expect_success 'branches - correct branch checked out, OIDs agree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout new-branch &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule branch-super-cloned sub1
+'
+
+test_expect_success 'branches - correct branch checked out, OIDs disagree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout new-branch &&
+ test_commit -C branch-super-cloned/sub1 new-commit &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule ! branch-super-cloned sub1
+'
+
+test_expect_success 'branches - other branch checked out, correct branch exists, OIDs agree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout main &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule branch-super-cloned sub1
+'
+
+test_expect_success 'branches - other branch checked out, correct branch exists, OIDs disagree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout new-branch &&
+ test_commit -C branch-super-cloned/sub1 new-commit &&
+ git -C branch-super-cloned/sub1 checkout main &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule ! branch-super-cloned sub1
+'
+
+test_expect_success 'branches - other branch checked out, correct branch does not exist' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ test_must_fail git -C branch-super-cloned submodule update
+'
+
test_done
diff --git a/unpack-trees.c b/unpack-trees.c
index 8a762aa077..56b71d37ce 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -2289,7 +2289,8 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
if (S_ISGITLINK(ce->ce_mode)) {
struct object_id oid;
- int sub_head = resolve_gitlink_ref(ce->name, "HEAD", &oid);
+ int sub_head =
+ resolve_gitlink_ref(ce->name, "HEAD", &oid, NULL);
/*
* If we are not going to update the submodule, then
* we don't care.