---
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/) 並安裝
> 
:::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
```
> 
2. Android Project 資料夾下,在創建專案時就有預設 `.git` 檔案
> 
:::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
```
> 
```shell=
# 查詢全部設置
git config --list
```
> 
### 定義指令別名 - 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
```
> 
2. 設定 `--global alias`
```shell=
# 設定 status 別名為 sta
git config alias.sta status
# 查看本域 config 設定
git config --list
```
> 
## 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
```
> 
2. `git diff <file>` 查看特定檔案的**修改內容**,diff 是與最後一次 patch 比對差異,`+` 號代表了新增
```shell=
git diff Hello_Git.txt
```
> 
3. `git diff` 假設有**多個檔案被更改**
```shell=
# 新增檔案
echo "YoMan" > Yo.txt
# 使用 git status 查看,會發先多了一個 Untracked files
git status
# 將該目錄下所有檔案加入 git 倉庫
git add .
# 再次查看狀態
git status
```
> 
:::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 查詢指定提交
```
> 
5. `show log --oneline` 查看簡單的 id & title
> 
### 刪除檔案 - 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
```
> 
2. 更新檔案狀態 git add -u
```shell=
// 更新檔案
git add -u
// 同上
git add --update
```
> 
### 移動檔案 - 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" # 描述時中間有空白格就必須使用雙引號括起來
```
> 
:::warning
* git mv **不可以搬移檔案到倉庫以外的地**方,否則會出 fatal: outside repository 的錯誤,並無法移動
> 
:::
### 重新命名 - 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"
```
> 
:::info
* **git 會自動忽略空資料夾**,並不會把空資料夾方入倉庫中,所以 rm -rf Myfile 在 git status 上並不會有差異
> 
:::
### 忽略文件 - .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` 中添加測試文件就可以
> 
:::
* 當然也可以使用指令,只加入某個檔案
| 指令 | 功能 |
| -------- | -------- |
| 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
```
> 
### 檔案狀態 - status
* 從 **git status** 可以看到幾種狀態
| 狀態 | 檔案狀態 | 意義 |
| - | -------- | -------- |
| `Untracked` | Untracked files | 全新檔案(尚未添加) |
| `Unmodified`/`Staged` | Changed to commit | 檔案更改並 git 知道,但未提交(已經在 Git 控管之下) |
| `Modofied` | Changes not staged for commit | 檔案更改,但 git 尚未暫存(不會進入下次的 commit 中) |
> 
1. **全新檔案 `Untracked files`**:git 尚未檔案感知到有該檔案的存在
```shell=
# 新增檔案
echo "New File" >> NewFile.txt
ls -l
# 查看檔案狀態
git status
```
> 
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
```
> 
:::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
```
> 
:::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 追蹤的內容
> 
2. **git reset Head <指定檔案>**
對於已經添加(已經 add)的檔案進行 `git reset Head` 指令,可以將其退回到 HEAD 的狀態(HEAD 之後說明)
> 
## 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
```
> 
### HEAD 關鍵字
* **HEAD 為 git 的關鍵字,意指==目前所在 patch 的位置==,也就是++串列的頭++** (常常是代表你當前所在的位置)
> 
| 指令 | 功能 |
| -------- | -------- |
| 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
```
> 
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
```
> 
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 # 檔案沒有被移除
```
> 
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 的掌控)
>
> 
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 並無差異**
> 
:::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}
```
> 
2. **git log -g** 查看詳細 HEAD 訊息
```shell=
git log -g
```
> 
### 修改、訂正 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
```
>

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
```
> 
:::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,且強制清除檔案的修改內容(會強制移除) |
> 
:::
3. **git commit --amend** 編譯最後一次提交的訊息
```shell=
git commit --amend
# 提交 ID 會改變
git log --oneline
# 查看修改的描述
git show
```
> 
4. **git commit --amend -m** 不使用編譯器
```shell=
# 使用指令修改
git commit --amend -m "AmendTest" -m "Use command fix by --amend"
git show
git log --oneline
```
> 
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
```
> 
:::danger
* 要特別 **注意使用 --amend**:
因為**就算進入 vim 後不修改 :`q!` 它仍會為你產生新的 ID**,補救方案,**使用 git reflog 查看歷史 HEAD,並使用 git reset --soft HEAD@{1}**
> 
**--實做--**
> 
:::
### 移除單個 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
```
> 
**--實做--**
> 
:::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
```
修改前
> 
進入文字編譯階段:可以看到排序方式是以逆序,越下面越新
> 
修改後,Mark(隱藏) `7464849 Test1` 後,儲存(`:wq`)並退出
> 
:::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
```
> 
2. git reset --hard 到第一次提交的地方
> 
```shell=
# 移動到第一次提交的 patch
git reset --hard 29d7d18
# 會發生衝突
git cherry-pick 4332364
# 查看 diff 差異
git diff
# 查看 status 狀態
git status
```
發生 both modified 代表兩個 patch 都有改動到該檔案(目前就是指 `num.txt` 檔案發生衝突)
> 
3. **解決衝突的檔案**:使用 vim 編輯 `num.txt` 檔案,自己解決衝突
```shell=
# 編譯衝突文件
vim num.txt
```
在 vim 編譯中把 2.Kyle 移除,並移除其他的符號(`===`, `>>>`, `<<<`, ... 等等,其他非修改的字)並儲存檔案(`:x!` or `:wq`)
| 符號 | 代表 |
| -------- | -------- |
| <<<<<<< HEAD | 在這個符號以下會(到 = 符號)顯示,**這裡是 ==HEAD 對該檔案做的修改==** |
| ======= | 分隔線 |
| >>>>>>> 4332364... <Patch 描述> | **==目前打算 cherry-pick 的 Patch,對這個檔案做的修改==** |
這種標記可能會出現在多個地方(多地方衝突)
> 
4. git diff 觀察修改後的檔案
```shell=
# 查看修改後 diff 差異
git diff
```
| 前 | 後 | 內容 | 修改 |
| -------- | -------- | -------- | - |
| | | 1.Alien(HEAD) | 無修改 |
| -(與 HEAD 比較) | | 2.Kyle | 移除 |
| | + | 3.Pan(與 HEAD 比較) | 新增 |
> 
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
```
> 
`git cherry-pick --continue` 進入編譯,儲存離開
> 
查看 log 紀錄,以下顯示成功移除指定 Patch 並加入指定 Patch
> 
:::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
```
> 
2. `git rebase -i HEAD^^` 使用 vim 移動 Patch 順序 (交換兩個 Patch 順序,如下圖),當然也可以使用指定 id
```shell=
# 發生衝突
git rebase -i HEAD^^
```
修改前後順序
> 
改變順序後,發生衝突
> 
3. 處理衝突:使用 vim 編輯 `num.txt` 檔案,手動處理衝突
```shell=
vim num.txt
git diff
git add -u
git diff --cached
# 提交
git rebase --continue
```
修改前後,並儲存
> 
新增後的差異
> 
continue 提交 Patch 過後會接續處理,Pan Patch 的問題
> 
:::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 編譯順序的內容
> 
新增檔案後並提交
> 
查看 log,**交換成功 !!!**
> 
:::success
* rebase 衝突需要一個一個解決
:::
### 指定還原 - revert
* 以往要修改其中一個 Patch 時,需要使用 `cherry-patch` 多個檔案 or `rebase` 編譯多個檔案;`revert` 指定還原的概念是說,**把定特的 Patch 內容做修改,再加入新串列**
> 這個概念就像是發現多的字串,於是在後面串一個 Patch 來消除多餘的字(類似於 +100 -100 的概念)
> 
| 指令 | 功能 |
| -------- | -------- |
| git revert -i <id\> | 修改內容,修改後會串接上新的 Patch |
| git revert --continue | **add 檔案後的操作** |
| git revert --abort | 拋棄這次的 revert 動作 |
> 
:::info
revert 也如同 cherry-pick & rebase 一樣會有版本衝突的問題,必須手動編譯解決
:::
1. 按照目前的 `gitk` 狀態圖,假設發現 `fix bug 1` 修復有問題,但是已經提交兩次 Patch (`fix bug3` & `fix bug 2` 已經提交)
> 
2. 這時就可以使用 revert -i <指定 ID>
```shell=
# revert 指定 id (也就是 fux bug 1)
git revert db37e77
# 狀態
git status
```
發生衝突檔案,這時就有手動解決
> 
3. 使用 vim 編輯器解決衝突檔案,最後在儲存起來
```shell=
# 編輯檔案
vim fixBug.txt
```
> 
4. `git add` 添加改變後的檔案
```shell=
git add fixBug.txt
```
綠色的點點就是我們 add 之後的檔案(`Local changes checked in to index but not committed`)
> 
5. `revert -- continue` 添加完檔案後就繼續 `revert`
```shell=
# 繼續 revert
git revert --continue
git status
```
編譯 commit 描述,之後就可以儲存離開
> 
6. 查看結果:以 `gitk` 查看 `revert` 後的結果
> 
```shell=
# 狀態
git status
# 查看文件
cat fixBug.txt
```
> 
## `.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
```
> 
### index 文件
* 存有開法者本地所有的 Patch 資訊 (16 進位儲存)
> 
### refs/tags 目錄
* 存有所有我們所創建的標籤,若要指定刪除遠端標籤,就是指定該目錄
> 
## Appendix & FAQ
:::info
:::
###### tags: `Git`