Мои ветви имеют diveregd после "squa sh и слияния". Как мне это исправить? - PullRequest
0 голосов
/ 03 мая 2020

Я сделал 'Squa sh и объединить', чтобы закрыть пиар. Хотя все изменения теперь присутствуют в master, все равно говорится, что ветви разошлись, и впереди и позади все еще есть изменения.

This branch is 1 commit ahead, 28 commits behind develop. 

Как это исправить? 28 коммитов позади - это те, которые были раздавлены во время «сква sh и слияния». Я прошел через https://help.github.com/en/github/administering-a-repository/about-merge-methods-on-github#squashing -your-merge-commits , но не смог найти соединение.

Я предположил (неправильно?), Что «squa sh и слияние» будет выполнять быстрое слияние, чтобы предотвратить расхождение. Я не хотел, чтобы все коммиты с develop переместились на master, поэтому я сделал «squa sh and merge» вместо ребазинга. Я думаю, что что-то упускаю из-за squa sh и слияния.

Я уже сталкивался с подобными вопросами по SO, но он заканчивается предложениями сделать интерактивную ребазу и коммиты squa sh перед слиянием. до master. Но я не хочу терять историю в develop.

Ответы [ 2 ]

1 голос
/ 03 мая 2020

Итак, ваши заявленные требования:

1) В мастер должен быть добавлен только один коммит

2) Все коммиты должны оставаться в ветви разработки (без потери истории)

3) Новый коммит в основной ветке должен быть связан с оригинальными коммитами, чтобы git не думал, что они разошлись

Ближайший git придет на встречу этим три требования одновременно - это простое объединение.

$ git checkout master
$ git merge develop

Если вы сделаете это, верно, что когда git отображает историю master, она по умолчанию включает коммиты из develop ; вы можете избежать этого, используя опцию --first-parent для команд ilke git log.

Фактический граф фиксации в конечном итоге будет выглядеть так:

o -- x -- x -- M <--(master)
 \            /
  x --- x -- x <--(develop)

, поэтому M (коммит слияния) добавлен к master, и он «запоминает» свою связь с полной историей на develop.

На самом деле вся разница в выполнении «сква sh слияния» (как вы заметили) заключается в том, что M «забывает» свое отношение к develop - 2-й родитель не записывается.

A rebase требование жертвенности рабочего процесса (1) - а также требует переписывания коммитов таким образом, что создает новые промежуточные коммиты, которые могут или не могут быть в рабочем состоянии - в пользу линейной истории.

"Squa sh -and-merge" может "исправить" ограничения rebase, но это пожертвует требованием (3) - и если вы не оставите develop в качестве своей собственной разветвленной ветви навсегда, то вы потеряете требование (2).

Лично я считаю, что эти затраты слишком высоки, когда единственной выплатой является линейная история ; некоторые люди действительно любят эту линейность и клянутся, что ее легче понять, но я не очень ее ценю.

В любом случае, это те компромиссы, которые должна сделать каждая команда, принимая решение стратегия интеграции изменений.

1 голос
/ 03 мая 2020

Как это исправить?

Это только исправимо. Обычно вы должны просто полностью удалить ветку. Затем вы можете создать новую ветку, даже такую, которая все еще называется develop. Тогда это другая ветвь, даже если это одно и то же имя.

Эта последовательность команд может исправить это, но быть очень осторожным и убедиться, что вы понимаете каждую из них. :

git checkout master
git reset --hard develop
git push --force-with-lease origin master

Команда git reset --hard отбросит любую незафиксированную работу, поэтому будьте особенно осторожны всякий раз, когда вы ее используете.

Я дам некоторые альтернативы ниже. Есть много вариантов с различными эффектами, особенно для любых других людей, которые могут использовать репозиторий GitHub. Если вы - единственный человек, использующий репозиторий GitHub, вы можете делать все, что захотите.

