11.7. Recovering Old Changes¶
Let’s say you want to work with an earlier version of your source code because you’ve introduced a bug, or perhaps you accidentally deleted a file. A version control system lets you undo, or roll back, your working copy to the content of an earlier commit. The exact command you’ll use depends on the state of the files in the working copy.
Keep in mind that version control systems only add information. Even when you delete a file from a repo, Git will remember it so you can restore it later. Rolling back a change actually adds a new change that sets a file’s con- tent to its state in a previous commit. You’ll find detailed information on various kinds of rollbacks at https://github.blog/2015-06-08-how-to-undo-almost -anything-with-git/.
11.7.1. Undoing Uncommitted Local Changes¶
If you’ve made uncommitted changes to a file but want to revert it to the version in the latest commit, you can run git restore <filename> . In the fol- lowing example, we modify the README.md file but don’t yet stage or com- mit it:
C:UsersAlwizcoin>git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: README.md no changes added to commit (use "git add" and/or "git commit -a") C:UsersAlwizcoin>git restore README.md C:UsersAlwizcoin>git status On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean
After you’ve run the git restore README.md command, the content of README.md reverts to that of the last commit. This is effectively an undo for the changes you’ve made to the file (but haven’t yet staged or commit- ted). But be careful: you can’t undo this “undo” to get those changes back.
You can also run git checkout . to revert all changes you’ve made to every file in your working copy. ## Unstaging a Staged File If you’ve staged a modified file by running the git add command on it but now want to remove it from staging so it won’t be included in the next com- mit, run git restore –staged <filename> to unstage it:
C:UsersAl>git restore --staged README.md Unstaged changes after reset: M spam.txt
README.md remains modified as it was before git add staged the file, but the file is no longer in the staged state. ## Rolling Back the Most Recent Commits Suppose you’ve made several unhelpful commits and you want to start over from a previous commit. To undo a specific number of the most recent commits, say, three, use the git revert -n HEAD~3..HEAD command. You can replace the 3 with any number of commits. For example, let’s say you tracked the changes to a mystery novel you were writing and have the fol- lowing Git log of all your commits and commit messages.
C:UsersAlnovel>git log --oneline de24642 (HEAD -> master) Changed the setting to outer space. 2be4163 Added a whacky sidekick. 97c655e Renamed the detective to 'Snuggles'. 8aa5222 Added an exciting plot twist. 2590860 Finished chapter 1. 2dece36 Started my novel.
Later you decide you want to start over again from the exciting plot twist at hash 8aa5222 . This means you should undo the changes from the last three commits: de24642 , 2be4163 , and 97c655e . Run git revert -n HEAD~3..HEAD to undo these changes, and then run git add . and git commit -m “<commit message>” to commit this content, just as you would with any other change:
C:UsersAlnovel>git revert -n HEAD~3..HEAD C:UsersAlnovel>git add . C:UsersAlnovel>git commit -m "Starting over from the plot twist." [master faec20e] Starting over from the plot twist. 1 file changed, 34 deletions(-) C:UsersAlnovel>git log --oneline faec20e (HEAD -> master) Starting over from the plot twist. de24642 Changed the setting to outer space. 2be4163 Added a whacky sidekick. 97c655e Renamed the detective to 'Snuggles'. 8aa5222 Added an exciting plot twist. 2590860 Finished chapter 1. 2dece36 Started my novel.
Git repos generally only add information, so undoing these commits still leaves them in the commit history. If you ever want to undo this “undo,” you can roll it back using git revert again. ## Rolling Back to a Specific Commit for a Single File Because commits capture the state of the entire repo instead of individual files, you’ll need a different command if you want to roll back changes for a single file. For example, let’s say I had a Git repo for a small software proj- ect. I’ve created an eggs.py file and added functions spam() and bacon() , and then renamed bacon() to cheese() . The log for this repo would look some- thing like this:
C:UsersAlmyproject>git log --oneline 895d220 (HEAD -> master) Adding email support to cheese(). df617da Renaming bacon() to cheese(). ef1e4bb Refactoring bacon(). ac27c9e Adding bacon() function. 009b7c0 Adding better documentation to spam(). 0657588 Creating spam() function. d811971 Initial add.
But I’ve decided I want to revert the file back to before I added bacon() without changing any other files in the repo. I can use the git show <hash>: <filename> command to display this file as it was after a specific commit. The command would look something like this:
C:UsersAlmyproject>git show 009b7c0:eggs.py <contents of eggs.py as it was at the 009b7c0 commit>
Using the git checkout <hash> – <filename> , I could set the contents of eggs.py to this version and commit the changed file as normal. The git checkout command only changes the working copy. You’ll still need to stage and commit these changes like any other change:
C:UsersAlmyproject>git checkout 009b7c0 -- eggs.py C:UsersAlmyproject>git add eggs.py C:UsersAlmyproject>git commit -m "Rolled back eggs.py to 009b7c0" [master d41e595] Rolled back eggs.py to 009b7c0 1 file changed, 47 deletions(-) C:UsersAlmyproject>git log --oneline d41e595 (HEAD -> master) Rolled back eggs.py to 009b7c0 895d220 Adding email support to cheese(). df617da Renaming bacon() to cheese(). ef1e4bb Refactoring bacon(). ac27c9e Adding bacon() function. 009b7c0 Adding better documentation to spam(). 0657588 Creating spam() function. d811971 Initial add.
The eggs.py file has been rolled back, and the rest of the repo remains the same. ## Rewriting the Commit History If you’ve accidentally committed a file that contains sensitive information, such as passwords, API keys, or credit card numbers, it’s not enough to edit that information out and make a new commit. Anyone with access to the repo, either on your computer or cloned remotely, could roll back to the commit that includes this info.
Actually removing this information from your repo so it’s unrecover- able is tricky but possible. The exact steps are beyond the scope of this book, but you can use either the git filter-branch command or, preferably, the BFG Repo-Cleaner tool. You can read about both at https://help.github .com/en/articles/removing-sensitive-data-from-a-repository.
The easiest preventative measure for this problem is to have a secrets.txt, confidential.py, or similarly named file where you place sensitive, private infor- mation, and add it to .gitignore so you’ll never accidentally commit it to the repo. Your program can read this file for the sensitive info instead of having the sensitive info directly in its source code.