Git (Часть 2 — Cookbook)

В предыдущей заметке я создавал некий пустой проект и добавлял его в систему управления версиями Git. Здесь я приведу небольшую «cookbook» по работе с Git. Подробная информация про Git содержится в книге Scott Chacon and Ben Straub — Pro Git (Second Edition) — 2014.

Справка по Git

Аргументами утилиты git являются всякие команды (например git config, git diff и т. д.). Незаменимая команда для получения справки по командам утилиты git — это команда help. Например, чтобы получить справку по команде git diff, напечатайте следующее:

git help diff

Конфигурирование Git

Минимально необходимое конфигурирование Git заключается в указании имени и адреса электронной почты пользователя. Эти данные будут содержаться в каждом коммите, чтобы было понятно кто сделал этот коммит. Можно установить имя пользователя и почту глобально для всех репозиториев (тогда вы используете ключ --global):

git config --global user.name "Ваше Имя"
git config --global user.email "you@example.com"

… или только для текущего репозитория (тогда вы не используете ключ --global):

git config user.name "Ваше Имя"
git config user.email "you@example.com"

Клонирование удаленного репозитория

У всякого удаленного репозитория есть URL. Например у моего репозитория osdevlearning URL следующий: https://dvsav@bitbucket.org/dvsav/osdevlearning.git. Клонирование репозитория — это копирование его с удаленного сервера на локальный компьютер. Часто проект начинается именно с клонирования.

git clone https://dvsav@bitbucket.org/dvsav/osdevlearning.git

По-умолчанию при клонировании репозитория git присваивает ему обозначение origin. Далее в этой заметке удаленный репозиторий будет обозначаться именно так.

Создание локального репозитория

Как было сказано выше, чтобы создать локальный репозиторий, можно клонировать удаленный. Другой вариант — создать новый локальный репозиторий командой git init. Но сначала надо перейти в папку с вашим проектом. Команда git init создаст в этой папке папку .git, где будет храниться база данных для созданного репозитория. Git автоматически создаст ветку под названием master (о ветках см. ниже).

cd osdevlearning
git init

Добавление ссылки на удаленный репозиторий

Вы можете работать сразу с несколькими удаленными репозиториями. Для добавления ссылки на удаленный репозиторий надо выполнить команду:

git remote add ИмяРепозитория URLРепозитория

ИмяРепозитория — это просто его локальное обозначение. Пример:

git remote add another_repo https://dvsav@bitbucket.org/dvsav/repo2.git

Очистка удаленного репозитория

Предположим, я хочу начать с чистого листа и очистить удаленный репозиторий osdevlearning, т. е. удалить всю историю коммитов. Можно конечно просто зайти на сайт BitBucket и удалить репозиторий osdevlearning, а затем создать новый репозиторий с таким же именем. Но есть и другой способ. Удаляю папку osdevlearning на локальном ПК и создаю ее заново:

cd ~
rm -rfv osdevlearning
mkdir osdevlearning

Создаю локальный репозиторий в папке osdevlearning:

cd osdevlearning
git init

Создаю первый коммит:

git commit -m 'Initial commit' --allow-empty

Ключ --allow-empty нужен, чтобы разрешить коммиту быть пустым.
Добавляю ссылку на удаленный репозиторий:

git remote add origin https://dvsav@bitbucket.org/dvsav/osdevlearning.git

И наконец, отправляю коммит на удаленный репозиторий. При этом в удаленном репозитории удаляются все старые коммиты:

git push --force --set-upstream origin master

Всё, теперь у меня есть локальный и удаленный репозитории, оба совершенно чистые с одним пустым коммитом.

Stage-Commit-Push

При работе над проектом вы производите в файлах проекта некие изменения. Какие именно изменения вы произвели с момента последнего коммита, вы можете посмотреть при помощи команды status:

git status

Затем вы индексируете изменения некоторых файлов. Индексирование — это указание, какие из сделанных изменений войдут в коммит. Коммит — это фиксация изменений, т. е. сохранение снимка состояния проекта с учетом изменений в базе данных. Индексирование делается при помощи команды add:

git add <file1> <file2> ... <fileN>

Вы можете проиндексировать все изменения при помощи ключа -A:

git add -A

Затем вы можете передумать и удалить изменения некоторых файлов из области индексации:

git reset <fileX> <fileY>

А если вы хотите отменить сами изменения, то используйте следующую команду:

git checkout -- <fileX> <fileY>

Теперь вы фиксируете проиндексированные изменения, снабжая их кратким описанием. Снимок проекта с учетом этих изменений сохраняется в локальную базу данных (которая находится в папке .git):

git commit -m "Add some significant changes to the project"

И наконец, вы отправляете коммит в удаленный репозиторий:

git push origin master

Здесь origin — это обозначение для удаленного репозитория, оно как бы заменяет длинный URL этого репозитория. master — в данном случае название текущей ветки. Ветка master создается автоматически при создании репозитория.
При выполнении команды push, в зависимости от настроенного вами метода аутентификации, вас могут спросить имя пользователя и пароль — это будут имя пользователя и пароль от аккаунта на хостинге репозиториев (например, GitHub или Bitbucket).

