{"thread":{"id":"23415","subject":"[PATCH v2 0/9] Improve handling of moving and removing submodules","startedAt":"2010-04-10T18:23:41Z","lastAt":"2010-04-11T01:25:35Z","messageCount":14,"participants":["Peter Collingbourne","Sverre Rabbelier","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":9},"messages":[{"id":"139165","messageId":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":null,"subject":"[PATCH v2 0/9] Improve handling of moving and removing submodules","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:41Z","receivedAt":"2010-04-10T18:23:41Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"Changes since v1:\n - Generate a more user friendly unique ID\n - Added tests for \"git rm\" error cases (thanks Jonathan Nieder)\n\nPeter Collingbourne (9):\n      Generate unique ID for submodules created using \"git submodule add\"\n      Implement \"git mv\" for submodules\n      git rm: test failure behaviour for multiple removals\n      git rm: display a warning for every unremovable file\n      git rm: collect file modes\n      Add a mode parameter to the remove_path function\n      git rm: do not abort due to an initialised submodule\n      git submodule: infrastructure for reading .gitmodules files in arbitrary locations\n      git rm: remove submodule entries from .gitmodules\n\n Documentation/git-mv.txt        |    7 ++-\n Documentation/git-rm.txt        |    5 ++-\n Documentation/git-submodule.txt |    8 +++-\n builtin/apply.c                 |    2 +-\n builtin/mv.c                    |   33 +++++++++++--\n builtin/rm.c                    |   45 ++++++++++++++---\n dir.c                           |    4 +-\n dir.h                           |    2 +-\n git-submodule.sh                |   98 ++++++++++++++++++++++++++++++++++--\n merge-recursive.c               |   27 ++++++----\n t/t3600-rm.sh                   |   69 +++++++++++++++++++++++++\n t/t7403-submodule-sync.sh       |    2 +-\n t/t7405-submodule-merge.sh      |   13 +++++\n t/t7406-submodule-update.sh     |    6 +-\n t/t7407-submodule-foreach.sh    |   14 +++---\n t/t7409-submodule-mv-rm.sh      |  105 +++++++++++++++++++++++++++++++++++++++\n 16 files changed, 395 insertions(+), 45 deletions(-)\n"},{"id":"139171","messageId":"1270923830-11830-2-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 1/9] Generate unique ID for submodules created using \"git submodule add\"","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:42Z","receivedAt":"2010-04-10T18:23:42Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch causes \"git submodule add\" to generate a unique ID for\nthe submodule which is used as its name.  The ID is formed by\nconcatenating the basename of the submodule path, a dash (\"-\")\ncharacter and the 7-character truncated SHA1 hash of the pid, date\nand initial path.\n\nThe purpose of this patch is to avoid name conflicts which may arise\ndue to the ability to rename submodules.\n\nThe justification for truncating the SHA1 to 7 characters is that a\nsubmodule naming clash is a highly infrequent event (as compared to\na file naming clash) so we can minimise the ugliness of generated\nsubmodule names by using a smaller number of characters.\n\nThe justification for including the submodule path basename is that we\nshould include some component of the submodule path in the submodule\nname to make it possible to identify a particular submodule by its\nname.  At the same time we should be conscious of the fact that the\nsubmodule path may change.  Including the entire submodule path in\nthe submodule name is likely to confuse users if the path changes.\nThe basename is the component of the path which is least likely\nto change, which is a factor in favour of its inclusion in the\nsubmodule name.\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n Documentation/git-submodule.txt |    8 +++++++-\n git-submodule.sh                |   31 +++++++++++++++++++++++++++++--\n t/t7403-submodule-sync.sh       |    2 +-\n t/t7406-submodule-update.sh     |    6 +++---\n t/t7407-submodule-foreach.sh    |   14 +++++++-------\n 5 files changed, 47 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex 2502531..1bf78b6 100644\n--- a/Documentation/git-submodule.txt\n+++ b/Documentation/git-submodule.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git submodule' [--quiet] add [-b branch]\n-\t      [--reference <repository>] [--] <repository> [<path>]\n+\t      [--reference <repository>] [-n <name>] [--] <repository> [<path>]\n 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]\n 'git submodule' [--quiet] init [--] [<path>...]\n 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase]\n@@ -199,6 +199,12 @@ OPTIONS\n \t(the default). This limit only applies to modified submodules. The\n \tsize is always limited to 1 for added/deleted/typechanged submodules.\n \n+-n <name>::\n+--name <name>::\n+\tThis option is only valid for the add command.\n+\tName of the new submodule.  By default, a unique identifier\n+\tis generated.\n+\n -N::\n --no-fetch::\n \tThis option is only valid for the update command.\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 187461c..de29f3a 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -114,6 +114,23 @@ module_clone()\n }\n \n #\n+# Generate a unique identifier.  Used to name a submodule.\n+#\n+gen_uid()\n+{\n+\tpath=\"$1\"\n+\n+\tpathbase=$(basename \"$path\")\n+\techo -n \"$pathbase\"-\n+\n+\t(\n+\t echo \"$path\"\n+\t echo $$\n+\t date\n+\t) | git hash-object --stdin | cut -c1-7\n+}\n+\n+#\n # Add a new submodule to the working tree, .gitmodules and the index\n #\n # $@ = repo path\n@@ -131,6 +148,11 @@ cmd_add()\n \t\t\tbranch=$2\n \t\t\tshift\n \t\t\t;;\n+\t\t-n | --name)\n+\t\t\tcase \"$2\" in '') usage ;; esac\n+\t\t\tname=$2\n+\t\t\tshift\n+\t\t\t;;\n \t\t-q|--quiet)\n \t\t\tGIT_QUIET=1\n \t\t\t;;\n@@ -235,8 +257,13 @@ cmd_add()\n \tgit add \"$path\" ||\n \tdie \"Failed to add submodule '$path'\"\n \n-\tgit config -f .gitmodules submodule.\"$path\".path \"$path\" &&\n-\tgit config -f .gitmodules submodule.\"$path\".url \"$repo\" &&\n+\tif test -z \"$name\"\n+\tthen\n+\t\tname=$(gen_uid \"$path\")\n+\tfi\n+\n+\tgit config -f .gitmodules submodule.\"$name\".path \"$path\" &&\n+\tgit config -f .gitmodules submodule.\"$name\".url \"$repo\" &&\n \tgit add .gitmodules ||\n \tdie \"Failed to register submodule '$path'\"\n }\ndiff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh\nindex 7538756..f2c66f8 100755\n--- a/t/t7403-submodule-sync.sh\n+++ b/t/t7403-submodule-sync.sh\n@@ -18,7 +18,7 @@ test_expect_success setup '\n \tgit clone . super &&\n \tgit clone super submodule &&\n \t(cd super &&\n-\t git submodule add ../submodule submodule &&\n+\t git submodule add -n submodule ../submodule submodule &&\n \t test_tick &&\n \t git commit -m \"submodule\"\n \t) &&\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex 1382a8e..5bcac8f 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -31,7 +31,7 @@ test_expect_success 'setup a submodule tree' '\n \tgit clone super rebasing &&\n \tgit clone super merging &&\n \t(cd super &&\n-\t git submodule add ../submodule submodule &&\n+\t git submodule add -n submodule ../submodule submodule &&\n \t test_tick &&\n \t git commit -m \"submodule\" &&\n \t git submodule init submodule\n@@ -49,12 +49,12 @@ test_expect_success 'setup a submodule tree' '\n \t git commit -m \"submodule update\"\n \t) &&\n \t(cd super &&\n-\t git submodule add ../rebasing rebasing &&\n+\t git submodule add -n rebasing ../rebasing rebasing &&\n \t test_tick &&\n \t git commit -m \"rebasing\"\n \t) &&\n \t(cd super &&\n-\t git submodule add ../merging merging &&\n+\t git submodule add -n merging ../merging merging &&\n \t test_tick &&\n \t git commit -m \"rebasing\"\n \t)\ndiff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh\nindex 2a52775..a0390dd 100755\n--- a/t/t7407-submodule-foreach.sh\n+++ b/t/t7407-submodule-foreach.sh\n@@ -21,9 +21,9 @@ test_expect_success 'setup a submodule tree' '\n \tgit clone super submodule &&\n \t(\n \t\tcd super &&\n-\t\tgit submodule add ../submodule sub1 &&\n-\t\tgit submodule add ../submodule sub2 &&\n-\t\tgit submodule add ../submodule sub3 &&\n+\t\tgit submodule add -n sub1 ../submodule sub1 &&\n+\t\tgit submodule add -n sub2 ../submodule sub2 &&\n+\t\tgit submodule add -n sub3 ../submodule sub3 &&\n \t\tgit config -f .gitmodules --rename-section \\\n \t\t\tsubmodule.sub1 submodule.foo1 &&\n \t\tgit config -f .gitmodules --rename-section \\\n@@ -82,28 +82,28 @@ test_expect_success 'setup nested submodules' '\n \tgit clone submodule nested3 &&\n \t(\n \t\tcd nested3 &&\n-\t\tgit submodule add ../submodule submodule &&\n+\t\tgit submodule add -n submodule ../submodule submodule &&\n \t\ttest_tick &&\n \t\tgit commit -m \"submodule\" &&\n \t\tgit submodule init submodule\n \t) &&\n \t(\n \t\tcd nested2 &&\n-\t\tgit submodule add ../nested3 nested3 &&\n+\t\tgit submodule add -n nested3 ../nested3 nested3 &&\n \t\ttest_tick &&\n \t\tgit commit -m \"nested3\" &&\n \t\tgit submodule init nested3\n \t) &&\n \t(\n \t\tcd nested1 &&\n-\t\tgit submodule add ../nested2 nested2 &&\n+\t\tgit submodule add -n nested2 ../nested2 nested2 &&\n \t\ttest_tick &&\n \t\tgit commit -m \"nested2\" &&\n \t\tgit submodule init nested2\n \t) &&\n \t(\n \t\tcd super &&\n-\t\tgit submodule add ../nested1 nested1 &&\n+\t\tgit submodule add -n nested1 ../nested1 nested1 &&\n \t\ttest_tick &&\n \t\tgit commit -m \"nested1\" &&\n \t\tgit submodule init nested1\n-- \n1.6.5\n"},{"id":"139174","messageId":"1270923830-11830-3-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 2/9] Implement \"git mv\" for submodules","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:43Z","receivedAt":"2010-04-10T18:23:43Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch teaches \"git mv\" how to handle moving submodules, including\nhow to update the .gitmodules file.\n\nThe .gitmodules update is handled by an undocumented subcommand to\n\"git submodule\" named \"mvconfig\".\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n Documentation/git-mv.txt |    7 ++-\n builtin/mv.c             |   33 ++++++++++++++--\n git-submodule.sh         |   16 +++++++-\n t/t7409-submodule-mv.sh  |   94 ++++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 143 insertions(+), 7 deletions(-)\n create mode 100755 t/t7409-submodule-mv.sh\n\ndiff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt\nindex bdcb585..632a6a9 100644\n--- a/Documentation/git-mv.txt\n+++ b/Documentation/git-mv.txt\n@@ -14,8 +14,8 @@ DESCRIPTION\n -----------\n This script is used to move or rename a file, directory or symlink.\n \n- git mv [-f] [-n] <source> <destination>\n- git mv [-f] [-n] [-k] <source> ... <destination directory>\n+ git mv [-f] [-n] [-M] <source> <destination>\n+ git mv [-f] [-n] [-k] [-M] <source> ... <destination directory>\n \n In the first form, it renames <source>, which must exist and be either\n a file, symlink or directory, to <destination>.\n@@ -39,6 +39,9 @@ OPTIONS\n --dry-run::\n \tDo nothing; only show what would happen\n \n+-M::\n+\tDo not try to update submodule paths in .gitmodules\n+\n \n Author\n ------\ndiff --git a/builtin/mv.c b/builtin/mv.c\nindex c07f53b..21fd03f 100644\n--- a/builtin/mv.c\n+++ b/builtin/mv.c\n@@ -9,6 +9,7 @@\n #include \"cache-tree.h\"\n #include \"string-list.h\"\n #include \"parse-options.h\"\n+#include \"run-command.h\"\n \n static const char * const builtin_mv_usage[] = {\n \t\"git mv [options] <source>... <destination>\",\n@@ -53,11 +54,12 @@ static struct lock_file lock_file;\n int cmd_mv(int argc, const char **argv, const char *prefix)\n {\n \tint i, newfd;\n-\tint verbose = 0, show_only = 0, force = 0, ignore_errors = 0;\n+\tint verbose = 0, show_only = 0, force = 0, ignore_errors = 0, skip_module_update = 0;\n \tstruct option builtin_mv_options[] = {\n \t\tOPT__DRY_RUN(&show_only),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"force move/rename even if target exists\"),\n \t\tOPT_BOOLEAN('k', NULL, &ignore_errors, \"skip move/rename errors\"),\n+\t\tOPT_BOOLEAN('M', NULL, &skip_module_update, \"don't update submodule entries\"),\n \t\tOPT_END(),\n \t};\n \tconst char **source, **destination, **dest_path;\n@@ -96,13 +98,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t/* Checking */\n \tfor (i = 0; i < argc; i++) {\n \t\tconst char *src = source[i], *dst = destination[i];\n-\t\tint length, src_is_dir;\n+\t\tint length, src_is_dir, pos;\n \t\tconst char *bad = NULL;\n \n \t\tif (show_only)\n \t\t\tprintf(\"Checking rename of '%s' to '%s'\\n\", src, dst);\n \n \t\tlength = strlen(src);\n+\t\tpos = cache_name_pos(src, length);\n \t\tif (lstat(src, &st) < 0)\n \t\t\tbad = \"bad source\";\n \t\telse if (!strncmp(src, dst, length) &&\n@@ -111,7 +114,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t} else if ((src_is_dir = S_ISDIR(st.st_mode))\n \t\t\t\t&& lstat(dst, &st) == 0)\n \t\t\tbad = \"cannot move directory over file\";\n-\t\telse if (src_is_dir) {\n+\t\telse if (src_is_dir &&\n+\t\t\t\t!(pos >= 0 &&\n+\t\t\t\t  S_ISGITLINK(active_cache[pos]->ce_mode))) {\n \t\t\tconst char *src_w_slash = add_slash(src);\n \t\t\tint len_w_slash = length + 1;\n \t\t\tint first, last;\n@@ -162,7 +167,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \t\t\t\targc += last - first;\n \t\t\t}\n-\t\t} else if (cache_name_pos(src, length) < 0)\n+\t\t} else if (pos < 0)\n \t\t\tbad = \"not under version control\";\n \t\telse if (lstat(dst, &st) == 0) {\n \t\t\tbad = \"destination exists\";\n@@ -223,5 +228,25 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"Unable to write new index file\");\n \t}\n \n+\tif (!show_only && !skip_module_update)\n+\t\tfor (i = 0; i < argc; i++) {\n+\t\t\tconst char *src = source[i], *dst = destination[i];\n+\t\t\tint pos;\n+\n+\t\t\tif (modes[i] == WORKING_DIRECTORY)\n+\t\t\t\tcontinue;\n+\n+\t\t\tpos = cache_name_pos(dst, strlen(dst));\n+\t\t\tassert(pos >= 0);\n+\n+\t\t\tif (S_ISGITLINK(active_cache[pos]->ce_mode)) {\n+\t\t\t\tconst char *argv_submodule[] = {\n+\t\t\t\t\t\"submodule\", \"mvconfig\", src, dst, NULL\n+\t\t\t\t};\n+\n+\t\t\t\trun_command_v_opt(argv_submodule, RUN_GIT_CMD);\n+\t\t\t}\n+\t\t}\n+\n \treturn 0;\n }\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex de29f3a..f1e4e22 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -859,6 +859,20 @@ cmd_sync()\n \t\tfi\n \tdone\n }\n+#\n+# Updates the entry in .gitmodules to move a submodule.\n+# This command is called by \"git mv\" for each submodule it moves.\n+#\n+cmd_mvconfig()\n+{\n+\tsrc=\"$1\"\n+\tdst=\"$2\"\n+\n+\tname=$(module_name \"$src\") || exit\n+\tgit config -f .gitmodules submodule.\"$name\".path \"$dst\" ||\n+\t\tdie \"Could not update .gitmodules entry for $name\"\n+\tgit add .gitmodules || die \"Could not add .gitmodules to index\"\n+}\n \n # This loop parses the command line arguments to find the\n # subcommand name to dispatch.  Parsing of the subcommand specific\n@@ -869,7 +883,7 @@ cmd_sync()\n while test $# != 0 && test -z \"$command\"\n do\n \tcase \"$1\" in\n-\tadd | foreach | init | update | status | summary | sync)\n+\tadd | foreach | init | update | status | summary | sync | mvconfig)\n \t\tcommand=$1\n \t\t;;\n \t-q|--quiet)\ndiff --git a/t/t7409-submodule-mv.sh b/t/t7409-submodule-mv.sh\nnew file mode 100755\nindex 0000000..9eb3fb1\n--- /dev/null\n+++ b/t/t7409-submodule-mv.sh\n@@ -0,0 +1,94 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Peter Collingbourne\n+#\n+\n+test_description='git submodule mv\n+\n+These tests exercise the \"git mv\" command for submodules.\n+'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\techo file > file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m upstream\n+\tgit clone . super &&\n+\tgit clone super submodule &&\n+\t(cd super &&\n+\t git submodule add -n reg ../submodule reg &&\n+\t git clone reg unreg &&\n+\t git add unreg &&\n+\t test_tick &&\n+\t git commit -m \"submodules\"\n+\t)'\n+\n+test_expect_success 'move registered submodule' '\n+\t(cd super &&\n+\t git mv reg reg2 &&\n+\t test -z \"$(git ls-files reg)\" &&\n+\t test -n \"$(git ls-files reg2)\" &&\n+\t test ! -d reg &&\n+\t test -d reg2 &&\n+\t test -d reg2/.git &&\n+\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"reg2\" &&\n+\t test_tick &&\n+\t git commit -a -m \"move reg\"\n+\t)\n+'\n+\n+test_expect_success 'move unregistered submodule' '\n+\t(cd super &&\n+\t git mv unreg unreg2 &&\n+\t test ! -d unreg &&\n+\t test -d unreg2 &&\n+\t test -d unreg2/.git &&\n+\t test_tick &&\n+\t git commit -a -m \"move unreg\"\n+\t)\n+'\n+\n+test_expect_success 'move unregistered uninitialised submodule' '\n+\t(cd super &&\n+\t rm -rf unreg2 &&\n+\t mkdir unreg2 &&\n+\t git mv unreg2 unreg &&\n+\t test -z \"$(git ls-files unreg2)\" &&\n+\t test -n \"$(git ls-files unreg)\" &&\n+\t test ! -d unreg2 &&\n+\t test -d unreg &&\n+\t test_tick &&\n+\t git commit -a -m \"move unreg2\"\n+\t)\n+'\n+\n+test_expect_success 'move registered submodule without changing .gitmodules' '\n+\t(cd super &&\n+\t git mv -M reg2 reg &&\n+\t test ! -d reg2 &&\n+\t test -d reg &&\n+\t test -d reg/.git &&\n+\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"reg2\" &&\n+\t git mv -M reg reg2\n+\t)\n+'\n+\n+test_expect_success 'move multiple submodules at once' '\n+\t(cd super &&\n+\t mkdir test\\ dir &&\n+\t git mv unreg reg2 test\\ dir/ &&\n+\t test ! -d unreg && \n+\t test ! -d reg2 && \n+\t test -d test\\ dir/unreg && \n+\t test -d test\\ dir/reg2 && \n+\t test -z \"$(git ls-files unreg)\" &&\n+\t test -n \"$(git ls-files test\\ dir/unreg)\" &&\n+\t test -z \"$(git ls-files reg2)\" &&\n+\t test -n \"$(git ls-files test\\ dir/reg2)\" &&\n+\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"test dir/reg2\"\n+\t)\n+'\n+\n+test_done\n-- \n1.6.5\n"},{"id":"139166","messageId":"1270923830-11830-4-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 3/9] git rm: test failure behaviour for multiple removals","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:44Z","receivedAt":"2010-04-10T18:23:44Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch causes the failure cases for the \"git rm\" command\nto be tested.  Specifically it tests that if the first removal\nfails the operation is aborted with an error message, and that\nif subsequent removals fail, the operation proceeds.\n\nBased-on-work-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n t/t3600-rm.sh |   54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 54 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t3600-rm.sh b/t/t3600-rm.sh\nindex 0aaf0ad..5186844 100755\n--- a/t/t3600-rm.sh\n+++ b/t/t3600-rm.sh\n@@ -126,6 +126,60 @@ test_expect_success 'Remove nonexistent file with --ignore-unmatch' '\n \tgit rm --ignore-unmatch nonexistent\n '\n \n+test_expect_success 'If the first (in alphabetical order) removal fails, rm is cancelled' '\n+\ttouch xyzzy &&\n+\tmkdir -p plugh &&\n+\ttouch plugh/xyzzy &&\n+\tgit add xyzzy plugh/xyzzy &&\n+\tgit commit --allow-empty -a -m \"two files to remove\" &&\n+\tchmod a-w plugh &&\n+\tgit ls-files --stage >before &&\n+\ttest $(grep xyzzy before | wc -l) = 2 &&\n+\n+\ttest_must_fail git rm xyzzy plugh/xyzzy &&\n+\n+\ttest -e plugh/xyzzy &&\n+\ttest -e xyzzy &&\n+\tgit ls-files --stage >after &&\n+\ttest_cmp before after\n+'\n+! test -e plugh || chmod 775 plugh\n+rm -fr before after plugh xyzzy\n+\n+test_expect_success 'Best-effort behavior if the second removal fails' '\n+\ttouch plugh &&\n+\tmkdir -p xyzzy &&\n+\ttouch xyzzy/plugh &&\n+\tgit add plugh xyzzy/plugh &&\n+\tgit commit --allow-empty -a -m \"two files to remove\" &&\n+\tchmod a-w xyzzy &&\n+\t: >expect &&\n+\n+\tgit rm plugh xyzzy/plugh &&\n+\n+\ttest -e xyzzy/plugh &&\n+\t! test -e plugh &&\n+\tgit ls-files --stage plugh xyzzy/plugh >actual &&\n+\ttest_cmp expect actual\n+'\n+! test -e xyzzy || chmod 775 xyzzy\n+rm -fr expect actual plugh xyzzy\n+\n+test_expect_success 'Message when first removal fails' '\n+\ttouch xyzzy &&\n+\tmkdir -p plugh &&\n+\ttouch plugh/xyzzy &&\n+\tgit add xyzzy plugh/xyzzy &&\n+\tgit commit --allow-empty -a -m \"two files to remove\" &&\n+\tchmod a-w plugh &&\n+\n+\ttest_must_fail git rm xyzzy plugh/xyzzy 2>msg &&\n+\n+\tgrep \"git rm: '\\''plugh/xyzzy'\\'':\" msg\n+'\n+! test -e plugh || chmod 775 plugh\n+rm -fr msg plugh xyzzy\n+\n test_expect_success '\"rm\" command printed' '\n \techo frotz > test-file &&\n \tgit add test-file &&\n-- \n1.6.5\n"},{"id":"139169","messageId":"1270923830-11830-5-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 4/9] git rm: display a warning for every unremovable file","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:45Z","receivedAt":"2010-04-10T18:23:45Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"When ‘git rm’ was built in (d9b814cc, 2006-05-19), its\nsemantics changed: before, it just removed files until it\nencountered an error and then would error out, whereas since\nthen, it makes an attempt to either remove all files or remove\nnone at all.  In particular, if ‘git rm’ fails to remove a\nfile after other files have already been removed, it does not\nabort but instead silently accepts the error.\n\nBetter to warn the user in this case!\n\nThis problem is particularly noticeable when dealing with submodules\nbecause the rmdir operation will fail for every initialised submodule.\nThe removal of the contents of an initialised submodule directory\nshould always be user controlled, due to the possibility of\nunpropagated changes to the submodule.  Therefore, the user should\nalways be informed of any such removal failures.\n\nBased-on-work-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n builtin/rm.c  |    2 ++\n t/t3600-rm.sh |   15 +++++++++++++++\n 2 files changed, 17 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex f3772c8..05a5158 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -259,6 +259,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!removed)\n \t\t\t\tdie_errno(\"git rm: '%s'\", path);\n+\t\t\telse\n+\t\t\t\twarning(\"git rm: '%s': %s\", path, strerror(errno));\n \t\t}\n \t}\n \ndiff --git a/t/t3600-rm.sh b/t/t3600-rm.sh\nindex 5186844..ecddd67 100755\n--- a/t/t3600-rm.sh\n+++ b/t/t3600-rm.sh\n@@ -180,6 +180,21 @@ test_expect_success 'Message when first removal fails' '\n ! test -e plugh || chmod 775 plugh\n rm -fr msg plugh xyzzy\n \n+test_expect_success 'Message when second removal fails' '\n+\ttouch plugh &&\n+\tmkdir -p xyzzy &&\n+\ttouch xyzzy/plugh &&\n+\tgit add plugh xyzzy/plugh &&\n+\tgit commit --allow-empty -a -m \"two files to remove\" &&\n+\tchmod a-w xyzzy &&\n+\n+\tgit rm plugh xyzzy/plugh 2>msg &&\n+\n+\tgrep \"git rm: '\\''xyzzy/plugh'\\'':\" msg\n+'\n+! test -e xyzzy || chmod 775 xyzzy\n+rm -fr expect actual plugh xyzzy\n+\n test_expect_success '\"rm\" command printed' '\n \techo frotz > test-file &&\n \tgit add test-file &&\n-- \n1.6.5\n"},{"id":"139170","messageId":"1270923830-11830-6-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 5/9] git rm: collect file modes","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:46Z","receivedAt":"2010-04-10T18:23:46Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch causes git rm to collect file modes alongside file names\nin its list data structure.\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n builtin/rm.c |    9 ++++++---\n 1 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex 05a5158..61ec2cf 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -18,15 +18,18 @@ static const char * const builtin_rm_usage[] = {\n static struct {\n \tint nr, alloc;\n \tconst char **name;\n+\tunsigned int *mode;\n } list;\n \n-static void add_list(const char *name)\n+static void add_list(const char *name, unsigned int mode)\n {\n \tif (list.nr >= list.alloc) {\n \t\tlist.alloc = alloc_nr(list.alloc);\n \t\tlist.name = xrealloc(list.name, list.alloc * sizeof(const char *));\n+\t\tlist.mode = xrealloc(list.mode, list.alloc * sizeof(unsigned int));\n \t}\n-\tlist.name[list.nr++] = name;\n+\tlist.name[list.nr] = name;\n+\tlist.mode[list.nr++] = mode;\n }\n \n static int check_local_mod(unsigned char *head, int index_only)\n@@ -182,7 +185,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\tstruct cache_entry *ce = active_cache[i];\n \t\tif (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))\n \t\t\tcontinue;\n-\t\tadd_list(ce->name);\n+\t\tadd_list(ce->name, ce->ce_mode);\n \t}\n \n \tif (pathspec) {\n-- \n1.6.5\n"},{"id":"139168","messageId":"1270923830-11830-7-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 6/9] Add a mode parameter to the remove_path function","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:47Z","receivedAt":"2010-04-10T18:23:47Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch adds a mode parameter to remove_path which determines\nwhether unlink or rmdir is used.  All calls to remove_path have\nbeen modified to supply the mode parameter.\n\nThis patch also adds a test case for a bug fixed by the addition\nof the mode parameter to remove_path.\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n builtin/apply.c            |    2 +-\n builtin/rm.c               |    3 ++-\n dir.c                      |    4 ++--\n dir.h                      |    2 +-\n merge-recursive.c          |   27 ++++++++++++++++-----------\n t/t7405-submodule-merge.sh |   13 +++++++++++++\n 6 files changed, 35 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/apply.c b/builtin/apply.c\nindex d56cabf..2f50a53 100644\n--- a/builtin/apply.c\n+++ b/builtin/apply.c\n@@ -3139,7 +3139,7 @@ static void remove_file(struct patch *patch, int rmdir_empty)\n \t}\n \tif (!cached) {\n \t\tif (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {\n-\t\t\tremove_path(patch->old_name);\n+\t\t\tremove_path(patch->old_mode, patch->old_name);\n \t\t}\n \t}\n }\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex 61ec2cf..6ac5114 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -256,7 +256,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\tint removed = 0;\n \t\tfor (i = 0; i < list.nr; i++) {\n \t\t\tconst char *path = list.name[i];\n-\t\t\tif (!remove_path(path)) {\n+\t\t\tunsigned int mode = list.mode[i];\n+\t\t\tif (!remove_path(mode, path)) {\n \t\t\t\tremoved = 1;\n \t\t\t\tcontinue;\n \t\t\t}\ndiff --git a/dir.c b/dir.c\nindex cb83332..2d9cd9a 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -1047,11 +1047,11 @@ void setup_standard_excludes(struct dir_struct *dir)\n \t\tadd_excludes_from_file(dir, excludes_file);\n }\n \n-int remove_path(const char *name)\n+int remove_path(unsigned int mode, const char *name)\n {\n \tchar *slash;\n \n-\tif (unlink(name) && errno != ENOENT)\n+\tif ((S_ISGITLINK(mode) ? rmdir(name) : unlink(name)) && errno != ENOENT)\n \t\treturn -1;\n \n \tslash = strrchr(name, '/');\ndiff --git a/dir.h b/dir.h\nindex 3bead5f..0e48d2a 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -98,6 +98,6 @@ extern void setup_standard_excludes(struct dir_struct *dir);\n extern int remove_dir_recursively(struct strbuf *path, int flag);\n \n /* tries to remove the path with empty directories along it, ignores ENOENT */\n-extern int remove_path(const char *path);\n+extern int remove_path(unsigned int mode, const char *path);\n \n #endif\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 206c103..f4ac8c9 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -399,7 +399,7 @@ static int update_stages(const char *path, struct diff_filespec *o,\n }\n \n static int remove_file(struct merge_options *o, int clean,\n-\t\t       const char *path, int no_wd)\n+\t\t       unsigned int mode, const char *path, int no_wd)\n {\n \tint update_cache = o->call_depth || clean;\n \tint update_working_directory = !o->call_depth && !no_wd;\n@@ -409,7 +409,7 @@ static int remove_file(struct merge_options *o, int clean,\n \t\t\treturn -1;\n \t}\n \tif (update_working_directory) {\n-\t\tif (remove_path(path))\n+\t\tif (remove_path(mode, path))\n \t\t\treturn -1;\n \t}\n \treturn 0;\n@@ -739,6 +739,8 @@ static void conflict_rename_rename(struct merge_options *o,\n {\n \tchar *del[2];\n \tint delp = 0;\n+\tunsigned int ren1_mode = ren1->pair->two->mode;\n+\tunsigned int ren2_mode = ren2->pair->two->mode;\n \tconst char *ren1_dst = ren1->pair->two->path;\n \tconst char *ren2_dst = ren2->pair->two->path;\n \tconst char *dst_name1 = ren1_dst;\n@@ -747,13 +749,13 @@ static void conflict_rename_rename(struct merge_options *o,\n \t\tdst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);\n \t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n \t\t       ren1_dst, branch2, dst_name1);\n-\t\tremove_file(o, 0, ren1_dst, 0);\n+\t\tremove_file(o, 0, ren1_mode, ren1_dst, 0);\n \t}\n \tif (string_list_has_string(&o->current_directory_set, ren2_dst)) {\n \t\tdst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);\n \t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n \t\t       ren2_dst, branch1, dst_name2);\n-\t\tremove_file(o, 0, ren2_dst, 0);\n+\t\tremove_file(o, 0, ren2_mode, ren2_dst, 0);\n \t}\n \tif (o->call_depth) {\n \t\tremove_file_from_cache(dst_name1);\n@@ -778,7 +780,7 @@ static void conflict_rename_dir(struct merge_options *o,\n {\n \tchar *new_path = unique_path(o, ren1->pair->two->path, branch1);\n \toutput(o, 1, \"Renaming %s to %s instead\", ren1->pair->one->path, new_path);\n-\tremove_file(o, 0, ren1->pair->two->path, 0);\n+\tremove_file(o, 0, ren1->pair->two->mode, ren1->pair->two->path, 0);\n \tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);\n \tfree(new_path);\n }\n@@ -794,7 +796,7 @@ static void conflict_rename_rename_2(struct merge_options *o,\n \toutput(o, 1, \"Renaming %s to %s and %s to %s instead\",\n \t       ren1->pair->one->path, new_path1,\n \t       ren2->pair->one->path, new_path2);\n-\tremove_file(o, 0, ren1->pair->two->path, 0);\n+\tremove_file(o, 0, ren1->pair->two->mode, ren1->pair->two->path, 0);\n \tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);\n \tupdate_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);\n \tfree(new_path2);\n@@ -826,6 +828,7 @@ static int process_renames(struct merge_options *o,\n \t\tstruct rename *ren1 = NULL, *ren2 = NULL;\n \t\tconst char *branch1, *branch2;\n \t\tconst char *ren1_src, *ren1_dst;\n+\t\tunsigned int ren1_srcmode;\n \n \t\tif (i >= a_renames->nr) {\n \t\t\tren2 = b_renames->items[j++].util;\n@@ -868,6 +871,8 @@ static int process_renames(struct merge_options *o,\n \t\tren1_src = ren1->pair->one->path;\n \t\tren1_dst = ren1->pair->two->path;\n \n+\t\tren1_srcmode = ren1->pair->one->mode;\n+\n \t\tif (ren2) {\n \t\t\tconst char *ren2_src = ren2->pair->one->path;\n \t\t\tconst char *ren2_dst = ren2->pair->two->path;\n@@ -892,7 +897,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t\tconflict_rename_rename(o, ren1, branch1, ren2, branch2);\n \t\t\t} else {\n \t\t\t\tstruct merge_file_info mfi;\n-\t\t\t\tremove_file(o, 1, ren1_src, 1);\n+\t\t\t\tremove_file(o, 1, ren1_srcmode, ren1_src, 1);\n \t\t\t\tmfi = merge_file(o,\n \t\t\t\t\t\t ren1->pair->one,\n \t\t\t\t\t\t ren1->pair->two,\n@@ -926,7 +931,7 @@ static int process_renames(struct merge_options *o,\n \t\t\tstruct diff_filespec src_other, dst_other;\n \t\t\tint try_merge, stage = a_renames == renames1 ? 3: 2;\n \n-\t\t\tremove_file(o, 1, ren1_src, o->call_depth || stage == 3);\n+\t\t\tremove_file(o, 1, ren1_srcmode, ren1_src, o->call_depth || stage == 3);\n \n \t\t\thashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);\n \t\t\tsrc_other.mode = ren1->src_entry->stages[stage].mode;\n@@ -1082,7 +1087,7 @@ static int process_entry(struct merge_options *o,\n \t\t\tif (a_sha)\n \t\t\t\toutput(o, 2, \"Removing %s\", path);\n \t\t\t/* do not touch working file if it did not exist */\n-\t\t\tremove_file(o, 1, path, !a_sha);\n+\t\t\tremove_file(o, 1, a_mode, path, !a_sha);\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n@@ -1129,7 +1134,7 @@ static int process_entry(struct merge_options *o,\n \t\t\toutput(o, 1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n \t\t\t       \"Adding %s as %s\",\n \t\t\t       conf, path, other_branch, path, new_path);\n-\t\t\tremove_file(o, 0, path, 0);\n+\t\t\tremove_file(o, 0, mode, path, 0);\n \t\t\tupdate_file(o, 0, sha, mode, new_path);\n \t\t} else {\n \t\t\toutput(o, 2, \"Adding %s\", path);\n@@ -1171,7 +1176,7 @@ static int process_entry(struct merge_options *o,\n \t\t * this entry was deleted altogether. a_mode == 0 means\n \t\t * we had that path and want to actively remove it.\n \t\t */\n-\t\tremove_file(o, 1, path, !a_mode);\n+\t\tremove_file(o, 1, a_mode, path, !a_mode);\n \t} else\n \t\tdie(\"Fatal merge failure, shouldn't happen.\");\n \ndiff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh\nindex 9a21f78..d87ed9e 100755\n--- a/t/t7405-submodule-merge.sh\n+++ b/t/t7405-submodule-merge.sh\n@@ -71,4 +71,17 @@ test_expect_success 'merging with a modify/modify conflict between merge bases'\n \n '\n \n+test_expect_success 'merging a submodule deletion' '\n+\n+\tgit reset --hard HEAD &&\n+\tgit checkout -b test3 a &&\n+\trm -rf sub &&\n+\tgit update-index --remove sub &&\n+\tgit commit -m empty &&\n+\tgit checkout -b test4 c &&\n+\ttest -d sub &&\n+\tgit merge test3 &&\n+\ttest \\! -d sub\n+'\n+\n test_done\n-- \n1.6.5\n"},{"id":"139172","messageId":"1270923830-11830-8-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 7/9] git rm: do not abort due to an initialised submodule","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:48Z","receivedAt":"2010-04-10T18:23:48Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch causes the \"git rm\" command to consider \"directory not\nempty\" errors as nonfatal, which will be caused by a submodule being\nin an initialised state.  As this is a normal state for a submodule,\nit should not cause us to abort.  Neither should we recursively delete\nthe submodule directory as it may contain unsaved data.\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n builtin/rm.c |    6 ++++--\n 1 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex 6ac5114..02ee259 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -250,7 +250,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t * abort the \"git rm\" (but once we've successfully removed\n \t * any file at all, we'll go ahead and commit to it all:\n \t * by then we've already committed ourselves and can't fail\n-\t * in the middle)\n+\t * in the middle).  However failure to remove a submodule\n+\t * directory due to the submodule being initialised is never\n+\t * a fatal condition.\n \t */\n \tif (!index_only) {\n \t\tint removed = 0;\n@@ -261,7 +263,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\t\t\tremoved = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n-\t\t\tif (!removed)\n+\t\t\tif (!removed && errno != EEXIST && errno != ENOTEMPTY)\n \t\t\t\tdie_errno(\"git rm: '%s'\", path);\n \t\t\telse\n \t\t\t\twarning(\"git rm: '%s': %s\", path, strerror(errno));\n-- \n1.6.5\n"},{"id":"139167","messageId":"1270923830-11830-9-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 8/9] git submodule: infrastructure for reading .gitmodules files in arbitrary locations","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:49Z","receivedAt":"2010-04-10T18:23:49Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch modifies the module_name function in git-submodule.sh to take\nan optional parameter which specifies the path of the .gitmodules\nfile to read.\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n git-submodule.sh |    8 +++++++-\n 1 files changed, 7 insertions(+), 1 deletions(-)\n\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex f1e4e22..75c50b8 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -64,12 +64,18 @@ module_list()\n # Map submodule path to submodule name\n #\n # $1 = path\n+# $2 = .gitmodules file, default \".gitmodules\"\n #\n module_name()\n {\n+\tmodfile=\"$2\"\n+\tif test -z \"$modfile\"\n+\tthen\n+\t\tmodfile=\".gitmodules\"\n+\tfi\n \t# Do we have \"submodule.<something>.path = $1\" defined in .gitmodules file?\n \tre=$(printf '%s\\n' \"$1\" | sed -e 's/[].[^$\\\\*]/\\\\&/g')\n-\tname=$( git config -f .gitmodules --get-regexp '^submodule\\..*\\.path$' |\n+\tname=$( git config -f \"$modfile\" --get-regexp '^submodule\\..*\\.path$' |\n \t\tsed -n -e 's|^submodule\\.\\(.*\\)\\.path '\"$re\"'$|\\1|p' )\n        test -z \"$name\" &&\n        die \"No submodule mapping found in .gitmodules for path '$path'\"\n-- \n1.6.5\n"},{"id":"139173","messageId":"1270923830-11830-10-git-send-email-peter@pcc.me.uk","threadId":"23415","inReplyTo":"1270923830-11830-1-git-send-email-peter@pcc.me.uk","subject":"[PATCH v2 9/9] git rm: remove submodule entries from .gitmodules","fromName":"Peter Collingbourne","fromEmail":"peter@pcc.me.uk","sentAt":"2010-04-10T18:23:50Z","receivedAt":"2010-04-10T18:23:50Z","isPatch":true,"sender":{"key":"peter@pcc.me.uk","avatar":"https://avatars.githubusercontent.com/u/425024?v=4"},"body":"This patch teaches \"git rm\" how to remove submodules from the\n.gitmodules file.  The .gitmodules update is handled by an undocumented\nsubcommand to \"git submodule\" named \"rmconfig\".\n\nSigned-off-by: Peter Collingbourne <peter@pcc.me.uk>\n---\n Documentation/git-rm.txt   |    5 ++-\n builtin/rm.c               |   25 ++++++++++-\n git-submodule.sh           |   45 ++++++++++++++++++-\n t/t7409-submodule-mv-rm.sh |  105 ++++++++++++++++++++++++++++++++++++++++++++\n t/t7409-submodule-mv.sh    |   94 ---------------------------------------\n 5 files changed, 177 insertions(+), 97 deletions(-)\n create mode 100755 t/t7409-submodule-mv-rm.sh\n delete mode 100755 t/t7409-submodule-mv.sh\n\ndiff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt\nindex c21d19e..81c1bbd 100644\n--- a/Documentation/git-rm.txt\n+++ b/Documentation/git-rm.txt\n@@ -7,7 +7,7 @@ git-rm - Remove files from the working tree and from the index\n \n SYNOPSIS\n --------\n-'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...\n+'git rm' [-f | --force] [-n] [-r] [-M] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...\n \n DESCRIPTION\n -----------\n@@ -49,6 +49,9 @@ OPTIONS\n         Allow recursive removal when a leading directory name is\n         given.\n \n+-M::\n+\tDo not try to remove submodule entry in .gitmodules\n+\n \\--::\n \tThis option can be used to separate command-line options from\n \tthe list of files, (useful when filenames might be mistaken\ndiff --git a/builtin/rm.c b/builtin/rm.c\nindex 02ee259..3c26a43 100644\n--- a/builtin/rm.c\n+++ b/builtin/rm.c\n@@ -9,6 +9,7 @@\n #include \"cache-tree.h\"\n #include \"tree-walk.h\"\n #include \"parse-options.h\"\n+#include \"run-command.h\"\n \n static const char * const builtin_rm_usage[] = {\n \t\"git rm [options] [--] <file>...\",\n@@ -139,7 +140,7 @@ static int check_local_mod(unsigned char *head, int index_only)\n static struct lock_file lock_file;\n \n static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;\n-static int ignore_unmatch = 0;\n+static int ignore_unmatch = 0, skip_module_update = 0;\n \n static struct option builtin_rm_options[] = {\n \tOPT__DRY_RUN(&show_only),\n@@ -149,6 +150,8 @@ static struct option builtin_rm_options[] = {\n \tOPT_BOOLEAN('r', NULL,             &recursive,  \"allow recursive removal\"),\n \tOPT_BOOLEAN( 0 , \"ignore-unmatch\", &ignore_unmatch,\n \t\t\t\t\"exit with a zero status even if nothing matched\"),\n+\tOPT_BOOLEAN('M', NULL,             &skip_module_update,\n+\t\t\t\t\"don't update submodule entries\"),\n \tOPT_END(),\n };\n \n@@ -276,5 +279,25 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"Unable to write new index file\");\n \t}\n \n+\tif (!skip_module_update)\n+\t\tfor (i = 0; i < list.nr; i++) {\n+\t\t\tif (S_ISGITLINK(list.mode[i])) {\n+\t\t\t\tconst char *path = list.name[i];\n+\n+\t\t\t\tconst char *argv_submodule[] = {\n+\t\t\t\t\t\"submodule\", \"rmconfig\", NULL, NULL, NULL, NULL\n+\t\t\t\t};\n+\t\t\t\tint argc = 2;\n+\n+\t\t\t\tif (index_only)\n+\t\t\t\t\targv_submodule[argc++] = \"--cached\";\n+\n+\t\t\t\targv_submodule[argc++] = \"--\";\n+\t\t\t\targv_submodule[argc++] = path;\n+\n+\t\t\t\trun_command_v_opt(argv_submodule, RUN_GIT_CMD);\n+\t\t\t}\n+\t\t}\n+\n \treturn 0;\n }\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 75c50b8..baadaa5 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -879,6 +879,49 @@ cmd_mvconfig()\n \t\tdie \"Could not update .gitmodules entry for $name\"\n \tgit add .gitmodules || die \"Could not add .gitmodules to index\"\n }\n+#\n+# Removes the entry in .gitmodules to remove a submodule.\n+# This command is called by \"git rm\" for each submodule it removes.\n+#\n+cmd_rmconfig()\n+{\n+\twhile test $# -ne 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t--cached)\n+\t\t\tindex_only=1\n+\t\t\tshift\n+\t\t\t;;\n+\t\t--)\n+\t\t\tshift\n+\t\t\tbreak\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\tdone\n+\tpath=\"$1\"\n+\n+\tif test -z \"$index_only\"\n+\tthen\n+\t\tname=$(module_name \"$path\") || exit\n+\t\tgit config -f .gitmodules --remove-section submodule.\"$name\" ||\n+\t\t\tdie \"Could not update .gitmodules entry for $name\"\n+\t\tgit add .gitmodules || die \"Could not add .gitmodules to index\"\n+\telse\n+\t\tgit cat-file -p :0:.gitmodules > .git/gitmodules.index ||\n+\t\t\t{ rm .git/gitmodules.index; die \"Could not extract .gitmodules from index\"; }\n+\t\tname=$(module_name \"$path\" .git/gitmodules.index) || { rm .git/gitmodules.index; exit; }\n+\t\tgit config -f .git/gitmodules.index --remove-section submodule.\"$name\" ||\n+\t\t\t{ rm .git/gitmodules.index; die \"Could not update .gitmodules entry for $name\"; }\n+\t\tblob=$(git hash-object -w --stdin < .git/gitmodules.index) ||\n+\t\t\t{ rm .git/gitmodules.index; die \"Could not create blob for .gitmodules\"; }\n+\t\trm .git/gitmodules.index || die \"Could not remove temporary .gitmodules file\"\n+\t\tgit update-index --cacheinfo 100644 \"$blob\" .gitmodules ||\n+\t\t\tdie \"Could not add .gitmodules to index\"\n+\tfi\n+}\n \n # This loop parses the command line arguments to find the\n # subcommand name to dispatch.  Parsing of the subcommand specific\n@@ -889,7 +932,7 @@ cmd_mvconfig()\n while test $# != 0 && test -z \"$command\"\n do\n \tcase \"$1\" in\n-\tadd | foreach | init | update | status | summary | sync | mvconfig)\n+\tadd | foreach | init | update | status | summary | sync | mvconfig | rmconfig)\n \t\tcommand=$1\n \t\t;;\n \t-q|--quiet)\ndiff --git a/t/t7409-submodule-mv-rm.sh b/t/t7409-submodule-mv-rm.sh\nnew file mode 100755\nindex 0000000..91b7866\n--- /dev/null\n+++ b/t/t7409-submodule-mv-rm.sh\n@@ -0,0 +1,105 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Peter Collingbourne\n+#\n+\n+test_description='git submodule mv, rm\n+\n+These tests exercise the \"git mv\" and \"git rm\" commands for submodules.\n+'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\techo file > file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m upstream\n+\tgit clone . super &&\n+\tgit clone super submodule &&\n+\t(cd super &&\n+\t git submodule add -n reg ../submodule reg &&\n+\t git clone reg unreg &&\n+\t git add unreg &&\n+\t test_tick &&\n+\t git commit -m \"submodules\"\n+\t)'\n+\n+test_expect_success 'move registered submodule' '\n+\t(cd super &&\n+\t git mv reg reg2 &&\n+\t test -z \"$(git ls-files reg)\" &&\n+\t test -n \"$(git ls-files reg2)\" &&\n+\t test ! -d reg &&\n+\t test -d reg2 &&\n+\t test -d reg2/.git &&\n+\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"reg2\" &&\n+\t test_tick &&\n+\t git commit -a -m \"move reg\"\n+\t)\n+'\n+\n+test_expect_success 'move unregistered submodule' '\n+\t(cd super &&\n+\t git mv unreg unreg2 &&\n+\t test ! -d unreg &&\n+\t test -d unreg2 &&\n+\t test -d unreg2/.git &&\n+\t test_tick &&\n+\t git commit -a -m \"move unreg\"\n+\t)\n+'\n+\n+test_expect_success 'move unregistered uninitialised submodule' '\n+\t(cd super &&\n+\t rm -rf unreg2 &&\n+\t mkdir unreg2 &&\n+\t git mv unreg2 unreg &&\n+\t test -z \"$(git ls-files unreg2)\" &&\n+\t test -n \"$(git ls-files unreg)\" &&\n+\t test ! -d unreg2 &&\n+\t test -d unreg &&\n+\t test_tick &&\n+\t git commit -a -m \"move unreg2\"\n+\t)\n+'\n+\n+test_expect_success 'move registered submodule without changing .gitmodules' '\n+\t(cd super &&\n+\t git mv -M reg2 reg &&\n+\t test ! -d reg2 &&\n+\t test -d reg &&\n+\t test -d reg/.git &&\n+\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"reg2\" &&\n+\t git mv -M reg reg2\n+\t)\n+'\n+\n+test_expect_success 'move multiple submodules at once' '\n+\t(cd super &&\n+\t mkdir test\\ dir &&\n+\t git mv unreg reg2 test\\ dir/ &&\n+\t test ! -d unreg && \n+\t test ! -d reg2 && \n+\t test -d test\\ dir/unreg && \n+\t test -d test\\ dir/reg2 && \n+\t test -z \"$(git ls-files unreg)\" &&\n+\t test -n \"$(git ls-files test\\ dir/unreg)\" &&\n+\t test -z \"$(git ls-files reg2)\" &&\n+\t test -n \"$(git ls-files test\\ dir/reg2)\" &&\n+\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"test dir/reg2\"\n+\t)\n+'\n+\n+test_expect_success 'remove multiple submodules at once' '\n+\t(cd super &&\n+\t git rm -r test\\ dir &&\n+\t test ! -d test\\ dir/unreg && \n+\t test -d test\\ dir/reg2 && \n+\t test -z \"$(git ls-files test\\ dir/unreg)\" &&\n+\t test -z \"$(git ls-files test\\ dir/reg2)\" &&\n+\t test -z \"$(git config -f .gitmodules submodule.reg.path)\"\n+\t)\n+'\n+\n+test_done\ndiff --git a/t/t7409-submodule-mv.sh b/t/t7409-submodule-mv.sh\ndeleted file mode 100755\nindex 9eb3fb1..0000000\n--- a/t/t7409-submodule-mv.sh\n+++ /dev/null\n@@ -1,94 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2010 Peter Collingbourne\n-#\n-\n-test_description='git submodule mv\n-\n-These tests exercise the \"git mv\" command for submodules.\n-'\n-\n-. ./test-lib.sh\n-\n-test_expect_success setup '\n-\techo file > file &&\n-\tgit add file &&\n-\ttest_tick &&\n-\tgit commit -m upstream\n-\tgit clone . super &&\n-\tgit clone super submodule &&\n-\t(cd super &&\n-\t git submodule add -n reg ../submodule reg &&\n-\t git clone reg unreg &&\n-\t git add unreg &&\n-\t test_tick &&\n-\t git commit -m \"submodules\"\n-\t)'\n-\n-test_expect_success 'move registered submodule' '\n-\t(cd super &&\n-\t git mv reg reg2 &&\n-\t test -z \"$(git ls-files reg)\" &&\n-\t test -n \"$(git ls-files reg2)\" &&\n-\t test ! -d reg &&\n-\t test -d reg2 &&\n-\t test -d reg2/.git &&\n-\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"reg2\" &&\n-\t test_tick &&\n-\t git commit -a -m \"move reg\"\n-\t)\n-'\n-\n-test_expect_success 'move unregistered submodule' '\n-\t(cd super &&\n-\t git mv unreg unreg2 &&\n-\t test ! -d unreg &&\n-\t test -d unreg2 &&\n-\t test -d unreg2/.git &&\n-\t test_tick &&\n-\t git commit -a -m \"move unreg\"\n-\t)\n-'\n-\n-test_expect_success 'move unregistered uninitialised submodule' '\n-\t(cd super &&\n-\t rm -rf unreg2 &&\n-\t mkdir unreg2 &&\n-\t git mv unreg2 unreg &&\n-\t test -z \"$(git ls-files unreg2)\" &&\n-\t test -n \"$(git ls-files unreg)\" &&\n-\t test ! -d unreg2 &&\n-\t test -d unreg &&\n-\t test_tick &&\n-\t git commit -a -m \"move unreg2\"\n-\t)\n-'\n-\n-test_expect_success 'move registered submodule without changing .gitmodules' '\n-\t(cd super &&\n-\t git mv -M reg2 reg &&\n-\t test ! -d reg2 &&\n-\t test -d reg &&\n-\t test -d reg/.git &&\n-\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"reg2\" &&\n-\t git mv -M reg reg2\n-\t)\n-'\n-\n-test_expect_success 'move multiple submodules at once' '\n-\t(cd super &&\n-\t mkdir test\\ dir &&\n-\t git mv unreg reg2 test\\ dir/ &&\n-\t test ! -d unreg && \n-\t test ! -d reg2 && \n-\t test -d test\\ dir/unreg && \n-\t test -d test\\ dir/reg2 && \n-\t test -z \"$(git ls-files unreg)\" &&\n-\t test -n \"$(git ls-files test\\ dir/unreg)\" &&\n-\t test -z \"$(git ls-files reg2)\" &&\n-\t test -n \"$(git ls-files test\\ dir/reg2)\" &&\n-\t test \"$(git config -f .gitmodules submodule.reg.path)\" = \"test dir/reg2\"\n-\t)\n-'\n-\n-test_done\n-- \n1.6.5\n"},{"id":"139178","messageId":"m2rfabb9a1e1004101141g3712cb1fl69bc3e0f8f91abf0@mail.gmail.com","threadId":"23415","inReplyTo":"1270923830-11830-2-git-send-email-peter@pcc.me.uk","subject":"Re: [PATCH v2 1/9] Generate unique ID for submodules created using \"git submodule add\"","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2010-04-10T18:41:20Z","receivedAt":"2010-04-10T18:41:20Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Sat, Apr 10, 2010 at 20:23, Peter Collingbourne <peter@pcc.me.uk> wrote:\n> ---\n\nI think this commit message would benefit from having an example of\nsuch a ID in it. I think I understand the idea, but an example would\nhelp me to verify that the idea I have is correct.\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"139225","messageId":"7vfx32sruh.fsf@alter.siamese.dyndns.org","threadId":"23415","inReplyTo":"1270923830-11830-7-git-send-email-peter@pcc.me.uk","subject":"Re: [PATCH v2 6/9] Add a mode parameter to the remove_path function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-04-11T01:25:26Z","receivedAt":"2010-04-11T01:25:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Peter Collingbourne <peter@pcc.me.uk> writes:\n\n> This patch adds a mode parameter to remove_path which determines\n> whether unlink or rmdir is used.  All calls to remove_path have\n> been modified to supply the mode parameter.\n>\n> This patch also adds a test case for a bug fixed by the addition\n> of the mode parameter to remove_path.\n\nWhen the mode of the thing on the filesystem doesn't match with what your\ncallers expect (e.g. the caller \"merge-recursive\" thought there should be\na gitlink but the filesystem actually had a plain file there), what should\nhappen?  What happens with your patch?\n"},{"id":"139227","messageId":"7vaatasruc.fsf@alter.siamese.dyndns.org","threadId":"23415","inReplyTo":"1270923830-11830-8-git-send-email-peter@pcc.me.uk","subject":"Re: [PATCH v2 7/9] git rm: do not abort due to an initialised submodule","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-04-11T01:25:31Z","receivedAt":"2010-04-11T01:25:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Peter Collingbourne <peter@pcc.me.uk> writes:\n\n> This patch causes the \"git rm\" command to consider \"directory not\n> empty\" errors as nonfatal, which will be caused by a submodule being\n> in an initialised state.  As this is a normal state for a submodule,\n> ...\n> Signed-off-by: Peter Collingbourne <peter@pcc.me.uk>\n> ---\n>  builtin/rm.c |    6 ++++--\n>  1 files changed, 4 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/rm.c b/builtin/rm.c\n> index 6ac5114..02ee259 100644\n> --- a/builtin/rm.c\n> +++ b/builtin/rm.c\n> @@ -250,7 +250,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>  \t * abort the \"git rm\" (but once we've successfully removed\n>  \t * any file at all, we'll go ahead and commit to it all:\n>  \t * by then we've already committed ourselves and can't fail\n> -\t * in the middle)\n> +\t * in the middle).  However failure to remove a submodule\n> +\t * directory due to the submodule being initialised is never\n> +\t * a fatal condition.\n>  \t */\n\n\nYour messages both in the commit log and comment talk only about\nsubmodules, ...\n\n> @@ -261,7 +263,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)\n>  \t\t\t\tremoved = 1;\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n> -\t\t\tif (!removed)\n> +\t\t\tif (!removed && errno != EEXIST && errno != ENOTEMPTY)\n>  \t\t\t\tdie_errno(\"git rm: '%s'\", path);\n>  \t\t\telse\n>  \t\t\t\twarning(\"git rm: '%s': %s\", path, strerror(errno));\n\n... but the code does not seem to limit itself to the case where a\nsubmodule removal has failed.\n\nHow does this patch affect the failure case for regular files and\ndirectories without any submodules?\n"},{"id":"139226","messageId":"7v4ojisru8.fsf@alter.siamese.dyndns.org","threadId":"23415","inReplyTo":"1270923830-11830-3-git-send-email-peter@pcc.me.uk","subject":"Re: [PATCH v2 2/9] Implement \"git mv\" for submodules","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-04-11T01:25:35Z","receivedAt":"2010-04-11T01:25:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Peter Collingbourne <peter@pcc.me.uk> writes:\n\n> +\t\tOPT_BOOLEAN('M', NULL, &skip_module_update, \"don't upda...\n\nIf you are moving them by default, it is confusing to make up a negative\n\"skip\" option like this.\n\nInstead, add a boolean \"module_update\" that defaults to true, and give\n\"--[no-]module-update\" without a short single-letter option, i.e.\n\n\tOPT_BOOLEAN(0 , \"module-update\", &module_update, \"update submodule entries\")\n\nExactly the same comment applies to your \"git rm\" patch.\n\n> @@ -96,13 +98,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n>  \t/* Checking */\n>  \tfor (i = 0; i < argc; i++) {\n>  \t\tconst char *src = source[i], *dst = destination[i];\n> -\t\tint length, src_is_dir;\n> +\t\tint length, src_is_dir, pos;\n>  \t\tconst char *bad = NULL;\n>  \n>  \t\tif (show_only)\n>  \t\t\tprintf(\"Checking rename of '%s' to '%s'\\n\", src, dst);\n>  \n>  \t\tlength = strlen(src);\n> +\t\tpos = cache_name_pos(src, length);\n>  \t\tif (lstat(src, &st) < 0)\n>  \t\t\tbad = \"bad source\";\n>  \t\telse if (!strncmp(src, dst, length) &&\n> @@ -111,7 +114,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n>  \t\t} else if ((src_is_dir = S_ISDIR(st.st_mode))\n>  \t\t\t\t&& lstat(dst, &st) == 0)\n>  \t\t\tbad = \"cannot move directory over file\";\n> -\t\telse if (src_is_dir) {\n> +\t\telse if (src_is_dir &&\n> +\t\t\t\t!(pos >= 0 &&\n> +\t\t\t\t  S_ISGITLINK(active_cache[pos]->ce_mode))) {\n\nThis looks like a funny indentation (perhaps a \"diff\" artifcat???)...\n\n> diff --git a/git-submodule.sh b/git-submodule.sh\n> index de29f3a..f1e4e22 100755\n> --- a/git-submodule.sh\n> +++ b/git-submodule.sh\n> @@ -859,6 +859,20 @@ cmd_sync()\n>  \t\tfi\n>  \tdone\n>  }\n> +#\n> +# Updates the entry in .gitmodules to move a submodule.\n> +# This command is called by \"git mv\" for each submodule it moves.\n> +#\n> +cmd_mvconfig()\n> +{\n> +\tsrc=\"$1\"\n> +\tdst=\"$2\"\n> +\n> +\tname=$(module_name \"$src\") || exit\n> +\tgit config -f .gitmodules submodule.\"$name\".path \"$dst\" ||\n> +\t\tdie \"Could not update .gitmodules entry for $name\"\n> +\tgit add .gitmodules || die \"Could not add .gitmodules to index\"\n> +}\n\nThis does not seem to depend on the first \"give random name to submodules\"\npatch in the series, or does it?\n"}]}