最近在開發專案的一個小需求的時候,發生了一件尷尬的事情。那就是當我把新功能開發完成的時候,忽然發現自己開發使用的分支是錯誤的分支。不過我記得之前學習git
的時候有一個git stash
的命令可以把當前沒有提交的內容存檔起來,然後可以在切換分支之後把當前的存檔應用到目標分支。不過因為平時不怎麼使用這個命令,所以有點生疏了,需要再次去看看文件。
花了十幾分鍾,把git stash
相關的命令又再次溫習了一下,接著就順利地把這個問題給解決掉。因為平時的開發也都是遵循相關的git
流程,一般不會出現什麼錯誤,而且平時使用的git
命令也都是一些常用的。這次出現這個問題有一部分原因是因為這個專案不是一個長期維護的專案,當開發新功能的時候,一開啟專案,就以為還在自己的開發分支。也沒及時檢查一下開發的分支是否正確。更深層次的原因還是因為git
掌握得不夠好。也正好借這個機會,把相關的命令再次好好複習一下,也挺好的。
其實當你在錯誤的分支開發了新功能之後,這裡會有三種情況:
- 新功能還沒有在本地進行
commit
(提交),也就是我這次遇到的情況 - 新功能已經在本地提交了,但是還沒有
push
到遠端倉庫 - 新功能已經在本地提交了,且
push
到了遠端倉庫
雖然我遇到的是第一種情況,那麼當我解決這個問題之後,我很自然的就會想:如果遇到了另外兩種情況我該怎麼處理呢?這篇文章就跟大家一起探討一下針對上述三種情況下,如果你在錯誤的分支開發了新功能,我們應該怎麼做。
新功能還沒有在本地進行commit
(提交)
在這種情況下我們可以在當前分支下使用:
git stash
這個命令表示把我們當前修改的內容暫存起來,然後我們的工作區就恢復到在沒有開發新功能之前的樣子。
這個時候我們需要切換到正確的工作分支,然後執行命令:
git stash apply
這個命令表示把我們之前暫存的內容,應用到當前分支。這樣我們就相當於把修改的內容從一個分支移動到了另一個分支,是不是很簡單呢。
上面那兩個命令也是我解決這個問題中使用的命令。我覺得不能滿足於只解決這個問題,我需要詳細的瞭解一下有關git stash
的命令,接下來的內容是關於git stash
的一些深入的內容,我們不僅要知其然,還要知其所以然。
首先我們需要知道使用git stash
命令會把我們工作區和暫存區的修改儲存下來,然後將這些修改的內容從當前的檔案中移出並儲存在存檔庫裡面。所以我們就回到了之前沒有修改過內容的乾淨的工作區。
git stash
在沒有新增任何引數的時候相當於git stash push
命令,我們使用git stash
建立一個當前修改的快照的時候,命令執行完會給出如下的資訊:
Saved working directory and index state WIP on <branchname>: <hash> <commit message>
其中branchname
是你當前所在分支的名字,hash
是當前分支最近一次提交的hash值,commit message
就是你最近的一次提交的時候新增的提交資訊。
對於當前只想儲存一個快照的情況下使用git stash
是比較方便直觀的,如果你在當前分支想儲存多個快照,那麼最好給每一個快照新增一些解釋資訊,以便使用的時候能夠知道每一個快照都是幹嘛的。
我們可以使用git stash push -m message
來給每一個快照新增詳細的說明資訊,比如:
git stash push -m “add feature 1”
在這個命令列執行完成之後,在終端上會顯示如下的資訊:
Saved working directory and index state On <branchname>: add feature 1
根據終端顯示的資訊,我們可以知道當前這個快照是在那個分支產生的,並且有了add feature 1
這個詳細的描述,等到以後使用的時候會更加的清楚一點。
當我們有了很多快照的時候,我們可能想看一下當前的快照列表。這個時候我們可以使用git stash list
來看一下當前的快照列表。在終端執行git stash list
後,如果你在之前新增了一些快照的話,會顯示如下的一些資訊:
stash@{0}: On <branchname>: add feature 1
stash@{1}: On <branchname>: add feature 0
stash@{2}: On <branchname>: <message>
stash@{3}: WIP on <branchname>: 47e52ae <commit message>
stash@{4}: On <branchname>: <message>
從上面的資訊中我們可以知道最新的快照是排在最上面的,儲存快照的是一個棧,所以最新新增的快照是放在最上面的。
如果我們想檢視最近一次快照跟生成快照當時已提交的檔案之間的變化情況的話,可以使用命令git stash show
。這個命令預設展示的是檔案的差別統計。如果想展示具體改動的內容的話,可以使用git stash show -p
。
因為我們有不止一個快照,所以我們還想要看之前的快照跟產生這個快照當時已提交的版本之間的差異的話,我們可以在上面的命令後面新增快照的索引,比如如果你想看上面add feature 0
這個快照的檔案變動的話,可以使用下面的命令:
git stash show stash@{1} # 簡略的資訊
git stash show -p stash@{1} # 詳細的內容更改
接下來就到了應用(恢復)快照的時候了,如果這時候你想把某個快照的內容應用於當前的分支的話,只需要執行命令:
git stash apply # 將最新的快照內容應用於當前分支
git stash apply stash@{n} # n表示具體的快照索引
這樣就可以把之前保留的快照內容應用到當前的版本中了,在應用快照的過程中可能會產生衝突,這時候需要手動把衝突的內容處理一下,然後再次提交就可以了。
git stash apply
可以新增--index
引數,這個引數的作用是在應用快照的時候,會把之前已經新增到暫存區(索引區)的更改依舊儲存在暫存區,如果不新增這個引數的話,所有的變更都會變成在工作區的變更(也就是沒有儲存在索引區的狀態)。
我們可以測試一下,對一個檔案進行更改,然後把更改新增(使用git add
)到暫存區,然後再次新增一個更改,這次不新增到暫存區。我們執行git status
命令會看到如下的內容:
On branch dev
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: 20200830/index.html
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: 20200830/README.md
當我們執行git stash
命令,然後執行git stash apply
命令之後,會看到如下資訊:
On branch dev
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: 20200830/README.md
modified: 20200830/index.html
no changes added to commit (use "git add" and/or "git commit -a")
所以當不使用--index
引數的時候,不會儲存之前在暫存區的狀態。
關於git stash
還有一些其它的命令,比如:
git stash drop
:丟棄一個快照git stash pop
:應用最新的快照到當前分支,如果應用成功的話就把這個快照從儲存快照的棧中移除git stash clear
:清除所有的快照
關於git stash
一些常用的命令和操作上面已經講解的差不多啦,如果大家想繼續瞭解更多的話,可以參考git-stash。
上面的內容主要是在我們新開發的功能還沒有提交的情況下所做的一些處理,當我們開發的新功能已經在本地提交了的情況下,我們該如何處理呢?接下來我們就來探討一下這個問題。
新功能已經在本地提交了,但是還沒有push
到遠端倉庫
如果新開發的功能已經在本地提交了,但是我們開發的這個分支是一個錯誤的分支。這個時候根據情況的不同,可以有兩種處理的方式。
新的功能需要新增在一個新的分支
首先我們需要知道在我們新增新功能之前,當前分支處於哪一個提交。可以執行命令:
git log --oneline
檢視當前分支的提交,可以看到有以下內容的輸出:
085095f (HEAD -> master) update 5
47e52ae update 3
14fefac update 2
fd01444 add README.md
3c76ad1 init
找到我們新增新功能時,當前分支所處的提交。假如是fd01444
,那麼我們接下來要做的操作就是將HEAD
指標指向fd01444
,也就是把我們當前分支已提交的內容重置到我們開發新功能之前的樣子。我們需要執行下面的命令:
git reset fd01444 # fd01444是某次提交的hash值
如果沒有指明重置的模式的話,預設會使用--mixed
模式,這樣的話我們在fd01444
這次提交之後的所有提交都會被重置為沒有提交的狀態。接下來我們需要把這些新開發的功能遷移到一個新的分支。這時候我們可以使用下面的命令進行操作:
git checkout -b <newbranch>
這樣我們就建立了一個新的分支,並且把新新增的功能也都遷移了過去,接下來就是常規的新增和提交操作了。
新功能需要新增在另一個分支上
如果我們需要把當前新增的新功能遷移到另一個已經存在的分支,那麼我們需要做的前幾個步驟跟上面的操作是一樣的:
git log --oneline # 查詢新功能開發之前的提交
git reset <commit hash> # 將當前分支重置到新功能開發之前的提交
接下來我們現在的狀態就回到了新功能還沒有提交的狀態,那麼就可以繼續使用git stash
相關的命令去操作了。
我們還有另外一個方法也能夠將已提交到當前分支的功能新增到另一個分支上,那就是使用git cherry-pick
命令。首先我們還是先用git log --oneline
查詢當前已提交的功能的hash值,然後切換到目標分支,執行命令:
git cherry-pick <commit hash>
這樣就把我們在另一個分支開發的功能,新增到我們想要的分支了。如果有衝突的話,需要手動處理一下衝突。然後我們回到最初的分支,再次執行git reset <commit hash>
命令,把已提交的內容進行重置,然後執行命令:
git checkout -- .
把當前分支沒有新增到暫存區的內容都清除掉,這樣也可以達到我們上面所說的,把新功能新增到另一個分支的目的。
新功能已經在本地提交了,且push
到了遠端倉庫
第三種情況就是,我們已經把新開發的功能push
到遠端的倉庫了,但是我們忽然發現新功能不應該在這個分支開發,我們這個時候應該怎麼辦呢?
首先我們應該保持當前的工作區是沒有修改的,是一個乾淨的狀態。不然使用撤銷命令的時候會提示你需要把當前的檔案內容變更先提交或者生成快照。當我們的工作區的狀態是乾淨的時候,我們就可以進行撤銷操作了。
首先需要知道我們應該撤銷那一次提交的狀態。使用git log --oneline
檢視要撤銷的提交的索引,然後執行下面的命令:
git revert <commit>
這個時候命令執行的終端會進入編輯器模式,讓你填寫提交的資訊。當然你也可以使用引數--no-edit
這樣就不會在進行撤銷操作的時候開啟編輯模式了。
如果需要撤銷的提交比較多的話,我們可以使用..
表示一個提交記錄的範圍。比如c1..c2
就表示c2
的可達提交,且排除c1
的可達提交。所謂可達的提交指的是:提交本身及其祖先鏈中提交的集合。
我們可以舉個例子:
... a - b - c - d - HEAD
如果上面表示的是某個分支的提交記錄,那麼對於b..d
表示的就是c d
這兩個提交,對於a..d
表示的就是b c d
這三個提交。如果大家想了解更多相關的內容,可以在git-rev-list這裡深入的學習一下。
所以我們如果想快速的撤銷一段範圍的提交的話,可以執行類似下面這樣的命令:
git revert 54dc134..a72d612 --no-edit
上述命令的54dc134
就表示c1
,a72d612
就表示c2
,--no-edit
表明我們在執行撤銷操作的時候不開啟編輯模式。
我們如果需要對遠端的分支進行撤銷的話,首先考慮的就是使用git revert
命令,因為git revert
命令不會修改歷史的提交記錄,只是在原來的提交基礎上新增新的提交,所以不會造成程式碼的丟失。在多人合作的情況下使用git revert
命令撤銷push
到遠端的操作還是很有必要的。
如果大家對於上面的這些問題有更好的解決方案的話,歡迎大家在文章下面留言,我們可以一起探討一下,一起進步。如果你對文章有什麼意見和建議的話也歡迎在文章下面留言,或者在這裡提出來,我會持續努力改進的。也歡迎大家關注我的公眾號關山不難越,及時獲取最新的文章更新。
參考連結: