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

Я понимаю сценарий, представленный в Pro Git о рисках git rebase. Автор в основном говорит вам, как избежать дублированных коммитов:

Не перегружать коммиты, которые вы переместили в общий репозиторий.

Я расскажу вам о своей конкретной ситуации, потому что я думаю, что это не совсем соответствует сценарию 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, поэтому я хотел бы знать две вещи:

  • Почему Git дублирует эти коммиты при перезагрузке? Есть ли какая-то особая причина сделать это вместо того, чтобы просто применять C5 и C6 после C7?
  • Как я могу избежать этого? Было бы разумно это сделать?

Ответ 1

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

Ответ 2

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

Вы опустили факт, что вы запустили 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 (в соответствии с ответом Justin).

Несколько более длинное объяснение

Каждый хеш фиксации в 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

Initial set of linear commits in a repository

Затем вы продолжаете изменять фиксации.

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

The same commits with new hashes

Здесь важно заметить, что хеши фиксации разные. Это ожидаемое поведение, так как вы что-то изменили (что-либо) о них. Это нормально, НО:

A graph log showing that master is out-of-sync with the remote

Попытка нажать покажет вам ошибку (и подскажет, что вы должны запустить 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

Или, показано другим способом:

A graph log showing a merge commit

И теперь мы имеем локальную локальную запись. Если бы мы выполнили git push, мы отправили их на сервер.

Чтобы не дойти до этой стадии, мы могли бы запустить git push --force (где вместо этого мы запускали git pull). Это отправило бы наши коммиты с новыми хэшами на сервер без проблем. Чтобы устранить проблему на этом этапе, мы можем reset вернуться, прежде чем мы запустим git pull:

Посмотрите на reflog (git reflog), чтобы узнать, что сделал хэш хэш, прежде чем мы запустили git pull.

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

Выше мы видим, что ba7688a было фиксацией, на которой мы находились, перед запуском git pull. С этим хешем фиксации мы можем reset вернуться к этому (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 log showing linear commits atop duplicated commits

Или, показано другим способом:

A log graph showing linear commits atop duplicated commits

В этом сценарии мы хотим удалить повторяющиеся коммиты, но сохраним полученные на них коммиты - мы хотим сохранить C6-C10. Как и в большинстве случаев, есть несколько способов сделать это:

Или:

  • Создайте новую ветвь на последней дублированной фиксации 1cherry-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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

  • Работа над "Особенностью-веткой" (дочерний элемент "Develop-branch" )
  • Заблокировать и нажать изменения в разделе "Возможности-ветвь"
  • Оформить заказ "Развивать-филиал" (Материнская ветвь функций) и работать с ним.
  • Заблокировать и нажать изменения в "Develop-branch"
  • Оформить заказ "Свойства-ветвь" и вытащить изменения из репозитория (в случае, если кто-то еще совершил работу)
  • Rebase "Features-branch" на "Develop-branch"
  • Нажимаем силу изменений на "Feature-branch"

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

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

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

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

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