15-源代码控制 - Some-soda

15-源代码控制

第 15 章 源代码控制

LazyVim 自带了几个功能来管理你的源代码控制历史记录,并且你也可以使用一些优秀的第三方插件。其中一些插件适用于多种版本控制系统,而另一些则以 git 为中心。本书将假设你使用 git,因为,嗯,你很可能正在使用Git,即使你同时也可以使用其他系统。

15.1. 集成终端(一篇牢骚)

出于我无法解释的原因,Neovim 自带一个终端模拟器。一个在终端中运行的编辑器自带一个终端,这对我来说很奇怪。你完全可以在终端中打开 Neovim,在 Neovim 中打开一个终端,然后在 Neovim 内的终端中再打开 Neovim。

如果你真想把事情搞得一团糟,再加几层嵌套的 ssh 会话。

我的编辑器里不需要终端。我已经有了一个终端,一个很棒的终端。当我需要新的终端窗口时,我只使用 Kitty 的分割、标签页和窗口。smart-splits 插件允许我在编辑器和终端之间无缝切换,Kitty 甚至为我管理通过 ssh 安装它自己。

或者我按 Control-z,这是 Vim 用户过去访问终端的传统方式。这是一个我真希望没有过时的快捷方式。按 Control-z 会“暂停”Neovim。如果你不知情,你会以为它没保存就关闭了你的编辑器,因为窗口消失了,你回到了你的终端。

译者注: Control-z 是 Unix/Linux 系统中将前台进程暂停并放入后台的标准信号 (SIGTSTP)。

但别害怕!它只是被暂停了,正如输出中的 'nvim' has stopped 消息所示:

suspended neovim dark

图 76. 暂停的 Neovim

正如这张截图也显示的,你可以使用任何 shell 中的 jobs 命令查看已停止(或正在运行)的后台作业列表。fg(foreground,前台的缩写)命令会重新启动暂停的 Neovim 进程。如果你有多个暂停的作业,可以使用 fg %# 命令选择一个特定的作业 ID(例如 fg %1 将运行 jobs 输出第一列中 ID 为 1 的作业)。

这不是 Neovim 特有的功能。Control-z 技巧适用于(几乎)任何长时间运行的 shell 命令。你甚至可以用 bg 命令代替 fg,让一个暂停的任务在后台继续运行(不过如果后台作业打印到 stdout,你很快就会糊涂)。

在终端分割和 Control-z 之间,编辑器根本不需要嵌入自己的终端。尽管如此,Neovim 还是自带了集成终端,所以我可能应该解释一下如何使用它。

15.2. 集成终端(这次是真的)

你可以随时在 Lazyvim 中使用快捷键 Control-/ 弹出一个终端。它将出现在你所有其他编辑器窗口的前面(除非你启用了 Edgy extra,那样它会出现在编辑器的下半部分),并且可以再次用 Control-/ 关闭。

Neovim 的终端窗口是一个超级奇怪的混合体,既是终端又是 Vim 窗口。一旦终端打开,你可以使用普通模式命令在其中导航。

然而,与插入模式不同,Escape不会让你进入普通模式,即使你的手指现在已经条件反射地去按 Escape 了。这实际上是有道理的,因为 Escape 是各种终端程序中经常需要输入的键,所以 Neovim 强占它是不礼貌的。LazyVim 设置了快捷键 <Escape><Escape>(快速按两次 Escape)从终端模式切换到普通模式,或者你可以使用难以输入的默认咒语 <Control-\><Control-n>

一旦进入普通模式,你可以使用包括 Seek 和 Search 模式在内的任何导航键在终端窗口的任何地方导航。如果你需要将一些输出的文本 yank 到剪贴板,这偶尔会有帮助。

ai 这样的键会将你送回“终端模式”,这实际上只是将每个按键发送给当前在终端中运行的程序(可能是你的 shell)。

令人烦恼的是,这意味着你不能使用普通模式在命令行上重新定位光标;它会回到你上次进入普通模式时的位置。

如果你想使用 Vim 普通模式来编辑你的命令行(在任何终端中;不仅仅是在 Neovim 内部),请配置你的 shell 使用“Vi 模式”。所有现代 shell 都支持某种版本的这个功能,它通常允许你使用 Escape 将 shell 置于伪普通模式。它为你提供了像 wb 这样的导航命令,以及像 dc 这样的基本行编辑命令来编辑命令行。

有第三方插件试图使终端体验更加一致和愉快,但在我看来,它们不值得花费时间。我可以只按 cmd-enter 获得一个新的 Kitty 终端窗格,并拥有完全正常的终端体验。

15.3. 检查你的 Git 状态

Lazyvim 预配置了一些精心配置的插件,让你的版本控制生活好很多。

其中最简单的是使用文件选择器列出自上次提交以来发生更改的文件。这将与其他文件选择器操作类似,只是它只列出在 git 中有修改的文件。

你可以用 <Space>gs 打开它。我经常用它来切换与我当前正在处理的功能相关的文件,实际上我更喜欢它而不是我们在第 9 章讨论的缓冲区选择器(它只显示打开的文件)。

译者注: <Space>gs (Git Status)。

这个选择器截图显示自上次提交以来我修改了两个文件:

fzf gitstatus dark

图 77. Picker 中的 Git 状态

确保安装了 delta-pager CLI 工具以获得漂亮的差异显示。

预览窗格显示了我添加和删除的行的差异。在左侧,你可以看到我聚焦到了 page.svx,右侧是该文件中部分更改的预览。

需要注意的令人困惑的地方是结果窗格中的前两列。这两列标记为 +-。我不确定为什么选择了这些符号,因为它们并不反映文件或行是添加还是删除。+ 列包含已暂存以进入下一次提交的文件,而 - 列包含已更改但尚未暂存的文件的状态。

这些列指示每个文件的 git 状态,它们的含义可能极其难记。符号本身很简单:

  • ~ 表示该行上的文件自上次提交以来包含修改
  • - 表示它已被删除
  • ? 表示它是一个未跟踪的文件(已添加到工作目录但未暂存或提交)
  • + 表示它是一个已在 git 中暂存的新文件

如果符号出现在第一列,则表示该文件已暂存并将包含在下一次提交中。如果它在第二列,则表示该文件尚未暂存。如果 ~ 同时出现在两列中,则表示其部分内容已暂存,部分内容尚未暂存。

除了让你有效地查看 git 状态外,这个选择器还允许你暂存整个文件。为此,聚焦一个文件并按 <Left> 键将文件移动到“已暂存”列。要取消暂存,请使用 <Right>。如果你想丢弃文件中的所有更改,请使用 <Control-x>

15.3.1. 其他选择器

Snacks.nvim 自带用于查看和搜索提交历史的选择器(<Space>gc),有点像日志浏览器,以及用于检出分支的选择器。后者出于某种原因没有快捷键,但如果你喜欢用选择器 UI 来完成这项任务,可以使用命令 :lua Snacks.picker.git_branches() 或为其绑定一个按键。

译者注: <Space>gc (Git Commits)。

你可以通过输入 :lua Snacks.picker.git 然后按 Tab 来找到各种不太常用的与 git 相关的选择器。

15.4. 当前聚焦文件的状态

每个缓冲区都有几个关于该文件更改的微妙指示。考虑这个截图:

git signs dark

图 78. 符号列中的 Git 状态

注意左侧边栏,行号的右边。它包含一个绿色条、一个小的红色三角形和一个短的橙色条。这些指示器分别显示行已被添加、删除和修改。

译者注: 这些指示器通常由 gitsigns.nvim 插件提供。

此外,在状态栏中,文件进度指示器的左侧,我们看到这些图标,它们总结了相同的信息:

git statusbar dark

图 79. 状态栏中的 Git 状态

15.5. 从编辑器暂存

你可以直接从编辑器将文件添加到 git 的索引区(这样它们就准备好提交了)。<Space>gh 菜单(助记符是 “git hunks”,git 块)有一堆有趣的子命令:

