git 如何更改分支的起点?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38427050/
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
How to change the starting point of a branch?
提问by Daniel Kobe
Usually I create a branch by running a command like git checkout -b [branch-name] [starting-branch]
. In one case, I forgot to include the starting-branch
, and now I want to correct it. How do I do so after the branch has already been created?
通常我通过运行类似的命令来创建一个分支git checkout -b [branch-name] [starting-branch]
。在一种情况下,我忘记包含starting-branch
,现在我想更正它。分支已经创建后我该怎么做?
回答by torek
The short answer is that once you have some commits, you want to git rebase
them, using the long form of git rebase
: git rebase --onto newbaseupstream
. To find out how to identify each of these, see the (very) long answer below. (Unfortunately, it got a bit out of hand and I do not have time to shorten it.)
简短的回答是,一旦你有一些提交,要git rebase
他们使用的长形式git rebase
:。要了解如何识别每一个,请参阅下面的(非常)长答案。(不幸的是,它有点失控,我没有时间缩短它。)git rebase --onto newbaseupstream
The problem you have here is that in Git, branches don't havea "starting point"—at least, not in any useful way.
你有这里的问题是,在Git中,分支机构不具有-至少,不能以任何有用的方式是“起点”。
The term "branch", in Git, is ambiguous
Git 中的“分支”一词是不明确的
The first issue here is that, in Git, the word "branch" has at least two distinct meanings. Usually, when we talk loosely about "the branch", it's clear from context whether we mean the branch name—the thing that's a word like master
or develop
or feature-X
—or the thing that I call "branch ancestry" or "branch structure", or more informally, a "DAGlet".1See also What exactly do we mean by "branch"?
这里的第一个问题是,在 Git 中,“分支”这个词至少有两个不同的含义。通常,当我们松散地谈论“分支”时,从上下文可以清楚地看出我们是指分支名称——像master
或develop
或这样的词feature-X
——或者我称之为“分支祖先”或“分支结构”的东西,还是更多非正式地,一个“DAGlet”。1另请参阅“分支”究竟是什么意思?
In this particular case, unfortunately, you mean both of these, at the same time.
不幸的是,在这种特殊情况下,您同时指的是这两种情况。
1The term DAG is short for Directed Acyclic Graph, which is what the commit graph is: a set of vertices or nodes, and directional (from child to parent) edges, such that there are no cycles through the directed edges from any node back to itself. To this I simply add the "-let" diminutive suffix. The resulting word has a happy resemblance to the word aglet, plus a certain assonancewith the word "dagger", making it sound slightly dangerous: "Is this a DAGlet which I see before me?"
1术语 DAG 是有向无环图的缩写,这就是提交图:一组顶点或节点,以及方向(从子节点到父节点)边,这样从任何节点返回的有向边都没有循环给自己。为此,我只是添加了“-let”小后缀。由此产生的词与aglet一词很相似,再加上与“dagger”这个词有一定的谐音,听起来有点危险:“这是我在我面前看到的 DAGlet 吗?”
Draw your commit graph
绘制你的提交图
Whenever you need to grapple with these issues, it helps to draw a graph of what you have now, or at least some useful subset of what you have now. There are of course many ways to draw this (see that linked question for several options, including some bad ones :-) ), but in plain text in a StackOverflow answer, I generally draw them like this:
每当您需要解决这些问题时,绘制一张您现在拥有的或至少是您现在拥有的有用子集的图表会很有帮助。当然有很多方法可以绘制这个(请参阅链接问题的几个选项,包括一些不好的:-)),但是在 StackOverflow 答案中的纯文本中,我通常这样绘制它们:
...--o--o--o <-- master
\
o--o--o--o <-- develop
The round o
nodes represent commits, and the branch names master
and develop
point to one specific tipcommit on each branch.
圆形o
节点表示commits和分支名称,master
并develop
指向每个分支上的一个特定提示提交。
In Git, every commit points back to its parent commit(s), and this is how Git forms branch structures. By "branch structures", I mean here particular subsets of the overall ancestry part of the graph, or what I call the DAGlets. The name master
points to the tip-most commit of the master
branch, and that commit points back (leftward) to another commit that is the previous commit on the branch, and that commit points leftward again, and so on.
在 Git 中,每个提交都指向其父提交,这就是 Git 形成分支结构的方式。通过“分支结构”,我在这里指的是图整体祖先部分的特定子集,或者我所说的 DAGlets。该名称master
指向master
分支的最尖端提交,该提交指向回(向左)另一个提交,即分支上的前一个提交,该提交再次指向左,依此类推。
When we need to talk about specific commits within this graph, we can use their actual names, which are the big ugly 40-character hashes that identify each Git object. Those are really clumsy though, so what I do here is replace the little round o
s with uppercase letters:
当我们需要讨论此图中的特定提交时,我们可以使用它们的实际名称,即标识每个 Git 对象的丑陋的 40 个字符的大哈希值。不过这些真的很笨拙,所以我在这里做的是o
用大写字母替换小圆s:
...--A--B--C <-- master
\
D--E--F--G <-- develop
and now it's easy to say, e.g., that the name master
points to commit C
, and C
points to B
, and B
points to A
, which points back to more history that we don't really care about and hence just left as ...
.
现在很容易说,例如,名称master
指向 commit C
,C
指向B
,B
指向A
,这指向更多我们并不真正关心的历史,因此只保留为...
。
Where does a branch begin?
分支从哪里开始?
Now, it's perfectly obvious, to you and me, based on this graph drawing, that branch develop
, whose tip commit is G
, starts at commit D
. But it's not obvious to Git—and if we draw the same graph a little differently, it may be less obvious to you and me too. For instance, look at this drawing:
现在,对你和我来说,非常明显,基于这张图,那个分支develop
,其提示 commit 是G
,从 commit 开始D
。但这对 Git 来说并不明显——如果我们以稍微不同的方式绘制同一张图,对你我来说也可能不那么明显。例如,看看这张图:
o <-- X
/
...--o--o--o--o--o--o <-- Y
Obviously branch X
has just the one commit and the main line is Y
, right? But let's put some letters in:
显然分支X
只有一次提交,主线是Y
,对吧?但是让我们输入一些字母:
C <-- X
/
...--A--B--D--E--F--G <-- Y
and then move Y
down a line:
然后Y
向下移动一行:
C <-- X
/
...--A--B
\
D--E--F--G <-- Y
and then look what happens if we move C
down to the main line, and realize that X
is master
and Y
is develop
? Which branch iscommit B
on after all?
然后看看如果我们C
向下移动到主线会发生什么,并意识到这X
是master
和Y
是develop
?毕竟是B
在哪个分支上提交的?
In Git, commits may be on manybranches simultaneously; DAGlets are up to you
在 Git 中,提交可能同时发生在多个分支上;DAGlets 由你决定
Git's answer to this dilemma is that commits A
and B
are on bothbranches. The beginning of branch X
is way off to the left, in the ...
part. But so is the beginning of branch Y
.As far as Git is concerned, a branch "starts" at whatever root commit(s) it can find in the graph.
Git 对这个困境的回答是 commitsA
并且B
在两个分支上。分支的起点X
在左侧,在...
零件中。 但是分支的开始也是如此Y
。就 Git 而言,分支“开始”于它在图中可以找到的任何根提交。
This is important to keep in mind in general. Git has no real concept of where a branch "started", so we wind up having to give it extra information. Sometimes that information is implied, and sometimes it is explicit. It's also important, in general, to remember that commits are often on manybranches—so instead of specifying branches, we usually specify commits.
记住这一点很重要。Git 对分支“开始”的位置没有真正的概念,因此我们最终不得不为其提供额外的信息。有时这些信息是隐含的,有时是明确的。一般来说,记住提交通常在许多分支上也很重要——所以我们通常不指定分支,而是指定提交。
We just often use branch names to do this. But if we give Git just a branch name, and tell it to find all the ancestors of the tip commit of that branch, Git goes all the way back in history.
我们只是经常使用分支名称来做到这一点。但是,如果我们只给 Git 一个分支名称,并告诉它查找该分支的提示提交的所有祖先,那么 Git 会一路追溯到历史。
In your case, if you write the name develop
and ask Git to select that commit and its ancestors, you get commits D-E-F-G
(which you wanted) andcommit B
, andcommit A
, and so on (which you didn't). The trick, then, is to somehow identify which commits you don'twant, along with which commits you do.
在你的情况,如果你写的名字develop
,并要求GIT中选择承诺和它的祖先,你会得到提交D-E-F-G
(你想要的)和承诺B
,并承诺A
,等(你没有)。 那么,诀窍就是以某种方式确定您不想要哪些提交,以及您做了哪些提交。
Normally we use the two-dot X..Y
syntax
通常我们使用两点X..Y
语法
With most Git commands, when we want to select some particular DAGlet, we use the two-dot syntax described in gitrevisions, such as master..develop
. Most2Git commands that work on multiple commits treat this as: "Select all commits starting from the tip of the develop
branch, but then subtract from that set, the set of all commits starting from the tip of the master
branch." Look back at our graph drawing of master
and develop
: this says "do take commits starting from G
and working backwards"—which gets us too many, since it includes commits B
and A
and earlier—"but excludecommits starting from C
and working backwards." It's that excludepart that gets us what we want.
对于大多数 Git 命令,当我们想要选择某些特定的 DAGlet 时,我们使用gitrevisions 中描述的两点语法,例如master..develop
. 大多数2Git命令上多次提交的工作将此视为:“选择从前端开始的所有提交develop
的分支,但随后从该组,一组从前端开始的所有提交的减master
。分支” 在我们的图形回头看图纸master
和develop
:这个说:“你做的一切,从提交开始G
和工作倒退” -这会让我们太多,因为它包含提交B
与A
和earlier-“但不包括从开始的提交C
和工作倒退。” 就是排除让我们得到我们想要的部分。
Hence, writing master..develop
is how we name commits D-E-F-G
, and have Git compute that automatically for us, without having to first sit down and draw out a big chunk of the graph.
因此,写作master..develop
是我们命名 commits 的方式D-E-F-G
,并且让 Git 自动为我们计算,而不必先坐下来绘制一大块图表。
2Two notable exceptions are git rebase
, which is in its own section just below, and git diff
. The git diff
command treats X..Y
as simply meaning X Y
, i.e., it effectively just ignores the two dots entirely. Note that this has a very different effect than set subtraction: in our case, git diff master..develop
simply diffs the tree for commit C
against the tree for commit G
, even though master..develop
never has commit C
in the first set.
2两个值得注意的例外是git rebase
,它在下面的单独部分中,和git diff
。该git diff
命令将其X..Y
视为简单的含义X Y
,即,它实际上只是完全忽略了两个点。请注意,这与集合减法的效果非常不同:在我们的例子中,git diff master..develop
只需将提交C
树与提交树进行比较G
,即使在第一组中master..develop
从未提交C
过。
In other words, mathematically speaking, master..develop
is normally ancestors(develop) - ancestors(master)
, where the ancestors
function includes the specified commit, i.e., is testing ≤ rather than just <. Note that ancestors(develop)
does not include commit C
at all. The set subtraction operation simply ignores the presence of C
in the set ancestors(master)
. But when you feed this to git diff
, it does notignore C
: it does not diff, say, B
against G
. While that might be a reasonable thing to do, git diff
instead steals the three-dot master...develop
syntax to accomplish this.
换句话说,从数学上讲,master..develop
通常是ancestors(develop) - ancestors(master)
,其中ancestors
函数包括指定的提交,即测试 ≤ 而不仅仅是 <。请注意,这ancestors(develop)
根本不包括提交C
。集合减法操作简单地忽略C
集合中的ancestors(master)
。但是,当您将其提供给 时git diff
,它不会忽略C
:它不会区分,例如,B
反对G
。虽然这可能是做一些合理的事情,git diff
而不是抢断3个-dotmaster...develop
语法来做到这一点。
Git's rebase
is a little bit special
Git 的rebase
有点特别
The rebase
command is almost always used to move3one of these DAGlet commit-subsets from one point in the graph to another. In fact, that's what rebase is, or was originally anyway, defined to do. (Now it has a fancy interactiverebase mode, which does this and a bunch more history editing operations. Mercurial has a similar command, hg histedit
, with a slightly better name, and much tighter default semantics.4)
该rebase
命令几乎总是用来移动3这些DAGlet提交-子集之一从一个点在图中到另一个。事实上,这就是 rebase 的定义,或者说最初定义为要做的事情。(现在它有一个奇特的交互式rebase 模式,可以执行此操作和更多的历史编辑操作。Mercurial 有一个类似的命令,hg histedit
,名称稍好一些,默认语义更严格。4)
Since we always (or almost always) want to move a DAGlet, git rebase
builds in this subset selection for us. And, since we always (or almost always) want to move the DAGlet to come just after the tip of some otherbranch, git rebase
defaults to choosing the target (or --onto
) commit using a branch name, and then uses that same branch name in the X..Y
syntax.5
由于我们总是(或几乎总是)想要移动 DAGlet,git rebase
因此为我们构建了这个子集选择。而且,由于我们总是(或几乎总是)希望将 DAGlet 移动到其他分支的尖端之后,因此git rebase
默认--onto
使用分支名称选择目标(或)提交,然后在X..Y
语法中使用相同的分支名称. 5
3Technically, git rebase
actually copiescommits, rather than moving them. It has to, because commits are immutable, like all Git's internal objects. The true name, the SHA-1 hash, of a commit is a checksum of the bits making up the commit, so any time you change anything—including something as simple as the parent ID—you have to make a new, slightly-different, commit.
3从技术上讲,git rebase
实际上是复制提交,而不是移动它们。它必须,因为提交是不可变的,就像所有 Git 的内部对象一样。提交的真实名称,即 SHA-1 哈希值是构成提交的位的校验和,因此无论何时更改任何内容(包括父 ID 之类的简单内容),您都必须创建一个新的、略有不同的, 犯罪。
4In Mercurial, quite unlike Git, branches really dohave starting points, and—more important for histedit
—commits record their phase: secret, draft, or published. History editing readily applies to secret or draft-phase commits, and not so much to published commits. This is true of Git as well, but since Git has no concept of commit phases, Git's rebase must use these other techniques.
4在水银,完全不同于Git的,分支机构真的不要有起点,以及-更重要histedit
-commits记录他们的阶段:秘密,草案或出版。历史编辑很容易应用于秘密或草稿阶段的提交,而不是发布的提交。Git 也是如此,但由于 Git 没有提交阶段的概念,因此 Git 的 rebase 必须使用这些其他技术。
5Technically the <upstream>
and --onto
arguments can just be raw commit IDs. Note that 1234567..develop
works just fine as a range selector, and you can rebase --onto 1234567
to place the new commits after commit 1234567
. The only place that git rebase
truly needs a branch nameis for the name of the current branch, which it normally just reads from HEAD
anyway. However, we usually wantto use a name, so that's how I describe it all here.
5从技术上讲,<upstream>
和--onto
参数可以只是原始提交 ID。请注意,它1234567..develop
作为范围选择器工作得很好,您可以--onto 1234567
在 commit 之后重新设置新提交1234567
。唯一git rebase
真正需要分支名称的地方是当前分支的名称,它通常只是从中读取HEAD
。然而,我们通常想使用一个名字,所以这就是我在这里描述的方式。
That is, if we're currently on branch develop
, and in this situation that we drew before:
也就是说,如果我们当前在 branch 上develop
,并且在我们之前绘制的这种情况下:
...--A--B--C <-- master
\
D--E--F--G <-- develop
we probably just want to move the D-E-F-G
chain onto the tip of master
, to get this:
我们可能只想将D-E-F-G
链移到 的尖端master
,以得到这个:
...--A--B--C <-- master
\
D'-E'-F'-G' <-- develop
(The reason I changed the names from D-E-F-G
to D'-E'-F'-G'
is that rebase is forced to copythe original commits, rather than actually moving them. The new copies are just as good as the originals, and we can use the same single letter name, but we should at least note, however vaguely, that these are in fact copies. That's what the "prime" marks, the '
characters, are for.)
(我从改名字的原因D-E-F-G
来D'-E'-F'-G'
是重订被迫复制原来的提交,而不是实际移动它们,新副本一样好原稿,我们可以使用相同的单个字母的名字,但我们应该至少请注意,无论多么模糊,这些实际上都是复制品。这就是“主要”标记、'
字符的用途。)
Because this is what we usually want, git rebase
will do this automatically if we just name the otherbranch. That is, we're on develop
now:
因为这是我们通常想要的,git rebase
如果我们只是命名另一个分支会自动执行此操作。也就是说,我们develop
现在:
$ git checkout develop
and we want to rebase commits that are on branch develop
and are noton master
, moving them to the tip of master
. We might express this as git somecmdmaster..develop master
, but then we would have to type the word master
twice (such a dreadful fate). So instead, Git's rebase
infers this when we just type in:
我们要变基是在分支提交develop
,并没有上master
,将它们转移到前端master
。我们可以将其表达为,但随后我们将不得不输入两次这个词(如此可怕的命运)。因此,当我们输入以下内容时,Git 会推断出这一点:git somecmdmaster..develop master
master
rebase
$ git rebase master
The name master
becomes the left side of the two-dot ..
DAGlet selector, and the name master
alsobecomes the target of the rebase; and Git then rebases D-E-F-G
onto C
. Git gets our branch's name, develop
, by reading out the current branch name. In fact, it uses a shortcut, which is that when you need the current branch name, you can normally just write HEAD
instead. So master..develop
and master..HEAD
mean the same thing, because HEAD
isdevelop
.
名称master
成为两点..
DAGlet选择器的左侧,名称master
也成为rebase的目标;然后 Git 重新建立D-E-F-G
到C
. Git通过读取当前分支名称来获取我们的分支名称, develop
。其实它使用了一个快捷方式,就是当你需要当前的分支名称时,你通常可以直接写出来HEAD
。所以master..develop
和master..HEAD
意思是一样的,因为HEAD
是develop
。
Git's rebase
calls this name the <upstream>
.That is, when we say git rebase master
, Git claims, in the documentation, that master
is the <upstream>
argument to git rebase
. The rebase command then operates on commits in <upstream>..HEAD
, copying them after whatever commit is in <upstream>
.
Gitrebase
将此名称称为<upstream>
. 也就是说,当我们说git rebase master
,Git的索赔,在文档中,这master
是<upstream>
参数git rebase
。然后 rebase 命令对 中的提交进行操作<upstream>..HEAD
,在 中的任何提交之后复制它们<upstream>
。
This is going to become a problem for us soon, but for now, just make note of it.
这很快就会成为我们的问题,但现在,只需记下它。
(Rebase also has the sneaky, but desirable, side feature of omitting any of the D-E-F-G
commits that sufficiently resembles commit C
. For our purposes we can ignore this.)
(Rebase 也有一个偷偷摸摸但令人向往的附带特性,即省略任何D-E-F-G
与 commit 非常相似的提交C
。出于我们的目的,我们可以忽略这一点。)
What's wrong with the other answer to this question
这个问题的另一个答案有什么问题
In case the other answer gets deleted, or becomes one of several other answers, I'll summarize it here as "use git branch -f
to move the branch label." The flaw in the other answer—and, perhaps more importantly, precisely whenit's a problem—becomes obvious once we draw our graph DAGlets.
如果其他答案被删除,或成为其他几个答案之一,我将在此处将其总结为“用于git branch -f
移动分支标签”。一旦我们绘制了 DAGlets 图形,另一个答案中的缺陷——也许更重要的是,恰恰是当它出现问题时——就会变得明显。
Branch names are unique, but tip commits are not necessarily so
分支名称是唯一的,但提示提交不一定如此
Let's take a look at what happens when you run git checkout -b newbranch starting-point
. This asks Git to root around in the current graph for the given starting-point, and make the new branch label point to that specific commit. (I know I said above that branches don't havea starting point. This is still mostly true: we're giving the git checkout
command a starting point now, but Git is about to set it and then, crucially, forgetit.) Let's say that starting-point
is another branch name, and let's draw a whole bunch of branches:
让我们来看看运行时会发生什么git checkout -b newbranch starting-point
。这要求 Git 在当前图形中为给定的起点定位,并使新的分支标签指向该特定提交。(我知道我说上面分支机构不具有起点这仍然是通常的情况:。我们给了git checkout
一个起点命令现在,但Git是要设置它,然后,重要的是,算了吧。)让我们说这starting-point
是另一个分支名称,让我们绘制一大堆分支:
o--o--o--o <-- brA
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- brC
\
o--o <-- brD
Since we have four branch names, we have four branch tips: four branch-tip commits, identified by the names brA
through brD
. We pick one and make a new branch name newbranch
that points to the same commitas one of these four. I have arbitrarily picked brA
here:
由于我们有四个分支名称,因此我们有四个分支提示:四个分支提示提交,由名称brA
到brD
. 我们选择一个并创建一个新的分支名称newbranch
,指向与这四个之一相同的提交。我在brA
这里随意挑选:
o--o--o--o <-- brA, newbranch
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- brC
\
o--o <-- brD
We now have five names, and five ... er, four? ... well, sometip commits. The tricky bit is that brA
and newbranch
both point to the sametip commit.
我们现在有五个名字,五个……呃,四个?...好吧,一些提示提交。棘手的一点是,brA
与newbranch
都指向相同的尖端提交。
Git knows—because git checkout
sets it—that we're now on newbranch
. Specifically Git writes the name newbranch
into HEAD
. We can make our drawing a bit more accurate by adding this information:
Git 知道——因为git checkout
设置了它——我们现在在newbranch
. 具体来说,Git 将名称newbranch
写入HEAD
. 我们可以通过添加以下信息使我们的绘图更加准确:
o--o--o--o <-- brA, HEAD -> newbranch
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- brC
\
o--o <-- brD
At this point, the four commits that used to be only on branch brA
are now on both brA
and newbranch
. And, by the same token, Git no longer knows that newbranch
starts at the tip of brA
. As far as Git is concerned, both brA
and newbranch
contain those four commits and all the earlier ones too, and both of them "start" way back in time somewhere.
此时,过去仅在分支brA
上的四个提交现在都在brA
和 上newbranch
。而且,出于同样的原因,Git 不再知道newbranch
从brA
. 至于Git的来讲,无论是brA
和newbranch
包含这四个提交和所有早期的过了,他们都“开始”,在时间的地方回来的路上。
When we make new commits, the current namemoves
当我们进行新的提交时,当前名称移动
Since we're on branch newbranch
, if we make a new commit now, the new commit's parent will be the old tip commit, and Git will adjust the branch name newbranch
to point to the new commit:
由于我们在 branch newbranch
,如果我们现在进行新提交,新提交的父项将是旧的提示提交,并且 Git 将调整分支名称newbranch
以指向新提交:
o <-- HEAD -> newbranch
/
o--o--o--o <-- brA
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- brC
\
o--o <-- brD
Note that none of the other labels moved: the four "old" branches stay put, only the current (HEAD
) branch changes. It changes to accommodate the new commit we just made.
请注意,其他标签都没有移动:四个“旧”分支保持原样,只有当前 ( HEAD
) 分支发生变化。它会更改以适应我们刚刚进行的新提交。
Note that Git continues to have no idea that branch newbranch
"started" at brA
. It's just the case, now, that newbranch
contains one commit that brA
does not, plus the four commits that they both contain, plus all those earlier commits.
请注意,Git 仍然不知道分支newbranch
“开始”于brA
. 现在的情况就是这样,它newbranch
包含一个brA
没有提交的提交,加上它们都包含的四个提交,以及所有那些较早的提交。
What git branch -f does
什么 git branch -f does
Using git branch -f
lets us move a branch label. Let's say, for whatever mysterious reason, we don't want branch label brB
to point where it does in our current drawing. Instead, we want it to point to the same commit as brC
. We can use git branch -f
to changethe place to which brB
points, i.e., to move the label:
使用git branch -f
让我们移动一个分支标签。假设,无论出于什么神秘的原因,我们不希望分支标签brB
指向它在当前绘图中的位置。相反,我们希望它指向与brC
. 我们可以使用git branch -f
来改变到的位置brB
点,即移动标签:
$ git branch -f brB brC
o <-- HEAD -> newbranch
/
o--o--o--o <-- brA
/
...--o--o--o--o--o--o [abandoned]
\
o--o--o <-- brC, brB
\
o--o <-- brD
This makes Git "forget" or "abandon" those three commits that were only on brB
before. That's probably a bad idea—why didwe decide to do this strange thing?—so we probably want to put brB
back.
这使得 Git“忘记”或“放弃”brB
之前只进行过的三个提交。这可能是一个坏主意,为什么没有我们决定这样做奇怪的事情? -所以我们可能要放brB
回去。
Reflogs
引用
Fortunately, "abandoned" commits are normally remembered in what Git calls reflogs. Reflogs use an extended syntax, name@{selector}
. The selectorpart is usually either a number or date, such as brB@{1}
or brB@{yesterday}
. Every time Git updates a branch name to point to some commit, it writes a reflog entry for that branch, with the pointed-to commit's ID, a time-stamp, and an optional message. Run git reflog brB
to see these. The git branch -f
command wrote the new target as brB@{0}
, bumping up all the old numbers, so now brB@{1}
names the previoustip commit. So:
幸运的是,“放弃”的提交通常会在 Git 称为reflogs 的内容中被记住。引用日志使用扩展语法. 的选择器部分通常是一个数字或日期,例如或。每次 Git 更新分支名称以指向某个提交时,它都会为该分支写入一个 reflog 条目,其中包含指向提交的 ID、时间戳和可选消息。跑去看看这些。该命令将新目标写为,增加了所有旧数字,因此现在命名前一个提示提交。所以:name@{selector}
brB@{1}
brB@{yesterday}
git reflog brB
git branch -f
brB@{0}
brB@{1}
$ git branch -f brB 'brB@{1}'
# you may not need the quotes, 'brB@{...}' --
# I need them in my shell, otherwise the shell
# eats the braces. Some shells do, some don't.
will put it back (and also renumber all the numbers again: each update replaces the old @{0}
and makes it @{1}
, and @{1}
becomes @{2}
, and so on).
会把它(并再次重新编排所有的数字:每次更新取代了旧的@{0}
,并使得它@{1}
,并@{1}
成为@{2}
,等等)。
Anyway, suppose that we do our git checkout -b newbranch
while we're on brC
, and fail to mention brA
. That is, we start with:
无论如何,假设我们git checkout -b newbranch
在进行时做了我们的工作brC
,而没有提到brA
。也就是说,我们开始:
o--o--o--o <-- brA
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- HEAD -> brC
\
o--o <-- brD
and run git checkout -b newbranch
. Then we get this:
并运行git checkout -b newbranch
。然后我们得到这个:
o--o--o--o <-- brA
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- brC, HEAD -> newbranch
\
o--o <-- brD
If we meantto make newbranch
point to commit brA
, we can in fact do that right now, with git branch -f
. But let's say we make a new commit before realizing that we made newbranch
start at the wrong point. Let's draw it in:
如果我们的意思做newbranch
点提交brA
,我们其实可以做到这一点,现在,有git branch -f
。但是假设我们在意识到我们newbranch
在错误的点开始之前做了一个新的提交。让我们把它画进去:
o--o--o--o <-- brA
/
...--o--o--o--o--o--o <-- brB
\
o--o--o <-- brC
\ \
| o <-- HEAD -> newbranch
\
o--o <-- brD
If we use git branch -f
now, we'll abandon—lose—the commit we just made. What we want instead is to rebase it, onto the commit that branch brA
points-to.
如果我们git branch -f
现在使用,我们将放弃——丢失——我们刚刚所做的提交。我们想要的是将它重新绑定到分支brA
指向的提交上。
A simple git rebase
copies too much
一个简单的git rebase
副本太多
What if, instead of using git branch -f
, we use git rebase brA
? Let's analyze this using—what else—our DAGlets. We start with the above drawing above, with the extended leg going out to brD
, though in the end we get to ignore that leg, and with the section going to brB
, most of which we also get to ignore. What we don't get to ignore is all that stuff in the middle, that we get by tracing the lines back.
如果,而不是使用git branch -f
,我们使用git rebase brA
?让我们使用——还有什么——我们的 DAGlets 来分析这一点。我们从上面的图开始,将伸展的腿延伸到brD
,尽管最后我们可以忽略那条腿,而部分将转到brB
,其中大部分我们也可以忽略。我们不能忽视的是中间的所有东西,我们通过追溯线条得到的东西。
The git rebase
command, in this form, will use brA..newbranch
to pick commits to copy. So, starting with the whole DAGlet, let's mark (with *
) all the commits that are on (or contained in) newbranch
:
git rebase
此形式的命令将用于brA..newbranch
选择要复制的提交。因此,从整个 DAGlet 开始,让我们标记(使用*
)所有在(或包含在)上的提交newbranch
:
o--o--o--o <-- brA
/
...--*--*--*--o--o--o <-- brB
\
*--*--* <-- brC
\ \
| * <-- HEAD -> newbranch
\
o--o <-- brD
Now, let's un-mark (with x
) all the commits that are on (or contained in) brA
:
现在,让我们取消标记(使用x
)所有在(或包含在)上的提交brA
:
x--x--x--x <-- brA
/
...--x--x--*--o--o--o <-- brB
\
*--*--* <-- brC
\ \
| * <-- HEAD -> newbranch
\
o--o <-- brD
Whatever remains—all the *
commits—are the ones that git rebase
will copy. That's way too many!
剩下的——所有的*
提交——都是git rebase
将复制的。 这太多了!
We need to get git rebase
to copy just the one commit. What this means is that for the <upstream>
argument, we need to give git rebase
the name brC
.6That way, Git will use brC..HEAD
to select the commits to copy, which will be just the one commit we need to copy.
我们只需要git rebase
复制一次提交。这意味着对于<upstream>
参数,我们需要给出git rebase
名称brC
。6这样,Git 将用于brC..HEAD
选择要复制的提交,这将只是我们需要复制的一个提交。
But—alas!—now we have a big problem, because git rebase
wants to copy the commit to a point right after the <upstream>
we just gave it. That is, it wants to copy the commits to just after brC
. That's where the commits are now! (Well, the one commit is.) So this is no good at all!
但是 - 唉! - 现在我们遇到了一个大问题,因为git rebase
想要将提交复制到<upstream>
我们刚刚给它之后的一个点。也就是说,它想将提交复制到brC
. 这就是现在提交的地方!(嗯,一次提交是。)所以这根本不好!
Fortunately, git rebase
has an escape hatch, specifically the --onto
argument. I mentioned this several times before, but now is when we need it. We want the copies to go right after brA
, so that's what we will supply as the --onto
argument. Git's rebase
uses the <upstream>
by default, but if we give it an --onto
, it uses that instead. So:
好在git rebase
有逃生舱口,专门的--onto
说法。我之前多次提到过这一点,但现在正是我们需要它的时候。我们希望副本紧跟在 之后brA
,这就是我们将提供的--onto
参数。Git默认rebase
使用,但如果我们给它一个,它就会使用它。所以:<upstream>
--onto
$ git branch # just checking...
brA
brB
brC
brD
master
* newbranch
OK, good, we're still on newbranch
. (Note that git status
works here too, and if you use one of those fancy shell prompt setup things, you can even get your current branch name to be in your prompt, so that you don't need to run git status
as often.)
好的,好的,我们还在继续newbranch
。(请注意,git status
这也适用于此处,如果您使用其中一种花哨的 shell 提示设置,您甚至可以将当前分支名称放在提示中,这样您就不需要git status
经常运行了。)
$ git rebase --onto brA brC
Now Git will select commits in brC..HEAD
, which is the right set of commits to copy, and copy them right after the tip of brA
, which is the right place to copy them to. Once the copies are all done, Git will abandon the original commits7and make the name newbranch
point to the new, tip-most, copied commit.
现在 Git 将选择 commits in brC..HEAD
,这是要复制的正确提交集,并在 的提示之后立即复制它们brA
,这是将它们复制到的正确位置。一旦副本全部完成,Git 将放弃原始提交7并将名称newbranch
指向新的、最尖端的、复制的提交。
Note that this works even if you have nonew commits on the new branch. This is the one case where git branch -f
alsoworks. When there are no commits, this git rebase
carefully copies all zero of them :-) and then makes the name, newbranch
, point to the same commit as brA
. Hence git branch -f
is not always wrong; but git rebase
is always right—albeit somewhat clumsy: you must identify both the <upstream>
and the --onto
points manually.
请注意,即使您在新分支上没有新提交,这也有效。这是一种git branch -f
也有效的情况。当没有提交时,这会git rebase
小心地复制它们的所有零:-),然后使名称newbranch
, 指向与brA
. 因此git branch -f
并不总是错误的;但git rebase
总是正确的——尽管有点笨拙:您必须手动识别点<upstream>
和--onto
点。
6Or, as we noted in an earlier footnote, we can give git rebase
the ID of the commit to which the name brC
points. Either way, we have to supply this as the upstream
argument.
6或者,正如我们在前面的脚注中所指出的,我们可以给出git rebase
名称brC
指向的提交的 ID 。无论哪种方式,我们都必须提供这个作为upstream
参数。
7Except, of course, reflog entry newbranch@{1}
will remember the old, now-abandoned, tip commit. Additional reflog entries for newbranch
may remember yet more commits, and remembering the tip commit suffices to keep all its ancestors alive. The reflog entries eventually expire—after 30 days for some cases, and 90 for others, by default—but this gives you up to a month or so, by default, to recover from mistakes.
7当然,除了 reflog 条目newbranch@{1}
会记住旧的、现已废弃的提示提交。额外的 reflog 条目newbranch
可能会记住更多的提交,并且记住提示提交就足以保持其所有祖先的存活。reflog 条目最终会过期——默认情况下,在某些情况下是 30 天后,而在其他情况下则是 90 天——但默认情况下,这会给你最多一个月左右的时间来从错误中恢复。
回答by Philippe
If you don't have commits in the new branch, using a 'git reset --hard' is the easier.
如果您在新分支中没有提交,则使用 'git reset --hard' 更容易。
If you have commits in the new branch...
如果您在新分支中有提交...
If your branch start on an older commit that you want to, just do a 'git rebase'.
如果您的分支从您想要的较旧提交开始,只需执行 'git rebase'。
If, more unlikely, your branch start on a newer commit or on a completely different branch, use a 'git rebase --onto'
如果不太可能,您的分支开始于较新的提交或完全不同的分支,请使用“git rebase --onto”
回答by werkritter
You want the branch to point to a different commit. You can do that by running
您希望分支指向不同的提交。你可以通过运行来做到这一点
git branch -f <branch-name> <starting-branch>
Note that if branch-name
is the current branch, you have to switch to a different branch first, for example with git checkout master
.
请注意,如果branch-name
是当前分支,则必须先切换到不同的分支,例如使用git checkout master
.