Как исключить ненужные коммиты из запроса на включение? - PullRequest
0 голосов
/ 10 февраля 2019

Я столкнулся с проблемой при использовании git branch.Я послал PR, чтобы объединить разрабатываемую ветку с главной веткой, но это включало ненужные коммиты.Я хотел бы удалить их из PR.

  1. Я создал ветвь разработки из главной ветки и добавил D1, коммиты D2.Наконец, я слил ветвь разработки с мастером, используя 'squash and merge'.
  2. И я создал ветку объектов из ветви разработки и добавил F1, коммиты F2.
  3. Втем временем другой человек добавил M4-коммит в основную ветку.
  4. Я отправил запрос на слияние, чтобы объединить функциональную ветку с ведущей.Однако этот PR включал в себя коммиты D1, D2, F1 и F2.

Мне пришлось создать ветвь объекта из master.Это была моя ошибка: (


 ───M1───M2───M3───S────M4 (master)
    └────D1───D2───┘ (develop)
              └────F1───F2 (feature)

Как включить только коммиты F1 и F2, кроме D1 и D2?

Ответы [ 4 ]

0 голосов
/ 10 февраля 2019

Ответ Рене правильный, но все эти слегка причудливые (с использованием набора символов в виде ячеек) диаграммы немного устарели, начиная с вашей собственной:

───M1───M2───M3───S────M4 (master)
   └────D1───D2───┘ (develop)
             └────F1───F2 (feature)

Причина в следующем:

... Я слил ветку разработки с мастером, используя 'squash and merge'.

The "squash"кнопка «Объединить» в кликабельных графических интерфейсах или git merge --squash в командной строке делает новый коммит, который не является объединением .Вместо:

...--M1--M2--M3--S--M4   <-- master
      \         /
       D1-----D2   <-- develop

то, что вы на самом деле получили, было:

...--M1--M2--M3--S--M4   <-- master
       \
        D1--D2   <-- develop

Нет никаких отношений предка / потомка между S и любым из D s, ни между какими-либоM, но M1 и любой из D с.Это как раз то, что отключает последующие операции Git.Если вы сделаете фактическое слияние - используя обычные git merge, или git merge --no-ff, или не щадящую щелчковую кнопку GitHub, , тогда S (и, следовательно, M4 также) будут потомкамиD1 и D2.

Но это не то, что мы имеем.Поэтому, когда мы смотрим на feature и master, мы видим это:

...--M1--M2--M3--S--M4   <-- master
       \
        D1--D2--F1--F2   <-- feature

Мы видим, что feature имеет четыре коммитов, достижимых из его коммитов tip (включая tip)передайте), которые недоступны с master, а именно D1-D2-F1-F1.Независимо от того, есть ли какое-либо имя, указывающее на D2, эти четыре коммита являются четырьмя, которые находятся на feature, которые также не включены на master.(Commit M1 находится на обеих ветвях, как и все, что осталось от него.)

Я думаю, что хороший способ думать об этом состоит в том, что "слияние в сквош" имеет побочный эффект: убивает ветвь, которая была только что слита.Коммиты D1 и D2 теперь фактически "мертвы" и должны считаться, по крайней мере, умеренно радиоактивными.Как сказал Ахмад Кундакджи , в конечном итоге они часто будут безвредны.Но они могут сделать вашу историю коммитов уродливой - как в , почему это было зафиксировано дважды, один раз как два отдельных коммита, а затем как один больший? , если feature фактически объединено вместо сквоша - объединено«- и в худшем случае они могут вызвать конфликты позже из-за некоторого изменения в коммите, который является потомком S.

Поскольку объединенная ветвь теперь« мертва », ее следует удалить.То есть вы должны запустить git branch -D develop.Но перед этим убедитесь, что коммиты D1 и D2 сами не содержатся ни в каких других ветвях - которые, конечно, в вашем случае равны .Если они содержатся в других ветвях, вы должны реконструировать эти ветки как варианты, которые больше не имеют этих двух коммитов.

Обратите внимание, что «перебазировать и объединить» (еще одна кликающая кнопка на GitHub - фактическивсе три объединены в одну кнопку с внутренним раскрывающимся списком, но это просто способ обозначить три разные кнопки) также имеет побочный эффект уничтожения ветвей, поскольку rebase действительно означает копировать старые коммиты вновые, затем прекратите использовать старые коммиты в пользу новых копий .

Я бы нарисовал график post- git rebase --onto master develop feature таким образом, сохранив "мертвые" develop на рисунке:

                       F1'-F2'  <-- feature
                      /
...--M1--M2--M3--S--M4   <-- master
       \
        D1--D2   <-- develop
              \
               F1--F2   [abandoned]

