Как git-cherry-pick только изменения для определенных файлов?


587

Если я хочу объединить ветку Git с изменениями, внесенными только в некоторые файлы, измененные в конкретном коммите, который включает в себя изменения в нескольких файлах, как это может быть достигнуто?

Предположим , что Git коммит называется stuffимеет изменения в файлах A, B, Cи , Dно я хочу , чтобы объединить только stuffизменения «s к файлам Aи B. Звучит как работа, git cherry-pickно cherry-pickзнает, как объединить все коммиты, а не подмножество файлов.

Ответы:


689

Я бы сделал это с 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удалить все новые, но нежелательные файлы, введенные вишневым коммитом.
— rlat

4
Дополнительное примечание к последнему методу: я использую метод, git add -pкоторый позволяет вам в интерактивном режиме решить, какие изменения вы хотите добавить в индекс для каждого файла
— Матфей

6
Это не так здорово в том случае, если выбранный вишневый коммит не применяется к текущей рабочей копии, потому что он настолько отличается, но один файл будет применяться корректно.
— Ограниченное искупление

3
Вы также можете удалить выборочно с git reset -p HEAD. Это эквивалент, add -pно очень немногие знают, что он существует.
— Патрик Шлютер

1
Очень полезный трюк. Я поместил это в суть на тот случай, если кому-то понадобится быстрый скрипт gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
— Дамиано,

146

Другие методы не работали для меня, так как в коммите было много изменений и конфликтов с множеством других файлов. То, что я придумал, было просто

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как в ответе Марка Лонгаира ?
— Тобиас Кинцлер

4
Нет, checkout SHA -- fileбудет проверять именно версию в SHA, в то время как show SHA -- file | applyбудут применены только изменения в SHA (точно так же, как cherry-pick). Имеет значение, если (а) существует более одного коммита, изменяющего данный файл в исходной ветке, или (б) есть коммит, изменяющий файл в вашей текущей целевой ветке.
— Майкл Андерсон

9
Просто нашел другое отличное применение для этого: выборочный возврат, когда вы хотите вернуть только один файл (так как git revertотменяет весь коммит). В этом случае просто используйтеgit show -R SHA -- file1.txt file2.txt | git apply -
— Майкл Андерсон

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 -вместо просто 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(Нет) и т. Д. Для каждого фрагмента кода.

Опция -por patchработает с множеством команд в git, в том числе git stash save -pпозволяет выбрать то, что вы хотите сохранить в своей текущей работе.

Я иногда использую эту технику, когда проделал большую работу и хотел бы выделить ее и зафиксировать в коммитах на основе нескольких тем, используя git add -pи выбирая то, что я хочу для каждого коммита :)


3
Я регулярно использую git-add -p, но я не знал, git-checkoutтакже есть -pфлаг - это решает проблемы слияния, которые есть у -pответа ?
— Тобиас Кинцлер

1
по крайней мере -p, допускается ручное редактирование для такого противоречивого раздела, что cherry-pick, вероятно, в любом случае также приведет к. Я проверю это в следующий раз, когда мне это понадобится, безусловно, интересный подход
— Тобиас Кинцлер,

2
Один из двух лучших ответов, которые не убивают параллельные изменения в ветвях.
— Акостадинов

1
Посмотрите этот ответ, чтобы узнать, как выбрать те блоки, которые нужно применить: stackoverflow.com/a/10605465/4816250 В частности, опция 's' была очень полезна.
— jvd10

1
git reset -p HEADтакже позволяет -pлегко выйти из него, если вы хотите удалить только некоторые патчи из индекса.
— Патрик Шлютер

42

Возможно, преимущество этого метода перед ответом Джефроми в том, что вам не нужно помнить, какое поведение 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сообщением фиксации тоже останется то же самое
— Тобиас Кинцлер

8
@Tobias: Это будет работать, только если измененные файлы stuffне были изменены в вашей текущей ветке или где-либо между общим предком HEADи stuffи верхушкой stuff. Если они есть, то cherry-pickсоздает правильный результат (по сути, результат слияния), в то время как ваш метод отбрасывает изменения в текущей ветви и сохраняет все изменения от общего предка до stuff- не только те, что в одиночный коммит.
— Каскабель

2
@Tobias Kienzler: Я предполагал, что ваша отправная точка достаточно сильно отличается от родительской, stuffпоскольку результат выбора вишни уйдет Aи будет Bотличаться от их содержания в коммите stuff. Однако, если бы это было так же, вы правы - вы могли бы просто сделать, как вы говорите.
— Марк Лонгэйр,

@Jeromi, @Mark: спасибо за ваш отзыв, в моем случае я лечу ветки совершенно несвязанными файлами, что привело меня к моему предложению. Но на самом деле у меня рано или поздно возникли проблемы с этим, так что спасибо, что подняли этот вопрос
— Тобиас Кинцлер,

Я думаю, что мой ответ в этой другой теме может быть то, что вы после.
— Ян

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 не будет MERGE файл, он просто переопределит любые другие изменения, сделанные в целевой ветви. Вам нужно будет объединить изменения вручную:

$ git diff HEAD filename


5
Я тоже так думал , но это ужасно терпит неудачу, если файлы изменились в обеих ветвях, так как отбрасывает изменения вашей текущей ветки
— Тобиас Кинцлер

Вы правы, необходимо уточнить, что таким образом git не MERGE, он просто переопределяет. Затем вы можете сделать «git diff HEAD filename», чтобы увидеть, что изменилось, и выполнить слияние вручную.
— Cminatti

18

Ситуация:

Допустим, вы в своей ветке, 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

13

Я бы просто все выбрал, а потом сделал бы это:

git reset --soft HEAD^

Затем я отменил бы изменения, которые мне не нужны, а затем сделал бы новый коммит.


11

Используйте git merge --squash branch_nameэто, чтобы получить все изменения из другой ветки и подготовить коммит для вас. Теперь удалите все ненужные изменения и оставьте тот, который вы хотите. И мерзавец не узнает, что произошло слияние.


Спасибо, я не знал об этом варианте слияния. Это жизнеспособная альтернатива, если вы хотите выбрать вишню большую часть всей ветви (но в отличие от вишни она не будет работать, если нет общего предка)
— Тобиас Кинцлер,

4

Я нашел другой способ, который предотвращает любое противоречивое слияние при сборке вишни, которое IMO легко запомнить и понять. Поскольку на самом деле вы выбираете не коммит, а его часть, вам нужно сначала разделить его, а затем создать коммит, который соответствует вашим потребностям, и выбрать его.

Сначала создайте ветку из коммита, который вы хотите разделить, и извлеките его:

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

Затем верните предыдущий коммит:

$ git reset HEAD~1

Затем добавьте файлы / изменения, которые вы хотите в cherry-pick:

$ 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

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

Он делает именно то, что хочет ОП. Он делает разрешение конфликтов, когда это необходимо, так же, как mergeэто делает. Это делает, addно не commitваши новые изменения.


1

Ты можешь использовать:

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

Обозначение <commit>^указывает (первого) родителя <commit>. Следовательно, эта команда diff выбирает изменения, сделанные <path>в коммите <commit>.

Обратите внимание, что это еще ничего не зафиксирует (как это git cherry-pickделает). Так что если вы хотите этого, вам придется сделать:

git add <path>
git commit
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.