提交 95cf715a 编写于 作者: J Junio C Hamano

Merge branch 'ss/submodule-add-in-c' into seen

"git submodule add" being rewritten in C.

* ss/submodule-add-in-c:
  t7400: add test to check 'submodule add' for tracked paths
  submodule: port submodule subcommand 'add' from shell to C
  dir: change the scope of function 'directory_exists_in_index()'
......@@ -19,7 +19,6 @@
#include "diffcore.h"
#include "diff.h"
#include "object-store.h"
#include "dir.h"
#include "advice.h"
#define OPT_QUIET (1 << 0)
......@@ -2744,6 +2743,395 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
return !!ret;
}
struct add_data {
const char *prefix;
const char *branch;
const char *reference_path;
const char *sm_path;
const char *sm_name;
const char *repo;
const char *realrepo;
int depth;
unsigned int force: 1;
unsigned int quiet: 1;
unsigned int progress: 1;
unsigned int dissociate: 1;
};
#define ADD_DATA_INIT { NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0 }
/*
* Guess dir name from repository: strip leading '.*[/:]',
* strip trailing '[:/]*.git'.
*/
static char *guess_dir_name(const char *repo)
{
const char *p, *start, *end, *limit;
int after_slash_or_colon;
after_slash_or_colon = 0;
limit = repo + strlen(repo);
start = repo;
end = limit;
for (p = repo; p < limit; p++) {
if (starts_with(p, ".git")) {
/* strip trailing '[:/]*.git' */
if (!after_slash_or_colon)
end = p;
p += 3;
} else if (*p == '/' || *p == ':') {
/* strip leading '.*[/:]' */
if (end == limit)
end = p;
after_slash_or_colon = 1;
} else if (after_slash_or_colon) {
start = p;
end = limit;
after_slash_or_colon = 0;
}
}
return xstrndup(start, end - start);
}
static void fprintf_submodule_remote(const char *str)
{
const char *p = str;
const char *start;
const char *end;
char *name, *url;
start = p;
while (*p != ' ')
p++;
end = p;
name = xstrndup(start, end - start);
while(*p == ' ')
p++;
start = p;
while (*p != ' ')
p++;
end = p;
url = xstrndup(start, end - start);
fprintf(stderr, " %s\t%s\n", name, url);
free(name);
free(url);
}
static int check_sm_exists(unsigned int force, const char *path) {
int cache_pos, dir_in_cache = 0;
if (read_cache() < 0)
die(_("index file corrupt"));
cache_pos = cache_name_pos(path, strlen(path));
if(cache_pos < 0 && (directory_exists_in_index(&the_index,
path, strlen(path)) == index_directory))
dir_in_cache = 1;
if (!force) {
if (cache_pos >= 0 || dir_in_cache)
die(_("'%s' already exists in the index"), path);
} else {
struct cache_entry *ce = NULL;
if (cache_pos >= 0)
ce = the_index.cache[cache_pos];
if (dir_in_cache || (ce && !S_ISGITLINK(ce->ce_mode)))
die(_("'%s' already exists in the index and is not a "
"submodule"), path);
}
return 0;
}
static void modify_remote_v(struct strbuf *sb)
{
int i;
for (i = 0; i < sb->len; i++) {
const char *start = sb->buf + i;
const char *end = start;
while (sb->buf[i++] != '\n')
end++;
if (!strcmp("fetch", xstrndup(end - 6, 5)))
fprintf_submodule_remote(xstrndup(start, end - start - 7));
}
}
static int add_submodule(struct add_data *info)
{
/* perhaps the path exists and is already a git repo, else clone it */
if (is_directory(info->sm_path)) {
char *sub_git_path = xstrfmt("%s/.git", info->sm_path);
if (is_directory(sub_git_path) || file_exists(sub_git_path))
printf(_("Adding existing repo at '%s' to the index\n"),
info->sm_path);
else
die(_("'%s' already exists and is not a valid git repo"),
info->sm_path);
free(sub_git_path);
} else {
struct strvec clone_args = STRVEC_INIT;
struct child_process cp = CHILD_PROCESS_INIT;
char *submodule_git_dir = xstrfmt(".git/modules/%s", info->sm_name);
if (is_directory(submodule_git_dir)) {
if (!info->force) {
struct child_process cp_rem = CHILD_PROCESS_INIT;
struct strbuf sb_rem = STRBUF_INIT;
cp_rem.git_cmd = 1;
fprintf(stderr, _("A git directory for '%s' is "
"found locally with remote(s):\n"),
info->sm_name);
strvec_pushf(&cp_rem.env_array,
"GIT_DIR=%s", submodule_git_dir);
strvec_push(&cp_rem.env_array, "GIT_WORK_TREE=.");
strvec_pushl(&cp_rem.args, "remote", "-v", NULL);
if (!capture_command(&cp_rem, &sb_rem, 0)) {
modify_remote_v(&sb_rem);
}
error(_("If you want to reuse this local git "
"directory instead of cloning again from\n "
" %s\n"
"use the '--force' option. If the local "
"git directory is not the correct repo\n"
"or you are unsure what this means choose "
"another name with the '--name' option."),
info->realrepo);
return 1;
} else {
printf(_("Reactivating local git directory for "
"submodule '%s'."), info->sm_path);
}
}
free(submodule_git_dir);
strvec_push(&clone_args, "clone");
if (info->quiet)
strvec_push(&clone_args, "--quiet");
if (info->progress)
strvec_push(&clone_args, "--progress");
if (info->prefix)
strvec_pushl(&clone_args, "--prefix", info->prefix, NULL);
strvec_pushl(&clone_args, "--path", info->sm_path, "--name",
info->sm_name, "--url", info->realrepo, NULL);
if (info->reference_path)
strvec_pushl(&clone_args, "--reference",
info->reference_path, NULL);
if (info->dissociate)
strvec_push(&clone_args, "--dissociate");
if (info->depth >= 0)
strvec_pushf(&clone_args, "--depth=%d", info->depth);
if (module_clone(clone_args.nr, clone_args.v, info->prefix)) {
strvec_clear(&clone_args);
return -1;
}
prepare_submodule_repo_env(&cp.env_array);
cp.git_cmd = 1;
cp.dir = info->sm_path;
strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
if (info->branch) {
strvec_pushl(&cp.args, "-B", info->branch, NULL);
strvec_pushf(&cp.args, "origin/%s", info->branch);
}
if (run_command(&cp))
die(_("unable to checkout submodule '%s'"), info->sm_path);
}
return 0;
}
static void config_added_submodule(struct add_data *info)
{
char *key, *var = NULL;
struct child_process cp = CHILD_PROCESS_INIT;
key = xstrfmt("submodule.%s.url", info->sm_name);
git_config_set_gently(key, info->realrepo);
free(key);
cp.git_cmd = 1;
strvec_pushl(&cp.args, "add", "--no-warn-embedded-repo", NULL);
if (info->force)
strvec_push(&cp.args, "--force");
strvec_pushl(&cp.args, "--", info->sm_path, ".gitmodules", NULL);
key = xstrfmt("submodule.%s.path", info->sm_name);
git_config_set_in_file_gently(".gitmodules", key, info->sm_path);
free(key);
key = xstrfmt("submodule.%s.url", info->sm_name);
git_config_set_in_file_gently(".gitmodules", key, info->repo);
free(key);
key = xstrfmt("submodule.%s.branch", info->sm_name);
if (info->branch)
git_config_set_in_file_gently(".gitmodules", key, info->branch);
free(key);
if (run_command(&cp))
die(_("failed to add submodule '%s'"), info->sm_path);
/*
* NEEDSWORK: In a multi-working-tree world, this needs to be
* set in the per-worktree config.
*/
if (!git_config_get_string("submodule.active", &var) && var) {
/*
* If the submodule being adding isn't already covered by the
* current configured pathspec, set the submodule's active flag
*/
if (!is_submodule_active(the_repository, info->sm_path)) {
key = xstrfmt("submodule.%s.active", info->sm_name);
git_config_set_gently(key, "true");
free(key);
}
} else {
key = xstrfmt("submodule.%s.active", info->sm_name);
git_config_set_gently(key, "true");
free(key);
}
}
static int module_add(int argc, const char **argv, const char *prefix)
{
const char *branch = NULL, *custom_name = NULL, *realrepo = NULL;
const char *reference_path = NULL, *repo = NULL, *name = NULL;
char *path;
int force = 0, quiet = 0, depth = -1, progress = 0, dissociate = 0;
struct add_data info = ADD_DATA_INIT;
struct strbuf sb = STRBUF_INIT;
struct option options[] = {
OPT_STRING('b', "branch", &branch, N_("branch"),
N_("branch of repository to add as submodule")),
OPT_BOOL('f', "force", &force, N_("allow adding an otherwise "
"ignored submodule path")),
OPT__QUIET(&quiet, N_("print only error messages")),
OPT_BOOL(0, "progress", &progress, N_("force cloning progress")),
OPT_STRING(0, "reference", &reference_path, N_("repository"),
N_("reference repository")),
OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
OPT_STRING(0, "name", &custom_name, N_("name"),
N_("sets the submodule’s name to the given string "
"instead of defaulting to its path")),
OPT_INTEGER(0, "depth", &depth, N_("depth for shallow clones")),
OPT_END()
};
const char *const usage[] = {
N_("git submodule--helper add [<options>] [--] [<path>]"),
NULL
};
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (!is_writing_gitmodules_ok())
die(_("please make sure that the .gitmodules file is in the working tree"));
if (reference_path && !is_absolute_path(reference_path) && prefix)
reference_path = xstrfmt("%s%s", prefix, reference_path);
if (argc == 0 || argc > 2) {
usage_with_options(usage, options);
} else if (argc == 1) {
repo = argv[0];
path = guess_dir_name(repo);
} else {
repo = argv[0];
path = xstrdup(argv[1]);
}
if (!is_absolute_path(path) && prefix)
path = xstrfmt("%s%s", prefix, path);
/* assure repo is absolute or relative to parent */
if (starts_with_dot_dot_slash(repo) || starts_with_dot_slash(repo)) {
char *remote = get_default_remote();
char *remoteurl;
struct strbuf sb = STRBUF_INIT;
if (prefix)
die(_("relative path can only be used from the toplevel "
"of the working tree"));
/* dereference source url relative to parent's url */
strbuf_addf(&sb, "remote.%s.url", remote);
if (git_config_get_string(sb.buf, &remoteurl))
remoteurl = xgetcwd();
realrepo = relative_url(remoteurl, repo, NULL);
free(remoteurl);
free(remote);
} else if (is_dir_sep(repo[0]) || strchr(repo, ':')) {
realrepo = repo;
} else {
die(_("repo URL: '%s' must be absolute or begin with ./|../"),
repo);
}
/*
* normalize path:
* multiple //; leading ./; /./; /../;
*/
normalize_path_copy(path, path);
/* strip trailing '/' */
if (is_dir_sep(path[strlen(path) -1]))
path[strlen(path) - 1] = '\0';
if (check_sm_exists(force, path))
return 1;
strbuf_addstr(&sb, path);
if (is_nonbare_repository_dir(&sb)) {
struct object_id oid;
if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
die(_("'%s' does not have a commit checked out"), path);
}
if (!force) {
struct strbuf sb = STRBUF_INIT;
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
cp.no_stdout = 1;
strvec_pushl(&cp.args, "add", "--dry-run", "--ignore-missing",
"--no-warn-embedded-repo", path, NULL);
if (pipe_command(&cp, NULL, 0, NULL, 0, &sb, 0)) {
fprintf(stderr, _("%s"), sb.buf);
return 1;
}
strbuf_release(&sb);
}
name = custom_name ? custom_name : path;
if (check_submodule_name(name))
die(_("'%s' is not a valid submodule name"), name);
info.prefix = prefix;
info.sm_name = name;
info.sm_path = path;
info.repo = repo;
info.realrepo = realrepo;
info.reference_path = reference_path;
info.branch = branch;
info.depth = depth;
info.progress = !!progress;
info.dissociate = !!dissociate;
info.force = !!force;
info.quiet = !!quiet;
if (add_submodule(&info))
return 1;
config_added_submodule(&info);
free(path);
return 0;
}
#define SUPPORT_SUPER_PREFIX (1<<0)
struct cmd_struct {
......@@ -2777,6 +3165,7 @@ static struct cmd_struct commands[] = {
{"config", module_config, 0},
{"set-url", module_set_url, 0},
{"set-branch", module_set_branch, 0},
{"add", module_add, SUPPORT_SUPER_PREFIX},
};
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
......
......@@ -1655,12 +1655,6 @@ struct dir_entry *dir_add_ignored(struct dir_struct *dir,
return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, len);
}
enum exist_status {
index_nonexistent = 0,
index_directory,
index_gitdir
};
/*
* Do not use the alphabetically sorted index to look up
* the directory name; instead, use the case insensitive
......@@ -1688,8 +1682,8 @@ static enum exist_status directory_exists_in_index_icase(struct index_state *ist
* the files it contains) will sort with the '/' at the
* end.
*/
static enum exist_status directory_exists_in_index(struct index_state *istate,
const char *dirname, int len)
enum exist_status directory_exists_in_index(struct index_state *istate,
const char *dirname, int len)
{
int pos;
......
......@@ -370,6 +370,15 @@ int read_directory(struct dir_struct *, struct index_state *istate,
const char *path, int len,
const struct pathspec *pathspec);
enum exist_status {
index_nonexistent = 0,
index_directory,
index_gitdir
};
enum exist_status directory_exists_in_index(struct index_state *istate,
const char *dirname, int len);
enum pattern_match_result {
UNDECIDED = -1,
NOT_MATCHED = 0,
......
......@@ -146,166 +146,7 @@ cmd_add()
shift
done
if ! git submodule--helper config --check-writeable >/dev/null 2>&1
then
die "$(eval_gettext "please make sure that the .gitmodules file is in the working tree")"
fi
if test -n "$reference_path"
then
is_absolute_path "$reference_path" ||
reference_path="$wt_prefix$reference_path"
reference="--reference=$reference_path"
fi
repo=$1
sm_path=$2
if test -z "$sm_path"; then
sm_path=$(printf '%s\n' "$repo" |
sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
fi
if test -z "$repo" || test -z "$sm_path"; then
usage
fi
is_absolute_path "$sm_path" || sm_path="$wt_prefix$sm_path"
# assure repo is absolute or relative to parent
case "$repo" in
./*|../*)
test -z "$wt_prefix" ||
die "$(gettext "Relative path can only be used from the toplevel of the working tree")"
# dereference source url relative to parent's url
realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit
;;
*:*|/*)
# absolute url
realrepo=$repo
;;
*)
die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")"
;;
esac
# normalize path:
# multiple //; leading ./; /./; /../; trailing /
sm_path=$(printf '%s/\n' "$sm_path" |
sed -e '
s|//*|/|g
s|^\(\./\)*||
s|/\(\./\)*|/|g
:start
s|\([^/]*\)/\.\./||
tstart
s|/*$||
')
if test -z "$force"
then
git ls-files --error-unmatch "$sm_path" > /dev/null 2>&1 &&
die "$(eval_gettext "'\$sm_path' already exists in the index")"
else
git ls-files -s "$sm_path" | sane_grep -v "^160000" > /dev/null 2>&1 &&
die "$(eval_gettext "'\$sm_path' already exists in the index and is not a submodule")"
fi
if test -d "$sm_path" &&
test -z $(git -C "$sm_path" rev-parse --show-cdup 2>/dev/null)
then
git -C "$sm_path" rev-parse --verify -q HEAD >/dev/null ||
die "$(eval_gettext "'\$sm_path' does not have a commit checked out")"
fi
if test -z "$force"
then
dryerr=$(git add --dry-run --ignore-missing --no-warn-embedded-repo "$sm_path" 2>&1 >/dev/null)
res=$?
if test $res -ne 0
then
echo >&2 "$dryerr"
exit $res
fi
fi
if test -n "$custom_name"
then
sm_name="$custom_name"
else
sm_name="$sm_path"
fi
if ! git submodule--helper check-name "$sm_name"
then
die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
fi
# perhaps the path exists and is already a git repo, else clone it
if test -e "$sm_path"
then
if test -d "$sm_path"/.git || test -f "$sm_path"/.git
then
eval_gettextln "Adding existing repo at '\$sm_path' to the index"
else
die "$(eval_gettext "'\$sm_path' already exists and is not a valid git repo")"
fi
else
if test -d ".git/modules/$sm_name"
then
if test -z "$force"
then
eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2
die "$(eval_gettextln "\
If you want to reuse this local git directory instead of cloning again from
\$realrepo
use the '--force' option. If the local git directory is not the correct repo
or you are unsure what this means choose another name with the '--name' option.")"
else
eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
fi
fi
git submodule--helper clone ${GIT_QUIET:+--quiet} ${progress:+"--progress"} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${dissociate:+"--dissociate"} ${depth:+"$depth"} || exit
(
sanitize_submodule_env
cd "$sm_path" &&
# ash fails to wordsplit ${branch:+-b "$branch"...}
case "$branch" in
'') git checkout -f -q ;;
?*) git checkout -f -q -B "$branch" "origin/$branch" ;;
esac
) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")"
fi
git config submodule."$sm_name".url "$realrepo"
git add --no-warn-embedded-repo $force "$sm_path" ||
die "$(eval_gettext "Failed to add submodule '\$sm_path'")"
git submodule--helper config submodule."$sm_name".path "$sm_path" &&
git submodule--helper config submodule."$sm_name".url "$repo" &&
if test -n "$branch"
then
git submodule--helper config submodule."$sm_name".branch "$branch"
fi &&
git add --force .gitmodules ||
die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
# NEEDSWORK: In a multi-working-tree world, this needs to be
# set in the per-worktree config.
if git config --get submodule.active >/dev/null
then
# If the submodule being adding isn't already covered by the
# current configured pathspec, set the submodule's active flag
if ! git submodule--helper is-active "$sm_path"
then
git config submodule."$sm_name".active "true"
fi
else
git config submodule."$sm_name".active "true"
fi
git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper add ${force:+--force} ${GIT_QUIET:+--quiet} ${progress:+--progress} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@"
}
#
......
......@@ -48,7 +48,7 @@ test_expect_success 'submodule update aborts on missing gitmodules url' '
test_expect_success 'add aborts on repository with no commits' '
cat >expect <<-\EOF &&
'"'repo-no-commits'"' does not have a commit checked out
fatal: '"'repo-no-commits'"' does not have a commit checked out
EOF
git init repo-no-commits &&
test_must_fail git submodule add ../a ./repo-no-commits 2>actual &&
......@@ -193,6 +193,17 @@ test_expect_success 'submodule add to .gitignored path with --force' '
)
'
test_expect_success 'submodule add to path with tracked contents fails' '
(
cd addtest-ignore &&
mkdir track &&
git add -f track &&
git commit -m "add tracked path" &&
! git submodule add "$submodurl" submod >output 2>&1 &&
test_file_not_empty output
)
'
test_expect_success 'submodule add to reconfigure existing submodule with --force' '
(
cd addtest-ignore &&
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册