Добавить изменения в master в удаленную ветку

У меня следующая ситуация: у меня есть две удаленные ветки: origin/master, origin/featureX

После нескольких изменений в featureX было добавлено что-то фундаментальное в master, на котором будет основываться featureX.

поэтому дерево выглядит так:

m0 - m1 - m2(f) - m3      //<-- m2(f) is the fundamental change
  \- f1 - f2    - f3

теперь коротко сказал, что хочу изменить его в основном на это:

m0 - m1 - m2(f) - m3
               \- f1 - f2 - f3

Я посмотрел на переполнение стека и нашел ответы, которые давали следующие возможности:

  1. git мастер перебазирования

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

  1. функция слияния gitX

Это может быть вариант! Но M3 нужно было бы либо вообще не существовать, либо его нужно было бы включить в ветку функций. Тем не менее, тогда дерево будет выглядеть так:

m0 - m1 - m2(f) - m3
  \                 \
   - f1 - f2    - f3 - fm4

И я не знаю, правильно ли это, потому что это делает историю немного запутанной.

Будет ли git merge в этом случае действительно подходящим вариантом? Или есть лучший вариант?

РЕДАКТИРОВАТЬ: я мог бы поверить, что единственный вариант - создать новую (удаленную) ветку, начинающуюся с m2 (f), перебазировать ветку featureX в нее и удалить featureX. Но я хотел бы увидеть более красивое решение, если оно есть


person Apahdos    schedule 07.08.2019    source источник
comment
лично я не против переписать историю на фиче-ветке; Я бы не рекомендовал менять master историю. Вы пробовали git push --force в своей функциональной ветке после перезаписи истории? Если это действительно запрещено, то слияние будет вашим единственным вариантом.   -  person Chris Maes    schedule 07.08.2019
comment
К вашему сведению, только вчера я пытался объяснить различия между rebase и master и какой из них выбрать: stackoverflow.com/a/57378888/2082964   -  person Chris Maes    schedule 07.08.2019
comment
@ChrisMaes хорошо, я посмотрел на различия между слиянием и перебазированием за последние пару дней, и я думаю, что знаю это. И мне не очень нравится идея git push --force, поскольку люди злятся, если они работают над чем-то в ветке featureX, а я изменил историю, над которой они работают. Я понимаю, что git push --force определенно правильная идея, если я знаю, что раньше никто не тянул. Также сервер по-прежнему запрещает это в этой ситуации. Я пробовал ^^   -  person Apahdos    schedule 07.08.2019
comment
Я мог бы поверить, что единственный вариант - создать новую (удаленную) ветку, начинающуюся с m2 (f), перебазировав ветку featureX в нее и удалив featureX. Но хотелось бы более красивое решение   -  person Apahdos    schedule 07.08.2019


Ответы (1)


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

Мне нужно больше места на графике, поэтому давайте перерисуем это:

m0 - m1 - m2(f) - m3      //<-- m2(f) is the fundamental change
  \- f1 - f2    - f3

as:

       A--B--C--D   <-- origin/master
      /
...--*
      \
       E--F--G   <-- origin/featureX

где A — это то, что вы назвали m0, B — это то, что вы назвали m1, и так далее. Коммит * находится в обеих ветвях и представляет (один) хэш-идентификатор, который вы увидите, если запустите:

git merge-base --all origin/master origin/featureX

Обратите внимание, что хотя мы используем здесь имена удаленного отслеживания origin/master и origin/featureX для идентификации конкретных коммитов D и G, ветви состоят из коммитов до D, включая коммит * и все, что угодно. ранее — и коммиты до G, включая * и ранее, но не включая последовательность A-B-C-D. Git сам находит эти коммиты, начиная с концов и работая в обратном порядке: D , затем C, затем B, затем A, затем * и так далее для одной из ветвей, и G, затем F , затем E, затем * и так далее для другого.

