如何 git-cherry-pick 仅更改某些文件?

Posted

技术标签:

【中文标题】如何 git-cherry-pick 仅更改某些文件?【英文标题】:How to git-cherry-pick only changes to certain files? 【发布时间】:2011-08-08 16:34:00 【问题描述】:

如果我想将仅对特定提交中更改的某些文件所做的更改合并到 Git 分支中,其中包括对多个文件的更改,如何实现?

假设名为stuff 的Git 提交对文件ABCD 进行了更改,但我只想合并stuff 对文件A 和@ 的更改987654328@。这听起来像是 git cherry-pick 的工作,但 cherry-pick 只知道如何合并整个提交,而不是文件的子集。

【问题讨论】:

【参考方案1】:

我会使用 cherry-pick -n (--no-commit) 来完成,它可以让您在提交之前检查(和修改)结果:

git cherry-pick -n <commit>

# unstage modifications you don't want to keep, and remove the
# modifications from the work tree as well.
# this does work recursively!
git checkout HEAD <path>

# commit; the message will have been stored for you by cherry-pick
git commit

如果绝大多数修改是您不想要的,那么您可以重新设置所有内容,而不是检查单个路径(中间步骤),然后添加您想要的内容:

# unstage everything
git reset HEAD

# stage the modifications you do want
git add <path>

# make the work tree match the index
# (do this from the top level of the repo)
git checkout .

【讨论】:

除了git checkout .,我还建议git clean -f 删除任何新的但不需要的文件,这些文件是由精心挑选的提交引入的。 后一种方法的附加说明:我使用git add -p,它可以让您以交互方式决定要添加到索引中的更改每个文件 这不是很好的情况下,樱桃挑选的提交不适用于当前的工作副本,因为它是如此不同,但 one 文件 干净地应用。 您也可以使用git reset -p HEAD 选择性地取消暂存。它相当于add -p,但很少有人知道它的存在。 非常有用的技巧。我把它放在一个要点中,以防有人需要它作为一个快速脚本gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc【参考方案2】:

其他方法对我不起作用,因为提交对很多其他文件有很多更改和冲突。我想出的只是

git show SHA -- file1.txt file2.txt | git apply -

它实际上并没有 add 文件或为你做一个提交,所以你可能需要跟进它

git add file1.txt file2.txt
git commit -c SHA

或者,如果您想跳过添加,您可以使用 --cached 参数到 git apply

git show SHA -- file1.txt file2.txt | git apply --cached -

你也可以对整个目录做同样的事情

git show SHA -- dir1 dir2 | git apply -

【讨论】:

有趣的方法,谢谢。但是show SHA -- file | applycheckout SHA -- file 的作用不一样吗?就像Mark Longair's answer 一样? 不,checkout SHA -- file 将准确检查 SHA 中的版本,而 show SHA -- file | apply 将仅应用 SHA 中的更改(就像 cherry-pick 一样)。如果 (a) 有多个提交更改源分支中的给定文件,或者 (b) 有一个提交更改了当前目标分支中的文件,这很重要。 刚刚发现了另一个很好的用途:选择性还原,当您只想还原一个文件时(因为git revert 撤消了整个提交)。在这种情况下,只需使用git show -R SHA -- file1.txt file2.txt | git apply - @RoeiBahumi 有完全不同的含义。 git diff SHA -- file1.txt file2.txt | git apply - 表示将文件的当前版本与 SHA 上的版本之间的所有差异应用到当前版本。本质上它与git checkout SHA -- file1.txt file2.txt 相同。请参阅我之前的评论,了解为什么这与 git show 版本不同。 如果您必须解决冲突,请使用git apply -3 - 而不是仅使用git apply -,然后如果发生冲突,您可以使用标准的冲突解决技术,包括使用git mergetool。跨度> 【参考方案3】:

我通常将 -p 标志与来自另一个分支的 git checkout 一起使用,我发现它比我遇到的大多数其他方法更容易和更细化。

原则上:

git checkout <other_branch_name> <files/to/grab in/list/separated/by/spaces> -p

示例:

git checkout mybranch config/important.yml app/models/important.rb -p

然后您会看到一个对话框,询问您想要在“blob”中进行哪些更改,这几乎适用于连续代码更改的每一块,然后您可以为每个代码发送 y(是)n(否)等代码块。

-ppatch 选项适用于 git 中的各种命令,包括 git stash save -p,它允许您从当前工作中选择要存储的内容

