Git fork
at reftables-rust 447 lines 13 kB view raw
1/* 2 * "git rm" builtin command 3 * 4 * Copyright (C) Linus Torvalds 2006 5 */ 6 7#define USE_THE_REPOSITORY_VARIABLE 8 9#include "builtin.h" 10#include "advice.h" 11#include "config.h" 12#include "environment.h" 13#include "lockfile.h" 14#include "dir.h" 15#include "gettext.h" 16#include "hash.h" 17#include "tree-walk.h" 18#include "object-name.h" 19#include "parse-options.h" 20#include "read-cache.h" 21 22#include "string-list.h" 23#include "setup.h" 24#include "sparse-index.h" 25#include "submodule.h" 26#include "pathspec.h" 27 28static const char * const builtin_rm_usage[] = { 29 N_("git rm [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch]\n" 30 " [--quiet] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" 31 " [--] [<pathspec>...]"), 32 NULL 33}; 34 35static struct { 36 int nr, alloc; 37 struct { 38 const char *name; 39 char is_submodule; 40 } *entry; 41} list; 42 43static int get_ours_cache_pos(const char *path, unsigned int pos) 44{ 45 while ((pos < the_repository->index->cache_nr) && !strcmp(the_repository->index->cache[pos]->name, path)) { 46 if (ce_stage(the_repository->index->cache[pos]) == 2) 47 return pos; 48 pos++; 49 } 50 return -1; 51} 52 53static void print_error_files(struct string_list *files_list, 54 const char *main_msg, 55 const char *hints_msg, 56 int *errs) 57{ 58 if (files_list->nr) { 59 unsigned int i; 60 struct strbuf err_msg = STRBUF_INIT; 61 62 strbuf_addstr(&err_msg, main_msg); 63 for (i = 0; i < files_list->nr; i++) 64 strbuf_addf(&err_msg, 65 "\n %s", 66 files_list->items[i].string); 67 if (advice_enabled(ADVICE_RM_HINTS)) 68 strbuf_addstr(&err_msg, hints_msg); 69 *errs = error("%s", err_msg.buf); 70 strbuf_release(&err_msg); 71 } 72} 73 74static void submodules_absorb_gitdir_if_needed(void) 75{ 76 int i; 77 for (i = 0; i < list.nr; i++) { 78 const char *name = list.entry[i].name; 79 int pos; 80 const struct cache_entry *ce; 81 82 pos = index_name_pos(the_repository->index, name, strlen(name)); 83 if (pos < 0) { 84 pos = get_ours_cache_pos(name, -pos - 1); 85 if (pos < 0) 86 continue; 87 } 88 ce = the_repository->index->cache[pos]; 89 90 if (!S_ISGITLINK(ce->ce_mode) || 91 !file_exists(ce->name) || 92 is_empty_dir(name)) 93 continue; 94 95 if (!submodule_uses_gitfile(name)) 96 absorb_git_dir_into_superproject(name, NULL); 97 } 98} 99 100static int check_local_mod(struct object_id *head, int index_only) 101{ 102 /* 103 * Items in list are already sorted in the cache order, 104 * so we could do this a lot more efficiently by using 105 * tree_desc based traversal if we wanted to, but I am 106 * lazy, and who cares if removal of files is a tad 107 * slower than the theoretical maximum speed? 108 */ 109 int i, no_head; 110 int errs = 0; 111 struct string_list files_staged = STRING_LIST_INIT_NODUP; 112 struct string_list files_cached = STRING_LIST_INIT_NODUP; 113 struct string_list files_local = STRING_LIST_INIT_NODUP; 114 115 no_head = is_null_oid(head); 116 for (i = 0; i < list.nr; i++) { 117 struct stat st; 118 int pos; 119 const struct cache_entry *ce; 120 const char *name = list.entry[i].name; 121 struct object_id oid; 122 unsigned short mode; 123 int local_changes = 0; 124 int staged_changes = 0; 125 126 pos = index_name_pos(the_repository->index, name, strlen(name)); 127 if (pos < 0) { 128 /* 129 * Skip unmerged entries except for populated submodules 130 * that could lose history when removed. 131 */ 132 pos = get_ours_cache_pos(name, -pos - 1); 133 if (pos < 0) 134 continue; 135 136 if (!S_ISGITLINK(the_repository->index->cache[pos]->ce_mode) || 137 is_empty_dir(name)) 138 continue; 139 } 140 ce = the_repository->index->cache[pos]; 141 142 if (lstat(ce->name, &st) < 0) { 143 if (!is_missing_file_error(errno)) 144 warning_errno(_("failed to stat '%s'"), ce->name); 145 /* It already vanished from the working tree */ 146 continue; 147 } 148 else if (S_ISDIR(st.st_mode)) { 149 /* if a file was removed and it is now a 150 * directory, that is the same as ENOENT as 151 * far as git is concerned; we do not track 152 * directories unless they are submodules. 153 */ 154 if (!S_ISGITLINK(ce->ce_mode)) 155 continue; 156 } 157 158 /* 159 * "rm" of a path that has changes need to be treated 160 * carefully not to allow losing local changes 161 * accidentally. A local change could be (1) file in 162 * work tree is different since the index; and/or (2) 163 * the user staged a content that is different from 164 * the current commit in the index. 165 * 166 * In such a case, you would need to --force the 167 * removal. However, "rm --cached" (remove only from 168 * the index) is safe if the index matches the file in 169 * the work tree or the HEAD commit, as it means that 170 * the content being removed is available elsewhere. 171 */ 172 173 /* 174 * Is the index different from the file in the work tree? 175 * If it's a submodule, is its work tree modified? 176 */ 177 if (ie_match_stat(the_repository->index, ce, &st, 0) || 178 (S_ISGITLINK(ce->ce_mode) && 179 bad_to_remove_submodule(ce->name, 180 SUBMODULE_REMOVAL_DIE_ON_ERROR | 181 SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))) 182 local_changes = 1; 183 184 /* 185 * Is the index different from the HEAD commit? By 186 * definition, before the very initial commit, 187 * anything staged in the index is treated by the same 188 * way as changed from the HEAD. 189 */ 190 if (no_head 191 || get_tree_entry(the_repository, head, name, &oid, &mode) 192 || ce->ce_mode != create_ce_mode(mode) 193 || !oideq(&ce->oid, &oid)) 194 staged_changes = 1; 195 196 /* 197 * If the index does not match the file in the work 198 * tree and if it does not match the HEAD commit 199 * either, (1) "git rm" without --cached definitely 200 * will lose information; (2) "git rm --cached" will 201 * lose information unless it is about removing an 202 * "intent to add" entry. 203 */ 204 if (local_changes && staged_changes) { 205 if (!index_only || !ce_intent_to_add(ce)) 206 string_list_append(&files_staged, name); 207 } 208 else if (!index_only) { 209 if (staged_changes) 210 string_list_append(&files_cached, name); 211 if (local_changes) 212 string_list_append(&files_local, name); 213 } 214 } 215 print_error_files(&files_staged, 216 Q_("the following file has staged content different " 217 "from both the\nfile and the HEAD:", 218 "the following files have staged content different" 219 " from both the\nfile and the HEAD:", 220 files_staged.nr), 221 _("\n(use -f to force removal)"), 222 &errs); 223 string_list_clear(&files_staged, 0); 224 print_error_files(&files_cached, 225 Q_("the following file has changes " 226 "staged in the index:", 227 "the following files have changes " 228 "staged in the index:", files_cached.nr), 229 _("\n(use --cached to keep the file," 230 " or -f to force removal)"), 231 &errs); 232 string_list_clear(&files_cached, 0); 233 234 print_error_files(&files_local, 235 Q_("the following file has local modifications:", 236 "the following files have local modifications:", 237 files_local.nr), 238 _("\n(use --cached to keep the file," 239 " or -f to force removal)"), 240 &errs); 241 string_list_clear(&files_local, 0); 242 243 return errs; 244} 245 246static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; 247static int ignore_unmatch = 0, pathspec_file_nul; 248static int include_sparse; 249static char *pathspec_from_file; 250 251static struct option builtin_rm_options[] = { 252 OPT__DRY_RUN(&show_only, N_("dry run")), 253 OPT__QUIET(&quiet, N_("do not list removed files")), 254 OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")), 255 OPT__FORCE(&force, N_("override the up-to-date check"), PARSE_OPT_NOCOMPLETE), 256 OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), 257 OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, 258 N_("exit with a zero status even if nothing matched")), 259 OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")), 260 OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), 261 OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), 262 OPT_END(), 263}; 264 265int cmd_rm(int argc, 266 const char **argv, 267 const char *prefix, 268 struct repository *repo UNUSED) 269{ 270 struct lock_file lock_file = LOCK_INIT; 271 int i, ret = 0; 272 struct pathspec pathspec; 273 char *seen; 274 275 repo_config(the_repository, git_default_config, NULL); 276 277 argc = parse_options(argc, argv, prefix, builtin_rm_options, 278 builtin_rm_usage, 0); 279 280 parse_pathspec(&pathspec, 0, 281 PATHSPEC_PREFER_CWD, 282 prefix, argv); 283 284 if (pathspec_from_file) { 285 if (pathspec.nr) 286 die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); 287 288 parse_pathspec_file(&pathspec, 0, 289 PATHSPEC_PREFER_CWD, 290 prefix, pathspec_from_file, pathspec_file_nul); 291 } else if (pathspec_file_nul) { 292 die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); 293 } 294 295 if (!pathspec.nr) 296 die(_("No pathspec was given. Which files should I remove?")); 297 298 if (!index_only) 299 setup_work_tree(); 300 301 prepare_repo_settings(the_repository); 302 the_repository->settings.command_requires_full_index = 0; 303 repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); 304 305 if (repo_read_index(the_repository) < 0) 306 die(_("index file corrupt")); 307 308 refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL); 309 310 seen = xcalloc(pathspec.nr, 1); 311 312 if (pathspec_needs_expanded_index(the_repository->index, &pathspec)) 313 ensure_full_index(the_repository->index); 314 315 for (unsigned int i = 0; i < the_repository->index->cache_nr; i++) { 316 const struct cache_entry *ce = the_repository->index->cache[i]; 317 318 if (!include_sparse && 319 (ce_skip_worktree(ce) || 320 !path_in_sparse_checkout(ce->name, the_repository->index))) 321 continue; 322 if (!ce_path_match(the_repository->index, ce, &pathspec, seen)) 323 continue; 324 ALLOC_GROW(list.entry, list.nr + 1, list.alloc); 325 list.entry[list.nr].name = xstrdup(ce->name); 326 list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode); 327 if (list.entry[list.nr++].is_submodule && 328 !is_staging_gitmodules_ok(the_repository->index)) 329 die(_("please stage your changes to .gitmodules or stash them to proceed")); 330 } 331 332 if (pathspec.nr) { 333 const char *original; 334 int seen_any = 0; 335 char *skip_worktree_seen = NULL; 336 struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; 337 338 for (i = 0; i < pathspec.nr; i++) { 339 original = pathspec.items[i].original; 340 if (seen[i]) 341 seen_any = 1; 342 else if (ignore_unmatch) 343 continue; 344 else if (!include_sparse && 345 matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) 346 string_list_append(&only_match_skip_worktree, original); 347 else 348 die(_("pathspec '%s' did not match any files"), original); 349 350 if (!recursive && seen[i] == MATCHED_RECURSIVELY) 351 die(_("not removing '%s' recursively without -r"), 352 *original ? original : "."); 353 } 354 355 if (only_match_skip_worktree.nr) { 356 advise_on_updating_sparse_paths(&only_match_skip_worktree); 357 ret = 1; 358 } 359 free(skip_worktree_seen); 360 string_list_clear(&only_match_skip_worktree, 0); 361 362 if (!seen_any) 363 exit(ret); 364 } 365 clear_pathspec(&pathspec); 366 free(seen); 367 368 if (!index_only) 369 submodules_absorb_gitdir_if_needed(); 370 371 /* 372 * If not forced, the file, the index and the HEAD (if exists) 373 * must match; but the file can already been removed, since 374 * this sequence is a natural "novice" way: 375 * 376 * rm F; git rm F 377 * 378 * Further, if HEAD commit exists, "diff-index --cached" must 379 * report no changes unless forced. 380 */ 381 if (!force) { 382 struct object_id oid; 383 if (repo_get_oid(the_repository, "HEAD", &oid)) 384 oidclr(&oid, the_repository->hash_algo); 385 if (check_local_mod(&oid, index_only)) 386 exit(1); 387 } 388 389 /* 390 * First remove the names from the index: we won't commit 391 * the index unless all of them succeed. 392 */ 393 for (i = 0; i < list.nr; i++) { 394 const char *path = list.entry[i].name; 395 if (!quiet) 396 printf("rm '%s'\n", path); 397 398 if (remove_file_from_index(the_repository->index, path)) 399 die(_("git rm: unable to remove %s"), path); 400 } 401 402 if (show_only) 403 return 0; 404 405 /* 406 * Then, unless we used "--cached", remove the filenames from 407 * the workspace. If we fail to remove the first one, we 408 * abort the "git rm" (but once we've successfully removed 409 * any file at all, we'll go ahead and commit to it all: 410 * by then we've already committed ourselves and can't fail 411 * in the middle) 412 */ 413 if (!index_only) { 414 int removed = 0, gitmodules_modified = 0; 415 struct strbuf buf = STRBUF_INIT; 416 int flag = force ? REMOVE_DIR_PURGE_ORIGINAL_CWD : 0; 417 for (i = 0; i < list.nr; i++) { 418 const char *path = list.entry[i].name; 419 if (list.entry[i].is_submodule) { 420 strbuf_reset(&buf); 421 strbuf_addstr(&buf, path); 422 if (remove_dir_recursively(&buf, flag)) 423 die(_("could not remove '%s'"), path); 424 425 removed = 1; 426 if (!remove_path_from_gitmodules(path)) 427 gitmodules_modified = 1; 428 continue; 429 } 430 if (!remove_path(path)) { 431 removed = 1; 432 continue; 433 } 434 if (!removed) 435 die_errno("git rm: '%s'", path); 436 } 437 strbuf_release(&buf); 438 if (gitmodules_modified) 439 stage_updated_gitmodules(the_repository->index); 440 } 441 442 if (write_locked_index(the_repository->index, &lock_file, 443 COMMIT_LOCK | SKIP_IF_UNCHANGED)) 444 die(_("Unable to write new index file")); 445 446 return ret; 447}