Команда git для створення однієї гілки як іншої


81

Я намагаюся взяти гілку зі змінами та повернути її, щоб вона була ідентичною вищій течії, від якої вона відійшла. Зміни як локальні, так і перенесені на github, тому жодні git resetабо git rebaseнасправді не життєздатні, оскільки вони змінюють історію, що погано з гілкою, яка вже була висунута.

Я також пробував git mergeіз різними стратегіями, але жодна з них не скасовувала локальні зміни, тобто якби я додав файл, злиття може повернути інші файли у відповідність, але я все одно матиму цей файл, який вище мати.

Я міг би просто створити нову гілку вгору за течією, але я б дуже хотів злиття, яке з точки зору історії редагувань застосовує всі зміни, щоб взяти мою гілку і зробити її ідентичною попередній, щоб я міг безпечно просувати цю зміну без історії клобукування. Чи існує така команда або серія команд?


9
Якщо ви не дбаєте про збереження змін, чому б просто не видалити та відтворити гілку? "Історія проекту" не повинна бути святою. Git - це інструмент, який допомагає розробникам спілкуватися. Якщо ці зміни не допомагають цьому, викиньте їх.
wnoise

+100 @wnoise - особливо якщо зміни вже були об’єднані.
wuputah

7
Я дбаю про збереження історії, оскільки вона опублікована для співпраці, і я, можливо, захочу повернутися до неї. Навіщо заважати користуватися контролем редагування, якщо ви зберігаєте лише останню версію?
Arne Claassen

1
Це суб’єктивний аргумент, але для мене мета VCS не в тому, щоб записати всі дрібниці історії проекту, а лише в тому, щоб записати зміни до вмісту (коміти), щоб дозволити вам маніпулювати деревом / історією на основі щодо цих комітів (розгалуження, злиття, перебазування, скидання тощо) і дозволяють переглядати звіти на основі історії (різниці, журнали, звинувачення тощо). git - це "дурний відстежувач вмісту" - я розглядаю його як інструмент управління вихідним кодом, а не машину часу.
wuputah

5
Як ви вже сказали, це суб'єктивно. Я дбаю про те, щоб мати змогу переглядати покинуті підходи та мати можливість бачити, які рішення були прийняті в минулому. І я дбаю про те, щоб моє рішення кинути щось не знищило точки об’єднання, на які можуть вказувати інші.
Arne Claassen

Відповіді:


107

Ви можете об’єднати свою devгілку, що стоїть вище, із своєю гілкою, за допомогою спеціального драйвера злиття "keepTheirs" :
Дивіться " " git merge -s theirs"потрібен - але я знаю, що він не існує ".
У вашому випадку .gitattributesпотрібен буде лише один , і такий keepTheirsсценарій, як:

mv -f $3 $2
exit 0

git merge --strategy=theirs Моделювання №1

Показується як злиття, а вище за течією як перший батько.

Jefromi згадує (у коментарях) merge -s ours, об'єднавши вашу роботу вгору за течією (або в тимчасовій гілці, що починається з вищого стоку ), а потім швидко перенаправивши свою гілку до результату цього злиття:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Це дає перевагу запису попередника вище за течією як першого батька, так що злиття означає "поглинати цю застарілу гілку теми", а не "знищувати цю гілку теми і замінювати її вище" .

(Редагувати 2011):

Про цей робочий процес повідомляється в цій публікації блогу ОП :

Чому я хочу це знову?

Поки мій репо не мав нічого спільного з публічною версією, це було добре, але оскільки я хотів би мати можливість спілкуватися на WIP з іншими членами команди та сторонніми співавторами, я хочу переконатися, що мої публічні філії надійний для інших, щоб розгалужуватись і витягувати, тобто більше немає перебазування та скидання речей, які я надсилав до віддаленого резервного копіювання, оскільки це зараз на GitHub та для громадськості.

Отже, це залишає мене з тим, як я повинен діяти далі.
У 99% випадків моя копія потрапляє у майстер вищого рівня, тому я хочу працювати над своїм майстром і більшу частину часу проштовхувати його вгору.
Але час від часу те, що я маю wip, стає недійсним через те, що потрапляє в течію, і я відмовляюся від якоїсь своєї частини wip.
На цьому етапі я хочу повернути свого майстра синхронізовано з випускним потоком, але не знищувати жодні точки коміту на моєму публічно просунутому майстрі. Тобто я хочу злиття з верхнім потоком, яке закінчується набором змін, які роблять мою копію ідентичною вище .
І ось що git merge --strategy=theirsслід робити.


git merge --strategy=theirs Моделювання №2

Показується як злиття, з нашим першим із батьків.

(запропоновано jcwenger )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Моделювання №3

