合并两个文件使用 nvim -d 或 vimdiff。

huangapple go评论61阅读模式
英文:

merge two files with nvim -d or vimdiff

问题

昨天,Arch社区宣布成功迁移到git。恭喜!

阅读新闻文章以及使用新存储库的步骤时,我遇到了这样一行:“合并pacman pacnew /etc/pacman.conf.pacnew文件”。

我使用nvim -d(或vimdiff)比较了这两个文件(我的现有文件和新文件),我在一个git存储库内是否可以像我习惯的那样合并这些文件呢?

英文:

Yesterday the arch community announced the successful migration to git. Congrats at this point.

Reading the news article and the steps to use the new repositories I stumbled over the lines "merge the pacman pacnew /etc/pacman.conf.pacnew file".

I compared the two files (my existing and the new one) with nvim -d (or vimdiff) and thought is it possible to merge the files like I am used to within a git repository?

答案1

得分: 1

我的经验来自于Fedora/Centos,在那里rpm可能会生成*.rpmsave*.rpmnew文件,但我假设*.pacnew文件的行为完全相同。


而我想知道是否可以像我在git存储库中习惯的那样合并这些文件?

是的,可以。需要进行一些设置,但绝对可行。

步骤1,安装etckeeper

Etckeeper是一个程序,它

钩入像apt、yum、dnf或pacman这样的包管理器,以在包升级期间自动提交对/etc所做的更改。

这是每个人都应该安装的程序,无论是否计划使用git合并

Etckeeper从2011年开始完全支持Arch。

步骤2,为/etc创建一个额外的工作树

一般的工作树

当你使用git克隆或初始化存储库时,你会得到一个包含git在.git目录中存储提交的存储库对象存储、也存储在.git中的索引/暂存/缓存以及一个工作目录,其中包含与当前提交对应的文件。它们之间是一对一的关系。

但是git通过worktree命令支持将多个工作目录连接到相同的存储库对象存储中。额外的工作树共享原始的.git目录,但它们有自己的索引,并且文件被检出到不同的目录中。只有一个工作树可以检出给定的分支(尽管你当然可以检出任何特定的提交,或使用一个镜像分支)。

创建额外工作树的典型用例包括

  • 运行git测试
  • 检出旧版本,你希望能够一次性比较多个文件的旧版本与当前版本(difftool只允许你逐个比较一个文件)
  • 当你需要在一个分支上创建修复之前,不想放弃正在进行中的具有冲突的变基时。

用于存储/etc内容的工作树

如果在交互式变基时将/etc用作你的工作树,请注意git会检出旧的提交,因此/etc突然包含各种配置文件的旧版本。这对一些服务来说可能是一个问题。当有冲突时,git插入"<<<<<"/">>>>>"标记,你将得到绝对有无效语法/内容的配置文件,这不太可能不是一个问题。

因此,出于这些原因,你应该将/etc视为生产系统,通常不会触碰它,而是创建一个额外的工作树作为暂存系统,在其中你可以自由地破坏和修改。

cd /etc
git worktree add /root/etc-worktree worktree-main main
cd /root/etc-worktree

步骤3,利用git进行合并

为了利用git的合并功能,你需要将自己的更改与上游更改分开放在不同的分支上(这听起来比实际情况要复杂一些)。通过拥有一个包含上游更改的分支,然后你可以合并这个分支,git就能够正确使用它的合并算法。

你只需要为那些最终生成.pacnew.rpmnew文件的文件做这个操作,而且你可以事后做这个操作,所以可以只从一个main分支和一个worktree-main分支开始,随着时间的推移,根据需要添加上游分支。

你需要为每个你想合并上游更改的文件创建一个分支(例如,这仅适用于你已经对其进行了本地更改,这样会阻止包管理器更新的文件)。

示例

我的一台机器最初是在2017年安装的Fedora 26。为什么我知道?因为etckeeper是我在新机器上要做的事项清单上安装的第二个程序(只有安装完整的vim版本的优先级更高),并且/etc/hosts在其中出现的第一个提交具有提交消息"Fresh install of Fedora 26"。我是否提到etckeeper有多么棒?