Это ключ ко всему. название ветки определяет последнюю фиксацию в ветке. Git называет это концом ветки. Сама ветвь состоит из этого коммита, плюс родитель (родители) концевого коммита, плюс родители родителей, плюс родители тех, и так далее, вплоть до начала времени для репозитория Git. Итак, если бы у вас было имя ветки для коммита D — а вы могли бы; это может быть ваша собственная master — коммиты D, затем C, затем B, затем A и так далее будут веткой в ​​целом, а коммит D будет концом этой ветки. У вас есть имя, не относящееся к ветке — имя для удаленного отслеживания, origin/master — для фиксации D, так что, хотя это и не имя ветви, оно также подойдет.

Но вам даже не нужно название ветки! Выберите на мгновение коммит B по его хэш-идентификатору. Назовите его верхушкой этой безымянной ветки. Зафиксируйте B, затем A, затем * и так далее, все они образуют анонимную ветвь. Если бы у вас было имя ветки, идентифицирующее B, это имя было бы именем для этой ветки. Если бы у вас было два имени для B, эта анонимная ветвь была бы той же ветвью, что и ветви, образованные любым из этих двух имен. Каждая фиксация является или может быть верхушкой ветки. Вам редко нужно имя ветки, чтобы использовать фиксацию в качестве ветки. Основное исключение из этого правила — время, когда вам нужно имя, — это когда вы собираетесь использовать git fetch или git push для отправки как имени, и фиксации в какой-либо другой Git.1

Чтобы создать новую коммит слияния, объединяющий C (ваш m2(f)) и G (конец origin/featureX), нам понадобится новое имя локальной ветки, но это достаточно просто. :

git checkout -b new-name --no-track origin/featureX

или проще просто:

git checkout featureX

если у нас еще нет featureX и мы собираемся добавить нашу новую фиксацию в источник featureX, чтобы после git push origin/featureX идентифицировал эту новую фиксацию. Я предположу последнее и нарисую это:

       A--B--C--D   <-- origin/master
      /
...--*
      \
       E--F--G   <-- featureX (HEAD), origin/featureX

Теперь мы просто запускаем git merge и присваиваем ему хэш-идентификатор коммита C или что-то еще, что указывает коммит C:

git merge <hash>

or:

git merge origin/master^

or:

git merge origin/master~1

C является родителем D, поэтому любые имена master^, master^1, master~ и master~1 говорят Git найти C, начиная с D и возвращаясь к его первому (и единственному) родителю.

Теперь команда git merge будет:

  1. Найдите базу слияния: в нашем случае это коммит *.
  2. Запустите два git diff --find-renames, чтобы увидеть, что отличается от * до G — что мы изменили в featureX, а затем еще раз, чтобы увидеть, что отличается от * до C: что они изменили в безымянная ветвь, которая заканчивается коммитом master~1.
  3. Попытайтесь объединить эти изменения автоматически.
  4. Если на шаге 3 все прошло успешно, сделайте новую коммит слияния. Если нет, остановитесь на середине шага 3, оставив вас наводить порядок.

Предполагая, что все делает идет хорошо, вы получите следующее:

       A--B--C--D   <-- origin/master
      /       \
...--*         H   <-- featureX (HEAD)
      \       /
       E--F--G   <-- origin/featureX

и ваше слияние готово с git push по origin. Если ваш git push завершится успешно, ваш origin/featureX также будет идентифицировать коммит H, а featureX в репозитории Git по адресу origin, где бы он ни находился, будет идентифицировать коммит H.

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

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

...--o--o--o   <-- name1
      \
       A--B--C   <-- name2

У них — кем бы они ни были — также есть name2 идентифицирующая фиксация C в их репозитории. Обратите внимание, что в группе «они» наверное полтора десятка человек, у каждого из которых есть по одному репозиторию с именем name2. Все эти независимые name2 идентифицируют коммит C.

Теперь вы запускаете:

git checkout name2; git rebase name1

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

             A'-B'-C'   <-- name2
            /
...--o--o--o   <-- name1
      \
       A--B--C   [abandoned]

Затем вы можете использовать git push --force, чтобы заставить Git по происхождению забыть его A-B-C в пользу этого нового и улучшенного A'-B'-C'.

Это обновляет Git по адресу origin... но теперь все эти другие люди, может быть, полдюжины людей, на которых ссылались "они", все должны обновите свои репозитории Git, чтобы подобрать новую A'-B'-C' цепочку коммитов и сделать их имя name2 идентифицирующим коммит C' вместо C.

