Команди Git дублюються в одній гілці після того, як виконано ребазування


131

Я розумію сценарій, представлений у Pro Git, про "Перили випуску" . Автор в основному розповідає, як уникнути дублювання комітетів:

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

Я збираюся розповісти вам мою конкретну ситуацію, тому що я думаю, що це не зовсім відповідає сценарію Pro Git, і я все ще закінчуюсь дублюючими документами.

Скажімо, у мене є два віддалені відділення зі своїми місцевими колегами:

origin/master    origin/dev
|                |
master           dev

Усі чотири галузі містять однакові комісії, і я збираюся розпочати розвиток у dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

Після декількох зобов'язань я натискаю зміни на origin/dev:

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

Мені потрібно повернутися, щоб masterшвидко виправити:

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

І повернусь до devоновлення змін, щоб включити швидке виправлення у свою фактичну розробку:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

Якщо я показую історію комітетів за допомогою GitX / gitk, я помічаю, що origin/devтепер містить два однакові коміти C5'і C6'які відрізняються від Git. Тепер, якщо я натисну на це зміни, origin/devце результат:

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

Можливо, я не повністю розумію пояснення в Pro Git, тому хотів би знати дві речі:

  1. Чому Git дублює ці зобов’язання під час перезавантаження? Чи є певна причина для цього замість того, щоб просто подавати заявку C5та C6після неї C7?
  2. Як я можу цього уникнути? Було б розумно це зробити?

Відповіді:


86

Тут ви не повинні використовувати ребауз, достатньо буде простого злиття. Книга Pro Git, яку ви пов’язали, в основному пояснює цю точну ситуацію. Внутрішня робота може дещо відрізнятися, але ось як я це візуалізую:

  • C5і C6тимчасово витягуються зdev
  • C7 застосовується до dev
  • C5і C6відтворюються на вершині C7, створюючи нові відмінності і, отже, нові коміти

Таким чином, у вашій devгалузі, C5і C6ефективно більше не існує: вони тепер C5'і C6'. Коли ви натискаєте на origin/dev, git бачить C5'і C6'як нові вчиняє, і прив'язує їх до кінця історії. Дійсно, якщо ви подивитеся на відмінності між C5і C5'в origin/dev, ви помітите, що хоча вміст однаковий, номери рядків, ймовірно, різні, що робить хеш комітів різним.

Я повторю правило Pro Git: ніколи не перезавантажуйте комітети, які коли-небудь існували де-небудь, крім вашого локального сховища . Використовуйте замість злиття.


У мене є те саме питання, як зараз я можу виправити свою віддалену історію філій, чи є інший варіант, крім видалення гілки та відтворення її за допомогою вибору вишні ??
Wazery

1
@xdsy: Ява поглянь на це і це .
Джастін ᚅᚔᚈᚄᚒᚔ

2
Ви кажете: "C5 і C6 тимчасово витягнуті з dev ... C7 застосовано до dev". Якщо це так, то чому C5 і C6 відображаються перед C7 під час впорядкування комісій про походження / розробку?
KJ50

@ KJ50: Тому що C5 і C6 вже були натиснуті на origin/dev. При devповторній основі його історія змінюється (C5 / C6 тимчасово видаляється та повторно застосовується після C7). Змінення історії надісланих репостів, як правило, є справді поганою ідеєю ™, якщо ви не знаєте, що робите. У цьому простому випадку цю проблему можна вирішити, зробивши силовий натиск devна origin/devпісля відновлення бази даних та повідомивши всіх інших, хто працює, про origin/devте, що вони, мабуть, мають поганий день.
Зрештою,

3
Варто зазначити одне: хеші C5 та C5 ', безумовно, різні, але не через те, що номери рядків різні, а для наступних двох фактів, яких будь-який достатній для різниці: 1) хеш, про який ми говоримо - хеш всього вихідного дерева після фіксації, а не хеш дельта-різниці, і тому C5 'містить те, що походить від C7, тоді як C5 ні, і 2) Батьків C5' відрізняється від C5, і ця інформація також включається в кореневий вузол дерева комітів, що впливає на хеш-результат.
Озгур Мурат

113

Коротка відповідь

Ви пропустили той факт, що ви бігли git push, отримали таку помилку і продовжили біг git pull:

To git@bitbucket.org:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Незважаючи на те, що Git намагається бути корисною, її поради "git pull", швидше за все, не є тим, що ви хочете зробити .

Якщо ви:

Трохи довше пояснення

