標題:我如何教 Git
發表:真實
描述:
標籤: git, 學習
canonical_url:https://blog.ltgt.net/teaching-git/
封面圖片:https://marklodato.github.io/visual-git-guide/conventions.svg.png
我使用 Git 已經十幾年了。八年前,我必須為一家即將建立開源專案的合作夥伴公司舉辦有關 Git(和 GitHub)的培訓課程,我將在這裡向您介紹我的教學方式。順便說一句,從那時起,我們在工作中建立了使用相同(或類似)方法的內部培訓課程。話雖如此,我並沒有發明任何東西:這很大程度上受到了其他人之前寫的內容的啟發,包括[the <cite>Pro Git</cite> book](https://git-scm. com/book/),儘管順序不同,但 <abbr title="in my view">IMO</abbr> 可以有所作為。
我寫這篇文章的原因是,多年來,我不斷看到人們實際上使用 Git,但沒有真正理解他們在做什麼;他們正在使用 Git。他們要么被鎖定在一個非常具體的工作流程中,他們被告知要遵循,並且無法適應另一個開源專案正在使用的工作流程(這也適用於開源維護人員並不真正了解外部貢獻者如何使用 Git) ),或者如果任何事情沒有按照他們想像的方式執行,或者他們在呼叫Git 命令時犯了錯誤,他們就會完全迷失。我受到 Julia Evans 對 Git 的(更新)興趣的啟發而寫下來,因為她有時會在社交網絡上徵求評論。
我的目標不是真正教你有關 Git 的知識,而是更多地分享我教授 Git 的方法,以便其他可能會教導的人從中獲得靈感。因此,如果您正在學習 Git,那麼這篇文章並不是專門為您而寫的(抱歉),因此可能不是自給自足的,但希望其他學習資源的連結足以填補空白,使其成為也是有用的學習資源。如果您是視覺學習者,這些外部學習資源都是有插圖的,甚至是視覺學習的。
一旦我們清楚了為什麼我們使用VCS(版本控制系統)來記錄commits 中的更改(或者換句話說,我們_將我們的更改_提交到歷史記錄;我假設你對這個術語有一定的熟悉),讓我們多了解一下Git具體來說。
我認為理解 Git 至關重要的一件事是獲得其背後概念的準確心理模型。
首先,這並不是很重要,但Git 實際上並沒有記錄changes,而是記錄我們文件的snapshots(至少在概念上是這樣;它將使用packfiles 來有效地儲存內容,並且在某些情況下方實際上會儲存changes –diffs–),並且會按需產生差異。不過,這有時會顯示在某些命令的結果中(例如為什麼某些命令顯示一個檔案被刪除而另一個檔案被加入,而其他命令顯示一個檔案被重新命名)。
現在讓我們深入探討一些 Git 概念,或是 Git 如何實現一些常見的 VCS 概念。
Git commit 是:
一個或多個父親提交,或第一次提交沒有父親提交 (root)
提交訊息
作者和作者日期(實際上是帶有時區偏移的時間戳)
提交者和提交日期
和我們的檔案:相對於儲存庫根的路徑名、mode(UNIX 檔案系統權限)及其內容
每次提交都會獲得一個標識符,該標識符是透過計算該資訊的 SHA1 雜湊值確定的:更改逗號,您將獲得不同的 SHA1,即不同的提交物件。 (<abbr title="For What it's value">Fwiw</abbr>,Git 正在慢慢轉向 SHA-256 作為哈希功能)。
Git 的儲存是內容尋址,這表示每個_物件_都使用直接從其內容派生的名稱進行存儲,並採用 SHA1 雜湊的形式。
從歷史上看,Git 將所有內容儲存在文件中,我們仍然可以這樣推理。文件的內容儲存為 blob,目錄儲存為 tree(一個文字文件,列出目錄中的文件及其名稱、模式和表示其內容的 blob 的 SHA1,以及其子目錄及其名稱和 SHA1他們的樹)
如果您想了解詳細訊息,Julia Evans(再次)寫了一篇令人驚嘆的[博客文章](https://jvns.ca/blog/2023/09/14/in-a-git-repository-- where-do-your-檔案-即時-/);或者您可以從 <cite>Pro Git</cite> 書中閱讀。
<圖>
<img src=https://git-scm.com/book/en/v2/images/commit-and-tree.png width=800 height=443 alt='包含5 個框的圖表,分為3 列,每個框標有 5 位 SHA1 前綴;左邊的子標籤為“commit”,包含元資料“tree”,中間是框的 SHA1,“author”和“committer”的值均為“Scott”,文字為“The initial commit of我的專案”;中間的框被子標記為“tree”,包括三行,每行標記為“blob”,其餘 3 個框的 SHA1 以及看起來像文件名的內容:“README”、“LICENSE”和“test.rb” ”;最後 3 個框,在右側垂直對齊,都是子標籤為「blob」的內容,包含看起來像是 README、LICENSE 和 Ruby 原始檔內容開頭的內容;有箭頭連結框:提交指向樹,樹指向 blob。'>
<figcaption>提交及其樹(來源:<a src=https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell><cite>Pro Git</引用></a>)</figcaption>
</圖>
commit 中的父親提交 建立一個代表我們歷史的有向無環圖:有向無環圖 由連結的節點(我們的提交)組成與有向邊一起(每個提交連結到其父提交,有一個方向,因此directed)並且不能有循環/循環(提交永遠不會是它自己的祖先,它的祖先提交都不會連結到它作為父提交)。
<圖>
<img src=https://git-scm.com/book/en/v2/images/commits-and-parents.png width=800 height=265 alt='包含 6 個框排列成 2 行 3 列的圖表;第一行的每個框都標有 5 位 SHA1 前綴,子標籤為“commit”,元資料“tree”和“parent”均帶有 5 位 SHA1 前綴(每次都不同)、“author”和“ committer」的值都是“Scott”,以及一些代表提交訊息的文字;左邊的盒子沒有「父」值,另外兩個盒子將左邊的盒子的 SHA1 作為「父」;這些框之間有一個箭頭,指向代表「父」的左側;順便說一句,左邊的框與上圖中的提交框具有相同的 SHA1 和相同的內容;最後,每個提交框也指向其下方的一個框,每個框都標記為「快照 A」、「快照 B」等,並且可能代表從每個提交連結的「樹」物件。'>
<figcaption>提交及其父級(來源:<a src=https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell><cite>Pro Git</ cite ></a>)</figcaption>
</圖>
現在 SHA1 哈希對於人類來說是不切實際的,雖然 Git 允許我們使用唯一的 SHA1 前綴而不是完整的 SHA1 哈希,但我們需要更簡單的名稱來引用我們的提交:輸入 references。這些是我們選擇的提交的標籤(而不是 Git)。
有幾種參考:
branches 是moving 引用(請注意,main
或master
並不特殊,它們的名稱只是一個約定)
*_標籤_是_不可變_引用
HEAD
是一個特殊的引用,指向當前提交。它通常指向一個分支而不是直接指向一個提交(稍後我們會看到原因)。當一個引用指向另一個引用時,這稱為符號引用。
Git 會在某些操作期間為您設定其他特殊參考(FETCH_HEAD
、ORIG_HEAD
等)
<圖>
<img src=https://git-scm.com/book/en/v2/images/branch-and-history.png width=800 height=430 alt='帶有 9 個框的圖; 6 個盒子的排列方式與上圖相同,並且標記相同(三個提交及其 3 個樹);最右邊(最新)提交上方的兩個框,箭頭指向它,分別標記為“v1.0”和“master”;最後一個框位於“master”框上方,有一個箭頭指向它,並標記為“HEAD”。'>
<figcaption>分支及其提交歷史記錄(來源:<a src=https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell><cite>Pro Git< /引用></a>)</figcaption>
</圖>
當您在 Git 儲存庫中工作時,您在 Git 歷史記錄中操作和記錄的檔案位於您的_工作目錄_中。要建立提交,您需要在 index 或_暫存區域_中_暫存_檔案。完成後,您附加一則提交訊息並將您的_staged_檔案移至history。
為了關閉循環,_工作目錄_是根據_歷史記錄_中的給定提交進行初始化的。
<圖>
<img src=https://git-scm.com/book/en/v2/images/areas.png width=800 height=441 alt='包含3 位參與者的序列圖:「工作目錄」、「暫存區域」和「.git directpry(儲存庫)」;有一條“簽出專案”訊息從“.git 目錄”到“工作目錄”,然後從“工作目錄”到“暫存區域”進行“階段修復”,最後從“暫存區域”進行“提交”區域」到「.git 目錄」。'>
<figcaption>工作樹、暫存區域和 Git 目錄(來源:<a href="https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F#_the_third_states" ><cite>Pro Git</cite></a>)</figcaption>
</圖>
並非所有檔案都需要_追蹤_歷史記錄:由建置系統(如果有)產生的檔案、特定於您的編輯器的檔案以及特定於您的作業系統或其他工作環境的檔案。
Git 允許定義要忽略的檔案或目錄的命名模式。這實際上並不意味著Git 會忽略它們並且無法跟踪,但如果不跟踪它們,多個Git 操作將不會向您顯示它們或操縱它們(但您可以手動將它們加入到歷史記錄中,並且從那時起,他們將不再被忽略)。
忽略檔案是透過將路徑名稱(可能使用 glob)放入忽略檔案中來完成的:
儲存庫中任何位置的 .gitignore
檔案定義了包含目錄的忽略模式;這些忽略文件會在歷史記錄中被跟踪,作為開發人員之間共享它們的一種方式;在這裡,您將忽略建置系統產生的那些檔案(Gradle 專案的“build/”,Eleventy 網站的“_site/”等)
.git/info/excludes
是您機器上的本機儲存庫;很少使用,但有時很有用,所以很高興了解一下
最後 ~/.config/git/ignore
對機器來說是全域的(對你的使用者);在這裡,您將忽略特定於您的電腦的文件,例如特定於您使用的編輯器的文件,或特定於您的作業系統的文件(例如macOS 上的“.DS_Store”或Windows 上的“Thumbs. db”) )
這是所有這些概念的另一種表示:
<圖>
<img src=https://marklodato.github.io/visual-git-guide/conventions.svg width=907 height=529 alt='有 10 個框的圖; 5 個框在中心排成一行,標有 5 位 SHA1 前綴,它們之間有從右向左指向的箭頭;一條註釋將它們描述為“提交物件,由 SHA-1 哈希標識”,另一條註釋將其中一個箭頭描述為“子項指向父項”;一對框(看起來像一個水平分割成兩個框的單一框)位於最右邊(最新)提交的上方,有一個向下指向它的箭頭,該對的上面的框被標記為“HEAD”並描述為“引用當前分支”;下面的框被標記為“main”並被描述為“目前分支”;第七個框位於另一個提交上方,有一個向下指向它的箭頭;它被標記為“穩定”並被描述為“另一個分支”;最後兩個框位於提交歷史記錄下,一個在另一個之上;最底部的框標記為“工作目錄”並描述為“您'看到'的文件”,它和提交歷史記錄之間的另一個框標記為“階段(索引)”並描述為“要存取的文件”在下次提交中”。'>
<figcaption>提交、引用和區域(來源:<a href=https://marklodato.github.io/visual-git-guide/index-en.html#conventions><cite>可視化 Git 參考</cite >< /a>,馬克‧洛達托)</figcaption>
</圖>
這就是我們開始討論 Git 指令以及它們如何與圖表互動的地方:
git init
初始化一個新的儲存庫
git status
取得檔案狀態的摘要
git diff
顯示任意兩個工作目錄、索引、HEAD
之間的更改,或實際上任何提交之間的更改
git log
顯示並搜尋您的歷史記錄
建立提交
git add
將檔案加入index
git commit
將index 轉換為commit (帶有新增的commit 訊息)
git add -p
以互動方式將檔案新增至 index:選擇要新增的變更以及僅將哪些變更保留在工作目錄中,逐一檔案、逐個部分(稱為 hunk)
管理分支機構
gitbranch
顯示分支,或建立分支*git switch
(也稱為git checkout
)將分支(或任何提交,實際上是任何樹)簽出到您的工作目錄
git switch -b
(也稱為 git checkout -b
)作為 gitbranch
和 gitswitch
的捷徑git grep
搜尋您的工作目錄、索引或任何提交;這是一種增強的“grep -R”,它支援 Git
gitblame
來了解更改給定文件每一行的最後一次提交(因此,誰應該為錯誤負責)
git stash
將未提交的更改放在一邊(這包括_staged_文件,以及工作目錄中的_tracked_文件),然後_unstash_它們。
當您建立提交(使用「git commit」)時,Git 不僅建立提交物件,還移動「HEAD」以指向它。如果「HEAD」實際上指向一個分支(通常是這種情況),Git 會將該分支移動到新的提交(並且「HEAD」將繼續指向該分支)。每當當前分支是另一個分支的祖先(該分支指向的提交也是另一個分支的一部分)時,提交將使“HEAD”移動相同,並且分支將發散。
當您切換到另一個分支(使用“git switch”或“git checkout”)時,“HEAD”會移至新的目前分支,並且您的工作目錄和索引將設定為重新組合該提交的狀態(未提交的更改將暫時保留;如果 Git 無法做到這一點,它將拒絕切換)。
如需更多詳細資訊和視覺表示,請參閱commit 和[checkout](https://marklodato. github .io/visual-git-guide/index-en.html#checkout)Mark Lotato 的<cite>可視化Git 參考</cite>的部分(請注意,該參考是幾年前寫的,當時git switch
和 git Restore
不存在,而 git checkout
是我們所擁有的一切;因此 checkout 部分涵蓋的內容比 git switch
多一點)。
當然,<cite>Pro Git</cite> 這本書也是一個很好的視覺表示參考; <cite>Branches in a Nutshell</cite> 子章節 涵蓋了所有內容的很大一部分上述的。
正如我們在上面所看到的,由於其內容尋址存儲,對提交的任何“更改”(例如使用“git commit --amend”)實際上都會導致不同的提交(不同的 SHA1)。 _舊提交_不會立即消失:Git 使用_垃圾收集_最終刪除無法從任何_引用_存取的提交。這意味著,如果您設法找回提交SHA1,則可以恢復許多錯誤(“git reflog”可以在此處提供幫助,或者符號“branch-name>@{<n}”,例如“main@{ 1}”) main
在更改之前指向的最後一次提交)。
我們在上面已經看到了分支是如何發散的。
但分歧要求最終_合併_變回來(使用“git merge”)。 Git 在這方面非常擅長(我們稍後會看到)。
合併的一個特殊情況是目前分支是要合併到的分支的祖先。在這種情況下,Git 可以執行 fast-forward merge。
由於兩個分支之間的操作可能始終針對同一對分支,因此 Git 允許您設定一個分支來追蹤另一個分支。另一個分支被稱為_追蹤_它的分支的上游。例如,設定時,「git status」將告訴您兩個分支彼此之間有多少分歧:目前分支是[最新](https://blog.ltgt.net/confusing-git-terminology /#your- branch-is-up-to-date-with-originmain) 及其上游分支,_後面_和[可以快轉](https://blog.ltgt.net/confusing-git-terminology/ #can-be- fast-forwarded),_超前_許多提交,或它們有分歧,每個提交都有一定數量。其他命令將使用該資訊為參數提供良好的預設值,以便可以省略它們。
要整合來自另一個分支的更改,而不是合併,另一種選擇是cherry-pick(使用同名命令)單一提交,而不包含其歷史記錄:Git 將計算該提交帶來的更改並將相同的更改應用於當前分支,建立一個與原始分支類似的新提交(如果您想了解更多有關Git 實際操作方式的訊息,請參閱Julia Evans 的<cite>如何gitcherry-pick 和revert 使用3 路合併< /cite> )。
最後,工具帶中的另一個指令是「rebase」。
您可以將其視為一次進行許多選擇的方法,但它實際上更強大(正如我們將在下面看到的)。但在其基本用途中,它只是這樣:您給它一系列提交(在作為起點的任何提交和作為終點的現有分支之間,預設為當前分支)和一個目標,並且它會挑選所有這些提交位於目標之上,並最終更新用作終點的分支。這裡的指令的形式是git rebase --onto=<target> <start> <end>
。與許多 Git 命令一樣,參數可以省略,並且具有預設值和/或特定含義:因此,git rebase
是 git rebase --fork-point upper
的簡寫,其中 upstream
是 [upstream]當前分支的(https://blog.ltgt.net/confusing-git-terminology/#untracked-files-remote-tracking-branch-track-remote-branch)(我會忽略`--fork-point`這裡,它的作用很微妙,在日常使用上並不那麼重要),它本身就是`git rebase upper HEAD的簡寫(其中
HEAD必須指向一個分支),它本身就是
git rebase 的簡寫-- on=upstream uploaded ,
git rebase --onto=upstream $(git merge-baseupstream HEAD) HEAD的簡寫,並將rebase
upstream的最後一個共同祖先與當前分支之間的所有提交另一方面,手和當前分支(即自從它們分歧以來的所有提交),並將它們重新應用到“上游”之上,然後更新當前分支以指向新的提交。明確使用
--onto` (其值與起始點不同)實際上很少見,請參閱[我之前的文章](https://blog.ltgt.net/confusing-git-terminology/#git- rebase- --onto) 對於一個用例。
我們無法在沒有互動式變體「git rebase -i」的情況下呈現「git rebase」:它以與非互動式變體完全相同的行為開始,但在計算需要完成的操作之後,它將允許您對其進行編輯(作為編輯器中的文字文件,每行一個操作)。預設情況下,所有選定的提交都是精心挑選的,但您可以對它們重新排序,跳過某些提交,甚至將某些提交合併到單一提交中。實際上,您可以挑選最初未選擇的提交,甚至建立合併提交,從而完全重寫整個歷史記錄!最後,您還可以停止對其進行編輯(然後使用“git commit --amend”,和/或可能在繼續變基之前建立新的提交),和/或在兩次提交之間執行給定的命令。最後一個選項非常有用(例如,驗證您沒有在歷史記錄的每個點上破壞您的專案),您可以在--exec
選項中傳遞該命令,Git 將在每個重新基底提交之間執行它(這也適用於非互動式變基;在互動模式下,當能夠編輯變基場景時,您將看到在每個櫻桃選擇行之間插入執行行)。
更多詳細資訊和視覺表示,請參閱merge、[cherry pick](https://marklodato . github.io/visual-git-guide/index-en.html#cherry-pick) 和 rebase Mark Lodato 的<cite>視覺化Git 參考</cite> 部分,以及[<cite>基本分支和合併</cite>](https://git-scm.com/book/en/v2/Git-分支-基本-分支和合併),[<cite>變基</cite>](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)和<cite>重寫歷史< /cite> <cite>Pro Git</cite> 書的子章節。
您也可以查看 David Drysdale 的 <cite>Git Visual Reference</cite> 中的「分支和合併」圖。
目前,我們只在我們的儲存庫中進行本地工作。
但 Git 是專門為與他人合作而建構的。
讓我介紹一下遙控器。
當您_複製_儲存庫時,該儲存庫將成為本機儲存庫的遠端,名為「origin」(就像「main」分支一樣,這只是預設值,名稱本身沒有什麼特別的,除了有時用作省略命令參數時的預設值)。然後,您將開始工作,建立本地提交和分支(因此從遠端forking),同時遠端可能會從其作者那裡獲得更多提交和分支。因此,您需要將這些遠端變更同步到本機儲存庫,並希望快速了解與遠端相比您在本機所做的變更。 Git 處理這個問題的方式是在一個特殊的命名空間中記錄它所知道的遠端(主要是分支)的狀態:「refs/remote/」。這些被稱為遠端追蹤分支。 Fwiw,本機分支儲存在「refs/heads/」命名空間中,標籤儲存在「refs/tags/」中(來自遠端的標籤通常直接「匯入」到「refs/tags/」中,因此例如您會遺失位置資訊他們來自)。您可以根據需要擁有任意多個遙控器,每個遙控器都有一個名稱。 (請注意,遙控器不一定位於其他電腦上,它們實際上可以位於同一台電腦上,直接從檔案系統存取,因此您無需進行任何設定即可使用遙控器。)
每當你從遠端 fetch 時(使用 git fetch
、git pull
或 git Remote update
),Git 都會與它對話以下載它還不知道的提交,並更新 remote-tracking遠端分支 。要取得的確切引用集以及取得它們的位置將傳遞給 git fetch
命令(如 refspecs )以及儲存庫的.git/config
中定義的預設值,預設由git clone
或git remote add
配置以取得所有分支(遠端上的refs/heads/
中的所有內容)並放置它們位於refs/remote/<remote>
中(因此origin
遙控器的refs/remote/origin/
)具有相同的名稱(因此遙控器上的refs/heads/main
變成refs/remote / origin/main
本地)。
<圖>
<img src=https://git-scm.com/book/en/v2/images/remote-branches-5.png width=800 height=577 alt='帶有3 個大方框的圖表,代表機器或儲存庫,包含代表提交歷史的較小框和箭頭;一個框標記為“git.outcompany.com”,子標記為“origin”,並包含名為“master”的分支中的提交;另一個框標記為“git.team1.outcompany.com”,子標記為“teamone”,並包含名為“master”的分支中的提交; 「origin」和「teamone」中的提交 SHA1 雜湊值相同,除了「origin」在其「master」分支上多了一個提交,即「teamone」在「後面」;第三個框標記為“我的電腦”,它包含與其他兩個框相同的提交,但這次分支被命名為“origin/master”和“teamone/master”;它還在名為“master”的分支中包含另外兩個提交,與遠端分支的較早點不同。'>
<figcaption>遠端和遠端追蹤分支(來源:<a href=https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches><cite>Pro Git</cite>< / a>)</figcaption>
</圖>
然後,您將使用與分支相關的命令來獲取從_遠端追蹤分支_到本地分支的更改(“git merge”或“git rebase”),或“git pull”,這只不過是“git fetch”的簡寫後面跟著
git merge或
git rebase`。 <abbr title="By the way">順便說一句</abbr>,在很多情況下,當你建立本地分支時,Git 會自動將_遠端追蹤分支_設定為本地分支的上游(它會告訴你相關資訊)當這種情況發生時)。
要與其他人共用您的更改,他們可以將您的儲存庫新增為遠端儲存庫並從中pull(意味著透過網路存取您的電腦),或者您可以_push_到遠端儲存庫。 (如果您要求某人從您的遙控器中提取更改,這稱為...拉請求,您可能在 GitHub 或類似服務中聽說過這個術語。)
推送與提取類似,相反:您將提交發送到遠端並更新其分支以指向新提交。作為安全措施,Git 只允許遠端分支快速轉送;如果您想推送以非快轉方式更新遠端分支的更改,則必須使用「git push --force-with-lease」(或「git push --force」)_force_它,但要小心:-- force-with-lease
將首先確保您的_遠端追蹤分支_與遠端分支是最新的,以確保自上次_fetched_以來沒有人將變更推送到分支;--force
不會執行該檢查,而是按照您的指示執行操作,風險由您自己承擔)。
與「git fetch」一樣,您可以將要更新的分支傳遞給「git push」命令,但如果您不這樣做,Git 會提供良好的預設行為。如果你不指定任何東西,Git 會從目前分支的上游推斷遠程,所以大多數時候 git push
相當於 git push origin
。這實際上是“git Push origin main”的簡寫(假設當前分支是“main”),它本身是“git Push origin main:main”的簡寫,是“git Push origin refs/heads/main:refs/”的簡寫heads/main,意思是將本地的
refs/heads/main推送到
origin遠端的
refs/heads/main`。有關使用不同來源和目標指定 refspecs 的一些用例,請參閱我之前的文章。
<圖>
<img src=https://lurklurk.org/gitpix/push2.svg width=1052 height=744 alt='代表「git push」指令的圖表,有四個 git 圖表(點,有些有標籤,用線連接) 排列成兩行兩列;列之間的箭頭表示左列是「之前」狀態,右列是「之後」狀態;上面一行中的圖位於雲內部,代表遠端儲存庫,並且有兩個分支,“master”和“other”,它們偏離了共同的祖先;左下圖與上面的圖形狀相同,只是標籤更改為“origin/master”和“origin/other”,並且每個分支有更多提交:與“origin”分支相比,“master”分支有兩個額外的提交/master”,而“other”比“origin/other”多了一個提交;與左上圖相比,右上圖在其「master」分支中多了兩次提交;右下圖與左下圖相同,除了「origin/master」現在指向與「master」相同的提交;換句話說,在「之前」狀態下,遠端缺少三個提交,而在「git Push」之後,本地「master」分支的兩個提交被複製到遠端,而「其他」保持不變。'>
<figcaption><code>git Push</code>(資料來源:<a href=https://lurklurk.org/gitpix/gitpix.html><cite>Git 視覺參考</cite></a>,David Drysdale )</圖標題>
</圖>
更多詳細資訊和視覺表示,請參閱<cite>遠端分支</cite>,< cite >使用遙控器</cite>,以及<cite>為專案做出貢獻</ cite> <cite>Pro Git</cite> 書的子章節,以及「處理遠程來自David Drysdale 的<cite>Git Visual Reference</cite> 的「儲存庫」圖表。
<cite>Pro Git</cite> 的<cite>為專案做出貢獻</cite>一章也涉及在GitHub 等平台上為開源專案做出貢獻,您必須先_fork_儲存庫,然後透過_pull requests_進行貢獻(或合併請求)。
這些是針對初學者的,希望不會引起太多爭議。
嘗試保留_clean_歷史記錄:
明智地使用合併提交
清晰且高品質的提交訊息(請參閱[<cite>提交指南</cite>](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project #_commit_guidelines)在<cite>Pro Git</cite> 中)
make atomic commits:每個提交應該獨立於歷史記錄中跟隨它的提交進行編譯和執行
這僅適用於您與他人分享的歷史記錄。
在本地,想怎麼做就怎麼做。對於初學者,我會給以下建議:
不要直接在“main”(或“master”,或您在遠端上沒有專門擁有的任何分支)上工作,而是建立本機分支;它有助於解耦不同任務的工作:即將開始處理另一個錯誤或功能,同時等待有關當前任務的說明的更多詳細資訊?切換到另一個分支,稍後您可以透過切換回來回到該分支;它還使從遠端更新變得更容易,因為如果您的本地分支只是同名遠端分支的副本,沒有任何本地更改(除非您想推送這些更改),您確信不會發生衝突到該分支)
毫不猶豫地重寫你的提交歷史記錄(git commit --amend
和/或 git rebase -i
),但不要太早這樣做;在工作時堆疊許多小提交是完全可以的,並且只在共享之前重寫/清理歷史記錄
同樣,請毫不猶豫地重新調整本機分支以整合上游變更(直到您共用該分支,此時您將遵循專案的分支工作流程)
如果出現任何問題並且您迷路了,我的建議是使用 gitk
或 gitk HEAD @{1}
,也可能使用 gitk --all
(我在這裡使用 gitk
但使用任何工具你喜歡),可視化你的Git 歷史並嘗試了解發生了什麼。由此,您可以回滾到先前的狀態(git reset @{1}
)或嘗試修復問題(擇優選擇提交等)。合併失敗,您可以使用“git rebase --abort”或“git merge - -abort」等命令中止並回滾到先前的狀態。
為了讓事情變得更簡單,請不要猶豫,在任何可能具有破壞性的命令(git rebase
)之前,建立一個分支或標籤作為“書籤”,如果事情沒有按預期進行,您可以輕鬆重置。當然,在執行這樣的命令後,請檢查歷史記錄和文件,以確保結果是您所期望的。
這只是其中的一小部分,還有更多值得探索!
分離的「HEAD」:git checkout
手冊頁 有一個關於該主題的很好的部分,另請參閱[我之前的帖子](https ://blog.ltgt.net/confusing-git-terminology/#detached-head-state),要獲得良好的視覺表示,請參閱[<cite>使用分離的HEAD 進行提交</ cite>](https:// /marklodato.github.io/visual-git-guide/index-en.html#detached) Mark Lodato 的 <cite>視覺化 Git 參考</cite> 部分。
Hooks:這些是可執行檔(大多數情況下是 shell 腳本),Git 將執行它們來回應儲存庫上的操作;人們使用它們在每次提交之前檢查程式碼(如果失敗則中止提交),產生或後處理提交訊息,或在有人推送到儲存庫後觸發伺服器上的操作(觸發建置和/或部署)。
一些很少需要的命令可以在您真正需要時節省您的時間:
git bisect
:一個進階命令,透過測試多個提交(手動或透過腳本)來幫助您找出哪個提交引入了錯誤;對於線性歷史,這是使用二分法並且可以手動完成,但是一旦您有許多合併提交,這就會變得更加複雜,並且最好讓 git bisect 來完成繁重的工作。
git filter-repo
:實際上是一個第三方命令,作為Git 自己的filter-branch
的替代品,它允許重寫儲存庫的整個歷史記錄,以刪除錯誤新增的文件,或協助將儲存庫的一部分提取到另一個儲存庫。
我們完成了。
有了這些知識,人們應該能夠將任何 Git 命令映射到如何修改提交的有向無環圖,並了解如何修復錯誤(在錯誤的分支上執行合併?基於錯誤的分支重新建置?)並不是說理解這些事情會很容易,但至少應該是可能的。