Если все они заранее согласились с тем, что имя name2 будет перемещаться таким образом, у них нет причин для жалоб. Если они не все согласились с этим, то они, вероятно, имеют право пожаловаться на вас, если вы сделаете это с ними. Прежде чем перемещать имена чужих веток без перемотки вперед, убедитесь, что они согласны с этим. Перебазирование и другие параметры «перезаписи истории» не являются быстрыми перемещениями вперед. Обычные операции git merge и git commit обычно приводят к быстрому перемещению вперед2.


1Даже с git push вам нужно имя только для одного конца процесса. Например, вы можете запустить:

git push origin <hash-id>:new-branch-name

чтобы создать ветку с именем new-branch-name на origin, используя хэш-идентификатор. Идентификатор хеша должен быть идентификатором какой-либо существующей фиксации в вашем собственном репозитории.

С git fetch вам часто нужно имя для передачи в их Git:

git fetch origin <name>

которое затем становится вашим origin/name именем для удаленного отслеживания. Однако, если их репозиторий Git настроен на разрешение хэш-идентификаторов, даже здесь вы можете использовать git fetch origin hash-id:name. Разрешение хэш-идентификаторов не по умолчанию; тот, кто управляет Git по адресу origin, должен явно git config установить параметр uploadpack.allowReachableSHA1InWant или uploadpack.allowAnySHA1InWant, чтобы включить это.

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

...--o--o--H   <-- name

в репозитории Git по адресу origin. Вы делаете клон и делаете свое собственное имя name для идентификации коммита H:

...--o--o--H   <-- name, origin/name

Кто-то еще тоже делает клон, поэтому у них точно такое же изображение своих ветвей.

Теперь вы делаете новый коммит, который мы можем назвать I. Но они также делают новый коммит, который мы можем назвать J. Вы и они оба пытаетесь git push сделать коммит origin. Давайте нарисуем коммиты, которые теперь равны origin, опустив имена:

             I
            /
...--o--o--H
            \
             J

Имя name на origin может указывать на либо I или J, но не на то и другое одновременно. Какой коммит он должен использовать? Какой бы из них вы ни выбрали, другой коммит не может быть найден в репозитории, потому что, чтобы найти коммит, вы начнете с имени и будете работать назад.

Если другой парень опередит вас и первым успешно подтолкнет свой коммит J, картина будет такой:

...--o--o--H--J   <-- name

в своем репозитории и в репозитории Git по адресу origin. (Ну, у него также есть origin/name для идентификации коммита J). Теперь вы можете git fetch origin получить это в ваш репозиторий:

             I   <-- name
            /
...--o--o--H--J   <-- origin/name

Теперь у вас есть два варианта: скопировать I в I', которое следует за J, затем забыть I — это git rebase– или объединить I с J и сделать так, чтобы ваше имя name определяло фиксацию слияния< /эм>. Эта перебазировка "безопасна", поскольку никто еще не зафиксировал I. Ваша попытка git push origin name не удалась: Git в источнике отклонил ее как не ускоренную перемотку вперед. Поскольку вы единственный человек, совершивший коммит I, в любых других репозиториях Git не может быть никаких других имен, имя которых идентифицирует коммит I. Вы можете заменить I новым и улучшенным I':

             I   [abandoned]
            /
...--o--o--H--J   <-- origin/name
               \
                I'  <-- name

Теперь вы можете git push origin name отправить им (происхождение) коммит I' и попросить их установить свое имя name для идентификации I', и они, вероятно, будут:

...--o--o--H--J--I'  <-- name

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

person torek    schedule 07.08.2019
comment
Ух ты! Спасибо за все эти усилия. Это дало мне более четкое представление о том, как работает rebase! Даже несмотря на то, что это не совсем дало мне ожидаемый ответ на мой вопрос, оно дало очень подробное описание того, когда использовать любой из двух вариантов, которые я указал в вопросе. Особенно для будущих читателей это может быть очень полезно! Спасибо - person Apahdos; 08.08.2019