У цій публікації в блозі згадується :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

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


git merge --strategy=theirs Моделювання №4

(те саме повідомлення в блозі)

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

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Моделювання №5

(запропоновано Бараком А. Перлмуттером ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Моделювання №6

(запропонований тим самим Майклом Гебетсройтером ):

Майкл Гебецройтер підслухався, стверджуючи, що я "обдурюю";) і дав інше рішення за допомогою команд сантехніки нижчого рівня:

(це не було б git, якби це було неможливо лише за допомогою команд git, все в git з diff / patch / apply не є реальним рішенням;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Моделювання №7

Необхідні кроки можна описати так:

  1. Замініть робоче дерево на попереднє
  2. Застосувати зміни до індексу
  3. Додайте вище за течією як другого батька
  4. Здійснити

Команда git read-treeзамінює індекс іншим деревом, виконуючи другий крок , і має прапори для оновлення робочого дерева, виконуючи перший крок . Під час фіксації git використовує SHA1 у .git / MERGE_HEAD як другий батько, тому ми можемо заповнити це, щоб створити коміт злиття. Отже, цього можна досягти за допомогою:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit

7
Ви завжди можете просто використовувати нашу замість їхньої: перевірте іншу гілку, об’єднайте свою в неї, а потім швидко перемотуйте свою до об’єднання. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream. (Використовуйте тимчасову гілку вгорі за течією, якщо це необхідно.) Це має перевагу запису попереднього предка як першого батька, так що злиття означає "поглинання цієї застарілої гілки теми", а не "знищення цієї гілки теми та заміна це з висхідним потоком ".
Cascabel

@Jefromi: відмінна ідея, як завжди. Я включив це у свою відповідь.
VonC

Інший варіант - наприклад, злиття git --strategy = їх моделювання №1 - за винятком того, що він зберігає Brange як першого батьківського об'єднання: git checkout -b tmp origin / upstream git merge -s ours downstream # ігноруючи всі зміни від git git замовлення за течією git merge --squash tmp # застосувати зміни з tmp, але не як злиття. git rev-parse upstream> .git / MERGE_HEAD #record upstream, як друга голова злиття git commit -m "rebaselined our from upstream" # make commit. git branch -D tmp # видалення tmp
jcwenger

2
Нічого собі, хто б міг подумати, що --strategy = їх можна реалізувати у багатьох випадках. Тепер, якби це могло бути просто в наступній версії git
Arne Claassen

VonC та його знання вражають. Він як JonSkeet з git. :)
sjas

13

Мені здається, що вам просто потрібно зробити:

$ git reset --hard origin/master

Якщо немає змін, які потрібно проштовхнути вгору за течією, і ви просто хочете, щоб гілка вгору за потоком була вашою поточною гілкою, це зробить це. Робити це локально не шкідливо, але ви втратите будь-які локальні зміни **, які не були натиснуті на майстер.

** Насправді зміни все ще існують, якщо ви вчинили їх локально, оскільки коміти все ще будуть у вас git reflog, як правило, принаймні протягом 30 днів.


це працює, якщо вам потрібна ця зміна за будь-яку ціну, оскільки це може змінити історію гілки (вам потрібно натиснути клавішу -f). які можна налаштувати на блокування, тому в основному це буде працювати лише для власних приватних сховищ на практиці.
ribamar

12

Ви можете зробити це досить легко зараз:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Це призводить до синхронізації локального репо з початком та зберігає історію.


5
git merge -s recursive -Xtheirsне автоматично об’єднує двійкові файли, тому ви потрапляєте в конфліктну ситуацію, яку вам доведеться вирішити вручну. Робочі процеси на основі git merge -s oursцього не страждають.
Стефаан

Здається, це створює порожній коміт.
Робін Грін,

Мої вибачення - насправді це працює, але git showв коммітуванні злиття відображаються лише дозволи конфліктів, і жодних резолюцій конфлікту немає, якщо -Xtheirsвони використовуються, очевидно.
Робін Грін,

5

Ще одне моделювання для git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Альтернативою подвійному скидання було б застосування зворотного патча:

git diff --binary ref-to-be-merged | git apply -R --index

Цікаве використання зворотного патча. +1
VonC

Скидання для мене не спрацювало, я отримав "фатальний: неоднозначний аргумент 'HEAD2': невідома редакція або шлях не в робочому дереві." . (Так, я друкував HEAD^2) Метод виправлення справді спрацював.
user247702

@Stijn: Швидше за все, ви насправді ^неправильно ввели текст . Інколи інші натискання клавіш, такі як "ctrl-c", відображаються як "^ C". - Якщо ви дійсно ввели правильний "^", ви знайшли серйозну помилку у вашій версії git.
michas

