summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Lehmann <Jens.Lehmann@web.de>2012-06-13 18:50:10 +0200
committerJunio C Hamano <gitster@pobox.com>2013-12-26 11:59:05 -0800
commitd4fa852597ffab711675528f2baccb1a0fb3bc82 (patch)
tree7dc8adec59992ce41b57730fb3acca261ce25895
parentde40c413f0e600668db1ccbf5bdf34694c29843f (diff)
downloadgit-jl/submodule-recursive-checkout.tar.gz
Teach checkout to recursively checkout submodulesjl/submodule-recursive-checkout
Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
-rw-r--r--Documentation/git-checkout.txt8
-rw-r--r--builtin/checkout.c14
-rw-r--r--submodule.c14
-rw-r--r--submodule.h3
-rwxr-xr-xt/t2013-checkout-submodule.sh215
5 files changed, 251 insertions, 3 deletions
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 91294f89c8..aabcc657b2 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,6 +225,14 @@ This means that you can use `git checkout -p` to selectively discard
edits from your current working tree. See the ``Interactive Mode''
section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
+--[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 until `-f` is used. If nothing (or --no-recurse-submodules)
+ is used, the work trees of submodules will not be updated, only the
+ hash recorded in the superproject will be changed.
+
<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
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5df3837e31..ac2f8d819e 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -21,6 +21,9 @@
#include "submodule.h"
#include "argv-array.h"
+static const char *recurse_submodules_default = "off";
+static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+
static const char * const checkout_usage[] = {
N_("git checkout [options] <branch>"),
N_("git checkout [options] [<branch>] -- <file>..."),
@@ -1111,6 +1114,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
N_("do not limit pathspecs to sparse entries only")),
OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
N_("second guess 'git checkout no-such-branch'")),
+ { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules,
+ "checkout", "control recursive updating of submodules",
+ PARSE_OPT_OPTARG, option_parse_update_submodules },
+ { OPTION_STRING, 0, "recurse-submodules-default",
+ &recurse_submodules_default, NULL,
+ "default mode for recursion", PARSE_OPT_HIDDEN },
OPT_END(),
};
@@ -1132,6 +1141,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
}
+ set_config_update_recurse_submodules(
+ parse_fetch_recurse_submodules_arg("--recurse-submodules-default",
+ recurse_submodules_default),
+ recurse_submodules);
+
if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
die(_("-b, -B and --orphan are mutually exclusive"));
diff --git a/submodule.c b/submodule.c
index 3365987d71..bdce1b283d 100644
--- a/submodule.c
+++ b/submodule.c
@@ -398,6 +398,20 @@ int parse_update_recurse_submodules_arg(const char *opt, const char *arg)
}
}
+int option_parse_update_submodules(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ *(int *)opt->value = RECURSE_SUBMODULES_OFF;
+ } else {
+ if (arg)
+ *(int *)opt->value = parse_update_recurse_submodules_arg(opt->long_name, arg);
+ else
+ *(int *)opt->value = RECURSE_SUBMODULES_ON;
+ }
+ return 0;
+}
+
int submodule_needs_update(const char *path)
{
struct string_list_item *path_option;
diff --git a/submodule.h b/submodule.h
index b42ae91eeb..9841da3669 100644
--- a/submodule.h
+++ b/submodule.h
@@ -3,6 +3,7 @@
struct diff_options;
struct argv_array;
+struct option;
enum {
RECURSE_SUBMODULES_ON_DEMAND = -1,
@@ -23,6 +24,8 @@ int parse_submodule_config_option(const char *var, const char *value);
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
int parse_update_recurse_submodules_arg(const char *opt, const char *arg);
+int option_parse_update_submodules(const struct option *opt,
+ const char *arg, int unset);
int submodule_needs_update(const char *path);
int populate_submodule(const char *path, unsigned char sha1[20], int force);
int depopulate_submodule(const char *path);
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
index 06b18f8bc1..bc3e1cab1c 100755
--- a/t/t2013-checkout-submodule.sh
+++ b/t/t2013-checkout-submodule.sh
@@ -4,17 +4,57 @@ test_description='checkout can handle submodules'
. ./test-lib.sh
+submodule_creation_must_succeed() {
+ # checkout base ($1)
+ git checkout -f --recurse-submodules $1 &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached $1 &&
+
+ # checkout target ($2)
+ if test -d submodule; then
+ echo change>>submodule/first.t &&
+ test_must_fail git checkout --recurse-submodules $2 &&
+ git checkout -f --recurse-submodules $2
+ else
+ git checkout --recurse-submodules $2
+ fi &&
+ test -e submodule/.git &&
+ test -f submodule/first.t &&
+ test -f submodule/second.t &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached $2
+}
+
+submodule_removal_must_succeed() {
+ # checkout base ($1)
+ git checkout -f --recurse-submodules $1 &&
+ git submodule update -f &&
+ test -e submodule/.git &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached $1 &&
+
+ # checkout target ($2)
+ echo change>>submodule/first.t &&
+ test_must_fail git checkout --recurse-submodules $2 &&
+ git checkout -f --recurse-submodules $2 &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached $2 &&
+ ! test -d submodule
+}
+
test_expect_success 'setup' '
mkdir submodule &&
(cd submodule &&
git init &&
test_commit first) &&
- git add submodule &&
+ echo first > file &&
+ git add file submodule &&
test_tick &&
git commit -m superproject &&
(cd submodule &&
test_commit second) &&
- git add submodule &&
+ echo second > file &&
+ git add file submodule &&
test_tick &&
git commit -m updated.superproject
'
@@ -36,7 +76,8 @@ test_expect_success '"checkout <submodule>" updates the index only' '
git checkout HEAD^ submodule &&
test_must_fail git diff-files --quiet &&
git checkout HEAD submodule &&
- git diff-files --quiet
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD
'
test_expect_success '"checkout <submodule>" honors diff.ignoreSubmodules' '
@@ -62,4 +103,172 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/
! test -s actual
'
+test_expect_success '"checkout --recurse-submodules" removes deleted submodule' '
+ git config -f .gitmodules submodule.submodule.path submodule &&
+ git config -f .gitmodules submodule.submodule.url submodule.bare &&
+ (cd submodule && git clone --bare . ../submodule.bare) &&
+ echo submodule.bare >>.gitignore &&
+ git config submodule.submodule.ignore none &&
+ git add .gitignore .gitmodules submodule &&
+ git submodule update --init &&
+ git commit -m "submodule registered" &&
+ git checkout -b base &&
+ git checkout -b delete_submodule &&
+ rm -rf submodule &&
+ git rm submodule &&
+ git commit -m "submodule deleted" &&
+ submodule_removal_must_succeed base delete_submodule
+'
+
+test_expect_success '"checkout --recurse-submodules" repopulates submodule' '
+ submodule_creation_must_succeed delete_submodule base
+'
+
+test_expect_success '"checkout --recurse-submodules" repopulates submodule in existing directory' '
+ git checkout --recurse-submodules delete_submodule &&
+ mkdir submodule &&
+ submodule_creation_must_succeed delete_submodule base
+'
+
+test_expect_success '"checkout --recurse-submodules" replaces submodule with files' '
+ git checkout -f base &&
+ git checkout -b replace_submodule_with_dir &&
+ git update-index --force-remove submodule &&
+ rm -rf submodule/.git .gitmodules &&
+ git add .gitmodules submodule/* &&
+ git commit -m "submodule replaced" &&
+ git checkout -f base &&
+ git submodule update -f &&
+ git checkout --recurse-submodules replace_submodule_with_dir &&
+ test -d submodule &&
+ ! test -e submodule/.git &&
+ test -f submodule/first.t &&
+ test -f submodule/second.t
+'
+
+test_expect_success '"checkout --recurse-submodules" removes files and repopulates submodule' '
+ submodule_creation_must_succeed replace_submodule_with_dir base
+'
+
+test_expect_failure '"checkout --recurse-submodules" replaces submodule with a file' '
+ git checkout -f base &&
+ git checkout -b replace_submodule_with_file &&
+ git update-index --force-remove submodule &&
+ rm -rf submodule .gitmodules &&
+ echo content >submodule &&
+ git add .gitmodules submodule &&
+ git commit -m "submodule replaced with file" &&
+ git checkout -f base &&
+ git submodule update -f &&
+ git checkout --recurse-submodules replace_submodule_with_file &&
+ test -d submodule &&
+ ! test -e submodule/.git &&
+ test -f submodule/first.t &&
+ test -f submodule/second.t
+'
+
+test_expect_success '"checkout --recurse-submodules" removes the file and repopulates submodule' '
+ submodule_creation_must_succeed replace_submodule_with_file base
+'
+
+test_expect_failure '"checkout --recurse-submodules" replaces submodule with a link' '
+ git checkout -f base &&
+ git checkout -b replace_submodule_with_link &&
+ git update-index --force-remove submodule &&
+ rm -rf submodule .gitmodules &&
+ ln -s submodule &&
+ git add .gitmodules submodule &&
+ git commit -m "submodule replaced with link" &&
+ git checkout -f base &&
+ git submodule update -f &&
+ git checkout --recurse-submodules replace_submodule_with_link &&
+ test -d submodule &&
+ ! test -e submodule/.git &&
+ test -f submodule/first.t &&
+ test -f submodule/second.t
+'
+
+test_expect_success '"checkout --recurse-submodules" removes the link and repopulates submodule' '
+ submodule_creation_must_succeed replace_submodule_with_link base
+'
+
+test_expect_success '"checkout --recurse-submodules" updates recursively' '
+ git checkout --recurse-submodules base &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD &&
+ git checkout -b updated_submodule &&
+ (cd submodule &&
+ echo x >>first.t &&
+ git add first.t &&
+ test_commit third) &&
+ git add submodule &&
+ test_tick &&
+ git commit -m updated.superproject &&
+ git checkout --recurse-submodules base &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD
+'
+
+test_expect_failure '"checkout --recurse-submodules" needs -f to update a modifed submodule commit' '
+ (
+ cd submodule &&
+ git checkout --recurse-submodules HEAD^
+ ) &&
+ test_must_fail git checkout --recurse-submodules master &&
+ test_must_fail git diff-files --quiet submodule &&
+ git diff-files --quiet file &&
+ git checkout --recurse-submodules -f master &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD
+'
+
+test_expect_failure '"checkout --recurse-submodules" needs -f to update modifed submodule content' '
+ echo modified >submodule/second.t &&
+ test_must_fail git checkout --recurse-submodules HEAD^ &&
+ test_must_fail git diff-files --quiet submodule &&
+ git diff-files --quiet file &&
+ git checkout --recurse-submodules -f HEAD^ &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD &&
+ git checkout --recurse-submodules -f master &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD
+'
+
+test_expect_failure '"checkout --recurse-submodules" ignores modified submodule content that would not be changed' '
+ echo modified >expected &&
+ cp expected submodule/first.t &&
+ git checkout --recurse-submodules HEAD^ &&
+ test_cmp expected submodule/first.t &&
+ test_must_fail git diff-files --quiet submodule &&
+ git diff-index --quiet --cached HEAD &&
+ git checkout --recurse-submodules -f master &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD
+'
+
+test_expect_failure '"checkout --recurse-submodules" does not care about untracked submodule content' '
+ echo untracked >submodule/untracked &&
+ git checkout --recurse-submodules master &&
+ git diff-files --quiet --ignore-submodules=untracked &&
+ git diff-index --quiet --cached HEAD &&
+ rm submodule/untracked
+'
+
+test_expect_failure '"checkout --recurse-submodules" needs -f when submodule commit is not present (but does fail anyway)' '
+ git checkout --recurse-submodules -b bogus_commit master &&
+ git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submodule
+ BOGUS_TREE=$(git write-tree) &&
+ BOGUS_COMMIT=$(echo "bogus submodule commit" | git commit-tree $BOGUS_TREE) &&
+ git commit -m "bogus submodule commit" &&
+ git checkout --recurse-submodules -f master &&
+ test_must_fail git checkout --recurse-submodules bogus_commit &&
+ git diff-files --quiet &&
+ test_must_fail git checkout --recurse-submodules -f bogus_commit &&
+ test_must_fail git diff-files --quiet submodule &&
+ git diff-files --quiet file &&
+ git diff-index --quiet --cached HEAD &&
+ git checkout --recurse-submodules -f master
+'
+
test_done