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

209

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

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

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

git filter-branch

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

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

ebneter
источник

Ответы:

118

Просто создайте прививку родителя вашего нового корневого коммита без родителя (или с пустым коммитом, например, с настоящим корневым коммитом вашего репозитория). Например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 <your.email@example.com>
Date:   Fri May 24 14:04:10 2013 +0200

    Another message

commit 4a46bc886318679d8b15e05aea40b83ff6c3bd47 (grafted)
Author: Your Name <your.email@example.com>
Date:   Thu May 23 22:27:48 2013 +0200

    Some message

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

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

apenwarr
источник
6
Мне пришлось сделать, git filter-branch --tag-name-filter cat -- --allчтобы обновить теги. Но у меня также есть старые теги, указывающие на старую историю, которую я хочу удалить. Как я могу избавиться от всех этих старых тегов? Если я не удаляю их, то старая история не исчезает, и я все еще могу видеть ее gitk --all.
Крейг МакКуин
9
«Просто создайте прививку для родительского элемента вашего нового корневого коммита без родительского коммита», требуется некоторая проработка. Я попробовал это и не смог понять синтаксис "нет родителя". Страница руководства утверждает, что требуется родительский идентификатор фиксации; использование всех нулей просто дает мне ошибку.
Мариус Гедминас
6
Если кому-то еще интересно, как именно это работает, это довольно просто:echo "<NEW-ROOT-HASH>" > .git/info/grafts
Friederbluemle
3
Я согласен, объясняя, что такое взяточничество было бы более чем полезно
Чарльз Мартин
4
Цитируется по ссылочной вики-странице о прививках. «Начиная с Git 1.6.5, была добавлена ​​более гибкая замена git, которая позволяет заменять любой объект любым другим объектом и отслеживает ассоциации с помощью ссылок, которые можно вставлять и перемещать между репозиториями». Так что этот ответ может быть устаревшим для текущих версий git.
ThorSummoner
130

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

Если вы хотите освободить место в своем репозитории git, но не хотите перестраивать все свои коммиты (перебазирование или трансплантация), и при этом можете толкать / извлекать / объединять людей, имеющих полное репо, вы можете использовать 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 не поддерживали клонирование / push / pull из / для мелких репозиториев.

Александр Т.
источник
9
+1 Это правильный ответ для новых версий Git. (О, и, пожалуйста, возвращайтесь в PPCG !)
wizzwizz4
6
Как вы можете cdк папке, которая была только что удалена? Я чувствую, что здесь какая-то недостающая информация. Кроме того, есть ли способ применить эти изменения к удаленному репо?
Трогдор,
4
@Jez Это был бы другой самый популярный ответ. Этот ответ не для вас, если вы хотите навсегда избавиться от истории. Это для работы с огромной историей.
Никто
4
Чтобы ответить на мой собственный вопрос: git clone file:///Users/me/Projects/myProject myClonedProject --shallow-since=2016-09-02работает как шарм!
Микро
5
@Jez, вы можете конвертировать ваше мелкое репо в обычный, запустив git filter-branch -- --all. Это изменит все хеши в нем, но после этого вы сможете перенести его в новый репо
Ed'ka
61

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

#!/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, мое редактирование было отклонено, поэтому вот информация!

Крис Мэйс
источник
Я ценю объяснения , данные для git pruneи git gcкоманд. Есть ли объяснение для остальных команд в сценарии? В настоящее время неясно, какие аргументы передаются ему и что делает каждая команда. Спасибо.
user5359531
2
@ user5359531 спасибо за ваше замечание, я добавил еще несколько комментариев для каждой команды. Надеюсь это поможет.
Крис Мэйс
4
Конфликты
слияний повсюду
3
@Warpzit Я избавился от конфликтов слияний, добавив -pв rebaseкоманду, как это было предложено в другом ответе
leonbloy
1
Я точно следовал этому, и все, что я получил, было той же историей, что и раньше, с новой веткой, начинающейся с коммита, который я хотел удалить с той же историей, что и раньше. История не была удалена.
DrStrangepork
51

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

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

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

