Перебазировать ветку после внесения изменений? - PullRequest
0 голосов
/ 22 апреля 2020

Можно ли выполнить перебазирование в исправленные коммиты и git автоматически пропустить старые версии коммитов?

Мотивация: Gerrit patch-sets

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

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

Пример

Допустим, Мне нужно сделать sh коммит D для проверки, но я уже внес дополнительные изменения, которые пока не подходят для отправки.

A---B---C(origin/master)---D---E---F(devel)

>>> git push origin <hash-of-D>:refs/for/master

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

Для простых изменений я могу просто перебросить ветку devel в интерактивном режиме

>>> git rebase origin/master devel
edit D
pick E
pick F

A---B---C(origin/master)---D'---E---F(devel)

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

>>> git checkout -b amend <hash-of-D>
>>> ### Make some changes
>>> git commit --all --amend

A---B---C(origin/master)---D'(amend)
        |
        '---D---E---F(devel)

>>> git push origin HEAD:refs/for/master

Теперь мне нужно перебазировать, но, поскольку D и D 'перекрываются, автоматическое слияние c может завершиться неудачей или отменить изменения, сделанные из D в D'. На данный момент возможны решения для вида:

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

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

1 Ответ

1 голос
/ 22 апреля 2020

Используйте git rebase --onto. Это все еще немного сложно, но вам не нужно быть интерактивным и конкретно указывать Git drop конкретные коммиты: вы выбираете через командную строку , какие коммиты копируются, а какие нет.

На самом деле, вы делаете это все время, потому что git rebase в основном работает путем копирования некоторого набора коммитов. То есть git rebase имеет три или четыре фазы, в зависимости от того, как вы считаете:

  1. Перечислите коммиты ha sh ID коммитов для копирования. Они становятся pick командами, когда вы используете интерактивное изменение стиля, так что вы на самом деле уже знаете, как работает эта часть.

  2. Используйте git checkout --detach (или эквивалентный) для go в отсоединенный режим HEAD при конкретном целевом коммите. Целевой коммит зависит от вас: вы сообщаете Git через командную строку, что коммит для проверки здесь.

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

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

Если я могу немного перерисовать ваш пример, вы на самом деле начинаете с этого:

A--B--C   <-- origin/master
       \
        D    [refs/for/master in the Gerrit repo at `origin`]
         \
          E--F   <-- devel

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

        D'  [whatever name you like can go here]
       /
A--B--C   <-- origin/master
       \
        D   [refs/for/master in the Gerrit repo at `origin`]
         \
          E--F   <-- devel

Чтобы скопировать E-F в автоматическом режиме, вам нужен способ назвать коммит D. Его фактический идентификатор ha sh всегда будет работать, но идентификаторы ha sh большие, уродливые и раздражающие. Это работает намного лучше, если вы вставите в свой собственный репозиторий имя, подойдет любой тип имени, которое вы сможете запомнить.

Доступны следующие "виды" имен:

  • имена ветвей: git branch создает и удаляет их;
  • имена тегов: git tag создает и удаляет их; и
  • любое другое название вашего изобретения: это (легкая) боль в области тела, так как вы должны использовать их полные имена, включая префиксы, такие как refs/for/ или refs/xyzzy/. Пространство имен Gerrit refs/for/ является одним из этих изобретений: оно принадлежит не вам, а Gerrit, но это всего лишь целая категория, в которой каждый может прикреплять имена, и если каждый оставляет refs/for/ Gerrit и изобретает свои собственные личные вещи что не refs/for/, они не будут сталкиваться.

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

Итак, Предположим, у вас есть:

        D'  <-- in-review/master/2
       /
A--B--C   <-- origin/master
       \
        D   <-- in-review/master/1
         \
          E--F   <-- devel

, где in-review/master/<em>number</em> - ваш личный способ запомнить Я отправил этот коммит с помощью git push origin refs/for/master. Так как вы сделали это дважды, у нас есть два разных числа. (Я только что изобрел эту систему именования для этого ответа, так что это может быть ужасно. Выберите ту, которая подходит для you .)

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

git checkout devel
git rebase -i origin/master

коммитов, которые git rebase перечисляет для копирования: D-E-F.

Это потому, что он фактически перечисляет F-E-D-C-B-A - каждый коммит, который можно найти, запустив в F коммит, названный через devel и работающий в обратном направлении. Затем отдельно перечисляются C-B-A: каждый коммит, который можно найти, начиная с C, коммит с именем origin/master и работая в обратном направлении. Он выбивает любой коммит в списке second из списка first , оставляя F-E-D, который затем возвращается к необходимому порядку выбора вишни.

