Практикум: твой первый день с Git от нуля до push

Рейтинг: 57.8% · 13 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Практикум: твой первый день с Git от нуля до push

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push (вы здесь)
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Зачем этот урок и какую боль он закрывает

Ты прошёл уроки 0-11. По отдельности всё понятно: коммит, индекс, ветка, удалёнка. Но в голове это лежит как куча запчастей, а не как собранный мотоцикл. Самая частая боль новичка звучит так: "вроде каждую команду знаю, а сесть и сделать реальный проект от пустой папки до push на GitHub не могу - теряюсь, в каком порядке что нажимать". Этот урок ровно про это. Никакой новой теории. Только руки. Мы пройдём один сквозной мини-проект end-to-end: создадим папку, проинициализируем репозиторий, настроим себя, наделаем осмысленных коммитов, заведём ветку фичи, сольём её обратно, выложим всё на GitHub и подтянем правку, сделанную через веб. Это точка сборки между фундаментом и продвинутыми темами - после неё git с нуля перестанет быть страшным.

Держи в голове одну модель из прошлых уроков, она склеивает всё остальное. У Git три состояния файла: рабочая директория (где ты редактируешь), индекс/staging (что попадёт в следующий коммит) и история (зафиксированные коммиты). Команда

Код: Выделить всё

git status
показывает разницу между этими тремя деревьями, а

Код: Выделить всё

git log --oneline --graph
показывает саму историю как граф. На каждом шаге практикума мы будем смотреть в обе - чтобы три состояния и граф коммитов "осели" не как текст в учебнике, а как мышечная память. Легенда графа всюду одна: узел - это коммит, стрелка - связь коммит к родителю, ярлык вроде main или HEAD - это указатель-ветка.

Изображение

Шаг 1-3: git init, identity и первый коммит README

Создаём папку проекта и инициализируем репозиторий. Это база любого туториала git и любого первого проекта git.

Код: Выделить всё

mkdir todo-cli && cd todo-cli
git init -b main
Флаг важен. В ванильном Git образца 2026 года

Код: Выделить всё

git init
без него всё ещё создаёт ветку master и печатает подсказку про init.defaultBranch - встроенным дефолтом main станет только в Git 3.0 (ожидается к концу 2026). Так что либо задаём имя явно через , либо ставим один раз глобально:

Код: Выделить всё

git config --global init.defaultBranch main
. Смотрим, что получилось:

Код: Выделить всё

$ git status
On branch main

No commits yet

nothing to commit (create README to get started)
"On branch main" - ветка есть. "No commits yet" - истории пока ноль, ветка main указывает в пустоту (она родится вместе с первым коммитом). Внутри появилась скрытая папка - там объекты, refs, index, весь служебный механизм из прошлых уроков. Удалишь её - проект снова станет обычной папкой без истории.

Теперь identity. Git подписывает каждый коммит автором, и без настройки он либо угадает кривое имя из системы, либо откажется коммитить. Настраиваем один раз глобально (для всех репозиториев):

Код: Выделить всё

git config --global user.name "Ivan Petrov"
git config --global user.email "ivan@example.com"
Email должен совпадать с тем, что привязан к аккаунту на форже - тогда коммиты на GitHub приклеятся к твоему профилю. Создаём README и делаем первый коммит:

Код: Выделить всё

$ printf '# todo-cli\n\nПростой список дел в терминале.\n' > README.md
$ git add README.md
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   README.md

$ git commit -m "Первый коммит: README с описанием проекта"
[main (root-commit) a1b2c3d] Первый коммит: README с описанием проекта
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
Разбираем вывод. перенёс файл из рабочей директории в индекс - в status он уехал в секцию "Changes to be committed".

Код: Выделить всё

git commit
сделал из индекса снимок-коммит. Слово root-commit значит, что у него нет родителя - это самый первый узел графа. - короткий хеш этого коммита (у тебя он будет другой). На графе теперь один узел, и main с HEAD на него показывают.

Шаг 4-6: .gitignore и осмысленные коммиты через git add -p

