8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何从 Git 存储库的提交历史记录中移除/删除大文件?

cybersokari 1月前

74 0

我无意中将 DVD-rip 放入了一个网站项目中,粗心地 git commit -a -m ...,然后,存储库就膨胀了 2.2 GB。下次我进行一些编辑时,删除了视频文件,然后

我无意中将 DVD-rip 放入了一个网站项目中, git commit -a -m ... 结果存储库膨胀了 2.2 GB。下次我进行一些编辑,删除视频文件并提交所有内容,但压缩文件仍然存在于存储库的历史记录中。

我知道我可以从这些提交开始分支,并将一个分支重新定位到另一个分支。但是我应该怎么做才能合并这两个提交,以便大文件不会显示在历史记录中并在垃圾收集过程中被清除?

帖子版权声明 1、本帖标题:如何从 Git 存储库的提交历史记录中移除/删除大文件?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由cybersokari在本站《github》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 在我第十次思考这个问题之后,正确的答案是 git 应该拒绝签入这些文件,而不是制造这一切混乱。

  • 2022 年奏效的新答案

    请勿使用:

    git filter-branch
    

    此命令在推送后可能不会更改远程存储库。如果在使用它之后进行克隆,您将看到没有任何变化,并且存储库仍然很大。这个命令似乎已经过时了。例如,如果您使用 https://github.com/18F/C2/issues/439 ,这将不起作用。

    解决方案

    该解决方案基于使用:

    git filter-repo
    

    步骤:

    (1)找出 .git 中最大的文件(将 10 改为你想要显示的文件数量):

    git rev-list --objects --all | grep -f <(git verify-pack -v  .git/objects/pack/*.idx| sort -k 3 -n | cut -f 1 -d " " | tail -10)
    

    (2)通过传递要删除的文件的路径和名称开始过滤这些大文件:

     git filter-repo --path-glob '../../src/../..' --invert-paths --force
    

    或者使用文件的扩展名,例如过滤所有 .zip 文件:

     git filter-repo --path-glob '*.zip' --invert-paths --force
    

    或者,例如过滤所有.a 库文件:

     git filter-repo --path-glob '*.a' --invert-paths --force
    

    或者您在步骤 1 中找到的任何内容。

    (3)

     git remote add origin [email protected]:.../...git
    

    (4)

    git push --all --force
    
    git push --tags --force
    

    完毕!!!

  • 第 2 项中的 \'Strat\' 是什么意思?您在该步骤中执行了什么操作?请解释 3 执行的操作,尤其是 \'.../...git\'。我已经有一个远程仓库。所有 .../ 的内容是什么?

  • 我喜欢这个解决方案。发帖人应该提到“filter-repo”不是原生 git 命令,您必须安装一个 python 脚本:github.com/newren/git-filter-repo

  • 这是来自未来的信息吗?请告诉我 20222 年的生活是什么样的。我不敢相信你还在使用 git。

  • j.ss 1月前 0 只看Ta
    引用 7

    步骤 1 可以通过 git filter-repo --analyze 完成。

  • 我得到 \'git:'filter-repo' 不是 git 命令。请参阅“git --help”。\' git 版本 2.25.1

  • 使用 BFG Repo-Cleaner ,它是的更简单、更快捷的替代方案 git-filter-branch ,专为从 Git 历史记录中删除不需要的文件而设计。

    仔细按照 使用说明操作 。核心部分就是这些:

    java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git
    

    任何超过 100 MB 的文件(不在您的 最新 提交中)都将从您的 Git 存储库历史记录中删除。然后您可以使用 git gc 清除死数据:

    git reflog expire --expire=now --all && git gc --prune=now --aggressive
    

    修剪后,我们可以强制推送到远程仓库*

    git push --force
    

    注意 :无法在 GitHub 上强制推送受保护的分支

    BFG 通常 比跑步快 10-50 git-filter-branch ,并且通常更容易使用。

    全面披露:我是 BFG Repo-Cleaner 的作者。

  • @tony 值得重复整个克隆和清除​​过程,以查看是否再次出现要求您拉取的消息,但这几乎肯定是因为您的远程服务器配置为拒绝非快进更新(即,它配置为阻止您丢失历史记录 - 这正是您想要做的)。您需要在远程服务器上更改该设置,或者如果失败,则将更新的存储库历史记录推送到全新的空白存储库。

  • 引用 11

    @RobertoTyley 太棒了,你节省了我的时间,非常感谢。顺便说一句,也许应该在你的步骤之后执行 git push --force,否则远程仓库仍然没有改变。

  • +1 添加 git push --force。还值得注意的是:远程可能不允许强制推送(gitlab.com 默认不允许。必须“取消保护”分支)。

  • BFG 对我来说绝对有效。只需几分钟即可将 517MB 的 repo 压缩到 38MB。在找到这个答案之前,其他方法都不起作用。

  • 当给出 \'is repo packed\' 错误时,未记录的问题(大部分)。在目标 repo 上使用 git gc,然后重新执行你使用 BFG 所做的任何操作。一旦解决了这个问题,效果就很好了。可以使用更明确的文档,但我不是最快的学习者 ;p

  • 概括

    首先修复本地历史记录。您有多种选择,这些选择的易用性各不相同,具体取决于您的历史记录的复杂程度 HEAD 以及意外删除的提交。

    • git reset --soft
    • git rebase --interactive
    • git commit-tree
    • git filter-repo
    • git filter-branch (尽量避免这种情况)

    如果你使用 rip 推送了历史记录,则可能需要修复共享存储库上的历史记录(删除并重新推送分支或 git push --force ),并且你的合作者将不得不 根据重写的历史记录重新调整他们的工作 .

    您可能还会发现 “从存储库中删除敏感数据” 是一个有用的资源。

    设置

    我将使用具体的示例历史来说明可能的修复方法,该示例历史模拟了一个简单的代表性序列

    1. 添加 index.html
    2. 添加 site.css oops.iso
    3. 添加 site.js 和删​​除 oops.iso

    要在您的设置中重新创建此示例中的精确 SHA-1 哈希值,请首先设置几个环境变量。如果您使用的是 bash

    export GIT_AUTHOR_DATE="Mon Oct 29 10:15:31 2018 +0900"
    export GIT_COMMITTER_DATE="${GIT_AUTHOR_DATE}"
    

    如果你在 Windows 命令 shell 中运行

    set GIT_AUTHOR_DATE=Mon Oct 29 10:15:31 2018 +0900
    set GIT_COMMITTER_DATE=%GIT_AUTHOR_DATE%
    

    然后运行下面的代码。要在实验后回到相同的起点,请删除存储库,然后重新运行代码。

    #! /usr/bin/env perl
    
    use strict;
    use warnings;
    use Fcntl;
    
    sub touch { sysopen FH, $_, O_WRONLY|O_CREAT and close FH or die "$0: touch $_: $!" for @_; 1 }
    
    my $repo = 'website-project';
    mkdir $repo or die "$0: mkdir: $!";
    chdir $repo or die "$0: chdir: $!";
    system(q/git init --initial-branch=main --quiet/) == 0       or die "git init failed";
    system(q/git config user.name 'Git User'/) == 0              or die "user.name failed";
    system(q/git config user.email '[email protected]'/) == 0 or die "user.email failed";
    # for browsing history - http://blog.kfish.org/2010/04/git-lola.html
    system "git config alias.lol  'log --graph --decorate --pretty=oneline --abbrev-commit'";
    system "git config alias.lola 'log --graph --decorate --pretty=oneline --abbrev-commit --all'";
    
    my($index,$oops,$css,$js) = qw/ index.html oops.iso site.css site.js /;
    touch $index or die "touch: $!";
    system("git add .")          == 0 or die "A: add failed\n";
    system("git commit -m A")    == 0 or die "A: commit failed\n";
    touch $oops, $css or die "touch: $!";
    system("git add .")          == 0 or die "B: add failed\n";
    system("git commit -m B")    == 0 or die "B: commit failed\n";
    unlink $oops or die "C: unlink: $!"; touch $js or die "C: touch: $!";
    system("git add .")          == 0 or die "C: add failed\n";
    system("git commit -a -m C") == 0 or die "C: commit failed\n";
    
    system("git lol --name-status --no-renames");
    

    输出显示存储库的结构是

    * 1982cb8 (HEAD -> main) C
    | D oops.iso
    | A site.js
    * 6e90708 B
    | A oops.iso
    | A site.css
    * d29f991 A
      A index.html
    

    笔记

    • --no-renames 选项 git lol 用于禁用重命名检测,这样 git 就不会看到删除一个空文件并添加另一个文件作为重命名。大多数时候你不需要它。
    • 同样,当您完成了对这个示例存储库的操作后,请记住删除 GIT_AUTHOR_DATE 环境 GIT_COMMITTER_DATE 变量或仅删除 exit 您用来跟随的 shell。
    • 考虑通过更新您的 .gitignore .

    简单案例

    如果你还没有发布你的历史记录,那么你可以修复它并完成它。有几种方法可以满足你的要求。

    git reset --soft

    要保留除翻录之外的所有内容(文件内容和提交消息),请首先返回 HEAD 到 DVD 翻录之前的提交,并假装您第一次就做对了。

    git reset --soft d29f991
    

    确切的调用将取决于您的本地历史记录。在这种特殊情况下,您可以软重置, HEAD~2 但当您的历史记录具有不同的形状时,盲目地重复这一点会产生令人困惑的结果。

    之后添加要保留的文件。软重置不会影响工作树和索引中的文件,因此这些文件 oops.iso 将消失。

    git add site.css site.js
    

    您可能能够摆脱 git add . ,特别是如果您更新了 .gitignore 。 这可能是您一开始就陷入麻烦的原因,因此以防万一,请先运行, git status 然后

    git commit -q -C ORIG_HEAD
    

    软重置在 处保留一个“书签” ORIG_HEAD ,因此 -C ORIG_HEAD 使用其提交消息。

    从这里 git lol --name-status --no-renames 运行

    * a19013d (HEAD -> main) C
    | A site.css
    | A site.js
    * d29f991 A
      A index.html
    

    git rebase --interactive

    为了完成与上述相同的操作但 git 同时引导,请使用交互式变基。

    git rebase --interactive d29f991
    

    然后你将看到一个带有

    pick 6e90708 B
    pick 1982cb8 C
    
    # Rebase d29f991..1982cb8 onto d29f991 (2 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    # e, edit <commit> = use commit, but stop for amending
    # s, squash <commit> = use commit, but meld into previous commit
    # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
    #                    commit's log message, unless -C is used, in which case
    #                    keep only this commit's message; -c is same as -C but
    #                    opens the editor
    # x, exec <command> = run command (the rest of the line) using shell
    # b, break = stop here (continue rebase later with 'git rebase --continue')
    # d, drop <commit> = remove commit
    # l, label <label> = label current HEAD with a name
    # t, reset <label> = reset HEAD to a label
    # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
    # .       create a merge commit using the original merge commit's
    # .       message (or the oneline, if no original merge commit was
    # .       specified); use -c <commit> to reword the commit message
    

    改为 pick squash 行了 C 。记住:使用交互式变基时,你总是“向上挤压”,而不是向下挤压。

    正如下面的有用注释所示,如果简单的话,你可以将行的命令更改 B reword 并在那里编辑提交消息。否则,保存并退出编辑器以获取另一个编辑器,用于压缩 B C .

    git commit-tree

    您可能想使用 来执行此 git rebase --onto 操作,但这并不等同于压缩。特别是,如果您意外添加 rip 的提交还包含您想要保留的其他工作,则 rebase 将仅重播其后的提交,因此 site.css 不会随之而来。

    在聚会上用 git 管道表演压扁戏法给你的朋友留下深刻印象。

    git reset --soft d29f991
    git merge --ff-only \
      $(git commit-tree 1982cb8^{tree} -p d29f991 \
          -F <(git log --format=%s -n 1 1982cb8))
    

    此后的历史就和其他人完全相同了。

    * a19013d (HEAD -> main) C
    | A site.css
    | A site.js
    * d29f991 A
      A index.html
    

    用英语来说,上面的命令会创建一个新的提交,其树与删除 rip 后得到的树相同( 1982cb8^{tree} 在本例中),但其父级是 d29f991 ,然后将当前分支快进到该新提交。

    请注意,在实际使用中,您可能希望 %B 整个提交消息正文(而不仅仅是 %s 其主题)采用漂亮的格式。

    git filter-repo

    以下命令将删除 oop.iso 历史记录中出现的所有内容。

    创建存储库的全新克隆并将 cd 其放入其根目录中。插图存储库看起来不像是全新克隆,因此我们必须 --force 在下面的命令中添加选项。

    git filter-repo --invert-paths --path oops.iso
    

    由此产生的历史是

    * f6c1006 (HEAD -> main) C
    | A site.js
    * f2498a6 B
    | A site.css
    * d29f991 A
      A index.html
    

    困难案例

    如果您确实运行了 git push ,那么您可以执行上述操作之一,但您需要重写历史记录。

    您需要选择 git push 覆盖 --force 远程分支或删除分支并再次推送。这两个选项都可能需要远程存储库所有者或管理员的帮助。

    不幸的是,这会严重破坏您的合作者。请参阅 “Recovering From Upstream Rebase” in the git rebase documentation 了解修复历史记录后其他人必须执行的必要步骤。

    git filter-branch (不要使用这个!)

    由于历史原因,这个旧命令被保留了下来,但它很慢,而且很难正确使用。只有在万不得已的情况下才使用这条路线。

    我在从 Subversion 导入大量二进制测试数据时遇到了类似的问题,并写了一篇关于 从 git 存储库中删除数据的文章 .

    执行以下命令

    git filter-branch --prune-empty -d /dev/shm/scratch \
      --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
      --tag-name-filter cat -- --all
    

    将产生输出

    WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.
    Proceeding with filter-branch...
    
    Rewrite 6e907087c76e33fdabe329da7e0faebde165f2c2 (2/3) (0 seconds passed, remaining 0 predicted)    rm 'oops.iso'
    Rewrite 1982cb83f26aa3a66f8d9aa61d2ad08a61d3afd8 (3/3) (0 seconds passed, remaining 0 predicted)    
    Ref 'refs/heads/main' was rewritten
    

    各个选项的含义为:

    • --prune-empty 由于过滤操作而 变为空的提交(
    • -d 命名一个尚不存在的临时目录,用于构建过滤历史记录。如果您在现代 Linux 发行版上运行,则 tree in /dev/shm will result in faster execution .
    • --index-filter 是主要事件,并在历史记录中的每个步骤中针对索引运行。您想删除 oops.iso 找到它的任何位置,但它并不存在于所有提交中。 git rm --cached -f --ignore-unmatch oops.iso 当 DVD-rip 存在时,该命令会删除它,否则不会失败。
    • --tag-name-filter 描述如何重写标签名称。 的过滤器 cat 是身份操作。 您的存储库(如上面的示例)可能没有任何标签,但我包含了此选项以实现完全通用性。
    • -- 指定选项的结束 git filter-branch
    • --all 以下 -- 是所有引用的简写。您的存储库(如上面的示例)可能只有一个引用(主引用),但我包含了此选项以实现完全的通用性。

    经过一番搅动,历史现在是这样的:

    * f6c1006 (HEAD -> main) C
    | A site.js
    * f2498a6 B
    | A site.css
    | * 1982cb8 (refs/original/refs/heads/main) C
    | | D   oops.iso
    | | A   site.js
    | * 6e90708 B
    |/  
    |   A   oops.iso
    |   A   site.css
    * d29f991 A
      A index.html
    

    请注意,新 B 提交仅添加 site.css ,而新 C 提交仅添加 site.js 。标记为 的分支 refs/original/refs/heads/main 包含您的原始提交,以防您犯错。要删除它,请按照 “缩减存储库的检查表”中的步骤操作。

    $ git update-ref -d refs/original/refs/heads/main
    $ git reflog expire --expire=now --all
    $ git gc --prune=now
    

    还有一个更简单的替代方法,就是克隆存储库以丢弃不需要的部分。

    $ cd ~/src
    $ mv repo repo.old
    $ git clone file:///home/user/src/repo.old repo
    

    使用 file:///... 克隆 URL 复制对象而不是仅创建硬链接。

    现在你的历史记录是:

    * f6c1006 (HEAD -> main) C
    | A site.js
    * f2498a6 B
    | A site.css
    * d29f991 A
      A index.html
    
  • 为什么使用 git filter-branch 时无法推送,无法将某些引用推送到 '[email protected]:product/myproject.git' 为防止您丢失历史记录,非快进更新被拒绝,请在再次推送之前合并远程更改。

  • RedX 1月前 0 只看Ta
    引用 17

    将 -f(或 --force)选项添加到您的 git push 命令中:“通常,该命令拒绝更新不是用于覆盖它的本地引用的祖先的远程引用。此标志禁用检查。这可能会导致远程存储库丢失提交;请谨慎使用它。”

  • 引用 18

    这是一个非常详尽的答案,解释了如何使用 git-filter-branch 从历史记录中删除不需要的大文件,但值得注意的是,自从 Greg 写下他的答案以来,BFG Repo-Cleaner 已经发布,它通常更快、更容易使用——详情请参阅我的答案。

  • 在我执行上述任一步骤后,远程存储库(在 GitHub 上)都不会删除大文件。只有本地存储库会删除。我强制推送,但没有任何效果。我遗漏了什么?

  • 这也适用于目录。... \'git rm --cached -rf --ignore-unmatch path/to/dir\'...

返回
作者最近主题: