Git
19 вопросов
Как отменить коммит в Git?
Отменить коммит можно тремя способами в зависимости от ситуации.
Если коммит ещё не отправлен в общий репозиторий (push не делался), используйте git reset. Команда git reset --soft HEAD~1 отменит последний коммит, но сохранит изменения в индексе (staging area) — вы сможете изменить сообщение и закоммитить заново. Команда git reset --mixed HEAD~1 (это поведение по умолчанию) отменит коммит и уберёт изменения из индекса, но оставит их в рабочей директории. Команда git reset --hard HEAD~1 полностью удалит и коммит, и изменения — используйте её только если точно уверены, что код не нужен.
Если коммит уже отправлен в общий репозиторий, используйте git revert. Команда git revert HEAD создаёт новый коммит, который отменяет изменения предыдущего. Это безопасно для командной работы, потому что история не переписывается, а добавляется новый коммит отмены. Можно отменить несколько коммитов: git revert HEAD~3..HEAD.
Если нужно изменить только сообщение последнего коммита или добавить забытый файл, используйте git commit --amend. Эта команда заменяет последний коммит новым с тем же содержимым, но другим сообщением или дополнительными файлами. Не используйте --amend для коммитов, которые уже отправлены в общий репозиторий — это перепишет историю и создаст проблемы у других разработчиков.
Правило: reset — для локальных коммитов, revert — для общих, amend — для исправления последнего коммита до push.
Как разрешить конфликт при слиянии в Git?
Конфликт возникает, когда две ветки изменяют одни и те же строки файла и Git не может автоматически объединить изменения. Вот пошаговый процесс разрешения.
Сначала выполните git status, чтобы увидеть conflicted files. Git помечает конфликты в файлах маркерами: <<<<<<< HEAD (ваши изменения), ======= (разделитель) и >>>>>>> branch-name (изменения из другой ветки).
Откройте файл в редакторе и найдите маркеры конфликтов. Вам нужно выбрать, какие изменения оставить: ваши, чужие, или объединить оба. Удалите маркеры <<<<<<<, ======= и >>>>>>>, оставив только нужный код. Сохраните файл.
После разрешения всех конфликтов в файле выполните git add <файл> для каждого разрешённого файла. Когда все конфликты разрешены, выполните git commit — Git создаст merge-коммит автоматически с сообщением по умолчанию.
Для сложных конфликтов используйте инструменты визуального слияния: git mergetool запустит настроенный инструмент (VS Code, Meld, KDiff3). В VS Code можно разрешать конфликты прямо в редакторе — кнопки Accept Current, Accept Incoming, Accept Both.
Если вы хотите отменить слияние и вернуться к состоянию до него, выполните git merge --abort. Это полезно, когда конфликтов слишком много и проще начать заново.
Совет: чтобы уменьшить количество конфликтов, чаще делайте rebase или merge из основной ветки в свою feature-ветку, а также делайте небольшие коммиты, затрагивающие меньше файлов.
В чём разница между git merge и git rebase?
Merge и rebase — два способа объединить изменения из одной ветки в другую, но они работают по-разному.
git merge создаёт новый merge-коммит, который объединяет истории обеих веток. История сохраняется полностью — видно, какая ветка когда отделялась и объединялась. Это безопасно для общих веток, потому что не переписывает историю. Недостаток — история может стать запутанной с множеством merge-коммитов, особенно в больших командах. Используйте merge, когда объединяете feature-ветку в main или когда работаете с публичными репозиториями.
git rebase переносит ваши коммиты поверх целевой ветки, создавая линейную историю. Git берёт ваши коммиты, отменяет их, обновляет ветку до целевого состояния, и применяет ваши коммиты заново. История выглядит чистой и прямой, без merge-коммитов. Недостаток — rebase переписывает историю, что опасно для общих веток: если кто-то уже сделал pull вашей ветки, после rebase их история не совпадёт с вашей. Используйте rebase для локальных feature-веток перед merge в main.
Главное правило: никогда не делайте rebase публичных веток (main, develop, или веток, на которые ссылаются другие разработчики). Если ветка уже отправлена в remote и кто-то может на ней работать — используйте merge.
Многие команды используют гибридный подход: rebase feature-ветки перед merge, затем merge в main с --no-ff (no fast-forward) для сохранения точки слияния. Это даёт чистую историю коммитов и при этом видно, какие коммиты принадлежали feature-ветке.
Как работать с ветками в Git?
Ветки — основа работы в Git. Они позволяют вести параллельную разработку без вмешательства в основную линию.
Создание ветки: git branch <name> создаёт новую ветку, но не переключается на неё. Команда git checkout -b <name> (или git switch -c <name> в новых версиях Git) создаёт ветку и сразу переключается на неё. Для создания ветки от определённого коммита: git checkout -b <name> <commit-hash>.
Просмотр веток: git branch показывает локальные ветки, git branch -a показывает все, включая remote. Текущая ветка помечена звёздочкой. Команда git branch -v показывает последний коммит каждой ветки.
Переключение: git checkout <name> или git switch <name>. Для переключения на ветку remote: git checkout -b <name> origin/<name>.
Удаление ветки: git branch -d <name> удаляет ветку, но только если она объединена с текущей. Команда git branch -D <name> удаляет принудительно, даже если есть несоединённые изменения. Для удаления remote-ветки: git push origin --delete <name>.
Слияние: переключитесь на целевую ветку (git checkout main), затем git merge <feature-branch>. Если нет конфликтов, Git создаст merge-коммит или сделает fast-forward.
Переименование: git branch -m <new-name> переименовывает текущую ветку. Для remote: переименуйте локально, затем git push origin -u <new-name> и удалите старую remote-ветку.
Хорошая практика: называйте ветки по схеме feature/краткое-описание, bugfix/issue-номер, hotfix/описание. Это помогает команде понимать назначение ветки.
Что такое git stash и как им пользоваться?
git stash сохраняет незакоммиченные изменения во временное хранилище, возвращая рабочую директорию к состоянию последнего коммита. Это полезно, когда нужно переключиться на другую ветку, но текущие изменения ещё не готовы для коммита.
Сохранение: git stash сохраняет изменения в индексе и рабочей директории. По умолчанию stash не включает untracked файлы — используйте git stash -u для их включения. Можно добавить описание: git stash push -m "работа над авторизацией".
Просмотр: git stash list показывает все сохранённые stash-записи. Каждая запись имеет вид stash@{0}, stash@{1} и т.д. Команда git stash show stash@{0} показывает файлы в конкретной записи, а git stash show -p stash@{0} показывает diff.
Восстановление: git stash pop применяет последнюю запись и удаляет её из списка. Команда git stash apply stash@{1} применяет запись, но не удаляет её — полезно, если хотите применить изменения в нескольких ветках. Если при pop возникают конфликты, запись не удаляется, и вы можете разрешить конфликты вручную.
Удаление: git stash drop stash@{0} удаляет конкретную запись. Команда git stash clear удаляет все записи — используйте с осторожностью.
Практический сценарий: вы работаете над feature-веткой, появляется срочный баг в main. Выполняете git stash, переключаетесь на main, фиксите баг, коммитите, возвращаетесь на feature-ветку и делаете git stash pop — изменения восстановлены.
Ограничение: stash хранится локально и не синхронизируется с remote. Если нужно перенести изменения между машинами, лучше создайте временную ветку и закоммитьте туда.
Как настроить .gitignore и что туда добавлять?
Файл .gitignore указывает Git, какие файлы и директории игнорировать — не отслеживать и не включать в коммиты. Это критически важно для чистоты репозитория.
Что добавлять в .gitignore: файлы сборки и артефакты (node_modules/, dist/, build/, *.o, *.class), файлы зависимостей (vendor/, package-lock.json — зависит от стратегии команды), файлы IDE и редакторов (.idea/, .vscode/, *.swp), файлы операционной системы (.DS_Store, Thumbs.db), файлы с секретами (.env, config/local.json, *.pem, *.key), файлы логов (*.log, logs/) и временные файлы (*.tmp, *.bak).
Синтаксис: каждая строка — правило. Звёздочка заменяет любое количество символов: *.log игнорирует все log-файлы. Слэш в конце означает директорию: node_modules/ игнорирует папку целиком. Восклицательный знак отменяет игнорирование: !important.log включит файл, даже если *.log его игнорирует. Двойная звёздочка — рекурсивный путь: logs/**/*.tmp игнорирует .tmp-файлы на любой глубине внутри logs/.
Структура: .gitignore в корне репозитория применяется ко всему проекту. Файл .gitignore в поддиректории добавляет правила для этой директории. Глобальный .gitignore (git config --global core.excludesfile ~/.gitignore_global) применяется ко всем проектам пользователя — удобно для файлов OS и IDE.
Шаблоны: сайт gitignore.io (или github.com/github/gitignore) содержит готовые шаблоны для языков и фреймворков. Для Node.js проекта: node_modules/, dist/, .env, .env.local, npm-debug.log*. Для Python: __pycache__/, *.pyc, .venv/, .env.
Важно: .gitignore не удаляет файлы, уже отслеживаемые Git. Если файл уже в репозитории, добавление его в .gitignore не поможет — нужно сначала git rm --cached <файл>, затем закоммитить удаление, и только потом добавить в .gitignore.
Как писать хорошие сообщения коммитов в Git?
Хорошее сообщение коммита помогает команде понимать историю и упрощает отладку. Вот правила и практики.
Структура сообщения: первая строка — краткая сводка, не более 50 символов, в повелительном наклонении: "Добавить валидацию формы регистрации", а не "Добавил валидацию". После пустой строки — подробное описание: что изменилось и почему, не как (код сам показывает как). Описание — до 72 символов на строку.
Conventional Commits — популярный стандарт форматирования. Формат: type(scope): description. Типы: feat (новая функция), fix (исправление бага), docs (документация), style (форматирование), refactor (рефакторинг), test (тесты), chore (обслуживание), perf (производительность), ci (CI-конфигурация). Пример: feat(auth): добавить OAuth2 авторизацию через Google. Это позволяет автоматически генерировать changelog и управлять версиями.
Практические правила: один коммит — одно логическое изменение. Не смешивайте рефакторинг и новую функцию в одном коммите. Если изменение затрагивает несколько файлов, но все они часть одной задачи — это один коммит. Коммитьте часто — маленькие коммиты легче откатывать и проверять.
Чего избегать: сообщений типа "fix", "update", "wip", "разные правки". Сообщение "исправить баг" не говорит ничего — какой баг, где, как. Лучше: "fix(cart): исправить расчёт суммы при пустом промокоде". Не пишите сообщения в прошлом времени — "добавил" вместо "добавить".
Многострочные сообщения: используйте git commit без -m — откроется редактор. Или несколько флагов -m: git commit -m "краткое описание" -m "подробное описание во втором абзаце".
Проверка перед коммитом: git diff --cached показывает staged-изменения. Просмотрите их перед коммитом, чтобы случайно не включить отладочный код или временные файлы.
Как работать с remote-репозиториями (origin, fork) в Git?
Remote-репозиторий — это версия вашего проекта, хранящаяся на сервере (GitHub, GitLab, Bitbucket). Управление remote-репозиториями — ключевая навык для командной работы.
Просмотр remote: git remote -v показывает все настроенные remote с URL. По умолчанию первый remote называется origin. Команда git remote show origin показывает подробную информацию: ветки, tracking-настройки.
Добавление remote: git remote add <name> <url>. Например, если вы сделали fork проекта и хотите получать обновления из оригинала: git remote add upstream https://github.com/author/project.git. Теперь можно делать git fetch upstream, чтобы получать изменения из оригинального репозитория.
Удаление и переименование: git remote remove <name> удаляет remote. Команда git remote rename <old> <new> переименовывает.
Отправка изменений: git push origin <branch> отправляет локальную ветку в remote. Флаг -u (git push -u origin <branch>) устанавливает tracking — после этого git push и git pull будут работать без указания remote и ветки. Команда git push --force принудительно перезаписывает remote-ветку — используйте только для личных веток после rebase. Безопасная альтернатива: git push --force-with-lease — перезапишет только если никто другой не добавил коммиты.
Получение изменений: git fetch origin скачивает изменения, но не применяет их — вы можете просмотреть их через git log origin/main. Команда git pull = git fetch + git merge — скачивает и сразу объединяет. Для линейной истории: git pull --rebase.
Работа с fork: типичный цикл — fork на GitHub, git clone вашего fork, git remote add upstream оригинал, создание feature-ветки, коммиты, git push origin feature-branch, затем pull request через веб-интерфейс. Для синхронизации с оригиналом: git fetch upstream, git checkout main, git merge upstream/main, git push origin main.
Как безопасно откатывать изменения в командной работе?
Откат изменений в командной работе требует осторожности — неправильный откат может затереть чужую работу или создать конфликты у всей команды. Вот безопасные подходы.
git revert — самый безопасный способ. Создаёт новый коммит, который отменяет изменения указанного коммита. История не переписывается, поэтому безопасен для общих веток. Команда git revert <commit-hash> откатывает один коммит. Можно откатить диапазон: git revert HEAD~3..HEAD создаст три revert-коммита. Если revert вызывает конфликты (код вокруг изменился), разрешите их как при обычном merge.
git reset — переписывает историю, используйте только для локальных веток или веток, которые никто не использует. git reset --hard <commit> перемещает указатель ветки назад и удаляет все изменения после этого коммита. Никогда не делайте reset --hard на ветках, отправленных в remote, если другие разработчики на них работают — это создаст расхождения.
git revert vs git reset: revert добавляет новый коммит отмены — безопасно для всех. reset удаляет коммиты из истории — безопасно только локально. Правило: если ветка в remote и кто-то мог сделать pull — используйте revert. Если ветка локальная или вы единственный разработчик — reset допустим.
Откат push: если вы уже сделали push нежелательных коммитов, используйте git revert, затем git push. Если нужно удалить коммиты из remote (например, случайно запушили секреты), сделайте git reset --hard <последний-хороший-коммит>, затем git push --force-with-lease. Предупредите команду — им придётся git fetch и git reset --hard origin/<branch>, потому что их локальная история больше не совпадает.
Восстановление случайно удалённых коммитов: git reflog показывает историю перемещений HEAD. Найдите хэш нужного коммита и выполните git reset --hard <hash> — коммит восстановится. Reflog хранит историю 90 дней по умолчанию.
Золотое правило: при сомнении — используйте revert, а не reset. Revert всегда безопасен, потому что он добавляет, а не удаляет.
Как установить и настроить Git с нуля?
Установка и настройка Git — первый шаг к контролю версий. Процесс зависит от операционной системы.
Установка: на Windows скачайте Git for Windows с git-scm.com — установщик включает Git Bash и Git GUI. На macOS используйте Homebrew: brew install git, или установите Xcode Command Line Tools: xcode-select --install. На Linux (Ubuntu/Debian): sudo apt install git, на Fedora: sudo dnf install git.
Проверка: git --version покажет установленную версию. Если команда не найдена, перезапустите терминал или проверьте PATH.
Базовая настройка: укажите имя и email — они будут в каждом коммите. git config --global user.name "Ваше Имя" и git config --global user.email "you@example.com". Используйте реальный email, привязанный к GitHub/GitLab, чтобы коммиты связывались с вашим аккаунтом. Проверить настройки: git config --list.
Настройка SSH-ключей: вместо пароля при каждом push используйте SSH. Создайте ключ: ssh-keygen -t ed25519 -C "you@example.com". Нажмите Enter для пути по умолчанию. Добавьте ключ в ssh-agent: eval "$(ssh-agent -s)" и ssh-add ~/.ssh/id_ed25519. Скопируйте публичный ключ: cat ~/.ssh/id_ed25519.pub и добавьте его в GitHub: Settings → SSH and GPG keys → New SSH key. Проверьте: ssh -T git@github.com должно показать "Hi username!".
Полезные глобальные настройки: git config --global init.defaultBranch main — устанавливает main как ветку по умолчанию вместо master. git config --global core.editor "code --wait" — использует VS Code как редактор для коммитов. git config --global pull.rebase true — делает pull с rebase вместо merge для линейной истории.
Первый репозиторий: создайте папку, выполните git init, создайте файл, git add ., git commit -m "Initial commit". Если используете GitHub: git remote add origin git@github.com:username/repo.git, git push -u origin main.
Чем отличается git pull от git fetch?
git fetch и git pull оба получают изменения из remote-репозитория, но работают по-разному. Понимание разницы помогает избегать неожиданных конфликтов.
git fetch скачивает изменения из remote, но не применяет их к вашей рабочей директории. Обновляются только remote-tracking-ветки (origin/main, origin/feature и т.д.). Ваша локальная ветка и рабочие файлы остаются нетронутыми. После fetch вы можете просмотреть изменения: git log origin/main, git diff main origin/main. Это безопасно — вы ничего не ломаете, просто получаете информацию о новых коммитах.
git pull = git fetch + git merge. Сначала скачивает изменения, затем сразу объединяет их с вашей текущей веткой. Если в remote есть новые коммиты, а у вас — локальные изменения, pull создаст merge-коммит или, при расхождениях, конфликты. Это может быть неожиданным, если вы не проверили, что именно изменилось в remote.
Когда использовать fetch: всегда, когда хотите посмотреть, что нового в remote, перед применением. Особенно полезно перед началом работы — выполните git fetch утром, проверьте git log origin/main, и если всё в порядке, сделайте git merge или git rebase. Также fetch нужен для просмотра чужих feature-веток без переключения на них.
Когда использовать pull: когда вы уверены, что в remote нет неожиданных изменений и хотите быстро обновиться. Например, вы единственный разработчик на ветке или изменения тривиальны.
Pull с rebase: git pull --rebase вместо merge применяет ваши локальные коммиты поверх скачанных. Это даёт линейную историю без merge-коммитов. Можно настроить по умолчанию: git config --global pull.rebase true. Многие команды предпочитают этот вариант.
Практический совет: используйте git fetch по привычке, а git pull — только когда точно знаете, что получите. Это избавит от неожиданных конфликтов в неподходящий момент.
Как посмотреть историю коммитов и найти нужный?
История коммитов — главный инструмент для понимания, что и когда изменилось в проекте. Git предлагает несколько способов просмотра и поиска.
Базовый просмотр: git log показывает историю в обратном хронологическом порядке — последние коммиты сверху. Каждый коммит содержит хэш, автора, дату и сообщение. Команда git log --oneline показывает компактный формат — один коммит на строку, только хэш и сообщение. Полезно для обзора большой истории.
Фильтрация: git log -5 показывает последние 5 коммитов. git log --author="Иван" фильтрует по автору. git log --since="2 weeks ago" — коммиты за последние 2 недели. git log --since="2026-01-01" --until="2026-06-01" — диапазон дат. git log -- <файл> показывает только коммиты, затрагивающие конкретный файл.
Поиск по сообщению: git log --grep="авторизация" ищет коммиты с ключевым словом в сообщении. Флаг -i делает поиск нечувствительным к регистру: git log --grep="auth" -i. Можно искать по регулярным выражениям: git log --grep="fix\(.*\)".
Визуализация: git log --graph --oneline --all показывает дерево веток в виде графа — удобно для понимания структуры слияний. Флаг --all включает все ветки, не только текущую. Альтернатива: git log --graph --oneline --decorate — добавляет метки веток и тегов.
Изменения в коммите: git show <hash> показывает полный diff конкретного коммита. git show HEAD — последний коммит. git show HEAD~2 — третий с конца. Команда git diff <hash1>..<hash2> показывает разницу между двумя коммитами.
Поиск бага с bisect: git bisect start, git bisect bad (текущий коммит сломан), git bisect good <hash> (этот коммит работал). Git автоматически переключает коммиты в середине диапазона. Вы тестируете и отмечаете git bisect good или git bisect bad. Git находит коммит, который внёс баг. Завершение: git bisect reset.
Blame: git blame <файл> показывает, кто и в каком коммите написал каждую строку файла. Полезно для понимания контекста изменений. git blame -L 10,20 <файл> — только строки 10-20.
Что такое git cherry-pick и когда его использовать?
git cherry-pick берёт изменения из конкретного коммита и применяет их к текущей ветке, создавая новый коммит с теми же изменениями. Это полезно, когда нужно перенести отдельное изменение, не объединяя всю ветку.
Когда использовать: вы сделали fix в feature-ветке, но он нужен и в main — cherry-pick переносит только этот коммит. Или вы случайно закоммитили в wrong branch — cherry-pick переносит коммит в правильную ветку. Также для переноса hotfix между релизными ветками: cherry-pick fix из main в release-1.2.
Как использовать: переключитесь на целевую ветку (git checkout main), затем git cherry-pick <commit-hash>. Git применит изменения из указанного коммита и создаст новый коммит в текущей ветке. Хэш нового коммита будет другим, но изменения и сообщение — те же. Можно перенести несколько коммитов: git cherry-pick <hash1> <hash2> <hash3> или диапазон: git cherry-pick <hash1>..<hash2>.
Конфликты при cherry-pick: если код вокруг изменений отличается между ветками, возникнет конфликт. Разрешите его как при обычном merge: отредактируйте файлы, git add, git cherry-pick --continue. Для отмены: git cherry-pick --abort.
Отличие от merge: merge объединяет всю ветку со всеми коммитами. cherry-pick переносит только указанные коммиты. Отличие от rebase: rebase переносит все коммиты ветки поверх другой. cherry-pick — точечный перенос.
Осторожность: cherry-pick создаёт дубликат коммита с новым хэшем. Если позже сделать merge ветки, из которой был cherry-pick, возможны конфликты из-за одинаковых изменений. Старайтесь cherry-pick только когда merge или rebase невозможны или нецелесообразны.
Практический сценарий: вы исправили критический баг в develop, но его нужно срочно доставить в main. git checkout main, git cherry-pick <fix-commit-hash>, git push. Fix в main без merge всей develop-ветки.
Как удалить ветку локально и в remote-репозитории?
Удаление веток — рутинная задача после завершения работы. Ветки нужно удалять и локально, и в remote, чтобы репозиторий не засорялся.
Локальное удаление: git branch -d <name> удаляет ветку, но только если она объединена с текущей (merged). Git проверит, что все коммиты ветки есть в текущей — если нет, выдаст ошибку. Это защита от потери работы. Команда git branch -D <name> удаляет принудительно, даже если есть несоединённые коммиты. Используйте -D только если уверены, что изменения не нужны — после удаления восстановить ветку можно только через reflog.
Удаление в remote: git push origin --delete <name> удаляет ветку на сервере (GitHub, GitLab). Сокращённая форма: git push origin :<name>. После удаления remote-ветки другие разработчики увидят её как "gone" при следующем git fetch.
Удаление tracking-веток: после удаления remote-ветки локальная tracking-копия (origin/<name>) остаётся. Очистите её: git fetch --prune или git remote prune origin. Это удаляет локальные ссылки на несуществующие remote-ветки.
Массовая очистка: git branch --merged main показывает ветки, объединённые с main — их можно безопасно удалять. Команда для удаления всех merged-веток кроме main: git branch --merged main | grep -v "main" | xargs git branch -d. Будьте осторожны — убедитесь, что не удалите ветку, на которой работаете.
Восстановление удалённой ветки: git reflog покажет хэш последнего коммита удалённой ветки. Выполните git branch <name> <hash> — ветка восстановится. Reflog хранит историю 90 дней, так что восстановление возможно в течение этого срока.
Хорошая практика: удаляйте feature-ветки сразу после merge в main. Это keeps веток чистым и облегчает навигацию. На GitHub можно настроить автоматическое удаление веток после merge: Settings → Options → Automatically delete head branches.
Как отменить git add (убрать файлы из индекса)?
Если вы случайно добавили файлы в индекс через git add и хотите убрать их до коммита, есть несколько способов.
git restore --staged <файл> — современный способ (Git 2.23+). Убирает файл из индекса, но сохраняет изменения в рабочей директории. Например: git restore --staged index.html уберёт файл из staged, но изменения в файле останутся. Команда git restore --staged . убирает из индекса все файлы.
git reset HEAD <файл> — классический способ, работает во всех версиях Git. Делает то же самое: убирает файл из индекса, изменения остаются. git reset HEAD . убирает все файлы. Команда git reset (без аргументов) убирает из индекса всё.
git reset --soft HEAD~1 — если вы уже закоммитили, но хотите вернуть изменения в индекс (uncommit). Коммит отменяется, изменения остаются staged. git reset --mixed HEAD~1 (или просто git reset HEAD~
Полный сброс: git reset --hard HEAD удаляет все изменения в рабочей директории и индексе — возвращает к состоянию последнего коммита. Используйте только если уверены, что изменения не нужны. Это необратимо (кроме через reflog).
Разница между состояниями: Modified — файл изменён, но не добавлен в индекс. Staged — файл добавлен через git add, готов к коммиту. Committed — изменения сохранены в коммите. git add переводит Modified → Staged. git restore --staged переводит Staged → Modified. git commit переводит Staged → Committed.
Практический сценарий: вы сделали git add . и поняли, что добавили .env случайно. Выполните git restore --staged .env — файл уберётся из индекса. Добавьте .env в .gitignore, чтобы избежать этого в будущем. Если уже закоммитили .env: git rm --cached .env, закоммитьте удаление, добавьте в .gitignore.
Что делать, если сделал push в неправильную ветку?
Push в неправильную ветку — частая ошибка. Действия зависят от того, заметили ли вы сразу и есть ли уже pull от других разработчиков.
Ситуация
Ситуация
Ситуация
Ситуация
Профилактика: настройте branch protection на GitHub — запретите прямой push в main. Используйте pull requests для всех изменений. Перед push проверяйте текущую ветку: git branch — звёздочка показывает текущую.
Как объединить несколько коммитов в один (squash)?
Squash — объединение нескольких коммитов в один. Это полезно, когда вы сделали много мелких коммитов в feature-ветке ("wip", "fix typo", "fix again") и хотите представить их как один чистый коммит в истории.
Через interactive rebase: git rebase -i HEAD~4, где 4 — количество коммитов для объединения. Откроется редактор со списком коммитов. Каждый коммит помечен словом pick. Измените pick на squash (или просто s) для коммитов, которые хотите объединить с предыдущим. Первый коммит оставьте pick — он будет базовым. Сохраните и закройте редактор. Откроется второй редактор для редактирования сообщения объединённого коммита — впишите чистое описание всех изменений.
Пример: у вас 4 коммита — "feat: add form", "fix: validation", "typo", "style: button". Выполните git rebase -i HEAD~
После сохранения впишите новое сообщение: "feat: add registration form with validation and styling". Получится один коммит вместо четырёх.
Через git merge --squash: при слиянии feature-ветки в main выполните git merge --squash feature-branch. Git объединит все изменения, но не создаст merge-коммит. Затем git commit -m "feat: add registration form" — один коммит со всеми изменениями. Это проще, но теряется история feature-ветки.
Через git reset --soft: для объединения последних N коммитов выполните git reset --soft HEAD~N. Все изменения из N коммитов соберутся в индексе. Затем git commit -m "новое сообщение" — один коммит вместо N. Быстро, но не даёт выбрать, какие коммиты объединять.
После squash на уже запушенной ветке: git push --force-with-lease. История переписана, поэтому нужен force-push. Предупредите команду, если ветка общая.
Хорошая практика: делайте squash перед merge feature-ветки в main. Это даёт чистую историю main, где каждый коммит — одна завершённая функция, а не серия "wip" и "fix typo".
Как восстановить удалённый коммит или ветку в Git?
В Git почти ничего не удаляется безвозвратно. Даже после reset --hard или удаления ветки, коммиты остаются в reflog и могут быть восстановлены в течение 90 дней.
git reflog — главный инструмент восстановления. Reflog записывает каждое перемещение HEAD: коммиты, checkout, reset, pull. Выполните git reflog — увидите историю с хэшами коммитов и описаниями действий. Найдите хэш коммита до удаления. Запись вида "a1b2c3d HEAD@{2}: reset: moving to HEAD~1" означает, что на HEAD@{3} был коммит, который вы сбросили.
Восстановление коммита: git reset --hard <hash> перемещает текущую ветку к указанному коммиту. Если не хотите переписывать текущую ветку, создайте новую: git branch recovered <hash>, затем git checkout recovered.
Восстановление удалённой ветки: git reflog покажет последний коммит ветки. Найдите запись вида "abc1234 branchname@{1}: commit: last commit". Выполните git branch branchname <hash> — ветка восстановится с этим коммитом как HEAD.
Восстановление после git reset --hard: если вы случайно сделали reset --hard и потеряли изменения, git reflog покажет предыдущее состояние HEAD. Найдите хэш до reset и выполните git reset --hard <hash>. Изменения вернутся.
Восстановление после force-push: если кто-то сделал force-push и затёр ваши коммиты в remote, коммиты всё ещё есть в вашем локальном reflog. Найдите хэш через git reflog, создайте ветку git branch backup <hash>, затем git push origin backup. Коммиты снова в remote.
Восстановление uncommitted изменений: если вы сделали git stash drop и потеряли stash, git fsck --no-reflog --unreachable покажет dangling-объекты. Найдите commit-объект с изменениями вашего stash: git fsck --no-reflog --unreachable | grep commit. Затем git stash apply <hash> восстановит stash.
Сроки: reflog хранит записи 90 дней для reachable-коммитов и 30 дней для unreachable. После этого Git удаляет объекты через git gc. Если прошло больше 90 дней — восстановление невозможно. Поэтому проверяйте reflog сразу после случайного удаления.
Профилактика: перед reset --hard или force-push создавайте backup-ветку: git branch backup-$(date +%s). Это занимает секунду и спасает от потери работы.
Как настроить git hooks для автоматизации?
Git hooks — скрипты, которые запускаются автоматически при определённых событиях Git. Они помогают автоматизировать проверки кода, форматирование и тесты.
Где хранятся hooks: в каждом репозитории есть папка .git/hooks с примерами hooks с расширением .sample. Чтобы активировать hook, удалите расширение .sample и сделайте файл исполняемым: chmod +x .git/hooks/pre-commit. Hooks не версонируются — они локальные для каждого разработчика. Для общих hooks используйте Husky или similar инструменты.
Client-side hooks: pre-commit запускается перед коммитом. Если возвращает ненулевой код — коммит отменяется. Используйте для линтинга, форматирования, проверки секретов. prepare-commit-msg — перед открытием редактора сообщения, можно генерировать шаблон. pre-push — перед push, можно запускать тесты.
Server-side hooks: pre-receive, update, post-receive — на сервере (GitHub, GitLab). Используются для проверки прав, отклонения push при нарушении правил. На GitHub настраиваются через GitHub Actions или Branch Protection Rules.
Husky — популярный инструмент для общих client-side hooks. Установка: npm install --save-dev husky, npx husky init. Это создаёт папку .husky/ с hooks, которые версонируются в репозитории. Все разработчики получают одинаковые hooks автоматически. Пример .husky/pre-commit: npx lint-staged — запускает линтер только для staged-файлов.
lint-staged — companion для Husky. Конфигурация в package.json: "lint-staged": {"*.js": "eslint --fix", "*.ts": "tsc --noEmit", "*.css": "prettier --write"}. Запускает инструменты только для файлов в индексе, а не для всего проекта — быстро.
pre-commit framework — альтернатива Husky, мультиязычная. Установка: pip install pre-commit, pre-commit install. Конфигурация в .pre-commit-config.yaml. Поддерживает hooks на Python, Node, Go, Rust. Имеет встроенные hooks: trailing-whitespace, end-of-file-fixer, check-yaml.
Практический pre-commit hook: проверка отсутствия console.log, проверка форматирования, проверка секретов (git-secrets, trufflehog), запуск unit-тестов для затронутых файлов. Если hook падает — коммит отменяется, разработчик видит ошибки и исправляет.
Совет: не делайте hooks слишком медленными. pre-commit должен выполняться за секунды, иначе разработчики будут его обходить (--no-verify). Тяжёлые проверки (интеграционные тесты, полный анализ) — в CI/CD, не в hooks.