Skip to content

Latest commit

 

History

History
416 lines (290 loc) · 31.7 KB

File metadata and controls

416 lines (290 loc) · 31.7 KB
layout ../../layouts/Layout.astro
title Git
description Version control, branching, pull requests, rebase, merge, revert и командный workflow
category Основы и инструменты
kind questions
order 20

Git

Version control и командный workflow

Зачем команде version control workflow?

Короткий ответ

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, а объяснить, как выбранный процесс снижает риски именно вашей команды и какие проблемы он создает взамен.

Чем feature branch workflow отличается от trunk-based development?

Короткий ответ

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 — краткосрочная изоляция против ранней интеграции.

Кто должен отвечать за качество version-controlled code?

Короткий ответ

Ответственность разделена: автор отвечает за изменение, 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 и автоматических ограничений.

Где лучше вести issues и почему это важно?

Короткий ответ

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 должен оставлять достаточно контекста, чтобы через несколько месяцев понять причину изменения, а не только его название.

Для чего нужен Husky?

Короткий ответ

Husky настраивает Git hooks в проекте. Например, pre-commit может запускать lint-staged, а commit-msg — проверять формат сообщения. Hooks дают быстрый локальный feedback, но не заменяют CI: их можно пропустить или не установить.

Полный ответ

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 или действия, изменяющие удаленные ресурсы.

На интервью полезно объяснить не только "Husky запускает lint", но и границу ответственности: Husky улучшает developer experience и предотвращает простые ошибки локально, а CI остается независимым источником истины перед merge.

Какие version control systems вы использовали?

Короткий ответ

Ожидается не только название 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.

Git commands

Чем git revert отличается от git reset?

Короткий ответ

git revert создает новый commit, отменяющий изменения выбранного commit, поэтому подходит для общей опубликованной ветки. git reset перемещает указатель ветки и может менять index и working tree, поэтому обычно применяется к локальной истории.

Полный ответ

git revert <commit> вычисляет обратный patch и создает новый commit. Исходный commit и вся опубликованная история сохраняются, поэтому коллеги могут получить отмену обычным pull. Revert может потребовать разрешения conflicts, если более поздние изменения затронули те же строки. Для revert merge commit нужно явно указать mainline parent через -m.

git reset <target> перемещает текущую branch и HEAD на другой commit. Режим определяет, что произойдет с index и working tree:

  • --soft перемещает branch, но оставляет изменения staged;
  • --mixed используется по умолчанию, сбрасывает index, но сохраняет изменения в файлах;
  • --hard приводит branch, index и tracked files к выбранному commit и может уничтожить незакоммиченные изменения.

Например, ошибочный commit уже попал в main. Безопасное действие — git revert <sha> и новый pull request с отменой. Если тот же commit существует только в локальной feature-ветке, можно выполнить git reset --soft HEAD~1, исправить staged changes и создать commit заново.

Reset опубликованной ветки изменяет ее историю и обычно требует force push. Это ломает parent chain, на который могли опираться branches коллег. Поэтому его не используют для общей ветки без явной координации.

Иногда удаленный reset commit можно восстановить через reflog, пока объект не очищен сборщиком мусора, но это аварийный механизм, а не стратегия безопасности. Главное правило для интервью: revert добавляет историю и безопасно отменяет публичное изменение, reset переписывает локальное положение branch.

Чем merge отличается от rebase?

Короткий ответ

merge объединяет две линии истории и сохраняет существующие commits. rebase последовательно переносит commits на новую базу, создавая для них новые hashes. Merge безопаснее для общей истории, rebase удобен для очистки локальной feature-ветки.

Полный ответ

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 общую ветку, которой пользуются другие разработчики, без договоренности. Универсально лучшей стратегии нет: команда выбирает ее с учетом требований к audit trail, частоты интеграции и удобства диагностики. На интервью важно объяснить изменение commit identity и правило "rebase private history, merge shared history".

Как переключиться на hotfix с незакоммиченными изменениями?

Короткий ответ

Можно сделать временный commit, сохранить изменения через git stash push -u или открыть hotfix в отдельном git worktree. Выбор зависит от того, нужно ли сохранить staged state и работать с двумя branches одновременно.

Полный ответ

Самый прозрачный вариант — создать небольшой временный commit в текущей feature-ветке. Он надежно хранится в истории, его можно позже изменить через interactive rebase или squash перед merge. Такой подход особенно удобен, если работа уже логически делится на commit, даже если она еще не готова для pull request.

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.

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 надежнее неименованного набора изменений.

Что происходит с коммитами при rebase на свежий develop?

Короткий ответ

Git находит commits feature-ветки после общей базы и последовательно применяет их поверх свежего develop. Из-за новых parent links commits получают новые hashes, а conflicts разрешаются во время replay.

Полный ответ

При 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 не была неожиданно обновлена другим разработчиком.

На интервью ключевой механизм стоит сформулировать так: rebase replay-ит patches на новой базе, поэтому создается новая цепочка commits, а не просто меняется визуальное расположение старых.