diff --git a/src/pages/git/index.md b/src/pages/git/index.md index 88f278c..ac18c1e 100644 --- a/src/pages/git/index.md +++ b/src/pages/git/index.md @@ -23,9 +23,30 @@ Version control workflow определяет, как создаются branche **Полный ответ** -Version control workflow определяет, как создаются branches, pull requests, releases, hotfixes и rollback. Без общей -договоренности изменения сложнее ревьюить, релизы сложнее собирать, а история становится шумной. Workflow должен -соответствовать размеру команды, частоте релизов и риску продукта. +Version control workflow описывает путь изменения от задачи до production: где создать ветку, как оформить коммиты и +pull request, какие проверки обязательны, кто выполняет review, каким способом изменения попадают в основную ветку и как +выпускаются или откатываются. + +Например, команда может договориться о следующем процессе: + +1. `main` защищена от прямого push; +2. работа ведется в короткоживущей feature-ветке, связанной с issue; +3. pull request должен пройти lint, tests и build; +4. минимум один reviewer проверяет корректность и понятность изменения; +5. после squash merge commit попадает в release notes; +6. проблемный релиз откатывается через `revert`, а не переписыванием общей истории. + +Такой workflow делает состояние репозитория предсказуемым и переносит часть контроля качества из человеческой памяти в +branch protection и CI. По связям issue → pull request → commit → release можно восстановить, зачем появилось изменение +и где обсуждались ограничения. + +Слишком сложный процесс тоже вреден: длинные release-ветки, много ручных согласований и крупные pull requests +увеличивают lead time и количество конфликтов. Небольшой продукт с частыми релизами обычно выигрывает от trunk-based +development и коротких веток, а продукт с несколькими поддерживаемыми версиями или строгим release governance может +использовать release branches. + +На интервью важно не назвать единственный "правильный" workflow, а объяснить, как выбранный процесс снижает риски именно +вашей команды и какие проблемы он создает взамен. @@ -43,9 +64,30 @@ Feature branch workflow держит изменения в отдельных в **Полный ответ** -Feature branch workflow держит изменения в отдельных ветках до merge. Trunk-based development предполагает маленькие -частые изменения в основной ветке, часто с feature flags и сильными automated checks. Первый подход проще для -изолированной работы, второй лучше для частых релизов и меньших merge conflicts. +В feature branch workflow каждая задача разрабатывается в отдельной ветке и попадает в основную ветку после pull +request, review и CI. Это удобно для изоляции незавершенной работы, внешних contributors и изменений, которым требуется +отдельное обсуждение. Но чем дольше живет ветка, тем сильнее она расходится с `main`: интеграция откладывается, +конфликты становятся крупнее, а обратная связь приходит поздно. + +Trunk-based development строится вокруг постоянно интегрируемой основной ветки. Разработчики делают небольшие изменения +и сливают их часто, обычно через очень короткие branches или напрямую при строгой защите trunk. Незавершенную +функциональность скрывают feature flags, branch by abstraction или совместимыми промежуточными изменениями. Поэтому +deployment можно отделить от release: код уже находится в production, но функция еще выключена для пользователей. + +Например, новую форму можно добавлять по частям: + +- сначала совместимый backend API; +- затем UI за выключенным feature flag; +- после этого telemetry и постепенное включение; +- в конце удаление старого пути и флага. + +Trunk-based development требует быстрых reliable checks, небольших pull requests, дисциплины обратной совместимости и +умения дробить работу. Feature branches проще начать использовать, но они не должны превращаться в ветки, живущие +недели. Также trunk-based development не означает отсутствие review или бесконтрольный push в `main`: review может +оставаться обязательным, просто изменение должно проходить его быстро. + +Выбор зависит от частоты релизов, зрелости CI/CD, размера изменений и требований к изоляции. Главный trade-off — +краткосрочная изоляция против ранней интеграции. @@ -63,9 +105,31 @@ release process. Хорошая команда фиксирует branch protect **Полный ответ** -Ответственность разделена: автор отвечает за изменение, reviewer — за проверку, maintainers — за правила репозитория и -release process. Хорошая команда фиксирует branch protection, required checks, review rules и ownership, чтобы качество -не зависело только от внимательности одного человека. +Качество кода — общая ответственность, но у участников разные зоны контроля. + +Автор должен понять задачу, ограничить scope, проверить diff, добавить или обновить tests, описать риски и убедиться, +что изменение можно безопасно доставить. Reviewer проверяет не только style, но и корректность решения, edge cases, +поддерживаемость, безопасность и соответствие архитектуре. Maintainers задают правила: branch protection, required +checks, merge strategy, permissions, CODEOWNERS, release process и порядок работы с инцидентами. + +Автоматизация создает повторяемый базовый уровень качества: + +- formatter и linter проверяют механические правила; +- unit, integration и end-to-end tests проверяют поведение; +- type checking и build находят несовместимости; +- security и dependency checks обнаруживают известные риски; +- branch protection не позволяет обойти обязательные проверки случайным merge. + +При этом CI не понимает продуктовый смысл, а reviewer не должен вручную выполнять работу линтера. Хороший процесс +распределяет проверки по самому дешевому и надежному уровню. + +Опасная модель — считать reviewer последней линией защиты и перекладывать на него ответственность автора. Это приводит к +огромным pull requests и формальному approval. Обратная крайность — считать, что зеленый CI гарантирует качество: тесты +могут не покрывать логическую ошибку, миграцию данных или деградацию UX. + +После дефекта команда должна улучшать систему, а не искать одного виноватого: добавить test, checklist, ownership rule +или наблюдаемость. На интервью полезно привести пример, где качество обеспечивалось сочетанием личной ответственности, +review и автоматических ограничений. @@ -83,9 +147,29 @@ requests, bugs, decisions и releases были связаны между соб **Полный ответ** -Issues должны жить в одном понятном месте: GitHub Issues, Jira, YouTrack, Linear или другой системе. Важно, чтобы pull -requests, bugs, decisions и releases были связаны между собой. Иначе команда теряет контекст, почему изменение было -сделано и какие ограничения обсуждались. +Команде нужен один source of truth для задач и дефектов. Конкретный инструмент вторичен: это может быть GitHub Issues, +Jira, YouTrack или Linear. Важнее, чтобы участники знали, где искать актуальный статус, критерии готовности, +ответственного, решения и связи с кодом. + +Хороший issue обычно содержит: + +- проблему и пользовательский эффект; +- acceptance criteria и явные ограничения; +- ссылки на дизайн, логи, ADR или связанные задачи; +- результат обсуждения спорных решений; +- связь с pull request, release и последующим мониторингом. + +Например, pull request может содержать `Closes #123`. После merge GitHub автоматически закроет issue, а из задачи можно +перейти к diff и понять, каким изменением она реализована. Для production bug полезно также связать issue с incident, +исправлением, тестом, который воспроизводит проблему, и версией релиза. + +Проблема возникает, когда одна и та же задача вручную дублируется в Jira и GitHub Issues. Статусы и описания расходятся, +а команда не понимает, какая запись актуальна. Если несколько систем неизбежны, одна должна быть основной, а остальные — +ссылаться на нее или синхронизироваться автоматически. + +Issue не обязан хранить весь технический контекст. Долгосрочное архитектурное решение лучше зафиксировать в ADR, а +чувствительные данные, credentials и персональную информацию нельзя переносить в публичный tracker. Но issue должен +оставлять достаточно контекста, чтобы через несколько месяцев понять причину изменения, а не только его название. @@ -97,15 +181,37 @@ requests, bugs, decisions и releases были связаны между соб **Короткий ответ** -Husky настраивает Git hooks в проекте. Например, pre-commit может запускать lint-staged, а commit-msg - проверять формат -сообщения. +Husky настраивает Git hooks в проекте. Например, `pre-commit` может запускать lint-staged, а `commit-msg` — проверять +формат сообщения. Hooks дают быстрый локальный feedback, но не заменяют CI: их можно пропустить или не установить. **Полный ответ** -Husky настраивает Git hooks в проекте. Например, `pre-commit` может запускать lint-staged, а `commit-msg` - проверять -формат сообщения. +Husky помогает хранить настройку Git hooks рядом с кодом проекта. Git вызывает hooks в определенные моменты, например +перед созданием commit, после merge или перед push. Husky подключает проектные scripts к этим событиям, чтобы все +разработчики могли использовать одинаковые локальные проверки. + +Типичный набор: + +- `pre-commit` запускает `lint-staged`, чтобы форматировать и проверять только staged files; +- `commit-msg` запускает commitlint и проверяет соглашение о сообщениях; +- `pre-push` выполняет небольшой набор быстрых tests или type checking. + +Преимущество hooks — ранний feedback. Разработчик узнает о formatting error до push, а не после нескольких минут +ожидания CI. Проверка staged files также обычно быстрее полного запуска lint по репозиторию. + +Но hooks нельзя считать защитной границей: + +- установка dependencies могла не выполниться; +- hook можно пропустить через `--no-verify`; +- локальная среда может отличаться от CI; +- слишком долгий hook разработчики начнут обходить; +- platform-specific shell command может не работать у части команды. + +Поэтому обязательные проверки должны повторяться в CI, а локальные hooks стоит делать быстрыми, детерминированными и +понятными. В hook не следует помещать тяжелый end-to-end suite или действия, изменяющие удаленные ресурсы. -Hooks дают быстрый локальный feedback, но не заменяют CI: их можно пропустить или не установить. +На интервью полезно объяснить не только "Husky запускает lint", но и границу ответственности: Husky улучшает developer +experience и предотвращает простые ошибки локально, а CI остается независимым источником истины перед merge. @@ -123,9 +229,27 @@ resolution, pull request и code review. Если был опыт SVN, Mercurial **Полный ответ** -Ожидается не только название Git, но и понимание ежедневных операций: branch, commit, merge, rebase, revert, conflict -resolution, pull request и code review. Если был опыт SVN, Mercurial или monorepo tooling, полезно объяснить, чем -отличались процессы и какие ограничения это создавало. +Это вопрос про практический опыт, а не проверка списка названий. Сильный ответ начинается с основной системы, например +Git, а затем показывает, какие задачи вы решали: создавали branches, формировали понятные commits, обновляли ветку через +merge или rebase, разрешали conflicts, откатывали изменения, участвовали в code review и выпускали releases. + +Полезно привести конкретный сценарий: + +> В основной работе использовал Git и GitHub. Для задач создавал короткие branches, открывал pull requests, проходил +> required CI checks и squash merge. При обновлении feature-ветки использовал rebase, а изменения в общей ветке отменял +> через revert. Для параллельной работы с hotfix применял worktree. + +Git — distributed version control system: каждый clone содержит историю и позволяет создавать commits и branches +локально. В централизованной SVN основная история находится на сервере, а типичный workflow и модель branching +отличаются. Опыт с другой VCS стоит упоминать только вместе с практическими отличиями, а не как набор терминов. + +Monorepo tools, GitHub, GitLab и Bitbucket не являются отдельными version control systems. Это инфраструктура вокруг +Git: hosting, pull requests, permissions, CI/CD и инструменты масштабирования репозитория. На интервью лучше разделять +эти понятия. + +Если опыт ограничен Git, не нужно придумывать работу с SVN или Mercurial. Гораздо ценнее подробно рассказать, как вы +разрешили сложный conflict, восстановили потерянный commit через `reflog`, выбрали merge strategy или улучшили командный +workflow. @@ -139,16 +263,33 @@ resolution, pull request и code review. Если был опыт SVN, Mercurial **Короткий ответ** -git revert создает новый коммит, отменяющий изменения выбранного коммита. Он безопасен для общей ветки, потому что не -переписывает историю. +`git revert` создает новый commit, отменяющий изменения выбранного commit, поэтому подходит для общей опубликованной +ветки. `git reset` перемещает указатель ветки и может менять index и working tree, поэтому обычно применяется к +локальной истории. **Полный ответ** -`git revert` создает новый коммит, отменяющий изменения выбранного коммита. Он безопасен для общей ветки, потому что не -переписывает историю. +`git revert ` вычисляет обратный patch и создает новый commit. Исходный commit и вся опубликованная история +сохраняются, поэтому коллеги могут получить отмену обычным pull. Revert может потребовать разрешения conflicts, если +более поздние изменения затронули те же строки. Для revert merge commit нужно явно указать mainline parent через `-m`. -`git reset` перемещает указатель ветки. Варианты `--soft`, `--mixed` и `--hard` по-разному работают с index и рабочими -файлами. Reset опубликованной ветки требует последующего force push и может сломать работу коллег. +`git reset ` перемещает текущую branch и `HEAD` на другой commit. Режим определяет, что произойдет с index и +working tree: + +- `--soft` перемещает branch, но оставляет изменения staged; +- `--mixed` используется по умолчанию, сбрасывает index, но сохраняет изменения в файлах; +- `--hard` приводит branch, index и tracked files к выбранному commit и может уничтожить незакоммиченные изменения. + +Например, ошибочный commit уже попал в `main`. Безопасное действие — `git revert ` и новый pull request с отменой. +Если тот же commit существует только в локальной feature-ветке, можно выполнить `git reset --soft HEAD~1`, исправить +staged changes и создать commit заново. + +Reset опубликованной ветки изменяет ее историю и обычно требует force push. Это ломает parent chain, на который могли +опираться branches коллег. Поэтому его не используют для общей ветки без явной координации. + +Иногда удаленный reset commit можно восстановить через `reflog`, пока объект не очищен сборщиком мусора, но это +аварийный механизм, а не стратегия безопасности. Главное правило для интервью: `revert` добавляет историю и безопасно +отменяет публичное изменение, `reset` переписывает локальное положение branch. @@ -160,14 +301,36 @@ git revert создает новый коммит, отменяющий изме **Короткий ответ** -merge объединяет истории и обычно создает merge commit. Реальные commit hashes существующей ветки сохраняются. +`merge` объединяет две линии истории и сохраняет существующие commits. `rebase` последовательно переносит commits на +новую базу, создавая для них новые hashes. Merge безопаснее для общей истории, rebase удобен для очистки локальной +feature-ветки. **Полный ответ** -`merge` объединяет истории и обычно создает merge commit. Реальные commit hashes существующей ветки сохраняются. +Git хранит commits как граф. `git merge feature` связывает две линии истории: при необходимости создается merge commit с +двумя parents. Уже существующие commits и их hashes не меняются. Если текущая branch не расходилась, Git может выполнить +fast-forward без отдельного merge commit. + +`git rebase main` находит commits feature-ветки после общей базы и последовательно применяет их patches поверх +актуального `main`. У новых commits меняется parent, а значит меняется и hash, даже если содержимое файлов осталось тем +же. В результате история выглядит линейной. + +Практический workflow может быть таким: + +1. перед review разработчик выполняет `git fetch` и `git rebase origin/main`; +2. разрешает conflicts в контексте каждого переносимого commit; +3. запускает tests; +4. обновляет уже опубликованную feature-ветку через `git push --force-with-lease`; +5. после approval команда делает squash merge или fast-forward по своим правилам. + +Merge лучше показывает реальную структуру совместной работы и не требует переписывать опубликованные commits. Rebase +упрощает линейное чтение истории и позволяет привести локальные commits в порядок, но conflicts иногда приходится решать +несколько раз — для каждого переносимого commit. Обычный rebase также может распрямить merge commits, если специально не +использовать режим сохранения merges. -`rebase` переносит коммиты на новую базу, создавая для них новые hashes и линейную историю. Rebase удобен для локальной -feature-ветки, но опубликованную общую историю без договоренности не переписывают. +Не стоит rebase общую ветку, которой пользуются другие разработчики, без договоренности. Универсально лучшей стратегии +нет: команда выбирает ее с учетом требований к audit trail, частоты интеграции и удобства диагностики. На интервью важно +объяснить изменение commit identity и правило "rebase private history, merge shared history". @@ -179,17 +342,31 @@ feature-ветки, но опубликованную общую историю **Короткий ответ** -Безопасные варианты: +Можно сделать временный commit, сохранить изменения через `git stash push -u` или открыть hotfix в отдельном +`git worktree`. Выбор зависит от того, нужно ли сохранить staged state и работать с двумя branches одновременно. **Полный ответ** -Безопасные варианты: +Самый прозрачный вариант — создать небольшой временный commit в текущей feature-ветке. Он надежно хранится в истории, +его можно позже изменить через interactive rebase или squash перед merge. Такой подход особенно удобен, если работа уже +логически делится на commit, даже если она еще не готова для pull request. -- сделать небольшой временный коммит в текущей ветке; -- выполнить `git stash push -u`, переключиться на hotfix, затем вернуть изменения через `git stash pop`; -- использовать отдельный `git worktree`, чтобы одновременно работать с двумя ветками. +`git stash push -u -m "WIP: feature"` временно сохраняет tracked и untracked changes и очищает working tree. После +hotfix можно вернуться в исходную branch и выполнить `git stash pop` или безопаснее сначала `git stash apply`. Флаг `-u` +важен, если появились новые untracked files; ignored files он не включает. При изменении тех же строк во время hotfix +возврат stash может создать conflicts. -Не стоит терять изменения через `reset --hard` или переносить незавершенный код в hotfix. +`git worktree add ../project-hotfix -b hotfix/issue origin/main` создает вторую working directory того же репозитория. В +одной директории остается незавершенная feature, в другой можно сразу исправлять production bug. Это хороший вариант, +когда нужно параллельно запускать приложение или сравнивать две branches. Git не позволяет одновременно checkout одной и +той же branch в двух worktrees, поэтому для hotfix создают отдельную branch. + +Перед переключением полезно проверить `git status`, чтобы понимать staged, unstaged и untracked changes. Не следует +использовать `git reset --hard` или удалять файлы ради чистого working tree: так легко потерять работу. Также не стоит +случайно переносить незавершенный feature-код в hotfix. + +На интервью сильный ответ предлагает несколько безопасных вариантов и объясняет, почему `worktree` лучше stash при +длительной параллельной работе, а временный commit надежнее неименованного набора изменений. @@ -201,16 +378,38 @@ feature-ветки, но опубликованную общую историю **Короткий ответ** -Git берет коммиты feature-ветки и последовательно применяет их поверх нового develop. Содержимое может сохраниться, но -commits получают новые parent links и hashes. +Git находит commits feature-ветки после общей базы и последовательно применяет их поверх свежего `develop`. Из-за новых +parent links commits получают новые hashes, а conflicts разрешаются во время replay. **Полный ответ** -Git берет коммиты feature-ветки и последовательно применяет их поверх нового `develop`. Содержимое может сохраниться, но -commits получают новые parent links и hashes. +При `git rebase develop` Git сначала определяет общую базу текущей feature-ветки и `develop`. Затем временно отделяет +уникальные commits feature-ветки, перемещает branch на новый `develop` и по очереди воспроизводит изменение каждого +commit. Это не физическое перемещение старых объектов, а создание новых commits с похожими patches. + +Hash commit зависит не только от diff, но и от parent, author/committer metadata и сообщения. После смены parent hashes +становятся другими. Старые commits некоторое время могут оставаться доступными через `reflog`, но branch уже указывает +на новую цепочку. + +Если patch конфликтует с актуальным `develop`, rebase останавливается: + +1. разработчик разрешает conflict; +2. добавляет исправленные файлы через `git add`; +3. продолжает `git rebase --continue`; +4. при необходимости отменяет весь процесс через `git rebase --abort`. + +`git rebase --skip` удаляет текущий patch из новой истории, поэтому применять его нужно только при понимании, что +изменение уже присутствует или больше не требуется. Patch-equivalent commit, который уже попал в `develop`, Git также +может не воспроизводить повторно. Обычный rebase меняет структуру merge commits; для сохранения сложной topology нужен +отдельный режим. + +После rebase важно запустить tests: текстовый conflict может отсутствовать, но поведение feature способно логически +сломаться из-за изменений в `develop`. Если feature-ветка уже опубликована, обычный push будет отклонен. Используют +`git push --force-with-lease`, который проверяет, что удаленная branch не была неожиданно обновлена другим +разработчиком. -Конфликты разрешают по одному, продолжая `git rebase --continue`. После rebase уже опубликованной ветки обычно нужен -`git push --force-with-lease`, а не обычный force push. +На интервью ключевой механизм стоит сформулировать так: rebase replay-ит patches на новой базе, поэтому создается новая +цепочка commits, а не просто меняется визуальное расположение старых.