Просмотр состояния локального репозитория

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

git status

Мы также хотим видеть историю изменений в целом со всеми ветвлениями. Следующая команда красиво отображает эту информацию на экране:

git log --oneline --decorate --graph --all

Просмотр удаленных репозиториев

Следующая команда отображает список удаленных репозиториев:

git remote

Можно посмотреть информацию о конкретном репозитории (о его ветках):

git remote show origin

Просмотр изменений в файлах проекта

Следующая команда показывает, что вы изменили в файлах проекта, но пока еще не проиндексировали:

git diff

А вот эта команда показывает, что вы изменили и проиндексировали:

git diff --staged

Ветвления

Ветки помогают синхронизировать работу членов команды (один член команды — одна ветка). Ветки помогают синхронизировать работу над отдельными задачами (одна задача — одна ветка). Ветки помогают разграничить стабильные версии проекта (обычно — ветка master) и версии, находящиеся в разработке (обычно — ветка develop).
В качестве примера достаточно рассмотреть тематическую ветку (topic branch). Предположим, я нахожусь в ветке master и мне необходимо решить некую проблему, назовем ее the_issue. Я создаю одноименную ветку и перехожу на эту ветку:

git branch the_issue
git checkout the_issue

Сделать все то же самое за одну команду можно так:

git checkout -b the_issue

Далее я выполняю ряд изменений в проекте и делаю ряд коммитов (см. раздел Stage-Commit-Push). Когда я окончательно решил проблему the_issue, я хочу чтобы все изменения, которые я произвел, попали в ветку master. Я выполняю слияние ветки the_issue в ветку master:

git checkout master
git merge the_issue

Если слияние прошло успешно, то ветка the_issue мне больше не нужна, и ее можно удалить:

git branch -d the_issue

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

git add <file1> <file2> ... <fileN>

Затем я выполняю коммит:

git commit -m "merge the_issue into master"

После чего уже удаляю ветку the_issue:

git branch -d the_issue

Приведенная выше команда удаляет ветку в локальном репозитории. Удалить ветку в удаленном репозитории можно так:

git push origin -delete the_issue

Памятка

Ветка — это ссылка на коммит.
Коммит имеет обычно один родительский коммит (тот, который был перед ним).
Коммит, который образовался в результате слияния двух веток имеет два родительских коммита.

Второй способ скопировать в ветку master изменения, которые сделаны в ветке the_issue — это так называемое перемещение (rebase). Этот способ может сделать более наглядной историю изменений, но его рекомендуется использовать только в том случае, если в ветке the_issue работает только один программист. В таком случае хороший тон — это сначала сделать rebase, а затем merge:

git checkout the_issue

git rebase master

# resolve any conflicts
git rebase --continue

# if you cannot resolve conflicts then do:
# git rebase --abort

git checkout master
git merge the_issue

Что здесь происходит:

  1. мы переходим на ветку the_issue (если мы еще не там)
  2. сверху к последнему коммиту master пристраиваются все коммиты из the_issue (чтобы понять, посмотрите картинки в книжке Pro Git)
  3. мы переходим на ветку master
  4. сливаем the_issue в master

Затем ветку the_issue тоже можно удалять.

Патчи (patches)

Патч — это простое средство для обмена изменениями кода в текстовом виде. Патч представляет собой текстовый файл, в котором указывается какие строки и в каком файле исходного кода были изменены. Создать патч можно при помощи команды git diff:

git diff tag1 tag2 > mypatch.patch

Здесь tag1 и tag2 — это теги, которые идентифицируют два коммита, между которыми diff вычисляет разницу, а разница перенаправляется в файл mypatch.patch. Затем вы пересылаете патч, скажем, своему коллеге, и он может применить его к своей рабочей копии репозитория, запустив утилиту patch:

patch -p1 < ./mypatch.patch

Здесь предполагается, что текущей папкой в командной строке является корень репозитория (поэтому ключ -p1). Второй аргумент команды patch — это путь к файлу патча.
Предварительно можно безопасно проверить, не вызовет ли патч конфликтов при помощи ключа —dry-run:

patch -p1 --dry-run < ./mypatch.patch

В git также есть всякие автоматизированные команды для формирования и применения патчей: git format-patch, git am, git apply.

Reset current branch to some specific commit

А если я слил чью-то ветку в свою, еще не отправил изменения в удаленный репозиторий (не сделал push), передумал и захотел отменить слияние? Можно выполнить действие «reset current branch to commit», выбрав тот коммит, который был закоммичен до слияния:

git reset -q --hard 5ae8ad8b

Здесь 5ae8ad8b — это hash коммита. Помимо наиболее подходящей в данном случае опции -hard есть еще опции -mixed и -soft, которые не удаляют сами изменения, которые возникли в результате слияния.

Скачивание изменений из удаленного репозитория в локальный репозиторий

Предположим, один член команды сделал несколько коммитов и отправил их в удаленных репозиторий. У другого члена команды нет этих изменений на его локальном ПК, но он хочет их иметь. Он скачивает изменения с удаленного репозитория при помощи команды

git fetch origin

