git stash申请没有返回工作目录? [英] Git stash apply did not return working directory?

查看:63
本文介绍了git stash申请没有返回工作目录?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我提交了一些文件并将其推送到远程.然后发现我的内容有误,想要还原该推送以进行一些编辑.我藏匿,还原并想重新应用存储,但是在应用后,我的工作目录仍然缺少文件.请帮忙.这是历史.

$ git commit -m "Model package"
[dev ec5e61d] Model package
 40 files changed, 1306 insertions(+), 110 deletions(-)

$ git push

$ git log
commit ec5e61d2064a351f59f99480f1bf95927abcd419
Author: Me
Date:   Mon Feb 6 

    Model package


$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
error: Your local changes to the following files would be overwritten by merge:
        model/R/train.R
Please, commit your changes or stash them before you can merge.
Aborting



$ git stash
Saved working directory and index state WIP on dev: ec5e61d Model package
HEAD is now at ec5e61d Model package


$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
[dev 062f107] Revert "Model package"
 40 files changed, 135 insertions(+), 1331 deletions(-)


$ git stash apply
CONFLICT (modify/delete): model/R/train.R deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of model/R/train.R left in tree.


$ git stash apply
model/R/train.R: needs merge
unable to refresh index

$ git commit model/R/train.R
[dev ed41d20] Resolve merge conflict
 1 file changed, 138 insertions(+)
 create mode 100644 model/R/train.R


 $ git stash apply
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
  (use "git push" to publish your local commits)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   scripts/gm.r

但是我的文件没有从存储中退回!

$ git stash list
stash@{0}: WIP on dev: ec5e61d Model package

$ git stash show
 model/R/train.R | 79 ++++++++++++++++++++++----------------------
 scripts/gm.r     | 87 ++++++++++++++++++++++++++++++++-----------------
 2 files changed, 97 insertions(+), 69 deletions(-)

解决方案

您在这里混合了很多东西.特别是,您使用了git stash git revert,同时进行了各种提交.我们需要将它们分开.

git stash的作用

git stash可能会变得非常复杂,而 1 的正常使用非常简单.您创建一个存储,它是各种各样的提交.这具有清理"工作树和索引la git reset --hard HEAD的副作用(实际上,在git stash中有一个文字git reset --hard HEAD命令).然后,您切换到其他提交,即git stash apply储存,如果一切顺利,请切换至git stash drop储存.

apply步骤将您执行的特殊存储提交转换为补丁,然后像git apply -3那样应用它,并在必要时进行三步合并.因此,如果您想使用普通的git commit模拟简化的过程,则可以执行此操作-不需要git reset --hard部分,因为您为新的提交创建了真实的分支:

git checkout -b tempbranch
git add -A
git commit -m 'stash my work-in-progress'
git checkout <something else>
git show tempbranch | git apply -3

要删除提交(如删除存储),您只需删除临时分支:

git branch -D tempbranch

尽管如此,但是您保存并应用了存储,提取可以导致执行三向合并.结果,它可能会发生合并冲突.这就是您遇到的.


1 的主要复杂之处在于:git stash分别保存为当前索引为当前工作树,分别为两个提交(无分支).因此,这很像两次运行git commit.但是,默认情况下,当您使用git stash apply时,它将合并这两个提交更改为工作树中的一项更改.

如果您在进行隐藏之前没有上演任何事情,那么这种并发症就变得无关紧要了.或者,如果您上演了所有工作,然后又没有更改工作树,那么 也会使这种复杂性变得无关紧要.无论如何,如果您应用不带--index的存储区,则应用步骤将在需要时偏向于对工作树的更改而不是对索引的更改.

另一个复杂的情况是,如果在git stash save步骤中添加-u-a选项,Git将保存 third 提交.这些三提交的隐藏文件较难应用,应谨慎使用.


提交和修补程序

在进行合并冲突之前,让我们非常简要地了解一下Git提交中的内容以及补丁(或diff)是什么.

commit 是快照.这就是您告诉Git跟踪的所有文件,它们是以您运行git commit时的格式存在的.更具体地说,这就是当时 index 中的内容.索引,也称为暂存区域,您可以在其中使用git add更新索引中的项目来告诉Git在下一次提交中要保存的内容.

