threads / patch / 10698

patchMake git-clean a builtin

Subject: [PATCH] Make git-clean a builtin

## tl;dr

13 messages between Nov 7, 2007 and Nov 10, 2007. Diffs are folded; open one to read it.

replies: 12people: 8as markdown or json

Shawn Bohrer· Nov 7, 2007, 05:18 UTC · lore

This replaces git-clean.sh with builtin-clean.c, and moves git-clean.sh to the examples.

This also introduces a change in behavior where the -d parameter is required to remove an entire directory of untracked files even when the directory is passed as a path. For example:

   git clean dir/
Now requires
   git clean -d dir/

if 'dir' only contains untracked files. This is consistent with the old behavior when two or more paths were specified.

Thanks to Johannes Schindelin for the conversion to using the parse-options API.

Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>
---
 Makefile                                      |    3 +-
 builtin-clean.c                               |  134 +++++++++++++++++++++++++
 builtin.h                                     |    1 +
 git-clean.sh => contrib/examples/git-clean.sh |    0 
 git.c                                         |    1 +
 5 files changed, 138 insertions(+), 1 deletions(-)
 create mode 100644 builtin-clean.c
 rename git-clean.sh => contrib/examples/git-clean.sh (100%)
Show changes to 5 files +138 −1

Makefile, builtin-clean.c, builtin.h, git-clean.sh, git.c

diff --git a/Makefile b/Makefile
index c427fee..932ff08 100644
--- a/Makefile
+++ b/Makefile
@@ -209,7 +209,7 @@ BASIC_LDFLAGS =
 
 SCRIPT_SH = \
 	git-bisect.sh git-checkout.sh \
-	git-clean.sh git-clone.sh git-commit.sh \
+	git-clone.sh git-commit.sh \
 	git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
 	git-pull.sh git-rebase.sh git-rebase--interactive.sh \
 	git-repack.sh git-request-pull.sh \
@@ -325,6 +325,7 @@ BUILTIN_OBJS = \
 	builtin-check-attr.o \
 	builtin-checkout-index.o \
 	builtin-check-ref-format.o \
+	builtin-clean.o \
 	builtin-commit-tree.o \
 	builtin-count-objects.o \
 	builtin-describe.o \
