Git推送具有相同文件的新分支,再次上传所有文件

时间:2018-01-12 14:37:37

标签: git push

考虑以下情况。

  1. 创建新分支
  2. 提交10MB文件
  3. Git push(上传10MB文件)
  4. 创建一个新分支(孤儿)
  5. 提交相同的10mb文件(未进行任何更改,同一对象sha hash)
  6. Git push上传10MB文件AGAIN
  7. 我的期望是,已经上传的文件不会再使用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.
    

    在技术方面,我们有:

    1. 两个不共享相同父级的提交(历史完全不同)
    2. 这两个提交具有完全相同的树sha id(因此引用相同的目标文件)
    3. 同时执行两次提交会导致将同一树中的所有对象传输两次。虽然我希望Git检测到第二次提交中的树已经存在,或者该树中的文件对象已经在服务器上。
    4. 答案(我不能回答,因为有人将此标记为重复)。

      遗憾的是,解决方案并非那么简单。

      每次Git想要同步两个存储库时,它都会构建一个包文件,其中包含所有必需的对象(如文件,提交,树)。执行git push时,远程会将所有现有引用(分支)及其头部提交SHA发送到客户端。这就是问题:pack protocol并不是指每个对象使用,而是每次提交。 因此,根据协议本身,上面解释的行为是正确的。为了解决这个问题,我构建了一个简单的脚本,每个人都可以使用它来基于对象执行git push,而不是提交。

      您可以在此处找到它:https://github.com/marcj/git-objects-sync

      它的作用:

      1. 进行一次提交(只需一次,你需要在每次未同步的父提交上执行它)并构建一个属于该提交的对象SHA(文件,树,提交)列表(父提交除外)。
      2. 将此列表发送到服务器,服务器回复它尚未拥有的对象的SHA
      3. 客户端根据缺少的对象SHA构建一个包文件,并将其发送到服务器,并提供需要更新的信息到哪个提交。
      4. 服务器接收包文件,解压缩并使用给定的提交SHA更新ref。
      5. 当然这有一些缺点,但我在链接的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文件及其树对象。

5 个答案:

答案 0 :(得分:1)

Git只使用交换引用来开发其清单并打包对象。如果你推动的分支没有与遥控器共同的祖先,它将打包并重新上传从该分支可到达的所有对象。

序列就是这样的

  • 您:将当前远程参考的列表发送到远程
  • 远程:将无法从该列表中访问的所有对象发送到客户端和新参考列表
  • 您:将对象添加到本地数据库并更新远程引用
  • 您:发现您的分支上的所有提交都无法从任何远程引用访问(没有共同的祖先,分支上的所有提交都将被发送)
  • 您:从这些提交的差异中构建清单(找到10mb文件)
  • 您:打包并发送到远程

遥控器将识别其数据库中已有的对象并使用现有对象。

逻辑是,不时发送一些不必要的文件比在每次推送时走完整个历史(可能有数十或数十万个被访问和比较的对象)更有效。

答案 1 :(得分:1)

我认为您只需要停止使用--orphan来创建新的实验分支。

<强>工作流

  1. 创建您的初始项目。
  2. 将核心/公共文件添加并提交到主分支
  3. 为每个实验创建所需的所有非孤立分支。根据主分支创建它们。
  4. 就是这样。

    发生了什么事?

    您坚持认为您没有使用分支,而您只使用引用。但是,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} }。