hunks menu dark

图 80. Git Hunks 菜单

你可以使用 <Space>ghS 来暂存整个文件,这会将其移动到我们上面讨论的 git 状态选择器的左列。如果你想暂存包含部分更改的补丁,请导航到你想要暂存的 hunk([h]h 对此超级方便)并按下 <Space>ghs

大多数人有一个不好的习惯,就是直接提交所有东西,而不是适当地整理他们的历史记录,但如果你是少数几个正确使用 git 的人之一(请成为那样的人),你会经常使用 <Space>ghs 命令。

你也可以使用 <Space>ghr 重置一个 hunk(有效地使其恢复到上次提交时的状态)。如果你想重置整个文件,请使用“但更大”的 <Space>ghR。重置是破坏性操作,所以要小心(不过 u 代表undo 通常可以让你回到原来的状态)。

如果你不小心暂存了一个 hunk,请使用 <Space>ghu 来取消暂存。与重置不同,这不会更改文件;更改仍然存在;它们只是不再被暂存了。

15.6. Git 信息快捷键

blame line(追溯行)(<Space>ghb) 命令显示最后更改光标当前所在行的提交,这对于回答那个极其重要的问题“我当时究竟为什么这么做?”很有用。

Preview hunk(预览块)(<Space>ghp) 临时渲染一个 hunk 的原始版本和更改后版本(一个在另一个上方),这样你就能确切地看到更改了什么。

Diff this(比较这个)(<Space>ghd<Space>ghD) 命令做同样的事情,只是在并排视图中显示,我们将在本章后面讨论。

就我个人而言,我使用这些命令中的许多都太频繁了,以至于弹出它们所需的按键次数显得太多。所以我创建了一个 extend-gitsigns.lua 文件在我的 plugins 目录中,将它们从 <Space>gh 复制到 <Space>h

列表 52. Git Hunks 菜单键映射

-- lua/plugins/extend-gitsigns.lua
return {
  "lewis6991/gitsigns.nvim",
  keys = {
    {
      "<leader>hb", -- 映射 <Space>hb
      "<cmd>Gitsigns blame_line<cr>", -- 执行 Gitsigns blame 命令
      desc = "Blame Line (追溯行)" -- 描述
    },
    {
      "<leader>hs", -- 映射 <Space>hs
      "<cmd>Gitsigns stage_hunk<cr>", -- 执行 Gitsigns 暂存 hunk 命令
      desc = "Stage Hunk (暂存块)"
    },
    {
      "<leader>hS", -- 映射 <Space>hS
      "<cmd>Gitsigns stage_buffer<cr>", -- 执行 Gitsigns 暂存缓冲区命令
      desc = "Stage Buffer (暂存文件)"
    },
    {
      "<leader>hr", -- 映射 <Space>hr
      "<cmd>Gitsigns reset_hunk<cr>", -- 执行 Gitsigns 重置 hunk 命令
      desc = "Reset Hunk (重置块)"
    },
    {
      "<leader>hR", -- 映射 <Space>hR
      "<cmd>Gitsigns reset_buffer<cr>", -- 执行 Gitsigns 重置缓冲区命令
      desc = "Reset Buffer (重置文件)"
    },
    {
      "<leader>hu", -- 映射 <Space>hu
      "<cmd>Gitsigns undo_stage_hunk<cr>", -- 执行 Gitsigns 取消暂存 hunk 命令
      desc = "Undo Stage Hunk (取消暂存块)"
    },
    -- 可以考虑添加 ghP (Preview Hunk) 和 ghD (Diff Buffer) 的映射
    -- { "<leader>hp", "<cmd>Gitsigns preview_hunk<cr>", desc = "Preview Hunk" },
    -- { "<leader>hd", "<cmd>Gitsigns diffthis<cr>", desc = "Diff Buffer" },
  },
}

我是通过从 LazyVim 网站上的 git-signs 配置中复制它们,并从 map 调用转换为 keys = 格式得到的。

