编辑:现在我们有了git log --graph --decorate --oneline --all 输出,可以更轻松地查看正在发生的事情并提出建议。
有一个基于 Java 的工具,基本上可以为您完成以下所有工作。我自己从未使用过它,但它在 * 上有自己的标签。
稍微难一点的方法:git filter-branch
由于您知道文件的名称,您可以使用git filter-branch 以自动方式执行以下所有步骤。只需将索引过滤器与git rm --cached --ignore-unmatch path/to/X.sql 一起使用,并过滤所有master、test 和v0.8 分支。但一般来说,您应该只在知道自己在做什么之后才使用filter-branch,并且可以按照以下所有步骤操作。
困难的方法:很像 filter-branch 会做的事情,但是手动
这已经很长了,在这里引用会使它更长,但我认为这样更清楚,所以我们开始吧。 :-)
实际的图表如下所示:
* 408ef30 (master) h
|\
| * 7d4ecd3 (origin/master, origin/HEAD) new every
| * c63f869 every bug
| * a60a14a querydate bug fixed
| * 957a6d3 problem in every
| * 602891c problem in every
| * 9e827d2 problem in every
| | * 0463323 (HEAD -> v0.8, test) branch pushed to remote
| |/
|/|
* | 4b6d7c8 Merge branch 'master' of https://github.com/X/X
|\ \
| |/
| * 539e3dc pagedown removed, bibtex bug resolved
* | 631d55a once
* | 4aa7275 once
|/
从这里我们可以看出v0.8是一个分支(不是标签:知道有用但在推送方面没有区别,只是就我们在时所做的事情而言修复东西)。该特定分支指向提交0463323。还有一个额外的分支名称test,它也指向同一个提交。
0463323 的父级是 4b6d7c8 Merge branch 'master' of ...。因为4b6d7c8 是一个合并,它有两个父母。这两个父提交是539e3dc pagedown removed, bibtex bug resolved 和631d55a once。
提交 539e3dc 在 origin/master 上,因此已经在 GitHub 上。它不可能有有问题的大文件,也不可能有它的任何父提交(也在 GitHub 上)。但是,提交 631d55a 不在 GitHub 上,其父提交 4aa7275 也不在。更下方的一行丢失了,但我们可以从 |/ 行看到,提交 4aa7275 和提交 539e3dc 必须让他们的历史记录加入到那里的任何提交中。
我们仍然不能确定大文件是从哪里溜进来的,也不能确定它后来被删除的地方,但我们只从四种可能性开始:
-
0463323 branch pushed to remote(尽管它的名字,它实际上并没有被推送;它的推送失败)
4b6d7c8 Merge branch 'master' of ...
631d55a once
4aa7275 once
(这四个也在git log v0.8 --not --remotes=origin 输出中)。
原始答案中的推理仍然有效。大文件不能在最顶层的提交中(分支名称v0.8 指向的那个),因为我们会在那里看到大文件。它也不能在该提交的父级中,因为如果是,当我们查看最顶层的提交时,我们会看到该文件被删除,而我们没有。
这使得提交 631d55a once 和 4aa7275 once 成为剩余的潜在罪魁祸首。这些提交中至少有一个,可能两者都有,有我们不想要的大文件。
我们可以解决这个问题,但是...
从v0.8 开始并沿着链条向下(追溯历史)是我们找到这四个候选提交的原因。但是,请看一下图表的顶部,标记为 master (408ef30 h) 的提交所在的位置。这个提交也是一个合并提交,有两个父级。一位家长是7d4ecd3 new every,标记为origin/master。另一位家长是4b6d7c8 Merge branch 'master' of ...。
此合并提交 4b6d7c8 连接到正常且位于 origin/master 上的 pagedown removed 提交,但也连接到我们怀疑是错误的两个 once 提交中的最新一次。
这意味着为了清除这一切,我们需要:
- 找出两个
once 提交中的哪一个是坏的(可能两者都是);我们不再需要这些了。
- 至少写一个我们确实想要的新提交,它的父级是
4aa7275 once的父级:未显示的提交刚刚离开图表底部。
有多种方法可以解决此问题,但这是我认为最简单的一种。我假设在两个 once 提交中有一些好,并且您确实希望在这两个提交之后合并,并且您这样做 希望在合并之后创建一个名为 v0.8 的分支,并且您确实希望 master 成为这个新链的大部分顶部的合并提交,包括中间合并提交,将origin/master 合并回新链中。
如果这些假设是错误的,这不是您想要做的(过滤器分支或 BFG 清洁器“简单”的方法也不是您真正想要的)。但这都超出了这个答案的范围。
在任何情况下,在我们采取任何步骤之前,工作树应该是干净的(git status 应该没有显示任何要提交的内容,并且我们不应该有可以暂存提交的修改文件)。如果您有正在进行的工作,您将需要提交或存储它(如果需要,可以稍后将此提交或存储添加到“修复”分支)。不过,我会假设工作树是干净的。
创建一个新的“修复”分支
第一步是建立一个新分支,我们将在其中做正确的事情。这个新分支应该从4aa7275 once 的父提交分支出来,这也是539e3dc pagedown removed, bibtex bug resolved 的父提交。如果我们有那个特定提交的实际 ID,我们可以在这里使用它,但我们没有。相反,我们可以使用来自gitrevisions 的^ 或~ 后缀语法:
git checkout -b repairwork 539e3dc~1
这将创建一个名为 repairwork 的新分支,指向位于图表底部的父提交。
接下来,我们要取4aa7275好的部分,不取坏部分:
git cherry-pick -n 4aa7275
-n(您可以拼写为--no-commit)告诉git cherry-pick 从4aa7275 中提取更改,但尚未提交它们。现在git status 将显示为提交暂存的更改。
为了简单起见,假设我们刚刚挑选的提交是添加了我们不想要的大文件的提交。我们所要做的就是删除它:例如git rm hugefile。或者,也许提交 631d55a once 是删除它的提交,并且您希望将其中的任何其他更改压缩到这个新提交中。在这种情况下,您可以使用另一个git cherry-pick -n 而不是git rm hugefile,这次是631d55a。
假设再次简单起见,虽然631d55a 删除了大文件,但它包含一些您希望单独保存的额外更改,即您希望仍然有两个提交.在这种情况下,您应该 git rm 大文件,git commit 结果,然后 git cherry-pick 631d55a(没有 -n / --no-commit:因为它不添加大文件没关系现在就提交)。
让我们画出目前为止的内容:
* xxxxxxx (HEAD -> repairwork) once
* xxxxxxx once
|
| * 408ef30 (master) h
| |\
| | * 7d4ecd3 (origin/master, origin/HEAD) new every
| | * c63f869 every bug
| | * a60a14a querydate bug fixed
| | * 957a6d3 problem in every
| | * 602891c problem in every
| | * 9e827d2 problem in every
| | | * 0463323 (v0.8, test) branch pushed to remote
| | |/
| |/|
| * | 4b6d7c8 Merge branch 'master' of https://github.com/X/X
| |\ \
| | |/
| | * 539e3dc pagedown removed, bibtex bug resolved
| * | 631d55a once
| * | 4aa7275 once
| |/
|//
* xxxxxxx some commit msg
请注意,我们在这里所做的一切都会向存储库添加新的提交。 Git 很像《星际迷航》中的the Borg,因为每次你做任何事情时,你都只是简单地添加到它的集合中。我们在这里所做的是添加与原始文件非常相似的新提交,只是不再包含大文件。
现在我们有两个 once 提交——或者,如果更有意义的话,将两个 once 提交压缩为一个 once 提交——它们是(或是)相似但省略(s ) 巨文件,我们可以重做Merge branch 'master' of ...这一步,即复制提交4b6d7c8。
很遗憾,无法直接复制合并。最简单的事情就是重新执行合并。我们在repairwork 上进行了一些新的提交,所以我们可以运行git merge 539e3dc。这会将我们的新once 提交与539e3dc pagedown removed, bibtex bug resolved 合并,就像我们之前运行git merge 以创建4b6d7c8 时所做的那样。当合并完成并且我们有机会编辑合并提交消息时,我们可以放入任何我们想要的消息,这可能是相同的“合并分支'master'......”的东西,或者我们可以编写自己的更多- 有意义的消息,例如“重新合并没有大文件”。
让我们画出这个结果的部分:
* xxxxxxx (HEAD -> repairwork) "re-merge without huge file"
|\
* | xxxxxxx once
* | xxxxxxx once
我们现在可以创建一个更正的v0.8 分支。
我们现在要做的就是git checkout -b v0.8-fixed(它需要一个不同的名称,v0.8 已被使用)然后是git cherry-pick v0.8 或git cherry-pick 0463323。任一cherry-pick 命令都执行相同的操作:我们只是将名称v0.8 解析为目标提交。一旦我们完成了挑选,我们就完成了旧的、损坏的v0.8,所以我们可以重命名它并重命名我们更正的v0.8:
git checkout -b v0.8-fixed # make new branch
git cherry-pick v0.8 # copy one commit to it
git branch -m v0.8 v0.8-broken # rename broken branch
git branch -m v0.8 # rename our branch
如果我们现在git log --graph --decorate --oneline --all,它的开头是这样的:
* xxxxxxx (HEAD -> v0.8) branch pushed to remote
* xxxxxxx (repairwork) "re-merge without huge file"
|\
* | xxxxxxx once
* | xxxxxxx once
现在应该可以将v0.8 推送到遥控器。这仍然有四个要传输的提交,但是这四个都没有大文件。
我们现在也可以删除旧的test 分支(git branch -D test),并使test 指向当前提交(git branch test)。
请注意,大文件仍在我们的存储库中:
-
它在v0.8-broken 下,它有四个提交链,其中至少一个有巨大的文件。
我们可以简单地强制删除v0.8-broken,一旦我们确定我们已经完成了它,即一旦“固定”v0.8 被推送并且所有人看起来都很好。
它也在master 的下方,因为我们还没有修复master:master 的父母之一是4b6d7c8 Merge branch 'master' of https://github.com/X/X,并且该特定提交具有631d55a once 作为其中之一它的父母和631d55a 和/或4aa7275 拥有巨大的文件。
我们可以通过同样的过程修复master,即创建新的“好”或“修复”分支,然后复制提交和/或重新进行合并。创建 new 分支将丢失当前的 master 上游设置(尽管这也很容易修复)。不过,修复master 有一条捷径,因为只有一个合并需要重做。我们可以进入master,将其硬重置为一个好的提交,然后重新进行合并:
git checkout master
git reset --hard <some commit>
git merge <another commit>
当我们这样做时,我们可以选择要硬重置的提交,以及要合并的提交。合并结果作为其 first 父级,具有我们硬重置的提交。它的 second 父级是我们在 git merge 命令中命名的任何提交。
在您的原始序列中,第一个父级是另一个合并,第二个是origin/master。这可能是您想要的,尽管它被昵称为"foxtrot merge",而且它通常是错误的方式。 (这是您使用 git pull 得到的结果,而 git pull 通常是错误的做法,原因在其他问题及其链接中描述。)
(下面一行的原始答案。)
正如我在对您的其他问题的评论中指出的那样,git push 的工作原理是确定您与要推送到的遥控器有哪些共同的提交,以及您有哪些他们没有的提交。1 在这种情况下,遥控器被命名为origin。从这里我们无法分辨出你和他们有哪些共同的承诺,以及你有哪些他们没有的承诺:
git push --set-upstream origin v0.8
但你可以。我们稍后会谈到这一点。首先,这里的背景信息与我发表的评论相同,但更详细。
您的git push 命令需要发送v0.8 解析到的提交(或带注释的标签对象)(我猜这是您正在显示的046332334e1f944f64a110f92434cdc26e9fafd0,尽管您没有显示如何获得此特定ID )。你的 git push 发送这个提交,加上任何其他需要的提交、树和 blob,然后要求他们的 Git 设置一个名为 v0.8 的分支或标签(不清楚这是哪一个)指向那个提交 ID .然后,您和他们将保持同步,至少在此 v0.8 方面。
在与 Git 将推送的这组提交相关联的某个地方,有一个 Git 树,其中包含一个非常大的文件(或 blob)对象。究竟哪个提交是你必须确定然后做一些事情的事情。
这是一个如何这样的事情发生的例子。例如,假设您开始与上游存储库同步。然后,您可以通过执行以下操作在现有或新分支上添加新提交:
git add . && git commit -m 'add stuff'
在这个“东西”中就是那个巨大的文件。哎呀,好吧,我们可以删除它并再次提交,对吧?
git rm bigfile && git commit -m 'rm 1.5 GB file'
如果我们此时尝试推送,推送将失败,因为他们(远程,在本例中为 GitHub)设置了一些东西来检测和拒绝大文件。我们将推送两个提交:一个添加bigfile,另一个删除它。这意味着我们必须自己推送大文件,这需要很长时间,因为您的数据速率是有限的(大约 500 MiB,大约 72 kiB/s = 大约 7111 秒 = 大约 118.5 分钟 = 将近两个小时)。
但显然这不是这个特定的点,因为如果是这样,假设您的 git diff-tree 参数是正确的,我们会在 diff-tree 输出中看到大文件的 删除。但是,如果我们不推送还,而是继续添加更多的提交,然后然后推送,我们仍然必须推送巨大的文件:它在其中一个提交,我们必须推送它们中的所有:只有当它的 ID 与其所有内容的哈希匹配时,一个提交才有效,并且一个提交的内容包括其父提交的 ID,其中包括他们的父母,依此类推,一直追溯到时间的开始。2存储库必须具有所有中间提交才能拥有所有最终提交。3
因此,诀窍是找到引用大文件的提交。只有您可以这样做,因为只有您拥有大文件。
如何找到提交
这里是列出 Git 将推送的提交的方法。如果需要,首先运行 git fetch origin 来更新您的存储库(可能不需要,但无论如何通常都值得这样做),然后运行以下命令:
git log v0.8 --not --remotes=origin
(这不是很完美,因为它忽略了origin 上的标签,但在最坏的情况下,这会列出太多的提交,而不是太少)。
这里的想法很简单:您的远程跟踪分支记录他们在每个分支上的每个提交。 (这就是我们运行git fetch 以更新此信息的原因。)您在v0.8 上有一些他们没有的提交。我们使用v0.8 来选择在v0.8 上是 的每个提交,然后将--not --remotes=origin 添加到de - 选择在任何origin/* 远程上的每个提交- 跟踪分支。 (这就是错误出现的地方:我们还应该排除他们在他们拥有的标签上的提交,但在这一点上,我们无法轻易分辨 他们 有哪些标签。If Git kept "remote tags",而不是填充它们全部放到一个命名空间中,我们可以在这里修复它。)
剩下的可能是我们必须推送的提交,所以我们可以git log 那些。添加-m -p --name-status 以获得每个提交的名称和状态差异(包括讨厌的合并提交,git log 通常会跳过差异;这是-m 标志)。
不过,我们还有更多信息,因此您很可能不需要这样做。我们来看看你的 Git 和 GitHub 的 Git 是通过什么对话的:
Counting objects: 180, done.
据此,我们知道,在您的 Git 和他们的 Git 进行对话以确定您拥有哪些提交、树、blob 和带注释标签的对象之后,您的 Git 必须发送哪些提交、树、blob 和带注释的标签对象,您的Git 有 180 个这样的对象。
Delta compression using up to 4 threads.
Compressing objects: 100% (92/92), done.
您的 Git 能够针对您的 Git 知道其 Git 拥有的对象或您的 Git 发送的对象压缩其中的 92 个对象,因为如果他们的 Git 有提交,它也有每棵树和与该提交相关的 blob,和该提交历史的所有中的每个提交、树和 blob,回到时间的开始。 (再次参见脚注 2。)
Writing objects: 100% (180/180), 538.00 MiB | 72.00 KiB/s, done.
Total 180 (delta 142), reused 110 (delta 87)
所有 180 个对象都通过了。我不确定附加数字的真正含义(只是它们来自git pack-objects --fix-thin)。
remote: error: GH001: Large files detected. You may want to try ...
remote: error: Trace: eef60ca4521006cb11e4b7f181bc7a1a
remote: error: See http://git.io/iEPt8g for more information.
remote: error: File X.sql is 1537.98 MB; this exceeds ...
所有这些以remote: 为前缀的消息都来自他们的 Git 运行的脚本。 GitHub 所做的一件事是(显然)扫描传入的提交以查找大文件。它找到了这样一个,X.sql,大小为 1.5 GB(压缩到其大小的 1/3,因为您的 Git 只需要发送 0.5 GB :-))。
其中一个说 trace: 并打印一个 Git 哈希值。
我找不到任何关于此 trace 消息显示的细节,但要使其直接有用,它应该是提交 ID。
你可以自己测试一下:
git cat-file -t eef60ca4521006cb11e4b7f181bc7a1a
将显示相关对象的类型(如果它是有效对象)。如果它被证明是一个 blob 或树,而不是一个提交,那么它没有被记录的原因是它有点没用——不是我们找不到包含特定树或 blob 的提交,而是他们拥有最多 -有用的信息就在那里,但给了我们不太有用的信息。
如果它是提交 ID,请查看该特定提交(例如git log -1 eef60ca4521006cb11e4b7f181bc7a1a)。然后使用git rebase -i 之类的东西来修改该提交,或者将它的提交与删除大文件的提交一起压缩。由于大文件不在端点提交中,因此您已经有一个删除提交;它是否适合压缩取决于提交,以及您希望通过推送向世界其他地方展示的提交历史记录中显示的内容。
只是为了完整性:
To https://github.com/X/X.git
! [remote rejected] v0.8 -> v0.8 (pre-receive hook declined)
error: failed to push some refs to 'https://github.com/X/X.git'
这告诉我们大文件拒绝发生在预接收钩子中,并且您正在通过https 推送。左边的v0.8 是你的名字,右边的v0.8 是他们的。即使显式推送标签,Git 也不区分分支和标签推送失败:
$ git push origin refs/tags/derp2
Total 0 (delta 0), reused 0 (delta 0)
remote: pre receive hook
remote: found tag
To [redacted]
! [remote rejected] derp2 -> derp2 (pre-receive hook declined)
error: failed to push some refs to '[redacted]'
虽然成功报告为new tag。 (我设置了一个测试pre-receive 钩子,它简单地拒绝所有标签,以检查这一点)。
1更准确地说,您的 Git 从其 Git 获取名称(分支、标签和其他引用)和对象 ID 的列表。通常,这些可以是任何类型的对象。然而,分支名称只能指向提交;标签名称通常指向带注释的标签,或直接指向提交。我玩过手动标记 blob 和树,这确实有效,但不正常。
2这种结构,其中树的非叶节点携带其子节点的哈希值,称为哈希树或Merkle tree。在 Git 和 Mercurial 等版本控制系统中,提交图是一个 DAG,其父/子关系颠倒,因此提交可以是只读的,但该理论仍然适用。
3shallow 存储库是放宽此规则的存储库。根据定义,浅存储库不具有权威性,因为它们的 Merkle 树无法验证。 Git 的实现目前只允许浅层存储库在“获取”方向上工作(执行获取的 Git 为每个“连根拔起”的提交获取正确的父 ID,但随后使用特殊的嫁接条目存根图以使其表现得好像它是根提交)。发送方和接收方必须合作才能完成这项工作。