Это делает более понятным, что F1 и F2 все еще существуют под их уникальными хэш-идентификаторами фиксации.Они просто больше не легко найти , так как нет имени, которое мы можем использовать, чтобы найти их. 1 Начиная с имени feature, мы сначала находим заменяющую копию F2'что мы должны использовать вместо F2, затем F1', что мы должны использовать вместо F1, затем M4, затем S и так далее, назад в прошлое.Команды Git, такие как git log, не найдут старые скучные коммиты, которые мы заменили блестящими новыми, которые начинаются с F2' вместо M1.

(И, сейчас безопасно удалить develop, если нет других ветвей , а также включают D1. Обратите внимание, что включение D2 автоматически включает D1, так что это единственное, что мы должны упомянуть здесь.)

В тВ длинной команде git rebase --onto master develop feature у нас есть три интересных аргумента (плюс ключевое слово option --onto, конечно).Начиная с конца и работая в обратном направлении, поскольку Git выполняет не , мы имеем:

  • feature в качестве дополнительного аргумента, который the git rebase документация вызывает [<branch>]: git rebase говорит начать с git checkout feature.Если вы сделаете это сами, вы можете оставить этот последний аргумент.Это буквально просто передается git checkout, 2 , поэтому оно всегда должно быть именем ветви.

  • develop как то, что в документации git rebase называется [<upstream>]: сообщает git rebase, что обязывает не копировать.Если вы пропустите этот аргумент, Git использует все, что настроено в качестве целевой (или текущей) ветви upstream .Это имя передается через git rev-parse, поэтому оно может быть практически любым: необработанный хэш-идентификатор, имя тега, git describe выходные данные, имя ветви, относительная операция, подобная HEAD~2, и т. Д.on.

  • --onto master, как то, что документация git rebase вызывает <newbase>: сообщает git rebase , куда поместить копии фиксации , что в конечном итоге направляет егогде указать точку ветвления после завершения копирования.Если вы пропустите это, по умолчанию будет <upstream>, и, как и <upstream>, оно будет передано через git rev-parse.

Так что эта команда командной строки git rebase означает:

После проверки feature скопируйте все коммиты, доступные из кончика филиала, исключая любые коммиты, которые также достижимы из коммита, обозначенного develop, а также исключая любые другие коммиты, которыеВы, git rebase, чувствуете необходимость пропустить. 3 Делайте копии в правильном топологически отсортированном порядке, чтобы сначала копировались более ранние, менее зависимые коммиты, а затем копируются более зависимые коммитыпотом.Разместите каждую копию так, чтобы скопированная first коммит была получена сразу после коммита, обозначенного master.Когда вы скопировали последний коммит, дерните имя ветки feature так, чтобы оно указывало на последний скопированный коммит или, если коммиты не были скопированы, непосредственно на коммит, обозначенный master.

Когда Git закончил делать это, вы получите коммиты, которые выглядят так, как мы их нарисовали.(Вы также находитесь на ветке feature, если только ребята из Git не исправили то, что я считаю незначительной ошибкой в ​​git rebase - похоже, если вы скажете, что rebase запускается с помощью git checkout feature, но вы, скажем,master, в конце концов он должен оставить вас на master, а не на feature. Конечно, если перебазировка должна останавливаться из-за конфликтов, она останавливается в режиме «отсоединенного HEAD», но когда вы git rebase --continue илиgit rebase --abort чтобы возобновить или прекратить операцию, она должна в конечном итоге вернуть вас туда, где вы были, даже если это не feature.)


1 Там - это имен, по которым вы можете найти оригинальные F2 и F1, хранящиеся в Git's reflogs .Рефлог для любого ссылочного имени, в том числе для имен ветвей, содержит журнал, в котором фиксируется это имя ветки, идентифицируемое по их хэш-идентификаторам, по какой-то конкретной отметке времени.Каждое обновление ссылки, сделанное, например, git update-ref refs/heads/feature, сохраняет значение предыдущее в журнале и добавляет значение new с отметкой времени, когда это новое значение было простозаписано (и сообщение reflog о том, что происходит).

Запустите git reflog feature, чтобы просмотреть записи reflog для refs/heads/feature.Для самого HEAD есть дополнительный журнал.Запустите git reflog или git reflog HEAD, чтобы увидеть это.Обратите внимание, что старые записи в конце концов истекают;подробнее об этом см. подкоманду git reflog expire и документацию git reflog .

Обратите внимание, что git reflog show - это используемая здесь подкоманда -на самом деле просто запускается git log -g, поэтому вы можете использовать git log -g вместо git reflog здесь.

