Команда git prune ("prune" переводится как "обрезка") является внутренней служебной утилитой, которая очищает недоступные (unreachable) или "осиротевшие" (orphaned) объекты (ветви репозитория) Git. Недоступными (unreachable) являются объекты, которые недоступны по любым ссылкам. Любая фиксация (commit) к которой нельзя получить доступ через branch или tag, считается unreachable. Команда git prune обычно не запускается напрямую. Prune считается командой сбора мусора, и является дочерней для команды git gc.
[Обзор git prune]
Чтобы лучше понять эффект от применения git prune, нам нужно смоделировать сценарий, в котором commit стал недоступным. Следующая последовательность команд симулирует такую ситуацию.
~ $ cd git-prune-demo/
~/git-prune-demo $ git init .
Initialized empty Git repository in /Users/kev/Dropbox/git-prune-demo/.git/
~/git-prune-demo $ echo "hello git prune" > hello.txt
~/git-prune-demo $ git add hello.txt
~/git-prune-demo $ git commit -am "added hello.txt"
Показанная выше последовательность команд создаст новый репозиторий git-prune-demo. Один commit содержит добавление нового файла hello.txt в репозиторий, с содержимым "hello git prune". После этого немного изменим этот файл, и создадим новый commit с этой модификацией.
~/git-prune-demo $ echo "this is second line txt" >> hello.txt
~/git-prune-demo $ cat hello.txt
hello git prune
this is second line txt
~/git-prune-demo $ git commit -am "added another line to hello.txt"
[main 5178bec] added another line to hello.txt
1 file changed, 1 insertion(+)
Команда git log покажет в нашем демонстрационном репозитории историю из двух фиксаций:
~/git-prune-demo $ git log
commit 5178becc2ca965e1728554ce1cb8de2f2c2370b1
Author: kevzettler < kevzettler@gmail.com>
Date: Sun Sep 30 14:49:59 2018 -0700
added another line to hello.txt
commit 994b122045cf4bf0b97139231b4dd52ea2643c7e
Author: kevzettler < kevzettler@gmail.com>
Date: Sun Sep 30 09:43:41 2018 -0700
added hello.txt
Вывод команды git log покажет 2 commit-а и их соответствующие сообщения, касающиеся добавления файла hello.txt и его изменения. На следующем шаге сделаем один из commit-ов недоступным (unreachable) с помощью команды git reset. Мы сбросим состояние репозитория к первому commit-у, "added hello.txt":
~/git-prune-demo $ git reset --hard 994b122045cf4bf0b97139231b4dd52ea2643c7e
HEAD is now at 994b122 added hello.txt
Теперь, если мы снова запустим git log для проверки состояния репозитория, то увидим, что остался только один commit:
~/git-prune-demo $ git log
commit 994b122045cf4bf0b97139231b4dd52ea2643c7e
Author: kevzettler < kevzettler@gmail.com>
Date: Sun Sep 30 09:43:41 2018 -0700
added hello.txt
Теперь наш демонстрационный репозиторий содержит отсоединенную фиксацию (detached commit). Второй сделанный нами commit с сообщением "added another line to hello.txt" больше не отображается командой git log, он является отсоединенным. Может показаться, что мы потеряли, или удалили этот commit, однако Git очень строг с удалением истории. Мы можем убедиться, что он все еще доступен, но отключен (detached), используя команду git checkout:
~/git-prune-demo $ git checkout 5178becc2ca965e1728554ce1cb8de2f2c2370b1
Note: checking out '5178becc2ca965e1728554ce1cb8de2f2c2370b1'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b < new-branch-name>
HEAD is now at 5178bec... added another line to hello.txt
~/git-prune-demo $ git log
commit 5178becc2ca965e1728554ce1cb8de2f2c2370b1
Author: kevzettler < kevzettler@gmail.com>
Date: Sun Sep 30 14:49:59 2018 -0700
added another line to hello.txt
commit 994b122045cf4bf0b97139231b4dd52ea2643c7e
Author: kevzettler < kevzettler@gmail.com>
Date: Sun Sep 30 09:43:41 2018 -0700
added hello.txt
Когда мы переключаемся (checkout) на отключенный commit, Git достаточно продвинут, чтобы сообщить, что мы находимся в отключенном состоянии (detached state). Если мы снова запустим git log, увидим, что commit с сообщением "added another line to hello.txt" все еще присутствует в репозитории.
Сейчас наш тестовый репозиторий находится в правильном состоянии для практической проверки действия команды git prune. Сначала вернемся к основной ветви с помощью команды git checkout:
~/git-prune-demo $ git checkout main
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
5178bec added another line to hello.txt
If you want to keep it by creating a new branch, this may be a good time
to do so with:
git branch < new-branch-name> 5178bec
Switched to branch 'main'
При возврате к ветке main командой git checkout, Git снова достаточно умен, чтобы напомнить нам, что позади остался отсоединенный commit. Теперь давайте попробуем отрезать (prune) этот отсоединенный commit, для этого запустим команду git prune, передав ей некоторые опции: --dry-run и --verbose покажут, что может быть подвернуто prune, без выполнения этого реального действия.
~/git-prune-demo $ git prune --dry-run --verbose
Вывод у этой команды скорее всего окажется пустым. Пустой вывод подразумевает, что на самом деле ничего не было удалено. Почему это могло произойти? Скорее всего ветка не была полностью отключена. Git где-нибудь все еще сохраняет ссылку на неё. Это основной пример, почему git prune не предназначена для использования отдельно, без gc. Это также отличный пример, как сложно потерять какие-либо данные в репозитории Git.
Скорее всего Git хранит ссылку на наш detached commit в reflog. Мы можем проверить это путем запуска команды git reflog. Мы должны увидеть некоторый вывод, описывающий последовательность действий, которые были предприняты для достижения текущего состояния (более подробно про команду git reflog см. [2]). В дополнение к сохранению истории в reflog, у Git есть внутренняя дата истечения срока, после которых будут вырезаться (prune) отсоединенные commit-ы. И опять-таки, это все подробности реализации обработки git gc, и команда git prune не должна использоваться отдельно.
Чтобы завершить нашу симуляцию git prune, мы должны очистить reflog:
~/git-prune-demo $ git reflog expire --expire=now --expire-unreachable=now --all
Показанная выше команда принудительно сделает истекшими (expire) все записи reflog, которые старше текущего времени (now). Это жесткая и опасная команда, которую никогда не запускают в повседневном применении Git. Мы выполнили эту команду только для демонстрации успешных действий git prune. С тотально очищенным reflog мы можем теперь выполнить git prune.
~/git-prune-demo $ git prune --dry-run --verbose --expire=now
1782293bdfac16b5408420c5cb0c9a22ddbdd985 blob
5178becc2ca965e1728554ce1cb8de2f2c2370b1 commit
a1b3b83440d2aa956ad6482535cbd121510a3280 commit
f91c3433eae245767b9cd5bdb46cd127ed38df26 tree
Эта команда должна вывести список ссылок на объекты (Git SHA object references), как показано на примере выше.
[Использование git prune]
-n, или --dry-run Тестирование prune. Никаких действий не производится, просто будет выведен список операций обрезки - что сделала бы prune без этой опции.
-v, или --verbose Вывод объектов и действий, выполняемых prune.
--progress Выводит индикатор прогресса выполняемых действий.
--expire=time Принудительное устаревание объектов, которые старее указанного времени.
< head>... При указании будут сохранены все параметры из этой ссылки (ref) на заголовок (head).
[FAQ]
Q01. В чем отличие между git prune, git fetch --prune и git remote prune?
Команды git remote prune и git fetch --prune выполняют одинаковые действия: удаляют ссылки на ветви, которые не существуют в remote-репозитории. Это очень полезное действие при работе в команде, когда ветки remote-репозитория удаляются после слияния с основной веткой проекта (main branch). Вторая команда, git fetch --prune, подключится к remote-репозиторию и перед обрезкой получает его последнее состояние. По сути это комбинация команд:
git fetch --all && git remote prune
Обычная команда git prune работает полностью по-другому. Как уже обсуждалось выше, git prune будет удалять локальные отсоединенные фиксации (locally detached commits).
Q02. Как очистить устаревшие ветки?
Команда git fetch --prune самое лучшее средство для очистки устаревших веток. Она подключится к общему сетевому репозиторию (shared remote repository) и извлечет все ссылки на remote-ветки. Затем она удалит remote-ссылки, которые больше не используются в remote-репозитории.
Q003. Удаляет ли git remote prune origin локальную ветку?
Нет, git remote prune origin обрезает ссылки только на remote-ветки, которые больше не существуют. Git хранит как локальные, так и remote-ссылки. В репозитории содержатся наборы ссылок local/origin и remote/origin. Команда git remote prune origin обрезает только ссылки remote/origin. Это безопасно для локальной работы, потому что локальные ссылки (local/origin) сохраняются.
Подведем итоги. Команда git prune предназначена для использования как дочерняя команда для очистки мусора командой git gc. Весьма нежелательно, чтобы вы использовали команду git prune в ежедневной работе. Для полного понимания эффектов git prune полезно хорошо разобраться в назначении других команд: git log, git reflog и git checkout (их описание можно найти на сайте atlassian.com).
[Ссылки]
1. Git Prune site:atlassian.com. 2. git reflog site:atlassian.com. 3. What are the differences between git remote prune, git prune, git fetch --prune, etc site:stackoverflow.com. 4. Краткий справочник по Git. |