Make git-clean a builtin
PATCH 1/1 Make git-clean a builtin
This replaces git-clean.sh with builtin-clean.c, and moves git-clean.sh to the examples.
--- Makefile | 3 +- builtin-clean.c | 177 +++++++++++++++++++++++++ builtin.h | 1 + git-clean.sh => contrib/examples/git-clean.sh | 0 git.c | 1 + 5 files changed, 181 insertions(+), 1 deletions(-) create mode 100644 builtin-clean.c rename git-clean.sh => contrib/examples/git-clean.sh (100%)
Makefile +2 -1
@@ -206,7 +206,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-fetch.sh \ git-ls-remote.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
@@ -327,6 +327,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 \
builtin-clean.c new file +177 -0
177 lines not on this page · raw message
builtin.h +1 -0
@@ -23,6 +23,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);
contrib/examples/git-clean.sh renamed from git-clean.sh +0 -0
git.c +1 -0
@@ -320,6 +320,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 }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP },
Signature · 1 line
1.5.3.GIT
Quoted 11 lines · Shawn Bohrer
builtin-clean.c new file +11
-0+static int remove_directory (const char *path) +{ +DIR *d; +struct dirent *dir; + d =opendir (path); +if (d) { +chdir (path); +while ((dir =readdir (d)) !=NULL ) { +if (strcmp ( dir->d_name,"." ) ==0 || +strcmp ( dir->d_name,".." ) ==0 ) +continue ;
builtin-clean.c line 51
Quoted 10 lines
builtin-clean.c new file +10
-0+if (dir->d_type ==DT_DIR ) +remove_directory (dir->d_name); +else +unlink (dir->d_name); + } + } +closedir (d); +chdir (".." ); +return rmdir (path); +}
The unconditional chdir(..) after the conditional chdir(path) seems like asking for trouble to me...
builtin-clean.c line 146
Quoted 6 lines
builtin-clean.c new file +6
-0+while (fgets (path,sizeof (path), cmd_fout) !=NULL ) { +struct stat st; +char *p; + p =strrchr (path,'\n' ); +if ( p !=NULL ) + *p ='\0' ;
What happens in case p == NULL? It simply tries to remove the partial path?
builtin-clean.c line 174
Quoted 4 lines
builtin-clean.c new file +4
-0+fclose (cmd_fout); +finish_command (&cmd); +if (!ignored && !access (git_path ("info/exclude" ),F_OK )) +free (buf);
There is a race condition here of the value of access() changes between the two calls. Not one likely to trigger but it should be easy to avoid alltogether.
builtin-clean.c line 177
Quoted 3 lines
builtin-clean.c new file +3
-0+free (argv_ls_files); +return 0 ; +}
Gruesse,
Signature · 2 lines
Frank Lichtenheld <[email protected]> www: http://www.djpig.de/
Quoted 17 lines · Frank Lichtenheld
Quoted 16 lines · Shawn Bohrer
builtin-clean.c new file +16
-0+static int remove_directory (const char *path) +{ +DIR *d; +struct dirent *dir; + d =opendir (path); +if (d) { +chdir (path); +while ((dir =readdir (d)) !=NULL ) { +if (strcmp ( dir->d_name,"." ) ==0 || +strcmp ( dir->d_name,".." ) ==0 ) +continue ; +if (dir->d_type ==DT_DIR ) +remove_directory (dir->d_name); +else +unlink (dir->d_name); + }
Quoted 8 lines
builtin-clean.c line 51
Quoted 5 lines
builtin-clean.c new file +5
-0+ } +closedir (d); +chdir (".." ); +return rmdir (path); +}The unconditional chdir(..) after the conditional chdir(path) seems like asking for trouble to me...
Agreed, I'm not sure what I was thinking there.
Quoted 9 lines
builtin-clean.c line 146
Quoted 6 lines
builtin-clean.c new file +6
-0+while (fgets (path,sizeof (path), cmd_fout) !=NULL ) { +struct stat st; +char *p; + p =strrchr (path,'\n' ); +if ( p !=NULL ) + *p ='\0' ;What happens in case p == NULL? It simply tries to remove the partial path?
If p == NULL then the path didn't have a EOL character. This shouldn't ever really happen since fgets() leaves the EOL character as part of the string, and it is processing the output of git-ls-files which will provide one path per line. If it does happen for some reason then either we will happily remove the file/directory, or if the path is garbage then we will simply fail to remove anything.
Quoted 8 lines
builtin-clean.c line 174
Quoted 4 lines
builtin-clean.c new file +4
-0+fclose (cmd_fout); +finish_command (&cmd); +if (!ignored && !access (git_path ("info/exclude" ),F_OK )) +free (buf);There is a race condition here of the value of
access()changes between the two calls. Not one likely to trigger but it should be easy to avoid alltogether.
Yes, though unlikely I agree this wasn't very smart in the first place so I fixed it and will send reworked patch soon.
--
Shawn
PATCH 1/1 Make git-clean a builtin
This replaces git-clean.sh with builtin-clean.c, and moves git-clean.sh to the examples.
---
Reworked patch with fixes to Frank's suggestions.
Makefile | 3 +- builtin-clean.c | 177 +++++++++++++++++++++++++ builtin.h | 1 + git-clean.sh => contrib/examples/git-clean.sh | 0 git.c | 1 + 5 files changed, 181 insertions(+), 1 deletions(-) create mode 100644 builtin-clean.c rename git-clean.sh => contrib/examples/git-clean.sh (100%)
Makefile +2 -1
@@ -206,7 +206,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-fetch.sh \ git-ls-remote.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
@@ -327,6 +327,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 \
builtin-clean.c new file +177 -0
177 lines not on this page · raw message
builtin.h +1 -0
@@ -23,6 +23,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);
contrib/examples/git-clean.sh renamed from git-clean.sh +0 -0
git.c +1 -0
@@ -320,6 +320,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 }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP },
Signature · 1 line
1.5.3.GIT
Hi,
Quoted 1 line · Shawn Bohrer
+static int
remove_directory(const char *path)
Please use remove_directory_recursively(), introduced in commit 7155b727c9baae9ef6d7829370aefc09c4ab64e2 to 'next'.
I have not looked at the rest of your patch yet.
Ciao,
Dscho
PATCH 1/1 Make git-clean a builtin
This replaces git-clean.sh with builtin-clean.c, and moves git-clean.sh to the examples.
---
Rewritten to use remove_directory_recursively() per Dscho's suggestion. Patch is now based ontop of 'next'.
Makefile | 3 +- builtin-clean.c | 161 +++++++++++++++++++++++++ builtin.h | 1 + git-clean.sh => contrib/examples/git-clean.sh | 0 git.c | 1 + 5 files changed, 165 insertions(+), 1 deletions(-) create mode 100644 builtin-clean.c rename git-clean.sh => contrib/examples/git-clean.sh (100%)
Makefile +2 -1
@@ -206,7 +206,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-ls-remote.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \
@@ -324,6 +324,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 \
builtin-clean.c new file +161 -0
161 lines not on this page · raw message
builtin.h +1 -0
@@ -23,6 +23,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);
contrib/examples/git-clean.sh renamed from git-clean.sh +0 -0
git.c +1 -0
@@ -320,6 +320,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 }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config }, { "count-objects", cmd_count_objects, RUN_SETUP },
Signature · 1 line
1.5.3.GIT
Hi,
builtin-clean.c line 83
Quoted 2 lines · Shawn Bohrer
builtin-clean.c new file +2
-0+if (ignored && ignored_only) +usage (builtin_clean_usage);
Maybe a helpful message in that case, too? (It is not apparent from the usage what the user has done wrong here.)
builtin-clean.c line 87
Quoted 3 lines
builtin-clean.c new file +3
-0+if (disabled) { +die ("clean.requireForce set and -n or -f not given; refusing to clean" ); + }
Please lose the curly brackets here. They are absolutely useless, and the rest of the git source code avoids unnecessary curly brackets.
builtin-clean.c line 93
Quoted 6 lines
builtin-clean.c new file +5
-0+/* Paths (argc - i) + 8 (Possible arguments)*/ + argv_ls_files =xmalloc ((argc - i +8 ) *sizeof (const char *)); + argv_ls_files[0 ] ="ls-files" ; + argv_ls_files[1 ] ="--others" ; + argv_ls_files[2 ] ="--directory" ;
As Linus already noted, it is probably easy, and much more efficient, to call read_directory() here. The best example how to use read_directory() is... builtin-ls-files.c.
git.c static void handle_internal_command(int argc, const char **argv) · line 323
Quoted 9 lines
git.c +1
-0@@ -320,6 +320,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 },
You definitely want to have NEED_WORK_TREE here, too.
Ciao,
Dscho
Quoted 3 lines · Shawn Bohrer
This replaces
git-clean.shwithbuiltin-clean.c, and movesgit-clean.shto the examples.
This looks better, but I think you'd be even better off actually using the "read_directory()" interface directly, instead of exec'ing off "git ls-files" and parsing the line output.
I also would still worry a bit about 'chdir(x)' and 'chdir("..")', because quite frankly, they are *not* mirrors of each other (think symlinks, but also error behaviour due to directories that might be non-executable). Now, admittedly, if a directory isn't executable, I can imagine other git things having problems (anybody want to test?), but that whole pattern is just very fragile and not very reliable.
Linus
Thanks for the input.
Quoted 3 lines · Linus Torvalds
This looks better, but I think you'd be even better off actually using the "
read_directory()" interface directly, instead of exec'ing off "git ls-files" and parsing the line output.
Perhaps, I'll take a look at how git-ls-files does it and see if I can do that directly. Since I'm new to git (and C) it will probably take me a while to re-implement though.
Quoted 6 lines
I also would still worry a bit about 'chdir(x)' and '
chdir("..")', because quite frankly, they are *not* mirrors of each other (think symlinks, but also error behaviour due to directories that might be non-executable). Now, admittedly, if a directory isn't executable, I can imagine other git things having problems (anybody want to test?), but that whole pattern is just very fragile and not very reliable.
Yes it does seem fragile, but 'chdir("-")' doesn't work in C and I couldn't find any equivalents. I actually did think about symlinks, and my code does do the right thing since I test if it is a directory before doing the 'chdir(x)'. Symlinks are therefore treated as normal files and removed.
I did not think about non-executable directories, and you are correct that my code will fail to remove a directory if it is non-executable. I also tested a git-ls-files with non-executable directories, and it will fail to show you any files that are more than one level deep for example:
|-- docs | |-- contributing | | `-- patches.txt | `-- manual.txt
If docs is non-executable it will only return 'docs/manual.txt'
--
Shawn
Quoted 18 lines · Shawn Bohrer
Thanks for the input.
Quoted 3 lines · Linus Torvalds
This looks better, but I think you'd be even better off actually using the "
read_directory()" interface directly, instead of exec'ing off "git ls-files" and parsing the line output.Perhaps, I'll take a look at how git-ls-files does it and see if I can do that directly. Since I'm new to git (and C) it will probably take me a while to re-implement though.
Quoted 6 lines
I also would still worry a bit about 'chdir(x)' and '
chdir("..")', because quite frankly, they are *not* mirrors of each other (think symlinks, but also error behaviour due to directories that might be non-executable). Now, admittedly, if a directory isn't executable, I can imagine other git things having problems (anybody want to test?), but that whole pattern is just very fragile and not very reliable.
Quoted 5 lines
Yes it does seem fragile, but '
chdir("-")' doesn't work in C and I couldn't find any equivalents. I actually did think about symlinks, and my code does do the right thing since I test if it is a directory before doing the 'chdir(x)'. Symlinks are therefore treated as normal files and removed.
'chdir -' is just supported by the shell, and the C interface could use chdir(OLDPWD).
builtin-clean.c line 49
Quoted 6 lines · Shawn Bohrer
builtin-clean.c new file +4
-0+static int remove_directory (const char *path) +{ +chdir (path); +chdir (".." );
This doesn't always put you back where you started, due to symlinks. For example:
cat >foo.c <<'EOF'
int main(int argc, char **argv) {
chdir(argv[1]);
chdir("..");
execlp("ls", 0);
}
EOF
gcc -o foo foo.c
ln -s /tmp sub
./foo sub
will show that you end up in the root directory. Something like this is more robust:
fd = open(".", O_RDONLY);
chdir(path);
...
fchdir(fd);In general, you shouldn't end up there because you don't actually recurse for symlinks, but there is a race condition (and losing it means you start recursively removing unintended directories -- oops).
On top of which, this line from the same function isn't very portable:
+ if (dir->d_type == DT_DIR)
since POSIX specifies nothing but dir->d_name (Solaris, for example, doesn't define d_type). You need to stat the file.
All of that being said, I think a lot of this is already done in dir.[ch]. At the very least, you should be able to use remove_dir_recursively, and for bonus points you can get rid of the start_command call to ls-files by just walking the dir tree yourself. I don't know if the latter is required, but it's nice when the C-ification actually cleans up a bit and uses the internal C interfaces (which are more efficient and often more clear to read) rather than just converting shell to C.
-Peff
Quoted 3 lines · Jeff King
This doesn't always put you back where you started, due to symlinks. For example:
[and other complaints]
Oops, didn't see that there was another thread for the reworked patch. My comments still stand, but it looks like others have weighed in as well. Sorry.
-Peff
Quoted 5 lines · Jeff King
fd = open(".", O_RDONLY);
chdir(path);
...
fchdir(fd);fchdir() is not portable.
I think it would be better to not chdir() at all. Yes, that means having to prepend the prefix to the names, but that is what git generally does (for that - and other - reasons).
Linus
Quoted 5 lines · Linus Torvalds
fchdir()is not portable.I think it would be better to not
chdir()at all. Yes, that means having to prepend the prefix to the names, but that is what git generally does (for that - and other - reasons).
I was using the "even Solaris has it!" test, but yes, it's not POSIX. I don't know how common it actually is (for curiosity's sake, do you know of a common platform that lacks it?). But I do agree that just building up the path and avoiding the chdir at all is simple and portable.
-Peff
Quoted 7 lines · Jeff King
Quoted 1 line · Linus Torvalds
fchdir()is not portable.I was using the "even Solaris has it!" test, but yes, it's not POSIX. I don't know how common it actually is (for curiosity's sake, do you know of a common platform that lacks it?).
Windows. ;)
-- Hannes
Quoted 10 lines · Johannes Sixt
Quoted 7 lines · Jeff King
Quoted 1 line · Linus Torvalds
fchdir()is not portable.I was using the "even Solaris has it!" test, but yes, it's not POSIX. I don't know how common it actually is (for curiosity's sake, do you know of a common platform that lacks it?).
Windows. ;)
Not just windows. Almost no "older" Unix has it. Including older versions of Linux.
Linus