Безопасный ли вариант фиксации запроса на слияние? - PullRequest
1 голос
/ 19 октября 2019

Моя команда разработчиков недавно перешла на использование Gitlab. Я предположил, что при запросах о слиянии мы должны раздавить коммиты. Я получил большое давление назад, что это было бы небезопасно.

Типичный цикл разработки функций включает ежедневные коммиты и нажатия на ветку функций. Он также включает один раз в день git-тянет от мастера, чтобы синхронизироваться с другими изменениями. Иногда это включает в себя разрешение конфликтов слияния.

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

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

Ответы [ 3 ]

2 голосов
/ 19 октября 2019

Когда Git выполняет трехстороннее слияние (которое является стилем слияния по умолчанию), он рассматривает три точки: базу слияния (обычно общий предок) и две головы. Он никак не учитывает какие-либо коммиты между ними.

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

Теперь, насколько легко просмотреть историю и найти предполагаемое поведение при слиянииконфликт происходит - это другая история;так как все, что у вас есть, это гигантские коммиты с раздвоенными углами с каждой стороны, выяснить правильное разрешение может быть сложнее. Но сами конфликты не должны быть другими.

1 голос
/ 19 октября 2019

Нет причин бояться ... многие люди делают ЭТО с git rebase -i. Лично я иду по альтернативному пути, который не оставит вещи позади и не требует перебазирования:

0 голосов
/ 20 октября 2019

С риском повторить (частично или полностью) то, что есть в ответе bk2204 , я попробую сделать это.

(Обратите внимание, что проблема с любымответ, потому что вы использовали слово safe . Это не термин с четким и очевидным техническим определением. Что-то совершенно безопасное в одной ситуации может быть менее-менее в другой. Аналогии всегда распадаются, когда их толкают слишком сильно,но подумайте, например, о том, чтобы идти по улице без ношения, защиты для рук, коленей и локтей, против масштабирования по улице на роликовых коньках - или я должен сказать «роликовые коньки» ? - без этой защиты.)

Я всегда хотел сказать, что Git не о файлах , а о коммитах (которые затем содержат файлы). Это справедливо и в этом случае:

  • Каждый коммит имеет снимок (всех ваших) файлов плюс некоторые метаданные.
  • Объединение просто объединяет снимки - обычно 3 из них -в соответствии с определенным набором правил.
  • Регулярное слияние и сквош-слияние используют один и тот же механизм для достижения окончательного набора файлов , которые должны войти в следующий коммит.
  • Но коммитов , сделанных git merge --squash (или эквивалентной щелкающей кнопкой на веб-интерфейсе), отличаются от коммитов, сделанных git merge (или эквивалентной клик-кнопкой).

В этом случае вся разница заключается в родительской связи окончательной фиксации. На первый взгляд, это может показаться небольшой разницей ... но способ фиксации связи в Git, - это история, в Git. Нет такой вещи как file history: есть только в commit history. Так что эта небольшая разница на самом деле огромна - особенно если учесть, как слияние сквоша должно, вообще говоря, влиять на поведение людей в будущем.

Чтобы увидеть, как это работает на практике, необходимо понять коммит график . Это приводит нас к метаданным, о которых я упоминал в первом пункте выше.

Визуализация графика фиксации

Иногда мне бывает полезно взглянуть на фактическое конкретное содержание реального коммита,Вот коммит в Git-репозитории для самого Git:

$ git rev-parse HEAD
08da6496b61341ec45eac36afcc8f94242763468
$ git cat-file -p 08da6496b61341ec45eac36afcc8f94242763468 | sed 's/@/ /'
tree 27fee9c50528ef1b0960c2c342ff139e36ce2076
parent 07f25ad8b235c2682a36ee76d59734ec3f08c413
author Junio C Hamano <gitster pobox.com> 1570770961 +0900
committer Junio C Hamano <gitster pobox.com> 1570771489 +0900

Eighth batch

Signed-off-by: Junio C Hamano <gitster pobox.com>

