1. 程式人生 > >git reset revert rebase 區別

git reset revert rebase 區別

最近合併他人程式碼,因為程式碼是從git程式碼庫之外來的,於是出了各種問題。於是又翻看了git教程。這裡推薦兩篇:
Git教程 - 廖雪峰的官方網站 非常通俗易懂,教了最基本的,十分實用。
###Git 基礎圖解、分支圖解、全面教程、常用命令### 把git的命令以圖解的形式給了出來,讓人更理解git背後的處理原則。
但我讀完後,仍然不理解git reset/revert,於是自己實踐,終於得出了結果。
首先,說下Git的處理基準:

一、Git 基礎原理

  1. 每個檔案都有各自的記錄,稱之為樹也可(這是git reset讓人迷糊的地方);
  2. Git庫中有commit/index/working directory 3個狀態之分,如下圖:
    Git的工作狀態

    History,所有提交過的commit儲存的位置,也就是說只要commit過,都是歷史,可以回到過去~
    Stage(index),也就是當前git add過的但尚未commit的檔案
    Work directory,就是當前正在改的程式碼了
    基礎知識
  3. Git之中並無分支,但4個指標(很像C/C++語言的指標):
    (1)我們常用的branch(像master/dev/…)
    (2)動態的HEAD,也就是當前使用者在Git樹中的位置,這個名稱是固定的,三個命令的工作就是讓這個HEAD來回走;
    (3)index指標;
    (4)work directory指標。
  4. git commit 的本質是讓index/work directory/HEAD指向同一個commit。
    commit
  5. Git 不會主動刪除 任何一個commit,無論這個commit是否在某個分支的路徑中,或者HEAD是否指向它,所以理論上無論任何時候,只要知道commit-id就可以到達這個commit。

二、命令比較

2.1 Git 測試樹

現在說完基礎知識,在建一個測試環境,Git樹如下:
這裡寫圖片描述

$ git log --all --graph --oneline # 檢視Git樹資訊

master下有dev和other兩個分支,我們的測試主要在dev分支下,不用考慮other分支。dev分支下有個log.txt檔案,檔案內容和當前的commit內容相同

2.2 git revert 命令

現在先來說revert

$ git reset 2e5e386               # 也就是取消dev2這個commit
重置後取消暫存的變更:
M       log.txt
$ cat log.txt
<<<<<<< HEAD
dev4
=======
dev
>>>>>>> parent of 2e5e386... dev2
$ echo "revert dev2" > log.txt            
$ git add .
$ git revert --continue           # revert的話,最好用這個來提交

完成後,Git樹狀態如下:
這裡寫圖片描述
也就是說,git revert “commit-id”後,git 會比較”commit-id”與當前(也就是HEAD)內容的區別,由使用者修改完成後,再提交為新的commit。這個命令最好讓人理解。

2.3 git reset 命令

這個命令是這三個命令中最讓人迷糊的,有3個引數soft/mixed(預設)/hard。只要把這3個命令和Git的3個工作區對比就可以了。
先用圖說明下:
soft 引數
soft
mixed 引數
mixed
hard 引數
hard
配合第一節說的Git基礎原理,基本就可以明白git reset 在做什麼了。

但還有一個問題,如果reset當前分支好幾步的commit節點,如何處理?
答:Git取變化的檔案樹中最新的內容。

有點迷糊啊,那來個例子:
繼續之前的Git log。
先測試 soft 模式

$ git reset --soft 2e5e386
$ git status
位於分支 dev
要提交的變更:                   # !!! 這是add 過的狀態  !!!
  (使用 "git reset HEAD <檔案>..." 以取消暫存)
        修改:     log.txt
$ git checkout -- log.txt
$ cat log.txt
revert dev4                   # 這是log.txt檔案樹中最新的內容,HEAD指向了"dev2"

再測試 mixed 模式

$ git reset --hard 8785cbf    # 回到revert的位置
$ git reset 2e5e386           # 使用mixed模式到"dev2"
$ git status
位於分支 dev
尚未暫存以備提交的變更:          # !!! 這是需要 add 過的狀態  !!!
  (使用 "git add <檔案>..." 更新要提交的內容)
  (使用 "git checkout -- <檔案>..." 丟棄工作區的改動)
        修改:     log.txt
$ cat log.txt
revert dev4                   # 這是log.txt檔案樹中最新的內容,HEAD指向了"dev2"

最後測試 hard 模式 (很危險,會清除work directory未儲存的內容,最好commit後再使用這個模式)

$ git reset --hard 8785cbf    # 回到revert的位置
$ git reset --hard 2e5e386    
$ git status
位於分支 dev
無檔案要提交,乾淨的工作區

需要說明的是,git reset後,再commit時,新的commit並不在當前分支中,因為它確實不是一個有名字的分支,這時狀態是detached HEAD,如下圖。
這裡寫圖片描述
想要儲存,就在這時

$ git checkout -b new-branch-name

2.4 git rebase 命令

這個命令很好理解,就是不想合併分支時,把某個分支的commit自動新增到另一個分支之後,用下圖可以比較清楚地解釋這個命令。
這裡寫圖片描述