<h1>Git筆記--Git介紹</h1> Git 是一個分散式版本控制系統,用於追蹤專案中所有檔案的歷史變動。它不是只備份整個檔案,而是記錄從一個版本到下一個版本,哪些地方做了什麼修改。 Git 可以做什麼? - 記錄歷史:你可以看到誰、什麼時候、改了什麼。 - 版本切換:可以回到之前的版本,或切出分支做實驗。 - 多人協作:多人可以同時修改專案,Git 會幫你合併或提醒衝突。 - 備份與同步:把程式碼推到遠端(GitHub、GitLab 等)就像雲端備份。 <code>Repository</code> 就像你的專案「倉庫」,<code>Commit</code> 是每次「儲存記錄」,<code>Branch</code>是「分線開發」,<code>Merge</code> 是「整合成果」,<code>Remote</code> 是「雲端書櫃」,而 <code>Pull</code> / <code>Push</code> 則是「下載與上傳最新版本」。 --- 一、基礎概念 --- <details><summary>1. <code>Repository</code>(Repo,儲存庫): 指專案的版本控制資料夾,Git 會在其中追蹤所有檔案的新增、修改與刪除狀態。</summary> <div> --- 分為 本地儲存庫(存在於開發者的電腦中)與 遠端儲存庫(通常位於 GitHub、GitLab 等伺服器上)。 - <code>Local Repository</code>:在你電腦本地。 - 在本機使用 <code>git init</code> 創建後,Git 會生成一個隱藏資料夾:<code>.git/</code> - <code>Remote Repository</code>:在伺服器(如 GitHub/GitLab)。 </div> </details> <details><summary>2. <code>Working Directory</code>(工作區) / <code>Staging Area</code>(暫存區) / <code>Repository</code>(版本庫)</summary> <div> --- <b><u>Git 的工作流程本質上是一場「四階段提交」,可分為四個區域、步驟: </u></b>: 運作流程: <code>Working Directory</code> → <code>Staging Area</code> → <code>Local Repository</code> → <code>Remote Repository</code> | 區域 | 中文名稱 | 功能定位 | 操作指令 | | --------------------- | ---- | ----------------- | --------------------- | | <b>Working Directory</b> | 工作區 | 你實際編輯的檔案所在處(相當於草稿區)| <code>git add</code> 將修改送往暫存區 | | <b>Staging Area</b> | 暫存區 | 暫存已準備提交的變更(相當於審稿區)| <code>git commit</code> 將內容提交成版本 | | <b>Repository</b> | 版本庫 | 已正式提交的版本記錄(相當於出版的正式版本)| <code>git push</code> 推送到遠端版本庫(Remote Repository) | <details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary><b>Working Directory</b>、<b>Staging Area</b>和<b>Repository</b>在AndroidStudio中的對應位置</summary> <div> --- <h3>Working Directory(工作區)</h3> 平常我們編輯程式碼的地方,包含個目錄都是 ![image](https://hackmd.io/_uploads/HyHjPaVCll.png =60%x) --- <h3>Staging Area(暫存區)</h3> 在 Android Studio 中,Staging Area (暫存區) 並非一個獨立的實體視窗,而是作為「審核點」存在,其整合了Git操作、暫存區、暫存取管理的概念為一個視窗。 - 當開發者開啟 Commit 工具視窗時,即是在管理暫存區。 - 當在檔案上點擊右鍵選擇 Git → Add,或在 Commit 介面中勾選該檔案時,就是將它移入暫存區。 | 將檔案置入站存區的管理之下 | 操作、管理暫存區 | | -------- | -------- | | ![image](https://hackmd.io/_uploads/ryoWoTV0xx.png) | ![image](https://hackmd.io/_uploads/BJbF56ERlx.png)| --- <h3>Repository(版本庫)</h3> 在 Android Studio 中看不到,但它是整個 Git 系統的核心,他在專案根目錄下隱藏的 <code>.git</code> 資料夾中,儲存所有提交(Commit)的歷史紀錄。 ![image](https://hackmd.io/_uploads/SyZe6TVRxl.png =60%x) </div> </details> </div> </details> <details><summary>3. <code>Commit</code>(提交): 表示將目前的程式碼變更記錄至本地的版本歷史中,形成一個具備訊息與時間戳記的「版本快照」。</summary> <div> --- >[!Note][Git筆記--snapshot(快照)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/git_snapshot) 它會把「暫存區(Staging Area)」中所有已標記的檔案狀態,存進「本地版本庫(Local Repository)」,可視為專案的「儲存點」,開發者可隨時回溯至該狀態。 ```bash= git commit -m "新增使用者登入功能" ``` </div> </details> <details><summary>4. <code>HEAD</code>: 代表了 Git 的「現在的位置的指標」(也就是正在查看或操作的那個版本)</summary> <div> --- 在 Git 中有許多 <code>commit</code>(每一個都有唯一的 SHA-1 編號)。而 <code>HEAD</code> 就像一個書籤,它會指向某一個 <code>branch</code> 的最新 <code>commit</code>。 ==<code>HEAD</code> 類似於在地圖上的定位點,是 Git 用來記住「你現在在哪裡」的機制==。 - <code>HEAD</code> 指向某個分支: 代表「我正在這個分支上工作」---- <code>HEAD</code> 的通常狀態 - <code>HEAD</code> 直接指向某個 <code>commit</code>: 代表「我正在查看歷史版本」---- ==<code>Detached HEAD</code> 狀態== >[!Note][Detached HEAD](https://titangene.github.io/article/git-detached-head.html) 狀態 >- 當 <code>HEAD</code> 直接指向某個過去的 <code>commit</code>(而不是指向分支)時,就進入了 <code>Detached HEAD</code> 狀態,此時我們可以查看、測試舊程式碼。 >- Git會發出警告 <code>You are in 'detached HEAD' state</code> >- 這時候像是「時光旅行」到過去查看程式碼,但要小心,如果在這狀態下做修改又沒建立分支,做出的修改可能會「飄在空中」找不回來。 </div> </details> --- 二、分支與合併 --- <details><summary>1. <code>Branch</code>(分支): 是從主要開發線(例如 <code>main</code> 或 <code>master</code>)分出的一條獨立開發軌跡,用於平行進行新功能開發、修復或實驗性變更。</summary> <div> --- 分支是 Git 的強大核心功能,可以同時開多條分支,各自進行不同功能的開發,互不干擾,甚至可以在開發完成後再合併回主線。 ![image](https://hackmd.io/_uploads/ByOMNHZReg.png) ```bash= git checkout -b feature-branch ``` <details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary><code>Feature Branch</code>(功能分支): 是專門用來開發「新功能」的分支,通常從主分支(main 或 master)切出來,</summary> <div> --- <code>Feature Branch</code> 通常只專注於某個功能的開發,完成後再合併回主分支。是團隊開發中最常用的分支策略之一。 ![image](https://hackmd.io/_uploads/ByOlQ8-Ree.png) ![image](https://hackmd.io/_uploads/SkzH7I-Aee.png) </div> </details> </div> </details> <details><summary>2. <code>Switch</code>(切換): 切換目前所在的分支,或建立並切換到新分支。</summary> <div> --- <code>Switch</code>是「讓你移動 <code>HEAD</code>(目前工作指標)」到不同分支的指令。這樣就可以在不同開發線之間自由切換。 ![image](https://hackmd.io/_uploads/HkujlL-Age.png) ```bash= git switch branch-name # 切換 git switch -c new-branch # 建立並切換 ``` 當使用Switch切換分支時會: - HEAD 指標移動: 從指向 <code>main</code> 改為指向 <code>feature-login</code> - 工作目錄內容改變: 新增了 <code>LoginActivity.kt</code> 和 <code>LoginViewModel.kt</code> - <code>HomeFragment.kt</code> 消失了: 因為這個檔案只存在於 <code>main</code> 分支 </div> </details> <details><summary>3. <code>Merge</code>(合併): 將某一個本地的分支(Branch)上的修改內容,整合到另一個本地的分支中</summary> <div> --- <code>Merge</code> 是一個本地的操作,會保留分支的合併紀錄,產生一個新的「合併節點」,其目的是整合多條開發線的成果,使專案保持一致的進度與版本狀態。 例如: 在一個功能分支(<code>feature</code> / <code>login</code>)開發完畢,想把它合併回主分支(<code>main</code>)。 這時你就會使用 <code>git merge</code>。 ![image](https://hackmd.io/_uploads/ryTLESb0ll.png) ```bash= git merge feature-branch ``` <details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary>Merge 的三種結果: Fast-Forward Merge (快轉合併)、Three-Way Merge (三方合併)、Merge Conflict (合併衝突)</summary> <div> --- Fast-Forward Merge (快轉合併) --- - 發生條件: <code>main</code> 分支在分出 <code>feature</code> 後,完全沒有新的提交 - 結果: <code>main</code> 指標直接「快轉」到 <code>feature</code> 的位置,不產生新提交 ![image](https://hackmd.io/_uploads/ryQTmv-Aeg.png) --- Three-Way Merge (三方合併)(最常見) --- - 發生條件: <code>main</code> 和 <code>feature</code> 兩邊都有新提交,需要真正的合併。 - 邏輯: Git 會根據共同祖先(Base Commit)、目前分支(HEAD)、要合併進來的分支(Merge branch)的差異,自動合併檔案與程式碼 - 結果: 產生一個新的「合併提交」(Merge Commit),但若同一段程式在兩個分支中都有不同修改,或一方刪除了檔案、另一方仍修改了該檔案,Git 就無法自動決定哪個版本應保留,此時便會產生 Merge Conflict(合併衝突) ![image](https://hackmd.io/_uploads/ryYJVPWAel.png) --- Merge Conflict (合併衝突) --- - 發生條件: 兩個分支修改了同一檔案的同一位置,Git 無法自動決定 - 結果: 合併暫停,需要手動解決衝突後才能完成 ![image](https://hackmd.io/_uploads/Sk2WNwb0el.png) </div> </details> </div> </details> <details><summary>4. <code>Rebase</code>(變基): 將一個分支的基底改到另一個分支上</summary> <div> --- 會重新排列歷史,使提交紀錄看起來像是線性進行。簡單來說,就是本來在隊伍中間分叉出去,現在你把自己的整段紀錄「拔起來」,接到目前的隊伍末端。 - 和 <code>Fast-Forward Merge</code> 差在,<code>Fast-Forward Merge</code> 是你已經在隊伍末端,然後直接宣稱你就是新的隊頭。 ![image](https://hackmd.io/_uploads/HkG5VH-0xe.png) ```bash= git rebase main ``` >[!Warning]不要對公共分支做 Rebase >Rebase 會改寫提交,若已推送到遠端共享分支,可能造成協作問題 >所以應該只對你自己的 feature 分支做 Rebase </div> </details> <details><summary>5. <code>cherry-pick</code>: 將另一個分支中已存在的單一或多個 <code>commit</code>,複製並套用到目前所在的分支並且生成新的 <code>commit</code>。</summary> <div> --- 也就是拿一個特定的 <code>Commit</code>,複製到另一個分支,而不是整個分支的歷史 ![image](https://hackmd.io/_uploads/BkrqIvUAex.png) ![image](https://hackmd.io/_uploads/ryYj8DUAex.png) </div> </details> --- 三、遠端與協作 --- <details><summary>1. <code>Clone</code> / <code>Fork</code>: 將專案複製到自己的 本地/遠端庫</summary> <div> --- <code>Clone</code>和<code>Fork</code>都是用來下載整個遠端倉庫(含所有分支、提交歷史)的功能,差別在於: - <code>Clone</code>將遠端倉庫下載到本地,使用 <code>Clone</code> 的話: - <code>Push</code>/<code>Pull</code> 的對象都會是原始的遠端儲存庫 - 想要 <code>Push</code> 的話會需要有額外的寫入權限才行 - <code>Fork</code>將遠端倉庫下載到自己帳戶下的遠端倉庫,使用 <code>Fork</code> 的話: - <code>Push</code>/<code>Pull</code> 的對象都會是自己帳號下的那一個「遠端複製庫」 - 想要 <code>Push/Pull</code> 的話會不需要有額外的權限 <b><u>開源貢獻時,通常是這樣的順序</u></b>: 1. 使用 <code>Fork</code> 把專案複製到你自己的 GitHub 帳號。 ↓ 2. 使用 <code>Clone</code> 把這個自己 <code>Fork</code> 的版本下載到本地電腦。 ↓ 3. 修改、<code>Commit</code>、<code>Push</code> 後推回你的遠端倉庫(<code>fork</code> 出來的那個)。 ↓ 4. 發出 <code>Pull Request</code>,請原專案維護者審核你的修改。 </div> </details> <details><summary>2. <code>Pull</code>(拉取): 將檔案的更新同步至本地</summary> <div> --- 從遠端儲存庫下載最新的變更至本地端,保持版本同步。我們可以將<code>pull</code>視為<code>fetch</code> + <code>merge</code>的組合。 - <code>git pull</code> 就像是快速便利的自動模式,而 <code>git fetch</code> + <code>git merge</code> 則是手動模式,讓你有機會在合併前先檢查。 - 和 <code>Clone</code> 容易搞混,<code>Clone</code>是第一次完整下載,而<code>pull</code>是有專案後的後續更新。 </div> </details> <details><summary>3. <code>Push</code>(推送): 將本地的變動上傳到遠端</summary> <div> --- 將本地提交的變更上傳至遠端儲存庫。 <b><u>Push 的其他模式</u></b>: - <code>git push</code>(預設模式): 檢查當前的 commit,是否包含該分支的最新內容,是否為最安全的推送模式。 - <code>--force</code>: 無條件進行推送並覆蓋,使用上相當危險。 - <code>--force-with-lease</code>: 檢查當前 commit 的目標分支狀態,是否與本地記錄的遠端狀態一致(有人更新過就會不一致),是才允許推送。推薦替代 <code>--force</code>。檢查的是「狀態的一致性」。 - <code>--set-upstream</code>(<code>-u</code>): 將本地分支與遠端分支進行「綁定」(Tracking),通常用於新分支第一次推送。 - 本地的 <code>feature</code> 分支和遠端的 <code>origin/feature</code> 分支在預設情況下只是「名字一樣」,彼此並沒有實質的連結。 - 一旦綁定成功,以後在這個分支執行 <code>git push</code> 或 <code>git pull</code>,你不需要再輸入 <code>origin</code> 或分支名稱,Git 自動知道要去哪裡抓資料。 - --tags: 推送本的的所有 Tag,預設的 Push 只會推送分支,要推送 Tag 的話需使用此模式。 - --all: 將本地的所有分支全都推送到 Remote,普通的 push 只會推送「當前的分支」,但使用此選項的話可以一次推送全部的分支。 - --delete: 刪除指定的遠端分支或 Tag。 - 原子推送,--atomic,確保多個分支同時推送成功。,只要有一個分支推送失敗,全部都會被取消(All or nothing)。 >[!Caution]<code>git push --force</code> 需慎用。 ><code>git push --foece</code> 是強行推送模式,這是在告訴 <code>Remote</code>:「別管你那邊的紀錄了,聽我的,我說現在是什麼樣子,就是什麼樣子!」,==<code>Remote</code> 會被迫刪除那些 <code>Commit</code>,強行「往後」跳回到你指定的節點==。需要嘗試使用 <code>git reflog</code>。 >[!Warning]注意事項 >| 狀況 | 建議做法 | >| ------------------ | ---------------------------- | >| 若遠端更新比你早 | 先 `git pull` 同步後再 <code>push</code>,避免衝突。 | >| 若出現 merge conflict | 手動解決衝突後再 <code>commit</code>。 | >| 若要檢查遠端狀況不想合併 | 使用 `git fetch`。 | </div> </details> <details><summary>4. <code>origin</code>、<code>main</code>、<code>origin/分支名</code></summary> <div> - <code>origin</code>: 代表「遠端儲存庫(<code>remote repository</code>)」的名稱 - <code>main</code>: 代表「本地端」目前本地倉庫中的主要分支(<code>branch</code>) - <code>origin/分支名</code>: 代表「遠端追蹤分支(<code>remote-tracking branch</code>)」,也就是==代表本地端上,最後一次從遠端倉庫抓取(<code>fetch</code>)或同步(<code>pull</code>)後,遠端該分支的狀態快照==。 <b><u>例如</u></b>: - <code>origin/main</code> 代表遠端追蹤分支最後一次同步(<code>fetch</code>/<code>pull</code>)<code>遠端 main 分支</code>的結果。 - <code>origin/feature</code> 代表遠端追蹤分支最後一次同步(<code>fetch</code>/<code>pull</code>)<code>遠端 feature</code> 分支的結果 >[!Note]遠端追蹤分支(remote-tracking branch) >是本地保存的遠端的分支的最新狀態快照,我們通常說只會說「遠端追蹤分支」但是其實「遠端追蹤分支」和「本地遠端追蹤分支」是同樣的東西 >也就是遠端分支在你電腦裡的鏡像,用來對照遠端現在長什麼樣 </div> </details> <details><summary>5. <code>fetch</code>: 從遠端抓取最新狀態到本地的「遠端追蹤分支」上,類似於對遠端「預覽」。</summary> <div> --- 有些類似於<code>pull</code>同樣是抓檔案下來,但==和<code>pull</code>不同,<code>fetch</code> 是將資料同步到「本地的遠端追蹤分支」上而不是「本地分支」上==,因此不會改動本地的工作分支,==相當於在本地對遠端進行「預覽」==。 | 操作 | 是否建議先 fetch | 理由 | | ------------ | ----------- | --------------------------------------------------------------------- | | <code>git pull</code> | ✅ 強烈建議 | `pull` 實際上是 `fetch + merge`,但若要安全檢查建議先手動 <code>fetch</code> | | <code>git merge</code> | ✅ | 確保遠端分支是最新的,避免基於過期版本合併 | | <code>git rebase</code> | ✅ | 若不 <code>fetch</code>,可能導致你 <code>rebase</code> 在舊的 <code>base</code> 上,增加衝突風險 | | <code>git push</code> | ✅ | 若遠端已更新,你的 <code>push</code> 可能被拒絕(non-fast-forward),先 <code>fetch</code> 才能確認是否需 <code>rebase</code> 或 <code>merge</code> | >[!Warning]fetch 的重要性 >在進行 <code>rebase</code>、<code>merge</code>、<code>pull</code>、或開分支等操作前都應先執行 <code>git fetch</code>,來確保本地的遠端追蹤分支更新到最新狀態。 >就像在開車上路前,先打開地圖來更新地圖資訊一樣。 </div> </details> <details><summary>6. <code>Tracking Branch</code>(追蹤分支): 是一種本地分支的「關係」設定,它「追蹤」一個遠端分支的狀態,方便你進行 同步(<code>pull</code>/<code>fetch</code>/<code>push</code>)</summary> <div> --- 是「本地分支標記了自己對應哪個遠端分支」的連結設定,也就是讓「這個本地分支知道自己的遠端庫是誰」的設定。 >[!Note]遠端追蹤分支(remote-tracking branch) 和 追蹤分支(Tracking Branch) >遠端追蹤分支和追蹤分支是兩種名稱很像但完全不同概念,一個代表本地端對遠端的快照,是一種「狀態」,一個代表本地端對遠端的連結設定,是一種「設定」、「關係」 </div> </details> <details><summary>7. <code>upstream</code>(上游): 代表追蹤分支(Tracking Branch)所指向的那個遠端分支</summary> <div> --- 是一種「角色」、使一種設定中的「設定欄位」,代表追蹤分支所指向的那個遠端分支,它是這段同步關係中的參考目標。 ==Tracking Branch 內有 upstream 設定,upstream 設定的值指定了 Remote-Tracking Branch== | 概念 | 定位 | 比喻 | | --- | --- | --- | | **Remote-tracking branch** | 狀態/實體 | 「照片」(有內容) | | **Tracking branch** | 設定/關係 | 「通訊錄」(有連結設定) | | **Upstream** | 設定欄位/屬性 | 通訊錄裡的「主管欄位」 | | **Upstream 的值** (如 origin/main) | 目標/角色 | 「張經理」(被標記為主管的人) | </div> </details> <details><summary>8. <code>Pull Request</code>(拉取請求,PR) / <code>Merge Request</code>(合併請求,MR): 是一種協作流程,外部開發者向專案維護者發出請求,請專案維護者 <code>Pull</code> 外部開發者的修改並檢查、審核、討論,以決定是否將這些修改合併到主分支中。</summary> <div> --- - <code>Pull Request</code>/<code>Merge Request</code> 是一樣的概念,只是在不同平台會叫不同的名子。 - 當完成某個功能分支的開發後,想要把這些修改「合併」到主分支(例如 <code>main</code> 或 <code>develop</code>),但希望讓其他人先檢查、審核、討論,就會建立一個 <code>Pull Request</code>。 - 類似於當你寫好一份報告(功能分支),想放進公司正式檔案(主分支),但在放進去之前,必須先交給主管審閱。主管審閱後同意,才會被正式合併。 </div> </details> --- 四、除錯工具 --- <details><summary>1. <code>Tag</code>: 給某個 <code>Commit</code> 貼上自定義標籤(通常是用版本號),讓我們之後能快速找到那個版本</summary> <div> --- 1. 輕量標籤 (Lightweight Tag): 單純的建立標籤 ```bash= git tag v1.0.0 ``` ==2. 附註標籤 (Annotated Tag)==: 建立並給標籤加上額外的附註、作者、日期、訊息等,通常會推薦使用附註標籤。 ```bash= git tag -a v1.0.0 -m "Initial release" ``` - <code>-a</code>: 註釋(通常是版本號) - <code>-m</code>: 加上說明文字 <font size=4><b><u>常用操作</u></b></font>: ```bash= # 查看所有標籤 git tag # 查看特定標籤的詳細資訊 git tag -l "v1.*" # 列出所有 v1.x.x 版本 # 推送標籤到遠端 git push origin v1.0.0 # 推送所有標籤 git push origin --tags # 刪除本地標籤 git tag -d v1.0.0 # 刪除遠端標籤 git push origin --delete v1.0.0 ``` <font size=4><b><u>常用範例</u></b></font>: ```bash= # 1. 完成新功能開發並測試完成 git commit -m "完成登入功能" # 2. 準備發布,建立標籤標記這個版本 git tag -a v1.2.0 -m "新增登入功能" # 3. 推送到遠端讓團隊成員看到 git push origin v1.2.0 # 4. 未來如果要查看或回到這個版本 git checkout v1.2.0 # 切換到該版本查看 ``` </div> </details> <details><summary>2. <code>Stash</code>(暫存): 暫時把尚未 <code>commit</code> 的修改「藏起來」</summary> <div> --- <code>Stash</code> 會把已修改但未提交的檔案、已暫存 (staged) 的檔案,全部「打包起來」放進一個臨時區域,然後把工作區和暫存區還原成乾淨的狀態。 - 中文名和 <code>Staging Area(暫存區)</code> 聽起來很像,但實際上沒有太大的關係 - <code>git stash</code> 就像暫時把文件塞進抽屜裡,先去做別的事情,晚點再拿出來繼續改。 - 當我們臨時性的要去做別的事(例如: 緊急修復某個 Bug),就會使用 <code>Stash</code> 來進行暫存 - 「不想 <code>commit</code>,但又不想丟掉改動,就先 <code>stash</code> 起來。」 <font size=4><b><u>常用指令</u></b></font>: ```bash= # 暫存 git stash # 暫存並附加備註 git stash push -m "訊息" # 顯示所有暫存項目 git stash list # 顯示最新 stash 的差異摘要 git stash show # 顯示完整差異內容(patch) git stash show -p stash@{0} # 套用最新 stash git stash apply # 套用特定 stash git stash apply stash@{1} # 刪除特定暫存項目 git stash drop stash@{0} # 清空所有 stash git stash clear ``` </div> </details> <details><summary>3. <code>Bisect</code>(對分): 是 Git 提供是一個「二分搜尋除錯工具」,用二分搜尋法快速找出哪一個 <code>commit</code> 引入了 bug。</summary> <div> --- 當一個 Bug 出現,但不知道它是什麼時候、被哪個提交( <code>commit</code> )引入時,我們就會使用 <code>git bisect</code> 。 - <code>git bisect</code>的運作就像是玩「猜數字」遊戲一樣,它每次都將範圍縮小一半,能以最少的步驟找到目標。 <details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary><code>git bisect</code>的操作步驟</summary> <div> --- 步驟 1.啟動 <code>Bisect</code>: 告訴 Git「我要開始找 bug 了」 ```bash= git bisect start ``` 步驟 2.標記壞的 <code>commit</code>: 告訴 Git「這個版本有問題」 ```bash= # 和Tag聯動使用 git bisect v1.2.0 # 直接標記 git bisect bad # 或指定特定 commit git bisect bad HEAD ``` 步驟 3.標記好的 <code>commit</code>(過去某個正常的版本): 告訴 Git「這個版本是正常的」,這樣 Git 就知道問題必定出在這個「壞的」和「好的」兩個 <code>Commit</code> 之間 ```bash= # 和Tag聯動標記 git bisect good v1.0.0 # 或使用 commit hash git bisect good a1b2c3d ``` 步驟 4.Git 自動切換到中間點,在進行測試後標記「 good 」或「 bad 」: 透過對測試完的版本標記「 good 」或「 bad 」,Git 會持續二分,直到找出「第一個壞掉的 <code>commit</code>」 ```bash= # 如果這個版本有問題 git bisect bad # 如果這個版本正常 git bisect good ``` 步驟 5.找到問題後,結束 Bisect: 結束 Bisect 模式,回到原本的分支狀態 ```bash= git bisect reset ``` </div> </details> </div> </details> <details><summary>4. <code>Reflog</code>(Reference Logs,引用日誌): 是 Git 的檔案救援、保命機制,他會記錄「所有會導致 <code>HEAD</code> 或分支位置改變的操作」來使我們得以回朔操作。</summary> <div> --- <font size=4><b><u>常用指令</u></b></font>: ```bash= # 查看所有操作記錄 git reflog # 查看特定分支的 reflog git reflog show main # 查看最近 5 筆記錄 git reflog -5 # 根據時間查看 git reflog show HEAD@{2.days.ago} # 恢復到特定操作點 git reset --hard HEAD@{3} ``` <font size=4><b><u>具體會被 <code>Reflog</code> 紀錄的操作</u></b></font>: | 類別 | 範例指令 | 為什麼會記錄 | | ---------------------- | ---------------------------------- | ------------------------- | | **提交行為** | `git commit` | <code>HEAD</code> 從舊 <code>commit</code> → 新 <code>commit</code> | | **切換分支** | `git switch`、`git checkout branch` | <code>HEAD</code> 指向新的分支 | | **重設版本** | `git reset --hard`、`--soft` | <code>HEAD</code> 回到舊 <code>commit</code> | | **合併操作** | `git merge`、`git rebase` | <code>HEAD</code> 移動至新的合併結果 | | **回滾操作** | `git revert` | <code>HEAD</code> 指向新生成的回滾 <code>commit</code> | | **建立/刪除分支** | <code>git branch &lt;name&gt;</code> | <code>分支 reference</code> 變動 | | **變更 Tag (Annotated)** | `git tag -a` | <code>tag reference</code> 變動(但不記錄在 <code>HEAD</code>) | </div> </details> <details><summary>5. <code>Reset</code>: 改變 <code>HEAD</code> 和分支 <code>HEAD</code> 的位置,並根據選項決定是否重設暫存區與工作目錄</summary> <div> --- >[!Warning] <code>Reset</code> 並不會真的把 <code>Commit</code> 刪掉 >使用 <code>Reset</code> 並不會真的把 <code>Commit</code> 刪除,而是會1. 移動 <code>HEAD</code> → 2. 將舊的 <code>Commit</code> 隱藏 → 3. 放進「資源回收桶」中,在 90天(預設) 內還可以透過 <code>reflog</code> 找回;因此看起來就像被刪除一樣。 - <code>reset</code> 回到過去某個 <code>Commit</code> 時,你的 <code>Local Repository</code>(本地倉庫)的進度條會直接縮短,若在此時使用 <code>git push</code> 推送到 <code>remote</code>, Git 會因為和歷史對不起來而發生衝突。 <b><u>Reset的三種模式</u></b>: | 模式 | 行為 | 暫存區 | 工作目錄 | 用途 | | ------------- | ------------------- | ---- | ---- | -------------- | | `--soft` | 只改 HEAD | ❌ 不變 | ❌ 不變 | 重新提交用 | | `--mixed`(預設) | 改 HEAD + 暫存區 | ✅ 改變 | ❌ 不變 | 取消暫存 | | `--hard` | 改 HEAD + 暫存區 + 工作目錄 | ✅ 改變 | ✅ 改變 | 完全回到舊狀態 | ![image](https://hackmd.io/_uploads/SyqsPMr0ll.png) </div> </details> <details><summary>6. <code>Revert</code>(回復): 產生一個新的 <code>commit</code> 來反轉某個已存在的 <code>commit</code> 的變更</summary> <div> --- - 類似於<code>reset</code> ,但 <code>revert</code> 不改動歷史,它保留原本的 <code>commit</code> - 由於 <code>Revert</code>,的原理是產生一個新的 <code>commit</code>,而不是像 <code>Reset</code>直接將消除 <code>commit</code>,不會在 <code>Remote Repository</code> 因為歷史不同造成衝突,因此==可安全用於多人協作的分支==。 - <code>Revert</code> 是透過「反著做」來讓結果倒退回到想要的狀態,但歷史保持完整。 ![image](https://hackmd.io/_uploads/Bk37Z8BAlx.png) </div> </details> <details><summary>7. <code>Restore</code>: 把檔案的狀態,恢復成某個來源的版本</summary> <div> --- - 它處理和作用的是檔案狀態(本地的實際文件),不是處理 <code>commit</code> 歷史(已經提交到 <code>Repository</code> 的文件) - 只適合用在尚未 <code>commit</code> 之前,已經<code>commit</code> 之後最好改用 <code>Reset</code> 或 <code>Revert</code> ```bash= git restore file.txt ``` </div> </details> --- 五、團隊協作 --- <details><summary>1. <code>Git Flow</code>: 是一套「分支管理策略」,教你「如何有條理地管理程式碼的版本與分支」</summary> <div> --- 是一套使用 Git 的工作流程規範,類似於 [設計模式](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/DesignPattern),告訴大家「用 Git 時,最推薦的流程與分工方式」。 <b><u>Git Flow 定義了兩種「主要分支」+三種「輔助分支」:</u></b> | 分支名稱 | 類型 | 用途 | 類比 | | ----------- | ---- | -------------------- | ------------------ | | `main` | 主要分支 | 永久儲存「正式」、「已上線」、「穩定」、「可發佈的程式碼」 | 正式上市的產品 | | `develop` | 主要分支 | 所有新功能整合的地方,所有的新功能和錯誤修復都是先合併到這裡,是下個版本的準備區 | 研發中心 | | `feature/*` | 輔助分支 | 用於開發單一新功能。從 <code>develop</code> 分支出來,完成後再合併回 <code>develop</code>。 | 研發中的各項新產品 | | `release/*` | 輔助分支 | 準備要發佈的版本,用來修錯與微調,完成後必須合併回 <code>main</code> 和 <code>develop</code>。 | 準備上市的產品包裝與測試階段 | | `hotfix/*` | 輔助分支 | 緊急修正上線版本的錯誤 | 出貨後緊急回收修正的版本 | <details style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><summary><font size =4>Git Flow 的一般流程</font></summary> <div> --- 步驟1. 從 <code>develop</code> 建立新功能分支 -- 在安全的獨立環境開發新功能。 ```bash= git checkout develop git checkout -b feature/new-login ``` 步驟2. 功能完成後合併回 <code>develop</code> -- 將新功能整合進主要開發版本 ```bash= git checkout develop git merge --no-ff feature/new-login git branch -d feature/new-login ``` 步驟3. 當要發佈版本時建立 <code>release</code> 分支 -- 進行最終測試與版本號設定。 ```bash= git checkout develop git checkout -b release/1.0.0 ``` 步驟4. 發佈完成後合併回 <code>main</code> 與 <code>develop</code> -- 確保 <code>main</code> 與 <code>develop</code> 都有相同最終版本。 ```bash= git checkout main git merge --no-ff release/1.0.0 git tag -a 1.0.0 git checkout develop git merge --no-ff release/1.0.0 git branch -d release/1.0.0 ``` 步驟5. 如果線上出現重大錯誤,建立 <code>hotfix</code> 分支 -- 修正完後同樣合併回 <code>main</code> 與 <code>develop</code>。 ```bash= git checkout main git checkout -b hotfix/1.0.1 ``` </div> </details> </div> </details> <details><summary>2. <code>Trunk-based Workflow</code>(主幹式工作流程,TBD): 是一種相較於 <code>Git Flow</code> 更簡潔的 Git 分支策略,所有開發人員都會把代碼直接整合到一個主要分支上</summary> <div> --- 如果說 <code>Git Flow</code> 是一種嚴謹、多階段的生產線,那麼 <code>Trunk-based Workflow</code> 就是一種快速、持續、單一的流水線。 - 核心理念是 -- 「少分支、快整合、持續整合(CI)」 - 相較於Git Flow 模式可以把錯誤停留在 <code>feature</code> 或 <code>release</code> 分支,TBD 仰賴每個人去維持、管理、測試、維持穩定,因此 TBD 對個人的能力要求更高。 - 由於 TBD 模式只要有小錯誤就會立即汙染主幹,因此最好搭配 TDD(<code>Test-Driven Development</code>)。 <b><u>TBD 基本概念</u></b>: | 元素 | 說明 | | ---------------------------- | ------------------------------- | | **主幹(Trunk/Main)** | 唯一長期存在的分支。所有人都往這裡整合。 | | **短命分支(Short-lived Branch)** | 若需要開新功能,會建立小分支(幾小時或一天內合併回主幹)。 | | **持續整合(CI)** | 每次提交(<code>commit</code>)都會自動測試、編譯,確保主幹永遠可用。 | </div> </details> --- Q&A --- <details><summary><font size=4>為甚麼 <code>Reset</code> 消除的順序是從 <code>Repository(儲存庫)</code> 開始而不是從 <code>Working Directory(工作區)</code> 開始?</font></summary> <div> --- 描述: --- 乍看之下,<code>Reset</code> 的三種模式的消除順序(<code>Repository → Staging Area → Working Directory</code>)似乎不太合理 - 就變動的嚴重性來說,應該是<code>Working Directory</code> &lt; <code>Staging Area</code> &lt; <code>Repository</code> - 所以照理來說<code>soft → mix → hard</code>消除的順序也應該要和風險性一致的從最低的<code>Working Directory → Staging Area → Repository</code>開始消除 但實際上卻是相反,是由 <code>Repository</code> 開始清除。 <font size=5>Ans</font>: --- 之所以會這樣做是因為<code>git reset</code>的運作邏輯: - <code>git reset</code> 的第一個、也是唯一一個必定會發生的動作,就是將 <code>HEAD</code> 指標移動到您指定的目標 <code>Commit</code> 上。 - 一旦 <code>Repository</code> 的 <code>HEAD</code> 指標移動完成,接下來 Git 會根據您選擇的模式,來決定是否要用新 <code>HEAD</code> 所在 <code>Commit</code> 的內容,去「覆蓋」或「清空」外層的區域。 <b><u>也就是說,「嚴重性」的觀點,是建立在「<code>HEAD</code> 移動後」的前提下來進行的,因此</u></b> : 1. <code>--soft</code> 模式 (影響最輕):僅移動 <code>HEAD</code>。它只改動了 <code>HEAD</code>,完全不影響 <code>Staging Area</code> 和 <code>Working Directory</code>。 2. <code>--mixed</code> 模式 (中等影響):移動 <code>HEAD</code>,然後用新 <code>HEAD</code> 的內容來「清空」<code>Staging Area</code>。 3. <code>--hard</code> 模式 (影響最重):移動 <code>HEAD</code>,清空 <code>Staging Area</code>,然後用新 <code>HEAD</code> 的內容強行覆蓋 <code>Working Directory</code>。這是最「破壞性」的操作,因為它直接清除了您未提交的工作。 </div> </details> <details><summary><code>Reset</code>、<code>Revert</code>、<code>Restore</code> 之間差在哪裡?</summary> <div> --- 簡單來講,就是三者的試用狀況不同。 - 想還原尚未 commit 的變動 → git restore (處理還沒存進 Git 的變動) - 想把整個分支往回推 → git reset - 想在不破壞團隊歷史的情況下撤銷? → git revert </div> </details> --- 參考資料 --- - [【Git】從零開始學習 Git - 30 天的學習筆記 ](https://ithelp.ithome.com.tw/m/users/20141010/ironman/4499) - [新手也能懂的Git教學](https://medium.com/@flyotlin/%E6%96%B0%E6%89%8B%E4%B9%9F%E8%83%BD%E6%87%82%E7%9A%84git%E6%95%99%E5%AD%B8-c5dc0639dd9) - [不熟 Git 嗎?好巧我也是,不如我們一起來學吧! ](https://ithelp.ithome.com.tw/users/20162483/ironman/6374) - [Day 26 - [版本控管] 01-從 Git CLI 到 GUI](https://ithelp.ithome.com.tw/m/articles/10349851) - [Git](https://git-scm.com/) - [Git中文網](https://git.p2hp.com/) - [Git 其然,Git 其所以然](https://ithelp.ithome.com.tw/users/20103676/ironman/2846) - [[Git] 基礎觀念](https://ithelp.ithome.com.tw/m/articles/10240279) - [[Git] 初始設定](https://ithelp.ithome.com.tw/m/articles/10240965) - [[Git] 常用指令表](https://ithelp.ithome.com.tw/m/articles/10241407) - [[Git] 應用情境](https://ithelp.ithome.com.tw/m/articles/10242283) - [淺入 Git:detached HEAD](https://titangene.github.io/article/git-detached-head.html)