Git перебазирует ветку, которая не содержит новых коммитов, никакой силы, необходимой для нажатия? - PullRequest
0 голосов
/ 13 января 2019

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

Я нахожусь здесь из-за довольно травмирующего опыта на работе: я перевел мастера в неправильную ветку и затем выполнил git push .. Который на мгновение, казалось, облажался довольно плохо. Теперь я пытаюсь понять, что пошло не так и почему мои изменения были перенесены, хотя я не использовал флаг --force.

Небольшая информация о нашей стратегии ветвления в git:

У нас есть более старые ветки «версии», в которых мы реализуем только исправления ошибок (ветка v1.0, ветка v2.0 и т. Д.), Потому что мы должны поддерживать эти старые версии. Затем есть основная ветвь, в которой мы фактически реализуем новые функции.

При обнаружении ошибки в старой версии программного обеспечения, например ветка v1.0, мы разветвляемся от этой версии, чтобы создать ветку функций для исправления. Затем мы перебазируем изменения в этой ветке функций поверх ветки v1.0 и затем выполняем слияние в ускоренном режиме на v1.0. Затем через коммит слияния мы объединяем v1.0 с v2.0 и, наконец, с master, чтобы ошибка исправлялась и во всех новых версиях продукта. Таким образом, процесс внесения исправления / изменения в ветку более старой версии (например, ветка v1.0) выглядит следующим образом:

  1. Ветка из v1.0 (создать ветку объектов)
  2. Внести некоторые изменения
  3. git rebase origin/v1.0, чтобы переместить изменения из функциональной ветви поверх последней ветки v1.0
  4. git push -f origin feature_branch
  5. ветвь функции быстрого слияния в v1.0
  6. Слияние v1.0 с v2.0 через слияние-коммит
  7. слияние v2.0 в master с помощью коммит-слияния
  8. Теперь все изменения (только исправления ошибок), сделанные в v1.0, также относятся к более новым версиям программного обеспечения

Так, например, для исправления в v1.0 программного обеспечения объединение происходит так:

FB -> v1.0 -> v2.0 -> master

Короче говоря : v1.0 является самой старой версией продукта, v2.0 будет содержать все коммиты с v1.0 плюс дополнительные коммиты с функциями, сделанные в версии v2.0, а master будет содержать все коммиты начиная с версии 2.0 плюс дополнительные функции, предназначенные для новой версии продукта.

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

Я работал над функцией, которая должна была входить только в основную ветку, поэтому, естественно, я попытался переназначить master в свою ветку, чтобы мои изменения превысили все остальные. Но вместо того, чтобы перебрасывать основную ветвь в мою ветвь функций, я был в ветке v1.0 и перебазировал основную ветку в ветку v1.0 (так, не моя ветвь функций) .. Это привело к перебазированию мастера в ветке v1.0. Что еще хуже, без тщательной проверки я затем также добавил ветку v1.0. Результат : ветка v1.0 теперь выглядела точно как master ... не очень хорошо.

Теперь мой вопрос: я просто выполнил ошибочную git push, я не --force push ветку v1.0. Насколько я понимаю, перебазирование заключается в том, что когда вы делаете перебазирование, вы переписываете историю ветки, поэтому вам нужно использовать git push -force, иначе пульт не примет ваши изменения. Не было ли в этом случае перезаписи истории, потому что master уже содержал все коммиты ветки v1.0 плюс кучу дополнительных коммитов, которых не было в ветке v1.0?

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

Ответы [ 2 ]

0 голосов
/ 13 января 2019

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


Люди привыкли думать, что "rebase" == "нужно принудительно толкать", и эти два в некотором роде связаны; но это не просто акт перебазирования, который создает необходимость форсировать толчок. Это удаление коммитов из истории ветки (скажем, BranchX) , а затем это только ветвь X, которую нужно принудительно нажать .

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

... O <--(origin/v1.0)(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

Здесь ... означает «некоторая история, которая нас не особо волнует», x означает «коммит, но не тот, на который я собираюсь специально ссылаться по имени в этом обсуждении», M означает « коммит слияния, но не тот, на который я собираюсь специально ссылаться по имени в этом обсуждении ". Другие буквы означают коммиты, на которые я мог бы ссылаться по имени. Если бы я мог ссылаться на слияние по имени, я назову это что-то вроде M1. Затем /, \ и -- показывают отношения родитель-потомок между коммитами (более новый коммит справа), а имя в паренах - это ссылка (например, ветвь) со стрелкой, которая показывает текущий коммит ссылки. .

В дополнение к локальным филиалам я показал ссылки на удаленное отслеживание, т. Е. Понимание вашего репо того, где находятся филиалы на пульте.

Итак ...

Предполагаемое поведение

1) Ветка от v1.0

