压缩线性提交历史时发生冲突
当尝试压缩/修复线性分支时,我仍然需要进行手动合并,这怎么可能?该存储库已从 Subversion 转换。每个冲突要么是“自动挑选失败”,要么是“由于提交消息为空而中止提交”。后者我可以理解,但是 --fixup-empty
或其他东西会很有用。
典型输出:
user@machine:/path (master|REBASE-i)$ git add * && git rebase --continue
[detached HEAD c536940] fixup!
Author: John Doe <[email protected]>
2 files changed, 57 insertions(+), 4 deletions(-)
Automatic cherry-pick failed. After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply 8854a54... >6d5f180 foo
user@machine:/path (master|REBASE-i)$ git st
# Not currently on any branch.
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: filename.ics
#
no changes added to commit (use "git add" and/or "git commit -a")
How is it possible that when trying to squash/fixup a linear branch I still have to do manual merges? The repo has been converted from Subversion. Every conflict is either "Automatic cherry-pick failed" or "Aborting commit due to empty commit message". The latter I could understand, but a --fixup-empty
or something would be useful.
Typical output:
user@machine:/path (master|REBASE-i)$ git add * && git rebase --continue
[detached HEAD c536940] fixup!
Author: John Doe <[email protected]>
2 files changed, 57 insertions(+), 4 deletions(-)
Automatic cherry-pick failed. After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply 8854a54... >6d5f180 foo
user@machine:/path (master|REBASE-i)$ git st
# Not currently on any branch.
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: filename.ics
#
no changes added to commit (use "git add" and/or "git commit -a")
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这些工作:
These work:
这是我的建议,用于实现您拥有某种
--fixup-empty
功能的想法:这会将空提交消息替换为“未知”,并且如果您想在使用
git-svn
转换具有一些空提交消息但无法转换的 Subversion 存储库,因为它失败并显示“由于空提交消息而中止提交”。Here's my suggestion for achieving your idea of having some kind of
--fixup-empty
functionality:This replaces empty commit messages with 'Unknown' and is particularly useful if you want to do a rebase after using
git-svn
to convert a Subversion repository that had some empty commit messages but can't because it fails with "Aborting commit due to empty commit message".我遇到了同样令人费解的问题。我想将更改从一大堆顺序提交转换为单个提交,但是使用带有一系列挤压的交互式变基会令人沮丧地产生合并冲突。这是不久前的事了,我刚才无法在一个最小的例子中重现它。无论如何,我确实知道如何解决它,所以在这里:
我将提出两种不同的方法来完成帖子所要求的操作 - 一种笨重但“不那么可怕”,因为很清楚你要做什么正在做的,第二个是优雅的,仅使用 git 命令,但需要您更加相信自己对 git 的理解。
设置
假设您有一个提交 ID
idA
作为初始状态,将提交 IDidB
作为最终状态,中间还有一堆其他提交。事实上,您想要压缩所有中间提交,这表明您不再关心如何从初始状态到最终状态 - 您只想要一个使您从 idA 点移动的提交指向idB
。假设
idB
对应于当前的HEAD
并说它也是您的master
分支。我们将创建一个名为squashed
的新分支,它具有与master
相同的工作树内容,但在提交 IDidA.
解决方案 1
git checkout master
(您可能已经在master
上)bash
或文件资源管理器将整个工作树复制到其他位置。只需确保忽略.git
目录——这是大多数操作系统上的默认行为。因此,需要明确的是,打开存储库文件夹,选择全部,复制并将其粘贴到桌面上或任何您想要的位置的新文件夹中。git checkout -b scraped idA
创建一个名为squashed
的新分支,并将idA
作为其最新提交,并将其设为当前分支。master
的内容(您在步骤 2 中放在其他位置的内容)粘贴回 repo 文件夹中。如果您的文件资源管理器询问,请告诉它替换所有已更改的文件。git add .
,然后git commit
。您的新提交将包含从idA
到idB
的所有更改。解决方案 2
瞧。当您使用
symbolic-ref
将HEAD移动到squash
分支的尖端时,工作树状态和新HEAD位置之间的差异集将被计算并暂存。我想回应一些人表示这是“危险”、“低级”
git
hack 的担忧。我将尽力解释它的工作原理,让您感到轻松自在。您的.git
文件夹是所有版本化文件内容所在的位置。其中两个文件夹是objects
和refs
。refs
文件夹包含告诉 git 您的分支名称以及它们对应的提交的文件。例如,如果您打开.git/refs/heads/master
,您将看到master
最新提交的提交 ID。objects
文件夹包含一堆文件,这些文件位于具有两个字符的十六进制名称的子文件夹中。对象可以是多种不同的东西,包括补丁、提交和整个文件。在.git
文件夹的顶层还有一个index
文件。 这是一篇关于索引文件内容的精彩文章。对于当前的讨论,您需要了解的是,索引文件告诉您哪个对象(在objects
文件夹中)对应于当前分支和提交上每个文件的最新提交版本。在此背景下,解决方案的作用如下:
symbolic-ref
命令只是“突然”告诉
git
您位于squash
分支而不是master
分支不接触工作树。这意味着您的文件都对应于您了解和喜爱的master
状态,但是git
将此视为带有一堆未提交的idA
更改(即,index
文件指定所有文件的当前签出版本是idA
的版本,这是衡量工作树更改的参考)。事实上,正是这些变化让您声明idB
。这也正是解决方案 1 所做的。在这种情况下,由于symbolic-ref
的实现方式,所有上述更改都已上演(即git add
-ed),因此您所要做的就是是git commit
。这没有什么“危险”,因为它不会改变master
或git
正在跟踪的任何objects
。您刚刚在refs
文件夹中创建了一个名为squashed
的新文件,并在objects
文件夹中创建了一个与该新组合提交相对应的新文件。您的master
就在您离开的地方,您只是有一个名为squashed
的新分支,其内容相同,但提交历史记录较短。如果您不确定发生了什么,请放心,您可以git checkout master
,它会在您离开的地方。如果您想采用
squashed
作为新的master
,您可以继续,然后您将拥有旧分支,其中所有多余的提交都保存为
old -master
和master
正是您想要的。希望这有帮助!
I ran into this same puzzling question. I wanted to turn the changes from a whole bunch of sequential commits into a single commit, but using interactive rebasing with a sequence of squashes was frustratingly giving merge conflicts. This was a while back, and I was not able to reproduce it in a minimal example just now. In any case, I do know how to solve it, so here you go:
I'll present two different ways of doing what the post asked -- one that is clunky but "less scary," because it is clear exactly what you're doing, and a second that is elegant and uses only
git
commands, but requires you to trust your understanding ofgit
a bit more.Setup
Let's suppose you have a commit ID
idA
as the initial state and commit IDidB
as the final state, with a bunch of other commits in between. The fact that you want to squash all the in-between commits says that you no longer care how you got from the initial state to the final state -- you just want a commit that moves you from pointidA
to pointidB
.Let's suppose that
idB
corresponds to the currentHEAD
and say that it's also yourmaster
branch. We're going to make a new branch calledsquashed
that has the same working tree contents asmaster
, but has just one single commit after commit IDidA
.Solution 1
git checkout master
(you might already be onmaster
)bash
or your file explorer to copy the whole working tree to somewhere else. Just make sure you leave out the.git
directory -- which is the default behavior on most operating systems. So, to be clear -- open the repo folder, select all, copy, and paste it to some new folder, on your desktop or wherever you want.git checkout -b squashed idA
to make a new branch calledsquashed
withidA
as its latest commit, and make it your current branch.master
(that you put somewhere else in Step 2) back into the repo folder. If your file explorer asks, tell it to replace all the files that have changed.git add .
and thengit commit
. Your new commit will have all the changes that take you fromidA
toidB
.Solution 2
And voilà. When you use
symbolic-ref
to move the HEAD to the tip of thesquash
branch, the set of diffs between the working tree state and the new HEAD location are computed and staged.I want to respond to the concern that some have expressed that this is a "dangerous", "low-level"
git
hack. I will try to explain just enough about how it works that you can feel at ease. Your.git
folder is where all the versioned file contents live. Two of the folders in it areobjects
, andrefs
. Therefs
folder contains files that tell git the names of your branches and what commits they correspond to. So if you open up.git/refs/heads/master
, for example, you'll see the commit ID of the latest commit tomaster
. Theobjects
folder contains a bunch of files that are in subfolders with two-character hex names. The objects can be several different things including patches, commits, and whole files. Also in the top level of the.git
folder is anindex
file. This is a great post on the contents of the index file. What you need to know for the present discussion is that the index file tells you what object (in theobjects
folder) corresponds to the latest committed version of each file on the current branch and commit.Against that background, here is what the solution does: the
symbolic-ref
command simply tellsgit
"suddenly" that you're on thesquash
branch instead of themaster
branch without touching the working tree. That means that your files all correspond to the state ofmaster
you know and love, butgit
is seeing this asidA
with a bunch of uncommitted changes (that is, theindex
file specifies that the current checked out version of all the files are those ofidA
, and this is the reference against which working tree changes are measured). The exact changes, in fact, that get you to stateidB
. This is exactly what Solution 1 does as well. In this case, because of the waysymbolic-ref
is implemented, all of the said changes are already staged (i.e.git add
-ed), so all you have to do isgit commit
. There is nothing "dangerous" about this, because it doesn't changemaster
or any of theobjects
thatgit
is keeping track of. You've just created a new file in therefs
folder calledsquashed
, and one new file in theobjects
folder corresponding to that new combined commit. Yourmaster
is right there where you left it, you just have a new branch calledsquashed
with the same contents but a shorter commit history. If you aren't sure what's going on, rest assured you cangit checkout master
and it will be right there where you left it.If you want to adopt
squashed
as your newmaster
, you can go ahead andAnd then you'll have the old branch with all the superfluous commits saved as
old-master
, andmaster
will be just what you wanted.Hope this helps!