当我完成大量工作并希望将其分离出来并使用git add -p 提交更多基于主题的提交并为每个提交选择我想要的内容时,我有时会使用此技术:)

【讨论】:

我经常使用git-add -p,但我不知道git-checkout 也有-p 标志 - 这是否解决了the non--p answer 的合并问题? 至少-p 将允许手动编辑这样一个冲突的部分,cherry-pick 无论如何也可能会产生。下次需要时我会测试它,绝对是一种有趣的方法 不会杀死对分支的并发更改的两个最佳答案之一。 查看这个答案以了解如何选择要应用的帅哥:***.com/a/10605465/4816250 's' 选项特别有用。 git reset -p HEAD 还允许使用-p,当您只想从索引中删除一些补丁时,可以方便地退出。【参考方案4】:

也许这种方法相对于Jefromi's answer 的优势在于您不必记住git reset 的哪个行为是正确的:)

 # Create a branch to throw away, on which we'll do the cherry-pick:
 git checkout -b to-discard

 # Do the cherry-pick:
 git cherry-pick stuff

 # Switch back to the branch you were previously on:
 git checkout -

 # Update the working tree and the index with the versions of A and B
 # from the to-discard branch:
 git checkout to-discard -- A B

 # Commit those changes:
 git commit -m "Cherry-picked changes to A and B from [stuff]"

 # Delete the temporary branch:
 git branch -D to-discard

【讨论】:

感谢您的回答。现在这启发了我思考,为什么不跳过cherry-pick,直接使用git checkout stuff -- A B?对于git commit -C stuff,提交消息也将保持不变 @Tobias:只有在 stuff 上修改的文件尚未在您当前的分支或 HEADstuff 的共同祖先与 @ 的尖端之间的任何地方修改时,这才有效987654331@。如果有,那么 cherry-pick 会创建正确的结果(本质上是合并的结果),而您的方法会丢弃当前分支中的更改,并保留从共同祖先到 stuff 的所有更改 -不仅仅是那个单一提交中的那些。 @Tobias Kienzler:我假设您的起点与stuff 的父级有很大不同,因此樱桃采摘的结果将使AB 的内容与他们的不同提交 stuff 中的内容。但是,如果它只是一样,你是对的 - 你可以照你说的做。 @Jeromi,@Mark:感谢您的反馈,在我的情况下,我正在使用完全不相干的文件处理分支,这导致了我的建议。但事实上我迟早会遇到麻烦,所以谢谢你提出这个问题 我认为my answer in this other thread 可能是你所追求的。【参考方案5】:

Cherry pick 是从特定的“提交”中挑选更改。最简单的解决方案是选择某些文件的所有更改是使用

 git checkout source_branch <paths>...

例如:

$ git branch
* master
  twitter_integration
$ git checkout twitter_integration app/models/avatar.rb db/migrate/20090223104419_create_avatars.rb test/unit/models/avatar_test.rb test/functional/models/avatar_test.rb
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   app/models/avatar.rb
#   new file:   db/migrate/20090223104419_create_avatars.rb
#   new file:   test/functional/models/avatar_test.rb
#   new file:   test/unit/models/avatar_test.rb
#
$ git commit -m "'Merge' avatar code from 'twitter_integration' branch"
[master]: created 4d3e37b: "'Merge' avatar code from 'twitter_integration' branch"
4 files changed, 72 insertions(+), 0 deletions(-)
create mode 100644 app/models/avatar.rb
create mode 100644 db/migrate/20090223104419_create_avatars.rb
create mode 100644 test/functional/models/avatar_test.rb
create mode 100644 test/unit/models/avatar_test.rb

来源和完整解释http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

更新:

使用这种方法,git 不会合并文件,它只会覆盖在目标分支上所做的任何其他更改。您需要手动合并更改:

$ git diff HEAD 文件名

【讨论】:

I thought so too,但是如果文件在 两个 分支上都发生了更改,这将非常失败,因为它会丢弃当前分支的更改 你是对的,必须澄清这种方式 git 不会合并,它只是覆盖。然后,您可以执行“git diff HEAD filename”以查看更改的内容并手动进行合并。【参考方案6】:

情况:

你在你的分支上,比如说master,你在任何其他分支上都有你的提交。您只需从该特定提交中选择一个文件。

方法:

第 1 步:在所需的分支上结帐。

git checkout master

第 2 步:确保您已复制所需的提交哈希。

git checkout commit_hash path\to\file

第 3 步:您现在对所需分支上的所需文件进行了更改。您只需要添加并提交它们。

