本文主要介紹了為什么要選擇使用Git LFS,進而說明了在Codeup中如何使用Git LFS,以及在使用Git LFS時常見問題的處理方式和Git LFS的工作原理。
為什么選擇使用Git LFS?
Git LFS為了解決大文件托管的效率問題,提供了五大特性:
更大:支持GB級別的大文件版本控制。
更小:讓Git倉庫空間占用減小。
更快:倉庫的克隆和拉取更快。
透明:Git使用上對用戶完全透明。
兼容:權限控制上完全兼容(兼容Codeup權限控制)。
了解更多:什么是Git LFS大文件存儲?
下載和安裝Git LFS
下載:
Linux Debian和RPM packages:https://packagecloud.io/github/git-lfs/install。
Mac:使用
brew install git-lfs
。Windows:目前lfs已經集成在了Git for Windows 中,直接下載和使用最新版本的Windows Git即可。
安裝:如果您選擇使用二進制包下載后安裝,直接執行解壓后的
./install.sh
腳本即可,這個腳本會做兩件事情:在$PATH中安裝Git LFS的二進制可執行文件。
執行
git lfs install
命令,讓當前環境支持全局的LFS配置。# 讓倉庫支持LFS $ git lfs install Updated pre-push hook. Git LFS initialized.
讓本地新倉庫支持Git LFS
以下將以一個通用的場景進行實際的演示和說明(環境為Linux/macOS )。
步驟一:創建一個新的Git空倉庫
在Codeup上創建一個空白的新倉庫,名為“git-lfs”,如何創建,請參見步驟一:新建第一個代碼庫。
將該倉庫克隆到本地,并進入該目錄:
# 請注意替換您實際的代碼庫地址 $git clone https://codeup.aliyun.com/您的企業分組/git-lfs.git Cloning into 'git-lfs'... warning: You appear to have cloned an empty repository. $cd git-lfs $tree .git/hooks/ .git/hooks/ ├── applypatch-msg.sample ├── commit-msg.sample ├── execute-commands.sample ├── post-receive.sample ├── post-update.sample ├── pre-applypatch.sample ├── pre-commit.sample ├── prepare-commit-msg.sample ├── pre-push.sample ├── pre-rebase.sample ├── pre-receive.sample └── update.sample 0 directories, 12 files # 此時Git LFS相關的Hook還未替換
步驟二:配置Git LFS
為了將以示例
.bigfile
后綴結尾的文件使用Git LFS進行存儲,需要執行track命令建立追蹤:$git lfs track "*.bigfile" Tracking "*.bigfile"
重要使用 lfs track 命令時,"*.bigfile"的雙引號非常重要,否則將影響pattern的文件匹配功能。
同理,如需跟蹤其他后綴的文件,如.jpg,可以寫為
git lfs track "*.jpg"
。執行
git lfs track
(不帶任何參數),可以查看當前已跟蹤的Git LFS File 類型:$git lfs track Listing tracked patterns *.bigfile (.gitattributes) Listing excluded patterns
track 命令實際上是修改了倉庫中的
.gitattributes
文件,將該文件add添加到暫存區。$git add .gitattributes
可以通過以下命令查看文件相關變動:
$git diff --cached diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c441ad2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.bigfile filter=lfs diff=lfs merge=lfs -text
步驟三:讓Git LFS配置生效
為了讓"*.bigfile"的配置生效,需要將.gitattributes
文件進行提交:
$git commit -m "Add \"*.bigfile\" LFS config "
[master (root-commit) d052478] Add "*.bigfile" LFS config
1 file changed, 1 insertion(+)
create mode 100644 .gitattributes
$git log --oneline
d052478 (HEAD -> master) Add "*.bigfile" LFS config
步驟四:新建一個.bigfile文件進行測試
接下來,在工作空間創建一個名為dyrone.bigfile的文件,大小為1GB:
# mac環境可以使用mkfile命令替換dd命令
$dd if=/dev/zero of=dyrone.bigfile bs=1G count=1
1+0 records in
1+0 records out
1073741824 bytes (1.1 GB) copied, 2.41392 s, 445 MB/s
$du -sh dyrone.bigfile
1.1G dyrone.bigfile
將dyrone.bigfile添加到暫存區:
$git add dyrone.bigfile
由于dyrone.bigfile后綴命中了.gitattributes
中設置的"*.bigfile"
的文件格式,所以將做為 LFS 文件處理。
什么是Git LFS Pointer
Git LFS并不會將完整的文件屬性和內容進行計算寫入INDEX(暫存區的實際文件),而是會生成一個Git LFS Pointer文件,該文件指向了真正的LFS存儲對象名稱。可以通過執行git diff --cached
命令查看倉庫INDEX和HEAD之間的差異,也就是dyrone.bigfile對應的Pointer文件的內容:
$git diff --cached
diff --git a/dyrone.bigfile b/dyrone.bigfile
new file mode 100644
index 0000000..9d7c19f
--- /dev/null
+++ b/dyrone.bigfile
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:49bc20df15e412a64472421e13fe86ff1c5165e18b2afccf160d4dc19fe68a14
+size 1073741824
Pointer文件內容所表達的含義:
version https://git-lfs.github.com/spec/v1 :代表git-lfs協議的版本。
version...v1:代表當前git-lfs服務端遵從的協議版本。
git-lfs.github.com:此域名地址為git-lfs開源項目的官方地址。
oid sha256:49bc20df15e412a64472421e13fe86ff1c5165e18b2afccf160d4dc19fe68a14:
oid:代表Git LFS object id。
sha256:64位16進制,其代表真實文件的名稱,名稱通過sha256生成,唯一值。
size: 代表文件實際大小,單位:byte。
本地的Git LFS文件是如何存儲的?
在Pointer文件被添加到暫存區的同時,真正的1GB
的大文件dyrone.bigfile被存儲在倉庫的LFS緩存目錄下,名稱被修改為Pointer文件中所指向的oid
字符串名稱:
$tree .git/lfs
.git/lfs
├── objects
│ └── 49
│ └── bc
│ └── 49bc20df15e412a64472421e13fe86ff1c5165e18b2afccf160d4dc19fe68a14
└── tmp
4 directories, 1 file
在本地倉庫中,Git LFS是按照這種方式來進行大文件實際存儲,原本的dyrone.bigfile被表示成了兩個文件,Pointer文件的blob是對應Commit真正引用的對象,而實際LFS文件被緩存在.git/lfs目錄。而此時工作區中的文件沒有發生任何變化。
步驟五:推送文件到遠端
接下來,將dyrone.bigfile的變更提交并推送到遠端:
$git commit -m "Add a really big file"
[master 8032589] Add a really big file
1 file changed, 3 insertions(+)
create mode 100644 dyrone.bigfile
其中,"1 file changed, 3 insertions(+)" 表示Pointer文件已經提交,可以執行git show HEAD
查看提交詳情:
$git show HEAD
commit 8032589f47a748171e84da94ce6440fe139e99f9 (HEAD -> master)
Author: dyroneteng <tenglong***@alibaba-inc.com>
Date: Tue Sep 15 17:25:58 2020 +0800
Add a really big file
diff --git a/dyrone.bigfile b/dyrone.bigfile
new file mode 100644
index 0000000..9d7c19f
--- /dev/null
+++ b/dyrone.bigfile
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:49bc20df15e412a64472421e13fe86ff1c5165e18b2afccf160d4dc19fe68a14
+size 1073741824
接著,將提交推送到遠端Codeup已準備好的空倉庫:
$git push
Uploading LFS objects: 0% (0/1), 3.9 MB | 0 B/s
Uploading LFS objects: 0% (0/1), 79 MB | 30 MB/s
Uploading LFS objects: 0% (0/1), 207 MB | 50 MB/s
Uploading LFS objects: 0% (0/1), 326 MB | 51 MB/s
Uploading LFS objects: 0% (0/1), 534 MB | 56 MB/s
Uploading LFS objects: 100% (1/1), 1.1 GB | 58 MB/s, done.
Counting objects: 3, done.
Delta compression using up to 32 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 410 bytes | 410.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://codeup.aliyun.com/xxxxx/git-lfs.git
d052478..8032589 master -> master
如果存在LFS文件需要上傳,在推送過程中將會顯示LFS上傳進度。
至此,這個倉庫中.bigfile的文件已經成功使用LFS進行管理,而其他文件使用Git進行管理。
簡要解讀此過程的標準輸出:
如果推送的文件中,包含Git LFS文件,那么實際上push將被分為兩個部分:
第一部分: Git LFS oid文件上傳。
第二部分: 當LFS oid文件上傳結束后,繼續使用Git協議上傳Pointer文件的相關對象。
第一部分 "Uploading LFS objects: 100% (1/1), 1.1 GB | 58 MB/s, done." , 會顯示Git LFS oid文件上傳過程中的進度信息,如總上傳個數、當前上傳、oid文件大小和上傳速度等。
第二部分 "Writing objects: 100% (3/3), 410 bytes | 410.00 KiB/s, done." ,例子共三個對象:blob(1)、tree(1)、commit(1)。
兩個部分,任一部分失敗,則整個推送將失敗。
除了過程的標注輸出增多的變化以外,也側面體現了Git LFS是如何進行推送的:在推送時,Git LFS文件被單獨上傳到LFS 服務器上,而Pointer文件保持不變推送到Git 服務器上,可以觀察倉庫目錄大小來驗證這一點。
可以發現,本地倉庫大小僅僅為188K
(排除LFS緩存目錄),這也基本是遠端Git倉庫的大小,也達到了Git LFS瘦身倉庫的目的。
$du -sh --exclude=lfs .git
188K .git
$du -sh .git
1.1G .git
克隆已使用Git LFS的倉庫
本地需預先已安裝Git LFS工具,否則克隆后的LFS文件將以指針而非期望的原始文件呈現。當克隆一個已經使用的Git LFS倉庫時,git-lfs
會自動替換倉庫中相關hook,進而讓Git LFS生效。
# 樣例倉庫,如嘗試,可替換為實際已經 LFS 的倉庫地址
$ git clone gi*@codeup.aliyun.com:您的企業分組/dyrone.git
Cloning into 'dyrone'...
remote: Counting objects: 41, done.
remote: Total 41 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (41/41), 2.98 MiB | 5.01 MiB/s, done.
Filtering content: 100% (3/3), 5.01 MiB | 3.83 MiB/s, done.
# Filtering content表示正在將下載下來的LFS文件smudge為實際工作區的文件
$ cd dyrone
$ tree .git/hooks
.git/hooks
├── applypatch-msg.sample
├── commit-msg.sample
├── execute-commands.sample
├── fsmonitor-watchman.sample
├── post-checkout
├── post-commit
├── post-merge
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample
├── pre-merge-commit.sample
├── pre-push
├── pre-push.sample
├── pre-rebase.sample
├── pre-receive.sample
├── prepare-commit-msg.sample
└── update.sample
0 directories, 17 files
可以看到,例如pre-push等Git LFS需要用到的相關Hook已經被替換,進而支持Git Flow,對用戶Git操作保持兼容和透明。
如何將歷史文件轉換為LFS管理
更多復雜場景遷移指南,參見LFS 遷移指南。
如何撤銷LFS跟蹤并使用Git管理
您可以取消繼續跟蹤某類文件,并將其從cache中清理:
git lfs untrack "*.bigfile"
git rm --cached "*.bigfile"
如果您想將這些文件添加回常規Git跟蹤,可以執行以下操作:
git add "*.bigfile"
git commit -m "restore "*.bigfile" to git from lfs"
Git LFS工作原理
在前面的一些示例之后,相信大家對Git LFS有了一定的理解,其工作原理如下:
Git LFS場景
如圖所示,可以針對jpg格式的圖片利用Git LFS的存儲功能,在推送過程中將其上傳至大文件存儲服務。同時,與大文件對應的指針文件將與其他普通代碼文件一并推送到遠端Git倉庫中。
Git LFS處理流程介紹
Git場景
無論是針對小型的代碼文本文件,還是較大型的圖片文件,在相關變更從本地提交到遠程倉庫時,所有相關文件資源都會完整地存儲在Git服務器上。以圖片為例,如果圖片文件數量逐漸增多且改動頻次持續上升,倉庫的體積將會迅速膨脹。
Git LFS的限制
Windows平臺,單個文件不支持超過
4G
,issues 2434。Windows用戶必須保證已經安裝
Git Credential Manager
,否則可能導致操作被無限掛起,issues 1763。不同于Gitlab硬編碼的LFS下載token超時時間(30分鐘),Codeup會根據將要下載的文件列表動態計算token超時時間,但是如果位于網絡環境不好的環境,仍舊可能導致token超時的情況。如果需要根據需求調整,可以聯系Codeup系統管理員處理。