Skip to the content.

Вернуться к главной странице, списку всех тем

3. Управление версиями проектов, автоматизация при внесении изменений

Что ты узнаешь: как хранить историю изменений кода, работать над проектом вместе с другими, не затирая чужую работу, и запускать автоматические проверки при каждом изменении

Что нужно знать заранее: темы 1 и 2 — терминал, файлы и права, SSH

Сколько времени займёт: 2 часа теории + 6 часов практики

Твой шаг в сквозном проекте: положишь «Заметки» в git-репозиторий на GitHub и настроишь пайплайн, который сам проверяет код при каждом изменении


Представь, что вы с друзьями вместе пишете книгу. Версий текста много, каждый постоянно что-то добавляет и удаляет. Чтобы не запутаться, вы используете Git. Его цель — следить, кто и что изменил, и позволять вернуться к старым версиям, если что-то пошло не так

Для DevOps-инженера Git — это ещё и нечто большее. Дальше в курсе всё, что мы делаем, будет описано текстовыми файлами: конфиг nginx, Dockerfile, манифесты Kubernetes, плейбуки Ansible. Все они лежат в git. Отсюда следует правило, к которому мы придём в теме 9: состояние инфраструктуры — это то, что записано в репозитории, а не то, что кто-то когда-то руками поправил на сервере

Основные задачи Git

Основные понятия Git

  1. Репозиторий — каталог с файлами проекта и историей их изменений. История и настройки лежат в скрытой директории .git
  2. Коммит — зафиксированное состояние проекта с описанием, что было сделано, и уникальным идентификатором (хешем)
  3. Ветка (branch) — отдельная линия разработки, подвижный указатель на коммит
  4. Слияние (merge) — объединение изменений из одной ветки в другую
  5. Конфликт — ситуация, когда двое изменили одну и ту же строку и Git не может решить сам, чью версию оставить
  6. Клонирование (clone) — создание локальной копии удалённого репозитория
  7. Push / Pull — отправка изменений на удалённый сервер и получение их оттуда
  8. Tag — неподвижная метка на конкретном коммите, обычно для обозначения версии релиза
  9. Remote — ссылка на удалённый репозиторий; по умолчанию называется origin

Три состояния файла — то, что нужно понять в первую очередь

Это единственная действительно новая идея в Git, всё остальное — команды поверх неё. Файл в репозитории находится в одном из состояний:

рабочий каталог  ──git add──▶  индекс (staging)  ──git commit──▶  история (.git)
   ты правишь                 «пойдёт в коммит»              «зафиксировано навсегда»

Двухшаговость (add, потом commit) нужна, чтобы из десяти правок собрать один осмысленный коммит, а не свалить всё в кучу. Посмотреть, что в каком состоянии, — git status. Эту команду опытный инженер запускает десятки раз в день

Куда указывает HEAD

HEAD — указатель на то место в истории, где ты сейчас находишься. Обычно он указывает на ветку, а ветка — на последний её коммит. git switch другая-ветка двигает HEAD, и файлы в рабочем каталоге меняются на соответствующие. Если HEAD указывает прямо на коммит, а не на ветку, это состояние называется detached HEAD — коммиты в нём делать можно, но они потеряются, если не создать ветку

GitHub и GitLab

GitHub — онлайн-платформа для хранения git-репозиториев и совместной работы. Огромный склад, где программисты хранят свои репозитории, делятся ими и работают вместе

Этот курс размещён на GitHub в виде репозитория — ты читаешь его страницы, которые собираются прямо из markdown-файлов

GitLab — похожая платформа с ключевым отличием: её можно установить на собственный сервер. Крупные компании часто выбирают именно её, потому что хотят хранить код внутри своего контура. Понятия почти совпадают, но названия разные:

