Git не хранит изменения.Git хранит снимки.
Думайте об этом как о дневных высоких температурах.Предположим, я говорю вам только , что сегодня на несколько градусов теплее, чем вчера.Можете ли вы сказать мне, какая температура была вчера и сегодня?Если нет, то почему?Если я скажу вам только, что сегодня было 50˚F, было ли сегодня теплее, чем на прошлой неделе?Если вы не можете сказать мне, почему нет?Какая информация позволит вам предоставить абсолютных температур на каждый день, если вам дают только различия?Или, если вам дают только абсолютные температуры каждый день, как бы вы представили разницу между любыми двумя данными днями?
Подумав об этом, вы должны иметь четкое представление об «изменении» (дельта или разница:сегодня теплее, чем вчера, который мы вычитаем, вычитая два снимка) против «снимка» (вчера было X градусов, а сегодня Y, что позволяет нам получить снимок, только если у нас есть стартовый снимок для добавить это к), и как перейти от одного к другому.Так что теперь следующая часть будет иметь смысл: Чтобы перейти от снимков к дельтам, вы должны дать Git два конкретных снимка.
Итак, теперь давайте вернемся к вашей формулировке:
Дорогой мерзавец, у меня есть некоторые изменения в feature-branch-a
...
Чтобы иметь изменений , вы должны выбрать два снимка.Какие два снимка вы выбираете?
... которые я хочу добавить к feature-branch-b
После того, как вы выбрали два снимка в функциональной ветви A, выможет заставить Git превратить их в изменения (различия) и применить их к снимку в конце ветви B. Это не так уж сложно.Но вы бы хотели сделать это автоматически , и вот тут вы столкнетесь с проблемами:
... Суть в том, что feature-branch-a
является исконной ветвью feature-branch-b
.
Ветви не являются предками ветвей, не являются детьми и не имеют таких отношений.Или, ну, может быть, они и делают: проблема здесь в том, что мы не определили, что мы, или вы , обозначаем как «ветвь».См. Что именно мы подразумеваем под "ветвью"?
В Git ветвь name , как и feature-branch-a
, просто определяет один конкретный коммит, тем самымуникальный хэш-идентификатор коммита. фиксирует имеет отношения предок / потомок, и на основе вашего описания и ошибки при выполнении git merge
мы можем нарисовать эти отношения:
...--o--o--A <-- feature-branch-a
\
o--o--...--B <-- feature-branch-B
Я выбрал два особенно интересныхкоммиты здесь: commit A
, чьи предки слева от него - безымянные круглые o
узлы - и commit B
, чьи предки являются коммитами слева от B
, который включает в себя commit A
.
То есть снимки, содержащиеся в feature-branch-A
, представляют собой A
, а коммиты, оставшиеся от A
.Моментальные снимки, содержащиеся в feature-branch-B
, представляют собой B
, а оставшиеся - , включая A
и оставшиеся A
. Каждый коммит "в" или "в" ветви A уже находится в / в ветви B.
Если вы запустите:
git checkout feature-branch-b
git merge feature-branch-a
путь git merge
Чтобы выяснить, какие изменения нужно объединить, нужно найти лучшего общего предка между кончиком текущей ветви, т.е. коммитом B
, и кончиком указанной вами ветви, т.е. коммитом A
. лучший предок - тот, которого можно достичь из обоих коммитов , но в некотором неопределенном, но очевидном смысле ближайших к ним также.
Если вместо этого выглядело так:
o--...--A <-- branch-A
/
...--o--*
\
o--...--B <-- branch-B
, то база слияния будет коммитом с пометкой: она не только на обеих ветвях, она также близка к вамможет одновременно достигать A
и B
, в то время как является на обеих ветвях.(Коммиты слева от *
также находятся на обеих ветвях, но находятся «еще дальше».)
Итак,Git может объединить их самостоятельно, сравнив - git diff
-ing- *
против A
, чтобы увидеть, что они сделали, и *
-vs- B
чтобы увидеть, что вы сделали.Git может затем объединить свои улучшения - *
-vs- A
- с вашими, *
-vs-B.Применение комбинированных изменений к *
дает вам снимок с обоими наборами улучшений, объединяя обе линии работы во что-то, что готово зафиксировать как коммит слияния (немного особенныйвид коммита: тот, у которого два родителя вместо одного, но в остальном обычный снимок, как и при любом другом коммите).
Но с учетом вашей настройки:
...--o--o--A <-- feature-branch-a
\
o--o--...--B <-- feature-branch-B (HEAD)
Git обнаружит, что основой слияния является сам коммит A
.Это на обеих ветках, и как можно ближе к A
и как можно ближе к B
.Так что Git будет отличаться A
против A
и обнаружит, что нечего объединять!
Это явно не то, что вы хотите.Но объединять нечего: кто бы ни сделал B
, он начал с A
и с тех пор добавил улучшений .Это все, что есть.Если вы хотите, чтобы Git удалил этих улучшений, вы можете сделать это, но не с git merge
.
Если какой-то коммит, где-то между A
и B
, будет не В конце концов, это улучшение, вы можете сказать Git об этом:
...--o--o--A <-- feature-branch-a
\
o--X--...--B <-- feature-branch-B (HEAD)
Если коммит X
ухудшает ситуацию, вы можете запустить git revert <hash-of-X>
.Git превратит X
в набор изменений - сравнивая его с родительским коммитом, расположенным слева от него на чертеже, - и попытается «не изменить» все в текущем коммите вотменить именно то, что было сделано в X
.Это может быть успешным само по себе или может не получиться при конфликте слияния.(Внутри Git действительно делает git merge
, используя X
в качестве общей отправной точки, o
слева от него в качестве другого наконечника ветвления и B
в качестве вашего собственного наконечника ветвления. Это своего родасбивает с толку: может быть проще думать о нем, как о Git, просто «обращающем изменения». Слияние технически лучше, потому что оно обрабатывает некоторые более сложные случаи, но обычно это одно и то же.)
В конечном счете, тем не менее,ключом к пониманию этого является осознание того, что слияние не означает «сделай то же самое».Это означает, что объединяет изменения, начиная с общей начальной точки .Если у вас нет общей начальной точки, это сложно - современный Git не использует свой «режим угадывания», если вы не добавите --allow-unrelated-histories
- и если начальной точкой является один из двух коммитов, объединять нечего.