Git: подстраховка для ваших проектов

Я очень хорошо помню 10 января 2010 года: в этот день мы потеряли всю историю изменений нашего проекта. Мы использовали Subversion в качестве системы контроля версий, которая сохраняла историю нашего проекта в центральном репозитории на сервере. К тому же мы регулярно выполняли резервное копирование данных с сервера — по крайней мере, мы так думали. Сервер сломался, и все резервные копии оказались нерабочими. Наш проект уцелел, однако вся история изменений была утеряна.

Вскоре после поломки сервера мы перешли на Git. Управление версиями мне всегда казалось пыткой; оно было слишком сложным и недостаточно удобным, чтобы я мог оценить его ценность, тем не менее, я выполнял его как нелюбимую обязанность. Однако прошло некоторое время работы с новой системой и я начал понимать, каким полезным может быть Git на самом деле. С того времени он не раз спасал мою шею.

В этой статье я расскажу о том, каким образом Git может помочь вам избежать ошибок и как восстановить проект, если они все-таки имели место.

Каждому члену команды по резервной копии

Поскольку Git является распределённой системой управления версиями, каждый член нашей команды, который клонировал проект (или же «создал её рабочую копию», если вы привыкли к терминологии Subversion), автоматически создаёт на своём диске резервную копию. Эта резервная копия содержит самую последнюю версию проекта, а также полную историю его изменений.

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

Ветки позволяют разделить то, что нужно разделить

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

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

Почему ветки важны

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

(a) Я уже успел понять, что создание небольших атомарных коммитов с небольшим количеством изменений — полезная привычка, когда имеешь дело с системой управления версиями. Однако, если я делал так в процессе разработки нового компонента, каждый коммит смешивал мой полусырой компонент с основной кодовой базой до тех пор, пока я не закончу. Мои коллеги были не в восторге от того, что из-за моего незаконченного компонента в проекте появлялись ошибки.

(б) Чтобы избежать перемешивания незаконченной работы с другими вещами (моего авторства или авторства моих коллег), я работал над компонентом в отдельном месте. Я создавал копию папки проекта, с которой мог работать, никому не мешая, и коммитил мою разработку после её завершения. Однако внесение всех изменений в самом конце работы вело к тому, что у меня получался один огромный раздутый коммит, содержащий все изменения. Ни мои коллеги, ни я глядя на него впоследствии не могли разобраться, что именно было изменено в этом коммите.

Я постепенно пришёл к осознанию того, что мне придётся освоить ветки, если я хочу улучшить свой процесс разработки.

Работа в различных контекстах

Любой проект предусматривает множество контекстов, в которых ведётся работа; каждый компонент, исправление ошибки, эксперимент или альтернатива вашему продукту — это своего рода контекст сам по себе. Его можно рассматривать как отдельный «тематический раздел», чётко отграниченный от других таких разделов.

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

Использование веток вселило в меня уверенность, что я не могу напортачить. Если что-то идёт не так, я могу вернуться назад, отменить действие, начать с нуля или сменить контекст.

Основы использования веток

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

Чтобы создать новую ветку на основании текущего состояния, всё что вам нужно сделать — это выбрать имя и выполнить одну команду в командной строке. Допустим, мы хотим начать работу над новой версией контактной формы, следовательно создадим новую ветку под названием «contact-form»:

$ git branch contact-form

Если выполнить команду git branch не указывая имя ветки, мы получим список всех веток, которые есть в данный момент (а ключ «-v» предоставит немного дополнительной информации):

$ git branch -v

Скриншот

Вы, наверное, заметили маленькую звёздочку напротив ветки под названием «master». Она обозначает ветку, которая на данный момент является активной. Итак, перед тем, как начать работу над нашей контактной формой, нам нужно сделать её активным контекстом:

$ git checkout contact-form

Теперь Git сделал эту ветку нашим текущим рабочим контекстом. (На языке Git это называется «HEAD branch»). Все изменения и любой коммит, который мы совершим с текущего момента, будут влиять только на этот контекст — остальные контексты останутся нетронутыми. Если мы захотим переключить контекст на другую ветку, мы просто снова используем команду git checkout.

Если мы хотим добавить изменения из одной ветки в другую, мы можем выполнить их «слияние» в текущий рабочий контекст. Представьте, что некоторое время мы работали над нашим компонентом «contact-form», а теперь хотим добавить эти изменения в ветку «master». Всё, что нам нужно сделать — переключиться на эту ветку и вызвать git merge:

$ git checkout master
$ git merge contact-form

Использование веток

Я настоятельно советую активно использовать ветки в ежедневной работе. Ветки — это одна из ключевых концепций, вокруг которых был построен Git. Они требуют минимальных затрат и изумительно просты в создании и управлении. Существует множество ресурсов, если вы хотите узнать больше о том, как их использовать.

Отмена действий

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