(я заменил @ на пробел, чтобы, возможно, сократить количество спама, который идет на pobox.com. Примечаниечто вы на самом деле можете просто git cat-file -p HEAD тоже: здесь я использую необработанный хеш-идентификатор, чтобы помочь закрепить идею о том, что важен именно хеш-идентификатор.)

Это фактически полный и полный текст коммита. Идентификатор хэша, 08da6496b61341ec45eac36afcc8f94242763468, представляет собой криптографическую контрольную сумму этого материала. 1 Git косвенно хранит снимок как объект tree , 2 , который имеетсвой собственный хэш-идентификатор. Остальная часть текста - это остальная часть метаданных: автор и коммиттер (имя, адрес электронной почты и отметка даты и времени);родительские коммиты, перечисленные по хэш-идентификатору;и сообщение журнала.

Итак, каждый коммит имеет свой уникальный хэш-идентификатор. Этот хэш-идентификатор относится к , что commit;никакая другая фиксация не может когда-либо иметь такой же хэш-идентификатор. 3 Более того, каждая фиксация по хеш-идентификатору ссылается на непосредственную родительскую фиксацию (и). Эти родительские хэш-идентификаторы должны быть идентификаторами некоторых коммитов, которые существовали до этого коммита. Для обычных коммитов (без слияния) (одиночный) родитель - это тот коммит, который вы извлекли во время выполнения коммита.

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

A <-B <-C

Здесь, commit A - это первый коммит, который мы сделали. У него есть no parent, потому что parent - это коммит, который существовал до коммита A, а его нет. Этот особый случай - то, что Git называет корневой коммит . 4 Но затем, когда мы сделали коммит A, мы сделали коммит B, поэтому родительский элемент B равен A. Точно так же мы использовали commit B, когда мы сделали commit C, поэтому родительский элемент C равен B.

Когда что-то содержит хэш-идентификатор коммита, мы говорим, что эта вещь указывает на коммит. Поэтому коммит C указывает на B, что указывает на A, который является корневым коммитом, и ничто не указывает на него.

На этих чертежах легко увидеть, где заканчивается цепочка, но в реальномGit-репозиторий с реальными - и случайным образом - хеш-идентификаторами - это сложно. Нам нужен быстрый и простой способ найти коммит C, на данный момент. Итак, отчасти как для нас, простых людей, которые не могут вспомнить необработанные хэш-идентификаторы, Git предоставляет нам имена ветвей .

Имя ветки просто содержит хэш-идентификатор одного коммита. В нашем случае мы будем использовать имя master для хранения фактического хеш-идентификатора коммита, который мы называем C. Мы можем нарисовать это так:

A--B--C   <-- master

Имя master указывает на последний коммит в цепочке, то есть C. Я прекратил рисовать внутренние стрелки, потому что скоро их становится сложнее. Просто помните, что они всегда указывают назад. Они являются следствием строк parent в реальных коммитах, поэтому они никогда не могут измениться - в отличие от имени ветви стрелки, которые do изменяются.

Давайте добавим новый коммит D в нашу цепочку, выполнив git checkout master и затем обновив файлы, git add -ing и git commit -ing. Новый коммит D укажет на существующий коммит C как родительский элемент D:

A--B--C   <-- master
       \
        D

Тогда, поскольку D является новым последним коммит для цепочки, Git обновит наше текущее имя ветки, master, чтобы указать D вместо C. Мы также можем переместить D вверх по линии на чертеже. Результат:

A--B--C--D   <-- master

Чтобы добавить ветку, мы просто создаем новое имя, указывающее на текущий коммит. Давайте создадим новую ветвь dev и укажем на D:

A--B--C--D   <-- dev, master

Теперь нам понадобится еще один бит обозначений: мы должны вспомнить, какую имя ветви мы провериливне. У нас есть коммит D, но когда мы добавляем новый коммит E, только одно из двух имен ветвей должно переместиться. Для этого мы добавим (HEAD) после одного из названий веток. Это имя , которое мы получили;git commit переместит это имя. Поэтому, если мы git checkout dev, мы останемся с коммитом D - и всеми его файлами - и затем, когда мы сделаем новый коммит, мы получим:

