Представь ситуацию: ты пишешь проект несколько недель. В какой-то момент добавляешь новую функцию — и что-то ломается. Ты не помнишь что именно менял. Откатиться некуда. Резервной копии нет.
Именно для таких ситуаций существует Git — система контроля версий. Она запоминает каждое изменение в проекте: что изменилось, когда и зачем. В любой момент можно вернуться к рабочей версии, посмотреть историю изменений или работать над несколькими задачами параллельно — не боясь сломать то, что уже работает.
В этом уроке мы познакомимся с Git на примере Боба — разработчика, который начинает новый проект и хочет с самого начала организовать работу правильно.
Git — это программа, которая работает локально на твоём компьютере и отслеживает изменения в файлах проекта.
Несколько ключевых моментов:
- Git не требует интернета для базовой работы — всё хранится на твоём компьютере
- Git работает с любыми текстовыми файлами: Python, SQL, Markdown, конфиги
- Git не следит за бинарными файлами (картинки, видео,
.exe) — для этого есть другие инструменты - Git не занимается синхронизацией между компьютерами сам по себе — для этого используют GitHub (рассмотрим в следующем уроке)
- Перейди на git-scm.com и скачай установщик
- Запусти установщик. На вопросе про редактор по умолчанию выбери Nano или VS Code — с Vim новичку будет сложно
- Все остальные настройки оставь по умолчанию
- После установки откроется программа Git Bash — это терминал, в котором мы будем работать
Важно для Windows: мы будем работать в Git Bash, а не в стандартном
cmdили PowerShell. Git Bash понимает Unix-команды и работает одинаково на Windows и Mac.
На Mac Git часто уже установлен. Проверь:
git --versionЕсли Git не установлен, Mac предложит установить Command Line Tools — соглашайся. Либо установи через Homebrew:
brew install gitПосле установки открой терминал (Git Bash на Windows, Terminal на Mac) и выполни:
git --versionТы должен увидеть что-то вроде:
git version 2.43.0
Git управляется через терминал. Прежде чем идти дальше — несколько команд, без которых не обойтись:
| Команда | Что делает |
|---|---|
pwd |
Показывает текущую папку (где ты сейчас находишься) |
ls |
Список файлов в текущей папке |
cd название_папки |
Войти в папку |
cd .. |
Выйти на уровень выше |
mkdir название |
Создать новую папку |
Windows (cmd/PowerShell): вместо
lsиспользуйdir. В Git Bash работает иls.
Пример навигации:
pwd
# /Users/bob
cd Documents
pwd
# /Users/bob/Documents
mkdir tasks-api
cd tasks-api
pwd
# /Users/bob/Documents/tasks-apiЭти команды нужны только для навигации. Всё остальное — уже Git.
Перед первым использованием Git нужно представиться — указать имя и email. Эти данные будут записываться в каждый коммит.
git config --global user.name "Bob"
git config --global user.email "bob@example.com"Проверить настройки:
git config --listФлаг --global означает что настройка применится ко всем проектам на этом компьютере. Сделать это нужно один раз.
Боб — Python-разработчик. Он хочет написать простой REST API для управления задачами. Проект небольшой: одна сущность Task, четыре маршрута. Боб уже работал с FastAPI, поэтому выбор фреймворка очевиден.
Боб понимает: проект будет развиваться. Он хочет с самого начала работать правильно — чтобы в истории коммитов было видно как проект рос, а не один коммит «всё сразу».
tasks-api/
├── app/
│ ├── __init__.py
│ ├── main.py
│ └── models.py
├── .gitignore
├── README.md
└── requirements.txt
Боб создаёт папку проекта и инициализирует Git-репозиторий:
mkdir tasks-api
cd tasks-api
git initВывод:
Initialized empty Git repository in /Users/bob/Documents/tasks-api/.git/
Git создал скрытую папку .git — именно в ней хранится вся история проекта. Трогать её вручную не нужно.
Проверим статус:
git statusOn branch main
No commits yet
nothing to commit (create/copy files and start working)
Репозиторий создан, но он пустой. Пора добавлять файлы.
Боб не начинает сразу с кода. Сначала — файлы конфигурации. Это хорошая практика.
.gitignore — файл, который говорит Git какие файлы не нужно отслеживать. В Python-проектах это:
__pycache__/— кэш байткода Python.venv/илиvenv/— виртуальное окружение (у каждого разработчика своё).env— файл с секретами (пароли, API-ключи).DS_Store— служебный файл macOS*.pyc— скомпилированные файлы Python
Боб создаёт файл .gitignore:
# Python
__pycache__/
*.py[cod]
*.pyo
.venv/
venv/
env/
# Переменные окружения
.env
.env.local
# IDE
.vscode/
.idea/
# macOS
.DS_Store
# Логи
*.log
Почему .gitignore важен: если случайно закоммитить
.envс паролями и отправить на GitHub — это утечка данных..gitignoreзащищает от таких ошибок.
fastapi==0.110.0
uvicorn==0.27.1
git statusOn branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
requirements.txt
nothing added to commit but untracked files present (use "git add" to include in what will be committed)
Git видит новые файлы, но ещё не отслеживает их. Они в статусе Untracked.
Прежде чем делать первый коммит — важно понять модель Git:
Рабочая директория → Staging area → История (репозиторий)
(твои файлы) (git add) (git commit)
Рабочая директория — обычные файлы на диске. Ты их редактируешь.
Staging area (индекс) — промежуточная зона. Сюда ты добавляешь файлы командой git add, говоря Git: «эти изменения войдут в следующий коммит».
История — коммит зафиксирован командой git commit. Теперь это часть истории, к которой можно вернуться.
Зачем нужен staging area? Представь: ты за день изменил 10 файлов. Пять из них — новая функция, пять — исправление бага. Staging area позволяет сделать два отдельных коммита из одного дня работы — история будет чистой и понятной.
Боб добавляет файлы в staging area:
git add .gitignore
git add requirements.txtИли всё сразу:
git add .Проверяем:
git statusOn branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: requirements.txt
Теперь делаем коммит:
git commit -m "init: project setup with requirements and gitignore"[main (root-commit) a3f8c12] init: project setup with requirements and gitignore
2 files changed, 20 insertions(+)
create mode 100644 .gitignore
create mode 100644 requirements.txt
Первый коммит сделан. a3f8c12 — это уникальный идентификатор коммита (хеш).
Коммит-сообщение — это запись в дневнике проекта. Через месяц ты скажешь спасибо себе за понятные сообщения.
Распространённый формат — Conventional Commits:
тип: краткое описание что сделано
| Тип | Когда использовать |
|---|---|
init |
Инициализация проекта |
feat |
Новая функция |
fix |
Исправление бага |
refactor |
Рефакторинг без изменения поведения |
docs |
Изменения в документации |
chore |
Обслуживание: обновление зависимостей, конфиги |
Примеры хороших сообщений:
feat: add GET /tasks endpoint
fix: handle empty task list response
docs: update README with API examples
Примеры плохих сообщений:
fix
changes
wip
asdfgh
Теперь Боб начинает писать сам проект. Он создаёт структуру папок:
mkdir appПустой файл — нужен чтобы Python воспринимал папку как модуль:
from pydantic import BaseModel
from typing import Optional
class Task(BaseModel):
id: Optional[int] = None
title: str
description: Optional[str] = None
done: bool = Falsefrom fastapi import FastAPI, HTTPException
from app.models import Task
app = FastAPI()
# Временное хранилище задач (в памяти)
tasks: list[Task] = []
task_counter = 0
@app.get("/tasks")
def get_tasks():
return tasks
@app.post("/tasks", status_code=201)
def create_task(task: Task):
global task_counter
task_counter += 1
task.id = task_counter
tasks.append(task)
return task# Tasks API
Простой REST API для управления задачами.
## Запуск
```bash
pip install -r requirements.txt
uvicorn app.main:app --reloadGET /tasks— список всех задачPOST /tasks— создать задачу
Проверяем статус:
```bash
git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
app/
Добавляем и коммитим:
git add .
git commit -m "feat: add Task model and basic CRUD endpoints"git logcommit b7d2e91 (HEAD -> main)
Author: Bob <bob@example.com>
Date: Mon Jun 1 10:34:21 2026 +0300
feat: add Task model and basic CRUD endpoints
commit a3f8c12
Author: Bob <bob@example.com>
Date: Mon Jun 1 10:15:04 2026 +0300
init: project setup with requirements and gitignore
Более компактный вывод:
git log --onelineb7d2e91 (HEAD -> main) feat: add Task model and basic CRUD endpoints
a3f8c12 init: project setup with requirements and gitignore
HEAD — указатель на текущий коммит. Сейчас мы на ветке main, на последнем коммите.
Пока Боб работал прямо в ветке main. Для первоначальной настройки это нормально. Но теперь он хочет добавить новые эндпоинты — и делать это в main рискованно: если что-то сломается, main окажется в нерабочем состоянии.
Решение — ветки.
Ветка — это независимая линия разработки. Изменения в одной ветке не влияют на другие ветки, пока ты сам их не объединишь (merge).
Боб решает работать по следующей схеме:
main ← стабильный код. Сюда попадает только проверенное
↑
test ← тестирование перед выходом в main
↑
dev ← активная разработка. Здесь Боб пишет новый код
Логика работы:
- Пишешь новый код в
dev - Переносишь в
test, проверяешь что ничего не сломалось - Если всё хорошо — переносишь в
main
Это называется упрощённый Git Flow. Он хорошо работает и для одного разработчика, и для небольшой команды.
git branch dev
git branch testСмотрим список веток:
git branch dev
* main
test
Звёздочка * показывает текущую ветку. Боб всё ещё на main.
Переключаемся на dev:
git checkout devSwitched to branch 'dev'
Современный способ: вместо
git checkoutможно использоватьgit switch dev— команда появилась в Git 2.23 и делает то же самое, но с более понятным названием. В этом уроке используемgit checkout— он встречается чаще в документации и примерах.
Создать ветку и сразу переключиться на неё можно одной командой:
git checkout -b feature/new-branchБоб в ветке dev. Теперь он добавляет новые эндпоинты — PUT и DELETE.
Обновляет app/main.py:
from fastapi import FastAPI, HTTPException
from app.models import Task
app = FastAPI()
tasks: list[Task] = []
task_counter = 0
@app.get("/tasks")
def get_tasks():
return tasks
@app.post("/tasks", status_code=201)
def create_task(task: Task):
global task_counter
task_counter += 1
task.id = task_counter
tasks.append(task)
return task
@app.put("/tasks/{task_id}")
def update_task(task_id: int, updated: Task):
for i, task in enumerate(tasks):
if task.id == task_id:
updated.id = task_id
tasks[i] = updated
return updated
raise HTTPException(status_code=404, detail="Task not found")
@app.delete("/tasks/{task_id}", status_code=204)
def delete_task(task_id: int):
for i, task in enumerate(tasks):
if task.id == task_id:
tasks.pop(i)
return
raise HTTPException(status_code=404, detail="Task not found")Коммитим:
git add app/main.py
git commit -m "feat: add PUT and DELETE endpoints for tasks"Добавляет ещё один эндпоинт — получить задачу по ID:
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
for task in tasks:
if task.id == task_id:
return task
raise HTTPException(status_code=404, detail="Task not found")git add app/main.py
git commit -m "feat: add GET /tasks/{id} endpoint"Смотрим историю в ветке dev:
git log --onelinee4a1c03 (HEAD -> dev) feat: add GET /tasks/{id} endpoint
c8b3d72 feat: add PUT and DELETE endpoints for tasks
b7d2e91 (main, test) feat: add Task model and basic CRUD endpoints
a3f8c12 init: project setup with requirements and gitignore
Видно что dev ушла вперёд на 2 коммита. main и test пока на месте.
Перед тем как переносить изменения, полезно посмотреть что именно изменилось.
Разница между текущей веткой и другой веткой:
git diff mainРазница между двумя конкретными ветками:
git diff main devРазница между staged изменениями и последним коммитом:
git diff --stagedБоб доволен: оба коммита выглядят хорошо, код написан. Теперь он переключается на test и переносит туда изменения из dev:
git checkout test
git merge devUpdating b7d2e91..e4a1c03
Fast-forward
app/main.py | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
Fast-forward означает: ветка test просто «догнала» dev — конфликтов нет, история линейная.
Проверяем:
git log --onelinee4a1c03 (HEAD -> test, dev) feat: add GET /tasks/{id} endpoint
c8b3d72 feat: add PUT and DELETE endpoints for tasks
b7d2e91 (main) feat: add Task model and basic CRUD endpoints
a3f8c12 init: project setup with requirements and gitignore
Теперь test и dev смотрят на один и тот же коммит.
Боб запускает проект и проверяет что все эндпоинты работают:
uvicorn app.main:app --reloadВсё работает. Можно переносить в main.
git checkout main
git merge testUpdating b7d2e91..e4a1c03
Fast-forward
app/main.py | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
Итоговая история:
git log --onelinee4a1c03 (HEAD -> main, test, dev) feat: add GET /tasks/{id} endpoint
c8b3d72 feat: add PUT and DELETE endpoints for tasks
b7d2e91 feat: add Task model and basic CRUD endpoints
a3f8c12 init: project setup with requirements and gitignore
Все три ветки теперь на одном коммите. main содержит стабильный, проверенный код.
Боб видит что хочет добавить фильтрацию задач по статусу. Он снова переключается на dev — главная ветка не трогается.
git checkout devОбновляет app/main.py — добавляет параметр done в GET /tasks:
@app.get("/tasks")
def get_tasks(done: Optional[bool] = None):
if done is None:
return tasks
return [task for task in tasks if task.done == done]Добавляет импорт Optional из typing в начало файла (уже есть в models.py, но нужен и в main.py):
from typing import Optionalgit add app/main.py
git commit -m "feat: add filtering by done status in GET /tasks"И снова цикл: dev → test → main:
git checkout test
git merge dev
# Проверяем, запускаем, всё работает
git checkout main
git merge testgit log --onelinef2c9a84 (HEAD -> main, test, dev) feat: add filtering by done status in GET /tasks
e4a1c03 feat: add GET /tasks/{id} endpoint
c8b3d72 feat: add PUT and DELETE endpoints for tasks
b7d2e91 feat: add Task model and basic CRUD endpoints
a3f8c12 init: project setup with requirements and gitignore
Это и есть ритм работы Боба: новый код → dev → test → main. Цикл повторяется.
Если ты изменил файл, но ещё не делал git add, и хочешь вернуть его к состоянию последнего коммита:
git checkout -- app/main.pyОсторожно: эта команда безвозвратно удаляет несохранённые изменения.
Если сделал git add, но передумал включать файл в коммит:
git restore --staged app/main.pygit show a3f8c12Или по относительной позиции (предыдущий коммит):
git show HEAD~1Если хочешь посмотреть как выглядел проект в определённый момент — не удаляя новые коммиты:
git checkout a3f8c12Чтобы вернуться обратно:
git checkout mainБоб прошёл полный цикл локальной работы с Git:
- Инициализировал репозиторий (
git init) - Настроил
.gitignoreиrequirements.txt - Делал осмысленные коммиты с понятными сообщениями
- Создал три ветки:
main,test,dev - Работал в
dev, тестировал вtest, фиксировал стабильный код вmain - Повторил цикл несколько раз
| Команда | Что делает |
|---|---|
git init |
Инициализировать репозиторий |
git status |
Текущее состояние |
git add . |
Добавить все изменения в staging |
git add файл |
Добавить конкретный файл |
git commit -m "сообщение" |
Зафиксировать изменения |
git log |
История коммитов |
git log --oneline |
Краткая история |
git diff |
Посмотреть изменения |
git branch |
Список веток |
git branch имя |
Создать ветку |
git checkout имя |
Переключиться на ветку |
git checkout -b имя |
Создать ветку и переключиться |
git merge имя |
Влить ветку в текущую |
git show хеш |
Посмотреть коммит |
Всё что мы делали — хранится только на компьютере Боба. Если компьютер сломается — история исчезнет. Кроме того, Боб хочет продолжать работу дома.
В следующем уроке мы подключим GitHub — удалённый репозиторий, который хранит историю проекта в облаке и позволяет работать с нескольких компьютеров.