课程 21 · 历史与融合
合并与变基:通过图表进行选择
从相同分歧的两个相同副本运行合并和变基,比较两个图、父级和文件树,并了解在它们之间做出决定的一条规则:还有谁已经拥有您的提交。
- 级别: 中级
- 阅读 3 分钟
- 6 分钟的视频
- 已更新
- 在这节课之前: 合并具有不同历史记录的分支, 了解快进合并, 将未发表的作品重新建立到新的基础上
进度已保存在此浏览器中。
播放时会加载来自 YouTube 的视频(隐私增强模式)。
你将学到什么
- 从相同的副本开始合并和变基,因此比较是公平的。
- 读取合并提交的两个父级和重新基址提交的单个新父级。
- 解释为什么两个副本可以以相同的文件结尾,但仍然具有不同的历史记录。
- 选择一条规则:仅重写其他人没有的提交。
开始之前
您需要第 20 课中的分歧的两个单独副本,名为 gittree-merge 和 gittree-rebase。在每个副本中,分支 feature/signup-form 保存您的一个提交(称为 S),分支 updated-main 保存一个不同的提交(称为 N),两者都从同一个提交 M 开始,并且 rescue/signup-before-rebase 指向 S。主题分支已签出,工作树是干净的。不要根据第一个结果制作第二个副本。
这里的一切都是本地的并且采用免费计划(GitTree要求您登录):没有托管帐户,没有提供商,没有互联网。每个身份都是虚构的(example.com),您永远不应该在真实的项目中运行本课程。
在 GitTree 中或使用本机 Git 命令进行更改,而不是在同一副本中进行更改。之后读取结果总是安全的。菜单标签来自GitTree 2.0自己的字符串;本机命令在一次性副本中使用 Git 2.43 运行。 GitTree 步骤未在 macOS、Windows 或 Linux 上运行;应用程序的快捷方式使用 macOS 上的 Command,其中此页面写入 Ctrl。
概念和状态效应
只有当两个副本都从相同的引用和相同的文件开始时,比较才是公平的。如果您先合并然后重新设置该结果,您会问一个不同的问题:重播现在在其范围内有一个合并提交。
Merge 保留两条线的工作。它添加了一个新提交 K,有两个父提交:首先是 S,然后是 N。S 和 N 保留其 ID。
Rebase 在 N 之上重播您的提交。结果 S′ 与 S 具有相同的更改,但有一个新的父级,并且提交的 ID 包括其父级,因此 S′ 有一个新 ID。旧的 S 幸存下来只是因为救援分支仍然指向它。
当没有冲突时,两个副本都以相同的文件树结束。相同的树并不意味着相同的历史:一个图保留菱形,另一个图保留直线,并且提交 ID 不同。
哪一个是正确的并不是品味问题。如果其他人已经有您的提交,则重播它们会破坏他们的副本,因此请合并。如果提交是私有的,则变基是安全的,并且如果您的团队愿意的话,可以提供线性历史记录。两者都不会让代码变得更好。
在每个副本中,仅主题分支移动。服务器、updated-main 和原来的 main 没有被触及。
准备准确的项目状态
做一个分歧,然后复制两次。从第 18 课留下的项目的单独普通副本开始(主要是合并提交 M)。保持原始的 gittree-features 不变,并且永远不要在源签出中运行它。演示身份为 GitTree Demo,demo@example.com,仅在此存储库中关闭签名。
完整的文件输入
仅在下面指定的阶段保存。这些是完整的 UTF-8/LF 正文,最后有换行符;永远不要粘贴 Markdown 栅栏。未更改的可运行文件继承命名输入。单独变体中的版本不得覆盖原始版本。
index.html — 注册
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GitTree Features — course demo</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="site-header">
<a href="#main">GitTree Features</a>
</header>
<main id="main" tabindex="-1">
<section class="hero" aria-labelledby="hero-title">
<p class="eyebrow">GitTree Learn demo project</p>
<h1 id="hero-title">See your work clearly.</h1>
<p id="hero-copy">Choose one change, then check what Git saved.</p>
</section>
<section class="features" aria-labelledby="features-title">
<h2 id="features-title">Follow your work</h2>
<div class="cards">
<article class="feature-card">
<h3>History</h3>
<p>Read commits and branches together.</p>
</article>
<article class="feature-card">
<h3>Changes</h3>
<p>Review the exact content selected for a commit.</p>
</article>
<article class="feature-card">
<h3>Recovery</h3>
<p>Inspect the scope of a recovery point before using it.</p>
</article>
</div>
</section>
<section aria-labelledby="plans-title">
<h2 id="plans-title">Choose the workflow you need</h2>
<div class="cards">
<article class="feature-card">
<h3>Local Git</h3>
<p>Local Git workflows work without signing in.</p>
</article>
<article class="feature-card">
<h3>Connected workflows</h3>
<p>Provider integrations and Nour require Pro or an applicable trial.</p>
</article>
</div>
</section>
<section aria-labelledby="signup-title">
<h2 id="signup-title">Try the demo form</h2>
<form id="signup-form" novalidate>
<label for="demo-email">Demo email</label>
<input id="demo-email" name="email" type="email" autocomplete="off" aria-describedby="signup-hint">
<p id="signup-hint">Use a fictional example.com address. Nothing is sent.</p>
<button type="submit">Check the address locally</button>
<p id="signup-result" role="status"></p>
</form>
</section>
</main>
<footer><p>Built by a fictional team for GitTree Learn. Review one change at a time.</p></footer>
<script type="module" src="site.js"></script>
</body>
</html>docs/integration-note.md — DOC_MERGE
# Integration review
This note was written on main while signup work was private.在GitTree
使用 Command+Shift+P(在 Ubuntu 上为 Ctrl+Shift+P)打开调色板,输入准确的命令名称并选择它。对指定分支的操作使用该分支的右键单击菜单。
- 打开gittree-merge。然后选择 New Tab 并从其 Recent 列表中打开 gittree-rebase。每个选项卡都以其文件夹名称为标题,因此您可以区分这两个选项卡。
- 在 gittree-merge 选项卡中,保持
feature/signup-form处于选中状态。右键单击updated-main并选择 Merge updated-main into feature/signup-form。 - 在 Record it as 下选择 Always a merge commit,然后选择 Merge 一次。新提交有两个父提交:首先是 S,然后是 N。
- 切换到 gittree-rebase 选项卡。它仍然位于 S 和 N,与您保存时一模一样。不要使用合并副本。
- 右键单击
updated-main并选择 Rebase feature/signup-form onto updated-main。阅读 Options for this rebase 并保留两个选项,然后选择 Rebase feature/signup-form 一次。 - 选择重播的提交。它的父级是N并且它的ID是新的。老S仍以
rescue/signup-before-rebase命名。 - 在每个选项卡中打开 Show Files →
index.html和docs/integration-note.md。两份副本中的页面和注释相同。然后用 Back to Graph 返回。 - 比较两个图,然后在选择结果之前说出哪个规则决定该分支的选择。保留两份副本原样。
对于课程中的每个普通提交:Summary 是规定的主题,Description 为空,并且选项 Amend Previous Commit、Skip Hooks、Add Signed-off-by、Different Author、Push After Commit 和 Allow Empty Commit 保持关闭状态。本课的承诺不是普通的。
在航站楼
在第 18 课结果的单独干净副本中构建分歧(您在应用程序中所做的工作,以本机命令编写):
git switch --create feature/signup-form将完整的注册页面保存为 index.html,然后:
git add -- index.html
git commit --quiet -m "feat: add the local signup demonstration"
git branch rescue/signup-before-rebase
BASE_SHA="$(git rev-parse HEAD~1)"
git switch --quiet --create updated-main "$BASE_SHA"在 updated-main 将完整的 DOC_MERGE 注释保存为 docs/integration-note.md,然后:
git add -- docs/integration-note.md
git commit --quiet -m "docs: add the integration review"
git switch --quiet feature/signup-formBASE_SHA 是两条线都源自的提交 M。现在从这个确切的状态 (cd .. && cp -R gittree-features gittree-merge && cp -R gittree-features gittree-rebase) 制作两个独立的副本。在合并副本中:
git merge --quiet --no-ff --no-edit updated-main
git rev-list --parents -n 1 HEAD
git rev-parse 'HEAD^{tree}'
git status --short在未触及的变基副本中:
git rebase --quiet --no-autostash --no-update-refs --no-autosquash updated-main
git rev-list --parents -n 1 HEAD
git rev-parse 'HEAD^{tree}'
git status --shortgit rev-list --parents -n 1 HEAD 打印提交的 ID,后跟其父项:合并副本中的三个 ID,变基副本中的两个 ID。两个 HEAD^{tree} 行具有相同的 ID,并且 git status --short 在这两行中不打印任何内容。两个 HEAD 提交 ID 不同。您的 ID 与其他人的 ID 不同;将您的内容相互比较,而不是与此页面进行比较。
如果主题没有自己的提交,并且 updated-main 位于其前面,则 git merge 将默认快进并且不创建合并提交。那就是不同的输入,不同的结果;有目的地选择它,切勿伪造双亲合并。
结果
Where you end: 合并副本有一个新提交 K,其父提交为 S 和 N; rebase 副本有 S' 和父 N,旧的 S 由救援分支保留。文件树是相等的,两个状态都是干净的,并且原始的main、M和服务器未受影响。
常见错误
- 假设直图意味着旧的提交 ID 仍然存在。变基总是会创建新的提交。
- 从合并结果开始变基。这是一个不同的实验,重播可以包括合并提交。
- 比较两个 HEAD ID 并期望它们一致。相反,比较树木。
- 完成后重复合并或变基。
如果出现问题
不要丢弃任何一个结果来强制它们匹配。如果树不同,请比较起始引用、任何冲突解决方案和任何额外提交,然后从保存的分歧重新开始。救援分支仅保留提交的历史记录;它不保留未提交的文件。
检查你的理解情况
回答每个问题,然后阅读原因。标记为“必填”的问题将有助于完成本课程。
0 个(共 6 个)正确
正确回答所需的问题以完成课程。
在此浏览器中检查。
练习
报告两个父列表、两个 HEAD ID 和两个树 ID。解释为什么不同 HEAD ID 旁边的相同树 ID 是预期的结果。然后预测当主题没有自己的提交时默认合并会做什么,但不这样做。
您自己的确认:该网站无法看到您的计算机。
要完成本课程,请正确回答所需的问题并确认实践练习。
回顾
- 相同的开始,不同的历史。
- 同等的树木并不意味着同等的历史。
- 当其他人有你的提交时合并。仅对私有的进行变基。
有关的
术语表介绍
Rebase — 重新设置;重播——重播;救援部门 — فف فٸ。
视频文字记录
视频的旁白,逐条提示。
显示成绩单
- 合并还是变基?同样的工作可以离开 两个不同的历史。
- 我们将从同一个起点运行两者 点并比较。
- 您的分支有一项提交。更新后的 分行还有另一个。
- 两者都来自同一个提交,所以两者都不是 包含另一个。
- 首先,准备分歧并制作两个 相同的副本。
- 公平的测试需要相同的参考和文件 在两份副本中。
- 这是第一个副本。我们将并入 这个。
- 这是第二份。我们保留它 变基未受影响。
- 右键单击 update-main 并选择其 合并命令。
- 在记录为下,选择始终合并 提交。
- 选择合并一次,等待真正的合并 结果。
- 新的提交有两个父母:你的 提交,然后更新主的。
- 现在是第二个副本。还是有这两个 原始提交。
- 再次右键单击updated-main,然后选择 这次重新设定基准。
- 该对话框命名分支和 目标。将这两个选项都关闭。
- 变基一次。 Git 重放您的提交 更新主的顶部。
- 重放的提交有一个新的父提交,所以 它得到一个新的ID。
- 旧的提交没有丢失:救援 分支仍然指向它。
- 现在比较文件。该页面是 两份副本相同。
- 集成说明也是如此。相同的文件, 不同的历史。
- 看这两个图:一个菱形,一个 合并提交和一条直线。
- 那么您应该使用哪一个呢?问还有谁 有你的承诺。
- 如果其他人有,则合并。变基会 改写他们的历史。
- 如果提交是私有的,则 rebase 是 安全,并给出一条直线。
- 两者都不会让你的代码变得更好。规则 是关于所有权。
- 现在在终端中证明这一点。首先, 每个小费的父母。
- 合并副本的三个 ID:提交、 和它的两个父母。
- 变基副本的两个 ID:提交、 和它的一个父母。
- 然后是文件树:一个 ID,打印出来 两次。相同的文件。
- 但两个提交 ID 不同。平等 树木,不同的历史。
- 更改输入:假设您的分支有 没有自己的提交。
- 默认合并将快进, 没有合并提交。
- 永远不要对别人的分支进行变基 已经有了。
- 如果树不同,则比较起始树 首先参考。
- 不要删除一个结果来强制两个结果 比赛。
- 练习:报告父母、身份证和 树木来自您自己的副本。
- 接下来,重新排序、压缩和重新措辞 本地提交。