Skip to content

Git Шпаргалка

Распределённая система контроля версий для отслеживания изменений исходного кода.

01

Конфигурация и инициализация

Глобальная и локальная конфигурация

Git хранит конфигурацию на трёх уровнях: system (/etc/gitconfig), global (~/.gitconfig для пользователя) и local (.git/config для репозитория). Нижние уровни переопределяют верхние. Всегда устанавливайте user.name и user.email перед коммитом, иначе коммиты будут использовать бесполезную идентичность по умолчанию. Установка init.defaultBranch в main избегает устаревшего значения master и соответствует современным соглашениям.

git
# set user identity (required for commits)
git config --global user.name "Alice Lee"
git config --global user.email "[email protected]"

# set default editor and branch name
git config --global core.editor "code --wait"
git config --global init.defaultBranch main

# view all settings and their origin
git config --list --show-origin

# edit config files directly
git config --global --edit        # ~/.gitconfig
git config --edit                 # repo .git/config

Создание и клонирование репозиториев

git init создаёт пустой репозиторий, добавляя скрытую директорию .git, хранящую все данные версий. git clone копирует удалённый репозиторий, включая полную историю. Используйте --depth 1 для поверхностного клона, когда нужен только последний снимок (например, CI-сборки) — это значительно уменьшает размер загрузки. --single-branch избегает получения несвязанных веток.

git
# create a new repo from scratch in current dir
mkdir my-app && cd my-app
git init

# clone a remote repository
git clone https://github.com/user/repo.git

# clone into a specific folder name
git clone https://github.com/user/repo.git my-folder

# clone a single branch (saves bandwidth)
git clone -b dev --single-branch https://github.com/user/repo.git

# shallow clone: only the latest commit
git clone --depth 1 https://github.com/user/repo.git

Алиасы и ярлыки

Алиасы позволяют определять более короткие имена для часто используемых команд или последовательностей команд. Они хранятся в секции [alias] вашего gitconfig. Алиас, начинающийся с !, выполняется как команда оболочки, обеспечивая сложные рабочие процессы. Алиас lg выше создаёт компактный визуальный граф истории, крайне полезный для понимания структуры веток.

git
# create shortcuts for common commands
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

# now use them
git co main
git lg

# alias that runs an external command (starts with !)
git config --global alias.unstage "reset HEAD --"
git unstage file.txt

Справка и документация

Git поставляется с исчерпывающей встроенной документацией. git help <command> открывает man-страницу в пейджере. Флаг -h даёт быструю одноэкранную сводку опций. Руководства (git help -g) включают учебник, глоссарий терминов и справочник повседневного рабочего процесса — отлично для новичков, изучающих терминологию.

git
# open the full manual for a command
git help commit
git commit --help

# show a concise synopsis
git commit -h

# list all git commands
git help -a

# list all guides (tutorial, glossary, etc.)
git help -g
git help glossary

Шаблоны .gitignore

.gitignore указывает Git, какие файлы исключить из контроля версий — необходимо для артефактов сборки, зависимостей и секретов. Шаблоны используют glob-синтаксис; завершающий слеш соответствует директориям. Префикс ! инвертирует шаблон, принуждая файл отслеживаться. Сам .gitignore следует коммитить, чтобы команда разделяла правила игнорирования. Уже отслеживаемые файлы не затрагиваются новыми шаблонами игнорирования — сначала удалите их через git rm --cached.

git
# .gitignore file — patterns for files to skip
node_modules/
*.log
.env
.env.local
dist/
build/

# but track this specific file even if it matches
!important.log

