update submodules: add a config option to determine if submodules are updated
A message that is not in the archive (sent off-list or not yet received): [email protected]/
v6 cover Checkout aware of Submodules!
previous work:
https://public-inbox.org/git/[email protected]/
https://public-inbox.org/git/[email protected]/
v6:
* added support for read-tree (see last patch) to see how generic the code of the previous patches is. I am pretty pleased with that patch.
* marked two functions static. Thanks Ramsay!
* fixed the recursive test; it still fails but it is the code that fails, not the test. For this I had to change the setup code slightly.
* 2 new patches adding tiny refactoring to the submodule test lib.
* interdiff (to origin/sb/checkout-recurse-submodules, which is v5) below.
v5:
* as v4 was the first version queued by Junio, we do have an interdiff below!
* renamed functions
* changed the API, now the caller has to take care of the submodule strategy themselves. (Note this can be different for different situations, e.g. when a submodule is deleted, we can do that for any strategy except none and !command. But for moving to a new state of the submodule we currently only implement "checkout" [unspecified defaults to checkout]. warning about the others, doing nothing there.)
v4:
* addressed all comments of Brian, Junio and Brandon. Thanks!
* one major point of change is the introduction of another patch "lib-submodule-update.sh: do not use ./. as submodule remote", as that took some time to track down the existing bug.
v3:
* moved tests from t2013 to the generic submodule library.
* factored out the refactoring patches to be up front
* As I redid the complete implementation, I have the impression this time it is cleaner than previous versions.
I think we still have to fix the corner cases of directory/file/submodule conflicts before merging, but this serves as a status update on my current way of thinking how to implement the worktree commands being aware of submodules.
Thanks,
Stefan
v2:
* based on top of the series sent out an hour ago
"[PATCHv4 0/5] submodule embedgitdirs"
* Try to embed a submodule if we need to remove it.
* Strictly do not change behavior if not giving the new flag.
* I think I missed some review comments from v1, but I'd like to get the current state out over the weekend, as a lot has changed so far. On Monday I'll go through the previous discussion with a comb to see if I missed something.
v1:
When working with submodules, nearly anytime after checking out a different state of the projects, that has submodules changed you'd run "git submodule update" with a current version of Git.
There are two problems with this approach:
* The "submodule update" command is dangerous as it doesn't check for work that may be lost in the submodule (e.g. a dangling commit).
* you may forget to run the command as checkout is supposed to do all the work for you.
Integrate updating the submodules into git checkout, with the same safety promises that git-checkout has, i.e. not throw away data unless asked to. This is done by first checking if the submodule is at the same sha1 as it is recorded in the superproject. If there are changes we stop proceeding the checkout just like it is when checking out a file that has local changes.
The integration happens in the code that is also used in other commands such that it will be easier in the future to make other commands aware of submodule.
This also solves d/f conflicts in case you replace a file/directory with a submodule or vice versa.
The patches are still a bit rough, but the overall series seems promising enough to me that I want to put it out here.
Any review, specifically on the design level welcome!
Thanks,
Stefan
Stefan Beller (14):
lib-submodule-update.sh: reorder create_lib_submodule_repo lib-submodule-update.sh: define tests for recursing into submodules make is_submodule_populated gently connect_work_tree_and_git_dir: safely create leading directories update submodules: add submodule config parsing
update submodules: add a config option to determine if submodules are
updated
update submodules: introduce is_interesting_submodule update submodules: move up prepare_submodule_repo_env update submodules: add submodule_go_from_to
unpack-trees: pass old oid to verify_clean_submodule
unpack-trees: check if we can perform the operation for submodules
read-cache: remove_marked_cache_entries to wipe selected submodules. entry.c: update submodules when interesting builtin/checkout: add --recurse-submodules switch
Documentation/git-read-tree.txt +6 -0
@@ -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.
builtin/checkout.c +2 -2
@@ -29,8 +29,8 @@ static const char * const checkout_usage[] = { NULL, }; -int option_parse_recurse_submodules (const struct option *opt, -const char *arg,int unset) +static int option_parse_recurse_submodules (const struct option *opt, +const char *arg,int unset) {if (unset) { recurse_submodules =RECURSE_SUBMODULES_OFF ;
builtin/read-tree.c +29 -0
@@ -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 ]; +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?" );
submodule.c +1 -1
@@ -1257,7 +1257,7 @@ static int submodule_has_dirty_index(const struct submodule *sub)return finish_command (&cp); } -void submodule_reset_index (const char *path) +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);
t/lib-submodule-update.sh +50 -23
140 lines not on this page · raw message
t/t1013-read-tree-submodule.sh +7 -0
@@ -5,6 +5,13 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +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"
PATCH v6 1/18 lib-submodule-update.sh: reorder create_lib_submodule_repo
Redraw the ASCII art describing the setup using more space, such that it is easier to understand. The leaf commits are now ordered the same way the actual code is ordered.
Add empty lines to the setup code separating each of the leaf commits, each starting with a "checkout -b".
--- t/lib-submodule-update.sh | 49 ++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 20 deletions(-)
t/lib-submodule-update.sh +29 -20
@@ -15,22 +15,27 @@ # - 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 +# | / ^ +# | / 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_repo && (
@@ -49,10 +54,11 @@ create_lib_submodule_repo () { 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 +73,7 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify sub1" && - git checkout -b "replace_sub1_with_directory" "add_sub1" && + git checkout -b replace_sub1_with_directory add_sub1 && git submodule update && git -C sub1 checkout modifications && git rm --cached sub1 &&
@@ -75,22 +81,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 ) }
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 2/18 lib-submodule-update.sh: do not use ./. as submodule remote
In reply to Stefan BellerAdding the repository itself as a submodule does not make sense in the real world. In our test suite we used to do that out of convenience in some tests as the current repository has easiest access for setting up 'just a submodule'.
However this doesn't quite test the real world, so let's do not follow this pattern any further and actually create an independent repository that we can use as a submodule.
When using './.' as the remote the superproject and submodule share the same objects, such that testing if a given sha1 is a valid commit works in either repository. As running commands in an unpopulated submodule fall back to the superproject, this happens in reset_work_tree_to to determine if we need to populate the submodule. Fix this bug by checking in the actual remote now.
--- t/lib-submodule-update.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +13 -2
@@ -37,6 +37,17 @@ # 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_repo && ( cd submodule_update_repo &&
@@ -49,7 +60,7 @@ 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 &&
@@ -162,7 +173,7 @@ reset_work_tree_to () { 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}") + test $(cd "../submodule_update_sub1" && git rev-parse --verify "$sha1^{commit}") then git submodule update --init --recursive "sub1" fi
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 3/18 lib-submodule-update: teach test_submodule_content the -C <dir> flag
In reply to Stefan Beller--- t/lib-submodule-update.sh | 5 +++++ 1 file changed, 5 insertions(+)
t/lib-submodule-update.sh +5 -0
@@ -193,6 +193,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 "$1" == "-C" + then + cd "$2" + shift; shift; + fi if test $# != 2 then echo "test_submodule_content needs two arguments"
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
Re: [PATCH 03/18] lib-submodule-update: teach test_submodule_content the -C <dir> flag
t/lib-submodule-update.sh test_superproject_content () { · line 196
Quoted 2 lines · Stefan Beller
test_submodule_content() {t/lib-submodule-update.sh +1
-0+ if test "$1" == "-C"
Use a single '=' for portability in sh. It's also a good idea to prefix variables with 'x' or some such, since "$1" could be "-n" or some other supported switch for test(1).
So, something like:
if test x"$1" = "x-C"
...or use a case statement.
On Debian systems, I use the "checkbashisms" from the "devscripts" package to find and avoid bash dependencies.
Thanks.
Re: [PATCH 03/18] lib-submodule-update: teach test_submodule_content the -C <dir> flag
Quoted 9 lines · Eric Wong
Quoted 2 lines · Stefan Beller
test_submodule_content() {
+ if test "$1" == "-C"Use a single '=' for portability in sh. It's also a good idea to prefix variables with 'x' or some such, since "$1" could be "-n" or some other supported switch for test(1).
So, something like:
Quoted 6 lines
if test x"$1" = "x-C"...or use a case statement.
On Debian systems, I use the "checkbashisms" from the "devscripts" package to find and avoid bash dependencies.
Thanks for review as well as the tip for using
checkbashisms,
I'll resend this series with this fix as well as another fix in git-submodule.sh
Thanks,
Stefan
Re: [PATCH 03/18] lib-submodule-update: teach test_submodule_content the -C <dir> flag
Quoted 3 lines · Stefan Beller
as well as another fix in
git-submodule.sh
I spoke too early, that seems like a false positive.
PATCH v6 4/18 lib-submodule-update.sh: replace sha1 by hash
In reply to Stefan BellerCleaning up code by generalising it. Currently the mailing list discusses yet again how to migrate away from sha1.
--- t/lib-submodule-update.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
t/lib-submodule-update.sh +3 -3
@@ -171,9 +171,9 @@ 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 "../submodule_update_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
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 5/18 lib-submodule-update.sh: define tests for recursing into submodules
In reply to Stefan BellerCurrently lib-submodule-update.sh provides 2 functions test_submodule_switch and test_submodule_forced_switch that are used by a variety of tests to ensure that submodules behave as expected. The current expected behavior is that submodules are not touched at all (see 42639d2317a for the exact setup).
In the future we want to teach all these commands to recurse into submodules. To do that, we'll add two testing functions to submodule-update-lib.sh: test_submodule_switch_recursing and test_submodule_forced_switch_recursing.
These two functions behave in analogy to the already existing functions just with a different expectation on submodule behavior. The submodule in the working tree is expected to be updated to the recorded submodule version. The behavior is analogous to e.g. the behavior of files in a nested directory in the working tree, where a change to the working tree handles any arising directory/file conflicts just fine.
--- t/lib-submodule-update.sh | 506 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 504 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +504 -2
@@ -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 =>
@@ -19,8 +20,8 @@ # / ^ # / remove_sub1 # / -# add_sub1 /-------O -# | / ^ +# add_sub1 /-------O---------O--------O modify_sub1_recursively +# | / ^ add_nested_sub # | / modify_sub1 # v/ # O------O-----------O---------O
@@ -48,6 +49,17 @@ create_lib_submodule_repo () { 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 &&
@@ -84,6 +96,26 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify 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 &&
@@ -150,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.
@@ -180,6 +221,27 @@ reset_work_tree_to () { ) } +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).
@@ -700,3 +762,443 @@ 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" + ######################### 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_success "$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_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 && + test_submodule_content sub1 origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_success "$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_success "$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 ignroed + 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 + ) + ' + + test_expect_success "$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" + ######################### 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 && + test_must_fail $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_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 && + : >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 + ) + ' +}
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 6/18 make is_submodule_populated gently
In reply to Stefan BellerWe need the gentle version in a later patch. As we have just one caller, migrate the caller.
--- builtin/grep.c | 2 +- submodule.c | 7 ++----- submodule.h | 8 +++++++- 3 files changed, 10 insertions(+), 7 deletions(-)
builtin/grep.c +1 -1
@@ -616,7 +616,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.
submodule.c +2 -5
@@ -234,15 +234,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);
submodule.h +7 -1
@@ -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);
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 7/18 connect_work_tree_and_git_dir: safely create leading directories
In reply to Stefan BellerIn a later patch we'll use connect_work_tree_and_git_dir when the directory for the gitlink file doesn't exist yet. This patch makes connect_work_tree_and_git_dir safe to use for both cases of either the git dir or the working dir missing.
To do so, we need to call safe_create_leading_directories[_const] on both directories. However this has to happen before we construct the absolute paths as real_pathdup assumes the directories to be there already.
So for both the config file in the git dir as well as the .git link file we need to
a) construct the name
b) call SCLD
c) get the absolute path
d) once a-c is done for both we can consume the absolute path to compute the relative path to each other and store those relative paths.
The implementation provided here puts a) and b) for both cases first, and then performs c and d after.
One of the two users of 'connect_work_tree_and_git_dir' already checked for the directory being there, so we can loose that check as connect_work_tree_and_git_dir handles this functionality now.
--- dir.c | 32 +++++++++++++++++++++----------- submodule.c | 11 ++--------- 2 files changed, 23 insertions(+), 20 deletions(-)
dir.c +21 -11
@@ -2728,23 +2728,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_); -char *work_tree =real_pathdup (work_tree_); +char *git_dir, *work_tree; -/* Update gitfile */ -strbuf_addf (&file_name,"%s/.git" , work_tree); -write_file (file_name.buf,"gitdir: %s" , -relative_path (git_dir, work_tree, &rel_path)); +/* 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_); + work_tree =real_pathdup (work_tree_); + +/* 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);
submodule.c +2 -9
@@ -1442,8 +1442,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 ) {
@@ -1466,13 +1464,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); -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);
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 8/18 update submodules: add submodule config parsing
In reply to Stefan BellerSimilar to b33a15b08 (push: add recurseSubmodules config option, 2015-11-17) and 027771fcb1 (submodule: allow erroneous values for the fetchRecurseSubmodules option, 2015-08-17), we add submodule-config code that is later used to parse whether we are interested in updating submodules.
We need the die_on_error parameter to be able to call this parsing function for the config file as well, which if incorrect lets Git die.
As we're just touching the header file, also mark all functions extern.
--- submodule-config.c | 20 ++++++++++++++++++++ submodule-config.h | 17 +++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-)
submodule-config.c +20 -0
@@ -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) {
submodule-config.h +9 -8
@@ -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 */
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 9/18 update submodules: add a config option to determine if submodules are updated
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
Have a central place to store such settings whether we want to update a submodule.
--- submodule.c | 6 ++++++ submodule.h | 1 + 2 files changed, 7 insertions(+)
submodule.c +6 -0
@@ -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;
@@ -542,6 +543,11 @@ 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; +} +static int has_remote (const char *refname,const struct object_id *oid,int flags,void *cb_data) {
submodule.h +1 -0
@@ -64,6 +64,7 @@ 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);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,
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 10/18 submodules: introduce check to see whether to touch a submodule
In reply to Stefan BellerIn later patches we introduce the --recurse-submodule flag for commands that modify the working directory, e.g. git-checkout.
It is potentially expensive to check if a submodule needs an update, because a common theme to interact with submodules is to spawn a child process for each interaction.
So let's introduce a function that checks if a submodule needs to be checked for an update before attempting the update.
--- submodule.c | 16 ++++++++++++++++ submodule.h | 7 +++++++ 2 files changed, 23 insertions(+)
submodule.c +16 -0
@@ -548,6 +548,22 @@ 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) {
submodule.h +7 -0
@@ -65,6 +65,13 @@ extern void show_submodule_inline_diff(FILE *f, const char *path, 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,
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 11/18 update submodules: move up prepare_submodule_repo_env
In reply to Stefan BellerIn a later patch we need to prepare the submodule environment with another git directory, so split up the function.
Also move it up in the file such that we do not need to declare the function later before using it.
--- submodule.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)
submodule.c +17 -12
@@ -356,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
@@ -1390,18 +1407,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.
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 12/18 update submodules: add submodule_move_head
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
This piece of code will be used universally for all these working tree modifications as it
* supports dry run to answer the question:
"Is it safe to change the submodule to this new state?" e.g. is it overwriting untracked files or are there local changes that would be overwritten?
* supports a force flag that can be used for resetting the tree.
--- submodule.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ submodule.h | 7 ++++ 2 files changed, 142 insertions(+)
submodule.c +135 -0
@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; } +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/" , 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/" , 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) {
submodule.h +7 -0
@@ -96,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
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 13/18 unpack-trees: pass old oid to verify_clean_submodule
In reply to Stefan BellerThe check (which uses the old oid) is yet to be implemented, but this part is just a refactor, so it can go separately first.
--- unpack-trees.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-)
unpack-trees.c +10 -7
@@ -1407,7 +1407,8 @@ 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) {
@@ -1427,16 +1428,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); }/*
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 14/18 unpack-trees: check if we can perform the operation for submodules
In reply to Stefan Beller--- unpack-trees.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- unpack-trees.h | 1 + 2 files changed, 123 insertions(+), 9 deletions(-)
unpack-trees.c +122 -9
@@ -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 )
@@ -1412,7 +1494,11 @@ static int verify_clean_submodule(const char *old_sha1, 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,
@@ -1578,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)) {
@@ -1588,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);
@@ -1643,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?
@@ -1663,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
unpack-trees.h +1 -0
@@ -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 };
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 15/18 read-cache, remove_marked_cache_entries: wipe selected submodules.
In reply to Stefan Beller--- read-cache.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
read-cache.c +25 -2
@@ -18,6 +18,8 @@#include "varint.h" #include "split-index.h" #include "utf8.h" +#include "submodule.h" +#include "submodule-config.h" /* Mask for the name length in ce_flags in the on-disk index */
@@ -520,6 +522,22 @@ int remove_index_entry_at(struct index_state *istate, int pos)return 1 ; } +static void remove_submodule_according_to_strategy (const struct submodule *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 (sub->path,"HEAD" ,NULL , \ +SUBMODULE_MOVE_HEAD_FORCE ); +break ; +case SM_UPDATE_NONE : +case SM_UPDATE_COMMAND : + ;/* Do not touch the submodule. */ + } +} +/* * Remove all cache entries marked for removal, that is where * CE_REMOVE is set in ce_flags. This is much more effective than
@@ -532,8 +550,13 @@ void remove_marked_cache_entries(struct index_state *istate)for (i = j =0 ; i < istate->cache_nr; i++) {if (ce_array[i]->ce_flags &CE_REMOVE ) { -remove_name_hash (istate, ce_array[i]); -save_or_free_index_entry (istate, ce_array[i]); +const struct submodule *sub =submodule_from_ce (ce_array[i]); +if (sub) { +remove_submodule_according_to_strategy (sub); + }else { +remove_name_hash (istate, ce_array[i]); +save_or_free_index_entry (istate, ce_array[i]); + } }else ce_array[j++] = ce_array[i];
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 16/18 entry.c: update submodules when interesting
In reply to Stefan Beller--- entry.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
entry.c +30 -0
@@ -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) {
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 17/18 builtin/checkout: add --recurse-submodules switch
In reply to Stefan Beller--- Documentation/git-checkout.txt | 7 +++++++ builtin/checkout.c | 28 ++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 23 +++++++++++++++++------ t/t2013-checkout-submodule.sh | 5 +++++ 4 files changed, 57 insertions(+), 6 deletions(-)
Documentation/git-checkout.txt +7 -0
@@ -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
builtin/checkout.c +28 -0
@@ -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" ));
t/lib-submodule-update.sh +17 -6
@@ -782,6 +782,11 @@ test_submodule_forced_switch () { test_submodule_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 checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -891,7 +896,7 @@ test_submodule_switch_recursing () { ' # Replacing a submodule with files in a directory must succeeds # when the submodule is clean - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULT "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -903,7 +908,7 @@ test_submodule_switch_recursing () { ) ' # ... absorbing a .git directory. - test_expect_success "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + test_expect_$RESULT "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -931,7 +936,7 @@ test_submodule_switch_recursing () { ' # ... must check its local work tree for untracked files - test_expect_success "$command: replace submodule with a file must fail with untracked files" ' + test_expect_$RESULT "$command: replace submodule with a file must fail with untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -987,7 +992,8 @@ test_submodule_switch_recursing () { ) ' - test_expect_success "$command: modified submodule updates submodule recursively" ' + # recursing deeper than one level doesn't work yet. + test_expect_failure "$command: modified submodule updates submodule recursively" ' prolog && reset_work_tree_to_interested add_nested_sub && (
@@ -1006,6 +1012,11 @@ test_submodule_switch_recursing () { # 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" '
@@ -1100,7 +1111,7 @@ test_submodule_forced_switch_recursing () { ) ' # Replacing a submodule with files in a directory ... - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULT "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -1151,7 +1162,7 @@ test_submodule_forced_switch_recursing () { ' # ... but stops for untracked files that would be lost - test_expect_success "$command: replace submodule with a file" ' + test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
t/t2013-checkout-submodule.sh +5 -0
@@ -63,6 +63,11 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ! test -s actual ' +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=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"
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 18/18 builtin/read-tree: add --recurse-submodules switch
In reply to Stefan BellerA new known failure mode is introduced[1], which is actually not a failure but a feature in read-tree. Unlike checkout for which the recursive submodule tests were originally written, read-tree does warn about ignored untracked files that would be overwritten. For the sake of keeping the test library for submodules genric, just mark the test as a failure.
[1] KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED
--- Documentation/git-read-tree.txt | 6 ++++++ builtin/read-tree.c | 29 +++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 7 ++++++- t/t1013-read-tree-submodule.sh | 7 +++++++ 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-read-tree.txt +6 -0
@@ -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.
builtin/read-tree.c +29 -0
@@ -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 ]; +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?" );
t/lib-submodule-update.sh +6 -1
@@ -787,6 +787,11 @@ test_submodule_switch_recursing () { then RESULT=failure fi + RESULT1=success + if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 + then + RESULT1=failure + fi ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -827,7 +832,7 @@ test_submodule_switch_recursing () { ) ' # ... but an ignored file is fine. - test_expect_success "$command: added submodule removes an untracked ignored file" ' + test_expect_$RESULT1 "$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 &&
t/t1013-read-tree-submodule.sh +7 -0
@@ -5,6 +5,13 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +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"
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
v7 cover Checkout aware of Submodules!
In reply to Stefan Bellerprevious work:
https://public-inbox.org/git/[email protected]
https://public-inbox.org/git/[email protected]/
v7:
* addressed Erics comment by fixing the bashism in t/lib-submodule-update.sh
diff to v6:
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 5caae06bc5..949ebd546c 100755
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -255,7 +255,7 @@ 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 "$1" == "-C"
+ if test x"$1" = "x-C"
then
cd "$2"
shift; shift;
* interdiff to v5 belowv6:
* added support for read-tree (see last patch) to see how generic the code of the previous patches is. I am pretty pleased with that patch.
* marked two functions static. Thanks Ramsay!
* fixed the recursive test; it still fails but it is the code that fails, not the test. For this I had to change the setup code slightly.
* 2 new patches adding tiny refactoring to the submodule test lib.
* interdiff (to origin/sb/checkout-recurse-submodules, which is v5) below.
v5:
* as v4 was the first version queued by Junio, we do have an interdiff below!
* renamed functions
* changed the API, now the caller has to take care of the submodule strategy themselves. (Note this can be different for different situations, e.g. when a submodule is deleted, we can do that for any strategy except none and !command. But for moving to a new state of the submodule we currently only implement "checkout" [unspecified defaults to checkout]. warning about the others, doing nothing there.)
v4:
* addressed all comments of Brian, Junio and Brandon. Thanks!
* one major point of change is the introduction of another patch "lib-submodule-update.sh: do not use ./. as submodule remote", as that took some time to track down the existing bug.
v3:
* moved tests from t2013 to the generic submodule library.
* factored out the refactoring patches to be up front
* As I redid the complete implementation, I have the impression this time it is cleaner than previous versions.
I think we still have to fix the corner cases of directory/file/submodule conflicts before merging, but this serves as a status update on my current way of thinking how to implement the worktree commands being aware of submodules.
Thanks,
Stefan
v2:
* based on top of the series sent out an hour ago
"[PATCHv4 0/5] submodule embedgitdirs"
* Try to embed a submodule if we need to remove it.
* Strictly do not change behavior if not giving the new flag.
* I think I missed some review comments from v1, but I'd like to get the current state out over the weekend, as a lot has changed so far. On Monday I'll go through the previous discussion with a comb to see if I missed something.
v1:
When working with submodules, nearly anytime after checking out a different state of the projects, that has submodules changed you'd run "git submodule update" with a current version of Git.
There are two problems with this approach:
* The "submodule update" command is dangerous as it doesn't check for work that may be lost in the submodule (e.g. a dangling commit).
* you may forget to run the command as checkout is supposed to do all the work for you.
Integrate updating the submodules into git checkout, with the same safety promises that git-checkout has, i.e. not throw away data unless asked to. This is done by first checking if the submodule is at the same sha1 as it is recorded in the superproject. If there are changes we stop proceeding the checkout just like it is when checking out a file that has local changes.
The integration happens in the code that is also used in other commands such that it will be easier in the future to make other commands aware of submodule.
This also solves d/f conflicts in case you replace a file/directory with a submodule or vice versa.
The patches are still a bit rough, but the overall series seems promising enough to me that I want to put it out here.
Any review, specifically on the design level welcome!
Thanks,
Stefan
Stefan Beller (14):
lib-submodule-update.sh: reorder create_lib_submodule_repo lib-submodule-update.sh: define tests for recursing into submodules make is_submodule_populated gently connect_work_tree_and_git_dir: safely create leading directories update submodules: add submodule config parsing
update submodules: add a config option to determine if submodules are
updated
update submodules: introduce is_interesting_submodule update submodules: move up prepare_submodule_repo_env update submodules: add submodule_go_from_to
unpack-trees: pass old oid to verify_clean_submodule
unpack-trees: check if we can perform the operation for submodules
read-cache: remove_marked_cache_entries to wipe selected submodules. entry.c: update submodules when interesting builtin/checkout: add --recurse-submodules switch
Documentation/git-read-tree.txt +6 -0
@@ -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.
builtin/checkout.c +2 -2
@@ -29,8 +29,8 @@ static const char * const checkout_usage[] = { NULL, }; -int option_parse_recurse_submodules (const struct option *opt, -const char *arg,int unset) +static int option_parse_recurse_submodules (const struct option *opt, +const char *arg,int unset) {if (unset) { recurse_submodules =RECURSE_SUBMODULES_OFF ;
builtin/read-tree.c +29 -0
@@ -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 ]; +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?" );
submodule.c +1 -1
@@ -1257,7 +1257,7 @@ static int submodule_has_dirty_index(const struct submodule *sub)return finish_command (&cp); } -void submodule_reset_index (const char *path) +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);
t/lib-submodule-update.sh +50 -23
140 lines not on this page · raw message
t/t1013-read-tree-submodule.sh +7 -0
@@ -5,6 +5,13 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +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"
PATCH v6 1/18 lib-submodule-update.sh: reorder create_lib_submodule_repo
Redraw the ASCII art describing the setup using more space, such that it is easier to understand. The leaf commits are now ordered the same way the actual code is ordered.
Add empty lines to the setup code separating each of the leaf commits, each starting with a "checkout -b".
--- t/lib-submodule-update.sh | 49 ++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 20 deletions(-)
t/lib-submodule-update.sh +29 -20
@@ -15,22 +15,27 @@ # - 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 +# | / ^ +# | / 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_repo && (
@@ -49,10 +54,11 @@ create_lib_submodule_repo () { 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 +73,7 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify sub1" && - git checkout -b "replace_sub1_with_directory" "add_sub1" && + git checkout -b replace_sub1_with_directory add_sub1 && git submodule update && git -C sub1 checkout modifications && git rm --cached sub1 &&
@@ -75,22 +81,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 ) }
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 2/18 lib-submodule-update.sh: do not use ./. as submodule remote
In reply to Stefan BellerAdding the repository itself as a submodule does not make sense in the real world. In our test suite we used to do that out of convenience in some tests as the current repository has easiest access for setting up 'just a submodule'.
However this doesn't quite test the real world, so let's do not follow this pattern any further and actually create an independent repository that we can use as a submodule.
When using './.' as the remote the superproject and submodule share the same objects, such that testing if a given sha1 is a valid commit works in either repository. As running commands in an unpopulated submodule fall back to the superproject, this happens in reset_work_tree_to to determine if we need to populate the submodule. Fix this bug by checking in the actual remote now.
--- t/lib-submodule-update.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +13 -2
@@ -37,6 +37,17 @@ # 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_repo && ( cd submodule_update_repo &&
@@ -49,7 +60,7 @@ 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 &&
@@ -162,7 +173,7 @@ reset_work_tree_to () { 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}") + test $(cd "../submodule_update_sub1" && git rev-parse --verify "$sha1^{commit}") then git submodule update --init --recursive "sub1" fi
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 3/18 lib-submodule-update: teach test_submodule_content the -C <dir> flag
In reply to Stefan Beller--- t/lib-submodule-update.sh | 5 +++++ 1 file changed, 5 insertions(+)
t/lib-submodule-update.sh +5 -0
@@ -193,6 +193,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"
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
Re: [PATCH 03/18] lib-submodule-update: teach test_submodule_content the -C <dir> flag
Quoted 10 lines · Stefan Beller
Signed-off-by Stefan Beller <[email protected]>--- t/lib-submodule-update.sh | 5 +++++ 1 file changed, 5 insertions(+)t/lib-submodule-update.sh +0
-0
t/lib-submodule-update.sh test_superproject_content () { · line 199
Quoted 7 lines
t/lib-submodule-update.sh +4
-0@@ -193,6 +193,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;
That's old fashoned like me ;-)
It seems "shift [<n>]" is already used elsewhere in our test scripts without getting complaints.
'git-difftool-helper.sh' is the only one that uses number above 1 in the scripted Porcelains, so it is possible that an esoteric platform without a shell that understands it may be built and used _without_ passing our test suite, so I'd be wary of using it in the scripted Porcelains, but this is part of the test suite, so I would think it is OK.
Quoted 4 lines
t/lib-submodule-update.sh +1
-0+ fi if test $# != 2 then echo "test_submodule_content needs two arguments"
PATCH v6 4/18 lib-submodule-update.sh: replace sha1 by hash
In reply to Stefan BellerCleaning up code by generalising it. Currently the mailing list discusses yet again how to migrate away from sha1.
--- t/lib-submodule-update.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
t/lib-submodule-update.sh +3 -3
@@ -171,9 +171,9 @@ 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 "../submodule_update_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
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 5/18 lib-submodule-update.sh: define tests for recursing into submodules
In reply to Stefan BellerCurrently lib-submodule-update.sh provides 2 functions test_submodule_switch and test_submodule_forced_switch that are used by a variety of tests to ensure that submodules behave as expected. The current expected behavior is that submodules are not touched at all (see 42639d2317a for the exact setup).
In the future we want to teach all these commands to recurse into submodules. To do that, we'll add two testing functions to submodule-update-lib.sh: test_submodule_switch_recursing and test_submodule_forced_switch_recursing.
These two functions behave in analogy to the already existing functions just with a different expectation on submodule behavior. The submodule in the working tree is expected to be updated to the recorded submodule version. The behavior is analogous to e.g. the behavior of files in a nested directory in the working tree, where a change to the working tree handles any arising directory/file conflicts just fine.
--- t/lib-submodule-update.sh | 506 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 504 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +504 -2
@@ -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 =>
@@ -19,8 +20,8 @@ # / ^ # / remove_sub1 # / -# add_sub1 /-------O -# | / ^ +# add_sub1 /-------O---------O--------O modify_sub1_recursively +# | / ^ add_nested_sub # | / modify_sub1 # v/ # O------O-----------O---------O
@@ -48,6 +49,17 @@ create_lib_submodule_repo () { 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 &&
@@ -84,6 +96,26 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify 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 &&
@@ -150,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.
@@ -180,6 +221,27 @@ reset_work_tree_to () { ) } +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).
@@ -700,3 +762,443 @@ 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" + ######################### 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_success "$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_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 && + test_submodule_content sub1 origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_success "$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_success "$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 ignroed + 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 + ) + ' + + test_expect_success "$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" + ######################### 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 && + test_must_fail $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_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 && + : >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 + ) + ' +}
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
Re: [PATCH 05/18] lib-submodule-update.sh: define tests for recursing into submodules
t/lib-submodule-update.sh test_submodule_forced_switch () { · line 947
Quoted 1 line · Stefan Beller
t/lib-submodule-update.sh +1
-0+ # ... and ignored files are ignroed
ignored.
PATCH v6 6/18 make is_submodule_populated gently
In reply to Stefan BellerWe need the gentle version in a later patch. As we have just one caller, migrate the caller.
--- builtin/grep.c | 2 +- submodule.c | 7 ++----- submodule.h | 8 +++++++- 3 files changed, 10 insertions(+), 7 deletions(-)
builtin/grep.c +1 -1
@@ -616,7 +616,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.
submodule.c +2 -5
@@ -234,15 +234,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);
submodule.h +7 -1
@@ -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);
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 7/18 connect_work_tree_and_git_dir: safely create leading directories
In reply to Stefan BellerIn a later patch we'll use connect_work_tree_and_git_dir when the directory for the gitlink file doesn't exist yet. This patch makes connect_work_tree_and_git_dir safe to use for both cases of either the git dir or the working dir missing.
To do so, we need to call safe_create_leading_directories[_const] on both directories. However this has to happen before we construct the absolute paths as real_pathdup assumes the directories to be there already.
So for both the config file in the git dir as well as the .git link file we need to
a) construct the name
b) call SCLD
c) get the absolute path
d) once a-c is done for both we can consume the absolute path to compute the relative path to each other and store those relative paths.
The implementation provided here puts a) and b) for both cases first, and then performs c and d after.
One of the two users of 'connect_work_tree_and_git_dir' already checked for the directory being there, so we can loose that check as connect_work_tree_and_git_dir handles this functionality now.
--- dir.c | 32 +++++++++++++++++++++----------- submodule.c | 11 ++--------- 2 files changed, 23 insertions(+), 20 deletions(-)
dir.c +21 -11
@@ -2728,23 +2728,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_); -char *work_tree =real_pathdup (work_tree_); +char *git_dir, *work_tree; -/* Update gitfile */ -strbuf_addf (&file_name,"%s/.git" , work_tree); -write_file (file_name.buf,"gitdir: %s" , -relative_path (git_dir, work_tree, &rel_path)); +/* 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_); + work_tree =real_pathdup (work_tree_); + +/* 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);
submodule.c +2 -9
@@ -1442,8 +1442,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 ) {
@@ -1466,13 +1464,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); -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);
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 8/18 update submodules: add submodule config parsing
In reply to Stefan BellerSimilar to b33a15b08 (push: add recurseSubmodules config option, 2015-11-17) and 027771fcb1 (submodule: allow erroneous values for the fetchRecurseSubmodules option, 2015-08-17), we add submodule-config code that is later used to parse whether we are interested in updating submodules.
We need the die_on_error parameter to be able to call this parsing function for the config file as well, which if incorrect lets Git die.
As we're just touching the header file, also mark all functions extern.
--- submodule-config.c | 20 ++++++++++++++++++++ submodule-config.h | 17 +++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-)
submodule-config.c +20 -0
@@ -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) {
submodule-config.h +9 -8
@@ -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 */
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 9/18 update submodules: add a config option to determine if submodules are updated
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
Have a central place to store such settings whether we want to update a submodule.
--- submodule.c | 6 ++++++ submodule.h | 1 + 2 files changed, 7 insertions(+)
submodule.c +6 -0
@@ -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;
@@ -542,6 +543,11 @@ 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; +} +static int has_remote (const char *refname,const struct object_id *oid,int flags,void *cb_data) {
submodule.h +1 -0
@@ -64,6 +64,7 @@ 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);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,
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 10/18 submodules: introduce check to see whether to touch a submodule
In reply to Stefan BellerIn later patches we introduce the --recurse-submodule flag for commands that modify the working directory, e.g. git-checkout.
It is potentially expensive to check if a submodule needs an update, because a common theme to interact with submodules is to spawn a child process for each interaction.
So let's introduce a function that checks if a submodule needs to be checked for an update before attempting the update.
--- submodule.c | 16 ++++++++++++++++ submodule.h | 7 +++++++ 2 files changed, 23 insertions(+)
submodule.c +16 -0
@@ -548,6 +548,22 @@ 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) {
submodule.h +7 -0
@@ -65,6 +65,13 @@ extern void show_submodule_inline_diff(FILE *f, const char *path, 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,
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 11/18 update submodules: move up prepare_submodule_repo_env
In reply to Stefan BellerIn a later patch we need to prepare the submodule environment with another git directory, so split up the function.
Also move it up in the file such that we do not need to declare the function later before using it.
--- submodule.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)
submodule.c +17 -12
@@ -356,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
@@ -1390,18 +1407,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.
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 12/18 update submodules: add submodule_move_head
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
This piece of code will be used universally for all these working tree modifications as it
* supports dry run to answer the question:
"Is it safe to change the submodule to this new state?" e.g. is it overwriting untracked files or are there local changes that would be overwritten?
* supports a force flag that can be used for resetting the tree.
--- submodule.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ submodule.h | 7 ++++ 2 files changed, 142 insertions(+)
submodule.c +135 -0
@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; } +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/" , 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/" , 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) {
submodule.h +7 -0
@@ -96,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
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 13/18 unpack-trees: pass old oid to verify_clean_submodule
In reply to Stefan BellerThe check (which uses the old oid) is yet to be implemented, but this part is just a refactor, so it can go separately first.
--- unpack-trees.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-)
unpack-trees.c +10 -7
@@ -1407,7 +1407,8 @@ 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) {
@@ -1427,16 +1428,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); }/*
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 14/18 unpack-trees: check if we can perform the operation for submodules
In reply to Stefan Beller--- unpack-trees.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- unpack-trees.h | 1 + 2 files changed, 123 insertions(+), 9 deletions(-)
unpack-trees.c +122 -9
@@ -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 )
@@ -1412,7 +1494,11 @@ static int verify_clean_submodule(const char *old_sha1, 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,
@@ -1578,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)) {
@@ -1588,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);
@@ -1643,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?
@@ -1663,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
unpack-trees.h +1 -0
@@ -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 };
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 15/18 read-cache, remove_marked_cache_entries: wipe selected submodules.
In reply to Stefan Beller--- read-cache.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)
read-cache.c +25 -2
@@ -18,6 +18,8 @@#include "varint.h" #include "split-index.h" #include "utf8.h" +#include "submodule.h" +#include "submodule-config.h" /* Mask for the name length in ce_flags in the on-disk index */
@@ -520,6 +522,22 @@ int remove_index_entry_at(struct index_state *istate, int pos)return 1 ; } +static void remove_submodule_according_to_strategy (const struct submodule *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 (sub->path,"HEAD" ,NULL , \ +SUBMODULE_MOVE_HEAD_FORCE ); +break ; +case SM_UPDATE_NONE : +case SM_UPDATE_COMMAND : + ;/* Do not touch the submodule. */ + } +} +/* * Remove all cache entries marked for removal, that is where * CE_REMOVE is set in ce_flags. This is much more effective than
@@ -532,8 +550,13 @@ void remove_marked_cache_entries(struct index_state *istate)for (i = j =0 ; i < istate->cache_nr; i++) {if (ce_array[i]->ce_flags &CE_REMOVE ) { -remove_name_hash (istate, ce_array[i]); -save_or_free_index_entry (istate, ce_array[i]); +const struct submodule *sub =submodule_from_ce (ce_array[i]); +if (sub) { +remove_submodule_according_to_strategy (sub); + }else { +remove_name_hash (istate, ce_array[i]); +save_or_free_index_entry (istate, ce_array[i]); + } }else ce_array[j++] = ce_array[i];
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
Re: [PATCH 15/18] read-cache, remove_marked_cache_entries: wipe selected submodules.
Quoted 20 lines · Stefan Beller
Signed-off-by Stefan Beller <[email protected]>--- read-cache.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-)read-cache.c +2
-0@@ -18,6 +18,8 @@#include "varint.h" #include "split-index.h" #include "utf8.h" +#include "submodule.h" +#include "submodule-config.h" /* Mask for the name length in ce_flags in the on-disk index */ @@ -520,6 +522,22 @@ int remove_index_entry_at(struct index_state *istate, int pos)return 1 ;
read-cache.c int remove_index_entry_at(struct index_state *istate, int pos) · line 532
Quoted 10 lines
read-cache.c +8
-0} +static void remove_submodule_according_to_strategy (const struct submodule *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(sub->path, "HEAD", NULL, \
What is this backslash doing here?
Quoted 14 lines
read-cache.c +8
-0+ SUBMODULE_MOVE_HEAD_FORCE); +break ; +case SM_UPDATE_NONE : +case SM_UPDATE_COMMAND : + ;/* Do not touch the submodule. */ + } +} +/* * Remove all cache entries marked for removal, that is where * CE_REMOVE is set in ce_flags. This is much more effective than @@ -532,8 +550,13 @@ void remove_marked_cache_entries(struct index_state *istate)for (i = j =0 ; i < istate->cache_nr; i++) {
read-cache.c void remove_marked_cache_entries(struct index_state *istate) · line 559
Quoted 10 lines
read-cache.c +7
-2if (ce_array[i]->ce_flags &CE_REMOVE ) { -remove_name_hash (istate, ce_array[i]); -save_or_free_index_entry (istate, ce_array[i]); +const struct submodule *sub =submodule_from_ce (ce_array[i]); +if (sub) { +remove_submodule_according_to_strategy (sub); + }else { +remove_name_hash (istate, ce_array[i]); +save_or_free_index_entry (istate, ce_array[i]); + }
I cannot readily tell as the proposed log message is on the sketchy side to explain the reasoning behind this, but this behaviour change is done unconditionally (in other words, without introducing a flag that is set by --recurse-submodules command line flag and switches behaviour based on the flag) here. What is the end-user visible effect with this change? Can we have a new test in this same patch to demonstrate the desired behaviour introduced by this change (or the bug this change fixes)?
Quoted 3 lines
read-cache.c +0
-0}else ce_array[j++] = ce_array[i];
Re: [PATCH 15/18] read-cache, remove_marked_cache_entries: wipe selected submodules.
Quoted 7 lines · Junio C Hamano
Quoted 3 lines · Stefan Beller
Signed-off-by Stefan Beller <[email protected]>+ submodule_move_head(sub->path, "HEAD", NULL, \
What is this backslash doing here?
I am still bad at following coding conventions, apparently. will remove.
Quoted 12 lines
Quoted 12 lines
read-cache.c +6
-2@@ -532,8 +550,13 @@ void remove_marked_cache_entries(struct index_state *istate)for (i = j =0 ; i < istate->cache_nr; i++) {if (ce_array[i]->ce_flags &CE_REMOVE ) { -remove_name_hash (istate, ce_array[i]); -save_or_free_index_entry (istate, ce_array[i]); +const struct submodule *sub =submodule_from_ce (ce_array[i]); +if (sub) { +remove_submodule_according_to_strategy (sub); + }else { +remove_name_hash (istate, ce_array[i]); +save_or_free_index_entry (istate, ce_array[i]);
Quoted 8 lines
Review void remove_marked_cache_entries(struct index_state *istate) · line 559
Quoted 1 line
(quoted diff) +1
-0+ }I cannot readily tell as the proposed log message is on the sketchy side to explain the reasoning behind this, but this behaviour change is done unconditionally (in other words, without introducing a flag that is set by --recurse-submodules command line flag and switches behaviour based on the flag) here. What is the end-user visible effect with this change?
submodule_from_ce returns always NULL, when such flag is not given. From 10/18:
+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);
+}
should_update_submodules is always false if such a flag is not set, such that we end up in the else case which is literally the same as the removed lines (they are just indented).
Quoted 3 lines
Can we have a new test in this same patch
to demonstrate the desired behaviour introduced by this change (or the bug this change fixes)?
This doesn't fix a bug, but in case the flag is given (in patches 17,18) this new code removes submodules that are no longer recorded in the tree.
Re: [PATCH 15/18] read-cache, remove_marked_cache_entries: wipe selected submodules.
Quoted 14 lines · Stefan Beller
submodule_from_cereturns always NULL, when such flag is not given. From 10/18:(quoted diff) +10
-0+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); +}
Quoted 3 lines
should_update_submodulesis always false if such a flag is not set, such that we end up in the else case which is literally the same as the removed lines (they are just indented).
I see.
I didn't think a function this deep in the callchain that does not take any parameter could possibly change the behaviour based on the end-user input. I was expecting that such a state (i.e. are we recursive? are we allowed to forcibly update the working tree files?) would be kept part of something like "struct checkout" and passed around the callchain.
That was why I didn't look at how that function answers "should update?" question, and got puzzled. Because it would imply there is some hidden state that is accessible by everybody--a global variable or something--which would point at a deeper design issue.
Re: [PATCH 15/18] read-cache, remove_marked_cache_entries: wipe selected submodules.
Quoted 29 lines · Junio C Hamano
Quoted 14 lines · Stefan Beller
submodule_from_cereturns always NULL, when such flag is not given. From 10/18:(quoted diff) +10
-0+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); +}Quoted 3 lines
should_update_submodulesis always false if such a flag is not set, such that we end up in the else case which is literally the same as the removed lines (they are just indented).I see.
I didn't think a function this deep in the callchain that does not take any parameter could possibly change the behaviour based on the end-user input. I was expecting that such a state (i.e. are we recursive? are we allowed to forcibly update the working tree files?) would be kept part of something like "struct checkout" and passed around the callchain.
Quoted 4 lines
That was why I didn't look at how that function answers "should update?" question, and got puzzled. Because it would imply there is some hidden state that is accessible by everybody--a global variable or something--which would point at a deeper design issue.
Well it is just as deep as e.g. reacting on some bits of struct unpack_trees_options in unpack-trees.c ?
But I can see how this is an issue with design.
I also think my previous answer was a straw man.
We only need to differentiate between 'ce' being a submodule or not, because it should not be marked CE_REMOVE in the non-recursive state.
Side-question:
Is there some doc (commit message), that explains the difference between CE_REMOVE and CE_WT_REMOVE ?
Thanks,
Stefan
Re: [PATCH 15/18] read-cache, remove_marked_cache_entries: wipe selected submodules.
Quoted 3 lines · Stefan Beller
Side-question:
Is there some doc (commit message), that explains the difference betweenCE_REMOVEandCE_WT_REMOVE?
That's something you need to ask Duy, I think, as it was introduced at e663db2f ("unpack-trees(): add CE_WT_REMOVE to remove on worktree alone", 2009-08-20) and was added for the sparse checkout stuff.
During my review of the series that added the feature, I only had time to make sure that the patches do not change the behaviour when it is not in use, ignoring the other side of "if" statement that checked ce_skip_worktree(ce) ;-)
PATCH v6 16/18 entry.c: update submodules when interesting
In reply to Stefan Beller--- entry.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
entry.c +30 -0
@@ -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) {
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
Re: [PATCH 16/18] entry.c: update submodules when interesting
Quoted 45 lines · Stefan Beller
Signed-off-by Stefan Beller <[email protected]>--- entry.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)entry.c +10
-0@@ -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
entry.c int checkout_entry(struct cache_entry *ce, · line 282
Quoted 10 lines
entry.c +10
-0+*/ + 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);
We found that the path ce->name is supposed to be a submodule that is checked out, we found that the submodule is not yet populated, we tried to make sure what is on that path, and its failure would cause us to die(). All that is sensible.
Then we want to make sure the filesystem entity at the path currently is a directory and otherwise unlink (i.e. we may have an unrelated file that is not tracked there, or perhaps the user earlier decided that replacing the submodule with a single file is a good idea, but then getting rid of the change with "checkout -f" before doing "git add" on that file). That is also sensible.
But if that unlink fails, shouldn't we die, just like the case where we cannot tell what is at the path ce->name?
And if that unlink succeeds, instead of having an empty directory, we start the "move-head" call to switch from NULL to ce->oid without having any directory. Wouldn't we want to mkdir() here (and remove mkdir() in submodule_move_head() if there is one---if there isn't then I do not think this codepath would work)?
If we had a directory here, but if that directory is not empty, should we proceed? I am assuming (I haven't carefully read "move-head") that it is OK because the "run an equivalent of 'checkout --detach ce->oid'" done by "move-head" would notice a possible information loss to overwrite an untracked path (everything is "untracked" as the head moves from NULL to ce->oid in this case), and prevent it from happening.
Am I reading the design correctly?
Quoted 12 lines
entry.c +9
-0+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) {
Re: [PATCH 16/18] entry.c: update submodules when interesting
Quoted 15 lines · Junio C Hamano
entry.c int checkout_entry(struct cache_entry *ce, · line 282
Quoted 6 lines
entry.c +6
-0+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);We found that the path
ce->nameis supposed to be a submodule that is checked out, we found that the submodule is not yet populated, we tried to make sure what is on that path, and its failure would cause us todie(). All that is sensible.
...
But if that unlink fails, shouldn't we die, just like the case where we cannot tell what is at the pathce->name?
Quoted 5 lines
And if that unlink succeeds, instead of having an empty directory, we start the "move-head" call to switch from NULL to
ce->oidwithout having any directory. Wouldn't we want tomkdir()here (and removemkdir()insubmodule_move_head()if there is one---if there isn't then I do not think this codepath would work)?
In addition to mkdir(), would we also need the .git file that point into superproject's .git/modules/ too?
Re: [PATCH 16/18] entry.c: update submodules when interesting
Quoted 17 lines · Junio C Hamano
Quoted 15 lines · Junio C Hamano
entry.c int checkout_entry(struct cache_entry *ce, · line 282
Quoted 6 lines
entry.c +6
-0+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);We found that the path
ce->nameis supposed to be a submodule that is checked out, we found that the submodule is not yet populated, we tried to make sure what is on that path, and its failure would cause us todie(). All that is sensible.
...
But if that unlink fails, shouldn't we die, just like the case where we cannot tell what is at the pathce->name?
Quoted 8 lines
Quoted 5 lines
And if that unlink succeeds, instead of having an empty directory, we start the "move-head" call to switch from NULL to
ce->oidwithout having any directory. Wouldn't we want tomkdir()here (and removemkdir()insubmodule_move_head()if there is one---if there isn't then I do not think this codepath would work)?In addition to
mkdir(), would we also need the .git file that point into superproject's .git/modules/ too?
The move_head function takes care of it
Both creation as well as deletion are handled in the move_head function, when either new or old is NULL.
We can drag that out of that function and have it at the appropriate places manually.
Why do you think it is better design to have mkdir here and not in move_head? (For me having everything in move_head was easier to understand, maybe it is not for others)
Thanks,
Stefan
Re: [PATCH 16/18] entry.c: update submodules when interesting
Quoted 6 lines · Stefan Beller
Quoted 2 lines
In addition to
mkdir(), would we also need the .git file that point into superproject's .git/modules/ too?The
move_headfunction takes care of it
Both creation as well as deletion are handled in themove_headfunction, when either new or old is NULL.
Oh, if it does the creation of directory and adjusting of .git gitfile, and handles other anomalies, I have no problem with it.
It was just that this codepath tried to handle some kind of anomaly (namely, the user originally thought the submodule needs to be changed to a regular file and then changed mind and wants to restore it from the index) and yet not doing the full anomaly handling (like "not even an empty directory exists" case) that confused me.
PATCH v6 17/18 builtin/checkout: add --recurse-submodules switch
In reply to Stefan BellerThis exposes a flag to recurse into submodules in builtin/checkout making use of the code implemented in prior patches.
A new failure mode is introduced in the submodule update library, as the directory/submodule conflict is not solved in prior patches.
--- Documentation/git-checkout.txt | 7 +++++++ builtin/checkout.c | 28 ++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 23 +++++++++++++++++------ t/t2013-checkout-submodule.sh | 5 +++++ 4 files changed, 57 insertions(+), 6 deletions(-)
Documentation/git-checkout.txt +7 -0
@@ -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
builtin/checkout.c +28 -0
@@ -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" ));
t/lib-submodule-update.sh +17 -6
@@ -782,6 +782,11 @@ test_submodule_forced_switch () { test_submodule_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 checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -891,7 +896,7 @@ test_submodule_switch_recursing () { ' # Replacing a submodule with files in a directory must succeeds # when the submodule is clean - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULT "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -903,7 +908,7 @@ test_submodule_switch_recursing () { ) ' # ... absorbing a .git directory. - test_expect_success "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + test_expect_$RESULT "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -931,7 +936,7 @@ test_submodule_switch_recursing () { ' # ... must check its local work tree for untracked files - test_expect_success "$command: replace submodule with a file must fail with untracked files" ' + test_expect_$RESULT "$command: replace submodule with a file must fail with untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -987,7 +992,8 @@ test_submodule_switch_recursing () { ) ' - test_expect_success "$command: modified submodule updates submodule recursively" ' + # recursing deeper than one level doesn't work yet. + test_expect_failure "$command: modified submodule updates submodule recursively" ' prolog && reset_work_tree_to_interested add_nested_sub && (
@@ -1006,6 +1012,11 @@ test_submodule_switch_recursing () { # 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" '
@@ -1100,7 +1111,7 @@ test_submodule_forced_switch_recursing () { ) ' # Replacing a submodule with files in a directory ... - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULT "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -1151,7 +1162,7 @@ test_submodule_forced_switch_recursing () { ' # ... but stops for untracked files that would be lost - test_expect_success "$command: replace submodule with a file" ' + test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
t/t2013-checkout-submodule.sh +5 -0
@@ -63,6 +63,11 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ! test -s actual ' +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=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"
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
PATCH v6 18/18 builtin/read-tree: add --recurse-submodules switch
In reply to Stefan BellerA new known failure mode is introduced[1], which is actually not a failure but a feature in read-tree. Unlike checkout for which the recursive submodule tests were originally written, read-tree does warn about ignored untracked files that would be overwritten. For the sake of keeping the test library for submodules genric, just mark the test as a failure.
[1] KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED
--- Documentation/git-read-tree.txt | 6 ++++++ builtin/read-tree.c | 29 +++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 7 ++++++- t/t1013-read-tree-submodule.sh | 7 +++++++ 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-read-tree.txt +6 -0
@@ -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.
builtin/read-tree.c +29 -0
@@ -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 ]; +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?" );
t/lib-submodule-update.sh +6 -1
@@ -787,6 +787,11 @@ test_submodule_switch_recursing () { then RESULT=failure fi + RESULT1=success + if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 + then + RESULT1=failure + fi ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -827,7 +832,7 @@ test_submodule_switch_recursing () { ) ' # ... but an ignored file is fine. - test_expect_success "$command: added submodule removes an untracked ignored file" ' + test_expect_$RESULT1 "$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 &&
t/t1013-read-tree-submodule.sh +7 -0
@@ -5,6 +5,13 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +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"
Signature · 1 line
2.12.0.rc1.52.ge239d7e709.dirty
Re: [PATCH 18/18] builtin/read-tree: add --recurse-submodules switch
Quoted 5 lines · Stefan Beller
A new known failure mode is introduced[1], which is actually not a failure but a feature in read-tree. Unlike checkout for which the recursive submodule tests were originally written, read-tree does warn about ignored untracked files that would be overwritten. For the sake of keeping the test library for submodules genric, just
generic
v8 cover Checkout aware of Submodules!
In reply to Stefan Bellerprevious work:
https://public-inbox.org/git/[email protected]/
v8:
* Thanks Ramsay; another variable is static now!
* Thanks Junio; fixed typos (in comments and commit messages)!
* dropped the patch touching read-cache.c; I realized it is bogus after inspecting it and trying to write a commit message.
* All commits have commit messages now.
* diff to Junios version below.
v7:
* addressed Erics comment by fixing the bashism in t/lib-submodule-update.sh
diff to v6:
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 5caae06bc5..949ebd546c 100755
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -255,7 +255,7 @@ 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 "$1" == "-C"
+ if test x"$1" = "x-C"
then
cd "$2"
shift; shift;
* interdiff to v5 belowv6:
* added support for read-tree (see last patch) to see how generic the code of the previous patches is. I am pretty pleased with that patch.
* marked two functions static. Thanks Ramsay!
* fixed the recursive test; it still fails but it is the code that fails, not the test. For this I had to change the setup code slightly.
* 2 new patches adding tiny refactoring to the submodule test lib.
* interdiff (to origin/sb/checkout-recurse-submodules, which is v5) below.
v5:
* as v4 was the first version queued by Junio, we do have an interdiff below!
* renamed functions
* changed the API, now the caller has to take care of the submodule strategy themselves. (Note this can be different for different situations, e.g. when a submodule is deleted, we can do that for any strategy except none and !command. But for moving to a new state of the submodule we currently only implement "checkout" [unspecified defaults to checkout]. warning about the others, doing nothing there.)
v4:
* addressed all comments of Brian, Junio and Brandon. Thanks!
* one major point of change is the introduction of another patch "lib-submodule-update.sh: do not use ./. as submodule remote", as that took some time to track down the existing bug.
v3:
* moved tests from t2013 to the generic submodule library.
* factored out the refactoring patches to be up front
* As I redid the complete implementation, I have the impression this time it is cleaner than previous versions.
I think we still have to fix the corner cases of directory/file/submodule conflicts before merging, but this serves as a status update on my current way of thinking how to implement the worktree commands being aware of submodules.
Thanks,
Stefan
v2:
* based on top of the series sent out an hour ago
"[PATCHv4 0/5] submodule embedgitdirs"
* Try to embed a submodule if we need to remove it.
* Strictly do not change behavior if not giving the new flag.
* I think I missed some review comments from v1, but I'd like to get the current state out over the weekend, as a lot has changed so far. On Monday I'll go through the previous discussion with a comb to see if I missed something.
v1:
When working with submodules, nearly anytime after checking out a different state of the projects, that has submodules changed you'd run "git submodule update" with a current version of Git.
There are two problems with this approach:
* The "submodule update" command is dangerous as it doesn't check for work that may be lost in the submodule (e.g. a dangling commit).
* you may forget to run the command as checkout is supposed to do all the work for you.
Integrate updating the submodules into git checkout, with the same safety promises that git-checkout has, i.e. not throw away data unless asked to. This is done by first checking if the submodule is at the same sha1 as it is recorded in the superproject. If there are changes we stop proceeding the checkout just like it is when checking out a file that has local changes.
The integration happens in the code that is also used in other commands such that it will be easier in the future to make other commands aware of submodule.
This also solves d/f conflicts in case you replace a file/directory with a submodule or vice versa.
The patches are still a bit rough, but the overall series seems promising enough to me that I want to put it out here.
Any review, specifically on the design level welcome!
Thanks,
Stefan
builtin/read-tree.c +1 -1
@@ -21,7 +21,7 @@static int nr_trees;static int read_empty;static struct tree *trees[MAX_UNPACK_TREES ]; -int recurse_submodules =RECURSE_SUBMODULES_DEFAULT ; +static int recurse_submodules =RECURSE_SUBMODULES_DEFAULT ;static int list_tree (unsigned char *sha1) {
read-cache.c +2 -25
@@ -18,8 +18,6 @@#include "varint.h" #include "split-index.h" #include "utf8.h" -#include "submodule.h" -#include "submodule-config.h" /* Mask for the name length in ce_flags in the on-disk index */
@@ -522,22 +520,6 @@ int remove_index_entry_at(struct index_state *istate, int pos)return 1 ; } -static void remove_submodule_according_to_strategy (const struct submodule *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 (sub->path,"HEAD" ,NULL , \ -SUBMODULE_MOVE_HEAD_FORCE ); -break ; -case SM_UPDATE_NONE : -case SM_UPDATE_COMMAND : - ;/* Do not touch the submodule. */ - } -} -/* * Remove all cache entries marked for removal, that is where * CE_REMOVE is set in ce_flags. This is much more effective than
@@ -550,13 +532,8 @@ void remove_marked_cache_entries(struct index_state *istate)for (i = j =0 ; i < istate->cache_nr; i++) {if (ce_array[i]->ce_flags &CE_REMOVE ) { -const struct submodule *sub =submodule_from_ce (ce_array[i]); -if (sub) { -remove_submodule_according_to_strategy (sub); - }else { -remove_name_hash (istate, ce_array[i]); -save_or_free_index_entry (istate, ce_array[i]); - } +remove_name_hash (istate, ce_array[i]); +save_or_free_index_entry (istate, ce_array[i]); }else ce_array[j++] = ce_array[i];
t/lib-submodule-update.sh +1 -1
@@ -954,7 +954,7 @@ test_submodule_switch_recursing () { ) ' - # ... and ignored files are ignroed + # ... 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 &&
PATCH v8 1/17 lib-submodule-update.sh: reorder create_lib_submodule_repo
Redraw the ASCII art describing the setup using more space, such that it is easier to understand. The leaf commits are now ordered the same way the actual code is ordered.
Add empty lines to the setup code separating each of the leaf commits, each starting with a "checkout -b".
--- t/lib-submodule-update.sh | 49 ++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 20 deletions(-)
t/lib-submodule-update.sh +29 -20
@@ -15,22 +15,27 @@ # - 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 +# | / ^ +# | / 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_repo && (
@@ -49,10 +54,11 @@ create_lib_submodule_repo () { 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 +73,7 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify sub1" && - git checkout -b "replace_sub1_with_directory" "add_sub1" && + git checkout -b replace_sub1_with_directory add_sub1 && git submodule update && git -C sub1 checkout modifications && git rm --cached sub1 &&
@@ -75,22 +81,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 ) }
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 2/17 lib-submodule-update.sh: do not use ./. as submodule remote
In reply to Stefan BellerAdding the repository itself as a submodule does not make sense in the real world. In our test suite we used to do that out of convenience in some tests as the current repository has easiest access for setting up 'just a submodule'.
However this doesn't quite test the real world, so let's do not follow this pattern any further and actually create an independent repository that we can use as a submodule.
When using './.' as the remote the superproject and submodule share the same objects, such that testing if a given sha1 is a valid commit works in either repository. As running commands in an unpopulated submodule fall back to the superproject, this happens in reset_work_tree_to to determine if we need to populate the submodule. Fix this bug by checking in the actual remote now.
--- t/lib-submodule-update.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +13 -2
@@ -37,6 +37,17 @@ # 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_repo && ( cd submodule_update_repo &&
@@ -49,7 +60,7 @@ 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 &&
@@ -162,7 +173,7 @@ reset_work_tree_to () { 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}") + test $(cd "../submodule_update_sub1" && git rev-parse --verify "$sha1^{commit}") then git submodule update --init --recursive "sub1" fi
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 3/17 lib-submodule-update: teach test_submodule_content the -C <dir> flag
In reply to Stefan Beller--- t/lib-submodule-update.sh | 5 +++++ 1 file changed, 5 insertions(+)
t/lib-submodule-update.sh +5 -0
@@ -193,6 +193,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"
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 4/17 lib-submodule-update.sh: replace sha1 by hash
In reply to Stefan BellerCleaning up code by generalising it. Currently the mailing list discusses yet again how to migrate away from sha1.
--- t/lib-submodule-update.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
t/lib-submodule-update.sh +3 -3
@@ -171,9 +171,9 @@ 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 "../submodule_update_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
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 5/17 lib-submodule-update.sh: define tests for recursing into submodules
In reply to Stefan BellerCurrently lib-submodule-update.sh provides 2 functions test_submodule_switch and test_submodule_forced_switch that are used by a variety of tests to ensure that submodules behave as expected. The current expected behavior is that submodules are not touched at all (see 42639d2317a for the exact setup).
In the future we want to teach all these commands to recurse into submodules. To do that, we'll add two testing functions to submodule-update-lib.sh: test_submodule_switch_recursing and test_submodule_forced_switch_recursing.
These two functions behave in analogy to the already existing functions just with a different expectation on submodule behavior. The submodule in the working tree is expected to be updated to the recorded submodule version. The behavior is analogous to e.g. the behavior of files in a nested directory in the working tree, where a change to the working tree handles any arising directory/file conflicts just fine.
--- t/lib-submodule-update.sh | 506 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 504 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +504 -2
@@ -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 =>
@@ -19,8 +20,8 @@ # / ^ # / remove_sub1 # / -# add_sub1 /-------O -# | / ^ +# add_sub1 /-------O---------O--------O modify_sub1_recursively +# | / ^ add_nested_sub # | / modify_sub1 # v/ # O------O-----------O---------O
@@ -48,6 +49,17 @@ create_lib_submodule_repo () { 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 &&
@@ -84,6 +96,26 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify 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 &&
@@ -150,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.
@@ -180,6 +221,27 @@ reset_work_tree_to () { ) } +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).
@@ -700,3 +762,443 @@ 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" + ######################### 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_success "$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_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 && + test_submodule_content sub1 origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_success "$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_success "$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 + ) + ' + + test_expect_success "$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" + ######################### 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 && + test_must_fail $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_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 && + : >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 + ) + ' +}
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 6/17 make is_submodule_populated gently
In reply to Stefan BellerWe need the gentle version in a later patch. As we have just one caller, migrate the caller.
--- builtin/grep.c | 2 +- submodule.c | 7 ++----- submodule.h | 8 +++++++- 3 files changed, 10 insertions(+), 7 deletions(-)
builtin/grep.c +1 -1
@@ -616,7 +616,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.
submodule.c +2 -5
@@ -234,15 +234,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);
submodule.h +7 -1
@@ -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);
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 7/17 connect_work_tree_and_git_dir: safely create leading directories
In reply to Stefan BellerIn a later patch we'll use connect_work_tree_and_git_dir when the directory for the gitlink file doesn't exist yet. This patch makes connect_work_tree_and_git_dir safe to use for both cases of either the git dir or the working dir missing.
To do so, we need to call safe_create_leading_directories[_const] on both directories. However this has to happen before we construct the absolute paths as real_pathdup assumes the directories to be there already.
So for both the config file in the git dir as well as the .git link file we need to
a) construct the name
b) call SCLD
c) get the absolute path
d) once a-c is done for both we can consume the absolute path to compute the relative path to each other and store those relative paths.
The implementation provided here puts a) and b) for both cases first, and then performs c and d after.
One of the two users of 'connect_work_tree_and_git_dir' already checked for the directory being there, so we can loose that check as connect_work_tree_and_git_dir handles this functionality now.
--- dir.c | 32 +++++++++++++++++++++----------- submodule.c | 11 ++--------- 2 files changed, 23 insertions(+), 20 deletions(-)
dir.c +21 -11
@@ -2728,23 +2728,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_); -char *work_tree =real_pathdup (work_tree_); +char *git_dir, *work_tree; -/* Update gitfile */ -strbuf_addf (&file_name,"%s/.git" , work_tree); -write_file (file_name.buf,"gitdir: %s" , -relative_path (git_dir, work_tree, &rel_path)); +/* 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_); + work_tree =real_pathdup (work_tree_); + +/* 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);
submodule.c +2 -9
@@ -1442,8 +1442,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 ) {
@@ -1466,13 +1464,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); -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);
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
Re: [PATCH 07/17] connect_work_tree_and_git_dir: safely create leading directories
Quoted 54 lines · Stefan Beller
In a later patch we'll use
connect_work_tree_and_git_dirwhen the directory for the gitlink file doesn't exist yet. This patch makesconnect_work_tree_and_git_dirsafe to use for both cases of either the git dir or the working dir missing.To do so, we need to call safe_create_leading_directories[_const] on both directories. However this has to happen before we construct the absolute paths as
real_pathdupassumes the directories to be there already.So for both the config file in the git dir as well as the .git link file we need to
a) construct the name
b) call SCLD
c) get the absolute path
d) once a-c is done for both we can consume the absolute path to compute the relative path to each other and store those relative paths.The implementation provided here puts a) and b) for both cases first, and then performs c and d after.
One of the two users of '
connect_work_tree_and_git_dir' already checked for the directory being there, so we can loose that check asconnect_work_tree_and_git_dirhandles this functionality now.Signed-off-by Stefan Beller <[email protected]>--- dir.c | 32 +++++++++++++++++++++----------- submodule.c | 11 ++--------- 2 files changed, 23 insertions(+), 20 deletions(-)dir.c +5
-7@@ -2728,23 +2728,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_); -char *work_tree =real_pathdup (work_tree_); +char *git_dir, *work_tree; -/* Update gitfile */ -strbuf_addf (&file_name,"%s/.git" , work_tree); -write_file (file_name.buf,"gitdir: %s" , -relative_path (git_dir, work_tree, &rel_path)); +/* Prepare .git file */ +strbuf_addf (&gitfile_sb,"%s/.git" , work_tree_);
dir.c void untracked_cache_add_to_index(struct index_state *istate, · line 2747
Quoted 10 lines
dir.c +9
-0+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_); + work_tree =real_pathdup (work_tree_);
Just a note that this is a spot that'll be affected by the change to real_pathdup() which adds a 'die_on_error' parameter to correct bad behaviour I introduced.
Quoted 49 lines
dir.c +7
-4+ +/* 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);submodule.c +2
-9@@ -1442,8 +1442,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 ) {@@ -1466,13 +1464,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); -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);Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
Signature · 1 line
Brandon Williams
Re: [PATCH 07/17] connect_work_tree_and_git_dir: safely create leading directories
Quoted 6 lines · Brandon Williams
dir.c void untracked_cache_add_to_index(struct index_state *istate, · line 2747
Quoted 2 lines
dir.c +2
-0+ git_dir =real_pathdup (git_dir_); + work_tree =real_pathdup (work_tree_);Just a note that this is a spot that'll be affected by the change to
real_pathdup()which adds a 'die_on_error' parameter to correct bad behaviour I introduced.
Thanks for a reminder. I _think_ I resolved this correctly when I replaced the previous one with this round to re-queue on 'pu'.
PATCH v8 8/17 update submodules: add submodule config parsing
In reply to Stefan BellerSimilar to b33a15b08 (push: add recurseSubmodules config option, 2015-11-17) and 027771fcb1 (submodule: allow erroneous values for the fetchRecurseSubmodules option, 2015-08-17), we add submodule-config code that is later used to parse whether we are interested in updating submodules.
We need the die_on_error parameter to be able to call this parsing function for the config file as well, which if incorrect lets Git die.
As we're just touching the header file, also mark all functions extern.
--- submodule-config.c | 20 ++++++++++++++++++++ submodule-config.h | 17 +++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-)
submodule-config.c +20 -0
@@ -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) {
submodule-config.h +9 -8
@@ -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 */
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 9/17 update submodules: add a config option to determine if submodules are updated
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
Have a central place to store such settings whether we want to update a submodule.
--- submodule.c | 6 ++++++ submodule.h | 1 + 2 files changed, 7 insertions(+)
submodule.c +6 -0
@@ -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;
@@ -542,6 +543,11 @@ 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; +} +static int has_remote (const char *refname,const struct object_id *oid,int flags,void *cb_data) {
submodule.h +1 -0
@@ -64,6 +64,7 @@ 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);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,
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 10/17 submodules: introduce check to see whether to touch a submodule
In reply to Stefan BellerIn later patches we introduce the --recurse-submodule flag for commands that modify the working directory, e.g. git-checkout.
It is potentially expensive to check if a submodule needs an update, because a common theme to interact with submodules is to spawn a child process for each interaction.
So let's introduce a function that checks if a submodule needs to be checked for an update before attempting the update.
--- submodule.c | 16 ++++++++++++++++ submodule.h | 7 +++++++ 2 files changed, 23 insertions(+)
submodule.c +16 -0
@@ -548,6 +548,22 @@ 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) {
submodule.h +7 -0
@@ -65,6 +65,13 @@ extern void show_submodule_inline_diff(FILE *f, const char *path, 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,
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 11/17 update submodules: move up prepare_submodule_repo_env
In reply to Stefan BellerIn a later patch we need to prepare the submodule environment with another git directory, so split up the function.
Also move it up in the file such that we do not need to declare the function later before using it.
--- submodule.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)
submodule.c +17 -12
@@ -356,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
@@ -1390,18 +1407,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.
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 12/17 update submodules: add submodule_move_head
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
This piece of code will be used universally for all these working tree modifications as it
* supports dry run to answer the question:
"Is it safe to change the submodule to this new state?" e.g. is it overwriting untracked files or are there local changes that would be overwritten?
* supports a force flag that can be used for resetting the tree.
--- submodule.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ submodule.h | 7 ++++ 2 files changed, 142 insertions(+)
submodule.c +135 -0
@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; } +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/" , 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/" , 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) {
submodule.h +7 -0
@@ -96,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
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
Re: [PATCH 12/17] update submodules: add submodule_move_head
Quoted 25 lines · Stefan Beller
In later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
This piece of code will be used universally for all these working tree modifications as it
* supports dry run to answer the question:
"Is it safe to change the submodule to this new state?" e.g. is it overwriting untracked files or are there local changes that would be overwritten?
* supports a force flag that can be used for resetting the tree.Signed-off-by Stefan Beller <[email protected]>--- submodule.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ submodule.h | 7 ++++ 2 files changed, 142 insertions(+)submodule.c +0
-0@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; }
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1250
Quoted 10 lines
submodule.c +9
-0+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 );
The formatting of this line is a little odd. Also you can drop the backslash.
Mostly because I haven't dug too deep yet/can't remember: Does diff-index do the correct thing with nested submodules? If I remember correctly it does so this shouldn't be a problem.
Quoted 150 lines
submodule.c +126
-0+ 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/" , 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/" , 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) {submodule.h +7
-0@@ -96,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 Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
Signature · 1 line
Brandon Williams
Re: [PATCH 12/17] update submodules: add submodule_move_head
Quoted 8 lines · Brandon Williams
Quoted 8 lines
submodule.c +0
-0@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; }
Quoted 12 lines
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1250
Quoted 9 lines
submodule.c +9
-0+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 );The formatting of this line is a little odd. Also you can drop the backslash.
Heh. I think I saw and pointed out the same during the review of the previous round X-<. It is a bit disappointing.
Quoted 5 lines
Quoted 5 lines
submodule.c +5
-0+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); +
Quoted 8 lines
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1272
Quoted 8 lines
submodule.c +8
-0+ cp.git_cmd =1 ; + cp.no_stdin =1 ; + cp.dir = path; + +argv_array_pushf (&cp.args,"--super-prefix=%s/" , path); +argv_array_pushl (&cp.args,"read-tree" ,"-u" ,"--reset" ,NULL ); + +argv_array_push (&cp.args,EMPTY_TREE_SHA1_HEX );
Somewhat related; will this use of --super-prefix be affected when we split it into two for "adjust pathspec" prefix and "adjust output" prefix?
Re: [PATCH 12/17] update submodules: add submodule_move_head
Quoted 35 lines · Junio C Hamano
Quoted 8 lines · Brandon Williams
Quoted 8 lines
submodule.c +0
-0@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; }Quoted 12 lines
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1250
Quoted 9 lines
submodule.c +9
-0+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 );The formatting of this line is a little odd. Also you can drop the backslash.
Heh. I think I saw and pointed out the same during the review of the previous round X-<. It is a bit disappointing.
Quoted 9 lines
Quoted 9 lines
submodule.c +9
-0+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; +
Quoted 8 lines
Quoted 4 lines
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1272
Quoted 4 lines
submodule.c +4
-0+argv_array_pushf (&cp.args,"--super-prefix=%s/" , path); +argv_array_pushl (&cp.args,"read-tree" ,"-u" ,"--reset" ,NULL ); + +argv_array_push (&cp.args,EMPTY_TREE_SHA1_HEX );Somewhat related; will this use of --super-prefix be affected when we split it into two for "adjust pathspec" prefix and "adjust output" prefix?
Maybe? From what we discussed we can have 'super_prefix' maintain its current meaning (Path from root of parent down to the submodule) while adding a new env var or option to pass the 'prefix' variable to the child process (Path from root of parent to the directory the original cmd was executed in).
So this shouldn't be affected unless we decide to change the semantics of 'super_prefix'.
And I'm always torn on how best to do naming for the newer functionality. I think the easiest thing to do would be to have a --prefix option to pass the 'prefix' from one process to the next, but I'm sure there are other opinions on the matter.
Signature · 1 line
Brandon Williams
Re: [PATCH 12/17] update submodules: add submodule_move_head
In reply to Junio C HamanoQuoted 23 lines · Junio C Hamano
Quoted 8 lines · Brandon Williams
Quoted 8 lines
submodule.c +0
-0@@ -1239,6 +1239,141 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; }Quoted 12 lines
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1250
Quoted 9 lines
submodule.c +9
-0+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 );The formatting of this line is a little odd. Also you can drop the backslash.
Quoted 2 lines
Heh. I think I saw and pointed out the same during the review of the previous round X-<. It is a bit disappointing.
Yes. I remember having a local mixup of branches, such that I fear I may have missed other feedback of the previous round as well then. I do remember removing a backslash, so maybe I got confused with another backslash as well.
I'll reroll with this fixed.
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
submodule.c +9
-0+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; +
Quoted 8 lines
Quoted 4 lines
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1272
Quoted 4 lines
submodule.c +4
-0+argv_array_pushf (&cp.args,"--super-prefix=%s/" , path); +argv_array_pushl (&cp.args,"read-tree" ,"-u" ,"--reset" ,NULL ); + +argv_array_push (&cp.args,EMPTY_TREE_SHA1_HEX );Somewhat related; will this use of --super-prefix be affected when we split it into two for "adjust pathspec" prefix and "adjust output" prefix?
Let's see how the superproject prefixing evolves and what Brandon sends out. When the superprefix changes its meaning I'll adjust here.
Re: [PATCH 12/17] update submodules: add submodule_move_head
In reply to Stefan BellerQuoted 39 lines · Stefan Beller
submodule.c +39
-0+/** +* 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);
submodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1326
Quoted 10 lines
submodule.c +10
-0+ } + } + +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/" , path);
Missed this one too. Same question as the other spot.
Signature · 1 line
Brandon Williams
Re: [PATCH 12/17] update submodules: add submodule_move_head
In reply to Stefan Bellersubmodule.c int bad_to_remove_submodule(const char *path, unsigned flags) · line 1269
Quoted 10 lines · Stefan Beller
submodule.c +10
-0+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/" , path);
What happens with nested submodules? As in can we reach this code path with super_prefix already being set? If so we would need to include that as part of the super_prefix being passed to the child. Just looking for some clarification.
Quoted 8 lines
submodule.c +8
-0+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" ); +} +
Signature · 1 line
Brandon Williams
PATCH v8 13/17 unpack-trees: pass old oid to verify_clean_submodule
In reply to Stefan BellerThe check (which uses the old oid) is yet to be implemented, but this part is just a refactor, so it can go separately first.
--- unpack-trees.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-)
unpack-trees.c +10 -7
@@ -1407,7 +1407,8 @@ 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) {
@@ -1427,16 +1428,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); }/*
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 14/17 unpack-trees: check if we can perform the operation for submodules
In reply to Stefan BellerIn a later patch we'll support submodule entries to be in sync with the tree in working tree changing commands, such as checkout or read-tree.
When a new submodule entry changes in the tree, we need to check if there are conflicts (directory/file conflicts) for the tree. Add this check for submodules to be performed before the working tree is touched.
--- unpack-trees.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- unpack-trees.h | 1 + 2 files changed, 123 insertions(+), 9 deletions(-)
unpack-trees.c +122 -9
@@ -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 )
@@ -1412,7 +1494,11 @@ static int verify_clean_submodule(const char *old_sha1, 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,
@@ -1578,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)) {
@@ -1588,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);
@@ -1643,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?
@@ -1663,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
unpack-trees.h +1 -0
@@ -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 };
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 15/17 entry.c: create submodules when interesting
In reply to Stefan BellerWhen a submodule is introduced with a new revision we need to create the submodule in the worktree as well. As 'submodule_move_head' handles edge cases, all we have to do is call it from within the function that creates new files in the working tree for workingtree operations.
--- entry.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
entry.c +30 -0
@@ -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) {
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 16/17 builtin/checkout: add --recurse-submodules switch
In reply to Stefan BellerThis exposes a flag to recurse into submodules in builtin/checkout making use of the code implemented in prior patches.
A new failure mode is introduced in the submodule update library, as the directory/submodule conflict is not solved in prior patches.
--- Documentation/git-checkout.txt | 7 +++++++ builtin/checkout.c | 28 ++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 23 +++++++++++++++++------ t/t2013-checkout-submodule.sh | 5 +++++ 4 files changed, 57 insertions(+), 6 deletions(-)
Documentation/git-checkout.txt +7 -0
@@ -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
builtin/checkout.c +28 -0
@@ -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" ));
t/lib-submodule-update.sh +17 -6
@@ -782,6 +782,11 @@ test_submodule_forced_switch () { test_submodule_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 checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -891,7 +896,7 @@ test_submodule_switch_recursing () { ' # Replacing a submodule with files in a directory must succeeds # when the submodule is clean - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULT "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -903,7 +908,7 @@ test_submodule_switch_recursing () { ) ' # ... absorbing a .git directory. - test_expect_success "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + test_expect_$RESULT "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -931,7 +936,7 @@ test_submodule_switch_recursing () { ' # ... must check its local work tree for untracked files - test_expect_success "$command: replace submodule with a file must fail with untracked files" ' + test_expect_$RESULT "$command: replace submodule with a file must fail with untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -987,7 +992,8 @@ test_submodule_switch_recursing () { ) ' - test_expect_success "$command: modified submodule updates submodule recursively" ' + # recursing deeper than one level doesn't work yet. + test_expect_failure "$command: modified submodule updates submodule recursively" ' prolog && reset_work_tree_to_interested add_nested_sub && (
@@ -1006,6 +1012,11 @@ test_submodule_switch_recursing () { # 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" '
@@ -1100,7 +1111,7 @@ test_submodule_forced_switch_recursing () { ) ' # Replacing a submodule with files in a directory ... - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULT "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -1151,7 +1162,7 @@ test_submodule_forced_switch_recursing () { ' # ... but stops for untracked files that would be lost - test_expect_success "$command: replace submodule with a file" ' + test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
t/t2013-checkout-submodule.sh +5 -0
@@ -63,6 +63,11 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ! test -s actual ' +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=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"
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
PATCH v8 17/17 builtin/read-tree: add --recurse-submodules switch
In reply to Stefan BellerA new known failure mode is introduced[1], which is actually not a failure but a feature in read-tree. Unlike checkout for which the recursive submodule tests were originally written, read-tree does warn about ignored untracked files that would be overwritten. For the sake of keeping the test library for submodules generic, just mark the test as a failure.
[1] KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED
--- Documentation/git-read-tree.txt | 6 ++++++ builtin/read-tree.c | 29 +++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 7 ++++++- t/t1013-read-tree-submodule.sh | 7 +++++++ 4 files changed, 48 insertions(+), 1 deletion(-)
Documentation/git-read-tree.txt +6 -0
@@ -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.
builtin/read-tree.c +29 -0
@@ -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?" );
t/lib-submodule-update.sh +6 -1
@@ -787,6 +787,11 @@ test_submodule_switch_recursing () { then RESULT=failure fi + RESULT1=success + if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 + then + RESULT1=failure + fi ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -827,7 +832,7 @@ test_submodule_switch_recursing () { ) ' # ... but an ignored file is fine. - test_expect_success "$command: added submodule removes an untracked ignored file" ' + test_expect_$RESULT1 "$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 &&
t/t1013-read-tree-submodule.sh +7 -0
@@ -5,6 +5,13 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +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"
Signature · 1 line
2.12.0.rc1.45.g207f5fbb2b
v9 cover Checkout aware of Submodules!
In reply to Stefan Bellerprevious work:
https://public-inbox.org/git/[email protected]/
v9:
* inderdiff to current origin/sb/checkout-recurse-submodules below
* fixed a '\' that was violating the style guide.
* reordered the patches, such that
-> the first two patches are Valerys series and could go on its own as
a cleanup
-> added a new patch "submodule.c: get_super_prefix_or_empty" to
have cleaner code.
* split up the error conditions in the test lib for readability
v1..v8: see description in link to previous work above.
Thanks,
Stefan
submodule.c +16 -10
@@ -1239,6 +1239,14 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; } +static const char *get_super_prefix_or_empty () +{ +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 ;
@@ -1246,8 +1254,8 @@ static int submodule_has_dirty_index(const struct submodule *sub)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 ); +argv_array_pushl (&cp.args,"diff-index" ,"--quiet" , +"--cached" ,"HEAD" ,NULL ); cp.no_stdin =1 ; cp.no_stdout =1 ; cp.dir = sub->path;
@@ -1266,7 +1274,8 @@ static void submodule_reset_index(const char *path) cp.no_stdin =1 ; cp.dir = path; -argv_array_pushf (&cp.args,"--super-prefix=%s/" , 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 );
@@ -1323,7 +1332,8 @@ int submodule_move_head(const char *path, cp.no_stdin =1 ; cp.dir = path; -argv_array_pushf (&cp.args,"--super-prefix=%s/" , 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 )
@@ -1573,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); -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);
@@ -1648,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,'/' );
t/lib-submodule-update.sh +16 -11
@@ -782,15 +782,20 @@ test_submodule_forced_switch () { test_submodule_switch_recursing () { command="$1" - RESULT=success + RESULTDS=success if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 then - RESULT=failure + RESULTDS=failure + fi + RESULTR=success + if test "$KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED" = 1 + then + RESULTR=failure fi - RESULT1=success + RESULTOI=success if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 then - RESULT1=failure + RESULTOI=failure fi ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear checks it out ...
@@ -832,7 +837,7 @@ test_submodule_switch_recursing () { ) ' # ... but an ignored file is fine. - test_expect_$RESULT1 "$command: added submodule removes an untracked ignored file" ' + 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 &&
@@ -901,7 +906,7 @@ test_submodule_switch_recursing () { ' # Replacing a submodule with files in a directory must succeeds # when the submodule is clean - test_expect_$RESULT "$command: replace submodule with a directory" ' + test_expect_$RESULTDS "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -913,7 +918,7 @@ test_submodule_switch_recursing () { ) ' # ... absorbing a .git directory. - test_expect_$RESULT "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + 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 && (
@@ -941,7 +946,7 @@ test_submodule_switch_recursing () { ' # ... must check its local work tree for untracked files - test_expect_$RESULT "$command: replace submodule with a file must fail with untracked files" ' + test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -998,7 +1003,7 @@ test_submodule_switch_recursing () { ' # recursing deeper than one level doesn't work yet. - test_expect_failure "$command: modified submodule updates submodule recursively" ' + test_expect_$RESULTR "$command: modified submodule updates submodule recursively" ' prolog && reset_work_tree_to_interested add_nested_sub && (
@@ -1116,13 +1121,13 @@ test_submodule_forced_switch_recursing () { ) ' # Replacing a submodule with files in a directory ... - test_expect_$RESULT "$command: replace submodule with 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 && - test_must_fail $command replace_sub1_with_directory && + $command replace_sub1_with_directory && test_superproject_content origin/replace_sub1_with_directory ) '
t/t1013-read-tree-submodule.sh +1 -0
@@ -5,6 +5,7 @@ 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
t/t2013-checkout-submodule.sh +1 -0
@@ -64,6 +64,7 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ' 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"
PATCH v9 1/19 connect_work_tree_and_git_dir: safely create leading directories
In a later patch we'll use connect_work_tree_and_git_dir when the directory for the gitlink file doesn't exist yet. This patch makes connect_work_tree_and_git_dir safe to use for both cases of either the git dir or the working dir missing.
To do so, we need to call safe_create_leading_directories[_const] on both directories. However this has to happen before we construct the absolute paths as real_pathdup assumes the directories to be there already.
So for both the config file in the git dir as well as the .git link file we need to
a) construct the name
b) call SCLD
c) get the absolute path
d) once a-c is done for both we can consume the absolute path to compute the relative path to each other and store those relative paths.
The implementation provided here puts a) and b) for both cases first, and then performs c and d after.
One of the two users of 'connect_work_tree_and_git_dir' already checked for the directory being there, so we can loose that check as connect_work_tree_and_git_dir handles this functionality now.
--- dir.c | 32 +++++++++++++++++++++----------- submodule.c | 11 ++--------- 2 files changed, 23 insertions(+), 20 deletions(-)
dir.c +21 -11
@@ -2728,23 +2728,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_); -char *work_tree =real_pathdup (work_tree_); +char *git_dir, *work_tree; -/* Update gitfile */ -strbuf_addf (&file_name,"%s/.git" , work_tree); -write_file (file_name.buf,"gitdir: %s" , -relative_path (git_dir, work_tree, &rel_path)); +/* 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_); + work_tree =real_pathdup (work_tree_); + +/* 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);
submodule.c +2 -9
@@ -1445,8 +1445,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 +1467,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); -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);
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 2/19 submodule--helper.c: remove duplicate code
In reply to Stefan BellerFrom: Valery Tolstov <[email protected]>
Remove code fragment from module_clone that duplicates functionality of connect_work_tree_and_git_dir in dir.c
--- builtin/submodule--helper.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-)
builtin/submodule--helper.c +2 -20
@@ -579,9 +579,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 ;
@@ -653,27 +651,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); - -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); +/* Connect module worktree and git dir */ +connect_work_tree_and_git_dir (path, sm_gitdir); -/* 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);
@@ -689,7 +672,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);
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 3/19 lib-submodule-update.sh: reorder create_lib_submodule_repo
In reply to Stefan BellerRedraw the ASCII art describing the setup using more space, such that it is easier to understand. The leaf commits are now ordered the same way the actual code is ordered.
Add empty lines to the setup code separating each of the leaf commits, each starting with a "checkout -b".
--- t/lib-submodule-update.sh | 49 ++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 20 deletions(-)
t/lib-submodule-update.sh +29 -20
@@ -15,22 +15,27 @@ # - 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 +# | / ^ +# | / 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_repo && (
@@ -49,10 +54,11 @@ create_lib_submodule_repo () { 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 +73,7 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify sub1" && - git checkout -b "replace_sub1_with_directory" "add_sub1" && + git checkout -b replace_sub1_with_directory add_sub1 && git submodule update && git -C sub1 checkout modifications && git rm --cached sub1 &&
@@ -75,22 +81,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 ) }
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 4/19 lib-submodule-update.sh: do not use ./. as submodule remote
In reply to Stefan BellerAdding the repository itself as a submodule does not make sense in the real world. In our test suite we used to do that out of convenience in some tests as the current repository has easiest access for setting up 'just a submodule'.
However this doesn't quite test the real world, so let's do not follow this pattern any further and actually create an independent repository that we can use as a submodule.
When using './.' as the remote the superproject and submodule share the same objects, such that testing if a given sha1 is a valid commit works in either repository. As running commands in an unpopulated submodule fall back to the superproject, this happens in reset_work_tree_to to determine if we need to populate the submodule. Fix this bug by checking in the actual remote now.
--- t/lib-submodule-update.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +13 -2
@@ -37,6 +37,17 @@ # 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_repo && ( cd submodule_update_repo &&
@@ -49,7 +60,7 @@ 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 &&
@@ -162,7 +173,7 @@ reset_work_tree_to () { 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}") + test $(cd "../submodule_update_sub1" && git rev-parse --verify "$sha1^{commit}") then git submodule update --init --recursive "sub1" fi
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 5/19 lib-submodule-update: teach test_submodule_content the -C <dir> flag
In reply to Stefan Beller--- t/lib-submodule-update.sh | 5 +++++ 1 file changed, 5 insertions(+)
t/lib-submodule-update.sh +5 -0
@@ -193,6 +193,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"
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 6/19 lib-submodule-update.sh: replace sha1 by hash
In reply to Stefan BellerCleaning up code by generalising it. Currently the mailing list discusses yet again how to migrate away from sha1.
--- t/lib-submodule-update.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
t/lib-submodule-update.sh +3 -3
@@ -171,9 +171,9 @@ 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 "../submodule_update_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
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 7/19 lib-submodule-update.sh: define tests for recursing into submodules
In reply to Stefan BellerCurrently lib-submodule-update.sh provides 2 functions test_submodule_switch and test_submodule_forced_switch that are used by a variety of tests to ensure that submodules behave as expected. The current expected behavior is that submodules are not touched at all (see 42639d2317a for the exact setup).
In the future we want to teach all these commands to recurse into submodules. To do that, we'll add two testing functions to submodule-update-lib.sh: test_submodule_switch_recursing and test_submodule_forced_switch_recursing.
These two functions behave in analogy to the already existing functions just with a different expectation on submodule behavior. The submodule in the working tree is expected to be updated to the recorded submodule version. The behavior is analogous to e.g. the behavior of files in a nested directory in the working tree, where a change to the working tree handles any arising directory/file conflicts just fine.
--- t/lib-submodule-update.sh | 506 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 504 insertions(+), 2 deletions(-)
t/lib-submodule-update.sh +504 -2
@@ -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 =>
@@ -19,8 +20,8 @@ # / ^ # / remove_sub1 # / -# add_sub1 /-------O -# | / ^ +# add_sub1 /-------O---------O--------O modify_sub1_recursively +# | / ^ add_nested_sub # | / modify_sub1 # v/ # O------O-----------O---------O
@@ -48,6 +49,17 @@ create_lib_submodule_repo () { 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 &&
@@ -84,6 +96,26 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify 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 &&
@@ -150,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.
@@ -180,6 +221,27 @@ reset_work_tree_to () { ) } +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).
@@ -700,3 +762,443 @@ 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" + ######################### 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_success "$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_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 && + test_submodule_content sub1 origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_success "$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_success "$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 + ) + ' + + test_expect_success "$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" + ######################### 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_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 && + : >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 + ) + ' +}
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 8/19 make is_submodule_populated gently
In reply to Stefan BellerWe need the gentle version in a later patch. As we have just one caller, migrate the caller.
--- builtin/grep.c | 2 +- submodule.c | 7 ++----- submodule.h | 8 +++++++- 3 files changed, 10 insertions(+), 7 deletions(-)
builtin/grep.c +1 -1
@@ -616,7 +616,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.
submodule.c +2 -5
@@ -234,15 +234,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);
submodule.h +7 -1
@@ -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);
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 9/19 update submodules: add submodule config parsing
In reply to Stefan BellerSimilar to b33a15b08 (push: add recurseSubmodules config option, 2015-11-17) and 027771fcb1 (submodule: allow erroneous values for the fetchRecurseSubmodules option, 2015-08-17), we add submodule-config code that is later used to parse whether we are interested in updating submodules.
We need the die_on_error parameter to be able to call this parsing function for the config file as well, which if incorrect lets Git die.
As we're just touching the header file, also mark all functions extern.
--- submodule-config.c | 20 ++++++++++++++++++++ submodule-config.h | 17 +++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-)
submodule-config.c +20 -0
@@ -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) {
submodule-config.h +9 -8
@@ -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 */
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 10/19 update submodules: add a config option to determine if submodules are updated
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
Have a central place to store such settings whether we want to update a submodule.
--- submodule.c | 6 ++++++ submodule.h | 1 + 2 files changed, 7 insertions(+)
submodule.c +6 -0
@@ -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;
@@ -542,6 +543,11 @@ 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; +} +static int has_remote (const char *refname,const struct object_id *oid,int flags,void *cb_data) {
submodule.h +1 -0
@@ -64,6 +64,7 @@ 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);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,
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 11/19 submodules: introduce check to see whether to touch a submodule
In reply to Stefan BellerIn later patches we introduce the --recurse-submodule flag for commands that modify the working directory, e.g. git-checkout.
It is potentially expensive to check if a submodule needs an update, because a common theme to interact with submodules is to spawn a child process for each interaction.
So let's introduce a function that checks if a submodule needs to be checked for an update before attempting the update.
--- submodule.c | 16 ++++++++++++++++ submodule.h | 7 +++++++ 2 files changed, 23 insertions(+)
submodule.c +16 -0
@@ -548,6 +548,22 @@ 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) {
submodule.h +7 -0
@@ -65,6 +65,13 @@ extern void show_submodule_inline_diff(FILE *f, const char *path, 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,
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 12/19 update submodules: move up prepare_submodule_repo_env
In reply to Stefan BellerIn a later patch we need to prepare the submodule environment with another git directory, so split up the function.
Also move it up in the file such that we do not need to declare the function later before using it.
--- submodule.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)
submodule.c +17 -12
@@ -356,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
@@ -1390,18 +1407,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.
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 13/19 submodule.c: get_super_prefix_or_empty
In reply to Stefan BellerIn a later patch we need to use the super_prefix, and in case it is NULL we can just assume it is empty. Create a helper function for this.
We already have some use cases for this helper function, convert them, too.
--- submodule.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-)
submodule.c +10 -6
@@ -1239,6 +1239,14 @@ int bad_to_remove_submodule(const char *path, unsigned flags)return ret; } +static const char *get_super_prefix_or_empty () +{ +const char *s =get_super_prefix (); +if (!s) + s ="" ; +return s; +} +static int find_first_merges (struct object_array *result,const char *path,struct commit *a,struct commit *b) {
@@ -1438,11 +1446,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); -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);
@@ -1513,8 +1518,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,'/' );
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 14/19 update submodules: add submodule_move_head
In reply to Stefan BellerIn later patches we introduce the options and flag for commands that modify the working directory, e.g. git-checkout.
This piece of code will be used universally for all these working tree modifications as it
* supports dry run to answer the question:
"Is it safe to change the submodule to this new state?" e.g. is it overwriting untracked files or are there local changes that would be overwritten?
* supports a force flag that can be used for resetting the tree.
--- submodule.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ submodule.h | 7 ++++ 2 files changed, 144 insertions(+)
submodule.c +137 -0
@@ -1247,6 +1247,143 @@ static const char *get_super_prefix_or_empty()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) {
submodule.h +7 -0
@@ -96,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
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 15/19 unpack-trees: pass old oid to verify_clean_submodule
In reply to Stefan BellerThe check (which uses the old oid) is yet to be implemented, but this part is just a refactor, so it can go separately first.
--- unpack-trees.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-)
unpack-trees.c +10 -7
@@ -1407,7 +1407,8 @@ 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) {
@@ -1427,16 +1428,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); }/*
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 16/19 unpack-trees: check if we can perform the operation for submodules
In reply to Stefan BellerIn a later patch we'll support submodule entries to be in sync with the tree in working tree changing commands, such as checkout or read-tree.
When a new submodule entry changes in the tree, we need to check if there are conflicts (directory/file conflicts) for the tree. Add this check for submodules to be performed before the working tree is touched.
--- unpack-trees.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- unpack-trees.h | 1 + 2 files changed, 123 insertions(+), 9 deletions(-)
unpack-trees.c +122 -9
@@ -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 )
@@ -1412,7 +1494,11 @@ static int verify_clean_submodule(const char *old_sha1, 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,
@@ -1578,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)) {
@@ -1588,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);
@@ -1643,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?
@@ -1663,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
unpack-trees.h +1 -0
@@ -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 };
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 17/19 entry.c: create submodules when interesting
In reply to Stefan BellerWhen a submodule is introduced with a new revision we need to create the submodule in the worktree as well. As 'submodule_move_head' handles edge cases, all we have to do is call it from within the function that creates new files in the working tree for workingtree operations.
--- entry.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
entry.c +30 -0
@@ -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) {
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 18/19 builtin/checkout: add --recurse-submodules switch
In reply to Stefan BellerThis exposes a flag to recurse into submodules in builtin/checkout making use of the code implemented in prior patches.
A new failure mode is introduced in the submodule update library, as the directory/submodule conflict is not solved in prior patches.
--- Documentation/git-checkout.txt | 7 +++++++ builtin/checkout.c | 28 ++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 26 +++++++++++++++++++++----- t/t2013-checkout-submodule.sh | 6 ++++++ 4 files changed, 62 insertions(+), 5 deletions(-)
Documentation/git-checkout.txt +7 -0
@@ -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
builtin/checkout.c +28 -0
@@ -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" ));
t/lib-submodule-update.sh +21 -5
@@ -782,6 +782,16 @@ test_submodule_forced_switch () { 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 ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear checks it out ... test_expect_success "$command: added submodule is checked out" '
@@ -891,7 +901,7 @@ test_submodule_switch_recursing () { ' # Replacing a submodule with files in a directory must succeeds # when the submodule is clean - test_expect_success "$command: replace submodule with a directory" ' + test_expect_$RESULTDS "$command: replace submodule with a directory" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -903,7 +913,7 @@ test_submodule_switch_recursing () { ) ' # ... absorbing a .git directory. - test_expect_success "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + 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 && (
@@ -931,7 +941,7 @@ test_submodule_switch_recursing () { ' # ... must check its local work tree for untracked files - test_expect_success "$command: replace submodule with a file must fail with untracked files" ' + test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
@@ -987,7 +997,8 @@ test_submodule_switch_recursing () { ) ' - test_expect_success "$command: modified submodule updates submodule recursively" ' + # 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 && (
@@ -1006,6 +1017,11 @@ test_submodule_switch_recursing () { # 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" '
@@ -1151,7 +1167,7 @@ test_submodule_forced_switch_recursing () { ' # ... but stops for untracked files that would be lost - test_expect_success "$command: replace submodule with a file" ' + test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" ' prolog && reset_work_tree_to_interested add_sub1 && (
t/t2013-checkout-submodule.sh +6 -0
@@ -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"
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
PATCH v9 19/19 builtin/read-tree: add --recurse-submodules switch
In reply to Stefan BellerA new known failure mode is introduced[1], which is actually not a failure but a feature in read-tree. Unlike checkout for which the recursive submodule tests were originally written, read-tree does warn about ignored untracked files that would be overwritten. For the sake of keeping the test library for submodules generic, just mark the test as a failure.
[1] KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED
--- Documentation/git-read-tree.txt | 6 ++++++ builtin/read-tree.c | 29 +++++++++++++++++++++++++++++ t/lib-submodule-update.sh | 7 ++++++- t/t1013-read-tree-submodule.sh | 8 ++++++++ 4 files changed, 49 insertions(+), 1 deletion(-)
Documentation/git-read-tree.txt +6 -0
@@ -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.
builtin/read-tree.c +29 -0
@@ -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?" );
t/lib-submodule-update.sh +6 -1
@@ -792,6 +792,11 @@ test_submodule_switch_recursing () { 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" '
@@ -832,7 +837,7 @@ test_submodule_switch_recursing () { ) ' # ... but an ignored file is fine. - test_expect_success "$command: added submodule removes an untracked ignored file" ' + 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 &&
t/t1013-read-tree-submodule.sh +8 -0
@@ -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"
Signature · 1 line
2.12.0.rc1.49.g0cfd38c5f6.dirty
Re: [PATCHv9 00/19] Checkout aware of Submodules!
In reply to Stefan BellerQuoted 12 lines · Stefan Beller
previous work:
https://public-inbox.org/git/[email protected]/v9:
* inderdiff to current origin/sb/checkout-recurse-submodules below
* fixed a '\' that was violating the style guide.
* reordered the patches, such that
-> the first two patches are Valerys series and could go on its own as
a cleanup
-> added a new patch "submodule.c:get_super_prefix_or_empty" to
have cleaner code.
* split up the error conditions in the test lib for readability
Changes looks sensible to me.
Signature · 1 line
Brandon Williams