[英]How do I import a commit into a branch
我有一个git commit发生在某个分支中,现在主服务器没有它。 如何导入对母版的提交?
希望根据需要通过合并请求使用合并。
Git没有拉取请求。 1 Git Hub具有拉取请求,其他使您可以将Git用作服务的Web托管提供程序也是如此。 这些服务为它构建了一个花哨的,更用户友好的界面,有几种底层的Git机制。
您具体询问的是git merge
。 在不了解发生了什么的情况下,可以使用此命令和/或GitHub等提供的精美Web界面。 我自己,这不是一个好主意。 但是,要了解实际情况 ,需要先“掌握”一些基本概念。
其中之一与术语分支本身的含义有关:请参阅“分支”到底是什么意思? 当有人说“分支”时,有时是指分支名称(或者Git的各种其他名称中的一个都不是分支名称),有时是指一系列模糊定义的提交 。
同样,使用GitHub上的pull request接口不需要所有这些。 要使用它,您只需单击一些Web按钮。 但这是一个好主意-至少在我看来-要知道这些按钮会发生什么,为此,您确实需要了解Git的工作方式。 因此,让我们深入研究细节。
1这样,我的意思是没有git pull-request
命令。 有一个git request-pull
命令,它的作用是产生一个电子邮件消息。 这就是Git支持拉取请求的程度:它有一个命令来生成电子邮件,要求别人做某事。
Git提交是快照中所有文件的快照状态,并带有一些描述快照的元数据 。 (从技术上讲,提交是间接表示快照的,因为快照是作为树对象单独存储的,但是在大多数情况下,您不需要知道这一点:从提交到快照之间存在单向链接,因此,给定提交Git总是可以找到快照。这种链接意味着多个不同的提交可以代表同一个快照而无需占用两次空间,这对于多个目的很有用。
每个提交都由哈希ID标识,例如b5101f929789889c2e536d915698f58d5c5c6b7a
。 这些事情是大而丑陋的,对于人类来说是不可能的,但是它们是Git 找到提交的方式,因此它们对于Git的操作至关重要。 任何一个特定提交的哈希ID始终是唯一的:该哈希ID是该提交,没有其他哈希。 其他所有提交都具有不同的哈希ID。 而且, 宇宙中所有地方的所有Git都同意这些哈希ID计算。 给定两个不同的Git存储库,如果它们都有一些提交ID H(即,它们都有一个哈希为H的提交对象),则该对象的内容必须相同。 2
提交中的元数据包括您的姓名(或进行提交的人的姓名)和电子邮件地址,以及提交时间的时间戳。 它包括您的日志消息,告诉每个人您为什么进行此提交。 但它还包括紧接在此提交之前的提交的哈希ID。 我们称该提交为父 。 结果是一个向后看的链。 如果我们有一个只有三个提交的小型存储库,则可以这样绘制:
A <-B <-C
在这里, C
是最后的提交。 它具有一些独特的,丑陋的哈希ID。 它还存储提交B
的唯一哈希ID,因此一旦找到C
,就可以使用它来找到B
同时B
的哈希ID为A
,因此我们可以使用B
查找A
由于A
是第一个提交,因此它根本没有父提交(从技术上讲它是一个根提交),这使我们不再向后走。
要了解提交以及具有哈希ID的所有Git对象的另一件事,就是您永远无法更改其中的任何一个。 原因是哈希ID是对象内容的加密校验和。 如果您要提交一个提交对象并只更改一个位(例如,修改日志消息中单词的拼写),您将最终得到一个新的,不同的commit和一个不同的哈希ID。 因此,一旦提交,就永远了:立即获取哈希ID 。 3
这对我们意味着什么,我们不需要将提交中的箭头绘制为箭头。 提交一旦存在,它将是永久的,并且与其父级的联系也将是永久的。 我们只需要记住,它们只走了一条路:倒退。 新的链接会出现这种承诺,但他们不能出现此承诺的任何地方去新。
2请注意,仅在遇到并交换对象的两个Git之间维护相同的哈希ID表示相同的基础对象内容的要求。 只要两个从未连接的存储库从不尝试相互通信, 就可以进行此类doppelänger提交。
3您可以完全删除某个提交(如果有些痛苦的话),只需不再引用它即可。 最终,底层的Git对象消失了,有效地释放了哈希ID。 由于当前的哈希ID系统中有160位,因此在任何Git存储库中只有2 160个可能的对象。 幸运的是,这足够了。 但是, 信鸽原理与生日悖论相结合仍然引起了一些有趣的理论问题, 新发现的SHA-1碰撞对Git有何影响? 有讨论。
给定上述存储库,如果我们知道提交C
的哈希ID,则可以找到所有提交。 我们将其存储在哪里? 怎么样: 在分支名称中? 让我们选择一个像master
这样的名称,并用它来写下C
的哈希ID:
A--B--C <-- master
现在,通过检出提交C
并以通常的方式做一些工作来进行新的提交:
git checkout master
... do some work ...
git add ... various files ...
git commit
新的提交将打包一个新的快照,添加我们提供的任何日志消息,添加我们的名称和电子邮件地址以及时间戳,并且至关重要的是,将新提交的父级设置为提交C
:
A--B--C--D
作为提交的最后一步, git commit
将采用新提交的哈希ID(无论实际校验和是什么,现在所有部分都永远固定在适当的位置),并将该校验和写下*名称master
:
A--B--C--D <-- master
这就是分支名称:在这里存储最后一次提交的哈希ID。 仅仅人类不必记住哈希ID,因为Git可以为我们记住它们。 我们记得只有master
拥有最新提交的哈希ID,其余的事情要由Git完成。
当然,您可以创建多个分支名称。 每个仅指向一个特定的提交。 现在让我们创建一个新的分支dev
:
A--B--C--D <-- master, dev
注意master
和dev
都指向提交D
,并且所有四个提交都在两个分支上 。 但是Git需要一种方法来知道我们进行新提交时要更改的名称 。 这是特殊名称HEAD
出现的地方。我们让Git将此名称附加到一个(并且只有一个)分支名称:
A--B--C--D <-- master (HEAD), dev
要么:
A--B--C--D <-- master, dev (HEAD)
我们使用git checkout
进行此操作,它不仅检出提交,而且还附加了HEAD
。 如果HEAD
附加在master
并且我们进行了新的提交E
则它看起来像这样:
E <-- master (HEAD)
/
A--B--C--D <-- dev
如果现在我们将HEAD
切换到dev
(通过执行git checkout dev
)并进行新的提交F
,则得到:
E <-- master
/
A--B--C--D
\
F <-- dev (HEAD)
假设我们有一个带有一堆提交的存储库,其中最后几个看起来像这样:
I--J <-- br1 (HEAD)
/
...--H
\
K--L <-- br2
在这里,我们有一系列的提交以哈希为H
结尾,其中H
持有一些快照。 然后有人(也许是我们)在分支br1
上又做了两个I
和J
提交,我们现在br1
。 有人-也许我们,也许有人-从H
开始,又做了两次提交K
和L
即使其他人在另一个存储库中创建了K
和L
,也是如此。 我们都有H
,并且由于各地的所有Git都同意哈希ID计算,因此我们都从同一commit开始 。
git merge
命令将执行的操作是弄清楚我们在分支br1
更改了什么 , 以及它们在分支br2
更改了什么 。 然后它将合并这些更改。 但是我们已经注意到,“ 分支 ”一词往往含糊不清。 Git在这里真正要做的就是找到常见的提交 ,这是我们俩都从那里开始的。 我们已经看到这是提交H
因此,提交H
是合并操作的合并基础 。 另外两个有趣的提交只是由当前分支命名的一个提交-在br1
的尖端提交J
,而另一个分支命名的一个提交在br2
的尖端提交L
所有三个提交都是快照 ,因此Git需要对其进行比较 :
git diff --find-renames hash-of-H hash-of-J
找到我们在br1中所做的 git diff --find-renames hash-of-H hash-of-L
找到他们在br2中所做的 Git现在可以将这两组更改组合在一起 。 如果我们更改了某些文件而没有更改,则Git应该获取我们的新文件。 如果他们更改了某些文件而我们没有更改,则Git应该获取其新文件。 如果我们都更改了同一文件,则Git应该从合并基础提交H
本身的文件副本开始, 合并两个不同的更改,然后将合并的更改应用于该文件。
因此,在这种情况下,这就是git merge
作用:它git merge
了我们的更改及其更改,从而生成了一个新的合并快照。 我喜欢将合并更改的过程称为动词 merge或merge 。 重要的是要记住,这可以由其他命令完成,因为其他Git命令可以做到! 合并或合并为动词时使用Git的合并引擎来合并工作。
不过,在这种情况下, git merge
现在继续进行合并提交 。 这几乎只是一个普通的提交:与其他任何提交一样,它具有快照和日志消息等。 使它与众不同的是合并提交,它具有两个父提交而不是通常的一个。 第一个父级与通常的父级相同:这是我们在运行git merge
时签出的提交。 第二个父对象就是另一个提交-我们通过使用名称br2
选择了一个提交,在这种情况下,就是提交L
因此,现在git merge
进行合并 (合并为名词)或合并提交 (合并为形容词),如下所示:
I--J
/ \
...--H M
\ /
K--L <-- br2
我们的分支名称会怎样? 当然,一如既往。 Git将此新合并提交M
的新哈希ID写入当前分支名称:
I--J
/ \
...--H M <-- br1 (HEAD)
\ /
K--L <-- br2
这就是我们合并某人的提交的br2
,在这种情况下,某人是在br2
上做出K
和L
。
(请注意,通常,如果我们使用git checkout br2; git merge br1
,将获得相同的快照 。合并基数仍然是H
,两个技巧是L
和J
,并且合并工作会产生相同的结果。其他合并的第一个父对象是L
,而不是J
,因此交换父对象,并且最终的名称更新将更新名称br2
而不是br1
,但是,如果我们开始添加额外的合并选项,例如-X ours
或-X theirs
,更多的东西可能会有所不同。)
git merge
命令都会导致合并 值得一提的是这里还有另外两个皱纹。 假设我们有这个图:
...--A--B--C--D--E--H--I <-- branch1 (HEAD)
\ /
F----G <-- branch2
然后我们运行git merge branch2
。 我们之前已经在提交H
合并了branch2,提交H
具有父E
和G
合并基数被定义为(松散地-从技术上讲,它是DAG中的最低公共祖先)在两个分支上的最接近的提交,也就是提交G
,因为从I
我们可以向后走到H
然后是G
,当然也可以是从G
我们就在G
呆在那里。
在这种情况下, git merge branch2
会说已经是最新的 ,什么也不做。 没错:他们的承诺是G
,而我们的承诺是I
, I
已经有G
作为祖先(在本例中为祖父母),因此没有新的工作可以合并。
我们还可以有以下相关情况:
...--A--B--C--D--E--H--I <-- branch1
\ /
F----G <-- branch2 (HEAD)
我们在这里运行git merge branch1
。 这次, 我们的承诺是G
而他们的承诺是I
合并库仍然像以前一样提交G
在这种情况下,Git的默认行为是对自己说: 根据定义,将G
与G
比较的结果是空的。 从定义上说,什么都没有结合的结果就是某物。 所以我真正要做的就是git checkout hash-of-I
。 因此,我将这样做,但是同时,使名称branch2
指向I
也要提交。 结果是:
...--A--B--C--D--E--H--I <-- branch1, branch2 (HEAD)
\ /
F----G
Git将此称为快速操作 。 Git有时将其称为快速合并 ,这不是一个好术语,因为没有实际的合并。
您可以强制Git进行真正的合并-将G
与自身进行比较,不将任何东西与任何东西合并,然后进行真正的合并提交-给出:
...--A--B--C--D--E--H--I <-- branch1
\ / \
F----G------J <-- branch2 (HEAD)
要在此处强制进行真正的合并,请使用git merge --no-ff branch1
。
(有时候你想要或者需要一个真正的合并,有时快进,反而是OK。对于它的价值,GitHub的虚拟主机界面上的clicky按钮不允许或执行快进合并,即使你希望他们实际上,他们总是使用git merge --no-ff
。)
拉取请求,甚至是Git更原始的git request-pull
选项,仅在该流程涉及多个Git存储库时才有用。
在这种情况下,我们可能在存储库#1中具有一系列提交:
I--J <-- master
/
...--H
同时,在存储库#2中,我们有:
...--H
\
K--L <-- master
由于这是两个不同的存储库,因此它们有自己的专用分支名称 。 一个拥有它的主人控股哈希ID I
。 另一个具有其主持有哈希L
。 提交H
在两个存储库中,而提交IJ
仅在#1中,而KL
仅在#2中。
如果我们要以某种方式合并两个存储库,同时更改名称以免它们冲突,我们将回到常规的合并状态:
I--J <-- master of Repository #1
/
...--H
\
K--L <-- master of Repository #2
这正是GitHub通过clicky Web界面所做的事情 。 无论您是谁-#1或#2; 让我们选择#2的具体性-您告诉GitHub: 我希望他们合并我的主人,即提交 L。GitHub然后将您的提交(逐位复制 ,以使它们的哈希ID保持不变) 复制到其存储库中,将提交L
的哈希ID放在不是 master
且不是任何其他分支名称的特殊名称下。 4然后,他们在GitHub上运行git merge
,使用这种完全不是分支名称的特殊名称。 如果一切正常, 那么他们会告诉控制仓库1的任何人您的拉动请求。
现在,控制存储库1的任何人都可以单击“合并提取请求”按钮。 这需要GitHub已经完成5的合并,并在其GitHub存储库中适当地移动其 master
或任何分支名称:
I--J
/ \
...--H M <-- master
\ /
K--L
现在,提交K
和L
出现在其存储库中,可以通过跟随master
的第二个父级向后链接来访问。
对于想要发出拉取请求的人来说,这对您意味着意味着您必须安排GitHub上的存储库具有GitHub可以为您测试合并的提交或提交链。 然后,GitHub将向该存储库的所有者提出请求,该所有者只需单击一下即可完成更新,方法是更新其分支的名称以使用GitHub进行的测试合并。
提交以及测试合并结果由您放入GitHub存储库中的提交确定。 如果您在本地计算机上拥有自己的单独存储库,则可以将提交放入其中,并使用git push
将这些提交发送到 GitHub存储库。
显然,这有点令人费解—但是,如果使本地计算机的存储库和自己的GitHub存储库保持同步,以使它们始终“看起来相同”,则可以忽略此处的额外层。 忽略这一层的问题在于它仍然存在! 如果您让您的存储库和GitHub存储库不同步,则会再次显示出来。
4发出拉取请求时,GitHub为其分配一个唯一编号(对于目标存储库而言唯一)。 说这是拉取请求#123。 将提交复制到GitHub存储库后,GitHub用于提交的名称为refs/pull/123/head
。 GitHub用于它进行的测试合并的名称是refs/pull/123/merge
。 如果测试合并因冲突而失败,则GitHub不会最终建立一个,也不会创建第二个名称。
5如果控制PR目标存储库的任何人将新提交推送到其分支,则GitHub进行的测试合并将变为无效(这是“过时的”)。 GitHub将在适当的时候进行新的测试合并。 我不确定他们是否删除refs/pull/123/merge
之间的名称,因为我从未测试过。
如何通过请求请求做到这一点
(但请避免将来自功能分支的任何其他提交合并到master
,例如(feature-branch >> master)拉取请求可能会引起的,您可以执行以下操作)
1)直接在远程(github?bitbucket?other?您没有提到但随时发表评论,我会进行调整)的master
上创建一个名为hotfix/master
的新分支(例如),然后从本地获取来自远程的最后裁判:
git fetch
2)在这里,提取输出应包含您新创建的分支,因此让我们创建其本地副本:
git checkout hotfix/master
3)现在,我们导入您想要的提交:
git cherry-pick <commitHash>
或者,如果您想要的提交是功能分支的最后提交,则只需:
git cherry-pick <branchName>
4)解决在此级别上可能发生的任何冲突,并将分支推送到remote:
git push origin hotfix/master
5)最后,回到远程接口,在其中创建请求请求,并在hotfix/master
和master
之间创建一个请求。
您可以cherry-pick
它:
$ git cherry-pick <commit hash>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.