Git-clean is not safe when the submodules are not tracked in mainline. If we run git-clean on the mainline branch, when we have a submodule that only exists on a local branch, the entire .git directory of the untracked submodule will get deleted, possibly losing any un-pushed local changes to the submodule. This change doesn't delete any untracked submodule's .git directories during the recursive-delete (unless forced with the -m option to git-clean), so that the submodule history can be restored w/ the proper git commands. # Example illustrating problem: # Clone mainline project git clone git://github.com/thoughtbot/paperclip.git cd paperclip/ # Add a submodule not tracked by mainline git checkout -b test_submodule_clean git submodule add git://github.com/technoweenie/attachment_fu.git attachement_fu git commit -m "add submodule" # Make a modification to the submodule. Note that we haven't pushed the change cd attachement_fu/ git checkout -b mod_readme_in_submodule vi README git add README git commit -m "Small change in submodule" # Go back to mainline's master branch and do a clean cd .. git checkout master git clean -f -d # Our change to the submodule, that was never pushed, is now gone forever # because all the history stored in the submodule's .git direct is deleted. # There is no recovering from this. # This breaks the "git must be safe" rule, as we've lost potentially a lot of # changes to any submodule projects that didn't get pushed yet. Solve # this issue by not deleting any .git directories we come across during a # git-clean, unless the "-m" option is passed to git-clean. Signed-off-by: Jason Holden <jason.k.holden@xxxxxxxxx> --- Documentation/git-clean.txt | 6 +++++- builtin-clean.c | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index be894af..04a5a65 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree SYNOPSIS -------- [verse] -'git clean' [-d] [-f] [-n] [-q] [-x | -X] [--] <path>... +'git clean' [-d] [-f] [-n] [-q] [-m] [-x | -X] [--] <path>... DESCRIPTION ----------- @@ -41,6 +41,10 @@ OPTIONS Be quiet, only report errors, but not the files that are successfully removed. +-m:: + Clean any .git directories that may be left-over, untracked + submodules. + -x:: Don't use the ignore rules. This allows removing all untracked files, including build products. This can be used (possibly in diff --git a/builtin-clean.c b/builtin-clean.c index cd82407..60d78dc 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -15,7 +15,7 @@ static int force = -1; /* unset */ static const char *const builtin_clean_usage[] = { - "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...", + "git clean [-d] [-f] [-n] [-q] [-m] [-x | -X] [--] <paths>...", NULL }; @@ -31,6 +31,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) int i; int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; + int rm_untracked_submodules = 0; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; const char *path, *base; @@ -44,6 +45,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('f', NULL, &force, "force"), OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), + OPT_BOOLEAN('m', NULL, &rm_untracked_submodules, + "remove untracked submodules"), OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), OPT_BOOLEAN('X', NULL, &ignored_only, "remove only ignored files"), @@ -59,6 +62,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, 0); + + int keep_dot_git = 0; + if (rm_untracked_submodules == 0) + keep_dot_git = 1; + else + printf("Any untracked .git directories will be deleted (abandoned submodules)\n"); + + memset(&dir, 0, sizeof(dir)); if (ignored_only) dir.flags |= DIR_SHOW_IGNORED; @@ -141,7 +152,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) (matches == MATCHED_EXACTLY)) { if (!quiet) printf("Removing %s\n", qname); - if (remove_dir_recursively(&directory, 0, 0) != 0) { + if (remove_dir_recursively(&directory, 0, keep_dot_git) != 0) { warning("failed to remove '%s'", qname); errors++; } -- 1.6.3.2.207.ga8208 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html