如何将更改合并到单个文件中,而不是合并提交?

503

我有两个分支(A和B),我想将A分支中的一个文件与B分支中相应的一个文件合并。


2
已在此处讨论:https://dev59.com/9nRB5IYBdhLWcg3w-789 - positron
39
那篇帖子的大多数回复都是关于如何有选择性地合并“提交”而不是“文件”。这使得被选中的答案不正确。问题仍未得到回答。 - user153275
2
可能是如何使用git-merge选择性地合并文件?的重复问题。 - phuclv
12个回答

820

我遇到了同样的问题。具体来说,我有两个分支 AB,它们有相同的文件,但某些文件在编程接口上有所不同。现在文件 f 的方法在分支 B 中被修改了,该修改对于两个分支都很重要,但文件 f 与接口的差异无关。因此,我需要合并分支 B 中的文件 f 到分支 A 的文件 f

如果我假设所有更改都在分支 AB 中提交了,那么一个简单的命令已经为我解决了这个问题:

git checkout A

git checkout --patch B f
第一个命令切换到分支A,我想要合并B的版本文件f。第二个命令使用Bf文件中的fHEAD补丁文件f。您甚至可以接受/拒绝补丁的单个部分。在这里您可以指定任何提交,而不一定是HEAD
社区编辑:如果B上的f文件还不存在于A上,则省略--patch选项。否则,您将收到“无更改”的消息。

14
只有在您想要更新文件时,此方法才有效。如果我想将分支B中的新文件添加到分支A中呢? - Umair A.
37
您可以在不使用“--patch”选项的情况下,将B中的新文件添加到A中。 - bbak
10
嗯...当我尝试这个时,我得到了“没有更改”的消息,但确实有更改。好的,我需要进入相关文件所在的文件夹。编辑:这很可能是我在stackoverflow上看到的最喜欢的解决问题的方法 :-D - mal
10
我必须使用 git checkout --patch B -- f 才能使其正常工作。 - user545424
9
只需补充一点,如果您在文件中有多个更改块并且想要将它们全部暂存,您可以在交互阶段期间按下 a 键,而不是每次按下 y 键。或者使用 git checkout B -- f 命令。 - Dmitry Gonchar
显示剩余15条评论

29

这使用了git的内部差异工具。 可能需要一些工作,但很简单。

#First checkout the branch you want to merge into
git checkout <branch_to_merge_into>
    
#Then checkout the file from the branch you want to merge from
git checkout <branch_to_merge_from> -- <file> 
    
#Then you have to unstage that file to be able to use difftool
git reset HEAD <file> 

#Now use difftool to chose which lines to keep. Click on the mergebutton in difftool
git difftool

#Save the file in difftool and you should be done.

1
为了澄清--(空参数标签)的使用,git checkout docs: ARGUMENT DISAMBIGUATION说:“如果您想从索引中检出这些路径,请使用git checkout -- <pathspec>。”这是因为您可能有同名的分支和文件/路径。在这种情况下,当两者都存在时,而不是要求您澄清应该检出分支还是路径,git将选择默认检出分支。但是,如果--在前面,则git将检出文件/路径。 - SherylHohman
git diff 也可以用来验证差异。 - Pedro OS
使用git diff时,也可以在文件暂存的情况下使用--cached选项。顺便问一下,你能详细解释一下git checkout --patch方法和这个方法之间的区别吗?这两种方法有什么劣势吗?我更习惯于使用这个方法,但我想了解一下。 - undefined

20

以下是我在这种情况下的解决方案。虽然有点丑陋,但对我来说完全可行。

  1. 从你的工作分支创建另一个分支。
  2. 使用git pull/git merge命令合并包含你想复制的文件的修订版(SHA1)。这样会合并你的所有更改,但我们只使用这个分支来获取一个文件。
  3. 解决任何冲突等问题。检查你的文件。
  4. 检出你的工作分支。
  5. 检出从合并中提交的文件。
  6. 提交它。

我曾试过打补丁,但我的情况太糟糕了。简而言之,看起来像这样:

工作分支:A
实验性分支:B(包含我想要折叠进去的更改文件)

git checkout A

基于A创建新分支:

git checkout -b tempAB
将B合并到tempAB中。
git merge B

复制合并的sha1哈希值:

git log

commit 8dad944210dfb901695975886737dc35614fa94e
Merge: ea3aec1 0f76e61
Author: matthewe <matthewe@matthewe.com>
Date:   Wed Oct 3 15:13:24 2012 -0700

Merge branch 'B' into tempAB

检出你的工作分支:

git checkout A

检查您修改后的文件:

git checkout 7e65b5a52e5f8b1979d75dffbbe4f7ee7dad5017 file.txt

就是这样了。提交你的结果。


