How to undo (almost) anything in Git

One of the most useful features of any version control system is the ability to "undo" your erroneous operations. In Git, "undo" contains a number of slightly different functions.

When you make a new commit, Git saves a snapshot of your codebase at that particular point in time; later, you can use Git to go back to an earlier version of your project.

In this blog post, I'll explain some common scenarios where you need to "undo" changes you've made, and the best way to do so with Git.
Undo a "public" change

Scenario : You've performed a git push, sending your changes to GitHub, and now you realize that one of these commits is problematic, and you need to undo that commit.

Method: git revert <SHA>

principle: git revert will generate a new commit, which is the opposite (or inverse) of the commit corresponding to the specified SHA. If the original commit was "matter", the new commit is "antimatter" — anything deleted from the original commit will be added back in the new commit, and anything added to the original commit will be added to the new commit is deleted.

This is Git's safest and most basic undo scenario, since it doesn't change the history — so you can now git push a new "reverse" commit to counteract your erroneous commit.
Fix the last commit message

Scenario : You have a typo in the last commit message, have executed git commit -m "Fxies bug #42", but before git push you realize the message should be "Fixes bug #42".

Method: git commit --amend or git commit --amend -m "Fixes bug #42"

Principle: git commit --amend will update and replace the most recent commit with a new commit that will update any changes Combine with the content of the previous commit. If no changes are currently proposed, this operation simply rewrites the last commit message.
Undoing "local" changes

Scenario : A cat walks across the keyboard, accidentally saves changes, and then breaks the editor. However, you have not yet committed these changes. You want to restore everything in the modified file - exactly as it was when the last commit was made.

Method: git checkout -- <bad filename>

Principle: git checkout will modify the files in the working directory to a certain state recorded before Git. You can provide a branch name or a specific SHA you want to return, or by default Git will think you want to checkout HEAD, the last commit of the current checkout branch.

Remember: any changes you "undo" in this way really disappear completely. Because they were never committed, Git couldn't help us restore them afterwards. Make sure you understand what you're throwing away in this operation! (maybe check with git diff first)


Reset "local" changes