15.7. Lazygit

Lazygit(尽管与 LazyVim 和 Lazy.nvim 共享 Lazy 命名空间,但它是由完全不同的开发者开发的)是一个用于与 git 交互的终端 UI 工具。它是一个独立的程序,如果你想使用它,你需要用你操作系统的包管理器(例如 brew install lazygit)来安装。

LazyVim 预配置了使用快捷键 <Space>gg 在终端窗口中显示 lazygit。我不会详细介绍如何使用这个第三方程序。它可以在一个更用户友好的界面中完成几乎所有 git 能做的事情。

Lazygit 需要一些学习才能习惯,但它有有用的菜单和快捷键的助记符,所以学习曲线相对平缓。

具有讽刺意味的是,在我开始使用 LazyVim 之前,我(以独立形式从命令行)使用 lazygit 的次数要多得多。我过去用 lazygit 来暂存更改,但现在我改用我们刚刚介绍的 <Space>h 菜单了。

我现在也主要使用卓越的 Graphite 工具来完成我的大部分 git 工作,它简化了我过去使用 lazygit 的许多流程(尤其是 rebase)。我仍然每天都使用 lazygit;只是不像过去那样 100% 的时间都开着它了。

15.8. 差异模式 (Diff Mode)

Neovim 自带一个强大但稍微难学的差异查看模式。它并排显示“之前”和“之后”的文件,甚至可以配置为显示“父级”和更改后的状态,如果你想要一个花哨的合并工具的话。

有几种不同的方法可以让你进入 Diff 模式。基本的方法是在命令行打开两个文件时指定它:

列表 53. 在 Diff 模式下打开

nvim -d file1 file2

这会在一个链接的差异视图中并排打开指定的文件。不过,大多数时候,你不会有两个单独的文件。相反,你会想查看当前文件和暂存索引之间的差异,你可以用快捷键 <Space>ghd 来完成。或者使用 <Space>ghD 来显示当前文件和最后一次提交之间的差异,无论暂存了什么。

一旦你在 Diff 模式下操作完成,要回到普通文件可能会有点棘手。问题在于,当一个文件处于 diff 模式时,即使其他窗口被打开或关闭,它也会保持这种状态。秘诀是使用 :diffoff 命令,它将禁用当前缓冲区的“diff 视图”。不过,这并不会关闭两个并排的窗口;你需要使用普通的窗口和缓冲区管理工具,如 <Space>bd<Control-w>q 来完成。

请注意,默认情况下,差异视图会将两个文件之间完全相同的任何代码折叠成单行。使用代码展开命令 zo 来展开一个部分。

15.8.1. 编辑差异

如果你使用 <Space>ghd 命令在 diff 视图中显示你的文件与索引模式对比,你可以继续编辑文件以进行额外的更改。如果你这样做,只编辑右侧的文件。这是“工作区”文件。左侧的文件是“索引”文件;它显示已暂存的更改。如果你想“编辑”左侧的文件,请使用 <Space>ghs<Space>ghr<Space>ghu 从右侧暂存、重置和取消暂存 hunks。直接编辑索引文件并不是被禁止的,但这会混淆 Diff 模式机制,所以坚持在右侧进行编辑、暂存和取消暂存。

像这样使用 diff 视图时,我发现暂存、重置和取消暂存的快捷键最符合我习惯的心智模型。然而,Neovim 中内置了两个有点奇怪的命令,你有时可能更愿意使用它们::diffget:diffput。为了节省几次按键,它们更常被输入为 :diffg:diffp,或者你可以使用快捷键 dpdo(助记符 diff obtain,获取差异)。

译者注:

  • :diffget (或 do): 从另一个窗口获取更改,应用到当前窗口。
  • :diffput (或 dp): 将当前窗口的更改推送到另一个窗口。

这些命令最常在可视模式(或与范围一起)使用,它们本质上意味着(在该范围内)我们要么“让这个文件与另一个文件相同”,要么“让另一个文件与这个文件相同”。

