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

Боюсь, я не смог найти ничего похожего на этот конкретный сценарий.

У меня есть репозиторий git с большой историей: более 500 веток, более 500 тегов, начиная с середины 2007 года. Он содержит ~19 500 коммитов. Мы хотели бы удалить всю историю до 1 января 2010 года, чтобы сделать ее меньше и с ней было проще работать (мы будем хранить полную копию истории в архивном репозитории).

Я знаю коммит, который хочу сделать корнем нового репозитория. Однако я не могу понять, как правильно git mojo урезать репо, чтобы начать с этой фиксации. Я предполагаю, что какой-то вариант

git filter-branch

привлечение трансплантатов было бы необходимо; также может быть необходимо обработать каждую из более чем 200 веток, которые мы хотим сохранить отдельно, а затем снова склеить репозиторий (то, что я делаю умею делать).

Кто-нибудь когда-нибудь делал что-то подобное? У меня есть git 1.7.2.3, если это имеет значение.


person ebneter    schedule 23.12.2010    source источник


Ответы (11)


Просто создайте прививку родителя вашего нового корневого коммита без родителя (или к пустому коммиту, например, к реальному корневому коммиту вашего репозитория). Например. echo "<NEW-ROOT-SHA1>" > .git/info/grafts

После создания трансплантата он сразу вступает в силу; вы сможете посмотреть на git log и увидеть, что нежелательные старые коммиты исчезли:

$ echo 4a46bc886318679d8b15e05aea40b83ff6c3bd47 > .git/info/grafts
$ git log --decorate | tail --lines=11
commit cb3da2d4d8c3378919844b29e815bfd5fdc0210c
Author: Your Name <[email protected]>
Date:   Fri May 24 14:04:10 2013 +0200

    Another message

commit 4a46bc886318679d8b15e05aea40b83ff6c3bd47 (grafted)
Author: Your Name <[email protected]>
Date:   Thu May 23 22:27:48 2013 +0200

    Some message

Если все выглядит так, как задумано, вы можете просто сделать простое git filter-branch -- --all, чтобы сделать его постоянным.

ВНИМАНИЕ: после выполнения шага filter-branch все идентификаторы коммитов будут изменены, поэтому любой, кто использует старое репо, никогда не должен объединяться с кем-либо, использующим новое репо.

person apenwarr    schedule 05.02.2011
comment
Что ж, после создания файла '.git/info/grafts' и ветки filter мне по-прежнему нужна была копия 'git clone --no-local --no-hardlinks' (перед этим сделайте все свои локальные ветки отслеживания). Простое удаление '.git/info/grafts' не помогает! - person aanno; 18.12.2012
comment
Возможно, вы захотите перепроверить stackoverflow.com/questions/7654822/, если вы хотите уменьшить размер репозитория. - person aanno; 20.12.2012
comment
Мне пришлось сделать git filter-branch --tag-name-filter cat -- --all, чтобы обновить теги. Но у меня также есть старые теги, указывающие на старую историю, которую я хочу удалить. Как я могу избавиться от всех этих старых тегов? Если я их не удалю, то старая история не исчезнет, ​​и я все еще могу видеть ее с gitk --all. - person Craig McQueen; 25.06.2013
comment
Просто создайте прививку родителя вашего нового корневого коммита, чтобы ни один родитель не нуждался в доработке. Я попробовал это и не смог понять синтаксис для отсутствия родителя. Страница руководства утверждает, что требуется идентификатор родительской фиксации; использование всех нулей просто дает мне ошибку. - person Marius Gedminas; 10.08.2013
comment
Если кому-то еще интересно, как именно это работает, это довольно просто: echo "<NEW-ROOT-HASH>" > .git/info/grafts - person friederbluemle; 09.12.2013
comment
Может кто-нибудь объяснить, что это значит? Просто создайте трансплантат родителя вашего нового корневого коммита без родителя (или к пустому коммиту, например, к реальному корневому коммиту вашего репо). - person Mike S; 19.06.2014
comment
согласен, объяснить что такое прививка было бы более чем полезно - person Charles Martin; 23.07.2014
comment
Не работает для меня. Создал беспорядок в истории как со старыми, так и с новыми идентификаторами коммитов. - person rustyx; 24.07.2014
comment
Это, похоже, не удаляет старые коммиты; их можно увидеть в git-log и проверить. - person Wooble; 28.10.2014
comment
Параметр force фактически удалил ветку для меня. git filter-branch -f -- --all - person Jake88; 16.12.2014
comment
Цитата из связанной вики-страницы о прививках. Начиная с Git 1.6.5, была добавлена ​​более гибкая git replace, которая позволяет вам заменять любой объект любым другим объектом и отслеживать ассоциации с помощью ссылок, которые можно передавать и извлекать между репозиториями. Так что этот ответ может устареть для текущих версий git. - person ThorSummoner; 20.01.2015
comment
Отменяет ли этот метод предыдущие теги с коммитами? Кажется, он зашифровал некоторые теги для меня... - person datUser; 16.04.2015
comment
Не работает. git log после создания .git/info/grafts по-прежнему показывает начальную фиксацию. - person wl2776; 09.08.2018
comment
Это определенно НЕ работает больше $ git replace --convert-graft-file hint: Support for <GIT_DIR>/info/grafts is deprecated hint: and will be removed in a future Git version. hint: hint: Please use "git replace --convert-graft-file" hint: to convert the grafts into replace refs. hint: hint: Turn this message off by running hint: "git config advice.graftFileDeprecated false" И не похоже, что git replace --convert-graft-file имеет желаемый эффект. - person DanCat; 24.08.2018

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

