00 · экскурс
Git без боли:
понять один раз, пользоваться каждый день
Git это система контроля версий. Она хранит историю проекта как цепочку снимков, позволяет вернуться к любому из них, вести несколько линий работы параллельно и сливать результаты нескольких людей
без «отправь мне последнюю версию».
Это 90% повседневной работы. Любую команду на странице можно скопировать кликом. Клавиша / — поиск по шпаргалке.
Какую проблему он решает
| Без Git | С Git |
|---|---|
analysis_final_v3_правки_НОВЫЙ.ipynb | Один файл, вся история внутри: кто, когда, что и зачем изменил |
| «Вчера работало, сегодня нет, что я поменял?» | git diff, git log, git bisect находят изменение за минуты |
| Страшно пробовать новую идею, а вдруг сломаю рабочее | Ветка: эксперимент изолирован, основа не тронута |
| Двое правят один файл и затирают друг друга | Слияние объединяет правки, конфликты показывает явно |
| «Какой версией кода обучена эта модель / посчитан отчет?» | Хеш коммита однозначно фиксирует состояние кода |
Четыре идеи, на которых все держится
Коммит = снимок
Не «разница», а полное состояние проекта в момент времени плюс ссылка на предыдущий снимок.
У каждого есть уникальный хеш.
Ветка = указатель
Ветка — это просто подвижная метка на коммите. Создать ветку мгновенно и бесплатно, поэтому ими пользуются постоянно.
Все локально
У вас полная копия истории. Коммиты, ветки, откаты работают без сети. С сервером вы только синхронизируетесь.
Потерять сложно
Закоммиченное почти всегда можно вернуть (см. reflog). Безвозвратно теряется только то, что не было в коммите.
и живут pull/merge request'ы, ревью и CI. Команды Git везде одинаковые.
01 · модельКак устроен Git: четыре места, где живет код
Почти любая команда Git переносит изменения между этими зонами.
Если понимать, откуда и куда, команды перестают быть заклинаниями.
Рабочая папка
Обычные файлы на диске. То, что вы видите в редакторе и Jupyter.
Индекс (staging)
Черновик следующего коммита. Позволяет закоммитить не все подряд, а осмысленную часть правок.
Локальный репозиторий
Папка .git: вся история, ветки, теги. Живет рядом с проектом.
Удаленный репозиторий
Общая копия на сервере, обычно под именем origin. Через нее идет обмен с командой.
Состояния файла
| Состояние | Что значит | В git status -s |
|---|---|---|
| untracked | Новый файл, Git за ним еще не следит | ?? |
| modified | Отслеживается, изменен, не добавлен в индекс | M |
| staged | Добавлен в индекс, войдет в следующий коммит | M / A |
| committed | Сохранен в истории, рабочая папка чистая | — |
| ignored | Подходит под .gitignore, Git его не видит | — |
02 · стартБыстрый старт: настроить один раз
Представиться и включить полезные настройки
git config --global user.name "Имя Фамилия" git config --global user.email "you@company.ru" git config --global init.defaultBranch main # новая ветка по умолчанию — main git config --global pull.rebase true # pull без лишних merge-коммитов git config --global push.autoSetupRemote true # push новой ветки без -u (Git 2.37+) git config --global fetch.prune true # убирать ссылки на удаленные ветки git config --global core.quotepath false # нормальные кириллические имена файлов git config --global core.editor "code --wait" # редактор для сообщений (VS Code) # Переводы строк: на macOS/Linux — input, на Windows — true git config --global core.autocrlf input git config --global --list # проверить, что получилось
SSH-ключ, чтобы не вводить пароль
ssh-keygen -t ed25519 -C "you@company.ru" # Enter, Enter, (пароль по желанию) cat ~/.ssh/id_ed25519.pub # скопировать и добавить в GitHub/GitLab → SSH keys ssh -T git@github.com # проверка (для GitLab: git@gitlab.com)
Получить репозиторий
Проект уже есть на сервере
git clone git@github.com:team/project.git cd project
Начинаю с нуля в папке
cd my-project git init git add . git commit -m "Initial commit" git remote add origin git@github.com:me/my-project.git git push -u origin main # main — если настроили init.defaultBranch выше
.gitignore (генератор — в разделе Данные и ML), и только потом git add .. Иначе в историю уедут данные, окружение и секреты.03 · рутинаКаждый день: от задачи до pull request
Один и тот же сценарий подходит и для отчета, и для пайплайна, и для фичи.
- Обновить основную ветку
git switch main git pull
- Создать ветку под задачу
git switch -c feature/layer-usage-report
- Работать и смотреть, что изменилось
git status -sb # коротко: что изменено, в какой я ветке git diff # что именно изменено (еще не в индексе)
- Собрать коммит из осмысленных правок
git add src/report.py # файл целиком git add -p # или по кусочкам: y — взять, n — пропустить, s — разбить git diff --staged # проверить, что уйдет в коммит git commit -m "feat: добавил расчет активности по слоям"
- Отправить и открыть PR / MR
git push -u origin feature/layer-usage-report # -u не нужен, если включили push.autoSetupRemote
Сервер в ответ напечатает ссылку для создания pull/merge request. - После слияния — прибраться
git switch main git pull git branch -d feature/layer-usage-report
Если PR влили кнопкой «Squash and merge», Git не поймет, что ветка слита, и откажет. Тогдаgit branch -D— это безопасно, раз изменения уже вmain.
Хороший коммит
Одна мысль — один коммит
«Починил фильтр по дате» и «переименовал колонки» — два разных коммита. Так проще ревьюить и откатывать.
Сообщение отвечает «что и зачем»
fix: убрал дубли пользователей при джойне логов вместо правки или update.
Коммитить часто
Мелкие коммиты — точки сохранения. Перед PR их можно склеить (rebase -i).
Смотреть историю
git log --oneline --graph --decorate --all # дерево всех веток git log -5 --stat # последние 5 коммитов и затронутые файлы git log -p -- src/metrics.py # вся история правок одного файла git show a1b2c3d # что было в конкретном коммите git blame -L 40,60 src/metrics.py # кто и когда менял строки 40–60
04 · веткиВетки, merge и rebase
Ветка — указатель на коммит. HEAD — указатель на то, где вы сейчас. Коммит двигает текущую ветку вперед. Попробуйте руками.
Команды для веток
git branch # локальные ветки (* — текущая) git branch -a -vv # все ветки + с чем связаны и на сколько расходятся git switch -c exp/new-features # создать и перейти git switch main # перейти git switch - # вернуться в предыдущую ветку git branch -m new-name # переименовать текущую git branch -d feature/x # удалить слитую (-D — удалить любую) git push origin --delete feature/x # удалить на сервере
Merge или rebase
| merge | rebase | |
|---|---|---|
| Что делает | Соединяет две линии истории коммитом слияния | Переносит ваши коммиты поверх другой ветки, создавая их копии |
| История | Честная, с развилками | Линейная, читается как один поток |
| Хеши коммитов | Не меняются | Меняются — это новые коммиты |
| Когда | Влить готовую ветку в main; общие ветки | Подтянуть свежий main в свою личную ветку перед PR |
# Типичное обновление своей ветки перед PR git switch feature/x git fetch origin git rebase origin/main git push --force-with-lease # ветка уже была на сервере → нужна перезапись
05 · серверУдаленный репозиторий
origin — принятое имя сервера. origin/main — ваша локальная «фотография» ветки main на сервере на момент последнего fetch.
git fetch
Скачивает новое с сервера и обновляет origin/*. Ваши ветки и файлы не трогает. Всегда безопасно.
git pull
Это fetch + слияние (или rebase) в текущую ветку. Может вызвать конфликт.
git push
Отправляет ваши коммиты. Если на сервере есть чужие новые коммиты — отклонит, сначала нужен pull.
git remote -v # куда смотрит origin git fetch # узнать, что нового, ничего не ломая git log --oneline HEAD..origin/main # чего у меня еще нет git log --oneline origin/main..HEAD # что я еще не отправил git pull --rebase # забрать и положить свои коммиты сверху git push # отправить git push --force-with-lease # перезаписать СВОЮ ветку после rebase/amend
--force, только --force-with-leaseВторой вариант откажется перезаписывать ветку, если на сервере появились коммиты, которых вы не видели. Обычный --force молча сотрет чужую работу.Fork-модель (open source, чужие репозитории)
git clone git@github.com:me/project.git # свой форк git remote add upstream git@github.com:original/project.git # оригинал git fetch upstream git rebase upstream/main # обновить свою ветку от оригинала
06 · отменаОтмена и спасение
Самый востребованный раздел. Ответьте на пару вопросов — получите точную команду.
Три режима git reset
reset, revert, restore — в чем разница
| Команда | Работает с | Суть | Безопасна для общей ветки |
|---|---|---|---|
git restore | файлами | Вернуть файл к версии из индекса или коммита | да (история не меняется) |
git revert | коммитами | Создать новый коммит, отменяющий старый | да |
git reset | веткой | Передвинуть указатель ветки назад | нет, только до push |
Reflog — страховочная сетка
Git записывает каждое перемещение HEAD. Даже после reset --hard, неудачного rebase или удаления ветки коммиты остаются в хранилище еще как минимум 30 дней (обычно — 90).
git reflog # журнал: где был HEAD
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# f4e5d6c HEAD@{1}: commit: важная работа ← вот он
git reset --hard f4e5d6c # вернуть ветку на этот коммит
git branch rescue f4e5d6c # или аккуратнее: повесить на него новую ветку
git restore, reset --hard и clean -f пропадают окончательно. Последний шанс — локальная история IDE: Timeline в VS Code, Local History в PyCharm.07 · конфликтыКонфликты слияния
Конфликт — не ошибка. Git просит решить, какая версия правильная, когда две ветки изменили одни и те же строки.
Алгоритм
- Посмотреть, какие файлы в конфликте
git status
- Открыть файл, выбрать нужное, удалить маркеры
<<<<<<<,=======,>>>>>>>. В VS Code и PyCharm для этого есть кнопки и трехпанельный редактор. - Убедиться, что код работает — запустить тесты или скрипт.
- Отметить как решенный и завершить
git add prepare.py git commit # при merge git rebase --continue # при rebase
git merge --abort # передумал: вернуть все как было до merge git rebase --abort # то же для rebase git checkout --ours path/to/file # взять файл целиком из «моей» стороны git checkout --theirs path/to/file # взять файл целиком из входящей git diff --name-only --diff-filter=U # список файлов с конфликтами git config --global rerere.enabled true # запоминать решения повторяющихся конфликтов
а «theirs» — ваши собственные коммиты.
Как конфликтовать реже
Короткие ветки
Ветка на день-два почти не расходится с main. Ветка на месяц — гарантированная боль.
Чаще подтягивать main
git fetch && git rebase origin/main раз в день: мелкие конфликты вместо одного огромного.
Ноутбуки без выводов
Выводы и счетчики ячеек в .ipynb — главный источник конфликтов у аналитиков. Решение в следующем разделе.
08 · данные и MLGit для данных, ноутбуков и моделей
Git создан для текста. Данные, веса моделей и ноутбуки требуют нескольких дополнительных привычек.
| Что | В Git? | Как правильно |
|---|---|---|
| Код, SQL, конфиги, README | да | Обычные коммиты |
Ноутбуки .ipynb | да, без выводов | nbstripout или jupytext |
| Маленькие справочники и примеры данных (до ~1 МБ) | можно | data/sample/ |
| Сырые данные, выгрузки, parquet | нет | .gitignore + DVC или объектное хранилище |
| Веса моделей, чекпойнты | нет | DVC, Git LFS, реестр моделей (MLflow и др.) |
Пароли, токены, .env | никогда | .gitignore + .env.example без значений |
Окружение (.venv, node_modules) | нет | В Git — только файл зависимостей и lock-файл |
.gitignore действует только на неотслеживаемые файлы. Сначала git rm --cached путь (файл на диске останется), потом коммит. Почему файл игнорируется: git check-ignore -v путь.Jupyter-ноутбуки аналитикуML
Ноутбук — это JSON с кодом, выводами, картинками в base64 и счетчиками выполнения. Перезапустили ячейку — получили «изменение» на сотни строк. Три инструмента, от простого к мощному:
# 1. nbstripout — автоматически вырезает выводы при коммите (файл на диске не трогает) pip install nbstripout nbstripout --install --attributes .gitattributes # в корне репо, один раз git add .gitattributes # 2. nbdime — человеческий diff и merge для ноутбуков pip install nbdime nbdime config-git --enable --global nbdiff-web old.ipynb new.ipynb # 3. jupytext — пара .ipynb + .py; в Git кладем .py, он отлично диффится и ревьюится pip install jupytext jupytext --set-formats ipynb,py:percent notebooks/01_eda.ipynb
src/. Ноутбук импортирует код, а не содержит его целиком: меньше конфликтов, проще тестировать.Большие файлы: DVC и Git LFS MLаналитику
DVC
В Git лежит маленький файл-указатель .dvc с хешем, сами данные — в S3, MinIO, на сетевом диске. Умеет пайплайны и воспроизведение экспериментов. Выбор по умолчанию для датасетов и моделей.
Git LFS
Прозрачно подменяет большие файлы указателями, хранит их на сервере Git. Проще в настройке, но ограничен квотами хостинга. Хорош для немногих бинарных файлов.
# DVC: версионировать папку с данными pip install dvc dvc-s3 dvc init dvc remote add -d storage s3://my-bucket/dvc dvc add data/raw # создаст data/raw.dvc и допишет data/.gitignore git add data/raw.dvc data/.gitignore .dvc/config git commit -m "data: добавил сырые логи за сентябрь" dvc push # данные → в хранилище # У коллеги или на сервере обучения: git pull && dvc pull # Git LFS: отслеживать веса git lfs install git lfs track "*.pkl" "*.joblib" git add .gitattributes
Воспроизводимость: какой код дал этот результат MLаналитику
Записывайте хеш коммита рядом с каждым результатом: в лог эксперимента, в имя файла отчета, в метаданные модели. Флаг dirty честно скажет, были ли незакоммиченные правки.
git rev-parse --short HEAD # короткий хеш текущего коммита git describe --tags --always --dirty # v1.2.0-3-ga1b2c3d-dirty git tag -a model-v1.2.0 -m "прогноз сроков согласования, AUC=0.91" && git push origin model-v1.2.0
import subprocess
def git_state() -> dict:
run = lambda *a: subprocess.check_output(["git", *a], text=True).strip()
return {
"commit": run("rev-parse", "HEAD"),
"branch": run("rev-parse", "--abbrev-ref", "HEAD"),
"dirty": bool(run("status", "--porcelain")),
}
# mlflow.set_tags(git_state()) или wandb.config.update(git_state())
exp/…. Меняются только параметры модели — это конфиг и трекер экспериментов, а не двадцать веток. Несколько экспериментов одновременно без переключений — git worktree.Секреты и автоматические проверки
# .pre-commit-config.yaml — проверки запускаются сами перед каждым коммитом
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-added-large-files # не пропустит случайный файл больше 1 МБ
args: [--maxkb=1000]
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/kynan/nbstripout
rev: 0.7.1
hooks:
- id: nbstripout
- repo: https://github.com/gitleaks/gitleaks
rev: v8.20.0
hooks:
- id: gitleaks # ищет токены и пароли
pip install pre-commit pre-commit install # включить в этом репозитории pre-commit autoupdate # подтянуть актуальные версии хуков pre-commit run --all-files # прогнать по всему проекту
Структура проекта, с которой удобно жить
project/ ├── data/ # в .gitignore или под DVC │ └── sample/ # маленький пример — в Git ├── notebooks/ # 01_eda.ipynb, 02_features.ipynb — без выводов ├── src/ # функции, пайплайны, обучение ├── sql/ # запросы как файлы .sql, а не строки в ноутбуке ├── configs/ # параметры экспериментов (yaml) ├── models/ # в .gitignore / DVC / LFS ├── reports/ # итоговые графики и отчеты ├── tests/ ├── .gitignore ├── .pre-commit-config.yaml ├── pyproject.toml # зависимости + lock-файл (uv.lock / poetry.lock) — в Git └── README.md # как запустить за 5 минут
09 · командаРабота в команде
Модель ветвления
GitHub Flow по умолчанию
main всегда рабочий. Каждая задача — короткая ветка → PR → ревью → merge. Подходит большинству аналитических и ML-команд.
Trunk-based dev
Очень короткие ветки (часы), частые слияния в main, незаконченное прячется за feature-флагами. Требует хороших тестов и CI.
Git Flow
develop, release/*, hotfix/*. Оправдан для продуктов с версионными релизами. Для аналитики обычно избыточен.
Имена веток и сообщения коммитов
# Ветки: тип/краткое-описание (можно с номером задачи) feature/DT-142-layer-heatmap fix/duplicate-users-in-join exp/gradient-boosting-features docs/readme-setup # Коммиты (Conventional Commits): тип(область): что сделано feat(eda): добавил анализ активности по группам меню fix(etl): исправил часовой пояс при парсинге логов refactor(etl): вынес очистку логов в отдельный модуль data: обновил справочник слоев (версия от 01.10) docs: описал запуск пайплайна в README chore: обновил зависимости
Pull / Merge request без страданий
Маленький
До 300–400 строк изменений. Большой PR никто не читает внимательно — его просто одобряют.
С описанием
Что сделано, зачем, как проверить. Для аналитики — ключевой график или цифра «до/после».
Самопроверка
Перед запросом ревью пролистайте diff сами: git diff main...HEAD. Половина замечаний находится здесь.
Защита main
Запрет прямого push, обязательное ревью и зеленый CI настраиваются в GitHub/GitLab за пару минут.
Способ слияния PR
| Кнопка | Результат в main | Когда |
|---|---|---|
| Merge commit | Все коммиты ветки + коммит слияния | Важна подробная история ветки |
| Squash and merge | Один коммит на весь PR | В ветке много «wip» и «fix typo»; самый чистый main |
| Rebase and merge | Коммиты ветки линейно, без коммита слияния | Коммиты в ветке аккуратные и осмысленные |
10 · продвинутоеИнструменты, которые экономят часы
stash — отложить незаконченное
Срочно нужно переключиться, а коммитить рано.
git stash push -u -m "wip: heatmap" # спрятать правки (-u — вместе с новыми файлами)
git stash list
git stash pop # вернуть последнее и удалить из stash
git stash apply stash@{1} # вернуть конкретное, оставив в stash
git stash show -p stash@{0} # посмотреть содержимое
git stash drop stash@{0} # удалить
cherry-pick — забрать один коммит из другой ветки
git cherry-pick a1b2c3d # скопировать коммит в текущую ветку git cherry-pick a1b2c3d..f4e5d6c # диапазон (не включая первый) git cherry-pick --abort
rebase -i — навести порядок перед PR dev
git rebase -i HEAD~4 # переписать последние 4 коммита # В открывшемся списке замените pick на: # reword — изменить сообщение squash — склеить с предыдущим # fixup — склеить, сообщение выкинуть drop — удалить коммит # edit — остановиться и поправить; порядок строк = порядок коммитов git commit --fixup a1b2c3d # «это исправление к коммиту a1b2c3d» git rebase -i --autosquash main # fixup-коммиты сами встанут на место
bisect — найти коммит, который все сломал MLdev
Двоичный поиск по истории: 1000 коммитов проверяются за 10 шагов. Незаменим, когда метрика просела, отчет перестал сходиться или тест упал «непонятно когда».
git bisect start git bisect bad # сейчас сломано git bisect good v1.4.0 # здесь точно работало # Git переключает на середину → проверяете → git bisect good / bad → повторить git bisect run pytest tests/test_metrics.py # или полностью автоматически git bisect reset # вернуться обратно
Поиск по истории
git log -S "drop_duplicates" --oneline # коммиты, где строка появилась или исчезла git log -G "threshold\s*=\s*0\.\d+" --oneline # то же по регулярному выражению git log --grep "часовой пояс" # поиск по сообщениям коммитов git log --author "Иван" --since "2 weeks ago" git log --follow -p -- src/old_name.py # история файла сквозь переименования git grep -n "TODO" # быстрый поиск по текущим файлам репо git show a1b2c3d:src/metrics.py # файл, каким он был в том коммите git diff main...feature/x --stat # что изменила ветка относительно main
worktree — две ветки одновременно в разных папках ML
В одной ветке идет долгий расчет или обучение модели, а править код нужно в другой. Не надо ни клонировать заново, ни делать stash.
git worktree add ../project-exp2 -b exp/augment-v2 # новая папка с новой веткой git worktree list git worktree remove ../project-exp2
Большие репозитории
git clone --depth 1 URL # только последний снимок, без истории git clone --filter=blob:none URL # история есть, файлы докачиваются по требованию git sparse-checkout set src configs # держать на диске только нужные папки git count-objects -vH # сколько весит репозиторий
Теги и алиасы
git tag -a v1.2.0 -m "Релиз отчета за Q3" git push origin v1.2.0 # теги сами не отправляются git tag -l "v1.*" git config --global alias.st "status -sb" git config --global alias.lg "log --oneline --graph --decorate --all" git config --global alias.last "log -1 HEAD --stat" git config --global alias.undo "reset --soft HEAD~1"
Чистка
git clean -nd # показать, какие неотслеживаемые файлы будут удалены
git clean -fd # удалить их (необратимо!)
git branch --merged main | grep -v main | xargs git branch -d # удалить слитые ветки
11 · шпаргалкаВсе команды в одном месте
Клик по строке копирует команду. Фильтр понимает русские слова: «отменить», «удалить», «история».
12 · словарьТермины за минуту
- commit
- Снимок проекта с автором, датой, сообщением и ссылкой на родителя.
- hash (SHA)
- Уникальный идентификатор коммита, например
a1b2c3d. Достаточно первых 7 символов. - branch
- Именованный указатель на коммит, который сдвигается при новых коммитах.
- HEAD
- Указатель «я здесь». Обычно смотрит на ветку.
HEAD~1— предыдущий коммит. - detached HEAD
- HEAD смотрит прямо на коммит, а не на ветку. Новые коммиты не привязаны к ветке — создайте ее через
git switch -c. - index / staging
- Область подготовки следующего коммита.
- origin
- Стандартное имя удаленного репозитория, с которого сделан clone.
- origin/main
- Remote-tracking ветка: локальная копия состояния main на сервере на момент последнего fetch.
- upstream
- Ветка на сервере, с которой связана локальная. В fork-модели — еще и имя оригинального репозитория.
- fast-forward
- Слияние без merge-коммита: указатель просто переезжает вперед, потому что истории не расходились.
- PR / MR
- Pull request (GitHub) или merge request (GitLab): предложение влить ветку с обсуждением и ревью.
- tag
- Неподвижная метка на коммите: версия релиза, модели, отчета.
- stash
- «Полка» для временно отложенных незакоммиченных правок.
- reflog
- Локальный журнал перемещений HEAD. Главный инструмент спасения.
- merge
- Слияние: соединить две линии истории. Если они расходились — появляется коммит слияния с двумя родителями.
- rebase
- Перенос своих коммитов поверх другой ветки. История становится линейной, но коммиты получают новые хеши.
- conflict
- Две ветки изменили одни и те же строки. Git останавливается и просит человека выбрать правильную версию.
- cherry-pick
- Скопировать один конкретный коммит в текущую ветку.
- .gitignore
- Список файлов и папок, которые Git не должен замечать: данные, окружение, секреты.
- remote
- Удаленный репозиторий — общая копия на сервере. У одного проекта их может быть несколько (
origin,upstream).
Справка по любой команде: git help <команда> или git <команда> -h для краткой версии.