yoyodyn
источник
22
Я попробовал это, но получил конфликт слияния в rebaseшаге. Странно - я не ожидал, что в этих обстоятельствах возможны конфликты слиянием.
Крейг МакКуин
2
Используйте, git commit --allow-empty -m "Truncate history"если зафиксированный вами коммит не содержит файлов.
Friederbluemle
2
Как мне вернуть это к удаленному мастеру? Когда я это делаю, я получаю и старую, и новую историю.
rustyx
1
Каким должен быть «темп»? Что вы должны передать в качестве аргумента для этого? Есть ли пример того, как эти команды должны выглядеть, когда вы на самом деле их запускаете? Спасибо.
user5359531
1
Я считаю, что 1 доллар - это хэш коммита. (Более подробная информация представлена ​​в связанной статье).
Крис Нолет
34

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

Джефф Боуман
источник
Да, я думаю, что вы могли бы, вероятно, сделать то, что мы хотели с этим, если бы вы взорвали также отдельную ветку полной истории. (Мы пытались уменьшить хранилище.)
ebneter
1
Я был обескуражен ответом, находящимся вне сайта; но он действительно ссылается на сайт GitScm, и учебник, на который он ссылается, написан очень хорошо и, кажется, прямо к сути вопроса ОП.
ThorSummoner
@ThorSummoner К сожалению об этом! Я разработаю ответ немного более подробно на месте
Джефф Боуман
К сожалению, это не альтернатива переписыванию истории. В начале статьи есть запутанное предложение, которое, вероятно, произвело такое впечатление. Может ли это быть удалено из этого ответа? В статье вы увидите, что автор переписывает историю усеченной ветви, но предлагает способ присоединения устаревшей ветки «history» с помощью git replace. Я считаю, что это было исправлено в другом вопросе, где вы опубликовали этот ответ.
Митч
1
Обсуждение « git replaceпротив» git graftпроводится на stackoverflow.com/q/6800692/873282
koppor
25

Если вы хотите сохранить в вверх по течению хранилище с полной историей , но местные мелкие извлечений, сделать неглубокий клон с 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 . Если вы хотите этого, вы должны придерживаться прививки.

koppor
источник
1
Пункт № 1. сделал разницу для меня. Приветствия
clapas
21

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

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

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

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

Maikel
источник
Поддержка <GIT_DIR> / info / grafts устарела и будет удалена в следующей версии Git.
Danny
Пожалуйста, рассмотрите использование git replaceвместо. См stackoverflow.com/questions/6800692/...
Joel AZEMAR
3

При перебазировании или толкании к голове / мастеру эта ошибка может возникнуть

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 dashboard следует удалить главную ветку из «Защищенных веток»

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

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

git push -f origin master

или

git rebase --onto temp $1 master
HMagdy
источник
0

Здесь слишком много ответов, которые не актуальны, а некоторые не полностью объясняют последствия. Вот что сработало для меня, чтобы урезать историю, используя последний 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.

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

Шиталь шах
источник
-3

Вы можете удалить каталог, файлы, а также всю историю, связанную с dir или файлом, используя нижеприведенный jar [download it] и команды

Файл bfg.jar: https://rtyley.github.io/bfg-repo-cleaner/

git clone --bare repo-url cd repo_dir java -jar bfg.jar --delete-folder имя_папки git reflog expire --expire = сейчас - все && git gc --prune = сейчас - агрессивный толчок git --mirror repo_url

РахулМохан Колаканды
источник
-10
  1. удалить данные git, rm .git
  2. мерзавец
  3. добавить git remote
  4. принудительный толчок
Брэд Рейд
источник
6
это сработает, чтобы удалить ВСЕ истории, но не для того, что он просил: сохранять историю с января 2010 года
Крис Мэйс
1
Я просто хотел сказать спасибо, поскольку это помогло мне в моем сценарии, хотя это может быть неправильный ответ на вопрос
apnerve