iT邦幫忙

2025 iThome 鐵人賽

0
自我挑戰組

30天 Git 版本控制實戰筆記系列 第 27

Day 27:疑難雜症解決 - Git 問題終結者

  • 分享至 

  • xImage
  •  

今日目標
• 處理常見 Git 錯誤
• 救回誤刪的資料
• 解決複雜的合併問題
• 掌握進階恢復技巧


一、檔案救援
情境 1:誤刪檔案(還沒 commit)

刪除了重要檔案

rm important.txt

恢復方法 1:從暫存區恢復

git restore important.txt

恢復方法 2:從最新 commit 恢復

git restore --source=HEAD important.txt

恢復整個目錄

git restore src/

恢復所有刪除的檔案

git restore .
情境 2:誤刪 Commit

不小心 reset 刪除了 commits

git reset --hard HEAD~3 # 刪除了最近 3 個 commits

救回方法:使用 reflog

git reflog

找到刪除前的 commit hash

範例輸出:

abc123 HEAD@{0}: reset: moving to HEAD~3

def456 HEAD@{1}: commit: 重要功能 3

ghi789 HEAD@{2}: commit: 重要功能 2

恢復

git reset --hard def456

git reset --hard HEAD@{1}

確認

git log --oneline
情境 3:刪除了分支

不小心刪除分支

git branch -D feature-important

救回方法 1:從 reflog

git reflog | grep feature-important

找到最後一個 commit

重建分支

git branch feature-important abc123

救回方法 2:找懸空的 commits

git fsck --lost-found

顯示 dangling commits

git show abc123 # 確認是不是要找的
git branch feature-important abc123
情境 4:檔案改壞了想回到之前版本

查看檔案歷史

git log -- path/to/file.js

恢復到特定版本

git restore --source=abc123 -- path/to/file.js

或用 checkout

git checkout abc123 -- path/to/file.js


二、Commit 問題
情境 1:Commit 訊息寫錯

最後一次 commit

git commit --amend -m "正確的訊息"

更早的 commit(使用 rebase)

git rebase -i HEAD~3

編輯器中,把要改的 commit 從 pick 改成 reword

保存後會再開編輯器讓你改訊息

情境 2:Commit 到錯誤的分支

狀況:在 main 做了 commit,應該在 feature 分支

方法 1:移動最後一個 commit

git branch feature-xyz # 建立新分支(指向當前 commit)
git reset --hard HEAD~1 # main 回退
git checkout feature-xyz # 切到新分支

方法 2:移動到現有分支

git checkout feature-xyz
git cherry-pick main # 複製 main 的最後 commit
git checkout main
git reset --hard HEAD~1 # main 回退
情境 3:想拆分一個 commit

使用 interactive rebase

git rebase -i HEAD~3

把要拆的 commit 改成 edit

保存後會停在該 commit

取消這個 commit(保留變更)

git reset HEAD^

分別 commit

git add file1.js
git commit -m "功能 A"

git add file2.js
git commit -m "功能 B"

繼續

git rebase --continue
情境 4:想合併多個 commits

使用 interactive rebase

git rebase -i HEAD~5

編輯器中:

pick abc123 feat: 功能 1
fixup def456 fix: 修正錯字 # 改成 fixup
fixup ghi789 fix: 又修正 # 改成 fixup
pick jkl012 feat: 功能 2

保存,5 個 commits 變成 3 個

或使用 squash(會合併訊息)

pick abc123 feat: 功能 1
squash def456 fix: 修正錯字


三、合併衝突進階處理
複雜衝突的處理策略

查看衝突檔案

git status

查看衝突細節

git diff

策略 1:使用 mergetool

git mergetool

策略 2:選擇一方的版本

全部用我們的版本

git checkout --ours path/to/file.js

全部用他們的版本

git checkout --theirs path/to/file.js

策略 3:手動編輯

vim path/to/file.js

