ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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


C
Cascabel

我会使用 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,它可以让您以交互方式决定要将哪些更改添加到 每个文件 的索引中
如果樱桃挑选的提交不适用于当前的工作副本,这并不是那么好,因为它是如此不同,但是一个文件可以干净地应用。
您还可以使用 git reset -p HEAD 有选择地取消暂存。它相当于 add -p,但很少有人知道它的存在。
非常有用的技巧。我把它放在一个要点中,以防有人需要它作为一个快速脚本gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
k
kubanczyk

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

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

它实际上并没有add 文件或为您执行提交,因此您可能需要跟进

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

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

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
T
Tyrone Wilson

我通常将 -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”中进行哪些更改.

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

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


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

也许这种方法相对于 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 的共同祖先与 stuff 的尖端之间的任何地方修改时,这才有效。如果有,则 cherry-pick 创建正确的结果(本质上是合并的结果),而您的方法将丢弃当前分支中的更改,并保留从共同祖先到 stuff 的所有更改 -不仅仅是那个单一提交中的那些。
@Tobias Kienzler:我假设您的起点与 stuff 的父级有很大不同,因此樱桃采摘的结果会使 AB 的内容与提交 stuff 中的内容不同。然而,如果它只是一样,你是对的——你可以照你说的做。
@Jeromi,@Mark:感谢您的反馈,就我而言,我正在使用完全不相干的文件处理分支,这导致了我的建议。但事实上,我迟早会遇到麻烦,所以谢谢你提出这个问题
我认为 my answer in this other thread 可能是您所追求的。
c
cminatti

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,但如果文件在 both 分支上发生更改,这将非常失败,因为它会丢弃当前分支的更改
你是对的,必须澄清这种方式 git 不会合并,它只是覆盖。然后,您可以执行“git diff HEAD 文件名”以查看更改的内容并手动进行合并。
这对我来说效果很好。一位同事对拉取请求发表了评论,认为应该将一些更改拆分为单独的请求,以便清楚地分离问题。您的方法适用于这种情况。
您还可以使用它从特定提交中获取特定文件 git checkout e0032947bd37f44044962ae2f7229d339944f83c example.txt
t
techdreams

情况:

您在您的分支上,假设 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简单得多
覆盖对目标分支所做的任何更改。
迄今为止最好的解决方案!!
k
kubanczyk

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

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 行为对我来说太严格了,它拒绝在最轻微的差异上应用整个补丁。
你救了我。谢谢。
f
funroll

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

git reset --soft HEAD^

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


I
Indomitable

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


谢谢,我不知道那个合并选项。如果您想挑选整个分支的大部分,这是一个可行的选择(但与樱桃挑选相比,如果没有共同的祖先,它就不起作用)
e
eliastg

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

我会这样做:

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

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


与多个现有答案相同。
A
Alexey

我找到了另一种方法,可以防止在挑选樱桃时发生任何冲突合并,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

G
Garogolun

您可以使用:

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

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

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

git add <path>
git commit

这样,如果结果不适用,您可以指定任何有效范围,您可以选择使用 -3--3way 开关进行 3 路合并:... | git apply --3way
n
nvd

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

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

d
davidvandebunte

自动化一点:

#!/usr/bin/env bash

filter_commit_to_files() {
    FILES="$1"
    SHA="$2"
    git show "$SHA" -- $FILES | git apply --index -
    git commit -c "$SHA"
}

示例用法:

filter_commit_to_files "file1.txt file2.txt" 07271c5e

我通过从这里复制并粘贴到我的 shell 中来定义它。您不需要 here document