Archive
git – how to easily remove ‘deleted’ files
git is a distributed version control system. There are lots of tutorials online to teach you the basics of this great system; that’s not the intent of this post. Rather, I want to share a neat trick I found on another site.
When you move files that have been checked into git without using the git mv command, as often happens when using an IDE and renaming a file, you are left with untracked files, and deleted files.
Nick@Macintosh-3 ~/Desktop/git_example$ mkdir src Nick@Macintosh-3 ~/Desktop/git_example$ touch src/Hello.java Nick@Macintosh-3 ~/Desktop/git_example$ git add src/ Nick@Macintosh-3 ~/Desktop/git_example$ git ci -m "Initial commit" [master (root-commit) 97eb204] Initial commit 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Hello.java Nick@Macintosh-3 ~/Desktop/git_example master$ git status # On branch master nothing to commit (working directory clean) Nick@Macintosh-3 ~/Desktop/git_example master$ ls src Nick@Macintosh-3 ~/Desktop/git_example master$ mv src/Hello.java src/Goodbye.java Nick@Macintosh-3 ~/Desktop/git_example master$ git status # On branch master # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: src/Hello.java # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # src/Goodbye.java no changes added to commit (use "git add" and/or "git commit -a")
As soon as you delete the src/Hello.java and add the src/Goodbye.java file, git is smart enough to realize that you really have just renamed or moved the file:
Nick@Macintosh-3 ~/Desktop/git_example master$ git rm src/Hello.java rm 'src/Hello.java' Nick@Macintosh-3 ~/Desktop/git_example master$ git add src/Goodbye.java Nick@Macintosh-3 ~/Desktop/git_example master$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # renamed: src/Hello.java -> src/Goodbye.java #
While this pattern is not too onerous when you move just a few files around, when you are in the midst of refactoring you could have multiple files moved into different directories, leading to a raft of these deleted files that need to be manually removed from git. Because they are no longer at the old location in the file system, we cannot use tab completion to help remove the old files; you must type out the full path to the file to be deleted. This can be a big pain.
For instance, imagine I have the following tree structure:
Nick@Macintosh-3 ~/Desktop/git_example master$ tree src/ src/ `-- org `-- example `-- nick |-- 1.java |-- 10.java |-- 2.java |-- 3.java |-- 4.java |-- 5.java |-- 6.java |-- 7.java |-- 8.java `-- 9.java
And we change the package name from an external, non-git process/program:
Nick@Macintosh-3 ~/Desktop/git_example master$ mv src/org/example/nick src/org/example/blog Nick@Macintosh-3 ~/Desktop/git_example master$ git add src/org/example/blog/ Nick@Macintosh-3 ~/Desktop/git_example master$ git st # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: src/org/example/blog/1.java # new file: src/org/example/blog/10.java # new file: src/org/example/blog/2.java # new file: src/org/example/blog/3.java # new file: src/org/example/blog/4.java # new file: src/org/example/blog/5.java # new file: src/org/example/blog/6.java # new file: src/org/example/blog/7.java # new file: src/org/example/blog/8.java # new file: src/org/example/blog/9.java # # Changed but not updated: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: src/org/example/nick/1.java # deleted: src/org/example/nick/10.java # deleted: src/org/example/nick/2.java # deleted: src/org/example/nick/3.java # deleted: src/org/example/nick/4.java # deleted: src/org/example/nick/5.java # deleted: src/org/example/nick/6.java # deleted: src/org/example/nick/7.java # deleted: src/org/example/nick/8.java # deleted: src/org/example/nick/9.java #
It is a bit of a pain to remove all of the deleted files individually.
Fortunately, there is a shortcut.
Nick@Macintosh-3 ~/Desktop/git_example master$ git ls-files --deleted src/org/example/nick/1.java src/org/example/nick/10.java src/org/example/nick/2.java src/org/example/nick/3.java src/org/example/nick/4.java src/org/example/nick/5.java src/org/example/nick/6.java src/org/example/nick/7.java src/org/example/nick/8.java src/org/example/nick/9.java Nick@Macintosh-3 ~/Desktop/git_example master$ git rm `git ls-files --deleted` rm 'src/org/example/nick/1.java' rm 'src/org/example/nick/10.java' rm 'src/org/example/nick/2.java' rm 'src/org/example/nick/3.java' rm 'src/org/example/nick/4.java' rm 'src/org/example/nick/5.java' rm 'src/org/example/nick/6.java' rm 'src/org/example/nick/7.java' rm 'src/org/example/nick/8.java' rm 'src/org/example/nick/9.java'
If this is a common enough use case, we can add aliases in the global ~/.gitconfig file to support this.
[alias] br = branch ci = commit co = checkout st = status # list files ls = ls-files # list deleted files lsd = ls-files --deleted # remove deleted files rmd = !git rm `git ls-files --deleted`
See the great wiki page on Aliases for information. The exclamation mark is used to indicate that the command invoked is a non-git one; you can execute arbitrary shell commands this way. For instance:
[alias] # A stupid thing to do, but illustrates that arbitrary unix commands can be executed in this manner cal = !cal Nick@Macintosh-3 ~/Desktop/git_example master$ git cal September 2010 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
All credit to the git cheat sheet site that initially brought the ls-files –deleted trick to my attention. Hopefully the alias I have provided will be useful to some people.