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
- (0b33..b535) Added A.txt, B.txt, and C.jpg with separate commits.
- (0a573c34) Modified A.txt and B.txt, adding the string “FIX” and committed both files.
- (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.
|
|
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.
|
|
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
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.
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:
- Create a test branch at the last commit and switch to it.
- Perform a mixed reset to the desired node. This retains the working directory state, but moves the HEAD.
- Remove unnecessary files, such as B and C.
- Recommit the A file, which now includes all relevant fixes.
- Switch to the
new_merge
branch. - Right-click
Merge_A
and perform a rebase. If conflicts arise, since the A file already includes modifications, choose tokeep the local state
. - 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. - If necessary, hard reset the test branch to new_merge and repeat steps 2-7.
- (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.
- Mixed preserves current working directory files and moves the HEAD.
- Soft preserves current working directory files, stages them, and moves the HEAD.
- 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.