热释光;博士
好的
想法,也不是
坏的
(旁注:有一种不同的、通常更好的方法来解决使用
git merge
而不是
git cherry-pick
,尽管这与你的愿望和能力没有关系。它也有自己的缺点。有关这些的详细信息,请参阅
Stop cherry-picking, start merging
. 请务必阅读尾声:
Stop merging if you need to cherry-pick
.)
长:三个关键要点
快进
最终是关于
,它有一个完整的网站专门介绍这个想法
Think Like (a) Git
. 这本书值得一读。最后一点是,通过
复制
提交,然后放弃原件,取而代之的是新的和改进的副本。正是这种放弃,以及伴随它而来的非快进,要求停止使用过时的承诺,这带来了所有导致简单问题的麻烦
不重新设置共享分支的基础
太
很简单。
(还有一些其他的,包括结尾的一个,除了对存储库历史学家来说,它们通常不那么重要。)
我知道一般来说,您不应该对已将提交推送到其他人正在从中提取的远程存储库的分支重新设置基础。
只要使用/将要使用/正在使用此分支的所有用户都同意,就可以使用rebase。
但是在合并之后,开发分支和主分支基本上是相同的,只是有不同的头。但是如果我在合并之后立即将开发分支重新设置到主分支上,我相信这两个分支将有一个共同的头(合并提交)
这可能是真的,但只有在像你遇到的那种微不足道的情况下。此外,这里还有一些术语问题。特别是,我们必须定义“头”。如果你这么做的话
Git术语表
是的,我们需要一个不同的tern:我们需要开始使用
提示提交
head
和
branch
,间接地
提示提交:
头
A
named reference
到
commit
branch
$GIT_DIR/refs/heads/
git-pack-refs[1]
分支
犯罪
在一个分支上被称为该分支的尖端。分支的尖端由分支引用
head
,随着分支的进一步开发,它将继续前进。一个Git
repository
可以跟踪任意数量的分支,但是
working tree
仅与其中一个关联(“当前”或“签出”分支),以及
HEAD
指向那个分支。
HEAD
(字面和全大写)与“head”(全小写)非常不同。这种区别在Windows和MacOS这样的折叠系统上变得模糊甚至消失了,但在其他方面却是至关重要的:只有一个
头
开发分支中的提交id都不会改变
在大多数情况下,所有对
development
master
将
将
头
和
-
每次提交都会保存代码的完整快照。
-
Git通过hash ID查找提交(通常是对象,包括提交)
7ad088c9a811670756a3fb60ac2dab16b520797b
.
-
1
-
每个提交存储其父级(如果提交是普通提交)或父级(如果提交是合并提交,则至少两个,通常正好两个)的哈希ID。
-
任何提交的内容一旦提交,就永远无法更改(事实上,
Git对象一旦创建就可以更改。
2
)
因此,如果我们从
... <-F <-G <-H <--latest
我们只需要存储
最新的
提交到某个地方,以便Git可以查找散列
H
用它来找哈什
G
查找提交以查找哈希
F
,以此类推(最终,Git将到达第一个提交,它已经完成了
家长,因为它不能有一个,这让Git停止。)
出于绘图的目的,由于提交的内容不能更改,我们可以用线将它们连接起来,只要我们在内部记住这一点,我们就只能继续
(从新提交到旧提交)。新的提交会记住它们的父级,但是现有的提交不能在创建子级时将它们的子级添加到它们中,因为太晚了:到那时父级将被冻结。让我们画一个稍微复杂一点的图表:
...--G--H <-- master
\
I--J--K <-- develop
这里是commit的父级
I
H
. 这个
名称
主人
包含原始哈希ID
H
git checkout master
名称
包含原始哈希ID
K
. 这些是
提交“头”或
,使用gitglossary使用的定义。
1
Git通过在每次提交中添加日期和时间戳来确保这一点,这样即使您强制Git重新提交与一分钟前完全相同的内容,也可以重新使用您的姓名、电子邮件地址和日志消息
同一个父哈希
时间戳
是不同的。你的意思是
不能
如果您不做任何更改,请强制Git每秒进行一次以上的提交,但这是我准备接受的限制。:-)
这是由于Git对象的hash ID实际上是该对象数据内容的加密校验和。这有两个目的:在给定一个摘要校验和的情况下,可以很容易地查找实际数据;而且,它使检测数据损坏成为可能,因为仅仅改变一个数据位就会产生一个新的、不同的校验和。
这个词汇表与大多数人的日常用语不符
Gitglossary尝试使用该名称
对于分支名称本身
分支
后面
小费,还有
提示提交
对于提交
H
和
. 用户通常会将这三个词混为一谈,将它们归为一个词
. 他们甚至可以用同一个词“分支”来指代诸如
origin/master
和/或可从该名称访问的提交。我试着称之为
remote-tracking branch
远程跟踪名称
我自己的条款如下:
分支机构名称
主人
,
远程跟踪名称
像这样的名字
,
提示提交
达格利特
对于提交及其链接的集合,通常通过选取最后一个提交并向后工作来找到。
添加提交
最后,我们做什么都不重要
call these
,只要
we all understand what each other is talking about
. 不幸的是,在实践中,人们对最后一部分有困难。那么让我们来说明添加新提交的过程。
对于Git来说,提交散列id才是真正重要的,我在这里把它画成单个大写字母。这个
姓名
主人
develop
,
原点/主
\
现在我们开始工作,导致
git commit
或者
. 我们从以下几点开始:
git checkout master
名称
主人
犯罪
H
头
(全部大写)到
主人
...--G--H <-- master (HEAD)
\
I--J--K <-- develop
H
,或运行
git cherry-pick
something
例如。奔跑的动作
git提交
新的
提交,使Git更新
所以它现在可以容纳
提交哈希ID。我们的新提交将接受下一个字母
L
(或者实际上,获取一些大而丑陋的hash ID)我们将得到:
...--G--H--L <-- master (HEAD)
\
I--J--K <-- develop
添加合并提交
向后的
K
K
J
,那么
我
和
H
G
等等,跳绳
L
L
,我们将访问
,那么
H
和
G
等等,跳过整个
I-J-K
二者都
新的
将两者都用作其父级的提交。那是个好主意
合并提交
git merge develop
:
...--G--H--L------M <-- master (HEAD)
\ /
I--J--K <-- develop
合并提交
M
有
父母。最突出的是它的
第一个家长
L
,因为
L
是
这个
在我们跑步的时候
合并分支
. 这意味着如果我们使用DAGlet,我们从
M
或是任何让我们
到
M
,然后反向工作,我们将
跳过
在这里。这通常正是我们想要的:我们所做的所有承诺
工作
直接地
在
主人
快进行动
在Git中,我们可以在任何时候将任何分支名称指向当前存在的任何提交。现在我们有了:
\ /
我们可以创建一个新名称,例如
zorg
L
或
H
或
或者不管我们喜欢什么,出于什么原因。让我们选择提交
没有特别好的理由
做同样的事
git checkout zorg
佐尔格
:
...--G--H--L------M <-- master
\ /
I--J--K <-- develop
.
.....<-- zorg (HEAD)
佐尔格
倒过来工作?自
佐尔格
J
然后
H
...--G--H--I--J
.
现在让我们强行移动
指向
相反,使用
git reset --hard <hash-of-L>
. 现在我们有:
..........<-- zorg (HEAD)
.
...--G--H--L------M <-- master
\ /
I--J--K <-- develop
如果我们从
佐尔格
倒过来工作?显然,顺序
...--G--H--L
注意,提交
无法再从
佐尔格
现在让我们做一个
佐尔格
指向提交
,就像
做:
...--G--H--L------M <-- master, zorg (HEAD)
\ /
I--J--K <-- develop
现在可以访问哪些提交?让吉特跟着
二者都
父母
,这样我们就可以
...--G-H-(L and I-J-K)-M
. 所以对于这个特殊的举动,我们是否从
任何一个
L
从
J
全部的
我们以前的承诺,再加上一些新的承诺。
在图表中,提交
和
J
两者都是
祖先
佐尔格
朝着Git自己很难从这些祖先那里得到的方向前进
M
. 术语表(在我看来是错误的)定义了
合并分支
但它不仅仅适用于
. 这不是一个真正的财产
合并分支
标记运动本身
.
J
到
L
不
快进,因为
J
不是的祖先
L
特别是,假设我们
佐尔格
指向
J
,我们跑了:
git push origin zorg
这会让我们的Git打电话给另一个Git
origin
他们自己的
分支机构名称
佐尔格
,指向提交
J
.
三
因为这是一个
现在我们来做我们的工作
git reset --hard
佐尔格
L
,然后尝试
git push
做
有一个
佐尔格
J
L
J
所以这个
就会失败
非快进
git push --force
佐尔格
以这种非快进的方式。
但是,不管我们是否进行第二次推动,如果我们移动
佐尔格
指向
然后运行:
再一次,这次,他们会很高兴地接受这个请求。那是因为
这
J
L
M
,
是
快速前进的行动。所以他们最终会得到
指向提交
M
,符合我们自己的情况。
三
如果
起源
尚未提交
J
,我们的Git会派他们来的
任何必要的父母也会做出承诺。
樱桃采摘和再基
这个
吉特樱桃采摘
承诺。不幸的是,提交是一个快照,当我们复制一个快照时,我们不只是想获取快照。一个典型的例子是一些热修复程序,它可能像修复拼写错误或删除一个顽皮的单词或其他东西一样简单。我们想把它看作是
所以呢
吉特樱桃采摘
基本上变成了一个提交
一组变化,通过运行
git diff
一旦我们有了更改,我们就可以将它们应用到其他提交,在提交的完整集合中的其他地方,进行新的和不同的提交,比如我们的提交
上面。我们将让Git复制cherry picked提交的日志消息,但是新提交的hash ID将不同。
...--G--H--L <-- master
\
I--J--K <-- develop (HEAD)
git rebase master
现在
,Git将首先列出可从中访问的提交
头
即。,
...-G-H-I-J-K
然后从
主人
,
...-G-H-L
,离开片场
I-J-K公司
. 然后将继续进行
我
新的和改进的
I'
吉特樱桃采摘
我很抱歉
追求
L
:
I' <-- HEAD
/
...--G--H--L <-- master
\
I--J--K <-- develop
头
直接指向新提交
我很抱歉
)然后重复
J
和
K
I'-J'-K' <-- HEAD
/
...--G--H--L <-- master
\
I--J--K <-- develop
作为最后的把戏,
git rebase
强制命名
移动以指向最终复制的提交,在本例中,
K'
,并重新连接
:
I'-J'-K' <-- develop (HEAD)
/
...--G--H--L <-- master
\
I--J--K [abandoned]
注意在这种情况下,
. 如果
起源
Git有一个
指的是
K
,我们现在尝试发送
K'
起源
他们的
发展
K'
,它们将以非快进错误拒绝。
4
实际机制
是使用合并。合并的基本提交是被精心挑选的提交的父级,所以我们确实得到了这个diff,但是我们也得到了第二个diff
,然后是一个完整的三方合并。这个合并是通过一个普通的,非合并来结束的
提交:
也就是说,cherry pick做
合并分支
,
不过,除了一些棘手的情况,你可以把它看作
应用父项与子项的差异,就像它是一个补丁一样
. 事实上,有些
做后者,而其他类型的
git再基
使用
吉特樱桃采摘
内部!这没有什么特别好的理由:只是历史上的意外,因为
吉特樱桃采摘
最初实施的
没有
使用适当的三向合并。当发现这不足以应付棘手的案件时,
它本身得到了改进,但旧的
git再基
继续用老办法。所有更新的
git再基
我们使用新的cherry pick(因为它几乎总是相同或更好),但是为了向后兼容,最古老的rebase形式仍然使用旧的方式。
如果我们先合并,那就太快了!
M
先进去,我们从这个开始:
…--G--H--L--M<--硕士(校长)
\ /
I--J--K<--发展
git checkout develop
git rebase master
这一次,当Git列出
是的
不
可从
主人
,没有。从
M
,git达到
K
主人
不
M
:
...--G--H--L------M <-- master, HEAD
\ /
I--J--K <-- develop
最后一幕
git再基
就是强行取这个名字
发展
直到最后一次复制提交,在这种情况下
:
...--G--H--L------M <-- master, develop (HEAD)
\ /
I--J--K
如果我们跑的话会得到完全一样的效果
git checkout develop; git merge master
,在快进操作中
发展
要提交的点
M
. 我们现在可以了
git push origin develop
因为
发展
在
然后搬到
M
是快进的,这是允许的。
如果我们现在对
,它们将如下所示:
...--G--H--L------M <-- master
\ / \
I--J--K N--O <-- develop (HEAD)
不要
合并,也可以:
...--G--H--L------M <-- master
\ /
I--J--K--N--O <-- develop (HEAD)
两种方法的区别
关键的区别是如果我们
不要
快进
发展
,的父级
N
是
而不是
M
发展
线性自
O
到
N
到
J
O
N
到
,然后沿着
的父级
M
(忽略第一个)到
K
如果你要做大量的历史检查,也许是为了寻找和修复错误,也许只是出于历史的兴趣,直线的,从不快速前进的方法给了你可以使用的优势
--first-parent
(Git标志上写着*at merges,只跟在第一个父对象后面)让你将来的工作更容易。如果你永远都不想那样做,那么这种区别根本没有区别。
还有一种选择,虽然用处不大,但值得考虑。假设,在合并之后
,你做了一个
真正的合并
在
发展
git checkout develop
git merge --no-ff master
你得到的是一个承诺
N
...--G--H--L------M <-- master
\ / \
I--J--K---N <-- develop (HEAD)
第一
N
K
,第二个是
M
. 这个
哈希ID
两个提交中的
保存的快照
每一个都应该是相同的。
5
这意味着您可以像以前一样执行历史搜索技巧,当您完全没有合并时,但是您还可以显示未来的开发
发展
以与主线相同的代码开始
主人
.
(实际上,这没什么必要,只要从另外两种方法中选择一种,但如果你这样做了,你就会得到这样的结果。)
因为任何一个操作Git的人都有可能在这里强加某种差异。不过,这样做通常是个坏主意。如果您正在检查一些您所知甚少的国外Git存储库,也要记住这一点:如果您看到了这种模式,您可以比较合并的树
M
和
N
看看有没有人做了什么奇怪的事。