# ignore all .txt files in build/ but not subdirs
build/*.txt

# ignore everything in a folder except one file
secrets/*
!secrets/template.env
02

Индексирование и коммиты

Статус и индексирование

Git использует двухшаговую модель: изменения попадают в область индексирования (index) перед коммитом. git status показывает, что изменено, индексировано и не отслеживается. git add -p позволяет индексировать отдельные части патча — бесценно для разбиения беспорядочного рабочего дерева на сфокусированные, логичные коммиты. Используйте git restore --staged (современная команда) для отмены индексирования без потери правок.

git
# see current state of working tree
git status
git status -s              # short format
git status -sb             # short + branch info

# stage changes
git add file.txt           # one file
git add src/               # a directory
git add .                  # all changes in repo
git add -p                 # stage interactively by hunk

# unstage a file (keep changes in working dir)
git restore --staged file.txt
git reset HEAD file.txt    # older syntax

Коммит изменений

Коммит фиксирует снимок индексированных изменений. Пишите сообщения в повелительном наклонении ('add feature', а не 'added feature'). Флаг -a индексирует только уже отслеживаемые файлы — новые файлы всё равно требуют git add. --amend переписывает последний коммит; используйте для исправления опечатки или добавления забытого файла, но никогда не правьте коммиты, уже отправленные в общую ветку.

git
# commit staged changes with a message
git commit -m "feat: add login form"

# multi-line commit message
git commit -m "feat: add login form" -m "Closes #42"

# stage tracked files AND commit in one step
git commit -am "fix: correct typo"

# amend the previous commit (keep message)
git commit --amend --no-edit

# amend and edit the message
git commit --amend -m "feat: add login form (v2)"

Соглашения о сообщениях коммитов

Conventional Commits — широко распространённая спецификация для структурированных сообщений коммитов. Префикс типа (feat, fix, docs и т. д.) включает автоматическую генерацию changelog и семантическое версионирование. Маркер ! указывает на ломающее изменение. Пустая строка отделяет тему (<=50 символов) от тела, и ещё одна пустая строка отделяет футеры вроде 'Closes #123', которые автоматически закрывают issues на GitHub.

git
# Conventional Commits format
feat: add user registration endpoint
fix: resolve crash on empty cart
docs: update API reference
style: format login component
refactor: extract validation logic
test: add unit tests for parser
chore: upgrade dependencies

# with scope and breaking change marker
feat(api): add pagination to list endpoint
fix!: change default port (BREAKING CHANGE)

# with body and footer
feat: add dark mode

Implement theme toggle using CSS variables.
Closes #128

Просмотр истории с log

git log — основной инструмент для исследования истории. --oneline --graph --all — наиболее полезная комбинация для понимания структуры веток с одного взгляда. Можно фильтровать по автору, диапазону дат или содержимому сообщения через --grep. --stat показывает, какие файлы изменились и сколько строк, а --patch показывает полный diff каждого коммита.

git
# full history with details
git log
git log --oneline                 # one line per commit
git log --oneline --graph --all   # visual branch graph
git log -n 5                      # last 5 commits

# filter by author, date, or message
git log --author="Alice"
git log --since="2 weeks ago"
git log --grep="fix"

# show files changed in each commit
git log --stat
git log --patch                   # full diffs

Diff и Show

git diff сравнивает снимки — без аргументов показывает неиндексированные изменения относительно index. --staged сравнивает index с HEAD. git show отображает метаданные и патч одного коммита. Синтаксис HEAD:path позволяет просматривать содержимое любого файла на любом коммите без checkout — удобно для восстановления старых версий или изучения истории.

git
# compare working tree, index, and commits
git diff                  # unstaged changes
git diff --staged         # staged but uncommitted
git diff HEAD             # all changes vs last commit
git diff main feature     # compare two branches
git diff abc1234 def5678  # compare two commits

# inspect a single commit
git show HEAD             # latest commit diff + metadata
git show abc1234
git show HEAD:file.txt    # view a file at a given commit
03

Ветвление и слияние

Создание и переключение веток

Ветки в Git — лёгкие указатели на коммит — создание почти мгновенно. git switch (Git 2.23+) — современная, безопасная альтернатива checkout для смены веток, оставляющая checkout для восстановления файлов. -d отказывается удалять несмещённую ветку (защищая работу); -D форсирует удаление. Всегда удаляйте ветки после слияния для чистоты списка веток.

git
# list branches
git branch                 # local branches
git branch -a              # local + remote
git branch -vv             # with tracking info

# create and switch
git branch feature         # create only
git checkout feature       # switch to it
git checkout -b feature    # create + switch (classic)
git switch -c feature      # create + switch (modern)

# delete branches
git branch -d feature      # safe delete (merged only)
git branch -D feature      # force delete

Слияние веток

Fast-forward слияние просто сдвигает указатель ветки вперёд, когда цель не имеет новых коммитов — создавая линейную историю. --no-ff форсирует merge-коммит, сохраняя факт существования ветки (полезно для отслеживания фич). --squash объединяет все коммиты ветки в одно индексированное изменение, которое вы затем коммитите один раз — отлично для очистки шумной истории фичи перед интеграцией в main.

git
# merge feature into main
git checkout main
git merge feature

# fast-forward merge (default when possible)
#   main just moves forward to feature's commit

# create a merge commit (preserves branch history)
git merge --no-ff feature -m "Merge feature branch"

# squash all feature commits into one
git merge --squash feature
git commit -m "feat: add feature (squashed)"

# abort a merge with conflicts
git merge --abort

Разрешение конфликтов слияния

Конфликты возникают, когда одни и те же строки изменены по-разному в двух ветках. Git вставляет маркеры конфликта (<<<<<<<, =======, >>>>>>>), показывающие обе стороны. Разрешите, отредактировав файл до желаемого финального состояния, затем git add для отметки разрешённым. Коммит для завершения слияния. git mergetool запускает визуальный diff-инструмент. Если растеряны, git merge --abort возвращает в предмержевое состояние.

git
# a conflict produces markers in the file
# <<<<<<< HEAD
# my changes (current branch)
# =======
# their changes (incoming branch)
# >>>>>>> feature

# edit the file to resolve, then:
git add resolved-file.txt
git commit                # finalize the merge

# use a merge tool
git mergetool

# see which files conflict
git status

# abandon the merge
git merge --abort

Rebase

Rebase воспроизводит коммиты вашей ветки поверх другой ветки, создавая линейную историю без merge-коммитов. Интерактивный rebase (-i) — мощный инструмент: squash объединяет коммиты, reword редактирует сообщения, drop удаляет коммиты, edit приостанавливает для изменения коммита. НИКОГДА не делайте rebase коммитов, уже отправленных и общих — это переписывает историю и ломает репозитории товарищей. Используйте rebase только на собственных локальных ветках.

git
# rebase feature onto main (replay feature commits)
git checkout feature
git rebase main

# interactive rebase — rewrite last 5 commits
git rebase -i HEAD~5
# options: pick, reword, squash, fixup, drop, edit

# continue, skip, or abort during a rebase
git rebase --continue
git rebase --skip
git rebase --abort

# rebase while pulling
git pull --rebase origin main

Cherry-pick и reflog

Cherry-pick применяет отдельный коммит из другой ветки на текущую — полезно для обратного порта багфикса без слияния всей ветки. Reflog — локальный журнал каждого движения HEAD (коммиты, checkout, reset), хранящийся ~90 дней. Это ваша страховка: даже после разрушительного reset можно найти старый хеш коммита в reflog и восстановить его. Данные reflog только локальные и никогда не отправляются.

git
# apply a specific commit onto current branch
git cherry-pick abc1234
git cherry-pick abc1234 def5678   # multiple commits
git cherry-pick abc1234..def5678  # range (exclusive start)

# reflog: record of where HEAD has been
git reflog
git reflog show feature

# recover a 'lost' commit
git reset --hard HEAD@{2}

# view a branch's history of moves
git reflog show main
04

Удалённые репозитории

Управление remote'ами

Remote — именованная ссылка на другой репозиторий, обычно origin для вашего fork и upstream для исходного проекта. git remote -v показывает URL fetch и push. Fork'и на GitHub используют remote upstream для синхронизации с оригиналом: fetch из upstream, merge или rebase, затем push в origin. set-url удобен для переключения между HTTPS и SSH аутентификацией.

git
# list configured remotes
git remote -v
git remote show origin

# add a remote
git remote add origin https://github.com/user/repo.git

# add an upstream remote (for forks)
git remote add upstream https://github.com/original/repo.git

# rename or remove
git remote rename origin upstream
git remote remove origin

# change a remote URL (e.g. HTTPS to SSH)
git remote set-url origin [email protected]:user/repo.git

Fetch, Pull и Push

fetch скачивает удалённые данные, но не трогает рабочее дерево — безопасно для проверки перед слиянием. pull = fetch + merge (или rebase с --rebase). Первый push новой ветки требует -u для настройки отслеживания, чтобы будущие git push/pull работали без аргументов. --force-with-lease — безопасная альтернатива --force: перезаписывает remote только если никто другой не отправил изменения, предотвращая случайное затирание работы товарищей.

git
# download remote changes without merging
git fetch origin
git fetch --all --prune      # all remotes, drop deleted branches

# fetch + merge in one step
git pull
git pull --rebase            # rebase instead of merge

# push local commits to remote
git push origin main
git push -u origin feature   # push + set upstream tracking
git push                     # subsequent pushes (uses tracking)

# force push (rewrites remote history — DANGER)
git push --force-with-lease  # safer than --force

Отслеживание и синхронизация веток

Отслеживающие ветки связывают локальную ветку с удалённой, чтобы git pull и git push знали, куда fetch/push без указания. -u (сокращение от --set-upstream-to) устанавливает это при первом push. Когда товарищи удаляют удалённые ветки, ваши локальные remote-tracking refs устаревают — git remote prune origin очищает их. git fetch --prune делает это автоматически при fetch.

git
# set upstream for current branch
git branch -u origin/main
git branch --set-upstream-to=origin/main

# create a local branch tracking a remote one
git checkout -b feature origin/feature
git switch feature           # auto-detects tracking branch

# delete a remote branch
git push origin --delete feature

# list all remote-tracking branches
git branch -r
git remote prune origin      # remove stale remote refs

Рабочий процесс Pull Requests

Стандартный GitHub-поток: создайте ветку фичи, отправьте, откройте Pull Request для ревью и удалите ветку после слияния. Для fork'ов remote upstream позволяет тянуть изменения из исходного репозитория. Регулярная синхронизация main вашего fork с upstream предотвращает большие, болезненные слияния позже. Многие команды включают 'auto-delete branch on merge' для чистоты списка веток.

git
# typical feature workflow
git checkout -b feature/login
# ... make changes, commit ...
git push -u origin feature/login

# create PR on GitHub, then after merge:
git checkout main
git pull origin main
git branch -d feature/login

# sync a fork with its upstream
git fetch upstream
git checkout main
git merge upstream/main
git push origin main

Bare-репозитории и зеркала

Bare-репозиторий не имеет рабочего дерева — хранит только данные .git. Bare-репозитории используются на серверах (как self-hosted Git) как центральный remote, в который несколько людей push и pull. --mirror клонирует всё, включая remote-tracking refs, и используется для резервных копий или миграции репозитория между хостами. --all отправляет все ветки; --tags отправляет все теги.

git
# create a bare repo (no working tree) — for servers
git init --bare project.git

# mirror clone (full copy including all refs)
git clone --mirror https://github.com/user/repo.git

# push all branches and tags
git push --all
git push --tags

# push a specific branch to a specific remote
git push origin local-name:remote-name
05

Теги и релизы

Лёгкие и аннотированные теги

Теги отмечают определённые коммиты, обычно для релизов. Лёгкие теги — просто именованные указатели; аннотированные теги — полные Git-объекты, хранящие теггера, дату и сообщение — рекомендуются для релизов, так как они подписаны и неизменны. По соглашению v1.0.0 следует семантическому версионированию (major.minor.patch). Используйте git show <tag> для просмотра коммита и аннотации тега.

git
# lightweight tag (just a pointer to a commit)
git tag v1.0.0

# annotated tag (stores metadata + message)
git tag -a v1.0.0 -m "Release 1.0.0"

# tag a specific commit
git tag -a v0.9.0 abc1234 -m "Old release"

# list and inspect tags
git tag
git tag -l "v1.*"
git show v1.0.0

Отправка и совместное использование тегов

Теги локальны, пока явно не отправлены — частая ловушка. --follow-tags отправляет только аннотированные теги, достижимые из отправленных коммитов, что является самым безопасным значением по умолчанию для релизных процессов. Checkout тега помещает в состояние 'detached HEAD' (не на ветке) — подходит для проверки, но для изменений сначала создайте ветку: git checkout -b fix/v1.0.1 v1.0.0.

git
# tags are NOT pushed by default
git push origin v1.0.0       # push one tag
git push origin --tags       # push all tags

# push tags + branches together
git push origin main --follow-tags

# delete a tag locally and remotely
git tag -d v1.0.0
git push origin --delete v1.0.0

# checkout code at a tag (detached HEAD)
git checkout v1.0.0

Подписанные теги (GPG)

Подписанные теги и коммиты используют GPG (или SSH-ключи в более новых Git) для криптографического доказательства личности автора. Это предотвращает подмену — критично для релизов open-source. Дистрибьюторы и пользователи могут проверить тег через git tag -v. GitHub показывает бейдж 'Verified' на подписанных коммитах и тегах. Установите tag.gpgsign=true для автоматической подписи тегов.

git
# sign a tag with your GPG key
git tag -s v1.0.0 -m "Signed release 1.0.0"

# verify a signed tag
git tag -v v1.0.0

# configure signing key
git config --global user.signingkey ABCD1234
git config --global tag.gpgsign true   # sign all tags

# also sign commits
git commit -S -m "signed commit"
git config --global commit.gpgsign true

Семантическое версионирование

Семантическое версионирование (SemVer) придаёт смысл номерам версий: MAJOR для несовместимых изменений API, MINOR для новых обратносокместимых функций, PATCH для багфиксов. Суффиксы pre-release (-alpha, -beta, -rc) сигнализируют стабильность. Следование SemVer позволяет пользователям вашей библиотеки знать, безопасно ли обновление — автоматические инструменты могут парсить и сравнивать строки SemVer для обнаружения ломающих изменений.

git
# semantic versioning: MAJOR.MINOR.PATCH
v1.0.0   # initial stable release
v1.1.0   # new backward-compatible feature  -> MINOR
v1.1.1   # backward-compatible bug fix       -> PATCH
v2.0.0   # breaking change                   -> MAJOR

# pre-release tags
v2.0.0-alpha.1
v2.0.0-beta.1
v2.0.0-rc.1

# git tag with the version
git tag -a v1.2.0 -m "Add export feature"
git push origin v1.2.0

Describe и changelog

git describe находит ближайший тег, достижимый из коммита, и сообщает, на сколько коммитов впереди вы — идеально для генерации строк версии сборки вроде v1.2.0-3-gabc1234. Синтаксис диапазона лога v1.1.0..v1.2.0 показывает коммиты в v1.2.0, но не в v1.1.0 — именно то, что нужно для составления примечаний к релизу или changelog между двумя релизами.

git
# describe the closest tag (great for versioning)
git describe --tags
# output: v1.2.0-3-gabc1234
# meaning: 3 commits after v1.2.0, commit hash abc1234

# use it for build versioning
VERSION=$(git describe --tags --always)
echo "Building $VERSION"

# generate a changelog between two tags
git log v1.1.0..v1.2.0 --oneline
git log v1.1.0..v1.2.0 --pretty=format:"- %s" > CHANGELOG.md
06

Отмена изменений

Reset: Soft, Mixed, Hard

reset перемещает указатель текущей ветки. --soft сохраняет изменения индексированными (отменяется только коммит) — идеально для перекоммита. --mixed (по умолчанию) убирает из индекса, но сохраняет изменения рабочего дерева. --hard навсегда отбрасывает всё — единственное восстановление через reflog. Никогда не используйте --hard на отправленных коммитах, так как это переписывает общую историю и вызывает расхождение.

git
# reset moves HEAD and optionally the index/worktree
git reset --soft HEAD~1     # undo commit, keep staged
git reset --mixed HEAD~1    # undo commit + unstage (DEFAULT)
git reset --hard HEAD~1     # undo commit + discard changes

# reset to a specific commit
git reset --hard abc1234

# reset a single file to HEAD
git reset HEAD file.txt     # unstage
git checkout -- file.txt    # discard working changes

Revert (безопасная отмена)

В отличие от reset (который переписывает историю), revert добавляет новый коммит, инвертирующий целевой — безопасно для общих веток, так как история сохранена. Это правильный способ отменить уже отправленное изменение. Reverting merge-коммита требует -m 1 для указания, какую родительскую линию сохранить (1 = ветка, в которую слили).

git
# revert creates a NEW commit that undoes another
git revert abc1234
git revert HEAD             # undo last commit

# revert a range
git revert HEAD~3..HEAD

# revert without committing (stage the inverse)
git revert --no-commit abc1234

# revert a merge commit
git revert -m 1 abc1234     # -m 1 = keep main branch parent

Restore и Clean

git restore (Git 2.23+) — современная, сфокусированная команда для операций с рабочим деревом, разделяющая задачи с checkout. --staged убирает из индекса, не трогая рабочие изменения. git clean удаляет неотслеживаемые файлы — всегда запускайте с -n (dry run) для предпросмотра удаляемого. -x агрессивен: также удаляет gitignored файлы, что полезно для чистой сборки, но может стереть секреты или выходные данные сборки.

git
# discard unstaged changes in a file
git restore file.txt
git checkout -- file.txt    # older syntax

# unstage a file (keep working changes)
git restore --staged file.txt

# restore a file from a specific commit
git restore --source=abc1234 file.txt

# remove untracked files
git clean -n                # dry run (preview)
git clean -fd               # remove untracked files + dirs
git clean -fdx              # also remove ignored files

Amend и Fixup

--amend переписывает последний коммит — удобно для исправления опечатки или добавления забытого файла, но никогда не правьте отправленные коммиты. Рабочий процесс fixup элегантен: создавайте fixup-коммиты по мере обнаружения мелких проблем, затем git rebase -i --autosquash автоматически переупорядочивает и сжимает их в целевые коммиты. Это сохраняет историю чистой, позволяя коммитить мелкие исправления инкрементально.

git
# amend the last commit (message + content)
git add forgotten-file.txt
git commit --amend --no-edit

# change only the commit message
git commit --amend -m "better message"

# create a fixup commit (for later autosquash)
git commit --fixup=abc1234

# squash fixups during interactive rebase
git rebase -i --autosquash abc1234~1

Восстановление через reflog

Reflog — ваша страховка. Он записывает каждый коммит, checkout, reset и rebase — даже операции, «уничтожающие» коммиты. Записи сохраняются ~90 дней. Если случайно reset --hard или удалили ветку, найдите хеш осиротевшего коммита в reflog и reset к нему или создайте новую ветку, указывающую на него. Reflog только локальный, поэтому это работает даже офлайн.

git
# reflog records every HEAD movement
git reflog
# abc1234 HEAD@{0}: reset: moving to abc1234
# def5678 HEAD@{1}: commit: feat: add x
# ghi9012 HEAD@{2}: checkout: moving to feature

# recover a 'lost' commit
git reset --hard def5678

# recover a deleted branch
git branch recovered-feature def5678

# reflog for a specific branch
git reflog show feature
07

Stashing и рабочие процессы

Stashing изменений

Stash откладывает незакоммиченные изменения, позволяя переключать ветки или тянуть обновления с чистым деревом. apply сохраняет stash в списке (полезно, если хотите применить к нескольким веткам); pop применяет и удаляет. Stash'и — стек LIFO, на который ссылаются stash@{N}. Используйте clear с осторожностью — навсегда отбрасывает все stash'и.

git
# save uncommitted changes (reverts working tree to HEAD)
git stash
git stash push -m "wip: login form"   # with a message

# list, show, apply
git stash list
git stash show -p stash@{0}   # show diff
git stash apply               # apply latest, keep stash
git stash pop                 # apply latest, drop stash

# apply a specific stash
git stash apply stash@{2}

# drop a stash
git stash drop stash@{0}
git stash clear               # drop ALL stashes

Частичный и выборочный stash

Эти флаги дают тонкий контроль над тем, что попадает в stash. --keep-index полезен, когда вы индексировали логический коммит, но хотите протестировать только эти изменения — спрячьте остальное, запустите тесты, затем pop. -u включает неотслеживаемые файлы (иначе они остаются в рабочем дереве). -p позволяет выбирать конкретные части, зеркалируя git add -p.

git
# stash only staged changes
git stash --staged

# stash only unstaged changes (keep staged)
git stash --keep-index

# stash interactively by hunk
git stash -p

# stash including untracked files
git stash -u
git stash --include-untracked

# stash everything (even ignored)
git stash -a

Stash-ветки и create

git stash branch создаёт новую ветку на родительском коммите stash'а и применяет stash там — идеально, когда stash больше не применяется чисто к текущей ветке из-за конфликтов. Также можно экспортировать stash как файл патча через show -p для архивации или sharing. Stash'и локальны и никогда не отправляются, поэтому не должны использоваться для длительного хранения.

git
# create a branch from a stash (great for conflicts)
git stash branch feature/wip stash@{0}

# create a commit from a stash without applying
git stash store -m "saved wip" stash@{0}

# apply a stash to a different branch
git checkout other-branch
git stash apply stash@{0}

# inspect stash contents
git stash show stash@{0} --stat
git stash show -p stash@{0} > patch.diff

Модели рабочего процесса Git

GitHub Flow — простейший: одна ветка main, ветки фич с PR, развёртывание при слиянии — идеально для непрерывного развёртывания. Git Flow (модель Vincent Driessen) добавляет ветки develop, release и hotfix для структурированного управления релизами — подходит для версионных продуктов. Trunk-Based Development использует очень короткоживущие ветки и предпочитается высокопроизводительными DevOps-командами для максимальной скорости интеграции.

git
# GitHub Flow (simple, popular)
# main is always deployable; feature branches + PRs
git checkout -b feature/x
git push -u origin feature/x
# open PR, review, merge to main, deploy

# Git Flow (structured, with release branches)
# main: production releases
# develop: integration branch
# feature/*: features off develop
# release/*: release prep
# hotfix/*: urgent fixes off main

# Trunk-Based Development
# short-lived branches (1-2 days), frequent rebase on main

Submodules

Submodules встраивают один Git-репозиторий в другой — полезно для разделения библиотеки между проектами, сохраняя её версионируемой независимо. Родительский репозиторий хранит указатель на конкретный коммит submodule. Клоны не получают содержимое submodule по умолчанию; --recurse-submodules делает это за один шаг. Submodules могут быть капризными; для более простого управления зависимостями рассмотрите Git subtrees или пакетный менеджер.

git
# add another repo as a submodule
git submodule add https://github.com/user/lib.git libs/lib

# clone a repo with its submodules
git clone --recurse-submodules https://github.com/user/repo.git

# initialize submodules in an existing clone
git submodule update --init --recursive

# update submodules to their latest remote commit
git submodule update --remote

# record a submodule pointer change
git add libs/lib
git commit -m "chore: bump lib submodule"
08

Инспекция и отладка

Blame и annotate

git blame (также называемый annotate) показывает коммит и автора для каждой строки файла — необходимо для понимания, почему код выглядит так. -L ограничивает диапазоном строк, полезно для больших файлов. -w игнорирует чистые изменения пробелов, а -C обнаруживает код, перемещённый или скопированный из другого файла, давая более точную историю происхождения строк.

git
# show who last changed each line
git blame file.txt
git blame -L 10,20 file.txt        # only lines 10-20
git blame -e file.txt              # show author email
git blame -w file.txt              # ignore whitespace changes
git blame -C file.txt              # detect moved lines across files

# GUI annotation in some editors
git gui blame file.txt

Bisect (бинарный поиск багов)

bisect выполняет бинарный поиск по истории для точного определения коммита, внедрившего баг. Вы помечаете известный-хороший и известный-плохой коммит; Git checkout'ит середину, вы тестируете, затем помечаете good или bad, уменьшая диапазон вдвое каждый раз. Со скриптом весь процесс полностью автоматизирован — огромная экономия времени для регрессий в больших историях.

git
# find the commit that introduced a bug
git bisect start
git bisect bad                 # current commit is broken
git bisect good v1.0.0         # v1.0.0 was working

# Git checks out a midpoint; test it, then:
git bisect good                # or git bisect bad

# automate with a script that exits non-zero on bug
git bisect start HEAD v1.0.0 -- npm test

# finish and return to original branch
git bisect reset

# view the bisect log
git bisect log

Поиск кода и истории

git grep ищет по отслеживаемым файлам в рабочем дереве — быстрее, чем grep -r, так как использует index. Опция -S 'pickaxe' находит коммиты, добавившие или удалившие конкретную строку, бесценно для отслеживания, когда функция или баг были внедрены. -G похож, но соответствует regex где угодно в diff. Сочетая с фильтрами --author и --since, можно точно определить любое изменение в истории.

git
# search working tree for a string
git grep "TODO"
git grep -n "TODO" -- "*.js"
git grep -i "error" src/

# search across all commits (history)
git log -S "functionName" --oneline    # pickaxe: when added/removed
git log -G "functionName.*\(" --oneline  # regex match in diffs

# search commit messages
git log --grep="fix.*login" --oneline
git log --author="Alice" --since="1 week ago"

FSCK и висячие объекты

git fsck (file system check) проверяет целостность базы объектов и может найти висячие коммиты — полезно для восстановления, когда reflog недостаточен. git gc реорганизует и сжимает объекты для экономии места на диске; --prune=now немедленно удаляет недостижимые объекты. Git периодически auto-gc, но ручной запуск может сжать большой репозиторий. --aggressive пересчитывает дельты для максимального сжатия.

git
# check repository integrity
git fsck --full
git fsck --unreachable

# find dangling (unreachable) commits & blobs
git fsck --lost-found

# garbage collect and prune old objects
git gc
git gc --prune=now
git gc --aggressive

# count objects and disk usage
git count-objects -v

Archive и Bundle

git archive экспортирует чистый снимок коммита без директории .git — идеально для распространения релизов или отправки исходного кода тому, кому не нужна история. git bundle упаковывает репозиторий (или диапазон коммитов) в один файл, который можно clone или fetch — идеально для передачи репозиториев через изолированные сети или по email, когда нельзя использовать удалённый сервер.

git
# export a snapshot as a tar/zip (no .git history)
git archive --format=zip --output=app.zip HEAD
git archive --format=tar HEAD | gzip > app.tar.gz
git archive -o release.zip v1.0.0

# bundle a repo (with history) into a single file
git bundle create repo.bundle --all
git bundle create patch.bundle main..feature

# clone from a bundle (offline transfer)
git clone repo.bundle new-repo
git fetch patch.bundle feature
09

Продвинутые техники

Hooks

Hooks — скрипты, автоматически запускающиеся в определённых точках. Клиентские hooks (pre-commit, commit-msg, pre-push) обеспечивают локальную политику вроде линтинга или тестов. Серверные hooks (pre-receive, post-receive) запускаются на remote и могут обеспечивать защиту веток или запускать CI/CD. Директория .git/hooks содержит образцы скриптов с суффиксом .sample — переименуйте для активации. Инструменты вроде Husky или pre-commit framework управляют hooks в самом репозитории для согласованности команды.

git
# client-side hooks live in .git/hooks/
#   pre-commit    runs before a commit is created
#   commit-msg    validates the commit message
#   pre-push      runs before pushing to remote

# sample pre-commit hook (.git/hooks/pre-commit)
#!/bin/sh
npm run lint || exit 1
npm test || exit 1

# server-side hooks (on the remote)
#   pre-receive, update, post-receive

# make a hook executable
chmod +x .git/hooks/pre-commit

Worktrees

Worktrees позволяют иметь несколько рабочих директорий для одного репозитория, каждая на своей ветке — без клонирования. Это бесценно, когда нужно работать над hotfix, сохраняя рабочее дерево ветки фичи, или запускать долгую сборку на одной ветке, редактируя другую. Все worktrees разделяют одну базу объектов .git, поэтому использование диска минимально.

git
# create a second working tree of the same repo
git worktree add ../app-feature feature/x

# list worktrees
git worktree list

# create a worktree with a new branch
git worktree add -b hotfix/y ../app-hotfix main

# remove a worktree
git worktree remove ../app-feature

# prune stale worktree metadata
git worktree prune

Фильтрация и переписывание истории

Переписывание истории необходимо, когда секреты или большие файлы были закоммичены. git filter-repo — современная, быстрая замена git filter-branch. BFG Repo-Cleaner — дружелюбная альтернатива для удаления больших файлов или шаблонов паролей. После переписывания нужно force-push и уведомить всех коллабораторов о повторном clone — старые коммиты остаются в их reflog до истечения. Всегда немедленно ротируйте любые утёкшие секреты.

git
# remove a file from ALL of history (sensitive data)
git filter-repo --path secrets.env --invert-paths
# (install git-filter-repo; the old filter-branch is deprecated)

# rewrite author info across all commits
git filter-repo --mailmap mailmap.txt

# split a subdirectory into its own repo
git filter-repo --subdirectory-filter libs/lib

# the simpler BFG Repo-Cleaner (Java tool)
bfg --delete-files *.env
bfg --replace-text passwords.txt

Sparse Checkout и Partial Clone

Partial clone (--filter) получает коммиты и деревья, но скачивает blobs (содержимое файлов) лениво по мере обращения — значительно ускоряя clone огромных репозиториев. Sparse checkout ограничивает рабочее дерево конкретными директориями, поэтому вы видите только части, над которыми работаете. Вместе они делают огромные monorepo управляемыми: clone быстрый, а рабочее дерево остаётся маленьким.

git
# partial clone: download history on demand
git clone --filter=blob:none https://github.com/user/huge-repo.git

# sparse checkout: only certain directories
git clone --no-checkout https://github.com/user/repo.git
cd repo
git sparse-checkout init --cone
git sparse-checkout set src docs
git checkout main

# add a directory to the sparse set later
git sparse-checkout add tests

# disable sparse checkout (fetch everything)
git sparse-checkout disable

Reflog, Refspec и Notes

Refspecs дают явный контроль над сопоставлением refs при fetch/push — полезно для необычных рабочих процессов, как push локальной ветки фичи в remote main. git notes прикрепляет метаданные к коммиту без переписывания, что удобно для комментариев ревью или CI-ссылок. Notes хранятся в отдельном ref (refs/notes/commits) и должны явно отправляться и получаться.

git
# refspec: explicit fetch/push mapping
git fetch origin "refs/heads/*:refs/remotes/origin/*"
git push origin "refs/heads/feature:refs/heads/main"

# add a note to a commit (without changing it)
git notes add -m "Reviewed by Alice" abc1234
git notes show abc1234
git log --show-notes

# configure where notes are stored
git config --global notes.displayRef "refs/notes/*"

# push notes to a remote
git push origin refs/notes/commits
10

Лучшие практики и советы

Гигиена коммитов

Хорошие коммиты маленькие, атомарные и самодостаточные: одно логическое изменение на коммит. Это упрощает ревью, ускоряет bisect и делает revert хирургическим. Используйте git add -p для индексирования только частей, относящихся к одной задаче. Сообщения в повелительном наклонении ('add', а не 'added') читаются как инструкции кодовой базе. Rebase веток фич на последний main перед слиянием сохраняет историю линейной и понятной.

git
# commit small, focused, logical units
git add -p               # stage only relevant hunks
git commit -m "fix: handle null user in login"

# write clear, imperative messages
# GOOD: "feat: add password reset endpoint"
# BAD:  "stuff", "fix", "WIP", "asdf"

# one concern per commit
git add login.ts && git commit -m "feat: add login"
git add styles.css && git commit -m "style: format login"

# rebase before merging to keep history clean
git fetch && git rebase origin/main

Защита веток и ревью кода

Правила защиты веток предотвращают force-push, требуют PR-ревью и ограничивают слияние passing CI — необходимы для безопасности команды. Требование линейной истории форсирует rebase или squash merge, сохраняя историю читаемой. Подписанные коммиты (GPG или SSH) доказывают авторство и предотвращают подмену. Эти настройки конфигурируются на хостинг-платформе (GitHub/GitLab), а не в самом Git, но они обеспечивают хорошую Git-гигиену в масштабах организации.

git
# on GitHub/GitLab: protect the main branch
# - require pull request reviews
# - require status checks (CI) to pass
# - require linear history (no merge commits)
# - dismiss stale reviews on push
# - require signed commits

# enforce via config (GitHub CLI)
gh api repos/:owner/:repo/branches/main/protection \
  -X PUT -f required_status_checks[strict]=true

# sign commits and verify on receive
git config --global commit.gpgsign true
git config --global gpg.format ssh

Игнорирование и управление секретами

Закоммиченные секреты — самый частый инцидент безопасности Git. После отправки считайте секрет скомпрометированным — немедленно ротируйте, даже после удаления из истории, так как clone и fork сохраняют его. Профилактика лучше всего: .gitignore секретов, используйте переменные окружения или менеджер секретов (Vault, AWS Secrets Manager) и установите git-secrets или pre-commit hook, сканирующий API-ключи и пароли перед каждым коммитом.

git
# never commit secrets — use .gitignore
echo ".env" >> .gitignore
echo "*.pem" >> .gitignore
echo "secrets/" >> .gitignore

# if you accidentally committed a secret:
# 1. rotate/revoke the secret immediately
# 2. remove from history
git filter-repo --path .env --invert-paths
git push --force-with-lease

# use environment files or secret managers
echo "API_KEY=$API_KEY" > .env.local   # gitignored
# load via dotenv in your app

# use git-secrets to scan pre-commit
git secrets --install
git secrets --register-aws

Советы по производительности

Большие репозитории могут быть медленными. fsmonitor и untrackedcache делают git status намного быстрее, кэшируя состояние файловой системы. Shallow и partial clone уменьшают начальную загрузку. Периодически запускайте git gc для сжатия объектов. --no-verify пропускает pre-commit и commit-msg hooks — полезно для emergencies, но не должно стать привычкой, так как обходится проверки качества вашей команды.

git
# speed up status on large repos
git config --global core.fsmonitor true
git config --global core.untrackedcache true

# shallow clone for CI
git clone --depth 1 https://github.com/user/repo.git

# partial clone for huge monorepos
git clone --filter=blob:none https://github.com/user/monorepo.git

# periodic garbage collection
git gc --prune=now

# disable slow hooks temporarily
git commit --no-verify

Распространённые подводные камни

Избегайте этих классических ошибок: никогда не force-push в общие ветки (--force-with-lease — безопасный вариант); никогда не делайте rebase коммитов, на которых могут основывать работу другие; никогда не коммитьте на detached HEAD без предварительного создания ветки. Используйте Git LFS для больших бинарников — иначе они навсегда раздуют историю. Настройте окончания строк (autocrlf) по ОС для избежания шумных diff только с пробелами в кроссплатформенных командах.

git
# PITFALL: force-pushing to shared branches
git push --force origin main   # DON'T — use --force-with-lease

# PITFALL: rebasing pushed commits
# rebase only your local, unpushed branches

# PITFALL: committing on detached HEAD
git checkout v1.0.0
# changes here are not on a branch — create one:
git checkout -b fix/v1.0.1

# PITFALL: large binary files bloating history
# use Git LFS for binaries
git lfs install
git lfs track "*.psd"
git add .gitattributes

# PITFALL: CRLF/LF line endings across OS
git config --global core.autocrlf input   # on Linux/Mac
git config --global core.autocrlf true    # on Windows
11

Рабочий процесс Git Flow

Модель ветвления Git Flow

Git Flow — строгая модель ветвления для релизных проектов. main всегда хранит production-код; develop хранит интеграционную работу. Ветки фичи ответвляются от develop и сливаются обратно. Releases ответвляются от develop, стабилизируются, затем сливаются и в main (с тегом), и в develop. Hotfixes ответвляются от main и сливаются и в main, и в develop. Эта модель подходит проектам с запланированными релизами (десктопные приложения, on-premise ПО). Для непрерывного развёртывания GitHub Flow (main + ветки фич) проще. CLI-инструмент git-flow автоматизирует этот танец веток.

git
# Git Flow uses long-lived branches:
# main      → production-ready code
# develop   → latest delivered development changes
# feature/* → new features (branched from develop)
# release/* → release preparation (branched from develop)
# hotfix/*  → urgent production fixes (branched from main)

# Initialize git-flow (interactive setup)
git flow init

# Start a new feature
git flow feature start my-feature
# Creates feature/my-feature from develop

# Finish a feature (merges to develop, deletes branch)
git flow feature finish my-feature

# Publish a feature to remote
git flow feature publish my-feature

# Start a release
git flow release start 1.2.0
# Creates release/1.2.0 from develop

# Finish a release (merges to main AND develop, tags)
git flow release finish 1.2.0

GitHub Flow (проще)

GitHub Flow — простейший рабочий процесс: main всегда разворачиваем, ветки фич короткоживущие, всё сливается через Pull Requests. Нет ветки develop или release-веток — main разворачивается непрерывно. Хорошо подходит для веб-приложений с непрерывным развёртыванием. Ключевое правило: никогда не коммитьте напрямую в main; всегда используйте PR для ревью. Держите ветки фич маленькими и короткоживущими (днями, не неделями). Удаляйте ветки после слияния для чистоты репозитория. Эта модель приоритизирует скорость и простоту над структурированным управлением релизами Git Flow.

git
# GitHub Flow: only main + feature branches
# 1. Create branch from main
git checkout main
git pull origin main
git checkout -b feature/add-login

# 2. Commit changes
git add .
git commit -m "feat: add login page"

# 3. Push to remote
git push -u origin feature/add-login

# 4. Open Pull Request on GitHub
# 5. Review, discuss, get approval
# 6. Merge to main (via GitHub UI or CLI)
git checkout main
git pull origin main
git branch -d feature/add-login  # delete local branch

# 7. Deploy from main (continuous deployment)

Trunk-Based Development

Trunk-Based Development — самый экстремальный: разработчики коммитят напрямую в main (или очень короткоживущие ветки, сливаемые в течение 24 часов). Это включает истинную непрерывную интеграцию — все интегрируются постоянно. Незавершённые функции используют feature flags (развёрнуты, но скрыты) вместо долгоживущих веток. Требует сильного CI/CD, исчерпывающих тестов и инфраструктуры feature flags. Используется Google, Facebook и Netflix. Преимущества: нет merge hell, быстрая обратная связь, маленькие изменения. Вызовы: требует дисциплины, покрытия тестами и управления feature flags. Лучше всего для опытных команд с надёжным CI/CD.

git
# Trunk-Based: everyone commits to main (trunk)
# Short-lived feature branches optional

# Direct commit to main (small teams)
git checkout main
git pull
# make changes
git add . && git commit -m "fix: typo in header"
git push

# With short-lived branches (larger teams)
git checkout -b quick-fix
git commit -m "fix: validation bug"
git push
# PR merged within 24 hours

# Feature flags enable incomplete code on main
# Code is deployed but hidden behind a flag
if (featureFlag.isEnabled("new-checkout")) {
  showNewCheckout();
} else {
  showOldCheckout();
}

Рабочий процесс Fork & Pull

Рабочий процесс Fork & Pull стандартен для open source. Контрибьюторы fork'ают репозиторий (создавая свою копию), push'ат ветки в свой fork и открывают PR в исходный (upstream) репозиторий. Remote upstream позволяет синхронизировать fork с оригиналом. Всегда создавайте ветки фич от обновлённого main. Этот рабочий процесс позволяет любому контрибьютить без права записи. Мейнтейнеры ревьюят PR и сливают. Для синхронизации fork fetch upstream и merge/rebase регулярно. Некоторые проекты используют модель 'clone, branch, PR' для внутренних контрибьюторов с прямым push-доступом к веткам фич.

git
# For open-source projects (contributors don't have push access)

# 1. Fork the repo on GitHub (UI button)
# 2. Clone YOUR fork
git clone https://github.com/YOUR-USERNAME/project.git
cd project

# 3. Add upstream (original repo)
git remote add upstream https://github.com/ORIGINAL/project.git

# 4. Keep your fork updated
git fetch upstream
git checkout main
git merge upstream/main  # or: git rebase upstream/main
git push origin main

# 5. Create feature branch
git checkout -b feature/my-contribution

# 6. Push to YOUR fork
git push origin feature/my-contribution

# 7. Open Pull Request from your fork to upstream

Соглашения об именовании веток

Согласованные имена веток улучшают ясность и включают автоматизацию. Частые префиксы: feature, bugfix, hotfix, release, chore, docs, refactor, experiment. Включение номеров тикетов (PROJ-123) связывает ветки с issues и включает auto-linking. Слеши создают визуальную иерархию в Git GUI. Некоторые команды принуждают к именованию через Git hooks или CI-проверки. Соглашение должно быть задокументировано в CONTRIBUTING.md. Держите имена описательными, но краткими. Избегайте личных имён (johns-branch) — описывайте работу, а не автора. Согласованное именование значительно упрощает очистку веток и навигацию по истории.

git
# Common naming patterns:
feature/add-user-authentication
feature/PROJ-123-user-profile
bugfix/fix-login-redirect
bugfix/PROJ-456-crash-on-startup
hotfix/security-patch-xss
release/v2.0.0
chore/update-dependencies
docs/api-documentation
refactor/extract-auth-module
experiment/new-algorithm

# Using ticket numbers (Jira, Linear, etc.)
git checkout -b feature/PROJ-123-oauth-login

# Auto-linking in commit messages
git commit -m "PROJ-123: implement OAuth login"
# GitHub auto-links PROJ-123 to the Jira ticket

# Branch naming with slashes creates hierarchy
# in Git GUI tools (GitHub, GitKraken, SourceTree)
12

Углублённое изучение rebase

Интерактивный rebase

Интерактивный rebase (-i) — самый мощный инструмент редактирования истории. Позволяет переписывать, переупорядочивать, объединять, разделять или удалять коммиты перед push. squash объединяет коммит с родителем (сливая сообщения); fixup делает то же, но отбрасывает сообщение коммита (очистка 'WIP'-коммитов). edit приостанавливает rebase для изменения коммита (добавить файлы, изменить содержимое). reword позволяет изменить только сообщение. drop удаляет коммит. Всегда делайте rebase перед push для чистоты истории. Никогда не делайте rebase коммитов, которые другие уже вытянули — это переписывает общую историю.

git
# Rebase last 5 commits interactively
git rebase -i HEAD~5

# Opens editor with:
# pick a1b2c3d First commit
# pick e4f5g6h Second commit
# pick i7j8k9l Third commit
# pick m0n1o2p Fourth commit
# pick q3r4s5t Fifth commit

# Commands:
# pick   = use commit as-is
# reword = use commit, edit message
# edit   = pause to amend the commit
# squash = combine with previous commit
# fixup  = like squash, discard this message
# drop   = remove commit entirely
# exec   = run a shell command
# reorder = move lines to reorder commits

Squash коммитов

Squashing объединяет несколько коммитов в один, создавая чистую историю. Идеально для слияния ветки фичи: squash 20 'WIP'-коммитов в один значимый 'feat: add login'. Флаг --fixup создаёт специальный коммит, который --autosquash автоматически помещает и сжимает с целевым — отлично для исправления отзыва ревью без засорения истории. git reset --soft main с последующим одиночным коммитом сжимает всё сразу (проще интерактивного rebase для полного сжатия). Многие команды настраивают PR-слияния на auto-squash (опция GitHub 'Squash and merge').

git
# Squash the last 3 commits into one
git rebase -i HEAD~3

# In editor, change:
# pick a1b2c3d First
# squash e4f5g6h Second  (or 'fixup' to discard message)
# squash i7j8k9l Third

# Git prompts for combined commit message

# Squash everything since branching from main
git rebase -i main

# Auto-squash fixup commits
git commit --fixup a1b2c3d  # creates fixup! commit
git rebase -i --autosquash main
# Git automatically reorders and squashes fixup commits

# Squash all commits into one on a branch
git reset --soft main
git commit -m "feat: complete feature X"

Rebase vs Merge

Merge сохраняет полную историю ветки (с merge-коммитами, показывающими, где фичи ответвились и слились). Rebase воспроизводит коммиты поверх цели, создавая линейную историю без merge-коммитов. Merge безопаснее (без переписывания истории) и показывает контекст фичи. Rebase чище, но переписывает историю коммитов. Частая стратегия: rebase ветки фичи на последний main перед слиянием, затем merge (fast-forward или с --no-ff для merge-коммита). Это даёт чистые коммиты И merge-коммит, отмечающий фичу. Для публичных/общих веток предпочитайте merge для избежания конфликтов истории.

git
# MERGE: preserves complete history with merge commits
git checkout main
git merge feature
# Creates a merge commit, history looks like a graph:
# *   merge commit
# |\
# | * feature commit 2
# | * feature commit 1
# * main commit

# REBASE: linear history, no merge commits
git checkout feature
git rebase main
git checkout main
git merge feature  # fast-forward, no merge commit
# History is linear:
# * feature commit 2
# * feature commit 1
# * main commit

# When to use each:
# Merge: preserve context of feature branch, public repos
# Rebase: clean linear history, before pushing feature branch

Разрешение конфликтов rebase

Конфликты rebase возникают при воспроизведении коммитов на изменённую базу. Разрешите каждый конфликт, git add разрешённые файлы и git rebase --continue. Rebase обрабатывает по одному коммиту за раз, поэтому можно столкнуться с несколькими конфликтами. --skip отбрасывает коммит (используйте, если он становится пустым после rebase). --abort отменяет всё и возвращает в пред-rebase состояние — всегда безопасный выход. Для сложных конфликтов git mergetool запускает визуальный merge-инструмент (VS Code, Beyond Compare и т. д.). Ключевое отличие от конфликтов слияния: rebase может потребовать разрешения одного конфликта несколько раз (по разу на воспроизводимый коммит).

git
# During rebase, conflicts pause the process
git rebase main
# CONFLICT in file.txt

# 1. Resolve conflicts manually in the file
#    (look for <<<<<<< ======= >>>>>>> markers)

# 2. Stage resolved files
git add file.txt

# 3. Continue the rebase
git rebase --continue

# Skip a commit (if it's empty after rebase)
git rebase --skip

# Abort the entire rebase (return to original state)
git rebase --abort

# Use a merge tool for conflicts
git mergetool

# After resolving, the rebase continues
# with the next commit automatically

Rebase --onto (продвинутый)

git rebase --onto — продвинутая форма для точной пересадки коммитов. Синтаксис: rebase --onto NEW-BASE OLD-BASE BRANCH — берёт коммиты между OLD-BASE и BRANCH и воспроизводит их на NEW-BASE. Полезно для изменения базовой точки ветки (например, ваша фича была основана на другой фиче, которую слили; rebase на main для очистки). Также используется для удаления конкретных коммитов из истории (воспроизвести в обход). Это функция для опытных — сначала освойте обычный rebase. Всегда имейте резервную копию (reflog) перед продвинутым переписыванием истории.

git
# Scenario: feature branch was based on old-branch,
# but you want to rebase onto main instead

# git rebase --onto new-base old-base branch
git rebase --onto main old-branch feature

# This takes commits from old-branch..feature
# and replays them onto main

# Use case: remove commits from the middle
# Remove commit C from branch (replay A,B,D onto base)
#   base -> A -> B -> C -> D
# git rebase --onto base C D
# Result: base -> A -> B -> D'

# Use case: move a branch to a different parent
git rebase --onto main feature/sub-feature feature/main-feature
# Moves main-feature's unique commits from sub-feature to main
13

Cherry-pick и bisect

git cherry-pick

cherry-pick применяет конкретный коммит из одной ветки в другую. Случаи: применить багфикс к release-ветке, скопировать забытый к слиянию коммит или выборочно портировать фичи. Коммит получает новый хеш (другой родитель). Cherry-pick может вызывать конфликты, если целевая ветка разошлась. --no-commit индексирует изменения без коммита (полезно для объединения нескольких cherry-pick). Избегайте чрезмерного cherry-picking — может создавать дубликаты коммитов при最终 слиянии веток. Для систематического обратного порта используйте release-ветки со слияниями.

git
# Apply a specific commit to current branch
git cherry-pick a1b2c3d

# Cherry-pick multiple commits
git cherry-pick a1b2c3d e4f5g6h i7j8k9l

# Cherry-pick a range (exclusive start, inclusive end)
git cherry-pick A..E  # commits B, C, D, E

# Cherry-pick without committing (stage only)
git cherry-pick --no-commit a1b2c3d
# or: -n

# Cherry-pick and edit commit message
git cherry-pick --edit a1b2c3d

# Cherry-pick from another branch
git cherry-pick feature-branch~2

# If conflicts occur:
git add . && git cherry-pick --continue
git cherry-pick --abort  # cancel

git bisect (бинарный поиск)

git bisect выполняет бинарный поиск по истории коммитов для нахождения точного коммита, внедрившего баг. Вы помечаете текущее состояние как 'bad' и известный-рабочий коммит как 'good'. Git checkout'ит середину; вы тестируете и помечаете good/bad. Каждый шаг уменьшает пространство поиска вдвое — поиск бага в 1000 коммитов занимает ~10 шагов. bisect reset возвращает в исходную ветку. Бесценно для отслеживания регрессий. Комбинируйте с git bisect log для сохранения/восстановления сессий bisect. Коммит-виновник часто сразу раскрывает корневую причину.

git
# Find which commit introduced a bug
git bisect start

# Mark current (bad) commit
git bisect bad

# Mark a known-good commit (last working version)
git bisect good v1.0.0
# or: git bisect good a1b2c3d

# Git checks out a middle commit
# Test it, then mark:
git bisect good  # if this commit works
git bisect bad   # if this commit is broken

# Repeat until Git identifies the culprit:
# "a1b2c3d is the first bad commit"

# When done:
git bisect reset  # return to original branch

# Skip a commit (can't test, e.g., doesn't build)
git bisect skip

Автоматизированный bisect

Автоматизированный bisect запускает тестовый скрипт для каждого коммита, исключая ручное тестирование. Скрипт выходит с 0 (good), non-zero (bad) или 125 (skip — например, сбой сборки). Git автоматически помечает каждый коммит и находит виновника. Чрезвычайно мощно с тестовым набором: git bisect run npm test находит ломающий коммит за минуты. Можно ограничить поиск конкретными файлами (git bisect start -- path/to/file) для ускорения. Скрипт может быть чем угодно: тест, проверка сборки или curl-команда, проверяющая API. Сохраняйте лог bisect для воспроизведения или возобновления позже.

git
# Auto-bisect with a test script
git bisect start
git bisect bad HEAD
git bisect good v1.0.0

# Git runs this script for each commit
# Exit 0 = good, exit 1 = bad, 125 = skip
git bisect run npm test

# Or a custom script
git bisect run ./scripts/check-bug.sh

# Example check script:
#!/bin/bash
npm run build || exit 125  # skip if build fails
npm test -- --grep "login bug"
# exit 0 if test passes (good), 1 if fails (bad)

# Git automatically finds the bad commit
# without manual testing

# Bisect with a file range (faster)
git bisect start -- src/auth/login.js

git blame и annotate

git blame показывает автора и коммит для каждой строки файла — необходимо для понимания, почему код существует. -L ограничивает диапазоном строк (быстрее, сфокусированнее). -w игнорирует изменения только пробелов (показывает реального автора содержимого). -M обнаруживает код, перемещённый в том же файле; -C обнаруживает код, скопированный из других файлов (показывает исходного автора, а не копировщика). blame для понимания, а не обвинения — используйте для поиска контекста кода, затем читайте полный коммит через git show. Кнопка 'Blame' на GitHub даёт визуальный интерфейс. Комбинируйте с git log -S для поиска, когда был добавлен конкретный текст.

git
# Show who last modified each line
git blame file.txt

# Blame specific line range
git blame -L 10,20 file.txt

# Show full commit hash (not abbreviated)
git blame -l file.txt

# Show email instead of name
git blame -e file.txt

# Ignore whitespace changes
git blame -w file.txt

# Detect moved lines within file
git blame -M file.txt

# Detect moved lines from other files
git blame -C file.txt

# Blame with commit message
git blame --show-name --show-email file.txt | head

git revert (безопасная отмена)

git revert создаёт новый коммит, отменяющий предыдущий — безопасный способ отменить изменения на общих ветках (в отличие от reset, переписывающего историю). revert идеален для production-веток, где нельзя переписать историю. Reverting merge-коммита требует -m 1 (mainline parent) — это отменяет слияние, сохраняя историю ветки. Reverting revert повторно применяет исходное изменение (часто, когда revert был ошибочным). Для нескольких коммитов делайте revert в обратном порядке (сначала новые) для минимизации конфликтов. Всегда используйте revert на общих/публичных ветках; reset только на локальных.

git
# Revert a commit (creates a NEW commit that undoes it)
git revert a1b2c3d

# Revert multiple commits
git revert a1b2c3d e4f5g6h

# Revert a range
git revert A..E

# Revert without committing (stage the reversal)
git revert --no-commit a1b2c3d

# Revert a merge commit
git revert -m 1 a1b2c3d
# -m 1 specifies the mainline parent (parent 1 = the branch
# you merged INTO, parent 2 = the branch you merged FROM)

# If revert conflicts:
git add . && git revert --continue
git revert --abort
14

Reflog и восстановление

Основы git reflog

Reflog записывает каждое изменение HEAD и указателей веток — даже операции, «уничтожающие» коммиты (reset --hard, rebase, удаление веток). Это ваша страховка: «потерянные» коммиты восстанавливаются через reflog ~90 дней (по умолчанию). reflog локален (никогда не отправляется) и показывает хронологическую историю движений указателей. Для восстановления найдите хеш коммита в reflog и checkout/reset к нему. Синтаксис @{N} ссылается на записи: HEAD@{0} — текущая, HEAD@{1} — предыдущая. Если думаете, что «потеряли» работу, сначала проверьте reflog — она почти наверняка ещё там.

git
# View the reflog (reference log)
git reflog
# Shows ALL reference changes, including:
# - commits, checkouts, resets, rebases, merges
# Example output:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: add feature
# i7j8k9l HEAD@{2}: checkout: moving from main to feature

# Reflog for a specific branch
git reflog show feature-branch

# Reflog with dates
git reflog --date=iso

# Recover a "lost" commit
git reflog
# Find the commit hash, then:
git checkout a1b2c3d  # or: git reset --hard a1b2c3d

Восстановление удалённых веток

Удалённые ветки и reset-коммиты восстанавливаются через reflog. Объекты коммитов всё ещё существуют в хранилище объектов Git до сборки мусора (по умолчанию: 90 дней для недостижимых объектов). Для восстановления удалённой ветки найдите её tip-коммит в reflog и создайте новую ветку, указывающую на него. Для ошибок reset --hard reflog показывает предыдущую позицию HEAD — reset обратно. Для плохих rebase найдите запись reflog перед началом rebase и reset к ней. Ключевой урок: в Git почти ничто не теряется немедленно. Всегда проверяйте reflog перед паникой.

git
# Accidentally deleted a branch?
git branch -D feature  # force deleted

# Find it in reflog
git reflog
# e4f5g6h HEAD@{2}: commit: work on feature

# Recreate the branch at that commit
git branch feature e4f5g6h

# Or checkout and create
git checkout -b feature e4f5g6h

# Recover after reset --hard
git reset --hard HEAD~3  # oops, went too far
git reflog
# Find the commit before the reset
git reset --hard HEAD@{1}  # undo the reset

# Recover after a bad rebase
git reflog
git reset --hard HEAD@{5}  # before the rebase started

git fsck (висячие объекты)

git fsck проверяет целостность репозитория и находит висячие объекты — коммиты, blobs и деревья, на которые не ссылается ни одна ветка или тег. --lost-found записывает их в .git/lost-found/. Это последнее средство, когда reflog не имеет нужного (записи reflog истекают, или git gc отработал). Висячие коммиты часто результат прерванных операций или истёкших записей reflog. Инспектируйте через git show, затем восстановите, создав ветку. fsck --full проверяет целостность всех объектов (полезно для обнаружения повреждений). Периодически запускайте fsck на важных репозиториях для раннего обнаружения проблем.

git
# Find dangling (unreachable) commits
git fsck --lost-found

# Output:
# dangling commit a1b2c3d...
# dangling blob e4f5g6h...

# Inspect a dangling commit
git show a1b2c3d

# Recover it
git branch recovered a1b2c3d

# Full fsck (check repository integrity)
git fsck --full

# Check for dangling objects only
git fsck --no-reflogs --dangling

# When reflog doesn't have what you need
# (e.g., expired, or gc ran), fsck finds
# ALL dangling objects in the object store

Углублённое изучение git stash

git stash временно откладывает незакоммиченные изменения. push -m добавляет описательное сообщение (необходимо для управления несколькими stash). -u включает неотслеживаемые файлы; -a включает и ignored. apply применяет без удаления; pop применяет и удаляет. stash branch создаёт новую ветку из stash (полезно, если stash конфликтует с текущей веткой). Stash'и хранятся в стеке (LIFO) — ссылка stash@{N}. show -p отображает diff. Stash'и сохраняются между перезагрузками, но локальны (никогда не отправляются). Регулярно очищайте старые stash'и; они накапливаются. Для долгосрочной работы создавайте ветку вместо stash.

git
# Stash with message
git stash push -m "work in progress on login"

# Stash specific files
git stash push -m "wip" file1.txt file2.txt

# Stash including untracked files
git stash push -u  # or --include-untracked

# Stash including ignored files
git stash push -a  # or --all

# List stashes
git stash list
# stash@{0}: On feature: wip on login
# stash@{1}: On main: quick fix

# Apply a specific stash
git stash apply stash@{1}

# Apply and drop (pop)
git stash pop  # applies stash@{0} and removes it

# Show stash contents
git stash show -p stash@{0}

# Create branch from stash
git stash branch new-branch stash@{0}

# Drop a stash
git stash drop stash@{1}
git stash clear  # drop ALL stashes

Управление git tag

Теги отмечают конкретные коммиты как важные (релизы, вехи). Аннотированные теги (-a) хранят метаданные (теггер, дата, сообщение) и рекомендуются для релизов. Лёгкие теги — просто именованные указатели (без метаданных). Подписанные теги (-s) используют GPG для проверки (важно для security-релизов). Теги НЕ отправляются по умолчанию — используйте --tags для отправки. Семантическое версионирование (v1.2.3) — стандартное соглашение об именовании. Checkout тега даёт состояние detached HEAD (для проверки или сборки релиза). GitHub-релизы строятся на тегах — создайте тег, затем опубликуйте релиз с примечаниями.

git
# Create annotated tag (recommended)
git tag -a v1.0.0 -m "Release 1.0.0"

# Create lightweight tag
git tag v1.0.0

# Tag a specific commit
git tag -a v0.9.0 -m "beta" a1b2c3d

# List all tags
git tag
git tag -l "v1.*"  # filter by pattern

# Push tags to remote
git push origin v1.0.0      # single tag
git push origin --tags       # all tags

# Delete a tag
git tag -d v1.0.0            # local
git push origin --delete v1.0.0  # remote

# Show tag details
git show v1.0.0

# Checkout a tag (detached HEAD)
git checkout v1.0.0

# Signed tags (GPG)
git tag -s v1.0.0 -m "signed release"
15

Worktree и submodules

git worktree

git worktree создаёт дополнительные рабочие директории из того же репозитория — без клонирования. Каждый worktree checkout'ит свою ветку одновременно. Идеально для: работы над hotfix, сохраняя открытой ветку фичи, запуска тестов на одной ветке при кодинге на другой, или долгих сборок на одном worktree. Все worktrees разделяют директорию .git (объекты, refs), поэтому синхронизированы и экономят место на диске. Нельзя checkout одну и ту же ветку в двух worktree (Git предотвращает это для избежания конфликтов). Worktrees быстрее клонирования для многоветочных рабочих процессов.

git
# Create a new working directory linked to the repo
git worktree add ../project-hotfix hotfix-branch

# Work in the new directory
cd ../project-hotfix
# This is a full working tree on hotfix-branch
# Shares the same .git, so no cloning needed

# List worktrees
git worktree list
# /path/to/project          main
# /path/to/project-hotfix   hotfix-branch

# Remove a worktree
git worktree remove ../project-hotfix

# Prune stale worktree entries
git worktree prune

# Create worktree at new branch
git worktree add -b feature-x ../project-feature main

Основы git submodule

Submodules встраивают один Git-репозиторий в другой — полезно для включения общих библиотек или зависимостей. Родительский репозиторий хранит указатель (хеш коммита) на submodule, а не его содержимое. --recurse-submodules необходимо при clone (иначе submodules пусты). Обновление submodules (--remote) получает последние коммиты; затем нужно закоммитить новый хеш в родительском репозитории. Submodules сложны: ветки, конфликты и обновления требуют осторожного обращения. Для более простого управления зависимостями рассмотрите Git subtrees, пакетные менеджеры (npm, pip) или monorepo-стратегии. Используйте submodules, когда нужно отслеживать конкретный внешний коммит.

git
# Add a submodule to your repo
git submodule add https://github.com/user/lib.git libs/lib

# Clone a repo with submodules
git clone --recurse-submodules https://github.com/user/project.git

# If you forgot --recurse-submodules:
git submodule update --init --recursive

# Pull latest changes in all submodules
git submodule update --remote

# Pull specific submodule
git submodule update --remote libs/lib

# After updating submodules, commit the new hash
git add libs/lib
git commit -m "update lib submodule"

# Execute command in all submodules
git submodule foreach 'git status'

Рабочие процессы submodule

Работа внутри submodule аналогична работе в обычном репозитории — вы коммитите и push'ите из директории submodule. Родительский репозиторий отслеживает хеш коммита, поэтому после изменения submodule нужно коммитить и в родителе. При переключении веток содержимое submodule может не совпадать — запустите git submodule update --init --recursive для синхронизации. Удаление submodules требует трёх шагов: deinit (разрегистрация), git rm (удаление из отслеживания) и ручное удаление .git/modules. Рабочие процессы submodules подвержены ошибкам; всегда общайтесь с командой при обновлении submodules для избежания несоответствия хешей.

git
# Change code inside a submodule
cd libs/lib
# Make changes, commit, and push
git add .
git commit -m "fix: bug in lib"
git push origin HEAD:main

# Back in parent repo, update the pointer
cd ../..
git add libs/lib
git commit -m "chore: update lib submodule"

# Switch branches with submodules
git checkout main
git submodule update --init --recursive

# Delete a submodule
git submodule deinit -f libs/lib
git rm libs/lib
rm -rf .git/modules/libs/lib
git commit -m "remove lib submodule"

# Move a submodule
git mv libs/lib libs/new-lib

Git Hooks

Git hooks запускают скрипты в определённых точках жизненного цикла Git. Клиентские hooks (pre-commit, pre-push, commit-msg) обеспечивают локальные стандарты. pre-commit идеален для линтинга/форматирования; pre-push для запуска тестов; commit-msg для принуждения к conventional commits. Hooks НЕ отслеживаются Git (они в .git/hooks/), поэтому не синхронизируются между clone. Для разделения hooks между командой используйте инструмент вроде Husky (npm), pre-commit (Python) или коммитьте hooks в checked-in директорию и symlink'ните их. Серверные hooks (pre-receive, post-receive) запускаются на remote и могут обеспечивать политику для всех контрибьюторов.

git
# Hooks live in .git/hooks/ (not tracked by Git)
# Sample hooks are provided with .sample extension

# pre-commit: runs before commit is created
# .git/hooks/pre-commit
#!/bin/bash
npm run lint || exit 1  # block commit if lint fails

# pre-push: runs before pushing
#!/bin/bash
npm test || exit 1  # block push if tests fail

# commit-msg: validate commit message format
#!/bin/bash
# $1 is the path to the commit message file
if ! grep -qE "^(feat|fix|docs|chore):" "$1"; then
  echo "Use conventional commit format: type: message"
  exit 1
fi

# prepare-commit-msg: auto-populate message
#!/bin/bash
echo "# Branch: $(git branch --show-current)" >> "$1"

# Make hooks executable
chmod +x .git/hooks/pre-commit

Git LFS (Large File Storage)

Git LFS заменяет большие файлы (бинарники, видео, datasets) текстовыми указателями в Git, храня фактическое содержимое на отдельном LFS-сервере. Это сохраняет репозиторий лёгким — без LFS бинарные файлы навсегда раздули бы репозиторий (каждая версия сохраняется). Отслеживайте шаблоны файлов через git lfs track, затем коммитьте .gitattributes. После этого большие файлы работают прозрачно. git lfs migrate import ретроактивно конвертирует существующие файлы в LFS (переписывает историю — сначала согласуйте с командой). LFS требует поддержки сервера (GitHub, GitLab, Bitbucket поддерживают). Примечание: LFS имеет квоты bandwidth/storage на хостинг-платформах. Для действительно огромных файлов рассмотрите внешнее хранилище с URL.

git
# Initialize Git LFS
git lfs install

# Track large file types
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/**"

# This creates/updates .gitattributes
# Commit the .gitattributes file
git add .gitattributes
git commit -m "chore: configure LFS tracking"

# Add large files normally
git add design.psd
git commit -m "add design file"
git push

# View LFS tracked files
git lfs ls-files

# Pull LFS content (if not auto-downloaded)
git lfs pull

# Migrate existing files to LFS (rewrites history)
git lfs migrate import --include="*.psd" --everything

# Check LFS status
git lfs status
16

Stash

Save и Pop

git stash откладывает незакоммиченные изменения, позволяя переключать ветки или тянуть обновления с чистым рабочим деревом. pop применяет и удаляет верхний stash; apply сохраняет его. Stash — стек LIFO.

git
# Save uncommitted changes (tracked files only)
git stash

# Save including untracked files
git stash -u

# Pop the most recent stash (removes it)
git stash pop

# Apply without removing (keeps stash in list)
git stash apply

Именованные stash'и

Всегда передавайте -m для меток stash — сообщение по умолчанию — ветка и коммит, что редко описательно. stash@{N} ссылается на конкретный stash по индексу.

git
# Save with a descriptive message
git stash push -m "WIP: refactor auth flow"

# List all stashes with messages
git stash list

# Apply a specific stash by index
git stash apply stash@{1}

Stash-ветки

git stash branch создаёт новую ветку из коммита, где stash был изначально сделан, затем применяет stash там. Это самый чистый способ восстановления, когда stash больше не применяется чисто.

git
# Create a new branch from a stash
git stash branch feature-branch stash@{0}

# Useful when stash conflicts with current branch
# - Creates branch from the commit where stash was made
# - Applies stash on top
# - Drops stash if apply succeeds

Частичный stash

Прячьте только нужное через -p (интерактивный выбор частей) или перечислив конкретные файлы. --keep-index прячет неиндексированные изменения, но оставляет индексированные на месте.

git
# Interactively choose hunks to stash
git stash push -p

# Stash specific files only
git stash push -m "config tweaks" config.yml .env.local

# Stash with keep-index (staged changes stay)
git stash --keep-index

Управление stash

git stash show -p отображает полный diff stash. drop удаляет один stash; clear стирает все (необратимо). Stash'и только локальные; никогда не отправляются на remote.

git
# Show what's in a stash
git stash show -p stash@{0}

# Drop a specific stash
git stash drop stash@{0}

# Clear all stashes (irreversible)
git stash clear

# Show stash list with dates
git stash list --date=relative
17

Rebase

Базовый rebase

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

git
# Rebase current branch onto main
git checkout feature
git rebase main

# Abort if conflicts are too messy
git rebase --abort

# Continue after resolving conflicts
git rebase --continue

Интерактивный rebase

Интерактивный rebase (-i) позволяет переписать историю перед sharing: переупорядочить коммиты, сжать связанные в один чистый коммит, reword сообщения или drop ошибки. Всегда делайте это на неотправленных коммитах.

git
# Rebase last 5 commits interactively
git rebase -i HEAD~5

# Commands: pick, reword, edit, squash, fixup, drop
# squash  = combine with previous, keep message
# fixup   = combine with previous, discard message
# reword  = change commit message only

Squash и Fixup

--fixup создаёт коммит, помеченный как исправление другого. --autosquash при rebase автоматически помещает fixup! и squash! коммиты рядом с их целями. Это упрощает рабочий процесс «коммить рано, чисти позже».

git
# Create a fixup commit targeting an earlier commit
git commit --fixup a1b2c3

# Autosquash during rebase (auto-reorders fixups)
git rebase -i --autosquash HEAD~5

Rebase --onto

--onto — хирургический rebase: перемещает диапазон коммитов с одной базы на другую. Используйте для смены родителя ветки или удаления первых нескольких коммитов ветки.

git
# Move commits from one base to another
# git rebase --onto <new-base> <old-base> <branch>
git rebase --onto main feature-old feature-new

# Drop the first N commits of a branch
git rebase --onto main HEAD~3

Конфликты rebase

Во время rebase конфликты останавливаются на каждом коммите. Разрешите, git add, затем --continue для продолжения. --skip отбрасывает конфликтующий коммит целиком. --abort возвращает в пред-rebase состояние.

git
# During rebase, conflicts pause the process
git rebase main
# CONFLICT (content): Merge conflict in file.js

# Resolve in editor, then:
git add file.js
git rebase --continue

# Skip a commit that conflicts (drop it)
git rebase --skip

# Give up entirely
git rebase --abort
18

Cherry-Pick

Cherry-pick коммита

cherry-pick применяет конкретный коммит из другой ветки на текущую, создавая новый коммит с теми же изменениями. Новый коммит имеет другой хеш, так как родитель другой.

git
# Apply a specific commit to current branch
git cherry-pick a1b2c3d

# Cherry-pick multiple commits
git cherry-pick a1b2c3d e4f5g6h

# Cherry-pick a range (exclusive of start, inclusive of end)
git cherry-pick A..E

Cherry-pick без коммита

--no-commit (-n) индексирует cherry-picked изменения без создания коммита. Это позволяет объединить несколько cherry-pick в один коммит или изменить изменения перед коммитом.

git
# Apply changes to working tree without committing
git cherry-pick --no-commit a1b2c3d

# Stage the changes yourself
git add -p
git commit -m "Custom message"

Конфликты cherry-pick

Конфликты при cherry-pick приостанавливают операцию. Разрешите, git add и --continue. --skip отбрасывает текущий коммит. --abort отменяет cherry-pick и восстанавливает ветку.

git
git cherry-pick a1b2c3d
# error: could not apply a1b2c3d...

# Resolve conflicts, then:
git add resolved-file.js
git cherry-pick --continue

# Skip this commit
git cherry-pick --skip

# Abort the entire cherry-pick
git cherry-pick --abort

Cherry-pick из другой ветки

Классический рабочий процесс hotfix: исправить баг в maintenance-ветке, затем cherry-pick тот же коммит на main (и другие активные ветки). Это избегает слияния несвязанной работы фич.

git
# Find the commit hash on another branch
git log --oneline feature-branch

# Switch to target branch
git checkout main

# Cherry-pick the commit
git cherry-pick <hash>

# Useful for hotfixes: fix on a feature branch,
# then cherry-pick the fix to main

Стратегия cherry-pick

-X theirs/ours смещает разрешение конфликтов. -x добавляет строку, записывающую исходный хеш коммита — необходим для audit trail при cherry-pick hotfix'ов между ветками.

git
# Use a different merge strategy
git cherry-pick -X theirs a1b2c3d   # prefer their changes on conflict
git cherry-pick -X ours a1b2c3d     # prefer our changes on conflict

# Preserve original commit author
git cherry-pick -x a1b2c3d          # adds "(cherry picked from ...)" to message

# Edit commit message before committing
git cherry-pick -e a1b2c3d
19

Bisect

Базовый bisect

git bisect выполняет бинарный поиск по истории коммитов для нахождения коммита, внедрившего баг. Вы помечаете текущий коммит как bad и известный-good как good. После ~log2(N) шагов git называет виновный коммит.

git
# Start bisecting
git bisect start

# Mark current commit as bad (has the bug)
git bisect bad

# Mark a known-good commit (older)
git bisect good v1.0.0

# Git checks out the midpoint. Test, then mark:
git bisect good   # or
git bisect bad

# Exit bisect mode
git bisect reset

Bisect log и replay

git bisect log записывает каждое решение good/bad. Если вы ошибочно пометили коммит (частая ошибка), reset и replay лог, затем исправьте неверный шаг.

git
# Record what happens during bisect
git bisect log > bisect.log

# If you make a mistake, replay from the log
git bisect reset
git bisect replay bisect.log

# Visualize the bisect state
git bisect visualize

Автоматизированный bisect

git bisect run автоматизирует поиск: git checkout'ит каждого кандидата, запускает ваш скрипт и помечает коммит по exit code. Это значительно быстрее ручного тестирования.

git
# Auto-bisect: git runs a test script and marks good/bad itself
git bisect start HEAD v1.0.0 --     # bad=HEAD, good=v1.0.0
git bisect run npm test             # exit 0=good, 1-124=bad, 125=skip

# Or with a custom script
git bisect run ./scripts/check-bug.sh

Bisect по файлу

Передача пути в git bisect start ограничивает поиск коммитами, изменившими этот файл. Это пропускает сотни нерелевантных коммитов и фокусируется на файле, где вероятно живёт баг.

git
# Limit bisect to changes in a specific file
git bisect start -- path/to/file.js

# Combine with bad/good markers
git bisect bad HEAD
git bisect good v1.0.0

# Git only considers commits that touched that file

Bisect reset

Всегда запускайте git bisect reset по завершении — возвращает в исходную ветку и очищает состояние bisect. Без reset рабочее дерево остаётся на последнем протестированном коммите.

git
# Exit bisect mode and return to original branch
git bisect reset

# Reset to a specific branch/commit instead
git bisect reset <branch>

# View current bisect state
git bisect status
20

Submodules

Добавление submodule

Submodules встраивают один Git-репозиторий в другой — полезно для vendoring общих библиотек. git submodule add регистрирует submodule в .gitmodules. Новые clone требуют --recurse-submodules.

git
# Add a submodule at a specific path
git submodule add https://github.com/user/lib.git libs/lib

# This creates:
# - libs/lib/ (the submodule checkout)
# - .gitmodules (tracks submodule URLs and paths)

# Commit the new submodule
git commit -m "Add lib submodule"

# Clone a repo with submodules included
git clone --recurse-submodules https://github.com/user/repo.git

Обновление submodules

Submodule pinned к конкретному коммиту. git submodule update --remote получает последний коммит отслеживаемой ветки и обновляет указатель. Вы должны закоммитить это изменение указателя в родительском репозитории.

git
# Pull latest changes in all submodules
git submodule update --remote

# Update a specific submodule
git submodule update --remote libs/lib

# Then commit the updated pointer
git add libs/lib
git commit -m "Bump lib submodule"

# Initialize submodules after cloning
git submodule update --init --recursive

Submodule foreach

foreach запускает команду оболочки в каждой директории submodule — полезно для массовых операций вроде проверки статуса, pull обновлений или сборки. --recursive спускается во вложенные submodules.

git
# Run a command in every submodule
git submodule foreach 'git status'

# With recursion into nested submodules
git submodule foreach --recursive 'git checkout main'

# Use --quiet to suppress the "Entering..." messages
git submodule foreach --quiet 'git pull'

Deinit и remove

Удаление submodule — многошаговый процесс: deinit разрегистрирует, rm -rf .git/modules/... удаляет Git-данные submodule, а git rm удаляет рабочее дерево. Забывчивость о очистке .git/modules оставляет данные-сироты.

git
# Deinit a submodule (unregisters, keeps files)
git submodule deinit libs/lib

# Fully remove a submodule
git submodule deinit -f libs/lib
rm -rf .git/modules/libs/lib
git rm -f libs/lib
git commit -m "Remove lib submodule"

Ветки submodule

По умолчанию submodules в detached HEAD. Установка submodule.<name>.branch позволяет --remote отслеживать эту ветку. Для изменений внутри submodule: cd внутрь, checkout ветку, коммит, push.

git
# Configure a submodule to track a branch
git config -f .gitmodules submodule.libs/lib.branch main

# Now --remote updates from that branch
git submodule update --remote

# Work inside a submodule like a normal repo
cd libs/lib
git checkout feature-branch
git push
cd ../..
git add libs/lib
git commit -m "Update lib to feature-branch"
21

Hooks

Частые hooks

Git hooks — скрипты в .git/hooks/, автоматически запускающиеся в определённых точках. Клиентские hooks запускаются на вашей машине и могут блокировать действия. Серверные hooks запускаются на remote и обеспечивают политику. Hooks не версионированы по умолчанию — используйте Husky для их разделения.

git
# Client-side (run on your machine):
#   pre-commit        - runs before commit is created
#   prepare-commit-msg - can edit commit message
#   commit-msg        - validates commit message
#   pre-push          - runs before pushing
#   post-merge        - runs after git merge

# Server-side (run on the remote):
#   pre-receive       - runs before refs are updated
#   update            - runs once per ref
#   post-receive      - runs after refs are updated

pre-commit hook

pre-commit запускается перед созданием коммита; non-zero exit прерывает коммит. Частые использования: линтинг индексированных файлов, запуск сфокусированных тестов, форматирование кода. Держите его быстрым (<5 секунд), иначе разработчики будут обходить через --no-verify.

git
#!/bin/sh
# .git/hooks/pre-commit

# Run linter on staged files
npm run lint-staged
if [ $? -ne 0 ]; then
  echo "Linting failed. Fix errors and try again."
  exit 1
fi

# Run tests
npm test -- --findRelatedTests
if [ $? -ne 0 ]; then
  echo "Tests failed."
  exit 1
fi

exit 0

commit-msg hook

commit-msg получает путь к временному файлу сообщения коммита как $1. Может валидировать или переписывать сообщение. Non-zero exit отклоняет коммит. Это стандартный способ принуждения к Conventional Commits.

git
#!/bin/sh
# .git/hooks/commit-msg

# Enforce Conventional Commits format
pattern='^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.*\))?: .{1,72}$'

if ! grep -qE "$pattern" "$1"; then
  echo "Invalid commit message format."
  echo "Use: <type>(<scope>): <description>"
  exit 1
fi

Настройка Husky

Husky устанавливает Git hooks из версионированной директории .husky/, поэтому каждый член команды получает те же hooks после npm install. lint-staged запускает команды только на индексированных файлах, сохраняя pre-commit быстрым.

git
# Install Husky
npm install --save-dev husky
npx husky init

# Add a pre-commit hook
echo 'npm run lint-staged' > .husky/pre-commit

# Add a commit-msg hook
echo 'npx --no-install commitlint --edit $1' > .husky/commit-msg

# package.json
{
  "scripts": { "prepare": "husky" },
  "lint-staged": {
    "*.{js,ts}": ["eslint --fix", "prettier --write"]
  }
}

pre-push hook

pre-push запускается перед отправкой refs; non-zero exit прерывает. Читает предлагаемые push'и из stdin. Частые использования: блокировка push в защищённые ветки, запуск полного тестового набора.

git
#!/bin/sh
# .husky/pre-push

# Reads stdin: <local ref> <local sha> <remote ref> <remote sha>
while read local_ref local_sha remote_ref remote_sha; do
  if [ "$remote_ref" = "refs/heads/main" ]; then
    echo "Direct push to main is not allowed."
    exit 1
  fi
done

npm test
if [ $? -ne 0 ]; then
  echo "Tests failed. Push aborted."
  exit 1
fi
22

Worktrees

Добавление worktree

Worktree — отдельная рабочая директория, связанная с тем же репозиторием. Можно иметь несколько веток, checkout'нутых одновременно в разных директориях — без stash для переключения контекста.

git
# Create a worktree at a path, on a new branch
git worktree add ../project-feature feature-branch

# Worktree on an existing branch
git worktree add ../project-hotfix hotfix-branch

# Worktree at a specific commit (detached HEAD)
git worktree add --detach ../project-inspect v1.0.0

# List all worktrees
git worktree list

Рабочий процесс worktree

Worktrees проявляют себя при переключении контекста: срочный баг приходит, когда вы глубоко в фиче. Вместо stash и потери состояния IDE создайте worktree на main, исправьте баг там и вернитесь.

git
# Scenario: working on feature, urgent bug comes in
git worktree add ../project-hotfix main

cd ../project-hotfix
git checkout -b fix-urgent
# ... fix the bug, commit, push ...

cd ../project-feature
# Your feature work is untouched

# Clean up when done
git worktree remove ../project-hotfix

Remove и prune

remove удаляет директорию worktree и её административные метаданные. Ветка, на которой она была, остаётся. Если у worktree есть незакоммиченные изменения, remove отказывает, если не передать --force.

git
# Remove a worktree (must be clean or use --force)
git worktree remove ../project-feature

# Force remove even with uncommitted changes
git worktree remove --force ../project-feature

# Prune worktree admin files for deleted directories
git worktree prune

# Show what would be pruned
git worktree prune --dry-run -v

Преимущества worktree

Worktrees решают несколько проблем: без stash для переключения контекста, параллельные сборки/тесты, изолированные node_modules на ветку и параллельное сравнение веток. Общая база объектов означает минимальные накладные расходы диска.

git
# 1. No stashing needed for context switches
# 2. Run long builds/tests in one worktree while
#    continuing work in another
# 3. Each worktree has its own node_modules
# 4. Compare two branches side-by-side in two
#    editor windows
# 5. Shared object database = minimal disk overhead

# Typical layout:
# ~/projects/
#   main-repo/          (main branch)
#   main-repo-feature/  (feature worktree)
#   main-repo-hotfix/   (hotfix worktree)

Заблокированные worktrees

Заблокируйте worktree, когда он содержит работу, которую нельзя тревожить (долгие сборки, подключённый отладчик). Заблокированные worktree переживают prune. move перемещает worktree; repair чинит административные файлы.

git
# Lock a worktree (prevents it from being pruned)
git worktree lock ../project-feature --reason "Running long build"

# Unlock when done
git worktree unlock ../project-feature

# Move a worktree to a new path
git worktree move ../project-feature ../new-location/project-feature

# Repair after moving manually
git worktree repair ../new-location/project-feature
23

Reflog

Просмотр reflog

Reflog записывает каждое изменение HEAD и tips веток — коммиты, checkout, reset, rebase. Это локальная страховка: даже после разрушительной операции коммиты всё ещё в reflog.

git
# Show reflog for HEAD
git reflog
# a1b2c3d HEAD@{0}: commit: Fix bug
# e4f5g6h HEAD@{1}: checkout: moving to feature
# 789abc0 HEAD@{2}: reset: moving to HEAD~1

# Show reflog for a specific branch
git reflog show feature

# Show reflog with dates
git reflog --date=iso

Восстановление потерянных коммитов

После hard reset или rebase «потерянные» коммиты всё ещё достижимы через reflog. Найдите хеш в git reflog, затем reset --hard или cherry-pick для восстановления. Поэтому Git безопасен — почти ничто не теряется по-настоящему.

git
# Find the commit you lost (e.g., after a hard reset)
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: Important work  <-- this one

# Reset back to the lost commit
git reset --hard e4f5g6h

# Or cherry-pick it onto your current branch
git cherry-pick e4f5g6h

Reflog и Reset

ORIG_HEAD — это вспомогательная ссылка, указывающая на предыдущий HEAD после разрушительных операций (reset, merge, rebase). Команда git reset --hard ORIG_HEAD отменяет последнюю такую операцию одной командой.

git
# Undo a git reset --hard
git reflog
git reset --hard HEAD@{1}

# Undo a rebase
git reflog
git reset --hard HEAD@{5}

# Undo a merge
git reset --hard ORIG_HEAD
# ORIG_HEAD is set by merge, rebase, reset, and cherry-pick

Истечение срока и очистка

Записи reflog накапливаются и занимают место на диске. expire удаляет старые записи; --expire-unreachable нацелен только на записи, недоступные ни из одной ссылки. После истечения срока выполните git gc --prune=now для удаления недоступных объектов.

git
# Expire reflog entries older than 30 days
git reflog expire --expire=30.days

# Expire entries unreachable from current branches
git reflog expire --expire-unreachable=30.days

# Delete a specific reflog entry
git reflog delete HEAD@{2}

# Dry run (see what would be expired)
git reflog expire --dry-run --expire=now --all

Reflog для веток

Каждая ветка ведёт собственный reflog. Если вы удалите ветку командой git branch -D, коммиты всё ещё остаются в reflog — найдите хэш и воссоздайте ветку командой git branch <имя> <хэш>.

git
# Each branch has its own reflog
git reflog show main
# a1b2c3d main@{0}: commit: Update docs
# e4f5g6h main@{1}: pull origin main: Fast-forward

# Recover a deleted branch
git reflog  # find the hash the branch pointed to
git branch recovered-branch e4f5g6h

# Compare current state with a past reflog entry
git diff HEAD@{1} HEAD
24

LFS

Установка и отслеживание

Git LFS хранит большие файлы (изображения, видео, бинарники) вне репозитория Git, заменяя их в коммитах файлами-указателями. git lfs track регистрирует шаблоны в .gitattributes. Всегда коммитьте .gitattributes перед добавлением больших файлов.

git
# Install Git LFS (once per user)
git lfs install

# Track large file types
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/*"

# View tracking rules
cat .gitattributes

# Commit the .gitattributes file
git add .gitattributes
git commit -m "Configure LFS tracking"

Добавление и коммит файлов LFS

После регистрации шаблона файла git add автоматически индексирует файл через LFS. В коммите хранится небольшой файл-указатель (~130 байт) вместо бинарника. Фактическое содержимое загружается на сервер LFS при push.

git
# After configuring tracking, add files normally
git add design.psd
git commit -m "Add design file"

# Verify LFS is handling the file
git lfs ls-files
# a1b2c3d4 * design.psd

# The commit contains a pointer, not the actual file
git show HEAD -- design.psd
# version https://git-lfs.github.com/spec/v1
# oid sha256:...
# size 12345678

Клонирование и pull

Клонирование репозитория с LFS сначала загружает файлы-указатели, затем фактическое содержимое. Если загрузка LFS не удалась, git lfs pull повторяет попытку. git lfs fetch загружает объекты без записи их в рабочее дерево.

git
# Clone a repo with LFS files (downloads them automatically)
git clone https://github.com/user/repo.git

# If LFS files are missing, fetch them
git lfs pull

# Fetch LFS objects without checking them out
git lfs fetch

# Checkout LFS files for specific paths
git lfs checkout assets/

Миграция на LFS

git lfs migrate import ретроспективно преобразует большие файлы в истории в указатели LFS. Это переписывает все хэши коммитов — каждому коллаборатору придется заново клонировать репозиторий. Сначала запустите --dry-run, чтобы оценить последствия.

git
# Convert existing large files to LFS (rewrites history!)
git lfs migrate import --include="*.psd" --include-ref=refs/heads/main

# Migrate all branches
git lfs migrate import --include="*.psd" --everything

# Check what would be migrated (dry run)
git lfs migrate import --include="*.psd" --include-ref=refs/heads/main --dry-run

Управление LFS

git lfs status показывает ожидающие изменения LFS. git lfs env отображает конфигурацию. git lfs fsck проверяет, что все объекты LFS, на которые ссылаются файлы-указатели, присутствуют и не повреждены.

git
# View LFS status
git lfs status

# List all LFS files in the repo
git lfs ls-files

# Check LFS configuration
git lfs env

# Untrack a file type (does not remove existing LFS objects)
git lfs untrack "*.zip"

# Verify LFS objects are present and valid
git lfs fsck
25

Интеграция CI/CD

GitHub Actions, основы

GitHub Actions запускает рабочие процессы при push/PR. actions/checkout извлекает репозиторий; fetch-depth: 0 получает полную историю. npm ci устанавливает из lockfile (быстрее и строже, чем install). Кэшируйте зависимости с помощью ключа cache.

git
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test
      - run: npm run build

Условные рабочие процессы

Фильтруйте рабочие процессы по ветке или событию с помощью on: push: branches. Используйте условия if: на уровне задания, чтобы пропускать задачи в зависимости от контекста. needs: создаёт зависимости между заданиями — deploy запускается, только если проходит test.

git
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Git-хуки в CI

CI-конвейеры часто повторяют локальные хуки: сначала lint (быстрый отказ), затем тесты. needs: lint гарантирует, что тесты запускаются только при успешном линтинге, экономя минуты CI. Разделение задач позволяет использовать параллельные раннеры для ускорения.

git
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck

  test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test -- --coverage

Секреты и окружение

Храните секреты (API-ключи, токены) в настройках репозитория и ссылайтесь на них через ${{ secrets.NAME }}. Они никогда не выводятся в логах. Окружения могут требовать ручного подтверждения перед развёртыванием, добавляя контрольную точку.

git
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # requires manual approval
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DB_URL: ${{ secrets.DATABASE_URL }}
        run: ./scripts/deploy.sh "$API_KEY" "$DB_URL"

Матричные сборки

Матричные сборки запускают одно и то же задание параллельно в нескольких комбинациях ОС/языка. Это позволяет рано выявлять платформенно-зависимые баги. Используйте fail-fast: false, чтобы выполнять все комбинации, даже если одна завершилась неудачей. Держите матрицы разумными, чтобы контролировать расход минут CI.

git
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: ['18', '20', '22']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.