diff --git a/builtin-clean.c b/builtin-clean.c
new file mode 100644
index 0000000..fb2feb5
--- /dev/null
+++ b/builtin-clean.c
@@ -0,0 +1,134 @@
+/*
+ * "git clean" builtin command
+ *
+ * Copyright (C) 2007 Shawn Bohrer
+ *
+ * Based on git-clean.sh by Pavel Roskin
+ */
+
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "parse-options.h"
+
+static int force;
+
+static const char *const builtin_clean_usage[] = {
+	"git-clean [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...",
+	NULL
+};
+
+static int git_clean_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "clean.requireforce")) {
+		force = !git_config_bool(var, value);
+	}
+	return 0;
+}
+
+int cmd_clean(int argc, const char **argv, const char *prefix)
+{
+	int j;
+	int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
+	int ignored_only = 0, baselen = 0;
+	struct strbuf directory;
+	struct dir_struct dir;
+	const char *path = ".";
+	const char *base = "";
+	static const char **pathspec;
+	struct option options[] = {
+		OPT__QUIET(&quiet),
+		OPT__DRY_RUN(&show_only),
+		OPT_BOOLEAN('f', NULL, &force, "force"),
+		OPT_BOOLEAN('d', NULL, &remove_directories,
+				"remove whole directories"),
+		OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
+		OPT_BOOLEAN('X', NULL, &ignored_only,
+				"remove only ignored files"),
+		OPT_END()
+	};
+
+	git_config(git_clean_config);
+	argc = parse_options(argc, argv, options, builtin_clean_usage, 0);
+
+	memset(&dir, 0, sizeof(dir));
+	if (ignored_only) {
+		dir.show_ignored =1;
+		dir.exclude_per_dir = ".gitignore";
+	}
+
+	if (ignored && ignored_only)
+		die("-x and -X cannot be used together");
+
+	if (!show_only && !force)
+		die("clean.requireForce set and -n or -f not given; refusing to clean");
+
+	dir.show_other_directories = 1;
+
+	if (!ignored) {
+		dir.exclude_per_dir = ".gitignore";
+		if (!access(git_path("info/exclude"), F_OK)) {
+			char *exclude_path = git_path("info/exclude");
+			add_excludes_from_file(&dir, exclude_path);
+		}
+	}
+
+	pathspec = get_pathspec(prefix, argv);
+	read_cache();
+	read_directory(&dir, path, base, baselen, pathspec);
+	strbuf_init(&directory, 0);
+
+	for (j = 0; j < dir.nr; ++j) {
+		struct dir_entry *ent = dir.entries[j];
+		int len, pos;
+		struct cache_entry *ce;
+		struct stat st;
+
+		/*
+		 * Remove the '/' at the end that directory
+		 * walking adds for directory entries.
+		 */
+		len = ent->len;
+		if (len && ent->name[len-1] == '/')
+			len--;
+		pos = cache_name_pos(ent->name, len);
+		if (0 <= pos)
+			continue;	/* exact match */
+		pos = -pos - 1;
+		if (pos < active_nr) {
+			ce = active_cache[pos];
+			if (ce_namelen(ce) == len &&
+			    !memcmp(ce->name, ent->name, len))
+				continue; /* Yup, this one exists unmerged */
+		}
+
+		/* remove the files */
+		if (!lstat(ent->name, &st) && (S_ISDIR(st.st_mode))) {
+			strbuf_addstr(&directory, ent->name);
+			if (show_only && remove_directories) {
+				printf("Would remove %s\n", directory.buf);
+			} else if (quiet && remove_directories) {
+				remove_dir_recursively(&directory, 0);
+			} else if (remove_directories) {
+				printf("Removing %s\n", ent->name);
+				remove_dir_recursively(&directory, 0);
+			} else if (show_only) {
+				printf("Would not remove %s\n", directory.buf);
+			} else {
+				printf("Not removing %s\n", directory.buf);
+			}
+			strbuf_reset(&directory);
+		} else {
+			if (show_only) {
+				printf("Would remove %s\n", ent->name);
+				continue;
+			} else if (!quiet) {
+				printf("Removing %s\n", ent->name);
+			}
+			unlink(ent->name);
+		}
+	}
+
+	strbuf_release(&directory);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 525107f..5476a92 100644
--- a/builtin.h
+++ b/builtin.h
@@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
+extern int cmd_clean(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_describe(int argc, const char **argv, const char *prefix);
diff --git a/git-clean.sh b/contrib/examples/git-clean.sh
similarity index 100%
rename from git-clean.sh
rename to contrib/examples/git-clean.sh
diff --git a/git.c b/git.c
index 204a6f7..3fa8e4d 100644
--- a/git.c
+++ b/git.c
@@ -293,6 +293,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
 		{ "cherry", cmd_cherry, RUN_SETUP },
 		{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
+		{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
 		{ "commit-tree", cmd_commit_tree, RUN_SETUP },
 		{ "config", cmd_config },
 		{ "count-objects", cmd_count_objects, RUN_SETUP },
-- 
1.5.3.GIT
Bill Lear· Nov 7, 2007, 13:29 UTC · re: Johannes Schindelin · lore

Re: [PATCH] Make git-clean a builtin

On Wednesday, November 7, 2007 at 11:10:45 (+0000) Johannes Schindelin writes:
Show 8 quoted lines
>Hi,
>
>you still have quite a number of instances where you wrap just one line 
>into curly brackets:
>
>	if (bla) {
>		[just one line]
>	}
I've always found this a thoughtful practice.  It helps ensure nobody writes:
       if (bla)
           just_one_line();
           /* perhaps a comment, other stuff ... */
           just_another_line();

which I've seen happen countless times. It also is nice for others who come along and extend the branch from just one line to multiple ones, as the brackets are already in place.

Why do you find it objectionable?
Bill
Johannes Schindelin· Nov 7, 2007, 14:17 UTC · re: Bill Lear · lore

Re: [PATCH] Make git-clean a builtin

Hi,
On Wed, 7 Nov 2007, Bill Lear wrote:
Show 16 quoted lines
> On Wednesday, November 7, 2007 at 11:10:45 (+0000) Johannes Schindelin writes:
>
> > you still have quite a number of instances where you wrap just one 
> > line into curly brackets:
> >
> >	if (bla) {
> >		[just one line]
> >	}
> 
> I've always found this a thoughtful practice.  It helps ensure nobody 
> writes:
> 
>        if (bla)
>            just_one_line();
>            /* perhaps a comment, other stuff ... */
>            just_another_line();

But if there is only one line and you fail to add curly brackets when adding a second line, well, uhm, then I cannot help you with anything.

BTW I was talking about _one_ line, not a line and another one with a comment.

> It also is nice for others who come along and extend the branch from 
> just one line to multiple ones, as the brackets are already in place.
The fact is: these lines will stay single lines most likely for eternity.
> Why do you find it objectionable?
It distracts.  It's ugly.  It's unnecessary.

Ciao, Dscho

Matthieu Moy· Nov 7, 2007, 14:45 UTC · re: Bill Lear · lore

Re: [PATCH] Make git-clean a builtin

Bill Lear <rael@zopyra.com> writes:
Show 6 quoted lines
> I've always found this a thoughtful practice.  It helps ensure nobody writes:
>
>        if (bla)
>            just_one_line();
>            /* perhaps a comment, other stuff ... */
>            just_another_line();
It also simplify patches for cases like
 	if (bla) {
 		just_one_line();
+		another_added_line();
 	}
instead of
- 	if (bla)
+ 	if (bla) {
 		just_one_line();
+		another_added_line();
+	}
But it seems people here prefer not putting the braces in this case.
-- 
Matthieu
Jon Loeliger· Nov 7, 2007, 19:46 UTC · re: Bill Lear · lore

Re: [PATCH] Make git-clean a builtin

On Wed, 2007-11-07 at 07:29, Bill Lear wrote:
Show 22 quoted lines
> On Wednesday, November 7, 2007 at 11:10:45 (+0000) Johannes Schindelin writes:
> >Hi,
> >
> >you still have quite a number of instances where you wrap just one line 
> >into curly brackets:
> >
> >	if (bla) {
> >		[just one line]
> >	}
> 
> I've always found this a thoughtful practice.  It helps ensure nobody writes:
> 
>        if (bla)
>            just_one_line();
>            /* perhaps a comment, other stuff ... */
>            just_another_line();
> 
> which I've seen happen countless times.  It also is nice for others who
> come along and extend the branch from just one line to multiple ones,
> as the brackets are already in place.
> 
> Why do you find it objectionable?
I _totally_ agree with Bill.
jdl
Miles Bader· Nov 10, 2007, 22:43 UTC · re: Bill Lear · lore

Re: [PATCH] Make git-clean a builtin

Bill Lear <rael@zopyra.com> writes:
> Why do you find it objectionable?
It bloats the code, and makes it less readable.

[My conjecture is that the latter happens because braces are so visually striking that they attract the eye; for _long_ blocks, this property of braces helps, because it makes it easier to find them amongst the rest of the code, but for _short_ blocks, it hurts, because it draws the eye away from the actual code, and emphasizes structure at a time when structure doesn't need emphasizing (because it's utterly obvious already with such short blocks).]

-Miles
-- 
Run away!  Run away!
Shawn Bohrer· Nov 7, 2007, 14:54 UTC · re: Johannes Schindelin · lore

Re: [PATCH] Make git-clean a builtin

On Wed, Nov 07, 2007 at 11:10:45AM +0000, Johannes Schindelin wrote:
Show 7 quoted lines
> 
> you still have quite a number of instances where you wrap just one line 
> into curly brackets:
> 
> 	if (bla) {
> 		[just one line]
> 	}
Crap.  OK I count one instance unless you count:
	if (foo) {
		one_line();
	} else if (bar) {
		one_line();
		two_lines();
	} else {
		something_else();
	}

Now I suppose I can get rid of the curly braces here as well but I personally find that strange and ugly. So is there an official guideline on if else statements?

Of course I'll fix the other one I missed and send a new patch.
Johannes Schindelin· Nov 7, 2007, 15:04 UTC · re: Shawn Bohrer · lore

Re: [PATCH] Make git-clean a builtin

Hi,
On Wed, 7 Nov 2007, Shawn Bohrer wrote:
Show 19 quoted lines
> On Wed, Nov 07, 2007 at 11:10:45AM +0000, Johannes Schindelin wrote:
> > 
> > you still have quite a number of instances where you wrap just one line 
> > into curly brackets:
> > 
> > 	if (bla) {
> > 		[just one line]
> > 	}
> 
> Crap.  OK I count one instance unless you count:
> 
> 	if (foo) {
> 		one_line();
> 	} else if (bar) {
> 		one_line();
> 		two_lines();
> 	} else {
> 		something_else();
> 	}

I do count them. Personally, I find it highly distracting and ugly. Besides, we have the convention of putting the "}" not into the same line as "else". (See keyword "uncuddling" in the list archives.)

While it may be true that some parts of the code follow these rules less strictly, it does not mean that we should introduce more of that kind.

BTW there are plenty of examples in the existing code which illustrate our implicit coding conventions.

> Now I suppose I can get rid of the curly braces here as well but I 
> personally find that strange and ugly.  So is there an official 
> guideline on if else statements?

Not yet ;-) I can add it to the tentative v3 of Documentation/CodingStyle or CodingConventions or however the list would like to name it.

Ciao, Dscho

Brian Downing· Nov 7, 2007, 20:51 UTC · re: Johannes Schindelin · lore

Re: [PATCH] Make git-clean a builtin

On Wed, Nov 07, 2007 at 03:04:52PM +0000, Johannes Schindelin wrote:
> I do count them.  Personally, I find it highly distracting and ugly.  
> Besides, we have the convention of putting the "}" not into the same line 
> as "else".  (See keyword "uncuddling" in the list archives.)

I was under the impression that Git followed the kernel coding standards, which seem to want "cuddled" else statements:

136 Note that the closing brace is empty on a line of its own, _except_ in 137 the cases where it is followed by a continuation of the same statement, 138 ie a "while" in a do-statement or an "else" in an if-statement, like 139 this: 140 141 do { 142 body of do-loop 143 } while (condition); 144 145 and 146 147 if (x == y) { 148 .. 149 } else if (x > y) { 150 ... 151 } else { 152 .... 153 } 154 155 Rationale: K&R.

Searching the MARC list archives for "uncuddling" only yields the message I am replying to.

In addition, the kernel style seems to want braces for all branches of a conditional if any branch needs it:

163 Do not unnecessarily use braces where a single statement will do. 164 165 if (condition) 166 action(); 167 168 This does not apply if one branch of a conditional statement is a single 169 statement. Use braces in both branches. 170 171 if (condition) { 172 do_this(); 173 do_that(); 174 } else { 175 otherwise(); 176 }

This makes sense (to me), as at most you're only adding one extra line for the final closing brace, and it makes the whole conditional look more "balanced", IMHO.

But regardless, whatever the actual style for Git should be followed. Life's too short for arguments about coding style (even if divergence from K&R brace style is just plain wrong. :)

-bcd
Junio C Hamano· Nov 7, 2007, 21:49 UTC · re: Brian Downing · lore

Re: [PATCH] Make git-clean a builtin

bdowning@lavos.net (Brian Downing) writes:
Show 7 quoted lines
> This makes sense (to me), as at most you're only adding one extra line
> for the final closing brace, and it makes the whole conditional look more
> "balanced", IMHO.
>
> But regardless, whatever the actual style for Git should be followed.
> Life's too short for arguments about coding style (even if divergence
> from K&R brace style is just plain wrong.  :)

Ok. We do not have any particularly strong technical reason to deviate from the kernel style. Let's follow that.

Junio C Hamano· Nov 7, 2007, 20:42 UTC · re: Shawn Bohrer · lore

Re: [PATCH] Make git-clean a builtin

Shawn Bohrer <shawn.bohrer@gmail.com> writes:
Show 6 quoted lines
> This replaces git-clean.sh with builtin-clean.c, and moves
> git-clean.sh to the examples.
>
> This also introduces a change in behavior where the -d parameter is
> required to remove an entire directory of untracked files even when
> the directory is passed as a path.

The updated behaviour may be better, but this description at the first read makes one wonder if it is describing a regression as if it is a feature.

> ... For example ...
> ...
> if 'dir' only contains untracked files.  This is consistent with the
> old behavior when two or more paths were specified.

I think what you fixed are two inconsistencies in the original implementation. If you spelled out the existing inconsistency and described what your implementation does differently, the proposal would start looking like a real improvement, like this:

    1. When dir has only untracked files, these two behave differently:
        $ git clean -n dir
        $ git clean -n dir/
    the former says "Would not remove dir/", while the latter would
    say "Would remove dir/untracked" for all paths under it.
    With -d, the former would stop refusing, but the difference in
    reporting is still there.  The latter lists all paths under the
    directory.
    2. When there are more parameters, the latter behave differently:
        $ git clean -n dir/ foo
    refuses to remove dir/.  This is inconsistent.
    My reimplementation changes the behaviour by always
    requiring the -d option with or without the trailing slash.

Having said that, I do not particularly agree with the way the new implementation resolves the existing inconsistencies.

Wouldn't it be better to remove "dir" when the user explicitly told you to clean "dir", with or without the trailing slash? That's what the user asked you to do, isn't it?

Shawn Bohrer· Nov 8, 2007, 05:37 UTC · re: Junio C Hamano · lore

Re: [PATCH] Make git-clean a builtin

On Wed, Nov 07, 2007 at 12:42:16PM -0800, Junio C Hamano wrote:
Show 7 quoted lines
> 
> Having said that, I do not particularly agree with the way the
> new implementation resolves the existing inconsistencies.  
> 
> Wouldn't it be better to remove "dir" when the user explicitly
> told you to clean "dir", with or without the trailing slash?
> That's what the user asked you to do, isn't it?

Yes I suppose I agree. Of course I need to spend some more time staring at the code to figure out how to do so. Perhaps I can figure out what is causing the original inconsistency in git-ls-files while I'm at it.

← back to recent threads