2  * "git rm" builtin command
 
   4  * Copyright (C) Linus Torvalds 2006
 
   9 #include "cache-tree.h"
 
  10 #include "tree-walk.h"
 
  11 #include "parse-options.h"
 
  13 static const char * const builtin_rm_usage[] = {
 
  14         "git rm [options] [--] <file>...",
 
  23 static int check_local_mod(unsigned char *head, int index_only)
 
  26          * Items in list are already sorted in the cache order,
 
  27          * so we could do this a lot more efficiently by using
 
  28          * tree_desc based traversal if we wanted to, but I am
 
  29          * lazy, and who cares if removal of files is a tad
 
  30          * slower than the theoretical maximum speed?
 
  35         no_head = is_null_sha1(head);
 
  36         for (i = 0; i < list.nr; i++) {
 
  39                 struct cache_entry *ce;
 
  40                 const char *name = list.name[i];
 
  41                 unsigned char sha1[20];
 
  43                 int local_changes = 0;
 
  44                 int staged_changes = 0;
 
  46                 pos = cache_name_pos(name, strlen(name));
 
  48                         continue; /* removing unmerged entry */
 
  49                 ce = active_cache[pos];
 
  51                 if (lstat(ce->name, &st) < 0) {
 
  53                                 warning("'%s': %s", ce->name, strerror(errno));
 
  54                         /* It already vanished from the working tree */
 
  57                 else if (S_ISDIR(st.st_mode)) {
 
  58                         /* if a file was removed and it is now a
 
  59                          * directory, that is the same as ENOENT as
 
  60                          * far as git is concerned; we do not track
 
  67                  * "rm" of a path that has changes need to be treated
 
  68                  * carefully not to allow losing local changes
 
  69                  * accidentally.  A local change could be (1) file in
 
  70                  * work tree is different since the index; and/or (2)
 
  71                  * the user staged a content that is different from
 
  72                  * the current commit in the index.
 
  74                  * In such a case, you would need to --force the
 
  75                  * removal.  However, "rm --cached" (remove only from
 
  76                  * the index) is safe if the index matches the file in
 
  77                  * the work tree or the HEAD commit, as it means that
 
  78                  * the content being removed is available elsewhere.
 
  82                  * Is the index different from the file in the work tree?
 
  84                 if (ce_match_stat(ce, &st, 0))
 
  88                  * Is the index different from the HEAD commit?  By
 
  89                  * definition, before the very initial commit,
 
  90                  * anything staged in the index is treated by the same
 
  91                  * way as changed from the HEAD.
 
  94                      || get_tree_entry(head, name, sha1, &mode)
 
  95                      || ce->ce_mode != create_ce_mode(mode)
 
  96                      || hashcmp(ce->sha1, sha1))
 
 100                  * If the index does not match the file in the work
 
 101                  * tree and if it does not match the HEAD commit
 
 102                  * either, (1) "git rm" without --cached definitely
 
 103                  * will lose information; (2) "git rm --cached" will
 
 104                  * lose information unless it is about removing an
 
 105                  * "intent to add" entry.
 
 107                 if (local_changes && staged_changes) {
 
 108                         if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
 
 109                                 errs = error(_("'%s' has staged content different "
 
 110                                              "from both the file and the HEAD\n"
 
 111                                              "(use -f to force removal)"), name);
 
 113                 else if (!index_only) {
 
 115                                 errs = error(_("'%s' has changes staged in the index\n"
 
 116                                              "(use --cached to keep the file, "
 
 117                                              "or -f to force removal)"), name);
 
 119                                 errs = error(_("'%s' has local modifications\n"
 
 120                                              "(use --cached to keep the file, "
 
 121                                              "or -f to force removal)"), name);
 
 127 static struct lock_file lock_file;
 
 129 static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
 
 130 static int ignore_unmatch = 0;
 
 132 static struct option builtin_rm_options[] = {
 
 133         OPT__DRY_RUN(&show_only, "dry run"),
 
 134         OPT__QUIET(&quiet, "do not list removed files"),
 
 135         OPT_BOOLEAN( 0 , "cached",         &index_only, "only remove from the index"),
 
 136         OPT__FORCE(&force, "override the up-to-date check"),
 
 137         OPT_BOOLEAN('r', NULL,             &recursive,  "allow recursive removal"),
 
 138         OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
 
 139                                 "exit with a zero status even if nothing matched"),
 
 143 int cmd_rm(int argc, const char **argv, const char *prefix)
 
 146         const char **pathspec;
 
 149         git_config(git_default_config, NULL);
 
 151         argc = parse_options(argc, argv, prefix, builtin_rm_options,
 
 152                              builtin_rm_usage, 0);
 
 154                 usage_with_options(builtin_rm_usage, builtin_rm_options);
 
 159         newfd = hold_locked_index(&lock_file, 1);
 
 161         if (read_cache() < 0)
 
 162                 die(_("index file corrupt"));
 
 164         pathspec = get_pathspec(prefix, argv);
 
 165         refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
 
 168         for (i = 0; pathspec[i] ; i++)
 
 170         seen = xcalloc(i, 1);
 
 172         for (i = 0; i < active_nr; i++) {
 
 173                 struct cache_entry *ce = active_cache[i];
 
 174                 if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
 
 176                 ALLOC_GROW(list.name, list.nr + 1, list.alloc);
 
 177                 list.name[list.nr++] = ce->name;
 
 183                 for (i = 0; (match = pathspec[i]) != NULL ; i++) {
 
 185                                 if (!ignore_unmatch) {
 
 186                                         die(_("pathspec '%s' did not match any files"),
 
 193                         if (!recursive && seen[i] == MATCHED_RECURSIVELY)
 
 194                                 die(_("not removing '%s' recursively without -r"),
 
 195                                     *match ? match : ".");
 
 203          * If not forced, the file, the index and the HEAD (if exists)
 
 204          * must match; but the file can already been removed, since
 
 205          * this sequence is a natural "novice" way:
 
 209          * Further, if HEAD commit exists, "diff-index --cached" must
 
 210          * report no changes unless forced.
 
 213                 unsigned char sha1[20];
 
 214                 if (get_sha1("HEAD", sha1))
 
 216                 if (check_local_mod(sha1, index_only))
 
 221          * First remove the names from the index: we won't commit
 
 222          * the index unless all of them succeed.
 
 224         for (i = 0; i < list.nr; i++) {
 
 225                 const char *path = list.name[i];
 
 227                         printf("rm '%s'\n", path);
 
 229                 if (remove_file_from_cache(path))
 
 230                         die(_("git rm: unable to remove %s"), path);
 
 237          * Then, unless we used "--cached", remove the filenames from
 
 238          * the workspace. If we fail to remove the first one, we
 
 239          * abort the "git rm" (but once we've successfully removed
 
 240          * any file at all, we'll go ahead and commit to it all:
 
 241          * by then we've already committed ourselves and can't fail
 
 246                 for (i = 0; i < list.nr; i++) {
 
 247                         const char *path = list.name[i];
 
 248                         if (!remove_path(path)) {
 
 253                                 die_errno("git rm: '%s'", path);
 
 257         if (active_cache_changed) {
 
 258                 if (write_cache(newfd, active_cache, active_nr) ||
 
 259                     commit_locked_index(&lock_file))
 
 260                         die(_("Unable to write new index file"));