--- title: 'Git 使用 - 本地' disqus: kyleAlien --- Git 使用 - 本地 === ## OverView of Content 如有引用參考請詳註出處,感謝 :smile: > Git 原本就是基於 Linux 在方便管理 Linux kernal 而開發出來的,現在已被廣泛的應用在各種大小項目中,詳細可以 [**參考文章**](https://zlargon.gitbooks.io/git-tutorial/content/) 本章大部份都是實做 參考文章 [TOC] ## 安裝 Git * 在 Linux or Ubuntu 中**使用 shell 視窗用指令安裝** ```shell= sudo apt-get install git sudo apt-get install git-core ``` > 當然你也可以去下載源碼壓縮包,自己進行 make/make install * Window 中須去網站中下載 [**Git for windows**](https://gitforwindows.org/) 並安裝 > ![](https://i.imgur.com/QrhZfMD.png) :::info * Git 源碼的依賴包:`Curl`、`Zlib`、`Openssl`、`Expat`、`Libiconv` ``` mermaid graph TD; Git應用-->Curl; Git應用-->Zlib; Git應用-->Openssl; Git應用-->Expat; Git應用-->Libiconv; ``` ::: ## Git 基礎使用 雖然 Git 有各種 UI 界面可以操作,**但是 Git 的各種命令才是應該掌握的核心技能** ### 建立 Git 專案 - init * 接著 **到 project 中** 創建倉庫代碼,**創建完後就可以看到一個 ==隱藏的 .git 文件==資料夾** ```shell= # 創建指令 git init ``` 1. 當然你可以創自己的需要的檔案 ```shell= mkdir ~/桌面/EnjoyGit cd ~/桌面/EnjoyGit git init ``` > ![](https://i.imgur.com/z94ESjp.png) 2. Android Project 資料夾下,在創建專案時就有預設 `.git` 檔案 > ![](https://i.imgur.com/vU6PaiA.png) :::info * **當不需要該倉庫時只需要 `rm -rf .git` 就可以了** ::: ### 基礎配置 - config * config 配置有三個部分:子區塊可以覆蓋父區塊設定(類似 Override 的概念) | config 配置位置 | 功能介紹 | | -------- | -------- | | `/etc/gitconig` | 系統截別設置(選項 `--system`) | | `~/.gitconfig` | 用戶級別設置(選項 `--global`) | | `.git/config` | 項目節別設置,內建在每個項目之中 | * 以下使用用戶級別設置(`--global`),看看幾個常用指令 | 指令 | 功能介紹 | | -------- | -------- | | git config --global <option\> | **==global 代表了對於所有的 git Project 都採用這些設置==** | | git config --list | 也可以寫成 -l,它會**查看目前 git 的設置** | | - | - | | git config --global color.ui <true \ false\> | 開啟或關閉預設的色彩 | | git config --global core.editor vim | 預設.git 檔案的編譯器,使用 vim 當編譯器 | | git config --global core.ignorecase <true \ false\> | 是否忽略檔案的大小寫,**在 window & Mac 中預設是不會分大小寫的,而 Linux 系統則會區分** | * 首先要身份配置,這樣才可以知道 git 的基礎訊息,在下載 Android Source 時也會需要你去配置 Git 訊息 ```shell= # 配置,該配置可以隨時根改 git config --global user.name "Alien" git config --global user.email "Alien@gmaill.com" # 查詢 git config --global user.name git config --global user.email ``` > ![](https://i.imgur.com/KyjCIwy.png) ```shell= # 查詢全部設置 git config --list ``` > ![](https://i.imgur.com/aroP3nc.png) ### 定義指令別名 - alias * 可以在 config 中設定自己需要的指令 **別名**,這個動作可以方便去記憶,或是加快輸入指令,但 **若是常用指令則建議不要用 alias 別名** | 指令格式 | 功能 | | -------- | -------- | | git config --global alias.<別名> <原指令> | 全域的別名 | | git config alias.<別名> <原指令> | 本域的別名 | 1. 設定 `--global alias` ```shell= # 設定 status 別名為 st git config --global alias.st status # 查看全域變數設定 git config --global --list ``` > ![](https://i.imgur.com/l16FPGj.png) 2. 設定 `--global alias` ```shell= # 設定 status 別名為 sta git config alias.sta status # 查看本域 config 設定 git config --list ``` > ![](https://i.imgur.com/gjyjyYU.png) ## Git 檔案管理 ### 檔案新增 -add & 修改 -diff & 查看 -show * 現在來觀察我們對於同一個創建出來的檔案修改後 git 會有哪裡改變,先修改一下上面的 Hello_Git.c 文件為 txt 檔案(懶的在寫 c 程式範例 :D) | 指令 | 功能 | | -------- | -------- | | git diff | 查看 ==全部== 修改的內容 | | git diff <file or commit id\> | 查看 **指定** 修改的內容 | | git diff --cache/staged | 查看已提交 (commit) 的檔案 | | git add ./-A/-All | 該目錄下所有檔案加入本地倉庫 | | git commit -m <message\> | 使用非編譯模式提交代碼 | | git show <id\> | 可查看指定代碼的內容 | | git show --oneline | 可以簡單的看到主要的 6 碼 id & 標題 | 1. `git status` 查看哪些檔案被修改 ```shell= # 改名 mv Hello_Git.c Hello_Git.txt # 接續輸出 echo "Time to Work" >> Hello_Git.txt # 使用 git status 查看 git status ``` > ![](https://i.imgur.com/mhiDgN0.png) 2. `git diff <file>` 查看特定檔案的**修改內容**,diff 是與最後一次 patch 比對差異,`+` 號代表了新增 ```shell= git diff Hello_Git.txt ``` > ![](https://i.imgur.com/sVU82gL.png) 3. `git diff` 假設有**多個檔案被更改** ```shell= # 新增檔案 echo "YoMan" > Yo.txt # 使用 git status 查看,會發先多了一個 Untracked files git status # 將該目錄下所有檔案加入 git 倉庫 git add . # 再次查看狀態 git status ``` > ![](https://i.imgur.com/CgLyz2L.png) :::info 可使用 git add -a / --all / . 加入該目錄下所有檔案 ::: 4. 提交後的檔案(已 Commit)要查看必須要使用 git diff `--cached` or `--staged` 才能查看 ```shell= # 提交檔案 可使用 -m 代替,跳過文字編譯 git commit -m "Test commit -m" -m "Hey" # -m <標頭> -m <描述...可多個> # 查看 commit 後檔案是看不到的 git diff Hello_Git.txt # 必須使用 --cached or --staged git diff --cached Hello_Git.txt git diff --staged Hello_Git.txt ## 並使用 git log 查看 git log # 會顯示出 提交的時間、作者、描述、檔案 git log 6833f5 # 使用 ID 查詢指定提交 ``` > ![](https://i.imgur.com/vM0cYd7.png) 5. `show log --oneline` 查看簡單的 id & title > ![](https://i.imgur.com/hyEONQF.png) ### 刪除檔案 - rm | 指令 | 功能 | | -------- | -------- | | git rm <file\> | 刪除指定檔案 | | git rm -rf <filefloor\> | 刪除資料夾包括內容 | | git add -u/--update | 一次性加入所有更動的檔案,不只是修改的檔案,也包括刪除的檔案,**但是未追蹤檔案並不算在 update 中** | 1. 刪除檔案 git rm 查看 git status 的狀態,會顯示 **deleted 綠色的字(已 add 狀態)** ```shell= // 刪除指定檔案 git rm Yo.txt ``` > ![](https://i.imgur.com/zgsplEE.png) 2. 更新檔案狀態 git add -u ```shell= // 更新檔案 git add -u // 同上 git add --update ``` > ![](https://i.imgur.com/SfcC15V.png) ### 移動檔案 - mv * 與一般 Linux 操作相同,使用 mv 操作,它與 **git mv 的差別在於 ++不用多做出 add/rm 的動作++**(自動添加到儲存的狀態),但是提交仍然需要自己 commit 提交 | 指令 | 功能 | | -------- | -------- | | git mv <file\> <move Path\> | 移動檔案,**被移動的檔案其代表了也是移除的意思** | ```shell= # 在倉庫內新增資料夾 mkdir MyPath ls -l # 移動檔案 git mv Hello_Git.txt MyPath/ # 確認狀態 git status # renamed 狀態 # 提交 git commit -m "test move file" # 描述時中間有空白格就必須使用雙引號括起來 ``` > ![](https://i.imgur.com/GFSTVXn.png) :::warning * git mv **不可以搬移檔案到倉庫以外的地**方,否則會出 fatal: outside repository 的錯誤,並無法移動 > ![](https://i.imgur.com/NjW8TeH.png) ::: ### 重新命名 - mv * 在 git 中改名的方式可以如同 Linux 中的方式使用 mv 修改,**差別也在於一般指令的 mv 會被認定為檔案移除並要自己添加** 使用 `git mv` 會自動幫你做添加到追蹤中檔案 | 指令 | 功能 | | -------- | -------- | | git mv <old name\> <new name\>| 改名 | ```shell= # 使用 mv 改名稱 git mv Hello_Git.txt Hi_Git.txt git status git commit -m "test mv change File Name" ``` > ![](https://i.imgur.com/SO8KzfJ.png) :::info * **git 會自動忽略空資料夾**,並不會把空資料夾方入倉庫中,所以 rm -rf Myfile 在 git status 上並不會有差異 > ![](https://i.imgur.com/HBTASVO.png) ::: ### 忽略文件 - .gitignore * 大部分我們不需要全部的文件都上傳,而是上傳需要的文件,在提交文件時 Git 會檢查該目錄下有沒有 **==`.gitignore` 文件==**,它會判斷文件的內文容來忽略某些文件 1. 可以在每一個目錄中都存在 `.gitignore` 檔案 2. 作用範圍包含整個資料夾 & 資料夾下的子資料夾 ```shell= # 修改文件內容 vim .gitignore ... /src/test # 新增提交項目 git add . # 提交 git commit -m "fix gitignore file" ``` :::info * Android project 中的 build 資料夾就不需要,因為**它是由編譯器產生的,可直接使用**,在產生專案時它會自動產生 **`.gitignore` 文件**,不須自己創建 > Android 會自動幫我們指定 `.iml` 結尾的全部文件、build 目錄,在默認狀況下就不會加入版本控制中 * 在這當中如果有測試文件就可以排除,就是在 `.gitignore` 中添加測試文件就可以 > ![](https://i.imgur.com/ypqwS0x.png) ::: * 當然也可以使用指令,只加入某個檔案 | 指令 | 功能 | | -------- | -------- | | git add -f/--force <1...多個檔案>| **強制只加入某個檔案**,其他檔案則忽略 | ```shell= # 新增一個除錯檔案 touch myDebug.d ls -l # 改變一檔案 echo "1" >> NewFile.txt # 觀察所有檔案變化 git diff # 只加入 NewFile.txt 檔案 git add -f NewFile.txt git status ``` > ![](https://i.imgur.com/qch3awg.png) ### 檔案狀態 - status * 從 **git status** 可以看到幾種狀態 | 狀態 | 檔案狀態 | 意義 | | - | -------- | -------- | | `Untracked` | Untracked files | 全新檔案(尚未添加) | | `Unmodified`/`Staged` | Changed to commit | 檔案更改並 git 知道,但未提交(已經在 Git 控管之下) | | `Modofied` | Changes not staged for commit | 檔案更改,但 git 尚未暫存(不會進入下次的 commit 中) | > ![](https://i.imgur.com/0AC0JQj.png) 1. **全新檔案 `Untracked files`**:git 尚未檔案感知到有該檔案的存在 ```shell= # 新增檔案 echo "New File" >> NewFile.txt ls -l # 查看檔案狀態 git status ``` > ![](https://i.imgur.com/xfX6K1n.png) 2. **改動檔案 `Changes not staged for commit`**:如果對已添加追蹤的檔案(已 `git add <file>`)改動,那該檔案就會退回到 已追蹤,但尚未保存(Stage) 的狀態 ```shell= # 去任檔案內容並修改 cat Hi_Git.txt echo "Changes not staged for commit" >> Hi_Git.txt cat Hi_Git.txt # 查看檔案狀態 git status ``` > ![](https://i.imgur.com/WjKS73D.png) :::success * **可使用 git checkout <file\> 退回到未修改前的狀態** ::: 3. **add 改動的檔案 `Changed to commit`**:git 更新追蹤檔案的狀態 ```shell= # 去檔案修改其內容 echo "Changes not staged for commit" >> Hi_Git.txt git diff Hi_Get.txt git status ``` > ![](https://i.imgur.com/FvotX5X.png) :::success * 可使用 **git reset HEAD <file\>** 去把檔案退到未 add/patch 之前的狀態 ::: ### 檔案還原 - checkout * 在檔案狀態中我們有提到,可以還原檔案,那我們在這裡整理一下 | 指令 | 使用時機 | 功能 | | -------- | -------- | -------- | | git checkout -- <file\>| 已追蹤的檔案(非新檔案) | 可以退回到上一次追蹤的狀態,**==退到 delete 之前也可以使用==** | | git reset HEAD | 已 add 的檔案 | **未指定檔案則還原 ++全部++ 檔案** | | git reset HEAD <file\> | 已 add 的檔案 | **指定檔案,還原檔案狀態** | 1. **git checkout <指定檔案**> 從下圖中可以看出 `Hi_Git.txt` 檔案的改動尚未被 git 追蹤,透過 `git checkout Hi_Git.txt` 可以將檔案退回到 git 追蹤的內容 > ![](https://i.imgur.com/Pz5VMnf.png) 2. **git reset Head <指定檔案>** 對於已經添加(已經 add)的檔案進行 `git reset Head` 指令,可以將其退回到 HEAD 的狀態(HEAD 之後說明) > ![](https://i.imgur.com/D404GjO.png) ## Patch 管理 (Commit) :::success * **Patch& Commit**: commit 在英文中只有動詞的意思,所以如果要表達會變成 commit a commit (提交一個完整的程式) 比較繞口,所以**使用 commit a patch 來取代,具體來說 commit(n.) Patch 有很相像的意思,Patch 完善已有的程序** ::: | 指令 | 功能 | | -------- | -------- | | git log --pretty=raw | 查看 parent 的資訊,**沒有加入 `=raw` 是無法查看到 parent的** | * **git log --pretty=raw** 來觀察 parent,可以看**出上一次的 commit 就會是下一次的 parent**,這是一種 **父子關係** ```shell= # 在 EnjoyGit 資料夾中 git log --pretty=raw ``` > ![](https://i.imgur.com/MlY7ivA.png) ### HEAD 關鍵字 * **HEAD 為 git 的關鍵字,意指==目前所在 patch 的位置==,也就是++串列的頭++** (常常是代表你當前所在的位置) > ![](https://i.imgur.com/zH50H7l.png) | 指令 | 功能 | | -------- | -------- | | git log <-\-online> | 這除了可以觀察出提交的 title、ID 外 (-\-oneline 可有可無),還**可以看出 HEAD 在哪** | | git show HEAD<^> / HEAD<~1> | 以目前的 HEAD 作為基準點,**往前查詢,假設查詢該頭的前 2 個 patch 訊息可以使用 `git show HEAD^^`、`git show HEAD~2`**,可加入 --oneline 去簡化 | | git reset HEAD^ / HEAD<~1> | 以目前的 HEAD 作為基準點,**退回前一次 Patch**,檔案保留 | | git reset **-\-hard** HEAD^ | **跳轉上一次 Patch,++強制清除++ 版本之外的檔案**,但是**未 track 的檔案不會刪除** | | git rest **-\-hard** <commit_id\> | **跳轉指定 Patch,強制改變到該 id patch 的版本(可往前 or 往後)**,但是**未 track 的檔案不會改變** | 1. 觀察 **git log** 的 HEAD FILE ```shell= # 完整板 git log # 簡化板 git log --oneline ``` > ![](https://i.imgur.com/jXdSICt.png) 2. 以 HEAD 為基準,**git show HEAD~3** 查看前 3 版本的 Patch 的訊息 ```shell= # 先查看所有的 Patch git log --online # 離 HRAD 前 3 版本的 Patch git show HEAD^^^ # 功能同上,只是加了簡化板 oneline git show HEAD~3 --oneline ``` > ![](https://i.imgur.com/ChWXzh9.png) 3. 使用 **`git reset HEAD^`** 退回到 上次提交 (commit) 之前,**檔案依然存在,但是退回到 `Modified` 狀態** > 等同於 `git reset HEAD~1` ```shell= # 新增檔案並提交 touch HeadRest.txt git status git add HeadRest.txt git commit -m "New test file" -m "add one file to test Head reset" git status git log --oneline # 跳回前一次 Patch git reset Head^ # 觀察倉庫狀態 git status # 觀察實際檔案狀況 ls -l # 檔案沒有被移除 ``` > ![](https://i.imgur.com/oQh0jgr.png) 4. 使用 **git reset ++--hard++ HEAD^**,會發現檔案被移除 ```shell= # 加入上次刪除的檔案 & 並觀察 log git add HeadReset.txt git commit -m "Test reset Hard" git log --oneline # 強制跳回前一次 Patch git reset --hard HEAD^ git log --oneline # 觀察倉庫狀態 git status # 觀察實際檔案狀況 ls -l # 檔案 HeadReset.txt 被移除 !!! ``` > 可以發現 **Untrack 的檔案 myDebug.d 並無差異**(因為它還沒進入 git 的掌控) > > ![](https://i.imgur.com/OyhNs1U.png) 5. **git reset --hard <commit_id>** 以下會演示,退回到最初 patch & 前進到最新 patch 的差異 ```shell= git log --oneline ls -l # 退回到最初檔案 git reset --hard 6833f51 ls -l # 會發現少了 Newfile.txt 檔案 # 前進到最新檔案 git rest --hard fb411ed ls -l # Newfile.txt 檔案回來了 ``` > 可以發現 **Untrack 的檔案 myDebug.d 並無差異** > ![](https://i.imgur.com/gOfJhWA.png) :::danger 1. **git reset --hard HEAD^** 代表 **移除** 了上一次更改的內容,如果忘了紀錄上一次版號,要救回只能使用 ctrl + z 大法(或是 `reflog`),才有可能救的回來 > `--hard` 要小心使用 (好好紀錄上一次的 commit id 就很重要了) 2. 切回到上個版本忘了紀錄最今版本的 ID 還是可以查的到,git 會將所有 commit 過的 patch 都存下來 ::: ### 找回遺失的 Patch - reflog * 在 git 中,**凡是動到 HEAD 的操作都會被 git 記錄下來**,那我們先看看哪些動作會讓 HEAD 改變 1. git commit:提交動作 2. git reset --hard <commit_id\>:**轉移切換** HEAD 3. git cherry-pick/revert:**挑入/挑出** patch 的時候 4. git checkout <branch\>:**切換**分支的時候 5. git merge/rebase ...:**合併**分支的時候 | 指令 | 功能 | | -------- | -------- | | git reflog | **查看 HRAD 移動 or 改變的簡單紀錄** | | git log -g | 同上功能,但是紀錄詳細 | | git show @{<數字>} | 查看指定 HEAD 的詳細訊息 | 1. **git reflog** 查看簡單的 Patch 訊息 ```shell= # 查看 HEAD 紀錄 git reflog # 查看 指定 HEAD 紀錄 git show HEAD${1} git show HEAD${1} ``` > ![](https://i.imgur.com/tprx2ZY.png) 2. **git log -g** 查看詳細 HEAD 訊息 ```shell= git log -g ``` > ![](https://i.imgur.com/dtsg5gX.png) ### 修改、訂正 Patch - amend * 有時後會出現不小心提交了描述錯誤的檔案 or add 錯檔案,按照之前的作法必須 git reset HEAD^ 退回上一個版本,再進行修改,最後提交 | 指令 | 功能 | | -------- | -------- | | git reset --soft HEAD^ | **退回到 change to be committed,++不需要再次 add 動作++** | ```shell= # 退回上一個版本,保留檔案,但是要重新 add git reset HEAD^ # 加入正確檔案 git add RIGHT.txt # 提交 git commit -m "Fix Commit Error" ``` * 而在你尚未 push 之前,我們可以使用 commit 的` --amend` 選項直接修改提交的內容 (他的行為是創建另一個新的 id) | 指令 | 功能 | | -------- | -------- | | git commit **-\-amend** | **使用 vim (要設定 core.editor)編譯** 最後一次提交的檔案 | | git commit **-\-amend** -m <title\> -m <describe\> | 不使用編譯器,改為直接修改並提交 | | git commit **-\-amend** -m <title\> | amend 還有 ==**合併提交** 的功能== ,產生一個 新的提交 ID 並且 **覆蓋** | 1. 先提交一個錯誤描述 ```shell= echo "Test git reset --soft HEAD^" >> Hi_Git.txt # 更新修改的資料 git add -u git status # 提交,但描述 -m 錯誤 git commit -m "Error test" git status git log --oneline ``` > ![](https://i.imgur.com/0oTaUcm.png) 2. 使用 **git reset --soft HEAD^**,將檔案退回到 change to be committed 狀態(不用重新 add) ```shell= # 使用 git reset --soft HEAD^ git reset --soft HEAD^ # 查看 HEAD 是否移動 git log --oneline # 查看 status,退回到 Change to be committed 狀態 git status # 再次提交 git commit -m "Right Test" git log --oneline ``` > ![](https://i.imgur.com/0AmNBQZ.png) :::warning * 在圖片中可以看到它的提交 ID 已經不一樣了,e39d77b(Error) -> 0933fd7(right),因為 **==每一次提交,必定都會有不同的 ID==** | reset 指令 | 功能 | | -------- | -------- | | git reset HEAD^ | 回到前一個 patch,且恢復檔案的狀態(不刪除檔案,但必須重新 add 檔案) | | git reset --soft HEAD^ | 到前一個 patch,但保持檔案狀態為 Changes to be committed(免去 add 動作) | | git reset --hard HEAD^ | 回到前一個 patch,且強制清除檔案的修改內容(會強制移除) | > ![](https://i.imgur.com/A8jXhft.png) ::: 3. **git commit --amend** 編譯最後一次提交的訊息 ```shell= git commit --amend # 提交 ID 會改變 git log --oneline # 查看修改的描述 git show ``` > ![](https://i.imgur.com/chMe0vv.png) 4. **git commit --amend -m** 不使用編譯器 ```shell= # 使用指令修改 git commit --amend -m "AmendTest" -m "Use command fix by --amend" git show git log --oneline ``` > ![](https://i.imgur.com/euGox2S.png) 5. **git commit 合併提交**,這次修改檔案並提交 ```shell= echo "Test git commit --amend -m <title> to merg commit" >> Hi_Git.txt git add -u git commit --amend -m "merga by commit --amend" git show ``` > ![](https://i.imgur.com/6TUcJcx.png) :::danger * 要特別 **注意使用 --amend**: 因為**就算進入 vim 後不修改 :`q!` 它仍會為你產生新的 ID**,補救方案,**使用 git reflog 查看歷史 HEAD,並使用 git reset --soft HEAD@{1}** > ![](https://i.imgur.com/IHkGIga.png) **--實做--** > ![](https://i.imgur.com/ZFi0qiT.png) ::: ### 移除單個 Patch - rebase * 之前使用移除 Patch 的方式是 git reset --hard <commit_id\>,這個方法就是移動 HEAD 會導致失去中間的檔案,而這些遺失的檔案就可以使用 git cherry-pick 取回 > 改動後的檔案由於**有新的 ID,其 parent 關係也會改變** 1. 移動 HEAD 到指定檔案 2. `cherry-pick` or `rebase -i` 需要的檔案 3. 執行串接動作 * 以下指令可以用來重新排序、刪除特定檔案 | 指令 | 功能 | | -------- | -------- | | git cherry-pick | **取回遺失檔案,串接上目前的 HEAD,++串接上的 ID 會更改,並且成為 HEAD++** | | git rebase -i <**哪裡之後的開始修改's ID**> | -i 同等於 `--interactive`,**啟動 ==互動模式==,使用文字編譯器 vim 來編譯其順序**,簡單來說它完成多次 cherry-pick 的動作 | 1. 使用 **git cherry-pick** ```shell= # 查看目前的 git 串接狀態 git log --online # 使用 reset 移動 HEAD 到 2381fc2 git reset --hard 2381fc2 # 確認移動狀況 git log --online # 取得之前區要的檔案並且串接上 (有新 id & 成為新 HEAD) git cherry-pick fb411ed git cherry-pick f278864 ``` > ![](https://i.imgur.com/2kijlaD.png) **--實做--** > ![](https://i.imgur.com/Tymv4HZ.png) :::info * **移除過後的檔案也可以使用 cherry-pick 串接回來成為 HEAD** ```shell= // 原來 (remove Target File) -> Test Git Status -> AmendTest // 使用 cherry-pick 串回 Test Git Status -> AmendTest -> (remove Target File) ``` ::: :::warning * 只要每個 patch 的改動幅度不要太大 git 都可以幫我們順利接好,但是偶爾還是會發生無法拼接的情況 > 兩次 Patch 改動很相似,導致 Conflict 版本衝突,無法合併 Code,這時候 git 會要求我們手動去解 Confilct ::: 2. 使用 **git rebase -i <after id\>** ```shell= # 先查詢目前 log 的狀況 git log --oneline # 使用 rebase -i <id>,編譯 <id> 之前的檔案 git rebase -i 320cb7e # 先查詢目前 log 的狀況 git log --oneline ``` 修改前 > ![](https://i.imgur.com/HL2gG5F.png) 進入文字編譯階段:可以看到排序方式是以逆序,越下面越新 > ![](https://i.imgur.com/FJU00iv.png) 修改後,Mark(隱藏) `7464849 Test1` 後,儲存(`:wq`)並退出 > ![](https://i.imgur.com/K4gH4uG.png) :::info * **使用 `git rebase --abort` 可以遺棄修改的內容** ::: ### 版本衝突 - cherry-pick/rebase * 版本衝突會發生在 **==修改相同的檔案==(modify),新增(add)並提交後(patch),又突然要移除某一個版本(cherry-pick)** | 指令 | 功能 | | -------- | -------- | | git cherry-pick --abort | 遺棄修改的內容 | | git cherry-pick --continue | 提交修正衝突後的內容 | | git rebase --skip | 跳過正在執行的 Rebase TODO 動作 | | git rebase --abort | 遺棄修改的內容 | | git rebase --continue | 提交修正衝突後的內容 | :::warning 衝突這滿麻煩的... 還是修改一段程式後就提交,縮小差距(修改一小段就提交),提高 git 的判斷正確性 > 但如果是多人協作同個專案的話,那衝突倒是滿正常的 ::: * **第一種 cherry-pick** 1. patch 了三次 **相同的檔案** ```shell= echo "1.Alien" >> num.txt # 依序寫資料 git add -u # 更新 git commit -m "add Alien in num.txt" # 提交 echo "2.Kyle" >> num.txt # 依序寫資料 git add -u # 更新 git commit -m "add Kyle in num.txt" # 提交 echo "3.Pan" >> num.txt # 依序寫資料 git add -u # 更新 git commit -m "add Pan in num.txt" # 提交 git log --oneline ``` > ![](https://i.imgur.com/QrZGRm6.png) 2. git reset --hard 到第一次提交的地方 > ![](https://i.imgur.com/7heAWkp.png) ```shell= # 移動到第一次提交的 patch git reset --hard 29d7d18 # 會發生衝突 git cherry-pick 4332364 # 查看 diff 差異 git diff # 查看 status 狀態 git status ``` 發生 both modified 代表兩個 patch 都有改動到該檔案(目前就是指 `num.txt` 檔案發生衝突) > ![](https://i.imgur.com/2yRaeae.png) 3. **解決衝突的檔案**:使用 vim 編輯 `num.txt` 檔案,自己解決衝突 ```shell= # 編譯衝突文件 vim num.txt ``` 在 vim 編譯中把 2.Kyle 移除,並移除其他的符號(`===`, `>>>`, `<<<`, ... 等等,其他非修改的字)並儲存檔案(`:x!` or `:wq`) | 符號 | 代表 | | -------- | -------- | | <<<<<<< HEAD | 在這個符號以下會(到 = 符號)顯示,**這裡是 ==HEAD 對該檔案做的修改==** | | ======= | 分隔線 | | >>>>>>> 4332364... <Patch 描述> | **==目前打算 cherry-pick 的 Patch,對這個檔案做的修改==** | 這種標記可能會出現在多個地方(多地方衝突) > ![](https://i.imgur.com/Mj7LwDG.png) 4. git diff 觀察修改後的檔案 ```shell= # 查看修改後 diff 差異 git diff ``` | 前 | 後 | 內容 | 修改 | | -------- | -------- | -------- | - | | | | 1.Alien(HEAD) | 無修改 | | -(與 HEAD 比較) | | 2.Kyle | 移除 | | | + | 3.Pan(與 HEAD 比較) | 新增 | > ![](https://i.imgur.com/82g8k1J.png) 5. 更新檔案 `git add -u`,並繼續提交 `git cherry-pick --continue` ```shell= git add -u git status # 提交後的檔案要查看必須要使用 git diff `--cached` or `--staged` git diff --cached # cherry-pick --continue 繼續提交,進入編譯狀態,儲存後離開 git cherry-pick --continue git log --oneline ``` > ![](https://i.imgur.com/QFS8ccS.png) `git cherry-pick --continue` 進入編譯,儲存離開 > ![](https://i.imgur.com/8yUZ0rr.png) 查看 log 紀錄,以下顯示成功移除指定 Patch 並加入指定 Patch > ![](https://i.imgur.com/2aNGUDs.png) :::success * **Cherry-pick 後,原本的 patch id 就會改變,再接上你需要的 branch** ::: * **第二種 rebase**:rebase 發生充同的處理方法與 cherry-pick 差不多 (所以加快速度...很累的) 1. 把 Kyle 新增回 `num.txt `檔案,更新並提交,**目的將順序由 `1 -> 3` 改回 `1 -> 2`** ```shell= echo "2.Kyle" >> num.txt cat num.txt git add -u git commit -m "Add Kyle in num.txt" git log --oneline ``` > ![](https://i.imgur.com/ldFqAn3.png) 2. `git rebase -i HEAD^^` 使用 vim 移動 Patch 順序 (交換兩個 Patch 順序,如下圖),當然也可以使用指定 id ```shell= # 發生衝突 git rebase -i HEAD^^ ``` 修改前後順序 > ![](https://i.imgur.com/2GLztta.png) 改變順序後,發生衝突 > ![](https://i.imgur.com/Qv8VXzi.png) 3. 處理衝突:使用 vim 編輯 `num.txt` 檔案,手動處理衝突 ```shell= vim num.txt git diff git add -u git diff --cached # 提交 git rebase --continue ``` 修改前後,並儲存 > ![](https://i.imgur.com/XLh4Es7.png) 新增後的差異 > ![](https://i.imgur.com/xXCRiqp.png) continue 提交 Patch 過後會接續處理,Pan Patch 的問題 > ![](https://i.imgur.com/t4foPlF.png) :::info * **使用 rebase 會產生暫時的隱藏檔案**, .<原檔名>.swp,這個例子就會產生 `.num.txt.swp`,在 rebase 結束後就會消失 ::: 4. 再次編譯 vim `num.txt` 的檔案 :::info * 為什麼要解兩次的衝突? **你把 `rebase` 想成多次 `cherry-pick`,由於是多次,所以可能產生多次的衝突**,所以你必須解決每次提交的衝突點 > 建議如果有多次 commit,在你要合併回新分支時最好使用 `merge` 而不是 `rebase`(你可能不會記得當初為什麼要改動) ::: ```shell= vim num.txt git diff git add -u git diff --cached # 提交 git rebase --continue # 查看 ``` vim 編譯順序的內容 > ![](https://i.imgur.com/5V1AKoL.png) 新增檔案後並提交 > ![](https://i.imgur.com/b5Haqkh.png) 查看 log,**交換成功 !!!** > ![](https://i.imgur.com/9e475PT.png) :::success * rebase 衝突需要一個一個解決 ::: ### 指定還原 - revert * 以往要修改其中一個 Patch 時,需要使用 `cherry-patch` 多個檔案 or `rebase` 編譯多個檔案;`revert` 指定還原的概念是說,**把定特的 Patch 內容做修改,再加入新串列** > 這個概念就像是發現多的字串,於是在後面串一個 Patch 來消除多餘的字(類似於 +100 -100 的概念) > ![](https://i.imgur.com/KhH4C2R.png) | 指令 | 功能 | | -------- | -------- | | git revert -i <id\> | 修改內容,修改後會串接上新的 Patch | | git revert --continue | **add 檔案後的操作** | | git revert --abort | 拋棄這次的 revert 動作 | > ![](https://i.imgur.com/2uejFE6.png) :::info revert 也如同 cherry-pick & rebase 一樣會有版本衝突的問題,必須手動編譯解決 ::: 1. 按照目前的 `gitk` 狀態圖,假設發現 `fix bug 1` 修復有問題,但是已經提交兩次 Patch (`fix bug3` & `fix bug 2` 已經提交) > ![](https://i.imgur.com/7ua2AEo.png) 2. 這時就可以使用 revert -i <指定 ID> ```shell= # revert 指定 id (也就是 fux bug 1) git revert db37e77 # 狀態 git status ``` 發生衝突檔案,這時就有手動解決 > ![](https://i.imgur.com/rH1tNHc.png) 3. 使用 vim 編輯器解決衝突檔案,最後在儲存起來 ```shell= # 編輯檔案 vim fixBug.txt ``` > ![](https://i.imgur.com/Bxwok7e.png) 4. `git add` 添加改變後的檔案 ```shell= git add fixBug.txt ``` 綠色的點點就是我們 add 之後的檔案(`Local changes checked in to index but not committed`) > ![](https://i.imgur.com/GRWP0l2.png) 5. `revert -- continue` 添加完檔案後就繼續 `revert` ```shell= # 繼續 revert git revert --continue git status ``` 編譯 commit 描述,之後就可以儲存離開 > ![](https://i.imgur.com/tuSjz3D.png) 6. 查看結果:以 `gitk` 查看 `revert` 後的結果 > ![](https://i.imgur.com/z4XVdSr.png) ```shell= # 狀態 git status # 查看文件 cat fixBug.txt ``` > ![reference link](https://i.imgur.com/CbGBSXt.png) ## `.git` 目錄 以下將會簡單介紹 `.git` 目錄,以及它們的功能作用 ### config 文件 * config 內部存有部分使用者設定的值 (git config 設定),也有 branch 的資料 ```typescript= [core] repositoryformatversion = 0 filemode = false bare = false logallrefupdates = true symlinks = false ignorecase = true [remote "origin"] url = git@github.com:XXX/TestGit.git fetch = +refs/heads/\*:refs/remotes/origin/\* [branch "master"] remote = origin merge = refs/heads/master [branch "test"] remote = origin merge = refs/heads/test ``` > ![](https://i.imgur.com/pUlVmDp.png) ### index 文件 * 存有開法者本地所有的 Patch 資訊 (16 進位儲存) > ![](https://i.imgur.com/3Wom2P4.png) ### refs/tags 目錄 * 存有所有我們所創建的標籤,若要指定刪除遠端標籤,就是指定該目錄 > ![](https://i.imgur.com/PFAh4Cg.png) ## Appendix & FAQ :::info ::: ###### tags: `Git`