Прежде чем плодить файлы - .gitignore. Это список шаблонов, которые Git будет игнорировать: мусор сборки, секреты, локальные настройки. Правило простое: лучше завести его сразу, чем потом вычищать случайно закоммиченный пароль.

Код: Выделить всё

$ printf 'node_modules/\n*.log\n.env\n.DS_Store\n' > .gitignore
$ git add .gitignore
$ git commit -m "Добавил .gitignore для мусора и секретов"
Проверим, что он работает - создадим файл, который должен игнорироваться:

Код: Выделить всё

$ touch debug.log
$ git status
On branch main
nothing to commit, working tree clean
debug.log не виден для Git - шаблон его проглотил. "working tree clean" значит, что все три дерева совпадают: рабочая директория, индекс и последний коммит идентичны. Это эталонное чистое состояние, к нему стоит возвращать репозиторий перед сменой ветки.

Теперь напишем код и познакомимся с приёмом, который отличает аккуратного человека от того, кто валит всё в один коммит

Код: Выделить всё

git add . && git commit -m "stuff"
. Создадим файл с двумя логически разными кусками:

Код: Выделить всё

$ cat > todo.py <<'EOF'
def add_task(tasks, text):
    tasks.append({"text": text, "done": False})

def list_tasks(tasks):
    for i, t in enumerate(tasks):
        mark = "x" if t["done"] else " "
        print(f"[{mark}] {i}: {t['text']}")
EOF
Здесь две независимые функции - добавление и вывод. Логично разнести их по двум коммитам, чтобы история читалась. Помогает

Код: Выделить всё

git add -p
(patch) - интерактивный режим, где Git показывает изменения кусками (hunks) и спрашивает по каждому, добавлять его или нет:

Код: Выделить всё

$ git add -p todo.py
@@ -0,0 +1,8 @@
+def add_task(tasks, text):
+    tasks.append({"text": text, "done": False})
+
+def list_tasks(tasks):
...
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]?
Git собрал обе функции в один hunk, потому что между ними нет неизменного "якоря". Жмём s (split) - Git попробует разбить hunk на более мелкие; если соседние строки мешают, жмём e (edit) и руками оставляем в патче только строки add_task, удаляя из патча вторую функцию (строки с минусом в начале трогать нельзя, лишние плюсы превращаем в контекст или вырезаем). Подсказку по буквам всегда даёт ?. Закоммитили первую функцию:

Код: Выделить всё

$ git commit -m "Реализовал add_task: добавление задачи в список"
$ git add todo.py
$ git commit -m "Реализовал list_tasks: вывод задач с отметкой"
$ git log --oneline --graph
* e5f6a7b (HEAD -> main) Реализовал list_tasks: вывод задач с отметкой
* d4e5f6a Реализовал add_task: добавление задачи в список
* c3d4e5f Добавил .gitignore для мусора и секретов
* a1b2c3d Первый коммит: README с описанием проекта
Вот он, граф - прямая линия из четырёх узлов снизу вверх (старые внизу).

Код: Выделить всё

HEAD -> main
на вершине: HEAD показывает на ветку main, а main - на последний коммит. Каждый коммит стрелкой связан с родителем под ним. Так выглядит здоровая линейная история, где каждый коммит делает ровно одну осмысленную вещь.

Шаг 7-9: ветка фичи через git switch -c и слияние git merge

Новую функциональность делают в отдельной ветке, чтобы main всегда оставался рабочим. Это сердце того, как работать с git в команде. Создаём ветку и сразу переключаемся на неё одной командой:

Код: Выделить всё

$ git switch -c feature/done
Switched to a new branch 'feature/done'

Код: Выделить всё

git switch
и

Код: Выделить всё

git restore
вышли из экспериментального статуса и стали стабильными с Git 2.51 - сейчас это рекомендованный способ переключения веток. Старый

Код: Выделить всё

git checkout -b feature/done
делает то же самое и его полно в интернете, так что знать обе формы полезно, но по умолчанию учись на switch. Флаг значит create. Важное про граф: новая ветка не копирует коммиты, она просто новый ярлык, указывающий на тот же коммит, где ты стоял. Сейчас main и feature/done показывают в один узел e5f6a7b, отличается только HEAD - он переехал на feature/done.

