Я считаю, что вы хотите, чтобы git merge --squash
:
git checkout master
git merge --squash otherbranch
[resolve any conflicts if necessary, then]
git diff --cached # to see what you're about to commit
git commit # to make a single non-merge commit on master
Больше
Разница не является снимком, поэтому здесь есть существенное несоответствие. Позвольте мне попробовать старую аналогию температуры. Предположим, я говорю вам, что сегодня здесь на десять градусов теплее, чем вчера. (Хотя на самом деле это примерно то же самое :-)) Теперь у меня к вам вопрос: какая сегодня температура? Или, что эквивалентно, какая температура была вчера?
A снимок - это абсолют. Если я скажу вам, что это 68˚F / 20˚ C, и либо , что это на 10˚F теплее, чем вчера, или что вчера было 58˚F, то эти two частей информации достаточно. Недостаточно одного снимка или различий: вам нужно два снимка или один снимок и различие. (Обратите внимание, что бесконечного числа различий никогда не бывает достаточно: вам всегда нужен хотя бы один снимок в последовательности где-то . В противном случае может быть что-то такое, каким оно было в начале, и никогда не менялось, и вы этого вообще не увидите.)
Я думаю, что вы описываете ситуацию, подобную этой:
C--D--E--F--G <-- branch
/
....--A--B--H <-- master
или даже просто:
C--D--E--F--G <-- branch
/
....--A--B <-- master
В обеих этих ситуациях вы можете:
- сравнить снимок в
B
с снимком в G
, что дает вам набор изменений для преобразования B
до G
; и затем - применяет те же самые изменения к снимку в
H
, если он существует, или к снимку в G
, если H
не существует.
Теперь, если H
вообще не существует, то такая вещь, как «применить изменения», совершенно тривиальна. Результатом является снимок в G
. В этом случае вы можете просто скопировать снимок G в новый снимок - давайте назовем его G'
вместо H
, чтобы напомнить нам, что он точно соответствует G
- используя некоторую гипотетическую команду Git 1 это делает именно это. Результат будет:
C--D--E--F--G <-- branch
/
....--A--B--G' <-- master (HEAD)
Но если H
существует , вы, вероятно, захотите объединить изменения B
-vs- G
с параметрами B
-vs- H
, чтобы вы не стирали эффект от изменений, которые вы внесли, сделав H
в первую очередь. Команда, объединяющая эти изменения: git merge
.
A normal и full merge, которые вы получите с помощью git checkout master; git merge branch
, будет:
- найти базу слияния, которая в данном случае является коммитом
B
; - diff для слияния базы слияния с текущей подсказкой ветви
H
, чтобы найти "наши" изменения; - diff тот же самый базовый коммит против "их" (все еще наш, но
--theirs
во время слияния) коммит G
, чтобы найти "их" изменения; и - попытка объединить эти изменения.
Если возникают конфликты слияния, это обычное слияние прекращается и вы завершаете sh работу. Если нет, то это обычное слияние делает новый коммит с двумя родителями:
C--D--E--F--G <-- branch
/ \
....--A--B--H------------M <-- master (HEAD)
Мы пропустим концепцию того, что Git называет ускоренным слиянием вперед (что вовсе не является слиянием) и go прямо к тому, что делает git merge --squash
.
Когда вы добавляете опцию --squash
, вы говорите Git сделать две вещи:
- Остановитесь после шага объединения, даже если объединение работает. (Вы можете получить тот же эффект без элемента # 2, используя
git merge --no-commit
. Если обычное слияние остановится с конфликтом, то это объединение sh также прекращается с конфликтом, так что это точно такой же, как флаг --no-commit
.) Когда вы, пользователь, fini sh, слияние, запустив git commit
, Git, сделает не коммит слияния M
, а вместо сква sh коммит S
:
C--D--E--F--G <-- branch
/
....--A--B--H------------S <-- master (HEAD)
Ключевое различие между M
и S
в том, что S
указывает на коммит H
только , а не совершать G
вообще. (Текст сообщения о фиксации по умолчанию для S
также отличается, но, поскольку вы получаете возможность заменить этот текст, когда git commit
открывает ваш редактор в сообщении о фиксации, эта разница на самом деле не важна.)
Если commit H
не существует, операция git merge --squash
по-прежнему выполняет точно такой же процесс. База слияния по-прежнему фиксируется B
:
C--D--E--F--G <-- branch
/
....--A--B <-- master (HEAD)
, поэтому две разности:
B
против B
, чтобы увидеть, что мы изменили: ничего, конечно; и B
против G
, чтобы увидеть, что они изменили: все, что нужно, чтобы следующий снимок выглядел как коммит G
, конечно.
Путем объединения эти два сравнения и создание нового коммита S
, мы получаем коммит, соответствующий G
, который мы можем назвать G'
, если захотим, но я все равно позвоню S
:
C--D--E--F--G <-- branch
/
....--A--B--S <-- master (HEAD)
Обратите внимание, что после использования git merge --squash
ветвь branch
почти наверняка должна быть полностью уничтожена. Вы не должны go возвращаться к нему и делать больше коммитов , если только вы не уберете коммит S
с master
.
(Вы можете удивиться, почему git merge --squash
останавливается первым. Единственным разумным ответом является то, что это был исторический несчастный случай, поведение которого сохранилось с тех пор. Если оно не остановилось по умолчанию, вы можете запустить git merge --squash --no-commit
всякий раз, когда вы действительно хотите, чтобы это прекратилось. Но первое Реализация squa sh -merge работала так, что git merge
рано выходил, как раз перед тем, как запустить собственный внутренний git commit
. Так было проще кодировать. Конфликтующие случаи слияния и --no-commit
делают еще один шаг, который должен записывать, что происходит слияние во внутреннем файле состояния; , затем они выходят. Когда вы запускаете git commit
, Git либо замечает файл состояния "слияние в процессе", либо не , в зависимости от того, создал ли git merge
. Затем он либо делает обычную фиксацию без слияния, потому что не было записанного состояния, либо коммит слияния, потому что было записано состояние слияния.)
1 Существует несколько команд Git, которые могут достичь этого результата. Нет единственной команды, которая делает это за один шаг: для этого требуется как минимум две команды Git. Это верно даже с git merge --squash
.