与 Git 跟踪的文件交互合并和未跟踪的本地文件合并

12
我使用了一些软件包(例如GitLab),你可以通过从它们的 Git 存储库中克隆来安装。通常会有一些 config.example 文件(在版本控制下),您需要将其复制到您自己的 config 文件中(不受版本控制或在 .gitignore 中忽略),并进行适当的调整。

当上游软件包更新并更改配置文件选项时,这只会反映在 config.example 中。

是否有一系列 git 命令可以帮助我比较 config.example 文件的更改与 upstream/HEAD 中的新文件,并可能以交互方式将它们合并到我的本地 config 文件中?

如果我能得到类似于 git add/commit --interactive 的交互式补丁模式那就太好了。

7个回答

2
  • 您可以使用vimdiffvim作为合并工具。
  • Emacs也可以使用ediff-mode进行此操作。

2

git checkout --patch 选择差异块,最简单的方法是将您的内容放在上游路径中,然后进行清理:

cp config  config.example
git checkout -p upstream   config.example
mv config.example  config
git checkout @  config.example

这将从git add --patch中获取你所需的两个差异块选择。

2
你可以用许多方法来处理这件事,我能想到两种:使用git-merge-file或是传统的patch。使用git-merge-file方法可以提供一些交互性,而使用patch方法则没有。

git-merge-file解决方案

假设你有一个原始文件config.example,用它来创建一个本地未版本化的文件config.local。现在当上游更新config.example时,你可以按照以下步骤合并任何新更改。
$ git fetch
$ git show master:config.example > config.example.base
$ git show origin/master:config.example > config.example.latest
$ git merge-file config.local config.example.base config.example.latest

这将使用通常的冲突标记更新config.local,然后您需要使用您喜欢的合并工具(如Emacs中的ediff,我相信Vim也有类似的模式)来解决它们。例如,以下三个文件:

config.example.base:

Original: 
          some config

config.example.latest:

Original: 
          some config

Upstream:
          new upstream config

config.local:

Original: 
          some config

My changes:
          some other config

将会像这样合并:
Original: 
          some config

<<<<<<< config.local
My changes:
          some other config
=======
Upstream:
          new upstream config
>>>>>>> config.example.latest

你可以轻松地编写这个脚本。另外,git-merge-file 可以操作任何 3 个文件,它们不一定需要在 git 中进行版本控制。这意味着你可以使用它来合并任何三个文件!
使用 patch 的解决方案:
假设文件名相同,config.localconfig.example,以下方法应该可以运行。
$ git fetch
$ git diff master..origin/master -- config.example | sed -e 's%\(^[-+]\{3\}\) .\+%\1 config.local%g' > /tmp/mypatch
$ patch < /tmp/mypatch

我喜欢这个三方合并的答案,但不幸的是它完全不像 git add -p 那样交互式,这意味着我无法真正审查新的配置选项。尽管如此,我还是会点赞的,因为这可能是其他人前进的方式。 - Jörn Hees

1
如果您想要完整的git合并,可以通过设置索引条目使git执行任意内容,并仅在该条目上调用git的常规合并驱动程序来完成。Git将不同的内容版本称为“阶段”;它们分别是1:原始版本、2:您的版本、3:他们的版本。合并比较了从1到2和从1到3的更改,然后执行其操作。要设置它,请使用git update-index:
orig_example=        # fill in the commit with the config.example you based yours on
new_upstream=        # fill in the name of the upstream branch

( while read; do printf "%s %s %s\t%s\n" $REPLY; done \
| git update-index --index-info ) <<EOD
100644 $(git rev-parse $orig_example:config.example)  1 config
100644 $(git hash-object -w config)                   2 config
100644 $(git rev-parse $new_upstream:config.example)  3 config
EOD

如果您已经为该路径进行了自定义内容的合并,请执行以下操作:

git merge-index git-merge-one-file -- config

这将自动合并或留下常规的冲突标记,您可以根据需要进行修复,并使用git rm --cached --ignore-unmatch(或保留)索引条目。

顺便提一下,您放入索引中的路径名(这里的所有三个条目中的“config”)不必已经存在或与任何内容有关。您可以将其命名为“wip”或“deleteme”或任何您想要的名称。合并是在索引条目中标识的内容。

我认为这可能符合您的要求。如果您确实想从上游更改中挑选和选择,可以在config.example中放置自己的内容,然后执行git checkout -p upstream -- config.example,这相当于git add -p的反向操作,然后将事情恢复到原来的状态。


我最喜欢这个答案的最后一段(其他部分对我来说有点像黑魔法)。根据最后一段的想法,我只需执行cp config config.example ; git checkout -p upstream -- config.example然后交互式地决定如何合并更改,最后运行mv config.example config && git reset config.example && git checkout -- config.example来设置回原样。如果您将最后一段拆分为一个单独的答案,我会接受它,因为它清晰简明。 - Jörn Hees

0

有一个名为sdiff的工具可能符合你的需求。

在你的情况下,使用以下命令调用:sdiff -o 配置文件 配置文件示例 配置文件


0

以下代码应该可以正常工作:

git diff <some-args> | perl -pe 's/path\/to\/changes\/file/path\/other/g' > t
patch -p1 < t 
rm t

0

如果你想要将本地仓库中的config.example文件与upstream/HEAD中对应的文件进行比较,可以运行以下命令:

git diff upstream/HEAD config.example

很遗憾,我不知道有什么方法可以让git直接应用对于未被跟踪的文件所做的更改。


是的,我知道如何获取差异,但现在交互地应用到“config”上是我的实际问题 :-/ - Jörn Hees
你尝试过将更新的配置文件复制到config.example上,检查并提交,使用标准工具从源合并,然后将其移回config并重置为在提交更改之前的状态吗?如果文件不太不同步,您应该至少能够获得一些帮助来合并所做的更改。 - 1337joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接