是否可以在不重写历史记录的情况下缩小.git存储库?

Mar*_*oth 20 git large-files egit git-filter-branch jgit

git由于二进制测试文件和java .jar文件的历史包含,我们有许多存储库已经增长到难以管理的大小.

我们正准备完成git filter-branch这些存储库的操作,在它们被使用的任何地方重新克隆它们(从每个数十个到数百个部署,取决于回购)并且考虑到重写历史问题我想知道是否可能存在任何其他解决方案

理想情况下,我想在不重写每个存储库的历史记录的情况下将问题文件外部化.从理论上讲,这应该是可能的,因为你检查相同的文件,具有相同的大小和相同的哈希值,只是从不同的地方(远程而不是本地对象存储)获取它们.到目前为止,我找到的任何潜在解决方案似乎都不允许我这样做.

git-annex开始,我能找到解决问题的最接近的方法是如何追溯性地附加一个已经存在于git仓库中的文件,但就像删除大文件一样,这需要重新编写历史记录以进行转换原来git add变成了git annex add.

从那里开始,我开始查看git-annex不是列出的其他项目,所以我检查了git-bigfiles,git-mediagit-fat.不幸的是我们不能使用git-bigfiles fork,git因为我们是Eclipse 商店并使用gitEGit的混合.它看起来不像git-mediagit-fat也可以做我想要的,因为虽然你可以用外部等价物替换现有的大文件,但你仍然需要重写历史记录以删除已经存在的大文件一直致力于.

那么,是否可以在不重写历史记录的情况下缩小.git存储库,或者我们是否应该回到使用计划git filter-branch和整个重新部署的负载?


顺便说一句,相信这应该是可能的,但可能与git当前浅层克隆实现的限制相同.

Git已经为同一个blob支持多个可能的位置,因为任何给定的blob都可以在松散的对象存储(.git/objects)或包文件(.git/objects)中,所以理论上你只需要git-annex在这个级别上挂钩的东西而不是更高(例如,如果你愿意,有一个按需下载远程blob的概念).不幸的是,我找不到任何人已经实施甚至建议这样的事情.

Ric*_*sen 10

有点.您可以使用Git的替换功能来预留大膨胀历史记录,以便仅在需要时下载.它就像一个浅层克隆,但没有浅层克隆的限制.

想法是你通过创建一个新的root提交来重新启动分支,然后挑选旧分支的提示.通常你会以这种方式丢失所有历史记录(这也意味着你不必克隆那些大.jar文件),但是如果需要历史记录,你可以获取历史提交并使用git replace它们无缝地重新拼接它们.

请参阅Scott Chacon的优秀博客文章,了解详细解释和演练.

这种方法的优点:

  • 历史未被修改.如果你需要回到一个较旧的提交,完成它的大.jars而且一切,你仍然可以.
  • 如果您不需要查看旧的历史记录,那么本地克隆的大小很好而且很小,而且您制作的任何新克隆都不需要下载大量无用的数据.

这种方法的缺点:

  • 默认情况下,完整的历史记录不可用 - 用户需要跳过一些环节来获取历史记录.
  • 如果您确实需要经常访问历史记录,那么无论如何您最终都会下载膨胀的提交.
  • 这种方法仍然存在一些与重写历史相同的问题.例如,如果您的新存储库如下所示:

    * modify bar (master)
    |
    * modify foo  <--replace-->  * modify foo (historical/master)
    |                            |
    * instructions               * remove all of the big .jar files
                                 |
                                 * add another jar
                                 |
                                 * modify a jar
                                 |
    
    Run Code Online (Sandbox Code Playgroud)

    并且有人在他们合并的历史分支中有一个旧分支:

    * merge feature xyz into master (master)
    |\__________________________
    |                           \
    * modify bar                 * add feature xyz
    |                            |
    * modify foo  <--replace-->  * modify foo (historical/master)
    |                            |
    * instructions               * remove all of the big .jar files
                                 |
                                 * add another jar
                                 |
                                 * modify a jar
                                 |
    
    Run Code Online (Sandbox Code Playgroud)

    然后,大的历史提交将重新出现在您的主存储库中,并且您将回到您开始的位置.请注意,这并不比重写历史记录更糟糕 - 有人可能会在预重写提交中意外合并.

    这可以通过update在共享存储库中添加一个钩子来拒绝任何将重新引入历史根提交的推送来缓解.


Chr*_*ial 8

不,这是不可能的 - 你将不得不重写历史.但是这里有一些指示:

  • 正如VonC所提到的:如果它符合您的情况,请使用BFG-repo清洁剂 - 它比使用起来容易得多git filter-branch.
  • 你不需要再次克隆!只要运行这些命令,而不是git pull和你将被罚款(更换originmaster你的远程和分支):

    git fetch origin
    git reset --hard origin/master
    
    Run Code Online (Sandbox Code Playgroud)

    但请注意,与之不同的是git pull,您将丢失尚未推送到服务器的所有本地更改.

  • 如果你(或你团队中的其他人)完全理解git如何看待历史,什么git pull,git merge以及git rebase(也作为git rebase --onto),它会有很大帮助.然后让每个人都参与一个关于如何处理这种重写情况的快速培训(5-10分钟就足够了,基本的注意事项和注意事项).
  • 请注意,这git filter-branch本身不会造成任何伤害,但会导致许多标准工作流程造成伤害.如果人们没有采取相应行动并合并旧历史,如果您没有及时发现,可能只需要重新编写历史记录.
  • 您可以通过在服务器上写入(5行)适当的更新挂钩来阻止人们合并(更准确地推送)旧历史记录.只需检查推头的历史记录是否包含特定的旧提交.

  • @MarkBooth gitolite V3中的自定义更新挂钩称为VREF(如本答案:http://stackoverflow.com/a/11517112/6309),您可以定义尽可能多的"gitolite-update hooks"(或VRefs)根据需要:http://stackoverflow.com/a/10888358/6309.Gitolite V2将使用钩链(http://stackoverflow.com/a/15941289/6309). (2认同)