A--B--C--D   <-- master
          \
           E   <-- dev

1 Технически это контрольная сумма SHA-1 слова commit, за которым следует пробел, за которым следует размер в байтах, выраженный в десятичном формате, то есть 280, за которым следует символ ASCII NUL, за которым следуют байты содержимого:

$ (printf 'commit 280\0'; git cat-file -p HEAD) | shasum
08da6496b61341ec45eac36afcc8f94242763468  -

Этот метод работает для всех четырех типов объектов Git: вывести имя типа, пробел, размер объекта в байтах в десятичном виде ASCII, ASCII NUL, а затем содержимое,Для коммитов содержимым является текст коммита, который находится в следующем порядке: tree и хэш, каждый родительский порядок, author строка, committer строка, любые дополнительные строки заголовка, пустая строка, тема-и-Боди. Для аннотированных тегов посмотрите на любые аннотированные теги. Объекты BLOB-объектов имеют очевидный текст. Самое сложное - это объекты дерева, которые являются двоичными и имеют несколько полу произвольных правил, применяемых для обеспечения порядка. Объекты дерева станут большим камнем преткновения при преобразовании в SHA-256.

2 Это позволяет совместно использовать объекты дерева: если два разных коммита сохраняют один и тот же снимок, им не нужно повторно сохранять дерево. Это совместное использование применяется рекурсивно к поддеревьям и, конечно, к отдельным объектам BLOB-объектов. В конце концов, после того, как объект записан, ни одна его часть не может быть изменена - контрольная сумма идентификатора хеша изменится, и результатом будет другой объект - поэтому, если у вас одинаковое содержимое, он сводится к одному и тому же идентификатору хеша. База данных объектов, представляющая собой простое хранилище значений ключей, обнаруживает, что ключ уже существует, и, следовательно, не пытается снова сохранить данные.

3 Обратите внимание, что если вы сделаететот же коммит, с тем же деревом, тем же автором, тем же лог-сообщением и, что крайне важно, с одинаковыми метками времени и родителями , вы получаете тот же коммит. Если любой из них отличается, включая родительский хеш или отметку времени, вы получаете новый и другой коммит. Таким образом, если вы повторно фиксируете тот же моментальный снимок , вы делаете это в другое время и с другим родительским коммитом: следовательно, новый коммит не совпадает со старым. Следовательно, новый коммит получает новый хэш. Даже если вы используете GIT_AUTHOR_DATE и GIT_COMMITTER_DATE, чтобы заставить новый коммит использовать общую метку времени старого коммита, хеш parent будет другим: новый коммит ссылается на какой-то другой существующий коммит, другойот исходного родительского (ых) коммита, так что повторная фиксация одного и того же снимка дает вам новый и другой коммит.

Единственный способ заставить Git повторно использовать хеш коммита - это зафиксировать тот жеснимок, одновременно, с теми же родителями . В этом случае можно повторно использовать исходный коммит, поскольку новый коммит равен исходному коммиту. Они действительно идентичны. Никто не может отличить их друг от друга, поэтому они также могут быть одним и тем же коммитом.

4 Вы можете генерировать графы коммитов, которые имеют более одного корневого коммита, но во многих репозиториях только один оригиналкорень. Для получения дополнительного корневого коммита требуется один из нескольких немного странных приемов.


Слияние коммитов и граф фиксации

A слияние коммит в Git:любой коммит по крайней мере с двумя родителями (и обычно только с двумя родителями). Мы не можем сделать коммит слияния, пока у нас нет форка в графе. Например, предположим, что график является линейным с фиксацией H, после чего он расходится следующим образом:

          I--J   <-- master (HEAD)
         /
...--G--H
         \
          K--L   <-- dev

Мы уже сделали git checkout master, что означает, что мы зафиксировали коммит Jпрямо сейчас. Имя dev идентифицирует коммит L.

