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 directoriesmaint
commit
e394fa01d6
|
@ -256,6 +256,13 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
|
|||
out anyway. In other words, the ref can be held by more than one
|
||||
worktree.
|
||||
|
||||
--[no-]recurse-submodules::
|
||||
Using --recurse-submodules will update the content of all initialized
|
||||
submodules according to the commit recorded in the superproject. If
|
||||
local modifications in a submodule would be overwritten the checkout
|
||||
will fail unless `-f` is used. If nothing (or --no-recurse-submodules)
|
||||
is used, the work trees of submodules will not be updated.
|
||||
|
||||
<branch>::
|
||||
Branch to checkout; if it refers to a branch (i.e., a name that,
|
||||
when prepended with "refs/heads/", is a valid ref), then that
|
||||
|
|
|
@ -115,6 +115,12 @@ OPTIONS
|
|||
directories the index file and index output file are
|
||||
located in.
|
||||
|
||||
--[no-]recurse-submodules::
|
||||
Using --recurse-submodules will update the content of all initialized
|
||||
submodules according to the commit recorded in the superproject by
|
||||
calling read-tree recursively, also setting the submodules HEAD to be
|
||||
detached at that commit.
|
||||
|
||||
--no-sparse-checkout::
|
||||
Disable sparse checkout support even if `core.sparseCheckout`
|
||||
is true.
|
||||
|
|
|
@ -21,12 +21,31 @@
|
|||
#include "submodule-config.h"
|
||||
#include "submodule.h"
|
||||
|
||||
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
|
||||
|
||||
static const char * const checkout_usage[] = {
|
||||
N_("git checkout [<options>] <branch>"),
|
||||
N_("git checkout [<options>] [<branch>] -- <file>..."),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int option_parse_recurse_submodules(const struct option *opt,
|
||||
const char *arg, int unset)
|
||||
{
|
||||
if (unset) {
|
||||
recurse_submodules = RECURSE_SUBMODULES_OFF;
|
||||
return 0;
|
||||
}
|
||||
if (arg)
|
||||
recurse_submodules =
|
||||
parse_update_recurse_submodules_arg(opt->long_name,
|
||||
arg);
|
||||
else
|
||||
recurse_submodules = RECURSE_SUBMODULES_ON;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct checkout_opts {
|
||||
int patch_mode;
|
||||
int quiet;
|
||||
|
@ -1163,6 +1182,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
|||
N_("second guess 'git checkout <no-such-branch>'")),
|
||||
OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
|
||||
N_("do not check if another worktree is holding the given ref")),
|
||||
{ OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
|
||||
"checkout", "control recursive updating of submodules",
|
||||
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
|
||||
OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
|
||||
OPT_END(),
|
||||
};
|
||||
|
@ -1193,6 +1215,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
|
|||
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
|
||||
}
|
||||
|
||||
if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
|
||||
git_config(submodule_config, NULL);
|
||||
if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
|
||||
set_config_update_recurse_submodules(recurse_submodules);
|
||||
}
|
||||
|
||||
if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
|
||||
die(_("-b, -B and --orphan are mutually exclusive"));
|
||||
|
||||
|
|
|
@ -618,7 +618,7 @@ static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1,
|
|||
{
|
||||
if (!is_submodule_initialized(path))
|
||||
return 0;
|
||||
if (!is_submodule_populated(path)) {
|
||||
if (!is_submodule_populated_gently(path, NULL)) {
|
||||
/*
|
||||
* If searching history, check for the presense of the
|
||||
* submodule's gitdir before skipping the submodule.
|
||||
|
|
|
@ -15,10 +15,13 @@
|
|||
#include "builtin.h"
|
||||
#include "parse-options.h"
|
||||
#include "resolve-undo.h"
|
||||
#include "submodule.h"
|
||||
#include "submodule-config.h"
|
||||
|
||||
static int nr_trees;
|
||||
static int read_empty;
|
||||
static struct tree *trees[MAX_UNPACK_TREES];
|
||||
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
|
||||
|
||||
static int list_tree(unsigned char *sha1)
|
||||
{
|
||||
|
@ -96,6 +99,23 @@ static int debug_merge(const struct cache_entry * const *stages,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int option_parse_recurse_submodules(const struct option *opt,
|
||||
const char *arg, int unset)
|
||||
{
|
||||
if (unset) {
|
||||
recurse_submodules = RECURSE_SUBMODULES_OFF;
|
||||
return 0;
|
||||
}
|
||||
if (arg)
|
||||
recurse_submodules =
|
||||
parse_update_recurse_submodules_arg(opt->long_name,
|
||||
arg);
|
||||
else
|
||||
recurse_submodules = RECURSE_SUBMODULES_ON;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct lock_file lock_file;
|
||||
|
||||
int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
||||
|
@ -137,6 +157,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
|||
N_("skip applying sparse checkout filter")),
|
||||
OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
|
||||
N_("debug unpack-trees")),
|
||||
{ OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
|
||||
"checkout", "control recursive updating of submodules",
|
||||
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
|
@ -152,6 +175,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
|||
|
||||
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
|
||||
|
||||
if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) {
|
||||
gitmodules_config();
|
||||
git_config(submodule_config, NULL);
|
||||
set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON);
|
||||
}
|
||||
|
||||
prefix_set = opts.prefix ? 1 : 0;
|
||||
if (1 < opts.merge + opts.reset + prefix_set)
|
||||
die("Which one? -m, --reset, or --prefix?");
|
||||
|
|
|
@ -577,9 +577,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
const char *name = NULL, *url = NULL, *depth = NULL;
|
||||
int quiet = 0;
|
||||
int progress = 0;
|
||||
FILE *submodule_dot_git;
|
||||
char *p, *path = NULL, *sm_gitdir;
|
||||
struct strbuf rel_path = STRBUF_INIT;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
struct string_list reference = STRING_LIST_INIT_NODUP;
|
||||
char *sm_alternate = NULL, *error_strategy = NULL;
|
||||
|
@ -651,27 +649,12 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
strbuf_reset(&sb);
|
||||
}
|
||||
|
||||
/* Write a .git file in the submodule to redirect to the superproject. */
|
||||
strbuf_addf(&sb, "%s/.git", path);
|
||||
if (safe_create_leading_directories_const(sb.buf) < 0)
|
||||
die(_("could not create leading directories of '%s'"), sb.buf);
|
||||
submodule_dot_git = fopen(sb.buf, "w");
|
||||
if (!submodule_dot_git)
|
||||
die_errno(_("cannot open file '%s'"), sb.buf);
|
||||
/* Connect module worktree and git dir */
|
||||
connect_work_tree_and_git_dir(path, sm_gitdir);
|
||||
|
||||
fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
|
||||
relative_path(sm_gitdir, path, &rel_path));
|
||||
if (fclose(submodule_dot_git))
|
||||
die(_("could not close file %s"), sb.buf);
|
||||
strbuf_reset(&sb);
|
||||
strbuf_reset(&rel_path);
|
||||
|
||||
/* Redirect the worktree of the submodule in the superproject's config */
|
||||
p = git_pathdup_submodule(path, "config");
|
||||
if (!p)
|
||||
die(_("could not get submodule directory for '%s'"), path);
|
||||
git_config_set_in_file(p, "core.worktree",
|
||||
relative_path(path, sm_gitdir, &rel_path));
|
||||
|
||||
/* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
|
||||
git_config_get_string("submodule.alternateLocation", &sm_alternate);
|
||||
|
@ -687,7 +670,6 @@ static int module_clone(int argc, const char **argv, const char *prefix)
|
|||
free(error_strategy);
|
||||
|
||||
strbuf_release(&sb);
|
||||
strbuf_release(&rel_path);
|
||||
free(sm_gitdir);
|
||||
free(path);
|
||||
free(p);
|
||||
|
|
32
dir.c
32
dir.c
|
@ -2765,23 +2765,33 @@ void untracked_cache_add_to_index(struct index_state *istate,
|
|||
/* Update gitfile and core.worktree setting to connect work tree and git dir */
|
||||
void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
|
||||
{
|
||||
struct strbuf file_name = STRBUF_INIT;
|
||||
struct strbuf gitfile_sb = STRBUF_INIT;
|
||||
struct strbuf cfg_sb = STRBUF_INIT;
|
||||
struct strbuf rel_path = STRBUF_INIT;
|
||||
char *git_dir = real_pathdup(git_dir_, 1);
|
||||
char *work_tree = real_pathdup(work_tree_, 1);
|
||||
char *git_dir, *work_tree;
|
||||
|
||||
/* Update gitfile */
|
||||
strbuf_addf(&file_name, "%s/.git", work_tree);
|
||||
write_file(file_name.buf, "gitdir: %s",
|
||||
/* Prepare .git file */
|
||||
strbuf_addf(&gitfile_sb, "%s/.git", work_tree_);
|
||||
if (safe_create_leading_directories_const(gitfile_sb.buf))
|
||||
die(_("could not create directories for %s"), gitfile_sb.buf);
|
||||
|
||||
/* Prepare config file */
|
||||
strbuf_addf(&cfg_sb, "%s/config", git_dir_);
|
||||
if (safe_create_leading_directories_const(cfg_sb.buf))
|
||||
die(_("could not create directories for %s"), cfg_sb.buf);
|
||||
|
||||
git_dir = real_pathdup(git_dir_, 1);
|
||||
work_tree = real_pathdup(work_tree_, 1);
|
||||
|
||||
/* Write .git file */
|
||||
write_file(gitfile_sb.buf, "gitdir: %s",
|
||||
relative_path(git_dir, work_tree, &rel_path));
|
||||
|
||||
/* Update core.worktree setting */
|
||||
strbuf_reset(&file_name);
|
||||
strbuf_addf(&file_name, "%s/config", git_dir);
|
||||
git_config_set_in_file(file_name.buf, "core.worktree",
|
||||
git_config_set_in_file(cfg_sb.buf, "core.worktree",
|
||||
relative_path(work_tree, git_dir, &rel_path));
|
||||
|
||||
strbuf_release(&file_name);
|
||||
strbuf_release(&gitfile_sb);
|
||||
strbuf_release(&cfg_sb);
|
||||
strbuf_release(&rel_path);
|
||||
free(work_tree);
|
||||
free(git_dir);
|
||||
|
|
30
entry.c
30
entry.c
|
@ -2,6 +2,7 @@
|
|||
#include "blob.h"
|
||||
#include "dir.h"
|
||||
#include "streaming.h"
|
||||
#include "submodule.h"
|
||||
|
||||
static void create_directories(const char *path, int path_len,
|
||||
const struct checkout *state)
|
||||
|
@ -146,6 +147,7 @@ static int write_entry(struct cache_entry *ce,
|
|||
unsigned long size;
|
||||
size_t wrote, newsize = 0;
|
||||
struct stat st;
|
||||
const struct submodule *sub;
|
||||
|
||||
if (ce_mode_s_ifmt == S_IFREG) {
|
||||
struct stream_filter *filter = get_stream_filter(ce->name,
|
||||
|
@ -203,6 +205,10 @@ static int write_entry(struct cache_entry *ce,
|
|||
return error("cannot create temporary submodule %s", path);
|
||||
if (mkdir(path, 0777) < 0)
|
||||
return error("cannot create submodule directory %s", path);
|
||||
sub = submodule_from_ce(ce);
|
||||
if (sub)
|
||||
return submodule_move_head(ce->name,
|
||||
NULL, oid_to_hex(&ce->oid), SUBMODULE_MOVE_HEAD_FORCE);
|
||||
break;
|
||||
default:
|
||||
return error("unknown file mode for %s in index", path);
|
||||
|
@ -259,7 +265,31 @@ int checkout_entry(struct cache_entry *ce,
|
|||
strbuf_add(&path, ce->name, ce_namelen(ce));
|
||||
|
||||
if (!check_path(path.buf, path.len, &st, state->base_dir_len)) {
|
||||
const struct submodule *sub;
|
||||
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
/*
|
||||
* Needs to be checked before !changed returns early,
|
||||
* as the possibly empty directory was not changed
|
||||
*/
|
||||
sub = submodule_from_ce(ce);
|
||||
if (sub) {
|
||||
int err;
|
||||
if (!is_submodule_populated_gently(ce->name, &err)) {
|
||||
struct stat sb;
|
||||
if (lstat(ce->name, &sb))
|
||||
die(_("could not stat file '%s'"), ce->name);
|
||||
if (!(st.st_mode & S_IFDIR))
|
||||
unlink_or_warn(ce->name);
|
||||
|
||||
return submodule_move_head(ce->name,
|
||||
NULL, oid_to_hex(&ce->oid),
|
||||
SUBMODULE_MOVE_HEAD_FORCE);
|
||||
} else
|
||||
return submodule_move_head(ce->name,
|
||||
"HEAD", oid_to_hex(&ce->oid),
|
||||
SUBMODULE_MOVE_HEAD_FORCE);
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
return 0;
|
||||
if (!state->force) {
|
||||
|
|
|
@ -234,6 +234,26 @@ int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg)
|
|||
return parse_fetch_recurse(opt, arg, 1);
|
||||
}
|
||||
|
||||
static int parse_update_recurse(const char *opt, const char *arg,
|
||||
int die_on_error)
|
||||
{
|
||||
switch (git_config_maybe_bool(opt, arg)) {
|
||||
case 1:
|
||||
return RECURSE_SUBMODULES_ON;
|
||||
case 0:
|
||||
return RECURSE_SUBMODULES_OFF;
|
||||
default:
|
||||
if (die_on_error)
|
||||
die("bad %s argument: %s", opt, arg);
|
||||
return RECURSE_SUBMODULES_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int parse_update_recurse_submodules_arg(const char *opt, const char *arg)
|
||||
{
|
||||
return parse_update_recurse(opt, arg, 1);
|
||||
}
|
||||
|
||||
static int parse_push_recurse(const char *opt, const char *arg,
|
||||
int die_on_error)
|
||||
{
|
||||
|
|
|
@ -22,16 +22,17 @@ struct submodule {
|
|||
int recommend_shallow;
|
||||
};
|
||||
|
||||
int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
|
||||
int parse_push_recurse_submodules_arg(const char *opt, const char *arg);
|
||||
int parse_submodule_config_option(const char *var, const char *value);
|
||||
const struct submodule *submodule_from_name(const unsigned char *commit_or_tree,
|
||||
const char *name);
|
||||
const struct submodule *submodule_from_path(const unsigned char *commit_or_tree,
|
||||
const char *path);
|
||||
extern int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
|
||||
extern int parse_update_recurse_submodules_arg(const char *opt, const char *arg);
|
||||
extern int parse_push_recurse_submodules_arg(const char *opt, const char *arg);
|
||||
extern int parse_submodule_config_option(const char *var, const char *value);
|
||||
extern const struct submodule *submodule_from_name(
|
||||
const unsigned char *commit_or_tree, const char *name);
|
||||
extern const struct submodule *submodule_from_path(
|
||||
const unsigned char *commit_or_tree, const char *path);
|
||||
extern int gitmodule_sha1_from_commit(const unsigned char *commit_sha1,
|
||||
unsigned char *gitmodules_sha1,
|
||||
struct strbuf *rev);
|
||||
void submodule_free(void);
|
||||
extern void submodule_free(void);
|
||||
|
||||
#endif /* SUBMODULE_CONFIG_H */
|
||||
|
|
222
submodule.c
222
submodule.c
|
@ -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, '/');
|
||||
|
||||
|
|
23
submodule.h
23
submodule.h
|
@ -41,7 +41,13 @@ extern int submodule_config(const char *var, const char *value, void *cb);
|
|||
extern void gitmodules_config(void);
|
||||
extern void gitmodules_config_sha1(const unsigned char *commit_sha1);
|
||||
extern int is_submodule_initialized(const char *path);
|
||||
extern int is_submodule_populated(const char *path);
|
||||
/*
|
||||
* Determine if a submodule has been populated at a given 'path' by checking if
|
||||
* the <path>/.git resolves to a valid git repository.
|
||||
* If return_error_code is NULL, die on error.
|
||||
* Otherwise the return error code is the same as of resolve_gitdir_gently.
|
||||
*/
|
||||
extern int is_submodule_populated_gently(const char *path, int *return_error_code);
|
||||
extern int parse_submodule_update_strategy(const char *value,
|
||||
struct submodule_update_strategy *dst);
|
||||
extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
|
||||
|
@ -58,6 +64,14 @@ extern void show_submodule_inline_diff(FILE *f, const char *path,
|
|||
const char *del, const char *add, const char *reset,
|
||||
const struct diff_options *opt);
|
||||
extern void set_config_fetch_recurse_submodules(int value);
|
||||
extern void set_config_update_recurse_submodules(int value);
|
||||
/* Check if we want to update any submodule.*/
|
||||
extern int should_update_submodules(void);
|
||||
/*
|
||||
* Returns the submodule struct if the given ce entry is a submodule
|
||||
* and it should be updated. Returns NULL otherwise.
|
||||
*/
|
||||
extern const struct submodule *submodule_from_ce(const struct cache_entry *ce);
|
||||
extern void check_for_new_submodule_commits(unsigned char new_sha1[20]);
|
||||
extern int fetch_populated_submodules(const struct argv_array *options,
|
||||
const char *prefix, int command_line_option,
|
||||
|
@ -82,6 +96,13 @@ extern int push_unpushed_submodules(struct sha1_array *commits,
|
|||
extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
|
||||
extern int parallel_submodules(void);
|
||||
|
||||
#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)
|
||||
#define SUBMODULE_MOVE_HEAD_FORCE (1<<1)
|
||||
extern int submodule_move_head(const char *path,
|
||||
const char *old,
|
||||
const char *new,
|
||||
unsigned flags);
|
||||
|
||||
/*
|
||||
* Prepare the "env_array" parameter of a "struct child_process" for executing
|
||||
* a submodule by clearing any repo-specific envirionment variables, but
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# - New submodule (no_submodule => add_sub1)
|
||||
# - Removed submodule (add_sub1 => remove_sub1)
|
||||
# - Updated submodule (add_sub1 => modify_sub1)
|
||||
# - Updated submodule recursively (add_nested_sub => modify_sub1_recursively)
|
||||
# - Submodule updated to invalid commit (add_sub1 => invalid_sub1)
|
||||
# - Submodule updated from invalid commit (invalid_sub1 => valid_sub1)
|
||||
# - Submodule replaced by tracked files in directory (add_sub1 =>
|
||||
|
@ -15,23 +16,50 @@
|
|||
# - Tracked file replaced by submodule (replace_sub1_with_file =>
|
||||
# replace_file_with_sub1)
|
||||
#
|
||||
# --O-----O
|
||||
# / ^ replace_directory_with_sub1
|
||||
# / replace_sub1_with_directory
|
||||
# /----O
|
||||
# / ^
|
||||
# / modify_sub1
|
||||
# O------O-------O
|
||||
# ^ ^\ ^
|
||||
# | | \ remove_sub1
|
||||
# | | -----O-----O
|
||||
# | | \ ^ replace_file_with_sub1
|
||||
# | | \ replace_sub1_with_file
|
||||
# | add_sub1 --O-----O
|
||||
# no_submodule ^ valid_sub1
|
||||
# invalid_sub1
|
||||
# ----O
|
||||
# / ^
|
||||
# / remove_sub1
|
||||
# /
|
||||
# add_sub1 /-------O---------O--------O modify_sub1_recursively
|
||||
# | / ^ add_nested_sub
|
||||
# | / modify_sub1
|
||||
# v/
|
||||
# O------O-----------O---------O
|
||||
# ^ \ ^ replace_directory_with_sub1
|
||||
# | \ replace_sub1_with_directory
|
||||
# no_submodule \
|
||||
# --------O---------O
|
||||
# \ ^ replace_file_with_sub1
|
||||
# \ replace_sub1_with_file
|
||||
# \
|
||||
# ----O---------O
|
||||
# ^ valid_sub1
|
||||
# invalid_sub1
|
||||
#
|
||||
|
||||
create_lib_submodule_repo () {
|
||||
git init submodule_update_sub1 &&
|
||||
(
|
||||
cd submodule_update_sub1 &&
|
||||
echo "expect" >>.gitignore &&
|
||||
echo "actual" >>.gitignore &&
|
||||
echo "x" >file1 &&
|
||||
echo "y" >file2 &&
|
||||
git add .gitignore file1 file2 &&
|
||||
git commit -m "Base inside first submodule" &&
|
||||
git branch "no_submodule"
|
||||
) &&
|
||||
git init submodule_update_sub2 &&
|
||||
(
|
||||
cd submodule_update_sub2
|
||||
echo "expect" >>.gitignore &&
|
||||
echo "actual" >>.gitignore &&
|
||||
echo "x" >file1 &&
|
||||
echo "y" >file2 &&
|
||||
git add .gitignore file1 file2 &&
|
||||
git commit -m "nested submodule base" &&
|
||||
git branch "no_submodule"
|
||||
) &&
|
||||
git init submodule_update_repo &&
|
||||
(
|
||||
cd submodule_update_repo &&
|
||||
|
@ -44,15 +72,16 @@ create_lib_submodule_repo () {
|
|||
git branch "no_submodule" &&
|
||||
|
||||
git checkout -b "add_sub1" &&
|
||||
git submodule add ./. sub1 &&
|
||||
git submodule add ../submodule_update_sub1 sub1 &&
|
||||
git config -f .gitmodules submodule.sub1.ignore all &&
|
||||
git config submodule.sub1.ignore all &&
|
||||
git add .gitmodules &&
|
||||
git commit -m "Add sub1" &&
|
||||
git checkout -b remove_sub1 &&
|
||||
|
||||
git checkout -b remove_sub1 add_sub1 &&
|
||||
git revert HEAD &&
|
||||
|
||||
git checkout -b "modify_sub1" "add_sub1" &&
|
||||
git checkout -b modify_sub1 add_sub1 &&
|
||||
git submodule update &&
|
||||
(
|
||||
cd sub1 &&
|
||||
|
@ -67,7 +96,27 @@ create_lib_submodule_repo () {
|
|||
git add sub1 &&
|
||||
git commit -m "Modify sub1" &&
|
||||
|
||||
git checkout -b "replace_sub1_with_directory" "add_sub1" &&
|
||||
git checkout -b add_nested_sub modify_sub1 &&
|
||||
git -C sub1 checkout -b "add_nested_sub" &&
|
||||
git -C sub1 submodule add --branch no_submodule ../submodule_update_sub2 sub2 &&
|
||||
git -C sub1 commit -a -m "add a nested submodule" &&
|
||||
git add sub1 &&
|
||||
git commit -a -m "update submodule, that updates a nested submodule" &&
|
||||
git checkout -b modify_sub1_recursively &&
|
||||
git -C sub1 checkout -b modify_sub1_recursively &&
|
||||
git -C sub1/sub2 checkout -b modify_sub1_recursively &&
|
||||
echo change >sub1/sub2/file3 &&
|
||||
git -C sub1/sub2 add file3 &&
|
||||
git -C sub1/sub2 commit -m "make a change in nested sub" &&
|
||||
git -C sub1 add sub2 &&
|
||||
git -C sub1 commit -m "update nested sub" &&
|
||||
git add sub1 &&
|
||||
git commit -m "update sub1, that updates nested sub" &&
|
||||
git -C sub1 push origin modify_sub1_recursively &&
|
||||
git -C sub1/sub2 push origin modify_sub1_recursively &&
|
||||
git -C sub1 submodule deinit -f --all &&
|
||||
|
||||
git checkout -b replace_sub1_with_directory add_sub1 &&
|
||||
git submodule update &&
|
||||
git -C sub1 checkout modifications &&
|
||||
git rm --cached sub1 &&
|
||||
|
@ -75,22 +124,25 @@ create_lib_submodule_repo () {
|
|||
git config -f .gitmodules --remove-section "submodule.sub1" &&
|
||||
git add .gitmodules sub1/* &&
|
||||
git commit -m "Replace sub1 with directory" &&
|
||||
|
||||
git checkout -b replace_directory_with_sub1 &&
|
||||
git revert HEAD &&
|
||||
|
||||
git checkout -b "replace_sub1_with_file" "add_sub1" &&
|
||||
git checkout -b replace_sub1_with_file add_sub1 &&
|
||||
git rm sub1 &&
|
||||
echo "content" >sub1 &&
|
||||
git add sub1 &&
|
||||
git commit -m "Replace sub1 with file" &&
|
||||
|
||||
git checkout -b replace_file_with_sub1 &&
|
||||
git revert HEAD &&
|
||||
|
||||
git checkout -b "invalid_sub1" "add_sub1" &&
|
||||
git checkout -b invalid_sub1 add_sub1 &&
|
||||
git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 sub1 &&
|
||||
git commit -m "Invalid sub1 commit" &&
|
||||
git checkout -b valid_sub1 &&
|
||||
git revert HEAD &&
|
||||
|
||||
git checkout master
|
||||
)
|
||||
}
|
||||
|
@ -130,6 +182,15 @@ test_git_directory_is_unchanged () {
|
|||
)
|
||||
}
|
||||
|
||||
test_git_directory_exists() {
|
||||
test -e ".git/modules/$1" &&
|
||||
if test -f sub1/.git
|
||||
then
|
||||
# does core.worktree point at the right place?
|
||||
test "$(git -C .git/modules/$1 config core.worktree)" = "../../../$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper function to be executed at the start of every test below, it sets up
|
||||
# the submodule repo if it doesn't exist and configures the most problematic
|
||||
# settings for diff.ignoreSubmodules.
|
||||
|
@ -151,15 +212,36 @@ reset_work_tree_to () {
|
|||
git checkout -f "$1" &&
|
||||
git status -u -s >actual &&
|
||||
test_must_be_empty actual &&
|
||||
sha1=$(git rev-parse --revs-only HEAD:sub1) &&
|
||||
if test -n "$sha1" &&
|
||||
test $(cd "sub1" && git rev-parse --verify "$sha1^{commit}")
|
||||
hash=$(git rev-parse --revs-only HEAD:sub1) &&
|
||||
if test -n "$hash" &&
|
||||
test $(cd "../submodule_update_sub1" && git rev-parse --verify "$hash^{commit}")
|
||||
then
|
||||
git submodule update --init --recursive "sub1"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
reset_work_tree_to_interested () {
|
||||
reset_work_tree_to $1 &&
|
||||
# make the submodule git dirs available
|
||||
if ! test -d submodule_update/.git/modules/sub1
|
||||
then
|
||||
mkdir -p submodule_update/.git/modules &&
|
||||
cp -r submodule_update_repo/.git/modules/sub1 submodule_update/.git/modules/sub1
|
||||
GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1 config --unset core.worktree
|
||||
fi &&
|
||||
if ! test -d submodule_update/.git/modules/sub1/modules/sub2
|
||||
then
|
||||
mkdir -p submodule_update/.git/modules/sub1/modules &&
|
||||
cp -r submodule_update_repo/.git/modules/sub1/modules/sub2 submodule_update/.git/modules/sub1/modules/sub2
|
||||
GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1/modules/sub2 config --unset core.worktree
|
||||
fi &&
|
||||
# indicate we are interested in the submodule:
|
||||
git -C submodule_update config submodule.sub1.url "bogus" &&
|
||||
# sub1 might not be checked out, so use the git dir
|
||||
git -C submodule_update/.git/modules/sub1 config submodule.sub2.url "bogus"
|
||||
}
|
||||
|
||||
# Test that the superproject contains the content according to commit "$1"
|
||||
# (the work tree must match the index for everything but submodules but the
|
||||
# index must exactly match the given commit including any submodule SHA-1s).
|
||||
|
@ -173,6 +255,11 @@ test_superproject_content () {
|
|||
# Test that the given submodule at path "$1" contains the content according
|
||||
# to the submodule commit recorded in the superproject's commit "$2"
|
||||
test_submodule_content () {
|
||||
if test x"$1" = "x-C"
|
||||
then
|
||||
cd "$2"
|
||||
shift; shift;
|
||||
fi
|
||||
if test $# != 2
|
||||
then
|
||||
echo "test_submodule_content needs two arguments"
|
||||
|
@ -675,3 +762,464 @@ test_submodule_forced_switch () {
|
|||
)
|
||||
'
|
||||
}
|
||||
|
||||
# Test that submodule contents are correctly updated when switching
|
||||
# between commits that change a submodule.
|
||||
# Test that the following transitions are correctly handled:
|
||||
# (These tests are also above in the case where we expect no change
|
||||
# in the submodule)
|
||||
# - Updated submodule
|
||||
# - New submodule
|
||||
# - Removed submodule
|
||||
# - Directory containing tracked files replaced by submodule
|
||||
# - Submodule replaced by tracked files in directory
|
||||
# - Submodule replaced by tracked file with the same name
|
||||
# - tracked file replaced by submodule
|
||||
#
|
||||
# New test cases
|
||||
# - Removing a submodule with a git directory absorbs the submodules
|
||||
# git directory first into the superproject.
|
||||
|
||||
test_submodule_switch_recursing () {
|
||||
command="$1"
|
||||
RESULTDS=success
|
||||
if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
|
||||
then
|
||||
RESULTDS=failure
|
||||
fi
|
||||
RESULTR=success
|
||||
if test "$KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED" = 1
|
||||
then
|
||||
RESULTR=failure
|
||||
fi
|
||||
RESULTOI=success
|
||||
if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1
|
||||
then
|
||||
RESULTOI=failure
|
||||
fi
|
||||
######################### Appearing submodule #########################
|
||||
# Switching to a commit letting a submodule appear checks it out ...
|
||||
test_expect_success "$command: added submodule is checked out" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
$command add_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# ... ignoring an empty existing directory ...
|
||||
test_expect_success "$command: added submodule is checked out in empty dir" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
mkdir sub1 &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
$command add_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# ... unless there is an untracked file in its place.
|
||||
test_expect_success "$command: added submodule doesn't remove untracked file with same name" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
: >sub1 &&
|
||||
test_must_fail $command add_sub1 &&
|
||||
test_superproject_content origin/no_submodule &&
|
||||
test_must_be_empty sub1
|
||||
)
|
||||
'
|
||||
# ... but an ignored file is fine.
|
||||
test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" '
|
||||
test_when_finished "rm submodule_update/.git/info/exclude" &&
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
: >sub1 &&
|
||||
echo sub1 >.git/info/exclude
|
||||
$command add_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# Replacing a tracked file with a submodule produces a checked out submodule
|
||||
test_expect_success "$command: replace tracked file with submodule checks out submodule" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested replace_sub1_with_file &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
|
||||
$command replace_file_with_sub1 &&
|
||||
test_superproject_content origin/replace_file_with_sub1 &&
|
||||
test_submodule_content sub1 origin/replace_file_with_sub1
|
||||
)
|
||||
'
|
||||
# ... as does removing a directory with tracked files with a submodule.
|
||||
test_expect_success "$command: replace directory with submodule" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested replace_sub1_with_directory &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
|
||||
$command replace_directory_with_sub1 &&
|
||||
test_superproject_content origin/replace_directory_with_sub1 &&
|
||||
test_submodule_content sub1 origin/replace_directory_with_sub1
|
||||
)
|
||||
'
|
||||
|
||||
######################## Disappearing submodule #######################
|
||||
# Removing a submodule removes its work tree ...
|
||||
test_expect_success "$command: removed submodule removes submodules working tree" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t remove_sub1 origin/remove_sub1 &&
|
||||
$command remove_sub1 &&
|
||||
test_superproject_content origin/remove_sub1 &&
|
||||
! test -e sub1
|
||||
)
|
||||
'
|
||||
# ... absorbing a .git directory along the way.
|
||||
test_expect_success "$command: removed submodule absorbs submodules .git directory" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t remove_sub1 origin/remove_sub1 &&
|
||||
replace_gitfile_with_git_dir sub1 &&
|
||||
rm -rf .git/modules &&
|
||||
$command remove_sub1 &&
|
||||
test_superproject_content origin/remove_sub1 &&
|
||||
! test -e sub1 &&
|
||||
test_git_directory_exists sub1
|
||||
)
|
||||
'
|
||||
# Replacing a submodule with files in a directory must succeeds
|
||||
# when the submodule is clean
|
||||
test_expect_$RESULTDS "$command: replace submodule with a directory" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
|
||||
$command replace_sub1_with_directory &&
|
||||
test_superproject_content origin/replace_sub1_with_directory &&
|
||||
test_submodule_content sub1 origin/replace_sub1_with_directory
|
||||
)
|
||||
'
|
||||
# ... absorbing a .git directory.
|
||||
test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
|
||||
replace_gitfile_with_git_dir sub1 &&
|
||||
rm -rf .git/modules &&
|
||||
$command replace_sub1_with_directory &&
|
||||
test_superproject_content origin/replace_sub1_with_directory &&
|
||||
test_git_directory_exists sub1
|
||||
)
|
||||
'
|
||||
|
||||
# Replacing it with a file ...
|
||||
test_expect_success "$command: replace submodule with a file" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
|
||||
$command replace_sub1_with_file &&
|
||||
test_superproject_content origin/replace_sub1_with_file &&
|
||||
test -f sub1
|
||||
)
|
||||
'
|
||||
|
||||
# ... must check its local work tree for untracked files
|
||||
test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
|
||||
: >sub1/untrackedfile &&
|
||||
test_must_fail $command replace_sub1_with_file &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
|
||||
# ... and ignored files are ignored
|
||||
test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" '
|
||||
test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" &&
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
|
||||
: >sub1/ignored &&
|
||||
$command replace_sub1_with_file &&
|
||||
test_superproject_content origin/replace_sub1_with_file &&
|
||||
test -f sub1
|
||||
)
|
||||
'
|
||||
|
||||
########################## Modified submodule #########################
|
||||
# Updating a submodule sha1 updates the submodule's work tree
|
||||
test_expect_success "$command: modified submodule updates submodule work tree" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t modify_sub1 origin/modify_sub1 &&
|
||||
$command modify_sub1 &&
|
||||
test_superproject_content origin/modify_sub1 &&
|
||||
test_submodule_content sub1 origin/modify_sub1
|
||||
)
|
||||
'
|
||||
|
||||
# Updating a submodule to an invalid sha1 doesn't update the
|
||||
# superproject nor the submodule's work tree.
|
||||
test_expect_success "$command: updating to a missing submodule commit fails" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t invalid_sub1 origin/invalid_sub1 &&
|
||||
test_must_fail $command invalid_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
|
||||
# recursing deeper than one level doesn't work yet.
|
||||
test_expect_$RESULTR "$command: modified submodule updates submodule recursively" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_nested_sub &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t modify_sub1_recursively origin/modify_sub1_recursively &&
|
||||
$command modify_sub1_recursively &&
|
||||
test_superproject_content origin/modify_sub1_recursively &&
|
||||
test_submodule_content sub1 origin/modify_sub1_recursively &&
|
||||
test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively
|
||||
)
|
||||
'
|
||||
}
|
||||
|
||||
# Test that submodule contents are updated when switching between commits
|
||||
# that change a submodule, but throwing away local changes in
|
||||
# the superproject as well as the submodule is allowed.
|
||||
test_submodule_forced_switch_recursing () {
|
||||
command="$1"
|
||||
RESULT=success
|
||||
if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1
|
||||
then
|
||||
RESULT=failure
|
||||
fi
|
||||
######################### Appearing submodule #########################
|
||||
# Switching to a commit letting a submodule appear creates empty dir ...
|
||||
test_expect_success "$command: added submodule is checked out" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
$command add_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# ... and doesn't care if it already exists ...
|
||||
test_expect_success "$command: added submodule ignores empty directory" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
mkdir sub1 &&
|
||||
$command add_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# ... not caring about an untracked file either
|
||||
test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested no_submodule &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t add_sub1 origin/add_sub1 &&
|
||||
>sub1 &&
|
||||
$command add_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# Replacing a tracked file with a submodule checks out the submodule
|
||||
test_expect_success "$command: replace tracked file with submodule populates the submodule" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested replace_sub1_with_file &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 &&
|
||||
$command replace_file_with_sub1 &&
|
||||
test_superproject_content origin/replace_file_with_sub1 &&
|
||||
test_submodule_content sub1 origin/replace_file_with_sub1
|
||||
)
|
||||
'
|
||||
# ... as does removing a directory with tracked files with a
|
||||
# submodule.
|
||||
test_expect_success "$command: replace directory with submodule" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested replace_sub1_with_directory &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 &&
|
||||
$command replace_directory_with_sub1 &&
|
||||
test_superproject_content origin/replace_directory_with_sub1 &&
|
||||
test_submodule_content sub1 origin/replace_directory_with_sub1
|
||||
)
|
||||
'
|
||||
|
||||
######################## Disappearing submodule #######################
|
||||
# Removing a submodule doesn't remove its work tree ...
|
||||
test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t remove_sub1 origin/remove_sub1 &&
|
||||
$command remove_sub1 &&
|
||||
test_superproject_content origin/remove_sub1 &&
|
||||
! test -e sub1
|
||||
)
|
||||
'
|
||||
# ... especially when it contains a .git directory.
|
||||
test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t remove_sub1 origin/remove_sub1 &&
|
||||
replace_gitfile_with_git_dir sub1 &&
|
||||
rm -rf .git/modules/sub1 &&
|
||||
$command remove_sub1 &&
|
||||
test_superproject_content origin/remove_sub1 &&
|
||||
test_git_directory_exists sub1 &&
|
||||
! test -e sub1
|
||||
)
|
||||
'
|
||||
# Replacing a submodule with files in a directory ...
|
||||
test_expect_success "$command: replace submodule with a directory" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
|
||||
$command replace_sub1_with_directory &&
|
||||
test_superproject_content origin/replace_sub1_with_directory
|
||||
)
|
||||
'
|
||||
# ... absorbing a .git directory.
|
||||
test_expect_success "$command: replace submodule containing a .git directory with a directory must fail" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&
|
||||
replace_gitfile_with_git_dir sub1 &&
|
||||
rm -rf .git/modules/sub1 &&
|
||||
$command replace_sub1_with_directory &&
|
||||
test_superproject_content origin/replace_sub1_with_directory &&
|
||||
test_submodule_content sub1 origin/modify_sub1
|
||||
test_git_directory_exists sub1
|
||||
)
|
||||
'
|
||||
# Replacing it with a file
|
||||
test_expect_success "$command: replace submodule with a file" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
|
||||
$command replace_sub1_with_file &&
|
||||
test_superproject_content origin/replace_sub1_with_file
|
||||
)
|
||||
'
|
||||
|
||||
# ... even if the submodule contains ignored files
|
||||
test_expect_success "$command: replace submodule with a file ignoring ignored files" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
|
||||
: >sub1/expect &&
|
||||
$command replace_sub1_with_file &&
|
||||
test_superproject_content origin/replace_sub1_with_file
|
||||
)
|
||||
'
|
||||
|
||||
# ... but stops for untracked files that would be lost
|
||||
test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t replace_sub1_with_file origin/replace_sub1_with_file &&
|
||||
: >sub1/untracked_file &&
|
||||
test_must_fail $command replace_sub1_with_file &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test -f sub1/untracked_file
|
||||
)
|
||||
'
|
||||
|
||||
########################## Modified submodule #########################
|
||||
# Updating a submodule sha1 updates the submodule's work tree
|
||||
test_expect_success "$command: modified submodule updates submodule work tree" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t modify_sub1 origin/modify_sub1 &&
|
||||
$command modify_sub1 &&
|
||||
test_superproject_content origin/modify_sub1 &&
|
||||
test_submodule_content sub1 origin/modify_sub1
|
||||
)
|
||||
'
|
||||
# Updating a submodule to an invalid sha1 doesn't update the
|
||||
# submodule's work tree, subsequent update will fail
|
||||
test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested add_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t invalid_sub1 origin/invalid_sub1 &&
|
||||
test_must_fail $command invalid_sub1 &&
|
||||
test_superproject_content origin/add_sub1 &&
|
||||
test_submodule_content sub1 origin/add_sub1
|
||||
)
|
||||
'
|
||||
# Updating a submodule from an invalid sha1 updates
|
||||
test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" '
|
||||
prolog &&
|
||||
reset_work_tree_to_interested invalid_sub1 &&
|
||||
(
|
||||
cd submodule_update &&
|
||||
git branch -t valid_sub1 origin/valid_sub1 &&
|
||||
test_must_fail $command valid_sub1 &&
|
||||
test_superproject_content origin/invalid_sub1
|
||||
)
|
||||
'
|
||||
}
|
||||
|
|
|
@ -5,6 +5,14 @@ test_description='read-tree can handle submodules'
|
|||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/lib-submodule-update.sh
|
||||
|
||||
KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
|
||||
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
|
||||
KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
|
||||
|
||||
test_submodule_switch_recursing "git read-tree --recurse-submodules -u -m"
|
||||
|
||||
test_submodule_forced_switch_recursing "git read-tree --recurse-submodules -u --reset"
|
||||
|
||||
test_submodule_switch "git read-tree -u -m"
|
||||
|
||||
test_submodule_forced_switch "git read-tree -u --reset"
|
||||
|
|
|
@ -63,6 +63,12 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/
|
|||
! test -s actual
|
||||
'
|
||||
|
||||
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
|
||||
KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1
|
||||
test_submodule_switch_recursing "git checkout --recurse-submodules"
|
||||
|
||||
test_submodule_forced_switch_recursing "git checkout -f --recurse-submodules"
|
||||
|
||||
test_submodule_switch "git checkout"
|
||||
|
||||
test_submodule_forced_switch "git checkout -f"
|
||||
|
|
148
unpack-trees.c
148
unpack-trees.c
|
@ -10,6 +10,8 @@
|
|||
#include "attr.h"
|
||||
#include "split-index.h"
|
||||
#include "dir.h"
|
||||
#include "submodule.h"
|
||||
#include "submodule-config.h"
|
||||
|
||||
/*
|
||||
* Error messages expected by scripts out of plumbing commands such as
|
||||
|
@ -45,6 +47,9 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
|
|||
|
||||
/* ERROR_WOULD_LOSE_ORPHANED_REMOVED */
|
||||
"Working tree file '%s' would be removed by sparse checkout update.",
|
||||
|
||||
/* ERROR_WOULD_LOSE_SUBMODULE */
|
||||
"Submodule '%s' cannot checkout new HEAD.",
|
||||
};
|
||||
|
||||
#define ERRORMSG(o,type) \
|
||||
|
@ -161,6 +166,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
|
|||
_("The following working tree files would be overwritten by sparse checkout update:\n%s");
|
||||
msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
|
||||
_("The following working tree files would be removed by sparse checkout update:\n%s");
|
||||
msgs[ERROR_WOULD_LOSE_SUBMODULE] =
|
||||
_("Submodule '%s' cannot checkout new HEAD");
|
||||
|
||||
opts->show_all_errors = 1;
|
||||
/* rejected paths may not have a static buffer */
|
||||
|
@ -240,12 +247,75 @@ static void display_error_msgs(struct unpack_trees_options *o)
|
|||
fprintf(stderr, _("Aborting\n"));
|
||||
}
|
||||
|
||||
static int check_submodule_move_head(const struct cache_entry *ce,
|
||||
const char *old_id,
|
||||
const char *new_id,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
const struct submodule *sub = submodule_from_ce(ce);
|
||||
if (!sub)
|
||||
return 0;
|
||||
|
||||
switch (sub->update_strategy.type) {
|
||||
case SM_UPDATE_UNSPECIFIED:
|
||||
case SM_UPDATE_CHECKOUT:
|
||||
if (submodule_move_head(ce->name, old_id, new_id, SUBMODULE_MOVE_HEAD_DRY_RUN))
|
||||
return o->gently ? -1 :
|
||||
add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name);
|
||||
return 0;
|
||||
case SM_UPDATE_NONE:
|
||||
return 0;
|
||||
case SM_UPDATE_REBASE:
|
||||
case SM_UPDATE_MERGE:
|
||||
case SM_UPDATE_COMMAND:
|
||||
default:
|
||||
warning(_("submodule update strategy not supported for submodule '%s'"), ce->name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void reload_gitmodules_file(struct index_state *index,
|
||||
struct checkout *state)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < index->cache_nr; i++) {
|
||||
struct cache_entry *ce = index->cache[i];
|
||||
if (ce->ce_flags & CE_UPDATE) {
|
||||
int r = strcmp(ce->name, ".gitmodules");
|
||||
if (r < 0)
|
||||
continue;
|
||||
else if (r == 0) {
|
||||
submodule_free();
|
||||
checkout_entry(ce, state, NULL);
|
||||
gitmodules_config();
|
||||
git_config(submodule_config, NULL);
|
||||
} else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlink the last component and schedule the leading directories for
|
||||
* removal, such that empty directories get removed.
|
||||
*/
|
||||
static void unlink_entry(const struct cache_entry *ce)
|
||||
{
|
||||
const struct submodule *sub = submodule_from_ce(ce);
|
||||
if (sub) {
|
||||
switch (sub->update_strategy.type) {
|
||||
case SM_UPDATE_UNSPECIFIED:
|
||||
case SM_UPDATE_CHECKOUT:
|
||||
case SM_UPDATE_REBASE:
|
||||
case SM_UPDATE_MERGE:
|
||||
submodule_move_head(ce->name, "HEAD", NULL,
|
||||
SUBMODULE_MOVE_HEAD_FORCE);
|
||||
break;
|
||||
case SM_UPDATE_NONE:
|
||||
case SM_UPDATE_COMMAND:
|
||||
return; /* Do not touch the submodule. */
|
||||
}
|
||||
}
|
||||
if (!check_leading_path(ce->name, ce_namelen(ce)))
|
||||
return;
|
||||
if (remove_or_warn(ce->ce_mode, ce->name))
|
||||
|
@ -301,6 +371,9 @@ static int check_updates(struct unpack_trees_options *o)
|
|||
remove_marked_cache_entries(index);
|
||||
remove_scheduled_dirs();
|
||||
|
||||
if (should_update_submodules() && o->update && !o->dry_run)
|
||||
reload_gitmodules_file(index, &state);
|
||||
|
||||
for (i = 0; i < index->cache_nr; i++) {
|
||||
struct cache_entry *ce = index->cache[i];
|
||||
|
||||
|
@ -1358,17 +1431,26 @@ static int verify_uptodate_1(const struct cache_entry *ce,
|
|||
if (!lstat(ce->name, &st)) {
|
||||
int flags = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE;
|
||||
unsigned changed = ie_match_stat(o->src_index, ce, &st, flags);
|
||||
|
||||
if (submodule_from_ce(ce)) {
|
||||
int r = check_submodule_move_head(ce,
|
||||
"HEAD", oid_to_hex(&ce->oid), o);
|
||||
if (r)
|
||||
return o->gently ? -1 :
|
||||
add_rejected_path(o, error_type, ce->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
return 0;
|
||||
/*
|
||||
* NEEDSWORK: the current default policy is to allow
|
||||
* submodule to be out of sync wrt the superproject
|
||||
* index. This needs to be tightened later for
|
||||
* submodules that are marked to be automatically
|
||||
* checked out.
|
||||
* Historic default policy was to allow submodule to be out
|
||||
* of sync wrt the superproject index. If the submodule was
|
||||
* not considered interesting above, we don't care here.
|
||||
*/
|
||||
if (S_ISGITLINK(ce->ce_mode))
|
||||
return 0;
|
||||
|
||||
errno = 0;
|
||||
}
|
||||
if (errno == ENOENT)
|
||||
|
@ -1407,11 +1489,16 @@ static void invalidate_ce_path(const struct cache_entry *ce,
|
|||
* Currently, git does not checkout subprojects during a superproject
|
||||
* checkout, so it is not going to overwrite anything.
|
||||
*/
|
||||
static int verify_clean_submodule(const struct cache_entry *ce,
|
||||
static int verify_clean_submodule(const char *old_sha1,
|
||||
const struct cache_entry *ce,
|
||||
enum unpack_trees_error_types error_type,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
return 0;
|
||||
if (!submodule_from_ce(ce))
|
||||
return 0;
|
||||
|
||||
return check_submodule_move_head(ce, old_sha1,
|
||||
oid_to_hex(&ce->oid), o);
|
||||
}
|
||||
|
||||
static int verify_clean_subdirectory(const struct cache_entry *ce,
|
||||
|
@ -1427,16 +1514,18 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
|
|||
struct dir_struct d;
|
||||
char *pathbuf;
|
||||
int cnt = 0;
|
||||
unsigned char sha1[20];
|
||||
|
||||
if (S_ISGITLINK(ce->ce_mode) &&
|
||||
resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) {
|
||||
/* If we are not going to update the submodule, then
|
||||
if (S_ISGITLINK(ce->ce_mode)) {
|
||||
unsigned char sha1[20];
|
||||
int sub_head = resolve_gitlink_ref(ce->name, "HEAD", sha1);
|
||||
/*
|
||||
* If we are not going to update the submodule, then
|
||||
* we don't care.
|
||||
*/
|
||||
if (!hashcmp(sha1, ce->oid.hash))
|
||||
if (!sub_head && !hashcmp(sha1, ce->oid.hash))
|
||||
return 0;
|
||||
return verify_clean_submodule(ce, error_type, o);
|
||||
return verify_clean_submodule(sub_head ? NULL : sha1_to_hex(sha1),
|
||||
ce, error_type, o);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1575,9 +1664,15 @@ static int verify_absent_1(const struct cache_entry *ce,
|
|||
path = xmemdupz(ce->name, len);
|
||||
if (lstat(path, &st))
|
||||
ret = error_errno("cannot stat '%s'", path);
|
||||
else
|
||||
ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL,
|
||||
&st, error_type, o);
|
||||
else {
|
||||
if (submodule_from_ce(ce))
|
||||
ret = check_submodule_move_head(ce,
|
||||
oid_to_hex(&ce->oid),
|
||||
NULL, o);
|
||||
else
|
||||
ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL,
|
||||
&st, error_type, o);
|
||||
}
|
||||
free(path);
|
||||
return ret;
|
||||
} else if (lstat(ce->name, &st)) {
|
||||
|
@ -1585,6 +1680,10 @@ static int verify_absent_1(const struct cache_entry *ce,
|
|||
return error_errno("cannot stat '%s'", ce->name);
|
||||
return 0;
|
||||
} else {
|
||||
if (submodule_from_ce(ce))
|
||||
return check_submodule_move_head(ce, oid_to_hex(&ce->oid),
|
||||
NULL, o);
|
||||
|
||||
return check_ok_to_remove(ce->name, ce_namelen(ce),
|
||||
ce_to_dtype(ce), ce, &st,
|
||||
error_type, o);
|
||||
|
@ -1640,6 +1739,15 @@ static int merged_entry(const struct cache_entry *ce,
|
|||
return -1;
|
||||
}
|
||||
invalidate_ce_path(merge, o);
|
||||
|
||||
if (submodule_from_ce(ce)) {
|
||||
int ret = check_submodule_move_head(ce, NULL,
|
||||
oid_to_hex(&ce->oid),
|
||||
o);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
} else if (!(old->ce_flags & CE_CONFLICTED)) {
|
||||
/*
|
||||
* See if we can re-use the old CE directly?
|
||||
|
@ -1660,6 +1768,14 @@ static int merged_entry(const struct cache_entry *ce,
|
|||
update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
|
||||
invalidate_ce_path(old, o);
|
||||
}
|
||||
|
||||
if (submodule_from_ce(ce)) {
|
||||
int ret = check_submodule_move_head(ce, oid_to_hex(&old->oid),
|
||||
oid_to_hex(&ce->oid),
|
||||
o);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Previously unmerged entry left as an existence
|
||||
|
|
|
@ -21,6 +21,7 @@ enum unpack_trees_error_types {
|
|||
ERROR_SPARSE_NOT_UPTODATE_FILE,
|
||||
ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN,
|
||||
ERROR_WOULD_LOSE_ORPHANED_REMOVED,
|
||||
ERROR_WOULD_LOSE_SUBMODULE,
|
||||
NB_UNPACK_TREES_ERROR_TYPES
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue