Почему гранулярный staging важен
Хорошо составленная история Git рассказывает историю. Каждый commit представляет одно логическое изменение: исправление бага, рефакторинг, новую функциональность. Но реальная разработка редко бывает такой аккуратной. Вы исправляете баг, потом замечаете опечатку рядом, потом поправляете форматирование, потом начинаете небольшой рефакторинг — всё в одном файле. Если вы добавите в staging и закоммитите весь файл целиком, получится commit, смешивающий несвязанные изменения. Это усложняет ревью кода, усложняет поиск багов с помощью bisect и делает историю commit менее полезной как документацию.
Гранулярный staging позволяет разбить вашу работу на осмысленные commit, даже когда изменения переплетены внутри одного файла. Вместо того чтобы коммитить всё разом, вы точно выбираете, какие изменения принадлежат друг другу.
Три уровня staging
Staging на уровне файлов
Самый базовый уровень. Вы добавляете в staging целые файлы с помощью git add:
git add src/auth.ts src/utils.ts
Это подходит, когда каждый файл содержит только одно логическое изменение. Но как только файл содержит несколько несвязанных модификаций, staging на уровне файлов оказывается слишком грубым.
Staging на уровне hunk
Git может разбить изменения файла на «hunk» — непрерывные блоки изменённых строк, разделённые неизменённым кодом. С помощью git add -p (режим патча) Git показывает каждый hunk по очереди и спрашивает, добавить ли его в staging:
git add -p src/auth.ts
Для каждого hunk вы выбираете y (staging), n (пропустить), s (разделить на более мелкие hunk) или e (редактировать вручную). Это существенное улучшение по сравнению со staging на уровне файлов, но у него есть ограничения. Hunk определяются по близости: если два несвязанных изменения находятся рядом (разделены менее чем тремя строками контекста), Git объединяет их в один hunk. Разделить их требует ручного редактирования патча, что утомительно и чревато ошибками.
Staging на уровне строк
Staging на уровне строк — самый точный вариант. Вместо работы с файлами или hunk вы выбираете отдельные строки для staging. Хотите добавить в staging строки 12 и 15, но не строку 14? Пожалуйста. Это даёт вам полный контроль над тем, что попадёт в каждый commit, независимо от того, насколько близко расположены изменения в файле.
В командной строке staging на уровне строк означает ручное редактирование патчей с помощью git add -p и опции e. Нужно понимать формат unified diff, аккуратно модифицировать патч и надеяться, что вы не внесёте ошибки. Это работает, но не то, чем большинство разработчиков хотят заниматься регулярно.
Staging на уровне строк в GitSquid
GitSquid делает staging на уровне строк визуальным и понятным. Когда вы выбираете изменённый файл, средство просмотра diff показывает каждую изменённую строку. Чтобы добавить в staging конкретные строки:
- Кликните на одну строку, чтобы выбрать её.
- Cmd+Click (Ctrl+Click на Windows/Linux) для выбора нескольких отдельных строк.
- Shift+Click для выбора диапазона строк.
После выбора нужных строк добавьте их в staging. Только эти строки добавляются в индекс. Остальное остаётся как не добавленные в staging модификации в вашей рабочей директории, готовое для включения в другой commit.
Тот же рабочий процесс работает и в обратном направлении. Если вы уже добавили в staging строки, которые хотите убрать из области staging, вы можете выбрать их в staging diff и снять со staging только эти строки.
Как это работает внутри
Когда вы добавляете в staging отдельные строки, GitSquid генерирует патч на стороне сервера, включающий только выбранные изменения. Этот патч затем применяется к индексу с помощью механизма патчей Git. Благодаря генерации патча на серверной стороне, а не манипуляции diff на клиентской, результат надёжен и точно соответствует тому, что ожидает Git. Нет проблем с форматированием или ошибок смещения, которые могут возникнуть при ручном редактировании патчей.
Практические примеры
Разделение исправления бага и рефакторинга
Вы работаете над рефакторингом и замечаете баг. Вы исправляете его сразу, потому что исправление очевидно. Теперь ваш файл содержит два типа изменений: рефакторинг и исправление бага. С помощью staging на уровне строк вы выбираете только строки исправления бага, коммитите их с ясным сообщением вроде «fix null check in auth validation», а затем коммитите рефакторинг отдельно. Результат — два чистых, легко проверяемых commit вместо одного запутанного.
Разделение отладочного и продуктового кода
Вы добавили несколько инструкций console.log во время отладки, прямо рядом с реальными изменениями кода. Вместо того чтобы пытаться вспомнить, какие строки отладочные, а какие настоящие, вы визуально выбираете только продуктовые изменения, добавляете их в staging и коммитите. Отладочные строки остаются в рабочей директории, где вы можете продолжать их использовать или удалить позже.
Подготовка частичного commit для ревью
У вас большой набор изменений, но только часть работы готова к ревью. Staging на уровне строк позволяет закоммитить готовые части, оставив незавершённый код незакоммиченным. Ваш pull request остаётся сфокусированным, и ревьюеру не нужно продираться через незавершённый код.
Поддержание атомарности commit
Атомарный commit — это commit, содержащий одно полное логическое изменение. Он проходит тесты, не ломает сборку и может быть понят самостоятельно. Атомарные commit — это не просто вопрос аккуратности. У них есть практические преимущества:
- Ревью кода. Ревьюеры могут понять каждый commit независимо. Commit с заголовком «fix input validation», затрагивающий только логику валидации, легко проверить. Commit с заголовком «various changes», затрагивающий валидацию, стили и конфигурацию, — нет.
- Поиск багов. Когда что-то ломается,
git bisectможет точно определить commit, который внёс проблему. Атомарные commit делают результат осмысленным: вы находите конкретное изменение, вызвавшее баг, а не мешанину несвязанных модификаций. - Revert. Если commit нужно откатить, атомарный commit можно откатить чисто. Смешанный commit может заставить вас откатить изменения, которые вы на самом деле хотели сохранить.
- История как документация. Спустя месяцы, когда кто-то читает историю commit, чтобы понять, почему существует фрагмент кода, атомарные commit с ясными сообщениями рассказывают полезную историю.
Staging на уровне строк — это инструмент, который делает атомарные commit практичными. Без него создание чистых commit из беспорядочной реальной разработки требует дисциплины и ручных усилий. С ним процесс так же прост, как выбор строк, которые принадлежат друг другу.
Лучшие commit — те, которые делают ровно одну вещь. Гранулярный staging даёт вам контроль, чтобы это осуществить, даже когда ваша рабочая директория рассказывает другую историю.