Merge branch 'sb/checkout-recurse-submodules'

"git checkout" is taught the "--recurse-submodules" option.

* sb/checkout-recurse-submodules:
  builtin/read-tree: add --recurse-submodules switch
  builtin/checkout: add --recurse-submodules switch
  entry.c: create submodules when interesting
  unpack-trees: check if we can perform the operation for submodules
  unpack-trees: pass old oid to verify_clean_submodule
  update submodules: add submodule_move_head
  submodule.c: get_super_prefix_or_empty
  update submodules: move up prepare_submodule_repo_env
  submodules: introduce check to see whether to touch a submodule
  update submodules: add a config option to determine if submodules are updated
  update submodules: add submodule config parsing
  make is_submodule_populated gently
  lib-submodule-update.sh: define tests for recursing into submodules
  lib-submodule-update.sh: replace sha1 by hash
  lib-submodule-update: teach test_submodule_content the -C <dir> flag
  lib-submodule-update.sh: do not use ./. as submodule remote
  lib-submodule-update.sh: reorder create_lib_submodule_repo
  submodule--helper.c: remove duplicate code
  connect_work_tree_and_git_dir: safely create leading directories
This commit is contained in:
Junio C Hamano
2017-03-28 14:05:58 -07:00
17 changed files with 1084 additions and 113 deletions

View File