Список коммитов:

  • те, которые достижимы из текущей ветви (devel), минус
  • те, которые достижимы из upstream аргумента, который вы даете git rebase: origin/master, в данном случае .

На этом завершается шаг 1. (На самом деле все сложнее: больше коммитов можно выбить из списка. По умолчанию коммиты слияния выбрасываются автоматически. Дополнительные коммиты могут быть отбрасывается с помощью сопоставления идентификатора патча и режима перебазировки. Но давайте просто проигнорируем все это здесь.)

Аргумент upstream также предоставляет целевой коммит, который Git На шаге 2 будет использоваться git checkout, отсоединяющий HEAD.

Если бы вы могли просто сказать Git:

  • , используйте commit C в качестве цели ...
  • , но используйте commit D в качестве конца списка коммитов, чтобы выбить

, которые бы выполняли эту работу, без необходимости использовать git rebase -i и ручное редактирование. Оказывается, это легко сделать:

git rebase --onto in-review/master/2 in-review/master/1

Аргумент --onto отделяет часть target от upstream, освобождая upstream аргумент, означающий просто , обязывает не копировать .

Именно поэтому мы дали интересные коммиты, указывающие c имена. В ваш более сложный сценарий, вы начнете с:

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

В этом случае мы будем иметь:

...--G--H   <-- origin/master
         \
          I--J--K   <-- in-review/master/1
                 \
                  L   <-- feature/xyz

Если для коммита J необходимо внести изменения, вы проверяете коммит K и присваиваете ему новое имя ветки in-review/master/2:

git checkout -b in-review/master/2 in-review/master/1

, что дает вам это :

...--G--H   <-- origin/master
         \
          I--J--K   <-- in-review/master/1, in-review/master/2 (HEAD)
                 \
                  L   <-- feature/xyz

Теперь вы можете запустить git rebase -i origin/master и изменить второй коммит на edit. Когда перебазирование закончено, вы можете - в зависимости от того, решили ли вы также редактировать I и / или использовали --force - иметь:

          I'-J'-K'  <-- in-review/master/2 (HEAD)
         /
...--G--H   <-- origin/master
         \
          I--J--K   <-- in-review/master/1
                 \
                  L   <-- feature/xyz

или:

...--G--H   <-- origin/master
         \
          I--J'--K'   <-- in-review/master/2
           \
            J--K   <-- in-review/master/1
                \
                 L   <-- feature/xyz

Теперь вы можете git checkout feature/xyz; git rebase --onto in-review/master/2 in-review/master/1, точно так же, как и раньше.

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

...--G--H   <-- origin/master
         \
          I   <-- in-review/master/1
           \
            J    <-- in-review/feature/tall/1
             \
              K   <-- feature/short
               \
                L   <-- feature/long

Возможно, вам придется что-то делать с любым из промежуточных коммитов. Поскольку любое изменение происхождения и снимка любого коммита приводит к его копированию, если вы вынуждены изменить коммит I на новый I', вы должны придумать новый J' и K' и L' (и представить новый отзыв для J', предположительно).

Обратите внимание, что после копирования I в I' одна git checkout feature/long; git rebase --onto in-review/master/2 in-review/master/1 копирует J-K-L в J'-K'-L', но теперь есть три метки для перемещения. Это недостающий инструмент, который перемещает более одной метки. Но эта картинка слишком проста, как вы могли бы иметь:

...--G--H   <-- origin/master
         \
          I   <-- in-review/master/1
           \
            J    <-- in-review/feature/tall/1
             \
              K--L   <-- feature/short
               \
                M   <-- feature/long

, и теперь перебазирование feature/long само по себе не будет работать, поскольку оно не будет копировать L; и не будет перебазировать feature/short в одиночку, поскольку это скопирует L, но не M. Таким образом, инструмент multi-rebase должен знать:

  • , какие ветви интересны, и
  • , где перебазировать их как группу

, и он должен затем выясните, кто совершает копирование, создайте отображение из старого коммита ha sh в новый, пока группа как целое не будет полностью скопировано, и только затем переместите все имена ветвей в свой новый коммит ha sh Идентификаторы. Режим сохранения слияния (а-ля Git git rebase --rebase-merges) также будет правильным режимом по умолчанию для этого инструмента, так как здесь у нескольких ветвей могут быть шаблоны ветвления и слияния внутри их подграфов (ветвление и слияние друг с другом, или независимо друг от друга, или с обоими). ​​

Новый код rebase-merges является большей частью пути к этому необходимому инструменту, но в нем все еще отсутствует метод указания более чем одного имени ветви ( и, следовательно, по крайней мере, потенциально, несколько коммитов-подсказок) и код, который понадобится для настройки имен нескольких ветвей в конце всего процесса.

...