Коммиты Git дублируются в той же ветке после выполнения перебазирования.

Мне понятен сценарий, представленный в 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. Как мне этого избежать? Было бы разумно это сделать?

person elitalon    schedule 13.02.2012    source источник
comment
На этот вопрос есть множество отличных ответов. Интересно, есть ли у кого-то сжатая суть, статья или вики, а не книга, в которой собраны некоторые передовые практики последних дней? У меня несколько раз возникали OP и другие проблемы ветвления / слияния с использованием GitLab и GitHub, и кажется, что существует так много противоречивых советов, которые приводят к повторяющимся проблемам.   -  person learning2learn    schedule 03.07.2021


Ответы (5)


Вы не должны использовать здесь rebase, достаточно простого слияния. Книга 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: никогда не перемещайте коммиты, которые когда-либо существовали где-либо, кроме вашего локального репозитория. Вместо этого используйте слияние.

person Justin ᚅᚔᚈᚄᚒᚔ    schedule 13.02.2012
comment
У меня такая же проблема, как я могу исправить историю удаленных веток сейчас, есть ли другой вариант, кроме удаления ветки и ее воссоздания с помощью выбора вишни? - person Wazery; 26.05.2012
comment
@xdsy: взгляните на this и это. - person Justin ᚅᚔᚈᚄᚒᚔ; 27.05.2012
comment
Вы говорите, что C5 и C6 временно отключены от dev ... C7 применяется к dev. Если это так, то почему C5 и C6 появляются перед C7 в порядке коммитов в origin / dev? - person KJ50; 05.11.2014
comment
@ KJ50: Потому что C5 и C6 уже были переведены в origin/dev. Когда dev перебазирован, его история изменяется (C5 / C6 временно удаляется и повторно применяется после C7). Изменение истории отправленных репозиториев обычно является действительно плохой идеей ™, если вы не знаете, что делаете. В этом простом случае проблема может быть решена путем принудительного нажатия с dev на origin/dev после перебазирования и уведомления всех, кто работает над origin/dev, о том, что у них, вероятно, будет плохой день. И снова лучший ответ: не делайте этого ... используйте вместо этого слияние - person Justin ᚅᚔᚈᚄᚒᚔ; 21.09.2016
comment
Одно замечание: хеш-коды C5 и C5 ', безусловно, разные, но не из-за разных номеров строк, а из-за следующих двух фактов, из которых любого достаточно для разницы: 1) хеш, о котором мы говорим - это хэш всего исходного дерева после фиксации, а не хэш разницы дельты, и поэтому C5 'содержит все, что исходит от C7, а C5 - нет, и 2) Родитель C5' отличается от C5, и эта информация также включен в корневой узел дерева фиксации, влияющий на результат хеширования. - person Ozgur Murat; 26.05.2017
comment
если git merge master выполнен, то точки маркера следует немного изменить, например 1. C7 уже есть в мастере, 2. C5 и C6 воспроизводятся поверх C7 в мастере - person thepurpleowl; 08.08.2019
comment
Что делать, если вы не хотите, чтобы фиксация слияния запутывала вашу историю git. Это может быть утомительно при работе в больших командах над большими проектами. - person ScottyBlades; 06.12.2019
comment
@ScottyBlades Почему вам не нужен коммит слияния? Они жизненно важны, особенно для больших команд: они сообщают вам, когда работа, выполненная в другой ветке, была объединена с основной (или другой ветвью), и предоставляют сводку того, что изменилось. Эта статья может изменить ваше мнение о коммитах слиянием. - person Justin ᚅᚔᚈᚄᚒᚔ; 06.12.2019
comment
Уточнение: в большинстве случаев коммиты слиянием хороши. Бывали ситуации, когда я создавал посторонние коммиты слияния, которые не точно отображали слияния, как задумано, но, честно говоря, я не помню, как я облажался до этого. Но я был рад - вариант rebase был возможен, потому что другие разработчики отклоняли мою PR на их основе. - person ScottyBlades; 08.12.2019
comment
Спасибо, @Justin ᚅᚔᚈᚄᚒᚔ! когда вы говорите никогда не перебазировать коммиты, которые когда-либо существовали где-либо, вы имеете в виду C5 и C6 в этом примере, верно? (не C7). Но это означает, что я никогда не нажимаю на удаленные коммиты, которые я хочу перебазировать? И почему бы в этой ситуации не использовать rebase? Я думал, что это именно рабочий процесс «перебазировать на ветке, слить на главном». (Я также получаю дублированные коммиты после перебазирования в моей ветке). и последний вопрос ... если я работаю в ветке на 2 машинах (скажем, мой компьютер и сервер), я ДОЛЖЕН нажимать коммиты, поэтому я не могу следовать «никогда не переустанавливать ...». какой рабочий процесс лучше всего в таком случае? Благодарность - person Itamar Katz; 02.04.2020
comment
Я пользователь перебазирую, как сбой, никогда не сливаюсь, и он работает как шарм. лучшая часть git! Я бы никогда не перебазировал мастер, но все ветки в проектах, которые я контролирую, должны быть переустановлены на master, прежде чем им будет разрешено войти. Никакого беспорядка слияния, кристально чистая история. То, что здесь произошло, скорее всего, было другой ошибкой, как объясняет stackoverflow.com/a/30927009/2075537. Вам нужно будет принудительно выполнить фиксацию в удаленной ветке, но используйте удаленные ветки !! иначе у вас нет резервной копии !! - person Torge; 13.06.2020

