D:\sideБлогGit! Перевесь!

⚠️ Обращайте внимание на даты.
Этот блог больше не ведётся с 17 января 2023, и на тот момент с написания этой страницы (08.04.2015) прошло 7 лет.

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

Начнём с истории. Git был когда-то весьма низкоуровневой штукой. В репозитории была папка .git, с которой разработчики проделывали некие манипуляции руками (переносили файлы, что-то редактировали), после чего запускали чтение/запись в какие-то внутренние хранилища Git. Вообще говоря, папка-то никуда не делась, просто набор команд Git с тех пор очень сильно расширился и на неё перестали обращать внимание.

“Путь к просветлению” лежал через вот этот интерактивный учебник. Там великолепно описывается работа с Git в той части, что не касается управления отдельными файлами, разрешения конфликтов и прочего – исключительно коммиты, ветки, репозитории и их взаимодействие между собой.

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

Фактически, всё новое, что я узнал при работе с Git, можно разделить на два крупных раздела: что из себя представляют коммиты (и как с ними работают с учётом этого) и какими способами можно объединять изменения из разных источников.

Природа коммитов

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

b274eefba2cd357e774097250a2a160c2585c973

Даже считая, что на практике вам потребуются первые 6-10 символов. Но я не только не видел, чтобы кто-либо так делал – я и вам так делать не советую. Это лишняя трата времени. Если коммит вообще есть в репозитории, то его всегда можно найти без предварительных записей.

git reflog

Эта штука выведет всё, что вы творили с репозиторием. Даже такие вроде бы незначительные действия, как checkout. Даже перезапись последнего коммита с помощью commit --amend.

Раз уж мы заговорили об --amend – на самом деле это не перезапись. Эта команда откатывает ветку на один коммит назад (внутри репозитория) и делает из текущего состояния файлов новый коммит с новыми изменениями, так туда попадают изменения и из старого коммита, и ваши новые. Для пущего ощущения перезаписи в редакторе предлагается “пояснение” от того коммита, с которого был совершён откат.

И так работают практически все операции. Если вы перестаёте видеть коммит в git log --all, это ещё не значит, что он пропал. Почему это важно, станет понятно несколько позже.

merge против rebase

Довольно типичная ситуация – вы делаете что-то с репозиторием у себя, а при попытке залить изменения на сервер получаете отказ: кто-то успел записать туда собственные изменения, и вам нужно будет разобраться с ними. Разобраться – в смысле “встроить в свою локальную копию”. И есть два принципиально разных способа это сделать.

Здесь неплохо бы пояснить, что в Git называется “веткой”. Если представить себе репозиторий, как “карту” из коммитов, соединённых между собой, то ветки это эдакие пластиковые фигурки с именами, которые можно по карте перемещать.

То есть, ветка состоит всего лишь из названия и ссылки на коммит. И всё. Но поскольку многие команды двигают ветки в разные места, опытные Git’еры часто наставляют новичков:

Branch early, branch often!

Перефразируя, “в любой непонятной ситуации делай новую ветку”.

Слияние (merge)

Применяется по умолчанию, и не без причины – при этом все изменения сохраняются в оригинальном виде, как были. Поэтому он считается относительно “безопасным” способом слияния веток и используется по умолчанию, если вы сделали git pull, а с сервера пришли коммиты, которых у вас нет. Будет слияние.

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

При этом в истории коммитов это будет выглядеть, как будто бы ветка разошлась на два пути и потом сошлась обратно.

Перевешивание (rebase)

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

Примечательно в этом то, что история изменений ветки назначения остаётся прямой – ветка в итоге не разделяется и не соединяется.

Что внутри? Коммиты перезаписываются, перевешиваясь на новое место? Нет, старые коммиты остаются, где были, но вы потеряете способ на них ссылаться (эта особенность считается неприятной, поэтому rebase по умолчанию не используется). Эту неприятность можно запросто обойти, оставив на старом месте другую ветку. А уж создавать ветки в Git можно в самых неприличных количествах: я уже говорил выше, что в каждой ветке информации, на самом деле, очень мало.

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

Перемотка (fast-forward)

Стоп, я же сказал, что способов два? Ну да, два. Этот способ иногда срабатывает при слиянии, иногда при перевешивании, напрямую он не вызывается. Работает он тогда, когда соединяются два набора изменений, первый из которых является предком второго.

В этом случае никаких новых коммитов не возникает. Просто отстающая ветка переносится вперёд до другой, отсюда название “fast-forward” или “перемотка вперёд”, пришедшее из эпохи кассетных лент.

В сочетании с rebase это действие очень напоминает merge: оно может взять две ветки, одну подвесить на другую и перемотать другую до первой – так в репозитории последним станет коммит с содержимым обеих веток.

В чём отличие?

D---E---F---G
     \
      A---B---C

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

D---E---F---G---H
     \         /
      A---B---C

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

D---E---F---G---A---B---C

PS

В постах возникла довольно внушительная пауза, да. Это я устроился на новую работу, разругавшись с предыдущим работодателем в декабре. Причины на то были, наверное, все какие только могут быть: длинные задержки оплаты, многомесячный игнор со стороны начальства, отсутствие задания на разработку и отсутствие в команде взаимообучения. Разве что коллеги не жаловались. На новом месте всех этих проблем нет, платят вдвое больше, а задачи на порядок интереснее. Так что развитие чувствуется.

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

Но как только я найду время, можете ожидать ударную дозу CoffeeScript и экскурс в Node.js с его экосистемой.

Те из вас, кто ждёт пост по защите аккаунтов – спокойствие, я не забыл, просто решил в нём же рассказать не только о паролях, но и о входе с помощью других сайтов. Но если вам не терпится и вы знаете английский, рекомендую начать с сайта StackExchange “Безопасность информации”.

Ещё в ближайшие дни я хочу завести себе репозиторий .dotfiles – такое место, где хранятся файлы настроек, пригодные для переноса между машинами, .zshrc и .pryrc к примеру, а также добавить скриптиков для “экспресс-установки” нужных в работе вещей: RVM (+Ruby), NVM (+Node.js +пакеты), Haxe (+flixel), Zsh (+Antigen), Guake (+цветосхемы). Для чего? Чтобы меньше зависеть от рабочей ОС и иметь возможность быстро собрать рабочую среду где угодно.