# Class 3:Git 與 GitHub - 管理程式碼的起點
## 前言
本篇教材主要作為Git的詳細筆記與解釋,GitHub的內容以影片教學展示,可以參考[課程錄影檔](https://youtu.be/gbnOtBidrFU)
## 目錄
> :::spoiler 清單
> [TOC]
> :::
---
## 參考教學:
> 此篇HackMD筆記以Git 2.38.1為基準,因此會有像是switch、restore等新指令用法
> 前半段的示範教學較多,後半段多為整理常用指令,可以搭配[#git command](#git-command)查看,透過[🔗W3Hexschool - Git & GitHub 教學手冊](https://w3c.hexschool.com/git/cfdbd310)輔助學習
> 另外可以善用[🔗Learn Git Branching](https://learngitbranching.js.org/?locale=zh_TW)線上練習Git管理操作
> 最完整實例的[🔗繁中Git教學](https://gitbook.tw/)
> 使用VS Code開發可以參考此教學[🔗進行初始設定](https://www.roboleary.net/vscode/2020/09/15/vscode-git.html)
## 介紹Git & GitHub
#### 如何備份程式碼
> 
> 
> 
## Git 概念

### working directory跟worktree的差別
> 在中文的Git文章容易看到"工作目錄"
> 有時指的是working directory
> 有時卻是worktree
> 可以理解為中文廣義跟狹義的差別
> worktree包含working directory + stage

> 每一次提交會產生snapshot與對應的hash值
>
> 口語上我們可能會聽到用"commit紀錄"、"commit節點"來稱呼snapshot
> 這是因為每一個節點都是snapshot,由snapshots組成branch
- 工作目錄(狹義) (workdir、wd)
> 當前實際進行編輯文件的資料夾位置,亦稱作工作區域
> 即VS Code檔案總管開啟的"資料夾"或"工作區"
- 暫存區 (stage、index、staging area、.git/index)
> git管理該專案暫存變更的對照清單,紀錄目前程式碼跟上個版本的所有差異,亦稱作暫存區
> 若使用VS Code或IDE,會在原始檔控制(Source Control)呈現,以圖形化檢視與編輯
- 工作目錄(廣義) (work tree)
> work tree是一個抽象的概念,他讓編輯的工作區域可以虛擬出來
> 我可以在同一個專案中,開設不同的work tree
> 他們可以完全獨立的去處理檔案的修改、commit、在不同branch中切換變更
> 關於同一個專案為什麼可以同時用不同work tree編輯而不會互相干擾?
> https://chat.openai.com/share/c57b02c6-18c5-4884-8f71-97e71a8dc0a9
- 數據庫 (Repository、Repo)
> 通常一個Repo為一個或多個專案,以程式碼跟說明檔為主的檔案庫
>
> 數據庫由branch(分支)組成,每個Repo會有一個預設的Branch,也可以新增更多Branch來分類
- 本地數據庫 (Local Repo)
> git在當前作業系統中保存的Repo
- 遠端數據庫 (Remote Repo)
> 如Github、GitLab等,用於個人專案版本控制或團隊協作開發專案,方便共同更新與同步數據庫
> 或是開源讓任何人共同維護、更新專案
### 以下將使用簡稱:
- workdir
- stage
- worktree
- branch
- Local Repo
- Remote Repo
## Git 操作
> 操作Git需要有基本Terminal能力,以Linux為例包含cd、mkdir等
> 可參考[Linux - Ubuntu操作筆記教學](https://hackmd.io/@Charles5277/r19yxcQni/%2FBB6KG8NCTQ-1Ps2KRr82AA)
### 安裝
> [🔗Git官網安裝](https://git-scm.com/downloads)
Windows也可以透過winget安裝
> 打開終端機 / 命令提示字元 / 終端機
> 執行
```
winget install Git.Git -i
```
> 
>
> 
> [🔗Mac安裝Git教學](https://w3c.hexschool.com/git/fd6f6be)
> 使用`git --version`確認是否安裝成功
---
## 建議安裝的VS Code Extension
搭配VS Code進行Git的操作會方便許多,我們可以安裝以下套件,讓提交的歷史紀錄以圖形化顯示
- [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph)
- 以圖像化表示branch跟commit紀錄,方便直觀進行git command快捷操作
> 
---
以下建議可以開始搭配VS Code終端機操作
## 設定Git config內容
> 由於每次發佈檔案時,都會記錄該版本是哪位開發者變更的
> 因此初次需要設定姓名跟Email
```
git config --global user.name "姓名"
git config --global user.email "Email"
```
> 將"姓名"跟"Email"換成個人資訊
以下為進階Optional設定
> 若使用VS Code進行主要的專案開發,並且想讓Git的Message在VS Code上編輯,可以使用以下指令:
這個git config檔案設定了一些Git與Visual Studio Code (VSCode)的整合設定,讓你可以在使用Git時使用VSCode作為編輯器、差異檢視工具和合併工具。以下是各個設定的指令說明:
1. 編輯器設定:
- 指令: `git config --global core.editor "code --wait"`
- 功能: 將VSCode設定為全域(global)的Git編輯器,使用 `code --wait` 命令開啟VSCode並等待編輯器關閉後再繼續Git操作。
2. 差異檢視工具設定:
- 指令: `git config --global diff.tool vscode`
- 功能: 設定VSCode為差異檢視工具,當使用 `git diff` 命令時,Git會使用VSCode來顯示差異。
3. VSCode差異檢視工具設定:
- 指令: `git config --global difftool.vscode.cmd "code --wait --diff $LOCAL $REMOTE"`
- 功能: 設定使用VSCode作為差異檢視工具的命令,當你執行 `git difftool` 命令時,Git會使用VSCode打開兩個檔案的差異。
4. 合併工具設定:
- 指令: `git config --global merge.tool vscode`
- 功能: 設定VSCode為合併工具,當執行 `git merge` 命令時,Git會使用VSCode來進行合併操作。
5. VSCode合併工具設定:
- 指令: `git config --global mergetool.vscode.cmd "code --wait $MERGED"`
- 功能: 設定使用VSCode作為合併工具的命令,當執行 `git mergetool` 命令時,Git會使用VSCode打開合併後的檔案。
這些設定可以通過在終端機或命令提示字元中輸入相應的指令進行配置。請確保已經在系統中安裝了Git和VSCode,以及將 `code` 命令設定為可從終端機中啟動VSCode的全域指令。
> 查看已設定的Git config
```
git config --list
```
> 也可以直接修改Windows user/Linux home資料夾底下的.gitconfig檔案
> 例如:
> `C:\Users\user\.gitconfig`
> 或
> `/home/user/.gitconfig`
```md
[user]
email = example@gmail.com
name = your_name
[core]
editor = code --wait
[diff]
tool = vscode
[difftool "vscode"]
cmd = code --wait --diff $LOCAL $REMOTE
[merge]
tool = vscode
[mergetool "vscode"]
cmd = code --wait $MERGED
```
## 建立Local Repo (本地數據庫)
1. cd到想存放Repo的資料夾
2. 執行 `git init`
> 應可看到以下資訊
```
Initialized empty Git repository in "專案路徑"/.git/
```
## 檔案追蹤與提交版本 (add and commit)
### git add
當我們在workdir進行了檔案的任何變更,包含新增、檔名或內容的修改、刪除等,此時這些變更是尚未被追蹤的。
舉例,創建一個index.html的檔案,使用`git status`會看到以下資訊
```
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.html
nothing added to commit but untracked files present (use "git add" to track)
```
意思是Git偵測到一個未追蹤的檔案為`index.html`,將它加入stage就可以追蹤它(track),將檔案加入到stage使用
```
git add <檔案名稱>
```
如 `git add index.html`
會產生以下結果
```
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: index.html
```
> 
### git commit
stage中的檔案就是被追蹤(經過git add)後的檔案清單,將檔案從stage加入到Local Repo使用
```
git commit -m "<填寫版本資訊(即commit log)>"
```
例如 `git commit -m "feat: 新增網頁標題"`
接著就能在`git status`看到結果:
```
On branch main
nothing to commit, working tree clean
```
接著可以使用 `git log`,查看commit的紀錄
```
commit ef070ed2cc39d72e203822a6e1ffd89d3be52f1e (HEAD -> main)
Author: Charles5277 <abcd854884@gmail.com>
Date: Sat Mar 25 15:40:00 2023 +0800
feat: 新增網頁標題
```
#### 修改上次的commit訊息跟檔案內容
當剛進行commit後,突然想修改commit的message,或是想多加、多刪除內容,可以使用 `git commit --amend -m "要修改的訊息"` ,若此時stage區沒有其他變更,就只會變更message,如果stage區有內容,會直接把stage區合併進去
若使用vscode的"提交暫存(修改)"功能

此時workdir有內容,但stage區是空的,會詢問是否要將workdir的內容直接丟上去,此時若不想使用never功能,又不想先清空worktree,以及使用stash(隱藏)功能的話,可以直接在終端機打上述指令,就可以不影響worktree,單純改commmit
#### 約定式提交 (Conventional Commits)
不同人在寫commit時可能會有自己的習慣,導致當多人維護repo時,commit log會難以閱讀,因此我們需要遵循統一的約定式提交規範
- 以下是如果沒有規則,會發生的事情:
> 
>
> 
>
> 
>
> 以上這些提交描述「First Beautiful Commit」、「Fix again」、「Anoter one」都無法讓未來追程式碼的人立刻理解「改了什麼」和「為何要這樣修改」。因此,導入約定式提交能讓團隊成員強制遵守固定的規範和格式,填寫適當的內容。
> 參考素材來源:[約定式提交 Conventional Commits - by Summer。桑莫。夏天](https://www.cythilya.tw/2021/03/16/conventional-commits/)
- 清楚詳細的撰寫範例
> 參考素材來源:[Git Commit Message 這樣寫會更好,替專案引入規範與範例 - by WadeHuang的學習迷航記](https://wadehuanglearning.blogspot.com/2019/05/commit-commit-commit-why-what-commit.html)
> 參考素材來源:Charles的團隊專案
> - 加上icon前
> 
> - 加上icon後
> <br>
> 
以下遵照angular風格,提供範例
```
<icon><space><type>(<scope>):<space><subject><enter><body><enter><footer>
```
- <icon\>
> 建議遵循gitmoji規則使用icon,以及對應的type關係
> [gitmoji | An emoji guide for your commit messages](https://gitmoji.dev/)
- <space\>
> 單純一個空白字元
- <type\>
> 以我使用extension所自訂的config為例,提供了以下這些,有些版本可能不會拆分這麼多細項,只需與協作夥伴協調好要使用的type清單即可
- init
> 初始化項目
- deploy
> 部屬版本
- feature
> 新功能
- chore
> 其他,且不影響程式結構,如修改註解或顯示文字等
- fix
> 修復bug
- refactor
> 重構現有程式碼,無新增功能或修復bug
- test
> 新增測試用demo
- build
> 新增、更新或刪除依賴package包
- merge
> 標記合併分支
- revert
> 標記回退上一個或指定版本
- perf
> 關於效能改善
- style
> 調整code風格,不影響程式碼的內容。例如空白、分號、空格規則。
- docs
> 修改說明文檔
- ci
> 修改了ci持續集成相關的script
- <scope\>
> 影響的範圍,例如專案中的特定層面,非必填
- <subject\>
> [必填] 50字內簡短敘述,不須加句號
- <body\>
> 詳細敘述,可以分成多行,單行不要超過72字元,非必填
- <footer\>
> 如果有的話,填寫對應的issue,例如#17,非必填
> 以上參數皆可依照團隊需要將規範調整,但需要確保所有協作夥伴有共識,才能維持repo的commit整齊性
#### 輔助commit log工具 - git-commit-plugin
> VS Code可自定義,最方便的git commit extension
> [🔗 下載連結](https://marketplace.visualstudio.com/items?itemName=devCharles5277.git-commit-plugin-with-gitmoji)
- 功能介紹
> 互動式介面創建約定式提交
> 
> 
> 點擊Complete後即自動產生commit格式
> 
> 使讓多人協作repo時,commit風格統一
## 分支管理
### git branch
#### 關於HEAD
HEAD 是一個指標,代表你目前指定的版本狀態,HEAD可以指到
- branch
> 分支,預設有一主線為main
> 由snapshot(或稱commit節點)組成,多個分支可以共享相同的節點
> 從2022/10開始,Github的預設分支主線由master改為main,而git預設仍為master
- commit版本
> git graph上的節點,可以透過`git log`查看hash來指定HEAD移動到該節點
要將HEAD指到commit版本,可以使用`git switch -d <hash>`
執行 `git log`
```git
commit 5cac503a14baec3ad3da6618f08f577ae048567b (HEAD -> main)
Author: Charles5277 <abcd854884@gmail.com>
Date: Thu Mar 30 16:22:48 2023 +0800
feat: 新增css檔案
commit ef070ed2cc39d72e203822a6e1ffd89d3be52f1e
Author: Charles5277 <abcd854884@gmail.com>
Date: Sat Mar 25 15:40:00 2023 +0800
feat: 新增網頁標題
```
執行 `git switch -d ef070ed2cc39d72e203822a6e1ffd89d3be52f1e`
再執行 `git log`
```git
commit ef070ed2cc39d72e203822a6e1ffd89d3be52f1e (HEAD)
Author: Charles5277 <abcd854884@gmail.com>
Date: Sat Mar 25 15:40:00 2023 +0800
feat: 新增網頁標題
```
> 此時HEAD單獨指在第一個版本,而main目前在第二個版本
將HEAD移回最新狀態
執行`git swtich main`
及`git log`
```git
commit 5cac503a14baec3ad3da6618f08f577ae048567b (HEAD -> main)
Author: Charles5277 <abcd854884@gmail.com>
Date: Thu Mar 30 16:22:48 2023 +0800
feat: 新增css檔案
commit ef070ed2cc39d72e203822a6e1ffd89d3be52f1e
Author: Charles5277 <abcd854884@gmail.com>
Date: Sat Mar 25 15:40:00 2023 +0800
feat: 新增網頁標題
```
#### 創建分支
使用 `git branch <新分支名稱>`創建分支
這個新分支會以當前HEAD指到的位置作為起點
```git
git branch dev
```
或使用 `git switch -c <新分支名稱>`
可以直接在創建後同時將HEAD指過去
```git
git switch -c dev
```
#### HEAD指向commit跟branch的差別
HEAD身為指標,一次只能指向一個目標
先前提到HEAD可以指定在branch或某個commit版本
目前的git graph如圖

有2個branch,分別為main跟dev
- 情況1 HEAD指向main
> 使用git switch main

```git
git switch main
git log
```
得到結果
```git
commit 5cac503a14baec3ad3da6618f08f577ae048567b (HEAD -> main, dev)
Author: Charles5277 <abcd854884@gmail.com>
Date: Thu Mar 30 16:22:48 2023 +0800
feat: 新增css檔案
```
> 標示為(HEAD -> main, dev),代表HEAD指向main,而dev分支目前也處在同個版本
- 情況2 HEAD指向dev
> 使用git switch dev

```git
git switch dev
git log
```
得到結果
```git
commit 5cac503a14baec3ad3da6618f08f577ae048567b (HEAD -> dev, main)
Author: Charles5277 <abcd854884@gmail.com>
Date: Thu Mar 30 16:22:48 2023 +0800
feat: 新增css檔案
```
> 標示為(HEAD -> dev, main),代表HEAD指向dev,而main分支目前也處在同個版本
- 情況3 HEAD指向commit版本
> 使用 `git switch -d <hash>`
> 此處以 `git switch -d 5cac503a14baec3ad3da6618f08f577ae048567b`示範
> 對應圖上的C2

```git
git switch -d 5cac503a14baec3ad3da6618f08f577ae048567b
git log
```
得到結果
```ba
commit 5cac503a14baec3ad3da6618f08f577ae048567b (HEAD, main, dev)
Author: Charles5277 <abcd854884@gmail.com>
Date: Thu Mar 30 16:22:48 2023 +0800
feat: 新增css檔案
```
> 標示為(HEAD, main, dev),可以理解為HEAD指向了這個commit版本,而main跟dev的版本狀態也在這個版本
#### 分支差異
目前main跟dev兩個branch都在5cac50的版本上,現在我們試著在dev上更新版本

此時對於main來說只有兩筆commit紀錄
```git
git switch main
git log
```
```git
commit 5cac503a14baec3ad3da6618f08f577ae048567b (HEAD -> main)
Author: Charles5277 <abcd854884@gmail.com>
Date: Thu Mar 30 16:22:48 2023 +0800
feat: 新增css檔案
commit ef070ed2cc39d72e203822a6e1ffd89d3be52f1e
Author: Charles5277 <abcd854884@gmail.com>
Date: Sat Mar 25 15:40:00 2023 +0800
feat: 新增網頁標題
```
#### 刪除分支
若需要刪除分支,請將HEAD移到其他分支後,使用 `git branch -d <branch_name>` 將分支刪除,若刪除後該分支會有commit紀錄丟失,則會跳出警告,請將該分支目前的進度合併到其他branch上,再刪除該branch
若不在意刪除該branch後會有commit丟失,則可以使用 `git branch -D <branch_name>`
使用-D刪除後,該分支上的節點若沒有其他分支共同持有,則會直接消失,所以請配合git graph等git分支圖檢視工具確認是否會丟失非預期的commit節點
### git merge
#### branch merge - fast forward (分支合併 - 快轉模式)
目前main跟dev在同一條線上,因為main的位置是dev的起點。
而且main的版本比dev舊,此時要將main更新到dev時,
可以使用 `git switch main`切換到main後
使用 `git merge dev -e`,將main更新到dev的位置

#### branch merge - no fast forward(分支合併 - 不進行快轉)
若我們想要在上述情況產生另外的合併紀錄,而不是看起來都是一條直線往前推進的話,可以在merge時加上參數,改為 `git merge dev --no-ff`

若使用git graph、git lens等套件進行merge,可能會有自動產生message而無法編輯的情況,若希望自定merge時的message,可以直接使用 `git merge dev --no-ff -m "message"`,或是使用 `git merge dev --no-ff -e`,會在輸入後跳出編輯視窗讓你輸入訊息
若在main在進行與dev的合併前,已經有新的commit版本

則嘗試進行合併時,就自動不會執行fast forward

> 若要將main保持主線,而不想在某次merge變成支線,需特別注意合併時,
> 必須將HEAD移到main,再執行merge其他分支,才能形成其他分支匯入main的效果
#### 合併訊息填寫
當使用vscode進行git merge合併時,有使用`-e`參數進行編輯合併訊息,會出現以下視窗

第一行就是會成為紀錄的merge訊息,下方的註解區可以忽略或刪除
編輯完訊息後只需存檔,並關閉檔案就會完成合併
#### 進階合併分支


第二次從dev匯入main時,顏色變更的原因是,先前都是以main去merge dev,
因此main會取得dev的所有commit紀錄跟變更。
而此處在main進行merge dev後,再將dev進行merge main
此舉會讓main跟dev都進展到同樣的進度上,之後dev再繼續推展,
接著main又進行merge dev。
#### 合併衝突處理
### git rebase
rebase中文翻譯成重訂基底,使用`git rebase`對分支進行管理
rebase的作用跟merge同樣為合併分支,實際應用場景有2類:
#### 濃縮commit
當我們在同一條branch上commit太多版本,想把一部分的commit合併成一個時,可以使用 `git rebase -i HEAD~數字`進行合併
範例如下:

使用 `git rebase -i HEAD~3`

依序為
HEAD~3
HEAD~2
HEAD~1
假設要把HEAD\~1跟HEAD\~2併到HEAD\~3
即將要拿掉的commit的pick改成squash

然後存檔,關閉此檔案

接著會跳出這些檔案原本的commit紀錄,此時將內容全部註解掉,再到最上方編輯commit訊息 (若有裝git-commit-plugin可以將引導生成的commit訊息複製後貼過來

然後存檔,關閉此檔案
如果有使用git graph等,需要按一下重新整理,即可看到濃縮完成

若完成後想取消操作,可以按照[reset --hard取消](#使用reset---hard後想反悔)的方式
#### 將目前分支整根接到其他分支上
使用 `git rebase <目標分支>`
將當前branch追朔到根源,剪下後接到<目標分支>上

此處試著將test接到dev上,使用 `git rebase dev`
由於test的原基底為10a197eb
因此會將2c3b8450跟df043ca4剪下接到dev上

## 遠端數據庫 (Remote Repo)
### Local Repo與Remote Repo的綁定
- git remote
> 查看remote repo列表的branch簡稱
- `git remote add <remote repo簡稱的branch簡稱> <url>`
> 添加remote repo
- `git remote -v`
> 觀看remote repo的branch列表(包含 url)
- `git clone <url>`
> 下載remote repo
> 當進行git clone時,會將remote repo下載到當前的資料夾,並且自動執行 `git remote add origin`,因此若執行`git remote`,就會看到已經自動產生了origin
### 將Local Repo更新到Remote Repo
- git push
> 將local repo推送同步到remote repo (github)
- git push <數據庫簡稱> <分支名稱>
> clone後的remote預設會設為origin,且github等主流remote repo 的預設分支為main,因此可以使用
> `git push origin main`
> 或直接使用 `git push`
- git push -f
> 強行將Local Repo覆寫到Remote Repo
> 在本地進行reset、rebase等操作後,若執行pull或sync都會被Remote Repo覆蓋操作,因此需要先使用 `git push -f` 將較舊的狀態覆寫到Remote Repo
### 更新Local Repo變成Remote Repo狀態
- git fetch
> 將Remote Repo的內容下載到Local Repo
- git pull
> 將Remote Repo的內容抓下來並且合併對應的branch
> 相當於git fetch + git merge
> 有合併衝突時與git merge處理流程相同
#### fetch跟pull的使用時機
使用fetch 當抓下來的資料還不想立即合併,例如worktree尚未清空,但想先將Remote目前commit狀態下載時
#### 先拉再推 (The First Pull and Push)
當多人協作時,想將Local Repo Push到 Remote Repo時可能會發生衝突,因此會遵照「先拉再推」的準則
也就是執行 `git pull` 再執行 `git push`
若使用VS Code可以使用Sync功能一鍵完成這兩步
另外,這麼做會導致pull下來有需要合併branch時會自動產生merge的commit紀錄,如果不希望產生的話,必須改使用 `git pull --rebase` 再 `git push`,等同以rebase取代merge
而使用VS Code時,可以在設定的`git.rebaseWhenSync`指定Sync時是否要Rebase,預設為否

## 恢復、撤銷變更
### git restore
- git restore \<file_name>
> 將檔案的變更捨棄(只限定未放到stage區的workdir變更)
> 預設即為-W (worktree)
> 因此 git restore -W \<file_name>的效果是一樣的
- git restore -S \<file_name>
> 將檔案從stage區移回workdir(但內容變更不改變)
> 相當於取消git add
- git restore -W -S \<file_name>
> 一次完成從stage區直接捨棄變更
#### 從其他commit拿檔案
- git restore -s \<hash/branch> \<file_name>
> 抓某個commit版本的指定檔案到目前的workdir
### git reset
當我們想要取消最近一次的commit操作時,可以使用`git reset HEAD~`,若要取消當前branch的多筆commit可以使用`git reset HEAD~數字`或`git reset HEAD~..~`,例如想要取消3筆可以用`git reset HEAD~~~`或`git reset HEAD~3`
原本最新的變更會退回到worktree的workdir,若確定退回後不需要這些變更紀錄,可以使用 `git reset HEAD~ --hard`
當我們是要帶著HEAD當前指向的branch變更到指定位置,可以使用 `git reset <hash>` 或 `git reset <branch_name>` 將當前branch直接變更為指定commit的狀態,同樣可以選用`--hard`將原本位置的檔案變更捨棄
#### 使用reset --hard後想反悔
當誤用 `git reset --hard` 刪掉commit紀錄後,可以用 `git reflog`找回操作的所有歷史紀錄,找到想要復原commit的hash值,再透過 `git reset \<hash>` 將當前的branch指回該commit即可
#### 搭配 Git Graph

- Soft:提交過的commit放到暫存區、當前worktree保留,
- Mixed:提交過的commit放到工作目錄、當前stage清除、workdir保留
- Hard:提交過的跟當前變更都不要了,直接變成目標的狀態
> 指令預設採Mixed模式,可以改指定`--hard`或`--soft`
### git revert
> 否決掉某個指定的版本
當branch發展到一半,突然想改掉某個版本的設定,就會使用 `git revert`,但revert的操作不像rebase,並不會影響先前的commit紀錄,而是以新增commit的方式反向抵銷指定版本的操作,由於在git的檔案變更邏輯是以 `+` `-` 標示每行的異動狀況,因此進行revert時只要不是涉及merge的情況,大多能自動完成反向操作
`git revert <指定的commit>`
會自動產生對指定commit的反向操作
#### 同樣可以搭配 Git Graph

同時revert指定commit會參照該commit的前一個節點
若要恢復的版本,來源是2個以上的分支匯集,則需要指定 `-m` 參數

如圖,da3d4ab是由2個分支組成,`git revert da3d4ab`會無法得知應該參照的前一個節點應該取哪條分支,由左到右從1開始排序,因此若要參照parent1需要使用 `git revert da3d4ab -m 1`
### reset、revert,以及合併分支的rebase/merge使用取捨
由於reset跟rebase都會變更歷史commit紀錄,可以做到濃縮、減少、刪除commit等效果,因此較不建議使用在已經push到Remote Repo的情況,因為這代表需要用 `git push -f` 做強行推送,蓋掉原先的commit,在多人協作中容易產生困擾,因此若要在已經push到Remote Repo的情況使用reset跟rebase需要確定取得協作夥伴的共識
而revert就可以放心地使用,不過每次使用revert都會產生更多的commit紀錄,會造成commit更加臃腫
因此必要的時候在遠端使用rebase濃縮,或多開branch整理分支也是優化開發環境的好選擇
## worktree打包隱藏
### git stash
在進行git的日常操作時常有要求當前worktree必須淨空的條件,此時可以選擇將目前的變更commit或捨棄,但如果臨時想要先打包起來,進行完操作後再解開,可以使用 `git stash`,將目前的worktree(包含workdir跟stage區一起)打包並隱藏,要還原回來時使用 `git stash apply`
若有多個stash要管理,可以使用以下指令
- `git stash save "message"`
> 在建立stash時加上備註。
- `git stash list`
> 列出所有stash。
- `git stash clear`
> 刪除所有stash
> 其餘指令使用stash@{n}即可指定第n個stash
> 若使用stash則指定目前最新的
- `git stash show`
> 顯示最新stash的詳情
- `git stash@{n} branch <branch_name>`
> 創建一個新的分支,並將第n個stash的內容丟進去
- `git stash drop stash@{n}`
> 刪除第n個stash。
#### 搭配 VS Code + Git Graph
常用組合
- 將整個 worktree 打包

- 透過 Git Graph 管理 Stash

> 搭配[🔗VS-Code-Git介面中英對照表](#VS-Code-Git%E4%BB%8B%E9%9D%A2%E4%B8%AD%E8%8B%B1%E5%B0%8D%E7%85%A7%E8%A1%A8)
## Git Command總覽
- git init
> 當前位置創建Local Repo
- git config
> 設定檔相關操作
- git config --list
> 查看目前設定檔
- git add <檔案名稱>
> 將檔案從workdir加入stage
- git commit -m "<填寫說明訊息>"
> 將stage區提交到local repo,並加上commit log
- git commit --amend -m "要修改的訊息"
> 修改最近一次的commit訊息,或是連同檔案修改內容一同變更
- git log
> 查看當前有效的版本紀錄 (即commit history)
> 不會查看到已經斷開連接(沒有任何branch或HEAD綁定)的commit,需要的話請往下使用git reflog
- git switch
- git switch \<branch>
> 切換HEAD到指定分支
> 需要先清空worktree
> 若stage區還有資料未commit、workdir有還沒add到stage區的內容,需要先處理
- git switch main
> 回到最新版本主線
- git switch -f \<branch>
> 強制切換分支,直接捨棄worktree中未commit的內容
- git switch -d \<hash>
> 切換HEAD到commit節點,可以先透過git log查看hash
> d指的是detach,讓HEAD指向節點,非指向branch的狀態
- git switch -c \<new_branch>
> 創造新的分支並將HEAD切過去
- git remote
> 查看remote repo的branch列表
- git remote add <remote repo的branch簡稱> \<url>
> 添加remote repo
> 可以在一個local repo中添加多個remote repo branch
- git remote -v
> 觀看remote repo branch(包含 url)
- git clone \<url>
> 下載remote repo
- git push
> 將local repo更新到remote repo
- git push
> 推送到Remote Repo預設的名稱:origin,到預設的分支:main
> 等同 git push origin main
- git push -f
> 強行將Local Repo覆寫到Remote Repo
- git pull
> 將remote repo更新到local repo
- git branch
- git branch -a
> 查看所有現有分支
- git branch <新分支名稱>
> 在HEAD處創建新branch
- git branch -d <分支名稱>
> 將分支刪除,若有未合併的commit會提示並阻止,需要合併到其他分支後才能順利刪掉
- git branch -D <分支名稱>
> 將分支強行刪除,若有未合併的commit會直接丟棄
- git branch -m <原名稱> <新名稱>
> 將某個分支更名
- git merge
- git merge <分支名稱>
> 將HEAD指向的branch跟<分支名稱合併>
- git merge <分支名稱> --no-ff
> 指定不使用fast forward快進模式合併,而是留下分支線紀錄
- git merge <分支名稱> --no-ff -m <訊息>
> 指定不使用fast forward,且自訂merge時的訊息
- git merge <分支名稱> -e
> 在合併前編輯message
- git cherry-pick
- git cherry-pick <hash>
> 將某個commit單獨複製到當前分支上
- git cherry-pick <start-hash>..<end-hash>
> 將start到end的commit都擷取
- git cherry-pick <hash> -e <target-branch>
> 將指定commit單獨複製到指定分支上 (而非當前分支)
- git restore
- git restore \<file_name>
> 將檔案的變更捨棄 (只限定未放到stage區的workdir變更)
> 預設即為-W (worktree)
> 因此 git restore -W \<file_name>的效果是一樣的
- git restore -S \<file_name>
> 將檔案從stage區移回workdir (但內容變更不改變)
> 相當於取消git add
- git restore -W -S \<file_name>
> 一次完成從stage區直接捨棄變更
- git restore -s \<hash/branch> \<file_name>
> 抓某個commit版本的指定檔案到目前的workdir
- git reset
> 將HEAD目前所在的branch帶著移動到指定位置
- git reset \<hash> [--mixed]
> 預設就是mixed,所以可以不用加
> 將HEAD指向的branch帶到指定的commit節點上
> 原本位置的commit檔案狀態回到workdir
- git reset \<hash>/HEAD~數字 --soft
> 同mixed,差在退回stage而不是workdir
- git reset \<hash> --hard
> 同上,原本位置的commit檔案變更捨棄
- git reset HEAD~數字
> 將HEAD指向的branch返回線上前幾個的版本
- git reset HEAD~
> 將上一個commit的紀錄取消
> 使用 --hard同樣可以拋棄檔案變更
- git rebase
> 重新訂定該branch的基底(起點)
- git rebase -i HEAD~數字
> 將同branch上多個commit濃縮合併
- git rebase <目標分支>
> 將當前branch追朔到根源,剪下後接到<目標分支>上
- git revert
- git revert <指定的commit>
> 否定某個commit紀錄,自動進行反操作,試圖回到其commit的上一版狀態
- git revert <指定的commit> -m <parent_number>
> 當commit為多個分支匯入,有多個「上一個commmit」時,由左到右從1開始排序,要指定最左邊的branch使用 `git revert <指定的commit> -m 1`
> '左'/'右'為Git Graph的相對表示
- git reflog
> 查看所有操作紀錄
> 常用於救回已經斷開連結的commit紀錄
- git stash
> 將worktree打包隱藏
- git stash -u
> 將.gitignore的檔案也加入
- git stash save "message"
> 在建立stash時加上備註。
- git stash list
> 列出所有stash
- git stash clear
> 刪除所有stash
> 使用stash@{n}即可指定第n個stash
> 若使用stash則指定目前最新的
>
- git stash show
> 顯示最新stash的詳情
- git stash@{n} branch <branch_name>
> 創建一個新的分支,並將第n個stash的內容丟進去
- git stash drop stash@{n}
> 刪除第n個stash
## VS Code Git介面中英對照表
> 未寫說明為上述有完整介紹或使用頻率較低的功能
### 主選單
| 中文 | 英文 | 說明 |
|:----------:|:-----------:|:---------------:|
| 檢視及排序 | View & Sort | |
| 提取 | Pull | |
| 推送 | Push | |
| 複製 | Clone | |
| 簽出至… | Checkout to | 對應新版Switch |
| 擷取 | Fetch | |
| 提交 | Commit | |
| 變更 | Changes | 對應新版Restore |
| 提取、推送 | Pull、Push | |
| 分支 | Branch | |
| 遠端 | Remote | |
| 隱藏 | Stash | |
| 標籤 | Tags | |
### 提交
| 中文 | 英文 | 說明 |
|:--------------------------------: |:----------------------------------------: |:----------------------------------------------------------------------------: |
| 提交<br>提交暫存 | Commit<br>Commit Stage | 實務上兩者無差別<br>*註1|
| 全部提交 | Commit All | 將worktree變更直接commit<br>包含未add到stage區的變更 |
| 復原上個提交 | Undo Last commit | 相當於git reset HEAD~ --soft |
| 中止重訂基底 | Abort Rebase | 取消Rebase操作 |
| 提交暫存(修改)<br>全部提交(修改) | Commit Stage(Amend)<br>Commit All(Amend) | 參考[git commit](#git-commit) |
| 提交暫存(已登出) | Commit Stage(Signed off) | 增加簽章驗證<br>通常為嚴謹的Repo、<br>特定的開源Repo才會要求<br>一般不會使用 |
| 全部提交(已簽章) | Commit All(Signed off) | |
> *註1:若Stage區有內容,只會送出Stage區;<br>若Stage區沒有內容,則會詢問是否要順便加到Stage區並做Commit
>
> 
### 變更
| 中文 | 英文 | 說明 |
|:----------------: |:-------------------: |:-------------------------------: |
| 暫存所有變更 | Stage All Changes | 參考[git restore](#git-restore) |
| 取消所有暫存變更 | Unstage All Changes | |
| 捨棄所有變更 | Discard All Changes | |
### 提取、推送
| 中文 | 英文 | 說明 |
|-|-|-|
| 同步處理 | Sync | 先git pull<br>再git push 的整合快捷 |
| 提取 | Pull | 參考[Remote Repo](#遠端數據庫-Remote-Repo) |
| 提取(重訂基底) | Pull(Rebase) |
| 從…提取 | Pull from… | Pull Remote Repo的指定Branch |
| 推送 | Push | |
| 推送至… | Push to | 當有多個Remote Repo時以此指定 |
| 擷取 | Fetch | |
| 擷取(剪除) | Fetch Prune | |
| 從所有遠端擷取 | Fetch From All Remotes | |
### 分支
| 中文 | 英文 | 說明 |
|:------------------: |:----------------------: |:-----------------------------------------: |
| 合併分支 | Merge Branch | 參考[git merge](#git-merge) |
| 重訂基底分支… | Rebase Branch | 參考[Remote Repo](#fetch跟pull的使用時機) |
| 建立分支 | Create Branch | 參考[git merge](#git-merge) |
| 從下列來源建立分支 | Create Branch From | switch到指定位置後,建立新branch |
| 重新命名分支 | Rename Branch | |
| 刪除分支 | Delete Branch | |
| 發布分支 | Publish Brancch | 將HEAD當前的branch<br>更新並綁定到Remote Repo |
| 擷取(剪除) | Fetch Prune | |
| 從所有遠端擷取 | Fetch From All Remotes | |
### 遠端
| 中文 | 英文 | 說明 |
|:--------------: |:-------------: |:-------------------------------------------------: |
| 新增遠端存取庫 | Add Remote | 參考[Remote Repo](#Local-Repo與Remote-Repo的綁定) |
| 移除遠端存取庫 | Remove Remote | |
### 隱藏
| 中文 | 英文 | 說明 |
|:----------------------------: |:------------------------: |:----------------------------------: |
| 擱置變更 | Stash | 參考[Worktree打包隱藏](#git-stash) |
| 擱置變更(包含未被追蹤的檔案) | Stash(Include Untracked) | |
| 套用最新擱置 | Apply Lastest Stash | |
| 套用擱置… | Apply Stash | |
| 取回最近的擱置 | Pop Lastest Stash | |
| 取回擱置… | Pop Stash… | |
| 卸除隱藏項目… | Drop Stash… | |
| 卸除所有隱藏項目… | Drop All Stashes… | |
### 標籤
| 中文 | 英文 | 說明 |
|:--------: |:----------: |:----: |
| 建立標籤 | Create Tag | |
| 刪除標籤 | Remove Tag | |
---
###### tags: `社課` `Topic 1` `Git`