Добавим функцию отметки задачи выполненной и закоммитим:

Код: Выделить всё

$ cat >> todo.py <<'EOF'

def mark_done(tasks, index):
    tasks[index]["done"] = True
EOF
$ git add todo.py
$ git commit -m "Добавил mark_done: отметка задачи выполненной"
$ git log --oneline --graph --all
* 1a2b3c4 (HEAD -> feature/done) Добавил mark_done: отметка задачи выполненной
* e5f6a7b (main) Реализовал list_tasks: вывод задач с отметкой
* d4e5f6a Реализовал add_task: добавление задачи в список
...
Флаг показывает все ветки сразу. Видно расхождение: feature/done ушла вперёд на один коммит, main осталась на e5f6a7b. Теперь сливаем. Возвращаемся на main и подтягиваем фичу:

Код: Выделить всё

$ git switch main
Switched to branch 'main'
$ git merge feature/done
Updating e5f6a7b..1a2b3c4
Fast-forward
 todo.py | 3 +++
 1 file changed, 3 insertions(+)
Ключевое слово - Fast-forward. Поскольку main никуда не двигалась с момента ответвления, Git не делал отдельный коммит-слияние, а просто передвинул ярлык main вперёд на тот же коммит, куда показывает feature/done. История осталась линейной. Если бы main за это время получила свои коммиты, Git создал бы merge-коммит с двумя родителями (это уже урок про слияния). Удаляем отработавшую ветку - ярлык больше не нужен, коммиты-то остались в main:

Код: Выделить всё

$ git branch -d feature/done
Deleted branch feature/done (was 1a2b3c4).
Шаг 10-12: GitHub, git remote add, git push -u и git pull

Локально всё готово - выкладываем на форж. Это финальная цель: git github пример end-to-end. На github.com создай новый репозиторий с именем todo-cli, без галок "Add README" и "Add .gitignore" - они создадут лишние коммиты, и push упрётся в конфликт историй. Нужен пустой репозиторий. GitHub покажет URL; берём HTTPS-вариант. Привязываем удалёнку и пушим:

Код: Выделить всё

$ git remote add origin https://github.com/ivanpetrov/todo-cli.git
$ git remote -v
origin  https://github.com/ivanpetrov/todo-cli.git (fetch)
origin  https://github.com/ivanpetrov/todo-cli.git (push)
$ git push -u origin main
Enumerating objects: 12, done.
...
To https://github.com/ivanpetrov/todo-cli.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.
- это просто короткое локальное имя для длинного URL, удобная кличка удалёнки. Флаг (это

Код: Выделить всё

--set-upstream
) делает важную вещь: связывает локальную main с веткой

Код: Выделить всё

origin/main
на сервере. После этого можно писать просто

Код: Выделить всё

git push
и

Код: Выделить всё

git pull
без аргументов - Git помнит, куда. Делать нужно один раз. Если хочешь, чтобы Git сам заводил upstream при первом push любой новой ветки, включи

Код: Выделить всё

git config --global push.autoSetupRemote true
- опция есть с 2.37, но по умолчанию в 2026 она выключена. Смотрим, как изменился граф:

Код: Выделить всё

$ git log --oneline --graph
* 1a2b3c4 (HEAD -> main, origin/main) Добавил mark_done: отметка задачи выполненной
...
Рядом с main появился

Код: Выделить всё

origin/main
- это remote-tracking ветка, локальная "фотография" того, где сервер был на момент последней связи. Сейчас локальная и серверная сошлись в один узел.

Теперь обратное направление - правка через веб и

Код: Выделить всё

git pull
. Зайди на GitHub, открой README.md, нажми карандаш, допиши строчку "## Установка" и нажми Commit. Этот коммит появился на сервере, но локально его нет - твой origin/main устарел. Тянем:

Код: Выделить всё

$ git pull
remote: Enumerating objects: 5, done.
...
Updating 1a2b3c4..9f8e7d6
Fast-forward
 README.md | 2 ++
 1 file changed, 2 insertions(+)

Код: Выделить всё

git pull
- это

Код: Выделить всё

git fetch
(скачать новые коммиты с сервера) плюс

