当我在本地存储库中运行git diff --stat --cached origin/master
时,我看到某些已提交的文件已准备就绪。但是,当我尝试使用git push
时,它会尝试推送使用diff
不会出现的不同文件,这对我的远程存储库来说太大了。
这里发生了什么?解决方案?
git reset
你可能想要一个git reset --soft origin/master
,然后是git commit
。但请注意,这种git reset
是更危险的Git命令之一,因为它可以删除提交。 Git是围绕添加新提交而构建的,这些提交永远不会导致任何旧提交消失,因此意味着以前没有任何承诺的工作会丢失。由于git reset
删除了提交 - 或者至少可以删除它们 - 因此可能以这种方式丢失承诺的工作。
git push
推送提交,而不是文件。
git diff --cached origin/master
将名称origin/master
选择的提交与索引的当前内容进行比较。索引不是提交,因此这不会告诉你git push
会推送什么。
索引 - 它有几个额外的名称:它也称为临时区域,有时也称为缓存 - 是Git构建将在下一次提交中生成的所有文件的地方。最初,索引包含与当前提交中相同的所有文件。当前提交是你运行git checkout master
时检查的那个(注意我假设你运行git checkout master
,基于你问题中的名字origin/master
)。
现在,您在存储库中的每个提交都包含文件,可能包含很多文件。每次提交都充当该提交中所有文件的完整,大部分独立的快照。
如果您提交了某些内容,然后删除文件并再次提交,则新提交就像旧提交一样,但它只有少一个文件。
如果您提交了一些内容,那么更改文件和git add
以将更改复制到索引和git commit
中,新提交就像旧提交一样,除了您更改的文件在新提交中不同。
换句话说,每当你运行git commit
时,你的Git就会打包你索引中的任何内容,并将其转换为新的提交。通常,这些新提交只会添加到您现有的提交中。如果每个提交都有一个单字母的名称,而不是像a123456...
那样的丑陋名字,我们可能会像这样绘制它们:
...--F--G--H <-- master (HEAD)
添加新提交将选择下一个字母,将其添加到提交链并使master
指向刚刚添加的新提交:
...--F--G--H--I <-- master (HEAD)
当以这种方式使用时,Git只会添加新的提交。
git push
当你运行git push
时,你的Git会调用其他一些Git。你的Git和他们的Git进行了一次对话,找出你有没有提交的东西,你想要给他们的东西。你的Git然后说了这样的话:这些是我所拥有的,你没有。当你看完它们之后,请设置你自己的master
分支来记录提交#a123456 ...作为你自己的master
的最后一次提交,就像它是我master
上的最后一次提交,好吗?
您得到的错误是因为当您发送这些提交和该请求时,他们的Git会查看这些提交并确定某些提交中的某些文件太大。
这意味着为了让你的git push
成功,你必须停止向那些提交,即拥有大文件的提交。
现在,假设他们的origin/master
在提交链中的提交F
结束:
...--F <-- origin/master
\
G--H <-- master (HEAD)
进一步假设他们抱怨的大文件是在提交G
和/或提交H
。如果你从索引中删除那些大文件,并运行git commit
,你会得到新的提交I
,它不再包含那些大文件:
...--F <-- origin/master
\
G--H--I <-- master (HEAD)
但现在你运行git push origin master
,即发送它们提交G-H-I
,然后让他们设置他们的master
指向提交I
。他们检查提交G
和H
并发现其中有大文件。在提交I
中大文件消失并不重要;重要的是你让你的Git发送整个链,G-H-I
,并且大文件在早期的提交中。
你需要做的是以某种方式创建一个没有连接回G-H
序列的新提交,例如,以某种方式做出一个可能看起来像这样的提交:
J <-- master (HEAD)
/
...--F <-- origin/master
\
G--H--I <-- ???
(我假设你继续前进并提交I
- 如果没有,只是假装它不在图中。)一旦你有这个序列,你可以让你的Git调用他们的Git,发送他们J
-连接回到F
,但他们已经有F
-然后要求他们设置他们的master
指向承诺J
。由于J
没有大文件,这将是正常的,幸运的是,将通过他们施加的任何其他要求,他们将采取J
并将其放入他们的存储库。
如果你运行git reset --soft origin/master
,而你的HEAD
就像这样附加到master
,1你的Git会改变你的master
指向与你的origin/master
相同的提交:
...--F <-- master (HEAD), origin/master
\
G--H--I [abandoned]
在这一点上,整个“提交后”origin/master
的提交链不再具有master
的名称,让你找到它们。这就是为什么现在可以删除它们.2此命令的--soft
部分告诉Git:不要触摸索引或工作树。因此,您的索引和工作树仍然按照提交I
的方式设置。
如果你现在运行git commit
,你将得到一个新的提交,其快照来自索引,我们刚才注意到它设置为匹配commit I
。这就是创建新提交J
的原因。 Git使用当前的提交,即F
,作为新提交的父级,以便J
连接回F
:
J <-- master (HEAD)
/
...--F <-- origin/master
这正是我们想要的。
所以,现在我们可以运行git push origin master
,它让我们的Git调用他们的Git,将提交J
转移给他们,并要求他们设置他们的master
指向我们刚刚发送的新提交J
。其他提交 - 所有这些提交 - 永远不会离开我们自己的存储库。我们最好还是停止使用它们,因为它们有那些我们不想提交的大文件,所以这一切都可能是正确的。
1Git有两种模式:一种是HEAD
“附着”某些分支,如master
,另一种是HEAD
分离。分离的HEAD模式对于构建一些提交然后将分支名称附加到它们非常有用,并且由git rebase
之类的内部使用。不过,附带HEAD的情况更为常见。 :-)
默认情况下,2Git会将它们保留至少30天,以防你搞砸了,但现在很难找到它们没有方便的分支名称。