Кожен хеш комітів у Git заснований на ряді факторів, одним з яких є хеш коміту, який виходить перед ним.

Якщо ви будете упорядковувати комісії, ви зміните хеші фіксування; rebasing (коли він щось робить) змінить хеші фіксації. При цьому результат запущеного тексту git rebase master dev, devякий не синхронізований з master, створить нові коміти (і, таким чином, хеші), з тим самим вмістом, що і ті, devале з комісіями, masterвставленими перед ними.

Ви можете опинитися в подібній ситуації кількома способами. Я думаю про два способи:

  • У вас можуть бути домовленості щодо masterтого, на чому ви хочете базувати свою devроботу
  • Ви можете мати комітети dev, які вже були висунуті у віддалений пульт, який ви потім переходите до зміни (перезапис повідомлень про фіксацію, переупорядкування комітетів, скорочення комірок тощо)

Давайте краще розберемося, що сталося - ось приклад:

У вас є сховище:

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

Початковий набір лінійних комісій у сховищі

Потім ви переходите до зміни комітетів.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(Тут вам доведеться прийняти моє слово. У Git існує декілька способів змінити комітети. У цьому прикладі я змінив час C3, але ви вставляєте нові коміти, змінюєте повідомлення про фіксацію, упорядковуєте комітети, збивання здійснює разом тощо)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

Те саме відбувається з новими хешами

Тут важливо помітити, що хеші фіксації різні. Це очікувана поведінка, оскільки ви щось змінили (що-небудь) про них. Це нормально, АЛЕ:

Журнал графіків, що показує, що майстер не синхронізується з дистанційним

Намагаючись натиснути, ви побачите помилку (і підкажете, що вам слід бігти git pull).

$ git push origin master
To git@bitbucket.org:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Якщо ми запустимо git pull, ми побачимо цей журнал:

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Або показано іншим способом:

Журнал графіків, що показує об'єднання об'єднань

І тепер ми маємо дублікати комітетів на місцевому рівні. Якби ми запустили, git pushми відправили їх на сервер.

Щоб не потрапити на цей етап, ми могли бігти git push --force(куди ми замість цього бігали git pull). Це б надіслало наші зобов’язання з новими хешами на сервер без проблем. Щоб вирішити проблему на цьому етапі, ми можемо повернутись до того, як ми запустили git pull:

Подивіться на reflog ( git reflog), щоб побачити, яким був хеш на фіксацію, перш ніж ми бігли git pull.

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

Вище ми бачимо, що це ba7688aбуло зобов’язання, яке ми мали перед тим, як бігти git pull. Маючи в руці хеш-фіксацію, ми можемо повернутись до цього ( git reset --hard ba7688a), а потім запустити git push --force.

І ми закінчили.

Але зачекайте, я продовжував базувати роботу з дублюваних команд

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

Як це виглядає:

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

Журнал Git, що відображає лінійні коміти над дублюючими комітами

Або показано іншим способом:

Графік журналу, що показує лінійні коміти над дублюються комітами

У цьому сценарії ми хочемо видалити дублікати комітетів, але зберігаємо ті елементи, які ми базуємо на них - ми хочемо зберегти C6 до C10. Як і в більшості речей, для цього є ряд способів:

Або:

  • Створіть нову гілку на останньому дублюваному фіксуванні 1 , cherry-pickкожне введення (від C6 до C10 включно) на цю нову гілку, і трактуйте цю нову гілку як канонічну.
  • Виконати git rebase --interactive $commit, де $commitє фіксація перед обома дублюючими командами 2 . Тут ми можемо прямо видалити рядки для дублікатів.

1 Не має значення, яку з двох ви обираєте, ba7688aабо 2a2e220працюєте чудово.

2 У прикладі це було б 85f59ab.

TL; DR

Набір advice.pushNonFastForwardдля false:

git config --global advice.pushNonFastForward false

1
Добре дотримуватися порад "git pull ...", поки зрозумієш, що еліпсис приховує варіант "--rebase" (він же "-r"). ;-)
Г. Сільві Девіс

4
Я б порекомендував використовувати git push's --force-with-leaseсьогодні, оскільки це кращий дефолт
Whymarrh

4
Це або відповідь, або машина часу. Дякую!
ZeMoon