Удаление любой ветки удаляет ее reflog, но записи в журнале HEAD остаются.В некоторых будущих версиях Git планируется сохранить повторные журналы из удаленных ветвей, чтобы можно было «удалить» ветку, но эти планы пока довольно шероховаты и не сфокусированы, а также имеют некоторые проблемы с реализацией.

2 В какой-то момент слово буквально здесь было буквально правильным, так как git rebase был большим сценарием оболочки.Но теперь многие части git rebase написаны на C. Команда git checkout также написана на C, и когда вы собираете Git, вы создаете двоичные файлы, которые разделяют некоторый внутренний код реализации.Если git rebase является кодом C, который вызывает тот же внутренний код, что и отдельный git checkout двоичный код, является ли буквально вызывающим git checkout, или он теперь образный? Какова правильная семантика слова литерал здесь?Должен ли литерал требовать сопоставления внешних и конечных интерфейсов или только соответствующих конечных узлов?

3 Коммит, который git rebase пропускает самостоятельноявляются:

  • Слияние совершает.Это буквально невозможно скопировать.В некоторых режимах git rebase будет повторно выполнять слияния по мере необходимости.Этими режимами являются старый --preserve-merges и новомодный, улучшенный --rebase-merges;оба хитрые, и я не собираюсь пытаться описать их здесь.То есть, хотя в документации по rebase говорится о поиске списка коммитов для копирования с помощью <upstream>..HEAD, на самом деле он использует <upstream>...HEAD для нахождения симметричного различия двух наборов коммитов: тех, которые достижимы из HEAD, но не из восходящего потока.аргумент, и те, которые достижимы из аргумента восходящего потока, но не из HEAD.

В этой последней части используется способность команды git rev-list --left-right различать, с какой "стороны" происходят такие коммиты:фиксирует, что может быть скопировано справа - доступно с HEAD, но не с <upstream>.Тем не менее, те, которые находятся с левой стороны от этой симметричной разности, это те, которые «после точки отсечения», но достижимы с <upstream>.В этой конкретной ситуации эти коммиты - это S и M4.Так что Git вычисляет ID патча для этих двух коммитов, используя git patch-id.Он также вычисляет идентификатор патча для каждого из кандидатов для копирования - в данном случае F1 и F2.Если идентификаторы патчей любого из кандидатов на копирование совпадают с идентификаторами патчей в другой половине, Git приходит к выводу, что они, должно быть, уже были выбраны в <upstream> и должны быть опущены во время копирования.

Этот вывод обычно правильно, но в некоторых случаях это может быть неправильно! Это всегда хорошая идея для проверки результата любой из автоматизированных операций Git. Путь не так, например, если один коммит исправляет паразит } в строке сам по себе, и есть еще один заблудился } на отдельной строке в последующем ряду.Эти два исправления относятся к разным исходным строкам и могут иметь отличающиеся отступы , но для git patch-id они такое же изменение , что и идентификатор патчасоздается путем удаления номеров строк и некоторых пробелов.

0 голосов
/ 10 февраля 2019

Поскольку вы произвели слияние в сквош со своим первым PR, Git не знает, что D1 и D2 уже включены в master ветвь.

Вам необходимо rebase --onto ваша функцияпереход к (последнему) коммиту master branch:

git checkout feature
git rebase develop --onto master

Результат будет выглядеть следующим образом:

───M1───M2───M3───S────────────M4 (master)
    └────D1───D2──┘ (develop)   └────F1───F2 (feature)

Теперь вы можете push --force вашдобавьте ветку и обновите свой PR.Или закройте старый PR и создайте новый с текущей веткой объектов.

Кстати: по вашим названиям ветвей я предполагаю, что вы используете некую модель ветвления GitFlow.В этой модели вы не должны объединять ветвь development с master с помощью сквош-сквоша, потому что у вас всегда будет эта проблема с уже слитыми коммитами.

0 голосов
/ 10 февраля 2019

Попросите автора PR создать еще одну ветку последнего мастера (или ветку назначения в вашем репозитории)

Автор должен выбрать нужные коммиты и при необходимости сделать сквош и коммит

Автор отправляетсвежий PR

0 голосов
/ 10 февраля 2019

, пока изменения D1, D2 уже объединены с мастером, не будет проблем с их повторной отправкой через новый запрос на извлечение (D1, D2 не будут считаться изменениями)

Однако,в вашем случае вам необходимо перебазировать ваш Develop Branch с помощью M1 Commit, чтобы ваш PR обновлялся со всеми, а GIT обнаруживает F1 и F2 только как новые вещи.

  • или

Самый простой способ - просто создать PR от Master до Develop и объединить его после того, как вы извлечете обновления.

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