Если вы хотите освободить место в своем репозитории git, но не хотите перестраивать все свои коммиты (rebase или graft) и по-прежнему иметь возможность отправлять/вытягивать/сливать от людей, у которых есть полный репозиторий, вы можете использовать клон git мелкий клон (параметр --depth).

; Clone the original repo into limitedRepo
git clone file:///path_to/originalRepo limitedRepo --depth=10

; Remove the original repo, to free up some space
rm -rf originalRepo
cd limitedRepo
git remote rm origin

Возможно, вы сможете очистить существующее репо, выполнив следующие действия:

; Shallow to last 5 commits
git rev-parse HEAD~5 > .git/shallow

; Manually remove all other branches, tags and remotes that refers to old commits

; Prune unreachable objects
git fsck --unreachable ; Will show you the list of what will be deleted
git gc --prune=now     ; Will actually delete your data

Как удалить все локальные теги git?

Ps: более старые версии git не поддерживали клонирование/проталкивание/вытягивание из/в неглубокие репозитории.

person Alexandre T.    schedule 16.01.2016
comment
+1 Это правильный ответ для более новых версий Git. (О, и, пожалуйста, вернитесь на PPCG!) - person wizzwizz4; 09.03.2016
comment
Похоже, вам нужно как минимум git 1.9, чтобы это работало. Я не уверен в точной версии, потому что я только что перешел на 2.8, и она работала как шарм. - person Colin; 30.04.2016
comment
@Trogdor В ответе должно быть cd limitedRepo, так как именно здесь вам нужно удалить ссылку на несуществующее происхождение. Я отправил правку. - person Omn; 22.03.2018
comment
Когда я пытаюсь отправить этот неглубокий клон в новый репозиторий (что я хочу сделать, потому что хочу избавиться от истории своего репозитория и начать новый репо с гораздо меньшей историей), я получаю сообщение об ошибке. из Gitlab, что мелкое обновление не разрешено. Должен быть способ превратить неглубокий клон в обычное репо без повторного восстановления всей дополнительной истории. - person Jez; 22.06.2018
comment
@Jez Это был бы другой ответ с наибольшим количеством голосов. Этот ответ не для вас, если вы хотите навсегда избавиться от истории. Это для работы с с огромными историями. - person Nobody; 06.11.2018
comment
Что, если вы хотите сохранить несколько сотен коммитов из тысяч? Вычисление глубины может стать сложной задачей. Мне нравится подход clone, но есть ли способ использовать старый хэш коммита в качестве начального вместо номера глубины? - person Micros; 03.12.2018
comment
Чтобы ответить на мой собственный вопрос: git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02 Работает как шарм! - person Micros; 05.12.2018
comment
@Jez, вы можете преобразовать свое мелкое репо в обычное, запустив git filter-branch -- --all. Это изменит все хэши в нем, но после этого вы сможете отправить его в новое репо. - person Ed'ka; 27.01.2019
comment
@Jez Установите параметр receive.shallowupdate для нового репо, чтобы иметь возможность отправить в него неглубокий клон: stackoverflow.com/a/33086124/1063363 - person bam; 13.08.2019
comment
Если вы хотите обрезать историю на основе указанной даты , вы можете использовать параметр --shallow-since=<date> для создания неглубокого клона с историей после указанного времени вместо параметра --depth <depth>, который создает неглубокий клон с история усекается до указанного количества коммитов. - person SherylHohman; 13.01.2021
comment
Вам может понадобиться опция --no-single-branch. В противном случае вы потеряете все остальные ветки. - person 12431234123412341234123; 01.04.2021

