這是一張有關標題為 Unlocking Git Time Travel with Reset 的圖片

Unlocking Git Time Travel with Reset

Utilizing Git reset in VS Code for more granular commit operations.

Introduction

In the previous article, Rebasing and Merging Branches in Git, we explored how to rearrange, modify, and remove commits using rebase.

Rebase allows us to squash, reword, and drop commits in one go, ensuring a clean and well-managed branch.

In this article, we will discuss reset, which can make the HEAD jump to the specified commit without altering files, enabling more granular branch and commit operations.

Example Branch State

  1. (0b33..b535) Added A.txt, B.txt, and C.jpg with separate commits.
  2. (0a573c34) Modified A.txt and B.txt, adding the string “FIX” and committed both files.
  3. (32e8ea75) Finally, modified A.txt again, adding “FIX A2” and committed the changes.

If you want to merge all modifications of A.txt into one commit, while keeping the modifications of B.txt separate, you can use reset to split the commits.

Combining rebase and cherry-pick, you can achieve more complex and detailed adjustments.

Three Modes of Reset

⚠️ Before proceeding with the following operations, ensure that all changes are committed to the repository and that there are no staged changes.

git reset –mixed

Create a mixed branch at the last commit for explanation purposes. Currently, the HEAD points to 32e8ea75.

Right-click the commit and select “Reset current branch to this Commit…”

This allows repositioning the HEAD to the selected commit without changing the current files.

Since the working directory files and the current HEAD commit are inconsistent, the file differences will appear as changes.

git reset –soft

Create a soft branch at the last commit for explanation.

Unlike git reset –mixed, after a soft reset, files will be staged for the next commit.

If you unstage (git reset) the changes, it will be similar to git reset –mixed.

git reset –hard

This is straightforward, discarding all current changes and moving the HEAD to the specified commit.

⚠️ Note that this will clear the staging area as well, so save your current state before proceeding.

Recovering from Failed Operations with Reflog

If you reset without creating a new branch to save the current commit, the original commits become orphan commits, meaning they are not referenced by any branch and might be deleted during future garbage collection.

In such cases, you can use git reflog to display all Git operations and recover those orphan commits. To view the log with precise timestamps, use the --date=iso option. For a concise view of each commit’s content, use the --pretty=short option, or redirect the output to a file.

1
2
3
4
5
git reflog
git reflog --date=iso     # Display time
git reflog --pretty=short # Display commit content
git reflog > filename.log # Redirect reflog output to a file for review
# Exit by pressing q

From the video below, after an incorrect reset, you don’t want to reset anymore. The original commit ID can be found in reflog as 32e8ea7.

Thus, using git reset --hard 32e8ea7 will discard the current changes and move the HEAD back to 32e8ea7.

1
2
3
4
5
6
7
wells@server:~/git_test$ git reflog --date=iso
7150c7d (HEAD -> master) HEAD@{2024-05-08 14:09:00 +0800}: reset: moving to 7150c7d2c8075445eab3a82a14cb4dc47cba7dad
b535519 HEAD@{2024-05-08 14:08:55 +0800}: reset: moving to b53551978e66973ed2d390761c08a46dc584945c
0a573c3 HEAD@{2024-05-08 14:08:51 +0800}: reset: moving to 0a573c34858816c15c7c8f622098c317b0e97902
32e8ea7 HEAD@{2024-05-08 14:08:43 +0800}: reset: moving to 32e8ea7574cd1303bd391113b988ad3089506a90
32e8ea7 HEAD@{2024-05-08 14:08:16 +0800}: checkout: moving from master to master
32e8ea7 HEAD@{2024-05-08 14:04:39 +0800}: checkout: moving from hard to master

Complex Merges with Reset

When organizing commits, you might encounter situations where multiple files are included in a single commit. How can you merge them all at once?

For example, consider the following branch: Node → Commit A → Commit B → Commit C → Commit A & B → Update A

Git Branch Example

Git Branch Example

In the final Update A commit, you have finalized the changes and no longer need the previous A commits. Reset can quickly help integrate and separate the file contents of the commits.

Our goal is: Node → Commit A_withFixed → Commit B_withFixed → Commit C.

Git Rebase Result

Git Rebase Result

You can reset directly to Node and recommit each change one by one, but this will lose all commit timestamps.

To preserve commit timestamps and author information as much as possible, you can use two branches and rebase. The process is as follows:

  1. Create a test branch at the last commit and switch to it.
  2. Perform a mixed reset to the desired node. This retains the working directory state, but moves the HEAD.
  3. Remove unnecessary files, such as B and C.
  4. Recommit the A file, which now includes all relevant fixes.
  5. Switch to the new_merge branch.
  6. Right-click Merge_A and perform a rebase. If conflicts arise, since the A file already includes modifications, choose to keep the local state.
  7. After rebase, use git diff to evaluate the differences between the new_merge branch and the master branch. If there are no differences, new_merge and master are consistent.
  8. If necessary, hard reset the test branch to new_merge and repeat steps 2-7.
  9. (If needed) Delete the master branch, rename the new_merge branch to master, and push. Other users will encounter conflicts as they cannot find the original parent node and will need to rebase their changes onto the new master branch. This requires coordination among team members.

For a more detailed explanation, refer to the following demonstration:

This approach is similar to the edit operation during rebase, which pauses rebase for the user to reset the previous commit and recommit. The goals are largely the same.

Personally, I prefer using two branches + reset + rebase. If errors occur or conflicts arise during the process, you can always abort the rebase with git rebase --abort.

Conclusion

Reset allows you to move the HEAD to a specific commit with different modes for fine-tuned control.

  1. Mixed preserves current working directory files and moves the HEAD.
  2. Soft preserves current working directory files, stages them, and moves the HEAD.
  3. Hard discards all current working directory files and moves the HEAD to the specified commit.

For advanced merging operations, you can use two branches, reset, and rebase.

Alternatively, use rebase with the edit option to pause, reset, split commits, recommit, and continue rebase.

References

  1. Git Reset
Theme Stack designed by Jimmy