Справочник ключевых терминов курса Git для Junior Data Engineer.
Каталог проекта, в котором Git отслеживает изменения. Физически это набор файлов в рабочем каталоге плюс скрытая папка `.git/`, где Git хранит всю историю, ветки, конфиг и объекты. Различают local repository (на вашей машине, с полной историей) и remote repository (на сервере вроде GitHub, к которому push/pull). Git — distributed VCS: каждый клон содержит полную копию истории и работоспособен без сетевого соединения. Repository может быть bare (без working tree, обычно на сервере) или non-bare (обычный, у разработчика). Удаление `.git/` превращает каталог в обычные файлы без истории.
git init my-project # создать новый репо
git clone <url> my-project # клонировать существующий
ls -la my-project/.git/ # внутренности репо
# HEAD, config, objects/, refs/, hooks/, index, logs/Снимок состояния проекта в определённый момент времени плюс метаданные. Физически commit — это git object, содержащий: ссылку на tree object (snapshot структуры файлов), parent commit hash(es), автора (имя, email, timestamp), коммиттера (часто тот же, но при rebase/cherry-pick — другой), сообщение. Идентифицируется SHA-1 hash (с Git 2.42 опционально SHA-256), вычисленным из содержимого. Immutable — изменить commit нельзя, можно создать новый с такими же изменениями (что и делает amend/rebase). Merge commit имеет 2+ parents, root commit — 0 parents, обычный — 1 parent.
git commit -m "feat: add CSV parser"
# anatomy объекта commit:
# tree 9d2f... ← snapshot файлов
# parent 3a8c... ← предыдущий commit (может быть 0/1/2+)
# author Lev <l@e> 1715000000 +0300
# committer Lev <l@e> 1715000000 +0300
#
# feat: add CSV parser
git cat-file -p HEAD # посмотреть содержимоеПодвижный указатель (mutable pointer) на commit. Физически это файл в `.git/refs/heads/<branch>`, содержащий 40-символьный SHA коммита. При `git commit` Git создаёт новый commit с текущим HEAD как parent и обновляет указатель ветки на новый commit. Создание ветки — это просто запись нового файла с SHA: O(1) операция, никакого копирования файлов не происходит. Default branch обычно называется `main` (в новых репо) или `master` (в старых). Ветка не «содержит» коммиты — она лишь указывает на последний; история восстанавливается обходом parent-указателей.
git branch feature/etl-rewrite # создать ветку
git switch feature/etl-rewrite # переключиться (Git 2.23+)
git branch -d feature/old # удалить (если merged)
git branch -D feature/old # форсированное удаление
# Под капотом:
cat .git/refs/heads/main
# 3a8c2f1e9d... ← просто SHAСпециальный ref, указывающий на текущий checkout — обычно на ветку, реже напрямую на commit (detached HEAD). Физически — файл `.git/HEAD`, содержащий либо `ref: refs/heads/main` (symbolic ref на ветку), либо 40-символьный SHA (detached). HEAD определяет, какой commit станет parent следующего commit. При `git switch branch` обновляется `.git/HEAD` и working tree приводится к состоянию того commit. Detached HEAD возникает при `git checkout <sha>` — изменения тут опасны, потому что новые commits не привязаны к ветке и легко теряются.
cat .git/HEAD
# ref: refs/heads/main
git checkout 3a8c2f1e # detached HEAD
cat .git/HEAD
# 3a8c2f1e9d4b5c... ← напрямую SHA
git rev-parse HEAD # SHA текущего commit
git log HEAD~3..HEAD # последние 3 коммитаКаталог проекта, в котором лежат файлы такими, какими вы их видите и редактируете — НЕ внутри `.git/`. Это первая из «трёх областей» Git (working tree -> index -> repository). Изменения в working tree не отслеживаются Git, пока не сделать `git add` (в index) и `git commit` (в repository). `git status` сравнивает working tree с index (показывает modified/untracked) и index с HEAD (показывает staged). При `git checkout` / `git switch` working tree приводится к состоянию выбранного commit — несохранённые изменения могут быть потеряны.
# Три области:
# working tree -> git add -> index -> git commit -> repository (.git)
echo "DEBUG=1" >> config.py # модификация в working tree
git status # "modified: config.py" в красном
git diff # diff working tree vs indexПромежуточная область между working tree и repository. Физически — бинарный файл `.git/index` (НЕ путать с .gitignore), содержащий список tracked файлов с их SHA blob, timestamps, permissions. Когда вы делаете `git add file.py`, Git создаёт blob object с содержимым file.py и обновляет запись в index. `git commit` фиксирует содержимое index как новый commit. Index позволяет точечно собирать commit из части изменений — `git add -p` интерактивно выбирает hunks. `git reset HEAD <file>` убирает из index, `git restore --staged <file>` (Git 2.23+) — то же самое.
git add file.py # working tree -> index
git add -p # patch mode — выбрать hunks
git restore --staged file # убрать из index (Git 2.23+)
git ls-files --stage # содержимое index
# 100644 9d2f3c... 0 config.pyGit object, представляющий snapshot каталога: список (mode, type, hash, name) для каждого файла и поддиректории. Mode — права доступа (100644 для обычного файла, 100755 для исполняемого, 040000 для дерева). Type — blob (файл) или tree (подкаталог). Hash — SHA того blob/tree. Name — имя файла/каталога. Корневой tree commit описывает структуру всего проекта на момент commit; вложенные trees — поддиректории. Этот рекурсивный механизм позволяет Git эффективно хранить snapshots: неизменённые файлы share один blob, неизменённые поддиректории — один tree.
git cat-file -p HEAD^{tree}
# 100644 blob 9d2f3c... README.md
# 100644 blob 3a8b1e... config.py
# 040000 tree b7c9f4... src
git ls-tree HEAD # то же самое
git ls-tree -r HEAD # рекурсивноGit object, хранящий содержимое одного файла (БЕЗ имени — имя задаётся в parent tree). Имя blob — SHA от содержимого, поэтому два идентичных файла дают один blob (deduplication). Хранение: zlib-сжатый файл в `.git/objects/<первые-2-символа-SHA>/<остальные-38>` (loose object) или внутри packfile. Blob не знает о других blob и о имени — это чисто содержимое. Из-за SHA-content-addressing любое изменение файла даёт новый blob с новым SHA — старый blob продолжает существовать в objects/, пока не запустится `git gc`.
echo 'hello' | git hash-object --stdin
# ce013625030ba8dba906f756967f9e9ca394464a
git hash-object README.md # SHA blob этого файла
git cat-file -p <sha> # содержимое
git cat-file -t <sha> # тип: blob
ls .git/objects/ce/ # 013625030ba...Именованный указатель на конкретный commit, в отличие от ветки — НЕ перемещается. Хранится в `.git/refs/tags/<name>`. Два типа: lightweight tag (просто файл с SHA коммита, без метаданных) и annotated tag (полноценный git object с автором, датой, сообщением и опционально GPG-подписью). Annotated теги рекомендуются для релизов (v1.0.0, v2.4.1) — содержат tagger info и могут быть подписаны для verifiable releases. Lightweight подходит для временных меток. Push тегов происходит отдельно (`git push --tags` или `git push origin v1.0.0`) — обычный push их не передаёт.
# Lightweight
git tag v1.0.0
# Annotated (recommended for releases)
git tag -a v1.0.0 -m "Release 1.0.0"
# Подписанный (требует GPG)
git tag -s v1.0.0 -m "Signed release"
git tag -l # список
git show v1.0.0 # содержимое
git push origin v1.0.0 # push отдельного tagИменованная ссылка на внешний репозиторий (URL + alias). Конфигурация хранится в `.git/config` в секции `[remote "<name>"]`. При `git clone <url>` автоматически создаётся remote `origin`. Можно иметь несколько remotes (например, `origin` — ваш fork, `upstream` — оригинальный репо). `git fetch <remote>` скачивает объекты и обновляет remote-tracking branches (`refs/remotes/<remote>/<branch>`). `git push <remote> <branch>` отправляет ваши commits на сервер. URL может быть HTTPS (требует токен) или SSH (требует key pair).
git remote -v
# origin [email protected]:me/repo.git (fetch)
# origin [email protected]:me/repo.git (push)
git remote add upstream https://github.com/orig/repo.git
git remote set-url origin [email protected]:me/repo.git
git remote rename origin github
git remote remove old-remoteСоглашение об имени remote по умолчанию — alias для удалённого репо, откуда был сделан clone. НЕ магическое слово в Git, а просто конвенция (можно переименовать). При `git push` без аргументов Git ищет remote по конфигу ветки (branch.<name>.remote) — обычно origin. `origin/main`, `origin/feature/x` — это remote-tracking branches: локальные снимки состояния веток на сервере, обновляемые через `git fetch`. Не путать с самой веткой на сервере: `origin/main` — это ваш локальный кэш, может быть устаревшим до следующего fetch.
git clone <url> # автоматически создаёт remote origin
git remote -v # origin <url>
git branch -a # включая origin/*
# * main
# remotes/origin/main
# remotes/origin/feature/x
git fetch origin # обновить origin/* refsДва смысла. (1) Tracking relationship: ветка `feature/x` имеет upstream `origin/feature/x` — Git знает, куда push/pull. Устанавливается через `git push -u origin feature/x` (-u = --set-upstream) или `git branch --set-upstream-to`. (2) Конвенциональное имя remote для оригинального репозитория, когда вы fork: `origin` — ваш fork, `upstream` — original. При работе с fork-based contribution: `git fetch upstream`, `git merge upstream/main` для синхронизации с оригиналом перед PR.
# (1) Upstream branch tracking
git push -u origin feature/x # set upstream
git branch -vv # видно tracking
# * feature/x abc123 [origin/feature/x] msg
# (2) Fork workflow
git remote add upstream https://github.com/original/repo.git
git fetch upstream
git rebase upstream/mainСинтаксис маппинга ссылок между local и remote, формата `[+]<src>:<dst>`. `+` означает forced update (можно перезаписать non-fast-forward). По умолчанию `git push origin main` использует refspec `refs/heads/main:refs/heads/main`. `git fetch origin` использует refspec `+refs/heads/*:refs/remotes/origin/*` — обновляет все ветки в `refs/remotes/origin/`. Используется в `.git/config`: `fetch = +refs/heads/*:refs/remotes/origin/*`. Знание refspec помогает решать нетривиальные сценарии — push в другую ветку (`git push origin local:remote-branch`), delete remote branch (`git push origin :branch`).
# Push local main -> remote main
git push origin main
# равно: git push origin refs/heads/main:refs/heads/main
# Push local feature -> другая ветка на remote
git push origin feature:experimental
# Delete remote branch
git push origin :old-branch
# или: git push origin --delete old-branch
cat .git/config | grep -A2 remoteБлижайший общий предок (LCA — lowest common ancestor) двух веток в DAG коммитов. Это commit, от которого ветки разошлись. Используется как точка отсчёта в three-way merge: Git сравнивает merge base с каждой из веток (HEAD и MERGE_HEAD), вычисляет два набора изменений и пытается объединить. Если у двух веток несколько merge bases (рекурсивный сценарий с criss-cross merge), Git делает recursive merge — рекурсивно мёрджит сами bases. Знание merge base критично для разбора конфликтов — `git merge-base feature main` показывает SHA точки расхождения.
git merge-base feature main
# 3a8c2f1e9d... ← общий предок
# Визуализация:
# o---o feature
# /
# o---o---o main
# ^
# merge-base
git log --oneline 3a8c2f1e..HEAD # коммиты после расхожденияПростейший вид merge: целевая ветка является прямым предком источника, поэтому Git просто перемещает указатель ветки вперёд, без создания merge commit. Возможен, когда target branch не имеет своих коммитов после merge base. Команда `git merge --ff-only` разрешает только такой merge (fail иначе) — полезно в скриптах. `--no-ff` форсирует создание merge commit даже при возможности fast-forward — сохраняет explicit «merged feature X» в истории (политика GitFlow). По умолчанию `git pull` делает fast-forward, если возможно. Trunk-based workflows часто требуют ff-only через protected branches.
# Fast-forward возможен:
# main: o---o
# feature: o---o---o---o
# ^ HEAD feature
#
# git switch main
# git merge feature
# main: o---o---o---o ← указатель просто перемещается
# ^ HEAD main
git merge --ff-only feature # ff или fail
git merge --no-ff feature # всегда merge commitАлгоритм merge для случая, когда обе ветки имеют коммиты после merge base. Git сравнивает три версии: (1) merge base, (2) tip нашей ветки, (3) tip их ветки. Для каждого файла применяется логика: если изменена только с одной стороны — берётся изменённая; если с обеих сторон одинаково — берётся изменённая; если по-разному — conflict. Результат становится новым commit с двумя parents (merge commit). Алгоритм recursive (default) обрабатывает случаи нескольких merge bases. Существуют альтернативы: `-s ours` (берёт нашу версию полностью), `-s theirs` (наоборот), `-X ours/theirs` (только для конфликтных hunks).
git merge feature
# Auto-merging dag.py
# CONFLICT (content): Merge conflict in dag.py
# Automatic merge failed; fix conflicts and then commit
# Стратегии
git merge -s ort feature # default с Git 2.34
git merge -X theirs feature # предпочитать их при conflict
git merge --abort # откатитьПереписывание истории через перенос commits на новую базу. `git rebase main` берёт коммиты текущей ветки после merge base, временно их прячет, перемещает ветку на tip main, и заново применяет каждый коммит. Каждый rebased commit имеет НОВЫЙ SHA — это новый commit, не тот же. Поэтому rebase публичной (расшаренной) ветки катастрофичен: коллеги получат «расходящуюся историю». Interactive rebase (`-i`) позволяет reorder/squash/edit/drop коммиты. Часто используется перед PR для очистки локальной истории. Главное правило: НЕ rebase то, что уже push на remote и есть у коллег.
git switch feature/etl-rewrite
git rebase main # перенести на новый main
# Interactive
git rebase -i HEAD~5
# pick abc1 first commit
# squash def2 second
# reword 9a8b third
# drop 1c2d fourth
git rebase --abort # откатить
git rebase --continue # после resolve conflictЛокальный журнал всех движений HEAD и веток за последние ~90 дней (по умолчанию, настраивается через `gc.reflogExpire`). Хранится в `.git/logs/HEAD` и `.git/logs/refs/heads/<branch>`. Каждая запись: from SHA, to SHA, дата, операция (commit, merge, rebase, reset, checkout). Reflog — машина времени Git: позволяет восстановить «потерянный» коммит после `git reset --hard`, неудачного rebase, удалённой ветки. Reflog НЕ синхронизируется с remote — только локальный. Старые записи удаляются `git gc` после expire. Для commits, не достижимых из веток/тегов, reflog — последний шанс.
git reflog
# HEAD@{0} HEAD@{1} ...
# abc1234 (HEAD -> main) HEAD@{0}: commit: fix
# def5678 HEAD@{1}: reset: moving to HEAD~3
# 9a8b765 HEAD@{2}: commit: feat: csv parser
# Восстановить ветку после reset --hard
git reset --hard HEAD@{1}
# или
git reset --hard 9a8b765Сжатый бинарный файл (`.git/objects/pack/*.pack` + `.idx`), содержащий множество git objects в одном file с delta-compression. После какого-то порога Git собирает loose objects в packfile через `git gc` или автоматически. Pack использует zlib + delta encoding: похожие blobs хранятся как diff относительно базового, экономя место. Index file (`.idx`) даёт быстрый lookup объекта по SHA внутри pack. Когда вы делаете `git push`, Git создаёт временный packfile для эффективной передачи. `git fsck`, `git verify-pack -v <pack>` помогают диагностировать проблемы. Слишком много мелких packs замедляют — `git repack -a -d` объединяет.
ls .git/objects/pack/
# pack-3a8c.idx pack-3a8c.pack
git count-objects -v
# count: 4 ← loose objects
# size: 16 KiB
# in-pack: 12345 ← в packs
# packs: 2
git gc # объединить loose в pack
git repack -a -d # full repackСтруктура коммитов Git — направленный ациклический граф. Каждый commit имеет 0+ parent pointers (root commit — 0, обычный — 1, merge commit — 2+). Направленность: parent -> child. Ацикличность: commit не может быть своим предком, ведь SHA включает parent hash — циклическая зависимость невозможна. Ветки — это указатели на отдельные «листья» DAG. `git log --graph --all --oneline` визуализирует DAG. Merge создаёт vertex с двумя входами, rebase «выпрямляет» граф в линию. Понимание DAG критично для merge/rebase — вы должны видеть в голове «откуда commit, куда идёт».
git log --graph --all --oneline --decorate
# * a3b1c (HEAD -> main) Merge feature
# |\
# | * 8d2f (feature) feat: csv
# | * 7c1a feat: dag
# * | 5e9b fix: typo
# |/
# * 3a8c initialИнициализирует новый Git репозиторий: создаёт каталог `.git/` с подкаталогами (`objects/`, `refs/`, `hooks/`), файлами (`HEAD`, `config`, `description`), пустым index. После init у репо нет коммитов, HEAD указывает на несуществующую ветку (default обычно `main`, настраивается через `init.defaultBranch`). Работает только в каталоге без `.git/` — повторный init безопасен, но обычно ничего не меняет (только обновит hooks template). `git init --bare` создаёт bare repo для сервера (без working tree).
git init # инициализировать current dir
git init my-project # создать и инициализировать
git init --bare project.git # bare (для сервера)
# Default branch — главный config
git config --global init.defaultBranch main
ls -la .git/
# HEAD config description hooks/ info/ objects/ refs/Создаёт локальную копию remote репозитория: инициализирует `.git/`, добавляет remote `origin`, скачивает все объекты, создаёт remote-tracking branches (`refs/remotes/origin/*`), checkout default branch. Опции: `--depth N` (shallow clone — только последние N коммитов, экономит трафик/место для большого репо), `--branch <name>` (clone определённой ветки), `--single-branch` (только одну ветку, без остальных), `--bare`, `--mirror` (полное зеркало для backup). Под капотом — fetch + checkout. Shallow clones имеют ограничения (нельзя push/fetch шибко глубокого, нельзя merge старых коммитов).
git clone https://github.com/user/repo.git
git clone [email protected]:user/repo.git my-dir
git clone --depth 1 <url> # shallow
git clone --branch develop <url> # specific branch
git clone --recurse-submodules <url> # с submodulesДобавляет изменения из working tree в index (staging area), создавая blob objects в `.git/objects/`. После `git add file.py` Git вычисляет SHA содержимого, сохраняет blob и записывает имя файла + SHA + права в `.git/index`. Опции: `-A` или `--all` (включая deletions всего репо), `-u` (только модифицированные/удалённые tracked, БЕЗ untracked), `-p` (patch mode — интерактивный выбор hunks). `git add .` добавляет всё в текущем каталоге, `git add -p` — самая важная команда для clean PR (commit по одной логической части).
git add file.py # один файл
git add src/ # каталог
git add . # текущий каталог
git add -A # весь репо
git add -u # только tracked
git add -p # patch mode
git status # увидеть staged/unstagedСоздаёт новый commit из содержимого index с текущим HEAD как parent. Обновляет ветку, на которую указывает HEAD, на новый commit SHA. Опции: `-m "msg"` (inline сообщение), `-a` (auto-stage tracked файлы, не работает для untracked), `--amend` (заменяет последний commit новым с теми же changes + новыми изменениями — НОВЫЙ SHA), `--no-verify` (skip pre-commit hooks — обычно плохая идея), `--allow-empty` (commit без изменений). Conventional Commits — стандарт сообщений: `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, `test:`. `--amend` нельзя делать после push, если ветка расшарена.
git commit -m "feat: add CSV parser"
git commit -am "fix: typo" # add tracked + commit
git commit --amend # переписать последний
git commit --amend --no-edit # без изменения сообщения
git commit --fixup=<sha> # fixup для autosquashОтправляет commits из локальной ветки на remote и обновляет remote-tracking ref. По умолчанию push возможен только если remote ветка является предком local (fast-forward) — иначе rejected с non-fast-forward. `--force` (или `-f`) перезаписывает remote — опасно, может потерять чужие commits. `--force-with-lease` — безопасный force: push только если remote-tracking ref совпадает с тем, что на сервере (то есть никто не пушил между вашим fetch и push). `-u` или `--set-upstream` устанавливает tracking при первом push. `git push origin :branch` или `--delete branch` удаляет remote ветку. `--tags` отправляет все tags.
git push # на установленный upstream
git push -u origin feature/x # set upstream + push
git push --force-with-lease # безопасный force
git push origin --delete branch # удалить remote
git push origin v1.0.0 # push tag
git push --tags # все tagsСкачивает с remote новые объекты (commits, trees, blobs) и обновляет remote-tracking branches (`refs/remotes/origin/*`). НЕ изменяет working tree, не merge ничего в локальную ветку. После fetch вы можете изучить новые commits (`git log main..origin/main`) перед интеграцией. `git fetch --all` — все remotes. `--prune` (или `-p`) удаляет remote-tracking refs для веток, удалённых на сервере. `--tags` скачивает tags. Безопасная команда — её можно запускать когда угодно без побочных эффектов на ваши изменения.
git fetch # default remote (origin)
git fetch --all # все remotes
git fetch --prune # удалить stale origin/*
git fetch origin main # specific branch
# Что нового на сервере?
git log main..origin/main --onelineКомпозитная команда: `git fetch` + `git merge` (или `rebase` при `pull.rebase=true` или `--rebase`). Скачивает изменения с remote и сразу интегрирует их в текущую ветку. По умолчанию делает fast-forward, если возможно, иначе создаёт merge commit. Опасность: при divergent histories pull создаёт merge commit, замусоривая историю. Многие команды настраивают `pull.rebase = true` (или `pull.ff = only`), чтобы получать линейную историю. Junior часто путает: pull не «обновляет origin/*» отдельно — это всегда fetch+merge на текущую ветку.
git pull # fetch + merge
git pull --rebase # fetch + rebase
git pull --ff-only # fail если не fast-forward
# Глобально
git config --global pull.rebase true
git config --global pull.ff onlyИнтегрирует изменения из одной ветки в другую (текущую). Алгоритм: находит merge base, делает three-way merge. Результат — merge commit с двумя parents (или просто fast-forward без коммита). Опции: `--no-ff` (всегда создаёт merge commit, даже когда возможен fast-forward), `--ff-only` (только fast-forward или fail), `--squash` (применяет изменения как один commit БЕЗ merge commit — теряется связь с feature branch), `--abort` (откатить merge до начала), `-s ort` (default стратегия с Git 2.34), `-X theirs` или `-X ours` (при conflict предпочитать одну сторону).
git switch main
git merge feature/etl-rewrite
# Force merge commit (no fast-forward)
git merge --no-ff feature/etl-rewrite
# Squash
git merge --squash feature/etl-rewrite
git commit -m "feat: ETL rewrite (squashed)"
# Abort
git merge --abortПоказывает состояние working tree и index: что staged (готово к commit), что modified но не staged, что untracked. Также показывает текущую ветку, ahead/behind отношение к upstream, состояние merge/rebase in progress. Опции: `-s` (short format), `-b` (показывать branch info), `--porcelain` (parse-able для скриптов). Junior должен делать `git status` ПОСТОЯННО — перед commit, после pull, в любой непонятной ситуации. Это основной способ ориентироваться в репо.
git status
# On branch feature/etl
# Your branch is ahead of 'origin/feature/etl' by 2 commits.
#
# Changes to be committed:
# new file: dag.py
#
# Changes not staged for commit:
# modified: config.py
#
# Untracked files:
# notes.md
git status -sb # short formatПоказывает историю коммитов начиная с HEAD назад через parents. Опции: `--oneline` (компактный формат), `--graph` (ASCII DAG), `--all` (все ветки, не только текущая), `--decorate` (имена refs у коммитов), `-p` (с patch — diff каждого commit), `--stat` (статистика измененных файлов), `-n N` (последние N коммитов), `--since="2 weeks ago"`, `--author=<name>`, `--grep=<pattern>` (поиск в сообщениях), `<commit1>..<commit2>` (commits в range). Powerful: `git log --graph --oneline --all --decorate` — визуализация DAG всего репо.
git log --oneline -10
git log --graph --all --oneline --decorate
git log --since="1 week ago" --author=Lev
git log --grep="fix"
git log --stat
git log main..feature # коммиты в feature но не в main
git log -p dag.py # история изменений файлаПоказывает изменения между разными состояниями. По умолчанию `git diff` — working tree vs index (unstaged изменения). `git diff --staged` (или `--cached`) — index vs HEAD (что будет в commit). `git diff <commit>` — working tree vs commit. `git diff <c1> <c2>` — между двумя commits. `git diff <branch1>...<branch2>` — three-dot diff: изменения в branch2 от их merge-base. Опции: `--stat` (summary), `--name-only`, `--word-diff`, `--color-words`, `-w` (ignore whitespace). Один из главных tool для self-review перед commit.
git diff # working tree vs index
git diff --staged # index vs HEAD
git diff HEAD~3 HEAD # последние 3 commits
git diff main feature # между ветками
git diff main...feature # three-dot
git diff --stat # только статистика
git diff -- dag.py # один файлСовременная команда (Git 2.23+) для переключения веток — заменяет половину функционала `git checkout`, которая была перегружена. `git switch <branch>` переключает на существующую ветку, обновляя working tree. `-c <name>` создаёт новую ветку. `-` (минус) переключает на предыдущую (как `cd -`). `--detach <commit>` для detached HEAD намеренно. Не работает с restore-related операциями (для них `git restore`). Если есть незакоммиченные изменения в conflict с целевой веткой — switch fail, нужно stash или commit сначала.
git switch main # переключиться на main
git switch -c feature/new # создать и переключиться
git switch - # на предыдущую ветку
git switch --detach 3a8c2f1 # detached HEAD
git switch --discard-changes main # отбросить измененияСовременная команда (Git 2.23+) для восстановления файлов — вторая половина старого `git checkout`. `git restore <file>` восстанавливает файл в working tree из index (отменяет unstaged изменения — ОПАСНО, изменения теряются). `--staged <file>` убирает из index (НЕ из working tree) — теперь не staged, но изменения остаются. `--source=<commit> <file>` восстанавливает из конкретного коммита. `--staged --worktree` — и из index, и из working tree (полный откат).
git restore file.py # отменить unstaged (теряются изменения!)
git restore --staged file.py # убрать из index
git restore --source=HEAD~3 file.py # версия из 3 commits назад
git restore --source=main --staged --worktree file.py # полный откатПеремещает указатель ветки (и опционально index/working tree) на указанный commit. Три режима. `--soft <commit>` — только HEAD/branch, index и working tree не трогает (изменения остаются staged). `--mixed` (default) — HEAD + index, working tree не трогает (изменения unstaged). `--hard` — HEAD + index + working tree, ОПАСНО, изменения теряются. Типичные сценарии: `git reset --soft HEAD~3` (объединить 3 commits в один), `git reset HEAD <file>` (убрать из staging — старый синтаксис, лучше `git restore --staged`), `git reset --hard origin/main` (синхронизировать локальную ветку с remote).
git reset --soft HEAD~3 # отменить 3 commits, оставить changes staged
git reset --mixed HEAD~1 # отменить commit, unstage
git reset --hard HEAD~1 # ОПАСНО — удалить commit и изменения
git reset --hard origin/main # синхронизация с remote
git reset HEAD file.py # unstage (старый синтаксис)Создаёт НОВЫЙ commit, отменяющий изменения указанного commit. В отличие от reset не переписывает историю — старый commit остаётся, появляется новый «inverse». Безопасно для публичных веток. Полезно когда: запушили багу в main, нужно откатить, но reset --hard невозможен (history уже у коллег). `--no-commit` (или `-n`) применяет revert без коммита (можно объединить несколько reverts). Merge commit reverted с `-m 1` (specify parent). Опасность: revert НЕ удаляет commit из истории, только применяет inverse — при reverted-merge новый merge той же ветки НЕ повторит изменения (надо revert revert).
git revert <bad-commit> # создать revert commit
git revert HEAD # revert последний
git revert -n <c1> <c2> <c3> # revert несколько без commit
git revert --abort # откатить in-progress
git revert -m 1 <merge-commit> # revert merge commitВременно прячет незакоммиченные изменения (working tree + index) в стек stashes, восстанавливая чистое состояние. Полезно когда нужно срочно переключиться на другую ветку, не коммитя half-done работу. `git stash push` (или просто `git stash`) — сохранить. `git stash list` — посмотреть stack. `git stash pop` — применить и удалить top (по умолчанию stash@{0}). `git stash apply` — применить без удаления. `-u` (или `--include-untracked`) включает untracked. `-m "msg"` — описание stash. Stashes хранятся локально, не push на remote.
git stash # сохранить changes
git stash push -m "WIP refactor" # с описанием
git stash list # все stashes
# stash@{0}: On main: WIP refactor
# stash@{1}: On feature: WIP test
git stash pop # apply + drop
git stash apply stash@{1} # apply specific
git stash drop stash@{0} # удалить
git stash clear # удалить всеПрименяет изменения из конкретного commit на текущую ветку, создавая новый commit с такими же изменениями (новый SHA, может быть конфликт). Полезно для: переноса hotfix из main в release-ветку, выборочного переноса commits без полной merge ветки. `git cherry-pick <commit>` или несколько: `git cherry-pick <c1>..<c2>` (range, не включает c1) или `git cherry-pick <c1>^..<c2>` (с включением). `-x` добавляет в сообщение ссылку «(cherry picked from commit ...)». `--abort` / `--continue` / `--skip` при конфликте.
git cherry-pick abc1234 # один commit
git cherry-pick abc1234 def5678 # несколько
git cherry-pick abc..def # range (не включая abc)
git cherry-pick abc^..def # range (включая abc)
git cherry-pick -x abc1234 # с reference в сообщении
git cherry-pick --abort # отменаСоздаёт, удаляет, ищет tags. `git tag <name>` — lightweight tag на HEAD. `git tag -a <name> -m "msg"` — annotated. `git tag -s <name>` — GPG-signed. `git tag -l "v1.*"` — поиск по pattern. `git tag -d <name>` — удалить local. `git push origin --delete <tag>` — удалить remote. `git tag <name> <commit>` — tag не HEAD, а другой commit. Tags НЕ передаются автоматически при `git push` — нужно `git push --tags` или `git push origin <tag>`. После tag создан, лучше его не двигать — multiple tags на один commit допустимы.
git tag v1.0.0 # lightweight
git tag -a v1.0.0 -m "Release" # annotated
git tag -l # список
git tag -l "v1.*" # фильтр
git tag -d v1.0.0 # удалить local
git push origin v1.0.0 # push tag
git push origin --delete v1.0.0 # удалить remoteПоказывает, кто и когда последний раз изменил каждую строку файла. Для каждой строки: SHA commit, автор, дата, номер строки, содержимое. Используется чтобы понять контекст «зачем эта строка тут», найти автора для вопроса, отследить когда регрессия попала. Опции: `-L <start>,<end>` (только диапазон строк), `-w` (ignore whitespace changes), `-C` (track copied lines между файлами), `-M` (track moved lines внутри файла). НЕ для shaming — для understanding. На GitHub blame view доступен через UI.
git blame dag.py
# 3a8c2f1 (Lev 2026-03-15) def extract():
# 7b9d4e5 (Alice 2026-04-02) data = read_csv()
git blame -L 10,20 dag.py # только строки 10-20
git blame -w -M dag.py # ignore ws + track moves
git log -p --follow dag.py # альтернатива — история файлаBinary search по истории для поиска commit, в котором был внесён баг. Workflow: `git bisect start` -> `git bisect bad` (текущий commit плохой) -> `git bisect good <commit>` (известный хороший) -> Git чекаутит commit посередине range -> вы тестируете -> `git bisect good` или `bisect bad` -> повтор -> пока не сузится до одного. Для N коммитов нужно log2(N) шагов: для 1024 — всего 10 итераций. `git bisect run <script>` автоматизирует через exit code (0 = good, не-0 = bad). `git bisect reset` возвращает на исходную ветку.
git bisect start
git bisect bad HEAD # сейчас сломано
git bisect good v1.0.0 # неделю назад работало
# Git чекаутит commit в середине
# тестируем...
git bisect good # или bad
# повторяем
git bisect reset # выйти
# Автоматизация
git bisect run pytest tests/test_etl.pyFile system check для репозитория: проверяет integrity объектов, ищет dangling/unreachable objects (не достижимые из refs). Полезно при диагностике corrupted repo или поиске «потерянных» коммитов после reset --hard. `--lost-found` создаёт `.git/lost-found/` с unreachable objects. `--unreachable` показывает объекты, не достижимые из refs. `--full` полная проверка (medlennее). Если fsck находит broken objects — репо повреждён, может потребоваться recovery из remote/backup.
git fsck # базовая проверка
git fsck --full --unreachable # полная + unreachable
git fsck --lost-found # в .git/lost-found/
# Поиск потерянного commit
git fsck --unreachable --no-reflogs | grep commit
# unreachable commit 3a8c2f1...
git show 3a8c2f1 # содержимое
git cherry-pick 3a8c2f1 # вернутьОчищает репозиторий: упаковывает loose objects в packfiles, удаляет unreachable objects старше gc.pruneExpire (default 2 weeks), удаляет stale reflog entries (старше gc.reflogExpire — 90 дней). Запускается автоматически при некоторых командах, если накопилось > gc.auto loose objects (default 6700). `git gc --aggressive` делает более тщательную delta-compression (медленно, для одноразовой оптимизации). `git gc --prune=now` удаляет unreachable немедленно. ВАЖНО: после gc reflog для удалённых ranges потерян — recovery невозможен.
git gc # auto gc
git gc --aggressive --prune=now # тщательная очистка
git count-objects -v # размер до/после
# Отключить auto
git config gc.auto 0
# Удалить unreachable старше 1 hour
git gc --prune=1.hour.agoУдаляет unreachable objects из object database. Обычно вызывается внутри `git gc` — отдельно почти не нужен. `git prune --expire <date>` — указать когда objects считаются «старыми» (default 2 weeks). `git prune --dry-run` — показать что было бы удалено. ВАЖНО: prune навсегда уничтожает объекты — после prune recovery commit через reflog/fsck может стать невозможным. Часто `git remote prune origin` (или `git fetch --prune`) — другое, удаляет stale remote-tracking refs для веток, удалённых на сервере.
git prune --dry-run # что было бы удалено
git prune --expire=now # удалить ВСЁ unreachable
# Удалить stale origin/* refs
git remote prune origin
git fetch --prune # эквивалентСложный workflow от Vincent Driessen (2010), популярный в early-2010s: основные ветки `main` (production) и `develop` (интеграция), supporting branches `feature/*` (из develop, в develop), `release/*` (из develop для подготовки релиза, в main и develop), `hotfix/*` (из main, в main и develop). Подходит для проектов с явными release циклами и parallel versions. Критика: излишне сложен для команд с continuous deployment, генерирует лишние merge commits. Вытесняется trunk-based и GitHub Flow в современных DE-командах.
# Feature
git switch develop
git switch -c feature/csv-loader
# работа...
git switch develop
git merge --no-ff feature/csv-loader
# Release
git switch -c release/1.2.0 develop
# QA, bumps...
git switch main && git merge --no-ff release/1.2.0
git tag -a v1.2.0
git switch develop && git merge --no-ff release/1.2.0Простой workflow от GitHub: одна long-lived ветка `main`, для любого изменения создаётся `feature/*` или `fix/*` ветка, открывается PR, после review и CI green — merge в main. Main всегда deployable. Подходит для команд с continuous deployment, web-проектов, маленьких/средних команд. Pros: простота, минимум церемонии, быстрое intergration. Cons: нет formal release branches (для версионированных libraries это проблема), всё зависит от CI/тестов на PR. Большинство DE-команд используют этот или trunk-based.
git switch main
git pull --rebase
git switch -c feature/parquet-loader
# код...
git commit -m "feat: parquet loader"
git push -u origin feature/parquet-loader
# открыть PR на GitHub -> review -> CI -> merge
git switch main
git pull
git branch -d feature/parquet-loaderWorkflow, где все разработчики коммитят в одну ветку (trunk, обычно `main`) часто — несколько раз в день. Feature branches очень короткоживущие (< 1 дня) или отсутствуют (коммиты сразу в trunk через pair programming). Использует feature flags для скрытия незаконченных функций. Требует strong CI и pre-commit testing. Pros: минимальная divergence, нет painful merges, fast integration. Cons: нужны feature flags infrastructure, строгая дисциплина. Распространён в Google, Facebook, продвинутых DevOps командах.
# Все работают в main, feature flags скрывают unfinished
git switch main
git pull --rebase
# короткая работа, частые коммиты
git commit -m "feat: parquet loader behind flag"
git push
# Feature flag в коде:
if feature_enabled("parquet_loader"):
parquet_load(...)
else:
legacy_load(...)Запрос на интеграцию изменений из одной ветки в другую — формат collaboration на GitHub (Pull Request) и GitLab (Merge Request). PR содержит: title, description, базовая и source branch, список commits, diff, обсуждение, reviewers, статус CI, labels. PR — основной инструмент code review: коллеги читают diff, оставляют comments на конкретные строки, suggested changes, approve/request changes. После approve и passing CI — merge (button on UI). PR может быть Draft (work in progress, не готов к review). PR не существует в Git core — это feature хостинга (GitHub/GitLab/Bitbucket).
# Открыть PR через GitHub CLI
gh pr create --base main --head feature/parquet \
--title "feat: parquet loader" \
--body "Adds support for reading .parquet files..."
gh pr list
gh pr view 42
gh pr review 42 --approve
gh pr merge 42 --squashПроцесс асинхронного просмотра кода коллегами перед merge. На GitHub/GitLab выполняется через PR/MR: reviewer оставляет inline comments на строки diff, может приложить suggested changes (одной кнопкой принимаются автором), отмечает Approve / Request changes / Comment. Цели: ловля багов до prod, sharing знаний, поддержание code style, mentoring junior. Best practices: small PRs (< 400 строк), clear description, atomic commits, ответы на каждый comment, не merge без approve. Junior DE — обязательная часть работы и обучения.
gh pr review 42 --comment -b "LGTM with minor nits"
gh pr review 42 --approve
gh pr review 42 --request-changes -b "see comments"
# Suggested change на GitHub
# ```suggestion
# new_code_line
# ```Merge стратегия: все commits из feature branch объединяются в один commit, который применяется к target branch как single change. Использует `git merge --squash` под капотом + commit. Pros: чистая линейная история на main, каждый PR — один commit, легко revert. Cons: теряются intermediate commits (детали разработки), теряется feature branch как entity в DAG. На GitHub: «Squash and merge» button в PR. Часто комбинируется с branch protection rule «require linear history». Default для многих DE-команд с GitHub Flow.
# Локально
git switch main
git merge --squash feature/parquet
git commit -m "feat: parquet loader (#42)"
# На GitHub
gh pr merge 42 --squash --delete-branch
# Settings -> Pull Requests -> Allow squash mergingMerge стратегия: commits из feature branch rebased на tip target branch, потом fast-forward applied — без merge commit. Сохраняет все commits feature ветки (в отличие от squash), но даёт линейную историю (в отличие от merge commit). Pros: detailed history без merge commits, atomic commits сохраняются. Cons: каждый rebased commit получает новый SHA, поэтому если кто-то clonёд branch — проблемы; работает с force-push, что добавляет risk. На GitHub: «Rebase and merge» button. Используется в командах, ценящих atomic commits как research/debug инструмент.
# Локально
git switch feature/parquet
git rebase main
git switch main
git merge --ff-only feature/parquet
# На GitHub
gh pr merge 42 --rebase --delete-branchCI/CD платформа, интегрированная в GitHub. Workflows определяются YAML-файлами в `.github/workflows/*.yml`, запускаются на events (push, pull_request, schedule, manual). Workflow содержит jobs, jobs содержат steps. Каждая job выполняется на runner (Linux/Windows/macOS, ubuntu-latest самый частый). Marketplace actions (например `actions/checkout`, `actions/setup-python`) — переиспользуемые блоки. Secrets хранятся в repo/org settings, доступны через `${{ secrets.NAME }}`. Билтины: matrix builds, conditional jobs, dependent jobs, artifacts, caching. Бесплатно для public репо, limit minutes для private.
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install -r requirements.txt
- run: pytestНастройка GitHub/GitLab, ограничивающая прямые push в защищённую ветку (обычно main). Возможные ограничения: require PR before merging, require N approvals, require passing status checks (CI), require up-to-date branch, require signed commits, prevent force push, prevent deletion, restrict who can push. Конфигурируется на Settings -> Branches -> Add rule. Обязательная практика для production кода — даже maintainer не должен push в main напрямую. С 2023 на GitHub также Rulesets — более гибкая альтернатива rules.
# Github UI: Settings -> Branches -> Add rule
# Branch name pattern: main
# [x] Require a pull request before merging
# [x] Require approvals: 1
# [x] Require status checks to pass before merging
# - test (required)
# - lint (required)
# [x] Require linear history
# [x] Do not allow bypassing the above settingsРезультат проверки от CI/external service, прикреплённый к commit. На GitHub status check может быть от GitHub Actions job, external CI (CircleCI, Jenkins), bot (Codecov, Dependabot). Каждый check имеет status (pending, success, failure, error) и описание. Branch protection rule может требовать определённые checks pass, прежде чем merge разрешён. Required checks указываются по имени job из workflow или name external app. Important: required check должен запускаться на PR, иначе он будет «pending» вечно — обычно настраивается на `pull_request` event.
# В branch protection указаны required checks:
# - test
# - lint
# - codecov/project
# Просмотр в PR:
# All checks have passed
# 3 successful checks
# - test (GitHub Actions)
# - lint (GitHub Actions)
# - codecov/project (Codecov)
gh pr checks 42Open-source инструмент для сканирования репо на наличие secrets (API keys, passwords, tokens, AWS credentials) в коде и истории. Использует regex patterns для известных форматов (AWS access key, GitHub token, Stripe key, etc) и Shannon entropy для random strings. Запускается локально (`gitleaks detect`), как pre-commit hook (`gitleaks protect --staged`), или в CI (GitHub Actions). Поддерживает `.gitleaks.toml` для кастомных rules и whitelist. Важно: даже если secret удалён в новом commit, он остаётся в истории — gitleaks сканирует ВСЮ историю по умолчанию.
# Локальный scan
gitleaks detect --source . --verbose
# Pre-commit
gitleaks protect --staged
# CI
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# .gitleaks.toml
[allowlist]
regexes = ['fake-test-key']Инструмент для быстрой очистки больших/чувствительных файлов из истории Git. Java-based, в 10–720x быстрее `git filter-branch`. Use cases: удалить случайно закоммиченный 2GB CSV из истории, заменить пароль в каждом commit на `***REMOVED***`. Команды: `bfg --delete-files <name>`, `bfg --strip-blobs-bigger-than 50M`, `bfg --replace-text passwords.txt`. После BFG обязательно `git reflog expire --expire=now --all && git gc --prune=now --aggressive`. Важно: HEAD commit не трогается — то, что в latest commit, надо удалить и commit отдельно ДО запуска BFG.
# Удалить большой файл из истории
bfg --strip-blobs-bigger-than 50M
# Удалить конкретный файл
bfg --delete-files secrets.txt
# Заменить текст
bfg --replace-text passwords.txt
# Cleanup после
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --forceСовременная замена устаревшего `git filter-branch` (последний 100x медленнее и имеет проблемы). Python-based tool для rewriting git history: удаление файлов из истории, переименование, изменение author/email, splitting/joining repos. Recommended by Git core team. Аналог BFG для большинства задач, но более гибкий. Установка: `pip install git-filter-repo`. После filter-repo Git refuses push на remote — нужно push --force, и все коллеги должны re-clone. Опции: `--invert-paths --path secrets.txt` (удалить только этот файл), `--strip-blobs-bigger-than 10M`, `--mailmap` (изменить email).
pip install git-filter-repo
# Удалить файл из всей истории
git filter-repo --invert-paths --path .env
# Удалить большие файлы
git filter-repo --strip-blobs-bigger-than 10M
# Изменить email во всех коммитах
git filter-repo --mailmap mailmap.txt
git push origin --force --allПара ключей (private + public) для аутентификации с remote git server без пароля. Private key (`~/.ssh/id_ed25519`) НИКОГДА не share — хранится на машине, опционально с passphrase. Public key (`~/.ssh/id_ed25519.pub`) — добавляется в GitHub/GitLab account settings. SSH URL начинается с `[email protected]:` (не `https://`). Recommended: ed25519 (быстрее, безопаснее RSA), passphrase + ssh-agent (для удобства). Проверка: `ssh -T [email protected]`. Switch repo URL: `git remote set-url origin [email protected]:user/repo.git`.
# Генерация ключа
ssh-keygen -t ed25519 -C "[email protected]"
# Добавить в ssh-agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Public key в GitHub
cat ~/.ssh/id_ed25519.pub
# скопировать в GitHub -> Settings -> SSH keys
# Проверка
ssh -T [email protected]
# Hi user! You've successfully authenticated...Криптографическая подпись commits/tags через GPG (или SSH с Git 2.34+, или Sigstore). Доказывает, что commit действительно создан владельцем приватного ключа. На GitHub signed commits отображаются как «Verified» badge. Setup: создать GPG key (`gpg --full-generate-key`), добавить public key в GitHub, настроить Git (`user.signingkey`, `commit.gpgsign true`). Подпись commits: `git commit -S -m "msg"` (или auto с config). Tags: `git tag -s v1.0.0`. Проверка: `git log --show-signature`. Branch protection rule «require signed commits» форсирует это для всей команды.
# Setup
gpg --full-generate-key
gpg --list-secret-keys --keyid-format=long
# sec rsa4096/3AA5C34371567BD2
git config --global user.signingkey 3AA5C34371567BD2
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git commit -S -m "feat: signed"
git log --show-signatureExtension для Git, хранящий большие файлы (binaries, datasets, models) во внешнем storage, а в git — только pointer-файлы. Решает проблему распухания репо при коммите больших файлов. Workflow: `git lfs install` (one-time setup), `git lfs track "*.parquet"` создаёт запись в `.gitattributes`, дальше `git add` для tracked файлов кладёт указатель в репо и сам файл в LFS storage. Pros: small repo, fast clone. Cons: требует поддержки на server (GitHub/GitLab), отдельный bandwidth/storage (на GitHub — quotas), не подходит для частых random access (DVC лучше для ML datasets).
git lfs install # one-time
git lfs track "*.parquet" # обновляет .gitattributes
git add .gitattributes
git add data.parquet # -> LFS storage
git commit -m "data: add parquet"
git push # push LFS + pointer
git lfs ls-files # tracked LFS filesPython tool для управления pre-commit hooks (НЕ Git built-in pre-commit hook, а framework сверху). Конфигурируется в `.pre-commit-config.yaml` — список hooks (ruff, black, isort, sqlfluff, detect-secrets, yamllint, etc) с версиями. Установка: `pip install pre-commit`, потом `pre-commit install` создаёт `.git/hooks/pre-commit`, запускающий framework. На каждый `git commit` запускаются настроенные checks; если fail — commit блокируется. CI integration через pre-commit.ci (auto-PR on update) или GitHub Actions. Стандарт для современных Python/DE проектов.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
pip install pre-commit
pre-commit install
pre-commit run --all-filesФайл, описывающий, какие пути Git должен игнорировать (не tracking, не показывать в status, не add при `git add .`). Поддерживает glob patterns: `*.log`, `__pycache__/`, `node_modules/`, отрицание `!`, anchored patterns начинающиеся с `/`. Hierarchical: `.gitignore` в любом каталоге применяется к этому каталогу и вниз. Глобальный `~/.gitignore_global` для OS-specific (`.DS_Store`, `Thumbs.db`), per-user IDE (`.idea/`). ВАЖНО: gitignore работает только для untracked файлов; если файл уже в index, нужно `git rm --cached <file>` сначала.
# .gitignore (Python проект)
__pycache__/
*.pyc
.venv/
.env
.env.*
!.env.example
*.log
.DS_Store
.idea/
.vscode/
dist/
build/
*.egg-info/
# Глобальный
git config --global core.excludesfile ~/.gitignore_globalФайл, задающий атрибуты файлам для Git: line endings (text/binary/eol), diff/merge drivers, LFS tracking, export-ignore (для archives), filter operations. Решает Windows/Unix CRLF проблемы (`* text=auto`), кастомные diff для JSON/CSV (`*.json diff=json`), маркирует LFS-файлы (`*.parquet filter=lfs`). Pattern syntax как в .gitignore. Хранится в репо (`.gitattributes`) или в каталоге, или глобально (`~/.config/git/attributes` + `core.attributesFile`). Часто забывается, но критичен для cross-platform DE-команд.
# .gitattributes
* text=auto eol=lf
*.sh text eol=lf
*.py text eol=lf
*.png binary
# LFS
*.parquet filter=lfs diff=lfs merge=lfs -text
*.csv filter=lfs diff=lfs merge=lfs -text
# Кастомный diff для notebooks
*.ipynb diff=jupyternotebook