$ git push origin master:refs/heads/qa/master

Если хочется, чтобы Git автоматически делал так при вызове git push origin, можно добавить в конфигурационный файл значение для push:

[remote "origin"] url = git@github.com:schacon/simplegit-progit.git fetch = +refs/heads/*:refs/remotes/origin/* push = refs/heads/master:refs/heads/qa/master

Опять же, это приведёт к тому, что при вызове git push origin локальная ветка master будет по умолчанию отправляться в удалённую ветку qa/master.

Удаление ссылок

Кроме всего прочего, спецификации ссылок можно использовать следующим образом для удаления ссылок на удалённом сервере:

$ git push origin :topic

Так как спецификация ссылки задаётся в виде :, опускание означает, что указанную ветку на удалённом сервере надо сделать пустой, что приводит к её удалению.

Протоколы передачи

Git может передавать данные между репозиториями одним из двух основных способов: через HTTP или через "умные" протоколы для транспортов file://, ssh:// и git://. В данном разделе мы кратко рассмотрим как эти два протокола работают.

Тупой протокол

Git-транспорт работающий по HTTP часто называют "тупым" протоколом, потому что для его работы во время передачи данных не требуется исполнения никакого Git-специфичного кода на стороне сервера. Процесс извлечения данных представляет собой последовательность GET-запросов, клиент обращается к стандартной структуре каталогов Git. Давайте рассмотрим процесс получения данных по HTTP на примере библиотеки simplegit:

$ git clone http://github.com/schacon/simplegit-progit.git

Первое действие, выполняемое данной командой — загрузка файла info/refs. Данный файл записывается командой update-server-info, поэтому для использования HTTP-транспорта необходимо запускать эту команду в post-receive хуке:

=> GET info/refs ca82a6dff817ec66f44342007202690a93763949 refs/heads/master

Теперь у нас имеется список удалённых веток и их хеши. Далее, нам надо посмотреть куда ссылается HEAD, чтобы знать на какую версию переключиться после завершения работы команды.

=> GET HEAD ref: refs/heads/master

Нам надо переключиться на ветку master после завершения процесса. На данном этапе можно начать обход дерева. Начальной точкой является объект-коммит ca82a6, о чём мы узнали из файла info/refs, и мы начинаем с его загрузки:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949 (179 bytes of binary data)

Объект получен, он был в рыхлом формате на сервере, и мы получили его по HTTP используя статический GET-запрос. Теперь можно его разархивировать, отрезать заголовок и посмотреть на его содержимое:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949 tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon 1205815931 -0700 committer Scott Chacon 1240030591 -0700 changed the version number

Далее, необходимо загрузить ещё два объекта: cfda3b — объект-дерево, который обозначен как содержимое только что загруженного коммита, и 085bb3 — родительский коммит:

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 (179 bytes of data)

Так, мы получили следующий объект-коммит. Прихватим и наш объект-дерево:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf (404 - Not Found)

Ой! Похоже, этого объекта-дерева нет на сервере в рыхлом формате, поэтому мы получили ответ 404. У этого могут быть разные причины: объект в другом репозитории, или в упакованном файле текущего репозитория. Сперва Git проверяет список альтернативных репозиториев:

=> GET objects/info/http-alternates (empty file)

Если бы этот запрос вернул нам список альтернативных URL, Git обратился по ним в поиске "рыхлых" и pack-файлов — это такой механизм, позволяющий не дублировать данные проектам, являющимися форками друг для друга. Так как в данном случае альтернативных адресов нет, объект должен быть в pack-файле. Для того, чтобы узнать, какие упакованные файлы есть на сервере, необходимо загрузить файл со списком pack-файлов: objects/info/packs (который также генерируется update-server-info):

=> GET objects/info/packs P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

На сервере имеется только один pack-файл, поэтому объект точно там, но необходимо проверить индексный файл, чтобы в этом убедиться. Если бы на сервере было несколько pack-файлов, загрузив сначала индексы, мы смогли бы определить в каком именно pack-файле находится нужный нам объект:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx (4k of binary data)

Теперь, когда мы получили индекс упакованного файла, можно проверить, тут ли наш объект. Это возможно благодаря тому, что в индексе хранятся SHA-1 объектов содержащихся в pack-файле, а также их смещения. Необходимый объект там присутствует, так что продолжим и получим весь pack-файл:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack (13k of binary data)

Итак, мы получили наш объект-дерево, можно продолжить обход списка коммитов. Все они лежат внутри упакованного файла, который мы только что скачали, так что снова обращаться к серверу не надо. Git извлекает рабочую копию ветки master, на которую ссылается HEAD.

Полный вывод этого процесса выглядит так:

$ git clone http://github.com/schacon/simplegit-progit.git Initialized empty Git repository in /private/tmp/simplegit-progit/.git/ got ca82a6dff817ec66f44342007202690a93763949 walk ca82a6dff817ec66f44342007202690a93763949 got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Getting alternates list for http://github.com/schacon/simplegit-progit.git Getting pack list for http://github.com/schacon/simplegit-progit.git Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835 Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835 which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 walk a11bef06a3f659402fe7563abf99ad00de2209e6

Умный протокол

Методика работы HTTP проста, но неэффективна, поэтому чаще используются "умные" протоколы. Эти протоколы обслуживаются процессом на стороне сервера, который учитывает особенности работы Git — он считывает локальные данные, выясняет что есть и чего не хватает на клиенте и генерирует для него соответствующие данные. Существует два набора процессов передачи данных: процессы для загрузки данных и процессы для скачивания.

Загрузка данных

Для загрузки данных на удалённый сервер используются процессы send-pack и receive-pack. Процесс send-pack запускается на стороне клиента и подключается к receive-pack на стороне сервера.

Например, выполняется команда git push origin master и origin определён как URL использующий протокол SSH. Git запускает процесс send-pack, который устанавливает соединение с сервером по протоколу SSH. Он пытается запустить команду на удалённом сервере через вызов команды ssh, который выглядит следующим образом:

$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'" 005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs 003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic 0000

Команда git-receive-pack тут же посылает в ответ по одной строке на каждую из имеющихся в наличии ссылок — в данном случае только ветку master и её SHA. Первая строка также содержит список возможностей сервера (здесь это report-status и delete-refs).

Каждая строка начинается с 4-байтового шестнадцатеричного значения, содержащего длину оставшейся строки. Первая строка начинается с 005b, это 91 в 16-ричном виде, значит в этой строке ещё 91 байт. Следующая строка начинается с 003e, что означает 62, то есть надо прочитать 62 байта. Далее следует строка 0000, которая означает, что сервер закончил листинг своих ссылок.

Теперь, когда процесс send-pack выяснил состояние сервера, он определяет коммиты, которые есть локально, но которых нет на сервере. Для каждой ссылки, которая будет обновлена текущей командой push, процесс send-pack передаёт процессу receive-pack эти данные. Например, если мы обновляем ветку master, и добавляем ветку experiment, ответ send-pack будет выглядеть следующим образом:

0085ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status 00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment 0000

Значение SHA-1 из одних нулей означает, что раньше здесь ничего не было — так получилось из-за того, что мы добавили новую ссылку experiment. Если бы мы удаляли ссылку, было бы на оборот: одни нули были бы справа.

Git отправляет строку для каждой ссылки, для которой производится обновление. В строке содержится старый хеш, новый хеш и имя обновляемой ссылки. Первая строка также содержит возможности клиента. Далее, клиент загружает упакованный файл со всеми объектами, которых ещё нет на сервере. В конце, сервер отвечает статусным сообщением сообщающем об успехе (или ошибке):

000Aunpack ok

Скачивание данных

Если выполняется скачивание данных, используются процессы fetch-pack и upload-pack. Клиент запускает процесс fetch-pack, который подключающийся к процессу upload-pack на удалённой машине для определения, какие данные будут переданы.

Существуют разные способы запуска upload-pack на удалённом репозитории. Можно запустить его по SSH так же, как и receive-pack. Ещё можно вызвать процесс через Git-демон, по умолчанию принимающий соединения на порте 9418. Процесс fetch-pack после подключения отправляет демону данные примерно следующего вида:

003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0

Начальные 4 байта задают размер последующих данных, далее следует команда, которую следует запустить, завершаемая нулевым байтом, а потом имя сервера и последний нулевой байт. Git-демон проверяет возможность выполнения команды, а также, что репозиторий существует и имеет необходимые права доступа. Если всё хорошо, демон запускает процесс upload-pack и передаёт запрос ему.

Если извлечение данных производится по SSH, fetch-pack выполняет другие действия:

$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"

В обоих случаях, после того как fetch-pack подключится, upload-pack передаст обратно следующее:

0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \ side-band side-band-64k ofs-delta shallow no-progress include-tag 003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master 003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic 0000

Это очень похоже на ответ receive-pack, но только возможности другие. В добавок upload-pack отсылает обратно ссылку HEAD, чтобы клиент понимал, на какую ветку переключиться, если выполняется клонирование.

На данном этапе процесс fetch-pack смотрит на объекты, имеющиеся в наличии и для недостающих объектов отвечает словом "want" и за ним SHA объекта. Для уже имеющихся объектов процесс отправляет их хеши со словом "have". В конце списка он пишет "done", и это даёт понять процессу upload-pack, что пора начинать отправлять упакованный файл с необходимыми данными:

0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta 0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 0000 0009done

Это самый основной случай передачи данных. В более сложных случаях, клиент поддерживает функции multi_ack или side-band, но этот пример иллюстрирует основные взаимодействия используемые процессами умного протокола.

Обслуживание и восстановление данных

Иногда, требуется выполнить очистку — сделать репозиторий более компактным, почистить импортированный репозиторий, или восстановить потерянную работу. Данный раздел охватывает некоторые из этих сценариев.

Обслуживание

Иногда Git сам выполняет команду запускающую автоматический сборщик мусора. Чаще всего, эта команда ничего не делает. Однако, если неупакованных объектов слишком много, или у вас слишком много pack-файлов, Git запускает полноценную команду git gc. Здесь gc это сокращение от "garbage collect", что означает "сборка мусора". Эта команда выполняет несколько действий: собирает все объекты в рыхлом формате и упаковывает их в pack-файлы, объединяет несколько упакованных файлов в один большой, удаляет объекты недостижимые ни из одного коммита и те, которые хранятся дольше нескольких месяцев.

Вы также можете запустить сборку мусора вручную:

$ git gc --auto

Опять же, как правило, эта команда ничего не делает. Необходимо иметь 7000 несжатых объектов или более 50 упакованных файлов, чтобы запустился настоящий gc. Данные пределы можно изменить с помощью параметров gc.auto и gc.autopacklimit в конфигурационном файле.

Другое действие, выполняемое gc — упаковка ссылок в единый файл. Предположим, репозиторий содержит следующие ветки и теги:

$ find .git/refs -type f .git/refs/heads/experiment .git/refs/heads/master .git/refs/tags/v1.0 .git/refs/tags/v1.1

Если выполнить git gc, данные файлы в каталоге refs перестанут существовать. Git перенесёт их в файл .git/packed-refs в угоду эффективности. Файл будет иметь следующий вид:

$ cat .git/packed-refs # pack-refs with: peeled cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0 9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1 ^1a410efbd13591db07496601ebc7a059dd55cfe9

При обновлении ссылки, Git не будет редактировать этот файл, а добавит новый файл в refs/heads. Для получения хеша для нужной ссылки, Git сначала проверит наличие ссылки в каталоге refs, а к файлу packed-refs обратится только в случае неудачи. Однако, если в каталоге refs файла нет, скорее всего, он в packed-refs.

Заметьте, последняя строка файла начинается с ^. Это означает, что метка непосредственно над ней является аннотированной и данная строка это коммит, на который аннотированная метка указывает.

Восстановление данных

В какой-то момент при работе с Git, вы нечаянно можете потерять коммит. Как правило, такое случается, когда вы удаляете ветку, в которой находились некоторые наработки, а потом оказывается, что они всё-таки были нужными. Либо вы жёстко сбросили ветку, тем самым отказавшись от коммитов, которые теперь понадобились. Как же в таком случае заполучить свои коммиты обратно?

Рассмотрим пример, в котором жёстко сбросим ветку master в тестовом репозитории на какой-нибудь более ранний коммит и затем восстановим потерянные коммиты. Для начала, рассмотрим в каком состоянии находится репозиторий на данном этапе:

$ git log --pretty=oneline ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit 484a59275031909e19aadb7c92262719cfcdf19a added repo.rb 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Теперь сдвинем ветку master на несколько коммитов назад:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9 HEAD is now at 1a410ef third commit $ git log --pretty=oneline 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Итак, теперь два последних коммита по-настоящему потеряны — они не достижимы ни из одной ветки. Необходимо найти SHA последнего коммита и создать ветку, указывающую на него. Сложность в том, чтобы найти этот самый SHA последнего коммита, ведь вряд ли вы его запомнили, да?

Зачастую, самый быстрый способ — использовать инструмент под названием git reflog. Во время вашей работы, Git записывает все изменения HEAD. Каждый раз при переключении веток и коммите, добавляется запись в reflog. Также обновление производится при вызове git update-ref, это, в частности, является причиной необходимости использования этой команды вместо прямой записи значения хеша в ref-файл, как было рассмотрено в разделе "Ссылки в Git" в этой главе. Итак, изменения HEAD в хронологическом порядке можно увидеть, вызвав git reflog:

$ git reflog 1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEAD ab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD

Здесь мы видим два коммита, на которых мы когда-то находились, однако информации не так много. Более интересный вывод можно получить, используя git log -g, что даст стандартный вывод лога для записей из reflog:

$ git log -g commit 1a410efbd13591db07496601ebc7a059dd55cfe9 Reflog: HEAD@{0} (Scott Chacon ) Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:22:37 2009 -0700 third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b Reflog: HEAD@{1} (Scott Chacon ) Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:15:24 2009 -0700 modified repo a bit

Похоже, что нижний коммит это тот, который мы потеряли, и он может быть восстановлен созданием ветки, указывающей на него. Например, создадим ветку с именем recover-branch, указывающую на этот коммит (ab1afef):

$ git branch recover-branch ab1afef $ git log --pretty=oneline recover-branch ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit 484a59275031909e19aadb7c92262719cfcdf19a added repo.rb 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Здорово, теперь у нас есть ветка recover-branch, указывающая туда, куда ранее указывала master, и потерянные коммиты вновь доступны. Теперь, положим, потерянная ветка по какой-то причине не попала в reflog, для этого удалим восстановленную ветку и весь reflog. Теперь два первых коммита недоступны ниоткуда:

$ git branch -D recover-branch $ rm -Rf .git/logs/

Теперь данные из .git/logs/ удалены, а значит и reflog больше нет, так как все его данные находились там. Как восстановить коммиты теперь? Один способ — использовать утилиту git fsck, проверяющую базу на целостность. Если выполнить её с ключом --full, будут показаны все объекты недостижимые из других объектов:

$ git fsck --full dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4 dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9 dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

В данном случае потерянный коммит указан после слов "dangling commit" (dangling commit в пер. с англ. — "висячий" коммит). Его можно восстановить аналогичным образом, добавив ветку, указывающую на данный хеш.

Удаление объектов

У Git есть много замечательных особенностей, но одна из них способна вызвать проблемы — команда git clone загружает проект вместе со всей историей включая все версии всех файлов. Это нормально, если в репозитории хранится только исходный код, так как Git хорошо оптимизирован под такой тип данных и может эффективно сжимать их. Однако, если когда-либо в проект был добавлен большой файл, каждый кто потом захочет клонировать проект будет вынужден скачивать этот большой файл, даже если он был удалён в следующем же коммите. Он будет в базе всегда просто потому, что он доступен в истории.

Это может стать огромной проблемой при конвертации репозиториев Subversion или Perforce в Git. В данных системах вам не нужно загружать всю историю, поэтому добавление больших бинарных файлов не имеет там особых последствий. Если при импорте из другой системы или при каких-либо других обстоятельствах стало ясно, что ваш репозиторий намного больше, чем он должен быть, то как раз сейчас мы расскажем как можно найти и удалить большие объекты.

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

Для примера, добавим большой файл в свой тестовый репозиторий, удалим его в следующем коммите, а потом найдём и удалим его полностью из базы. Для начала добавим большой файл в нашу историю:

$ curl http://kernel.org/pub/software/scm/git/git-1.6.3.1.tar.bz2 > git.tbz2 $ git add git.tbz2 $ git commit -am 'added git tarball' [master 6df7640] added git tarball 1 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 git.tbz2

Упс, кажется, этот огромный архив нам в проекте не нужен. Избавимся от него:

$ git rm git.tbz2 rm 'git.tbz2' $ git commit -m 'oops - removed large tarball' [master da3f30d] oops - removed large tarball 1 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 git.tbz2

Теперь "соберём мусор" в базе и узнаем её размер:

$ git gc Counting objects: 21, done. Delta compression using 2 threads. Compressing objects: 100% (16/16), done. Writing objects: 100% (21/21), done. Total 21 (delta 3), reused 15 (delta 1)

Чтобы быстро узнать сколько у нас занято места, можно воспользоваться командой count-objects:

$ git count-objects -v count: 4 size: 16 in-pack: 21 packs: 1 size-pack: 2016 prune-packable: 0 garbage: 0

Запись size-pack — это размер упакованных файлов в килобайтах, то есть всего занято 2 MБ. Перед последним коммитом, использовалось около 2 КБ, то есть, удаление файла не удалило его из истории. Из-за того, что мы однажды случайно добавили большой файл, при каждом клонировании этого репозитория каждому человеку придётся скачивать все эти 2 МБ, только для того, чтобы получить этот крошечный проект. Попробуем избавиться от этого файла.

Сперва найдём его. В данном случае, мы знаем, что это за файл. Но если бы не знали, как можно было бы определить, какие файлы занимают много места? При вызове git gc все объекты упаковываются в один файл, несмотря на это определить самые крупные файлы можно запустив служебную команду git verify-pack, и отсортировав её вывод по третьей колонке, в которой записан размер файла. К тому же, так как нас интересуют только самые крупные файлы, оставим только последние несколько строк, направив вывод команде tail:

$ git verify-pack -v .git/objects/pack/pack-3f8c0...bb.idx | sort -k 3 -n | tail -3 e3f094f522629ae358806b17daf78246c27c007b blob 1486 734 4667 05408d195263d853f09dca71d55116663690c27c blob 12908 3478 1189 7a9eb2fba2b1811321254ac360970fc169ba2330 blob 2056716 2056872 5401

Большой объект в самом внизу, его размер — 2 МБ. Для того, чтобы узнать, что это за файл, воспользуемся командой rev-list, которая уже упоминалась в главе 7. Если передать ей ключ --objects, то она выдаст хеши всех коммитов, а также хеши объектов и соответствующие им имена файлов. Воспользуемся этим для определения имени выбранного объекта:

$ git rev-list --objects --all | grep 7a9eb2fb 7a9eb2fba2b1811321254ac360970fc169ba2330 git.tbz2

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

$ git log --pretty=oneline -- git.tbz2 da3f30d019005479c99eb4c3406225613985a1db oops - removed large tarball 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 added git tarball

Необходимо переписать все коммиты, начиная с 6df76 для полного удаления данного файла. Для этого воспользуемся командой filter-branch, которая приводилась в главе 6:

$ git filter-branch --index-filter \ 'git rm --cached --ignore-unmatch git.tbz2' -- 6df7640^.. Rewrite 6df764092f3e7c8f5f94cbe08ee5cf42e92a0289 (1/2)rm 'git.tbz2' Rewrite da3f30d019005479c99eb4c3406225613985a1db (2/2) Ref 'refs/heads/master' was rewritten

Опция --index-filter похожа на --tree-filter использовавшуюся в главе 6, за исключением того, что вместо передачи команды, модифицирующей файлы на диске, мы используем команду, изменяющую файлы в индексе. Вместо удаления файла чем-то вроде rm file, стоит сделать это командой git rm --cached, так как нам надо удалить файл из индекса, а не с диска. Причина, по которой мы делаем именно так — скорость. Нет необходимости извлекать каждую ревизию на диск, чтобы применить фильтр, а это может очень сильно ускорить процесс. Можете использовать и tree-filter для получения аналогичного результата, если хотите. Опция --ignore-unmatch команды git rm отключает вывод сообщения об ошибке в случае отсутствия файлов, соответствующих шаблону. И последнее, команда filter-branch переписывает историю начиная с коммита 6df7640, потому что мы знаем, что именно с этого коммита появилась проблема. По умолчанию перезапись начинается с самого первого коммита, что потребовало бы гораздо больше времени.

Теперь наша история не содержит ссылок на данный файл. Однако, в reflog и в новом наборе ссылок, добавленном Git'ом в .git/refs/original после выполнения filter-branch, ссылки на него всё ещё присутствуют. Поэтому необходимо их удалить, а потом переупаковать базу. Необходимо избавиться от всех возможных ссылок на старые коммиты перед переупаковкой:

$ rm -Rf .git/refs/original $ rm -Rf .git/logs/ $ git gc Counting objects: 19, done. Delta compression using 2 threads. Compressing objects: 100% (14/14), done. Writing objects: 100% (19/19), done. Total 19 (delta 3), reused 16 (delta 1)

Посмотрим, сколько места удалось сохранить:

$ git count-objects -v count: 8 size: 2040 in-pack: 19 packs: 1 size-pack: 7 prune-packable: 0 garbage: 0

Размер упакованного репозитория сократился до 7 КБ, что намного лучше, чем 2 МБ. Из значения поля size видно, что большой объект всё ещё хранится в одном из ваших "рыхлых" объектов, но, что самое важное, при любой последующей отправке данных наружу и в том числе при клонировании он передаваться не будет. Если очень хочется, можно удалить его навсегда локально, выполнив git prune --expire.

Итоги

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

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

Загрузка...