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 [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>..."; 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,int index_only) 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; 68int local_changes =0; 69int staged_changes =0; 70 71 pos =cache_name_pos(name,strlen(name)); 72if(pos <0) 73continue;/* removing unmerged entry */ 74 ce = active_cache[pos]; 75 76if(lstat(ce->name, &st) <0) { 77if(errno != ENOENT) 78fprintf(stderr,"warning: '%s':%s", 79 ce->name,strerror(errno)); 80/* It already vanished from the working tree */ 81continue; 82} 83else if(S_ISDIR(st.st_mode)) { 84/* if a file was removed and it is now a 85 * directory, that is the same as ENOENT as 86 * far as git is concerned; we do not track 87 * directories. 88 */ 89continue; 90} 91if(ce_match_stat(ce, &st,0)) 92 local_changes =1; 93if(no_head 94||get_tree_entry(head, name, sha1, &mode) 95|| ce->ce_mode !=create_ce_mode(mode) 96||hashcmp(ce->sha1, sha1)) 97 staged_changes =1; 98 99if(local_changes && staged_changes) 100 errs =error("'%s' has staged content different " 101"from both the file and the HEAD\n" 102"(use -f to force removal)", name); 103else if(!index_only) { 104/* It's not dangerous to git-rm --cached a 105 * file if the index matches the file or the 106 * HEAD, since it means the deleted content is 107 * still available somewhere. 108 */ 109if(staged_changes) 110 errs =error("'%s' has changes staged in the index\n" 111"(use --cached to keep the file, " 112"or -f to force removal)", name); 113if(local_changes) 114 errs =error("'%s' has local modifications\n" 115"(use --cached to keep the file, " 116"or -f to force removal)", name); 117} 118} 119return errs; 120} 121 122static struct lock_file lock_file; 123 124intcmd_rm(int argc,const char**argv,const char*prefix) 125{ 126int i, newfd; 127int show_only =0, force =0, index_only =0, recursive =0, quiet =0; 128int ignore_unmatch =0; 129const char**pathspec; 130char*seen; 131 132git_config(git_default_config); 133 134 newfd =hold_locked_index(&lock_file,1); 135 136if(read_cache() <0) 137die("index file corrupt"); 138 139for(i =1; i < argc ; i++) { 140const char*arg = argv[i]; 141 142if(*arg !='-') 143break; 144else if(!strcmp(arg,"--")) { 145 i++; 146break; 147} 148else if(!strcmp(arg,"-n")) 149 show_only =1; 150else if(!strcmp(arg,"--cached")) 151 index_only =1; 152else if(!strcmp(arg,"-f")) 153 force =1; 154else if(!strcmp(arg,"-r")) 155 recursive =1; 156else if(!strcmp(arg,"--quiet")) 157 quiet =1; 158else if(!strcmp(arg,"--ignore-unmatch")) 159 ignore_unmatch =1; 160else 161usage(builtin_rm_usage); 162} 163if(argc <= i) 164usage(builtin_rm_usage); 165 166 pathspec =get_pathspec(prefix, argv + i); 167 seen = NULL; 168for(i =0; pathspec[i] ; i++) 169/* nothing */; 170 seen =xcalloc(i,1); 171 172for(i =0; i < active_nr; i++) { 173struct cache_entry *ce = active_cache[i]; 174if(!match_pathspec(pathspec, ce->name,ce_namelen(ce),0, seen)) 175continue; 176add_list(ce->name); 177} 178 179if(pathspec) { 180const char*match; 181int seen_any =0; 182for(i =0; (match = pathspec[i]) != NULL ; i++) { 183if(!seen[i]) { 184if(!ignore_unmatch) { 185die("pathspec '%s' did not match any files", 186 match); 187} 188} 189else{ 190 seen_any =1; 191} 192if(!recursive && seen[i] == MATCHED_RECURSIVELY) 193die("not removing '%s' recursively without -r", 194*match ? match :"."); 195} 196 197if(! seen_any) 198exit(0); 199} 200 201/* 202 * If not forced, the file, the index and the HEAD (if exists) 203 * must match; but the file can already been removed, since 204 * this sequence is a natural "novice" way: 205 * 206 * rm F; git rm F 207 * 208 * Further, if HEAD commit exists, "diff-index --cached" must 209 * report no changes unless forced. 210 */ 211if(!force) { 212unsigned char sha1[20]; 213if(get_sha1("HEAD", sha1)) 214hashclr(sha1); 215if(check_local_mod(sha1, index_only)) 216exit(1); 217} 218 219/* 220 * First remove the names from the index: we won't commit 221 * the index unless all of them succeed. 222 */ 223for(i =0; i < list.nr; i++) { 224const char*path = list.name[i]; 225if(!quiet) 226printf("rm '%s'\n", path); 227 228if(remove_file_from_cache(path)) 229die("git-rm: unable to remove%s", path); 230} 231 232if(show_only) 233return0; 234 235/* 236 * Then, unless we used "--cached", remove the filenames from 237 * the workspace. If we fail to remove the first one, we 238 * abort the "git rm" (but once we've successfully removed 239 * any file at all, we'll go ahead and commit to it all: 240 * by then we've already committed ourselves and can't fail 241 * in the middle) 242 */ 243if(!index_only) { 244int removed =0; 245for(i =0; i < list.nr; i++) { 246const char*path = list.name[i]; 247if(!remove_file(path)) { 248 removed =1; 249continue; 250} 251if(!removed) 252die("git-rm:%s:%s", path,strerror(errno)); 253} 254} 255 256if(active_cache_changed) { 257if(write_cache(newfd, active_cache, active_nr) || 258close(newfd) ||commit_locked_index(&lock_file)) 259die("Unable to write new index file"); 260} 261 262return0; 263}