Merge branch 'hv/fetch-moved-submodules-on-demand'
"git fetch --recurse-submodules" now knows that submodules can be moved around in the superproject in addition to getting updated, and finds the ones that need to be fetched accordingly. * hv/fetch-moved-submodules-on-demand: submodule: simplify decision tree whether to or not to fetch implement fetching of moved submodules fetch: add test to make sure we stay backwards compatible
This commit is contained in:
commit
b4d658b501
@ -22,6 +22,9 @@ struct submodule {
|
||||
int recommend_shallow;
|
||||
};
|
||||
|
||||
#define SUBMODULE_INIT { NULL, NULL, NULL, RECURSE_SUBMODULES_NONE, \
|
||||
NULL, NULL, SUBMODULE_UPDATE_STRATEGY_INIT, {0}, -1 };
|
||||
|
||||
struct submodule_cache;
|
||||
struct repository;
|
||||
|
||||
|
202
submodule.c
202
submodule.c
@ -21,7 +21,7 @@
|
||||
#include "parse-options.h"
|
||||
|
||||
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
|
||||
static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
|
||||
static struct string_list changed_submodule_names = STRING_LIST_INIT_DUP;
|
||||
static int initialized_fetch_ref_tips;
|
||||
static struct oid_array ref_tips_before_fetch;
|
||||
static struct oid_array ref_tips_after_fetch;
|
||||
@ -674,11 +674,11 @@ const struct submodule *submodule_from_ce(const struct cache_entry *ce)
|
||||
}
|
||||
|
||||
static struct oid_array *submodule_commits(struct string_list *submodules,
|
||||
const char *path)
|
||||
const char *name)
|
||||
{
|
||||
struct string_list_item *item;
|
||||
|
||||
item = string_list_insert(submodules, path);
|
||||
item = string_list_insert(submodules, name);
|
||||
if (item->util)
|
||||
return (struct oid_array *) item->util;
|
||||
|
||||
@ -687,40 +687,68 @@ static struct oid_array *submodule_commits(struct string_list *submodules,
|
||||
return (struct oid_array *) item->util;
|
||||
}
|
||||
|
||||
struct collect_changed_submodules_cb_data {
|
||||
struct string_list *changed;
|
||||
const struct object_id *commit_oid;
|
||||
};
|
||||
|
||||
/*
|
||||
* this would normally be two functions: default_name_from_path() and
|
||||
* path_from_default_name(). Since the default name is the same as
|
||||
* the submodule path we can get away with just one function which only
|
||||
* checks whether there is a submodule in the working directory at that
|
||||
* location.
|
||||
*/
|
||||
static const char *default_name_or_path(const char *path_or_name)
|
||||
{
|
||||
int error_code;
|
||||
|
||||
if (!is_submodule_populated_gently(path_or_name, &error_code))
|
||||
return NULL;
|
||||
|
||||
return path_or_name;
|
||||
}
|
||||
|
||||
static void collect_changed_submodules_cb(struct diff_queue_struct *q,
|
||||
struct diff_options *options,
|
||||
void *data)
|
||||
{
|
||||
struct collect_changed_submodules_cb_data *me = data;
|
||||
struct string_list *changed = me->changed;
|
||||
const struct object_id *commit_oid = me->commit_oid;
|
||||
int i;
|
||||
struct string_list *changed = data;
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
struct oid_array *commits;
|
||||
const struct submodule *submodule;
|
||||
const char *name;
|
||||
|
||||
if (!S_ISGITLINK(p->two->mode))
|
||||
continue;
|
||||
|
||||
if (S_ISGITLINK(p->one->mode)) {
|
||||
/*
|
||||
* NEEDSWORK: We should honor the name configured in
|
||||
* the .gitmodules file of the commit we are examining
|
||||
* here to be able to correctly follow submodules
|
||||
* being moved around.
|
||||
*/
|
||||
commits = submodule_commits(changed, p->two->path);
|
||||
oid_array_append(commits, &p->two->oid);
|
||||
} else {
|
||||
/* Submodule is new or was moved here */
|
||||
/*
|
||||
* NEEDSWORK: When the .git directories of submodules
|
||||
* live inside the superprojects .git directory some
|
||||
* day we should fetch new submodules directly into
|
||||
* that location too when config or options request
|
||||
* that so they can be checked out from there.
|
||||
*/
|
||||
continue;
|
||||
submodule = submodule_from_path(commit_oid, p->two->path);
|
||||
if (submodule)
|
||||
name = submodule->name;
|
||||
else {
|
||||
name = default_name_or_path(p->two->path);
|
||||
/* make sure name does not collide with existing one */
|
||||
submodule = submodule_from_name(commit_oid, name);
|
||||
if (submodule) {
|
||||
warning("Submodule in commit %s at path: "
|
||||
"'%s' collides with a submodule named "
|
||||
"the same. Skipping it.",
|
||||
oid_to_hex(commit_oid), name);
|
||||
name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
commits = submodule_commits(changed, name);
|
||||
oid_array_append(commits, &p->two->oid);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -742,11 +770,14 @@ static void collect_changed_submodules(struct string_list *changed,
|
||||
|
||||
while ((commit = get_revision(&rev))) {
|
||||
struct rev_info diff_rev;
|
||||
struct collect_changed_submodules_cb_data data;
|
||||
data.changed = changed;
|
||||
data.commit_oid = &commit->object.oid;
|
||||
|
||||
init_revisions(&diff_rev, NULL);
|
||||
diff_rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
|
||||
diff_rev.diffopt.format_callback = collect_changed_submodules_cb;
|
||||
diff_rev.diffopt.format_callback_data = changed;
|
||||
diff_rev.diffopt.format_callback_data = &data;
|
||||
diff_tree_combined_merge(commit, 1, &diff_rev);
|
||||
}
|
||||
|
||||
@ -894,7 +925,7 @@ int find_unpushed_submodules(struct oid_array *commits,
|
||||
const char *remotes_name, struct string_list *needs_pushing)
|
||||
{
|
||||
struct string_list submodules = STRING_LIST_INIT_DUP;
|
||||
struct string_list_item *submodule;
|
||||
struct string_list_item *name;
|
||||
struct argv_array argv = ARGV_ARRAY_INIT;
|
||||
|
||||
/* argv.argv[0] will be ignored by setup_revisions */
|
||||
@ -905,9 +936,19 @@ int find_unpushed_submodules(struct oid_array *commits,
|
||||
|
||||
collect_changed_submodules(&submodules, &argv);
|
||||
|
||||
for_each_string_list_item(submodule, &submodules) {
|
||||
struct oid_array *commits = submodule->util;
|
||||
const char *path = submodule->string;
|
||||
for_each_string_list_item(name, &submodules) {
|
||||
struct oid_array *commits = name->util;
|
||||
const struct submodule *submodule;
|
||||
const char *path = NULL;
|
||||
|
||||
submodule = submodule_from_name(&null_oid, name->string);
|
||||
if (submodule)
|
||||
path = submodule->path;
|
||||
else
|
||||
path = default_name_or_path(name->string);
|
||||
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
if (submodule_needs_pushing(path, commits))
|
||||
string_list_insert(needs_pushing, path);
|
||||
@ -1065,7 +1106,7 @@ static void calculate_changed_submodule_paths(void)
|
||||
{
|
||||
struct argv_array argv = ARGV_ARRAY_INIT;
|
||||
struct string_list changed_submodules = STRING_LIST_INIT_DUP;
|
||||
const struct string_list_item *item;
|
||||
const struct string_list_item *name;
|
||||
|
||||
/* No need to check if there are no submodules configured */
|
||||
if (!submodule_from_path(NULL, NULL))
|
||||
@ -1080,16 +1121,26 @@ static void calculate_changed_submodule_paths(void)
|
||||
|
||||
/*
|
||||
* Collect all submodules (whether checked out or not) for which new
|
||||
* commits have been recorded upstream in "changed_submodule_paths".
|
||||
* commits have been recorded upstream in "changed_submodule_names".
|
||||
*/
|
||||
collect_changed_submodules(&changed_submodules, &argv);
|
||||
|
||||
for_each_string_list_item(item, &changed_submodules) {
|
||||
struct oid_array *commits = item->util;
|
||||
const char *path = item->string;
|
||||
for_each_string_list_item(name, &changed_submodules) {
|
||||
struct oid_array *commits = name->util;
|
||||
const struct submodule *submodule;
|
||||
const char *path = NULL;
|
||||
|
||||
submodule = submodule_from_name(&null_oid, name->string);
|
||||
if (submodule)
|
||||
path = submodule->path;
|
||||
else
|
||||
path = default_name_or_path(name->string);
|
||||
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
if (!submodule_has_commits(path, commits))
|
||||
string_list_append(&changed_submodule_paths, path);
|
||||
string_list_append(&changed_submodule_names, name->string);
|
||||
}
|
||||
|
||||
free_submodules_oids(&changed_submodules);
|
||||
@ -1136,6 +1187,31 @@ struct submodule_parallel_fetch {
|
||||
};
|
||||
#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
|
||||
|
||||
static int get_fetch_recurse_config(const struct submodule *submodule,
|
||||
struct submodule_parallel_fetch *spf)
|
||||
{
|
||||
if (spf->command_line_option != RECURSE_SUBMODULES_DEFAULT)
|
||||
return spf->command_line_option;
|
||||
|
||||
if (submodule) {
|
||||
char *key;
|
||||
const char *value;
|
||||
|
||||
int fetch_recurse = submodule->fetch_recurse;
|
||||
key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
|
||||
if (!repo_config_get_string_const(the_repository, key, &value)) {
|
||||
fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
|
||||
}
|
||||
free(key);
|
||||
|
||||
if (fetch_recurse != RECURSE_SUBMODULES_NONE)
|
||||
/* local config overrules everything except commandline */
|
||||
return fetch_recurse;
|
||||
}
|
||||
|
||||
return spf->default_option;
|
||||
}
|
||||
|
||||
static int get_next_submodule(struct child_process *cp,
|
||||
struct strbuf *err, void *data, void **task_cb)
|
||||
{
|
||||
@ -1149,49 +1225,35 @@ static int get_next_submodule(struct child_process *cp,
|
||||
const struct cache_entry *ce = active_cache[spf->count];
|
||||
const char *git_dir, *default_argv;
|
||||
const struct submodule *submodule;
|
||||
struct submodule default_submodule = SUBMODULE_INIT;
|
||||
|
||||
if (!S_ISGITLINK(ce->ce_mode))
|
||||
continue;
|
||||
|
||||
submodule = submodule_from_path(&null_oid, ce->name);
|
||||
if (!submodule) {
|
||||
const char *name = default_name_or_path(ce->name);
|
||||
if (name) {
|
||||
default_submodule.path = default_submodule.name = name;
|
||||
submodule = &default_submodule;
|
||||
}
|
||||
}
|
||||
|
||||
switch (get_fetch_recurse_config(submodule, spf))
|
||||
{
|
||||
default:
|
||||
case RECURSE_SUBMODULES_DEFAULT:
|
||||
case RECURSE_SUBMODULES_ON_DEMAND:
|
||||
if (!submodule || !unsorted_string_list_lookup(&changed_submodule_names,
|
||||
submodule->name))
|
||||
continue;
|
||||
default_argv = "on-demand";
|
||||
break;
|
||||
case RECURSE_SUBMODULES_ON:
|
||||
default_argv = "yes";
|
||||
if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) {
|
||||
int fetch_recurse = RECURSE_SUBMODULES_NONE;
|
||||
|
||||
if (submodule) {
|
||||
char *key;
|
||||
const char *value;
|
||||
|
||||
fetch_recurse = submodule->fetch_recurse;
|
||||
key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
|
||||
if (!repo_config_get_string_const(the_repository, key, &value)) {
|
||||
fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
|
||||
}
|
||||
free(key);
|
||||
}
|
||||
|
||||
if (fetch_recurse != RECURSE_SUBMODULES_NONE) {
|
||||
if (fetch_recurse == RECURSE_SUBMODULES_OFF)
|
||||
break;
|
||||
case RECURSE_SUBMODULES_OFF:
|
||||
continue;
|
||||
if (fetch_recurse == RECURSE_SUBMODULES_ON_DEMAND) {
|
||||
if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
|
||||
continue;
|
||||
default_argv = "on-demand";
|
||||
}
|
||||
} else {
|
||||
if (spf->default_option == RECURSE_SUBMODULES_OFF)
|
||||
continue;
|
||||
if (spf->default_option == RECURSE_SUBMODULES_ON_DEMAND) {
|
||||
if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
|
||||
continue;
|
||||
default_argv = "on-demand";
|
||||
}
|
||||
}
|
||||
} else if (spf->command_line_option == RECURSE_SUBMODULES_ON_DEMAND) {
|
||||
if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
|
||||
continue;
|
||||
default_argv = "on-demand";
|
||||
}
|
||||
|
||||
strbuf_addf(&submodule_path, "%s/%s", spf->work_tree, ce->name);
|
||||
@ -1282,7 +1344,7 @@ int fetch_populated_submodules(const struct argv_array *options,
|
||||
|
||||
argv_array_clear(&spf.args);
|
||||
out:
|
||||
string_list_clear(&changed_submodule_paths, 1);
|
||||
string_list_clear(&changed_submodule_names, 1);
|
||||
return spf.result;
|
||||
}
|
||||
|
||||
|
@ -478,7 +478,47 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea
|
||||
git fetch >../actual.out 2>../actual.err
|
||||
) &&
|
||||
! test -s actual.out &&
|
||||
test_i18ncmp expect.err actual.err
|
||||
test_i18ncmp expect.err actual.err &&
|
||||
(
|
||||
cd submodule &&
|
||||
git checkout -q master
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .gitmodule entry" '
|
||||
(
|
||||
cd downstream &&
|
||||
git fetch --recurse-submodules
|
||||
) &&
|
||||
add_upstream_commit &&
|
||||
head1=$(git rev-parse --short HEAD) &&
|
||||
git add submodule &&
|
||||
git rm .gitmodules &&
|
||||
git commit -m "new submodule without .gitmodules" &&
|
||||
printf "" >expect.out &&
|
||||
head2=$(git rev-parse --short HEAD) &&
|
||||
echo "From $pwd/." >expect.err.2 &&
|
||||
echo " $head1..$head2 master -> origin/master" >>expect.err.2 &&
|
||||
head -3 expect.err >>expect.err.2 &&
|
||||
(
|
||||
cd downstream &&
|
||||
rm .gitmodules &&
|
||||
git config fetch.recurseSubmodules on-demand &&
|
||||
# fake submodule configuration to avoid skipping submodule handling
|
||||
git config -f .gitmodules submodule.fake.path fake &&
|
||||
git config -f .gitmodules submodule.fake.url fakeurl &&
|
||||
git add .gitmodules &&
|
||||
git config --unset submodule.submodule.url &&
|
||||
git fetch >../actual.out 2>../actual.err &&
|
||||
# cleanup
|
||||
git config --unset fetch.recurseSubmodules &&
|
||||
git reset --hard
|
||||
) &&
|
||||
test_i18ncmp expect.out actual.out &&
|
||||
test_i18ncmp expect.err.2 actual.err &&
|
||||
git checkout HEAD^ -- .gitmodules &&
|
||||
git add .gitmodules &&
|
||||
git commit -m "new submodule restored .gitmodules"
|
||||
'
|
||||
|
||||
test_expect_success 'fetching submodules respects parallel settings' '
|
||||
@ -530,4 +570,39 @@ test_expect_success 'fetching submodule into a broken repository' '
|
||||
test_must_fail git -C dst fetch --recurse-submodules
|
||||
'
|
||||
|
||||
test_expect_success "fetch new commits when submodule got renamed" '
|
||||
git clone . downstream_rename &&
|
||||
(
|
||||
cd downstream_rename &&
|
||||
git submodule update --init &&
|
||||
# NEEDSWORK: we omitted --recursive for the submodule update here since
|
||||
# that does not work. See test 7001 for mv "moving nested submodules"
|
||||
# for details. Once that is fixed we should add the --recursive option
|
||||
# here.
|
||||
git checkout -b rename &&
|
||||
git mv submodule submodule_renamed &&
|
||||
(
|
||||
cd submodule_renamed &&
|
||||
git checkout -b rename_sub &&
|
||||
echo a >a &&
|
||||
git add a &&
|
||||
git commit -ma &&
|
||||
git push origin rename_sub &&
|
||||
git rev-parse HEAD >../../expect
|
||||
) &&
|
||||
git add submodule_renamed &&
|
||||
git commit -m "update renamed submodule" &&
|
||||
git push origin rename
|
||||
) &&
|
||||
(
|
||||
cd downstream &&
|
||||
git fetch --recurse-submodules=on-demand &&
|
||||
(
|
||||
cd submodule &&
|
||||
git rev-parse origin/rename_sub >../../actual
|
||||
)
|
||||
) &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user