Laz*_*rks 9 git branch git-branch
有没有办法让现有的分支成为git中的孤儿?
git checkout --orphan
似乎只创造了一个新的孤儿?
我是否理解您,您希望孤立分支已经拥有提交历史记录?如果是这样,这是一个解决方案.
首先,你需要选择一个提交来启动新的分支.在我的例子中,这将是HEAD~2
,sha1=df931da
.
说,我们有一个简单的回购.git log --oneline --graph --decorate
显示以下内容:
* 4f14671 (HEAD, master) 4
* 1daf6ba 3
* df931da 2
* 410711d 1
Run Code Online (Sandbox Code Playgroud)
行动吧!
# Move to the point where we want new branch to start.
? gitorphan git:(master) git checkout HEAD~2
Run Code Online (Sandbox Code Playgroud)
这里和更进一步的? gitorphan git:(master)
部分是zsh的提示,而不是命令的一部分.
# make an orphan branch
? gitorphan git:(df931da) git checkout --orphan orphanbranch
Switched to a new branch 'orphanbranch'
# first commit in it
? gitorphan git:(orphanbranch) ? git commit -m'first commit in orphan'
[orphanbranch (root-commit) f0d071a] first commit in orphan
2 files changed, 2 insertions(+)
create mode 100644 1
create mode 100644 2
# Check that this is realy an orphan branch
? gitorphan git:(orphanbranch) git checkout HEAD^
error: pathspec 'HEAD^' did not match any file(s) known to git.
# Now cherry-pick from previous branch a range of commits
? gitorphan git:(orphanbranch) git cherry-pick df931da..master
[orphanbranch 7387de1] 3
1 file changed, 1 insertion(+)
create mode 100644 3
[orphanbranch 4d8cc9d] 4
1 file changed, 1 insertion(+)
create mode 100644 4
Run Code Online (Sandbox Code Playgroud)
现在,分支orphanbranch
在单个提交中具有df931da处的工作树的快照,并且进一步提交就像它们在主服务器中一样.
? gitorphan git:(orphanbranch) git log --oneline
4d8cc9d 4
7387de1 3
f0d071a first commit in orphan
Run Code Online (Sandbox Code Playgroud)
您git checkout --orphan
只创建新的孤立分支是正确的。诀窍在于,此过程使索引不受干扰。因此,只要您的Git不太古老,Nick Volynkin的答案就会起作用。
如果要保留原始提交消息,则可以替换他的:
$ git commit -m'first commit in orphan'
Run Code Online (Sandbox Code Playgroud)
与:
$ git commit -C master~2
Run Code Online (Sandbox Code Playgroud)
如果您的Git已经足够老了,还git checkout --orphan
应该这样做:
$ git commit -C master~2
Run Code Online (Sandbox Code Playgroud)
您可以在其中选择起点git log
或使用~
具有现有分支名称的语法(这将继续master~2
在Nick的答案中使用)。
如果您想要的只是一个食谱,那应该可以解决问题,但是,如果您想知道发生了什么,以及为什么它起作用(何时不起作用),请继续阅读。:-)
在进行下一步之前,最好先定义一些项目并描述正在发生的事情。
首先,让我们做一个明确的区分分支之间的名称,如master
或newbr
和的各个部分提交图形。分支名称仅指向图中的一个提交,称为“ 提交提交”或“ 分支提示 ”:
*--o--o---o--o <-- master
\ /
o--o--o--o <-- brA
\
o <-- brB
Run Code Online (Sandbox Code Playgroud)
该图有三个分支提示,指向的通过master
,brA
和brB
。brB
例如,尖端的祖先以一条摇摆的线向后移动,总是向左移动,有时也向上移动到(单个)根提交*
(与所有其他非根o
提交区分开)。提交*
没有左边提交的事实(没有父提交指向)是使它成为根提交的原因。
此根落实在所有分支上。其他提交也在多个分支上。在上有一个合并提交,即使例如有两个未提交的提交,它也master
从中引入提交。若要返回到根目录,您必须在合并中向左直走,并且在合并时也要向下和向左,然后再在拆分处向上和向左返回。brA
brA
master
master
brA
请注意,我们可以有多个分支名称指向单个提交,或者分支名称指向嵌入在另一个分支中的“提示”提交:
*--o--o---o--o <-- master
\ /
o--o--o <-- brA
\
o <-- brB, brC
Run Code Online (Sandbox Code Playgroud)
在这里,我们brA
通过一次提交“重绕”分支,因此右侧的中间行提交是的尖端brA
,即使它是从尖端返回的一次提交brB
。我们添加了一个新分支,brC
该分支指向与(使其两次成为小费的提交相同的提交brB
;我们希望此提交不是英式“垃圾小费”一词的小费:嗯,这个提交是绝对的秘诀! ”)。
该图具有一系列节点o
,每个节点都指向一些通常位于其左侧的父级。连接节点的线(实际上是箭头)是有向边:单向街道或铁路线(如果需要的话),将图中的子节点连接回其父节点。
节点以及从子级到父级的有向边链接形成提交图。由于该图是针对(孩子父母)和非周期(一旦你离开一个节点,你永远不能回来的话),这就是所谓的d irected 一个循环摹拉斐或DAG。DAG具有各种良好的理论属性,对于该SO答案,我们可以忽略其中的大多数。
现在让我们考虑这个替代图形:
*--o--o---o--o <-- master
\ /
o--o--o <-- brA
*--o--o--o <-- orph
Run Code Online (Sandbox Code Playgroud)
这个新分支的尖端名为orph
,具有自己的根,并且与其他两个分支完全断开连接。
请注意,多个根是具有(非空)不相交子图的必要先决条件,但是取决于您如何查看这些图,它们可能还不够。如果我们要合并(尖端提交)brA
成orph
1,我们将得到:
*--o--o---o--o <-- master
\ /
o--o--o <-- brA
\
*--o--o--o---o <-- orph
Run Code Online (Sandbox Code Playgroud)
现在将两个“图形片段”合并在一起。然而,存在的子图(例如那些由起始orph^1
和brA
,的两个亲本orph
),其是不相交的。(这与创建孤立分支没有特别关系,只是您应该了解它们。)
1 Modern Git拒绝随意进行这种合并的尝试,因为这两个分支没有合并基础。较旧版本的Git会合并,不一定会产生明智的结果。
git checkout --orphan
该orph
分支是那种分支git checkout --orphan
使得:一个分支,将有一个新的,断开的根源。
它到达那里的方式是创建一个分支名称,该分支名称指向完全不提交。Git将此称为“未完成的分支”,处于这种状态的分支只有一种半存在,因为Git泄漏了实现。
根据定义,分支名称始终指向该分支上最尖端的提交。但这给Git带来了一个问题,尤其是在完全没有提交的全新仓库中:哪里可以master
指向?
事实是,未出生的分支无法指向任何地方,并且由于Git通过将分支名称记录为<name,commit-ID>对2来实现分支名称,因此它只有在提交后才能记录分支。Git解决这个难题的方法是作弊:分支名称根本不进入分支记录,而仅进入HEAD
记录。
的HEAD
,在Git中,记录了当前分支的名字。对于“分离式HEAD”模式,HEAD
记录实际的提交ID,实际上,这就是Git确定存储库/工作树是否处于“分离式HEAD”模式的方式:如果其HEAD
文件包含分支名称,则不分离,如果包含提交ID,则将其分离。(不允许其他状态。)
因此,要创建一个“孤立的分支”,或者在那个尴尬的时期(尚未提交)master
,Git将名称存储在中HEAD
,但实际上并没有创建分支名称。(也就是说,没有条目.git/refs/heads/
,也没有行.git/packed-refs
。)
作为一种特殊的副作用,这意味着您只能拥有一个未出生的分支。未出生分支的名称存储在中HEAD
。签出另一个分支(有无)--orphan
,是否有ID提交(任何更新的操作)HEAD
都会清除未出生分支的所有痕迹。(git checkout --orphan
当然,新的分支会用新的未出生分支替换它。)
首次提交后,新分支便应运而生,因为...
2对于“未压缩”的引用,该名称只是文件系统中的路径:.git/refs/heads/master
。提交ID只是该文件的内容。打包引用的存储方式不同,Git正在开发其他方法来处理名称到ID的映射,但这是最基本的方法,当前仍需要Git才能使用。
有两种明显的方法可以保留未出生的分支,但是Git都不使用。(作为记录,这些操作是:创建一个空文件,或使用特殊的“空哈希”。空文件技巧有一个明显的缺陷:面对命令或计算机崩溃,它将非常脆弱,远比使用空哈希值。)
通常,在Git 中进行新提交的过程如下:
更新和/或填充索引(也称为暂存区或缓存):git add
各种文件。此步骤将创建Git的blob对象,该对象存储实际的文件内容。
将索引写入一个或多个树对象(git write-tree
)。此步骤会创建(或在极少数情况下会重用)至少一棵(顶层)树。该树具有每个文件和子目录的条目;对于文件,它列出了blob-ID,对于子目录,列出了(创建后)包含子目录的文件和树的树。注意,顺便说一句,这使索引不受干扰,为下一次提交做好了准备。
编写一个提交对象(git commit-tree
)。这一步需要一堆物品。出于我们的目的,主要有趣的是与此提交一起使用的(单个)树对象(这是我们从第2步中刚刚获得的树对象)以及父提交ID的列表。
将新提交的ID写入当前分支名称。
步骤4是分支名称始终指向提示提交的方式和原因。该git commit
命令从中获取分支名称HEAD
。它还在步骤3中以相同的方式获取主要(或first,通常仅)父提交ID:它读取HEAD
以获取分支名称,然后从分支中读取提示提交ID。(对于合并提交,它从中读取其他父ID(通常只有一个MERGE_HEAD
)。)
Git commit
当然知道有关未出生和/或孤儿的分支。如果HEAD
说refs/heads/master
,但是分支master
不存在……那么,那master
一定是一个未出生的分支!因此,此新提交没有父ID。它仍然具有与以往相同的树,但这是一个新的根落实。仍然会将其ID写入分支文件,这具有创建branch的副作用。
因此,它将在实际创建分支的新的孤立分支上进行首次提交。
Git的cherry-pick
命令在理论上非常简单(有时操作会有些复杂)。让我们回到示例图并说明典型的“樱桃拾取”操作。这次,为了讨论图表中的某些特定提交,我将给它们提供单名字母:
...--o--o--A--B--C <-- mong
\
o--o <-- oose
Run Code Online (Sandbox Code Playgroud)
假设我们想B
从一个分支mong
到另一个分支挑选承诺oose
。这很容易,我们只需要:
$ git checkout oose; git cherry-pick mong~1
Run Code Online (Sandbox Code Playgroud)
在那里mong~1
候任提交B
。(这之所以有效,是因为mong
指定commit C
,并且C
parent的父项是B
,并且mong~1
意思是“沿第一个父级链接的主线移回一个父commit。同样,mong~2
指定commit A
,并mong~3
指定o
前一个A
,依此类推。不要遍历具有多个父级的合并提交,此处的操作非常简单。)
但是git cherry-pick
实际上如何工作?答案是:它首先运行git diff
。也就是说,它构造了一个补丁,其类型为git log -p
或git show
。
记住(根据我们先前的讨论),每个提交都有一个附加的树对象。该树保留了该提交时的整个源树:进行该提交时索引/临时区域中所有内容的快照。
这意味着提交B
具有与其关联的整个完整工作树。但是,我们要摘樱桃的变化,我们在做B
,而不是树 B
。也就是说,如果我们进行了更改README.txt
,我们希望得到所做的更改:不是旧版本README.txt
,也不是新版本,而仅仅是更改。
我们发现这一点的方式是,我们从提交B
返回到其父级,即commit A
。Commit A
还具有一个完整的工作树。我们只运行git diff
了两次提交,向我们展示了我们在中所做的更改README.txt
以及所做的任何其他更改。
现在我们有了diff / patch,我们回到现在的位置-branch的最尖端提交oose
,以及我们在工作树和索引/临时区域中与该提交相对应的文件。(git cherry-pick
默认情况下,如果索引与我们的工作树不匹配,该命令将完全拒绝运行,因此我们知道它们是相同的。)现在,Git只需应用(与一样git apply
)我们刚刚通过比较提交A
和获取而获得的补丁。B
。
因此,无论我们从A
到进行了什么更改B
,我们现在都进行了更改,到当前的提交/索引/工作树。如果一切顺利,这将为我们提供修改后的文件,Git会自动git add
将其添加到索引中;然后Git运行git commit
使用commit的日志消息进行新的提交B
。如果我们运行了git cherry-pick -x
,Git将短语“从...中摘樱桃”添加到新提交的日志消息中。
(提示:您通常想使用-x
。 它可能应该是默认值。主要的例外是当您不保留刚刚挑选的原始提交时。有人还可以说使用cherry-pick
通常是错误的-这是一种表示您确实在做错某事,确实是现在,现在必须将其纸上记录,并且从长远来看,纸上记录可能不会成立,但这完全是另一个[长期]发布。)
VonC指出,在Git 2.9.1和更高版本中,它git cherry-pick
在孤立分支中工作;在即将发布的版本中,它既可以用于序列,也可以用于单个提交。但是有一个原因,使这种情况不可能长期存在。
请记住,通过将提交相对于其父项(或者,如果是合并提交,则是使用该选项选择的父项)来比较,从而cherry-pick
将一棵树变成一个补丁-m
。然后,它将补丁应用到当前提交。但是,孤立分支(我们尚未建立的分支)没有提交,因此也没有当前提交,并且至少从哲学的角度来看,没有索引,也没有工作树。根本没有补丁。
但是,实际上,我们可以(并且现在Git可以)完全绕开它。如果我们曾经进行过一次当前提交(如果我们在某个时候已经签出了某些内容),那么现在,只要有最新提交的“当前提交”,我们现在仍然有一个索引和一个工作树。
这是做什么的git checkout --orphan orphanbranch
。您签出一些现有的提交,并因此填充索引和工作树。然后,您git checkout --orphan newbranch
和git commit
新提交将使用当前索引来创建(或实际上重用)树。该树与您在执行之前签出的提交是同一棵树git checkout --orphan orphanbranch
。3
这也是我的旧版Git食谱的主要部分:
$ commit=<hash> # or, e.g., commit=$(git rev-parse master~2)
$ git branch newbranch $( \
git log --no-walk --pretty=format:%B $commit | \
git commit-tree -F - "${commit}^{tree}" \
)
$ git checkout newbranch
$ git cherry-pick $commit..master # may want -x; see below
Run Code Online (Sandbox Code Playgroud)
首先,我们找到所需的提交及其树:与关联的树master~2
。(我们实际上并不需要该变量commit
,但是这样写就可以使我们从git log
输出中剪切并粘贴一个哈希,而不必计算它离master
我们将在此处使用的分支或尖端的距离有多远)
使用${commit}^{tree}
告诉Git查找与提交关联的实际树(这是标准gitrevisions
语法)。该git commit-tree
命令使用刚才提供的树将新提交写入存储库。新提交的父(子)来自于我们使用-p
选项提供的父ID :我们不使用父ID ,因此新提交没有父(即,是根提交)。
这个新提交的日志消息就是我们在标准输入中提供的内容。要获取此日志消息,我们使用git log --no-walk --pretty=format:%B
,它只是将消息的全文打印到标准输出中。
该git commit-tree
命令将新提交的ID作为其输出生成:
*--o--o---o--o <-- master
\ /
o--o--o--o <-- brA
\
o <-- brB
Run Code Online (Sandbox Code Playgroud)
(每次运行都会产生一个不同的ID,除非所有运行都在相同的一秒钟内运行,因为每个运行都有不同的时间戳集;此处的实际ID并不十分重要)。我们使用该ID来git branch
创建一个新的分支名称,该分支名称将指向此新的根提交,作为其尖端提交。
一旦在新分支上有了新的root提交,就可以git checkout
建立新分支,并且我们准备好挑选其余的提交。
3实际上,您可以像往常一样将它们组合在一起:
git checkout --orphan orphanbranch master~2
Run Code Online (Sandbox Code Playgroud)
它首先检出(放入索引和工作树)由标识的提交的内容master~2
,然后进行设置,HEAD
以使您位于未出生的分支orphanbranch
。
git cherry-pick
成孤儿分支是不是有用,因为我们可能会喜欢我在这里构建了一个较新的Git版本(它无法通过自己的某些测试-在t3404-rebase-interactive.sh期间死了,但在其他方面似乎还不错):
$ alias git=$HOME/.../git
$ git --version
git version 2.9.2.370.g27834f4
Run Code Online (Sandbox Code Playgroud)
让我们看看,用--orphan
,master~2
用新的名称orphanbranch
:
$ git checkout --orphan orphanbranch master~2
Switched to a new branch 'orphanbranch'
$ git status
On branch orphanbranch
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: a.py
new file: ast_ex.py
[snip]
Run Code Online (Sandbox Code Playgroud)
因为这是一个新分支,所以Git看起来好像一切都是新的。如果我现在尝试git cherry-pick
要么master~2
或master~1
:
$ git cherry-pick master~2
error: Your local changes would be overwritten by cherry-pick.
hint: Commit your changes or stash them to proceed.
fatal: cherry-pick failed
$ git cherry-pick master~1
error: Your local changes would be overwritten by cherry-pick.
hint: Commit your changes or stash them to proceed.
fatal: cherry-pick failed
Run Code Online (Sandbox Code Playgroud)
我要做的就是清除所有内容,在这种情况下,应用从master~3
到的更改master~2
不太可能起作用,或者git commit
无论如何只是进行初始操作,以基于from的树进行新的根提交master~2
。
如果有git checkout --orphan
,只需使用它来检查目标提交oldbranch~N
(或通过哈希ID,您可以从git log
输出中剪切并粘贴):
$ git checkout --orphan newbranch oldbranch~N
Run Code Online (Sandbox Code Playgroud)
然后立即执行新提交,如Nick Volynkin所说(您可以复制其消息):
$ git commit -C oldbranch~N
Run Code Online (Sandbox Code Playgroud)
从而创建分支;然后使用git cherry-pick
with oldbranch~N..oldbranch
来获取剩余的提交:
$ git cherry-pick oldbranch~N..oldbranch
Run Code Online (Sandbox Code Playgroud)
(也许使用-x
,取决于您是否打算从中剥离提交oldbranch
。)请记住,oldbranch~N..oldbranch
排除提交oldbranch~N
本身,但这实际上很好,因为这是我们作为新的根提交所做的。
假设您已签出一个新分支并进行了两次提交,如下所示。13hh93 是签出的校验和,54hdsf 是最新提交的校验和:
主=> new_branch_1 (13hh93) => new_branch_2 => new_branch_3 (54hdsf)
然后进行如下操作。步骤 1 开始结账。第 2 步从中创建一个孤立分支。步骤 3 将分支的其余部分应用到您的孤立分支。
1) git checkout 13hh93 2) git checkout new_orphan_branch --orphan 3) git diff 13hh93 54hdsf | git 应用