1010#include "cache.h"
1111#include "dir.h"
1212#include "parse-options.h"
13+ #include "refs.h"
1314#include "string-list.h"
1415#include "quote.h"
1516
@@ -20,6 +21,12 @@ static const char *const builtin_clean_usage[] = {
2021 NULL
2122};
2223
24+ static const char * msg_remove = N_ ("Removing %s\n" );
25+ static const char * msg_would_remove = N_ ("Would remove %s\n" );
26+ static const char * msg_skip_git_dir = N_ ("Skipping repository %s\n" );
27+ static const char * msg_would_skip_git_dir = N_ ("Would skip repository %s\n" );
28+ static const char * msg_warn_remove_failed = N_ ("failed to remove %s" );
29+
2330static int git_clean_config (const char * var , const char * value , void * cb )
2431{
2532 if (!strcmp (var , "clean.requireforce" ))
@@ -34,11 +41,112 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset)
3441 return 0 ;
3542}
3643
44+ static int remove_dirs (struct strbuf * path , const char * prefix , int force_flag ,
45+ int dry_run , int quiet , int * dir_gone )
46+ {
47+ DIR * dir ;
48+ struct strbuf quoted = STRBUF_INIT ;
49+ struct dirent * e ;
50+ int res = 0 , ret = 0 , gone = 1 , original_len = path -> len , len , i ;
51+ unsigned char submodule_head [20 ];
52+ struct string_list dels = STRING_LIST_INIT_DUP ;
53+
54+ * dir_gone = 1 ;
55+
56+ if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT ) &&
57+ !resolve_gitlink_ref (path -> buf , "HEAD" , submodule_head )) {
58+ if (!quiet ) {
59+ quote_path_relative (path -> buf , strlen (path -> buf ), & quoted , prefix );
60+ printf (dry_run ? _ (msg_would_skip_git_dir ) : _ (msg_skip_git_dir ),
61+ quoted .buf );
62+ }
63+
64+ * dir_gone = 0 ;
65+ return 0 ;
66+ }
67+
68+ dir = opendir (path -> buf );
69+ if (!dir ) {
70+ /* an empty dir could be removed even if it is unreadble */
71+ res = dry_run ? 0 : rmdir (path -> buf );
72+ if (res ) {
73+ quote_path_relative (path -> buf , strlen (path -> buf ), & quoted , prefix );
74+ warning (_ (msg_warn_remove_failed ), quoted .buf );
75+ * dir_gone = 0 ;
76+ }
77+ return res ;
78+ }
79+
80+ if (path -> buf [original_len - 1 ] != '/' )
81+ strbuf_addch (path , '/' );
82+
83+ len = path -> len ;
84+ while ((e = readdir (dir )) != NULL ) {
85+ struct stat st ;
86+ if (is_dot_or_dotdot (e -> d_name ))
87+ continue ;
88+
89+ strbuf_setlen (path , len );
90+ strbuf_addstr (path , e -> d_name );
91+ if (lstat (path -> buf , & st ))
92+ ; /* fall thru */
93+ else if (S_ISDIR (st .st_mode )) {
94+ if (remove_dirs (path , prefix , force_flag , dry_run , quiet , & gone ))
95+ ret = 1 ;
96+ if (gone ) {
97+ quote_path_relative (path -> buf , strlen (path -> buf ), & quoted , prefix );
98+ string_list_append (& dels , quoted .buf );
99+ } else
100+ * dir_gone = 0 ;
101+ continue ;
102+ } else {
103+ res = dry_run ? 0 : unlink (path -> buf );
104+ if (!res ) {
105+ quote_path_relative (path -> buf , strlen (path -> buf ), & quoted , prefix );
106+ string_list_append (& dels , quoted .buf );
107+ } else {
108+ quote_path_relative (path -> buf , strlen (path -> buf ), & quoted , prefix );
109+ warning (_ (msg_warn_remove_failed ), quoted .buf );
110+ * dir_gone = 0 ;
111+ ret = 1 ;
112+ }
113+ continue ;
114+ }
115+
116+ /* path too long, stat fails, or non-directory still exists */
117+ * dir_gone = 0 ;
118+ ret = 1 ;
119+ break ;
120+ }
121+ closedir (dir );
122+
123+ strbuf_setlen (path , original_len );
124+
125+ if (* dir_gone ) {
126+ res = dry_run ? 0 : rmdir (path -> buf );
127+ if (!res )
128+ * dir_gone = 1 ;
129+ else {
130+ quote_path_relative (path -> buf , strlen (path -> buf ), & quoted , prefix );
131+ warning (_ (msg_warn_remove_failed ), quoted .buf );
132+ * dir_gone = 0 ;
133+ ret = 1 ;
134+ }
135+ }
136+
137+ if (!* dir_gone && !quiet ) {
138+ for (i = 0 ; i < dels .nr ; i ++ )
139+ printf (dry_run ? _ (msg_would_remove ) : _ (msg_remove ), dels .items [i ].string );
140+ }
141+ string_list_clear (& dels , 0 );
142+ return ret ;
143+ }
144+
37145int cmd_clean (int argc , const char * * argv , const char * prefix )
38146{
39- int i ;
40- int show_only = 0 , remove_directories = 0 , quiet = 0 , ignored = 0 ;
41- int ignored_only = 0 , config_set = 0 , errors = 0 ;
147+ int i , res ;
148+ int dry_run = 0 , remove_directories = 0 , quiet = 0 , ignored = 0 ;
149+ int ignored_only = 0 , config_set = 0 , errors = 0 , gone = 1 ;
42150 int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT ;
43151 struct strbuf directory = STRBUF_INIT ;
44152 struct dir_struct dir ;
@@ -49,7 +157,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
49157 char * seen = NULL ;
50158 struct option options [] = {
51159 OPT__QUIET (& quiet , N_ ("do not print names of files removed" )),
52- OPT__DRY_RUN (& show_only , N_ ("dry run" )),
160+ OPT__DRY_RUN (& dry_run , N_ ("dry run" )),
53161 OPT__FORCE (& force , N_ ("force" )),
54162 OPT_BOOLEAN ('d' , NULL , & remove_directories ,
55163 N_ ("remove whole directories" )),
@@ -77,7 +185,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
77185 if (ignored && ignored_only )
78186 die (_ ("-x and -X cannot be used together" ));
79187
80- if (!show_only && !force ) {
188+ if (!dry_run && !force ) {
81189 if (config_set )
82190 die (_ ("clean.requireForce set to true and neither -n nor -f given; "
83191 "refusing to clean" ));
@@ -149,38 +257,26 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
149257
150258 if (S_ISDIR (st .st_mode )) {
151259 strbuf_addstr (& directory , ent -> name );
152- qname = quote_path_relative (directory .buf , directory .len , & buf , prefix );
153- if (show_only && (remove_directories ||
154- (matches == MATCHED_EXACTLY ))) {
155- printf (_ ("Would remove %s\n" ), qname );
156- } else if (remove_directories ||
157- (matches == MATCHED_EXACTLY )) {
158- if (!quiet )
159- printf (_ ("Removing %s\n" ), qname );
160- if (remove_dir_recursively (& directory ,
161- rm_flags ) != 0 ) {
162- warning (_ ("failed to remove %s" ), qname );
260+ if (remove_directories || (matches == MATCHED_EXACTLY )) {
261+ if (remove_dirs (& directory , prefix , rm_flags , dry_run , quiet , & gone ))
163262 errors ++ ;
263+ if (gone && !quiet ) {
264+ qname = quote_path_relative (directory .buf , directory .len , & buf , prefix );
265+ printf (dry_run ? _ (msg_would_remove ) : _ (msg_remove ), qname );
164266 }
165- } else if (show_only ) {
166- printf (_ ("Would not remove %s\n" ), qname );
167- } else {
168- printf (_ ("Not removing %s\n" ), qname );
169267 }
170268 strbuf_reset (& directory );
171269 } else {
172270 if (pathspec && !matches )
173271 continue ;
174- qname = quote_path_relative (ent -> name , -1 , & buf , prefix );
175- if (show_only ) {
176- printf (_ ("Would remove %s\n" ), qname );
177- continue ;
178- } else if (!quiet ) {
179- printf (_ ("Removing %s\n" ), qname );
180- }
181- if (unlink (ent -> name ) != 0 ) {
182- warning (_ ("failed to remove %s" ), qname );
272+ res = dry_run ? 0 : unlink (ent -> name );
273+ if (res ) {
274+ qname = quote_path_relative (ent -> name , -1 , & buf , prefix );
275+ warning (_ (msg_warn_remove_failed ), qname );
183276 errors ++ ;
277+ } else if (!quiet ) {
278+ qname = quote_path_relative (ent -> name , -1 , & buf , prefix );
279+ printf (dry_run ? _ (msg_would_remove ) : _ (msg_remove ), qname );
184280 }
185281 }
186282 }
0 commit comments