Этот метод прост для понимания. и работает нормально. Аргументом скрипта ($1) является ссылка (тег, хеш,...) на коммит, начиная с которого вы хотите вести историю.

#!/bin/bash
git checkout --orphan temp $1 # create a new branch without parent history
git commit -m "Truncated history" # create a first commit on this branch
git rebase --onto temp $1 master # now rebase the part of master branch that we want to keep onto this branch
git branch -D temp # delete the temp branch

# The following 2 commands are optional - they keep your git repo in good shape.
git prune --progress # delete all the objects w/o references
git gc --aggressive # aggressively collect garbage; may take a lot of time on large repos

ПРИМЕЧАНИЕ: старые теги останутся; поэтому вам может потребоваться удалить их вручную

примечание: я знаю, что это почти то же самое, что и @yoyodin, но здесь есть несколько важных дополнительных команд и информации. Я попытался отредактировать ответ, но, поскольку это существенное изменение ответа @yoyodin, мое редактирование было отклонено, так что вот информация!

person Chris Maes    schedule 21.05.2014
comment
Я ценю пояснения, данные для команд git prune и git gc. Есть ли объяснение остальным командам в скрипте? В нынешнем виде неясно, какие аргументы ему передаются и что делает каждая команда. Спасибо. - person user5359531; 10.03.2016
comment
@user5359531 user5359531 спасибо за ваше замечание, я добавил еще несколько комментариев для каждой команды. Надеюсь это поможет. - person Chris Maes; 11.03.2016
comment
@ChrisMaes — это git prune --progress для более старой версии git? Согласно документам, в большинстве случаев пользователям не нужно вызывать git prune напрямую, а вместо этого следует вызывать git gc, который выполняет обрезку наряду со многими другими вспомогательными задачами. - person YPCrumble; 01.04.2016
comment
@ypcrumble. Я не знаю точной истории этих функций... Но обратите внимание, что последние команды являются необязательными. Git GC должен запускаться автоматически через некоторое время... - person Chris Maes; 01.04.2016
comment
Слияние конфликтов повсюду ... не очень полезно - person Warpzit; 22.06.2016
comment
@Warpzit: на каком этапе вы столкнулись с этими конфликтами слияния? Это довольно странно... - person Chris Maes; 22.06.2016
comment
@ChrisMaes на этапе перебазирования. Немного расстраивает, так как это кажется самым простым решением. - person Warpzit; 22.06.2016
comment
вы уверены, что $1 является прямым предком коммита, который вы использовали, когда запускали скрипт? $1 должен быть в главной ветке (и предположим, что вы хотите очистить главную ветку)? Ни один из предыдущих шагов не дал вам ошибок? - person Chris Maes; 22.06.2016
comment
@ChrisMaes Я уверен, что это был мастер, я перешел на точку дальше, но та же проблема! - person Warpzit; 22.06.2016
comment
Давайте продолжим это обсуждение в чате. - person Chris Maes; 22.06.2016
comment
@Warpzit, тебе когда-нибудь удавалось выяснить, почему произошел конфликт? Я также испытываю конфликты слияния при перемещении. - person dvdchr; 15.03.2018
comment
@dvdchr Нет, но мы удалили старые большие файлы из истории и другие настройки вместо удаления всей истории. Также самой большой проблемой был сервер сборки, который мы изменили, чтобы использовать мелкое клонирование. - person Warpzit; 16.03.2018
comment
Для справки, это было очень медленно (медленнее, чем решение с graft/filter-branch), и процедура продолжала давать сбои, потому что требовалось около 60 ГБ дискового пространства, которого у меня не было. Однако это решение может работать для небольших репозиториев. - person piojo; 05.06.2018
comment
После нескольких попыток я всегда получаю конфликты слияния во время перебазирования. Удаление всех тегов не помогло. версия гита: 2.19. Кто-нибудь знает, почему возникают конфликты слияния? - person Scott Wiedemann; 02.10.2018
comment
@ScottWiedemann конфликты слияния могут возникнуть, если у вас сложная история со слияниями. Конфликты, вероятно, возникают при выполнении git rebase --onto temp $1 master - person Chris Maes; 02.10.2018
comment
@Warpzit Я избавился от конфликтов слияния, добавив -p к команде rebase, как было предложено в другом ответе. - person leonbloy; 28.11.2018
comment
Я точно следовал этому, и все, что я получил, было той же историей, что и раньше, с новой веткой, начинающейся с коммита, который я хотел сократить, с той же историей, что и раньше. Никакая история не была удалена. - person DrStrangepork; 21.10.2019

