Как удалить конкретную ревизию в истории git?

213

Предположим, что ваша история git выглядит так:

1 2 3 4 5

1–5 - это отдельные редакции. Вам нужно удалить 3, сохраняя при этом 1, 2, 4 и 5. Как это можно сделать?

Есть ли эффективный метод, когда после удаления нужно сотни ревизий?

1800 ИНФОРМАЦИЯ
источник
Этот вопрос плохо определен. В нем прямо не говорится, что автор хочет 1-2- (3 + 4) -5 или 1-2-4-5
RandyTek
14
Ну, через 8 лет я не могу точно сказать, какую проблему я пытался решить. Но в git всегда есть много способов что-то сделать, и есть много ответов, которые понравились разным людям, поэтому я думаю, что двусмысленность не доставляет слишком многим людям много затруднений
1800 ИНФОРМАЦИЯ
1
git rebase --onto 2 3 HEADв значительной степени означает изменение на 2, с коммитами между 3 и HEAD (HEAD является необязательным, или 5 в данном случае)
neaumusic

Ответы:

76

Чтобы объединить ревизию 3 и 4 в одну ревизию, вы можете использовать git rebase. Если вы хотите удалить изменения в редакции 3, вам нужно использовать команду редактирования в интерактивном режиме перебазирования. Если вы хотите объединить изменения в одну ревизию, используйте сквош.

Я успешно использовал эту технику сквоша, но мне никогда не нужно было удалять ревизию раньше. Надеемся, что документация git-rebase в разделе «Разделение коммитов» даст вам достаточно идей, чтобы понять это. (Или кто-то еще может знать).

Из документации git :

Начните с самого старого коммита, который вы хотите сохранить как есть:

git rebase -i <after-this-commit>

Редактор будет запущен со всеми коммитами в вашей текущей ветке (игнорируя коммиты слияния), которые идут после данного коммита. Вы можете переупорядочить коммиты в этом списке на свое усмотрение, и вы можете удалить их. Список выглядит примерно так:

забрать мёртвую пчелиную линию этого коммита
pick fa1afe1 Он-лайн следующего коммита
...

Онлайновые описания предназначены исключительно для вашего удовольствия; git-rebase будет смотреть не на них, а на имена коммитов ("deadbee" и "fa1afe1" в этом примере), поэтому не удаляйте и не редактируйте имена.

Заменив команду «pick» на команду «edit», вы можете указать git-rebase прекратить работу после применения этого коммита, чтобы вы могли редактировать файлы и / или сообщение о коммите, вносить изменения и продолжать перебазирование.

Если вы хотите сложить два или более коммитов в один, замените команду «pick» на «squash» для второго и последующих коммитов. Если у коммитов были разные авторы, он будет приписывать сжатый коммит автору первого коммита.

garethm
источник
42
-1 Вопрос хорошо определен, но этот ответ не так ясен. Автор не говорит, каково точное решение.
Александр Левчук
1
Это неверное руководство. Раздел SPLITTING COMMITS не является правильным. Вы хотите прочитать намного выше в руководстве - см. Ответ @Rares Vernica.
Александр Левчук
1
@AleksandrLevchuk Вопрос не является четко определенным: из того, как сформулирован вопрос, неясно, следует ли сохранять или отменять набор изменений в 3. Я согласен, что если изменения будут отменены, другие ответы предлагают более легкий подход. Однако, если изменения будут сохранены, это будет чисто косметическая операция. Оба подхода переписывают историю способами, которые опасны, если другие могут основывать работу на вершине ошибочной истории; в этом случае косметическая очистка не должна выполняться, а удаление изменений лучше выполнить с помощью git revert.
Теодор Мердок
125

Согласно этому комментарию (и я проверил, что это правда), ответ Радо очень близок, но оставляет мерзавца в оторванном состоянии. Вместо этого удалите HEADи используйте это для удаления <commit-id>из ветки, в которой вы находитесь:

git rebase --onto <commit-id>^ <commit-id>
Карима
источник
1
Если бы вы могли сказать мне, как также удалить этот идентификатор фиксации из истории, основанный только на идентификаторе фиксации, вы были бы моим героем.
kayleeFrye_onDeck
Можете ли вы включить объяснение, что волшебная команда делает в ответ? Т.е. что обозначает каждый параметр?
alexandroid
это круто, но что если я захочу удалить самый первый коммит в истории? (вот почему я пришел сюда: P)
Стерлинг Камден
2
По какой-то причине ничего не происходит, когда я запускаю это. Тем не менее, изменился, ^чтобы ~1заставить его работать.
Сурьма
Уже поздно, но я собираюсь добавить, что если вы столкнетесь с конфликтами слияния, прервите ребазирование, а затем повторно запустите его --strategy-option theirsв конце.
LastStar007
122

Вот способ удалить неинтерактивную информацию <commit-id>, зная только то, что <commit-id>вы хотели бы удалить:

git rebase --onto <commit-id>^ <commit-id> HEAD
Rado
источник
2
У меня тоже сработало. Кстати, что делает оператор ^? Означает ли это следующий коммит после указанного?
Хопия
3
@hopia это означает (первый) родитель указанного коммита. Смотрите "git help revisions"
Эмиль Стайрк,
12
См. Рекомендацию @ kareem в его ответе опустить, HEADчтобы избежать отстраненной головы.
mklement0
1
Гораздо проще, чем выяснить, сколько коммитов возвращается на поиск.
Дана Вудман
1
это круто, но что если я захочу удалить самый первый коммит в истории? (вот почему я пришел сюда: P)
Стерлинг Камден
76

Как отмечалось ранее, git-rebase (1) - ваш друг. Предполагая, что коммиты находятся в вашей masterветке, вы должны сделать:

git rebase --onto master~3 master~2 master

Перед:

1---2---3---4---5  master

После:

1---2---4'---5' master

Из git-rebase (1):

Диапазон коммитов также может быть удален с помощью rebase. Если у нас следующая ситуация:

E---F---G---H---I---J  topicA

тогда команда

git rebase --onto topicA~5 topicA~3 topicA

приведет к удалению коммитов F и G:

E---H'---I'---J'  topicA

Это полезно, если F и G были ошибочными или не должны быть частью темы A. Обратите внимание, что аргумент --onto и параметр могут быть любым допустимым коммитом.

rvernica
источник
3
не должно ли это быть --onto master~3 master~1?
Матиас
3
Если вы просто хотите удалить последний коммит, это --onto master ~ 1 master
MikeHoss
Я хотел бы нести этот золь. но я получаю MERGE CONFLICTошибку. Я использовал сценарий, упомянутый в stackoverflow.com/questions/2938301/remove-specific-commit, и не могу удалить 2-й коммит в этом примере.
Maan81
22

Если все, что вы хотите сделать, это удалить изменения, сделанные в редакции 3, вы можете использовать git revert.

Git revert просто создает новую ревизию с изменениями, которые отменяют все изменения в ревизии, которую вы возвращаете.

Это означает, что вы сохраняете информацию как о нежелательном коммите, так и о коммите, который удаляет эти изменения.

Вероятно, это будет гораздо более дружелюбно, если вообще возможно, что кто-то извлечет из вашего хранилища за это время, так как возврат - это просто стандартный коммит.

SpoonMeiser
источник
4
К сожалению, это не очень хорошее решение для меня, потому что кто-то случайно вложил в репо 100 МБ дерьма, взорвав его размер и сделав веб-интерфейс вялым.
Стивен Смит
18

Все ответы до сих пор не относятся к последним проблемам:

Есть ли эффективный метод, когда после удаления нужно сотни ревизий?

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

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

С : коммит только после коммита, который будет удален (чистый)

R : коммит, который будет удален

B : коммит, предшествующий коммиту, который нужно удалить (базовый)

Из-за ограничения «сотни ревизий» я предполагаю следующие предварительные условия:

  1. есть какой-то смущающий коммит, который вы хотели бы никогда не существовать
  2. есть ZERO последующих коммитов, которые на самом деле зависят от этого смущающего коммита (ноль конфликтов при возврате)
  3. вам все равно, что вы будете внесены в список «коммиттеров» из сотен промежуточных коммитов («автор» будет сохранен)
  4. вы никогда не разделяли хранилище
    • или вы на самом деле имеете достаточно влияния на всех людей, которые когда-либо клонировали историю с этим обязательством, чтобы убедить их использовать вашу новую историю
    • и вам наплевать на переписывание истории

Это довольно ограничительный набор ограничений, но есть интересный ответ, который действительно работает в этом угловом случае.

Вот шаги:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Если действительно нет конфликтов, то это должно продолжаться без дальнейших перерывов. Если есть конфликты, вы можете разрешить их и rebase --continueили решить просто жить с смущением и rebase --abort.

Теперь вы должны быть на masterтом, что больше не имеет R в нем. В saveточках ветвления, где вы были раньше, в случае , если вы хотите совместить.

Как вы хотите организовать переход всех остальных на вашу новую историю, зависит от вас. Вам нужно будет ознакомиться с stash, reset --hardи cherry-pick. И вы можете удалить base, remove-meи saveветви

jdsumsion
источник
как я могу это 150000 раз?
Гена Мороз
Я работал над удалением 3 последовательных коммитов из середины истории ветки, в которой кто-то зафиксировал кучу 150 Мб файлов.
Маркос
3

Я также приземлился в аналогичной ситуации. Используйте интерактивную перебазировку, используя команду ниже, и при выборе отбросьте 3-й коммит.

git rebase -i remote/branch
Sankalp
источник
2

Итак, вот сценарий, с которым я столкнулся, и как я его решил.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

вот Rкоммит, который мне нужно было удалить, и Iэто единственный коммит, который приходит послеR

Я сделал обратный коммит и раздавил их вместе

git revert [commit id of R]
git rebase -i HEAD~3

Во время интерактивного сквоша за последние 2 коммита.

Гаутама
источник
0

Ответы rado и kareem ничего не делают для меня (появляется только сообщение «Текущая ветка актуальна».) Возможно, это происходит потому, что символ «^» не работает в консоли Windows. Однако, согласно этому комментарию, замена «^» на «~ 1» решает проблему.

git rebase --onto <commit-id>^ <commit-id>
fdermishin
источник