Scenario : You committed something locally (haven't pushed it yet), but all of that stuff sucks, and you want to undo the previous three commits — something like They never happened the same.

Method: git reset <last good SHA> or git reset --hard <

Rationale: git reset will return your repository history to the specified SHA state. It's like these commits never happened. By default, git reset preserves the working directory. In this way, the commit is gone, but the modified content is still on disk. This is a safe choice, but usually we'll want to "undo" commits and changes in one step - that's what the --hard option does.
Reverting after undoing "local changes"

Scenario : You make a few commits, undo those changes with git reset --hard (see previous paragraph), and then you realize: you want to revert those changes!

How: git reflog and git reset or git checkout

How it works: git reflog is an awesome resource for restoring project history. You can restore just about anything - anything you've committed - just by going through the reflog.

You may already be familiar with the git log command, which displays a list of commits. git reflog is similar, but it shows a list of times when HEAD changed.

A few notes:

    it only deals with changes to HEAD. HEAD changes when you switch branches, commit with git commit, and undo the commit with git reset, but when you undo with git checkout -- <bad filename> (as we mentioned earlier), HEAD does not change. won't change — as mentioned, these changes were never committed, so the reflog can't help us recover them either.
    git reflog doesn't stay forever. Git will periodically clean up those "unused" objects. Don't expect commits from months ago to still be lying around.
    Your reflog is yours, just yours. You can't use git reflog to restore a commit that another developer didn't push.

reflog

So... how do you use reflog to "recover" a previously "undo" commit? It depends on what exactly you want to do:

    if you want to restore the project's history exactly to a certain point in time, use git reset --hard <SHA>
    if you want to rebuild one or more files in the working directory, To restore them to a point-in-time state, use git checkout <SHA> -- <filename>
    If you wish to re-submit one of these commits to your codebase, use git cherry-pick <SHA>

to exploit Another way of doing branches

Scenario : You make some commits and then realize that you started checking out the master branch. You want these commits to go into another feature branch.

Method: git branch feature, git reset --hard origin/master, and git checkout feature

Principle: You're probably used to creating new branches with git checkout -b <name> — this is a popular shortcut for creating new branches and checking them out right away — but you don't want to switch branches right away. Here, git branch feature creates a new branch called feature that points to your most recent commit, but still lets you check out on the master branch.

Next, rewind the master branch back to origin/master with git reset --hard before committing any new commits. But don't worry, those commits are still in the feature branch.

Finally, switch to the new feature branch with git checkout and leave all your recent work intact.


Timely branch, save the tedious

Scenario : You create a feature branch on the basis of the master branch, but the master branch is already lagging behind origin/master by a lot. Now that the master branch is in sync with origin/master, you want the commits on the feature to start now, not also from somewhere that is a lot behind.

Method: git checkout feature and git rebase master

Principle: To achieve this effect, you could have passed git reset (without --hard, so the changes can be kept on disk) and git checkout -b <new branch name> and then restarted Commit the changes, but by doing so, you will lose the commit history. We have a better way.

git rebase master will do the following:

    first it will find the common ancestor of the branch you are currently checking out and the master branch.
    Then it resets the currently checked out branch to that common ancestor, storing all previous commits in a temporary save area.
    Then it refers the currently checked out branch to the end of master, and resubmits the stored commit from the temporary save area to the last commit on the master branch.

Lots of undo/redo

scenarios : You start implementing a feature in a certain direction, but halfway through you realize another solution is better. You've made a dozen commits, but you only need some of them now. You want all other unwanted commits to disappear.

Method: git rebase -i <earlier SHA>

Principle: The -i parameter makes rebase enter "interactive mode". It starts out similar to the rebase discussed earlier, but before re-doing any commits, it pauses and allows you to revise each commit in detail.

rebase -i will open your default text editor with a list of candidate commits. As follows:

rebase-interactive1

The first two columns are keys: the first is the selected command, corresponding to the commit determined by the SHA in the second column. By default, rebase -i assumes that every commit is applied via the pick command.

To discard a commit, just delete that line in the editor. If you no longer need those bad commits in the project, you can delete lines 1, 3, and 4 in the above example.

If you need to keep the content of the commit, but edit the commit message, you can use the reword command. Replace pick in the first column with reword (or just r). One might think that simply rewriting the commit message here would work, but that doesn't work -- rebase -i ignores anything before the SHA column. The text after it is just there to help us remember what 0835fe2 does. When you're done with rebase -i, you'll be prompted for any commit messages you need to write.

If you need to merge two commits together, you can use the squash or fixup commands as follows:

rebase-interactive2

squash and fixup are merged "up" - a commit with these two commands is merged into its previous commit. In this example, 0835fe2 and 6943e85 would be merged into one commit, and 38f5e4e and af67f82 would be merged into another.

If you choose squash, Git will prompt us to give the newly merged commit a new commit message; fixup will send the message of the first commit in the merge list directly to the newly merged commit. Here, you know that af67f82 is a "finished...." commit, so you'll keep the commit message of 38f5e4e as, but you'll write a new message for the new commit that merges 0835fe2 and 6943e85.

When you save and exit the editor, Git applies your commits in order from top to bottom. You can change the order of applications by changing the commit order before saving. If you want, you can also merge af67f82 and 0835fe2 together by arranging as follows:

rebase-interactive3
Fix earlier commit

scenario: you forgot to add a file in an earlier commit, if the earlier commit could include this Forgotten files are great. You haven't pushed yet, but this commit is not recent, so you can't use commit --amend.

Methods: git commit --squash <SHA of the earlier commit> and git rebase --autosquash -i <even earlier SHA>

Rationale: git commit --squash will create a new commit with a commit message, similar to squash! Earlier commit. (You can also manually create a commit with a similar commit message, but commit --squash can save you typing.)

If you don't want to be prompted to enter a new commit message for the newly merged commit, you can also Make use of git commit --fixup . In this case, you will most likely use commit --fixup because you just want to use the commit message of the earlier commit when rebase.

rebase --autosquash -i will activate an interactive rebase editor, but when the editor opens, any squash! and fixup! commits in the commit list are already paired with the target commit, as follows:

rebase- autosquash

When using --squash and --fixup, you may not remember the SHA of the commit you want to fix—just remember that it was the 1st or 5th previous commit. You'll find Git's ^ and ~ operators especially useful. HEAD^ is the previous commit to HEAD. HEAD~4 is the 4th ahead of HEAD – or counted together, the 5th commit from the bottom.


stop tracking a file

Scenario: You accidentally added application.log to your repository, and now every time you run your application, Git will report uncommitted changes in application.log. You put *.login in the .gitignore file, but the file is still in the repository - how can you tell Git to "undo" tracking of this file?

Method: git rm --cached application.log

Principle: Although .gitignore will prevent Git from tracking file modifications, even if the file exists, it is only for files that have never been tracked before. Once a file has been added and committed, Git will keep watching for changes to that file. Similarly, if you force or override .gitignore with git add -f, Git will keep track of the changes. Then you don't have to use -f to add this file.

If you want to remove the otherwise ignored file from Git's tracker, git rm --cached will remove it from the tracker, but leave the file untouched on disk. Since it's now ignored, you won't see this file in git status again, and you won't accidentally commit changes to that file again.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326187729&siteId=291194637