Моя шпаргалка по работе с Git
Некоторое время назад я открыл для себя Git. И знаете, я проникся. То есть, по-настоящему проникся. Теперь я использую Git не только на работе (где я с ним, собственно, познакомился), но и для своих проектиков, которые я стал хранить на BitBucket. Последний начал поддерживать Git относительно недавно. В отличие от GitHub, BitBucket позволяет бесплатно создавать как открытые, так и закрытые репозитории.
В чем состоит отличие Git от Subversion?
Главное отличие Git от Subversion заключается в том, что Git – распределенная система контроля версий. Звучит ужасающе, но на практике это означает очень простую вещь. Каждый разработчик держит у себя на диске отдельный репозиторий. Обратите внимание – не копию репозитория, не некоторые бранчи, а тупо отдельный и при этом абсолютно полноценный репозиторий.
Пока мы работаем в рамках своего репозитория, все происходит в точности, как в Subversion. Мы коммитим и откатываем изменения, создаем, мержим и удаляем бранчи, разрешаем конфликты и тд. Помимо этого, предусмотрены команды для работы с репозиториями на удаленных машинах. Например, «git push» означает мерж локальных изменений в удаленный репозиторий, а «git pull» – наоборот, мерж изменений из удаленного репозитория в локальный. Обмен данными по сети обычно происходит с использованием протокола SSH.
В результате имеем:
- Git присущи все те же преимущества от использования VCS, что мы получаем в Subversion.
- Git дает нам нормальное шифрование «из коробки», безо всяких танцев с бубнами, как в случае с Subversion.
- Если сервер с «главным» репозиторием, куда пушат свои изменения все разработчики (хотя формально в Git нет никакого «главного» репозитория), вдруг прилег – ничего страшного. Делаем коммиты в локальный репозиторий и ждем, когда сервер вернется.
- Даже если сервер доступен, все равно удобнее сделать пяток локальных коммитов, а затем отправить их на сервер одним пушем.
- Сервер вообще не нужен. Вы можете использовать Git только локально. И не обязательно для работы с исходниками. Например, можно использовать Git для того, чтобы иметь возможность откатиться к предыдущим версиям файлов (каких-нибудь электронных таблиц) или вернуть случайно удаленные.
- Git не раскидывает по каталогам служебную информацию (помните «.svn»?), вместо этого она хранится только в корне репозитория.
- Git нынче очень моден (хотя это далеко не единственная распределенная система контроля версий, например, есть Mercurial и Darcs), в связи с чем растет число разработчиков, использующих его. Как следствие, используя Git, легче получить помощь на каком-нибудь форуме или собрать команду разработчиков, знакомых с этой VCS.
- Существует множество полезных утилит для работы с Git – Qgit, gitk, gitweb и другие. «Из коробки» есть импорт и экспорт в/из Subversion/CVS.
- Git поддерживают многие хостинги репозиториев (GitHub, BitBucket, SourceForge, Google Code, …) – есть из чего выбрать.
- Большой популярностью пользуется GitHub. Используя Git, вы увеличиваете вероятность того, что кто-то захочет безвозмездно написать патч для вашего open source проекта.
Пример использования Git
Я использовал Git при написании программы из заметки Генерация почти осмысленных текстов на Haskell, сидя под своей любимой FreeBSD. Вот как примерно выглядела моя работа с Git.
В первую очередь необходимо поставить Git:
pkg_add -r git
Затем создаем пару ssh ключей, если не создавали ее ранее:
ssh-keygen
cat ~/.ssh/id_rsa.pub
Заходим на БитБакет, создаем git-репозиторий под новый проект, а в свойствах аккаунта прописываем свой открытый ssh-ключ. Затем клонируем репозиторий:
cd ~/projects/haskell
git clone git@bitbucket.org:afiskon/hs-textgen.git
cd hs-textgen
Делаем какие-то изменения:
echo test > TODO.TXT
Добавляем новый файл в репозиторий и делаем коммит:
git add TODO.TXT
git commit -a
Поскольку я не указал описание коммита, запускается редактор VIM, с помощью которого я и ввожу описание. Затем я отправляю все сделанные мною изменения на БитБакет:
git push origin
Допустим, теперь я хочу сделать некоторые изменения в проекте, но не уверен, выйдет ли из этого что-то хорошее. В таких случаях создается новая ветка:
git branch new_feature
git checkout new_feature
Работаем с этой веткой. Если ничего хорошего не вышло, возвращаемся к основной ветке (она же «trunk» или «ствол»):
git checkout master
Если вышло что-то хорошее, мержим ветку в master (о разрешении конфликтов рассказано в следующем параграфе):
git commit -a # делаем коммит всех изменений в new_feature
git checkout master # переключаемся на master
git merge new_feature # мержим ветку new_feature
Не забываем время от времени отправлять наш код на BitBucket:
git push origin
Если мы правим код с нескольких компьютеров, то перед началом работы не забываем «накатить» в локальный репозиторий последнюю версию кода:
git pull origin
Работа в команде мало чем отличается от описанного выше. Только каждый программист должен работать со своей веткой, чтобы не мешать другим программистам. Одна из классических ошибок при начале работы с Git заключается в push’е всех веток, а не только той, с которой вы работали. Вообще я бы советовал первое время перед выполнением каждого push делать паузу с тем, чтобы подумать, что и куда сейчас уйдет. Для большей безопасности советую при генерации ssh-ключей указать пароль. Тогда каждый запрос пароля со стороны Git будет для вас сигналом «Эй, ты делаешь что-то, что затронет других».
Для работы с Git под Windows можно воспользоваться клиентом TortoiseGit. Если память не подводит, для работы ему нужен Git for Windows. Для генерации ключей можно воспользоваться утилитой PuTTyGen. Только не забудьте экспортировать открытый ключ в правильном формате, «Conversions → Export OpenSSH key».
Следует отметить, что мне лично TortoiseGit показался не слишком удобным. Возможно, это всего лишь дело привычки, но мне кажется намного удобнее работать с Git из консоли, чем с помощью контекстного меню в Проводнике.
Шпаргалка по командам
В этом параграфе приведена сухая шпаргалка по командам Git. Я далеко не спец в этой системе контроля версий, так что ошибки в терминологии или еще в чем-то вполне возможны.
Создать новый репозиторий:
git init project-name
Если вы планируете клонировать его по ssh с удаленной машины, также скажите:
git config --bool core.bare true
… иначе при git push вы будете получать странные ошибки вроде:
Refusing to update checked out branch: refs/heads/master
By default, updating the current branch in a non-bare repository
is denied, because it will make the index and work tree inconsistent
with what you pushed, and will require 'git reset --hard' to match
the work tree to HEAD.
Клонировать репозиторий с удаленной машины:
git clone git@bitbucket.org:afiskon/hs-textgen.git
Если хотим пушить один код в несколько репозиториев:
git remote add remotename git@gitlab.example.ru:repo.git
Добавить файл в репозиторий:
git add text.txt
Удалить файл:
git rm text.txt
Текущее состояние репозитория (изменения, неразрешенные конфликты и тп):
git status
Сделать коммит:
git commit -a -m "Commit description"
Сделать коммит, введя его описание с помощью $EDITOR:
git commit -a
Замержить все ветки локального репозитория на удаленный репозиторий (аналогично вместо origin можно указать и remotename, см выше):
git push origin
Аналогично предыдущему, но делается пуш только ветки master:
git push origin master
Запушить текущую ветку, не вводя целиком ее название:
git push origin HEAD
Замержить все ветки с удаленного репозитория:
git pull origin
Аналогично предыдущему, но накатывается только ветка master:
git pull origin master
Накатить текущую ветку, не вводя ее длинное имя:
git pull origin HEAD
Скачать все ветки с origin, но не мержить их в локальный репозиторий:
git fetch origin
Аналогично предыдущему, но только для одной заданной ветки:
git fetch origin master
Начать работать с веткой some_branch (уже существующей):
git checkout -b some_branch origin/some_branch
Создать новый бранч (ответвится от текущего):
git branch some_branch
Переключиться на другую ветку (из тех, с которыми уже работаем):
git checkout some_branch
Получаем список веток, с которыми работаем:
git branch # звездочкой отмечена текущая ветвь
Просмотреть все существующие ветви:
git branch -a # | grep something
Замержить some_branch в текущую ветку:
git merge some_branch
Удалить бранч (после мержа):
git branch -d some_branch
Просто удалить бранч (тупиковая ветвь):
git branch -D some_branch
История изменений:
git log
История изменений в обратном порядке:
git log --reverse
История конкретного файла:
git log file.txt
Аналогично предыдущему, но с просмотром сделанных изменений:
git log -p file.txt
История с именами файлов и псевдографическим изображением бранчей:
git log --stat --graph
Изменения, сделанные в заданном коммите:
git show d8578edf8458ce06fbc5bb76a58c5ca4a58c5ca4
Посмотреть, кем в последний раз правилась каждая строка файла:
git blame file.txt
Удалить бранч из репозитория на сервере:
git push origin :branch-name
Откатиться к конкретному коммиту (хэш смотрим в «git log»):
git reset --hard d8578edf8458ce06fbc5bb76a58c5ca4a58c5ca4
Аналогично предыдущему, но файлы на диске остаются без изменений:
git reset --soft d8578edf8458ce06fbc5bb76a58c5ca4a58c5ca4
Попытаться обратить заданный commit:
git revert d8578edf8458ce06fbc5bb76a58c5ca4a58c5ca4
Просмотр изменений (суммарных, а не всех по очереди, как в «git log»):
git diff # подробности см в "git diff --help"
Используем vimdiff в качестве программы для разрешения конфликтов, mergetool:
git config --global merge.tool vimdiff
git config --global mergetool.prompt false
Аналогично для difftool:
git config --global diff.tool vimdiff
git config --global difftool.prompt false
Конкретно vimdiff удобен тем, что позволяет читать diff’ы в split режиме, а также имеет подсветку синтаксиса:
git difftool (коммит_а) (коммит_б)
Отображаем табы как 4 пробела, например, в «git diff»:
git config --global core.pager 'less -x4'
Создание глобального файла .gitignore:
git config --global core.excludesfile ~/.gitignore_global
Разрешение конфликтов (когда оные возникают в результате мержа):
git mergetool
Создание тэга:
git tag some_tag # за тэгом можно указать хэш коммита
Удаление неотслеживаемых файлов:
git clean -df
Иногда требуется создать копию репозитория или перенести его с одной машины на другую. Это делается примерно так:
mkdir -p /tmp/git-copy
cd /tmp/git-copy
git clone --bare git@example.com:afiskon/cpp-opengl-tutorial1.git
cd cpp-opengl-tutorial1.git
git push --mirror git@example.com:afiskon/cpp-opengl-tutorial2.git
Следует отметить, что Git позволяет использовать короткую запись хэшей. Вместо «d8578edf8458ce06fbc5bb76a58c5ca4a58c5ca4» можно писать «d8578edf» или даже «d857».
Дополнение: Также в 6-м пункте Мини-заметок номер 9 приводится пример объединения коммитов с помощью git rebase, а в 10-м пункте Мини-заметок номер 11 вы найдете пример объединения двух репозиториев в один без потери истории.
Работа с сабмодулями
Более подробно сабмодули и зачем они нужны объясняется в заметке Простой кроссплатформенный OpenGL-проект на C++. Здесь упомянем самое главное.
Добавить сабмодуль:
git submodule add https://github.com/glfw/glfw glfw
Инициализация сабмодулей:
git submodule init
Обновление сабмодулей, например, если после git pull поменялся коммит, на который смотрит сабмодуль:
git submodule update
Удаление сабмодуля производится так:
- Скажите
git rm --cached имя_сабмодуля; - Удалите соответствующие строчки из файла .gitmodules;
- Также грохните соответствующую секцию в .git/config;
- Сделайте коммит;
- Удалите файлы сабмодуля;
- Удалите каталог .git/modules/имя_сабмодуля;
Дополнительные материалы
В качестве источников дополнительной информации я бы рекомендовал следующие:
Как обычно, любые замечания, дополнения и вопросы категорически приветствуются. И кстати, с наступающим вас!
Дополнение: Практика работы с системами контроля версий