有没有办法使现有的分支成为Git中的孤立分支?
git checkout --orphan
似乎只能创建一个新的孤立分支?
有没有办法使现有的分支成为Git中的孤立分支?
git checkout --orphan
似乎只能创建一个新的孤立分支?
git checkout --orphan
只创建新的孤立分支。关键是这个过程不会影响索引。因此,只要你的 Git 版本不太老,Nick Volynkin's answer 就可以起作用。$ git commit -m'first commit in orphan'
使用:
$ git commit -C master~2
git checkout --orphan
,那么这样做也可以:$ 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
您可以从git log
中选择起始点,或者使用现有分支名称的~
语法(这将继续使用像Nick回答中的master~2
)。如果您只想要一个配方,那么这应该可以解决问题,但是如果您想知道发生了什么以及为什么有效(以及何时无效),请继续阅读。 :-)
在我们进一步讨论之前,似乎定义一些项目并描述正在发生的事情是一个好主意。
首先,让我们清楚地区分分支名称,例如master
或newbr
,以及提交图的各个部分。 分支名称仅指向图表中的一个提交,指定为提示提交或分支提示:
*--o--o---o--o <-- master
\ /
o--o--o--o <-- brA
\
o <-- brB
master
、brA
和brB
指向。例如,brB
的末端祖先沿着一条波浪线向左移动,有时也会向上移动,直到(唯一的)根提交*
(与所有其他非根o
提交区分开来)。提交*
没有向左的提交-没有父提交指向它-这就使它成为根提交。master
上有一个合并提交,它将brA
中的提交合并进来,尽管brA
有两个提交master
没有。要跟随master
返回根,必须向左直走,并在合并处向下-向左走,然后向上-向左走,回到brA
分叉的地方。*--o--o---o--o <-- master
\ /
o--o--o <-- brA
\
o <-- brB, brC
brA
“倒回”了一个提交,所以右侧中间行的提交是brA
的最新提交,尽管它比brB
的最新提交早一个提交。我们添加了一个新的分支brC
,它指向与brB
相同的提交(这使得它成为两个最新提交,希望这个提交不是英式英语“rubbish tip”的意思:“呃,这个提交真是个大烂摊!”)。
o
,每个节点指向通常在其左侧的某些父节点。连接节点的线(或箭头)是有向边:连接图中子节点返回其父节点的单向街道或铁路线。*--o--o---o--o <-- master
\ /
o--o--o <-- brA
*--o--o--o <-- orph
orph
,有自己的根,并且与其他两个分支完全断开联系。brA
的(最新提交的)尖端合并到orph
中1,我们将得到以下结果:*--o--o---o--o <-- master
\ /
o--o--o <-- brA
\
*--o--o--o---o <-- orph
现在这两个“图碎片”已经连接在一起。然而,存在着子图(例如从orph^1
和brA
开始的那些,它们是orph
的两个父节点),它们是不相交的。(这与创建孤立分支并没有特别相关,只是你应该了解它们。)
1现代Git拒绝尝试这样的合并,因为这两个分支没有合并基础。旧版本的Git会执行合并,但结果不一定合理。
git checkout --orphan
--orphan
分支是git checkout --orphan
创建的一种分支:一个将具有新的、断开的根的分支。
它到达那里的方式是创建一个不指向任何提交的分支名称。Git称之为“未出生的分支”,处于这种状态的分支只有一种半存在的状态,因为Git通过泄露实现而暴露了实现细节。
分支名称定义上总是指向该分支上最新的提交。但这给Git带来了一个问题,特别是在一个完全新的没有任何提交的存储库中:master
应该指向哪里呢?
事实上,未出生的分支无法指向任何地方,因为Git通过记录它们作为<名称,提交ID>对来实现分支名称,所以它只能在有提交时记录分支。Git解决这个困境的方法是欺骗:分支名称根本不进入分支记录,而是仅进入HEAD
记录。
HEAD
中。检出另一个分支,带有或不带有--orphan
,或者通过ID提交任何提交 - 任何更新HEAD
的操作 - 都会清除所有未出生分支的痕迹。(当然,新的git checkout --orphan
将其替换为新的未出生分支的痕迹。)2使用“未打包”引用时,名称只是文件系统中的路径:.git/refs/heads/master
。然后,提交ID就是此文件的内容。打包的引用存储方式不同,Git正在发展其他处理名称到ID映射的方法,但这是最基本的,目前仍需要让Git工作。
保留未出现的分支有两种明显的方法,但Git都未使用。(记录一下,它们是:创建一个空文件或使用特殊的“null hash”。空文件技巧有一个明显的缺陷:在命令或计算机崩溃面前非常脆弱,远不如使用null hash。)
一般来说,在Git中进行新提交的过程如下:
更新和/或填充索引,也称为暂存区或缓存:git add
各种文件。此步骤创建Git的blob对象,存储实际文件内容。
将索引写入一个或多个tree对象(git write-tree
)。此步骤至少创建一个(顶级)树,对于每个文件和子目录,该树都有条目;对于文件,它列出了blob-ID,对于子目录,它列出了(在创建后)包含子目录文件和树的树。注意,这使索引保持不变,准备好进行下一次提交。
编写提交对象(git commit-tree
)。此步骤需要一堆项目。对于我们的目的来说,最主要的是与此提交相关的(单个)树对象——这是我们刚从第2步得到的——以及父提交ID列表。
将新提交的ID写入当前分支名称。
在理论上,Git的cherry-pick
命令非常简单(实践有时会变得有些复杂)。让我们回到我们的示例图表,并说明一个典型的cherry-pick操作。这次,为了讨论图表中的一些特定提交,我将给它们单个字母名称:
...--o--o--A--B--C <-- mong
\
o--o <-- oose
B
,并将其合并到分支中。这很简单,只需执行以下操作:$ git checkout oose; git cherry-pick mong~1
这里的mong~1
指代提交B
。(这是因为mong
指代提交C
,而C
的父提交是B
,mong~1
的意思是"沿着第一个父链接的主线向后移动一个父提交。同样,mong~2
指代提交A
,mong~3
指代在A
之前的o
等等。只要我们不遍历具有多个父提交的合并提交,一切都非常简单。)
但是git cherry-pick
实际上是如何工作的呢?答案是:它首先运行git diff
。也就是说,它构建了一个补丁,类似于git log -p
或git show
所显示的那种。
B
有一个完整的工作树与之关联。但是我们想要挑选B
中所做的更改,而不是B
的树。也就是说,如果我们更改了README.txt
,我们想要获取我们所做的更改:不是README.txt
的旧版本,也不是新版本,只是更改。B
回到其父提交,即提交A
。提交A
也有一个完整的工作树。我们只需在两个提交上运行git diff
,它会显示我们在README.txt
中所做的更改以及我们所做的任何其他更改。oose
的尖端提交和我们工作树和索引/暂存区中与该提交对应的文件。(默认情况下,如果我们的索引不匹配我们的工作树,git cherry-pick
命令将拒绝运行,因此我们知道它们是相同的。)现在Git只需(如同使用git apply
一样)应用我们刚刚通过比较提交A
和B
得到的补丁。A
到B
进行了哪些更改,我们现在都要对我们当前的提交/索引/工作树进行这些更改。如果一切顺利,这将给我们修改后的文件,Git会自动将其git add
到我们的索引中;然后Git运行git commit
以使用提交B
的日志消息创建一个新的提交。如果我们运行了git cherry-pick -x
,Git会将短语“cherry-picked from ...”添加到我们新提交的日志消息中。
(提示: 通常情况下,您希望使用 -x
。它可能应该成为默认设置。主要的例外情况是,当您将刚刚提取的原始提交丢弃时。还可以争论使用 cherry-pick
通常是错误的——这表明您之前做错了什么,现在不得不加以掩盖,而这种掩盖可能在长期内无法持续,但这是另一个[很长]发布的问题。)
VonC指出,在 Git 2.9.1 及更高版本中,git cherry-pick
可以在孤儿分支中工作; 在即将发布的版本中,它也适用于序列以及单个提交。但是,这样做不可能有很长时间的原因。
cherry-pick
将一个 树 转换为一个 补丁,通过将一个提交与其父提交进行差异比较(或在合并提交的情况下,使用 -m
选项选择的父提交)。然后将该补丁应用于当前提交。但是孤立分支——我们尚未创建的分支——没有提交,因此没有当前提交,并且——至少在哲学意义上——没有索引和工作树。简而言之,就是没有可供打补丁的内容。git checkout --orphan orphanbranch
命令的作用。你可以检出一些现有的提交,从而填充索引和工作树。然后你执行 git checkout --orphan newbranch
和 git commit
命令,新提交使用当前索引来创建或者实际上是重用一个树。该树与你在执行 git checkout --orphan orphanbranch
命令之前所检出的提交相同。3
这也是我关于非常老的 Git 的主要建议的来源。$ 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
首先,我们要找到所需的提交及其树:与master~2
相关联的树。 (实际上我们不需要变量commit
,但这样写可以让我们从git log
输出中复制和粘贴哈希值,而不必计算它距离master
或我们要在此使用的任何分支有多远。)
使用${commit}^{tree}
告诉Git找到与提交相关联的实际树(这是标准的gitrevisions
语法)。git commit-tree
命令将新的提交写入存储库,使用我们刚刚提供的树。新提交的父级来自我们使用-p
选项提供的父ID:我们不使用任何选项,因此新提交没有父级,即是根提交。
此新提交的日志消息是我们在标准输入上提供的。为了获取此日志消息,我们使用git log --no-walk --pretty=format:%B
,它只是将消息的完整文本打印到标准输出。
git commit-tree
命令的输出结果是新提交的 ID:
$ ... | git commit-tree "master~2^{tree}"
80c40c288811ebc44e0c26a5b305e5b13e8f8985
git branch
,以创建一个新的分支名称,该分支指向此新根提交作为其尖端提交。git checkout
到新分支,并准备好挑选剩余的提交。
3
。git checkout --orphan orphanbranch master~2
首先,它会检出(将其放入索引和工作树中)由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
让我们使用--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]
$ 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
master~3
应用更改可能不起作用,或者只需进行初始git commit
,以基于master~2
的树创建新的根提交。
如果你有git checkout --orphan
,只需使用它来检出目标提交oldbranch~N
(或通过哈希ID,可以从git log
输出中复制和粘贴):
$ git checkout --orphan newbranch oldbranch~N
然后立即进行新的提交,就像Nick Volynkin所说的那样(您可以复制它的消息):
$ git commit -C oldbranch~N
git cherry-pick
和 oldbranch~N..oldbranch
来获取剩余的提交记录:$ git cherry-pick oldbranch~N..oldbranch
oldbranch
中删除提交,可以考虑使用-x
。)请记住,oldbranch~N..oldbranch
不包括提交oldbranch~N
本身,但这实际上是有益的,因为它是我们作为新的根提交所做的一个。<!-- language: lang-bash -->
吗? - Nick Volynkin我理解你的意思是,你希望孤儿分支已经有了一些提交历史?如果是这样,下面是一个解决方案。
首先你需要选择一个提交记录作为新分支的起点。在我的例子中,这是HEAD~2
,也就是sha1=df931da
。
假设我们有一个简单的仓库,git log --oneline --graph --decorate
命令显示如下:
* 4f14671 (HEAD, master) 4
* 1daf6ba 3
* df931da 2
* 410711d 1
现在,行动起来!
# Move to the point where we want new branch to start.
➜ gitorphan git:(master) git checkout HEAD~2
在这里以及之后,➜ 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
现在分支orphanbranch
已经在单个提交中拥有了工作树的快照,此快照为df931da,并且后续提交与主分支中的提交一样。
➜ gitorphan git:(orphanbranch) git log --oneline
4d8cc9d 4
7387de1 3
f0d071a first commit in orphan
Nick Volynkin的回答涉及在新的孤立分支中至少进行一次提交。
这是因为git cherry-pick df931da..master
没有第一次提交将导致“无法将樱桃拣到空头
”。
但是,现在使用git 2.9.X/2.10(2016年第三季度)就不再需要了。
请见提交0f974e2(2016年6月6日),作者为Michael J Gruber(mjg
)。
(由Junio C Hamano -- gitster --
于提交25227f0合并,2016年7月6日)
cherry-pick
:允许选择未创建的分支
"git cherry-pick A
"可以在未创建的分支上运行,但是"git cherry-pick A..B
"则不行。
这意味着解决方案变成:
# make an orphan branch
➜ gitorphan git:(df931da) git checkout --orphan orphanbranch
Switched to a new branch 'orphanbranch'
# Now cherry-pick from previous branch a range of commits
➜ gitorphan git:(orphanbranch) git cherry-pick df931da..master
不需要先进行提交,再进行 cherry-pick。
git branch -m master old_master
git checkout --orphan master
-m = 将分支移动到新名称
checkout - 检出新的主干作为孤立分支
git update-ref -d refs/heads/BRANCH
git branch -d BRANCH
(甚至是git branch --delete --force BRANCH
)时,会打印错误消息而不是按照您的指示执行:error: Cannot delete branch 'BRANCH' checked out at '/home/me/folder'