[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.

相关推荐
谢家小布柔5 小时前
推送本地仓库到远程git仓库
git
mit6.8248 小时前
[Pro Git#4] 标签 | 理解 | 创建 | push
git·学习·github
码农老起8 小时前
常用代码开发工具技术分享
git·docker·github·visual studio code·visual studio
木槿7115 小时前
软件包git没有可安装候选
汇编·git
小旺仔爱代码18 小时前
Git工具
git
诸葛亮的芭蕉扇1 天前
前端工程中.git文件夹内容分析
前端·git·elasticsearch
sin22011 天前
Git简介和特点
git
Cachel wood1 天前
Vue.js前端框架教程1:Vue应用启动和Vue组件
大数据·前端·vue.js·git·elasticsearch·前端框架·ssh
ahhhhaaaa-1 天前
【工具】Git 操作大全
数据仓库·git·开发组件