Одна из наиболее замечательных возможностей Git состоит в том, что отменить можно практически любое действие. Это даёт мне достаточно уверенности, чтобы бесстрашно экспериментировать, ведь пока что мне так и не удалось сломать что-то настолько, чтобы это действительно нельзя было исправить.

Внесение поправок в последний коммит

Даже если вы очень аккуратно готовите свои коммиты, очень легко забыть указать какое-либо изменение или допустить опечатку в комментарии. С помощью ключа —amend к команде git commit Git даёт вам возможность изменить самый последний коммит, причём очень простым способом. Например, если вы забыли добавить какое-либо изменение и допустили опечатку в названии коммита, это можно легко исправить:

$ git add some/changed/files
$ git commit --amend -m "Комментарий, в этот раз без опечаток"

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

(Если вас интересует дополнительная информация по опции --amend, советую почитать отличное руководство от Ника Куаранто (Nick Quaranto)).

Отмена локальных изменений

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

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

Если вы хотите восстановить один файл, можете использовать команду git checkout:

$ git checkout -- файл/для/восстановления

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

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

$ git reset --hard HEAD

Это заменит все файлы в вашей рабочей директории на последнюю редакцию, для которой был совершён коммит. Так же, как и в случае с командой checkout, это приведёт к сбросу локальных изменений.

Будьте осторожны с этими операциями: поскольку локальные изменения не были зарегистрированы в репозитории, после сброса восстановить их будет невозможно!

Отмена изменений, для которых был совершён коммит

Само собой, отменить можно не только локальные изменения. Также можно отменить некоторые коммиты, когда это необходимо — например, если они привели к ошибке в коде.

В общем, есть две главные команды, которые позволяют отменить коммит:

(a) git reset

Иллюстрация

Команда git reset позволяет буквально вернуться в прошлое. Вы сообщаете к какой версии вы хотите вернуться, и она восстанавливает именно это состояние — отменяя все изменения, которые были осуществлены после этой временной точки. Нужно всего лишь сообщить ей хэшевый номер коммита, к которому вы хотите вернуться:

$ git reset --hard 2be18d9

Опция -—hard — это наиболее простой и чистый способ, однако она также удаляет все локальные изменения в вашей рабочей директории. Так что перед её применением, убедитесь, что у вас нет локальных изменений, которые дороги вашему сердцу.

(б) git revert

Иллюстрация

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

Команда revert позволяет откатить последствия от одного конкретного коммита. Она не удаляет какие-либо коммиты, в отличие от git reset. Напротив, она создаёт новый коммит, вносящий изменения, противоположные коммиту, который подлежит откату. Например, если вы удалили определённую строку кода, revert создаст новый коммит, добавляющий точно такую же строчку.

Чтобы её использовать, просто укажите SHA1-хеш коммита, который нужно откатить:

$ git revert 2be18d9

Поиск ошибок

Когда дело касается поиска ошибок, должен признать, что я убил не один час блуждая впотьмах. Часто бывало так, что я был уверен, что пару дней назад все работало, но понятия не имел когда же именно всё пошло наперекосяк. И только когда я узнал о git bisect, процесс поиска возникновения проблемы немного ускорился. В виде команды bisect Git предоставляет инструмент, помогающий обнаружить коммит, с которого всё пошло не так.

Представьте следующую ситуацию: мы знаем, что текущая версия (с меткой «2.0») приведена в негодность. Также мы знаем, что несколько коммитов назад (в версии «1.9»), всё было нормально. Ошибка была допущена где-то в этом промежутке.

Иллюстрация

Этой информации уже достаточно, чтобы начать охоту на ошибки с помощью git bisect:

$ git bisect start
$ git bisect bad
$ git bisect good v1.9

После начала процесса мы сообщили Git, что наш текущий коммит содержит ошибку и, следовательно, является «плохим» («bad»). Затем мы сообщили Git какой из предыдущих коммитов точно был рабочим (в качестве параметра для команды git bisect good).

Git после этого восстанавливает проект посередине между заведомо известными хорошим и плохим состоянием:

Иллюстрация

Теперь мы проводим тестирование этой версии (например, с помощью модульного тестирования, создания приложения, внедрения в тестовую систему и т.д.), чтобы определить является ли это состояние рабочим или же уже содержит ошибку. Когда мы это выясним, снова сообщаем Git о результате с помощью git bisect bad или git bisect good.

Предположим, что мы определили этот коммит тоже как «плохой». Фактически это означает, что ошибка была допущена раньше — и Git опять сократит количество коммитов под вопросом:

Иллюстрация

Таким образом вы очень быстро определите, где именно была допущена ошибка. Когда это произойдёт, выполните git bisect reset, чтобы закончить охоту за ошибкой и восстановить исходное состояние проекта.

Инструмент, способный спасти вашу шею

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

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

Дополнительные материалы