Як 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.