Как вставить фиксацию между двумя произвольными фиксациями в прошлом?

127

Предположим, у меня есть следующая история коммитов в моей локальной ветке:

A -- B -- C

Как мне вставить новую фиксацию между Aи B?

BartoszKP
источник
3
совершить под названием Ą ? :)
Antek
У меня очень похожий вопрос, как вставить новую версию в прошлое вместо фиксации.
Pavel P

Ответы:

178

Это даже проще, чем в ответе OP.

  1. git rebase -i <any earlier commit>, Это отображает список коммитов в настроенном текстовом редакторе.
  2. Найдите коммит, который вы хотите вставить после (допустим, это a1b2c3d). В редакторе измените эту строку pickна edit.
  3. Начните перебазирование, закрыв текстовый редактор (сохраните изменения). Это оставляет вас в командной строке с фиксацией, которую вы выбрали ранее ( a1b2c3d), как если бы она была только что зафиксирована .
  4. Внесите свои изменения и git commit( НЕ исправляя, в отличие от большинства edits). Это создает новый коммит после того, который вы выбрали.
  5. git rebase --continue, Это воспроизводит последовательные коммиты, оставляя ваш новый коммит вставленным в правильное место.

Помните, что это перепишет историю и сломает любого, кто попытается вытащить.

SLaks
источник
1
Это добавило новую фиксацию после фиксации, которая находится после той, на которую я выполнял перезагрузку (также последней фиксации), а не сразу после той, на которую я выполнял перезагрузку. Результат был таким же, как если бы я просто сделал новый коммит в конце с изменениями, которые я хотел вставить. Моя история стала A -- B -- C -- Dвместо желанной A -- D -- B -- C.
XedinUnknown
2
@XedinUnknown: Значит, вы неправильно использовали Rebase.
SLaks
3
Теперь Dкоммит может быть где угодно. Предположим, у нас есть A - B - Cи у нас есть коммит, Dкоторого даже нет в этой ветке. Мы знаем его SHA, однако можем git rebase -i HEAD~3. Теперь между Aи B pickлиниями, мы вставляем новую pick линию , которая говорит pick SHA, что дает хэш желаемого D. Это не обязательно должен быть полный хэш, только сокращенный. git rebase -iпросто вишня выбирает все коммиты, перечисленные pickстроками в буфере; они не обязательно должны быть оригинальными, перечисленными для вас.
Kaz
1
@Kaz Это похоже на другой правильный ответ.
BartoszKP
3
Еще проще, вы можете использовать breakключевое слово в редакторе в отдельной строке между двумя фиксациями (или в первой строке, чтобы вставить фиксацию перед указанной фиксацией).
SimonT
30

Оказывается, довольно просто, ответ нашел здесь . Предположим, вы на ветке branch. Выполните следующие шаги:

  • создайте временную ветвь из фиксации после того, как вы захотите вставить новую фиксацию (в данном случае фиксацию A):

    git checkout -b temp A
    
  • выполнить изменения и зафиксировать их, создав коммит, назовем его N:

    git commit -a -m "Message"
    

    (или git addсопровождаемый git commit)

  • перебазируйте коммиты, которые вы хотите иметь после нового коммита (в данном случае коммитов Bи C) на новый коммит:

    git rebase temp branch
    

(возможно , вам нужно использовать , -pчтобы сохранить слияния, если таковые были - благодаря более уже не существующий комментарий по ciekawy )

  • удалите временную ветку:

    git branch -d temp
    

После этого история выглядит следующим образом:

A -- N -- B -- C

Конечно, возможны конфликты при перебазировании.

Если ваша ветка не является локальной, это приведет только к перезаписи истории, что может вызвать серьезные проблемы.

BartoszKP
источник
2
Я не мог следовать принятому ответу SLaks, но это сработало для меня. После того, как я получил нужную историю коммитов, мне пришлось git push --forceизменить удаленное репо.
escapecharacter
1
При использовании rebase с параметром -Xtheirs конфликты автоматически разрешаются автоматически, поэтому git rebase temp branch -Xtheirs. Полезный ответ для внедрения в скрипт!
David C
Для таких новичков, как я, я хотел бы добавить это после git rebase temp branch, но до того git branch -d temp, как все, что вам нужно сделать, это исправить и обработать конфликты и проблемы слияния git rebase --continue, т.е. не нужно ничего фиксировать и т.д.
Пагсли,
19

Еще более простое решение:

  1. Создайте новый коммит в конце D. Теперь у вас есть:

    A -- B -- C -- D
    
  2. Затем запустите:

    $ git rebase -i hash-of-A
    
  3. Git откроет ваш редактор, и он будет выглядеть так:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Просто переместите D наверх, затем сохраните и выйдите

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Теперь у вас будет:

    A -- D -- B -- C
    