Теперь мы запускаем git merge dev. Git использует график , чтобы найти точку, в которой две ветви были объединены в последний раз, что в данном случае, очевидно, является коммитом H. Git называет это коммитом слияния . Два других интересных коммита - это наш текущий коммит, J, и тот, на который указывает имя dev, L.

Git теперь будет действовать:

  • diff содержимое базы слияния H commit против нашего текущего коммита J: это то, что мы изменили со времени основания;
  • diff H против другого коммитаL: это то, что они изменили со времени основания;
  • делают все возможное, чтобы объединить эти два набора изменений.

Комбинированные изменения применяются к содержимому из коммита H. Таким образом, мы получаем наши изменения плюс их изменения. Точные детали процесса объединения могут быть довольно хитрыми, особенно перед лицом конфликтов, но здесь мы больше заинтересованы в результате. Предположим, что конфликтов не было.

Поскольку мы запустили git merge (не git merge --squash), Git теперь подтвердит результат. Новый коммит продвигает нашу текущую ветвь, как и любой другой коммит, но на этот раз новый коммит указывает на оба двух коммитов с ветвлением. Следовательно, мы можем нарисовать результат следующим образом:

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

Commit M имеет двух родителей, J и L.

OfКонечно, мы можем добавить больше ежедневных коммитов, если захотим:

          I--J
         /    \
...--G--H      M--N   <-- master (HEAD)
         \    /
          K--L   <-- dev

Новый коммит N имеет M в качестве родителя, как обычно. Когда Git покажет нам коммиты, которые достижимы с master, Git начнет с N, затем вернется на один шаг назад к M и покажет нам commit M. Теперь у Git есть выбор: должен ли он вернуться на один шаг назад к J или на один шаг к L?

В общем, Git фактически делает оба из них. 5 Ни один из родителей не является «лучше» или «важнее», чем другой, поэтому Git будет следовать обоим трекам - обеим сторонам слияния - и посещать коммиты J и I, но также L иK. Эти треки объединяются в H, поэтому, как только Git достигает H, он может перейти к G и F и т. Д., Как и раньше.

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

Одна из причин, по которой это так важно, заключается в том, что база слияния future зависит от текущий объединить. Давайте нарисуем немного другой вариант, в котором dev объединяется в master более одного раза. Мы начнем с одного слияния M в master, например:

...--H---L--M--P   <-- master
      \    /
       I--J--N--O   <-- dev

(Да, нет K, я оставил его, чтобы слияние получило письмо M. :-))

Теперь мы будем git checkout master и git merge dev, чтобы сделать новое слияние q. Git должен найти базу слияния между коммитом P и коммитом O. Для этого он работает в обратном направлении от P, переходя к M, а затем к L и J. Он также работает в обратном направлении от O до N, а затем J.

Commit J доступен из обеих ветвей и является правильной базой для слияния. Это - база слияния , потому что M ссылается на L и J. Так что обратная ссылка, идущая от M до J, здесь важна. Если бы у нас его не было, Git продолжал бы идти дальше, с L до H и с J до I до H.

Другими словами, безссылка, предоставленная коммитом слияния M, слияние next должно было бы смотреть на разницу от H до P (наши изменения) и от H до O (их изменения) что включает в себя уже объединенные изменения . Имея ссылку , Git смотрит на разность от J до P - что в действительности является тем, что мы сделали в L и P - и с J до O,что в действительности является тем, что они сделали в N и O. Таким образом, последующее слияние будет проще (конечно, нетривиально, но проще).


5 с git log и некоторыми другими командами Git, выможно использовать --first-parent, чтобы сообщить игнорировать второго родителя и любых дополнительных родителей, если они существуют, для этой одной команды. Обратите внимание, что первый родитель слияния выбирается во время слияния. Это не может быть изменено позже. Это всегда тот коммит, на котором вы были, когда вы или кто-то другой сделал слияние. viewer должен решить, является ли этот первый родитель каким-то образом «более важным», чем любой другой родитель, но единственным встроенным флагом здесь является --first-parent: --second-parent нет, поэтому либо всеродители равны, или первый родитель более важен.


Слияние в сквош не является слиянием

