Всё, что нужно знать про Git
Доказано (опытом), что для успешной работы с Git требуется понимание структуры его объектной базы данных. К счастью, это очень просто. (А про то, чем Git интересен, я уже писал раньше.)
Часть первая из трёх: объекты
Git-репозиторий состоит из набора объектов, лежащих в папке .git/objects
. Каждый объект хранится в файле, названном в честь SHA-1 хеша его содержимого. Например, объект 55f6209f867f37598cfb56832adf74bee2921c3f
лежит в файле .git/objects/55/f6209f867f37598cfb56832adf74bee2921c3f
.
Есть четыре вида объектов (blob, tree, commit и tag), о которых мы сейчас поговорим.
1. Все файлы вашего проекта — это blob'ы. Возьмём каждый файл, припишем к нему в начало тип "blob" и размер, посчитаем SHA-1 и запишем это в репозиторий. (Имя файла в blob не входит, так что одинаковые по содержимому файлы окажутся записанными только один раз.)
2. Каждая директория проекта — это объект tree. Он ссылается на другие деревья и blob'ы, а также указывает их имена и атрибуты. Вот пример содержимого объекта tree:
100644 blob 6fccfdcbd0a1cdbf3bf2a5960e601b369c0a921c .gitignore 040000 tree 824b4bd4a0df733b5b5106b1b55c3630566759dc com.yoursway.sadr.core 040000 tree 2406b80d9623067f92db7f60d6eeabfed6966a9f com.yoursway.sadr.engine 040000 tree d1bae2405388774e484cfd3779adf7e1eda1f5d9 com.yoursway.sadr.python.core 040000 tree f3d282a4b963183fefdca6ff99a5995953a9c33e com.yoursway.sadr.python.idioms.core 100755 blob 14cf98b8521eb27ae556d9b707290e177b337ff4 propagate-settings-from-core
3. Наконец, объекты commit образуют историю проекта. Коммит ссылается на tree корневой директории проекта, а также имеет родителя или родителей (которых несколько в случае merge'а). Вот пример объекта commit:
tree fdb267652d8d669e86ad0df61afc81b92a7ce680 parent b95b2ca7e9854898d62fb028f2e6135b3439240d author Andrey Tarantsov1223355822 +0700 committer Andrey Tarantsov 1223355822 +0700 Huge performance fix: findGoalStateByGoal is now DFS. findGoalStateByGoal used to traverse every path is the graph and thus was very slow (as slow as 500 ms travesing 3M goals on every subgoal creation!) Changed to a real DFS.
Смотрим содержимое репозитория. Пусть последний коммит проекта имеет, например, имя c889ffc87048cca59f908d3e48becb59a14ce950. Посмотреть его содержимое мы можем, введя команду
git cat-file commit c889ffc87048cca59f908d3e48becb59a14ce950
,git ls-tree fdb267652d8d669e86ad0df61afc81b92a7ce680
git cat-file tree
выдала бы бинарный мусор, а вот вывод ls-tree
вы видели выше). Наконец, содержимое файла смотрится, опять же, командой
git cat-file blob 6fccfdcbd0a1cdbf3bf2a5960e601b369c0a921c
.Следует понимать, что вместо команды git cat-file
вы можете просто раззиповать нужный файл из .git/objects
. (Перед данными файла там окажется префикс из типа объекта и его размера.) Создать объект в репозитории можно командой git hash-object -t тип -w --stdin
.
Часть вторая из трёх: ссылки
Как узнать, что коммит c889ff… является последним коммитом проекта? Для этого Git еще хранит так называемые ссылки (references). Например, в файле .git/refs/heads/master
в текстовом виде хранится хеш последнего коммита на бранче master.
Ссылки могут ссылаться друг на друга. Например, ссылка .git/HEAD
обычно имеет такое содержимое:
ref: refs/heads/master
Набрав команду git rev-parse HEAD
, можно узнать, на какой именно коммит указывает ссылка с данным именем. Ссылки можно использовать везде вместо названий коммитов, например, можно набрать git cat-file commit refs/heads/master
. Имена ссылок можно сокращать, отбрасывая слева куски пути, если это не вызывает неоднозначности. Например, можно написать git cat-file commit master
. (Кстати, в мане rev-parse
описаны все способы указания коммитов в Git.)
Часть третья из трёх: индекс
Между вашей рабочей директорией и репозиторием есть промежуточное звено, называемое индексом (index). Все изменения, которые вы хотите закоммитить, вам нужно положить в индекс командой git add
, а потом по индексу создать коммит командой git commit
.
Хозяйке на заметку. На практике я всегда пользуюсь командой
git commit --inter -v
, которая запускает интерактивный add (git add -i
), а потом сразу делает коммит, причем (опция-v
) в редакторе показывает мне diff всего, что я собираюсь закоммитить.
Что такое индекс? Индекс похож на дерево (tree), но отличается от него тремя вещами:
Во-первых, индекс хранит ссылки на блобы файлов всех поддиректорий проекта.
Во-вторых, индекс хранит i-node'ы файлов — информацию, позволяющую быстро определять, изменился ли файл в рабочей директории по сравнению с индексом.
В-третьих, при наличии конфликтов merge'а индекс может хранить ссылки на три блоба для каждого файла (базовая версия, "их" версия и "ваша" версия).
Почему вам нужно знать про индекс? Во-первых, потому, что команда commit
закоммит не то содержимое файлов, которое лежит в рабочей директории, а то содержимое, которое было добавлено в индекс командой git add
. Во-вторых, потому, что при разрешении конфликтов merge'а вам может захотеться вытащить эти альтернативные версии файлов (git ls-files -u
покажет вам файлы из индекса, имеющие конфликты, т.е. больше одной версии).
Вот и всё
На манипуляции с объектами, ссылками и индексом строится весь Git. Чаще всего удобно полагаться на неё для выполнения нужных операций, но иногда можно вносить желаемые изменения руками.
Важно понимать, что Git не хранит дельты, метаинформацию о переименованиях файлов или что-либо еще, не описанное выше. Всё, что хранит Git — это копии состояния файлов проекта в разные моменты времени.
Git умеет хранить объекты и ссылки компактно в одном файле (которые называются pack'ами), сжимая их чем-то вроде LZW, причем размещая данные в таком порядке, что получается еще эффективнее, чем хранить дельты. Git умеет восстанавливать данные о переименованиях файлов, перемещениях и копированиях кусков файлов уже во время отображения истории.
Как изучать дальше?
Все команды подробно описаны в man pages, которые можно также читать на сайте. Важно понимать структуру объектной базы данных. Читайте Git tutorial (не забудьте прочесть вторую его часть) и описывающий всё-всё-всё Git user manual. Наконец, естественно, есть видеолекция Linus Torvalds on Git (Линус, объясняющий, что такое мастурбация — смотреть всем!)
Далее я попробую резюмировать то, что вам стоит узнать.
Лучше всего избегать merge'ей, они делают историю версий некрасивой (и не так удобно читаемой). Вместо git merge somebody/somebranch
старайтесь по возможности использовать git rebase somebody/somebranch
(но требуется хорошо понимать, что при этом происходит и чем вы рискуете; никогда так не делайте, если уже залили куда-то свои изменения). Особенно важно держать историю версий линейной, если вы экспортируете коммиты в какой-нибудь svn с помощью git-svn
.
Подружитесь с git reset
, который един в трёх лицах (soft, mixed и hard). Все три вам в жизни очень понадобятся. Еще полезная штука git stash
.
Git умеет импортировать историю из многих систем контроля версий (например см. git cvsimport
, также есть много отдельных импортировальщиков), двусторонне синхронизироваться с Subversion (см. git svn
). Что касается импорта из CVS, Git в процессе задействует команду cvsps
, которая в официальной версии имеет разные баги. Пропатченная версия имеется и очень рекомендуются к использованию при импорте из CVS.
Git умеет переписывать историю. Вообще-то, вооружившись приведенными сведениями, это можно сделать и руками (или хитрым скриптом), но намного быстрее использовать команды git commit --amend
, git rebase
(особенно git rebase --interactive
) и git filter-branch
; последний позволяет сделать буквально всё.
Выше ничего не сказано про теги; они есть, причем двух видов (теги можно делать просто ссылками в refs/tags/
, а можно дополнительно к ссылкам создавать полноценные объекты, в которые уже помещается комментарий и цифровая подпись). Не упоминался ref log — локальная история изменений каждой ссылки, позволяющая ответить на вопрос, какая версия была у вас в рабочей директории два дня назад (или какой коммит был у вас последним в бранче master
вчера). Если вы сделаете дикий rebase и вся история умрёт, ref log вас спасёт; смотрим его командой git log -g
.
Есть GitHub, где хостинг для open source-проектов бесплатный, а для проприетарных просто дешевый. GitHub делает управление репозиториями и отслеживание прогресса других маргинально проще, а заодно избавляет от необходимости регулярно вызывать git gc
в репозиториях на сервере. (В ваших локальных репозиториях её всё равно нужно регулярно исполнять.)
Наконец, Git — это не только его команды и система контроля версий. Это база данных, которую можно использовать для автоматической децентрализованной синхронизации файлов. Формат базы данных достаточно прост и позволяет работу с ней реализовать на других языках в вашем проекте. В частности, чтение и запись репозиториев Git уже реализована для Java в проекте jgit (и обрастает интересностями вроде встроенной поддержки Amazon S3). Если вам нужна распределенная база данных объектов, похожих на файлы, вам следует посмотреть в сторону Git.
9 comments:
про git stash не знал. пользуюсь для этого stgit, интересно сможет ли stash его полноценно заменить.
совет избегать merge -- это что, официальная политика гита? м-да, понимаю, почему его так многие не любят.
Избегать merge — это привилегия, которой Git наделяет работающих с ним профессионалов. Примерно в таком духе (...по мере роста опыта вы поймете, что...) это и подано в мануале.
Вместо рассказывания сказок о том, какой разработчик над чем работал и когда пуллился, линейная история проводит строгую политику партии по развитию проекта без сомнений и отклонений. :)
См. также: что Линус говорит тем, кто пытается коммитить мержи в ядро Linux'а.
> "Избегать merge — это привилегия, которой Git наделяет работающих с ним профессионалов."
Как пафосно!
Я так понимаю, вы уже дошли до стадии профи. У меня естьл вопрос: как у гита с обработкой русских имен файлов и русских комментариев к фиксациям (commit)? Используется ли юникод или utf-8 для их хранения, или все хранится в кодировке пользователя?
Предполагается, что commit message всегда UTF-8. Если попытаться записать туда не utf, Git, кажется, даже сообщит об этом. (Он вообще любит ругаться - например, на коммиты, которые добавляют пробелы в конце строк.)
Имена файлов, полагаю, тоже в UTF, но я сходу не помню. Не приходилось с такими дело иметь :)
А git работает под Windows ? Я слышал что официально только под cygwin
Git под виндой работает, есть mingw-порт. Кажется, он сейчас официальный.
касательно гита под винду: http://code.google.com/p/msysgit/
работает изумительно!
Post a Comment