@michas У Windows символ ^ використовується для екранування, тому для того, щоб використовувати його як літерал, вам потрібно уникнути його з собою. git reset --hard HEAD^^2; git reset --soft HEAD@{1}
Джошуа Уолш

4

Існує також спосіб з невеликою допомогою команди водопроводу - IMHO найпростіший. Скажімо, ви хочете наслідувати "їх" для 2 гілок:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Це об'єднує довільну кількість головок (2 у прикладі вище), використовуючи дерево однієї з них (бар у прикладі вище, що надає дерево їхнього), не враховуючи будь-які проблеми з файлами / файлами (коміт-дерево є командою низького рівня, тому не хвилює тих). Зверніть увагу, що голова може бути лише 1 (таким чином, еквівалент вишні з "їх").

Зверніть увагу, що батьківська голова, яка вказана першою, може впливати на деякі речі (див., Наприклад, --перший батько команди git-log) - тому майте це на увазі.

Замість git-show можна використовувати будь-що інше, що може видавати хеші дерева та фіксувати - що б не було використано для синтаксичного аналізу (cat-файл, rev-list, ...). Ви можете стежити за всім за допомогою git commit --amend, щоб інтерактивно прикрасити повідомлення коміту.


В моїх очах це найпростіше. Ви використовуєте сантехнічні команди, щоб сказати "Створити новий об'єкт коміту за допомогою цього дерева, цього першого батька, цього другого батька. Потім вкажіть голову на цей новий коміт.", Що саме ми хочемо "git merge -s theirs" зробити. Недоліком є ​​те, що вам потрібно зберегти 4 різні хеші, щоб ця операція працювала.
Michael R

2

Важкі руки, але біса, що може піти не так?

  • Перевірте гілку X, яку ви хочете виглядати як Y
  • cp -r .git /tmp
  • Перевірте відділення Y git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Здійснюйте та натискайте будь-яку різницю
  • ГОТОВО.

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

1

перейдіть на віддалену гілку вгору за течією та виконайте a git mergeзі стратегією злиття, встановленою на ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

Вся історія все ще буде присутня, але у вас буде додатковий коміт злиття. Тут найголовніше - почати з тієї версії, в якій ви хочете бути, і об’єднатись oursіз гіткою, на якій насправді знаходиться github.


1
Мені потрібно навпаки. Це забере мою гілку та інтегрує її у вищий потік, але залишить верхню головку незмінною. Але мені потрібно взяти верхній потік та інтегрувати його у свою гілку, залишаючи мою голову схожою на висхідну. В основному щось подібне --strategy=theirs, за винятком того, що найближчі --strategy=recursive -X=theirsне зовсім цього роблять.
Arne Claassen

--strategy=theirsє якраз протилежним --strategy=ours. Ви починаєте з протилежного кінця (тож починайте з github і об’єднайте в інший бік).
kelloti

немає --strategy=theirs, в чому проблема. Найближче - --strategy=recursive -X theirsце не зовсім протилежне, оскільки воно не видалить сторонні локальні зміни, якщо вони не суперечать.
Arne Claassen

Ці два протилежні: git checkout dev; git merge origin/master --strategy=oursаgit checkout origin/master; git merge dev --strategy=ours
wuputah

2
@Arne: Дивіться мій коментар щодо відповіді VonC. Наявність oursстратегії дозволяє повністю зробити theirsстратегію.
Cascabel

1

Використовуйте git reset НАЗАД!

Ви можете зробити гілку схожою на будь-який інший комміт git reset, але ви повинні зробити це навколо.

Щоб зробити гілку при фіксації <old>схожою на фіксацію <new>, ви можете це зробити

git reset --hard <new>

для того, щоб зробити <new>вміст робочого дерева.

Тоді зробіть

git reset --mixed <old> 

змінити гілку назад на початковий коміт, але залишивши робоче дерево у <new> стані .

Потім ви можете додавати та фіксувати зміни, щоб ваша гілка точно відповідала вмісту <new>коміту.

Цілком інтуїтивно зрозуміло, що для переходу від <old>стану до стану <new>потрібно зробити git reset від <new> до <old> . Однак з опцією --mixedзалишається робоче дерево, <new>а вказівник гілки встановлюється на <old>, так що при здійсненні змін гілка виглядає, як ми хочемо.

Увага

Не втрачайте своїх комісій, наприклад, забудьте, що <old>саме робите git reset --hard <new>.


0

Я виконував ці ролі:

Отримавши вихід, скиньте жорсткий з гілки, потім рекурсивно з їх, а потім примусово натисніть на гілку

НА ВЛАСНИЙ РИЗИК

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.