В любом случае, помните: это цель squa sh -and-merge: убить ветку . Идея состоит в том, что после этого squa sh -and-merge вы удалите имя ветви и навсегда откажетесь от всех 28 коммитов. Squa sh -and-merge сделал один новый коммит, содержащий ту же работу , что и 28 предыдущих коммитов, поэтому один новый коммит - новый и улучшенный замена для этих 28 коммитов.

Чтобы понять, что вы делаете, читайте дальше.

Чтобы действительно "получить" Git, вам нужно нарисовать графики

Я предположил (неправильно?), Что «squa sh и слияние» будет выполнять быстрое слияние для предотвращения расхождения. Я не хотел, чтобы все коммиты с develop переместились на master, поэтому вместо «перебазирования» я сделал «squa sh and merge». Я думаю, что я что-то упускаю в сква sh и слиянии.

Да. GitHub также выбрасывает лишнюю морщинку - или, может быть, убирает нужную морщину , в зависимости от того, как вы на нее смотрите. Сначала нам нужно немного побольше.

Git - это все о commits . Эти коммиты формируются в виде графика , в частности D прямой A cycli c G raph или DAG . Как это работает, на самом деле удивительно просто:

  • Каждый коммит имеет уникальный идентификатор ha sh. Этот идентификатор ha sh фактически является истинным именем коммита.

  • Каждый коммит состоит из двух частей: его данных - снимка всех ваших файлов - и некоторые метаданные , информация о самом коммите, например, кто его сделал (ваше имя и адрес электронной почты), когда (отметки даты и времени) и почему (ваше сообщение в журнале). Большая часть этих метаданных просто показывает вам, когда вы смотрите на коммит, но одна часть информации имеет решающее значение для самого Git: каждый коммит содержит необработанные идентификаторы ha sh его родительских коммитов.

Большинство коммитов имеют только одного родителя. Когда что-то содержит идентификатор кома ha sh, мы говорим, что это что-то указывает на коммит. Таким образом, коммит указывает на его родителя. Если у нас есть цепочка коммитов все подряд, мы можем нарисовать их следующим образом:

... <-F <-G <-H

, где заглавные буквы обозначают действительные идентификаторы ha sh. последний коммит - это коммит H. Он указывает на своего родителя, который предшествовал ему: commit G. Коммит G указывает на его родителя F и т. Д.

A имя ветви в Git - это просто имя, которое указывает на последний commit:

...--G--H   <-- master

Когда мы создаем новую ветвь, мы на самом деле говорим Git: создаем новое имя, которое указывает на какой-то существующий коммит . Итак, если мы сделаем ветку develop сейчас, мы получим это:

...--G--H   <-- develop, master

Теперь нам нужно знать, какое имя мы на самом деле используем, поэтому мы прикрепляем специальное имя HEAD с таким именем:

...--G--H   <-- develop (HEAD), master

Когда мы делаем новые коммиты, мы просто Git записываем коммит - это вычисляет уникальный идентификатор ha sh ID нового коммита - со снимком и с родительским значением, установленным в current commit (H на данный момент):

...--G--H
         \
          I

То, что происходит с именами теперь, также очень просто: то, к которому присоединено HEAD, Git записывает там есть новый коммит с sh ID. Все остальные: ничего не происходит вообще. Итак, наш новый график:

...--G--H   <-- master
         \
          I   <-- develop (HEAD)

Поскольку мы добавляем больше коммитов, они просто продолжают увеличивать ветку:

...--G--H   <-- master
         \
          I--J--K   <-- develop (HEAD)

(Здесь develop равно 3 ahead из master .)

Есть еще одна ключевая вещь, которую нужно понять: фиксирует G и H, и все те, что находятся перед master, также включены develop. * 1139 Другими словами, коммиты часто находятся на более чем одной ветви . (Это странная вещь в Git: многие другие системы контроля версий не работают таким образом.)

Истинное слияние и быстрая перемотка вперед

На данный момент , мы можем запустить git merge локально в наших собственных репозиториях (но не в GitHub), используя:

git checkout master; git merge develop

Git примет короткий путь - ускоренная перемотка вперед вещь, которую вы упомянули. Мы вернемся к этому через мгновение.