移除衝突標記,保留需要的內容

git add path/to/file.js
git commit
Rebase 衝突處理

開始 rebase

git rebase main

遇到衝突

CONFLICT (content): Merge conflict in file.js

解決衝突

vim file.js
git add file.js

繼續 rebase

git rebase --continue

如果每個 commit 都衝突,考慮放棄

git rebase --abort

改用 merge

git merge main
避免重複解決相同衝突

啟用 rerere(reuse recorded resolution)

git config --global rerere.enabled true

Git 會記住你如何解決衝突

下次遇到相同衝突時自動套用


四、遠端倉庫問題
情境 1:Push 被拒絕

錯誤訊息:

! [rejected] main -> main (fetch first)

原因:遠端有新的 commits

解決方法 1:先拉再推

git pull origin main

解決衝突(如果有)

git push origin main

解決方法 2:使用 rebase(保持線性歷史)

git pull --rebase origin main
git push origin main

解決方法 3:強制推送(危險!)

⚠️ 只在確定沒有協作者時使用

git push --force-with-lease origin main
情境 2:Clone 或 Pull 超級慢

方法 1:淺層 clone(只下載最近的歷史)

git clone --depth 1 https://github.com/user/repo.git

方法 2:只 clone 特定分支

git clone --single-branch --branch main https://github.com/user/repo.git

方法 3:使用代理或鏡像

git config --global http.proxy http://proxy:port
情境 3:遠端分支已刪除但本地還在

查看遠端追蹤分支

git branch -r

清理已刪除的遠端分支

git fetch --prune

git fetch -p

刪除對應的本地分支

git branch -d feature-old
情境 4:改了遠端 URL

查看目前的 remote

git remote -v

修改 URL

git remote set-url origin https://github.com/new-url/repo.git

或先刪除再加

git remote remove origin
git remote add origin https://github.com/new-url/repo.git

驗證

git remote -v


五、工作區狀態問題
情境 1:改了很多檔案想全部丟棄

丟棄所有未暫存的變更

git restore .

或舊語法

git checkout -- .

丟棄已暫存的變更

git restore --staged .
git restore .

git reset --hard HEAD

刪除未追蹤的檔案

git clean -fd

-f = force

-d = 包含目錄

預覽會刪除什麼

git clean -n
情境 2:Stash 相關問題

暫存包含未追蹤的檔案

git stash push -u -m "描述"

查看 stash 列表

git stash list

查看 stash 內容

git stash show stash@{0}
git stash show -p stash@{0} # 顯示 diff

恢復特定 stash

git stash apply stash@{2}

恢復並刪除

git stash pop stash@{0}

刪除 stash

git stash drop stash@{0}

清空所有 stash

git stash clear

從 stash 建立分支

git stash branch new-branch stash@{0}
情境 3:Detached HEAD 狀態

你看到:

HEAD detached at abc123

如果想保留這些變更

git branch temp-branch
git checkout main
git merge temp-branch

或用 cherry-pick

git checkout main
git cherry-pick abc123

如果不想保留

git checkout main

變更會丟失(但可以從 reflog 找回)


六、效能和空間問題
情境 1:倉庫太大

查看倉庫大小

du -sh .git

查看大檔案

git rev-list --objects --all |
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
awk '/^blob/ {print substr($0,6)}' |
sort --numeric-sort --key=2 |
tail -10

清理大檔案(使用 BFG)

java -jar bfg.jar --strip-blobs-bigger-than 50M
git reflog expire --expire=now --all
git gc --prune=now --aggressive

或使用 git filter-repo

pip install git-filter-repo
git filter-repo --strip-blobs-bigger-than 50M
情境 2:加速 Git 操作

啟用檔案系統監控

git config core.fsmonitor true
git config core.untrackedCache true

定期維護

git gc --aggressive
git prune

淺層 fetch

git fetch --depth=1

使用 partial clone

git clone --filter=blob:none https://github.com/user/repo.git