... O <--(origin/v1.0)(v1.0)(feature_branch)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

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

2) Внести некоторые изменения

      A <--(feature_branch)
     /
... O <--(origin/v1.0)(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

3) git rebase origin/v1.0

Этот шаг кажется немного пессимистичным. Есть ли у вас частые, одновременные изменения в старых версиях вашего продукта? Если нет, то я рассмотрю это только в качестве шага обработки исключений для случаев, когда на самом деле появляются новые изменения в v1.0. Приведенный выше график не изменится, но если предположить, что произошли промежуточные изменения

      A <--(feature_branch)
     /
    | B <--(origin/v1.0)
    |/
... O <--(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

тогда этот шаг даст вам

        A' <--(feature_branch)
       /
      B <--(origin/v1.0)
     /
    | A
    |/
... O <--(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

Обратите внимание, что feature_branch "перемещен", так что A был удален из его истории, в то время как B и новый коммит (A' - копия A) с исправлениями была добавлена ​​к его истории. история.

Я все еще показываю A на картинке, потому что он все еще существует, хотя на данный момент ничто не ссылается на него. (Но об этом я еще скоро расскажу ...) Это усиливает важный момент, который часто неправильно понимают: rebase не «двигался» A. Он создал новый коммит A', который отличается от A. Вот почему я говорю, что A был удален из истории feature_branch.

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

4) git push -f origin feature_branch

Это немного сбивает с толку, потому что вы не показываете, что вы ранее нажали feature_branch. Если вы этого не сделали, тогда флаг -f не нужен - потому что, даже если вы удалили коммиты из локальной истории feature_branch, пульт дистанционного управления об этом ничего не знает.

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

Итак, давайте предположим, что перед тем, как вас обмануть, у было нажато feature_branch, и диаграмма действительно выглядит как

        A' <--(feature_branch)
       /
      B <--(origin/v1.0)
     /
    | A <--(origin/feature_branch)
    |/
... O <--(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

(Это реальная причина, по которой я сохранил A на диаграмме.) Теперь вы не сможете нажать feature_branch без флага -f, потому что нажатие удалит A с пульта Понимание истории feature_branch.

Но сейчас самое время упомянуть ... основываясь на моих комментариях по поводу шага 3, вам следует опасаться рабочего процесса, в котором принудительное нажатие является обычным шагом. Так же, как пульт ДУ знает A как часть feature_branch и должен быть уведомлен, что история была отредактирована, если у любого другого разработчика есть fetch ed или pull ed feature-branch, то принудительное нажатие сделает их репо в сломанном состоянии. Им придется восстанавливаться, особенно если они внесли дополнительные изменения в feature-branch; и если они сделают это неправильно, это может отменить ребаз.

Тем не менее, картинка после push будет

        A' <--(feature_branch)(origin/feature_branch)
       /
      B <--(origin/v1.0)
     /
... O <--(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

(На этот раз я удалил A, потому что мы уже беспокоились об этом. Он все еще там, все еще доступен в reflog, но в конечном итоге gc уничтожит его, если вы не предпримете шаги, чтобы воскресить его.)

5) ускоренное слияние feature_branch в v1.0

Предположительно, вы также хотите нажать v1.0 после ускоренной перемотки вперед. Поскольку это быстрая перемотка вперед (даже для пульта дистанционного управления), принудительное нажатие не требуется; то есть каждый коммит, который удаленный когда-либо видел как часть v1.0, все еще является частью v1.0.

... O -- B -- A' <--(v1.0)(origin/v1.0)(feature_branch)(origin/feature_branch)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

5 и 6) объединить вперед

Это просто, и снова толчки не нужно заставлять.

... O ----- B ---- A' <--(v1.0)(origin/v1.0)(feature_branch)(origin/feature_branch)
     \              \
   .. M -- x ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- x -- M <--(origin/master)(master)

Хорошо. Теперь, насколько я понимаю, проблема возникла, когда вы создали функцию из master. На данный момент я собираюсь добавить отдельные имена к паре x коммитов и удалить некоторые ссылки, о которых мы не будем говорить

... O ----- B ---- A' <--(v1.0)(origin/v1.0)
     \              \
   .. M -- V ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- W -- M <--(origin/master)(master)

так что после шагов 1 и 2 у вас есть

... O ----- B ---- A' <--(v1.0)(origin/v1.0)
     \              \
   .. M -- V ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- W -- M <--(origin/master)(master)
                        \
                         C -- D <--(feature2)

Но затем вы начали работать через описанный выше рабочий процесс, как если бы это была v1.0 функция. Так что для шага 3

git rebase origin/v1.0

И, как вы знаете, это будет проблемой. Все в истории текущей ветки, а не в истории origni/v1.0, рассматривается как «необходимость копирования».

                     V' -- W' -- C' -- D' <--(feature)
                    /
... O ----- B ---- A' <--(v1.0)(origin/v1.0)
     \              \
   .. M -- V ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- W -- M <--(origin/master)(master)
                        \
                         C -- D

Коммиты слияния игнорируются (поведение по умолчанию rebase; в любом случае они не должны вносить четких изменений, хотя разрешение конфликтов и / или "злые слияния" могут нарушить это предположение). Но V и W не игнорируются. Как всегда, обратите внимание, что V и W остаются, а истории каждой ветви , за исключением текущей ветви, которую вы перебазировали, остаются без изменений.

Как и в приведенном выше рабочем процессе, теперь вы можете нажать feature. И, как указано выше, если бы у вас когда-либо было push ed feature до ребазинга, вам теперь пришлось бы принудительно толкнуть его ... , но ваш типичный рабочий процесс обманул вас в ожидании, что в любом случае , поэтому, пока он поднимает красный флаг, он не будет.

В любом случае, вы можете видеть, что v1.0 с радостью перенесется на feature (потому что история feature в любом случае включает всю историю v1.0), и это означает, что v1.0 будет толкать без силы.

Так вот как все пошло не так, но что делать дальше?

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

... O ----- B ---- A' <--(v1.0)(origin/v1.0)
     \              \
   .. M -- V ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- W -- M <--(origin/master)(master)
                        \
                         C -- D <--(feature2)

но потом ошибочно думаю, что вам нужно перейти на v1.0, слияние будет работать так же тихо, а результат будет таким же неправильным.

                     -------------------------------- M <--(v1.0)
                    /                                /
... O ----- B ---- A' <--(origin/v1.0)              |
     \              \                               |
   .. M -- V ------- M <--(origin/v2.0)(v2.0)       |
            \         \                             |
         ... M -- W -- M <--(origin/master)(master) |
                        \                          /
                         C ---------------------- D <--(feature2)

Это все еще включает все v2.0 и master изменения в v1.0.

Так что вы можете сделать?

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

1) создать ветку из v1.0 2) внести изменения 3) перемотка вперед v1.0 на feature (git merge --ff-only feature) 4) попытка толкнуть v1.0 без силы