提交不是补丁.这不是更改:它是快照.但是,每个 2 提交都有一个 parent (或上一个")提交,并且Git可以很容易地比较与其父级的提交.这会将提交变成一个补丁:

  • 您上次保存了A,B和C .
  • 您这次保存了A,B和D .
  • 因此,您删除了C,并添加了D.

因此,提交可以成为补丁.补丁只是说明:添加这些内容,删除其他内容.您(或Git)可以将补丁应用到其他提交,然后从结果中进行新提交.如果应用程序删除C并添加D,然后您进行新的提交,则 new 提交的补丁为删除C并添加D",这也是 old 提交的补丁.

将提交变成补丁,然后在其他地方再次播放,重复更改.


2 至少有一个提交(您所做的第一个提交)是特殊的,因为它以前没有 提交,即没有父母.另外,合并提交是特殊的,因为它们有两个(emem)(甚至更多)父母.但是我们对这里的普通单亲提交最感兴趣.

因为git stash进行提交,所以隐藏提交也具有父级.实际上,这就是Git如何将隐藏内容转换为补丁的方式,与Git将普通提交内容转换为补丁的方式非常相似.


使用git cherry-pickgit revert

(注意:您只直接使用了git revert,但由于它们非常相似,因此在此都包括了它们.)

如上所述,当我们将提交转换为补丁程序,然后将其应用于另一个分支 3 时,这是git cherry-pick操作.我们说的是上次在那边进行的更改是个好主意:让我们在这里再次进行相同的更改."我们可以对相同文件进行相同的更改(始终有效),也可以对仅足够相似的文件进行相同的更改,以便实际上可以进行相同的更改.

应用补丁"和制作樱桃"之间的本质区别在于,制作樱桃"需要源提交,而不只是补丁.默认情况下,git cherry-pick还会自动从中进行新提交,因此也可以重新使用原始提交的提交消息.

一旦您了解了补丁程序以及git cherry-pick的工作原理,git revert所做的事情就变得显而易见:它只是反向应用补丁程序.您将Git指向某个特定的提交,然后将该提交告诉 undo . Git将提交变成补丁.补丁显示删除C并添加D";然后Git删除D并添加C.与git cherry-pick一样,默认情况下git revert从结果中进行新提交.


3 从技术上讲,我们只需要将其应用于另一个 commit .但这引起了分支"的含义的问题:请参阅分支"到底是什么意思?


合并冲突

合并背后的想法很容易.我们-无论我们"进行了什么更改,而他们-无论他们"进行了什么更改-但我们都是从相同快照 starting 开始的.我们要求Git将我们的变化和他们的变化结合起来.例如,假设您都以F1F2F3开头,并且更改了文件F1,但他们没有,而他们更改了文件F2,但您没有.这真的很容易结合:将修改后的F1和修改后的F2合并起来.在F3中,您更改了文件顶部附近的一行,而他们更改了底部附近的一行.合并起来有点困难,但仍然不是问题:Git只需进行两项更改即可.

但是,某些更改根本无法合并.这些更改是合并冲突.

合并冲突有多种形式.当您"(在提交中)和他们"(在提交中)都修改同一文件中的同一行时,会发生最常见的情况.当我们接受提交并将其转换为补丁(删除C并添加D")时,就会发生这种情况,但是他们所做的更改是保持 C并添加E. Git不知道要使用哪个更改:我们应该保留C并同时添加D和E,还是删除C并仅保留E,还是什么?

这不是您所得到的:您遇到了修改/删除冲突.当您说在文件train.R中,我们应该删除C并添加D时,就会发生修改/删除冲突,并且他们说丢弃整个train.R文件".

所有合并冲突的情况下,Git举起隐喻之手并暂时放弃进行合并.它将冲突的文件写入您的工作树,声明合并冲突,然后停止.现在,完成 合并是您的工作-通过提供正确快照,然后git addgit commit结果.

在执行git merge时,合并冲突当然会发生(这时很明显,哪个是我们的",哪个是他们的"). 4 但是它们也可能在git apply -3(-3表示使用三路合并"),git cherry-pickgit revert期间发生.如果补丁不能很好地应用,Git可以找出要使用的通用基础,然后找出更改的两个部分:正在应用的补丁中的内容,以及从通用基础变为正确的提交的内容现在,您正在尝试使用git cherry-pickgit revert进行修补.


4 在所有情况下,我们的"版本都是HEAD版本.在rebase过程中也是如此,但是rebase通过重复执行一系列的樱桃动作来工作,这时,HEAD是您要构建的新替代分支.请参阅此评论 CommaToast:因为头部是心灵的所在地,身份是来源,身份是自我的来源,所以考虑HEAD所指的是我的"是更有意义的……"


关于git commit

的最后一点说明

我在上面提到过,通常您将git add个文件从工作树复制到索引/登台区域.在合并冲突期间,这也将文件标记为已解决,这就是为什么要先 edit 编辑然后在其上使用git add的原因.修复所有文件并对其进行git add编辑后,就可以git commit进行合并.这样就完成了合并,或者完成了樱桃选择或还原或变基步骤,或者您所做的任何有冲突的操作.

当您像这样运行git commit时,没有特定的命名文件,Git会提交索引(临时区域)的内容.结果,新的HEAD提交与索引匹配.稍后我们将使用这个事实.

但是,如果您运行git commit <path>,则此自动会暂存命名文件(就像您已经运行git add file一样),然后提交-好吧,某事.这部分有些棘手.默认设置为就像您运行git commit --only <path>一样.

使用--only,Git 不会获取任何其他已经暂存的文件.也就是说,如果您已经git add编写了五个文件(均未命名为README.txt),然后又运行了git commit README.txt,则Git会使用README.txt的工作树版本进行新的提交,而使用README.txt的版本来进行新提交.其他五个文件.换句话说,它仅会更改命名文件,并保留所有其他文件的先前版本.

使用--include,Git也会分阶段命名的<file>,并提交整个结果.也就是说,这就像在运行git commit之前执行git add <file>一样.这比--only行为更容易解释,但是当然,如​​果您愿意,可以只git add <file>.

但是,无论哪种情况,一旦进行了新的提交,就已经暂存了<file>,就好像您已经对它进行了git add编辑一样.因此,在我们的--only示例中,使用五个文件add -ed,索引现在更新了所有六个文件.当然,有一个新的HEAD提交,并且README.txt现在与该新的HEAD提交匹配,因此只有五个差异被上演.

所以,现在让我们看看你做了什么

您显示的第一个命令是这样:

$ git commit -m "Model package"
[dev ec5e61d] Model package
 40 files changed, 1306 insertions(+), 110 deletions(-)

我们没有看到您的任何git add,但是您必须添加40个文件,这样Git会说"40个文件已更改".但是您也必须不要进行git add编辑,就像我们稍后会看到的那样.

接下来,您显示:

$ git push

,无输出.目前尚不清楚该推动什么(如果有的话)(这取决于您的push.default配置,可能取决于当前分支是否具有上游设置,依此类推).但是,无论您推送了什么内容,您自己的本地存储库内容都不会更改,后来的证据表明推送成功,将提交ec5e61d推送到origin.

现在,您将显示两个带有一些输出的命令:

$ git log
commit ec5e61d2064a351f59f99480f1bf95927abcd419
Author: Me
Date:   Mon Feb 6 

    Model package

这是您所做的提交:ec5e61d,在分支dev上,如git commit输出所示.

$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
error: Your local changes to the following files would be overwritten by merge:
        model/R/train.R
Please, commit your changes or stash them before you can merge.
Aborting

这很有趣,因为它表明即使最近运行git commit并进行了ec5e61d提交,您的工作树仍已更改为model/R/train.R.

这意味着model/R/train.R index 中的内容与model/R/train.R的工作树中的内容不匹配.因此,我们不能确切地说出索引中的内容,只是它与工作树不匹配.

git revertAborting时,它表示一无所有:它发现您的工作树不是干净的",即与HEAD提交不匹配ec5e61d.默认情况下,git revert要求工作树与HEAD提交匹配.

接下来,您运行:

$ git stash
Saved working directory and index state WIP on dev: ec5e61d Model package
HEAD is now at ec5e61d Model package

git stash命令进行了提交,实际上是两次提交,一次提交给索引,一次提交给工作树.但是索引与HEAD提交相同,因此,我们真正需要关心的只是工作树提交-然后清理"工作树以匹配HEAD提交.

换句话说,stash提交(或者,我们关注的两个提交之一)具有model/R/train.R的工作树版本.它还具有已修改(跟踪)但尚未提交的任何其他文件的工作树版本,以及所有仍与HEADec5e61d提交匹配的所有剩余文件的工作树版本. (像往常一样,每个提交都包含每个文件.这就是成为快照"的意思.稍后,我们将能够看到更多关于藏匿的工作之间的区别的信息-树提交和ec5e61d提交.)

一旦git stash隐藏了所有这些内容,它将使用git reset --hard HEAD丢弃索引和工作树更改. (它们安全地保存在存储库中,因此在索引和工作树中不再需要.)因此,现在您的工作树中的model/R/train.R文件与HEAD中的文件匹配.

现在,您重新运行git revert:

$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
[dev 062f107] Revert "Model package"
 40 files changed, 135 insertions(+), 1331 deletions(-)

这一次,由于您要还原刚提交的更改,因此反向修补"很容易成功(撤销所做的操作很简单). Git在分支dev上进行新的提交062f107,因此最后两个提交是:

... <- ec5e61d <- 062f107   <-- dev

存储区本身已附加到提交ec5e61d(每个存储区直接附加到您进行存储时为HEAD的提交).

现在您尝试应用存储,但这会因合并冲突而失败:

$ git stash apply
CONFLICT (modify/delete): model/R/train.R deleted in Updated upstream
and modified in Stashed changes. Version Stashed changes of
model/R/train.R left in tree.

这特别有趣,因为这意味着model/R/train.R必须已通过还原提交062f107 删除,这意味着它必须已在提交中添加 ec5e61d.同时,隐藏提交在转换为补丁后会显示将更改更改为model/R/train.R."

由于Git不知道如何将完全删除此文件"与进行此更改"结合使用,因此它将整个隐藏的model/R/train.R工作树版本保留在工作树中.现在,您要弄清工作树中应该有什么 ,然后git add.

与此同时,Git的所有 other 更改都应作为补丁应用,Git did 则应作为补丁应用到所有其他文件.这些更改 已进行提交,即已经为这些文件更新了索引.

接下来,您运行:

$ git stash apply
model/R/train.R: needs merge
unable to refresh index

这将尝试再次应用存储,但是不能,因此(幸运的是)它什么也不做.这使未完成的合并未完成.

然后您运行:

$ git commit model/R/train.R
[dev ed41d20] Resolve merge conflict
 1 file changed, 138 insertions(+)
 create mode 100644 model/R/train.R

请注意,这具有git commit <file>形式.来自工作树的git add s model/R/train.R,然后跳过其他添加的文件,并从结果中进行新的HEAD提交ed41d20.因此,此新提交具有与旧HEAD 062f107 相同的文件,除了 model/R/train.R:

... <- ec5e61d <- 062f107 <- ed41d20   <-- dev

现在您再次运行git stash apply:

$ git stash apply
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
  (use "git push" to publish your local commits)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   scripts/gm.r

就像上次一样,这会将隐藏提交变成补丁,并尝试应用补丁.补丁程序无法正确应用,因此Git尝试进行三种方式的合并:查找基本版本,查找HEAD(ed41d20)版本(我们的")中的内容,对这些内容进行比较,然后将该差异与补丁进行组合.该差异与补丁完全相同,即补丁已被应用.因此,最后得到的是git status的输出,其中显示了一个修改后的文件,以及两个提交(当然,这些提交必须是062f107ed41d20),您知道origin尚不具有

最后,您显示以下内容:

$ git stash list
stash@{0}: WIP on dev: ec5e61d Model package

$ git stash show
 model/R/train.R | 79 ++++++++++++++++++++++----------------------
 scripts/gm.r     | 87 ++++++++++++++++++++++++++++++++-----------------
 2 files changed, 97 insertions(+), 69 deletions(-)

结束了我们对隐藏内容的了解:就像提交ec5e61d一样,除了两个文件model/R/train.Rscripts/gm.r. git diff有关将stc 5 中的提交从ec5e61d转换为工作树提交的说明,说在这两个文件中删除69条原始行,并插入97条替换行.

这是git stash apply试图做的,只是它无法从完全删除的model/R/train.R中删除任何行,因此它只是放置了整个保存的工作树stash@{0}工作树中文件model/R/train.R的版本.


5 再次,我们再次证明了隐藏中的索引提交与ec5e61d提交完全匹配.因此,我们可以忽略索引提交,而只专注于工作树提交.


下一步做什么

此时,您应该要做的事取决于您想要的结果.我不能给您正确的答案,但是我可以给您一些重要的问题:

  • 您想拥有什么 ?您希望在每次提交中提供哪些源树快照?
  • 其他人是否已经拥有提交ec5e61d的副本? (即,其他人是否有权访问您将提交ec5e61d推送到的上游存储库?)

提交ec5e61d在您的 存储库中都存在,并且您通过git push命令将其推回原位的存储库中.在该其他存储库中,名称dev可能指向ec5e61d.与父提交相比,它具有文件model/R/train.R作为新文件,并且还对39个其他文件进行了其他更改或新创建.

提交062f107仅存在于您的存储库中.这是对提交ec5e61d的确切撤消,因此,它可能毫无用处,除非您需要确保从任何其他存储库中完全撤消ec5e61d.

提交ed41d20也仅存在于您的存储库中.它是ec5e61d parent 中任何内容的副本,除了它添加了model/R/train.R的隐藏版本.

您当前的工作树与ed41d20基本匹配,只是修改了scripts/gm.r,而在ec5e61d和隐藏提交之间的补丁中所做的任何更改,也都在工作树中进行了相同的更改.并且,由于ed41d20ec5e61d的父代大部分匹配,因此ec5e61d的父代与工作树之间的差异等于您在scripts/gm.r的存储中更改的内容,以及model/R/train.R的隐藏版本.

您仍然有藏匿处.如果有用,请保留它直到不再有用.一旦确定不再有用,请使用git stash drop删除它(此步骤非常难于执行 ,因此请确保已完成操作).

您可能希望完全放弃最近的两次提交(还原和仅提交一个添加的文件版本model/R/train.R的提交).在这种情况下,您可以git reset --hard origin/dev放弃两个提交,并将工作树放回提交ec5e61d时的状态.

如果您是上游存储库的唯一用户(这是一个很大的"if"),并且提交ec5e61d本身并不是很有用,那么您可能希望丢弃 提交也是如此-但要小心,不仅因为"if"部分很大,而且还因为该特定提交变得更难恢复-尽管不如隐藏那么难.只要您还有隐藏位置,还原和git stash apply结果就很容易重复:您只需运行相同的git命令.

I committed and pushed some files to the remote. Then found that I had something wrong in it and wanted to revert the push to make some edits. I stashed, reverted, and want to re-apply the stash, but after application, my working directory is still missing the files. Please help. Here's the history.

$ git commit -m "Model package"
[dev ec5e61d] Model package
 40 files changed, 1306 insertions(+), 110 deletions(-)

$ git push

$ git log
commit ec5e61d2064a351f59f99480f1bf95927abcd419
Author: Me
Date:   Mon Feb 6 

    Model package


$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
error: Your local changes to the following files would be overwritten by merge:
        model/R/train.R
Please, commit your changes or stash them before you can merge.
Aborting



$ git stash
Saved working directory and index state WIP on dev: ec5e61d Model package
HEAD is now at ec5e61d Model package


$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
[dev 062f107] Revert "Model package"
 40 files changed, 135 insertions(+), 1331 deletions(-)


$ git stash apply
CONFLICT (modify/delete): model/R/train.R deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of model/R/train.R left in tree.


$ git stash apply
model/R/train.R: needs merge
unable to refresh index

$ git commit model/R/train.R
[dev ed41d20] Resolve merge conflict
 1 file changed, 138 insertions(+)
 create mode 100644 model/R/train.R


 $ git stash apply
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
  (use "git push" to publish your local commits)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   scripts/gm.r

But my files didn't return from the stash!

$ git stash list
stash@{0}: WIP on dev: ec5e61d Model package

$ git stash show
 model/R/train.R | 79 ++++++++++++++++++++++----------------------
 scripts/gm.r     | 87 ++++++++++++++++++++++++++++++++-----------------
 2 files changed, 97 insertions(+), 69 deletions(-)

解决方案

You have mixed together quite a few things here. In particular, you have used git stash and git revert, while also making various commits. We need to pick these apart.

What git stash does

While git stash can become very complicated,1 its normal use is fairly simple. You create a stash, which is a variety of commit. This has the side effect of "cleaning" the work-tree and index, a la git reset --hard HEAD (in fact there's a literal git reset --hard HEAD command inside git stash). Then you switch to some other commit, git stash apply the stash, and if all goes well, git stash drop the stash.

The apply step converts the special stash commit you made to a patch, and then applies it like git apply -3 would, doing a three-way merge if necessary. Hence, if you wanted to emulate simplified process with ordinary git commit you could do this—you don't need the git reset --hard part because you make a real branch for the new commit:

git checkout -b tempbranch
git add -A
git commit -m 'stash my work-in-progress'
git checkout <something else>
git show tempbranch | git apply -3

To drop the commit (like dropping the stash) you would then simply delete the temporary branch:

git branch -D tempbranch

Nonetheless, however you saved and applied the stash, extracting the stash can result in performing a three-way merge. As a result, it can have a merge conflict. This is what you encountered.


1The main complication is this: git stash saves, separately, both the current index and the current work-tree, as two commits (on no branch). It is therefore a lot like running git commit twice. But, when you use git stash apply, it combines those two commits into one change in the work-tree, by default.

If you did not stage anything before making the stash, this complication becomes irrelevant. Or, if you staged everything, and then did not change the work-tree, that also makes this complication irrelevant. In any case, if you apply the stash without --index, the apply step will, if needed, favor the work-tree changes over the index changes.

As another complication, if you add the -u or -a option to your git stash save step, Git saves a third commit. These three-commit stashes are harder to apply and should be used with care.


Commits and patches

Before we go on to the merge conflict, let's take a very brief look at what is in a Git commit, and what a patch (or diff) is.

A commit is a snapshot. It's all the files you told Git to keep track of, in the form they had when you ran git commit. More specifically, it's what was in the index at that time. The index, also called the staging area, is where you tell Git what to save in the next commit, using git add to update items in the index.

A commit is not a patch. It's not a change: it's a snapshot. However, every2 commit has a parent (or "previous") commit, and Git can easily compare a commit against its parent. This turns the commit into a patch:

  • You saved A, B, and C last time.
  • You saved A, B, and D this time.
  • Therefore, you deleted C, and added D.

Hence a commit can become a patch. A patch is simply instructions: add these things, delete these other things. You (or Git) can apply the patch to some other commit, and then make a new commit from the result. If the application deletes C and adds D, and you make a new commit, then the new commit's patch is "delete C and add D"—which is also the old commit's patch.

Turning a commit into a patch, and then playing it again elsewhere, repeats the change.


2At least one commit—the first one you ever make—is special because it has no previous commit, i.e., no parent. In addition, merge commits are special because they have two (or even more) parents. But we're mostly interested in ordinary, one-parent commits here.

Because git stash makes commits, the stash commits also have parents. This is, in fact, how Git turns stashes into patches, much the same way Git turns ordinary commits into patches.


Using git cherry-pick and git revert

(Note: you only used git revert directly, but I include both here because they are very similar.)

When we turn a commit into a patch, as above, and then apply it to another branch,3 this is a git cherry-pick operation. We're saying "the change we made last time over there, is a good idea: let's make the same change again, over here." We can make the same change to identical files—this always works—or to files that are merely sufficiently similar, so that we can in fact make the same changes.

The essential difference between "apply a patch" and "make a cherry-pick" is that "make a cherry-pick" takes a source commit, instead of just a patch. By default, git cherry-pick also makes a new commit from it automatically—so this can re-use the original commit's commit message, too.

Once you understand patches and how git cherry-pick works, what git revert does becomes trivially obvious: it simply reverse-applies the patch. You point Git at some particular commit and tell it to undo whatever that commit did. Git turns that commit into a patch; the patch says "delete C and add D"; and Git then deletes D and adds C. As with git cherry-pick, git revert makes a new commit from the result, by default.


3Technically, we need only apply it to another commit. But this gets into the question of what we mean by "branch": see What exactly do we mean by "branch"?


Merge conflicts

The idea behind a merge is easy enough. We—whoever "we" are—made some changes, and they—whoever "they" are—made some changes—but we both started from the same snapshot. We ask Git to combine our changes and their changes. For instance, suppose that you both started with F1, F2, and F3, and you changed file F1 and they didn't, and they changed file F2 and you didn't. That's really easy to combine: take your modified F1 and their modified F2. In F3, you changed a line near the top of the file, and they changed a line near the bottom. That's a little harder to combine, but still not a problem: Git just takes both changes.

Some changes, though, can't be combined at all. These changes are merge conflicts.

Merge conflicts have a number of forms. The most common ones occur when both "you" (in your commits) and "they" (in their commits) modify the same line(s) in the same file(s). This happens when we take our commit(s) and turn it/them into a patch—"delete C and add D"—but their change says to keep C and add E instead. Git doesn't know which change to use: should we keep C and add both D and E, or delete C and keep only E, or what?

This is not what you got, though: you got a modify/delete conflict. A modify/delete conflict occurs when you said that, in file train.R, we should delete C and add D—and they said "throw away the whole train.R file".

In all cases of merge conflicts, Git throws up its metaphorical hands and temporarily gives up doing the merge. It writes into your work-tree the conflicted file(s), declares the merge conflict, and stops. It is now your job to finish the merge—by coming up with the correct snapshot—and then git add and git commit the result.

Merge conflicts can, of course, occur when you're doing a git merge (and at this time it's pretty clear which is "ours" and which is "theirs").4 But they can also occur during git apply -3 (the -3 means "use three way merge"), git cherry-pick, and git revert. If the patch does not apply cleanly, Git can figure out which common base to use and then figure out the two parts of the changes: what's in the patch you are applying, and what changed from the common base to the commit you are on right now, that you're trying to patch with git cherry-pick or git revert.


4The "ours" version is, in all cases, the HEAD version. This is true during rebase as well, but rebase works by doing a repeated series of cherry-picks, and at this time, HEAD is the new replacement branch you're building. See this comment by CommaToast: "since the head is the seat of the mind, which is the source of identity, which is the source of self, it makes a bit more sense to think of whatever HEAD's pointing to as being ‘mine’ ...".


One last note about git commit

I mentioned above that you normally git add files to copy them from the work-tree to the index / staging area. This also marks the file as resolved, during a merge conflict, which is why it's your job to edit the file first, then use git add on it. Once you have fixed up and git add-ed all the files, you can git commit the resulting merge. This finishes the merge, or the cherry pick or revert or rebase step or whatever it was you were doing that had the conflict.

When you run git commit like this, with no specific named files, Git commits the index (staging-area) contents. As a result, the new HEAD commit matches the index. We'll use that fact in a moment.

If you run git commit <path>, though, this automatically stages the named file (as if you had run git add file), then commits—well, something. This part gets a bit tricky. The default is to behave as though you ran git commit --only <path>.

With --only, Git doesn't take any other already-staged files. That is, if you've git added five files, none named README.txt, and then you run git commit README.txt, Git makes a new commit using the work-tree version of README.txt but the HEAD version of the other five files. In other words, it only changes the named files, preserving the previous versions of all other files.

With --include, Git stages the named <file> too, and commits the entire result. That is, this is like doing git add <file> before running git commit. This is much easier to explain than the --only behavior, but of course if you want this, you can just git add <file>.

In either case, though, once the new commit is made, <file> has been staged, as if you had git add-ed it. So in our --only example, with five files add-ed, the index now has all six files updated. Of course, there's a new HEAD commit, and README.txt now matches the new HEAD commit, so there are only five differences staged.

So, now let's look at what you did

The first command you show is this:

$ git commit -m "Model package"
[dev ec5e61d] Model package
 40 files changed, 1306 insertions(+), 110 deletions(-)

We don't see any of your git adds, but you must have added 40 files so that Git said "40 files changed". But you must also not have git add-ed something, as we will see in a moment.

Next, you show:

$ git push

with no output. It's not immediately clear what, if anything, this pushed (this depends on your push.default configuration, and probably whether the current branch has an upstream setting, and so on). However, no matter what you may have pushed, your own local repository contents are not changed, and later evidence shows that the push succeeded, pushing commit ec5e61d to origin.

Now you show two commands with some output:

$ git log
commit ec5e61d2064a351f59f99480f1bf95927abcd419
Author: Me
Date:   Mon Feb 6 

    Model package

This is the commit you made: ec5e61d, on branch dev, as shown by the git commit output.

$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
error: Your local changes to the following files would be overwritten by merge:
        model/R/train.R
Please, commit your changes or stash them before you can merge.
Aborting

This is interesting, because it shows that you have changes in your work-tree to model/R/train.R even though you ran git commit recently, making the ec5e61d commit.

This means that whatever was in the index for model/R/train.R did not match what is now in the work-tree for model/R/train.R. We can't tell, from this, precisely what was in the index, just that it does not match the work-tree.

When git revert said Aborting, it means it did nothing at all: it found that your work-tree was not "clean", i.e., did not match the HEAD commit ec5e61d. By default git revert requires that the work-tree match the HEAD commit.

Next, you ran:

$ git stash
Saved working directory and index state WIP on dev: ec5e61d Model package
HEAD is now at ec5e61d Model package

The git stash command made its commit—really, two commits, one for the index and one for the work-tree; but the index is the same as the HEAD commit, so all we really have to care about is the work-tree commit—and then "cleaned up" the work-tree to match the HEAD commit.

In other words, the stash commit—or rather, the one of the two that we care about—has the work-tree version of model/R/train.R. It also has the work-tree version of any other files that were modified (and tracked) but not yet committed, and the work-tree version of all remaining files that still match the HEAD or ec5e61d commit as well. (Every commit, as always, has every file in it. That's what "being a snapshot" means. We'll be able to see, in a moment, more about what's different between the stashed work-tree commit and the ec5e61d commit.)

Once git stash has stashed all of these, it uses git reset --hard HEAD to discard the index and work-tree changes. (They are safely saved away in the stash, hence no longer needed here in the index and work-tree.) So now your model/R/train.R file in your work-tree matches that in HEAD.

Now you re-run your git revert:

$ git revert ec5e61d2064a351f59f99480f1bf95927abcd419
[dev 062f107] Revert "Model package"
 40 files changed, 135 insertions(+), 1331 deletions(-)

and this time, since you're reverting the very change you just committed, "reverse patching" easily succeeds (it's trivial to undo what you just did). Git makes a new commit, 062f107, on branch dev, so that the last two commits are:

... <- ec5e61d <- 062f107   <-- dev

The stash itself is attached to commit ec5e61d (each stash is directly attached to the commit that was HEAD at the time you made the stash).

Now you try to apply the stash, but this fails with a merge conflict:

$ git stash apply
CONFLICT (modify/delete): model/R/train.R deleted in Updated upstream
and modified in Stashed changes. Version Stashed changes of
model/R/train.R left in tree.

This is particularly interesting, as it means that model/R/train.R must have been deleted by the revert commit 062f107, which means it must have been added in commit ec5e61d. Meanwhile, the stash commit, when converted to a patch, says "make this change to model/R/train.R."

Because Git did not know how to combine "remove this file completely" with "make this change", it left the entire stashed work-tree version of model/R/train.R in the work-tree. It's now your job to figure out what should be in the work-tree, and git add that.

Meanwhile, any other changes Git should apply as a patch, Git did apply as a patch, to all the other files. These changes are staged for commit, i.e., the index is updated already for these files.

Next, you ran:

$ git stash apply
model/R/train.R: needs merge
unable to refresh index

This tries to apply the stash again, but can't, so (fortunately) it does nothing. This leave the unfinished merge unfinished.

Then you ran:

$ git commit model/R/train.R
[dev ed41d20] Resolve merge conflict
 1 file changed, 138 insertions(+)
 create mode 100644 model/R/train.R

Note that this has the git commit <file> form. This git adds model/R/train.R from the work-tree, then skips the other added files and makes a new HEAD commit ed41d20 from the result. So this new commit has all the same files as the old HEAD 062f107 except for model/R/train.R:

... <- ec5e61d <- 062f107 <- ed41d20   <-- dev

Now you ran git stash apply yet again:

$ git stash apply
On branch dev
Your branch is ahead of 'origin/dev' by 2 commits.
  (use "git push" to publish your local commits)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   scripts/gm.r

Just as last time, this turns the stash commit into a patch and attempts to apply the patch. The patch doesn't apply properly, so Git tries a three-way merge: find the base version, find what's in your HEAD (ed41d20) version ("ours"), diff those, and combine that diff with the patch. That diff turns out to be exactly the same as the patch, i.e., the patch is already applied. So you get the output of git status at the end, which shows one modified file, and the two commits—these must, of course, be 062f107 and ed41d20—that you have that origin does not yet have.

Last, you show these:

$ git stash list
stash@{0}: WIP on dev: ec5e61d Model package

$ git stash show
 model/R/train.R | 79 ++++++++++++++++++++++----------------------
 scripts/gm.r     | 87 ++++++++++++++++++++++++++++++++-----------------
 2 files changed, 97 insertions(+), 69 deletions(-)

which finish out our knowledge of what's in the stash: it's just like commit ec5e61d except for two files, model/R/train.R and scripts/gm.r. The git diff instructions for converting from commit ec5e61d into the work-tree commit in the stash5 say to delete 69 original lines, and insert 97 replacement lines, in those two files.

That's what git stash apply tried to do, only it was unable to delete any lines from a completely-deleted model/R/train.R, so it just put the entire saved-work-tree stash@{0} version of file model/R/train.R in the work-tree.


5Again, earlier, we proved that the index commit in the stash matches the ec5e61d commit exactly. Thus, we can ignore the index commit, and concentrate only on the work-tree commit.


What to do next

What you should do, at this point, depends on what result you want. I cannot give you the correct answers, but I can give you the important questions:

  • What commits do you want to have? What source tree snapshots do you want in each commit?
  • Does anyone else already have a copy of commit ec5e61d? (I.e., does anyone else have access to the upstream repository to which you pushed commit ec5e61d?)

Commit ec5e61d exists in both your repository, and the repository you pushed it to way back at the git push command. In that other repository, the name dev probably points to ec5e61d. It has file model/R/train.R as a new file, and additional changes to, or new creations of, 39 other files as well, when compared to its parent commit.

Commit 062f107 exists only in your repository. It's an exact backing-out of commit ec5e61d, and as such, it's probably useless, unless you need to make sure you exactly back out ec5e61d from any other repository.

Commit ed41d20 also exists only in your repository. It's a copy of whatever is in the parent of ec5e61d except that it has the stashed version of model/R/train.R added.

Your current work-tree mostly matches ed41d20 except that it has scripts/gm.r modified, with whatever was changed in the patch between ec5e61d and the stash commit, also changed the same way in the work-tree. And, since ed41d20 mostly matches the parent of ec5e61d, the difference between the parent of ec5e61d and the work-tree amounts to whatever you changed in scripts/gm.r in the stash, plus the stashed version of model/R/train.R.

You still have the stash, too. If that's useful, keep it until it is no longer useful. Once you are sure it is no longer useful, use git stash drop to drop it (this step is extremely hard to undo, so be very sure you are done with it).

You may well want to discard the most recent two commits (the revert, and the commit of just the one added file version of model/R/train.R) entirely. In that case you can git reset --hard origin/dev to discard the two commits and put your work-tree back the way it was when you made commit ec5e61d.

If you're the only user of the upstream repository (this is a very big "if"), and commit ec5e61d itself is not really useful, you may want to discard that commit as well—but be careful, not just because of the very big "if" part, but also because that particular commit becomes harder to recover—though not as hard as the stash. As long as you still have the stash, the revert and the git stash apply results are quite easy to repeat: you just run the same git commands.

这篇关于git stash申请没有返回工作目录?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