Мэтью
источник
6
Хорошая идея, однако может быть сложно ввести D на C, когда вы намереваетесь написать эти изменения. А.
BartoszKP
У меня есть ситуация, когда у меня есть 3 коммита, которые я хочу перебазировать вместе, и коммит в середине, который не связан. Очень приятно иметь возможность просто переместить этот коммит раньше или позже вниз по строке коммитов.
раскрывается
13

Предполагая, что история фиксации есть preA -- A -- B -- C, если вы хотите вставить фиксацию между Aи B, шаги следующие:

  1. git rebase -i hash-of-preA

  2. Git откроет ваш редактор. Контенту может понравиться это:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Измените первое pickна edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Сохранить и выйти.

  3. Измените свой код, а затем git add . && git commit -m "I"

  4. git rebase --continue

Теперь ваша история коммитов Git preA -- A -- I -- B -- C


Если вы столкнетесь с конфликтом, Git остановится на этой фиксации. Вы можете использовать git diffдля поиска маркеров конфликтов и их разрешения. После разрешения всех конфликтов вам нужно использовать, git add <filename>чтобы сообщить Git, что конфликт разрешен, а затем перезапустить git rebase --continue.

Если вы хотите отменить перебазирование, используйте git rebase --abort.

haolee
источник
11

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

Используя, git rebase -iвы получаете список коммитов с момента этого коммита. Просто добавьте «разрыв» вверху файла, это приведет к прерыванию перебазирования в этой точке.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

После запуска git rebaseтеперь остановится в точке «разрыва». Теперь вы можете редактировать свои файлы и создавать коммит в обычном режиме. Затем вы можете продолжить перебазирование с помощью git rebase --continue. Это может вызвать конфликты, которые вам придется исправить. Если вы заблудились, не забывайте, что вы всегда можете прервать использование git rebase --abort.

Эту стратегию можно обобщить, чтобы вставить фиксацию где угодно, просто поместите «разрыв» в то место, где вы хотите вставить фиксацию.

Переписав историю, не забудьте git push -f. Применяются обычные предупреждения о том, что другие люди скачивают вашу ветку.

axerologementy
источник
Извините, но мне сложно понять, как это «избежать перебазирования». Вы здесь бежите rebase. Нет большой разницы, создадите ли вы фиксацию во время перебазирования или заранее.
BartoszKP
Уупс, я имел в виду избегать "хака редактирования" во время перебазирования, наверное, я сформулировал это плохо.
аксерологементы 06
Правильно. В моем ответе также не используется функция «редактирования» перебазирования. Тем не менее, это еще один действенный подход - спасибо! :-)
BartoszKP 07
Это, безусловно, лучшее решение!
Теодор Р. Смит
6

Здесь уже много хороших ответов. Я просто хотел добавить решение «без перебазирования» за 4 простых шага.


Резюме

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

объяснение

(Примечание: одним из преимуществ этого решения является то, что вы не касаетесь своей ветки до последнего шага, когда вы на 100% уверены, что у вас все в порядке с конечным результатом, поэтому у вас есть очень удобный шаг «предварительного подтверждения» с учетом AB-тестирования .)


Исходное состояние (я предположил masterдля вашего имени ветки)

A -- B -- C <<< master <<< HEAD

1) Начните с направления ГОЛОВКИ в нужное место

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(При желании здесь, вместо того, чтобы отсоединять HEAD, мы могли бы создать временную ветку, с git checkout -b temp Aкоторой нам нужно будет удалить в конце процесса. Оба варианта работают, делайте так, как вы предпочитаете, поскольку все остальное остается прежним)


2) Создайте новый коммит D для вставки

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Затем принесите копии последних отсутствующих коммитов B и C (была бы такая же строка, если бы было больше коммитов)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(удобное AB-тестирование здесь, если необходимо)

Настал момент проверить ваш код, протестировать все, что нужно протестировать, и вы также можете сравнить / сравнить / проверить, что у вас было и что вы получите после операций.


4) В зависимости от ваших тестов между Cи C', либо нормально, либо нокаут.

(ЛИБО) 4-ОК) Наконец, переместите ссылкуmaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(ИЛИ) 4-КО) Просто оставьте masterбез изменений

Если вы создали временную ветку, просто удалите ее с помощью git branch -d <name>, но если вы выбрали отдельный маршрут HEAD, никаких действий на этом этапе не требуется, новые коммиты будут иметь право на сборку мусора сразу после того, как вы повторно подключитесь HEADс помощьюgit checkout master

В обоих случаях (OK или KO) на этом этапе просто произведите проверку еще masterраз, чтобы повторно подключиться HEAD.

RomainValeri
источник