git merge、git rebase、git reset、git revert、git fetch、git pull、git reflog……你知道這些 git 指令執行的究竟是什麼任務嗎?如果你還有些分不清楚,那千萬不能錯過這篇文章。在本文中,熟知 JavaScript、TypeScript、GraphQL、Serverless、AWS、Docker 和 Golang 的 21 歲年輕軟體顧問 Lydia Hallie 透過動圖形式直觀地介紹了這些常用 git 指令的工作過程,包你過目不忘。
儘管Git 是一款非常強大的工具,但如果我說Git 用起來簡直是惡夢,大多數人也會認同我的說法。我發現在使用 Git 時,在頭腦裡可視化地想像它會非常有用:當我執行一個特定命令時,這些分支會如何交互,又會怎樣影響歷史記錄?為什麼當我在 master 上執行硬重啟,force push 到原始分支以及 rimraf 我們的 .git 資料夾時,我的同事哭了? 我覺得創建一些最常用且最有用的 Git 命令的視覺化範例會是一個完美的用例!下面我將介紹的許多指令都有可選參數-你可以使用這些參數來改變對應指令的行為。而我的範例只會涵蓋命令的預設行為,而不會添加(或添加太多)可選配置! #擁有多個分支是很方便的,這樣可以將不同的新修改互相隔離開,而且還能確保你不會意外地向生產代碼推送未經許可或破損的代碼修改。但一旦這些修改得到了批准許可,我們就需要將其部署到我們的生產分支中! 可將一個分支的修改融入另一個分支的一種方式是執行 git merge。 Git 可執行兩種類型的合併:fast-forward 和 no-fast-forward。現在你可能分不清,但我們馬上就來看看它們的差異。 在當前分支相比於我們要合併的分支沒有額外的提交(commit)時,可以執行fast-forward 合併。 Git 很懶,首先會嘗試執行最簡單的選項:fast-forward!這類合併不會建立新的提交,而是會將我們正在合併的分支上的提交直接合併到目前分支。 完美!現在,我們在 dev 分支上所做的所有變更都合併到了 master 分支上。那麼 no-fast-forward 又是什麼意思呢? 如果你的當前分支相比於你想要合併的分支沒有任何提交,那當然很好,但很遺憾現實情況很少如此!如果我們在目前分支上提交我們想要合併的分支不具備的改變,那麼 git 將會執行 no-fast-forward 合併。 使用 no-fast-forward 合併時,Git 會在目前活動分支上建立新的 merging commit。這個提交的父親提交(parent commit)即指向這個活動分支,也指向我們想要合併的分支! 沒什麼大不了的,完美的合併!現在,我們在 dev 分支上所做的所有變更都合併到了 master 分支上。
#儘管Git 能夠很好地決定如何合併分支以及如何向文件添加修改,但它並不總是完全自己做決定。當我們想要合併的兩個分支的同一檔案中的同一行程式碼上有不同的修改,或者一個分支刪除了一個檔案而另一個分支修改了這個檔案時,Git 就不知道如何取捨了。 #####################在這樣的情況下,Git 會問你想要保留哪一個選項?假設在這兩個分支中,我們都編輯了 README.md 的第一行。 ############如果我們想把dev 合併到master,就會出現一個合併衝突:你想要標題是Hello! 還是Hey!?
當嘗試合併這些分支時,Git 會向你展示衝突出現的位置。我們可以手動移除我們不想保留的修改,保存這些修改,再次新增這個已修改的文件,然後提交這些修改。 完成!儘管合併衝突往往很讓人厭煩,但這是合理的:Git 不應該瞎猜我們想要保留哪些修改。
#我們剛看到可透過執行git merge 將一個分支的修改套用到另一個分支。另一種可將一個分支的修改融入另一個分支的方式是執行 git rebase。 git rebase 會將目前分支的提交複製到指定的分支之上。 完美,現在我們在 dev 分支上取得了 master 分支上的所有修改。 變基與合併有一個重大的差異:Git 不會嘗試確定要保留或不保留哪些檔案。我們執行 rebase 的分支總是含有我們想要保留的最新近的修改!這樣我們不會遇到任何合併衝突,而且可以保留一個漂亮的、線性的 Git 歷史記錄。 上面這個範例展示了在 master 分支上的變基。但是,在更大型的專案中,你通常不需要這樣的操作。 git rebase 在為複製的提交建立新的 hash 時會修改專案的歷史記錄。 如果你在開發一個 feature 分支並且 master 分支已經更新過,那麼變基底就很好用。你可以在你的分支上取得所有更新,這能防止未來合併衝突。 互動式變基(Interactive Rebase)#在為提交執行變基之前,我們可以修改它們!我們可以使用互動式變基來完成這項任務。互動式變基在你目前開發的分支上以及想要修改某些提交時會很有用。
在我們正在rebase 的提交上,我們可以執行以下6 個動作:
很棒!這樣我們就能完全控制我們的提交了。如果你想要移除一個提交,只需 drop 即可。
#如果你想把多個提交融合到一起以便得到清晰的提交歷史,那也沒有問題!
互動式變基能為你在 rebase 時提供大量控制,甚至可以控制目前的活動分支。
#當我們不想要先前提交的修改時,就會用到這個指令。也許這是一個 WIP 提交或可能是引入了 bug 的提交,這時候就要執行 git reset。
git reset 能讓我們不再使用目前檯面上的文件,讓我們可以控制 HEAD 應該指向的位置。
軟重置
#軟重置會將HEAD 移至指定的提交(或與HEAD 相比的提交的索引),而不會移除該提交之後加入的修改!
假設我們不想保留新增了一個 style.css 檔案的提交 9e78i,而且我們也不想保留新增了一個 index.js 檔案的提交 035cc。但是,我們確實又想要保留新加入的 style.css 和 index.js 檔案!這是軟重置的一個完美用例。
輸入 git status 後,你會看到我們仍然可以存取在之前的提交上所做的所有修改。這很好,這意味著我們可以修復這些文件的內容,之後再重新提交它們!
硬重置
有時候我們不想保留特定提交引入的修改。不同於軟重置,我們應該再也無需訪問它們。 Git 應該直接將整體狀態直接重置到特定提交之前的狀態:這甚至包括你在工作目錄和暫存檔案上的修改。
Git 丟棄了 9e78i 和 035cc 引入的修改,並將狀態重置到了 ec5be 的狀態。
另一種撤銷修改的方法是執行git revert。透過對特定的提交執行還原操作,我們會建立一個包含已還原修改的新提交。
假設 ec5be 新增了一個 index.js 檔案。但之後我們發現其實我們再也不需要由這個提交引入的修改了。那就還原 ec5be 提交吧!
完美!提交 9e78i 還原了由提交 ec5be 引入的修改。在撤銷特定的提交時,git revert 非常有用,同時也不會修改分支的歷史。
### ##########當一個特定分支包含我們的活動分支所需的某個提交時,我們對那個提交執行cherry-pick!對一個提交執行 cherry-pick 時,我們會在活動分支上建立一個新的提交,其中包含由揀選出來的提交所引入的修改。 ############假設 dev 分支上的提交 76d12 為 index.js 檔案新增了一項修改,而我們希望將其整合到 master 分支中。我們並不想要整個 dev 分支,而只需要這個提交! #現在 master 分支包含 76d12 引入的修改了。
如果你有一個遠端 Git 分支,例如在 GitHub 上的分支,當遠端分支上包含目前分支沒有的提交時,可以使用取回。例如合併了另一個分支或你的同事推送了一個快速修復。
透過在這個遠端分支上執行 git fetch,我們就可以在本地取得這些修改。這不會以任何方式影響你的本地分支:fetch 只是單純地下載新的資料而已。
#現在我們可以看到自上次推送以來的所有修改了。這些新數據也已經在本地了,我們可以決定用這些新數據做什麼了。
#儘管git fetch 可用來取得某個分支的遠端訊息,但我們也可以執行git pull。 git pull 其實是兩個指令合成了一個:git fetch 和 git merge。當我們從來源拉取修改時,我們首先是像 git fetch 那樣取回所有數據,然後最新的修改會自動合併到本地分支。
很好,我們現在與遠端分支完美同步了,也有了所有最新的修改!
每個人都會犯錯,但犯錯其實沒啥!有時候你可能會覺得你把 git repo 完全搞壞了,讓你想完全刪了事。
git reflog 是一個非常有用的指令,可以顯示已經執行過的所有動作的日誌。包括合併、重置、還原,基本上包含你對你的分支所做的任何修改。
如果你犯了錯,你可以根據 reflog 提供的資訊透過重置 HEAD 來輕鬆地重做!
假設我們其實並不需要合併原有分支。當我們執行 git reflog 指令時,我們可以看到這個 repo 的狀態在合併前位於 HEAD@{1}。那我們就執行一次 git reset,將 HEAD 重新指向在 HEAD@{1} 的位置。
以上是幾十張動圖告訴你 Git到底是怎麼玩的的詳細內容。更多資訊請關注PHP中文網其他相關文章!