Предположим, что вместо слияния M с git merge dev мы сделалиэто с git merge --squash dev. Git все равно:

  • найти базу слияния H;
  • diff H против L, чтобы увидеть, что мы изменили;
  • diff Hпротив J чтобы увидеть, что они изменились;и
  • объединяют две разницы, применяя комбинацию к снимку с H.

ThФлаг командной строки git merge --squash также устанавливает флаг --no-commit, так что Git заставляет нас запускать git commit для создания нового коммита M. Большинство щелкающих кнопок на веб-интерфейсах сами выполняют коммит, и у Git из командной строки больше нет веских причин останавливаться таким образом, за исключением того, что обратная совместимость требует этого. В любом случае, когда мы выполняем слияние - или какая-то кликающая кнопка делает это - мы получаем:

...--H---L--M   <-- master
      \
       I--J   <-- dev

Все иначе точно такое же, но коммит M не имеет возврата-ссылка на dev.

Если мы теперь продолжим как прежде и имеем:

...--H---L--M--P   <-- master
      \
       I--J--N--O   <-- dev

следующее объединение, которое мы запускаем, с или без --squash, чтобы ввести dev в master, будет сравнивать коммит H с коммитами P и O. Если нам нужно было сделать что-то умное с конфликтами слияния, когда мы сделали M, нам, вероятно, придется сделать это снова.

На практике это означает, что после git merge --squash вы должны почти всегда удалить ветку, которую вы только что слили. То есть, объединив коммит J, вы должны удалить имя dev:

...--H---L--M   <-- master
      \
       I--J   [abandoned]

Commits I и J некоторое время оставаться в вашем собственном хранилище- точно, как долго это сложно - но без имени , чтобы найти их, вам придется запомнить их хэш-идентификаторы или, по крайней мере, хеш-идентификатор commit J.

Осложнения

Здесь есть еще одно осложнение, если кто-либо сделал какой-либо другой указатель имени для коммитов I или J или любого коммита, из которого мы можем найти J или I. Например, если у нас есть:

...--H---L--M   <-- master
      \
       I--J   <-- dev
        \
         K   <-- d2

, а затем мы удаляем имя dev, так что фиксация J становится невозможной для поиска, если вы не помните его хэш-идентификатор, в результате мы получим значение похоже this:

...--H---L--M   <-- master
      \
       I--K   <-- d2

, которое теперь выглядит как сам коммит I - это нечто важное, что мы сделали при работе над d2, а не побочный эффект начала работы над d2после начала работы над dev и выполнения коммита I.

Это общее правило для Git, а не что-то особенное для слияния в сквош: важны коммиты , не так много имена ветвей . Имя ветви - это просто начальная или конечная точка, которую мы используем для find commit. Поэтому, когда вы перемещаете или удаляете имена ветвей, если commits все еще можно найти по какому-то другому имени ветви, они все еще там. Даже если они не могут быть найдены, они, вероятно, все еще где-то в вашем хранилище. Ваш Git в конце концов выбросит их, позже, решив, что они нежелательный мусор.

Мы находим коммиты, начиная с имен веток и работая в обратном направлении. Когда мы находим некоторый коммит таким образом, мы говорим, что он на ветке , но, возможно, точнее будет сказать, что он содержится в ветке . Любой коммит может содержаться во многих ветках. Набор ветвей, содержащих фиксацию, изменяется, когда мы добавляем, удаляем или перемещаем имена ветвей.

Поэтому мы должны помнить следующее правило: Фиксация graph никогда не меняетсяза исключением добавления подтверждений или невозможности найти подтверждений. Имена ветвей все время меняются - чаще всего таким образом, что мы находим новые коммиты и и продолжаем находить старые коммиты.

Учитывая все этодавайте посмотрим на вашу конкретную ситуацию