Короткий ответ

Вы пропустили тот факт, что вы запустили git push, получили следующую ошибку, а затем продолжили выполнение git pull:

To [email protected]:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to '[email protected]: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 push --force, чтобы обновить пульт с вашими коммитами после перебазирования (согласно ответу user4405677).
  • Если вы работаете над веткой с несколькими разработчиками одновременно, то вам, вероятно, вообще не следует использовать git rebase. Чтобы обновить dev изменениями с master, вам следует вместо запуска git rebase master dev запустить git merge master на dev (согласно ответу Джастина ).

Немного более длинное объяснение

Каждый хэш фиксации в Git основан на нескольких факторах, одним из которых является хэш предшествующей фиксации.

Если вы измените порядок коммитов, вы измените хеши коммитов; перебазирование (когда он что-то делает) изменит хеши коммитов. При этом результат выполнения 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 [email protected]:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '[email protected]: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:

Взгляните на журнал ссылок (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, _39 _ каждую фиксацию (с C6 по C10 включительно) в эту новую ветку и рассматривать эту новую ветвь как каноническую.
  • Запустите git rebase --interactive $commit, где $commit - это предшествующая фиксация для обеих дублированных фиксаций 2. Здесь мы можем сразу удалить строки для дубликатов.

1 Неважно, какой из двух вы выберете, ba7688a или 2a2e220 работают нормально.

2 В примере это будет 85f59ab.

TL;DR

Установите для advice.pushNonFastForward значение false:

git config --global advice.pushNonFastForward false
person Whymarrh    schedule 18.06.2015
comment
Это нормально, если вы понимаете, что многоточие скрывает параметр --rebase (также известный как -r). ;-) - person G. Sylvie Davies; 16.12.2016
comment
В настоящее время я бы порекомендовал использовать --force-with-lease git push так как это лучший вариант по умолчанию - person Whymarrh; 16.04.2018
comment
Либо этот ответ, либо машина времени. Спасибо! - person ZeMoon; 19.10.2018
comment
Очень аккуратное объяснение ... Я наткнулся на аналогичную проблему, которая дублировала мой код 5-6 раз после того, как я неоднократно пытался перебазировать ... просто чтобы убедиться, что код обновлен с помощью мастера ... но каждый раз, когда он нажимал new фиксируется в моей ветке, дублируя мой код. Не могли бы вы сказать мне, безопасно ли здесь принудительное нажатие (с возможностью аренды), если я единственный разработчик, работающий над моей веткой? Или лучше слить мастер с моим вместо перебазирования? - person Dhruv Singhal; 17.12.2019

Думаю, вы упустили важную деталь при описании своих шагов. Точнее говоря, ваш последний шаг git push на dev фактически дал бы вам ошибку, поскольку вы обычно не можете продвигать изменения, не относящиеся к fastforward.

Итак, вы сделали git pull перед последним нажатием, что привело к слиянию с C6 и C6 'в качестве родителей, поэтому оба останутся в списке в журнале. Более красивый формат журнала мог бы сделать более очевидным, что они являются объединенными ветвями дублированных коммитов.

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

Одним из выходов из этого могло быть git push -f принудительное нажатие, когда оно выдало ошибку, и стирание C5 C6 из источника, но если бы кто-то еще вытащил их, прежде чем вы стерли их, у вас было бы намного больше проблем. .. практически всем, у кого есть C5 C6, нужно будет делать специальные шаги, чтобы избавиться от них. Именно поэтому они говорят, что никогда не следует переустанавливать уже опубликованные материалы. Тем не менее, это выполнимо, если «публикация» ведется в рамках небольшой команды.

person user4405677    schedule 30.12.2014
comment
Пропуск git pull имеет решающее значение. Ваша рекомендация git push -f, хотя и опасна, вероятно, именно то, что ищут читатели. - person Whymarrh; 18.06.2015
comment
Верно. Когда я писал вопрос, я действительно сделал git push --force, просто чтобы посмотреть, что Git собирается делать. С тех пор я многое узнал о Git, и в настоящее время rebase является частью моего обычного рабочего процесса. Однако я делаю git push --force-with-lease, чтобы не перезаписать чужую работу. - person elitalon; 07.02.2018
comment
Использование --force-with-lease - хорошее решение по умолчанию, я также оставлю комментарий под своим ответом - person Whymarrh; 16.04.2018

Я обнаружил, что в моем случае эта проблема является следствием проблемы с конфигурацией Git. (С привлечением вытягивания и слияния)

Описание проблемы:

Симптомы: коммиты дублируются в дочерней ветке после переназначения, что подразумевает многочисленные слияния во время и после переназначения.

Рабочий процесс. Вот шаги рабочего процесса, который я выполнял:

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

Как следствие этого рабочего процесса, дублирование всех коммитов "Feature-branch" с момента предыдущего перебазирования ... :-(

Проблема возникла из-за извлечения изменений дочерней ветки перед перебазированием. Конфигурация извлечения Git по умолчанию - «слияние». Это меняет индексы коммитов, выполненных в дочерней ветке.

Решение: в файле конфигурации Git настройте pull для работы в режиме перебазирования:

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

Надеюсь, это поможет JN Grx

person JN Gerbaux    schedule 26.05.2016

Возможно, вы использовали удаленную ветку, отличную от текущей. Например, вы могли перейти от мастера, когда ваша ветка разрабатывает отслеживание разработки. 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.

person ScottyBlades    schedule 05.12.2019