Теперь, если вы попытаетесь включить ваши изменения в неправильную ветку, есть вероятность, что слияние не удастся (из-за --ff-only). Это поможет, только если ветви действительно разошлись; но это, по крайней мере, не хуже, чем статус-кво.

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

git pull --rebase

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

Тогда вам всегда следует по привычке быстро взглянуть на локальный результат, прежде чем нажимать - потому что проблемы всегда легче устранить, прежде чем их подтолкнуть. Посмотрите на журнал и, если что-то выглядит странно, изучите его. Вернитесь к требованию / истории / карточке / тому, что вам было сказано, и подтвердите, к какой ветви вы добавляете это. Может быть, визуализировать общее состояние репо с помощью таких инструментов, как gitk.

Итог, git очень гибкий и очень мощный, поэтому, если вы явно скажете ему сделать что-то не то, он, вероятно, сделает это. Это означает, что вы должны знать, что сказать, чтобы сделать это. Хорошей новостью является то, что обычно не 1213 * слишком трудно исправить после ошибок. Проще всего до того, как ошибка будет push ed, но более или менее всегда есть выход.

0 голосов
/ 13 января 2019

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

Таким образом, вы не изменили существующую историю v1.0, вы просто добавили ее. Не требуется принудительное нажатие.

Лучшее предложение о том, как предотвратить это в будущем, состоит в том, чтобы избежать перебазирования. Ветка от v1.0 для создания исправления. Вы уже используете объединение, чтобы внести изменения в master.

...