{"thread":{"id":"10818","subject":"[PATCH] Make git-clean a builtin","startedAt":"2007-11-12T01:48:47Z","lastAt":"2007-11-12T01:48:47Z","messageCount":1,"participants":["Shawn Bohrer"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"59413","messageId":"11948321274147-git-send-email-shawn.bohrer@gmail.com","threadId":"10818","inReplyTo":null,"subject":"[PATCH] Make git-clean a builtin","fromName":"Shawn Bohrer","fromEmail":"shawn.bohrer@gmail.com","sentAt":"2007-11-12T01:48:47Z","receivedAt":"2007-11-12T01:48:47Z","isPatch":true,"sender":{"key":"shawn.bohrer@gmail.com","avatar":"https://gravatar.com/avatar/6eb093ef7d276306d18366254e0c95ff6a5db58231ac7e82fe78c2800aaae1b6?d=mp&s=160"},"body":"This replaces git-clean.sh with builtin-clean.c, and moves\ngit-clean.sh to the examples.\n\nThis also introduces a change in behavior when removing directories\nexplicitly specified as a path.  For example currently:\n\n1. When dir has only untracked files, these two behave differently:\n\n    $ git clean -n dir\n    $ git clean -n dir/\n\nthe former says \"Would not remove dir/\", while the latter would say\n\"Would remove dir/untracked\" for all paths under it, but not the\ndirectory itself.\n\nWith -d, the former would stop refusing, however since the user\nexplicitly asked to remove the directory the -d is no longer required.\n\n2. When there are more parameters:\n\n    $ git clean -n dir foo\n    $ git clean -n dir/ foo\n\nboth cases refuse to remove dir/ unless -d is specified.  Once again\nsince both cases requested to remove dir the -d is no longer required.\n\nThanks to Johannes Schindelin for the conversion to using the\nparse-options API.\n\nSigned-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n---\n\nOn Wed, Nov 07, 2007 at 11:37:50PM -0600, Shawn Bohrer wrote:\n> On Wed, Nov 07, 2007 at 12:42:16PM -0800, Junio C Hamano wrote:\n> >\n> > Having said that, I do not particularly agree with the way the\n> > new implementation resolves the existing inconsistencies.\n> >\n> > Wouldn't it be better to remove \"dir\" when the user explicitly\n> > told you to clean \"dir\", with or without the trailing slash?\n> > That's what the user asked you to do, isn't it?\n>\n> Yes I suppose I agree.  Of course I need to spend some more time staring\n> at the code to figure out how to do so.  Perhaps I can figure out what\n> is causing the original inconsistency in git-ls-files while I'm at it.\n\nOK, I failed to fix the inconsistency between a single and multiple\npaths so I'm not sure if this patch is much better.  The problem appears\nto be that when dir.show_others_directories is set directories that only\ncontain untracked files are not recursed, which is fine with a single\npath because you can adjust the path, base and baselen to\nread_directory().  With multiple paths you need to find a common prefix\nto all of the paths before calling read_directory() which causes the\ninconsistency.\n\nIf anyone as a suggestion on how to fix this I'm all for it, but even\nwith the inconsistency we are no worse than we were with git-clean.sh\n\n Makefile                                      |    3 +-\n builtin-clean.c                               |  154 +++++++++++++++++++++++++\n builtin.h                                     |    1 +\n git-clean.sh => contrib/examples/git-clean.sh |    0 \n git.c                                         |    1 +\n 5 files changed, 158 insertions(+), 1 deletions(-)\n create mode 100644 builtin-clean.c\n rename git-clean.sh => contrib/examples/git-clean.sh (100%)\n\ndiff --git a/Makefile b/Makefile\nindex a2fcdb8..ef7420d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -213,7 +213,7 @@ BASIC_LDFLAGS =\n \n SCRIPT_SH = \\\n \tgit-bisect.sh git-checkout.sh \\\n-\tgit-clean.sh git-clone.sh git-commit.sh \\\n+\tgit-clone.sh git-commit.sh \\\n \tgit-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \\\n \tgit-pull.sh git-rebase.sh git-rebase--interactive.sh \\\n \tgit-repack.sh git-request-pull.sh \\\n@@ -329,6 +329,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-check-attr.o \\\n \tbuiltin-checkout-index.o \\\n \tbuiltin-check-ref-format.o \\\n+\tbuiltin-clean.o \\\n \tbuiltin-commit-tree.o \\\n \tbuiltin-count-objects.o \\\n \tbuiltin-describe.o \\\ndiff --git a/builtin-clean.c b/builtin-clean.c\nnew file mode 100644\nindex 0000000..55658e7\n--- /dev/null\n+++ b/builtin-clean.c\n@@ -0,0 +1,154 @@\n+/*\n+ * \"git clean\" builtin command\n+ *\n+ * Copyright (C) 2007 Shawn Bohrer\n+ *\n+ * Based on git-clean.sh by Pavel Roskin\n+ */\n+\n+#include \"builtin.h\"\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"parse-options.h\"\n+\n+static int force;\n+\n+static const char *const builtin_clean_usage[] = {\n+\t\"git-clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...\",\n+\tNULL\n+};\n+\n+static int git_clean_config(const char *var, const char *value)\n+{\n+\tif (!strcmp(var, \"clean.requireforce\"))\n+\t\tforce = !git_config_bool(var, value);\n+\treturn 0;\n+}\n+\n+int cmd_clean(int argc, const char **argv, const char *prefix)\n+{\n+\tint j;\n+\tint show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;\n+\tint ignored_only = 0, baselen = 0;\n+\tstruct strbuf directory;\n+\tstruct dir_struct dir;\n+\tconst char *path, *base;\n+\tstatic const char **pathspec;\n+\tstruct option options[] = {\n+\t\tOPT__QUIET(&quiet),\n+\t\tOPT__DRY_RUN(&show_only),\n+\t\tOPT_BOOLEAN('f', NULL, &force, \"force\"),\n+\t\tOPT_BOOLEAN('d', NULL, &remove_directories,\n+\t\t\t\t\"remove whole directories\"),\n+\t\tOPT_BOOLEAN('x', NULL, &ignored, \"remove ignored files, too\"),\n+\t\tOPT_BOOLEAN('X', NULL, &ignored_only,\n+\t\t\t\t\"remove only ignored files\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tgit_config(git_clean_config);\n+\targc = parse_options(argc, argv, options, builtin_clean_usage, 0);\n+\n+\tmemset(&dir, 0, sizeof(dir));\n+\tif (ignored_only) {\n+\t\tdir.show_ignored =1;\n+\t\tdir.exclude_per_dir = \".gitignore\";\n+\t}\n+\n+\tif (ignored && ignored_only)\n+\t\tdie(\"-x and -X cannot be used together\");\n+\n+\tif (!show_only && !force)\n+\t\tdie(\"clean.requireForce set and -n or -f not given; refusing to clean\");\n+\n+\tdir.show_other_directories = 1;\n+\n+\tif (!ignored) {\n+\t\tdir.exclude_per_dir = \".gitignore\";\n+\t\tif (!access(git_path(\"info/exclude\"), F_OK)) {\n+\t\t\tchar *exclude_path = git_path(\"info/exclude\");\n+\t\t\tadd_excludes_from_file(&dir, exclude_path);\n+\t\t}\n+\t}\n+\n+\tpathspec = get_pathspec(prefix, argv);\n+\tread_cache();\n+\n+\t/*\n+\t * Calculate common prefix for the pathspec, and\n+\t * use that to optimize the directory walk\n+\t */\n+\tbaselen = common_prefix(pathspec);\n+\tpath = \".\";\n+\tbase = \"\";\n+\tif (baselen)\n+\t\tpath = base = xmemdupz(*pathspec, baselen);\n+\tread_directory(&dir, path, base, baselen, pathspec);\n+\tstrbuf_init(&directory, 0);\n+\n+\tfor (j = 0; j < dir.nr; ++j) {\n+\t\tstruct dir_entry *ent = dir.entries[j];\n+\t\tint len, pos, specs;\n+\t\tstruct cache_entry *ce;\n+\t\tstruct stat st;\n+\t\tchar *seen;\n+\n+\t\t/*\n+\t\t * Remove the '/' at the end that directory\n+\t\t * walking adds for directory entries.\n+\t\t */\n+\t\tlen = ent->len;\n+\t\tif (len && ent->name[len-1] == '/')\n+\t\t\tlen--;\n+\t\tpos = cache_name_pos(ent->name, len);\n+\t\tif (0 <= pos)\n+\t\t\tcontinue;\t/* exact match */\n+\t\tpos = -pos - 1;\n+\t\tif (pos < active_nr) {\n+\t\t\tce = active_cache[pos];\n+\t\t\tif (ce_namelen(ce) == len &&\n+\t\t\t    !memcmp(ce->name, ent->name, len))\n+\t\t\t\tcontinue; /* Yup, this one exists unmerged */\n+\t\t}\n+\n+\t\tif (!lstat(ent->name, &st) && (S_ISDIR(st.st_mode))) {\n+\t\t\tint matched_path = 0;\n+\t\t\tstrbuf_addstr(&directory, ent->name);\n+\t\t\tif (pathspec) {\n+\t\t\t\tfor (specs =0; pathspec[specs]; ++specs)\n+\t\t\t\t\t/* nothing */;\n+\t\t\t\tseen = xcalloc(specs, 1);\n+\t\t\t\t/* Check if directory was explictly passed as\n+\t\t\t\t * pathspec.  If so we want to remove it */\n+\t\t\t\tif (match_pathspec(pathspec, ent->name, ent->len,\n+\t\t\t\t\t\t   baselen, seen))\n+\t\t\t\t\tmatched_path = 1;\n+\t\t\t\tfree(seen);\n+\t\t\t}\n+\t\t\tif (show_only && (remove_directories || matched_path)) {\n+\t\t\t\tprintf(\"Would remove %s\\n\", directory.buf);\n+\t\t\t} else if (quiet && (remove_directories || matched_path)) {\n+\t\t\t\tremove_dir_recursively(&directory, 0);\n+\t\t\t} else if (remove_directories || matched_path) {\n+\t\t\t\tprintf(\"Removing %s\\n\", directory.buf);\n+\t\t\t\tremove_dir_recursively(&directory, 0);\n+\t\t\t} else if (show_only) {\n+\t\t\t\tprintf(\"Would not remove %s\\n\", directory.buf);\n+\t\t\t} else {\n+\t\t\t\tprintf(\"Not removing %s\\n\", directory.buf);\n+\t\t\t}\n+\t\t\tstrbuf_reset(&directory);\n+\t\t} else {\n+\t\t\tif (show_only) {\n+\t\t\t\tprintf(\"Would remove %s\\n\", ent->name);\n+\t\t\t\tcontinue;\n+\t\t\t} else if (!quiet) {\n+\t\t\t\tprintf(\"Removing %s\\n\", ent->name);\n+\t\t\t}\n+\t\t\tunlink(ent->name);\n+\t\t}\n+\t}\n+\n+\tstrbuf_release(&directory);\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 525107f..5476a92 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);\n extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n+extern int cmd_clean(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_count_objects(int argc, const char **argv, const char *prefix);\n extern int cmd_describe(int argc, const char **argv, const char *prefix);\ndiff --git a/git-clean.sh b/contrib/examples/git-clean.sh\nsimilarity index 100%\nrename from git-clean.sh\nrename to contrib/examples/git-clean.sh\ndiff --git a/git.c b/git.c\nindex 204a6f7..3fa8e4d 100644\n--- a/git.c\n+++ b/git.c\n@@ -293,6 +293,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"check-attr\", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"cherry\", cmd_cherry, RUN_SETUP },\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n+\t\t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n \t\t{ \"config\", cmd_config },\n \t\t{ \"count-objects\", cmd_count_objects, RUN_SETUP },\n-- \n1.5.3.GIT\n"}]}