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


586

如果我想合并到Git分支中,而仅对某些提交中的某些文件所做的更改(包括对多个文件的更改)进行更改,那么如何实现呢?

假设git的承诺称为stuff具有对文件的改变ABC,和D,但我想只合并stuff的对文件的更改AB。这听起来像是一项工作,git cherry-pickcherry-pick只知道如何合并整个提交,而不是文件的子集。

Answers:


688

我可以使用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 .

10
除此以外,git checkout .我还建议git clean -f您删除经过精心挑选的提交所引入的所有新文件,但这些文件是不需要的。
拉特2015年

4
后一种方法的附加说明:我使用git add -p它可以交互地决定要向每个文件
matthaeus 2015年

6
在这种情况下,如果选择的提交不适用于当前的工作副本(因为它是如此不同),但是一个文件可以干净地应用,则不是很好。
限量赎罪

3
您也可以使用选择性地取消登台git reset -p HEAD。它相当于,add -p但是很少有人知道它的存在。
PatrickSchlüter'17

1
非常有用的技巧。我已将其放入要点中,以防有人需要它作为快速脚本要点。github.com/ PiDayDev / 68c39b305ab9d61ed8bb2a1195ee1afc
Damiano

145

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

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 -

2
有趣的方法,谢谢。但并不show SHA -- file | apply基本上做的是一样的checkout SHA -- file,如马克Longair的答案
Tobias Kienzler 2015年

4
不,checkout SHA -- file将签正好在SHA版本,而show SHA -- file | apply将只适用于SHA(就像摘樱桃一样)的变化。重要的是:(a)在源分支中有多个提交更改了给定文件,或者(b)在当前目标分支中有一个更改更改了文件。
Michael Anderson

9
刚刚发现了另一个很好的用途:选择性还原,仅当您要还原一个文件时(因为git revert撤消整个提交)。在这种情况下,请使用git show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson

2
@RoeiBahumi具有完全不同的含义。git diff SHA -- file1.txt file2.txt | git apply -意味着将文件的当前版本和SHA上的版本之间的所有差异应用于当前版本。本质上,它与git checkout SHA -- file1.txt file2.txt。请参阅我先前的评论,以了解为何与git show版本不同。
迈克尔·安德森

5
如果必须解决冲突,请使用git apply -3 -而不是just git apply -,如果发生冲突,则可以使用标准的冲突解决技术,包括使用git mergetool
qwertzguy

87

我通常将-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每次提交选择我想要的内容来提交更多基于主题的提交:)


3
我经常使用git-add -p,但我不知道git-checkout也有一个-p标志-是否可以解决-p答案的合并问题?
Tobias Kienzler

1
至少-p将允许对这样一个冲突的部分进行手动编辑,cherry-pick无论如何也可能会产生结果。下次需要时,我将进行测试,这绝对是一种有趣的方法
Tobias Kienzler

2
不会杀死分支的并发更改的两个最佳答案之一。
akostadinov

1
有关如何选择要应用的块的信息,请参见以下答案: stackoverflow.com/a/10605465/4816250 特别是's'选项非常有用。
jvd10

1
git reset -p HEAD还允许-p仅在从索引中删除一些补丁时可以方便地使用。
PatrickSchlüter17年

42

也许这种方法优于Jefromi的答案的优点是您不必记住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

2
感谢您的回答。既然这激发了我的思考,为什么不跳过cherry-pick和直接使用git checkout stuff -- A B?并与git commit -C stuff提交信息将保持不变,以及
托比亚斯Kienzler

8
@Tobias:只有修改的文件将工作stuff没有被修改你的当前分支或共同祖先之间的任何地方HEAD,并stuff和尖端stuff。如果有,则cherry-pick创建正确的结果(本质上是合并的结果),而您的方法将丢弃当前分支中的更改,并保留所有更改,从公共祖先一直到stuff-不仅限于此单次提交。
卡斯卡贝尔2011年

2
@Tobias Kienzler:我以为您的出发点与父母的出发点有很大不同,stuff樱桃选择的结果将离开,A并且B其内容与提交中的内容不同stuff。但是,如果只是一样的话,那么您是对的-您可以按照自己的意愿去做。
Mark Longair 2011年

@ Jeromi,@ Mark:感谢您的反馈,就我而言,我正在使用完全分离的文件来处理分支,这导致了我的建议。但是确实的确,我早晚会遇到麻烦,因此感谢您提出来
Tobias Kienzler 2011年

我想我在其他主题中的回答可能是您所追求的。
伊恩

30

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文件名


5
我也这么认为,但是如果两个分支上的文件都已更改,将导致失败,因为它丢弃了当前分支的更改
Tobias Kienzler 2013年

没错,必须澄清这种方式git不合并,而只是覆盖。然后,您可以执行“ git diff HEAD文件名”以查看更改内容并手动进行合并。
cminatti

17

情况:

假设您在您的分支上,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"

1
太棒了!对我来说,也可以使用\ path \ to \ directory \来处理目录中的所有更改
zaggi

12

我只是挑选所有内容,然后执行以下操作:

git reset --soft HEAD^

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


11

使用git merge --squash branch_name它将从另一个分支获取所有更改,并为您准备一个提交。现在,删除所有不需要的更改,然后保留所需的更改。git不会知道有合并。


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

4

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

现在您可以删除temp分支:

$ git branch -d temp -f

2

将一个分支合并到一个新的(南瓜)中,并删除不需要的文件:

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

2

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

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

git status

它正是OP想要的。它会在需要时进行冲突解决,方法merge也是如此。可以,add但是不包括commit您的新更改。


1

您可以使用:

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

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

请注意,这不会提交任何内容(git cherry-pick确实如此)。因此,如果您需要这样做,则必须执行以下操作:

git add <path>
git commit
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.