git 使两个分支相同
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36320517/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Making two branches identical
提问by zmbq
I have two branches - A and B. B was created from A.
我有两个分支——A 和 B。B 是从 A 创建的。
Work on both branches continued in parallel. Work on branch A was bad (resulting in a non-working version) while work on branch B was good. During this time branch B was sometimes merged into branch A (but not the other way around).
两个分支的工作并行进行。在分支 A 上的工作很糟糕(导致无法工作的版本),而在分支 B 上的工作很好。在此期间,分支 B 有时会合并到分支 A(但不是相反)。
Now I want to make branch A identical to branch B. I can't use git revert because I need to revert too many commits - I only want to revert the commits that were done on branch A but not as a result of merging branch B.
现在我想让分支 A 与分支 B 相同。我不能使用 git revert,因为我需要还原太多提交 - 我只想还原在分支 A 上完成的提交,但不是合并分支 B 的结果.
The solution I found was to clone branch B into a another folder, delete all the files from the working folder of branch A, copy the files from the temp branch B folder and add all untracked files.
我找到的解决方案是将分支 B 克隆到另一个文件夹中,从分支 A 的工作文件夹中删除所有文件,从临时分支 B 文件夹中复制文件并添加所有未跟踪的文件。
Is there a git command that does the same thing? Some git revert switch I missed?
是否有执行相同操作的 git 命令?我错过了一些 git revert 开关?
回答by torek
There are a lot of ways to do this and which one you should use depends on what result you want, and particular what you and anyone collaborating with you (if this is a shared repository) expect to see in the future.
有很多方法可以做到这一点,你应该使用哪一种取决于你想要什么结果,特别是你和任何与你合作的人(如果这是一个共享存储库)希望在未来看到什么。
The three main ways to do this:
执行此操作的三种主要方法:
- Don't bother. Abandon branch
A
, make a newA2
, and use that. Use
git reset
or equivalent to re-pointA
elsewhere.Methods 1 and 2 are effectively the same in the long run. For instance, suppose you start by abandoning
A
. Everyone develops onA2
instead for a while. Then, once everyone is usingA2
, you delete the nameA
entirely. Now thatA
is gone, you can even renameA2
toA
(everyone else using it will have to do the same rename, of course). At this point, what, if anything, looks different between the case where you used method 1 and the case where you used method 2? (There is one place you maystill be able to see a difference, depending on how long your "long run" is and when you expire old reflogs.)- Make a merge, using a special strategy (see "alternative merge strategies" below). What you want here is
-s theirs
, but it doesn't existand you must fake it.
- 不要打扰。放弃 branch
A
,创建一个 newA2
,然后使用它。 使用
git reset
或等效于重新指向A
别处。从长远来看,方法 1 和 2 实际上是相同的。例如,假设您从放弃
A
. 每个人都发展A2
了一段时间。然后,一旦每个人都在使用A2
,您就可以A
完全删除该名称。现在A
已经没有了,您甚至可以重命名A2
为A
(当然,其他使用它的人都必须进行相同的重命名)。在这一点上,使用方法 1 的情况和使用方法 2 的情况有什么不同?(有一个地方,你可能仍然能够看到的差异,这取决于多久你的“长远”是,当你丢弃旧reflogs。)- 使用特殊策略进行合并(请参阅下面的“替代合并策略”)。你想要的是
-s theirs
,但它不存在,你必须伪造它。
Side note: where a branch is "created from" is basically irrelevant: git doesn't keep track of such things and in general it is not possible to discover it later, so it probably should not matter to you either. (If it really does matter to you, you can impose a few restrictions on how you manipulate the branch, or mark its "start point" with a tag, so that you can recover this information mechanically later. But this is often a sign that you are doing something wrong in the first place—or at the least, expecting something of git that git does not provide. See also footnote 1 below.)
旁注:从哪里“创建”分支基本上是无关紧要的:git 不会跟踪这些事情,一般来说以后不可能发现它,所以它对你来说也可能无关紧要。(如果它真的对你很重要,你可以对你如何操作分支施加一些限制,或者用标签标记它的“起点”,以便你以后可以机械地恢复这些信息。但这通常表明你一开始就做错了——或者至少,期待 git 没有提供的东西。另见下面的脚注 1。)
Definition of branch (see also What exactly do we mean by branch?)
分支的定义(另请参阅我们所说的分支究竟是什么意思?)
A branch—or more precisely, a branch name—in git is simply a pointer to some specific commit within the commit graph. This is also true of other references like tag names. What makes a branch special, as compared with a tag for instance, is that when you are ona branch and make a newcommit, git automatically updates the branch name so that it now points to the new commit.
git 中的一个分支——或者更准确地说,一个分支名称——只是一个指向提交图中某个特定提交的指针。其他引用(如标记名称)也是如此。例如,与标记相比,分支的特殊之处在于,当您在分支上并进行新提交时,git 会自动更新分支名称,以便它现在指向新提交。
For instance, suppose we have a commit graph that looks something like this, where o
nodes (and the one marked *
as well) represent commits and A
and B
are branch-names:
例如,假设我们有一个看起来像这样的提交图,其中o
节点(以及标记的那个*
)代表提交,A
并且B
是分支名称:
o <- * <- o <- o <- o <- o <-- A
\
o <- o <- o <-- B
A
and B
each point to the tip-most commit of a branch, or more precisely, the branch data structureformed by starting at some commit and working back through all reachable commits, with each commit pointing to some parent commit(s).
A
并且B
各点到尖端最提交分支的,或更精确地,分支数据结构由起始于一些承诺和工作通过所有可达提交背面形成的,与每个提交指向一些父提交(S)。
If you use git checkout B
so that you're on branch B
, and make a new commit, the new commit is set up with the previous tip of B
as its (single) parent, and git changes the ID stored under the branch name B
so that B
points to the new tip-most commit:
如果您使用git checkout B
以便您在 branch 上B
并进行新提交,则新提交将使用前一个提示B
作为其(单个)父项设置,并且 git 更改存储在分支名称下的 ID,B
以便B
指向新的最提示提交:
o <- * <- o <- o <- o <- o <-- A
\
o <- o <- o <- o <-- B
The commit marked *
is the merge baseof the two branch tips. This commit, and all earlier commits, is on bothbranches.1The merge base matters for, well, merging (duh :-) ), but also for things like git cherry
and other release-management type operations.
标记的提交*
是两个分支提示的合并基础。此提交以及所有较早的提交都在两个分支上。1合并基础对于合并(duh :-) )很重要,但对于诸如git cherry
和其他发布管理类型的操作也很重要。
You mention that branch B
was occasionally merged back into A
:
您提到分支B
偶尔会合并回A
:
git checkout A; git merge B
This makes a new merge commit, which is a commit with two2parents. The firstparent is the previous tip of the current branch, i.e., the previous tip of A
, and the second parent is the named commit, i.e., the tip-most commit of branch B
. Redrawing the above a bit more compactly (but adding some more -
s to make the o
s line up better), we start with:
这会生成一个新的合并提交,这是一个具有两个2 个父项的提交。第一个父级是当前分支的前一个提示,即 的前一个提示A
,第二个父级是命名提交,即 branch 的最尖端提交B
。将上面的重绘更紧凑(但添加更多-
s 以使o
s 排列得更好),我们开始:
o--*--o--o--o--o <-- A
\
o---o--o---o <-- B
and end up with:
并最终得到:
o--*--o--o--o--o--o <-- A
\ /
o---o--o---* <-- B
We move the *
to the new the merge-base of A
and B
(which is in fact the tip of B
). Presumably we then add some more commits to B
and maybe merge a few more times:
我们将 移动*
到新的合并基础A
和B
(实际上是 的尖端B
)。大概我们然后添加一些更多的提交,B
并可能再合并几次:
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
What git does by default with git merge <thing>
git 默认做什么 git merge <thing>
To make a merge, you check out some branch (A
in these cases) and then run git merge
and give it at least one argument, typically another branch name like B
. The merge command starts by turning the name into a commit ID. A branch name turns into the ID of the tip-most commit on the branch.
要进行合并,您需要检查某个分支(A
在这些情况下),然后运行git merge
并为其提供至少一个参数,通常是另一个分支名称,例如B
. 合并命令首先将名称转换为提交 ID。分支名称变成分支上最尖端提交的 ID。
Next, git merge
finds the merge base. These are the commits we have been marking with *
all along. The technical definition of the merge base is the Lowest Common Ancestor in the commit graph (and in some cases there may be more than one) but we'll just go with "the commit marked *
" here for simplicity.
接下来,git merge
找到合并基。这些是我们一直在标记的提交*
。合并基础的技术定义是提交图中的最低公共祖先(在某些情况下可能不止一个),但*
为了简单起见,我们将在这里使用“标记的提交”。
Last, for ordinary merges, git runs two git diff
commands.3The first git diff
compares commit *
against the HEAD
commit, i.e., the tip of the current branch. The second diff compares commit *
against the argument commit, i.e., the tip of the other branch (you can name a specific commit and it need not be the tip of a branch, but in our case we're merging B
into A
so we get those two branch-tip commits).
最后,对于普通合并,git 运行两个git diff
命令。3第一个git diff
将提交*
与HEAD
提交进行比较,即当前分支的尖端。第二个差异进行比较提交*
反对论据提交,即另一个分支的末端(你能说出具体的承诺,它不一定是一个分支的末端,但在我们的例子中,我们正在合并B
成A
这样我们就得到这两个分支提示提交)。
When git finds some file modified, as compared to the merge-base version, in bothbranches, git tries to combine those changes in a semi-smart (but not really smart) way: if both changes add the same text to the same region, git keeps one copy of the addition. If both changes delete the same text in the same region, git deletes that text just once. If both changes modify text in the same region, you get a conflict, unless the modifications match exactly (then you get one copy of the modifications). If one side makes one change and the other side makes a different change, but the changes seem not to overlap, git takes both changes. This is the essence of a three-way merge.
当 git 发现一些文件被修改时,与合并基础版本相比,在两个分支中,git 尝试以半智能(但不是真正智能)的方式组合这些更改:如果两个更改都将相同的文本添加到同一区域, git 保留一份添加的副本。如果两个更改都删除了同一区域中的相同文本,则 git 只会删除该文本一次。如果两个更改都修改了同一区域中的文本,则会发生冲突,除非修改完全匹配(然后您将获得一份修改副本)。如果一侧进行了一项更改而另一侧进行了不同的更改,但这些更改似乎没有重叠,则 git 会同时进行这两项更改。这就是三路合并的本质。
Last, assuming all goes well, git makes a new commit that has two (or more as we already noted in footnote 2) parents. The work-treeassociated with this new commit is the one git came up with when it did its three-way merge.
最后,假设一切顺利,git 会进行一个新的提交,它有两个(或更多,正如我们在脚注 2 中已经指出的那样)。与这个新提交相关的工作树是 git 在进行三向合并时提出的。
Alternative merge strategies
替代合并策略
While git merge
's default recursive
strategy has -X
options ours
and theirs
, they do not do what we want here. These simply say that in the case of a conflict, git should automatically resolve that conflict by choosing "our change" (-X ours
) or "their change" (-X theirs
).
虽然git merge
的默认recursive
策略有-X
选项ours
和theirs
,但它们并没有做我们在这里想要的。这些只是说在发生冲突的情况下,git 应该通过选择“我们的更改”(-X ours
)或“他们的更改”(-X theirs
)来自动解决该冲突。
The merge
command has another strategy entirely, -s ours
: this one says that instead of diffing the merge base against the two commits, just use our source tree. In other words, if we're on branch A
and we run git merge -s ours B
, git will make a new merge commit with the second parent being the tip of branch B
, but the source tree matching the version in the previous tip of branch A
. That is, the code for the new commit will exactly match the code for its parent.
该merge
命令完全有另一种策略,-s ours
:这个策略说,不要将合并基础与两个提交进行比较,只需使用我们的源树。换句话说,如果我们在 branch 上A
运行git merge -s ours B
,git 将进行一个新的合并提交,第二个父级是 branch 的尖端B
,但源树与前一个 branch 尖端中的版本匹配A
。也就是说,新提交的代码将与其父提交的代码完全匹配。
As outlined in this other answer, there are a number of ways to force git to effectively implement -s theirs
. I think the simplest to explainis this sequence:
正如其他答案中所述,有多种方法可以强制 git 有效实施-s theirs
。我认为最简单的解释是这个序列:
git checkout A
git merge --no-commit -s ours B
git rm -rf . # make sure you are at the top level!
git checkout B -- .
git commit
The first step is to ensure that we are on branch A
, as usual. The second is to fire up a merge, but avoid committing the result yet (--no-commit
). To make the merge easier for git—this is not needed, it just makes things faster and quieter—we use -s ours
so that git can skip the diff steps entirely and we avoid all merge conflict complaints.
第一步是确保我们A
像往常一样在 branch 上。第二种是启动合并,但要避免提交结果 ( --no-commit
)。为了让 git 的合并更容易——这不是必需的,它只是让事情更快更安静——我们使用-s ours
这样 git 可以完全跳过差异步骤,我们避免所有合并冲突的投诉。
At this point we get to the meat of the trick. First we remove the entire merge result, since it is actually worthless: we do not want the work-tree from the tip commit of A
, but rather the one from the tip of B
. Then we check out every file from the tip of B
, making it ready to commit.
在这一点上,我们得到了诀窍。首先,我们移除整个合并结果,因为它实际上毫无价值:我们不想要来自 的提示提交的工作树A
,而是来自 的提示的工作树B
。然后我们检查从 尖端开始的每个文件B
,使其准备好提交。
Last, we commit the new merge, which has as its first parent the old tip of A
and as its second parent the tip of B
, but has the treefrom commit B
.
最后,我们提交新的合并,它的第一个父级是 的旧尖端,A
第二个父级是 的尖端B
,但有来自 commit的树B
。
If the graph just before the commit was:
如果提交之前的图表是:
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
then the new graph is now:
那么新图现在是:
...---o--...--o--o--o <-- A
/ / /
...-o--...--o--o--* <-- B
The new merge-base is the tip of B
as usual, and from the perspective of the commit graph, this merge looks exactly like any other merge. What's unusual is that the source treefor the new merge at the tip of A
exactly matches the source tree for the tip of B
.
新的合并基础是B
像往常一样的提示,从提交图的角度来看,这次合并看起来与任何其他合并完全一样。什么是不寻常的是,源代码树在尖端的新的合并A
完全源代码树相匹配的尖端B
。
1In this particular case, since the two branches have remained independent (never been merged), it's also probablythe point where one of the two branches was created (or maybe even where both were created), although you can't prove that at this point (because someone may have used git reset
or various other tricks to move the branch labels around in the past). As soon as we start merging, though, the merge base is clearly no longer the starting point, and a sensible starting point gets more difficult to locate.
1在这种特殊情况下,由于两个分支一直保持独立(从未合并),这也可能是创建两个分支之一(或者甚至创建两个分支的地方)的点,尽管您无法证明在这一点(因为过去可能有人使用过git reset
或其他各种技巧来移动分支标签)。然而,一旦我们开始合并,合并基地显然不再是起点,一个合理的起点变得更加难以定位。
2Technically, a merge commit is any commit with two or moreparents. Git calls merges with more than two parents "octopus merges". In my experience, they are not common except in the git repository for git itself, and in the end, they achieve the same thing as multiple ordinary two-parent merges.
2从技术上讲,合并提交是具有两个或多个父项的任何提交。Git 调用与两个以上父级合并的“章鱼合并”。根据我的经验,除了在 git 本身的 git 存储库中,它们并不常见,最终,它们实现了与多个普通双父合并相同的事情。
3The diffs are usually done internally to some extent, rather than running actual commands. This allows a lot of short-cut optimizations. It also means that if you write a custom merge driver, that custom merge driver is not run unless git finds that the file is modified in both diffs. If it's only modified in one of the two diffs, the default merge simply takes the modified one.
3差异通常在某种程度上在内部完成,而不是运行实际命令。这允许许多快捷优化。这也意味着如果您编写自定义合并驱动程序,除非 git 发现文件在两个差异中都被修改,否则不会运行该自定义合并驱动程序。如果它仅在两个差异之一中进行了修改,则默认合并仅采用修改后的一个。
回答by Ievgen
Checkout your target branch:
签出您的目标分支:
git checkout A;
To remove everything in branch A and make it to be the same as B:
要删除分支 A 中的所有内容并使其与 B 相同:
git reset B --hard;
If you need to discard all your local changes (kind of revert, but doesn't mess up git like git revert
):
如果您需要丢弃所有本地更改(有点还原,但不会像 git 那样搞砸git revert
):
git reset HEAD --hard;
When you are done, don't forget to update your remote branch (use --force
or -f
flag in case you need to override history).
完成后,不要忘记更新您的远程分支(如果您需要覆盖历史记录,请使用--force
或-f
标记)。
git push origin A -f;
回答by Pero122
Here is a last resort idea (it's dirty and not a git way of doing things)...
这是最后的手段(它很脏,而不是一种 git 做事方式)......
- Go to branch B (git checkout B).
- Copy all the content of that branch(ctrl + c)
- Go to branch A (git checkout A)
- Delete everything from branch A (select all with mouse and delete)
- Copy all the content from the branch B in the folder where all the branch A stuff was. (ctrl + v)
- Stage all the new changes (git add .)
- Commit the staged changes (git commit -m "Branch A is now the same as B")
- 转到分支 B(git checkout B)。
- 复制该分支的所有内容(ctrl + c)
- 转到分支 A (git checkout A)
- 删除分支 A 中的所有内容(用鼠标全选并删除)
- 将分支 B 中的所有内容复制到分支 A 的所有内容所在的文件夹中。(ctrl + v)
- 暂存所有新更改 (git add .)
- 提交阶段性更改(git commit -m“分支 A 现在与 B 相同”)
回答by Kolob Canyon
The answers in this post are waytoo complicated for my taste.
在这篇文章的回答方式太复杂,我的口味。
Here's what I did:
这是我所做的:
Assuming you're on the branch you want to change
假设您在要更改的分支上
Step 1 - Make the branch the same as master
第 1 步 - 使分支与主分支相同
git reset origin/master --hard
git reset origin/master --hard
Step 2 - Pull the branch that you want to be identical to
第 2 步 - 拉出要与之相同的分支
git pull origin branch-i-want-to-be-identical-to
git pull origin branch-i-want-to-be-identical-to
Step 3 (optional) - Force push it to remote to overwrite the remote history
第 3 步(可选)- 强制将其推送到远程以覆盖远程历史记录
git push --force
git push --force
回答by Akash
This is what worked for me with TortoiseGit
这对我有用 TortoiseGit
STEPS:
脚步:
EXPORTthe
branch
as ZIP you want to achieve identicality with to your preferred location:UNZIPthe archive/zip-file.
COPY the
.git
folder from the original-code-base to thisunzipped-folder
(ENSUREthetarget branch
is selected in the original-code-base)git add .
git commit -m "Achieving identicality with abc-branch"
git push
to push changes to repo. (--up-stream
if required)
出口的
branch
为ZIP要使用您的首选地点,以实现同一性:UNZIP存档/ zip文件。
复制
.git
从原始代码碱基夹到这个unzipped-folder
(ENSURE的target branch
在原始代码库中选择)git add .
git commit -m "Achieving identicality with abc-branch"
git push
将更改推送到 repo。(--up-stream
如果需要)
TortoiseGit : https://tortoisegit.org
TortoiseGit : https://tortoisegit.org
Good Luck...
祝你好运...
回答by Sergey Sahakyan
here is the nice way I am using to do this for me. The idea is to get diff of A and B, commit that diff, revert that to create opposite commit and than cherry-pick that last revert commit to A
这是我用来为我做这件事的好方法。这个想法是获取 A 和 B 的差异,提交该差异,将其还原以创建相反的提交,而不是最后一次将提交还原到 A
here is commands
这是命令
git checkout B
git checkout -b B_tmp
git merge --squash A
git commit -a -m 'A-B diff'
git revert B_tmp
git checkout A
git cherry-pick B_tmp
git branch -d B_tmp
and here you are, A is the same as B, and you have only one commit in A history that resets to be the same as B branch.
在这里,A 与 B 相同,并且您在 A 历史记录中只有一次提交重置为与 B 分支相同。
Check the bash script, you chave to change your repo directory and branch names, those are not variable yet, but can be easily managed
https://github.com/serg94/git_bash/blob/master/reset_dev
检查 bash 脚本,您必须更改 repo 目录和分支名称,这些还不是可变的,但可以轻松管理
https://github.com/serg94/git_bash/blob/master/reset_dev