七、權限和認證問題
情境 1:SSH 連線失敗

測試 SSH 連線

ssh -T git@github.com

除錯模式

ssh -vT git@github.com

常見問題:

1. SSH key 沒有加到 GitHub

2. SSH agent 沒有執行

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

3. 權限問題

chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
情境 2:HTTPS 認證問題

清除儲存的密碼

git credential-cache exit

git config --global --unset credential.helper

重新設定

git config --global credential.helper cache

或 macOS

git config --global credential.helper osxkeychain

檢查是否需要 Personal Access Token

GitHub 不再支援密碼,必須用 token


八、進階恢復技巧
使用 Git Reflog 救援

reflog 記錄所有 HEAD 移動

git reflog

輸出範例:

abc123 HEAD@{0}: reset: moving to HEAD~3

def456 HEAD@{1}: commit: 功能完成

ghi789 HEAD@{2}: commit: 進行中

回到任何時間點

git reset --hard HEAD@{2}

查看某個分支的 reflog

git reflog show feature-branch

reflog 保存時間(預設 90 天)

git config gc.reflogExpire "180 days"
找回已刪除的內容

方法 1:在歷史中搜尋內容

git log -S "要找的內容" --all

方法 2:搜尋 commit 訊息

git log --grep="關鍵字" --all

方法 3:找回已刪除檔案

git log --all --full-history -- path/to/deleted/file

找到 commit 後

git checkout ^ -- path/to/deleted/file

方法 4:使用 fsck 找懸空物件

git fsck --lost-found

檢查 .git/lost-found/ 目錄

完全搞砸了怎麼辦

終極救援方案

1. 備份當前狀態

cp -r .git .git.backup

2. 重新 clone

cd ..
git clone repo-clean
cd repo-clean

3. 從舊的 .git 找回需要的東西

查看 reflog

git --git-dir=../repo/.git.backup reflog

Cherry-pick 需要的 commits

git cherry-pick abc123

4. 確認無誤後刪除備份

rm -rf ../repo/.git.backup


九、常見錯誤訊息解析

❌ fatal: not a git repository

原因:不在 Git 倉庫中

解決:cd 到正確目錄,或 git init

❌ error: Your local changes would be overwritten

原因:本地有未提交的變更

解決:git stash 或 git commit

❌ fatal: refusing to merge unrelated histories

原因:兩個倉庫沒有共同歷史

解決:git pull origin main --allow-unrelated-histories

❌ error: failed to push some refs

原因:遠端有更新

解決:git pull 後再 push

❌ fatal: The current branch has no upstream branch

原因:本地分支沒有追蹤遠端

解決:git push -u origin branch-name

❌ error: pathspec 'file' did not match any files

原因:檔案不存在或路徑錯誤

解決:檢查檔案名稱和路徑

❌ fatal: reference is not a tree

原因:指定的 commit 不存在

解決:檢查 commit hash 是否正確


十、預防勝於治療

每日備份重要分支

git push origin main
git push origin develop

使用 alias 防止危險操作

git config --global alias.pushf 'push --force-with-lease'

永遠不要用 --force,改用 --force-with-lease

定期清理和維護

git gc --auto
git prune

重要操作前先建分支

git branch backup-$(date +%Y%m%d)

使用 pre-commit hooks 防止錯誤

例如:防止直接 commit 到 main


快速參考:救援指令速查表

檔案救援

git restore # 恢復檔案
git restore --source= # 從特定版本恢復

Commit 救援

git reflog # 查看所有操作
git reset --hard HEAD@{n} # 回到某個時間點
git cherry-pick # 複製 commit

分支救援

git fsck --lost-found # 找懸空的 commits
git branch # 重建分支

衝突處理

git checkout --ours # 使用我們的版本
git checkout --theirs # 使用他們的版本
git mergetool # 使用合併工具

清理

