Int*_*eXX 5 git git-merge git-revert team-explorer git-commit
好吧,我好像把一些事情搞砸了。
直到最近,我曾经能够进行合并提交,然后推送到原点,而无需显示单独的提交。现在,我在管道中只能看到合并提交:
在此开始之前,仅手动提交被推送到原点(或至少显示为这样):
这是行为更改后的团队资源管理器(VS 2019 v16.6.5):
...这是我当地的分支机构历史:
看到变化了吗?
a13adadf这一切都是在我恢复提交、修复并重新发布之后开始的。现在我遇到了某种奇怪的分支效应,我不知道如何让事情回到之前的状态。(我尝试研究这个问题,但是在搜索与 相关的任何内容时,信噪比非常低merge commit。)
如何让我的存储库“忽略”(即停止显示)合并提交?
(注意:我是唯一参与此存储库的开发人员。)
tor*_*rek 11
您之前可能执行过快进操作。如果条件正确,该git merge命令将执行此操作而不是合并:
--no-ff选项,这会禁用快进。\n\n\n
a13adadf这一切都是在我恢复提交、修复并重新发布之后开始的。
这一定已经创建了一个分支。这个词\xe2\x80\x94“分支”有一个问题,即\xe2\x80\x94,它会让你误入歧途,但你在问题中显示的图形片段表明这实际上是发生的事情。
\n\n\n如何让我的存储库“忽略”(即停止显示)合并提交?
\n
如果您只是想避免显示它们,您的查看器可能有一些选项可以执行此操作。
\n如果您想回到不创建\xe2\x80\x94之前的情况\xe2\x80\x94,您需要消除您创建的分支。
\n首先要记住的是 Git 的核心就是提交。刚接触 Git 的人,甚至那些已经使用它很长时间的人,常常认为 Git 是关于文件或分支的。但事实并非如此:它与提交有关。
\n每次提交都有编号,但这些数字不是简单的计数。相反,每次提交都会获得一个看似随机的\xe2\x80\x94,但实际上根本不是随机的\xe2\x80\x94哈希 ID。这些东西又大又难看,Git 有时会缩写它们(例如您的a13adadf),但其中每一个都是某个 Git 对象的数字 ID\xe2\x80\x94,在本例中,用于 Git 提交。
Git 有一个包含所有对象的大型数据库,可以通过 ID 进行查找。如果您给 Git 一个提交编号,它就会通过 ID 找到该提交的内容。
\n提交的内容分为两部分:
\n首先,有 Git 知道的所有文件的快照。这往往是大多数提交的大部分,除了一件事:文件以一种特殊的、只读的、仅限 Git 的、压缩的和去重复的格式存储。当您进行新提交时,其中大多数文件与先前的某些提交基本相同,新提交实际上不会再次存储文件。它只是重新使用现有的文件。换句话说,特定文件的特定版本会在许多提交重新使用它时进行摊销。重复使用是安全的,因为文件是只读的。
\n除了保存的快照之外,每个提交还存储一些元数据:有关提交本身的信息。这包括提交者的姓名和电子邮件地址,以及一些日期和时间信息等。值得注意的是,每个提交的元数据还存储供 Git 使用的提交号\xe2\x80\x94 以及该特定提交之前的提交的哈希 ID\xe2\x80\x94 。Git 将其称为父提交,或者对于合并提交,称为提交的父级。
\n这样做的目的是让 Git 能够向后工作。这就是 Git 的工作方式,倒着看。如果我们有一长串提交,全部连续,如下所示:
\n... <-F <-G <-H\nRun Code Online (Sandbox Code Playgroud)\n其中H代表链中最后一次H提交的实际哈希 ID,Git 将从 commit 开始,从其对象数据库中读取它。在 commit 中H,Git 会找到所有保存的文件,以及之前提交的哈希 ID G。G如果 Git 需要它,Git 将使用此哈希 ID从对象数据库中读取提交。这为 Git 提供了较早的快照,以及更早提交的哈希 ID F。
如果 Git 需要,Git 将使用哈希 ID F(存储在 中G)来读取F,当然还F包含另一个父哈希 ID。所以通过这种方式,Git 可以从最后一次提交开始向后工作。
这就给 Git 带来了一个问题:它如何快速找到链中最后一次提交的哈希 ID?这就是分支名称的用武之地。
\n考虑到上面的\xe2\x80\x94,并且故意变得有点懒,并将从提交到提交的连接绘制为一条线,而不是从子项到父项的箭头\xe2\x80\x94,我们现在可以像master这样绘制分支:
...--F--G--H <-- master\nRun Code Online (Sandbox Code Playgroud)\n该名称 master仅包含现有提交的实际哈希 ID H。
让我们添加另一个名称 ,develop它也包含哈希 IDH,如下所示:
...--F--G--H <-- develop, master\nRun Code Online (Sandbox Code Playgroud)\n现在我们有一个小问题:我们要使用哪个名称?在这里,Git 使用特殊名称HEAD来记住要使用哪个分支名称,所以让我们稍微更新一下绘图:
...--F--G--H <-- develop, master (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n这表示之后的结果git checkout master:当前分支名称是 now master,并master选择 commit H,因此这就是我们正在使用的提交(以及我们也在使用的分支名称)。
如果我们git checkout develop现在运行,Git 将切换到该分支。该名称仍然标识 commit H,因此没有其他需要更改的内容,但现在我们有:
...--F--G--H <-- develop (HEAD), master\nRun Code Online (Sandbox Code Playgroud)\n如果我们现在进行新的提交,Git 将:
\nH父级;I.Git 还会做一件事,但现在让我们画出这一部分。结果是:
\n...--F--G--H\n \\\n I\nRun Code Online (Sandbox Code Playgroud)\n那两个名字呢?还有一件事:Git 会将I哈希 ID 写入当前名称。如果是develop,我们得到这个:
...--F--G--H <-- master\n \\\n I <-- develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n请注意,该master名称保持不变,但名称develop已移动以指向最新的提交。
请注意,最初,当master和develop都选择 commit 时H,从某种意义上说,您与 一起使用哪一个并不重要git checkout。无论哪种方式,您都会将提交H作为当前提交。但是当你进行新的提交时,现在就很重要了,因为 Git 只会更新一个分支名称。没有人知道新提交的哈希 ID 是什么(因为它部分取决于您进行提交的确切时间),但一旦提交,develop将保留该哈希 ID(如果develop是当前名称)。
请注意,如果您现在git checkout master进行另一次新提交,则名称master将是这次更新的名称:
...--F--G--H--J <-- master (HEAD)\n \\\n I <-- develop\nRun Code Online (Sandbox Code Playgroud)\n不过,我们暂时假设您还没有这样做。
\n记住前面的图片,让我们git checkout master现在运行,然后返回使用 commit H:
...--F--G--H <-- master (HEAD)\n \\\n I <-- develop\nRun Code Online (Sandbox Code Playgroud)\n在这种状态下,我们git merge develop现在就跑吧。
git mergeGit 将执行它对\xe2\x80\x94执行的操作(请参阅下面的 \xe2\x80\x94),并发现合并基础是 commit H,这也是当前提交。另一个提交I位于提交之前H。这些是 Git 可以进行快进操作的条件。
快进并不是真正的合并。发生的情况是 Git 对自己说:如果我进行了真正的合并,我会得到一个快照与 commit 匹配的提交I。因此,我会走捷径,只需在拖动名称的同时检查提交即可。Imaster 结果如下:
...--F--G--H\n \\\n I <-- develop, master (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n现在没有理由在绘图中保留扭结\xe2\x80\x94我们可以将其全部排成一排。
\n有时,上述那种快进而不是合并的技巧根本不起作用。假设您从以下内容开始:
\n...--G--H <-- develop, master (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n并进行两个新的提交I-J:
I--J <-- master (HEAD)\n /\n...--G--H <-- develop\nRun Code Online (Sandbox Code Playgroud)\n现在您git checkout develop再进行两次提交K-L:
I--J <-- master\n /\n...--G--H\n \\\n K--L <-- develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n此时,无论您给 哪个名称git checkout,如果您git merge使用另一个名称运行,则无法从前进J到L,反之亦然。从J,您必须备份到I,然后转到共享提交H,然后才能前进到K然后L。
因此,这种合并不能是快进操作。相反,Git 会进行真正的合并。
\n为了执行合并,Git 使用:
\nHEAD) 提交:让我们先这样J做git checkout master;git merge develop选择提交L;最后\xe2\x80\x94 或者实际上,第一个\xe2\x80\x94commit 是合并基础,合并基础是根据称为最低公共祖先的图形操作来定义的,但简短且易于理解的版本是Git 工作从两个提交向后查找最佳共享的共同祖先。在本例中,这就是 commit H:两个分支的分歧点。虽然 commitG和更早的提交也可以共享,但它们不如 commit 好H。
所以 Git 现在将:
\nH快照与HEAD/J快照进行比较,看看我们更改了什么master;H快照与其他/L快照进行比较,看看它们发生了什么变化develop;和这是合并的过程,或者作为动词合并。如果可以的话,Git 会自己完成所有这些工作。如果成功,Git 将进行一次新的提交,我们将其称为M:
I--J\n / \\\n...--G--H M <-- master (HEAD)\n \\ /\n K--L <-- develop\nRun Code Online (Sandbox Code Playgroud)\n请注意,新提交又M指向提交和. 事实上,这就是使这个新提交成为合并提交的原因。因为快进实际上是不可能的,所以 Git必须进行此提交,才能实现合并。J L
你一开始是这样的情况:
\n...--G--H <-- master, develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n然后产生:
\n...--G--H <-- master\n \\\n I <-- develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n您使用git checkout master; git merge develop或类似的方式获得:
...--G--H--I <-- master (HEAD), develop\nRun Code Online (Sandbox Code Playgroud)\n之后您可以重复该过程,首先创建develop,然后创建两个develop 并 master命名新提交J:
...--G--H--I--J <-- master (HEAD), develop\nRun Code Online (Sandbox Code Playgroud)\n但此时你做了一些不同的事情:你git revert在master.
该git revert命令进行新的提交。新提交的快照就像前一个快照一样,只是回退了一个提交,所以现在您拥有:
K <-- master (HEAD)\n /\n...--G--H--I--J <-- develop\nRun Code Online (Sandbox Code Playgroud)\n中的快照K可能与中的快照匹配I(因此它重新使用所有这些文件),但提交号是全新的。
从这里开始,您git checkout develop编写了一个比 更好的提交J,我们可以将其称为L:
K <-- master\n /\n...--G--H--I--J--L <-- develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n然后你就回去master跑了git merge develop。这一次,Git必须进行新的合并提交。所以它就这么做了:
K--M <-- master (HEAD)\n / /\n...--G--H--I--J--L <-- develop\nRun Code Online (Sandbox Code Playgroud)\n现在,当您返回develop并进行新的提交时,您会得到相同的模式:
K--M <-- master\n / /\n...--G--H--I--J--L--N <-- develop (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n当您切换回masterand时git merge develop,Git 必须再次进行新的合并提交。快进是不可能的,相反你会得到:
K--M--O <-- master (HEAD)\n / / /\n...--G--H--I--J--L--N <-- develop\nRun Code Online (Sandbox Code Playgroud)\n假设您现在运行git checkout develop && git merge --ff-only master. 第一步选择develop作为当前分支。第二个要求与 合并master。这个额外的标志--ff-only告诉 Git:但只有当你可以快进时才这样做。
(我们已经相信 Git 可以快进地做到这一点,所以这个--ff-only标志只是一个安全检查。不过,我认为这是一个好主意。)
由于可以快进,您将得到:
\n K--M--O <-- master, develop (HEAD)\n / / /\n...--G--H--I--J--L--N\nRun Code Online (Sandbox Code Playgroud)\n请注意名称如何develop向前移动,指向 commit O,而不添加新的合并提交。这意味着您所做的下一次提交将 develop作为O其父提交,如下所示:
P <-- develop (HEAD)\n /\n K--M--O <-- master\n / / /\n...--G--H--I--J--L--N\nRun Code Online (Sandbox Code Playgroud)\n如果您现在git checkout master; git merge develop将获得快进,两个名称都标识新的提交P,并且您将回到提交develop允许快进的情况。
develop请注意,通过这样做,您实际上是在声称您根本不需要该名称如果你的工作模式是:
\nmaster以匹配那么您需要做的就是在 上进行新的提交master。
对另一个名称进行新的提交本质上并没有什么问题,如果这只是您有时的工作模式,那么这可能是一个好习惯:使用大量分支名称稍后会对您有所帮助,并且养成在之前创建新名称的习惯开始工作是好的。不过,您可能需要考虑使用比 更有意义的名称develop。
无论如何,请注意,Git 在这里关心的是提交。分支名称只是让 Git 帮助您查找特定提交的方式:每个名称找到的提交就是您使用该名称进行工作的点。实际的分支(如果有的话)是您所做的提交的函数。
\n换句话说:要使提交形成分支,您需要分支名称,但仅拥有分支名称并不会使提交形成分支。 那是:
\n...--F--G--H <-- master\n \\\n I--J <-- develop\nRun Code Online (Sandbox Code Playgroud)\n为您提供两个“最后”提交,但以 commit 结尾的单个线性链J。在一种意义上,有两个分支,其中一个以 结束H,其中一个以 结束J,但在另一种意义上,只有一个分支,以 结束J。我们可以添加更多名称,指向现有的提交:
...--F <-- old\n \\\n G--H <-- master\n \\\n I--J <-- develop\nRun Code Online (Sandbox Code Playgroud)\n现在有三个名称(和三个“最后”提交),但存储库中的实际提交集并未更改。我们只是F单独画了一条线,以便让名字old指向它。