Я случайно зафиксировал нежелательный файл (filename.orig
при разрешении слияния) в моем репозитории несколько коммитов назад, но до сих пор не замечал этого. Я хочу полностью удалить файл из истории репозитория.
Можно ли переписать историю изменений так, чтобы filename.orig
вообще не добавлялся в репозиторий?
связанные help.github.com/articles/…
Пожалуйста, не используйте этот рецепт, если ваша ситуация не та, которая описана в вопросе. Этот рецепт предназначен для исправления плохого слияния и воспроизведения ваших хороших коммитов на фиксированном слиянии.
Хотя filter-branch
будет делать то, что вы хотите, это довольно сложная команда, и я бы, вероятно, выбрал для этого git rebase
. Вероятно, это личное предпочтение. filter-branch
может сделать это с помощью одной, немного более сложной команды, тогда как решение rebase
выполняет эквивалентные логические операции по одному шагу за раз.
Попробуйте следующий рецепт:
# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>
# remove the incorrectly added file
git rm somefile.orig
# commit the amended merge
git commit --amend
# go back to the master branch
git checkout master
# replant the master branch onto the corrected merge
git rebase tmpfix
# delete the temporary branch
git branch -d tmpfix
(Обратите внимание, что вам на самом деле не нужна временная ветвь, вы можете сделать это с помощью «отсоединенной HEAD», но вам нужно записать идентификатор фиксации, сгенерированный на этапе git commit --amend
, чтобы передать его команде git rebase
вместо временное имя ветки.)
Разве git rebase -i
не был бы быстрее и таким же простым? $ git rebase -i <sh1-of-merge> Отметьте правильный как "edit" $ git rm somefile.orig $ git commit --amend $ git rebase --continue Однако по какой-то причине у меня все еще есть этот файл где-то в последнюю очередь раз я это сделал. Наверное, чего-то не хватает.
git rebase -i
очень полезен, особенно когда вам нужно выполнить несколько операций rebase-y, но сложно описать точно, когда вы на самом деле не указываете кому-то через плечо и не видите, что они делают с помощью своего редактора. Я использую vim, но не все будут довольны: «ggjcesquash <Esc> jddjp: wq» и такими инструкциями, как «Переместите верхнюю строку после текущей второй строки и измените первое слово в четвертой строке на« редактировать », теперь сохраните и quit "быстро кажутся более сложными, чем действия на самом деле. Обычно вы также выполняете некоторые действия --amend
и --continue
.
Я сделал это, но новый коммит был повторно применен поверх измененного с тем же сообщением. По-видимому, git сделал трехстороннее слияние между старой неизмененной фиксацией, содержащей нежелательный файл, и фиксированной фиксацией из другой ветки, и поэтому он создал новую фиксацию поверх старой, чтобы повторно применить файл.
Я попытался сделать это, когда понял, что OS X.DS_Store был добавлен, но 1) коммит, в котором он был добавлен, был дублирован с помощью rebase 2) Даже с rebase --onto (уже усложняя рецепт), конечно, более свежие версии был изменен .DS_Store, поэтому filter-branch помогло мне
@UncleCJ: Ваш файл был добавлен в коммит слияния? Это важно. Этот рецепт разработан, чтобы справиться с ошибкой фиксации слияния. Это не сработает, если ваш нежелательный файл был добавлен в обычную фиксацию в истории.
Я поражен, как я мог делать все это с помощью smartgit и вообще без терминала! Спасибо за рецепт!
@CharlesBailey Я использовал ваши инструкции, чтобы изменить историю локальной ветки (т.е. мои файлы не были получены в результате слияния), и это отлично сработало. К вашему сведению.
К вашему сведению: делая это для больших файлов, перебазирование может занять довольно много времени. Выглядит замороженным, но со временем работает.
Это было намного сложнее, чем я ожидал. В итоге мне пришлось несколько раз удалять файл (в каждой фиксации), затем git add --all :/
, а затем git rebase --continue
(поскольку он пролез через каждую фиксацию). Если это не сработало, то git rebase --skip
. Затем, наконец, вручную исправьте конфликты <<<<<
, которые я обнаружил в файлах. Вроде забрали, но папка .git
все равно огромна. Честно говоря, я не совсем уверен, что я сделал, но, похоже, это шаг в правильном направлении. (Я только что понял, что удалил файлы через проводник Windows, а НЕ с git rm
. Не уверен в последствиях ...)
Однажды я случайно добавил ОГРОМНЫЙ файл. Я закончил тем, что просто уничтожил свой каталог .git, повторно добавил файлы и запустил репозиторий git заново.
Это довольно круто. Я никогда полностью не понимал, что делает git rebase. Теперь я знаю! Спасибо.
Если с тех пор вы ничего не зафиксировали, просто git rm
файл и git commit --amend
.
Если у тебя есть
git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD
будет проходить каждое изменение с merge-point
на HEAD
, удалять filename.orig и перезаписывать изменение. Использование --ignore-unmatch
означает, что команда не завершится ошибкой, если по какой-то причине filename.orig отсутствует в изменении. Это рекомендуемый способ из раздела «Примеры» в страница руководства git-filter-branch.
Примечание для пользователей Windows: в пути к файлу должен используются косые черты.
Спасибо! git filter-branch работал у меня там, где пример перебазирования, приведенный в качестве ответа, не работал: шаги, казалось, сработали, но затем нажатие не удалось. Вытащил, затем успешно нажал, но файл все еще был рядом. Пытался повторить шаги перебазирования, а затем все пошло наперекосяк с конфликтами слияния. Я использовал немного другую команду filter-branch, «Улучшенный метод», приведенный здесь: github.com/guides/completely-remove-a-file-from-all-revision s git filter-branch -f --index-filter 'git update-index --remove filename' <introduction-revision-sha1> ..ГОЛОВА
Я не уверен, какой из них является методом улучшен. Официальная документация Git для git-filter-branch
, похоже, дает первое.
Ознакомьтесь с zyxware.com/articles/4027/…, я считаю его наиболее полным и прямым решением, которое включает filter-branch
.
Спасибо! Я бы предложил только одно улучшение: --prune-empty
; эта опция для git filter-branch
удалит любые коммиты, которые касались только файла, который вы хотите удалить (поскольку после удаления они будут просто пустыми коммитами)
@atomicules, если вы попытаетесь переместить локальное репо на удаленное, git будет сначала настаивать на извлечении с удаленного, потому что в нем есть изменения, которых у вас нет локально. Вы можете использовать флаг --force для отправки на удаленный компьютер - он полностью удалит файлы оттуда. Но будьте осторожны, убедитесь, что вы не будете принудительно перезаписывать что-либо, кроме файлов.
Не забудьте использовать "
, а не '
при использовании Windows, иначе вы получите неверно сформулированную ошибку «плохая редакция».
Вы также можете использовать:
git reset HEAD file/path
Если файл был добавлен в фиксацию, это даже не удаляет файл из индекса, а просто сбрасывает индекс до версии файла HEAD.
Это лучший способ:
http://github.com/guides/completely-remove-a-file-from-all-revisions
Только не забудьте сначала сделать резервную копию копий файлов.
РЕДАКТИРОВАТЬ
Редактирование Неон, к сожалению, было отклонено во время проверки. См. Сообщение Neons ниже, оно может содержать полезную информацию!
Например. чтобы удалить все файлы *.gz
, случайно сохраненные в репозиторий git:
$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now
Это все еще не сработало для меня? (Сейчас я использую git версии 1.7.6.1)
$ du -sh .git ==> e.g. 100M
Не знаю почему, поскольку у меня была только ОДНА главная ветка. В любом случае, я наконец-то полностью очистил репозиторий git, вставив его в новый пустой и пустой репозиторий git, например
$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M
(да!)
Затем я клонирую его в новый каталог и переместил его в папку .git. например
$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M
(да! наконец-то прибрался!)
Убедившись, что все в порядке, вы можете удалить каталоги ../large_dot_git
и ../tmpdir
(может быть, через пару недель или месяц, на всякий случай ...)
Это работало для меня до того, как "Это все еще не сработало для меня?" комментарий
Отличный ответ, но предложите добавить --prune-empty
в команду filter-branch.
Переписывание истории Git требует изменения всех затронутых идентификаторов коммитов, поэтому всем, кто работает над проектом, нужно будет удалить свои старые копии репозитория и сделать новый клон после того, как вы очистите историю. Чем большему количеству людей это доставляет неудобства, тем больше вам нужна веская причина для этого - ваш лишний файл на самом деле не вызывает проблемы, но если над проектом работают только ты, вы можете также очистить историю Git, если хотите. к!
Чтобы сделать это как можно проще, я бы рекомендовал использовать BFG Repo-Очиститель, более простую и быструю альтернативу git-filter-branch
, специально разработанную для удаления файлов из истории Git. Один из способов, которым это упрощает вашу жизнь, заключается в том, что он фактически обрабатывает ссылки все по умолчанию (все теги, ветки и т.д.), но он также быстрее 10 - 50x.
Вам следует внимательно выполнить следующие шаги: http://rtyley.github.com/bfg-repo-cleaner/#usage - но основной бит таков: загрузите Банка BFG (требуется Java 6 или выше) и выполните эту команду:
$ java -jar bfg.jar --delete-files filename.orig my-repo.git
Вся история вашего репозитория будет просканирована, и любой файл с именем filename.orig
(которого нет в вашем последний коммит) будет удален. Это значительно проще, чем использовать git-filter-branch
для того же!
Полное раскрытие: я являюсь автором BFG Repo-Cleaner.
Это отличный инструмент: одна команда, она дает очень четкий вывод и предоставляет файл журнала, который соответствует каждой старой фиксации новой. Я не люблю устанавливать Java, но это того стоит.
Это единственное, что у меня сработало, но это похоже на то, что я неправильно работал с git filter-branch. :-)
Просто чтобы добавить это к решению Чарльза Бейли, я просто использовал git rebase -i для удаления ненужных файлов из более ранней фиксации, и это сработало как шарм. Шаги:
# Pick your commit with 'e'
$ git rebase -i
# Perform as many removes as necessary
$ git rm project/code/file.txt
# amend the commit
$ git commit --amend
# continue with rebase
$ git rebase --continue
Самый простой способ, который я нашел, был предложен leontalbot
(в качестве комментария), это сообщение опубликовано Anoopjohn. Я думаю, что это стоит отдельного места в качестве ответа:
(Я преобразовал его в сценарий bash)
#!/bin/bash
if [[ $1 == "" ]]; then
echo "Usage: $0 FILE_OR_DIR [remote]";
echo "FILE_OR_DIR: the file or directory you want to remove from history"
echo "if 'remote' argument is set, it will also push to remote repository."
exit;
fi
FOLDERNAME_OR_FILENAME=$1;
#The important part starts here: ------------------------
git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
if [[ $2 == "remote" ]]; then
git push --all --force
fi
echo "Done."
Все заслуги Annopjohn
и leontalbot
за указание на это.
ПРИМЕЧАНИЕ
Имейте в виду, что сценарий не включает проверки, поэтому убедитесь, что вы не ошибаетесь и у вас есть резервная копия на случай, если что-то пойдет не так. У меня это сработало, но может не сработать в вашей ситуации. ИСПОЛЬЗУЙТЕ ЕГО ОСТОРОЖНО (перейдите по ссылке, если хотите знать, что происходит).
You should probably clone your repository first.
Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all
Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD
Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all
Хотя все ответы, кажется, относятся к треку ветвей фильтра, этот подчеркивает, как очистить ВСЕ ветки в вашей истории.
Определенно, git filter-branch
- это то, что вам нужно.
К сожалению, этого недостаточно для полного удаления filename.orig
из вашего репо, так как на него все еще можно ссылаться с помощью тегов, записей рефлога, пультов дистанционного управления и так далее.
Я также рекомендую удалить все эти ссылки, а затем вызвать сборщик мусора. Вы можете использовать сценарий git forget-blob
с веб-сайта это, чтобы сделать все это за один шаг.
git forget-blob filename.orig
Если вы хотите очистить последний коммит, я попробовал использовать git версии 2.14.3 (Apple Git-98):
touch empty
git init
git add empty
git commit -m init
# 92K .git
du -hs .git
dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake
# 5.1M .git
du -hs .git
git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now
# 92K .git
du -hs .git
git reflog expire --expire=now --all; git gc --prune=now
- очень плохая вещь. Если у вас не заканчивается место на диске, позвольте git garbage собрать эти коммиты через несколько недель.
Спасибо что подметил это. Мое репо было отправлено с множеством больших двоичных файлов, и репозиторий полностью копируется каждую ночь. Так что я просто хотел получить от этого все до мелочей;)
Связанный: Как удалить / удалить большой файл из истории коммитов в репозитории Git?.