builtin-rm.con commit Merge branch 'ph/enable-threaded' (652b0bb)
   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
  13static const char * const builtin_rm_usage[] = {
  14        "git rm [options] [--] <file>...",
  15        NULL
  16};
  17
  18static struct {
  19        int nr, alloc;
  20        const char **name;
  21} list;
  22
  23static void add_list(const char *name)
  24{
  25        if (list.nr >= list.alloc) {
  26                list.alloc = alloc_nr(list.alloc);
  27                list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
  28        }
  29        list.name[list.nr++] = name;
  30}
  31
  32static int remove_file(const char *name)
  33{
  34        int ret;
  35        char *slash;
  36
  37        ret = unlink(name);
  38        if (ret && errno == ENOENT)
  39                /* The user has removed it from the filesystem by hand */
  40                ret = errno = 0;
  41
  42        if (!ret && (slash = strrchr(name, '/'))) {
  43                char *n = xstrdup(name);
  44                do {
  45                        n[slash - name] = 0;
  46                        name = n;
  47                } while (!rmdir(name) && (slash = strrchr(name, '/')));
  48        }
  49        return ret;
  50}
  51
  52static int check_local_mod(unsigned char *head, int index_only)
  53{
  54        /* items in list are already sorted in the cache order,
  55         * so we could do this a lot more efficiently by using
  56         * tree_desc based traversal if we wanted to, but I am
  57         * lazy, and who cares if removal of files is a tad
  58         * slower than the theoretical maximum speed?
  59         */
  60        int i, no_head;
  61        int errs = 0;
  62
  63        no_head = is_null_sha1(head);
  64        for (i = 0; i < list.nr; i++) {
  65                struct stat st;
  66                int pos;
  67                struct cache_entry *ce;
  68                const char *name = list.name[i];
  69                unsigned char sha1[20];
  70                unsigned mode;
  71                int local_changes = 0;
  72                int staged_changes = 0;
  73
  74                pos = cache_name_pos(name, strlen(name));
  75                if (pos < 0)
  76                        continue; /* removing unmerged entry */
  77                ce = active_cache[pos];
  78
  79                if (lstat(ce->name, &st) < 0) {
  80                        if (errno != ENOENT)
  81                                fprintf(stderr, "warning: '%s': %s",
  82                                        ce->name, strerror(errno));
  83                        /* It already vanished from the working tree */
  84                        continue;
  85                }
  86                else if (S_ISDIR(st.st_mode)) {
  87                        /* if a file was removed and it is now a
  88                         * directory, that is the same as ENOENT as
  89                         * far as git is concerned; we do not track
  90                         * directories.
  91                         */
  92                        continue;
  93                }
  94                if (ce_match_stat(ce, &st, 0))
  95                        local_changes = 1;
  96                if (no_head
  97                     || get_tree_entry(head, name, sha1, &mode)
  98                     || ce->ce_mode != create_ce_mode(mode)
  99                     || hashcmp(ce->sha1, sha1))
 100                        staged_changes = 1;
 101
 102                if (local_changes && staged_changes)
 103                        errs = error("'%s' has staged content different "
 104                                     "from both the file and the HEAD\n"
 105                                     "(use -f to force removal)", name);
 106                else if (!index_only) {
 107                        /* It's not dangerous to git-rm --cached a
 108                         * file if the index matches the file or the
 109                         * HEAD, since it means the deleted content is
 110                         * still available somewhere.
 111                         */
 112                        if (staged_changes)
 113                                errs = error("'%s' has changes staged in the index\n"
 114                                             "(use --cached to keep the file, "
 115                                             "or -f to force removal)", name);
 116                        if (local_changes)
 117                                errs = error("'%s' has local modifications\n"
 118                                             "(use --cached to keep the file, "
 119                                             "or -f to force removal)", name);
 120                }
 121        }
 122        return errs;
 123}
 124
 125static struct lock_file lock_file;
 126
 127static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
 128static int ignore_unmatch = 0;
 129
 130static struct option builtin_rm_options[] = {
 131        OPT__DRY_RUN(&show_only),
 132        OPT__QUIET(&quiet),
 133        OPT_BOOLEAN( 0 , "cached",         &index_only, "only remove from the index"),
 134        OPT_BOOLEAN('f', "force",          &force,      "override the up-to-date check"),
 135        OPT_BOOLEAN('r', NULL,             &recursive,  "allow recursive removal"),
 136        OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
 137                                "exit with a zero status even if nothing matched"),
 138        OPT_END(),
 139};
 140
 141int cmd_rm(int argc, const char **argv, const char *prefix)
 142{
 143        int i, newfd;
 144        const char **pathspec;
 145        char *seen;
 146
 147        git_config(git_default_config, NULL);
 148
 149        argc = parse_options(argc, argv, builtin_rm_options, builtin_rm_usage, 0);
 150        if (!argc)
 151                usage_with_options(builtin_rm_usage, builtin_rm_options);
 152
 153        if (!index_only)
 154                setup_work_tree();
 155
 156        newfd = hold_locked_index(&lock_file, 1);
 157
 158        if (read_cache() < 0)
 159                die("index file corrupt");
 160
 161        pathspec = get_pathspec(prefix, argv);
 162        seen = NULL;
 163        for (i = 0; pathspec[i] ; i++)
 164                /* nothing */;
 165        seen = xcalloc(i, 1);
 166
 167        for (i = 0; i < active_nr; i++) {
 168                struct cache_entry *ce = active_cache[i];
 169                if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
 170                        continue;
 171                add_list(ce->name);
 172        }
 173
 174        if (pathspec) {
 175                const char *match;
 176                int seen_any = 0;
 177                for (i = 0; (match = pathspec[i]) != NULL ; i++) {
 178                        if (!seen[i]) {
 179                                if (!ignore_unmatch) {
 180                                        die("pathspec '%s' did not match any files",
 181                                            match);
 182                                }
 183                        }
 184                        else {
 185                                seen_any = 1;
 186                        }
 187                        if (!recursive && seen[i] == MATCHED_RECURSIVELY)
 188                                die("not removing '%s' recursively without -r",
 189                                    *match ? match : ".");
 190                }
 191
 192                if (! seen_any)
 193                        exit(0);
 194        }
 195
 196        /*
 197         * If not forced, the file, the index and the HEAD (if exists)
 198         * must match; but the file can already been removed, since
 199         * this sequence is a natural "novice" way:
 200         *
 201         *      rm F; git rm F
 202         *
 203         * Further, if HEAD commit exists, "diff-index --cached" must
 204         * report no changes unless forced.
 205         */
 206        if (!force) {
 207                unsigned char sha1[20];
 208                if (get_sha1("HEAD", sha1))
 209                        hashclr(sha1);
 210                if (check_local_mod(sha1, index_only))
 211                        exit(1);
 212        }
 213
 214        /*
 215         * First remove the names from the index: we won't commit
 216         * the index unless all of them succeed.
 217         */
 218        for (i = 0; i < list.nr; i++) {
 219                const char *path = list.name[i];
 220                if (!quiet)
 221                        printf("rm '%s'\n", path);
 222
 223                if (remove_file_from_cache(path))
 224                        die("git-rm: unable to remove %s", path);
 225        }
 226
 227        if (show_only)
 228                return 0;
 229
 230        /*
 231         * Then, unless we used "--cached", remove the filenames from
 232         * the workspace. If we fail to remove the first one, we
 233         * abort the "git rm" (but once we've successfully removed
 234         * any file at all, we'll go ahead and commit to it all:
 235         * by then we've already committed ourselves and can't fail
 236         * in the middle)
 237         */
 238        if (!index_only) {
 239                int removed = 0;
 240                for (i = 0; i < list.nr; i++) {
 241                        const char *path = list.name[i];
 242                        if (!remove_file(path)) {
 243                                removed = 1;
 244                                continue;
 245                        }
 246                        if (!removed)
 247                                die("git-rm: %s: %s", path, strerror(errno));
 248                }
 249        }
 250
 251        if (active_cache_changed) {
 252                if (write_cache(newfd, active_cache, active_nr) ||
 253                    commit_locked_index(&lock_file))
 254                        die("Unable to write new index file");
 255        }
 256
 257        return 0;
 258}