6
为了合并一个文件,我们需要做这么多步骤吗?把文件复制粘贴到另一个分支不是更容易吗? - Robin
2
@Robin 可能不行,因为合并会保留分支 A 和 B 之间文件的差异。而复制文件将覆盖你的工作分支 A 中任何额外的差异,并且可能不包含那些条目/编辑。例如,怀疑 A 最初就与 B 分歧,以其他方式开始。复制将覆盖这些差异。 - blamb

9
我发现这个方法简单又有用:如何“合并”另一个分支的特定文件

事实证明,我们正在努力尝试。我们好朋友 git checkout 是完成此任务的正确工具。

git checkout 源分支 <路径>...

我们只需要给 git checkout 提供要添加到主分支的特定文件的功能分支 A 的名称和路径即可。

请阅读整篇文章以获得更多理解。

22
这会覆盖文件,而不是合并它们。 - Alex G
对于你来说这可能会有所不同,这取决于你正在做什么以及你想要实现什么。这里的想法是分支B是A的分叉,你在B中修改了4个文件,但只想将其中2个合并到A中。常规合并会合并所有4个文件,但在这里你可以进行选择。这看起来可能会被覆盖,因为B包含了更新的文件。你需要用一些证据支持你的经验。 - Pawel Cioch
2
我同意它会覆盖原有的内容。我想你是指在该命令中使用“-p”选项。然后,在修补程序更改之前,它将覆盖您工作树文件中先前与您正在检出分支不同的任何部分。不幸的是。 - blamb
1
好的,这个想法来自2009年,现在的git新版本可能有不同的行为,需要-p或其他什么,但当我发布它时,它对我有效。但是也许我并不关心文件被覆盖,因为最新版本正是我所需要的。 - Pawel Cioch
3
覆盖重写,而你不在意?绝对误导,被点踩了。 - MrR

9

5
我想只合并另一个分支的一个文件,但我没有看到从另一个分支合并的选项 git merge-file - blamb

8
下面的命令将: (1) 比较正确分支的文件和主分支的文件 (2) 交互式地询问您要应用哪些修改。
git checkout --patch master <fn>

2
我认为你的意思是 git checkout --patch master <filespec> - chmaynard

2
以下内容将在索引中记录冲突,就像合并分支时一样。
export other=<sha1 or branch name of the other commit>
export base=$(git merge-base @ $other)

git diff $base $other <file> [<file> ...] | git apply --cached --3way 

这里的魔法在于--cached --3way apply的模式。成功地进行三方补丁应用取决于能够访问基础提交和其他提交中所需文件的 blob,如果我们使用相同的本地仓库运行 diff 命令生成补丁,则可以保证这一点。
这种方法与两种其他方法(checkout --patch需要交互式解决冲突,merge-file将冲突以diff3模式写入工作目录)不同,而且我认为更加优越。一些集成开发环境不支持内联工作树冲突标记,但大多数可以很好地处理记录在索引中的冲突。

直到大约 git 2.31 或 2.32 版本,--cached 和 --3way 是不兼容的。我目前使用的是 2.30 版本,但这似乎正是我需要的。所以我正在从源代码构建 git 2.41.0 版本。构建完成后我会确认一下。我真的希望 git read-tree --merge -u --prefix= 能够工作,但它拒绝执行。 - Bob Kerns
1
可以确认,效果非常好!更棒的是,你可以在一个命令中同时处理多个路径或整个目录!我建议在<文件>后面加上“...”来进行编辑。从源代码构建git非常值得,因为它带来了这种功能。非常感谢! - Bob Kerns
我还会建议这个被接受的答案,如果原帖作者还在并且能够注意到的话。对于希望进行交互式批准而不是中断程序的使用情况,可以通过结合 'git restore' 和 'git add -p' 来满足。 - Bob Kerns

1
假设B是当前分支:
$ git diff A <file-path> > patch.tmp
$ git apply patch.tmp -R

请注意,此操作仅适用于本地文件的更改。之后您需要提交更改。

对我来说,这会生成一个“错误:文件路径:已存在于工作目录中”的错误。 - kontur
你应该在当前目录下指定文件或文件路径。我使用 git diff Branch_A <file-path, filename> -- hash_commit > file_name.temp - R.Chatsiri

1
你可以将旧版本文件检出并保存为不同的名称,然后在两个文件上运行你的合并工具进行合并。
例如: git show B:src/common/store.ts > /tmp/store.ts (其中B是分支名/提交/标签) meld src/common/store.ts /tmp/store.ts

0

我会这样做:

git format-patch branch_old..branch_new file

这将为该文件生成一个补丁。

在目标分支branch_old上应用补丁

git am blahblah.patch


你能否轻松地查看补丁以提高安全性? - XavierStuvw
不确定我做错了什么,但是我无法让git生成补丁文件。虽然没有报告错误。 - sergico

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