---
tags: Git
---
# Git 版本控管實戰:新手進階篇
## Git 內部架構解析
### 版本控管(Version Control)
* 完整記錄軟體變化的過程(人、事、時、地、物)
* 紀錄版本變化而𧗠生出
* 查詢歷史紀錄
* 復原變更
* 比對差異
* 標記版本
* 變更追蹤
* 多人版控進一步衍生出
* **協同作業**
* 分支合併
* **版控流程**
* 發行管理
### 分散式版本控管(DVCS)
* 優點
* 本地端的工作區會保有完整的儲存庫
* 每個人都擁有一分完整的儲存庫備分(Full Backup) => 分散式
* 不需要 Server 支援即可運作版控 => 不用網路連線,大幅節省開發時間
* 操作都在本地儲存庫中 => 快、離線的版本、完整的歷史紀錄
* 擁有強悍的**合併追蹤**能力
* 取得他人變更版本後,可透過合併方式進行整合
* 合併多人的版本只要有存取共用儲存庫的權限或管道即可
* 缺點
* 無法**鎖定**版控策略(僅能使用合併策略)==> **專注在自己的分支**
* 無法針對專案進行**精細的權限控管**
* 若要鎖定特定資料夾,可透過 Submodules 實現 (Owner 完整的 repo,其他人則在 Submodules 內進行)
### 工作區(Workspace / Work Tree)
https://gitbook.tw/chapters/using-git/working-staging-and-repository.html
* 頻繁異動的開發目錄
* 在工作區執行任意 git 命令
* 內含 .git 隱藏資料夾(本地儲存庫)
* 沒有 .git 目錄 => `git init`
* 刪掉 .git 目錄 => 脫離版控
* 擁有 .git 目錄 == 擁有所有原始碼
* 工作區底下檔案的狀態
* untracked
* tracked
* unmodified
* modified
* staged
### 儲存庫(Repository)
* 本地儲存庫(Local Repository)
* 預設位於 .git 資料夾
* 遠端儲存庫(Remote Repository)
* 僅**儲存庫** (Bare Repository)
* 實際上是將本地的 .git 資料夾上傳到遠端儲存庫
* 透過 `git clone --bare` 僅下載遠端儲存庫回來
* 共用儲存庫(Shared Repository)
* `git init --bare` 建立共用儲存庫
* 並不是僅限於git Server,只要是能遠端同步的工具,都可以當作遠端儲存庫使用
* Dropbox、Google Drive ...etc
* 再用 `git clone file:///path/to/repo.git/` checkout 即可
#### `--bare`
* https://moelove.info/2016/12/04/Git-%E6%9C%AC%E5%9C%B0%E4%BB%93%E5%BA%93%E5%92%8C%E8%A3%B8%E4%BB%93%E5%BA%93/
* 在預設的情況下,不管是 `git init` 或 `git clone` 都會一併建立[工作區(Workspace / Work Tree)](#%E5%B7%A5%E4%BD%9C%E5%8D%80%EF%BC%88Workspace--Work-Tree%EF%BC%89)
* 包含 tags、local branches,但不包括 remote tracking branches
* 若不須要 git workspace,搭配 `--bare` 參數即可乎略(不建立)git work tree
* 若在沒有 work tree 的 repo 進行操作,則會出現訊息:`fatal: this operation must be run in a work tree`
#### `--mirror`
* https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---mirror
* https://stackoverflow.com/questions/3959924/whats-the-difference-between-git-clone-mirror-and-git-clone-bare
* 含蓋所有的 refs(即包含 tags、local branches、remote tracking branches)
### 了解 Git 資料結構
#### 物件(Object):immutable
* [Git Internals - Git Objects](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects)
* 用來保存**儲存庫**中所有的**檔案**與**版本紀錄**
* 類似當下狀態的 snapshot
* 區分四種物件類型
* blob
* 檔案內容
* `git hash-object <fileName>` => 根據檔案內容產生物件名稱
* commit => 提交
* author 和 committer 可以不一樣(ex. 重新 commit:rebase、`commit --amend`)
* tree => 目錄
* 檔案名稱不變,內容改變 => 新的 tree
* 檔案名稱改變,內容不變 => 新的 tree
* tag
* annotated tag 為物件型態(with `-a` property):以二進制檔案型式儲存
* lightweight tag 為參考型態(without `-a` property):以文字檔案型式儲存 (不會產生 tag 物件)
* 可使用 [`git cat-file`](https://git-scm.com/docs/git-cat-file) 可以知道 git objects 的類型
* `git cat-file` 看到的 ID 為檔案**內容**所運算(SHA-1 hash)而來,若 git object 檔名相同(即 ID 相同),則代表檔案內容一致
* `-t` : type 類型
* `-p` : print 內容
* `-s` : size 檔案大小
* 以下圖為範例
```shell=
$ git cat-file -t a821ff3dfa531821967cc0380570bb01356c05a2
tree
# 放置 git object 之資料夾名稱 = a8
# git object 檔案名稱(ID) = 21ff3dfa531821967cc0380570bb01356c05a2
# 使用 sha1 值前 2 碼當作目錄名稱,目的在於提升讀取效率
```
```
.git
|
...
|
|- objects // 所有資料都在 objects 底下
| |- a8
| | 21ff3dfa531821967cc0380570bb01356c05a2
| |
| |- f5
| | eea678d87a8664e4c76e12d3ef5c4ff775ad58
| |
| |- fb
| | 8821d9a4cd57a2e6c5e4ae6a6706bcb89f2ce7
| |- info
| '- pack
'- refs
|- heads
'- tags
```
* **頻繁變動的大檔案**不應 commit 到 Git (因為占空間)
* 1g 的檔案,就會產生 1g 的 blob => 使用 [Git Large File Storage (LFS)](https://git-lfs.github.com/) 解決 (需 server 有支援)
#### 索引(index)
* 記錄有哪些檔案即將要被提交到下一個 commit 版本中 (保存要進儲存庫之前的**所有檔案狀態**)
* `git add` 會將 tracked file 記錄到 git index file(git file status => Staged),並且建立一個 blob 物件([Blob 對象](http://www-cs-students.stanford.edu/~blynn/gitmagic/intl/zh_tw/ch08.html#_blob%E5%B0%8D%E8%B1%A1))
* working directory => index => object storage
* ~~俗話說的好:有 `git add` 有保佑~~,有 `git add` 過都會有檔案的 blob,即使沒有 commit 指向它
* `git commit` 時,會建立 commit object file,並指向 git index file 中異動檔案的 blob 物件
* 屬於一種「可變的」(mutable) 檔案類型
* 主要位於 `.git/index` 檔案
* 包含了所有**版控中**的檔案(可利用 .gitignore 排除檔案受到 index 版控)
* 空資料夾是無法加入版控的 (通常會用 `.gitkeep` 空檔使資料夾加入版控)
* 工作區與索引的檔案為一對一Mapping
* 索引有可能錯亂 XD (壞掉時將 index 砍掉重建就好了)
### Git 物件結構的優點
* 有效率的處理大型專案
* 以**檔案內容**換算 SHA1 Hash => 絕對不可能發產生衝突
* 歷史紀錄保護
* 每個版本包含上一個版本的 hash 值
* 檢查 git 儲存庫的完整性:`git fsck`
* 定期的封裝物件
* 對於不常用的物件會自動進行壓縮處理
* `git gc` => 壓縮到 objects/pack 目錄下,同時會將不常用到的物件刪除
* 使用 `git gc --prune=now` 會立刻清除 Unreachable 物件
* 等價於 `git gc` + `git prune --expire=now`
* ex: `git add .` 後又修改了同一份檔案後再次 `git add .` ==> 產生兩個 blob 物件,但 commit 僅參考到最後一份 blob 物件,第一份 blob 物件會在 `git gc` 時被清除
### 儲存庫、工作目錄與索引的關係圖
```
| | | |
|------------------ git commit -a ------------------>| |
| | | |
|------- git add (-u) ------>|----- git commit ----->| |
| | commit / tree / tag | |
| | |-- git push -->|
| | | |
工作目錄 索引 本地儲存庫 遠端儲存庫
| | | |
|<---------------------------- git pull -----------------------------|
| | | |
| | |<- git fetch --|
| | | |
|<----------- git checkout <branch_name> ------------| |
| | | |
|<-- git checkout -- file ---| | |
| | | |
| |-- git diff --cached --| |
| | | |
|--------- git diff ---------| | |
| | | |
```
### git checkout
從索引內取得檔案 / 資料夾內容
#### `--`
請參考:[還原](#還原)
* [git checkout -- . vs git checkout](https://stackoverflow.com/questions/41101998/git-checkout-vs-git-checkout)
* [Why do we use double dash in “git checkout -- .”?](https://stackoverflow.com/questions/23208156/why-do-we-use-double-dash-in-git-checkout)
### Git Reset 應用技巧
* https://dotblogs.com.tw/wasichris/2016/04/29/225157
* 主要用途:將當前分支復原變更
* `--mixed` | `--soft` | `--hard`
* `--mixed` [預設值] 工作目錄變更會保留,也就是保留 working directory 中的異動內容 (僅更新索引及指標位置)
* `--soft` 工作區目錄變更會保留,同時也保留 index 中的異動內容
* `--hard` 工作區檔案內容未 commit 的會被直接還原到指定版本
* [`--soft vs. --hard`](https://stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard)

|Reset 模式|所在位置(HEAD)|變更狀態紀錄(INDEX)|工作目錄|說明|
|--|--|--|--|--|
|soft|changed|unchanged|unchanged|僅移除 **commit** 變成新版未 commit,內容仍是新版的|
|mixed|changed|changed|unchanged|index 移除 **staged** 標記,變成 **Modified** or **Untracked**,內容是新版的|
|hard|changed|changed|changed|回到上一版版本,其間變更完全移除(接近 svn revert),內容及狀態皆是上一版|

* 取得檔案變更應使用 `git checkout` 而非濫用 `git reset`
* `git reset -p`: 互動式選擇還原區塊
#### 復原最近一次重置 (reset)、合併 (merge) 或 重訂基底 (rebase)
* `git reset --hard ORIG_HEAD`: 復原**重大變更**的前一版
#### `git reflog`
* 利用 git commit object 在 `git gc` 之前都存在的特性,將因 reset 而沒有指向的 commit 調出來
* `reflog` 列出來的內容是做完指令的結果,所以要還原必須再往前找一版
#### `commit --amend`
* 重新 commit 最近的 commit
* 修改 commit 的 Git user name、email:
* [`--reset-author`](https://git-scm.com/docs/git-commit#git-commit---reset-author)
* [`--author=<author>`](https://git-scm.com/docs/git-commit#git-commit---authorltauthorgt):`git commit --amend --author="Author Name <email@address.com>" --no-edit`
* https://help.github.com/articles/setting-your-commit-email-address-in-git/
* https://stackoverflow.com/questions/3042437/change-commit-author-at-one-specific-commit
* https://stackoverflow.com/a/1320317/8530187
* https://yulun.me/2014/git-tips-change-author-and-email-in-previous-commits/
## Git 分支合併技巧
### 分支 (Branching)
* 一個指向某個 commit id 的 pointer
* 每一個 commit 是將 index 的內容寫入 respository(請參考索引一節)
* branch、lightweight tag(非 `-a`)、HEAD 都是一種指向 commit 的**指標**(以文字檔案型式儲存著 commit id)
* 分支圖是從 refs 中找出所有儲存指標的檔案,並將它們所指向的 commit 及其 parents 繪製出來
* HEAD 指向一個沒有 branch 指向的 commit => 稱為 detached HEAD
* 分支刪除並不會保留任何紀錄
### 孤兒分支(orphan)
* `git checkout --orphan <branch_name>`
* 沒有 parent 的 commit object
* 一個 repo 中可以有多個 root commit
* 範例:
* GitHub Pages
* [台北捷運](https://github.com/othree/taipei-mrt)
* 設計師將寫好的版型放到 orphan 分支,前端工程師比對差異進行套版
### 關於「分支」的真正意義
* 標記"時間維度"的指標
* 「分支 Branch」是一個會隨時間演進的指標,當下時間點的索引狀態 (開發用)
* 任何一條分支的異動,都不會影響其他分支
* 「標籤 Tag」是一個不會隨時間演進的指標 (參考用)
* 「切換分支 Checkout」等同於「控制時間維度」
### 檢示所有分支詳細資訊
* https://git-scm.com/docs/git-branch#git-branch--v
### 分支更名(改名)
* https://git-scm.com/docs/git-branch#git-branch---move
* `git branch --move [<oldbranch>] <newbranch>`
* 重新命名 `.git/refs/heads/<oldbranch>` 分支
### 合併「不同世界」的分支
* `git merge --ff`(default)=> 快轉 (Fast Forward):快速合併,直接使用每一次異動的 commit,最好使用在同一分支的情況下,ex. 本地 merge 遠端
```
C <-- develop C <-- develop, test
| |
B <-- test ==> B
| |
A A
```
* 只有異動 commit 的父 commit 才能使用 Fast Forward 至異動(同一條 branch)
* `git merge --no-ff`=> No Fast Forward:將多個異動併為一個 commit
* 兩條意義不同的分支合併應該使用此合併策略,使線圖清晰
* master 每一個版本都會是穩定版
```
M
| \ <-- develop
| o <-- test
| o
M <-- develop | o
| o <-- test ===> o |
o o o |
o o o |
o / o /
I I
```
* `git merge --ff` vs. `git merge --ff-only`
* `--ff`:預設行為,若不行使用 Fast Forward 則用自動轉為 No Fast Forward
* `--ff-only`:明確要求必須使用 Fast Forward,若不能使用 Fast Forward 時則拒絕操作
* `git pull` == `git fetch` + `git merge`
* 所以遠端比本地新旳時候會無法 `git push`
* `git pull --rebase`
* https://ihower.tw/blog/archives/3843
* 加上 rebase 的意思是,會先
1. 把本地 repo 從上次 pull 之後的變更暫存起來
2. 回復到上次 pull 時的情況
3. 套用遠端的變更
4. 最後再套用剛暫存下來的本地變更
* [`git pull --rebase --autostash`](https://git-scm.com/docs/git-pull#git-pull---autostash)
* 無法 Fast Forward 的情形,本地端與遠端版本已經不是同一條分支了
```
O <-- origin/master
M | <-- master
o o
o /
I
```
* 解法一:`git cherry-pick <commit_id>`
* 解法二:`git rebase` (等同於 reset 後再做 cherry-pick)
### 合併沒有共同祖先的分支 (即 orphan branch)
* 透過 `git merge --allow-unrelated-histories <orphan_branch_name>` 合併孤兒分支
### `--no-ff` vs. `--no-ff --no-commit` vs. `--squash`
* [What are the differences between `--squash` and `--no-ff --no-commit`?](https://stackoverflow.com/questions/11983749/what-are-the-differences-between-squash-and-no-ff-no-commit)
* `--no-commit`:套用合併內容而不直接 commit,待使用者自行 commit
* 合併但不 commit (會同時 add to index)
* 合併成功 != 建置成功,確定可正常建置再自行 commit
* http://www.jianshu.com/p/58a166f24c81
* `--no-ff` 產生的 merge commit 上不會有 diff 資訊,要自行到被併入的 branch 中查看各個 commit
#### `--no-ff`
`(master) $ git merge --no-ff topic`
```
A -- B -- C topic A -- B -- C topic
/ ===> / \
D -- E -- F -- G master D -- E -- F -- G -- H master
```
#### `--no-ff --no-commit`
```bash
(master) $ git merge --no-ff --no-commit topic
Automatic merge went well; stopped before committing as requested
(master) $ git status
On branch master
Your branch is up to date with 'origin/master'.
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: README.md
modified: package.json
...
```
```
A -- B -- C topic A -- B -- C topic
/ ===> / \
D -- E -- F -- G master D -- E -- F -- G -- H master
```
#### `--squash`
```bash
(master) $ git merge --squash topic
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
(master) $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
modified: package.json
...
```
```
A -- B -- C topic A -- B -- C topic
/ ===> /
D -- E -- F -- G master D -- E -- F -- G -- H master
```
* 壓縮合併,但不產生線圖
* 等價於 cherry-pick & rebase -i squash
### 合併策略
* https://git-scm.com/docs/git-merge#_merge_strategies
* https://git-scm.com/docs/git-rebase#_merge_strategies
* https://git-scm.com/docs/merge-strategies
* https://www.jianshu.com/p/58a166f24c81
* https://www.cnblogs.com/chaoguo1234/p/5347623.html
* `git [merge | rebase] [-X ours | theirs] <commit>`
* `-X ours`:以目前 head 所在的 branch 為主
* `-X theirs`:以不是 head 所在的 branch 為主
* `git checkout [--ours | theirs]`
* https://gitbook.tw/chapters/branch/fix-conflict.html
* https://nitaym.github.io/ourstheirs/
* 衝突方向
* 目前 head 指向的分支內容:current change
* 對方分支內容:incoming change
* 例如:
* `(dev)$ git merge master`:dev <- current(在 dev 上建立 merge commit 將 master 內容 merge 至 dev,會把 merge commit 的另一個 parent 指向 master)
* `(dev)$ git rebase master`:master <- current(將 dev 上的 commit 一個一個重新 commit 到 master,head 指向 master 且會一個一個移動到後方重新 commit 的新 commit)
### 列出已合併/未合併的分支
* 已合併: `git branch --merged`
* 刪除已合併的分支 `git branch --merged | egrep -v "(^\*|master|dev)" | xargs git branch -d`
* egrep 排除 當前分支、master、dev
* https://stackoverflow.com/questions/6127328/how-can-i-delete-all-git-branches-which-have-been-merged
* 未合併: `git branch --no-merged`
## Rebase
git rebase 的重新 commit 為:
1. 建立一個沒有名字的 branch
2. 在此 branch 提交
3. 將原 branch 指向此 branch
https://blog.yorkxin.org/2011/07/29/git-rebase
```
A -- B -- C topic A' -- B' -- C' topic
/ ===> /
D -- E -- F -- G master D -- E -- F -- G master
```
* 在 topic 下指令 `git rebase master`
* 移花接木,將 master 的 commit 接在 topic 的 commit之前(**不影響 master 之線圖**)
1. 先 `checkout` 到指定 commit
2. 後 `cherry-pick` 到一個沒有 branch 指向的 commit
3. 再 `checkout` 回自己 commit 後 `reset` 回沒有名字的 branch
* rebase 的目的在於避免 merge 衝突
* 遠端有更新時,應當立刻 rebase!!
* 通常有遠端的時候才會需要用到
* [`git rebase --autostash`](https://git-scm.com/docs/git-rebase#git-rebase---autostash)
### `git rebase --skip`
* https://git-scm.com/docs/git-rebase#git-rebase---skip
* https://stackoverflow.com/questions/9539067/what-exactly-does-git-rebase-skip-do
* 跳過(略過)目前衝突的 commit,會放棄此 commit 的異動
### `git rebase -i`
* `git rebase -i <base_commit_id>`(可修改 base commit 後的 commit)
* 參數 i 代表:Interactive Mode 互動模式
* pick = 要這條 commit ,什麼都不改
* reword = 要這條 commit ,但要改 commit message
* edit = 要這條 commit,但要改 commit 的內容
* 修改完 commit 內容後可以再繼續新增其他 commit (於既有歷史紀錄中插入新版本)
* squash = 要這條 commit,但要跟前面那條合併,並保留這條的 messages
* 於 commit 時加上 `--squash=<commit>` 標註要壓縮哪個版本
* `git rebase -i --autosquash` 會自動將修正版本接在 commit 版本之後並設為 squash
* [git rebase --autosquash](https://git-scm.com/docs/git-rebase#git-rebase---autosquash)
* fixup = squash + 只使用前面那條 commit 的 message ,捨棄這條 message
* 於 commit 時加上 `--fixup=<commit>` 標註要修正哪個版本 (效果同 squash)
* exec = 執行一條指令(如執行 git 指令)
* drop = 不要這條 commit
* 還可以調整 commits 的順序,直接剪剪貼貼,改行的順序就行了
* rebase 修訂衝突後,應使用 `git add <file>` 標註衝突已解決,並使用 `git rebase --conitnue` 完成 (**而不是增加額外的commit**)
* 修改第一個初始 commit
* `git rebase -i --root`
* 最早可跳至初始 commit 後的狀態
* https://stackoverflow.com/questions/2119480/edit-the-root-commit-in-git
* https://git-scm.com/docs/git-rebase#git-rebase---root
### `git rebase --onto`
* https://blog.yorkxin.org/2011/07/29/git-rebase
* https://git-scm.com/docs/git-rebase
* 指定要 rebase 的 base commit
* 可用於要 rebase 已 merge 的 branch,解決只用 rebase 可能會無法正確 rebase 的情形
* `git rebase --onto <new base-commit> <current base-commit> <target-branchName>`
* `git rebase --onto <new base-commit> <current base-commit>`(rebase 目前的分支)
## `git cherry-pick`
* `--no-commit`:先不 commit,可利用此參數來多次 cherry-pick 不同的 commit 內容
* 在真正 commit 之前,都是屬於 cherry-pick 的過程
* 過程中可以 `git cherry-pick --abort` 來中斷 cherry-pick
* 可以跨 repository 挑選需要的 commit
### `git cherry-pick` 多個 commit
* 多個 commit:`git cherry-pick commit1 commit2`
* 多個連續 commit:`git cherry-pick preStartCommit..endCommit` 或 `git cherry-pick startCommit^..endCommit`
* https://git-scm.com/docs/git-cherry-pick#git-cherry-pick-codegitcherry-pickmaintmasternextcode
* https://stackoverflow.com/questions/1670970/how-to-cherry-pick-multiple-commits
* https://www.jianshu.com/p/08c3f1804b36
* https://segmentfault.com/q/1010000010185984
* `preStartCommit..endCommit` 代表從 `preStartCommit`(不包含此 commit 之異動內容)到 `endCommit` 為止的所有 commit
* `startCommit^..endCommit` 代表從 `startCommit`(包含此 commit 之異動內容)到 `endCommit` 為止的所有 commit
* cherry-pick 整個分支:`git cherry-pick parentBranchName..targetBranchName`
### 反向撿櫻桃 (Revert)
* `git revert <commit_id>`
## HEAD
* https://git-scm.com/docs/git-rev-parse#_specifying_revisions
* https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git
* **HEAD^**
一個 ^ 表示為第一個分支上的前一版,一個 ^ 就代表切換一次分支,後面接的數字則代表往前一版的第幾個分支
* Note: Windows command 需打 `HEAD^^` (`^`為跳脫字元 [Escape Characters](http://www.robvanderwoude.com/escapechars.php))
* **HEAD~**
一個 ~ 表示為前一版,預設都是於左數來第一個分支上往上找,後面接的數字則代表往前幾版
* **HEAD~** vs. **HEAD^**

### detached HEAD
* https://www.git-tower.com/learn/git/faq/detached-head-when-checkout-commit
### origin/HEAD
* [Re: [問題] 請問 git中 origin/HEAD 指的是](https://www.ptt.cc/bbs/Linux/M.1406900317.A.D51.html)
* [How does origin/HEAD get set?](https://stackoverflow.com/questions/8839958/how-does-origin-head-get-set)
* origin/HEAD 代表遠端的 default branch,也就是你 clone(與一些其他操作)時預設會切換到的 branch。預設會指到 origin/master
### ORIG_HEAD
https://gitbook.tw/chapters/branch/how-git-know-what-current-branch-is.html
> 當你在做一些比較「危險」的操作(例如像 `merge`、`rebase` 或 `reset` 之類的),Git 就會把 HEAD 的狀態存放在這裡,讓你隨時可以跳回危險動作之前的狀態
## 還原/清除工作目錄檔案
### 還原
* `git reset`
全部 Staged -> Unstaged,可搭配 `hard`、`soft`、`mixed` 等參數
* `git reset [--] <file>`
Staged -> Unstaged
`--`:代表將 `--` 後方的每個參數皆一率視為檔案路徑
* https://git-scm.com/docs/git-reset#git-reset-Resetasinglefileintheindex
* https://stackoverflow.com/questions/7147270/hard-reset-of-a-single-file
* https://stackoverflow.com/questions/6561142/difference-between-git-checkout-filename-and-git-checkout-filename/6561160#6561160
* `git reset <commitCode>`
會 checkout 至指定 commit,並將差異還原成 Unstaged
一樣可搭配 hard、soft、mixed 等參數
但要小心每一個參數處理差異的方式並不同
* `git checkout <file>`
Staged 中有記錄:以 Staged 來還原
Staged 中沒有記錄:以目前 commit 來還原,Unstaged 內容會消失
### 清除
* `git clean`
刪除 untracked 的檔案
* `git clean -n` 只列出將會清除的清單(Dry run, 不搭配其他參數則列出 non-ignored 的 untracked 檔案)
* `git clean -f` 執行清除檔案(不搭配其他參數則清除 non-ignored 的 untracked 檔案)
* 參數 n、f 可以搭配以下參數:
* `git clean -X` 要清除 untracked 檔案(ignored files)
* `git clean -x` 要清除所有 untracked 檔案(ignored and non-ignored files)
* `git clean -d` 要同時清除 untracked 目錄
* 可指定路徑
## 比對差異
* https://git-scm.com/docs/git-diff
### 不同的 git diff 方法
* 工作目錄(Work Tree) vs 更新索引(Index): `git diff`
* 工作目錄(Work Tree) vs 最新版本(HEAD): `git diff HEAD`
* 工作目錄(Work Tree) vs 歷史版本(Commits): `git diff <commit>`
* 更新索引(Index) vs 最新版本(HEAD): `git diff --cached <HEAD>` (HEAD 可省略)
* 更新索引(Index) vs 歷史版本(Commits): `git diff --cached <commit>`
* 歷史版本(Commits) vs 歷史版本(Commits): `git diff <src-commit> <target-commit>`
### 比對二進位檔案之間的差異
* `git diff --binary`
* 用於產生 patch
### 比對兩個版本之間的檔案異動清單與狀態
* `git diff --name-only`: 僅列出工作目錄與索引間異動的檔案
* `git diff --name-status`: 列出工作目錄與索引間異動的檔案及狀態
* `git diff --name-status <src-commit> <target-commit>`: 列出兩版本間異動的檔案及狀態
* 搭配 `--diff-filter` 指定特定狀態,配合 shell script 自動修正佈署環境檔案
### 使用 git diff 產生 patch 修補檔與套用修補檔的方法
https://juejin.im/post/5b5851976fb9a04f844ad0f4
1. 建立 patch: `git diff <src-commit> <target-commit> > my-patch.patch`
* 若包含二進位檔案須加上 `--binary`
2. 套用 patch: `git apply my-patch.patch`
* 套用前可先使用 `git apply --check` 檢查套用 patch 過程是否會發生衝突
#### 正式環境上版與退版
* 上版: `git apply <patch_file>`
* 退版: `git apply --reverse <patch_file>`
#### `format-patch` vs. `diff >`
https://yodalee.blogspot.com/2017/03/git-patch.html
> 常見的 diff 其實也就是git diff 生成的 patch,內容就是:這幾行刪掉,這幾行加上去,用 git diff > commit1.patch 就能輕鬆生成。git patch system 則是用 git format-patch 來產生,它提供比 diff 更豐富的資訊
## git stash
* git stash 也是一種分支(refs/stash)
* [`git stash show`](https://git-scm.com/docs/git-stash#git-stash-showltstashgt)
* `git stash pop <n>`:n 是離現在最近的第幾個 stash(0-base)
## git show
* https://git-scm.com/docs/git-show
* [`git show --patch`](https://git-scm.com/docs/git-show#git-show---patch)
* [Generating patches with -p](https://git-scm.com/docs/git-show#_generating_patches_with_p)
## git add / checkout --patch
https://zlargon.gitbooks.io/git-tutorial/content/advanced/add_checkout_part_of_file.html
## 檔案 / 資料夾大小寫更名
1. `git config --local core.ignorecase false` 將 git 設定為大小寫敏感
2. `git mv client/themeCore/components/core/productCard temp`
3. `git mv temp client/themeCore/components/core/ProductCard`
## Git 遠端儲存庫管理
* `git remote`
* [`--verbose`](https://git-scm.com/docs/git-remote#git-remote---verbose)
* `git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git`
* Syncing a fork
* http://fred-zone.blogspot.tw/2015/09/git-fork.html
* https://gitbook.tw/chapters/github/pull-from-github.html
* https://gitbook.tw/chapters/github/syncing-a-fork.html
* https://www.peterdavehello.org/2014/02/update_forked_repository/
* `$ git pull upstream master`
* 一般建議使用 `git pull --rebase`
* 衝突時,先 `git rebase --abort` 再說
* `git push [<遠端倉庫名稱>] [<本地分支名稱>][:<遠端分支名稱>]`
* `git push origin local-name`
* `git push origin local-name:remote-name`
* https://git-scm.com/docs/git-push#git-push-codegitpushorigincode
* https://git-scm.com/docs/git-push#git-push-ltrefspecgt82308203
* [git push --force and how to deal with it](https://evilmartians.com/chronicles/git-push---force-and-how-to-deal-with-it)
* [GIT: PUSHING TO A REMOTE BRANCH WITH A DIFFERENT NAME](https://penandpants.com/2013/02/07/git-pushing-to-a-remote-branch-with-a-different-name/)
* [將本地分支 push 到遠端](https://git-scm.com/docs/git-push#_description)
* https://help.github.com/articles/pushing-to-a-remote/
* `git push --force`
* https://git-scm.com/docs/git-push#git-push---force
* `git push --force-with-lease`
* https://git-scm.com/docs/git-push#git-push---no-force-with-lease
* https://blog.csdn.net/wpwalter/article/details/80371264
* `git branch -u <遠端倉庫名稱>[/<遠端分支名稱>] [<本地分支名稱>]` == `git branch --set-upstream-to=<遠端倉庫名稱>[/<遠端分支名稱>] [本地分支名稱]`
* [追蹤遠端分支](https://git-scm.com/docs/git-branch#git-branch--ultupstreamgt)
* https://zlargon.gitbooks.io/git-tutorial/content/remote/upstream.html
* `git checkout -b [<本地分支名稱>] --track <遠端倉庫名稱>[/<遠端分支名稱>]`
* 建立新的追蹤分支(tracking branch),為 `git checkout <分支名稱>` 的[預設行為之一](https://git-scm.com/docs/git-checkout#git-checkout-emgitcheckoutemltbranchgt)(如果有同名的遠端分支名稱)
* `git push -u <遠端倉庫名稱> <本地分支名稱>` == `git push --set-upstream <遠端倉庫名稱> <本地分支名稱>`
* `git branch -u <遠端倉庫名稱>[/<遠端分支名稱>] [<本地分支名稱>]`
* 刪除遠端分支
* https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches
* https://git-scm.com/docs/git-push#git-push---delete
* `git push origin --delete <branchName>`
* 會同時刪掉遠端追蹤分支
* 其他人則透過 `git fetch --prune` 刪掉遠端分支
* 刪除本地之遠端追蹤(remote-tracking)分支
* `git branch -r` 列出遠端追蹤分支
* https://git-scm.com/docs/git-branch#git-branch---delete
* https://git-scm.com/docs/git-branch#git-branch---remotes
* `git branch --delete --remotes origin/<branchName>`
## 標記版本
* 指向特定 commit 版本
* 標籤建立後通常不會再異動
* https://git-scm.com/docs/git-tag
### 建立與刪除輕量標籤 (lightweight tag)
* 輕量標籤: 就只是個指標,並不會建立任何物件
* 建立標籤: `git tag <tag_name> <commit>`
* 顯示訊息時只會顯示該版本的 commit 內容
* 刪除標籤: `git tag -d <tag_name>`
* 或手動刪除檔案 `.git/refs/tags/<tag_name>`
### 建立與刪除標示標籤 (annotated tag)
* 建立標籤: `git tag -a <tag_name> <commit>`
* 需輸入版本訊息 (使用 `tag -n` 顯示 tag 訊息)
* 通常用於 release
* 刪除標籤: `git tag -d <tag_name>`
### 遠端標籤
* 推送遠端標籤: `git push --tags`
* 刪除遠端標籤: `git push --delete origin <tag_name>`
* 取得遠端標籤: `git fetch`
* 若遠端標籤已不存在,要同時刪除本地標籤: `git fetch --prune-tags`
### 取出特定標籤的完整原始碼
* `git checkout <tag_name>`: 實際上是把標籤指向的版本取出 (因為標籤不是分支,所以會跑去 detached HEAD)
* 因此應加上 `-b` 同時建立分支, ex `git checout -b hotfix/6.0.1 6.0.1`
## Submodule
https://blog.chh.tw/posts/git-submodule/
## Git 協同作業實戰 ==> 好的分支合併流程!!
1. 集中式版控流程
* 共用儲存庫
* 類似 SVN
2. 整合式管理版控流程
* blessed repository 對每個人都是唯讀
* devloper 將修改 pull request 給 integration manager,通過後再合併
* 類似 GitHub fork
* pull 都從 blessed repository,push 則 push 自己的
* 透過 `git remote set-url --push origin <URL_TO_MY_REPO>` 設定
3. 獨裁者與副手工作流程
* 類似前一步,只是再加上副手幫忙 code review
## 認識 GitHub Flow
* https://guides.github.com/introduction/flow/
* http://calvert.logdown.com/posts/2014/09/21/understanding-the-github-flow
* http://blog.krdai.info/post/17485259496/github-flow
## Git Commit Message
* http://karma-runner.github.io/5.0/dev/git-commit-msg.html
* http://blog.fourdesire.com/2018/07/03/%E6%92%B0%E5%AF%AB%E6%9C%89%E6%95%88%E7%9A%84-git-commit-message/
* https://juffalow.com/other/write-good-git-commit-message
* https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/
## 其他
* 命令列呈現線圖 `git log --oneline --graph -n`
* `--oneline`: 僅顯示第一行訊息,即 commit log
* `--graph`: 以線圖呈現
* `-n`: 最近幾版
* 實用的 git alias
```
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.sts "status -s"
git config --global alias.br branch
git config --global alias.re remote
git config --global alias.di diff
git config --global alias.type "cat-file -t"
git config --global alias.dump "cat-file -p"
git config --global alias.lo "log --oneline"
git config --global alias.ll "log --pretty=format:'%h %ad | %s%d [%Cgreen%an%Creset]' --graph --date=short"
git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset %ad |%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset [%Cgreen%an%Creset]' --abbrev-commit --date=short"
# Windows 限定 (因為要 TortoiseGit)
git config --global alias.tlog "!start 'C:\PROGRA~1\TortoiseGit\bin\TortoiseGitProc.exe' /command:log /path:."
```
---
## Git 工具
* [必要安裝] [Git 命令列工具](https://git-scm.com/)
* [推薦安裝] [TortoiseGit](https://tortoisegit.org/) ( Windows only )
* [推薦安裝] [SourceTree](https://www.sourcetreeapp.com/)
* [Visual Studio Code](https://code.visualstudio.com/)
* [GitHub Desktop](https://desktop.github.com/)
* [Fork](https://git-fork.com/) ( Mac only )
* [SmartGit](http://www.syntevo.com/smartgit/)
* [GitExtensions](https://gitextensions.github.io/) ( Windows only )
* [tig](https://jonas.github.io/tig/) (Command Line)
* [GitKraken](https://www.gitkraken.com ) ( Commerical )
* [Tower](https://www.git-tower.com/) ( Commerical )
### 環境建立
* [Git 版本控管實戰:新手進階篇 - 範例版本庫 (Windows)](
https://gist.github.com/doggy8088/491d9da6b2452437f4af487137dd0340)
* [Start-Sleep](https://ss64.com/ps/start-sleep.html) 必須使用 PowerShell 才能使用
* [Git 版本控管實戰:新手進階篇 - 範例版本庫 (macOS)](
https://gist.github.com/doggy8088/e535363a8f5c8ef3aeb31a1d234d4937)
### Git 教學影片(課後問卷)
* [認識 Git 資料結構中的物件資料庫與物件之間的關係](https://www.youtube.com/watch?v=PZbSRy_ow0U)
* [認識 Git 資料結構中的索引與檔案狀態的變化關係](https://www.youtube.com/watch?v=5c_7v0cIFk4)
* [如何在 GitHub 使用 Fork / Pull Request 功能 (以 VS2013 為例)](https://www.youtube.com/watch?v=NTLAfy6lcdQ)
* [透過 Visual Studio 2013 匯入方案到 Git 儲存庫的正確做法 ](https://www.youtube.com/watch?v=97BVjQyK8ag)