Вы можете — я не буду говорить должен, потому что для чего-то подобного я обычно предпочитаю подход с перебазированием — просто слить с нужным вам коммитом.
Мне нужно больше места на графике, поэтому давайте перерисуем это:
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
будет:
- Найдите базу слияния: в нашем случае это коммит
*
.
- Запустите два
git diff --find-rename
s, чтобы увидеть, что отличается от *
до G
— что мы изменили в featureX
, а затем еще раз, чтобы увидеть, что отличается от *
до C
: что они изменили в безымянная ветвь, которая заканчивается коммитом master~1
.
- Попытайтесь объединить эти изменения автоматически.
- Если на шаге 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
master
историю. Вы пробовалиgit push --force
в своей функциональной ветке после перезаписи истории? Если это действительно запрещено, то слияние будет вашим единственным вариантом. - person Chris Maes   schedule 07.08.2019