1/* 2 * "git rm" builtin command 3 * 4 * Copyright (C) Linus Torvalds 2006 5 */ 6#include"cache.h" 7#include"builtin.h" 8#include"dir.h" 9#include"cache-tree.h" 10#include"tree-walk.h" 11#include"parse-options.h" 12#include"submodule.h" 13 14static const char*const builtin_rm_usage[] = { 15N_("git rm [options] [--] <file>..."), 16 NULL 17}; 18 19static struct{ 20int nr, alloc; 21struct{ 22const char*name; 23char is_submodule; 24} *entry; 25} list; 26 27static intget_ours_cache_pos(const char*path,int pos) 28{ 29int i = -pos -1; 30 31while((i < active_nr) && !strcmp(active_cache[i]->name, path)) { 32if(ce_stage(active_cache[i]) ==2) 33return i; 34 i++; 35} 36return-1; 37} 38 39static intcheck_submodules_use_gitfiles(void) 40{ 41int i; 42int errs =0; 43 44for(i =0; i < list.nr; i++) { 45const char*name = list.entry[i].name; 46int pos; 47struct cache_entry *ce; 48struct stat st; 49 50 pos =cache_name_pos(name,strlen(name)); 51if(pos <0) { 52 pos =get_ours_cache_pos(name, pos); 53if(pos <0) 54continue; 55} 56 ce = active_cache[pos]; 57 58if(!S_ISGITLINK(ce->ce_mode) || 59(lstat(ce->name, &st) <0) || 60is_empty_dir(name)) 61continue; 62 63if(!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 70return errs; 71} 72 73static intcheck_local_mod(unsigned char*head,int index_only) 74{ 75/* 76 * Items in list are already sorted in the cache order, 77 * so we could do this a lot more efficiently by using 78 * tree_desc based traversal if we wanted to, but I am 79 * lazy, and who cares if removal of files is a tad 80 * slower than the theoretical maximum speed? 81 */ 82int i, no_head; 83int errs =0; 84 85 no_head =is_null_sha1(head); 86for(i =0; i < list.nr; i++) { 87struct stat st; 88int pos; 89struct cache_entry *ce; 90const char*name = list.entry[i].name; 91unsigned char sha1[20]; 92unsigned mode; 93int local_changes =0; 94int staged_changes =0; 95 96 pos =cache_name_pos(name,strlen(name)); 97if(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); 103if(pos <0) 104continue; 105 106if(!S_ISGITLINK(active_cache[pos]->ce_mode) || 107is_empty_dir(name)) 108continue; 109} 110 ce = active_cache[pos]; 111 112if(lstat(ce->name, &st) <0) { 113if(errno != ENOENT && errno != ENOTDIR) 114warning("'%s':%s", ce->name,strerror(errno)); 115/* It already vanished from the working tree */ 116continue; 117} 118else if(S_ISDIR(st.st_mode)) { 119/* if a file was removed and it is now a 120 * directory, that is the same as ENOENT as 121 * far as git is concerned; we do not track 122 * directories unless they are submodules. 123 */ 124if(!S_ISGITLINK(ce->ce_mode)) 125continue; 126} 127 128/* 129 * "rm" of a path that has changes need to be treated 130 * carefully not to allow losing local changes 131 * accidentally. A local change could be (1) file in 132 * work tree is different since the index; and/or (2) 133 * the user staged a content that is different from 134 * the current commit in the index. 135 * 136 * In such a case, you would need to --force the 137 * removal. However, "rm --cached" (remove only from 138 * the index) is safe if the index matches the file in 139 * the work tree or the HEAD commit, as it means that 140 * the content being removed is available elsewhere. 141 */ 142 143/* 144 * Is the index different from the file in the work tree? 145 * If it's a submodule, is its work tree modified? 146 */ 147if(ce_match_stat(ce, &st,0) || 148(S_ISGITLINK(ce->ce_mode) && 149!ok_to_remove_submodule(ce->name))) 150 local_changes =1; 151 152/* 153 * Is the index different from the HEAD commit? By 154 * definition, before the very initial commit, 155 * anything staged in the index is treated by the same 156 * way as changed from the HEAD. 157 */ 158if(no_head 159||get_tree_entry(head, name, sha1, &mode) 160|| ce->ce_mode !=create_ce_mode(mode) 161||hashcmp(ce->sha1, sha1)) 162 staged_changes =1; 163 164/* 165 * If the index does not match the file in the work 166 * tree and if it does not match the HEAD commit 167 * either, (1) "git rm" without --cached definitely 168 * will lose information; (2) "git rm --cached" will 169 * lose information unless it is about removing an 170 * "intent to add" entry. 171 */ 172if(local_changes && staged_changes) { 173if(!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) 174 errs =error(_("'%s' has staged content different " 175"from both the file and the HEAD\n" 176"(use -f to force removal)"), name); 177} 178else if(!index_only) { 179if(staged_changes) 180 errs =error(_("'%s' has changes staged in the index\n" 181"(use --cached to keep the file, " 182"or -f to force removal)"), name); 183if(local_changes) { 184if(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} 195} 196} 197return errs; 198} 199 200static struct lock_file lock_file; 201 202static int show_only =0, force =0, index_only =0, recursive =0, quiet =0; 203static int ignore_unmatch =0; 204 205static struct option builtin_rm_options[] = { 206OPT__DRY_RUN(&show_only,N_("dry run")), 207OPT__QUIET(&quiet,N_("do not list removed files")), 208OPT_BOOLEAN(0,"cached", &index_only,N_("only remove from the index")), 209OPT__FORCE(&force,N_("override the up-to-date check")), 210OPT_BOOLEAN('r', NULL, &recursive,N_("allow recursive removal")), 211OPT_BOOLEAN(0,"ignore-unmatch", &ignore_unmatch, 212N_("exit with a zero status even if nothing matched")), 213OPT_END(), 214}; 215 216intcmd_rm(int argc,const char**argv,const char*prefix) 217{ 218int i, newfd; 219const char**pathspec; 220char*seen; 221 222git_config(git_default_config, NULL); 223 224 argc =parse_options(argc, argv, prefix, builtin_rm_options, 225 builtin_rm_usage,0); 226if(!argc) 227usage_with_options(builtin_rm_usage, builtin_rm_options); 228 229if(!index_only) 230setup_work_tree(); 231 232 newfd =hold_locked_index(&lock_file,1); 233 234if(read_cache() <0) 235die(_("index file corrupt")); 236 237/* 238 * Drop trailing directory separators from directories so we'll find 239 * submodules in the index. 240 */ 241for(i =0; i < argc; i++) { 242size_t pathlen =strlen(argv[i]); 243if(pathlen &&is_dir_sep(argv[i][pathlen -1]) && 244is_directory(argv[i])) { 245do{ 246 pathlen--; 247}while(pathlen &&is_dir_sep(argv[i][pathlen -1])); 248 argv[i] =xmemdupz(argv[i], pathlen); 249} 250} 251 252 pathspec =get_pathspec(prefix, argv); 253refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); 254 255 seen = NULL; 256for(i =0; pathspec[i] ; i++) 257/* nothing */; 258 seen =xcalloc(i,1); 259 260for(i =0; i < active_nr; i++) { 261struct cache_entry *ce = active_cache[i]; 262if(!match_pathspec(pathspec, ce->name,ce_namelen(ce),0, seen)) 263continue; 264ALLOC_GROW(list.entry, list.nr +1, list.alloc); 265 list.entry[list.nr].name = ce->name; 266 list.entry[list.nr++].is_submodule =S_ISGITLINK(ce->ce_mode); 267} 268 269if(pathspec) { 270const char*match; 271int seen_any =0; 272for(i =0; (match = pathspec[i]) != NULL ; i++) { 273if(!seen[i]) { 274if(!ignore_unmatch) { 275die(_("pathspec '%s' did not match any files"), 276 match); 277} 278} 279else{ 280 seen_any =1; 281} 282if(!recursive && seen[i] == MATCHED_RECURSIVELY) 283die(_("not removing '%s' recursively without -r"), 284*match ? match :"."); 285} 286 287if(! seen_any) 288exit(0); 289} 290 291/* 292 * If not forced, the file, the index and the HEAD (if exists) 293 * must match; but the file can already been removed, since 294 * this sequence is a natural "novice" way: 295 * 296 * rm F; git rm F 297 * 298 * Further, if HEAD commit exists, "diff-index --cached" must 299 * report no changes unless forced. 300 */ 301if(!force) { 302unsigned char sha1[20]; 303if(get_sha1("HEAD", sha1)) 304hashclr(sha1); 305if(check_local_mod(sha1, index_only)) 306exit(1); 307}else if(!index_only) { 308if(check_submodules_use_gitfiles()) 309exit(1); 310} 311 312/* 313 * First remove the names from the index: we won't commit 314 * the index unless all of them succeed. 315 */ 316for(i =0; i < list.nr; i++) { 317const char*path = list.entry[i].name; 318if(!quiet) 319printf("rm '%s'\n", path); 320 321if(remove_file_from_cache(path)) 322die(_("git rm: unable to remove%s"), path); 323} 324 325if(show_only) 326return0; 327 328/* 329 * Then, unless we used "--cached", remove the filenames from 330 * the workspace. If we fail to remove the first one, we 331 * abort the "git rm" (but once we've successfully removed 332 * any file at all, we'll go ahead and commit to it all: 333 * by then we've already committed ourselves and can't fail 334 * in the middle) 335 */ 336if(!index_only) { 337int removed =0; 338for(i =0; i < list.nr; i++) { 339const char*path = list.entry[i].name; 340if(list.entry[i].is_submodule) { 341if(is_empty_dir(path)) { 342if(!rmdir(path)) { 343 removed =1; 344continue; 345} 346}else{ 347struct strbuf buf = STRBUF_INIT; 348strbuf_addstr(&buf, path); 349if(!remove_dir_recursively(&buf,0)) { 350 removed =1; 351strbuf_release(&buf); 352continue; 353} 354strbuf_release(&buf); 355/* Fallthrough and let remove_path() fail. */ 356} 357} 358if(!remove_path(path)) { 359 removed =1; 360continue; 361} 362if(!removed) 363die_errno("git rm: '%s'", path); 364} 365} 366 367if(active_cache_changed) { 368if(write_cache(newfd, active_cache, active_nr) || 369commit_locked_index(&lock_file)) 370die(_("Unable to write new index file")); 371} 372 373return0; 374}