---
# System prepended metadata

title: Git 使用 - 本地
tags: [Git]

---

---
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`