所以我有一个git repo,其结构如下:
.DS_Store
.git
.gitignore
README.md
folder1/
folder2/
folder1
是我的工作,无需担心folder2
。我是master的分支,并开始处理folder1
中的文件。另一位开发人员也处理folder1
中的文件。然后他继续并合并他的变化。现在轮到我合并我的更改了。我的典型git merge
工作流程如下:
git fetch
git rebase -i origin/master
squash commits
git push -f my_branch
然而在这种情况下,我继续在我的分支中做了一个git pull,引发了一些冲突。使用git diff
我解决了冲突。继续像白痴一样,我做git commit -a
,它在提交屏幕中显示来自folder2
的文件作为修改过的文件。因为我意识到事情已经结束,我中止了提交。而不是通常"没有输入提交消息,提交已中止"消息我得到了这个:Merge branch 'master' of https://github.com/comapny/my_repo into my_branch
。而不是在主人身上重新设置我的分支,我认为我将主人重新安排到我的分支上。 git log
显示如下:
ME:
Merge branch 'master' of https://github.com/company/my_repo into my branch
ME:
final commit on my_branch
Other dev:
His merge
Other dev:
His merge
Other dev:
His merge
Other dev:
His merge
Other dev:
His merge
ME:
2nd commit on my_branch
ME:
1st commit on my_branch
我真的不确定发生了什么事,如果有人能告诉我我搞砸了什么,我会很高兴。我甚至不知道谷歌的用途。
P.S。 - 当我在github.com上访问我的repo
时,主人没有动过,我的分支最后一次提交为final commit on my_branch
所以我刚刚搞砸了本地
答案 0 :(得分:3)
好的,首先,让我们稍微回顾一下(这可以帮助你更加确定,或者更不确定)。
通常当你执行git commit -a
或git add foo; git add bar; git commit
时,Git会激活你在一个完全空的提交上配置的任何编辑器(或者更准确地说,一个由空行和一些注释组成的编辑器)剥离出来)。然后,如果退出编辑器而不编写消息,则会得到:
Aborting commit due to empty commit message.
在这种情况下,您处于git merge
的中间(因为git pull
只是git fetch
后跟git merge
)。合并有一些冲突,但你已经解决了。解决冲突后,您可以提交与git commit
的合并,就像您对非合并一样。
虽然这里有一个很大的区别,这就是你的意思:合并提交以非空消息开始。因此当你退出编辑器时,Git会读取文件。 ..它有一个非空消息,它继续进行并进行合并提交。
现在已经太晚了,但在这种情况下停止合并提交的一个技巧是删除非空合并消息,写出一个空文件。这样Git就会看到空消息并中止合并提交。 (合并本身仍然在进行中:要停止 你需要使用git merge --abort
。但这完全是另一个恢复过程,而不是我们现在需要的那个。)
另一个非常有用的事情要知道:在你推动之前,你所有的工作都是本地的。(好吧,除非你让其他人从你那里取来 - 但你不要;如果你做了,你会知道的。)
git push -f
使用-f
(强制标记)和git push
取消安全检查。如果你和其他人都在同一时间推进,你们中的一个会赢,另一个会输。这是悲伤的秘诀。您的正常工作流程将无需-f
:如果您设法参加其中一场比赛,您的git push
将失败,您将不得不再次git fetch && git rebase
,但它应该几乎无痛,并且不会强行推动,因此不会失去任何东西。
git status
在你做任何事之前检查你的位置从来都不错。 git status
命令执行此操作。使用它:它会告诉你你现在在哪个分支。
从上面可以看出,它会说On branch my_branch
,但检查起来不错。它也应该说nothing to commit, working directory clean
。
您可能希望保存刚刚进行的合并提交,因为它具有您的冲突解决方案。要将其保存在易于使用的方法中,请创建一个指向它的新分支或标记名称。 (除非你真的想要推新名称,否则不要推新名称。)
(你甚至可以在某处保存SHA-1哈希,包括将其写下来或复制粘贴到剪贴板或其他任何内容。这可以防止你意外地给出名称,但记住像19cfa3105d2...
这样的长字符串是烦人的,并且是分支和标记名称的用途。此外,没有名称,只要提交保留在您的存储库中,此值仅保持有效:默认情况下,每个提交通过您的reflogs保护至少30天,但没有包含它的分支或标记名称,它最终会过期。)
让我们使用临时分支名称,因为这很简单:
$ git branch saved-merge HEAD
这使得新分支saved-merge
的分支提示是当前(HEAD
)提交。我们可以省略HEAD
,但这只是明确的。
如果你真的不想要这个合并 - 如果你想回到正常的工作流程,通过从当前分支中删除这个提交 - git命令将其从分支中移除git reset
。 / p>
git reset
命令做了太多事情,这使得解释起来很复杂。但是,在这种特殊情况下,我们将使用它同时做两件事:
这意味着在我们执行此操作之前,您应该非常确定工作树中没有任何有价值的东西。换句话说,运行git status
并仔细检查结果。你可能只是做了,但再做一次,它对你有好处。 :-)说真的,偶尔反身git status
是一个好习惯。例如,通过告诉我我仍然处于被遗忘的变形或合并的中间,它已经使我不必多次恢复。在糟糕的旧版本 - 1.5天内,git status
错过了很多;现在好多了。
然后:
$ git reset --hard HEAD^
将删除合并。 (在某些shell中,^
字符是特殊的,备用拼写HEAD~1
更易于使用。)
首先,让我们快速浏览HEAD^
(又名HEAD~1
)。
名称HEAD
始终是指当前提交和/或当前分支。需要分支而不是提交的命令使用分支部分;像git reset
这样需要两者的命令,将它们用于两者。
git reset
命令重置您的HEAD。暗示了这个特定的HEAD:如果您说git reset 1234567
它仍会重置HEAD
,而是重置为1234567
。在我们的例子中,我们说git reset HEAD^
,所以它会重置HEAD,但它重置到的是HEAD^
。
^
后缀是个不错的小技巧。它做的是接受任何提交 - 像1234567
这样的原始数字,或者像HEAD
或my_branch
这样的名称 - 并找到基础提交,然后返回一个提交来自有
特别是,后面没有任何其他内容的^
后缀会返回第一个父。对于普通提交,这是唯一的父母,并且是"之前"州。对于像这样的合并提交,第一个父级仍然是"之前的#34;州;那里只有一个第二个父母,这就是合并的东西。
(旁白:您可以重复^
以进一步退回。名称HEAD^^
返回两次提交,HEAD^^^
返回3,依此类推。{{1} }是这方面的简写:~
表示HEAD~3
。由于HEAD^^^
中有一个^
,HEAD^
只是HEAD~1
的同义词。事实上,你也可以不用HEAD^
。我避免这样做,因为有一个1
符号,它意味着"第二个父母",所以我试着坚持使用未编号的HEAD^2
并编号^
,以避免使用错误的。{/ p>
~
告诉--hard
重新设置索引/登台区域和工作树,同时还要更改分支git reset
指示的内容。因此,当我们将这些全部放在一起时,HEAD
:
git reset --hard HEAD^
)(^
); HEAD
的内置操作的一部分)以匹配步骤1中找到的提交; reset
);和--hard
)。请注意,我们需要完成一次,因为如果我们再次这样做,那将会重新设置另一个提交。 (我们可以使用reflogs,或者我们保存合并提交ID的事实来恢复。基本上,一旦你提交,你几乎总能让事情恢复至少30天,这就是为什么有些人会使用这句话&#34 ;提前和经常提交#.;)
现在您可以使用--hard
,就像通常的工作流程一样。
这很可能会发生合并冲突。
您已在合并提交中解决了这些合并冲突。我们可以使用这些决议! (现在你知道我们为什么要保存了它。)
在git rebase -i
输出中,您显示了两次提交。您可能只在一个上遇到合并冲突,或者您可能会在两者上发生合并冲突。如果你很幸运,他们只会发生一次,保存的合并结果就是你想要的那个文件和/或案例。
(如果你运气不好,你可能想要或者需要重新进行多次合并,在这种情况下你可能无法重复使用已经完成的工作,但是你说你通常会把所有东西都压下来,即,最后你要求只有一个最终提交的东西,而不是一些东西的提交,然后是第二次提交剩下的东西,例如。)
所以,让我们说你执行git log
并将除rebase -i
之外的所有内容更改为pick
并让其运行。 Git将尝试挑选并组合所有提交并将它们应用到上游分支的顶端。其中一些可能会遇到合并冲突,但最后,您需要先前做出的解决方案。所以我们可以作弊,即使有重复的合并冲突,我们也可以采取已知的最终结果,如下所示:
squash
在这里要注意的一件事是,您有时可以通过这种方式进行重复更改。仔细检查最终结果。你应该检查最终结果,即使你不看到合并冲突,因为Git不聪明,有时会复制你不希望它的东西,或者自动合并错误。< / p>
(自动化测试很好,因为他们倾向于捕捉这些案例。)
完成所有操作并对结果完全满意后,使用$ git rebase -i
[edit, write, exit editor]
... rebase begins ...
... conflict occurs on files folder1/foo and folder2/bar ...
$ git checkout saved-merge -- folder1/foo folder2/bar
$ git rebase --continue
... another conflict, on just folder2/bar this time maybe ...
$ git checkout saved-merge -- folder2/bar
$ git rebase --continue
删除临时保存的分支。