Перейти к содержанию

Как удалить файл из репозитория Git, не удаляя его локально

Git стал широко используемой распределенной системой контроля версий. В этом руководстве рассмотрим, как удалить файл или каталог из репозитория Git, но сохранить его локальную копию.

Введение в проблему

Разберемся с проблемой на примере. Допустим, мы работаем над репозиторием Git myRepo:

$ ls -l
total 12
drwxr-xr-x 2 kent kent 60 May 12 23:00 logs/
-rw-r--r-- 1 kent kent 26 May 11 13:22 README.md
-rw-r--r-- 1 kent kent 21 May 11 13:22 some-file.txt
-rw-r--r-- 1 kent kent 16 May 12 22:40 user-list.txt

Мы склонировали репозиторий на локальный компьютер, и, как видно из вывода ls, есть три файла и каталог log в репозитории.

Предположим, что нужно удалить файл user-list.txt и каталог log из репозитория Git. Однако мы не хотим удалять их из нашей локальной рабочей копии.

Обычный сценарий: зафиксировали некоторые файлы или каталоги, а затем поняли, что должны игнорировать некоторые файлы. Поэтому удалим связанные файлы из репозитория, сохраним локальные копии и добавим соответствующие шаблоны в файл .gitignore, чтобы Git больше не отслеживал эти файлы.

Мы знаем, что команда git rm user-list.txt удалит файл из репозитория. Но она также удаляет локальный файл.

Конечно, можно переместить файл и каталог в другой каталог, отправить коммит, а затем скопировать их обратно в локальный рабочий каталог. Это решает проблему. Однако такой подход неэффективен, особенно если файл или каталог имеют большой размер.

Посмотрим, как решить эту проблему более эффективно.

Использование команды git rm –cached

Мы упоминали, что git rm FILE по умолчанию удалит файлы из индекса и локального рабочего дерева. Однако команда git rm предоставляет параметр –cached, позволяющий удалять файлы только из индекса репозитория и оставлять локальный файл нетронутым.

Далее попробуем с файлом user-list.txt сделать следующее:

$ git rm --cached user-list.txt
rm 'user-list.txt'

Как видно из приведенного выше вывода, файл user-list.txt был удален. Теперь выполним команду git status, чтобы убедиться в этом:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    user-list.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	user-list.txt

Как видим, user-list.txt «deleted». Кроме того, поскольку его локальная копия все еще существует, он помечен как «untracked» (неотслеживаемый). Аналогичным образом можно удалить каталог log. Однако, поскольку это каталог, нам нужно дополнительно передать параметр -r (рекурсивно) команде git rm:

$ git rm --cached -r logs
rm 'logs/server.log'

Теперь зафиксируем изменения:

$ git commit -m 'remove user-list.txt and logs'
[master ee8cfe8] remove user-list.txt and logs
 2 files changed, 4 deletions(-)
 delete mode 100644 logs/server.log
 delete mode 100644 user-list.txt

Затем проверим текущие подготовленные файлы с помощью команды git ls-files:

$ git ls-files -c
.gitignore
README.md
some-file.txt

Как видно из вывода, целевого файла и каталога больше нет. Кроме того, сохраняются локальные копии. Таким образом, мы решили проблему.

При желании можно добавить их в файл .gitignore, чтобы Git больше не мог их отслеживать.

Удаление всех файлов, определенных в .gitignore

Иногда нужно проверить индекс Git и удалить все файлы, определенные в .gitignore. Предположим, что мы закончили с нашим определением .gitignore. Тогда простым способом будет трехэтапный процесс:

  • сначала удалим все файлы из индекса: git rm -r –cached;
  • затем снова подготовим все файлы (файлы, определенные в .gitignore, будут автоматически игнорироваться: git add);
  • зафиксируем изменения: git commit -m git commit -m “a proper commit message”

В качестве альтернативы можно найти и удалить только те файлы, которые в настоящее время отслеживаются, но их следует игнорировать. Команда git ls-files может помочь найти файлы.

Давайте вернем нашу предыдущую фиксацию и снова удалим файл user-list.txt и каталог log. На этот раз сначала добавим их в файл .gitignore:

$ cat .gitignore
user-list.txt
logs/

Выясним, какие файлы хотели бы удалить из индекса Git:

$ git ls-files -i -c -X .gitignore
logs/server.log
user-list.txt

Как видим, приведенная выше команда перечислила промежуточные файлы, которые хотели удалить.

Теперь объединим команды git rm –cached и git ls-files, чтобы удалить их одним махом:

$ git rm --cached $(git ls-files -i -c -X .gitignore)
rm 'logs/server.log'
rm 'user-list.txt'

Стоит отметить, что команда удалит все файлы в каталоге log, поэтому она удалит пустой каталог log из индекса. В этом примере есть только один файл в каталоге log.

Теперь, если проверим оставленные файлы, удаленные исчезнут:

$ git ls-files -c
.gitignore
README.md
some-file.txt

И user-list.txt и logs/ все еще находятся в нашем локальном рабочем дереве:

$ ls -l
total 12
drwxr-xr-x 2 kent kent 60 May 13 00:45 logs/
-rw-r--r-- 1 kent kent 26 May 11 13:22 README.md
-rw-r--r-- 1 kent kent 21 May 11 13:22 some-file.txt
-rw-r--r-- 1 kent kent 16 May 13 00:45 user-list.txt

Удаленные файлы все еще находятся в истории Git

Мы решили проблему с помощью команды git rm –cached. Однако необходимо помнить, что мы просто удалили файл из индекса отслеживания Git. Мы по-прежнему можем видеть файл и его содержимое в истории коммитов Git. Например, мы все еще можем видеть содержимое user-list.txt, проверив предыдущий коммит:

$ git show 668fa2f user-list.txt
commit 668fa2f...
Author: ...
Date:   ...

    add user-list.txt and some-file.txt

diff --git a/user-list.txt b/user-list.txt
new file mode 100644
index 0000000..3da7fab
--- /dev/null
+++ b/user-list.txt
@@ -0,0 +1,3 @@
+kent
+eric
+kevin

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

Если это так, нужно удалить файлы из истории коммитов Git.

Заключение

В этой статье рассмотрели на примерах, как удалить файл или каталог из репозитория Git, но сохранить его локальную копию.

Оригинал