Дуже акуратне пояснення ... Я натрапив на подібну проблему, яка повторювала мій код 5-6 разів після того, як я кілька разів намагалася перезавантажити ... просто для того, щоб переконатися, що код оновлюється майстром ..., але кожного разу він натискав нові зобов’язання до моєї філії, дублюючи також мій код. Скажіть, будь ласка, чи безпечно це робити (з можливістю оренди), якщо я єдиний розробник, який працює у своїй галузі? Або злиття головного в моє замість звільнення - це кращий спосіб?
Dhruv Singhal

12

Я думаю, що ви пропустили важливу деталь, описуючи свої кроки. Більш конкретно, ваш останній крок, git pushу програмі dev, насправді дав би вам помилку, оскільки ви не можете нормально просувати нешвидкі зміни вперед.

Так ви зробили git pullдо останнього поштовху, що призвів до злиття з C6 та C6 'як батьків, тому обидва залишаться вказаними в журналі. Гарніший формат журналу, можливо, зробив би більш очевидним, що вони є об'єднаними гілками дублюваних комітетів.

Або ви зробили натомість git pull --rebase(або без явного, --rebaseякщо це має на увазі ваш конфігурація) замість цього, який витягнув оригінали C5 та C6 назад у вашому локальному розробнику (і надалі повторно перезавантажив наступні на нові хеші, C7 'C5' 'C6' ').

Одним із способів цього могло бути git push -fнатискання, коли воно видало помилку та витирало C5 C6 з походження, але якщо хтось ще їх витягнув перед тим, як ви їх витерли, у вас виникне ще багато проблем. В основному всім, хто має C5 C6, потрібно було б зробити спеціальні кроки, щоб позбутися від них. Саме тому вони кажуть, що ніколи не слід перезавантажувати все, що вже було опубліковано. Це все-таки можливо, якщо сказати, що "публікація" знаходиться в невеликій команді.


1
Опущення git pullмає вирішальне значення. git push -fМабуть, ваша небезпечна рекомендація - це те, що читачі шукають.
Whymarrh

Справді. Ще коли я писав запитання, яке я насправді робив git push --force, щоб побачити, що збирається робити Git Я з того часу дізнався про Git і сьогодні rebaseє частиною мого нормального робочого процесу. Проте я git push --force-with-leaseуникаю перезапису чужої роботи.
elitalon

Використання --force-with-lease- це гарний дефолт, я також залишаю коментар під своєю відповіддю
Whymarrh

2

Я з’ясував, що в моєму випадку це питання є наслідком проблеми конфігурації Git. (Залучаючи потягнення та злиття)

Опис проблеми:

Sympthoms: Здійснюється дублювання на дочірній гілці після повторної бази даних, що передбачає численні злиття під час і після відновлення.

Потік: Ось етапи робочого процесу, який я виконував:

  • Робота над «Особливості-галузь» (дитина «Розвиваємо-відділення»)
  • Здійснити зміни та натиснути зміни на "Особливості-гілка"
  • Оформити замовлення «Розвивай-відділення» (материнська гілка функцій) та працюй з ним.
  • Здійснюйте зміни та натисніть на "Розвиток-відділення"
  • Оформити замовлення "Особливості-гілка" та витягнути зміни зі сховища (якщо хтось інший погодився на роботу)
  • Перейдіть на сторінку "Особливості-гілка" на "Розробити-відгалуження"
  • Натисніть на зміни на "Feature-branch"

Як підступність цього робочого процесу, дублювання всіх комітетів "Feature-branch" з попередньої бази даних ... :-(

Виникнення цього питання було пов'язано з переходом змін у дитячій гілці перед початком роботи. Конфігурація тягання за замовчуванням Git - це "злиття". Це зміна індексів комітетів, виконаних на дочірньому відділенні.

Рішення: у файлі конфігурації Git налаштуйте функцію "pull" для роботи в режимі rebase:

...
[pull]
    rebase = preserve
...

Сподіваюсь, це може допомогти Ю. Гр


1

Можливо, ви витягнули з віддаленої гілки, відмінну від поточної. Наприклад, ви, можливо, вийшли з Master, коли ваша філія розвиває відстеження. Git буде пристойно тягнути у двох примірниках комітетів, якщо витягнутий із не відстежуваної гілки.

Якщо це трапиться, ви можете зробити наступне:

git reset --hard HEAD~n

де n == <number of duplicate commits that shouldn't be there.>

Потім переконайтеся, що ви витягуєте з правильної гілки, а потім запустіть:

git pull upstream <correct remote branch> --rebase

Якщо потягнути за допомогою --rebase, ви не додасте сторонні комітети, які можуть замутити історію фіксації.

Ось трохи руки, яка тримається за git rebase.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.