Код: Выделить всё

git merge
(влить их в текущую ветку). Снова Fast-forward: локальная main просто доехала до серверного коммита, потому что локально мы ничего нового не делали. Круг замкнулся: ты создал проект с нуля, прошёл все три состояния руками, сделал историю, ветку, слияние и полный обмен с форжем в обе стороны.

Типичные грабли первого дня
  • Забыл настроить user.email или указал не тот. Коммиты на GitHub не приклеятся к профилю, будут висеть "ничьи". Лечится: поставь правильный email и для будущих коммитов это сработает само; старые при необходимости переписывают отдельно.
  • Создал репозиторий на GitHub с README. Тогда

    Код: Выделить всё

    git push
    упрётся в "Updates were rejected" - на сервере уже есть коммит, которого нет у тебя. Для самого первого раза проще пересоздать пустым; общий путь - сделать

    Код: Выделить всё

    git pull --rebase origin main
    и потом push.
  • Свалил всё в

    Код: Выделить всё

    git add .
    до .gitignore.[/b] В индекс уедут node_modules, .env, .DS_Store. Убрать из индекса, не трогая файл на диске:

    Код: Выделить всё

    git rm --cached -r node_modules
    , потом дописать .gitignore и закоммитить.
  • Боишься switch/checkout с незакоммиченными правками. Если правки конфликтуют с целевой веткой, Git сам откажется переключаться и предупредит - данные он не теряет. Перед сменой ветки доводи дерево до "working tree clean" или прячь в stash.
  • Запутался master или main. Если

    Код: Выделить всё

    git init
    без дал тебе master, а на GitHub ждёшь main - переименуй локально:

    Код: Выделить всё

    git branch -m main
    , и пушь

    Код: Выделить всё

    git push -u origin main
    .
Мини-лаба: повтори всё руками за 15 минут

Сделай это, не подглядывая в текст выше - именно повтор без подсказки закрепляет.
Контрольные вопросы
  • Чем отличается от

    Код: Выделить всё

    git commit
    в терминах трёх деревьев - что куда перемещается?
  • Почему

    Код: Выделить всё

    git switch -c feature
    не копирует коммиты, и что на самом деле создаёт новую ветку?
  • Что значит Fast-forward при merge и при каком условии вместо него появился бы merge-коммит с двумя родителями?
  • Что конкретно делает флаг в

    Код: Выделить всё

    git push -u origin main
    и почему его достаточно сделать один раз?
Итог

Ты собрал весь фундамент в один рабочий навык: пустая папка - init - identity - коммиты - ветка - merge - GitHub - push - pull. Три состояния и граф ты теперь не зубришь, а видишь после каждой команды через status и log --graph. Это и есть тот самый первый день с git для начинающих, после которого инструмент перестаёт пугать. Дальше курс идёт вглубь - слияния с конфликтами, rebase, объектная модель - но точка опоры у тебя уже есть. Если шаг не сошёлся, не гадай: запусти

Код: Выделить всё

git status
, он почти всегда подсказывает следующую команду прямо в выводе.
👍3 ❤️5 🔥1 😄 🤔3
Аватара пользователя
simeonl
Сообщения: 1
Зарегистрирован: 25 май 2026, 18:18

Re: Практикум: твой первый день с Git от нуля до push

Сообщение simeonl »

Наконец-то всё вместе, а не по кускам. Я раньше реально терялся в каком порядке init и add делать, а тут прошёл по шагам и собралось. Спасибо!
👍 ❤️ 🔥1 😄 🤔
Аватара пользователя
admin_kolya
Сообщения: 1
Зарегистрирован: 26 май 2026, 10:54

Re: Практикум: твой первый день с Git от нуля до push

Сообщение admin_kolya »

А если я на GitHub случайно создал репо с README - проще удалить и заново пустым, или всё-таки pull --rebase делать? У меня как раз rejected вылез.
👍1 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Конфликты слияния: анатомия и уверенное разрешение
Следующая глава →
Удалённые репозитории: remote, fetch, базовый pull и refspec

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git push и git pull: отправка и получение измененийgit remote: удалённые репозитории

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя