Контроль версий как инженерная практика
Есть навыки, которые выглядят как «умение пользоваться инструментом», а на самом деле определяют, как работает вся команда. Контроль версий — главный из них. Человек, который освоил десяток команд Git, но не понимает, зачем они, будет каждую неделю попадать в ситуации вида «я всё потерял» и «мы затёрли работу друг друга». Человек, который понял модель, спокойно распутает любую из них — и, что важнее, выстроит вокруг репозитория процесс, в котором такие ситуации просто не возникают.
Эта глава — про контроль версий как инженерную практику, а не про команды Git. Она отвечает на вопросы «зачем это вообще», «как устроена модель на пальцах» и «какие командные решения принимаются вокруг репозитория». За командами, объектной моделью, разрешением конфликтов и восстановлением потерянного — отдельный трек «Git» из четырнадцати глав, ссылки на нужные места расставлены по тексту и собраны в конце.
1. Три вопроса, на которые отвечает контроль версий
Правильная аналогия для системы контроля версий — не «резервная копия», а журнал операций над проектом. Резервная копия отвечает на один вопрос: «как вернуть вчерашнее состояние». Журнал отвечает на три, и все три критичны:
«Что было раньше?» Функция работала в пятницу и сломалась в понедельник. Без истории у вас есть только текущий код и догадки. С историей — список из сорока изменений между двумя точками и способ за десяток шагов найти виновное (git bisect делает это делением пополам, см. «Поиск по истории»).
«Кто и зачем это сделал?» Строка выглядит бессмысленной, и рука тянется её удалить. История отвечает: строку добавили полтора года назад, в коммите с текстом «обход бага драйвера X при версии ядра ниже 5.4, воспроизводится только на проде». Удаление отменяется, а вместе с ним и трёхдневный инцидент. Именно здесь история работает как документация.
«Как двум людям не затереть друг друга?» Пока разработчик один, версии можно вести папками project_final_v2_ok. Как только их двое, появляется задача слияния параллельных изменений, и она не решается ни аккуратностью, ни договорённостями в чате — только инструментом, который умеет сравнивать и объединять.
Всё остальное — ветки, теги, PR, релизы — надстройки над этими тремя ответами. Если решение вокруг репозитория не улучшает ни один из них, оно, скорее всего, лишнее.
2. Что держать под версией, а что нет
Ходовое заблуждение: «репозиторий — это для кода». На практике под версией должно быть всё, что определяет поведение системы и что можно осмысленно сравнить построчно.
Держать под версией обязательно:
| Что | Почему | Куда дальше |
|---|---|---|
| Исходный код | Очевидно | — |
| Конфигурация приложения (без секретов) | Изменение конфига ломает прод не реже, чем изменение кода | «Окружения и конфигурация» |
| Инфраструктура как код | Терраформ-план — такое же изменение системы, как правка функции | «Terraform и IaC» |
| Миграции схемы БД | Порядок и обратимость важны так же, как сам SQL | «Слой данных» |
| Определения конвейеров CI | Иначе процесс сборки — устное знание одного человека | «CI/CD» |
| Документация и ADR | Решение живёт рядом с кодом и стареет вместе с ним | «ADR» |
| Зафиксированные версии зависимостей (lock-файлы) | Воспроизводимость сборки | «Сборка и зависимости» |
Не держать:
- Секреты. Пароли, токены, приватные ключи. Причина техническая, а не гигиеническая: история неизменяема, и попавший в неё секрет остаётся доступен всем, у кого есть клон, — навсегда. Правильное место — менеджер секретов, см. «Управление секретами».
- Артефакты сборки. Скомпилированные бинарники,
node_modules,dist/. Они выводятся из исходников; хранить их — значит раздувать репозиторий и получать конфликты там, где их не должно быть. - Большие бинарные файлы. Git хранит каждую версию файла целиком, а диффа для 200-мегабайтного видео не существует. Пятьдесят правок макета — это несколько гигабайт в истории у каждого, кто сделал клон. Решение — Git LFS: в репозиторий кладётся указатель, само содержимое живёт в отдельном хранилище. Для дизайн-макетов, датасетов и медиа это единственный работающий путь; подробности про большие репозитории — в «Монорепо».
Простое правило: если файл генерируется — его место в .gitignore; если файл определяет, что произойдёт на проде, — его место в репозитории; если файл открывает доступ — его место в хранилище секретов.
Два файла настройки, которые стоит завести в первый день проекта, а не после первого инцидента:
# .gitignore — то, что выводится из исходников и не должно попадать в историю
node_modules/
dist/
*.pyc
.env # локальные переменные окружения: секреты только здесь и только локально
.env.example # а этот, наоборот, коммитим — он документирует набор переменных
!.env.example
# .gitattributes — крупные бинарники уходят в LFS, а не в объектную базу
*.psd filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.lock text eol=lf # единый перевод строки: половина «конфликтов» между Windows и Linux — про это
Обратите внимание на пару .env / .env.example: сам файл с значениями исключён, а его шаблон с пустыми ключами закоммичен. Это дешёвый способ одновременно не утечь секретами и не заставлять нового человека выяснять список переменных по трассировкам падений.
3. Как мы сюда пришли
История контроля версий полезна не ради дат, а потому что каждый шаг снимал конкретное ограничение предыдущего — и понимание этих ограничений объясняет, почему сегодняшние инструменты устроены именно так.
Что происходило по существу. SCCS и RCS версионировали отдельные файлы, а работать двоим одновременно позволяли только через блокировку: пока файл занят, остальные ждут. CVS снял блокировку и разрешил параллельную правку, но по-прежнему мыслил файлами, а коммит не был атомарным — обрыв связи посередине оставлял репозиторий в полусохранённом состоянии. Subversion (2000, первый стабильный релиз в 2004) починил это: коммит атомарен и версионируется всё дерево сразу — идея, которую Git унаследовал и довёл до конца.
Разрыв 2005 года стоит отдельного абзаца. Ядро Linux с 2002 года разрабатывалось в проприетарном BitKeeper по бесплатной лицензии, и в апреле 2005-го эта лицензия была отозвана. Проекту с тысячами участников срочно понадобилась замена, которой не существовало. Линус Торвальдс написал первую работающую версию Git за несколько недель (первый коммит — 7 апреля 2005), почти одновременно Мэтт Макалл начал Mercurial. Обе системы распределённые, обе быстрые, обе решают одну задачу — и это не совпадение: задача была общей и острой.
GitHub (2008) изменил не технологию, а социальный процесс: pull request сделал обсуждение изменения перед вливанием нормой, а публичный профиль превратил вклад в открытый код в часть профессиональной репутации. Последняя строка таймлайна — про масштаб: когда репозиторий вырастает до миллионов файлов, полный клон перестаёт работать, и появляются частичный клон, sparse-checkout и виртуальные файловые системы.
Канонический источник по всему этому — бесплатная книга Pro Git и сайт git-scm.com.
4. Централизованная модель против распределённой
Разница между CVS/SVN и Git обычно объясняется как «в Git быстрее». Это верно, но это следствие. Настоящая разница — где живёт история.
В централизованной системе рабочая копия — это витрина: у вас есть текущие файлы, а история и все прочие версии лежат на сервере. Посмотреть лог, сравнить с прошлым месяцем, создать ветку — сетевые операции. Нет сети — нет работы. Сервер потерян без резервной копии — потеряна история проекта.
В распределённой системе каждый клон — полноценный репозиторий: вся история, все ветки, все теги лежат у вас на диске. «Центральный» репозиторий центральный только по договорённости команды, технически он такой же клон.
Отсюда три следствия, и только первое про скорость:
- Скорость.
log,diff,blame,checkout, создание ветки и коммит не ходят в сеть вовсе. Разница не в процентах, а на порядки: операция, которая занимала секунды, занимает миллисекунды. Когда операция становится мгновенной, ей начинают пользоваться — а значит, начинают чаще смотреть историю и чаще коммитить. - Меняется процесс, а не только скорость. Коммит перестаёт быть публичным актом. Можно сделать пять черновых коммитов, переупорядочить их, слить в два осмысленных и только потом показать команде. Ветка перестаёт быть дорогой операцией — в SVN ветка была событием, в Git это перестановка указателя, и ветки создают на каждую задачу.
- Устойчивость. История размножена по всем машинам команды. Потеря сервера — административная неприятность, а не катастрофа.
Сведём разницу в таблицу — она пригодится, если вам придётся объяснять переход команде, привыкшей к SVN или Perforce:
| Централизованная | Распределённая | |
|---|---|---|
| Где полная история | Только на сервере | В каждом клоне |
| Просмотр лога, diff, blame | Запрос по сети | Локально, мгновенно |
| Работа без сети | Практически невозможна | Полноценная, кроме обмена с коллегами |
| Создание ветки | Дорогая операция, событие | Перестановка указателя, рутина |
| Коммит | Публичный акт, сразу виден всем | Локальный черновик до push |
| Потеря сервера | Потеря истории проекта | Административная неприятность |
| Основная сложность | Очередь за общей веткой | Расхождение историй и слияния |
Обратная сторона честная: распределённость означает, что расхождение историй — норма, и с ним надо уметь работать. Отсюда весь пласт вопросов про слияние, перебазирование и конфликты, которого в централизованной модели просто не было в таком объёме, — см. «Merge и rebase» и «Конфликты».
5. Снимки вместо патчей
Это главное отличие модели данных Git, и его стоит понять по-настоящему, а не запомнить фразой.
Модель дельт (RCS, CVS, SVN): система хранит исходную версию каждого файла плюс цепочку изменений к ней. Чтобы получить состояние файла на определённый момент, надо взять базу и последовательно применить патчи. Экономно по диску — в 1982 году это был решающий аргумент.
Модель снимков (Git): при каждом коммите система сохраняет состояние всего дерева проекта целиком. Файл не изменился с прошлого раза — он не копируется, вместо него в снимок кладётся ссылка на уже сохранённое содержимое.
Дельты: файл.txt = база + патч1 + патч2 + патч3 + патч4 ...
чтобы получить версию N, надо применить N патчей по порядку
Снимки: коммит1 -> [A1] [B1] [C1]
коммит2 -> [A2] [B1] [C1] B и C не менялись: те же ссылки
коммит3 -> [A2] [B1] [C2] версия читается сразу, без пересчёта
Почему это проще и быстрее:
- Чтение любой версии — константное по числу коммитов. Достать состояние двухлетней давности стоит столько же, сколько достать вчерашнее: надо прочитать один снимок, а не проиграть две тысячи патчей.
- Целостность проверяется тривиально. Каждый объект адресуется хешем от собственного содержимого. Изменить что-то в истории незаметно невозможно: поменялось содержимое — поменялся хеш — не сошлось у родителя. Это то самое «наблюдение за целостностью данных», и оно не опция, а свойство модели.
- Дедупликация бесплатна. Два одинаковых файла в разных каталогах — один объект в хранилище. Файл, вернувшийся к прежнему содержимому, — снова тот же объект.
- Ветвление и слияние дёшевы. Ветка — это указатель на снимок. Сравнение двух веток — сравнение двух деревьев, а не свёртка двух цепочек патчей.
Экономию диска Git при этом не теряет: при упаковке в pack-файлы он всё-таки применяет дельта-сжатие между похожими объектами — но это деталь хранения, а не модель. Логически коммит остаётся полным снимком. Именно поэтому Git часто описывают как «маленькую файловую систему с мощными инструментами поверх».
Как именно устроены четыре типа объектов, чем tree отличается от blob и как из этого выводятся все команды — в «Внутреннем устройстве Git». Для повседневной работы достаточно образа «коммит = снимок дерева + ссылка на родителя + метаданные».
6. Три состояния и три раздела
Вторая половина рабочей ментальной модели — где физически находится файл прямо сейчас. Git различает три раздела:
- Рабочий каталог — обычные файлы на диске, извлечённые из какой-то версии проекта. То, что видит редактор.
- Индекс (он же staging area, «область подготовки») — промежуточный список того, что войдёт в следующий коммит. Это не буфер, а полноценный слепок будущего снимка.
- Каталог
.git— база объектов и метаданные. Собственно репозиторий; всё остальное производно от него.
Из трёх разделов следуют три состояния файла: изменён (правка есть на диске, но не отмечена), подготовлен (правка отмечена для следующего коммита), зафиксирован (правка сохранена в базе объектов).
Индекс кажется лишней сущностью ровно до первого раза, когда он оказывается нужен. Он позволяет собрать коммит выборочно: вы правили три вещи одновременно, а зафиксировать хотите их тремя разными коммитами с разными сообщениями. Без индекса пришлось бы либо коммитить всё скопом, либо жонглировать файлами вручную. С ним — git add -p и три чистых коммита.
Ловушка, на которую все наступают ровно один раз: индекс хранит содержимое на момент add. Отредактировали файл после git add — в коммит уйдёт старая версия, а новая останется в состоянии «изменён». Отсюда привычка смотреть git status перед коммитом.
Ежедневный цикл поверх этой модели — в «Ежедневном рабочем цикле».
7. Практика, а не команды: что такое хороший коммит
Здесь начинается то, ради чего написана глава. Команды Git выучиваются за неделю; практика различает команды, где история — актив, и команды, где она мусор.
Хороший коммит удовлетворяет трём условиям:
- Одна причина изменения. Не «один файл» и не «один час работы», а одна связная причина. Рефакторинг и исправление бага в одном коммите — плохо: откатить баг-фикс, не откатив рефакторинг, уже нельзя, и
bisectпокажет на коммит, где смешаны две вещи. - Рабочее состояние в каждой точке. Проект собирается и тесты проходят на каждом коммите ветки. Иначе поиск делением пополам спотыкается о нерабочие точки, а откат на произвольный коммит перестаёт быть безопасной операцией.
- Сообщение отвечает на «почему», а не на «что». «Что» видно в диффе — Git покажет изменённые строки лучше любого текста. Не видно намерения.
Плохо: Исправлен баг
Обновление
фиксы
WIP
Хорошо: Не отправлять письмо о заказе при повторной обработке события
Брокер доставляет OrderCreated хотя бы один раз, и при повторе
клиент получал второе письмо. Проверяем идемпотентный ключ
перед отправкой; ключ живёт 72 часа, что покрывает окно ретраев.
Воспроизведение: тест test_duplicate_order_created_sends_one_email.
Conventional Commits (conventionalcommits.org) — конвенция, добавляющая к сообщению машиночитаемый префикс: feat:, fix:, docs:, refactor:, chore:, плюс ! или BREAKING CHANGE: для несовместимых изменений.
feat(orders): добавить идемпотентный ключ в обработчик OrderCreated
fix(billing)!: считать НДС по ставке страны покупателя, а не продавца
Ценность не в красоте, а в автоматизации: по префиксам генерируется changelog, вычисляется следующая семантическая версия и принимается решение о типе релиза. Стоит это принимать не потому, что «так модно», а когда у вас есть конкретный потребитель этой разметки. Если его нет — договорённости «повелительное наклонение, до 72 символов в заголовке, тело отвечает на почему» уже достаточно.
Механика тут вторична, но одну привычку стоит назвать: коммит собирается из кусков правки, а не из файлов целиком. Вы час работали над тремя вещами сразу — это нормально и не обязано отражаться в истории одним комком:
git add -p src/orders.py # выбрать конкретные куски правки в индекс
git commit -m "fix(orders): не отправлять письмо при повторном событии"
git add -p src/orders.py # оставшиеся куски — во второй, независимый коммит
git commit -m "refactor(orders): вынести сборку письма в отдельную функцию"
Это ровно тот случай, ради которого существует индекс. Разбор команд и режимов — в «Ежедневном рабочем цикле».
История — это документация для будущей отладки. Через два года никто не вспомнит, почему коэффициент равен 1.07, но git log -S "1.07" найдёт коммит, где он появился, и покажет обоснование. Такой поиск возможен ровно в той мере, в какой сообщения содержат смысл, — см. «Поиск по истории».
8. Модель ветвления — решение о процессе, а не о командах
Ветка технически стоит копейки, поэтому вопрос не «умеем ли мы ветвиться», а «какой процесс мы описываем ветками». Три модели покрывают почти всё:
Trunk-based development. Одна долгоживущая ветка. Изменения вливаются в неё маленькими порциями, минимум раз в день; недоделанная функциональность прячется за фича-флагами. Требует хорошего автотеста и культуры мелких изменений, зато даёт минимум конфликтов и мгновенную интеграцию. Это модель команд с высокой частотой релизов; исследования DORA (dora.dev) стабильно связывают её с лучшими показателями поставки.
GitHub Flow. Одна основная ветка плюс короткоживущие ветки задач, каждая через pull request. Компромисс: ревью встроено в процесс, ветки живут дни, а не недели. Модель по умолчанию для веб-сервисов с непрерывной поставкой (документация GitHub).
GitFlow. Ветки main, develop, feature/*, release/*, hotfix/* с жёстким регламентом переходов. Предложена Винсентом Дриссеном в 2010 году; сам автор позже добавил к статье примечание, что для непрерывно поставляемых веб-приложений она избыточна. Остаётся оправданной там, где вы поддерживаете несколько версий одновременно: коробочный продукт, мобильное приложение с долгим циклом ревью в сторе, прошивка.
Критерий выбора всего два, и оба не про Git:
- Частота релизов. Релиз несколько раз в день — trunk-based или GitHub Flow. Релиз раз в квартал с фазой стабилизации — модель с релизными ветками.
- Стоимость ошибки. Откатить веб-сервис — минуты. Откатить прошивку у клиента или версию в App Store — недели. Чем дороже ошибка, тем больше оправдан регламент между «готово» и «у пользователя».
Подробный разбор моделей со схемами слияния — «Стратегии ветвления».
9. Ревью как часть контроля версий
Pull request — это не функция GitHub, а единица обсуждения изменения: набор коммитов, у которого есть автор, цель, обсуждение и решение. Он превращает «я закоммитил» в «мы согласились».
Практический вывод, который стоит помнить наизусть: размер PR — главный предиктор качества ревью. Классическое исследование SmartBear на кодовой базе Cisco показало, что эффективность обнаружения дефектов резко падает после 200–400 изменённых строк, а внимание рецензента — примерно после часа работы (разбор методики). Google в своих инженерных практиках делает из этого прямое правило: пишите маленькие CL.
Что из этого следует для повседневной работы:
- PR на 2000 строк не получает ревью — он получает «LGTM». Это не лень рецензента, а предел внимания.
- Большой PR надо резать по швам: сначала механический рефакторинг отдельным PR, потом изменение поведения. Ревьюер тогда читает двадцать содержательных строк, а не пять экранов переименований.
- Описание PR — это черновик будущего сообщения коммита и ADR: зачем, какие альтернативы отброшены, как проверить.
- Автоматизируемое не обсуждают: форматирование, стиль и очевидные ошибки должен ловить конвейер, а не человек — см. «Качество в потоке».
Рабочий минимум перед тем, как отправить PR на ревью:
- изменение решает одну задачу и укладывается в разумный объём — если нет, режется на два PR;
- конвейер зелёный: сборка, тесты, линтер, сканер секретов уже отработали;
- в описании есть «зачем» и способ проверить результат;
- отброшенные альтернативы названы — иначе ревьюер предложит их заново;
- изменения, ломающие совместимость, помечены явно, и указан план миграции.
Механика PR — в «Pull request» и «Совместной работе»; культура ревью, чек-листы и как давать обратную связь — в «Код-ревью и стандарты».
10. Репозиторий как центр инженерного процесса
Контроль версий не стоит особняком — он держит на себе почти всю остальную практику. Стоит увидеть эти связи явно, потому что каждая из них превращает историю из архива в рабочий инструмент.
| Связь | Что даёт репозиторий |
|---|---|
| Коммит запускает конвейер | Каждое изменение проверяется автоматически, а не «когда дойдут руки» — «CI/CD» |
| Тег определяет релиз | Версия — не строка в чате, а точка в истории, к которой можно вернуться |
История даёт git bisect |
Регрессия локализуется за log₂(N) шагов вместо перебора |
| IaC в репозитории даёт GitOps | Желаемое состояние инфраструктуры описано в ветке, оператор приводит кластер к нему — «Helm и GitOps» |
| Хуки автоматизируют проверки | Форматирование и линт до коммита, а не в ревью — «Хуки и автоматизация» |
| Подписанные коммиты и lock-файлы | Прослеживаемость цепочки поставки — «Supply chain» |
Обратите внимание на общий мотив: во всех строках репозиторий выступает источником истины. Не «место, где лежит код», а точка, из которой выводятся сборка, окружение, релиз и инфраструктура. Всё, что живёт мимо него — конфиг, поправленный руками на сервере; правило балансировщика, добавленное через консоль облака, — рано или поздно становится причиной инцидента, который невозможно объяснить, глядя в историю.
11. Типичные ошибки
Коммит «фиксы». Час работы, восемь файлов, сообщение из одного слова. Такой коммит нельзя ни откатить, ни объяснить, ни найти через полгода. Лечение — не дисциплина усилием воли, а привычка коммитить мелко и приводить историю в порядок перед публикацией ветки (интерактивное перебазирование, «Переписывание истории»).
Секрет в истории. Токен закоммичен и удалён следующим коммитом — кажется, что проблема решена. Она не решена: git rm удаляет файл из будущих снимков, но не из прошлых. Объект с секретом остаётся в базе и приезжает при каждом клоне. Порядок действий строго такой: сначала отозвать и перевыпустить секрет (это обязательно и первым делом, потому что он уже мог утечь), затем чистить историю (git filter-repo, BFG) и принудительно обновлять все копии, затем закрывать дыру в процессе — сканер секретов в конвейере и в pre-commit хуке. Механика чистки — в «Переписывании истории», правильное хранение — в «Управлении секретами».
Долгоживущие ветки. Ветка живёт три недели, за это время основная уходит на двести коммитов вперёд, и слияние превращается в отдельный проект с собственными багами. Сложность конфликта растёт нелинейно от времени расхождения. Лечение — резать задачу, вливать основную ветку в свою регулярно, прятать незаконченное за флагами.
Force-push в общую ветку. Перезаписывает историю, которую уже забрали коллеги: их следующая работа строится на исчезнувших коммитах. Правило простое: переписывать можно только историю, которую никто не забирал (своя ветка задачи). Общие ветки защищаются на уровне сервера — protected branches плюс --force-with-lease как минимальная страховка вместо голого --force.
Бинарники в репозитории. Обсуждали выше; добавим следствие — клон растёт до гигабайтов, CI тратит минуты на выкачивание на каждом запуске, а git gc перестаёт помогать. Проблема решается на входе (.gitignore, LFS), а задним числом — только переписыванием всей истории.
Мердж-коммиты без содержания. История, состоящая наполовину из «Merge branch ‘main’ into ‘main’», нечитаема. Это вопрос настройки: политика вливания в проекте должна быть выбрана осознанно (merge, squash или rebase) — сравнение в «Merge и rebase».
12. Куда идти дальше за глубиной
Эта глава сознательно остановилась на уровне модели и практики. Всё остальное подробно разобрано в треке «Git» — он же служит картой. Порядок, в котором его полезно читать:
- «Внутреннее устройство» — четыре типа объектов, DAG коммитов, ссылки. Час чтения, который окупается годами: после него команды выводятся, а не заучиваются.
- «Ежедневный рабочий цикл» — status, add, commit, log, отмена ошибок. Минимальный набор на каждый день.
- «Стратегии ветвления» — модели процесса подробно, с их стоимостью.
- «Merge и rebase» — две операции объединения и когда какая уместна.
- «Переписывание истории» — amend, интерактивное перебазирование, filter-repo и правила безопасности.
- «Конфликты» — как читать маркеры, как их не бояться и как уменьшать их число.
- «Восстановление» — reflog и почему в Git почти ничего нельзя потерять окончательно. Прочитайте до того, как понадобится.
- «Монорепо» — жизнь больших репозиториев: частичный клон, sparse-checkout, границы владения.
- «Совместная работа» — форки, права, защита веток, работа с внешними контрибьюторами.
- «Удалённые репозитории и протокол» — что реально происходит при push и fetch.
Мини-итог
- Контроль версий — не резервная копия, а журнал операций над проектом; он отвечает на «что было раньше», «кто и зачем» и «как не затереть друг друга».
- Под версией держат всё, что определяет поведение системы: код, конфиг, инфраструктуру, миграции, конвейеры, документацию. Не держат секреты, артефакты сборки и крупные бинарники.
- Распределённая модель меняет не только скорость, но и процесс: коммит становится черновиком, ветка — бесплатной, история — устойчивой к потере сервера.
- Снимок вместо цепочки патчей даёт постоянную стоимость чтения любой версии, дешёвое ветвление и проверяемую целостность через хеш содержимого.
- Три раздела (рабочий каталог, индекс,
.git) и три состояния файла — минимальная модель, из которой выводятся ежедневные команды. - Хороший коммит: одна причина, рабочее состояние, сообщение про «почему». Хорошая история — это документация для будущей отладки.
- Модель ветвления выбирается по частоте релизов и стоимости ошибки, а не по популярности схемы.
- Размер PR — главный предиктор качества ревью; всё автоматизируемое должно ловиться конвейером, а не человеком.
Источники
- Pro Git — Скотт Чакон и Бен Штрауб, бесплатная и до сих пор лучшая книга по теме.
- git-scm.com — официальная документация и справочник команд.
- Conventional Commits — спецификация машиночитаемых сообщений коммитов.
- Trunk Based Development и GitHub Flow — описания моделей ветвления от их сторонников.
- Винсент Дриссен. A successful Git branching model — оригинал GitFlow вместе с авторским примечанием об области применимости.
- Google Engineering Practices — практики код-ревью, включая правило маленьких изменений.
- DORA — исследования связи инженерных практик с результатами поставки.
- Git LFS — работа с большими файлами.
Что дальше
История изменений есть, процесс вокруг неё выстроен. Следующий вопрос — то, из чего эти изменения появляются: редактор, терминал, локальный запуск системы и всё, что превращает машину разработчика в рабочий инструмент.