Представьте ситуацию: вы отредактировали nginx.conf, перезапустили
nginx — и сайт перестал работать. Что было в файле до правки? Если вы
не сделали копию — остаётся только вспоминать. Или другой знакомый
сценарий из мира Windows:
backup.sh
backup_old.sh
backup_v2.sh
backup_v2_final.sh
backup_v2_final_FINAL.sh
Какой из этих файлов рабочий? Когда и что именно менялось? Кто менял? На эти вопросы невозможно ответить, глядя на имена файлов.
Система контроля версий решает все эти проблемы. Она хранит полную историю изменений каждого файла: кто, когда и зачем внёс правку. В любой момент можно посмотреть, что изменилось, сравнить версии и откатиться к любому состоянию проекта.
Git — самая популярная система контроля версий в мире. Её используют и разработчики, и системные администраторы, и DevOps-инженеры. Git создал Линус Торвальдс в 2005 году — тот самый человек, который создал ядро Linux.
Привязка к проекту. В предыдущих главах мы собрали wordpress-project: конфиги nginx и PHP, скрипт резервного копирования, файлы сайта. Сейчас всё это лежит «просто на диске» без какой-либо истории. В этой главе мы поставим проект под контроль версий Git — и каждое изменение будет задокументировано.
Git входит в стандартные репозитории Debian:
sudo apt update
sudo apt install gitПроверим версию:
git --versiongit version 2.47.2
Перед началом работы Git нужно указать ваше имя и email. Эти данные записываются в каждый коммит (фиксацию изменений), чтобы было понятно, кто автор:
git config --global user.name "Ivan Petrov"
git config --global user.email "ivan@example.com"Флаг --global означает, что настройки действуют для всех
репозиториев текущего пользователя. Они сохраняются в файле
~/.gitconfig.
Проверим, что настройки применились:
git config --listuser.name=Ivan Petrov
user.email=ivan@example.com
Совет. Укажите тот же email, который будете использовать при регистрации на GitLab — тогда платформа автоматически свяжет коммиты с вашим аккаунтом.
Прежде чем перейти к командам, разберёмся, как Git устроен изнутри. Любой проект под управлением Git имеет три зоны:
┌─────────────────┐
│ Рабочий каталог │ (working dir)
└────────┬────────┘
│ git add
┌────────▼────────┐
│ Индекс │ (staging area)
└────────┬────────┘
│ git commit
┌────────▼────────┐
│ Репозиторий │ (.git/)
└─────────────────┘
- Рабочий каталог — файлы на диске, которые вы редактируете.
- Индекс (staging area, область подготовки) — «черновик» будущего коммита. Сюда вы добавляете только те изменения, которые хотите зафиксировать.
- Репозиторий (.git/) — база данных всех коммитов. Хранится
в скрытом каталоге
.gitвнутри проекта.
Типичный рабочий цикл:
- Редактируете файлы в рабочем каталоге.
git add— переносите нужные изменения в индекс.git commit— фиксируете содержимое индекса как новый коммит.
Зачем нужен промежуточный индекс? Он позволяет зафиксировать не все
изменения сразу, а только те, которые относятся к одной задаче.
Например, вы исправили баг в nginx.conf и одновременно добавили
комментарии в backup.sh. Это два разных изменения — их лучше
оформить двумя отдельными коммитами.
Перейдём в каталог нашего проекта и создадим репозиторий:
cd ~/wordpress-project
git initInitialized empty Git repository in /home/user/wordpress-project/.git/
Git создал скрытый каталог .git/ — именно в нём хранится вся
история. Сам проект при этом не изменился.
Первая команда, которую стоит запомнить:
git statusOn branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
backup.sh
config/
hello.sh
logs/
site/
nothing added to commit but untracked files present (use "git add" to track)
Git видит все файлы, но пока не отслеживает ни один из них («Untracked files»). Прежде чем добавлять файлы, разберёмся, что не нужно хранить в репозитории.
Не все файлы проекта должны попадать в Git:
- Логи (
logs/) — постоянно растут, засоряют историю и не несут ценности для контроля версий. - Временные файлы (
*.swp,*.tmp) — создаются редакторами vim и nano, не имеют отношения к проекту. - Файлы с секретами — пароли, токены, приватные ключи. Если они попадут в удалённый репозиторий, их увидят другие люди.
Для этого в корне проекта создаётся файл .gitignore:
nano .gitignore# Логи
logs/
# Временные файлы редакторов
*.swp
*.tmp
*~
# Архивы резервных копий (создаются backup.sh)
*.tar.gz
Проверим, что .gitignore работает:
git statusOn branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
backup.sh
config/
hello.sh
site/
nothing added to commit but untracked files present (use "git add" to track)
Каталог logs/ исчез из списка — Git теперь его игнорирует.
| Шаблон | Что игнорирует |
|---|---|
logs/ |
Каталог logs и всё его содержимое |
*.swp |
Все файлы с расширением .swp |
*.log |
Все файлы с расширением .log |
build/ |
Каталог build |
!important.log |
Исключение: не игнорировать important.log |
# |
Комментарий |
**/temp |
Каталог temp на любом уровне вложенности |
Важно. Если файл уже был добавлен в Git до того, как вы вписали его в
.gitignore, Git продолжит его отслеживать. Чтобы убрать такой файл из-под контроля версий (не удаляя с диска), используйте:git rm --cached <файл>
Теперь добавим все нужные файлы и сделаем первый коммит:
git add .Точка (.) означает «добавить всё из текущего каталога» (с учётом
.gitignore). Можно добавлять файлы по одному:
git add backup.sh
git add config/nginx.confПосмотрим, что попало в индекс:
git statusOn branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: backup.sh
new file: config/nginx.conf
new file: config/php.ini
new file: hello.sh
new file: site/index.html
Всё верно — логи не попали. Фиксируем:
git commit -m "Начальный коммит: структура проекта wordpress"[main (root-commit) a1b2c3d] Начальный коммит: структура проекта wordpress
6 files changed, 42 insertions(+)
create mode 100644 .gitignore
create mode 100755 backup.sh
create mode 100644 config/nginx.conf
create mode 100644 config/php.ini
create mode 100755 hello.sh
create mode 100644 site/index.html
Флаг -m задаёт сообщение коммита — короткое описание того,
что и зачем вы сделали. Хорошее сообщение отвечает на вопрос «что
изменилось и почему», а не просто «что сделано»:
| Плохо | Хорошо |
|---|---|
fix |
Исправлен путь к сокету PHP-FPM в конфиге nginx |
update |
Добавлена ротация логов в backup.sh |
asdfg |
Начальный коммит: структура проекта |
git logcommit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 (HEAD -> main)
Author: Ivan Petrov <ivan@example.com>
Date: Mon Mar 17 10:00:00 2026 +0300
Начальный коммит: структура проекта wordpress
Каждый коммит имеет:
- Хеш (a1b2c3d...) — уникальный идентификатор, вычисленный по содержимому. Не нужно запоминать весь хеш — достаточно первых 7–8 символов.
- Автор — имя и email из
git config. - Дата — когда был сделан коммит.
- Сообщение — описание изменений.
Компактный формат:
git log --onelinea1b2c3d Начальный коммит: структура проекта wordpress
Отредактируем конфигурацию nginx — добавим комментарий:
nano config/nginx.confДобавьте в начало файла строку:
# Конфигурация nginx для WordPress
Теперь посмотрим, что изменилось:
git diffdiff --git a/config/nginx.conf b/config/nginx.conf
index 1234567..abcdefg 100644
--- a/config/nginx.conf
+++ b/config/nginx.conf
@@ -1,3 +1,4 @@
+# Конфигурация nginx для WordPress
server {
listen 80;
server_name example.com;Строки с + — добавленные, с - — удалённые. git diff показывает
разницу между рабочим каталогом и индексом (то есть ещё не
подготовленные изменения).
Зафиксируем это изменение:
git add config/nginx.conf
git commit -m "Добавлен комментарий в конфигурацию nginx"Посмотрим историю:
git log --onelineb2c3d4e Добавлен комментарий в конфигурацию nginx
a1b2c3d Начальный коммит: структура проекта wordpress
Теперь в истории два коммита. В любой момент можно вернуться к первому.
| Команда | Описание |
|---|---|
git init |
Создать новый репозиторий в текущем каталоге |
git status |
Показать текущее состояние (что изменено, что в индексе) |
git add <файл> |
Добавить файл в индекс |
git add . |
Добавить все изменения в индексе |
git commit -m "..." |
Зафиксировать изменения из индекса |
git log |
Показать историю коммитов |
git log --oneline |
Компактная история (одна строка на коммит) |
git diff |
Показать незафиксированные изменения |
git diff --staged |
Показать изменения, добавленные в индекс |
git show <хеш> |
Показать содержимое конкретного коммита |
До сих пор мы работали линейно: один коммит за другим в одной ветке
main. Но в реальных проектах часто нужно работать над несколькими
задачами параллельно, не мешая друг другу.
Ветка (branch) — это отдельная линия разработки. Вы можете создать ветку, вносить в ней любые изменения, а когда работа готова — слить результат обратно в основную ветку.
main: A ── B ── C ────────── F (merge)
\ /
feature: D ── E ──────
- В точке B создаётся ветка
feature. - Коммиты D и E делаются в ветке
feature— основная веткаmainпри этом не затрагивается. - В точке F ветка
featureсливается обратно вmain.
Допустим, мы хотим улучшить скрипт backup.sh — добавить вывод даты
и размера архива. Создадим для этого отдельную ветку:
git branch feature-backupПосмотрим список веток:
git branch feature-backup
* main
Звёздочка (*) показывает текущую ветку. Переключимся на новую:
git switch feature-backupSwitched to branch 'feature-backup'
Примечание. Команда
git switchпоявилась в Git 2.23. В старых руководствах вы встретитеgit checkout <ветка>— она делает то же самое, ноswitchпонятнее по смыслу. Можно создать ветку и сразу переключиться на неё одной командой:git switch -c feature-backup.
Отредактируем backup.sh — добавим вывод информации об архиве:
nano backup.shДобавьте в конец скрипта (перед завершением):
# Выводим информацию об архиве
echo "Дата: $(date '+%Y-%m-%d %H:%M')"
echo "Размер: $(du -h "$ARCHIVE" | cut -f1)"Зафиксируем изменение:
git add backup.sh
git commit -m "Добавлен вывод даты и размера архива в backup.sh"Работа в ветке завершена. Переключимся обратно на main и сольём
изменения:
git switch main
git merge feature-backupUpdating b2c3d4e..c3d4e5f
Fast-forward
backup.sh | 3 +++
1 file changed, 3 insertions(+)
Git выполнил fast-forward (перемотку вперёд) — просто передвинул
указатель main на последний коммит ветки feature-backup, потому
что в main не было новых коммитов после создания ветки.
Ветка больше не нужна — удалим её:
git branch -d feature-backupПосмотрим историю:
git log --oneline --graph* c3d4e5f Добавлен вывод даты и размера архива в backup.sh
* b2c3d4e Добавлен комментарий в конфигурацию nginx
* a1b2c3d Начальный коммит: структура проекта wordpress
| Команда | Описание |
|---|---|
git branch |
Показать список веток |
git branch <имя> |
Создать новую ветку |
git switch <имя> |
Переключиться на ветку |
git switch -c <имя> |
Создать ветку и сразу переключиться |
git merge <имя> |
Слить указанную ветку в текущую |
git branch -d <имя> |
Удалить ветку (только если она слита) |
git log --oneline --graph |
История с визуализацией веток |
Иногда Git не может автоматически слить изменения — это происходит, когда в двух ветках изменена одна и та же строка одного и того же файла. Такая ситуация называется конфликтом.
Конфликты — нормальная часть работы с Git. Не нужно их бояться. Давайте создадим конфликт намеренно, чтобы научиться его разрешать.
Шаг 1. Создадим ветку — это будет точка расхождения:
git switch -c feature-configСразу вернёмся в main:
git switch mainШаг 2. В ветке main отредактируем config/nginx.conf. Изменим
директиву listen:
nano config/nginx.confЗамените строку listen 80; на:
listen 80 default_server;
Зафиксируем:
git add config/nginx.conf
git commit -m "Добавлен default_server в директиву listen"Шаг 3. Переключимся на ветку и изменим ту же строку по-другому:
git switch feature-config
nano config/nginx.confЗдесь файл ещё содержит старую строку listen 80; — ветка ничего
не знает о коммите в main. Замените listen 80; на:
listen 443 ssl;
Зафиксируем:
git add config/nginx.conf
git commit -m "Переключение nginx на HTTPS (порт 443)"Шаг 4. Вернёмся в main и попробуем слить:
git switch main
git merge feature-configAuto-merging config/nginx.conf
CONFLICT (content): Merge conflict in config/nginx.conf
Automatic merge failed; fix conflicts and then commit the result.
Откроем файл с конфликтом:
nano config/nginx.confGit расставил маркеры конфликта:
<<<<<<< HEAD
listen 80 default_server;
=======
listen 443 ssl;
>>>>>>> feature-config
- Между
<<<<<<< HEADи=======— версия из текущей ветки (main). - Между
=======и>>>>>>> feature-config— версия из сливаемой ветки.
Ваша задача — выбрать правильный вариант (или объединить оба)
и удалить все маркеры (строки с <<<<<<<, ======= и
>>>>>>>). Допустим, мы хотим оставить порт 80
с default_server:
listen 80 default_server;
Сохраните файл и завершите слияние:
git add config/nginx.conf
git commit -m "Разрешён конфликт: оставлен listen 80 default_server"Удалим ветку:
git branch -d feature-configПосмотрим историю — теперь видно слияние:
git log --oneline --graph* e5f6a7b Разрешён конфликт: оставлен listen 80 default_server
|\
| * d4e5f6a Переключение nginx на HTTPS (порт 443)
* | c3d4e5f Добавлен default_server в директиву listen
|/
* b2c3d4e Добавлен комментарий в конфигурацию nginx
* a1b2c3d Начальный коммит: структура проекта wordpress
Совет. Если во время слияния вы запутались и хотите отменить всё, выполните
git merge --abort— Git вернёт файлы в состояние до начала слияния.
Иногда вы редактируете файлы, что-то идёт не так — и проще вернуться к последнему зафиксированному состоянию, чем разбираться, что именно сломалось.
Откатить один файл к версии из последнего коммита:
git restore config/nginx.confВсе незафиксированные изменения в этом файле будут потеряны — он вернётся к состоянию из последнего коммита.
Откатить все файлы в рабочем каталоге:
git restore .Внимание. Команда
git restoreбезвозвратно удаляет незафиксированные изменения. Если вы не делалиgit addиgit commit— восстановить правки будет невозможно. Поэтому коммитьте чаще: каждый коммит — это точка, к которой можно вернуться.
Если изменения уже добавлены в индекс (git add), но ещё не
зафиксированы — сначала уберите их из индекса:
git restore --staged config/nginx.confФайл останется изменённым в рабочем каталоге, но выйдет из индекса.
После этого можно откатить и сам файл командой git restore.
До сих пор вся работа шла локально — репозиторий существует только на вашем компьютере. Если диск сломается — история будет потеряна.
Удалённый репозиторий (remote) — это копия вашего репозитория на сервере. Он решает сразу несколько задач:
- Резервная копия — проект хранится не только у вас.
- Совместная работа — другие люди могут клонировать проект, вносить изменения и отправлять их обратно.
- CI/CD — автоматическая сборка и тестирование (об этом в главе 16).
GitLab — платформа для хранения Git-репозиториев с веб-интерфейсом, системой задач и встроенным CI/CD. Мы будем использовать его до конца курса.
Примечание. Примеры ниже используют публичный сервис gitlab.com. Если ваш преподаватель предоставил собственный сервер GitLab (self-hosted), замените
gitlab.comна его адрес во всех командах.
- Откройте в браузере https://gitlab.com и создайте аккаунт.
- Подтвердите email.
- Запомните своё имя пользователя (username) — оно будет частью URL вашего репозитория.
В главе 01 мы подключались к серверу по SSH. Тот же принцип
используется для безопасной связи с GitLab — без ввода пароля при
каждом push и pull.
Шаг 1. Сгенерируйте пару ключей (если ещё не сделали):
ssh-keygen -t ed25519 -C "ivan@example.com"Нажмите Enter на все вопросы (имя файла по умолчанию, пароль по желанию).
Шаг 2. Скопируйте публичный ключ:
cat ~/.ssh/id_ed25519.pubssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ivan@example.com
Шаг 3. Добавьте ключ в GitLab:
- Войдите на gitlab.com.
- Перейдите в Settings (иконка профиля → Edit profile) → SSH Keys.
- Вставьте содержимое публичного ключа в поле «Key».
- Нажмите Add key.
Шаг 4. Проверьте подключение:
ssh -T git@gitlab.comWelcome to GitLab, @username!
Если вы видите приветствие — всё настроено верно.
- На gitlab.com нажмите New project → Create blank project.
- Имя проекта:
wordpress-project. - Видимость: Private (только для вас) или Public (виден всем).
- Снимите галочку «Initialize repository with a README» — у нас уже есть локальный репозиторий.
- Нажмите Create project.
GitLab покажет инструкции для существующего репозитория. Нам нужна секция «Push an existing Git repository».
Свяжем локальный репозиторий с удалённым:
cd ~/wordpress-project
git remote add origin git@gitlab.com:<username>/wordpress-project.gitЗамените <username> на ваше имя пользователя GitLab.
Проверим, что remote добавлен:
git remote -vorigin git@gitlab.com:<username>/wordpress-project.git (fetch)
origin git@gitlab.com:<username>/wordpress-project.git (push)
origin — стандартное имя для основного удалённого репозитория.
Отправим все коммиты на сервер:
git push -u origin mainEnumerating objects: 18, done.
Counting objects: 100% (18/18), done.
Delta compression using up to 4 threads
Compressing objects: 100% (14/14), done.
Writing objects: 100% (18/18), 1.84 KiB | 1.84 MiB/s, done.
Total 18 (delta 3), reused 0 (delta 0)
To gitlab.com:<username>/wordpress-project.git
* [new branch] main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
Флаг -u (upstream) запоминает связь между локальной веткой main
и удалённой origin/main. После этого можно просто писать git push
без дополнительных аргументов.
Откройте проект на gitlab.com — вы увидите все свои файлы и историю коммитов в веб-интерфейсе.
Если кто-то (или вы сами с другого компьютера) внёс изменения в удалённый репозиторий, получить их можно командой:
git pullgit pull скачивает новые коммиты с сервера и автоматически сливает
их с вашей локальной веткой.
Чтобы скопировать удалённый репозиторий на новую машину:
git clone git@gitlab.com:<username>/wordpress-project.gitЭта команда создаст каталог wordpress-project/ с полной историей
коммитов и настроенным origin.
| Команда | Описание |
|---|---|
git remote add origin <url> |
Добавить удалённый репозиторий |
git remote -v |
Показать список удалённых репозиториев |
git push |
Отправить коммиты на сервер |
git push -u origin main |
Первая отправка с привязкой ветки |
git pull |
Получить и слить изменения с сервера |
git clone <url> |
Клонировать удалённый репозиторий |
-
Установите Git и настройте имя и email. Проверьте настройки командой
git config --list. -
Инициализируйте репозиторий в каталоге
~/wordpress-project. Выполнитеgit statusи объясните, что выводит команда. -
Создайте файл
.gitignore, который исключает каталогlogs/, файлы*.swpи архивы*.tar.gz. Убедитесь, чтоgit statusбольше не показываетlogs/. -
Добавьте все файлы в индекс и сделайте первый коммит. Посмотрите историю командой
git log. -
Отредактируйте
config/nginx.conf(добавьте комментарий). Посмотрите изменения черезgit diff, затем добавьте в индекс и зафиксируйте вторым коммитом. -
Создайте ветку
feature-backup. Переключитесь на неё, внесите изменение вbackup.sh, сделайте коммит. Вернитесь вmainи выполните слияние. Удалите ветку. Посмотрите историю сgit log --oneline --graph. -
Создайте конфликт слияния: в
mainизмените строку вconfig/nginx.conf, создайте ветку и измените ту же строку по-другому. Попробуйте выполнитьgit merge. Разрешите конфликт вручную и завершите слияние. -
Сгенерируйте SSH-ключ (если ещё не сделали), добавьте его в GitLab. Создайте проект на GitLab и отправьте репозиторий командой
git push -u origin main. -
Клонируйте свой репозиторий в другой каталог (
git clone ... ~/wordpress-project-clone). Убедитесь, что в клоне есть вся история коммитов (git log).
В этой главе вы:
- узнали, зачем нужен контроль версий и как устроен Git (три зоны: рабочий каталог, индекс, репозиторий);
- научились создавать репозиторий (
git init), отслеживать изменения (status,diff) и фиксировать их (add,commit); - настроили
.gitignore, чтобы логи, временные файлы и архивы не попадали в репозиторий; - освоили ветвление (
branch,switch,merge) и научились разрешать конфликты слияния; - подключили удалённый репозиторий на GitLab через SSH и научились
отправлять (
push), получать (pull) и клонировать (clone) проект.
Теперь ваш проект wordpress-project хранится не только на диске, но и на сервере GitLab — с полной историей всех изменений.
В следующей главе мы познакомимся с Docker — инструментом контейнеризации. Он позволит упаковать весь наш стек (nginx + PHP-FPM + WordPress) в изолированные контейнеры, которые можно запускать на любом сервере одной командой.