考虑这两个略有不同的文件:

diff mode dark

图 81. Diff 模式

左侧的文件代表我的索引状态,而右侧的文件是我的工作副本。索引版本缺少单词“Two”,所以我已经在右侧添加了它。它还有一个额外的“Four Point Five”行,我已经在右侧删除了。并且我修改了单词“Six”的拼写。

让我们探讨几种用 :diffg:diffp 使这些文件相同的方法。你可以在任一文件上使用这些命令,但通常只操作其中一个更有意义。对于这个例子,假设我正在处理右侧的文件。

我可以使用任何导航命令跳转到文件的第二行。如果你正在编辑一个真实的 git 索引文件,[h]h 快捷键对于在 hunks 之间跳转可能很有用。然而,当你在“diff”模式下时,你也可以使用 [c]c,它们表示“在更改之间跳转”,但当你在“diff”模式下时。(在非 diff 窗口中,LazyVim 已将这些键绑定到在类或类型之间跳转。)我通常只使用 [h]h,但在那些我使用的 diff 视图未附加到 git 历史记录的情况下,不应忘记 [c]c

所以,当我的光标在文件的第一行时,[c[h 将跳转到第二行,该行在我的文件中包含单词 Two,但在索引中没有。

我想暂存这个更改,所以我输入 :diffp,意思是“让另一个文件和这个文件一样”。

下一行在左侧文件中是 Four Point Five,但在右侧文件中被删除了。为了便于讨论,假设我想“取消暂存”这个更改,也就是说“让右侧文件和左侧文件一样”。要从右侧窗口执行此操作,我可以使用 Shift-V 进入行可视模式,并选择包含 FourFive 的行以及代表已删除行的那两个空白红行。现在我可以输入 :diffg:diffget,意思是“获取另一个窗口的内容并使我的窗口与其匹配”。由于 :diffget:diffput 接受范围,它会传递带有通常的 '<'> 标记的可视选择。

如果你发现自己喜欢上面的 diff 界面,但弄清楚哪些文件有差异会令人沮丧,你可能想配置 diffview.nvim 插件。我个人只使用 git status telescope 选择器,但 diffview.nvim 插件有一个漂亮的界面和一些方便的命令。

15.9. 配置 Vim Diff 作为合并工具

似乎每个人都讨厌解决合并冲突。有了 Diff 模式和 rebase,我实际上觉得这个过程有点享受。诀窍是拥有一个稍微复杂的 ~/.gitconfig(和一个非常大的显示器)。

我帮不了你解决显示器的问题,但 .gitconfig 需要看起来像这样:

列表 54. Git Mergetool 配置

[diff]
    tool = vimdiff
[merge]
    tool = vimdiff
    # 使用 zdiff3 风格显示冲突,更易读
    conflictstyle = zdiff3
[mergetool "vimdiff"]
    # 定义 vimdiff 工具如何启动 nvim 进行三路或四路合并
    # -d: 启动 diff 模式
    # $LOCAL: 本地版本 (通常是 HEAD)
    # $BASE: 共同祖先版本
    # $REMOTE: 远程版本 (要合并/rebase 的版本)
    # $MERGED: 包含冲突标记的待解决文件
    # -c: 执行 Ex 命令
    # '$wincmd w': 切换到下一个窗口 (通常是 MERGED 文件窗口)
    # 'wincmd J': 最大化当前窗口 (使 MERGED 文件窗口变大)
    cmd = nvim -d $LOCAL $BASE $REMOTE $MERGED -c '$wincmd w' -c 'wincmd J'

zdiff3 冲突样式通过自动解析相同的行,使差异更易于阅读。两个 tool = 行表示使用最后一行配置的 vimdiff 合并工具。

最后一行是一个命令,用于打开 Neovim,并带有 whopping FOUR 个窗口,并聚焦到合适的那个。

为了演示这一点,我创建了一个新的 git 仓库,其中包含两个具有冲突更改的分支。当我尝试将一个分支 rebase(我总是使用 rebase 而不是合并提交,因为它允许我在一次更改的隔离环境中处理冲突。这就是为什么对我来说每次提交只有一个更改很重要!)到另一个分支上时,当然,我最终遇到了这个错误:

列表 55. 一个可怕的 Git 冲突

✦ ❯ git rebase main
Auto-merging file
CONFLICT (content): Merge conflict in file
error: could not apply f611b6f... Uppercase
Could not apply f611b6f... Uppercase
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply f611b6f... Uppercase

要解决这个冲突,我运行 git mergetool。由于上面的 git 配置,它将打开 Neovim,并带有这四个不同的 diff 窗口:

mergetool dark

图 82. 超强的合并工具

顶部有三个窗口,底部有一个大窗格(也是痛苦 pain)。

  • 左上窗口 (LOCAL)

    显示“本地”更改。 “本地”的含义完全取决于你用来进入冲突状态的具体命令。在典型的 rebase 流程中,它返回到“main 分支的当前状态”。所以当发生冲突时,它会包含“另一个人的更改”,因此“本地”似乎不适用。

  • 中间窗口 (BASE)

    包含更改的“共同祖先”或“基础”。也就是说,这是在你或“另一个人”进行任何更改之前文件的状态。这个窗口在合并工具教程中不常被包含,但我发现在试图弄清楚基础和两个侧边窗口之间发生了什么变化时,它可能非常有帮助。

  • 右上窗口 (REMOTE)

    包含“远程”更改,像本地一样,这可能是一个用词不当。在 rebase 流程中,它通常意味着,“我在要 rebase 到 main 上的那个分支上所做的更改”。

  • 底部窗口 (MERGED)

    包含“文件的当前状态”,在 rebase 失败时,它包含凌乱的冲突标记。这是你应该进行编辑的唯一文件。

所有四个文件如果存在长的共同代码段,都会显示代码折叠。此外,如果你在下方的文件中滚动或移动光标,上方的文件也会滚动以保持所有内容同步,并且顶部三个窗口中的下划线将指示 diff 工具认为相对于下方窗口光标位置的“当前”行是哪一行。

大多数 rebase 流程从在下方窗口使用 vag:diffg 使其与上方窗口之一相同开始。然后你会使用 diffget 从左侧或右侧窗口获取 hunks,具体取决于上下文。你通常还需要进行一些手动编辑。

问题在于,:diffg 不知道要从哪个窗口获取内容,因为有多个窗口打开:

mergetool buffer error dark

图 83. Diffget 错误

相反,我们需要使用命令 :%diffg 22 是缓冲区编号。当你直接从命令行运行 merge-tool 时,缓冲区按它们打开的顺序列号。所以 1 是左侧缓冲区,2 是中间那个,3 是右侧那个,4 是下方窗口。如果你不确定,可以使用 <Space><comma> 快捷键显示缓冲区列表:

mergetool buffer numbers dark

图 84. Picker 中的缓冲区编号

在此列表中,第一列包含缓冲区编号。这个数字通常从 Neovim 最近一次打开时开始单调递增,所以如果你已经编辑了一段时间,它可能会变得很高。但是当你使用 git mergetool 时,它通常会打开一个全新的 Neovim 实例,并且预期编号是 1-4

运行 vag:%diffg 2 命令后,底部窗口看起来与中间窗口相同,这是在创建任一分支之前所有内容的状态。如果我使用 vag 然后 :%diffg 1,它看起来会与 main 相同,而 vag 后跟 :%diffg 3 会使其看起来与我的分支相同。然后我可以有选择地查看缓冲区之间的差异,并分别使用 :diffg # 从左侧或右侧获取更改。

合并冲突总是可能有点压力,但我发现四窗口视图通常更容易理解更改了什么以及为什么更改。话虽如此,我只有在遇到特别棘手的合并情况时才会使用它。通常,我使用 git-conflict.nvim 插件。

15.10. Git-conflict.nvim

虽然 merge-tool 在处理特别复杂的合并时非常有帮助,但对于简单的冲突,我通常发现直接编辑带有冲突标记的文件更快。一个名为 git-conflict.nvim 的插件提供了语法高亮和一些快捷键来帮助导航冲突。

用类似这样的配置来设置它:

列表 56. Git Conflict 配置

-- lua/plugins/git-conflict.lua
return {
  "akinsho/git-conflict.nvim",
  version = "*", -- 或者指定一个版本
  -- 建议延迟加载,只在检测到冲突时加载
  event = "BufReadPost",
  config = function()
    require('git-conflict').setup({
      default_mappings = {
        ours = "<leader>ho",    -- 选择我们的版本 (ours)
        theirs = "<leader>ht",  -- 选择他们的版本 (theirs)
        none = "<leader>h0",    -- 选择基础版本 (base/none)
        both = "<leader>hb",    -- 同时选择两者 (both)
        next = "]x",            -- 跳转到下一个冲突
        prev = "[x",            -- 跳转到上一个冲突
      },
      -- 其他配置选项...
    })
    -- 设置额外的快捷键
    vim.keymap.set("n", "<leader>gx", "<cmd>GitConflictListQf<cr>", { desc = "冲突列表 (Quickfix)" })
    vim.keymap.set("n", "<leader>gr", "<cmd>GitConflictRefresh<cr>", { desc = "刷新冲突" })
  end,
}

译者注: 更新了配置示例,使用了推荐的 config 函数方式来设置插件,并添加了 event 以实现延迟加载。同时将 keys 部分的映射移入了 config 函数内部。

我使用我之前为暂存 hunks 设置的 <Space>h 前缀,并为其添加了几个新命令。启用此扩展后,如果你打开一个有冲突的文件,它会以不同的颜色高亮冲突标记。在我的纯文本示例文件上,它看起来像这样:

git conflict dark

图 85. 冲突标记

冲突标记包括上面的“当前”(main 上的内容)代码,和下面的“新的”(正在 rebase 的内容)代码,以及中间的原始或基础代码(在任一更改之前)。

我可以使用 ]x 快捷键快速跳转到下一个冲突(在本例中只有一个)。然后我可以使用以下快捷键之一来解决冲突:

  • <Space>ho 选择顶部版本 (ours)
  • <Space>ht 选择底部版本 (theirs)
  • <Space>hb 同时选择两者 (both)
  • <Space>h0 回到中间的版本 (none/base)

ot 快捷键很难记住。严格来说它们的意思是“ours”(我们的)和“theirs”(他们的),但根据你执行合并或 rebase 的顺序,它并不总是语义上映射到你自己或别人的提交。我只记得 o 在字母表中 t 之前,所以它表示上面的更改。如果你愿意,也可以将它们映射到更具助记性的快捷键。

在所有情况下,尤其是在后两种情况下,你很可能需要进行一些手动编辑以使代码看起来正确。这是正常的。没有任何冲突管理扩展使用 AI 来语义地理解更改意图做什么,所以你仍然需要自己完成那部分工作!

大约百分之九十的情况下,这个插件就是我解决冲突所需的全部。我只在情况特别棘手或复杂时才使用 mergetool。

15.11. 总结

本章介绍了许多从 LazyVim 内部与 git 和版本控制交互的不同方法。你可能不会全部使用它们,但我想提供多种选择,以便你可以决定哪些最适合你。

也许你想使用 Lazygit,或者你想留在编辑器中使用 git-signs 和原生 Vim diff 模式提供的功能。也许你想安装一些额外的插件,如 git-conflict.nvim 或 diffview.nvim 来简化你的体验(你可能想看看的其他插件包括 Neogit 和 mini.git)。

或者也许你根本不想从编辑器管理这些东西,只想切换到终端模式并使用 git 或像 graphite 这样的包装器。无论哪种方式适合你,LazyVim 都提供了你需要的集成。

在下一章中,我们将承认现在不再是 2020 年了,并讨论人工智能。