Git LFS是什么呢?
Git是分布式版本控制系统,这意味着在克隆过程中会将仓库的所有历史传输到客户端。对于包涵大文件的项目,尤其是经常被修改的大文件,这意味初始化克隆需要大量时间,因为所有的文件都需要下载到客户端。Git LFS是由Atlassian, GitHub以及其他开源贡献者开发的Git的一个扩展,它通过“惰性”地下载相关的大文件的版本来有效降低大文件的影响,尤其在大文件通过checkout下载而不是克隆和获取期间。
Git LFS通过用小型指针(pointer)文件,来取代仓库中的大文件。在正常使用中,你将不会看见这些指针文件,因为它们是通过Git LFS来自动处理的:
当你向仓库添加一个文件,Git LFS使用一个指针来代替它的内容,并且把文件内容存储在Git LFS的缓存中。
当你推送提交到服务器时,被推送的新提交相关的Git LFS文件就会从Git LFS缓存中推送到远程的绑定你的仓库的Git LFS储备库。
当你切换一个包含Git LFS 指针的提交时,他们会被本地的Git LFS缓存文件取代,或者从远程Git LFS储备库下载。
Git LFS对使用者是无感知的:在工作区,你总是能看到真实文件的内容,这意味着你不需要更改存在的Git 工作流就可以使用Git LFS。你可以像之前一样简单的切换、编辑、添加和提交。克隆和拉取将会非常的迅速,因为你仅仅下载你检出的提交相关的大文件,而不是该文件的所有已经存在的版本。
为了使用Git LFS,你将需要一个支持Git LFS的服务器,比如Bitbucket Cloud 或者 Bitbucket Server。仓库使用者将需要安装Git LFS command-line client installed,或者支持Git LFS的图形化客户端,比如SourceTree。有趣的事实:Atlassian 公司的开发者Steve Streeting开发了SourceTree,他也是Git LFS项目的主要贡献者,因此SourceTree对Git LFS支持非常的棒。
安装 Git LFS
安装Git LFS的方式有三种:
a.使用你最喜欢的安装包管理器,使用Homebrew,MacPorts,dnf,packagecloud都可以安装git-lfs安装包。
b.从项目的的官网上下载和安装Git LFS。
c.安装SourceTree,它是免费的Git图形化客户端,自带了Git LFS。
一旦git-lfs已经在你你的电脑上,你可以执行git lfs install初始化Git LFS(你如果你安装了SourceTree可以跳过这步)。
$ git lfs install
Git LFS initialized.
你只需要执行一次git lfs install命令,一旦你初始化了你的仓库,Git LFS将在你执行克隆包含Git LFS的内容时仓库自动引导启用。
创建一个新的 Git LFS仓库
为了创建一个Git LFS支持的仓库,你需要在创建仓库之后执行git lfs install命令:
# initialize Git
$ mkdir Atlasteroids
$ cd Atlasteroids
$ git init
Initialized empty Git repository in /Users/tpettersen/Atlasteroids/.git/
# initialize Git LFS
$ git lfs install
Updated pre-push hook.
Git LFS initialized.
这个命令会你安装推送前hook(pre-push Git hook),它们将在你执行git push命令时传输Git LFS文件。
Git LFS对于所有的Bitbucket Cloud 仓库自动可用,对Bitbucket Server,你需要在你的仓库设置中启用Git LFS:
一旦Git LFS在你的仓库中被初始化后,你可以指定哪些文件使用Git LFS track 追踪。
克隆一个已经包含Git LFS的仓库
在Git LFS安装后,你可以像往常一样使用git clone命令克隆Git LFS仓库。在克隆过程的最后,Git会自动切换到默认的分支(通常是master),完成这次切换过程需要的所有Git LFS文件都会被下载下来,比如:
$ git clone git@bitbucket.org:tpettersen/Atlasteroids.gitCloning into 'Atlasteroids'...
remote: Counting objects: 156, done.
remote: Compressing objects: 100% (154/154), done.
remote: Total 156 (delta 87), reused 0 (delta 0)Receiving objects: 100% (156/156), 54.04 KiB | 31.00 KiB/s, done.
Resolving deltas: 100% (87/87), done.
Checking connectivity... done.
Downloading Assets/Sprites/projectiles-spritesheet.png (21.14 KB)
Downloading Assets/Sprites/productlogos_cmyk-spritesheet.png (301.96 KB)Downloading Assets/Sprites/shuttle2.png (1.62 KB)
Downloading Assets/Sprites/space1.png (1.11 MB)Checking out files: 100% (81/81), done.
仓库里的有四个PNG文件被Git LFS追踪,当执行git clone时,Git LFS文件一个一个被下载下来,因为指向他们的指针文件已经被切换到你的仓库了。
加快克隆速度
如果你正在克隆包含大量LFS文件的仓库,明确表明你在使用Git LFS命令可以提高响应能力:
$ git lfs clone git@bitbucket.org:tpettersen/Atlasteroids.gitCloning into 'Atlasteroids'...
remote: Counting objects: 156, done.
remote: Compressing objects: 100% (154/154), done.
remote: Total 156 (delta 87), reused 0 (delta 0)
Receiving objects: 100% (156/156), 54.04 KiB | 0 bytes/s, done.
Resolving deltas: 100% (87/87), done.
Checking connectivity... done.**
Git LFS**: (4 of 4 files) 1.14 MB / 1.15 MB
相比Git LFS逐个下载,git lfs clone等待切换完成,然后把必需的Git LFS文件打包下载,这利用并行下载的优势,明显减少了HTTP请求的数量和进程的陡增(这尤其在Windows中作用明显)。
拉取以及切换(Pulling and checking out)
像克隆一样,你可以正常使用git pull拉取Git LFS 仓库,在pull完成后,自动执行切换过程时,任何需要的Git LFS文件会被下载。
$ git pull
Updating 4784e9d..7039f0a
Downloading Assets/Sprites/powerup.png (21.14 KB)
Fast-forwardAssets/Sprites/powerup.png | 3 +
Assets/Sprites/powerup.png.meta | 4133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++2 files changed, 4136 insertions(+)
create mode 100644 Assets/Sprites/projectiles-spritesheet.png
create mode 100644 Assets/Sprites/projectiles-spritesheet.png.meta
不需要明确的命令来检索Git LFS内容,然后如果切换因为未知的原因失败,你可以使用git lfs pull为当前的提交下载所有丢失的Git LFS内容:
$ git lfs pull
Git LFS: (4 of 4 files) 1.14 MB / 1.15 MB
加快拉取速度
像git lfs clone类似,git lfs pull可以将Git LFS 文件打包下载,如果你明确知道自从上次拉取,已经有大量文件被修改了,你可能希望在切换过程中关闭自动Git LFS自动下载,然后使用git lfs pull命令将Git LFS内容打包下载。在你调用git pull时可以通过重写带-c参数的Git config来实现:
$ git -c filter.lfs.smudge= -c filter.lfs.required=false pull && git lfs pull
因为这条命令太长,你可能希望使用Git别名来快速运用 Git LFS pull命令:
$ git config --global alias.plfs "\!git -c filter.lfs.smudge= -c filter.lfs.required=false pull && git lfs pull"
$ git plfs
当需要下载大量Git LFS 文件时,这会大大提高响应能力(再重申一遍,尤其在Window上)。
使用Git LFS跟踪文件
当你在仓库中添加一个大文件时,你需要告诉Git LFS追踪它,可以通过git lfs track命令实现:
$ git lfs track "*.ogg"
Tracking *.ogg
注意”*.ogg”的双引号非常重要,如果遗漏了会导致你shell的未知问题发生,在你当前仓库将会为每个.ogg文件创建单独的入口:
# probably not what you want
$ git lfs track *.ogg
Tracking explode.ogg
Tracking music.ogg
Tracking phaser.ogg
Git LFS支持的模型和.gitignore 一模一样,比如:
# track all .ogg files in any directory
$ git lfs track "*.ogg"
# track files named music.ogg in any directory
$ git lfs track "music.ogg"
# track all files in the Assets directory and all subdirectories
$ git lfs track "Assets/"
# track all files in the Assets directory but *not* subdirectories
$ git lfs track "Assets/*"
# track all ogg files in Assets/Audio
$ git lfs track "Assets/Audio/*.ogg"
# track all ogg files in any directory named Music
$ git lfs track "**/Music/*.ogg"
# track png files containing "xxhdpi" in their name, in any directory
$ git lfs track "*xxhdpi*.png
These patterns are relative to the directory in which you ran the git lfs track command. To keep things simple, it is best to run git lfs track from the root of your repository. Note that Git LFS does not support negative patterns like .gitignore does.
After running git lfs track, you’ll notice a new file named .gitattributes in the directory you ran the command from. .gitattributes is a Git mechanism for binding special behaviors to certain file patterns. Git LFS automatically creates or updates .gitattributes files to bind tracked file patterns to the Git LFS filter. However, you will need to commit any changes to the .gitattributes file to your repository yourself:
$ git lfs track "*.ogg"
Tracking *.ogg
$ git add .gitattributes
$ git diff --cached
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..b6dd0bb
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.ogg filter=lfs diff=lfs merge=lfs -text
$ git commit -m "Track ogg files with Git LFS"
For ease of maintenance, it is simplest to keep all Git LFS patterns in a single .gitattributes file by always running git lfs track from the root of your repository. However, you can display a list of all patterns that are currently tracked by Git LFS (and the .gitattributes files they are defined in) by invoking git lfs track with no arguments:
$ git lfs track
Listing tracked paths
*.stl (.gitattributes)
*.png (Assets/Sprites/.gitattributes)
*.ogg (Assets/Audio/.gitattributes)
You can stop tracking a particular pattern with Git LFS by simply removing the appropriate line from your .gitattributes file, or by running the git lfs untrack command:
$ git lfs untrack "*.ogg"
Untracking *.ogg
$ git diff
diff --git a/.gitattributes b/.gitattributes
index b6dd0bb..e69de29 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +0,0 @@
-*.ogg filter=lfs diff=lfs merge=lfs -text
After running git lfs untrack you will again have to commit the changes to .gitattributes yourself.
提交和推送(Committing and pushing)
你可以像通常一样提交和推送到一个包含Git LFS内容的的仓库,如果你提交被Git LFS追踪管理的文件,你将会看到通过git push推送的内容传输的服务器端时候输出一些附加的信息:
$ git push
Git LFS: (3 of 3 files) 4.68 MB / 4.68 MB
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.16 KiB | 0 bytes/s, done.
Total 8 (delta 1), reused 0 (delta 0)
To git@bitbucket.org:tpettersen/atlasteroids.git
7039f0a..b3684d3 master -> master
如果因为某些原因推送失败,推送会取消并且你可以完全放心地再次尝试。像Git,Git LFS存储的内容是可以寻址的:内容是通过本身的SHA-256格式的哈希值作为key来存储的,这意味重新传输Git LFS文件是安全可行的。你不会用错误的版本的文件内容重写Git LFS文件。
在不同主机间转移Git LFS仓库
为了把一个Git LFS仓库迁移到另外一台服务器,你可以结合使用带有–all参数的git lfs fetch和git lfs push命令来实现。
比如,为了将一个叫做github的远程仓库移动到bitbucket远程仓库,你可以这样做😉:
# create a bare clone of the GitHub repository
$ git clone --bare git@github.com:kannonboy/atlasteroids.git
$ cd atlasteroids
# set up named remotes for Bitbucket and GitHub
$ git remote add bitbucket git@bitbucket.org:tpettersen/atlasteroids.git
$ git remote add github git@github.com:kannonboy/atlasteroids.git
# fetch all Git LFS content from GitHub
$ git lfs fetch --all github
# push all Git and Git LFS content to Bitbucket
$ git push --mirror bitbucket
$ git lfs push --all bitbucket
获取附加的Git LFS历史
Git LFS一般仅仅下载本地切换需要的提交的文件。但是,你可以使用git lfs fetch –recent强制Git LFS下载附加的内容,因为其他人最近修改了分支。
$ git lfs fetch --recent
Fetching master
Git LFS: (0 of 0 files, 14 skipped) 0 B / 0 B, 2.83 MB skipped Fetching recent branches within 7 days
Fetching origin/power-ups
Git LFS: (8 of 8 files, 4 skipped) 408.42 KB / 408.42 KB, 2.81 MB skipped
Fetching origin/more-music
Git LFS: (1 of 1 files, 14 skipped) 1.68 MB / 1.68 MB, 2.83 MB skipped
当你要出去吃饭的时候批处理要下载的文件这招很有用,或者你准备评审你同事的代码,但因为你不能在随后网络差的情况下下载,比如,你可能希望执行git lfs fetch –recent在登机之前下载最新的内容!
Git LFS认为任何分支和标签如果包含一周只能得提交就是最近。你可以通过设置lfs.fetchrecentrefsdays来配置这个天数数字。
# download Git LFS content for branches or tags updated in the last 10 days
$ git config lfs.fetchrecentrefsdays 10
默认,git lfs fetch –recent将会仅仅下载一个最近分支或者标签的Git LFS内容。
然而你可以配置Git LFS下载早于最近分支和标签的提交,这可以通过配置lfs.fetchrecentcommitsdays属性来实现:
# download the latest 3 days of Git LFS content for each recent branch or tag
$ git config lfs.fetchrecentcommitsdays 3
小心使用这个配置:如果你拥有快速变化的分支,这可能导致有大量的数据需要被下载。然而如果你需要评审分支上插入的修改,比如从其他分支挑选(cherry pick)的提交,或者重写了提交历史。
像我们在在不同主机间转移Git LFS仓库这部分讲的那样,你也可以使用git lfs fetch –all命令来选择获取所有的Git LFS内容:
$ git lfs fetch --all
Scanning for all objects ever referenced...
✔ 23 objects found
Fetching objects...
Git LFS: (9 of 9 files, 14 skipped) 2.06 MB / 2.08 MB, 2.83 MB skipped
删除 Git LFS 文件
你可以使用git lfs prune命令删除本地的Git LFS缓存中的文件。
$ git lfs prune
✔ 4 local objects, 33 retained
Pruning 4 files, (2.1 MB)
✔ Deleted 4 files
这会删除认为过旧的本地 Git LFS文件,没有被引用的文件被认为是过旧的文件:
- 当前切换的提交
一个还没有被推送的提交(到远程,或者任何在lfs.pruneremotetocheck设置的)
- 一个最近的提交
默认,一个最近的提交是过去十天的任何一个提交,这是通过添加如下内容计算的:
在获取附加的Git LFS历史部分讨论过的lfs.fetchrecentrefsdays属性的值。
lfs.pruneoffsetdays属性的值(默认为3)。
你可以为配置一个持用Git LFS内容更长的时间:
# don't prune commits younger than four weeks (7 + 21)
$ git config lfs.pruneoffsetdays 21
不像Git内置的垃圾回收, Git LFS内容不会自动删除,因此定期执行git lfs prune来保留你本地的仓库文件大小是很正确的做法。
你可以测试在git lfs prune –dry-run命令执行后有什么效果:
$ git lfs prune --dry-run
✔ 4 local objects, 33 retained
4 files would be pruned (2.1 MB)
更精确地查看哪个Git LFS对象被删除可以使用git lfs prune –verbose –dry-run命令:
$ git lfs prune --dry-run --verbose
✔ 4 local objects, 33 retained
4 files would be pruned (2.1 MB)
* 4a3a36141cdcbe2a17f7bcf1a161d3394cf435ac386d1bff70bd4dad6cd96c48 (2.0 MB)
* 67ad640e562b99219111ed8941cb56a275ef8d43e67a3dac0027b4acd5de4a3e (6.3 KB)
* 6f506528dbf04a97e84d90cc45840f4a8100389f570b67ac206ba802c5cb798f (1.7 MB)
* a1d7f7cdd6dba7307b2bac2bcfa0973244688361a48d2cebe3f3bc30babcf1ab (615.7 KB)
通过使用–verbose模式输出的十六进制的字符串是被删除的Git LFS对象的SHA-256哈希值(也被称作对象ID,或者OIDs)。你可以使用在找到引用某个Git LFS对象的路径或者提交章节介绍的技巧去找到其他想要删除的对象。
作为一个额外安全检查工作,你可以使用–verify-remote选项来检查Git LFS store是否存在想要删除的Git LFS对象的拷贝。
$ git lfs prune --verify-remote
✔ 16 local objects, 2 retained, 12 verified with remote
Pruning 14 files, (1.7 MB)
✔ Deleted 14 files
这让删除过程非常的非常的缓慢,但是这可以帮助你明白所有删除的对象都是可以从服务器端恢复的。你可以为你的系统用久开启–verify-remote 选项,这可以通过全局配置lfs.pruneverifyremotealways属性来实现。
$ git config --global lfs.pruneverifyremotealways true
或者你可以通过去掉–global选项来仅仅为当前会话的仓库开启远程验证。
删除远程服务器的Git LFS文件
Git LFS 命令行客户端工具不支持删除服务器文件,那如何实现删除他们就得依靠你的服务器提供方了。
在Bitbucket Cloud上,你可以通过仓库设置>Git LFS查看和删除Git LFS文件。
记住每个Git LFS文件通过SHA-256 OID索引,通过界面是看到不到文件路径的。这是因为在不同的提交中对一个给定的对象会有不同的路径,因此找到他们会很慢。
确定一个给定的Git LFS文件的具体内容,你有三个选择:
通过查看Bitbucket Git LFS界面的左侧的文件缩略图图标和文件类型
使用右侧链接中查找到文件的SHA-256 OID,然后使用下面章节讨论的方法下载文件。
找到引用某个Git LFS对象的路径或者提交
If you have a Git LFS SHA-256 OID, you can determine which commits reference it with git log –all -p -S
如果你有一个Git LFS的SHA-256 OID,你可以确定引用它的提交,使用git log –all -p -S
$ git log --all -p -S 3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc
commit 22a98faa153d08804a63a74a729d8846e6525cb0
Author: Tim Pettersen <tpettersen@atlassian.com>
Date: Wed Jul 27 11:03:27 2016 +1000
Projectiles and exploding asteroids
diff --git a/Assets/Sprites/projectiles-spritesheet.png
new file mode 100755
index 0000000..49d7baf
--- /dev/null
+++ b/Assets/Sprites/projectiles-spritesheet.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc
+size 21647
git log魔法会为所有的分支(-all)的所有提交生成一个补丁(-p),添加和删除一行(-S)包含特定字符串(一个Git LFS SHA-256 OID)。
这个补丁展示了LFS对象的提交和路径,以及查看到谁添加了它,什么时候被提交的。你可以简单的切换分支,如果需要Git LFS会下载这个文件,并出现在你的工作区。
如果你在你当前的HEAD,或者特定的分支中怀疑有一个特定的Git LFS对象,你可以使用git grep来找到文件路径来索引它:
# find a particular object by OID in HEAD
$ git grep 3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc HEAD
HEAD:Assets/Sprites/projectiles-spritesheet.png:oid sha256:3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc
# find a particular object by OID on the "power-ups" branch
$ git grep e88868213a5dc8533fc9031f558f2c0dc34d6936f380ff4ed12c2685040098d4 power-ups
power-ups:Assets/Sprites/shield2.png:oid sha256:e88868213a5dc8533fc9031f558f2c0dc34d6936f380ff4ed12c2685040098d4
你可以用任意包含Git LFS对象的引用,提交,树来替代HEAD,或者power-up。
包含或者排除Git LFS文件
在某些条件中,你可能想仅仅下载某个特定提交的Git LFS内容。比如,当配置一个持续化集成进行单元测试,你可能仅仅需要你的源代码,因此为了构建代码可能想去除不必要的大文件。
你可以通过使用git lfs fetch -X (或者 –exclude)排除一个通配模式或者一个目录:
$ git lfs fetch -X "Assets/**"
可替代的,你可能想仅仅包含一个特定的模式或者子目录。比如,一个音频工程仅仅想使用git lfs fetch -I (或者 –include)获取ogg和wav格式的文件。
$ git lfs fetch -I "*.ogg,*.wav"
如果你兼顾包含和排除需求。符合包含通配模式的文件和不匹配排除通配模式的文件会被获取。比如,你可以除了gifs目录,获取Assets目录的所有内容。
$ git lfs fetch -I "Assets/**" -X "*.gif"
排除和包含支持相同的模式,比如git lfs track和.gitignore。你可以为特定的仓库,通过设置lfs.fetchinclude和lfs.fetchexclude 配置属性让这些模式永久有效。
$ git config lfs.fetchinclude "Assets/**"
$ git config lfs.fetchexclude "*.gif"
这些配置可以通过加上–global选项,让其也可以运用在你系统的所有仓库中。
给Git LFS文件加锁
不幸的是,没有好的方式来解决二进制文件的合并冲突。在版本控制系统中传统解决避免这类合并冲突的方式是给文件加锁。Git LFS目前还不支持给文件加锁。然而已经有一些用来实施具体文件加锁的具体建议而且一些实现工作已经在开始做,因此让我们期待Git LFS发布它的那一天。
在这之前,最好的避免这类冲突的办法就是在你修改二进制文件的时候,通知团队成员,尤其当他们也可能修改的时候。
Git LFS是如何工作的
如果你对清理、模糊过滤、推送前hook以及其他关于 Git LFS的有趣的科学问题,你可以查看LinuxCon 2016上的来自Atlassian的Git LFS演讲。