[踩雷紀錄] Git 無法推送大型檔案至 repo
最近在進行更版的時候一個不注意把一個 tar 檔一並加入到了 commit 當中,而當我要推上 repo 的時候就發生了錯誤,原因似乎是 git 不允許推送單一檔案超過 100 MB,當下真是有點慌張,因為加入這個檔案之後我又做了幾次變更,送了幾個 commit,心想著紀錄永遠不會從 git 中消失,那這個檔案不就無解了,還好還是有被我找到解方,做法也不複雜,特別筆記一下這個用法
由於這個檔案已經被 commit 過了所以即使在新的 commit 中移除它也於事無補,git 會留存所有版本的紀錄,所以必須要另外想方法解才行
在開始前一定要記得第一步一定要先編輯 .gitignore,再一次把大型檔案加入提交這種事情是一定要避免的
Error Message
先記錄下當下看到的錯誤訊息:
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com
remote: error: Trace: dff1555...
remote: error: See http://git.io/iEPt8g for more information.
remote: error: File test.tar is 261.83 MB; this exceeds GitHub's file size limit of 100.00 MB To https://github.com/.../.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://github.com/.../.git'
屏蔽了一些不重要的資訊,明確的告知了哪個檔案過大導致了問題的發生,並且也有標示出發生的 commit,這讓我們很容易可以追蹤到問題
情境一: 剛剛才 commit 掉大型檔案
如果是這個情況,那恭喜,要做的事情簡單很多,只要兩行就可以解決了
git rm --cached test.tar
git commit --amend -C HEAD
git rm --cached可以將檔案移出 git 的紀錄當中- 而
git commit -amend則可以修改 commit 紀錄重新包一份給它
只要這兩行就可以簡單解決了
情境二: 大型檔案的 commit 在三個 commit 之前
這個情境就是筆者遇到的,在經過了幾個 commit 之後才發現曾經把不該加入的檔案提交了😢
那首先根據剛剛的錯誤訊息可以得知這個檔案是位在 commit dff1555...,讓我們先看看這個 commit 的位置在哪
git log --pretty=oneline --abbrev-commit
可以知道問題發生在前三個 commit,那我們要回朔到再往前一個 commit 來去進行修改
706d14191 最後一個 commit
810f4dbaf 倒數第二個 commit
dff155505 罪魁禍首
dd81500d8 問題發生前一個 commit
...
找到 commit 位置後輸入下面指令:
git rebase -i dd81500d8
git rebase -i 是用來幫忙整理 commit 紀錄的,可以用來修改 commit 的內容以及 commit 訊息,輸入之後會開啟 git 的預設編輯器並看到以下畫面:
pick dff1555 罪魁禍首
pick 810f4db 倒數第二個 commit
pick 706d141 最後一個 commit
# Rebase dd81500..dff1555 onto dd81500 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
接著在我們要修改的 commit 前面,把 pick 改成 edit,如下所示
edit dff1555 罪魁禍首
pick 810f4db 倒數第二個 commit
pick 706d141 最後一個 commit
...
接著就會進入 rebase 的階段,git 會帶你回到你要 edit 的 commit 階段,這時候就像是前一個情境一樣,你的 HEAD 回到了 dff1555 的位置,那操作也是一樣的,最後再離開 rebase 就好
git rm --cached test.tar
git commit --amend -C HEAD
git rebase --continue
情境三: 大型檔案的 commit 散佈在各處,且有多處變更
如果真的有個意外,讓這個大型檔案流傳了好幾個 commit,而且不同的 commit 間還有變更,那就不是改一個 commit 可以解決的問題了
git filter-branch --tree-filter "rm -f test.tar"
git push -f
其實這個解法好像更簡單,不過要確定操作正確,不然刪錯東西就麻煩了,下面簡單說明下指令:
git filter-branch: 這個指令的用途呢,其實就是checkout到每一個版本去做批次的操作,根據參數的 filter 去決定要做的操作--tree-filter: 這個 filter 則是代表要針對每個版本的檔案去做修改"rm -f test.tar": 跟前面的指令與參數結合代表,切換到每個版本去刪除掉test.tar這個檔案git push -f: 最後你更改了整個分支樹所以要強制推上 repo 去覆蓋
git filter-branch 其實還有蠻多用法的,如果有遇到特別的情境的話可以考慮再來寫一篇
結語
雖然是遇到這個 error 才特別查的做法,但其實不限於刪除大型檔案上,如果真的有一定要更動 commit 的需求也可以比照辦理,更加理解到了 git 的強大