В качестве альтернативы, мы можем запустить git merge --no-ff develop, заставив Git произвести истинное слияние. Это то, что будет делать кнопка «слияние» на GitHub, поэтому давайте посмотрим на нее. «Истинное» слияние всегда делает новый коммит слияния . Истинные слияния необходимы , если две ветви имеют , например: , если у нас было:

       o--o   <-- branch1
      /
...--o
      \
       o--o   <-- branch2

, и мы хотели бы объединить их, мы были бы вынуждены использовать истинное слияние. Это не то, что мы имеем; у нас есть:

...--G--H   <-- master (HEAD)
         \
          I--J--K   <-- develop

(Помните, мы сейчас на master: мы сделали git checkout master, поэтому H это текущий коммит. K это последний коммит в develop.)

GitHub просто вызывает истинное слияние в всех случаях, включая наш собственный простой. Чтобы выполнить истинное слияние, Git находит слияние с базой коммит (в данном случае это коммит H) и сравнивает эту базу слияния дважды, один раз с текущим коммитом H и один раз с commit K.

Очевидно, commit H соответствует commit H. Таким образом, с нашей стороны нет изменений, которые мы могли бы перенести. Нам нужны только те изменения, которые они внесли, начиная с H до K. Результат будет таким же, как снимок в K. Но мы форсируем слияние, поэтому Git делает новый коммит слияния, который я назову M для merge:

...--G--H---------M   <-- master (HEAD)
         \       /
          I--J--K   <-- develop

То, что делает M особенным - это делает это слияние - то, что у него есть не один родитель, а два. Его первый родитель - это коммит H, в котором мы были моментом go. Его второй родитель - commit K. Его снимок является результатом объединения работы, которую мы проделали на H -vs- H - вообще не работает - с работой, которую они проделали на H -vs- K, поэтому снимок нового коммита слияния соответствует моментальному снимку коммита K, но конечным результатом является этот новый коммит слияния.

Если мы разрешим Git выполнить ускоренную перемотку вперед вместо слияния вот что мы получили бы:

...--G--H
         \
          I--J--K   <-- develop, master (HEAD)

(что позволяет нам убрать излом с рисунка, хотя я не стал беспокоиться). Git называет это слиянием в ускоренном режиме, хотя фактического слияния не происходит. К сожалению * * * * * * * * * * * * * * * * * * * * * * * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * *}}}}}}}}}}}} выполнив истинное слияние, мы получили бы этот график:

...--G--H---------M   <-- master (HEAD)
         \       /
          I--J--K   <-- develop

Commit M будет иметь тот же моментальный снимок, что и commit K, и будет иметь двух родителей, H и K.

При выполнении сква sh слияния мы получаем вместо этого график:

...--G--H---------S   <-- master (HEAD)
         \
          I--J--K   <-- develop

Commit S имеет тот же моментальный снимок, что и commit K, но имеет только одного родителя, H , Идея в том, что вам нужен один обычный коммит, который будет иметь тот же эффект, что и все коммиты, которые вы сделали на develop. Этот единственный коммит - новый и улучшенный способ сделать работу. Старый способ - старые I-J-K коммиты - больше не нужны и должны быть оставлены; в конце концов, через 30 или более дней ваш Git удалит их по-настоящему. Таким образом, вы просто запускаете принудительное удаление name develop, производя:

...--G--H---------S   <-- master (HEAD)
         \
          I--J--K   [abandoned]

Если хотите, вы можете создать name develop снова, но на этот раз указывая на коммит S:

...--G--H--S   <-- develop, master (HEAD)
         \
          I--J--K   [abandoned]

Цель имен веток - позволить вам (и Git) найти коммиты

Обратите внимание, что при манипулировании График для добавления коммитов, мы перемещаем имена веток. Имена ветвей всегда указывают на последний коммит. Вот как Git определяет вещи для работы.

Однако мы можем заставить имя ветви перемещаться куда угодно. Если мы извлекли конкретную ветку, как в этом состоянии:

...--G--H--S   <-- master (HEAD)
         \
          I--J--K   <-- develop

, мы можем использовать git reset --hard для переключения коммитов и как бы оттолкнуть коммит S с пути:

          S   [abandoned]
         /
...--G--H   <-- master (HEAD)
         \
          I--J--K   <-- develop

Обратите внимание, что коммит S все еще существует . Просто, начиная с имени master, мы находим коммит H, затем работаем в обратном направлении и находим коммит G и так далее. Мы никогда не продвигаемся вперед, поэтому мы не можем получить коммит S. Он был заброшен, так же как и коммиты I-J-K, если бы мы удалили имя develop.

Если вы сохраните где-нибудь необработанный идентификатор * ha sh коммита - например, запишите его на бумаге, или на доске, или вырезать и вставить его в файл - вы можете использовать этот идентификатор sh позже, напрямую, чтобы Git нашел этот коммит. Если это все еще в хранилище (по умолчанию это будет не менее 30 дней), вы можете найти его таким образом. Но вы не найдете его с помощью большинства обычных команд git log и других команд Git.

Обратите внимание, что если коммит находится в более чем одной ветви , например, если у нас есть:

...--P   <-- branch1
      \
       Q   <-- branch2
        \
         R   <-- branch3

и мы удаляем имя branch2 - оно все еще может быть найдено. Результат удаления name branch2:

...--P   <-- branch1
      \
       Q--R   <-- branch3

в конце концов. Коммит Q по-прежнему существует в любом случае, но на этот раз мы можем найти его, начиная с R и работая в обратном направлении.

Именно поэтому важно нарисовать график или часть этого. Коммиты, которые вы можете найти , начиная с какого-то имени и работая в обратном направлении, - это коммиты, которые вы сможете найти и завтра - при условии, что вы не перемещаете имена, то есть.

На данный момент у вас есть два варианта

Вы можете либо оставить свой комод sh -составить коммит и забыть остальных, полностью удалив имя develop:

...--o--o--S   <-- master
         \
          o--o--...--o   [was develop; 28 commits, all abandoned]

или вы можете сформировать свой собственный локальный Git график, который будет выглядеть следующим образом:

          S   <-- origin/master
         /
...--o--o   <-- master (HEAD)
         \
          o--o--...--o   <-- develop, origin/develop

Обратите внимание, что ваш master определяет фиксацию за один шаг до фиксации S , Имя origin/master, являющееся вашей Git локальной копией GitHub master, указывает на коммит S.

Если ваш master уже выглядит это, вы уже настроены. Если ваш master выглядит следующим образом:

          S   <-- master (HEAD), origin/master
         /
...--o--o
         \
          o--o--...--o   <-- develop, origin/develop

вы бы запустили git reset --hard HEAD~1, чтобы переместить его назад на один, чтобы он выглядел как первый рисунок.

Теперь, когда ваш (локальный) Git хранилище настроено правильно, вы должны указать Git на GitHub переместить его master на один шаг назад. Для этого вам нужно использовать git push --force или git push --force-with-lease:

git push --force-with-lease origin master

Разница между ними заключается в том, что у --force-with-lease есть Git (тот, что на GitHub), который проверяет, что вы думаю, что они имеют как master, они действительно имеют как master. Так что --force-with-lease добавляет немного безопасности по сравнению с более простым --force, если кто-то еще также работает с этим другим хранилищем.

В обычном повседневном git push ваш Git вызывает другой Git - в этом случай, тот, что на GitHub - и выяснить, есть ли у вас новые коммиты для них. Если у вас есть новые коммиты для них, ваш Git отправляет их. Затем, в конце, ваш Git делает вежливый запрос к ним: Если все в порядке, пожалуйста, установите название вашей ветви ______, чтобы совершить га sh _______ . Git заполнит оба пробела: имя - это название ветви, которое вы используете, т. Е. master в git push origin master, а идентификатор ha sh - это идентификатор ha sh, который в настоящее время означает это имя в ваш Git.