Напоминаю, что origin в данном случае — это обозначение для удаленного репозитория.
Можно скачать изменения со всех имеющихся удаленных репозиториев:

git fetch --all

Команда fetch однако не выполняет слияния изменений, скачанных из удаленного репозитория с вашей текущей локальной веткой — вы должны выполнить это слияние вручную (предварительно можете посмотреть изменения командой git status):

git merge origin/master

Здесь origin/master — обозначение для ветки master в удаленном репозитории.
Можно выполнить fetch и merge в один прием:

git pull origin

Игнорирование файлов .gitignore

Не все файлы одинаково полезны. Например, некоторые файлы генерируются автоматически средами разработки. Я в основном пишу программы в Visual Studio, которая генерирует множество служебных файлов с расширениями .pdb, .ilk, .ipch, .obj, .tlog и другими. В Git предусмотрено средство, которое заставляет Git игнорировать определенные файлы (т. е. не добавлять автоматически в репозиторий и не отображать в списке неотслеживаемых) — это текстовый файл .gitignore, который вы создаете в папке вашего проекта (например в моем проекте osdevlearning я создаю файл .gitignore в папке osdevlearning). В файле .gitignore вы указываете т. н. паттерны — выражения которые определяют группы файлов (или папок), имена которых соответствуют некоторым условиям. Например паттерн *.obj означает все файлы с расширением .obj. Ниже приведен фрагмент файла .gitignore для одного из моих проектов в Visual Studio:

## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Xx]64/
[Xx]86/
[Bb]uild/
bld/
[Bb]in/
[Oo]bj/

Удаление файлов

Следующая команда удаляет файл из рабочей папки проекта:

git rm <fileX>

А вот эта команда не удаляет файл из рабочей папки, но удаляет его из списка отслеживаемых:

git rm --cached <fileX>

Последняя команда пригодится вам на случай, если забыли добавить какой-либо файл в .gitignore и случайно проиндексировали его.
Кстати, чтобы просмотреть имена всех отслеживаемых файлов, нужно выполнить следующую команду:

git ls-tree -r master --name-only

Здесь master — имя ветки, которую вы просматриваете.

Спрятывание изменений (stashing)

Со мной часто случается следующая история. Я работаю в своей ветке, и у меня есть незакоммиченные изменения. Какой-нибудь мой коллега, который работает в своей ветке, коммитит и отправляет в удаленный репозиторий некие изменения, которые мне очень хочется слить в мою ветку прямо сейчас. Но я не могу слить его ветку в свою, поскольку у меня, как я говорил, есть незакоммиченные изменения (а коммитить недоделанную работу я не хочу). Что делать? Оказывается мои незакоммиченные изменения можно «спрятать», т. е. сохранить в базе данных git, удалив их при этом из рабочей копии. После чего я могу слить ветку моего коллеги в свою, а затем восстановить мои спрятанные изменения.
Итак, чтобы спрятать все незакоммиченные изменения в текущей ветке, я выполняю команду:

git stash

То, что я спрятал, сохраняется в некоем стеке. Содержимое этого стека можно посмотреть так:

git stash list

Каждый элемент стека имеет имя stash@{i} (здесь и далее i — это номер элемента в стеке).
Далее я что-то делаю, например, сливаю ветку своего коллеги в свою.
Затем я могу восстановить мои спрятанные изменения:

git stash apply stash@{i}

Вышеприведенная команда применяет изменения к рабочей копии, но не удаляет их их базы данных git. Чтобы удалить изменения из стека, я выполняю команду:

git stash drop stash@{i}

Теги

Коммиты идентифицируются своими 40-разрядными шестнадцатеричными контрольными суммами. Более человекопонятный способ присвоить коммиту идентификатор — это теги (tags). Присвоив коммиту тег, можно затем ссылаться на этот коммит не по контрольной сумме, а по тегу. Присвоить коммиту тег можно так:

git tag mytag 3c10f56

Здесь mytag — это имя тега, а 3c10f56 — первые несколько цифр контрольной суммы соответствующего коммита.
Теперь можно ссылаться на соответствующий коммит по его тегу, например, перейти на этот коммит так:

git checkout mytag

Поиск бага методом дихотомии (bisect)

Если вы имеете цепочку коммитов, и знаете, что в начале цепочки бага нет, а в конце он есть, и если вы хотите найти, какой именно коммит привел к появлению бага, то вы можете это сделать методом дихотомии. Процесс ровно такой же, как процесс поиска корня уравнения с одним неизвестным методом дихотомии (деление отрезка пополам). А git поможет вам несколько автоматизировать процесс при помощи команды git bissect. Ниже я лишь перечислю команды которые вам понадобятся в процессе, остальное — читайте в git help bissect.

# manual process of searching a bug
git bisect start
git bisect bad        # the starting commit has a bug
git bisect good mytag # the commit with tag mytag doesn't have a bug

git bisect bad        # the commit in between the staring commit and mytag commit has a bug
git bisect good
git bisect good
git bisect bad

git bisect reset # get back to the starting commit

# if you have written an automated test to check for a bug presence
git bisect run ./testbugshellscript.sh

Добавить комментарий

Ваш адрес email не будет опубликован.