git add path\to\file
git commit -m "Your commit message"

【讨论】:

太棒了!对于我来说,也适用于 \path\to\directory\ 目录中的所有更改 非常感谢!这比cherry-pick简单得多 覆盖对目标分支所做的任何更改。 迄今为止最好的解决方案!!【参考方案7】:

为了完整起见,最适合我的是:

git show YOURHASH --no-color -- file1.txt file2.txt dir3 dir4 | git apply -3 --index -

它完全符合 OP 的要求。它会在需要时解决冲突,类似于merge 的做法。它确实 add 但不是 commit 您的新更改,请参阅 status

【讨论】:

真漂亮 谢谢!你能提一下3 的用途吗(... git apply -3 --index ...)? -3 表示使用“三向合并后备”。这实际上意味着 git 可以插入熟悉的冲突标记,也可以绕过已经应用的更改。默认的git apply 行为对我来说太严格了,它拒绝应用整个补丁,只要有一点点差异。 你救了我。谢谢。【参考方案8】:

我会挑选一切,然后这样做:

git reset --soft HEAD^

然后我会还原我不想要的更改,然后进行新的提交。

【讨论】:

【参考方案9】:

使用git merge --squash branch_name 这将从其他分支获取所有更改,并为您准备提交。 现在删除所有不需要的更改并保留您想要的更改。并且 git 不会知道有合并。

【讨论】:

谢谢,我不知道合并选项。如果您想挑选整个分支的大部分,这是一个可行的选择(但与樱桃挑选相比,如果没有共同的祖先,它就行不通)【参考方案10】:

我找到了另一种方法,可以防止在挑选樱桃时出现任何冲突合并,IMO 很容易记住和理解。由于您实际上不是在挑选提交,而是其中的一部分,因此您需要先拆分它,然后创建一个适合您需求的提交并挑选它。

首先从要拆分的提交中创建一个分支并签出它:

$ git checkout COMMIT-TO-SPLIT-SHA -b temp

然后恢复之前的提交:

$ git reset HEAD~1

然后添加您想要挑选的文件/更改:

$ git add FILE

并提交:

$ git commit -m "pick me"

注意提交哈希,我们称之为 PICK-SHA 并回到你的主分支,例如 master 强制结帐:

$ git checkout -f master

然后挑选提交:

$ git cherry-pick PICK-SHA

现在你可以删除临时分支了:

$ git branch -d temp -f

【讨论】:

【参考方案11】:

你可以使用:

git diff <commit>^ <commit> -- <path> | git apply

符号&lt;commit&gt;^ 指定&lt;commit&gt; 的(第一个)父级。因此,这个 diff 命令选择在提交 &lt;commit&gt; 中对 &lt;path&gt; 所做的更改。

请注意,这不会提交任何内容(就像 git cherry-pick 所做的那样)。因此,如果您想这样做,则必须这样做:

git add <path>
git commit

【讨论】:

这样您可以指定任何有效范围,如果结果不适用,您可以选择使用-3--3way 进行三向合并开关:... | git apply --3way【参考方案12】:

将一个分支合并到新分支(壁球)并删除不需要的文件:

git checkout master
git checkout -b <branch>
git merge --squash <source-branch-with-many-commits>
git reset HEAD <not-needed-file-1>
git checkout -- <not-needed-file-1>
git reset HEAD <not-needed-file-2>
git checkout -- <not-needed-file-2>
git commit

【讨论】:

【参考方案13】:

有时使用 checkout 从提交中提取特定文件可能更容易。在我看来,它为您提供了更多的控制权,并且没有必要在挑选后检查和取消暂存。

我会这样做:

git checkout <branch|hash> -- path/to/file1 path/to/filen

然后取消暂存必要的更改以适应代码并在提交之前对其进行测试。如果一切都按预期工作,则提交。

【讨论】:

与多个现有答案相同。

以上是关于如何 git-cherry-pick 仅更改某些文件?的主要内容,如果未能解决你的问题,请参考以下文章

在 JS/JQuery 中按 ENTER 键之前,如何仅捕获您在文本区域中输入的行? [复制]

仅更改某些 li 元素的列表样式[关闭]

如何仅在某些行上获得多个 Java 正则表达式匹配

如何在 C# 应用程序范围内更改仅日期部分将用于日期时间比较

影响所有图像的脚本而不是仅包含某些文本的div中的图像(jQuery)

MySQL 仅更新表中的某些字段