---
# System prepended metadata

title: git
tags: [Coding]

---

---
tags: Coding
---

# git
## 安裝
```
brew install git
```
## 忽略掉檔案
防止不必要的檔案被測到
![image](https://hackmd.io/_uploads/r1zd3WnCA.png)

```bash=
#設定git信箱跟名子
git config --global user.email 電子信箱
git config --global user.name 你的名子
#檢查giy信箱與名子
git config --global user.email
git config --global user.name
```
## 基本指令
```bash=
#看git狀態
git status
# 如果還沒初始化 
# 致命錯誤: 不是一個 git 版本庫（或者任何父目錄）：.git
初始化
git init
```
```bash=
#加入檔案到緩衝區
git add 檔案
#加入路徑下所有檔案
git add .
```
加完會被放在上面綠色緩衝的地方
![image](https://hackmd.io/_uploads/rJS9A350A.png)
之後有更改也會顯示
![image](https://hackmd.io/_uploads/rJvF-TcAR.png)


```bash=
#把綠色緩衝區的檔案提交
git commit -m "first save"
```
綠色的不見了 被記錄了
![image](https://hackmd.io/_uploads/ryjr1aqCR.png)

```bash=
#存擋紀錄 如果下面有冒號可以按q離開
git log
```
![image](https://hackmd.io/_uploads/HkT8lacAA.png)
## 重置檔案
```bash=
#取消被add進去緩衝區的檔案
git reset 檔案
#取消所有被add進去緩衝區的檔案
git reset .
```
```bash=
#取消前一個的commit 但至少要有兩個commit存在
#head後面是要往前幾個 --soft是軟執行 --hard是強制執行
git reset --soft head~1
```

![image](https://hackmd.io/_uploads/B1L1sMjCA.png)
連git log也會不見
```bash=
#回復被更改的檔案 到上次的commit 但在緩衝區的不受影響

#法一
git restore test.py
#法二
git checkout -- 檔案
```
![image](https://hackmd.io/_uploads/rJvF-TcAR.png)

```bash=
#回復到指定存擋點 後面像亂碼的是要恢復存擋點的編碼
#沒有用雲端保存的話 回復前的存擋點會消失
git reset f936f22342d095f65f2cece6c17a6eb5c33b2313

#查看存擋點做的改變
git show f936f22342d095f65f2cece6c17a6eb5c33b2313
```
![image](https://hackmd.io/_uploads/BkG4QT9AA.png)
## 連接到github
```bach=
#連上要上傳的git repostories
#後面網址是資料庫的位置 origi是源頭名稱
#token跟名稱要問gpt
git remote add origin https://使用者名稱/token @github.com/Bruce762/gittest.git 

#看資料庫有什麼分支
git remote

#master線上的存擋點上傳到github中的original
#加 -u可以在下次上傳時只打push就好
git push -u origin master
#用HEAD代表當前所在的線 就不用看現在在哪條
git push -u origin HEAD

#把資料庫中的東西下載下來
#之前的上傳紀錄什麼的也都可以看到
git clone https://github.com/Bruce762/gittest.git
```
git remote add origin出現錯誤時
```bash=
git remote add origin https://github.com/Bruce762/code.git
錯誤: 遠端 origin 已經存在。
```
移除舊的origin
```bash=
git remote remove origin
```
添加新的origin
```bash=
git remote add origin https://github.com/Bruce762/code.git
```
資料庫位置
![image](https://hackmd.io/_uploads/rJ4Yypj00.png =70%x)
第一條主線叫做master
![image](https://hackmd.io/_uploads/B1OJvxnRC.png =70%x)

## 更新最新資料
git fetch 只下載最新的更新，但不會自動將變更合併到你的本地分支。
git pull 則是 git fetch 後接著自動合併變更，相當於 git fetch 加上 git merge。
```bash=
git fetch
git pull
```
```bash=
#把github上最新的進度拉下來
#與其他人的push完發生衝突也要用pull拉下來
git pull

#但pull conflict要用下面兩種方式
#法ㄧ 會列出衝突的地方 如下圖
git merge --no-ff
#法二 直接把新的存擋接到github的最後面 不建議在共用分支上使用
git rebase
```
Accept Crrent change 保留綠色
Accept incoming change 保留藍色
Accept Both change 保留兩個
Compare changes 開一個視窗幫你比較

![截圖 2024-10-03 下午4.13.35](https://hackmd.io/_uploads/HyHFz0iAA.png =70%x)


## branch
```bash=
#查看自己在哪個支線
git branch
#創建新支線
git branch 支線名稱
#刪除本地支線 但不能刪自己所在的支線
git branch -d branch-name    # 安全刪除（已合併）
git branch -D branch-name    # 強制刪除
#刪除遠端支線
git push origin --delete branch-name
```
![image](https://hackmd.io/_uploads/BykRqRc0R.png =70%x)
```bash=
#切換支線
#法一
git checkout 支線名稱
#法二
git switch 支線名稱

#創建支線並切換過去
git checkout -b 支線名稱
#切換回切換前的支線
git checkout -
```
![image](https://hackmd.io/_uploads/B11fiCqR0.png=70%x)

```bash=
#合併支線 把要合併的支線內容加到目前所在的支線 並且解決衝突
#所以名子是目前支線 但內容是合併的支線的內容
git merge 合併的支線
```
![image](https://hackmd.io/_uploads/r1dxZyiC0.png =70%x)
### 本地與遠端分支
**查看目前 Git 專案所在的本地分支：**
```bash
git branch
```
輸出：
有星號 `*` 的那一行表示你目前所在的分支。
```
* main
  develop
  feature/login
```
**查看目前分支對應的遠端分支：**
```bash
git branch -vv
```
輸出
```
* feature/login  abc1234 [origin/feature/login] 登入功能完成
  main           def5678 [origin/main]           初始版本
```

**查看所有的遠端分支：**
```bash
git branch -r
```
輸出
```
origin/HEAD -> origin/main
origin/feature/login
origin/main
```
## 不小心push錯
現在雲端的9是不小心push錯的
![image](https://hackmd.io/_uploads/SJO---nRC.png =50%x)
```bash=
#硬重置
git reset --hard HEAD~1
#-f強制推送
git push -f origin <branch-name>
```
## branch合併
假設有主線(master)與支線(second)
![image](https://hackmd.io/_uploads/SJzcXg20A.png =70%x)

master主線
![image](https://hackmd.io/_uploads/B1n3fzhAA.png)

second支線(還未push)
![image](https://hackmd.io/_uploads/By6TGfnRA.png)

假如直接push支線的話
![image](https://hackmd.io/_uploads/SyPQ7Mn0C.png)
github上會有合併請求(pull request)
右上角綠色的
![image](https://hackmd.io/_uploads/HycdfghC0.png =70%x)
或是先到second支線 上面會出現contribute可以pull request
![image](https://hackmd.io/_uploads/ByM0xDnAC.png =70%x)

但上面會有can't auto merge代表衝突所以沒辦法合併
![image](https://hackmd.io/_uploads/S1HoGx20R.png =80%x)
### 法一：先rebase再push給github 請求pull quest(merge) 推薦
可以看到有誰pull request（推薦）
所以要用rebase把支線接到主線的屁股上
![image](https://hackmd.io/_uploads/rkofNlnCA.png)
先切到主線上pull到最新進度
然後切回支線做rebase接到master後面
```bash=
git switch master
git pull
git switch second
git rebase master #把目前所處的線接到master線上 記得緩衝區不能有東西
git rebase origin/master #另一種寫法
```
![image](https://hackmd.io/_uploads/HkZ8rl3CR.png)
但可能會遇到衝突
![image](https://hackmd.io/_uploads/rkZ7Bl30A.png)
解完衝突commit之後 要用下面指令 只是有時候會有復數衝突 要做比較多次
```bash=
git rebase --continue
````
可以看到second的東西被接到master後面了
![image](https://hackmd.io/_uploads/HyXrSz20C.png)

push只能上傳到原有基礎上的最新存擋點
但因為git log中存擋點的位置都改變了
就要用`-f`強制上傳 不然會出錯
但因為會改到整個git log 所以要小心使用
```bash=
git push -f
```
![image](https://hackmd.io/_uploads/S1xtLMhCC.png)

這個時候就可以提出合併請求了
![image](https://hackmd.io/_uploads/SJUyuGhAC.png)

經過比較後新的有完全包含舊的才行
![image](https://hackmd.io/_uploads/HJLCdf2CC.png =65%x)
管理者同意合併請求後
切回master 然後用pull更新會長這樣
```bash=
git switch master 
git pull
```
![image](https://hackmd.io/_uploads/HyWTof3C0.png =70%x)

#### 就算pull request成功 只要別人或其他檔案有先push或merge 就要再解決conflict
second支線狀態(rebase完的)
![截圖 2024-10-04 凌晨2.25.27](https://hackmd.io/_uploads/SkLAZv3RC.png =50%x)
在master還沒push前 先把second做pull request
![image](https://hackmd.io/_uploads/HksT-vnCR.png =50%x)
然後push master
![image](https://hackmd.io/_uploads/SkMGMPhA0.png =50%x)
結果發生conflict
![image](https://hackmd.io/_uploads/Skg3nGD3C0.png =50%x)

還是要再切到second去解決conflict
![image](https://hackmd.io/_uploads/BkiqmP2CA.png =50%x)
解決完conflict
push後就可以merge了
![image](https://hackmd.io/_uploads/H17_4whRC.png =50%x)

### 法二：先在自己的電腦上merge然後再push 不推薦
但比較不建議法二 主要是別人看不到自己的pull request
因為自己merge好的檔案可能 在雲端上有其他人先存擋了 可能又要多處理conflict

假如上面進度
master新增func5
![image](https://hackmd.io/_uploads/BJ0kvU3RR.png)
second新增func4
![image](https://hackmd.io/_uploads/rJqeDUnCC.png)
先切到master 之後git merge
```bash=
git switch master
git merge second #把second加到master做merge
```

```bash=
#如果merge發生衝突之後要手動git commit的話
git commit #會跑出文字編輯 要用:wq進行存擋 或:x!確認合併
#如果打成:q!會跳出去而沒有存擋
```

最後解決完衝突之後就可以push到github
但可能其他人先push檔案了 就要再解一次conflict
![image](https://hackmd.io/_uploads/SJSVwU2AC.png)
### 比較rebase merge
法ㄧ比較可以看到 pull request相較於法二來說比較清楚好溝通
rebase通常在支線上執行 merge在主線上

**merge commit：** git merge 會保留合併的歷史，顯示兩個分支如何交互，並會生成一個新的合併提交，這使得 Git 記錄中會出現一個分支合併的節點。這對於團隊協作來說能夠反映多個開發分支的歷史過程。
```
---B---C  (main)
     \    
      D---E  (feature)
```
當 feature 合併進 main 之後，結果會變成：
```
A---B---C---M  (main)
     \    /
      D---E  (feature)
```

**rebase：** git rebase 則會重新排列提交歷史，讓結果看起來像是一條線性歷史。這會使提交記錄更乾淨，但是原始的分支歷史（例如衝突解決的過程）會被覆蓋。
```
A---B---C  (main)
     \    
      D---E  (feature)
```
當 feature 重整到 main 之後，結果會變成：
```
A---B---C---D'---E'  (main 和 feature)
```

* merge 的適用場景：
    * 適合 團隊協作 中，保留分支的完整歷史紀錄與合併過程，能夠直觀反映分支的交互過程。
    * 在多個開發人員並行開發的情況下，保留合併提交能幫助追蹤問題和變更源頭。
* rebase 的適用場景：
    * 適合個人開發者或想保持提交歷史乾淨的情況下使用，以保持提交歷史的線性結構。
    * 在處理長期存在的功能分支時，常用 rebase 來與主分支保持同步，而不引入大量的合併提交。
    * 當需要提交歷史保持簡潔和整齊時，rebase 是一個很好的選擇，但需要謹慎使用，特別是避免在共享的分支上進行 rebase，因為會改變歷史，可能導致他人無法正常更新代碼。
## 如果沒有git檔案又有衝突的情況
```bash
git clone 最新的檔案
git checkout HEAD~1 #回到前幾的檔案之前
```
用完改動並且commit之後
```bash
git switch -c fix-file #先把 detached HEAD 內容掛到一條新分支(fix-file)
git push -u origin fix-file #推到遠端
```
## detached完commit後要合併回main
```swift
# 建立一個臨時分支來保存目前的 commit
git switch -c temp-save

# 切回 main 分支
git switch main

# 合併剛剛的 commit 到 main
git merge temp-save

# （選擇性）可以刪掉臨時分支
git branch -d temp-save
```
## gitignore沒辦法忽略檔案的狀況
在项目开发过程中个，一般都会添加 .gitignore 文件，规则很简单，但有时会发现，规则不生效。
原因是 .gitignore 只能忽略那些原来没有被track的文件，如果某些文件已经被纳入了版本管理中，则修改.gitignore是无效的。
那么解决方法就是先把本地缓存删除（改变成未track状态），然后再提交。
git rm -r --cached . 把所有檔案untrack
但如果是.DS_Store系統檔的話可能就要手動刪除

```bash=
git rm -r --cached . 
git add .
git commit -m 'update .gitignore'
git push
```

在這個情境中，我們有兩個開發者 A 和 B，分別負責不同的部分：A 負責撰寫程式碼，B 負責撰寫測試碼。他們使用 VS Code 來開發 Java 程式，專案採用 Maven 架構，並且透過 Git 來進行版本控制。以下是具體的版控流程，描述如何進行協作和合併專案。

## 情境範例

A 和 B 正在開發一個簡單的計算器應用程式，A 主要負責開發程式邏輯，而 B 負責編寫單元測試。兩人透過 GitHub 進行協作，並希望確保每個人的工作在相互獨立的分支上進行，最後再合併到主分支（`main`）。

### Git 協作流程

1. **設定 GitHub Repo**
   - A 在 GitHub 上建立一個新的 Maven 專案 Repo，並邀請 B 參與專案。
   - A 將此 Repo 克隆到本地：  
     ```bash
     git clone https://github.com/username/project.git
     ```

2. **建立分支**
   - 為了保持分工明確，A 和 B 各自建立自己的工作分支。
   - A 建立了一個名為 `feature/calculator-logic` 的分支，負責撰寫程式邏輯：  
     ```bash
     git checkout -b feature/calculator-logic
     ```
   - B 則建立了一個名為 `feature/test-cases` 的分支，用於撰寫測試代碼：  
     ```bash
     git checkout -b feature/test-cases
     ```

3. **A 開發程式邏輯**
   - A 在 `feature/calculator-logic` 分支中開發計算器的核心功能。完成後，A 將代碼提交並推送到遠端：  
     ```bash
     git add .
     git commit -m "Add calculator logic"
     git push origin feature/calculator-logic
     ```

4. **B 撰寫測試代碼**
   - B 切換到自己的分支 `feature/test-cases`，並開始撰寫測試代碼。B 可以通過拉取 A 的分支來獲取最新的程式邏輯：  
     ```bash
     git fetch origin
     git checkout feature/calculator-logic
     git pull origin feature/calculator-logic
     ```
   - 確認程式邏輯後，B 撰寫測試代碼，並推送到遠端：
     ```bash
     git checkout feature/test-cases
     git add .
     git commit -m "Add test cases for calculator"
     git push origin feature/test-cases
     ```

5. **發送 Pull Request**
   - A 和 B 完成各自的工作後，將分支的變更推送到 GitHub，並發送 Pull Request。
   - A 創建 PR 以將 `feature/calculator-logic` 分支合併到 `main`，B 則創建 PR 以將 `feature/test-cases` 分支合併到 `main`。
   - 兩人可以在 GitHub 上進行相互代碼審查（Code Review），確保合併到 `main` 分支的代碼質量。

6. **合併分支**
   - 當代碼審查完成並確認沒有問題後，A 和 B 各自批准他們的 PR 並將分支合併到 `main`。
   - 合併後，兩人將拉取最新的 `main` 分支：  
     ```bash
     git checkout main
     git pull origin main
     ```

7. **解決合併衝突（若有）**
   - 如果在合併時出現衝突，Git 會提示需要手動解決衝突。A 或 B 需要協調並解決這些衝突，然後重新提交合併。

8. **結束分支**
   - 分支合併後，A 和 B 可以選擇刪除各自的開發分支，以保持 Repo 的整潔：  
     ```bash
     git branch -d feature/calculator-logic
     git branch -d feature/test-cases
     ```