Skip to contentSkip to conversation

Teach rm to remove submodules unless they contain a git directory

PATCH In review Jens Lehmann 2 participants 3 messages Jun 15, 2016 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH v4 1/1 Teach rm to remove submodules unless they contain a git directory

Currently using "git rm" on a submodule - populated or not - fails with this error:
fatal: git rm: '<submodule path>': Is a directory
This made sense in the past as there was no way to remove a submodule without possibly removing unpushed parts of the submodule's history contained in its .git directory too, so erroring out here protected the user from possible loss of data.

But submodules cloned with a recent git version do not contain the .git directory anymore, they use a gitfile to point to their git directory which is safely stored inside the superproject's .git directory. The work tree of these submodules can safely be removed without loosing history, so let's teach git to do so.

Using rm on an unpopulated submodule now removes the empty directory from the work tree and the gitlink from the index. If the submodule's directory is missing from the work tree, it will still be removed from the index.

Using rm on a populated submodule using a gitfile will apply the usual checks for work tree modification adapted to submodules (unless forced). For a submodule that means that the HEAD is the same as recorded in the index, no tracked files are modified and no untracked files that aren't ignored are present in the submodules work tree (ignored files are deemed expendable and won't stop a submodule's work tree from being removed). That logic has to be applied in all nested submodules too.

Using rm on a submodule which has its .git directory inside the work trees top level directory will just error out like it did before to protect the repository, even when forced. In the future git could either provide a message informing the user to convert the submodule to use a gitfile or even attempt to do the conversion itself, but that is not part of this change.

---

Changes since v3:
- Added get_ours_cache_pos() helper to only check stage 2 of a conflict
- Added tests for modified submodules in the conflict case

 Documentation/git-rm.txt |  15 +++
 builtin/rm.c             | 125 ++++++++++++++---
 submodule.c              |  80 +++++++++++
 submodule.h              |   2 +
 t/t3600-rm.sh            | 343 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 550 insertions(+), 15 deletions(-)
Documentation/git-rm.txt +15 -0
index 5d31860..882cb11 100644
@@ -107,6 +107,21 @@ as well as modifications of existing paths.
 Typically you would first remove all tracked files from the working
 tree using this command:
 
+Submodules
+~~~~~~~~~~
+Only submodules using a gitfile (which means they were cloned
+with a git version 1.7.8 or newer) will be removed from the work
+tree, as their repository lives inside the .git directory of the
+superproject. If a submodule (or one of those nested inside it)
+still uses a .git directory, `git rm` will fail - no matter if forced
+or not - to protect the submodule's history.
+
+A submodule is considered up-to-date when the HEAD is the same as
+recorded in the index, no tracked files are modified and no untracked
+files that aren't ignored are present in the submodules work tree.
+Ignored files are deemed expendable and won't stop a submodule's work
+tree from being removed.
+
 ----------------
 git ls-files -z | xargs -0 rm -f
 ----------------
builtin/rm.c +110 -15

195 lines not on this page · raw message

submodule.c +80 -0

86 lines not on this page · raw message

submodule.h +2 -0
index 594b50d..f2e8271 100644
@@ -28,6 +28,8 @@ int fetch_populated_submodules(const struct argv_array *options,
 			       const char *prefix, int command_line_option,
 			       int quiet);
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
+int submodule_uses_gitfile(const char *path);
+int ok_to_remove_submodule(const char *path);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
 		    const unsigned char a[20], const unsigned char b[20], int search);
 int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
t/t3600-rm.sh +343 -0

347 lines not on this page · raw message

Signature · 1 line
1.7.12.1.429.g2919c44
#1 lore raw
Quoted 3 lines · Jens Lehmann

Changes since v3:
- Added get_ours_cache_pos() helper to only check stage 2 of a conflict
- Added tests for modified submodules in the conflict case

Thanks.

builtin/rm.c static int check_local_mod(unsigned char *head, int index_only) · line 101

Quoted 4 lines
builtin/rm.c +4 -0
+			/*
+			 * Skip unmerged entries except for populated submodules
+			 * that could loose history when removed.
+			 */

s/loose/lose/, I think.

#2 lore raw

Am 27.09.2012 18:53, schrieb Junio C Hamano:

Quoted 7 lines

builtin/rm.c static int check_local_mod(unsigned char *head, int index_only) · line 101

Quoted 4 lines · Jens Lehmann
builtin/rm.c +4 -0
+			/*
+			 * Skip unmerged entries except for populated submodules
+			 * that could loose history when removed.
+			 */

s/loose/lose/, I think.

Thanks for squashing that one in!

I also used "loose" in the commit message where "lose" would have been correct. I can resend a new version correcting both if you want.