- Уровень 1. Глава 1: Архитектура Git и Настройка
- Уровень 1. Глава 2: Базовый цикл работы (The Daily Routine)
- Уровень 1. Глава 3: Файл .gitignore
- Уровень 2. Глава 1: Магия ветвления (Branching)
- Уровень 2. Глава 2: Удаленные репозитории (Remotes)
- Уровень 2. Глава 3: Разрешение конфликтов (Conflict Resolution)
- Уровень 3. Глава 1: Отмена изменений (Undo)
- Уровень 3. Глава 2: Временный карман (Stashing)
- Уровень 3. Глава 3: Навигация и Поиск (Sherlock Mode)
- Уровень 4. Глава 1: Git Rebase (Переписываем историю)
- Уровень 4. Глава 2: Интерактивный Rebase (Playbook)
- Уровень 5. Глава 1: Git под капотом (Internals)
- Уровень 6. Глава 1: Масштабирование (Big Data & Monorepos)
- Уровень 6. Глава 2: Безопасность и Чистка (Security)
- Уровень 6. Глава 3: Методологии разработки (Workflows)
- Уровень 7. Глава 1: Продуктивность и Алиасы
- Уровень 7. Глава 2: Git Hooks (Автоматизация)
- Уровень 7. Глава 3: .gitattributes (Тонкая настройка)
- 🎉 Финал: Вы — Git Архитектор
Большинство старых систем (например, SVN или Perforce) — Централизованные (CVCS). Это значит, что история хранится на одном сервере. Если сервер упал — работа встала. Если нет интернета — вы не можете сохранить версию.
Git — это Распределенная система (DVCS).
Когда вы делаете git clone, вы не просто качаете последние файлы. Вы скачиваете весь репозиторий целиком, каждый файл, каждый коммит за всю историю проекта.
- Плюс: Ваш локальный компьютер — это полноценный бэкап.
- Плюс: 99% операций (коммит, просмотр истории, переключение веток) происходят локально и мгновенно, интернет не нужен.
Это то, на чем спотыкаются новички. В отличие от простого "Сохранить файл", в Git есть три зоны, через которые проходят данные.
- Working Directory (Рабочая директория): Это ваша папка с файлами, которую вы видите в редакторе кода. Здесь вы пишете код. Это "песочница".
- Staging Area (Index / Область подготовленных файлов): Это промежуточная зона. Сюда вы добавляете только те изменения, которые хотите включить в следующий снимок. Представьте это как "коробку", которую вы наполняете перед отправкой.
- Repository (.git directory): Место, где Git хранит базу данных всех версий (коммитов). Когда вы делаете коммит, содержимое "коробки" (Staging Area) навсегда сохраняется здесь.
Файл в вашей папке может находиться в одном из 4 состояний:
- Untracked (Неотслеживаемый): Новый файл, про который Git еще не знает. Он не попадет в коммит, пока вы явно не скажете Git следить за ним.
- Unmodified (Неизмененный): Файл, который уже есть в базе Git, и вы его не трогали с момента последнего коммита.
- Modified (Измененный): Файл есть в базе, вы его отредактировали, но еще не подготовили к коммиту (не положили в Staging Area).
- Staged (Подготовленный): Измененный файл, который вы отметили как готовый к коммиту.
Перед началом работы нужно представиться. Git "впечатывает" эту информацию в каждый создаваемый вами коммит. Изменить её задним числом сложно.
Откройте терминал.
# Устанавливаем имя автора (будет видно в истории)
# --global означает, что настройка применится для всех проектов текущего пользователя ОС
git config --global user.name "Ivan Ivanov"
# Устанавливаем email
# Важно: если вы работаете с GitHub/GitLab, этот email должен совпадать с тем,
# на который зарегистрирован аккаунт, чтобы коммиты привязались к профилю.
git config --global user.email "ivan@example.com"
Когда вы делаете коммит без сообщения, Git открывает текстовый редактор. По умолчанию это часто Vim, из которого новичку трудно выйти. Лучше настроить что-то привычное (VS Code, Nano, Notepad++).
# Назначить VS Code основным редактором для Git
# --wait говорит терминалу ждать, пока вы не закроете окно редактора
git config --global core.editor "code --wait"
Это критически важно, если в команде работают люди на разных ОС (Windows и macOS/Linux).
- В Windows конец строки — это
CRLF(Carriage Return + Line Feed). - В macOS/Linux — это
LF(Line Feed).
Если не настроить, Git будет видеть изменения в каждой строке файла просто потому, что кто-то открыл его на другой ОС.
Для Windows:
# При коммите конвертировать CRLF в LF, при выгрузке (checkout) — обратно в CRLF.
git config --global core.autocrlf true
Для macOS / Linux:
# При коммите конвертировать CRLF в LF (на всякий случай), при выгрузке ничего не трогать (оставлять LF).
git config --global core.autocrlf input
Раньше главной веткой по умолчанию была master. Сейчас индустрия переходит на main. Чтобы при создании новых проектов сразу создавалась main:
git config --global init.defaultBranch main
Чтобы увидеть все, что мы сейчас настроили:
git config --list
Итог главы: Мы разобрались, что Git хранит все локально, поняли концепцию "Рабочая папка -> Индекс -> Репозиторий" и настроили Git, чтобы он знал, кто мы и как обрабатывать файлы.
В этой главе мы пройдем полный цикл: создание репозитория, написание кода, подготовка изменений и их фиксация. Это те команды, которые вы будете вводить 90% времени.
Представим, что мы начинаем новый проект.
У вас есть два пути начать работу с Git:
А. Начать с нуля (git init) Если у вас есть локальная папка с кодом (или пустая папка), и вы хотите добавить в нее Git.
# Создаем папку проекта
mkdir my-project
cd my-project
# Инициализируем Git
git init
Что произошло: Внутри папки появилась скрытая директория .git. Именно в ней живет база данных Git. Пока вы не удалите эту папку, история вашего проекта в безопасности.
Б. Скачать существующий (git clone) Если проект уже есть на сервере (GitHub/GitLab/Bitbucket), вы его клонируете.
# Скачивает проект целиком, создавая папку с именем репозитория
git clone https://github.com/user/repo.git
Это ваша главная команда. В любой непонятной ситуации вводите git status. Она показывает, что сейчас происходит в вашей Рабочей директории и Индексе.
Давайте создадим файл:
echo "Hello World" > file.txt
Теперь спросим у Git, что он видит:
git status
Вывод: Git скажет Untracked files: file.txt.
Это состояние Untracked. Git видит файл, но не следит за ним. Если вы удалите файл сейчас, Git не сможет его восстановить.
Чтобы изменение попало в историю, его нужно сначала перенести в Staging Area (Индекс). Это как собрать товары в коробку перед тем, как заклеить её скотчем.
# Добавить конкретный файл в индекс
git add file.txt
# ИЛИ добавить ВСЕ измененные и новые файлы в текущей папке (чаще всего используют это)
git add .
Снова проверяем:
git status
Вывод: Changes to be committed. Файл теперь горит зеленым (в большинстве терминалов). Это состояние Staged. Файл в "коробке", готов к отправке.
Это момент истины. Мы делаем "снэпшот" (слепок) состояния всех файлов, находящихся в Staging Area, и сохраняем его в историю навсегда.
# -m означает "message" (сообщение)
git commit -m "Initial commit"
Что произошло:
- Git взял всё из Staging Area.
- Упаковал это в объекты.
- Присвоил коммиту уникальный ID (хеш, например
a1b2c3d). - Сдвинул указатель текущей ветки (
main) на этот новый коммит.
Хороший коммит-месседж спасает жизни.
- Плохо: "fix", "update", "changed code".
- Хорошо: "Add login functionality", "Fix NPE in UserAuth service".
Стандарт индустрии — использовать повелительное наклонение (Imperative mood), как будто вы приказываете коду:
- (Make it) Add feature X
- (Make it) Fix bug Y
- (Make it) Remove file Z
Давайте изменим наш файл.
echo "New line" >> file.txt
- Проверяем:
git status. Вывод:modified: file.txt. Файл отслеживается, но изменен. - Смотрим разницу:
git diff. Покажет конкретные добавленные строки. - Индексируем:
git add .Переносим изменения в Staging Area. - Коммитим:
git commit -m "Update file.txt content"
Лайфхак:
Если файлы уже отслеживаются (не новые), можно пропустить git add и сделать сразу коммит:
# Флаг -a (all) автоматически делает add для всех modified файлов перед коммитом
git commit -a -m "Update content shortcut"
Но будьте осторожны: это не добавит новые (untracked) файлы.
Как посмотреть, что мы натворили?
# Показать полную историю
git log
Вы увидите:
- Хеш коммита (SHA-1).
- Автора.
- Дату.
- Сообщение.
Если история длинная, используйте компактный вид:
# --oneline: один коммит — одна строка (короткий хеш + сообщение)
git log --oneline
Пример вывода:
a1b2c3d Update content shortcut
f4e5d6c Initial commit
Ваш повседневный цикл выглядит так:
- Поработали с кодом.
git status— посмотрели, что изменилось.git add .— подготовили изменения (собрали коробку).git commit -m "Описание"— сохранили версию (заклеили коробку и отправили на склад).
Это последняя глава первого уровня. После нее вы будете полностью готовы к "одиночному плаванию".
В любом проекте есть мусор. Это файлы, которые генерируются автоматически или являются локальными настройками, и им не место в репозитории. Примеры:
- Папки скомпилированного кода (
bin,build,target,node_modules). - Временные файлы IDE (
.idea,.vscode). - Системные файлы (
.DS_Storeна Mac,Thumbs.dbна Windows). - Логи (
*.log). - Секреты и конфиги с паролями (
.env).
Если вы закоммитите эти файлы, вы "загрязните" историю и будете мешать коллегам (у них могут быть другие настройки IDE или ОС).
Для решения этой проблемы существует файл .gitignore.
Git проверяет этот файл каждый раз перед командой git status или git add. Если файл подпадает под правило в .gitignore, Git делает вид, что этого файла не существует. Он даже не покажет его в списке Untracked files.
Важно: .gitignore работает только для Untracked файлов. Если вы уже успели закоммитить файл, а потом добавили его в игнор — Git продолжит следить за ним. (Как это исправить — ниже).
Создайте в корне проекта файл с именем .gitignore (точка в начале обязательна). Это обычный текстовый файл.
Пример содержимого для Java-проекта:
# Игнорировать все файлы с расширением .log
*.log
# Игнорировать папку target со всем содержимым
target/
# Игнорировать файлы .class, где бы они ни находились
**/*.class
# Игнорировать конкретный файл
secret_config.properties
# Исключение из правил (знак восклицания)
# Игнорировать все .md файлы, КРОМЕ README.md
*.md
!README.md
Разбор символов:
*— любые символы./в конце — означает директорию.!— "не игнорировать" (инверсия).#— комментарий.
Есть мусор, который создаете лично вы (ваша ОС или редактор), а не проект. Например, macOS создает .DS_Store в каждой папке. Заставлять каждого коллегу добавлять это в .gitignore проекта — дурной тон.
Лучше настроить глобальный список игнорирования на вашем компьютере:
- Создайте файл
~/.gitignore_globalв домашней папке. - Добавьте туда системный мусор (
.DS_Store,Thumbs.db). - Сообщите Git об этом файле:
git config --global core.excludesfile ~/.gitignore_global
Теперь эти файлы будут игнорироваться во всех ваших проектах.
Ситуация: Вы случайно закоммитили папку build/ или файл конфига. Потом спохватились, добавили build/ в .gitignore, но при команде git status Git все равно показывает изменения в этой папке.
Причина: .gitignore игнорирует только новые файлы. Если файл уже в базе (tracked), Git обязан отслеживать изменения в нем.
Решение: Нужно удалить файл из индекса (перестать следить), но оставить его на диске (не удалять физически).
# Удалить из индекса (--cached), но оставить файл на месте
git rm --cached filename
# Если нужно перестать следить за целой папкой
git rm -r --cached foldername/
После этого сделайте коммит: git commit -m "Stop tracking build folder". Теперь правило из .gitignore начнет работать.
Не пишите .gitignore вручную с нуля. Есть сайт gitignore.io.
Вы просто вводите свои технологии (например: Java, IntelliJ IDEA, Maven, Windows), и он генерирует идеальный файл, учитывающий все нюансы.
Поздравляю! Мы закрыли блок "Фундамент".
✅ Вы понимаете архитектуру (Working Dir -> Index -> Repo).
✅ Вы настроили Git под себя.
✅ Вы умеете создавать коммиты.
✅ Вы умеете прятать мусор через .gitignore.
В старых системах (SVN) создание ветки означало физическое копирование всех файлов проекта в новую папку. Это было долго и занимало место. В Git ветка (branch) — это просто подвижный указатель (ссылка) на один конкретный коммит. Это файл весом в 41 байт. Поэтому создание веток происходит мгновенно, даже если в проекте миллион файлов.
Ветвление позволяет создавать "параллельные вселенные" для разработки новых функций, не ломая основной код (main).
Представьте историю коммитов как цепочку бусин.
- Коммит знает, кто его родитель.
- Ветка (напр.
main) — это стикер, приклеенный к последней бусине. - HEAD — это "лазерная указка", которая говорит: "Вы находитесь здесь". Обычно HEAD указывает на активную ветку.
Когда вы делаете новый коммит, ветка (стикер), на которую указывает HEAD, автоматически переклеивается на новую бусину.
Раньше для всего использовалась команда git checkout. Но она была перегружена (делала и переключение веток, и восстановление файлов). В современных версиях Git (2.23+) появились специализированные команды.
Старая школа:
# Создать ветку и переключиться
git checkout -b feature-login
Новая школа (рекомендуется):
# Создать ветку (branch)
git branch feature-login
# Переключиться на неё (switch)
git switch feature-login
# Создать И сразу переключиться (create)
git switch -c feature-login
Теперь все изменения, которые вы делаете и коммитите, будут наращивать историю ветки feature-login, а ветка main останется на месте.
Вы закончили работу в feature-login и хотите вернуть изменения в main.
Сначала нужно перейти в ту ветку, КУДА мы хотим влить изменения:
git switch main
git merge feature-login
Здесь возможны два сценария:
Если за то время, пока вы работали в feature-login, в ветке main не появилось новых коммитов, Git делает простую вещь: он берет указатель main и "перематывает" его вперед до уровня feature-login.
- Результат: Линейная история.
- Новый коммит: Не создается.
Если main тоже ушла вперед (например, коллега успел что-то запушить), история разветвилась. "Перемотать" нельзя.
Git вынужден:
- Взять верхушку
main. - Взять верхушку
feature-login. - Найти их общего предка.
- Создать новый коммит слияния (Merge Commit), у которого будет два родителя.
В этот момент может открыться редактор для ввода сообщения коммита (обычно: Merge branch 'feature-login'). Просто сохраните и закройте.
Ветка нужна только пока идет работа. После слияния указатель feature-login больше не нужен (история уже в main).
# Безопасное удаление (delete)
# Git не даст удалить ветку, если изменения из неё еще не слиты в текущую ветку.
git branch -d feature-login
# Принудительное удаление (Delete Force)
# Используется, если вы зашли в тупик, работа не нужна, и вы хотите просто выбросить ветку.
git branch -D feature-login
Попробуйте этот сценарий в своем тестовом репозитории:
git switch -c new-feature(создали ветку).- Создайте файл, сделайте коммит.
git switch main(вернулись назад).- Заметьте, что файл исчез! (Git восстановил состояние main).
git merge new-feature(влили изменения).git branch -d new-feature(удалили указатель).
Итог главы: Мы научились создавать параллельные потоки разработки. Пока все это происходило локально на вашем компьютере.
Пока что весь ваш код жил только на вашем жестком диске. Если диск сгорит — проект пропадет. Чтобы этого не случилось и чтобы работать в команде, нам нужен сервер (GitHub, GitLab, Bitbucket).
В терминологии Git сервер — это Remote.
Связь локального репозитория с сервером настраивается одной командой.
# git remote add <короткое_имя> <url>
git remote add origin https://github.com/myname/myproject.git
- Что такое
origin? Это не магическое слово, а стандартное имя по умолчанию для "основного сервера". Вы можете назвать егоbackup,gitlabилиprod, но все привыкли кorigin. - Проверка:
git remote -vпокажет список всех подключенных серверов.
Как Git будет авторизовываться на сервере?
- HTTPS: Просто, но при каждом пуше нужно вводить логин/токен (или настраивать Credential Helper).
- SSH: Выбор профессионалов. Вы генерируете пару ключей (
ssh-keygen), публичный кладете в настройки GitHub, приватный остается у вас. Git общается с сервером без паролей.
Вы сделали коммиты локально. На сервере их еще нет. Нужно "толкнуть" их туда.
Первый пуш (настройка связи):
# -u (или --set-upstream) связывает вашу локальную ветку main
# с веткой main на сервере origin.
git push -u origin main
После того как связь установлена (благодаря -u), в будущем достаточно писать просто:
git push
Git сам поймет, куда и какую ветку отправлять.
Это самый важный теоретический момент главы. Многие новички используют git pull вслепую, не понимая, что это составная команда.
В Git есть Tracking branches (следящие ветки). Это копии состояния сервера на вашем компьютере. Они называются origin/main, origin/feature. Вы не можете их редактировать, они обновляются только при связи с сервером.
Эта команда стучится на сервер, скачивает все новые коммиты, обновляет ваши следящие ветки (origin/main), но НЕ трогает ваши рабочие файлы.
Это безопасно. Вы можете посмотреть, что там написали коллеги, не ломая свою работу.
git fetch origin
# Теперь можно сравнить свою работу с тем, что пришло:
git diff main origin/main
Это комбинация двух команд: fetch + merge.
Она скачивает изменения И тут же пытается влить их в вашу текущую ветку.
git pull origin main
# Равносильно:
# 1. git fetch origin
# 2. git merge origin/main
Совет эксперта: Если вы долго не обновлялись, лучше сделать fetch, посмотреть изменения через git log origin/main, и только потом делать merge. Но для рутины pull вполне подходит.
Представьте, что вы и ваш коллега работаете над одной веткой.
- Коллега делает
push. - Вы пытаетесь сделать
push, но Git выдает ошибку: "Rejected (non-fast-forward)".
- Причина: На сервере есть коммиты, которых нет у вас. История на сервере ушла вперед. Вы не можете просто перезаписать её.
- Решение: Вы должны сначала забрать чужую работу и объединить её со своей.
git pull # Скачать и слить
git push # Теперь можно отправлять
Именно на шаге 3 (при git pull) чаще всего возникают конфликты. О них — в следующей главе.
Итог главы:
Мы научились связывать локальный репозиторий с облаком (remote), отправлять код (push) и, главное, поняли разницу между аккуратным скачиванием (fetch) и скачиванием со слиянием (pull).
Конфликт — это не ошибка. Это ситуация, когда Git говорит: «Я вижу два изменения в одной и той же строке кода, и я недостаточно умен, чтобы понять, какое из них правильное. Человек, решай ты».
Конфликты чаще всего возникают при git merge или git pull.
Представьте:
- Вася в ветке
mainизменил строку 10 наprint("Hello"). - Петя в ветке
featureизменил строку 10 наprint("Hi"). - При попытке слияния Git паникует.
Git останавливает процесс слияния и помечает проблемные файлы как Unmerged.
Внутри файла это выглядит так:
<<<<<<< HEAD
print("Hello")
=======
print("Hi")
>>>>>>> feature
<<<<<<< HEAD: Начало вашей версии (то, что было в текущей ветке).=======: Разделитель.>>>>>>> feature: Конец пришедшей версии (из ветки, которую вливаем).
Не паникуйте. Вы ничего не сломали.
- Найти врага:
git statusпокажет список файлов, где есть конфликты (они будут красными в секции Unmerged paths). - Отредактировать: Откройте файл в редакторе. Вам нужно удалить маркеры (
<<<,===,>>>) и оставить только правильный код.
- Вариант А (Ours): Оставить
print("Hello"). - Вариант Б (Theirs): Оставить
print("Hi"). - Вариант В (Both): Объединить (
print("Hello"); print("Hi");).
- Сохранить: Сохраните файл в редакторе.
- Пометить как решенный: Это самое важное. Git не знает, что вы все исправили, пока вы не сделаете
git add.
git add file.py
- Завершить: Сделайте коммит. Обычно Git сам подставляет сообщение "Merge branch ...".
git commit
Совет: Современные IDE (IntelliJ IDEA, VS Code) показывают конфликты в виде удобного интерфейса с тремя окнами ("Ваше", "Результат", "Их"), где можно просто кликать стрелочки, не удаляя маркеры вручную.
Поздравляю! Вы прошли самый сложный психологический барьер. ✅ Вы умеете ветвиться. ✅ Вы работаете с сервером. ✅ Вы не боитесь слова "КОНФЛИКТ".
Переходим к Уровню 3: Инструментарий "Time Travel". Мы научимся исправлять ошибки: отменять изменения в файлах, откатывать коммиты и спасать удаленное.
Git прощает почти все. Главное — знать правильную команду. В этой главе мы разберем три степени "отката": restore, reset и revert.
Сценарий: Вы писали код, все сломали, файл еще не закоммичен. Хотите вернуть файл к состоянию "как было в последнем коммите".
Раньше для этого использовали git checkout, но теперь есть специальная команда.
# Отменить изменения в файле в Рабочей директории (вернуть как было)
# Внимание: изменения пропадут безвозвратно!
git restore file.txt
# Сценарий: Вы случайно сделали 'git add', но передумали коммитить.
# Файл нужно убрать из Staging (Unstage), но оставить изменения в файле.
git restore --staged file.txt
Сценарий: Вы сделали коммит (или 5 коммитов), поняли, что это бред, и хотите "отмотать время назад", будто этих коммитов не было.
Команда reset перемещает указатель вашей ветки назад по истории. У неё есть три режима жесткости.
Предположим, мы хотим отменить последний коммит (HEAD~1):
А. Soft Reset (Мягкий) Самый безопасный.
git reset --soft HEAD~1
- Коммит: Исчезает.
- Изменения: Остаются в Staging Area (зеленые в status).
- Зачем: Чтобы "пересобрать" коммит. Например, вы сделали коммит, забыли файл, сделали reset --soft, добавили файл и закоммитили снова (хотя для этого лучше
amend, но логика такая).
Б. Mixed Reset (Стандартный) Работает по умолчанию.
git reset --mixed HEAD~1 # или просто git reset HEAD~1
- Коммит: Исчезает.
- Изменения: Остаются в Working Directory (красные в status), но убраны из индекса.
- Зачем: Вы хотите продолжить работу над кодом, но разбить этот коммит на два разных.
В. Hard Reset (Жесткий) Опасно!
git reset --hard HEAD~1
- Коммит: Исчезает.
- Изменения: Уничтожаются. Файлы на диске возвращаются к состоянию прошлого коммита.
- Зачем: "Всё сломалось, хочу вернуть как было вчера".
Сценарий: Вы уже запушили плохой коммит на сервер (origin/main).
Если вы сделаете reset и запушите снова (push --force), вы сломаете историю всем коллегам. В общей ветке запрещено переписывать историю.
Вместо удаления старого коммита, мы создаем новый коммит, который делает обратное действие (удаляет добавленные строки, добавляет удаленные).
# Создать "анти-коммит" для указанного хеша
git revert <commit_hash>
История пойдет вперед, но код вернется назад. Это безопасно для командной работы.
Резюме главы:
- Не закоммитили?
git restore. - Закоммитили локально?
git reset(soft/mixed/hard). - Запушили?
git revert.
Сценарий: Вы находитесь в середине сложной задачи в ветке feature. Код "разобран": ничего не компилируется, повсюду print-ы.
Вдруг пишет лид: "Срочный баг на продакшене! Бросай всё, чини!"
Вы не можете сделать коммит (код не работает). Вы не можете просто переключить ветку (Git не даст уйти, если есть незакоммиченные изменения, конфликтующие с целевой веткой).
Решение: Stashing (Припрятать). Это временный буфер (стек), куда можно сложить текущие изменения, чтобы получить чистую рабочую директорию.
# 1. Спрятать изменения
git stash
# Теперь 'git status' покажет, что всё чисто.
# Можно переключаться на другую ветку, чинить баг и коммитить.
Когда вы вернулись обратно в свою ветку feature:
# 2. Вернуть изменения обратно
git stash pop
popделает две вещи: применяет изменения и удаляет их из стека.- Если хотите применить, но оставить копию в стеке (на всякий случай):
git stash apply.
По умолчанию git stash прячет только отслеживаемые (tracked) файлы. Если вы создали новый файл и не сделали ему git add, команда git stash оставит его валяться в папке.
Чтобы спрятать всё, включая новые файлы:
git stash -u # или --include-untracked
Stash — это стек. Вы можете делать git stash много раз.
# Посмотреть список спрятанного
git stash list
# Вывод:
# stash@{0}: WIP on feature-login: ...
# stash@{1}: WIP on master: ...
Чтобы достать конкретный тайник (не последний):
git stash pop stash@{1}
Git помнит всё. Проблема в том, как найти нужное в гигабайтах истории.
Просто git diff показывает разницу между Working Dir и Staging. Но это лишь вершина айсберга.
git diff: Работа vs Индекс (что я изменил, но не добавил?).git diff --staged: Индекс vs Последний коммит (что я собираюсь закоммитить?).git diff HEAD~1 HEAD: Сравнить последний коммит с предпоследним.git diff branchA branchB: Сравнить две ветки.
Вы нашли кусок ужасного кода и хотите узнать автора.
git blame filename.java
Git покажет файл построчно, и напротив каждой строки будет имя автора и хеш коммита. Совет: В IDE (IntelliJ/VS Code) это работает удобнее через правый клик -> Annotate with Git Blame.
Обычный grep или Ctrl+F ищет только в текущих файлах.
А если вы помните, что удалили функцию calculateTax() месяц назад, и она срочно понадобилась?
git grep "calculateTax" $(git rev-list --all)
Это найдет текст даже в удаленных файлах и старых коммитах.
Это "ядерное оружие" отладки.
Ситуация: В версии v2.0 все работало. В v2.5 (спустя 100 коммитов) тесты падают. Вы не знаете, какой из 100 коммитов все сломал.
Вместо того чтобы проверять каждый коммит, Git использует метод деления пополам.
- Запускаем:
git bisect start. - Говорим, что сейчас всё плохо:
git bisect bad. - Говорим, где всё было хорошо:
git bisect good v2.0.
Git сам переключит вас на середину истории (коммит №50). Вы проверяете (запускаете тест).
- Если работает: говорите
git bisect good. Git понимает, что ошибка во второй половине (51-100), и прыгает на №75. - Если не работает: говорите
git bisect bad. Ошибка в первой половине (1-49), Git прыгает на №25.
За 6-7 шагов Git найдет тот самый коммит, который внес баг.
В конце не забудьте: git bisect reset.
Итог Уровня 3: Мы закончили с инструментами исправления и поиска. Вы теперь можете отменять ошибки, прятать код в карман и находить иголку в стоге сена истории.
Добро пожаловать в "Божественный режим". До этого момента мы только добавляли что-то в историю. Теперь мы будем её менять.
Главный инструмент здесь — Rebase (Перебазирование). Это то, что отличает аккуратного сеньора от джуниора, который оставляет после себя "спагетти" из коммитов.
Представьте ситуацию: вы отвели ветку feature от main. Пока вы работали, в main появились новые коммиты. Теперь вам нужно забрать эти обновления к себе.
У вас два пути:
А. Merge (Слияние)
- Что делает: Создает специальный "Merge Commit", который связывает две истории.
- Результат: История выглядит как "ромбик" или косичка.
- Плюс: Это "честная" история. Видно, когда именно вы ответвились и когда слились.
- Минус: Если таких веток много, история превращается в нечитаемый хаос.
Б. Rebase (Перебазирование)
- Что делает: Git берет ваши коммиты из
feature, временно откладывает их, обновляет базу ветки до актуальногоmain, а затем накатывает ваши коммиты сверху по одному. - Результат: Абсолютно линейная история, как будто вы начали работу над фичей только что, от самого свежего
main. - Плюс: Идеальная чистота истории.
- Минус: Вы меняете хеши коммитов. История переписывается.
# 1. Переходим в свою ветку
git switch feature
# 2. Запускаем перебазирование на свежий main
git rebase main
Что происходит в этот момент: Git начинает применять ваши коммиты по очереди. Если в первом коммите конфликт — Git остановится.
Алгоритм при конфликте:
git status(видим конфликт).- Исправляем файл вручную.
git add fixed_file.txt(помечаем как решенный).- ВАЖНО: Не делайте
git commit! - Продолжаем процесс:
git rebase --continue
Паническая кнопка: Если вы запутались в конфликтах и хотите всё бросить:
git rebase --abort
Всё вернется к состоянию до начала ребейза.
Так как Rebase создает новые коммиты (даже если контент тот же, хеш меняется), это деструктивная операция.
НИКОГДА не делайте rebase для публичных веток (например, main/master), которыми пользуются другие люди.
Если вы сделаете rebase ветки, которую уже скачал ваш коллега, у вас разойдутся истории, и восстановить это будет очень больно. Rebase — только для ваших личных, локальных веток перед отправкой кода.
Это самый мощный инструмент для "причесывания" кода.
Если обычный rebase просто переносит ветку, то Interactive Rebase (-i) позволяет редактировать коммиты в процессе.
Сценарий: Вы сделали 4 коммита:
- "Start login logic"
- "WIP: Fix typo"
- "Finish login"
- "Forgot to add styles"
Вы хотите превратить это в один красивый коммит "Implement Login Feature" перед тем, как показать коллегам.
# "Хочу отредактировать последние 4 коммита"
git rebase -i HEAD~4
Git откроет текстовый редактор (Vim/Nano/Code) со списком коммитов. Выглядит это так:
pick a1b2c Start login logic
pick d3e4f WIP: Fix typo
pick g5h6i Finish login
pick j7k8l Forgot to add styles
В начале каждой строки стоит слово pick (оставить как есть). Вы можете менять эти слова на команды (Git подскажет их список внизу файла):
- reword (r): Оставить коммит, но дать отредактировать сообщение.
- edit (e): Остановить выполнение на этом коммите, чтобы вы могли поправить файлы (добавить/удалить что-то внутри кода).
- squash (s): Слить этот коммит с предыдущим (верхним).
- fixup (f): То же, что squash, но выкинуть сообщение коммита (идеально для исправлений опечаток).
- drop (d): Удалить коммит вообще (будто его не было).
Мы хотим слить всё в первый коммит. Редактируем файл так:
pick a1b2c Start login logic
squash d3e4f WIP: Fix typo <-- Сливаем с верхним
squash g5h6i Finish login <-- Сливаем с результатом выше
squash j7k8l Forgot to add styles <-- Сливаем с результатом выше
- Сохраняем и закрываем редактор.
- Git начнет применять изменения.
- Так как мы просили
squash, Git откроет редактор еще раз и спросит: "Какое общее сообщение сделать для этого мега-коммита?". - Мы пишем: "Implement Login Feature".
- Готово!
Теперь в истории (git log) вместо 4 мусорных коммитов у вас один идеальный.
Теперь вы владеете инструментами манипуляции историей.
rebaseделает историю линейной.rebase -i(squash/fixup) делает историю чистой
Добро пожаловать в машинное отделение.
Большинство разработчиков знают только "Фарфоровые" (Porcelain) команды: add, commit, checkout. Это красивые ручки и рычаги.
Но чтобы стать экспертом и чинить репозитории, когда всё сломалось, нужно знать "Сантехнические" (Plumbing) команды и устройство базы данных Git.
Git — это Content-Addressable Filesystem (Файловая система, адресуемая по содержимому), построенная поверх обычной файловой системы.
В основе Git лежит простая идея:
- Git берет контент (файл, структуру папки, сообщение коммита).
- Считает хеш (SHA-1) от этого контента.
- Использует этот хеш (40 символов) как имя файла.
- Сжимает контент (zlib) и кладет его в папку
.git/objects.
Если у вас есть два файла с абсолютно одинаковым содержимым в разных папках проекта, Git сохранит только один объект (Blob) и будет ссылаться на него дважды. Это дедупликация "из коробки".
В базе данных Git всего 4 типа объектов.
Хранит содержимое файла.
- Что внутри: Текст кода или бинарные данные.
- Чего нет: Имени файла, прав доступа, времени создания.
- Аналогия: Лист бумаги с текстом.
Хранит структуру директории.
- Что внутри: Список, сопоставляющий имена файлов с хешами их Blobs (или других Trees — подпапок).
- Аналогия: Оглавление папки.
Хранит снэпшот состояния проекта.
- Что внутри:
- Ссылка на главный объект Tree (корень проекта).
- Автор и дата.
- Сообщение коммита.
- Ссылка на Родителя (предыдущий коммит). Именно эта ссылка выстраивает цепочку истории.
Именованная метка.
- Что внутри: Ссылка на конкретный Commit + сообщение тега + подпись GPG (если есть).
Давайте "вскроем" Git и посмотрим на объекты вручную, используя команду git cat-file.
Зайдите в любой репозиторий и выполните:
Шаг 1: Посмотрим на последний коммит
# -p (pretty print) показывает содержимое объекта в читаемом виде
git cat-file -p HEAD
Вывод:
tree a1b2c3d... <-- Ссылка на "фотографию" файлов
parent e5f6g7h... <-- Ссылка на прошлый коммит
author Ivan <...> 1705666...
committer Ivan <...> 1705666...
Fix login bug <-- Сообщение
Шаг 2: Посмотрим на дерево (Tree)
Скопируйте хеш из строки tree (в примере a1b2c3d...) и подставьте в команду:
git cat-file -p a1b2c3d
Вывод:
100644 blob 8h9i0j... README.md
040000 tree 2k3l4m... src
Видите? Git просто сопоставляет имя README.md с объектом-блобом 8h9i0j....
Шаг 3: Посмотрим на содержимое файла (Blob)
Скопируйте хеш блоба 8h9i0j...:
git cat-file -p 8h9i0j
Вывод: Текст вашего README.md.
Вывод главы: Коммит — это просто обертка, указывающая на дерево, которое указывает на блобы. Вся магия Git — это граф ссылок.
Многие думают, что ветка — это какая-то сложная структура.
Давайте проверим. Зайдите в папку .git/refs/heads.
Вы увидите файлы с названиями ваших веток (main, feature).
Откройте файл main в текстовом редакторе (или через cat .git/refs/heads/main).
Что внутри?
e5f6g7h8... (40 символов хеша)
Всё. Ветка — это просто текстовый файл, в котором записан хеш одного коммита.
Когда вы делаете git commit, Git:
- Создает объект коммита.
- Обновляет файл
.git/refs/heads/main, записывая туда новый хеш.
А что такое HEAD?
Это ссылка на ссылку. Откройте файл .git/HEAD:
ref: refs/heads/main
Это говорит Git'у: "Сейчас мы стоим на ветке main". Если сделать checkout на хеш (Detached HEAD), то в файле HEAD будет записан просто хеш.
Так как Git создает новый объект на каждое изменение, папка .git/objects может разрастись. Плюс, если вы удалили ветку, коммиты, на которые она указывала, становятся "ничейными" (dangling).
Git умеет самоочищаться:
git gc
Эта команда:
- Удаляет осиротевшие объекты (которые недостижимы ни из одной ветки/тега).
- Упаковывает тысячи маленьких файлов-объектов в один большой сжатый Packfile (
.pack+.idx), чтобы экономить место и ускорить работу сети.
В современных версиях Git gc запускается автоматически фоном, но полезно знать о его существовании.
Итог Уровня 5: Теперь вы видите Матрицу.
- Нет никакой "магии". Есть хеши и ссылки.
- Ветка — это просто стикер на коммите.
- История — это связный список коммитов.
Git изначально создавался для исходного кода ядра Linux — тысяч маленьких текстовых файлов. Когда в репозиторий начинают класть видео, PSD-макеты или DLL-библиотеки, или когда проект разрастается до монорепозитория (как у Google или Facebook), стандартный Git начинает "захлебываться".
Эксперт должен уметь оптимизировать работу в таких экстремальных условиях.
Git хранит каждую версию каждого файла. Если вы изменили один пиксель в 100МБ файле texture.psd, Git сохранит новую копию весом 100МБ. Репозиторий пухнет мгновенно.
Решение: Git LFS (Large File Storage) Это расширение, которое заменяет большие файлы на маленькие текстовые указатели (pointer files). Сами тяжелые файлы хранятся на отдельном сервере.
Как это работает:
- Вы устанавливаете LFS:
git lfs install. - Говорите, за чем следить:
git lfs track "*.psd". (Это создает запись в.gitattributes). - Делаете
add/commitкак обычно. - Git видит файл весом 1КБ (указатель), а LFS фоном грузит 100МБ на сервер.
- Когда коллега делает
pull, он скачивает только те бинарники, которые нужны для текущей ветки (lazy loading).
Если репозиторий весит 10ГБ и содержит 50 микросервисов, делать полный git clone долго и бессмысленно, если вам нужно поправить только одну кнопку в админке.
Вот арсенал для работы с гигантами:
Идеально для CI/CD пайплайнов. Зачем качать историю за 10 лет, чтобы просто собрать билд?
# Скачать только последний коммит (без истории)
git clone --depth 1 <url>
Минус: Нельзя нормально мёржить ветки или смотреть git blame дальше одного коммита.
Вы клонируете базу данных репозитория, но в Рабочую директорию выгружаете только нужные папки.
git clone --no-checkout <url> repo
cd repo
git sparse-checkout init --cone
# Говорим: "Мне нужна только папка backend/api"
git sparse-checkout set backend/api
git checkout main
Теперь у вас на диске только одна папка, хотя Git знает обо всем проекте.
В отличие от Shallow Clone, вы скачиваете всю историю коммитов и деревьев (это быстро), но не скачиваете содержимое файлов (блобы), пока они не понадобятся.
# Скачать историю, но не качать содержимое файлов
git clone --filter=blob:none <url>
Когда вы сделаете checkout, Git докачает содержимое только нужных файлов. git log работает быстро и полноценно.
Частая задача: есть библиотека LibA, которая используется в проектах ProjectX и ProjectY. Копипастить код — плохо.
Это ссылка на конкретный коммит другого репозитория внутри текущего.
- Создание:
git submodule add <url> libs/LibA. - Проблема: Когда вы клонируете
ProjectX, папкаlibs/LibAбудет пустой. - Лечение: Нужно инициализировать подмодули:
git submodule update --init --recursive
- Мнение: Подмодули считают "болью" из-за сложности управления версиями. Легко забыть обновить ссылку, и у команды перестанет собираться проект.
Менее популярный, но более безопасный метод. Он просто вливает код другого проекта в папку вашего, сохраняя историю.
- Для обычного разработчика это выглядит как просто файлы. Не нужно делать
submodule init. - Сложнее настраивать для лида, но проще для команды.
В Enterprise-среде и Open Source доверие — это валюта. Как доказать, что код написали именно вы? И как удалить то, что не должны видеть посторонние?
В Git любой может настроить user.name "Linus Torvalds" и делать коммиты. Чтобы подтвердить личность, используется криптография.
GitHub/GitLab помечают подписанные коммиты зеленым значком Verified.
Настройка (через SSH ключ — современный способ):
- Git 2.34+ поддерживает подпись теми же ключами, что и SSH-доступ.
- Конфигурация:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
# Автоматически подписывать все коммиты
git config --global commit.gpgsign true
Теперь каждый ваш коммит зашифрован вашим приватным ключом. Подделать это невозможно.
Катастрофа: Вы случайно закоммитили файл aws_keys.json с паролями от облака.
Ошибка новичка: Удалить файл новым коммитом.
Почему это плохо: Файл остался в истории. Любой, кто сделает git checkout HEAD~1, увидит пароли. Боты сканируют GitHub на такие ошибки за секунды.
Нужно вырезать файл из всей истории, переписав хеши всех коммитов.
Раньше использовали git filter-branch (медленно и сложно). Сейчас стандарт — утилита git-filter-repo (нужно ставить отдельно, Python-скрипт).
# Удалить файл password.txt из всех коммитов во всей истории
git filter-repo --path password.txt --invert-paths
Внимание: Это меняет все хеши. После этого нужно делать git push --force, и все коллеги должны будут переклонировать репозиторий.
Git — это инструмент. Workflow — это правила игры для команды. Знание команд не поможет, если вы не договорились, как их использовать.
Разработан Винсентом Дриссеном в 2010. Очень структурированный, но сложный.
- Ветки:
master(прод),develop(разработка),feature/*,release/*,hotfix/*. - Когда использовать: В проектах с редкими релизами (коробочный софт, раз в месяц/квартал).
- Минус: Слишком бюрократичен для веб-разработки.
- Ветки: Только
main(всегда стабильна) иfeature-branches. - Цикл:
- Ветка от main.
- Коммиты.
- Pull Request (Code Review).
- Merge в main + Деплой.
- Когда использовать: Веб-сервисы, стартапы, CI/CD.
Выбор гигантов (Google) и современных DevOps команд.
- Идея: Долгоживущих веток нет. Все коммитят прямо в
main(или очень короткие ветки на 1-2 дня). - Как не сломать прод? Используются Feature Flags (код скрыт за условием
if (featureEnabled)). - Плюс: Нет "ада слияния" (merge hell) перед релизом.
Соглашение о том, как писать сообщения, чтобы потом автоматически генерировать CHANGELOG и поднимать версию (SemVer).
Формат:
<тип>(<область>): <описание>
feat(auth): add google login support
fix(api): fix NPE in user controller
chore: update dependencies
docs: update readme
Итог Уровня 6: Вы вышли на уровень архитектора процессов. ✅ Вы знаете, как работать с гигабайтами данных (LFS, Sparse Checkout). ✅ Вы умеете защищать код (Signing) и чистить его (filter-repo). ✅ Вы понимаете, какую стратегию ветвления выбрать для команды.
Мы добрались до финала. Здесь мы превратим Git из «инструмента, с которым нужно бороться» в «продолжение ваших рук».
Если вы вводите git checkout или git status полностью — вы теряете время.
Алиасы живут в глобальном конфиге ~/.gitconfig. Вы можете добавить их командой git config --global alias.<name> <command>, но проще и надежнее открыть файл в редакторе:
git config --global -e
Вставьте туда блок [alias]. Вот мой рекомендуемый "Набор джентльмена":
[alias]
# -- 1. Сокращения для ленивых --
s = status
st = status
co = checkout
sw = switch
br = branch
ci = commit
df = diff
# -- 2. Умные команды --
# Unstage: Вернуть файл из индекса в рабочую папку (обратно add)
unstage = restore --staged
# Last: Посмотреть последний коммит кратко
last = log -1 HEAD --stat
# -- 3. Визуализация (Самый важный алиас) --
# Рисует дерево ветвления прямо в консоли. Заменяет GUI-клиенты.
lg = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
# -- 4. Спасение --
# Показывает Reflog с датами в читаемом виде.
# Используйте, если потеряли коммит или случайно удалили ветку.
oops = reflog --format='%C(auto)%h %<|(20)%gd %C(blue)%cr%C(reset) %gs (%s)'
Теперь вместо git log --graph ... вы пишете просто git lg.
Хуки — это скрипты, которые Git запускает в ключевые моменты жизненного цикла.
Они лежат в скрытой папке .git/hooks/. Если вы заглянете туда, вы увидите файлы с расширением .sample. Уберите .sample, сделайте файл исполняемым, и он начнет работать.
Запускается до создания коммита. Если скрипт вернет ошибку (код выхода != 0), коммит не создастся.
- Зачем: Запуск линтеров (ESLint, Checkstyle), форматирование кода (Prettier), запуск быстрых Unit-тестов.
- Пример: "Не пускать коммит, если в коде осталось слово
TODOилиconsole.log".
Проверяет текст сообщения коммита.
- Зачем: Обеспечение стандарта Conventional Commits или проверка наличия ID задачи из Jira.
- Пример:
Regexпроверка, что сообщение начинается с[PROJ-123].
Запускается перед отправкой на сервер.
- Зачем: Запуск тяжелых тестов, которые долго гонять на каждый коммит, но нельзя пропустить на сервер.
Хуки в папке .git/hooks не коммитятся. Они локальны. Вы не можете просто добавить их в репозиторий, чтобы они заработали у всей команды.
Для решения этой проблемы используются менеджеры хуков, которые настраиваются через конфиг-файл в корне проекта:
- JS/Frontend: Библиотека Husky.
- Python/General: Фреймворк pre-commit.
Пример (.pre-commit-config.yaml):
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: trailing-whitespace # Удаляет лишние пробелы
- id: end-of-file-fixer # Добавляет пустую строку в конец
- id: check-yaml # Проверяет синтаксис YAML
Теперь любой, кто сделает git clone, получит эти проверки автоматически.
Файл .gitattributes позволяет задавать настройки Git для конкретных файлов или путей.
Чтобы раз и навсегда решить проблему "Windows vs Linux line endings", добавьте в корень проекта .gitattributes:
# Заставь Git автоматически нормализовать все текстовые файлы (в LF)
* text=auto
# Явно укажи, что эти файлы текстовые (даже если Git сомневается)
*.js text
*.java text
# Явно укажи, что это бинарники (не трогай переносы строк)
*.png binary
*.jpg binary
На GitHub есть цветная полоска "Languages". Иногда она врет (например, считает, что ваш проект на 90% состоит из JavaScript, потому что вы закоммитили папку jquery.min.js).
В .gitattributes можно исключить файлы из статистики:
# Не считай сгенерированные файлы и библиотеки в статистике языка
jquery.js linguist-vendored
dist/* linguist-generated
Для некоторых файлов (например, config.lock или автогенерируемых схем) конфликты слияния бессмысленны. Можно сказать Git: "В случае конфликта всегда бери мою версию".
database.xml merge=ours
Мы прошли путь от git init до настройки CI-пайплайнов и внутренней структуры блобов.