随着时间的推移,我向/etc/hosts添加了一些条目(多亏了etckeeper,我知道了确切的时间!)。最初的hosts来自Fedora 26,只包含两行,但是在2022年9月(我怎么知道的呢?...)一个名为setup的软件包的更新带来了一个新的hosts文件,其中包含5行附加的注释行。因为我修改了hosts,所以这个新版本最终被命名为/etc/hosts.rpmnew

所以这就是我处理这个更新的方式:

我运行了git log -p hosts来找出我开始修改它之前的最新提交是什么。实际上,它是提交1d49be6b Fresh install of Fedora 26。所以这是上游更新分支的起点。

我的分支命名策略是,将所有这些上游分支的名称前缀加上rpmnew,因为`*.rpm

英文:

My experience is from Fedora/Centos where rpm might produce *.rpmsave or *.rpmnew files, but I am assuming *.pacnew files behave in the exact same way.


> and thought is it possible to merge the files like I am used to within a git repository?

Yes it is. It requires a little bit of setup but it is absolutely doable.

Step 1, Install etckeeper

Etckeeper is a program that
> hooks into package managers like apt, yum, dnf or pacman to automatically commit changes made to /etc during package upgrades.

THIS IS PROGRAM THAT EVERYONE SHOULD INSTALL, independently of plans of using git merge.

Etckeeper has had full support for Arch since 2021.

Step 2, create an additional worktree for /etc

Worktree in general

When you clone or initializes a repository with git you end up having the repo object storage where git stores commits in the .git directory, the index/staging/cache which also is stored in .git and a working directory which contains the files corresponding to the current commit. And there is a one to one relation between them.

However git supports connecting multiple working directories to the same repo object storage through the worktree command. The additional worktrees shares the original .git directory but gets their own indexes, and the files are checked out in different directories. Only one single worktree can check out a given branch (although you can of course check out any specific commit, or use a mirroring branch).

Typical use cases for creating additional worktrees are for instance

  • running git test
  • check out an old version where you want to be able to compare the old version towards the current version for multiple files at once (difftool only lets you compare one file at the time)
  • you have a rebase with conflicts ongoing that you do not want to abandon while you need to create a fix on a different branch before that work is finished.

Worktree for storing the contents of /etc

If you use /etc as your worktree when doing interactive rebase, then notice that git checks out older commits, and thus /etc suddenly contains old versions of various config files. This not unlikely to represent a problem for some services. And when there is a conflicts where git inserts the "<<<<<"/">>>>>" markers, you will have config files that definitely have invalid syntax/content which is unlikely to not represent a problem.

So for these reasons, you should consider /etc as the production system that you normally do not touch and rather create an additional worktree as the staging system where you are free to break and make modifications to.

cd /etc
git worktree add /root/etc-worktree worktree-main main
cd /root/etc-worktree

Step 3, taking advantage of git for merging

In order to make use of git's merge power you need to keep your own changes separate from the upstream changes on separate branches (this sounds more complicated than it actually is). By having a branch which contains the upstream changes, you can then merge this branch and git is able to use its merge algorithm properly.

You only need to do this for files that end up producing .pacnew or .rpmnew files and you can do this retroactively, so it is fine to just start with one main branch and one worktree-main branch, and add upstream branches as needed over time.

You need one branch per file with upstream changes you want to merge (e.g. this only applies to files that you have made local changes to which then prevents the package manager to update).

Example

One of my machines started out being installed with Fedora 26 in 2017. Why do I know that? Because etckeeper is the second program to install on my list of what to do on a new machine (only installing a full vim version has higher priority), and the first commit which /etc/hosts is present in has commit message "Fresh install of Fedora 26". Did I mention how awesome etckeeper is?

Over time I have added a few entries to /etc/hosts (and thanks to etckeeper I know exactly when!). The initial hosts from Fedora 26 only contained two lines, but then in September 2022 (how do I know when?...) an update to the setup package came with a new hosts file which contained 5 additional comment lines. Since I had modified hosts this new version ended up as /etc/hosts.rpmnew.

So this is how I handled that update:

I ran git log -p hosts to figure out what the newest commit before I started modifying it was. It was actually commit 1d49be6b Fresh install of Fedora 26. So that is the starting point for the upstream update branch.

My branch naming policy is to prefix all such upstream branches with rpmnew since *.rpmnew files will be the source for the content of those. You can decide to use the full path in the branch or just the file name. For some cases like /etc/mock/default.cfg using rpmnew/mock/default.cfg is obviously a much better choice than rpmnew/default.cfg while using rpmnew/sshd_config for /etc/ssh/sshd_config might be ok, just make a choice (where a hybrid approach is a possibility).

So with that in mind, I created a rpmnew/hosts branch and updated it with the new hosts.rpmnew content.

cd /root/etc-worktree
git branch rpmnew/hosts 1d49be6b
git switch rpmnew/hosts
cp /etc/hosts.rpmnew hosts
git add hosts
git commit -m /etc/hosts.rpmnew
rm /etc/hosts.rpmnew

Notice that this resets the worktree's content back to 2017, but that's fine because is not used for anything else. If I had worked directly in /etc that would have been a huge problem.

So the upstream changes branch is updated and I can then merge it into the main branch (via worktree-main first).

cd /root/etc-worktree
git switch worktree-main
git merge --ff main  # `worktree-main` should strictly follow `main` and only
                     # occasionally be ahead when doing a merge.
git merge rpmnew/hosts

The last merge command might result in conflicts, and if so you need to resolve those. I highly recommend using KDiff3 for conflict resolution.

After the merge is done then worktree-main is one merge commit ahead of main. This one we want to update /etc to use.

cd /etc
git switch main
git merge --ff worktree-main

and that's it. /etc was moved forward from whatever today's content was to today's content plus an update to hosts and git made use of its merge functionality along the way.

答案2

得分: -1

以下是翻译好的部分:

在撰写这个问题的过程中,我意识到我可以像现在大家都在做的那样向这个智能AI提问。由于我没有找到解决我的问题的答案,所以我想:为什么不测试一下AI的建议并为未来的读者提供答案呢?

所以这就是AI告诉我如何合并两个文件的方法。我将以pacman.conf的示例来描述它:

打开两个文件以进行差异比较模式:

sudo cp /etc/pacman.conf /etc/pacman.conf.bak

sudo vimdiff /etc/pacman.conf /etc/pacman.conf.pacnew

或者

sudo nvim -d /etc/pacman.conf /etc/pacman.conf.pacnew

要在各种差异之间导航,请使用以下命令:

]c   # 将光标移到下一个差异
[c   # 将光标移到上一个差异

要合并差异处的条目,请使用以下命令:

do   # 将右侧文件的内容合并到左侧文件(获取差异)
dp   # 将左侧文件的内容合并到右侧文件(放置差异)

我必须提到的是,在最后发生了一些我没有预料到的事情:

如果最后的差异导致最后的行相等(或整个文件相等),差异将折叠最后的行。这可能在第一次尝试时会有点令人困惑。

英文:

While writing this question I realized, I could do what everybody is doing these days and ask the dump AI. Since I didn't found an answer to my problem, I thought: Why not test was the AI is saying an provide the answer for future readers.

So this is what the AI told me to do if I want to merge two files. I describe it with the example for the pacman.conf:

open the two files in diff mode:

sudo cp /etc/pacman.conf /etc/pacman.conf.bak

sudo vimdiff /etc/pacman.conf /etc/pacman.conf.pacnew

or

sudo nvim -d /etc/pacman.conf /etc/pacman.conf.pacnew

To navigate between the various diffs use the following commands:

]c   # move the cursor down to the next diff
[c   # move the cursor up to the previous diff

To merge the entries at a diff use the following commands:

do   # merge the content of right file to left file (diff obtain)
dp   # merge the content of left file to right file (diff put)

I must mention that at the end something happened, which I didn't expected:

If the last diff create changes which makes the last lines equal (or the complete file equal) the diff will collapse the last lines. This might be a bit irritating at the first try.

huangapple
  • 本文由 发表于 2023年5月22日 15:59:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76304098.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定