git clean -fd # 刪除未追蹤檔案
git reset --hard HEAD # 丟棄所有變更

遠端問題

git fetch --prune # 清理已刪除的遠端分支
git remote set-url origin # 更改 remote URL


今日重點回顧
✅ 核心技能:

  1. 檔案救援

    • restore 恢復檔案
    • reflog 找回 commits
    • fsck 找懸空物件
  2. Commit 處理

    • amend 修改訊息
    • rebase 重組歷史
    • cherry-pick 移動 commits
  3. 衝突解決

    • mergetool 工具輔助
    • ours/theirs 策略
    • rerere 記住解決方案
  4. 遠端問題

    • pull/push 策略
    • 清理遠端分支
    • 認證問題處理
  5. 進階恢復

    • reflog 完整追蹤
    • 搜尋歷史內容
    • 完全重置方案

記住:

  • 幾乎所有東西都能救回(在 reflog 過期前)
  • 預防勝於治療
  • 重要操作前先備份

立即行動

今天就練習(30 分鐘)

□ 查看你的 reflog
□ 練習 restore 和 reset
□ 建立一個測試 repo 練習救援

本週完成(1 小時)

□ 設定 rerere
□ 建立備份 alias
□ 練習 interactive rebase
□ 測試各種救援情境

練習腳本

mkdir git-rescue-practice
cd git-rescue-practice
git init

建立一些 commits

echo "v1" > file.txt
git add file.txt
git commit -m "v1"

echo "v2" > file.txt
git commit -am "v2"

echo "v3" > file.txt
git commit -am "v3"

練習:「誤刪」最近 2 個 commits

git reset --hard HEAD~2

練習救回

git reflog

找到 v3 的 hash

git reset --hard


常見問題 FAQ
Q1: 什麼情況下資料真的救不回來?
救不回的情況:
❌ 從未 commit 過的檔案被刪除
❌ reflog 已過期(預設 90 天)
❌ git gc 清除了懸空物件
❌ 檔案系統損壞

救得回的情況:
✅ 任何曾經 commit 的內容
✅ reflog 期限內的操作
✅ 「刪除」的分支
✅ reset/rebase 後的 commits
Q2: git reset 的三種模式有什麼差別?

--soft:只移動 HEAD

git reset --soft HEAD~1

Commit 內容回到 staging area

--mixed(預設):移動 HEAD + 重設 staging

git reset HEAD~1

Commit 內容回到 working directory

--hard:移動 HEAD + 重設 staging + working

git reset --hard HEAD~1

Commit 內容完全消失(但能從 reflog 救回)

Q3: 什麼時候該用 rebase,什麼時候用 merge?
用 Rebase:
✅ 自己的 feature 分支
✅ 想要線性歷史
✅ 整理 commits

用 Merge:
✅ 公開的分支(main, develop)
✅ 保留完整歷史
✅ 團隊協作

黃金法則:
永遠不要 rebase 已推送的公開分支!
Q4: force push 什麼時候可以用?
可以用的情況:
✅ 自己的 feature 分支
✅ 整理完歷史後
✅ 確定沒有其他人在用這個分支

絕對不要:
❌ 公開的分支(main, develop)
❌ 團隊共用的分支
❌ 不確定的時候

更安全的做法:
git push --force-with-lease

會檢查遠端是否被別人更新


實用工具

Git Alias(加速常用操作)

git config --global alias.undo 'reset --soft HEAD~1'
git config --global alias.unstage 'restore --staged'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual 'log --graph --oneline --all'
git config --global alias.cleanup 'clean -fd'

使用

git undo # 撤銷最後一次 commit
git unstage . # 取消 staging
git last # 查看最後一次 commit
git visual # 視覺化歷史


上一篇
Day 26:CI/CD 整合 - 自動化你的工作流程
下一篇
Day 28:建立作品集 - 用 GitHub 展示你的實力
系列文
30天 Git 版本控制實戰筆記30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言