考虑以下情况。
我的期望是,已经上传的文件不会再使用git push
上传。但实际发生的是,当一个新的分支被创建时(即使数千个较小的源文件,而不是一个10MB文件)将被一次又一次地上传。
我的问题:如何让Git检测到10mb文件已经上传?你知道一个解决方法/修复方法,让Git在推送提交时检测服务器上已有的对象吗? Git按其sha检测文件,因此它应该能够检测到提交树中的某些文件已存在于服务器上。
可能的用例:我有两个完全不同的分支,但在这两个分支中共享一些公共文件。当我推动一个分支时,当我推送第二个分支时,我不想再次上传公共文件。
实际使用案例:我使用Python脚本和一些较小的数据集(1MB - 10MB)进行了大量的机器学习实验。每次我开始实验时,我都会将所有必要的实验文件添加到新的Git树中,并在新的提交中使用该树而不进行分支。该提交在空中完全免费挂起,然后使用新的Git引用(例如refs / jobs / my-experiment-name)引用。当我现在有几个相同文件的两个实验(以及两个引用)时,当我推送这些引用时,Git再次推送所有对象。我的带宽很低,这确实减慢了我的工作量。
$ mkdir git-test && cd git-test
$ git init
$ git remote add origin git@gitlab.com:username/projectname.git
# create dummy 10MB file
$ head -c 10000000 /dev/urandom > dummy
$ git add dummy
$ git commit -m 'init'
# first push, uploads everything - makes sense
$ git push origin master
Counting objects: 3, done.
Delta compression using up to 6 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 9.54 MiB | 1.13 MiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
# create new empty branch, not based from master
$ git checkout --orphan branch2
# add same files again
$ git add dummy
$ git commit -m 'init on branch2'
# this uploads now again the dummy file (10MB), although the server
# has that object alread
$ git push origin branch3
Counting objects: 3, done.
Delta compression using up to 6 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 9.54 MiB | 838.00 KiB/s, done.
在技术方面,我们有:
遗憾的是,解决方案并非那么简单。
每次Git想要同步两个存储库时,它都会构建一个包文件,其中包含所有必需的对象(如文件,提交,树)。执行git push
时,远程会将所有现有引用(分支)及其头部提交SHA发送到客户端。这就是问题:pack protocol并不是指每个对象使用,而是每次提交。
因此,根据协议本身,上面解释的行为是正确的。为了解决这个问题,我构建了一个简单的脚本,每个人都可以使用它来基于对象执行git push
,而不是提交。
您可以在此处找到它:https://github.com/marcj/git-objects-sync
它的作用:
当然这有一些缺点,但我在链接的Github存储库中描述了它们。
使用上面的脚本,您现在可以看到:
marc@osx ~/git-test (branch11*) $ # added new branch11 as explained at the very top
marc@osx ~/git-test (branch11*) $ python git-sync.py refs/heads/branch11
Counting objects: 1, done.
Writing objects: 100% (1/1), 158 bytes | 158.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
marc@osx ~/git-test (branch11*) $ git push origin branch11
Everything up-to-date
如您所见,它只同步一个对象(提交对象),而不是同步dummy
文件及其树对象。
答案 0 :(得分:1)
Git只使用交换引用来开发其清单并打包对象。如果你推动的分支没有与遥控器共同的祖先,它将打包并重新上传从该分支可到达的所有对象。
序列就是这样的
遥控器将识别其数据库中已有的对象并使用现有对象。
逻辑是,不时发送一些不必要的文件比在每次推送时走完整个历史(可能有数十或数十万个被访问和比较的对象)更有效。
答案 1 :(得分:1)
我认为您只需要停止使用--orphan
来创建新的实验分支。
<强>工作流强>
就是这样。
发生了什么事?
您坚持认为您没有使用分支,而您只使用引用。但是,branches are a kind of reference。此外,git checkout --orphan <newthing>
does actually create a branch。麻烦的是,它的分支不知道以前添加到存储库的任何内容,因为它没有父项。它与创建一个全新的存储库基本相同。
如果您使用git checkout -b <newthing> master
创建新分支,那么git将不会打扰已经在主服务器中的已上传文件。
现在如何管理新的常用文件?
让我们说有一天你有一个新文件,你希望所有未来的实验都可以使用 - 一个新的共享/公共文件。您需要做的就是将该文件添加到master
并根据更新的主分支创建下一个实验分支。如果希望该文件可用于现有/先前创建的实验,则只需签出这些分支并运行git pull --rebase origin master
即可。这将引入您添加到master的提交,其中包含新添加的文件。
安装复杂性
当你开始做拉动时,事情可能会变得复杂起来。如何更新分支有几种不同的策略,使用--rebase
是其中一种策略。这不是必需的,但它可能是更好的方法。还有其他需要考虑的事项,例如如何管理冲突的变更,但这些似乎超出了这个问题的范围。有足够的资源来解释变基/合并等。
<强> TR; DR 强>
不要尝试手动管理提交树和父/子关系。让git
做点什么。
答案 2 :(得分:0)
实际使用案例:我使用Python脚本和一些较小的数据集(1MB - 10MB)进行了大量的机器学习实验。每次开始实验时,我都会将所有必要的实验文件添加到新的Git树
这似乎不是一个与git相关的问题,而是更多的内容管理问题:如果你有一套通用的静态文件,你想从Git repo分支重用到另一个分支,那组文件(永远不会改变)不一定要在Git回购中。
您可以考虑将其上传到服务器的静态路径中,并确保您的ML脚本(在Git仓库中版本化)通过从所述静态路径获取和复制该初始数据集来开始其学习过程。 或者他们可以使用他们自己的Git仓库,并且您的脚本将通过克隆/更新专用于这些数据的子文件夹来开始克隆/拉动该外部仓库。
这样,你完全绕过了版本控制问题,只有版本(在专门用于实验的多分支仓库中)从提交到提交实际可以改变什么:你的ML脚本,可能还有一些特定的(和更小的) )数据集,为新实验量身定制。
答案 3 :(得分:0)
在您的方案中使用Git LFS可能更好。 Git LFS用于管理git存储库上的大型文件。
根据this github issue(具有been resolved〜2年前),重复文件作为具有相同OID的单个实体进行管理。因此,他们只推了一次,只拿了一次。
至少Github和Gitlab已在所有存储库中包含对git-lfs的支持。
答案 4 :(得分:0)
正如其他人所说; Git只检查你正在推动的分支上的blob,但你可以通过将它包含在你的祖先中来欺骗Git来检查master分支中的blob。
您似乎真的想要在孤立分支上工作,因此只有在想要推送时才能合并主分支。您可以使用ours
策略忽略主分支的全部内容。
% git checkout --orphan branch2
% git rm -rf .
% git checkout master dummy
% git commit -m 'Init on branch2'
% git merge --strategy=ours --allow-unrelated-histories master -m 'Fake merge'
% git push origin branch2
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 336 bytes | 336.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To github.com:felipec/bounty-test.git
* [new branch] branch2 -> branch2
内容完全相同:
% git rev-parse branch2:
d0f549d94dbba116d782293722cf9b43e8a67819
% git rev-parse branch2^:
d0f549d94dbba116d782293722cf9b43e8a67819
如果您不想弄乱原始分支,可以创建一个新的分支,仅用于推送。
此外,你可以从master开始,然后扔掉所有文件:
% git checkout -b branch3 master
% git rm -rf .
% git checkout master dummy
% git commit -m 'Init on branch3'
% git push origin branch3
Counting objects: 2, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1/1), done.
Writing objects: 100% (2/2), 236 bytes | 236.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To github.com:felipec/bounty-test.git
* [new branch] branch3 -> branch3
如果你真的希望在Git中正确处理这个相当具体的用例,你可能想联系mailing list上的开发人员。他们可能会为您提供其他选择,但他们可能会同意在代码中可以改进的内容,而不会对其他情况进行重大折衷。
注意:我不知道为什么你必须在你的步骤中添加虚拟文件,在我这边做主分支的全部内容都是在{{1} }。