Merge branch 'sb/submodule-init'
Update of "git submodule" to move pieces of logic to C continues. * sb/submodule-init: submodule init: redirect stdout to stderr submodule--helper update-clone: abort gracefully on missing .gitmodules submodule init: fail gracefully with a missing .gitmodules file submodule: port init from shell to C submodule: port resolve_relative_url from shell to C
This commit is contained in:
@ -9,6 +9,211 @@
|
||||
#include "submodule-config.h"
|
||||
#include "string-list.h"
|
||||
#include "run-command.h"
|
||||
#include "remote.h"
|
||||
#include "refs.h"
|
||||
#include "connect.h"
|
||||
|
||||
static char *get_default_remote(void)
|
||||
{
|
||||
char *dest = NULL, *ret;
|
||||
unsigned char sha1[20];
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
|
||||
|
||||
if (!refname)
|
||||
die(_("No such ref: %s"), "HEAD");
|
||||
|
||||
/* detached HEAD */
|
||||
if (!strcmp(refname, "HEAD"))
|
||||
return xstrdup("origin");
|
||||
|
||||
if (!skip_prefix(refname, "refs/heads/", &refname))
|
||||
die(_("Expecting a full ref name, got %s"), refname);
|
||||
|
||||
strbuf_addf(&sb, "branch.%s.remote", refname);
|
||||
if (git_config_get_string(sb.buf, &dest))
|
||||
ret = xstrdup("origin");
|
||||
else
|
||||
ret = dest;
|
||||
|
||||
strbuf_release(&sb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int starts_with_dot_slash(const char *str)
|
||||
{
|
||||
return str[0] == '.' && is_dir_sep(str[1]);
|
||||
}
|
||||
|
||||
static int starts_with_dot_dot_slash(const char *str)
|
||||
{
|
||||
return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 1 if it was the last chop before ':'.
|
||||
*/
|
||||
static int chop_last_dir(char **remoteurl, int is_relative)
|
||||
{
|
||||
char *rfind = find_last_dir_sep(*remoteurl);
|
||||
if (rfind) {
|
||||
*rfind = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
rfind = strrchr(*remoteurl, ':');
|
||||
if (rfind) {
|
||||
*rfind = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (is_relative || !strcmp(".", *remoteurl))
|
||||
die(_("cannot strip one component off url '%s'"),
|
||||
*remoteurl);
|
||||
|
||||
free(*remoteurl);
|
||||
*remoteurl = xstrdup(".");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `url` argument is the URL that navigates to the submodule origin
|
||||
* repo. When relative, this URL is relative to the superproject origin
|
||||
* URL repo. The `up_path` argument, if specified, is the relative
|
||||
* path that navigates from the submodule working tree to the superproject
|
||||
* working tree. Returns the origin URL of the submodule.
|
||||
*
|
||||
* Return either an absolute URL or filesystem path (if the superproject
|
||||
* origin URL is an absolute URL or filesystem path, respectively) or a
|
||||
* relative file system path (if the superproject origin URL is a relative
|
||||
* file system path).
|
||||
*
|
||||
* When the output is a relative file system path, the path is either
|
||||
* relative to the submodule working tree, if up_path is specified, or to
|
||||
* the superproject working tree otherwise.
|
||||
*
|
||||
* NEEDSWORK: This works incorrectly on the domain and protocol part.
|
||||
* remote_url url outcome expectation
|
||||
* http://a.com/b ../c http://a.com/c as is
|
||||
* http://a.com/b ../../c http://c error out
|
||||
* http://a.com/b ../../../c http:/c error out
|
||||
* http://a.com/b ../../../../c http:c error out
|
||||
* http://a.com/b ../../../../../c .:c error out
|
||||
* NEEDSWORK: Given how chop_last_dir() works, this function is broken
|
||||
* when a local part has a colon in its path component, too.
|
||||
*/
|
||||
static char *relative_url(const char *remote_url,
|
||||
const char *url,
|
||||
const char *up_path)
|
||||
{
|
||||
int is_relative = 0;
|
||||
int colonsep = 0;
|
||||
char *out;
|
||||
char *remoteurl = xstrdup(remote_url);
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
size_t len = strlen(remoteurl);
|
||||
|
||||
if (is_dir_sep(remoteurl[len]))
|
||||
remoteurl[len] = '\0';
|
||||
|
||||
if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
|
||||
is_relative = 0;
|
||||
else {
|
||||
is_relative = 1;
|
||||
/*
|
||||
* Prepend a './' to ensure all relative
|
||||
* remoteurls start with './' or '../'
|
||||
*/
|
||||
if (!starts_with_dot_slash(remoteurl) &&
|
||||
!starts_with_dot_dot_slash(remoteurl)) {
|
||||
strbuf_reset(&sb);
|
||||
strbuf_addf(&sb, "./%s", remoteurl);
|
||||
free(remoteurl);
|
||||
remoteurl = strbuf_detach(&sb, NULL);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* When the url starts with '../', remove that and the
|
||||
* last directory in remoteurl.
|
||||
*/
|
||||
while (url) {
|
||||
if (starts_with_dot_dot_slash(url)) {
|
||||
url += 3;
|
||||
colonsep |= chop_last_dir(&remoteurl, is_relative);
|
||||
} else if (starts_with_dot_slash(url))
|
||||
url += 2;
|
||||
else
|
||||
break;
|
||||
}
|
||||
strbuf_reset(&sb);
|
||||
strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
|
||||
free(remoteurl);
|
||||
|
||||
if (starts_with_dot_slash(sb.buf))
|
||||
out = xstrdup(sb.buf + 2);
|
||||
else
|
||||
out = xstrdup(sb.buf);
|
||||
strbuf_reset(&sb);
|
||||
|
||||
if (!up_path || !is_relative)
|
||||
return out;
|
||||
|
||||
strbuf_addf(&sb, "%s%s", up_path, out);
|
||||
free(out);
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
static int resolve_relative_url(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
char *remoteurl = NULL;
|
||||
char *remote = get_default_remote();
|
||||
const char *up_path = NULL;
|
||||
char *res;
|
||||
const char *url;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
if (argc != 2 && argc != 3)
|
||||
die("resolve-relative-url only accepts one or two arguments");
|
||||
|
||||
url = argv[1];
|
||||
strbuf_addf(&sb, "remote.%s.url", remote);
|
||||
free(remote);
|
||||
|
||||
if (git_config_get_string(sb.buf, &remoteurl))
|
||||
/* the repository is its own authoritative upstream */
|
||||
remoteurl = xgetcwd();
|
||||
|
||||
if (argc == 3)
|
||||
up_path = argv[2];
|
||||
|
||||
res = relative_url(remoteurl, url, up_path);
|
||||
puts(res);
|
||||
free(res);
|
||||
free(remoteurl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
char *remoteurl, *res;
|
||||
const char *up_path, *url;
|
||||
|
||||
if (argc != 4)
|
||||
die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>");
|
||||
|
||||
up_path = argv[1];
|
||||
remoteurl = xstrdup(argv[2]);
|
||||
url = argv[3];
|
||||
|
||||
if (!strcmp(up_path, "(null)"))
|
||||
up_path = NULL;
|
||||
|
||||
res = relative_url(remoteurl, url, up_path);
|
||||
puts(res);
|
||||
free(res);
|
||||
free(remoteurl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct module_list {
|
||||
const struct cache_entry **entries;
|
||||
@ -100,6 +305,125 @@ static int module_list(int argc, const char **argv, const char *prefix)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void init_submodule(const char *path, const char *prefix, int quiet)
|
||||
{
|
||||
const struct submodule *sub;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
char *upd = NULL, *url = NULL, *displaypath;
|
||||
|
||||
/* Only loads from .gitmodules, no overlay with .git/config */
|
||||
gitmodules_config();
|
||||
|
||||
if (prefix) {
|
||||
strbuf_addf(&sb, "%s%s", prefix, path);
|
||||
displaypath = strbuf_detach(&sb, NULL);
|
||||
} else
|
||||
displaypath = xstrdup(path);
|
||||
|
||||
sub = submodule_from_path(null_sha1, path);
|
||||
|
||||
if (!sub)
|
||||
die(_("No url found for submodule path '%s' in .gitmodules"),
|
||||
displaypath);
|
||||
|
||||
/*
|
||||
* Copy url setting when it is not set yet.
|
||||
* To look up the url in .git/config, we must not fall back to
|
||||
* .gitmodules, so look it up directly.
|
||||
*/
|
||||
strbuf_reset(&sb);
|
||||
strbuf_addf(&sb, "submodule.%s.url", sub->name);
|
||||
if (git_config_get_string(sb.buf, &url)) {
|
||||
url = xstrdup(sub->url);
|
||||
|
||||
if (!url)
|
||||
die(_("No url found for submodule path '%s' in .gitmodules"),
|
||||
displaypath);
|
||||
|
||||
/* Possibly a url relative to parent */
|
||||
if (starts_with_dot_dot_slash(url) ||
|
||||
starts_with_dot_slash(url)) {
|
||||
char *remoteurl, *relurl;
|
||||
char *remote = get_default_remote();
|
||||
struct strbuf remotesb = STRBUF_INIT;
|
||||
strbuf_addf(&remotesb, "remote.%s.url", remote);
|
||||
free(remote);
|
||||
|
||||
if (git_config_get_string(remotesb.buf, &remoteurl))
|
||||
/*
|
||||
* The repository is its own
|
||||
* authoritative upstream
|
||||
*/
|
||||
remoteurl = xgetcwd();
|
||||
relurl = relative_url(remoteurl, url, NULL);
|
||||
strbuf_release(&remotesb);
|
||||
free(remoteurl);
|
||||
free(url);
|
||||
url = relurl;
|
||||
}
|
||||
|
||||
if (git_config_set_gently(sb.buf, url))
|
||||
die(_("Failed to register url for submodule path '%s'"),
|
||||
displaypath);
|
||||
if (!quiet)
|
||||
fprintf(stderr,
|
||||
_("Submodule '%s' (%s) registered for path '%s'\n"),
|
||||
sub->name, url, displaypath);
|
||||
}
|
||||
|
||||
/* Copy "update" setting when it is not set yet */
|
||||
strbuf_reset(&sb);
|
||||
strbuf_addf(&sb, "submodule.%s.update", sub->name);
|
||||
if (git_config_get_string(sb.buf, &upd) &&
|
||||
sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
|
||||
if (sub->update_strategy.type == SM_UPDATE_COMMAND) {
|
||||
fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"),
|
||||
sub->name);
|
||||
upd = xstrdup("none");
|
||||
} else
|
||||
upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy));
|
||||
|
||||
if (git_config_set_gently(sb.buf, upd))
|
||||
die(_("Failed to register update mode for submodule path '%s'"), displaypath);
|
||||
}
|
||||
strbuf_release(&sb);
|
||||
free(displaypath);
|
||||
free(url);
|
||||
free(upd);
|
||||
}
|
||||
|
||||
static int module_init(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct pathspec pathspec;
|
||||
struct module_list list = MODULE_LIST_INIT;
|
||||
int quiet = 0;
|
||||
int i;
|
||||
|
||||
struct option module_init_options[] = {
|
||||
OPT_STRING(0, "prefix", &prefix,
|
||||
N_("path"),
|
||||
N_("alternative anchor for relative paths")),
|
||||
OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
const char *const git_submodule_helper_usage[] = {
|
||||
N_("git submodule--helper init [<path>]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
argc = parse_options(argc, argv, prefix, module_init_options,
|
||||
git_submodule_helper_usage, 0);
|
||||
|
||||
if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < list.nr; i++)
|
||||
init_submodule(list.entries[i]->name, prefix, quiet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int module_name(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
const struct submodule *sub;
|
||||
@ -336,6 +660,25 @@ struct submodule_update_clone {
|
||||
SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \
|
||||
STRING_LIST_INIT_DUP, 0}
|
||||
|
||||
|
||||
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
|
||||
struct strbuf *out, const char *displaypath)
|
||||
{
|
||||
/*
|
||||
* Only mention uninitialized submodules when their
|
||||
* paths have been specified.
|
||||
*/
|
||||
if (suc->warn_if_uninitialized) {
|
||||
strbuf_addf(out,
|
||||
_("Submodule path '%s' not initialized"),
|
||||
displaypath);
|
||||
strbuf_addch(out, '\n');
|
||||
strbuf_addstr(out,
|
||||
_("Maybe you want to use 'update --init'?"));
|
||||
strbuf_addch(out, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to
|
||||
* run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise.
|
||||
@ -370,6 +713,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
|
||||
else
|
||||
displaypath = ce->name;
|
||||
|
||||
if (!sub) {
|
||||
next_submodule_warn_missing(suc, out, displaypath);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (suc->update.type == SM_UPDATE_NONE
|
||||
|| (suc->update.type == SM_UPDATE_UNSPECIFIED
|
||||
&& sub->update_strategy.type == SM_UPDATE_NONE)) {
|
||||
@ -387,19 +735,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
|
||||
strbuf_addf(&sb, "submodule.%s.url", sub->name);
|
||||
git_config_get_string(sb.buf, &url);
|
||||
if (!url) {
|
||||
/*
|
||||
* Only mention uninitialized submodules when their
|
||||
* path have been specified
|
||||
*/
|
||||
if (suc->warn_if_uninitialized) {
|
||||
strbuf_addf(out,
|
||||
_("Submodule path '%s' not initialized"),
|
||||
displaypath);
|
||||
strbuf_addch(out, '\n');
|
||||
strbuf_addstr(out,
|
||||
_("Maybe you want to use 'update --init'?"));
|
||||
strbuf_addch(out, '\n');
|
||||
}
|
||||
next_submodule_warn_missing(suc, out, displaypath);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@ -571,7 +907,10 @@ static struct cmd_struct commands[] = {
|
||||
{"name", module_name},
|
||||
{"clone", module_clone},
|
||||
{"sanitize-config", module_sanitize_config},
|
||||
{"update-clone", update_clone}
|
||||
{"update-clone", update_clone},
|
||||
{"resolve-relative-url", resolve_relative_url},
|
||||
{"resolve-relative-url-test", resolve_relative_url_test},
|
||||
{"init", module_init}
|
||||
};
|
||||
|
||||
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
|
||||
|
||||
Reference in New Issue
Block a user