Force-pu sh делает то же самое, за исключением того, что окончательный запрос вообще не вежлив: это требование, Установите название вашей ветви _______ на ________!

Другой Git, участвующий в git push, примет вежливый запрос, если он не потеряет любые коммиты. В этом случае ваш запрос или команда специально разработаны, чтобы заставить их потерять commit S. То есть они имеют:

...--o--o--S   <-- master
         \
          o--o--...--o   <-- develop

, и вы хотите, чтобы они оттолкнули S с пути и заставили их master указать на коммит до S. Таким образом, вам понадобится какое-то мощное пу sh.

Вы можете вернуть Squa sh -merge, затем выполнить ребазинг

Команда git revert работает добавив новый коммит, который отменяет эффект предыдущего коммита. Поскольку этот добавляет коммитов, Gits рады принять новый коммит. Этот конкретный метод здесь немного глуп, поэтому пока я нарисую эффект, вероятно, это не то, что вам нужно:

...--o--H--S--U   <-- master
         \
          o--o--...--o   <-- develop

Я дал начальную точку коммита H для develop a имя здесь. Причина в том, что снимок в коммите U, отмена для S, будет соответствовать снимку в коммите H.

Теперь вы можете использовать git rebase для скопировать всю серию коммитов, которые были develop, в новую серию коммитов. Эти новые коммиты имеют разные идентификаторы ha sh и разные родительские связи, но имеют такие же снимки , как и раньше:

                o--o--...--o   <-- develop (HEAD)
               /
...--o--H--S--U   <-- master, origin/master
         \
          o--o--...--o   <-- origin/develop

Вы сейчас находитесь в такой же ситуации, что и вы были до того, как вы случайно совершили коммит S. Вам придется принудительно-pu sh ваш обновленный develop на GitHub. (И тогда, конечно, вам все еще нужно объединить все!)

Вы можете go вперед и сделать реальное слияние

Вы можете оставить Squa sh commit S в поместите и сделайте реальное слияние:

git fetch                           # if needed
git checkout master
git merge --ff-only origin/master   # if needed

так, чтобы у вас было:

...--G--H--S   <-- master (HEAD), origin/master
         \
          I--...--R   <-- develop, origin/develop

, а затем:

git merge develop

для получения:

             ,--------<-- origin/master
            .
...--G--H--S--------M   <-- master (HEAD)
         \         /
          I--...--R   <-- develop, origin/develop

Теперь вы можете git push origin master заставить их принять коммит слияния M, что даст вам:

...--G--H--S--------M   <-- master (HEAD), origin/master
         \         /
          I--...--R   <-- develop, origin/develop

в вашем локальном Git хранилище.

Вы можете удалите S, перемотайте вперед-объедините ваш develop и force-pu sh

Наконец, это, вероятно, то, что вы хотели бы увидеть в своем личном, местном Git хранилище, прежде чем делать какие-либо принудительные толчки:

          S   <-- origin/master
         /
...--G--H--I--...--R   <-- develop, master (HEAD), origin/develop

Возможно, это то, что у вас есть:

          S   <-- origin/master
         /
...--G--H   <-- master (HEAD)
         \
          I--...--R   <-- develop, origin/develop

или, может быть, у вас теперь есть:

          S   <-- master (HEAD). origin/master
         /
...--G--H
         \
          I--...--R   <-- develop, origin/develop

Чтобы получить то, что вы хотите, в или случае вы можете просто:

git checkout master
git reset --hard develop

Поскольку ваш develop в настоящее время указывает на коммит R, это проверяет фиксацию R , делает ваш master пункт, чтобы совершить R, и ле дает вам график, который вы хотите. Затем вы можете использовать force-pu sh:

git push --force-with-lease origin master

для отправки любых ваших коммитов, которых у вас нет (нет, у них уже есть все эти коммиты), и командовать ими: I думаю, что ваш master идентифицирует коммит S. Если так, сделайте так, чтобы он идентифицировал commit R сейчас! Они обычно подчиняются этой команде, и вы получите то, что хотите.

...