воскресенье, 15 февраля 2009 г.

Практическое введение в Git

Возможно, я плохо искал, но простого пошагового введения в Git на русском для тех разработчиков и админов, которые кое-как научились использовать CVS или SVN, до сих пор нет. Много полезных материалов и ссылок можно найти на ALT Linux Wiki, однако значительная их часть содержит альтовскую дистрибутивостроительную специфику или, наоборот, несколько далека от практических задач. Я попытаюсь описать один из типичных сценариев использования Git без привязки к каким-либо задачам и без излишних подробностей - за ними лучше сразу идти в Git User's Manual.

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

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

Устанавливаем все необходимое и заводим на сервере центральный репозитарий:
# apt-get install git-core
# mkdir /home/repo/alpha.git
# cd /home/repo/alpha.git
# git init --bare --shared=true
Работать с ним будем по ssh, поэтому создаем для разработчиков учетные записи и добавляем их в группу, во владение которой передаем репозитарий:
# useradd -d /var/empty/ -s /usr/bin/git-shell user1
# useradd -d /var/empty/ -s /usr/bin/git-shell user2
# groupadd alpha
# usermod -G alpha user1
# usermod -G alpha user2
# chown -R root:alpha /home/repo/alpha.git
Теперь один из разработчиков может создать локальный репозитарий и сделать в нем несколько коммитов:
[user1@workstation1 ~]$ mkdir alpha
[user1@workstation1 ~]$ cd alpha
[user1@workstation1 alpha]$ git init
[user1@workstation1 alpha]$ git config user.name 'User 1'
[user1@workstation1 alpha]$ git config user.email 'user1@workstation1'
[user1@workstation1 alpha]$ echo 'line 1' > file
[user1@workstation1 alpha]$ git add .
[user1@workstation1 alpha]$ git commit -a -m 'add line 1'
[user1@workstation1 alpha]$ echo 'line 2' >> file
[user1@workstation1 alpha]$ git commit -a -m 'add line 2'
Каждый коммит принадлежит одному или нескольким бранчам. Для первого коммита автоматически создается бранч с именем master, в него же по умолчанию попадают следующие коммиты.

Несколько полезных команд:
  • git branch - показать список бранчей, выделив текущий, создать, удалить или переименовать бранч
  • git status - показать текущее состояние рабочей копии (какие новые файлы появились, а какие были удалены, что закоммитили, а что нет, и т.д.)
  • git log - показать историю коммитов, каждый из которых идентифицируется с помощью хеша sha1
  • git show - показать подробности последнего коммита или любого коммита по его хешу
  • git diff - показать разницу между последним коммитом и рабочей копией или между коммитами
Теперь подготовим наш репозитарий к отгрузке коммитов в центральный репозитарий на сервере:
[user1@workstation1 alpha]$ git remote add origin user1@gitserver:/home/repo/alpha.git
И отправим туда коммиты, принадлежащие локальному бранчу master - они попадут в удаленный бранч с таким же именем:
[user1@workstation1 alpha]$ git push origin master
В дальнейшем можно будет ограничиться git push, т.к. origin - дефолтное имя удаленного репозитария, а бранч master в нем уже существует. С другой стороны, ничто не мешает явно указывать URL удаленного репозитария и имена локального и удаленного бранчей:
[user1@workstation1 alpha]$ git push user1@gitserver:/home/repo/alpha.git master:master
Для получения коммитов из центрального репозитария в локальный бранч master его нужно было бы создать с параметром --track, однако бранч уже существует, поэтому просто укажем соответствующий ему удаленный бранч в настройках:
$ cat >> .git/config << EOF
> [branch "master"]
>       remote = origin
>       merge = refs/heads/master
> EOF
Получить новые коммиты из центрального репозитария можно так:
[user1@workstation1 alpha]$ git fetch
[user1@workstation1 alpha]$ git merge origin/master
или просто:
[user1@workstation1 alpha]$ git pull
В случае конфликтующих изменений операция merge не будет завершена коммитом - в этом случае нужно будет выполнить git commit самостоятельно после разрешения конфликта.

Разумеется новые коммиты могут появиться в центральном репозитарии только после того, как другой разработчик клонирует его, внесет туда какие-либо изменения и отправит их обратно - при этом настраивать свой репозитарий для взаимодействия с удаленным ему уже не потребуется:
[user2@workstation2 ~]$ git clone user2@gitserver:/home/repo/alpha.git
[user2@workstation2 ~]$ cd alpha/
[user2@workstation2 alpha]$ git config user.name 'User 1'
[user2@workstation2 alpha]$ git config user.email 'user2@workstation2'
[user2@workstation2 alpha]$ echo 'line 3' >> file
[user2@workstation2 alpha]$ git commit -a -m 'add line 3'
[user2@workstation2 alpha]$ git push
Теперь чуть подробнее о том, ради чего все эти мучения - ведь для выполнения аналогичных операций средствами CVS или SVN потребовалось бы меньше операций. Дело в том, что до тех пор, пока мы не отгрузили коммиты в удаленный репозитарий, мы вольны распоряжаться ими как нам вздумается. Например, заведем для экспериментов новый локальный бранч и сделаем в нем еще пару коммитов:
[user1@workstation1 alpha]$ git-checkout -b experimental
[user1@workstation1 alpha]$ echo 'line 4' >> file
[user1@workstation1 alpha]$ git commit -a -m 'add line 4'
[user1@workstation1 alpha]$ echo 'line 5' >> file
[user1@workstation1 alpha]$ git commit -a -m 'add line 5'
Если какие-то коммиты лучше было бы не делать, можно откатиться к любому заданному коммиту, указав его:
  • явно в виде sha1 id - их покажет git log
  • по имени тэга - его можно создать для коммита по его sha1 id с помощью git tag
  • относительно другого коммита - например, последнему коммиту, на который указывает HEAD, предшествует HEAD^:
[user1@workstation1 alpha]$ git reset --hard HEAD^
Также бывают полезны параметр --soft (откатиться, но сохранить содержимое измененных файлов для последующего редактирования) и параметр --amend команды git commit (включить содержимое старого коммита в новый).

Вернемся в бранч master, сделаем в нем еще один коммит и втянем туда результаты экспериментов:
[user1@workstation1 alpha]$ git checkout master
[user1@workstation1 alpha]$ subst '/line 1/iline 0' file
[user1@workstation1 alpha]$ git commit -a -m 'add line 0'
[user1@workstation1 alpha]$ git merge experimental
Вместо операции merge, которая создает общий коммит для бранчей, иногда бывает полезно использовать rebase - эта операция переупорядочивает коммиты для того, чтобы сохранить историю линейной. Очень полезно для изучения истории использовать gitk, gitg или qgit - эти инструменты помогут представить описанное более наглядно.

Разумеется, после того, как мы сделали git push, с историей лучше не забавляться - этим мы сильно осложним жизнь тем, кто уже основывает свои новые коммиты на ней. А вот до git push - сколько угодно :)
Отправить комментарий