9
9
#include "cache-tree.h"
10
10
#include "tree-walk.h"
11
11
#include "parse-options.h"
12
+ #include "submodule.h"
12
13
13
14
static const char * const builtin_rm_usage [] = {
14
15
N_ ("git rm [options] [--] <file>..." ),
@@ -17,9 +18,58 @@ static const char * const builtin_rm_usage[] = {
17
18
18
19
static struct {
19
20
int nr , alloc ;
20
- const char * * name ;
21
+ struct {
22
+ const char * name ;
23
+ char is_submodule ;
24
+ } * entry ;
21
25
} list ;
22
26
27
+ static int get_ours_cache_pos (const char * path , int pos )
28
+ {
29
+ int i = - pos - 1 ;
30
+
31
+ while ((i < active_nr ) && !strcmp (active_cache [i ]-> name , path )) {
32
+ if (ce_stage (active_cache [i ]) == 2 )
33
+ return i ;
34
+ i ++ ;
35
+ }
36
+ return -1 ;
37
+ }
38
+
39
+ static int check_submodules_use_gitfiles (void )
40
+ {
41
+ int i ;
42
+ int errs = 0 ;
43
+
44
+ for (i = 0 ; i < list .nr ; i ++ ) {
45
+ const char * name = list .entry [i ].name ;
46
+ int pos ;
47
+ struct cache_entry * ce ;
48
+ struct stat st ;
49
+
50
+ pos = cache_name_pos (name , strlen (name ));
51
+ if (pos < 0 ) {
52
+ pos = get_ours_cache_pos (name , pos );
53
+ if (pos < 0 )
54
+ continue ;
55
+ }
56
+ ce = active_cache [pos ];
57
+
58
+ if (!S_ISGITLINK (ce -> ce_mode ) ||
59
+ (lstat (ce -> name , & st ) < 0 ) ||
60
+ is_empty_dir (name ))
61
+ continue ;
62
+
63
+ if (!submodule_uses_gitfile (name ))
64
+ errs = error (_ ("submodule '%s' (or one of its nested "
65
+ "submodules) uses a .git directory\n"
66
+ "(use 'rm -rf' if you really want to remove "
67
+ "it including all of its history)" ), name );
68
+ }
69
+
70
+ return errs ;
71
+ }
72
+
23
73
static int check_local_mod (unsigned char * head , int index_only )
24
74
{
25
75
/*
@@ -37,15 +87,26 @@ static int check_local_mod(unsigned char *head, int index_only)
37
87
struct stat st ;
38
88
int pos ;
39
89
struct cache_entry * ce ;
40
- const char * name = list .name [i ];
90
+ const char * name = list .entry [i ]. name ;
41
91
unsigned char sha1 [20 ];
42
92
unsigned mode ;
43
93
int local_changes = 0 ;
44
94
int staged_changes = 0 ;
45
95
46
96
pos = cache_name_pos (name , strlen (name ));
47
- if (pos < 0 )
48
- continue ; /* removing unmerged entry */
97
+ if (pos < 0 ) {
98
+ /*
99
+ * Skip unmerged entries except for populated submodules
100
+ * that could lose history when removed.
101
+ */
102
+ pos = get_ours_cache_pos (name , pos );
103
+ if (pos < 0 )
104
+ continue ;
105
+
106
+ if (!S_ISGITLINK (active_cache [pos ]-> ce_mode ) ||
107
+ is_empty_dir (name ))
108
+ continue ;
109
+ }
49
110
ce = active_cache [pos ];
50
111
51
112
if (lstat (ce -> name , & st ) < 0 ) {
@@ -58,9 +119,10 @@ static int check_local_mod(unsigned char *head, int index_only)
58
119
/* if a file was removed and it is now a
59
120
* directory, that is the same as ENOENT as
60
121
* far as git is concerned; we do not track
61
- * directories.
122
+ * directories unless they are submodules .
62
123
*/
63
- continue ;
124
+ if (!S_ISGITLINK (ce -> ce_mode ))
125
+ continue ;
64
126
}
65
127
66
128
/*
@@ -80,8 +142,11 @@ static int check_local_mod(unsigned char *head, int index_only)
80
142
81
143
/*
82
144
* Is the index different from the file in the work tree?
145
+ * If it's a submodule, is its work tree modified?
83
146
*/
84
- if (ce_match_stat (ce , & st , 0 ))
147
+ if (ce_match_stat (ce , & st , 0 ) ||
148
+ (S_ISGITLINK (ce -> ce_mode ) &&
149
+ !ok_to_remove_submodule (ce -> name )))
85
150
local_changes = 1 ;
86
151
87
152
/*
@@ -115,10 +180,18 @@ static int check_local_mod(unsigned char *head, int index_only)
115
180
errs = error (_ ("'%s' has changes staged in the index\n"
116
181
"(use --cached to keep the file, "
117
182
"or -f to force removal)" ), name );
118
- if (local_changes )
119
- errs = error (_ ("'%s' has local modifications\n"
120
- "(use --cached to keep the file, "
121
- "or -f to force removal)" ), name );
183
+ if (local_changes ) {
184
+ if (S_ISGITLINK (ce -> ce_mode ) &&
185
+ !submodule_uses_gitfile (name )) {
186
+ errs = error (_ ("submodule '%s' (or one of its nested "
187
+ "submodules) uses a .git directory\n"
188
+ "(use 'rm -rf' if you really want to remove "
189
+ "it including all of its history)" ), name );
190
+ } else
191
+ errs = error (_ ("'%s' has local modifications\n"
192
+ "(use --cached to keep the file, "
193
+ "or -f to force removal)" ), name );
194
+ }
122
195
}
123
196
}
124
197
return errs ;
@@ -173,8 +246,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
173
246
struct cache_entry * ce = active_cache [i ];
174
247
if (!match_pathspec (pathspec , ce -> name , ce_namelen (ce ), 0 , seen ))
175
248
continue ;
176
- ALLOC_GROW (list .name , list .nr + 1 , list .alloc );
177
- list .name [list .nr ++ ] = ce -> name ;
249
+ ALLOC_GROW (list .entry , list .nr + 1 , list .alloc );
250
+ list .entry [list .nr ].name = ce -> name ;
251
+ list .entry [list .nr ++ ].is_submodule = S_ISGITLINK (ce -> ce_mode );
178
252
}
179
253
180
254
if (pathspec ) {
@@ -215,14 +289,17 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
215
289
hashclr (sha1 );
216
290
if (check_local_mod (sha1 , index_only ))
217
291
exit (1 );
292
+ } else if (!index_only ) {
293
+ if (check_submodules_use_gitfiles ())
294
+ exit (1 );
218
295
}
219
296
220
297
/*
221
298
* First remove the names from the index: we won't commit
222
299
* the index unless all of them succeed.
223
300
*/
224
301
for (i = 0 ; i < list .nr ; i ++ ) {
225
- const char * path = list .name [i ];
302
+ const char * path = list .entry [i ]. name ;
226
303
if (!quiet )
227
304
printf ("rm '%s'\n" , path );
228
305
@@ -244,7 +321,25 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
244
321
if (!index_only ) {
245
322
int removed = 0 ;
246
323
for (i = 0 ; i < list .nr ; i ++ ) {
247
- const char * path = list .name [i ];
324
+ const char * path = list .entry [i ].name ;
325
+ if (list .entry [i ].is_submodule ) {
326
+ if (is_empty_dir (path )) {
327
+ if (!rmdir (path )) {
328
+ removed = 1 ;
329
+ continue ;
330
+ }
331
+ } else {
332
+ struct strbuf buf = STRBUF_INIT ;
333
+ strbuf_addstr (& buf , path );
334
+ if (!remove_dir_recursively (& buf , 0 )) {
335
+ removed = 1 ;
336
+ strbuf_release (& buf );
337
+ continue ;
338
+ }
339
+ strbuf_release (& buf );
340
+ /* Fallthrough and let remove_path() fail. */
341
+ }
342
+ }
248
343
if (!remove_path (path )) {
249
344
removed = 1 ;
250
345
continue ;
0 commit comments