@ -17,6 +17,7 @@
#include "worktree.h"
static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
static int config_update_recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static int parallel_jobs = 1;
static struct string_list changed_submodule_paths = STRING_LIST_INIT_NODUP;
static int initialized_fetch_ref_tips;
@ -234,15 +235,12 @@ int is_submodule_initialized(const char *path)
return ret;
}
/*
* Determine if a submodule has been populated at a given 'path'
*/
int is_submodule_populated(const char *path)
int is_submodule_populated_gently(const char *path, int *return_error_code)
{
int ret = 0;
char *gitdir = xstrfmt("%s/.git", path);
if (resolve_gitdir(gitdir))
if (resolve_gitdir_gently(gitdir, return_error_code))
ret = 1;
free(gitdir);
@ -358,6 +356,23 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f,
strbuf_release(&sb);
}
static void prepare_submodule_repo_env_no_git_dir(struct argv_array *out)
{
const char * const *var;
for (var = local_repo_env; *var; var++) {
if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
argv_array_push(out, *var);
}
}
void prepare_submodule_repo_env(struct argv_array *out)
{
prepare_submodule_repo_env_no_git_dir(out);
argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
DEFAULT_GIT_DIR_ENVIRONMENT);
}
/* Helper function to display the submodule header line prior to the full
* summary output. If it can locate the submodule objects directory it will
* attempt to lookup both the left and right commits and put them into the
@ -545,6 +560,27 @@ void set_config_fetch_recurse_submodules(int value)
config_fetch_recurse_submodules = value;
}
void set_config_update_recurse_submodules(int value)
{
config_update_recurse_submodules = value;
}
int should_update_submodules(void)
{
return config_update_recurse_submodules == RECURSE_SUBMODULES_ON;
}
const struct submodule *submodule_from_ce(const struct cache_entry *ce)
{
if (!S_ISGITLINK(ce->ce_mode))
return NULL;
if (!should_update_submodules())
return NULL;
return submodule_from_path(null_sha1, ce->name);
}
static int has_remote(const char *refname, const struct object_id *oid,
int flags, void *cb_data)
{
@ -1203,6 +1239,151 @@ out:
return ret;
}
static const char *get_super_prefix_or_empty(void)
{
const char *s = get_super_prefix();
if (!s)
s = "";
return s;
}
static int submodule_has_dirty_index(const struct submodule *sub)
{
struct child_process cp = CHILD_PROCESS_INIT;
prepare_submodule_repo_env_no_git_dir(&cp.env_array);
cp.git_cmd = 1;
argv_array_pushl(&cp.args, "diff-index", "--quiet",
"--cached", "HEAD", NULL);
cp.no_stdin = 1;
cp.no_stdout = 1;
cp.dir = sub->path;
if (start_command(&cp))
die("could not recurse into submodule '%s'", sub->path);
return finish_command(&cp);
}
static void submodule_reset_index(const char *path)
{
struct child_process cp = CHILD_PROCESS_INIT;
prepare_submodule_repo_env_no_git_dir(&cp.env_array);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.dir = path;
argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
get_super_prefix_or_empty(), path);
argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
argv_array_push(&cp.args, EMPTY_TREE_SHA1_HEX);
if (run_command(&cp))
die("could not reset submodule index");
}
/**
* Moves a submodule at a given path from a given head to another new head.
* For edge cases (a submodule coming into existence or removing a submodule)
* pass NULL for old or new respectively.
*/
int submodule_move_head(const char *path,
const char *old,
const char *new,
unsigned flags)
{
int ret = 0;
struct child_process cp = CHILD_PROCESS_INIT;
const struct submodule *sub;
sub = submodule_from_path(null_sha1, path);
if (!sub)
die("BUG: could not get submodule information for '%s'", path);
if (old && !(flags & SUBMODULE_MOVE_HEAD_FORCE)) {
/* Check if the submodule has a dirty index. */
if (submodule_has_dirty_index(sub))
return error(_("submodule '%s' has dirty index"), path);
}
if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
if (old) {
if (!submodule_uses_gitfile(path))
absorb_git_dir_into_superproject("", path,
ABSORB_GITDIR_RECURSE_SUBMODULES);
} else {
struct strbuf sb = STRBUF_INIT;
strbuf_addf(&sb, "%s/modules/%s",
get_git_common_dir(), sub->name);
connect_work_tree_and_git_dir(path, sb.buf);
strbuf_release(&sb);
/* make sure the index is clean as well */
submodule_reset_index(path);
}
}
prepare_submodule_repo_env_no_git_dir(&cp.env_array);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.dir = path;
argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
get_super_prefix_or_empty(), path);
argv_array_pushl(&cp.args, "read-tree", NULL);
if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN)
argv_array_push(&cp.args, "-n");
else
argv_array_push(&cp.args, "-u");
if (flags & SUBMODULE_MOVE_HEAD_FORCE)
argv_array_push(&cp.args, "--reset");
else
argv_array_push(&cp.args, "-m");
argv_array_push(&cp.args, old ? old : EMPTY_TREE_SHA1_HEX);
argv_array_push(&cp.args, new ? new : EMPTY_TREE_SHA1_HEX);
if (run_command(&cp)) {
ret = -1;
goto out;
}
if (!(flags & SUBMODULE_MOVE_HEAD_DRY_RUN)) {
if (new) {
struct child_process cp1 = CHILD_PROCESS_INIT;
/* also set the HEAD accordingly */
cp1.git_cmd = 1;
cp1.no_stdin = 1;
cp1.dir = path;
argv_array_pushl(&cp1.args, "update-ref", "HEAD",
new ? new : EMPTY_TREE_SHA1_HEX, NULL);
if (run_command(&cp1)) {
ret = -1;
goto out;
}
} else {
struct strbuf sb = STRBUF_INIT;
strbuf_addf(&sb, "%s/.git", path);
unlink_or_warn(sb.buf);
strbuf_release(&sb);
if (is_empty_dir(path))
rmdir_or_warn(path);
}
}
out:
return ret;
}
static int find_first_merges(struct object_array *result, const char *path,
struct commit *a, struct commit *b)
{
@ -1371,18 +1552,6 @@ int parallel_submodules(void)
return parallel_jobs;
}
void prepare_submodule_repo_env(struct argv_array *out)
{
const char * const *var;
for (var = local_repo_env; *var; var++) {
if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
argv_array_push(out, *var);
}
argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
DEFAULT_GIT_DIR_ENVIRONMENT);
}
/*
* Embeds a single submodules git directory into the superprojects git dir,
* non recursively.
@ -1414,11 +1583,8 @@ static void relocate_single_git_dir_into_superproject(const char *prefix,
die(_("could not create directory '%s'"), new_git_dir);
real_new_git_dir = real_pathdup(new_git_dir, 1);
if (!prefix)
prefix = get_super_prefix();
fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
prefix ? prefix : "", path,
get_super_prefix_or_empty(), path,
real_old_git_dir, real_new_git_dir);
relocate_gitdir(path, real_old_git_dir, real_new_git_dir);
@ -1445,8 +1611,6 @@ void absorb_git_dir_into_superproject(const char *prefix,
/* Not populated? */
if (!sub_git_dir) {
char *real_new_git_dir;
const char *new_git_dir;
const struct submodule *sub;
if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
@ -1469,13 +1633,8 @@ void absorb_git_dir_into_superproject(const char *prefix,
sub = submodule_from_path(null_sha1, path);
if (!sub)
die(_("could not lookup name for submodule '%s'"), path);
new_git_dir = git_path("modules/%s", sub->name);
if (safe_create_leading_directories_const(new_git_dir) < 0)
die(_("could not create directory '%s'"), new_git_dir);
real_new_git_dir = real_pathdup(new_git_dir, 1);
connect_work_tree_and_git_dir(path, real_new_git_dir);
free(real_new_git_dir);
connect_work_tree_and_git_dir(path,
git_path("modules/%s", sub->name));
} else {
/* Is it already absorbed into the superprojects git dir? */
char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
@ -1496,8 +1655,7 @@ void absorb_git_dir_into_superproject(const char *prefix,
if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
die("BUG: we don't know how to pass the flags down?");
if (get_super_prefix())
strbuf_addstr(&sb, get_super_prefix());
strbuf_addstr(&sb, get_super_prefix_or_empty());
strbuf_addstr(&sb, path);
strbuf_addch(&sb, '/');