And*_* An 1 git gitattributes git-lfs
我在这里看到代码
https://gist.github.com/Srfigie/77b5c15bc5eb61733a74d34d10b3ed87
#Image
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
Run Code Online (Sandbox Code Playgroud)
(旁注:这并不是一个适合 StackOverflow 的好主题。无论如何我都会回答它。)
\n过滤作为通用目的,在gitattributes 文档中进行了描述:
\n\n\n可以将属性
\nfilter设置为命名配置中指定的过滤器驱动程序的字符串值。过滤器驱动程序由一个
\nclean命令和一个smudge命令组成,其中任何一个都可以不指定。签出时,当smudge指定命令时,将从其标准输入向该命令提供 blob 对象,并使用其标准输出来更新工作树文件。类似地,该clean命令用于在签入时转换工作树文件的内容。[剪]
diff和属性有类似(不相同)的用语merge;出于解释目的,我们将忽略它。同时,-text告诉 Git 所有此类文件都是“非文本”文件,对于 JPG 和 GIF 文件等图像文件来说当然也是如此。
所以:
\nfilter=lfs\nRun Code Online (Sandbox Code Playgroud)\n将 Git 操作链接到“涂抹和清理过滤器”(这些过滤器在其他地方定义,在.git/config或类似 \xe2\x80\x94Git-LFS 系统中,该系统用这些不同的过滤器“包装”Git,以添加Git 本身完全是的$HOME/.gitconfig服务不知道,为您安装所有这些东西)。
你的问题似乎是:这一切到底意味着什么?这在 Git 级别是如何工作的? 实际上,您不需要了解任何这些信息就可以使用Git-LFS,但学习它仍然是一个好主意,因为如果出现问题,您可能需要执行一些手术,并且在开始切割病人之前,最好先了解心脏、肺部和其他重要部位的位置。
\n现在是时候深入研究 Git 如何存储文件了。这是每个 Git 用户都应该知道的事情,至少在表面上是这样,因为它影响了很多 Git 的使用。
\n初步估计,Git 实际上并不是关于 文件,而是关于提交。但每次提交都会存储文件。事实上,每次提交都会存储每个文件的完整快照。就好像每次提交都是一个存档:一个 tar 或 zip 或 WinRAR 或其他什么,包含每个文件。
\n人们可以通过将git commit每个文件实际存储为存档来完全天真地做到这一点。但这会极大地浪费空间,因为大多数存储库中的大多数提交大多保存先前提交文件的重复项。也就是说,在典型的工作模式下,我们检查一些提交\xe2\x80\x94,获得大约一千或一万个文件\xe2\x80\x94的完整快照,然后我们只修改其中的一些并进行新的提交:一个新的快照,包含相同的 10,000 个文件或其他文件,除了我们更改的两三个文件。如果我们实际上每次都重新保存所有文件,存储库将迅速变得非常庞大。Git 会变得臃肿、缓慢且无法使用。1
为了避免这种情况,git commit 不要每次都存储每个文件的简单存档。相反,Git 将每个文件存储为内部blob 对象,使用计算机科学家所说的内容可寻址存储。在此系统中,Git 将每个文件的数据简化为哈希 ID(正式名称为对象 ID或 OID)。然后,提交会间接存储\xe2\x80\x94\xe2\x80\x94a 路径名path/to/file.ext以及内容的哈希 ID。这依赖于哈希 ID 的唯一性,这在技术上在数学上是不可能的2 ,但在实践中是可行的。3
所有这一切背后还有更多理论,但最终结果很简单:Git 删除重复内容。每当两次提交有两个具有相同内容的文件时,存储库中 存储的内容实际上是共享的。即使两次提交对文件使用不同的名称也是如此:重要的是内容,而不是名称。在 Git 中重命名文件不会更改存储的文件内容,因此其哈希 ID 保持不变,并且仍然只有该文件的一份副本。
\n这对于减少存储库的总体大小非常有效:当我们提取包含 10,000 个文件的提交,仅更改一个文件并进行新提交时,新提交实际上会重复使用9999个文件并存储一个新的(唯一修改的) ) 文件。 通过删除重复的相同内容,大多数“正常”文件可以在提交之间共享。即使是二进制文件也能受益于此。但这只是开始。
\n这种共享非常好:它大大减少了存储库的膨胀。许多以前的版本控制系统(Git 之前)以不同的方式尝试相同的事情:这些系统中存储的每次提交,不是新文件,而是所做的更改。如果文件没有发生任何更改,则不需要存储空间。
\n这种仅存储更改的想法通常称为增量压缩或增量编码。它不仅将重复项存储为“未使用空间”(因为没有什么可更改的),而且还将接近重复项存储在非常小的空间中。
\nGit在提交级别不使用增量编码,但确实使用增量编码。Git 只是在你运行之后才将git commit其偷偷地添加进来。Git 使用一些命令来完成此操作,作为用户,您永远不必了解这些命令:git pack-objects和git repack。它们采用底层对象\xe2\x80\x94,不仅仅是保存文件数据的内部blob对象,还包括Git 用于其其余工作的树、提交和标记对象\xe2\x80\x94,并做了很多聪明的事情增量编码以进一步缩小对象。Git 将其中的大部分内容隐藏在 后面git gc,您可以自己运行\xe2\x80\x94,这git gc意味着比其他内部 Git 命令\xe2\x80\x94 对用户的敌意要小一些,但您仍然不需要这样做,因为只要有利可图,Git 就会自动运行。git gc
由 Git 和其他版本控制系统执行的 Delta 编码部分依赖于称为香农熵或有时称为文件熵的一般概念(请参阅如何计算文件的熵?和熵(信息论))。 计算机程序员用来编写计算机程序的文本文件通常具有极低的熵,因此可以通过这种方式进行压缩,而且通常非常有效。
\nGit 还对对象使用zlib 压缩;这使用相同的熵原理,但以完全不同的方式。此方法的计算强度较低,因为它适用于单个文件(在文件内查找低熵数据,而不是跨多个文件的低熵信息)。它对文本文件的效果不如增量压缩,但由于计算成本更低,Git 会在每个对象存储到 Git 的对象数据库时预先执行此操作。
\n无论如何,这些压缩技巧的关键观察是:它对于文本文件非常有效,但对于已经压缩的二进制文件(如 JPG 图像)却很糟糕。 虽然输入很复杂,但最终结果很容易理解:Git 的存储对于计算机程序源代码非常有用,但对于JPG 图像和类似的东西却很糟糕。
\n为此,我们再添加一个观察结果:这些二进制文件不仅难以压缩(因此 Git 的压缩工作很糟糕),而且它们往往很大。你的整个应用程序,无论它是什么,可能有N兆字节,或N千兆字节,或者无论它有多大,对于某个数字N来说......其中一半或更多是这些大的二进制不可压缩文件!
\n1今天许多人可能会抱怨 Git仍然相当不可用,但它绝对不臃肿或缓慢,特别是与大多数替代方案相比。用管理的话来说,它的可用性受到其相对用户不友好的影响。如果 Git 不强迫用户攀爬陡峭的学习曲线,有时可能会很好。
\n2参见鸽巢原理。
\n3 “从理论上讲,理论和实践没有区别,但在实践中却存在差异。” 有可能产生哈希冲突:请参阅新发现的 SHA-1 冲突如何影响 Git? 但实际上,这似乎还没有发生\xe2\x80\x94,甚至对于 Google 研究人员制作的文件也没有,因为 Git 的标头被推到了数据前面。
\n使用上面的内容,我们观察到,如果我们完全从Git 中取出所有“大的、不可压缩的”文件,我们将得到一个不错的小型存储库,该存储库适合 GitHub 等常见托管服务的存储限制。那么,让我们想出一个方法来做到这一点。
\n当然,我们希望将这些文件存储在某个地方。这个“某个地方”必须是“不是 Git”,无论它是什么。让我们编写一组新的独立服务器程序,独立于 Git 在 GitHub 上使用的程序,并将它们安装在一组独立的服务器上,我们将其称为“Git-LFS 服务器”。这些服务器将被优化以存储大文件,而不会尝试压缩它们和花哨等等。他们只会存储文件内容,我们并不真正关心如何存储,只要我们可以通过稍后向服务器提供的某种唯一名称来取回内容即可。
\n现在我们想以某种方式将这个额外的侧服务器连接到 Git,以便:
\ngit switch somebranch\nRun Code Online (Sandbox Code Playgroud)\n提取所有文件,包括根本不存储在 Git 中的大文件。现在我们来谈谈涉及.gitattributes文件的特殊技巧。现在是时候回到 Git 内部的工作原理了。
正如您所知,您使用git switch或git checkout来签出提交。这会获取您或任何人运行创建已保存存档时 Git 创建的存档中的所有已git commit保存文件。
为了节省大量时间和精力,当您使用git checkout或提取该存档时,Git 会记录有关从该提交存档中提取的每个文件的两件事git switch。正如我们在上面看到的,文件数据的内部格式是它的“blob 哈希”。因此,Git 将 \xe2\x80\x94 存储在 Git 称为其索引或暂存区域\xe2\x80\x94 的内容中,每个提取的文件的blob 哈希和文件名。
您可以在输出中看到这些git ls-files --stage:
filter=lfs\nRun Code Online (Sandbox Code Playgroud)\ngit ls-files --stage这是在 Git 本身的 Git 存储库克隆上运行的结果。我们可以看到文件名和哈希 ID(以及更多信息,例如部件mode 100644;暂存区域的数据我在这里跳过,因为它与这些内容并不真正相关.gitattributes)。
为了使可用数据显示在您的工作树中,Git 必须:
\nxdiff/xutils.h)转换为 blob 哈希 ID(此处fd0bba94e8b4d2442ba59d0a4327d2d53e10210a);fd0bba94e8b4d2442ba59d0a4327d2d53e10210a其扩展为普通文本;和xutils.h命名的文件夹(目录)中命名的文件中。xdiffxdiff\\xutils.h如果您使用的是 Windows,则甚至可能是后者:无论您的操作系统如何要求您拼写文件夹和文件, Git 都会存储正斜杠。即使 Git 不存储文件夹,而只存储具有嵌入(正向)斜杠的长名称的文件,Git 也会在这里自动处理所有文件夹化和其他内容。
稍后,如果您运行git commit,Git 会简单地获取此索引或暂存区域\xe2\x80\x94 中的所有内容(包括 blob 哈希 ID 和文件的路径名\xe2\x80\x94)并保存所有这些内容进入新的提交。如果您修改了该文件,则必须运行:
git add xdiff/xutils.h\nRun Code Online (Sandbox Code Playgroud)\n在你跑之前git commit。此git add步骤将新的哈希 ID 存储到索引中,并在需要时存储新对象。Git在您运行时git add执行了 zlib 和/或它选择执行的任何其他压缩,以便一切都准备好进入新的提交。
同样,这是一个很多机制,我们必须将其全部提炼为这对我们意味着什么,我们的目标是存储不在 Git 中的“大”文件,同时将小文件存储在“Git”中并能够获取他们都回来了。让我们考虑一下:
\ngit add时间编码并在签出时解码。那么:如果我们在和/运行时插入自己的技巧会发生什么?git addgit checkoutgit switch 在我们棘手的小步骤中,我们将欺骗Git:我们将让 Git 存储一个唯一的密钥,并将真实文件存储在其他地方:Git-LFS 中。
当用户运行时git add,我们将从工作树中读取实际文件,并将其保存在某个地方:也许立即保存在 LFS 服务器上,也许稍后保存,但我们将在某个地方存储大文件。然后我们对 Git 撒谎,告诉它没有大文件,有一个小文件,其中包含我们稍后获取大文件所需的关键数据。
当用户运行git switch或时git checkout,我们将读取Git 存储的小文件。我们将使用它来连接 LFS 服务器并检索大文件。然后我们会对Git 撒谎并告诉它我们将小文件写入用户的工作树,而实际上将大文件写入用户的工作树。
通过巧妙地欺骗 Git,我们让 Git 存储这个小文件,我们称之为指针文件。我们使用指针文件的名称来知道真正的内容放在哪里;我们使用指针文件的数据来检索真实内容。
\nfilter使我们能够进行战略性谎言Git每当从存储库中提取文件时都会运行过滤器。smudge污迹过滤器背后的最初概念是,它可能会执行一些操作,例如,将Windows 的\\n(仅 LF 行结尾)转换为\\r\\n(CRLF 行结尾),或者像 RCS/CVS 那样扩展关键字,等等。但我们可以用它来用真实的数据替换指针文件数据,来自 Git 本身从未见过的大文件。
每当我们将文件复制回 Git时, Git都会运行过滤器。这里最初的概念是,我们可以将 CRLF 转换为仅 LF,或者用未扩展的关键字替换扩展的 CVS 样式关键字,等等。但我们可以用它来用指针文件数据替换整个大文件,这样 Git 就根本看不到这个大文件了。cleangit add
这就是 Git-LFS 的工作原理以及该filter生产线的作用。Git 永远看不到大文件,只能看到小指针文件。
diff和线呢merge?上面filter涵盖了签出步骤(工作树获取大文件而不是指针文件)和步骤git add(存储库获取指针文件而不是大文件)。但这些并不是我们必须潜入替代文件的唯一地方。
特别是,git diff需要比较提交中出现的文件。但提交现在存储的是指针文件,而不是真实的文件。我们需要一种方法来潜入 diff 操作并对 Git 撒谎。将驱动程序添加diff到.gitattributes文件中正是这样做的。编写 diff 过滤器驱动程序有点复杂,但原理很简单。
同样,git merge需要运行多个 git diff操作。定义merge驱动程序比定义驱动程序还要困难diff,但原则又简单又明显:如果我们要合并,我们想要合并真实文件,而不是指针文件,并且我们需要对 Git 撒谎来实现这一点。
所以所有这些东西都是关于对 Git 撒谎,巧妙而小心,以便 Git 永远不会看到“大”文件,而用户\xe2\x80\x94运行的人git diff或git show, git switch, git add, git commit, 等等\xe2\x80 \x94 可以使用存储库,就好像大文件存储在每个提交中一样,小文件实际上存储在每个提交中。它们不是,而 Git 的巧妙谎言只会让它们看起来像是。 Git 不知道我们在对它撒谎,而 Git-LFS 必须正确且一致地对它撒谎才能使这一切发挥作用。 如果它破裂了,你必须了解谎言发生在哪里。