Як git-cherry-pick змінювати лише певні файли?


586

Якщо я хочу об'єднати у гілку Git зміни, внесені лише до деяких файлів, змінених у певному комітеті, який включає зміни у декілька файлів, як це можна досягти?

Припустимо , що Git Комміт називається stuffмає зміни в файлах A, B, Cі , Dале я хочу , щоб об'єднати тільки stuffзміни «s до файлів Aі B. Це звучить як робота, git cherry-pickале cherry-pickзнає лише, як об’єднати цілі коміти, а не підмножину файлів.

Відповіді:


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

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

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 (так само, як і вишня). Має значення, якщо (a) є кілька команд, що змінюють даний файл у вихідній гілці, або (b) чи є фіксація зміни файлу у вашій поточній цільовій гілці.
— Майкл Андерсон

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 <other_branch_name> <files/to/grab in/list/separated/by/spaces> -p

приклад:

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

Тоді ви отримуєте діалогове вікно, яке запитує вас, які зміни ви хочете в "краплі", це в значній мірі працює на кожен шматок безперервної зміни коду, який ви можете подати сигнал 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 є правильною :)

 # 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

Вишневий вибір - це вибір змін у конкретному "здійсненні". Найпростіше рішення - вибрати всі зміни певних файлів - це використовувати

 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

Ви маєте рацію, потрібно уточнити, що цей спосіб git НЕ МЕРЕ, а просто перекриває. Потім ви можете зробити "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це, ви отримаєте всі зміни з іншої галузі та підготуєте для вас зобов’язання. Тепер видаліть усі непотрібні зміни та залиште потрібну. І git не буде знати, що відбулося злиття.


Дякую, я не знав про цей варіант злиття. Це життєздатна альтернатива, якщо ви хочете вибрати більшу частину цілої гілки (але, на відміну від вишні, вона не спрацює, якщо немає спільного предка)
— Тобіас Кіензлер

4

Я знайшов інший спосіб, який запобігає будь-яке конфліктне злиття під час збирання вишень, який IMO легко запам’ятати та зрозуміти. Оскільки ви насправді ви не приймаєте вишню на зобов’язання, а частина її, вам потрібно спочатку розділити її, а потім створити комісію, яка буде відповідати вашим потребам, і виберіть її.

Спершу створіть гілку з комітету, який ви хочете розділити, і оформити його:

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

Потім скасуйте попередню комісію:

$ git reset HEAD~1

Потім додайте файли / зміни, які ви хочете вишнювати:

$ git add FILE

і зробити це:

$ git commit -m "pick me"

зверніть увагу на хеш-фіксацію, давайте назвемо це PICK-SHA і повернемося до вашої основної гілки, наприклад, примушуючи замовити замовлення:

$ 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.