Git удалить файл из всех коммитов
При использовании репозитория иногда допускаются ошибки, в частности, в репозиторий могут быть добавлены лишние файлы — например врменные и объектные файлы. В следствии этого, объем репозитория увеличивается. Существующие системы хранения репозиториев имеют ограничения на объем памяти — Bitbucket предоставляет 2Гб, GitHub — 1 Гб. Кроме того, при клонировании проекта каждому разработчику придется скачать эти (ненужные) файлы.
В случае, если ошибка допущена в последнем коммите — проблему позволяет решить git commit --amend . Однако, если разработчик сделал серию коммитов, а затем — выполнил push, то отменить изменения привычными инструментами не получится.
Студенты-двоечники решают проблему путем создания нового репозитория (без лишних файлов), но тогда теряется история всех коммитов. Настоящий программист так делать не будет, а использует одну из двух утилит: git filter-branch или BFG Repo-Cleaner.
BFG Repo-Cleaner удобнее в управлении и работает быстрее, но имеет не все функции, которое есть у git filter-branch. Например, в BFG Repo-Cleaner нельзя удалить каталог, указать конкретный путь для удаления, а можно лишь только удалять файл(-ы) из всего репозитория.
Для экспериментов вы можете использовать как свой собственный репозиторий, так и использовать специально подготовленный. Будем полагать, что лишними файлами в нем являются картинки с расширением jpg в дирректории images.
Использование BFG Repo-Cleaner
Скачиваем BFG Repo-Cleaner с официального сайта. Поставляется он в виде jar-архива — поэтому для запуска нужно будет установить виртуальную машину Java.
Клонируем проблемный репозиторий с опцией --mirror , которая отображает локальные ветки источника в локальные ветки объекта, а также все ссылки (включая удаленные ветки, заметки и т.д.). Все эти ссылки перезаписываются по git remote update в целевом репозитории. Переходим в созданный каталог:
В этом каталоге не окажется файлов репозитория, но это полная копия базы данных git репозитория. Фрагмент содержимого каталога показан на скриншоте:
Для простоты, скопируем bfg-1.13.0.jar (у меня такая версия, у вас может быть другая) в текущую папку. Хотя можно в каждой команде указывать полный путь до BFG или добавить этот путь в переменную окружения PATH.
Для удаления всех файлов с расширением ‘*.jpg’ выполним команду:
java –jar bfg-1.13.0.jar –delete-files ‘*.jpg’ --no-blob-protection
Если не указать опцию —no-blob-protection, может возникнуть ошибка «содержимое выше может быть удалено из других коммитов, но так как «защищенные» коммиты все еще используют его, оно все еще будет существовать в вашем репозитории». Так как по умолчанию HEAD ветка защищена.
После завершения операции рекомендуется провести очистку от мусора (неиспользуемых объектов). Сделать это можно с помощью команд:
git reflog expire --expire=now --all && git gc --prune=now --aggressive
Первая команда отметит всю историю всех веток как подлежащую обработке сборщиком мусора. Происходит очистка логов. Вторая команда запускает сам сборщик. Используя reflog, можно вернуться в прошлое, отменяя почти любые действия, сделанные в Git.
Сохраним изменения в репозитории:
git push
Все файлы с расширением jpg были удалены из всех коммитов, также был удален каталог /images/ так как в нем не осталось файлов. Если бы там остался хоть один файл — каталог был бы сохранен.
Использование git filter-branch
Пример использования:
git filter-branch --prune-empty --index-filter "git rm -rf --cached --ignore-unmatch file1 dir1" HEAD
После выполнения команды файл file1 и каталог dir1 будут полностью удалены из истории проекта, опция --prune-empty также удалит образовавшиеся пустые коммиты, которые были связаны с этими файлами/каталогами.
Поскольку filter-branch формирует бэкап, для возможности отката, то для полной очистки потребуется удалить .git/refs/original/ , а затем выполнить очистку и оптимизацию репозитория с помощью git gc :
rm -rf .git/refs/original && git gc --aggressive
Следует учесть, что история проекта будет фактически переписана заново, а значит всем пользователям потребуется заново клонировать полученный репозиторий.
Теперь, в нашем примере лишними файлами, помимо картинок в каталоге images , будем считать лишним каталог folder1 , в котором хранятся файлы с расширением txt.
Клонируем репозиторий и переходим в его каталог:
Удаленим ненужные файлы:
Проведем очистку от мусора:
git reflog expire --expire=now --all && git gc --prune=now --aggressive
Отправим новую историю веток и тегов на сервер:
Сохраним изменения в репозитории:
git push
Резльтаты и полезные ссылки
Различия между BFG Repo-Cleaner и git filter-branch заключаются в том, что с помощью git filter-branch можно удалить файлы из заданного каталога, а также сам каталог, а с помощью BFG Repo-Cleaner нет. BFG Repo-Cleaner позволяет удалять файлы по имени без указания пути. Также BFG Repo-Cleaner требует установки java и скачивания jar файла.
BFG Repo-Cleaner удаляет сразу из всех веток, а в git filter-branch нужно указывать из каких веток нужно удалить.
За подготовку материалов выражаю благодарность Брюхановой Ульяне (своей студентке). На самом деле это ее работа.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
3 contributors
Users who have contributed to this file
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
Шпаргалка по консольным командам Git
Добавляйте свои команды и остальные полезности через Pull request .
Git — система контроля версий (файлов). Что-то вроде возможности сохраняться в компьютерных играх (в Git эквивалент игрового сохранения — коммит). Важно: добавление файлов к «сохранению» двухступенчатое: сначала добавляем файл в индекс ( git add ), потом «сохраняем» ( git commit ).
Любой файл в директории существующего репозитория может находиться или не находиться под версионным контролем (отслеживаемые и неотслеживаемые).
Отслеживаемые файлы могут быть в 3-х состояниях: неизменённые, изменённые, проиндексированные (готовые к коммиту).
Ключ к пониманию
Ключ к пониманию концепции git — знание о «трех деревьях»:
- Рабочая директория — файловая система проекта (те файлы, с которыми вы работаете).
- Индекс — список отслеживаемых git-ом файлов и директорий, промежуточное хранилище изменений (редактирование, удаление отслеживаемых файлов).
- Директория .git/ — все данные контроля версий этого проекта (вся история разработки: коммиты, ветки, теги и пр.).
Коммит — «сохранение» (хранит набор изменений, сделанный в рабочей директории с момента предыдущего коммита). Коммит неизменен, его нельзя отредактировать.
У всех коммитов (кроме самого первого) есть один или более родительских коммитов, поскольку коммиты хранят изменения от предыдущих состояний.
Простейший цикл работ
- Редактирование, добавление, удаление файлов (собственно, работа).
- Индексация/добавление файлов в индекс (указание для git какие изменения нужно будет закоммитить).
- Коммит (фиксация изменений).
- Возврат к шагу 1 или отход ко сну.
- HEAD — указатель на текущий коммит или на текущую ветку (то есть, в любом случае, на коммит). Указывает на родителя коммита, который будет создан следующим.
- ORIG_HEAD — указатель на коммит, с которого вы только что переместили HEAD (командой git reset . , например).
- Ветка ( master , develop etc.) — указатель на коммит. При добавлении коммита, указатель ветки перемещается с родительского коммита на новый.
- Теги — простые указатели на коммиты. Не перемещаются.
Перед началом работы нужно выполнить некоторые настройки:
Если вы в Windows:
Указание неотслеживаемых файлов
Файлы и директории, которые не нужно включать в репозиторий, указываются в файле .gitignore . Обычно это устанавливаемые зависимости ( node_modules/ , bower_components/ ), готовая сборка build/ или dist/ и подобные, создаваемые при установке или запуске. Каждый файл или директория указываются с новой строки, возможно использование шаблонов.
Длинный вывод в консоли: Vim
Вызов некоторых консольных команд приводит к необходимости очень длинного вывода в консоль (пример: вывод истории всех изменений в файле командой git log -p fileName.txt ). При этом прямо в консоли запускается редактор Vim. Он работает в нескольких режимах, из которых Вас заинтересуют режим вставки (редактирование текста) и нормальный (командный) режим. Чтобы попасть из Vim обратно в консоль, нужно в командном режиме ввести :q . Переход в командный режим из любого другого: Esc .
Если нужно что-то написать, нажмите i — это переход в режим вставки текста. Если нужно сохранить изменения, перейдите в командный режим и наберите :w .
I would like to put a Git project on GitHub but it contains certain files with sensitive data (usernames and passwords, like /config/deploy.rb for capistrano).
I know I can add these filenames to .gitignore, but this would not remove their history within Git.
I also don't want to start over again by deleting the /.git directory.
Is there a way to remove all traces of a particular file in your Git history?
11 Answers 11
For all practical purposes, the first thing you should be worried about is CHANGING YOUR PASSWORDS! It's not clear from your question whether your git repository is entirely local or whether you have a remote repository elsewhere yet; if it is remote and not secured from others you have a problem. If anyone has cloned that repository before you fix this, they'll have a copy of your passwords on their local machine, and there's no way you can force them to update to your "fixed" version with it gone from history. The only safe thing you can do is change your password to something else everywhere you've used it.
With that out of the way, here's how to fix it. GitHub answered exactly that question as an FAQ:
Note for Windows users: use double quotes (") instead of singles in this command
Update 2019:
This is the current code from the FAQ:
Keep in mind that once you've pushed this code to a remote repository like GitHub and others have cloned that remote repository, you're now in a situation where you're rewriting history. When others try pull down your latest changes after this, they'll get a message indicating that the changes can't be applied because it's not a fast-forward.
To fix this, they'll have to either delete their existing repository and re-clone it, or follow the instructions under "RECOVERING FROM UPSTREAM REBASE" in the git-rebase manpage.
Tip: Execute git rebase --interactive
In the future, if you accidentally commit some changes with sensitive information but you notice before pushing to a remote repository, there are some easier fixes. If you last commit is the one to add the sensitive information, you can simply remove the sensitive information, then run:
That will amend the previous commit with any new changes you've made, including entire file removals done with a git rm . If the changes are further back in history but still not pushed to a remote repository, you can do an interactive rebase:
That opens an editor with the commits you've made since your last common ancestor with the remote repository. Change "pick" to "edit" on any lines representing a commit with sensitive information, and save and quit. Git will walk through the changes, and leave you at a spot where you can:
For each change with sensitive information. Eventually, you'll end up back on your branch, and you can safely push the new changes.
I accidentally dropped a DVD-rip into a website project, then carelessly git commit -a -m . , and, zap, the repo was bloated by 2.2 gigs. Next time I made some edits, deleted the video file, and committed everything, but the compressed file is still there in the repository, in history.
I know I can start branches from those commits and rebase one branch onto another. But what should I do to merge the 2 commits so that the big file doesn't show in the history and is cleaned in the garbage collection procedure?
Please have also a look at my answer which uses git filter-repo . You should not longer use git filter-branch as it is very slow and often difficult to use. git filter-repo is around 100 times faster.
21 Answers 21
Use the BFG Repo-Cleaner, a simpler, faster alternative to git-filter-branch specifically designed for removing unwanted files from Git history.
Carefully follow the usage instructions, the core part is just this:
Any files over 100MB in size (that aren't in your latest commit) will be removed from your Git repository's history. You can then use git gc to clean away the dead data:
The BFG is typically at least 10-50x faster than running git-filter-branch , and generally easier to use.
@tony It's worth repeating the entire cloning & clearing procedure to see if the message asking you to pull re-occurs, but it's almost certainly because your remote server is configured to reject non-fast-forward updates (ie, it's configured to stop you from losing history - which is exactly what you want to do). You need to get that setting changed on the remote, or failing that, push the updated repo history to a brand new blank repo.
@RobertoTyley Thanks. I have tried it 3 different times and all resulted with the same message. So I'm also thinking that you're right about the remote server being configured to reject the non-fast-forward updates. I'll consider just pushing the updated repo to a brand new repo. Thank you!
@RobertoTyley Perfect, you save my time, thanks very much. By the way, maybe should do git push --force after your steps, otherwise the remote repo still not changed.
+1 to adding git push --force . Also worth noting: force pushes may not be allowed by the remote (gitlab.com doesn't, by default. Had to "unprotect" the branch).
What you want to do is highly disruptive if you have published history to other developers. See “Recovering From Upstream Rebase” in the git rebase documentation for the necessary steps after repairing your history.
You have at least two options: git filter-branch and an interactive rebase, both explained below.
Using git filter-branch
I had a similar problem with bulky binary test data from a Subversion import and wrote about removing data from a git repository.
Say your git history is:
Note that git lola is a non-standard but highly useful alias. With the --name-status switch, we can see tree modifications associated with each commit.
In the “Careless” commit (whose SHA1 object name is ce36c98) the file oops.iso is the DVD-rip added by accident and removed in the next commit, cb14efd. Using the technique described in the aforementioned blog post, the command to execute is:
- --prune-empty removes commits that become empty (i.e., do not change the tree) as a result of the filter operation. In the typical case, this option produces a cleaner history.
- -d names a temporary directory that does not yet exist to use for building the filtered history. If you are running on a modern Linux distribution, specifying a tree in /dev/shm will result in faster execution.
- --index-filter is the main event and runs against the index at each step in the history. You want to remove oops.iso wherever it is found, but it isn’t present in all commits. The command git rm --cached -f --ignore-unmatch oops.iso deletes the DVD-rip when it is present and does not fail otherwise.
- --tag-name-filter describes how to rewrite tag names. A filter of cat is the identity operation. Your repository, like the sample above, may not have any tags, but I included this option for full generality.
- -- specifies the end of options to git filter-branch
- --all following -- is shorthand for all refs. Your repository, like the sample above, may have only one ref (master), but I included this option for full generality.
After some churning, the history is now:
Notice that the new “Careless” commit adds only other.html and that the “Remove DVD-rip” commit is no longer on the master branch. The branch labeled refs/original/refs/heads/master contains your original commits in case you made a mistake. To remove it, follow the steps in “Checklist for Shrinking a Repository.”
For a simpler alternative, clone the repository to discard the unwanted bits.
Using a file:///. clone URL copies objects rather than creating hardlinks only.
Now your history is:
The SHA1 object names for the first two commits (“Index” and “Admin page”) stayed the same because the filter operation did not modify those commits. “Careless” lost oops.iso and “Login page” got a new parent, so their SHA1s did change.
Interactive rebase
With a history of:
you want to remove oops.iso from “Careless” as though you never added it, and then “Remove DVD-rip” is useless to you. Thus, our plan going into an interactive rebase is to keep “Admin page,” edit “Careless,” and discard “Remove DVD-rip.”
Running $ git rebase -i 5af4522 starts an editor with the following contents.
Executing our plan, we modify it to
That is, we delete the line with “Remove DVD-rip” and change the operation on “Careless” to be edit rather than pick .
Save-quitting the editor drops us at a command prompt with the following message.
As the message tells us, we are on the “Careless” commit we want to edit, so we run two commands.
The first removes the offending file from the index. The second modifies or amends “Careless” to be the updated index and -C HEAD instructs git to reuse the old commit message. Finally, git rebase --continue goes ahead with the rest of the rebase operation.
Минимизация ущерба
Итак, вы случайно закоммитили файл с конфиденциальной информацией. Назовём этот файл .env . Сразу после того, как это случилось, надо задать себе пару вопросов:
- Отправлен ли коммит в удалённый репозиторий?
- Является ли удалённый репозиторий общедоступным?
▍Коммит пока не отправлен в удалённый репозиторий
Файлы останутся в рабочей копии репозитория, вы сможете внести в проект необходимые изменения.
Если же вы хотите сохранить коммит и вам нужно просто удалить из него определённые файлы, тогда поступите так:
Параметр --amend можно использовать только для работы с самым свежим коммитом. Если вы, после неудачного коммита, добавили ещё несколько, воспользуйтесь такой командой:
▍Коммит отправлен в удалённый репозиторий
Если вы уже отправили коммит в удалённый репозиторий, то, в первую очередь, вам нужно знать о том, чем отличаются публичные и приватные репозитории.
Если ваш репозиторий является приватным, и при этом он не доступен ботам или людям, которым вы не доверяете, вы можете просто внести поправки в последний коммит, воспользовавшись парой вышеприведённых команд.
Если вы отправили в репозиторий, после проблемного коммита, и другие коммиты, это не помешает вам убрать файлы с конфиденциальными данными из истории Git, воспользовавшись командой git filter-branch или инструментом BFG Repo-Cleaner.
Вот пример использования git filter-branch :
Но, делая это, учитывайте два важных аспекта подобных изменений, вносимых в репозиторий:
- Вы меняете историю Git. Если на текущее состояние репозитория полагаются другие люди, если от этого состояния зависят какие-то ветки того же репозитория, его форки, открытые PR, то это нарушит их работу. В подобных случаях относитесь к репозиторию как к общедоступному и постарайтесь не вносить изменения в его историю.
- Вам нужно будет очистить кеш. Вам понадобится обратиться в службу поддержки платформы, на которой хранится ваш репозиторий, и попросить очистить его кеш. Несмотря на то, что вы исправили проблемный коммит или переписали историю репозитория, старый коммит, содержащий конфиденциальные данные, останется в кеше. Для того чтобы к нему обратиться, нужно будет знать его ID, но к нему можно будет получить доступ до тех пор, пока кеш не очистят.
Нужно ли создавать новые секретные ключи в том случае, если их актуальные версии попали в публичный репозиторий?
Если кратко ответить на вопрос, вынесенный в заголовок, то — нужно. Если ваш репозиторий общедоступен, или если вы, по любой причине, полагаете, что он — не место для хранения секретных данных, вам необходимо будет счесть попавшие в него конфиденциальные данные скомпрометированными.
Даже если вы удалили эти данные из репозитория, вы ничего не сможете сделать с ботами и с форками репозитория. Как же поступить?
- Деактивируйте все ключи или пароли. Это надо сделать в первую очередь. После того, как вы деактивируете ключи, конфиденциальные сведения, ушедшие в общий доступ, оказываются бесполезными.
- Настройте файл .gitignore . Сделайте в .gitignore записи о файлах с конфиденциальной информацией для того чтобы Git не отслеживал бы состояние этих файлов.
- Подготовьте коммит, в котором нет файлов с конфиденциальной информацией.
- Отправьте изменения в репозиторий, снабдите коммит пояснениями о возникшей ситуации. Не пытайтесь скрыть ошибку. Все программисты, работающие над проектом, включая вас, по достоинству оценят наличие в репозитории коммита с разъяснениями ситуации и с описанием того, что именно было исправлено с помощью данного коммита.
Рекомендации по хранению конфиденциальных файлов в проектах, в которых для контроля версий применяется Git
Для того чтобы не допустить утечек конфиденциальной информации стоит придерживаться следующих рекомендаций.
▍Храните секретные данные в файле .env (или в другом подобном файле)
Ключи к API и другие подобные сведения стоит хранить в единственном файле .env . При таком подходе, если Git не отслеживает состояние файла .env , вы, добавив в этот файл новый ключ, не отправите его случайно в репозиторий.
Ещё одно преимущество такого подхода заключается в том, что так у вас будет доступ ко всем ключам через глобальную переменную process .
▍Используйте, если это возможно, ключи API
Скомпрометированные ключи API легко деактивировать, такие ключи легко создать заново. Если это возможно — используйте именно их, а не нечто вроде логинов и паролей.
▍Храните ключи API, пользуясь средствами вашего инструмента для сборки проектов
Ключи API обычно нужны при сборке приложений. Инструменты для сборки проектов, вроде Netlify, позволяют держать ключи в защищённых хранилищах. Такие ключи автоматически внедряются в приложение с использованием глобальной переменной process .
Управление переменными окружения
▍Добавьте запись о файле .env в файл .gitignore
Сделайте так, чтобы Git не отслеживал бы файлы, содержащие конфиденциальную информацию.
▍Подготовьте шаблонный файл .env.template
Наличие подобного шаблонного файла помогает тем, кто работает над проектом, добавлять в проект ключи API, избавляя их от необходимости чтения документации.
▍Не меняйте историю Git в удалённых репозиториях
Постарайтесь строго придерживаться этого правила. Если вы следовали вышеприведённым рекомендациям, то историю Git вам менять и не потребуется.
Итоги
Надеюсь, мой материал поможет вам в безопасной работе с конфиденциальными данными.
А вам случалось отправлять в общедоступный репозиторий что-то такое, что туда попадать не должно?
Читайте также: