Взаимодействие с git

Хотя для git есть дикое количество графических интерфейсов, в его основе лежит консольная утилита, тонкими обёртками к которой вышеупомянутые графические интерфейсы являются.

Поэтому здесь мы обсудим именно процесс работы с консольной утилитой (который весьма специфичен по сравнению, например, с таковым для системы контроля версий гуглодокументов или же некоторых сред разработки; причём вся эта специфичность сохраняется и при работе с графическими интерфейсами).

Самое главное

Если хочется узнать что-то поподробнее, git help Вам в помощь. Также им рекомендуется пользоваться во всех непонятных ситуациях.

Начало работы

Для того, чтобы объявить текущую папку «git-репозиторием» (проще говоря — отдать под управление git), можно воспользоваться командой git init, находясь в этой папке.

Также можно создать новую папку под управлением git командой git init название_папки или же создать т.н. «голый» репозиторий командой git init название_папки --bare. Зачем нужен последний — см. соответствующий подраздел.

Два основных этапа работы

Содержимое git-репозитория называется Working Tree. Для того, чтобы сохранить состояние Working Tree в истории репозитория, нужно сделать две вещи:

  • пометить командой git add те файлы, изменения в которых следует сохранить
  • подтвердить сохранение изменений командой git commit

Для простоты первый шаг можно делать командой git add -A, которая помечает все изменения во всех файлах Working Tree (в том числе — новые файлы и удалённые файлы).

На втором шаге команда git commit вызывает некоторый текстовый редактор, в котором предлагается кратко описать суть произведённых изменений. Этот файл нужно сохранить и закрыть текстовый редактор. Если Вам вызванный git-ом текстовый редактор не нравится, можно либо перенастроить git (см. git help config), либо же пользоваться командой git commmit -m краткое_описание.

В заключение скажем, что если хочется игнорировать некоторые файлы, находящиеся в Working Tree, их можно прописать в файл с названием .gitignore, который следует поместить в ту папку, внутри которой (в том числе — в её подпапках, подпапках подпапок и т.д.) лежат эти самые игнорируемые файлы.

Ветки

Самая неочевидная часть git — т.н. «ветки».

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

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

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

Для работы с Branch используются в основном три комадны:

  • git branch — для создания новых веток и печати имеющихся
  • git checkout — для перехода к какой-то ветке или какой-то версии
  • git merge — для слияния текущей ветки с какой-нибудь другой

Для того, чтобы объяснить, как эти команды работают, введём ещё один термин: словом HEAD называется та версия, в которой «мы сейчас находимся».

При этом голова (т.е. HEAD) может быть в одном из двух состояний:

  • привязана к какой-то ветке
  • привязана к какой-то версии, но не к ветке (такое состояние называется detached)

Состояние, в котором находится HEAD, влияет на поведение команды git commit:

  • если голова привязана к ветке, то эта ветка (напомним ещё раз, что ветка — это просто название для версии, а не путь в графе) переходит на свежесозданную версию
  • если голова привязана к версии, то никакие ветки никуда не двигаются

Изначально в свежесозданном репозитории есть ровно одна ветка (обычно с названием master или main), к которой привязана голова.

Для того, чтобы привязать голову к какой-то версии, нужно выполнить команду git checkout с идентификатором этой версии в качестве аргумента (идентификаторы можно посмотреть командой git log).

Для того, чтобы привязать голову к какой-то ветке, нужно либо выполнить команду git checkout с этой самой веткой (напомним в третий раз, что ветка — это всего лишь название для версии) в качестве аргумента, либо выполнить команду git branch новая_ветка.

Слияние веток и работа с удалёнными репозиториями

Иногда ответвления истории образуются как некоторые независимые друг от друга нити работы над проектом (например, один человек изменяет одну часть, другой — другую).

В такой ситуации ответвления приходится в некоторый момент соединять. Причём зачастую эти ответвления находятся в разных репозиториях, склонированных из какого-то одного общего.

Один репозиторий

Если ответвления находятся в одном репозитории, то всё весьма просто:

  • переходим в одну из веток
  • делаем git merge другая_ветка
  • если всё ок, то на этом всё заканчивается
  • если же возникли какие-то конфликты, которые не удаётся исправить встроенными в git эвристиками, исправляем их вручную и делаем git commit

Настоятельно рекомендуется хотя бы раз попробовать вызвать нетривиальный конфликт и посмотреть, как именно git обозначает этот конфликт внутри соответствующего файла, чтобы потом это не стало неприятным сюрпризом.

Несколько репозиториев

Любой репозиторий (хоть — находящийся на том же компьютере, но в другой папке, хоть — находящийся на другом компьютере) можно склонировать командой git clone, аргументом которой должен быть путь к репозиторию. Обычно этот путь — одно из трёх:

  • относительный или абсолютный путь папки на том же компьютере
  • если другой компьютер доступен по ssh — имя другого компьютера (такое, как настроено в .ssh/config), за которым следует двоеточие, за которым следует имя папки на другом компьютере
  • для сервисов типа github — просто обычный интернет-локатор наподобие https://github.com/foobar/baz

Склонированный репозиторий называется удалённым (remote, а не deleted, как можно было бы подумать) по отношению к его клону. Внутри репозитория-клона становятся доступными новые команды:

  • git push — перенести текущую ветку (со всеми необходимыми версиями) на удалённый репозиторий и слить там с одноимённой веткой
  • git pull — перенести одноимённую с текущей ветку с удалённого репозитория в локальный и слить там с текущей

Голые репозитории

Некоторую проблему представляет git push в ту ветку, которая на удалённом репозитории является текущей. С этим можно бороться двумя способами:

  • не делать git push в текущую для удалённого репозитория ветку
  • сделать удалённый репозиторий вообще без Working Tree (т.н. bare-репозиторий)

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

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