GitHub GitLab Что это
Pull Request (PR) Merge Request (MR) Запрос на слияние ветки с обсуждением
GitHub Actions GitLab CI Система автоматизации
.github/workflows/*.yml .gitlab-ci.yml Файл с описанием пайплайна
Runner Runner Машина, которая выполняет шаги пайплайна

Как работают командой: ветки и Pull Request

Правило почти любой команды: в основную ветку (main) не коммитят напрямую. Вместо этого:

  1. Создаёшь ветку от main: git switch -c feature/add-healthcheck
  2. Делаешь в ней коммиты
  3. Отправляешь ветку на сервер: git push -u origin feature/add-healthcheck
  4. Открываешь Pull Request — просишь влить свою ветку в main
  5. Коллеги читают изменения, оставляют замечания, автоматика прогоняет проверки
  6. Когда всё зелено и одобрено — ветку вливают, а потом удаляют

Зачем эта церемония: в main попадает только просмотренный и проверенный код, а история изменений становится документацией — по каждому PR видно, что меняли и почему

Имена веток обычно осмысленные: feature/имя для новой функции, fix/имя для исправления, chore/имя для рутины

Как писать сообщения коммитов

Плохо: фиксы, ., работает, ещё раз. Через месяц никто, включая тебя, не поймёт, что произошло

Хорошо: первая строка — краткая суть в повелительном наклонении, до 72 символов; при необходимости пустая строка и подробности с ответом на вопрос «почему»:

Добавить healthcheck-эндпоинт в «Заметки»

Kubernetes из темы 5 умеет перезапускать зависшие поды,
но для этого ему нужен URL, который отвечает быстро и без
обращения к базе. /healthz именно такой.

Распространённое соглашение — Conventional Commits: feat:, fix:, docs:, chore:, refactor:. По таким префиксам автоматика умеет сама собирать список изменений релиза

.gitignore: что нельзя класть в репозиторий

Файл .gitignore перечисляет то, что Git должен игнорировать:

*.log            # логи
__pycache__/     # временные файлы Python
.env             # файл с паролями и токенами
node_modules/    # скачанные зависимости
*.key            # приватные ключи

Главное правило: в репозиторий не кладут секреты. Пароль, попавший в коммит, остаётся в истории навсегда, даже если ты удалишь его следующим коммитом — история хранит всё. Если это случилось, пароль считается скомпрометированным и его нужно менять. Как хранить секреты правильно, разберём в теме 9 на примере Vault

CI/CD: автоматизация

Представь, что вы строите дом на колёсах:

CI/CD делает этот процесс автоматическим: не нужно ждать, пока кто-то вручную выполнит каждый шаг. Автоматизированная последовательность шагов называется пайплайн (pipeline)

Пайплайн запускается по событию в репозитории: коммит, коммит в конкретную ветку, открытие Pull Request, слияние, выпуск тега, наступление времени по расписанию

Типичные шаги пайплайна:

  1. Линтинг — проверка, что код и конфиги написаны по правилам и без синтаксических ошибок. Самый дешёвый шаг, поэтому он всегда идёт первым
  2. Тесты — автоматическая проверка, что программа делает то, что должна
  3. Сборка — превращение исходного кода в то, что можно запустить: исполняемый файл или образ контейнера. Как рецепт превращается в блюдо
  4. Проверка безопасности — поиск известных уязвимостей в зависимостях и случайно закоммиченных секретов
  5. Публикация — отправка результата сборки (артефакта) в хранилище
  6. Развёртывание — выкладка на сервер
  7. Уведомление — сообщение команде в Telegram или почту, особенно если что-то упало

Порядок не случаен: шаги выстраивают от быстрых и дешёвых к медленным и дорогим, чтобы опечатка отваливалась за 10 секунд, а не после пятнадцатиминутной сборки. Этот принцип называется fail fast

Артефакт — результат сборки: архив, пакет, образ контейнера, отчёт о тестах. В теме 4 нашим артефактом станет docker-образ

Runner — машина (обычно контейнер), на которой выполняются шаги пайплайна. У GitHub есть бесплатные runner’ы в облаке; компании часто поднимают свои

Теоретические вопросы

  1. Что такое Git и зачем он нужен? Ответ: Система контроля версий: отслеживает изменения файлов и позволяет нескольким людям работать над проектом одновременно, не мешая друг другу. Даёт возможность вернуться к любой прошлой версии

  2. Чем репозиторий отличается от обычной папки? Ответ: В репозитории есть скрытый каталог .git с полной историей, настройками и метаданными. Удали его — останется обычная папка с файлами, но без истории

  3. Что такое staging area и зачем нужен отдельный шаг git add? Ответ: Промежуточная область, черновик следующего коммита. Позволяет собрать в один коммит только связанные изменения, а не всё подряд. Аналогия: add — положить товары в корзину, commit — оплатить на кассе

  4. Что хранится в коммите? Ответ: Снимок состояния файлов, автор, дата, сообщение, ссылка на родительский коммит и уникальный хеш. По хешу коммит можно найти всегда

  5. Зачем нужны ветки? Ответ: Чтобы вести параллельную разработку, не трогая стабильный код. Шоссе с основным движением (main) и съездами для новых функций; готовый съезд подключают обратно

  6. Чем ветка отличается от тега? Ответ: Ветка — подвижный указатель, он переезжает на каждый новый коммит. Тег — неподвижная метка на конкретном коммите, обычно версия релиза (v1.2.0)

  7. Что такое HEAD? Ответ: Указатель на текущую позицию в истории — обычно на ветку, в которой ты находишься. Именно от HEAD отсчитываются HEAD~1 (предыдущий коммит) и подобные обозначения

  8. Что такое merge conflict и почему он возникает? Ответ: Git не может автоматически объединить изменения, потому что одна и та же строка была изменена по-разному в двух ветках. Решение выбирает человек: Git размечает спорное место маркерами <<<<<<<, =======, >>>>>>>

  9. Чем merge отличается от rebase? Ответ: merge объединяет ветки, сохраняя обе истории и создавая коммит слияния. rebase переносит коммиты поверх другой ветки, делая историю линейной, но переписывая её. Правило: не делать rebase веток, которые уже отправлены и используются другими

  10. Что такое fast-forward merge? Ответ: Слияние без создания отдельного коммита: если в основной ветке не было новых коммитов, её указатель просто передвигается вперёд

  11. Чем git reset отличается от git revert? Ответ: reset двигает указатель ветки назад, убирая коммиты из истории — опасен для веток, которые уже отправлены. revert создаёт новый коммит, отменяющий изменения, история сохраняется. В общих ветках используют только revert

  12. Чем git fetch отличается от git pull? Ответ: fetch скачивает изменения с сервера, но не трогает твою рабочую ветку. pull = fetch + автоматическое слияние. fetch — «принести письма», pull — «принести и сразу вклеить в дневник»

  13. Чем Git отличается от GitHub? Ответ: Git — программа на твоём компьютере, работающая с версиями. GitHub — сайт, где репозитории хранят и обсуждают. Git — фотоаппарат, GitHub — фотогалерея в интернете. Git прекрасно работает и без GitHub

  14. Чем clone отличается от fork? Ответ: clone — копия репозитория на твоём компьютере. fork — копия чужого репозитория в твоём аккаунте на GitHub, нужна, чтобы предложить изменения в проект, куда у тебя нет прав на запись

  15. Что такое Pull Request и зачем он нужен? Ответ: Запрос на слияние ветки с возможностью обсуждения. Даёт ревью коллег и автоматические проверки до попадания кода в main. Черновик статьи, который отправляют редактору перед публикацией

  16. Для чего нужен .gitignore? Ответ: Перечислить, что Git не должен отслеживать: логи, кеши, скачанные зависимости, файлы с паролями. Список «не класть в чемодан»

  17. Что делать, если пароль случайно попал в коммит? Ответ: Считать пароль скомпрометированным и немедленно сменить его. Удаление следующим коммитом не помогает — старый коммит остаётся в истории и доступен любому, у кого есть копия репозитория

  18. Что такое CI и CD? Ответ: CI — автоматическая проверка и сборка кода при каждом изменении. CD — автоматическая доставка результата на сервер. CI — проверка деталей на заводе, CD — отправка изделия клиенту

  19. Что такое pipeline и какие события его запускают? Ответ: Цепочка автоматических шагов. Запускается коммитом, открытием или обновлением Pull Request, слиянием, выпуском тега, расписанием либо вручную

  20. Почему линтинг ставят раньше сборки? Ответ: Принцип fail fast: шаги идут от дешёвых к дорогим. Опечатку в YAML нет смысла искать после пятнадцатиминутной сборки, если линтер найдёт её за десять секунд

  21. Что такое артефакт? Ответ: Результат сборки: архив, пакет, образ контейнера, отчёт о тестах. Именно артефакт, а не исходный код, потом уезжает на сервер

  22. Что такое runner? Ответ: Машина или контейнер, где выполняются шаги пайплайна. Бывают облачные (предоставляет платформа) и собственные, поднятые внутри компании — их выбирают, когда пайплайну нужен доступ во внутреннюю сеть

  23. Как выглядит хороший пайплайн? Ответ: Линтинг → тесты → сборка → проверка безопасности → публикация артефакта → развёртывание → уведомление команды. Быстрый на старте, воспроизводимый, с понятным сообщением об ошибке

Вопросы с собеседований

Отдельный блок про GitLab CI — в российских компаниях спрашивают о нём чаще, чем о GitHub Actions

  1. Какие основные поля есть у задачи в GitLab CI? Ответ: stage (к какому этапу относится), image (в каком образе выполнять), script (команды), before_script и after_script, rules или only/except (когда запускать), artifacts (что сохранить после выполнения), cache (что переиспользовать между запусками), needs (от каких задач зависит), tags (на каком runner выполнять), when (manual — запуск по кнопке)

  2. Как в CI хранить пароли и токены? Ответ: В переменных CI/CD в настройках проекта, помеченных как masked (скрывать в логах) и protected (доступны только защищённым веткам). В репозитории их держать нельзя. Более зрелый вариант — брать секреты из Vault прямо в момент выполнения задачи, об этом тема 9

  3. Как сделать, чтобы несколько задач выполнялись одновременно? Ответ: Задачи одного этапа (stage) по умолчанию идут параллельно — достаточно назначить им один этап. Для более гибкой схемы используют needs: задача стартует сразу после указанных зависимостей, не дожидаясь всего этапа. Ещё есть parallel: N — запустить одну задачу в нескольких экземплярах, например чтобы разложить тесты

  4. Что такое артефакты и где они хранятся? Ответ: Файлы, которые задача сохраняет после выполнения: собранный пакет, отчёт о тестах, покрытие кода. Хранятся на сервере GitLab и доступны для скачивания и передачи в следующие задачи. У них задают срок жизни expire_in, иначе они быстро съедают место

  5. Что такое окружения stage, preprod и prod? Ответ: Отдельные копии системы под разные цели. stage (тестовое) — куда попадает каждое изменение для проверки. preprod — максимально похоже на боевое, для финальной проверки и нагрузочных тестов. prod — то, чем пользуются люди. Правило: изменение проходит их по очереди, а выкладка в prod обычно требует ручного подтверждения

  6. Как склонировать репозиторий по SSH? Ответ: git clone git@gitlab.com:группа/проект.git, предварительно добавив открытый ключ в настройках профиля. Отличие от HTTPS: не нужно вводить логин и токен при каждой операции. Проверить доступ — ssh -T git@gitlab.com

  7. Чем GitLab CI отличается от GitHub Actions по устройству? Ответ: В GitLab весь пайплайн описан одним файлом .gitlab-ci.yml и построен вокруг этапов. В GitHub Actions несколько файлов в .github/workflows/, а единица переиспользования — готовое действие (action) из общего каталога. Идеи одинаковые, различаются словарь и формат

Git глубже

  1. Я закоммитил не в ту ветку. Как перенести коммит? Ответ: git cherry-pick хеш — применить конкретный коммит в текущую ветку. Дальше убрать его из исходной: если ветка ещё никуда не отправлена, годится git reset --hard HEAD~1; если отправлена и её используют другие — только git revert

  2. Что делать с незакоммиченными правками, если срочно нужно переключиться на другую ветку? Ответ: git stash убирает их «в карман» и возвращает чистое дерево, git stash pop достаёт обратно. Список отложенного — git stash list. Это не хранилище: отложенное легко забыть и потерять, поэтому для чего-то ценного лучше сделать временный коммит в отдельной ветке

  3. Я сделал reset --hard и потерял коммиты. Их можно вернуть? Ответ: Почти всегда да. git reflog хранит историю перемещений HEAD за последние недели, включая то, что «удалено». Находишь нужный хеш и восстанавливаешься: git reset --hard хеш или git branch спасение хеш. Знание про reflog отличает того, кто пережил свою первую потерю данных, от того, кому это ещё предстоит

  4. Как найти коммит, в котором сломалась работающая функция? Ответ: git bisect — двоичный поиск по истории. Указываешь заведомо рабочий и заведомо сломанный коммиты, git предлагает середину, ты проверяешь и говоришь good или bad. За несколько шагов находится виновник даже среди тысячи коммитов. Процесс можно автоматизировать: git bisect run ./тест.sh

  5. Что такое squash и когда его применяют? Ответ: Схлопывание нескольких коммитов в один. Применяют при слиянии ветки, где было десять коммитов вида «фикс», «ещё фикс», «точно фикс»: в основную ветку попадает один осмысленный коммит. Обратная сторона — теряется детальная история работы, поэтому в некоторых командах предпочитают сохранять всё

  6. Чем аннотированный тег отличается от обычного? Ответ: Обычный (git tag v1.0) — просто указатель на коммит. Аннотированный (git tag -a v1.0 -m "описание") — полноценный объект с автором, датой, сообщением и возможностью подписи. Для релизов используют аннотированные: по ним видно, кто и когда выпустил версию

  7. Что такое семантическое версионирование? Ответ: Формат МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, например 2.4.1. Патч меняют при исправлениях, минорную — при новых возможностях с сохранением совместимости, мажорную — при ломающих изменениях. Это договорённость, позволяющая понять по номеру, безопасно ли обновляться. На неё опирается автоматика: в теме 9 Flux отбирает образы правилом >=1.0.0

  8. Из каких объектов состоит git внутри? Ответ: Четыре типа: blob (содержимое файла), tree (каталог: список имён со ссылками на blob и другие tree), commit (ссылка на tree, автор, сообщение, родитель) и tag. Всё адресуется хешем содержимого, поэтому одинаковые файлы хранятся один раз, а изменить историю незаметно невозможно — поменяется хеш и всё, что за ним следует

  9. Что такое git hooks? Ответ: Скрипты, запускаемые на события: pre-commit (перед коммитом — прогнать линтер, поискать случайно вписанные пароли), commit-msg (проверить формат сообщения), pre-push. Лежат в .git/hooks и потому не попадают в репозиторий — чтобы раздать их команде, используют инструменты вроде pre-commit. Хуки не заменяют проверки в CI: локально их легко обойти флагом --no-verify

  10. Чем trunk-based разработка отличается от Git Flow? Ответ: Git Flow — долгоживущие ветки (develop, release, hotfix) и редкие крупные релизы. Trunk-based — короткие ветки на день-два, всё быстро вливается в основную, а незаконченные функции прячут за флагами (тема 9). Второй подход лучше сочетается с непрерывной доставкой: чем дольше живёт ветка, тем больнее слияние

  11. Что такое монорепозиторий и в чём его компромисс? Ответ: Все проекты компании в одном репозитории. Плюсы: общий код переиспользуется без публикации пакетов, изменение через несколько сервисов делается одним коммитом, единые правила. Минусы: репозиторий разрастается, нужны инструменты сборки, понимающие, что именно изменилось, и права доступа настраиваются сложнее

Практическая часть

Задание 0. Настроить Git

Цель: представиться Git, иначе коммиты будут безымянными

Шаги:

git config --global user.name "Твоё Имя"
git config --global user.email "твоя@почта"
git config --global init.defaultBranch main   # называть первую ветку main, а не master
git config --global core.editor nano          # чем открывать сообщения коммитов
git config --list                             # проверить, что записалось

Настрой доступ на GitHub по SSH-ключу — это удобнее пароля и понадобится дальше:

ssh-keygen -t ed25519 -C "твоя@почта"     # на все вопросы можно нажать Enter
cat ~/.ssh/id_ed25519.pub                 # это ОТКРЫТЫЙ ключ, его копируем

Скопированное содержимое добавь на GitHub: Settings → SSH and GPG keys → New SSH key. Проверь:

ssh -T git@github.com     # ожидаем: Hi <логин>! You've successfully authenticated

Типичные ошибки:

Задание 1. Репозиторий и первые коммиты

Цель: понять связку «рабочий каталог → индекс → история»

Шаги:

mkdir my-first-project && cd my-first-project
git init
git status                       # смотри на вывод после каждого шага ниже

printf 'Alice\nBob\nCharlie\n' > team.txt
git status                       # team.txt в untracked files
git add team.txt
git status                       # теперь в changes to be committed
git commit -m "Добавить список участников команды"

echo "# Мой первый проект" > project-info.md
git add project-info.md
git commit -m "Добавить описание проекта"

git log --oneline --graph        # история в компактном виде
git show HEAD                    # что именно изменилось в последнем коммите

Ожидаемый вывод: git log --oneline показывает два коммита с короткими хешами

Типичные ошибки:

Задание 2. Ветки

Цель: освоить изоляцию работы и слияние

Шаги:

git switch -c feature/add-readme     # создать ветку и сразу перейти в неё
                                     # (старый эквивалент: git checkout -b)
printf '# Мой первый проект\nУчебный проект курса DevOps.\n' > README.md
git add README.md
git commit -m "Добавить README"

git switch main
ls                                   # README.md здесь нет — он остался в другой ветке
git log --oneline --all --graph      # видно расхождение веток

git merge feature/add-readme         # влить
ls                                   # теперь README.md на месте
git branch -d feature/add-readme     # удалить отработавшую ветку

Ожидаемый вывод: после merge файл появляется в main; git log --graph показывает, как ветка вернулась в основную линию

Типичные ошибки:

Задание 3. Подключение GitHub

Цель: научиться работать с удалённым репозиторием

Шаги:

  1. На GitHub создай репозиторий devops-practice без README
  2. Подключи локальный репозиторий и отправь код:

    git remote add origin git@github.com:<твой_логин>/devops-practice.git
    git remote -v                      # проверить, что записалось
    git branch -M main
    git push -u origin main            # -u запоминает связь, дальше хватит git push
    
  3. Создай на GitHub через веб-интерфейс ветку update-docs
  4. Забери её локально и поработай в ней:

    git fetch origin
    git switch update-docs
    echo "Новая функция: аутентификация" >> project-info.md
    git commit -am "Описать функцию аутентификации"
    git push origin update-docs
    
  5. На GitHub открой Pull Request из update-docs в main, посмотри вкладку Files changed и влей его
  6. Забери результат к себе:

    git switch main
    git pull
    git log --oneline
    

Типичные ошибки:

Задание 4. Разрешение конфликта

Цель: пережить конфликт в безопасных условиях, чтобы не паниковать в боевых

Шаги:

printf 'app_name=MyApp\nversion=1.0\nauthor=User\n' > config.txt
git add config.txt
git commit -m "Добавить файл конфигурации"

git switch -c feature/update-version
sed -i 's/version=1.0/version=2.0/' config.txt
git commit -am "Поднять версию до 2.0"

git switch main
sed -i 's/version=1.0/version=1.5/' config.txt
git commit -am "Поднять версию до 1.5"

git merge feature/update-version     # здесь будет конфликт
git status                           # покажет файл в состоянии both modified
cat config.txt                       # посмотри на маркеры конфликта

Внутри файла увидишь:

<<<<<<< HEAD
version=1.5          ← версия из ветки, в которой ты находишься (main)
=======
version=2.0          ← версия из ветки, которую вливаешь
>>>>>>> feature/update-version

Открой файл, оставь version=2.0, удали все три строки-маркера, затем:

git add config.txt
git commit                           # сообщение уже подставлено, просто сохрани
git log --oneline --graph

Ожидаемый вывод: появился коммит слияния, в файле осталась одна строка version=2.0

Типичные ошибки:

Задание 5. Сквозной проект: «Заметки» в git и первый пайплайн

Цель: положить приложение из тем 1–2 в репозиторий и настроить автоматическую проверку при каждом изменении

Шаги:

  1. Преврати каталог проекта в репозиторий:

    cd ~/notes
    git init
    
  2. Создай .gitignore — заметки пользователей и логи в репозиторий не кладём:

    notes.txt
    *.log
    __pycache__/
    .env
    *.key
    *.crt
    
  3. Добавь README.md с описанием сервиса: что это, как запустить, какие переменные окружения понимает (PORT, NOTES_FILE), какие эндпоинты есть (/, /healthz). Это не формальность: README — первое, что читает человек, попавший в незнакомый репозиторий

  4. Скопируй в репозиторий конфиги, которые мы писали руками, — теперь они тоже часть проекта:

    mkdir -p deploy
    sudo cp /etc/systemd/system/notes.service deploy/
    sudo cp /etc/nginx/conf.d/notes.conf deploy/
    sudo chown $USER deploy/*
    
  5. Первый коммит и отправка на GitHub:

    git add .
    git status                         # убедись, что notes.txt НЕ попал в список
    git commit -m "Первая версия сервиса «Заметки»"
    git branch -M main
    git remote add origin git@github.com:<твой_логин>/notes.git
    git push -u origin main
    
  6. Добавь пайплайн .github/workflows/ci.yml:

    name: CI
    
    # Когда запускать: при отправке в main и при любом Pull Request в main
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      check:
        runs-on: ubuntu-latest
        steps:
          # Скачать код репозитория на runner
          - uses: actions/checkout@v4
    
          - name: Установить Python
            uses: actions/setup-python@v5
            with:
              python-version: "3.12"
    
          # Самый дешёвый шаг — проверка синтаксиса. Идёт первым: fail fast
          - name: Проверить синтаксис приложения
            run: python -m py_compile app.py
    
          - name: Проверить стиль кода
            run: |
              pip install ruff
              ruff check app.py
    
          # Настоящий тест: поднимаем сервис и проверяем, что он отвечает
          - name: Проверить, что сервис отвечает
            run: |
              PORT=8080 python app.py &
              sleep 2
              curl --fail --silent http://localhost:8080/healthz
              echo "healthcheck прошёл"
    
          - name: Проверить конфиг nginx
            run: |
              sudo apt-get update && sudo apt-get install -y nginx
              sudo cp deploy/notes.conf /etc/nginx/conf.d/
              sudo nginx -t
    
  7. Отправь и посмотри результат:

    git add .github/workflows/ci.yml
    git commit -m "Добавить пайплайн проверки"
    git push
    

    Открой GitHub → вкладка Actions. Разверни выполнившийся запуск и прочитай вывод каждого шага

  8. Сломай специально. Создай ветку, внеси синтаксическую ошибку в app.py (например, убери закрывающую скобку), открой Pull Request:

    git switch -c fix/broken-syntax
    # внеси ошибку в app.py
    git commit -am "Проверка: что будет при поломке"
    git push -u origin fix/broken-syntax
    

    Открой PR на GitHub и посмотри: проверка станет красной, а кнопка слияния — предупреждающей. Именно так CI защищает основную ветку. Почини ошибку, сделай новый коммит в ту же ветку — проверка перезапустится сама и станет зелёной

Ожидаемый вывод: зелёная галочка у коммитов в main; красный крест на PR с ошибкой; в логе шага видно, какая именно строка сломана

Типичные ошибки:

Проверь себя: тема освоена, если ты можешь

Следующая тема: Docker и Compose →