[Git notes] git reset options and branch history backup

Clarify the differences between git reset --[options] <commit-hash or HEAD~n>

Using practical examples with simple code changes can definitely help clarify the differences between

git reset --soft <commit-hash or HEAD~n>

git reset --mixed <commit-hash or HEAD~n>

git reset --hard <commit-hash or HEAD~n>

Let's simulate a scenario where we modify a file called example.txt in different ways across several commits, and then see how each type of reset affects the repository.

Initial Setup

Suppose example.txt originally contains:

Line 1: Initial conten

Commit 1 (A):

  • Modify example.txt to:

    Line 1: Initial content
    Line 2: Added by commit A

  • Commit the change: git add example.txt
    git commit -m "Commit A: Add second line"

Commit 2 (B):

  • Modify example.txt to

Line 1: Initial content
Line 2: Added by commit A
Line 3: Added by commit B

  • Commit the change: git add example.txt
    git commit -m "Commit B: Add third line"

Commit 3 (C):

  • Modify example.txt to
复制代码
Line 1: Initial content
Line 2: Added by commit A
Line 3: Added by commit B
Line 4: Added by commit C
  • Commit the change: git add example.txt
    git commit -m "Commit B: Add third line"

Now, let's see how different resets work:

  1. git reset --soft HEAD~2

    • Goal: Undo the last two commits (B and C), but keep their changes staged.

    • Command :

      git reset --soft HEAD~

    • Result :

      • The repository HEAD is back to commit A.
      • Changes from commits B and C remain staged.
      • Running git status will show lines from commits B and C staged for recommitting.
      • Commit History After Reset : Only commit A remains in the main branch's history. Commits B and C are no longer part of the commit history, but their changes are ready to be committed anew.
      • A [main]
        Changes from B and C are staged.
  2. git reset --mixed HEAD~2 (default behavior of git reset)

    • Goal: Undo the last two commits (B and C), keep their changes in the working directory but not staged.

    • Command :

      复制代码
      git reset --mixed HEAD~2
    • Result :

      • HEAD is back to commit A.
      • Changes from commits B and C are in the working directory but not staged.
      • Running git status will show lines from commits B and C as modified but not staged.
      • Commit History After Reset : The commit history is similar to the --soft reset: only commit A is visible in main. The work from commits B and C is not staged but remains in the working directory, allowing you to stage and commit them again as desired.
      • A [main]
        Changes from B and C are modified but not staged.
  3. git reset --hard HEAD~2

    • Goal: Completely undo the last two commits (B and C) and remove all associated changes.

    • Command :

      git reset --hard HEAD~2

    • Result :

      • HEAD is back to commit A.
      • All changes from commits B and C are discarded.
      • example.txt is back to its state at commit A, with no trace of changes from B and C in the working directory.
      • A [main]
  • Soft Reset (--soft): Moves HEAD but leaves your working directory and staging area untouched as they were at the last commit before reset. Great for modifying commit history while keeping changes ready to recommit.
  • Mixed Reset (--mixed): Moves HEAD back and unstages changes but leaves them in your working directory. Useful for redoing commits if you want to modify how changes are staged.
  • Hard Reset (--hard): Moves HEAD back and discards all changes completely, both staged and unstaged. Use with caution as it can lead to data loss.

Steps to Safely Revert Changes and branch history backup

To preserve both the changes and the commit history while still reverting to a previous state in your original branch, the best practice is to create a new branch before performing any type of reset. This allows you to retain all commit history and changes in one branch while experimenting or reverting changes in another.

Suppose you are on the main branch and have made several commits that you wish to review or revert, but you want to ensure that none of your work is lost irreversibly:

  • Creating a Backup:

    git branch backup-branch # Creates a backup git checkout backup-branch # Switches to backup

  • Resetting the Main Branch:

    git checkout main # Return to main git reset --hard HEAD~3 # Reverts the last three commits

  • Result:

    • main branch is now reverted to an earlier state, with the last three commits removed.
    • backup-branch contains all the commits, including those removed from main.

Before creating backup-branch and resetting:

A -- B -- C -- D -- E [main]

After creating backup-branch and performing git reset --hard HEAD~3 on main:

A -- B [main]
\
C -- D -- E [backup-branch]

This approach gives you flexibility. You can move forward on the main branch from the older state, potentially taking a different direction, while still having the option to refer back to or reuse work done in those "removed" commits through the backup-branch.

Note: When using git reset, you can only directly influence the state of the branch from which you're performing the reset, and it directly affects the HEAD of that branch. Essentially, git reset modifies the position of HEAD and potentially changes the staging area and working directory depending on the options used (--soft, --mixed, or --hard).

