Основы Git
Данное руководство предназначено для людей, лишь краем уха слышавших о такой системе контроля версий (далее - СКВ), как Git (или слышавших и даже попробовавших ею воспользоваться, но по какой-то причине забросивших эту затею, так и не познав преимущества этой СКВ перед прочими).
Если вы считаете себя именитым гуру, которого уже ничем не удивить в этой жизни, смело закрывайте данное руководство - ничего нового вы здесь для себя не найдёте. Для остальных же ниже представлена краткая программа изучения Git:
- Что такое Git?
- Создание локального репозитория Git
- Выполнение коммитов
- Просмотр истории коммитов
- Изменение коммитов
- Отмена локальных правок
- Подключение удалённого репозитория
- Отправка изменений в удалённый репозиторий
- Получение изменений из удалённого репозитория
- Разрешение конфликтов
- Создание и слияние веток
Предполагается, что вы уже справились с установкой и настройкой Git. Если нет, ознакомьтесь со следующим руководством: Настройка Git
Все манипуляции мы будем выполнять из командной строки. Научившись работать в таком режиме, вам не составит труда разобраться в любом инструменте, визуализирующем работу с Git, будь то SmartGit, плагины для IntelliJ IDEA или Eclipse.
Что такое Git?
Итак, начнём с краткого ликбеза. Что же такое Git? Как гласит Wikipedia:
Git - распределённая система управления версиями файлов. Проект был создан Линусом Торвальдсом для управления разработкой ядра Linux, первая версия выпущена 7 апреля 2005 года. На сегодняшний день его поддерживает Джунио Хамано.
Кстати, встречаются отдельные личности, которые путают Git и небезызвестный GitHub. Давайте сразу расставим все точки над i. GitHub - это веб-сервис для хостинга многочисленный поделок на самых разных языках программирования. И так уж сложилось, что в его основе лежит СКВ Git. Кроме GitHub есть ещё BitBucket, Google Code и может быть что-то ещё менее известное.
Создание локального репозитория Git
С ликбезом покончено, откройте консоль и перейдите в каталог, где вы храните свои проекты. Представим, что перед вами стоит задача создания АБС (Автоматизированной Банковской Системы). Не клиента, а именно своей собственной АБС с нуля. Для неё вам требуется создать новый проект в отдельном каталоге, скажем, isimple-abs.
Создав указанный каталог, перейдите в него и введите следующую команду:
> git init
Результат должен быть таким:
Initialized empty Git repository in .../isimple-abs/.git/
В isimple-abs появился скрытый каталог .git, в котором содержится вся необходимая информация о текущем репозитории.
В любой непонятной ситуации набирайте в консоли git status
. Данная команда позволяет увидеть текущее состояние репозитория. На данный момент вы получите следующий вывод:
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
On branch master говорит о том, что мы сейчас находимся на ветке master. Вы вольны называть свои ветки как угодно (подробнее о ветках позже), но по умолчанию Git создаёт основную ветку с именем master (HEAD в терминах CVS).
Выполнение коммитов
Давайте теперь создадим первый файл с исходным кодом:
src/com/custsystems/abs/isimpleabs/Main.java
А внутри него точку входа для запуска нашего убийцы Diasoft'ов и Инверсий:
package com.custsystems.abs.isimpleabs;
public static void main(String... args) {
System.out.println("Hi, I am iSimpleABS!");
}
Вновь набрав в консоли git status
, мы получим следующую картину:
On branch master
Initial commit
Untracked files:
(use "git" add <file>..." to include in what will be committed)
src/
nothing added to commit but untracked files present (use "git add" to track)
Git нам сообщает о том, что в каталоге репозитория есть файлы, состояние которых им не отслеживается. Для того, чтобы сделать файлы отслеживаемыми, используйте команду git add <file_name>
. На месте file_name можно использовать шаблоны вида *.xml и им подобные. К примеру, команда git add .
добавляет в индекс все файлы из текущего каталога. Итак, если мы проверим статус репозитория после ввода команды git add src
, мы увидим следующее:
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: src/com/custsystems/abs/isimpleabs/Main.java
После выполнения команды git add
файлы попадают в т.н. staging area или индекс - в специальную область, из которой в последствии формируется коммит. К слову, коммиты выполняются командой git commit -m "<message>"
, где -m
- обязательный параметр, после которого нужно указать пояснение к коммиту. Сами пояснения желательно делать краткими, отражающими суть сделанных изменений, и как следствие этого, коммиты должны выполняться как можно чаще, как только вы почувствуете, что готовы подвести логический итог проделанной работе. По поводу языка, на котором следует писать пояснение, помнится, была уже дискуссия на эту тему в Skype. Лично я придерживаюсь правила писать все комментарии на английском, как на международном языке в сфере IT, что также гарантирует отсутствие каких-либо проблем с кодировками.
Делаем коммит:
> git commit -m "First commit"
[master (root-commit) 66dd95b] First commit
1 file changed, 5 insertions(+)
create mode 100644 src/com/custsystems/abs/isimpleabs/Main.java
Статус репозитория на этот раз покажет нам, что все изменения зафиксированы и ничего подозрительного нет:
On branch master
nothing to commit, working directory clean
Просмотр истории коммитов
Чтобы посмотреть историю коммитов, введите команду git log
:
commit 66dd95bc6064edf2df15013e21f44be50c815639
Author: %username% <%email%>
Date: Tue Mar 3 12:08:30 2015 +0300
First commit
commit 66dd95... - такая важная штука, как хеш коммита (у вас он будет отличаться), т.е. его уникальный идентификатор, позволяющий найти нужный коммит и, к примеру, вернуть репозиторий к состоянию на момент этого коммита.
Изменение коммитов
В вашем репозитории могут присутствовать файлы, которые специфичны только для вас и в случае совместной работы будут лишь захламлять репозиторий. Создадим, к примеру, файл, содержащий параметры для соединения с БД:
src/db.properties
Неважно, какое у него будет содержимое, важно, что мы не хотим видеть этот файл в репозитории, поскольку каждый работает со своей любимой БД. Для таких случаев существует файл .gitignore, который вы можете поместить в корень проекта и указать в нём маски игнорируемых файлов и каталогов. В данном случае он должен выглядеть так:
db.properties
Теперь у нас есть ещё один подлежащий коммиту файл - .gitignore. Можно сделать второй коммит, а можно изменить предыдущий командой git commit --amend
:
> git add .gitignore
> git commit --amend
После этого запустится текстовый редактор (Vim по умолчанию), в котором вам будет предложено изменить пояснение к предыдущему коммиту и сохраниться. Можно просто оставить всё как есть и выйти. В результате, в истории будет по-прежнему красоваться один единственный коммит.
Отмена локальных правок
Давайте теперь отредактируем класс com.custsystems.abs.isimpleabs.Main
, изменив сообщение с "Hi, I am iSimpleABS!" на "Hello, I am iSimpleABS!". Статус репозитория покажет, что файл src/com/custsystems/abs/isimpleabs/Main
изменён и чтобы включить его в коммит, нужно вновь выполнить команду git add
. Если изменения оказались нежелательными, вы всегда можете вернуться к последней версии, зафиксированной в репозитории командой git checkout <file_name>
:
> git checkout src/com/custsystems/abs/isimpleabs/Main.java
Та же команда позволяет возвращать весь репозиторий к состоянию на момент определённого коммита (git checkout <commit_hash>
) или переключаться между ветками.
Если вы не просто изменили какой-то файл, но ещё и добавили его в индекс, то такой способ уже не сработает. Перед выполнением команды git checkout
необходимо выполнить команду git reset <file_name>
, которая удаляет заданный файл из индекса.
Подключение удалённого репозитория
Допустим, вы захотели подключить к работе над проектом еще одного человека. Для этого вам потребуется удалённый репозиторий, для того, чтобы делиться своими наработками. Зайдите на наш сервер GitLab и создайте там новый проект isimple-abs. В качестве пространства имён оставьте своё имя. После создания проекта скопируйте SSH-ссылку на удалённый репозиторий и затем выполните команду:
> git remote add origin git@source.isimplelab.com:<username>/isimple-abs.git/
Т.о. мы добавили удалённый репозиторий с именем origin к нашему проекту и теперь можем заливать туда свои изменения, а также получать изменения, сделанные другими участниками проекта. origin - стандартное имя удалённого репозитория в Git.
Отправка изменений в удалённый репозиторий
Чтобы отправить изменения в origin выполните следующую команду:
> git push -u origin master
Параметр -u
позволяет привязать текущую локальную ветку к удалённой. Его нужно указывать только при первой отправке изменений. Отныне в статусе репозитория будет отображаться состояние локальной ветки по отношению к удалённой (в данном случае - Your branch is up-to-date with 'origin/master').
Получение изменений из удалённого репозитория
Для того, чтобы обновить локальный репозиторий из удаленного, выполните команду git pull
. Чтобы проверить команду, создайте новый файл src/com/custsystems/abs/isimpleabs/Greeter.java
через интерфейс GitLab со следующим содержимым:
package com.custsystems.abs.isimpleabs;
public class Greeter {
public void sayHello() {
System.out.println("Hi, I am iSimpleABS!");
}
}
В качестве пояснения к коммиту укажите Add Greeter class и нажмите на кнопку Commit Changes. Набрав после этого команду git pull
, вы обнаружите у себя в локальном репозитории тот самый класс com.custsystems.abs.isimpleabs.Greeter
.
Разрешение конфликтов
В ходе совместной работы нередки случаи возникновения конфликтов при изменении одних и тех же файлов разными участниками проекта. Давайте вновь изменим сообщение в классе com.custsystems.abs.isimpleabs.Main
следующим образом:
package com.custsystems.abs.isimpleabs;
public static void main(String... args) {
System.out.println("Welcome, " + args[0] + "!");
}
Затем заглянем на GitLab и перепишем этот же класс, использовав ранее созданный com.custsystems.abs.isimpleabs.Greeter
:
package com.custsystems.abs.isimpleabs;
public class Main {
public static void main(String... args) {
new Greeter().sayHello();
}
}
Сделаем коммит с сообщением Use Greeter for greeting.
В итоге, мы имеем три коммита в удалённом репозитории и конфликтующие с ними изменения в локальном. Что если попытаться сделать коммит локально и отправить изменения в удалённый репозиторий?
> git add src/com/custsystems/abs/isimpleabs/Main.java
> git commit -m "Greet current user"
> git push origin master
Мы получим гневное сообщение от Git:
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'git@source.isimplelab.com:<username>/isimple-abs.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Как свидетельствует сообщение, Git резонно ругается на то, что перед отправкой изменений обязательно нужно делать обновление проекта:
> git pull
remote: Counting objects: 15, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 8 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (8/8), done.
From source.isimplelab.com:<username>/isimple-abs
2b228db..004f1e5 master -> origin/master
Auto-merging src/com/custsystems/abs/isimpleabs/Main.java
CONFLICT (content): Merge conflict in src/com/custsystems/abs/isimpleabs/Main.java
Automatic merge failed; fix conflicts and then commit the result.
Итак, изменения в удалённом репозитории законфликтовали с нашими локальными изменениями. Если вы откроете файл src/com/custsystems/abs/isimpleabs/Main.java
, вы заметите, что Git слегка подредактировал его, указав в секции <<<<<<< HEAD
локальные правки, а в секции >>>>>>> 004f1...
- правки, пришедшие из удалённого репозитория. Вам предлагается отредактировать конфликтующие файлы, выбрав тот вариант, который вам нравится больше, после выполнить команду git add
для отредактированного файла и сделать дополнительный коммит, устраняющий конфликты. После этого можно смело делать git push origin master
.
Создание и слияние веток
Последнее, что мы рассмотрим - это ветки - важную часть процесса разработки ПО. Когда вы используете Git, принято создавать ветки под каждую обособленную задачу. Это избавляет от множества проблем, связанных с одновременной работой над несколькими фичами/багами, а также с необходимостью поддерживать основную ветку в актуальном состоянии.
Создать ветку в Git очень и очень просто:
> git branch <branch_name>
Команда git branch
без аргументов выводит перечень локальных веток. Чтобы переключиться на вновь созданную ветку введите команду git checkout <branch_name>
. Что-то знакомое, не правда ли? Как я уже упоминал ранее, git checkout
позволяет возвращать репозиторий к состоянию на момент определённого коммита, указав хеш этого коммита. Так вот, имя ветки - это всего лишь именованный указатель на определённый коммит. Кроме того, всегда присутствует специальный указатель на последний коммит - HEAD. Есть короткая команда для создания ветки и переключения на неё за один ход: git checkout -b <branch_name>
.
Давайте создадим ветку, в которой мы добавим новую фишку к нашей АБС:
> git checkout -b feature/payment
Создадим там новый класс com.custsystems.abs.isimpleabs.Payer
:
package com.custsystems.abs.isimpleabs;
public class Payer {
public void doPayment(double amount) {
System.out.println("Payment has been done (amount: " + amount + ")");
}
}
А также отредактируем класс com.custsystems.abs.isimpleabs.Main
:
package com.custsystems.abs.isimpleabs;
public class Main {
public static void main(String... args) {
new Greeter().sayHello();
new Payer().doPayment(Double.parseDouble(args[1]));
}
}
Сделаем коммит с сообщением Add payment feature:
> git add .
> git commit -m "Add payment feature"
Поскольку наша ветка только локальная, push делать не нужно. Создавайте удалённую ветку в случае обширных доработок или при необходимости совместной работы. Сделать это можно командой git push origin <branch_name>
.
Когда работа закончена, ветку с фичей нужно объединить с той веткой, от которой мы отпочковались. Для этого перво-наперво возвращаемся на ветку master:
> git checkout master
А затем выполняем слияние с веткой feature/payment:
> git merge feature/payment
Updating 5c35407..33fb1f2
Fast-forward
src/com/custsystems/abs/isimpleabs/Main.java | 1 +
src/com/custsystems/abs/isimpleabs/Payer.java | 7 +++++++
2 files changed, 8 insertions(+)
create mode 100644 src/com/custsystems/abs/isimpleabs/Payer.java
Слияние прошло успешно. Обратите внимание на сообщение Fast-forward - это выбранная стратегия слияния. Fast-forward - самая простая стратегия, применяемая в случае отсутствия в основной ветке коммитов более новыx, чем тот коммит, от которого пошло ветвление.
Ветка feature/payment больше нам не нужна и её можно удалить:
> git branch -d feature/payment
В случае удалённого репозитория - git push origin --delete <branch_name>
. Будьте осторожны, удаляя ветки из origin, прежде согласуйте это со всеми участниками разработки.
На этом всё. Разумеется, за бортом осталось ещё множество неосвещённых тем, но данное руководство вовсе не претендует на полноту изложения. Любопытные найдут больше информации на замечательном проекте GIT HowTo.