Возьмите текущий diff из master и превратите его в один коммит, не применяя каждый коммит поверх друг друга - PullRequest
0 голосов
/ 25 февраля 2020

Я хотел бы взять некоторую ветвь, X-коммиты перед мастером, и в основном сделать снимок текущего diff и создать из него один коммит.

Обычно я использую git rebase -i, squa sh все коммиты, кроме самого последнего, и это работает.

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

Я сделал это, вручную расширив всю ветвь и мастер, создав mychanges.diff, затем применив это к чистой ветке, но кажется, что в git должен быть собственный, чистый способ не перебазировать, применяя каждый коммит, а точнее снимок текущего состояния и создание отдельного коммита отличий от мастера.

1 Ответ

1 голос
/ 25 февраля 2020

Я считаю, что вы хотите, чтобы 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 сделать две вещи:

  1. Остановитесь после шага объединения, даже если объединение работает. (Вы можете получить тот же эффект без элемента # 2, используя git merge --no-commit. Если обычное слияние остановится с конфликтом, то это объединение sh также прекращается с конфликтом, так что это точно такой же, как флаг --no-commit.)
  2. Когда вы, пользователь, 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...