Scenario: Local Reset with an Aligned Remote Repo

  1. Initial State: Assume your local and remote repositories are synchronized.

    Local and Remote: A -- B -- C -- D [main] 
    
  2. Create a Backup Branch Locally:

    • You create a backup branch from the current state of main:

      git branch backup-branch git checkout backup-branch 
      
    • At this point, both your local main and backup-branch are identical, but this new branch is only local.

  3. Reset the Main Branch Locally:

    • You reset the main branch to an earlier commit, for example, B:

      git checkout main git reset --hard B 
      
    • Locally, your main branch now looks like:

      A -- B [main] 
      

Updating the Remote Repository

  • Pushing Changes : If you wish to update the remote repository to match your local reset state, you would need to use force push (if the history has diverged, which it typically does after a reset):

    git push origin main --force 
    
  • Backup Branch : If you want to push the backup-branch to the remote, simply use:

    git push origin backup-branch 
    

    This will create a new branch on the remote repository with all commits up to D.

Scenario: Resetting and Pushing in the main Branch

Initial State:

  • Assume your local and remote main branches are synchronized with the following commits:

    A -- B -- C -- D [main] 
    

    Local Reset:
    You decide to reset the main branch back to commit B:
    *

      git reset --hard B 
    
    • Locally, your branch now looks like this:

      A -- B [main]                              
      

      Making a New Commit :

      After the reset, you make a new commit E:

      git add .                             
      git commit -m "Commit E" 
      Locally, your branch now looks like this:
      A -- B -- E [main]
      

What Happens to the Remote Repository?

  • If you use git push --force, Git will update the remote main branch to exactly match your local main branch, which now looks like:

      A -- B -- E [main]
    
    • When you attempt to push your local main to the remote without using force: git push origin main
    • This push will likely be rejected because it would remove commits C and D from the remote, which Git interprets as a non-fast-forward update. The remote still has:
      • A -- B -- C -- D [main]

When you try to push your local changes to the remote:

  • Git compares the histories of the two branches.
  • The push would require removing commits C and D from the remote, which Git identifies as a non-fast-forward update because the commit E does not directly follow commit D. Instead, it appears to "replace" commits C and D

Why Doesn't Git Just Append E?

Appending E to make the history A -- B -- C -- D -- E would be the straightforward solution if you hadn't reset and E was simply a continuation of D. However, because your local main branch's history effectively rewrites the sequence from B directly to E, Git cannot append E without potentially discarding C and D, unless it merges or rebases, which could change the contents and intention of E.

Why Git Rejects Non-Fast-Forward by Default

  • Data Loss Prevention : Allowing such a push would mean that commits C and D are lost in the remote's main branch. Git rejects this by default to prevent data loss that could affect other collaborators who might be basing their work on those commits.
  • Repository Integrity : Maintaining a linear history on shared branches like main ensures that all collaborators have a consistent view of the project history. Disruptions in this history could lead to conflicts and confusion.

Creating a new branch to backup or preserve commits before making significant changes or resets to your main development branch is indeed a best practice in many development workflows. This strategy ensures you maintain the flexibility to revert or modify your approach without losing any historical data or previous work. Here's why this is often the best approach:

Git Staging Area and Working Directory

Let's consider a practical scenario to better understand these relationships:

  • Suppose you modify file.txt in your working directory:

    // Initial content Line 1: Hello World 
    
  • You then edit file.txt to add a second line:

    // Modified in working directory 
    Line 1: Hello World
     Line 2: New line added 
    
  • You stage this change:

    git add file.txt 
    
  • After staging, the changes (the addition of Line 2) are in both the staging area and the working directory.

  • If you now modify the same file again without staging the new change:

    // Further modified in working directory 
    Line 1: Hello World 
    Line 2: New line added 
    Line 3: Another new line added 
    

    The staging area still only has up to Line 2 staged. Line 3 is in the working directory but not staged.

相关推荐
王景程3 小时前
GitHub的主要用途及核心功能
git·github
Мартин.8 小时前
[Meachines] [Easy] LinkVortex Git leakage+Ghost 5.58+Double Link Bypass权限提升
git
甜到心里的蛋糕10 小时前
github汉化
git·github
可涵不会debug14 小时前
【C++】在线五子棋对战项目网页版
linux·服务器·网络·c++·git
Amy_cx17 小时前
卸载和安装Git小乌龟、git基本命令
git
铃响十分19 小时前
make/Makefile、进度条、git
git
念九_ysl20 小时前
git操作
git
画船听雨眠aa20 小时前
git的安装
git
bing_1581 天前
Git常用命令
git
森林的尽头是阳光2 天前
git克隆原项目到新目录,保留提交记录分支等,与原项目保持各自独立
git