Попробуйте этот метод Как обрезать историю git :

#!/bin/bash
git checkout --orphan temp $1
git commit -m "Truncated history"
git rebase --onto temp $1 master
git branch -D temp

Здесь $1 — это SHA-1 коммита, который вы хотите сохранить, и скрипт создаст новую ветку, содержащую все коммиты между $1 и master, и вся старая история будет удалена. Обратите внимание, что этот простой сценарий предполагает, что у вас нет существующей ветки с именем temp. Также обратите внимание, что этот скрипт не очищает данные git от старой истории. Запустите git gc --prune=all && git repack -a -f -F -d после того, как убедитесь, что вы действительно хотите потерять всю историю. Вам также может понадобиться rebase --preserve-merges, но имейте в виду, что реализация этой функции в git не идеальна. Проверьте результаты вручную, если вы используете это.

person yoyodyn    schedule 25.07.2011
comment
У меня работает, за исключением того, что мне пришлось обойти отсутствие git checkout --orphan в моей версии git: bogdan.org.ua/2011/03/28/ - person seanf; 04.05.2012
comment
Я пробовал это, но получил конфликты слияния на шаге rebase. Странно - я не ожидал, что в таких обстоятельствах могут возникнуть конфликты слияния. - person Craig McQueen; 25.06.2013
comment
Используйте git commit --allow-empty -m "Truncate history", если проверенный вами коммит не содержит файлов. - person friederbluemle; 02.10.2013
comment
Как передать это обратно удаленному мастеру? Когда я это делаю, я получаю как старую, так и новую историю. - person rustyx; 24.07.2014
comment
Какой должна быть «температура»? Что вы должны передать в качестве аргумента для этого? Есть ли пример того, как должны выглядеть эти команды, когда вы их на самом деле запускаете? Спасибо. - person user5359531; 10.03.2016
comment
Я считаю, что $1 - это хэш коммита. (Более подробная информация содержится в связанной статье). - person Chris Nolet; 23.05.2016
comment
Это было самое простое решение, и нет необходимости помещать его в файл bash. - person luis19mx; 28.06.2016
comment
@CraigMcQueen попробуйте использовать git rebase -p --onto temp $1 master-p). Это сохраняет коммиты слияния и позволяет избежать конфликтов слияния. В противном случае rebase пытается сгладить коммиты слияния. - person ZachB; 12.10.2018
comment
Этот ответ невероятно полезен; большое огромное спасибо Микко. - person Dmitri Zagidulin; 27.04.2021
comment
я получаю $ git rebase --onto temp $1 master First, rewinding head to replay your work on top of it... Fast-forwarded temp to temp. $ git branch -D temp error: Cannot delete branch 'temp' checked out at '/home/user/Public/newspapa' - person mevsme; 11.06.2021