Я идуg, чтобы пропустить большинство сложностей, возникающих при добавлении нескольких Git-репозиториев. git push просто отправляет коммиты в какой-то другой Git-репозиторий, прося их установить одно из их имен ветвей, чтобы указать последний из этих новых коммитов. git pull начинается с запуска git fetch, который получает все новые коммиты из какого-либо другого репозитория Git, а затем обновляет удаленное отслеживание имен вашего Git - origin/master и т. Д., Чтобы запомнить last commit в их соответствующих ветвях. git merge, который вы можете сделать после git fetch, иногда выполняет реальное слияние, как описано выше, чтобы сделать новый коммит слияния. Иногда он выполняет операцию fast-forward , которая сводится к проверке их коммитов и перетаскиванию вашего собственного имени ветки вперед, чтобы включить все новые коммиты, введенные этим. И, конечно же, git pull означает просто run git fetch, затем выполните вторую команду Git, обычно git merge.

Осложнения имеют значение, но фактическое слияние, если оно есть,всегда происходит в нашем собственном хранилище. 6 Мы также git fetch чьи-либо коммиты по мере необходимости. В конечном итоге мы храним все коммиты локально и можем визуализировать коммиты нашего Git - даже если они получены от какого-то другого Git - как просто существующие в нашем репозитории.

Типичный цикл разработки функций включает ежедневные коммитыи толкает на ветку функции. Он также включает один раз в день git-тянет от мастера, чтобы синхронизироваться с другими изменениями. Иногда это связано с разрешением конфликтов слияния.

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

...--H--K--L--N--O--R   <-- master
      \     \     \
       I--J--M--P--Q--S   <-- feature

где MQ - реальные коммиты слияния, некоторые из которых могут включать разрешение конфликтов слияния.

Если мы считаем, что feature готов сейчас , мы можем:

git checkout master
git merge feature

, который будет производить это:

...--H--K--L--N--O--R---T   <-- master
      \     \     \    /
       I--J--M--P--Q--S   <-- feature

Два родителя T: R (первый родитель) и S (второй). Мы можем теперь полностью удалить имя feature:

...--H--K--L--N--O--R---T   <-- master
      \     \     \    /
       I--J--M--P--Q--S

Фиксация S остается достижимой, пройдя через T к ее второму родителю. Само слияние выполняется путем сравнения базового снимка слияния в коммите O со снимками в коммитах R и S и их объединения.

Или мы можем git merge --squash, после чего мы действительно следует полностью удалить feature. Результат до удаления feature выглядит следующим образом:

...--H--K--L--N--O--R--T   <-- master
      \     \     \
       I--J--M--P--Q--S   <-- feature

содержимое коммита T было сделано одинаково в обоих случаях;просто у T на этот раз нет второго родителя (так что по определению это не коммит слияния ).

Предполагая, что мы не пытаемся запомнить коммиты по хэш-идентификатору и простозабудьте обо всех недоступных, результат, когда мы удаляем имя feature, выглядит следующим образом:

...--H--K--L--N--O--R--T   <-- master

6 С помощью щелкающих кнопок веб-интерфейса слияние происходитпоместите в их Git-репозиторий, после чего мы должны git fetch их объединить, а затем обновить собственные имена веток, чтобы включить новый коммит слияния. В общем, услуги хостинга не допускают слияний, в которых есть конфликты, так как услуги хостинга не обеспечивают способ разрешения конфликтов. Некоторые хостинговые службы могут добавлять такие функции с течением времени, но я бы предпочел делать конфликтующие слияния локально, сам: у вас гораздо больше данных, доступных таким образом.


Заключение

Ваш вопрос задан по поводу безопасность , но без определения сейф это на самом деле не несет ответственности. Что мы можем сказать, так это то, что работа, которую мы выполняли в отдельных коммитах I, J, P и S, больше не обнаруживается. Вместо этого, когда мы смотрим на снимки, которые остаются доступными для поиска, создается впечатление, что кто-то написал весь feature код / ​​изменения за ночь.

Thэто имеет хорошие и плохие аспекты. Для некоторых функций хорошее перевешивает плохое: нет отвлекающего второго родителя коммита слияния T, чтобы пройти через отдельные коммиты, такие как I, J, P и S, и нетнужно беспокоиться о коммитах слияния на этом пути. Но что, если в изменениях "все в одном" в T есть ошибка? Может быть, , эту ошибку легко найти, скажем, в коммите J или в коммите P, и трудно найти в T. Поэтому, возможно, было бы лучше сохранить отдельные коммиты.

