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