В качестве альтернативы перезаписи истории рассмотрите возможность использования git replace как в этой статье из книги Pro Git . Обсуждаемый пример включает замену родительского коммита для имитации начала дерева, при этом полная история сохраняется как отдельная ветвь для безопасного хранения.

person Jeff Bowman    schedule 26.10.2012
comment
Да, я думаю, вы, вероятно, могли бы сделать с этим то, что мы хотели, если бы вы также уничтожили отдельную ветку полной истории. (Мы пытались уменьшить репозиторий.) - person ebneter; 27.10.2012
comment
Меня обескуражил ответ, находящийся за пределами сайта; но он ссылается на сайт GitScm, и учебник, на который он ссылается, очень хорошо написан и кажется прямо относящимся к вопросу ОП. - person ThorSummoner; 20.01.2015
comment
@ThorSummoner Извините за это! Я немного разработаю ответ на месте - person Jeff Bowman; 20.01.2015
comment
К сожалению, это не альтернатива переписыванию истории. В начале статьи есть запутанная фраза, которая, вероятно, и создала такое впечатление. Можно ли удалить это из этого ответа? Вы увидите в статье, что автор переписывает историю усеченной ветки, но предлагает способ повторного присоединения устаревшей ветки истории с помощью git replace. Я считаю, что это было исправлено в другом вопросе, где вы разместили этот ответ. - person Mitch; 02.02.2016
comment
Мне пришлось довольно много прочитать, чтобы понять, как сжать мой репозиторий, действительно, оказалось, что git replace - это путь, пожалуйста, подумайте о том, чтобы прочитать stackoverflow.com/questions/6800692/ Я сделал это через git replace, и это работает просто отлично. - person Joel AZEMAR; 27.04.2020

Если вы хотите сохранить репозиторий upstream с полной историей, но с небольшими локальными извлечениями, выполните неглубокое клонирование с помощью git clone --depth=1 [repo].

После нажатия фиксации вы можете сделать

  1. git fetch --depth=1 для удаления старых коммитов. Это делает старые коммиты и их объекты недоступными.
  2. git reflog expire --expire-unreachable=now --all. Срок действия всех старых коммитов и их объектов
  3. git gc --aggressive --prune=all для удаления старых объектов

См. также Как удалить локальную историю git после коммита?.

Обратите внимание, что вы не можете отправить этот «мелкий» репозиторий в другое место: «мелкое обновление не разрешено». См. раздел Удаленное отклонение (поверхностное обновление не разрешено) после изменения удаленного URL-адреса Git. Если вы хотите этого, вы должны придерживаться прививки.

person koppor    schedule 08.05.2016
comment
Пункт № 1. имел значение для меня. Ваше здоровье - person clapas; 08.03.2017

Мне нужно было прочитать несколько ответов и некоторую другую информацию, чтобы понять, что я делаю.

<сильный>1. Игнорировать все, что старше определенной фиксации

Файл .git/info/grafts может определять поддельных родителей для коммита. Строка только с идентификатором фиксации говорит о том, что у фиксации нет родителя. Если мы хотим сказать, что нас интересуют только последние 2000 коммитов, мы можем ввести:

git rev-parse HEAD~2000 > .git/info/grafts

git rev-parse дает нам идентификатор коммита 2000-го родителя текущего коммита. Приведенная выше команда перезапишет файл графтов, если он есть. Сначала проверьте, есть ли он.

<сильный>2. Переписать историю Git (необязательно)

Если вы хотите сделать этого привитого поддельного родителя настоящим, запустите:

git filter-branch -- --all

Это изменит все идентификаторы коммитов. Каждая копия этого репозитория должна быть принудительно обновлена.