Если вы уверены, что никогда не захотите вернуть исходные коммиты, то придет конец сквошу. Если вы не уверены, это может быть не так.

Существует компромиссная позиция, которая в некоторых отношениях может быть лучшей из всех. Вместо сквош-слияния вы можете перебазировать ветвь dev на feature. Что делает rebase - это copy commits. Впрочем, у ребазинга есть свои недостатки.

Вот что мы имеем до слияния:

...--H--K--L--N--O--R   <-- master
      \     \     \
       I--J--M--P--Q--S   <-- feature

Есть шесть коммитов, доступных из имени feature, которые недоступны из имени master: I, J, M, P, Q и S.

Выполнение git checkout feature; git rebase master заставит Git найти эти шесть коммитов, выкинуть любые коммиты слияния из списка - оставив I-J-P-S - и затем copy эти коммиты, по одному за раз, так что новые копии приходят после коммита R. Каждая копия делается так, как если бы git cherry-pick. 7 К сожалению, каждый вишневый кир может иметь конфликты , и если это так, вы должны разрешить их. Как правило, они представляют собой те же конфликты, которые вы могли разрешить при пропущенных теперь слияниях, и, как правило, нуждаются в том же разрешении. 8 После копирования Git перемещает имя ветви, так что конечный результатвыглядит следующим образом:

                      I'-J'-P'-S'  <-- feature
                     /
...--H--K--L--N--O--R   <-- master
      \     \     \
       I--J--M--P--Q--S   [abandoned, but the name ORIG_HEAD works to find S]

Имена I', J' и т. д. подразумевают, что, хотя у нового коммита есть новый и другой хэш-идентификатор, это копия исходного коммита.

Вы можете сравнить снимок в копии S' со снимком в S, используя:

git diff ORIG_HEAD HEAD

Если они не совпадают, вывероятно, допустил ошибку при повторном разрешении конфликтов. 9

Теперь, когда у вас все аккуратно перебазировано, вы можете выполнить ускоренное слияние:

                      I'-J'-P'-S'  <-- feature, master
                     /
...--H--K--L--N--O--R

или истинное слияние:

                      I'-J'-P'-S'  <-- feature
                     /          \
...--H--K--L--N--O--R------------T   <-- master

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

Большой недостаток перебазирования состоит в том, что каждый кто работал с feature должен переключиться на использование нового rebased- feature comмит копии. Если функция готова и ее имя будет удалено, это обычно довольно просто. Но это определенно относится к другому правилу, которое мне нравится использовать в Git: Заменяйте коммиты только на новые и улучшенные копии, если каждый, кто использует эти коммиты, согласился перейти на новые и улучшенные копии. Это соглашение обычно следует заключать заранее.


7 У Rebase есть изумительный набор опций, включая -i, -k, -m и -s, и некоторые из этих опций заставляют его фактически использовать git cherry-pick. Другие методы используют git format-patch и git am для внутреннего использования, но предназначены для получения того же результата.

8 Существует еще одна функция и команда Git, git rerere с опцией enable,это позволяет Git сделать это за вас, но всегда помните, что Git не умный , он просто применяет простые правила замены текста. Как rerere работает и что может пойти не так, довольно сложно, и я не буду вдаваться в подробности здесь.

9 Обратите внимание, что если ORIG_HEAD будет записано какой-либо последующей операцией, вытакже можно использовать feature@{1}, чтобы найти S. Если вы попадаете в сложную ситуацию, когда вам нужно знать хэш-идентификатор для S, либо скопируйте и вставьте его куда-нибудь, либо создайте временную ветку или имя тега для его хранения.

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

git checkout feature
git branch feature.0    # or .1, .2, etc if I already have .0, etc
git rebase master

Теперь у меня есть имя feature.0, чтобы запомнить исходный коммит с веткой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...