我在一个新项目上使用 Git,该项目有两个并行的——但目前是实验性的——开发分支:
大师:现有代码库的导入以及我通常确定的一些修改
exp1:实验分支#1
exp2:实验分支#2
exp1
和 exp2
代表两种截然不同的架构方法。在我走得更远之前,我无法知道哪个(如果有的话)会起作用。当我在一个分支中取得进展时,我有时会进行一些对另一个分支有用的编辑,并且只想合并那些。
将选择性更改从一个开发分支合并到另一个分支的最佳方法是什么?
我考虑过的方法:
git merge --no-commit 然后手动取消暂存大量我不想在分支之间通用的编辑。手动将公共文件复制到临时目录,然后 git checkout 移动到另一个分支,然后从临时目录中手动复制到工作树中。上面的一个变种。暂时放弃 exp 分支并使用两个额外的本地存储库进行实验。这使得手动复制文件更加简单。
所有这三种方法似乎都很乏味且容易出错。我希望有更好的方法;类似于使 git-merge
更具选择性的过滤器路径参数。
git merge -s ours --no-commit
和一些git read-tree
的组合不是解决这个问题的好方法吗?请参阅stackoverflow.com/questions/1214906/…
我遇到了与您上面提到的完全相同的问题。但我发现 this 在解释答案时更清楚。
概括:
从要合并的分支中检查路径,$ git checkout source_branch --
或选择性地合并大块头 $ git checkout -p source_branch --
或者,使用重置,然后使用选项 -p
添加,
$ git reset <paths>...
$ git add -p <paths>...
最后提交 $ git commit -m "'合并'这些更改"
您使用 cherry-pick 命令从一个分支获取单个提交。
如果您想要的更改不在单个提交中,则使用此处显示的方法 split the commit into individual commits。粗略地说,您使用 git rebase -i
获取要编辑的原始提交,然后使用 git reset HEAD^
选择性地还原更改,然后使用 git commit
将该位提交为历史中的新提交。
There is another nice method here 在 Red Hat Magazine 中,他们使用 git add --patch
或可能的 git add --interactive
,如果您想将不同的更改拆分到单个文件(在该页面中搜索“split”),您可以只添加部分内容)。
拆分更改后,您现在可以只挑选您想要的。
要有选择地将文件从一个分支合并到另一个分支,请运行
git merge --no-ff --no-commit branchX
其中 branchX
是您要合并到当前分支的分支。
--no-commit
选项将暂存已由 Git 合并的文件,而不实际提交它们。这将使您有机会根据需要修改合并的文件,然后自己提交它们。
根据您要合并文件的方式,有四种情况:
1)你想要一个真正的合并。
在这种情况下,您以 Git 自动合并文件的方式接受合并文件,然后提交它们。
2) 有些文件你不想合并。
例如,您希望保留当前分支中的版本并忽略要合并的分支中的版本。
要选择当前分支中的版本,请运行:
git checkout HEAD file1
这将检索当前分支中 file1
的版本并覆盖 Git 自动合并的 file1
。
3)如果您想要 branchX 中的版本(而不是真正的合并)。
跑:
git checkout branchX file1
这将检索 branchX
中的 file1
版本并覆盖由 Git 自动合并的 file1
。
4) 最后一种情况是,如果您只想选择 file1 中的特定合并。
在这种情况下,您可以直接编辑修改后的 file1
,将其更新为您希望的 file1
版本,然后提交。
如果 Git 无法自动合并文件,它会将该文件报告为“未合并”并生成一个副本,您需要在其中手动解决冲突。
为了进一步解释示例,假设您要将 branchX
合并到当前分支中:
git merge --no-ff --no-commit branchX
然后运行 git status
命令来查看修改文件的状态。
例如:
git status
# On branch master
# Changes to be committed:
#
# modified: file1
# modified: file2
# modified: file3
# Unmerged paths:
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: file4
#
其中 file1
、file2
和 file3
是 git 已成功自动合并的文件。
这意味着所有这三个文件的 master
和 branchX
更改已组合在一起,没有任何冲突。
您可以通过运行 git diff --cached
; 检查合并是如何完成的。
git diff --cached file1
git diff --cached file2
git diff --cached file3
如果您发现某些合并不受欢迎,那么您可以
直接编辑文件保存 git commit
如果您不想合并 file1 并希望在当前分支中保留版本
跑
git checkout HEAD file1
如果您不想合并 file2 并且只想要 branchX 中的版本
跑
git checkout branchX file2
如果您希望 file3 自动合并,请不要执行任何操作。
此时 Git 已经合并了它。
上面的 file4
是 Git 的失败合并。这意味着在同一行上发生的两个分支中都有变化。这是您需要手动解决冲突的地方。您可以通过直接编辑文件或为您希望 file4
成为的分支中的版本运行 checkout 命令来放弃合并完成。
最后,别忘了git commit
。
git merge --no-commit branchX
只是快进,指针将被更新,因此 --no-commit 会被静默忽略
--no-ff
以防止这种行为怎么样?
我不喜欢上述方法。使用cherry-pick 非常适合选择单个更改,但是如果您想要引入所有更改(除了一些不好的更改),那就很痛苦了。这是我的方法。
没有可以传递给 git merge 的 --interactive
参数。
这是替代方案:
您在分支“功能”中有一些更改,并且您想以一种不马虎的方式将其中一些但不是全部带到“主”(即您不想挑选并提交每个)
git checkout feature
git checkout -b temp
git rebase -i master
# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change
# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
git checkout master
git pull . temp
git branch -d temp
因此,只需将其包装在一个 shell 脚本中,将 master 更改为 $to 并将 feature 更改为 $from 就可以了:
#!/bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp
git rebase -i $to
更改为 git rebase -i $to || $SHELL
,以便用户可以在 rebase 失败时根据需要调用 git --skip
等。还值得将这些行与 &&
而不是换行符链接在一起。
还有另一种方法:
git checkout -p
它是 git checkout
和 git add -p
的混合体,可能正是您正在寻找的:
-p, --patch
Interactively select hunks in the difference between the <tree-ish>
(or the index, if unspecified) and the working tree. The chosen
hunks are then applied in reverse to the working tree (and if a
<tree-ish> was specified, the index).
This means that you can use git checkout -p to selectively discard
edits from your current working tree. See the “Interactive Mode”
section of git-add(1) to learn how to operate the --patch mode.
虽然其中一些答案非常好,但我觉得没有人真正回答了 OP 的原始约束:从特定分支中选择特定文件。这个解决方案可以做到这一点,但如果有很多文件,它可能会很乏味。
假设您有 master
、exp1
和 exp2
分支。您想将每个实验分支中的一个文件合并到 master 中。我会做这样的事情:
git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b
# Save these files as a stash
git stash
# Merge stash with master
git merge stash
这将为您提供所需的每个文件的文件内差异。而已。一点也不差。在版本之间进行完全不同的文件更改很有用——在我的例子中,将应用程序从 Ruby on Rails 2 更改为 Ruby on Rails 3。
这将合并文件,但它会进行智能合并。我无法弄清楚如何使用这种方法来获取文件内差异信息(也许它仍然会产生极端差异。除非您使用 -s recursive -X ignore-all-space
选项,否则像空格这样烦人的小东西会被合并回来)
git checkout exp1 path/to/file_a path/to/file_x
git checkout feature <path>/*
来获取文件组。
1800 INFORMATION's answer 完全正确。但是,作为 Git 的新手,“使用 git cherry-pick”不足以让我在没有更多的互联网挖掘的情况下弄清楚这一点,所以我想我会发布一个更详细的指南,以防其他人在类似的船上。
我的用例是希望有选择地将其他人的 GitHub 分支中的更改提取到我自己的分支中。如果您已经有一个包含更改的本地分支,则只需执行步骤 2 和 5-7。
使用您想要引入的更改创建(如果未创建)本地分支。 $ git branch mybranch
git cherry
命令(首先参见手册)来识别您尚未合并的提交。
https://www.sourcemage.org
正在请求您的用户名和密码。该网站显示:“受限区域””。 (它也重定向到 HTTPS。)
以下是如何将 master
分支中的 Myclass.java
文件替换为 feature1
分支中的 Myclass.java
。即使 master
上不存在 Myclass.java
,它也可以工作。
git checkout master
git checkout feature1 Myclass.java
请注意,这将覆盖(而不是合并)并忽略主分支中的本地更改。
theirs
覆盖 ours
=> +1 干杯 ;)
2. Manual copying of common files into a temp directory followed by ...copying out of the temp directory into the working tree.
简单的方法,实际合并来自两个分支的特定文件,而不仅仅是用来自另一个分支的文件替换特定文件。
第一步:区分分支
git diff branch_b > my_patch_file.patch
创建当前分支和branch_b之间差异的补丁文件
第二步:对匹配模式的文件应用补丁
git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch
关于选项的有用说明
您可以在包含模式中使用 *
作为通配符。
不需要转义斜线。
此外,您可以改用 --exclude 并将其应用于除与模式匹配的文件之外的所有内容,或使用 -R 反转补丁
-p1 选项是 *Unix 补丁命令的保留,并且补丁文件的内容在每个文件名前面加上 a/
或 b/
(或更多取决于补丁文件的生成方式),您需要删除它们,以便它可以找出需要应用补丁的文件的路径的真实文件。
查看 git-apply 的手册页以获取更多选项。
第三步:没有第三步
显然你想提交你的更改,但谁说你在提交之前没有其他相关的调整。
以下是如何让历史记录跟踪来自另一个分支的几个文件,而无需大惊小怪,即使更“简单”的合并会带来更多您不想要的更改。
首先,您将采取不同寻常的步骤,提前声明您将要提交的是一个合并,而 Git 对您工作目录中的文件根本不做任何事情:
git merge --no-ff --no-commit -s ours branchname1
...其中“分支名称”是您声称要合并的任何内容。如果您要立即提交,它不会做任何更改,但它仍会显示来自另一个分支的祖先。如果需要,您也可以在命令行中添加更多分支、标签等。不过,此时没有要提交的更改,所以接下来从其他修订版中获取文件。
git checkout branchname1 -- file1 file2 etc.
如果您从多个其他分支合并,请根据需要重复。
git checkout branchname2 -- file3 file4 etc.
现在来自另一个分支的文件在索引中,准备好提交,并带有历史记录。
git commit
您将在该提交消息中进行大量解释。
但请注意,如果不清楚,这是一件搞砸的事情。它不符合“分支”的精神,而cherry-pick是一种更诚实的方式来做你想做的事情,在这里。如果您想对上次未带入的同一分支上的其他文件进行另一次“合并”,它将以“已经是最新的”消息阻止您。这是我们应该有的时候没有分支的症状,因为“来自”分支应该不止一个不同的分支。
git merge --no-ff --no-commit -s outs branchname1
) 正是我想要的!谢谢!
git merge --continue
而不是最后的 git commit
。谢谢!
这是我合并选择性文件的工作流程。
# Make a new branch (this will be temporary)
git checkout -b newbranch
# Grab the changes
git merge --no-commit featurebranch
# Unstage those changes
git reset HEAD
(You can now see the files from the merge are unstaged)
# Now you can chose which files are to be merged.
git add -p
# Remember to "git add" any new files you wish to keep
git commit
最简单的方法是将存储库设置为要合并的分支,然后运行
git checkout [branch with file] [path to file you would like to merge]
如果你跑
git status
你会看到文件已经暂存...
然后运行
git commit -m "Merge changes on '[branch]' to [file]"
简单的。
奇怪的是,git 还没有“开箱即用”这么方便的工具。在更新一些旧版本分支(仍然有很多软件用户)时,我会大量使用它,仅通过当前版本分支中的 一些 错误修复。在这种情况下,通常需要从主干文件中快速获取仅一些行代码,而忽略许多其他更改(不应该进入旧版本)......并且当然,在这种情况下需要交互式三向合并,git checkout --patch <branch> <file path>
不适用于这种选择性合并目的。
你可以轻松做到:
只需将此行添加到全局 .gitconfig
或本地 .git/config
文件中的 [alias]
部分:
[alias]
mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;' -"
这意味着您使用 Beyond Compare。如果需要,只需更改为您选择的软件。或者,如果您不需要交互式选择性合并,您可以将其更改为三向自动合并:
[alias]
mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;' -"
然后像这样使用:
git mergetool-file <source branch> <file path>
这将为您提供其他分支中任何文件的真正选择性树方式合并机会。
我发现 this post 包含最简单的答案。只需这样做:
git checkout <branch from which you want files> <file paths>
例子
将 .gitignore 文件从 branchB 拉到当前分支:
git checkout branchB .gitignore
有关更多信息,请参阅帖子。
它不完全是您想要的,但它对我很有用:
git checkout -p <branch> -- <paths> ...
这是一些答案的混合。
git checkout HEAD file1
保留当前版本并取消合并文件 file1
,可以使用 -p
选项选择要合并的文件的 部分。谢谢你的把戏!
我遇到了与您上面提到的完全相同的问题。但我发现 this Git blog 在解释答案时更清楚。
来自上述链接的命令:
# You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
对我来说,git reset --soft branch
是从另一个分支中选择性地选择更改的最简单方法,因为此命令将所有差异更改放入我的工作树中,我可以轻松地选择或恢复我需要的那个。
通过这种方式,我可以完全控制提交的文件。
我会做一个
git diff commit1..commit2 文件模式 | git-apply --index && git commit
通过这种方式,您可以限制来自分支的文件模式的提交范围。
它是从 Re: How to pull only a few files from one branch to another? 偷来的
git checkout branch ...
相同 - branch
中不存在的本地更改被删除(!)。它们在补丁文件中被标记为“已删除”,因为普通 diff 不会检查提交历史记录。
您可以使用 read-tree
将给定的远程树读取或合并到当前索引中,例如:
git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder
要执行合并,请改用 -m
。
另请参阅:How do I merge a sub directory in Git?
我喜欢 previous 'git-interactive-merge' 的答案,但有一个更简单的答案。让 Git 使用交互式和到的 rebase 组合为您执行此操作:
A---C1---o---C2---o---o feature
/
----o---o---o---o master
所以情况是你想要'feature'分支(分支点'A')中的C1和C2,但现在没有其他的。
# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp
与前面的答案一样,它会将您带到交互式编辑器中,您可以在其中为 C1 和 C2 选择“选择”行(如上所述)。保存并退出,然后它将继续进行 rebase 并在 master + C1 + C2 处为您提供分支“temp”和 HEAD:
A---C1---o---C2---o---o feature
/
----o---o---o---o-master--C1---C2 [HEAD, temp]
然后您可以将 master 更新为 HEAD 并删除 temp 分支,您就可以开始了:
# git branch -f master HEAD
# git branch -d temp
我编写了自己的脚本“pmerge”来部分合并目录。这是一项正在进行的工作,我仍在学习 Git 和 Bash 脚本。
此命令使用 git merge --no-commit
,然后取消应用与提供的路径不匹配的更改。
用法:git pmerge branch path
示例:git merge develop src/
我没有对它进行广泛的测试。工作目录应该没有任何未提交的更改和未跟踪的文件。
#!/bin/bash
E_BADARGS=65
if [ $# -ne 2 ]
then
echo "Usage: `basename $0` branch path"
exit $E_BADARGS
fi
git merge $1 --no-commit
IFS=$'\n'
# List of changes due to merge | replace nulls with newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
[[ $f == $2* ]] && continue
if git reset $f >/dev/null 2>&1; then
# Reset failed... file was previously unversioned
echo Deleting $f
rm $f
else
echo Reverting $f
git checkout -- $f >/dev/null 2>&1
fi
done
unset IFS
按文件选择性合并/提交的简单方法:
git checkout dstBranch
git merge srcBranch
// Make changes, including resolving conflicts to single files
git add singleFile1 singleFile2
git commit -m "message specific to a few files"
git reset --hard # Blow away uncommitted changes
git reset --hard
不会摆脱已提交的更改吗?
如果您没有太多已更改的文件,这将使您没有额外的提交。
<强> 1。临时复制分支
$ git checkout -b temp_branch
<强> 2。重置为上次想要的提交
$ git reset --hard HEAD~n
,其中 n
是您需要返回的提交数
<强> 3。从原始分支签出每个文件
$ git checkout origin/original_branch filename.ext
现在,如果需要,您可以提交并强制推送(覆盖远程)。
如果您只需要合并特定目录并保持其他所有内容不变并保留历史记录,则可以尝试这样做...在实验之前从 master
创建一个新的 target-branch
。
以下步骤假设您有两个分支 target-branch
和 source-branch
,并且您要合并的目录 dir-to-merge
在 source-branch
中。还假设您在目标中有其他目录(例如 dir-to-retain
),您不想更改和保留历史记录。此外,假设 dir-to-merge
中存在合并冲突。
git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict.
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.
# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.
当两个分支的当前提交之间只有几个文件发生更改时,我通过浏览不同的文件手动合并更改。
git difftool <branch-1>..<branch-2>
另见https://sites.google.com/site/icusite/setup/git-difftool
我将专注于我感兴趣的这个问题的子集:我有两个分支,我想将一个文件从一个文件伪合并到另一个文件中。
(我说“伪合并”是因为我不需要或不想要合并提交;我只想以我认为合适的方式组合文件的两个版本的贡献。)
我的方法基于 https://stackoverflow.com/a/39916536/341994 中采用的方法。不幸的是,该问题已作为重复项关闭(错误地,在我看来:它不是该问题的重复项,并且回答者在那里所做的回答和关闭重复项是错误的)。但是这个答案有一些问题,所以我对方法进行了现代化和清理。我使用 restore
而不是 checkout
和 reset
,而且我不会费心去提交任何我不需要的东西。
好的,想象一下我有三个文件:
$ ls
a b f
但我只想从 otherbranch
伪合并其中一个,a
。让我们看看他们,看看情况会是什么样子。这是我的版本:
$ cat a
line one
line two
line three
line four
line five
这是otherbranch的版本:
$ git show otherbranch:a
line one
line two edited
line three
line four
line five
line six
现在这里的技巧是我们将使用索引作为便签本(毕竟,这就是它的用途)。因此,我们从确保将我们的版本复制到索引中开始(步骤 1):
$ git add a
现在(第 2 步)我们可以使用 restore
从 otherbranch
获取版本(现在,restore
比 checkout
更好,因为它让我们更清楚地交谈):
$ git restore --source otherbranch a
乍一看,这看起来很糟糕。我们现在已经用 otherbranch
中的版本完全覆盖了我们的 a,如您所见:
$ cat a
line one
line two edited
line three
line four
line five
line six
但不用担心!之前版本的 a 仍在索引中,如您所见:
$ git diff a
diff --git a/a b/a
index abf51fa..333614b 100644
--- a/a
+++ b/a
@@ -1,6 +1,7 @@
line one
-line two
+line two edited
line three
line four
line five
+line six
很好,现在我们已经为关键动作做好了准备(步骤 3)。我们对文件从工作树到索引进行交互式补丁 add
。
我们可以说 git add -p a
来启动交互式补丁过程,在这种情况下,我们一次只吃一大块。但在这种情况下,只有一个大块,我还是想编辑它,所以我说:
$ git add --e a
结果是我们在编辑器中打开了一个差异补丁文件!它看起来像这样:
line one
-line two
+line two edited
line three
line four
line five
+line six
通过仔细编辑,我们现在可以决定我们想要接受哪些部分以及我们不接受哪些部分。让我们接受“第六行”而不是“第二行已编辑”。所以我们编辑成这样:
line one
line two
line three
line four
line five
+line six
我们关闭编辑器并将补丁应用于 a 的索引版本。但我们还没有完成! a 的 otherbranch
版本仍在工作树中:
$ cat a
line one
line two edited
line three
line four
line five
line six
我们喜欢的版本在索引中,记得吗?为了得到它,(第 4 步)我们只需简单地调用 git restore
(同样,这是现代方式;restore
比 reset
更好,并且可以应用于单个文件):
$ git restore a
现在我们的 a 是正确的,我们都完成了:
$ cat a
line one
line two
line three
line four
line five
line six
此时我们可以提交,但我们不必这样做;我们已经完成了我们打算完成的事情。
我想要什么:交互式地从一个分支(有几个混乱的提交)中挑选大块到一个新分支中的一个干净的提交中。
如果该差异中有任何二进制文件,git diff
+ git apply
将不起作用。
我的做法:
# New branch from a clean starting point, e.g. master
git checkout new-clean-branch origin/master
# Get all changes from the messy branch
# (quote the star so your shell doesn't expand it)
git checkout messy-branch -- '*'
# Unstage ("un-add") everything
git restore --staged .
# Interactively add hunks to be staged for commit
git add -p
如果您是 Gitkraken 用户here you have a small guide
总之:
移动到要进行更改的分支。 (例如开发)右键单击具有新更改的分支并选择“cherrypick commit”选项(例如功能-ABC)。最后接受并检查是否存在冲突。
不定期副业成功案例分享
foo.c
,请执行git reset HEAD foo.c
取消暂存该文件,然后您可以对其进行比较。我在尝试后发现了这一点并回到这里寻找答案git diff --cached
git checkout -p <revision> -- <path>
将与发出您描述的前三个命令相同:)