<сильный>3. Очистите место на диске

Я не делал шаг 2, потому что хотел, чтобы моя копия оставалась совместимой с исходным кодом. Я просто хотел сэкономить место на диске. Чтобы забыть все старые коммиты:

git prune
git gc

Альтернатива: мелкие копии

Если у вас есть неглубокая копия другого репозитория и вы просто хотите сэкономить место на диске, вы можете обновить .git/shallow. Но будьте осторожны, чтобы ничто не указывало на фиксацию, сделанную ранее. Итак, вы можете запустить что-то вроде этого:

git fetch --prune
git rev-parse HEAD~2000 > .git/shallow
git prune
git gc

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

Если у вас все еще есть старые ссылки (теги, ветки, удаленные головки), которые указывают на старые коммиты, они не будут очищены, и вы не сэкономите больше места на диске.

person Maikel    schedule 01.05.2017
comment
Поддержка ‹GIT_DIR›/info/grafts устарела и будет удалена в будущей версии Git. - person danny; 20.11.2019
comment
Вместо этого рассмотрите возможность использования git replace. См. stackoverflow.com/questions/6800692/ - person Joel AZEMAR; 27.04.2020

При rebase или push в head/master может возникнуть эта ошибка.

remote: GitLab: You are not allowed to access some of the refs!
To git@giturl:main/xyz.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@giturl:main/xyz.git'

Чтобы решить эту проблему, в панели управления git необходимо удалить основную ветку из "Защищенных веток".

введите здесь описание изображения

то вы можете запустить эту команду

git push -f origin master

or

git rebase --onto temp $1 master
person HMagdy    schedule 03.01.2017

Здесь слишком много ответов, которые не актуальны, а некоторые не полностью объясняют последствия. Вот что у меня сработало для обрезки истории с использованием последней версии git 2.26:

Сначала создайте фиктивный коммит. Эта фиксация появится как первая фиксация в вашем усеченном репо. Вам это нужно, потому что этот коммит будет содержать все базовые файлы для истории, которую вы храните. SHA — это идентификатор предыдущей фиксации той фиксации, которую вы хотите сохранить (в данном примере 8365366). Строка «Initial» будет отображаться как сообщение фиксации первой фиксации. Если вы используете Windows, введите приведенную ниже команду из командной строки Git Bash.

# 8365366 is id of parent commit after which you want to preserve history
echo 'Initial' | git commit-tree 8365366^{tree}

Приведенная выше команда напечатает SHA, например, d10f7503bc1ec9d367da15b540887730db862023.

Теперь просто введите:

# d10f750 is commit ID from previous command
git rebase --onto d10f750 8365366

Это сначала поместит все файлы на момент коммита 8365366 в фиктивный коммит d10f750. Затем он воспроизведет все коммиты после 8365366 поверх d10f750. Наконец, указатель ветки master будет обновлен до последней воспроизведенной фиксации.

Теперь, если вы хотите отправить эти усеченные репо, просто выполните git push -f.

Несколько вещей, о которых следует помнить (это относится и к другим методам, и к этому): Теги не передаются. Хотя идентификаторы коммитов и временные метки сохраняются, вы увидите, что GitHub показывает эти коммиты в единовременном заголовке, например Commits on XY date.

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

person Shital Shah    schedule 18.05.2020

Согласно репозиторию Git инструмента BFG, он удаляет большие или проблемные BLOB-объекты так же, как это делает git-filter-branch, но быстрее — и написан на Scala.

https://github.com/rtyley/bfg-repo-cleaner

person RahulMohan Kolakandy    schedule 07.08.2017

  1. удалить данные git, rm .git
  2. git инициировать
  3. добавить удаленный git
  4. принудительный толчок
person Brad Reid    schedule 22.01.2015
comment
что сработает для удаления ВСЕЙ истории, но не для того, что он просил: хранить историю с января 2010 года - person Chris Maes; 22.01.2015
comment
Просто хотел сказать спасибо, так как это помогло мне в моем сценарии, хотя это может быть неправильный ответ на вопрос. - person apnerve; 25.06.2019