Skip to content

Latest commit

 

History

History
952 lines (705 loc) · 34.7 KB

File metadata and controls

952 lines (705 loc) · 34.7 KB

Этап 13. Git: основы контроля версий

Зачем нужен контроль версий

Представьте ситуацию: вы отредактировали 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 --version
git 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 --list
user.name=Ivan Petrov
user.email=ivan@example.com

Совет. Укажите тот же email, который будете использовать при регистрации на GitLab — тогда платформа автоматически свяжет коммиты с вашим аккаунтом.

Три зоны Git

Прежде чем перейти к командам, разберёмся, как Git устроен изнутри. Любой проект под управлением Git имеет три зоны:

┌─────────────────┐
│ Рабочий каталог │ (working dir)
└────────┬────────┘
         │ git add
┌────────▼────────┐
│     Индекс      │ (staging area)
└────────┬────────┘
         │ git commit
┌────────▼────────┐
│   Репозиторий   │ (.git/)
└─────────────────┘
  • Рабочий каталог — файлы на диске, которые вы редактируете.
  • Индекс (staging area, область подготовки) — «черновик» будущего коммита. Сюда вы добавляете только те изменения, которые хотите зафиксировать.
  • Репозиторий (.git/) — база данных всех коммитов. Хранится в скрытом каталоге .git внутри проекта.

Типичный рабочий цикл:

  1. Редактируете файлы в рабочем каталоге.
  2. git add — переносите нужные изменения в индекс.
  3. git commit — фиксируете содержимое индекса как новый коммит.

Зачем нужен промежуточный индекс? Он позволяет зафиксировать не все изменения сразу, а только те, которые относятся к одной задаче. Например, вы исправили баг в nginx.conf и одновременно добавили комментарии в backup.sh. Это два разных изменения — их лучше оформить двумя отдельными коммитами.

Первый репозиторий

Перейдём в каталог нашего проекта и создадим репозиторий:

cd ~/wordpress-project
git init
Initialized empty Git repository in /home/user/wordpress-project/.git/

Git создал скрытый каталог .git/ — именно в нём хранится вся история. Сам проект при этом не изменился.

git status — текущее состояние

Первая команда, которую стоит запомнить:

git status
On 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»). Прежде чем добавлять файлы, разберёмся, что не нужно хранить в репозитории.

Что не хранить в репозитории: .gitignore

Не все файлы проекта должны попадать в Git:

  • Логи (logs/) — постоянно растут, засоряют историю и не несут ценности для контроля версий.
  • Временные файлы (*.swp, *.tmp) — создаются редакторами vim и nano, не имеют отношения к проекту.
  • Файлы с секретами — пароли, токены, приватные ключи. Если они попадут в удалённый репозиторий, их увидят другие люди.

Для этого в корне проекта создаётся файл .gitignore:

nano .gitignore
# Логи
logs/

# Временные файлы редакторов
*.swp
*.tmp
*~

# Архивы резервных копий (создаются backup.sh)
*.tar.gz

Проверим, что .gitignore работает:

git status
On 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 теперь его игнорирует.

Синтаксис .gitignore

Шаблон Что игнорирует
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 status
On 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 log — история коммитов

git log
commit 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 --oneline
a1b2c3d Начальный коммит: структура проекта wordpress

git diff — что изменилось

Отредактируем конфигурацию nginx — добавим комментарий:

nano config/nginx.conf

Добавьте в начало файла строку:

# Конфигурация nginx для WordPress

Теперь посмотрим, что изменилось:

git diff
diff --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 --oneline
b2c3d4e Добавлен комментарий в конфигурацию 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-backup
Switched 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-backup
Updating 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-config
Auto-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.conf

Git расставил маркеры конфликта:

<<<<<<< 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.

Удалённый репозиторий: GitLab

До сих пор вся работа шла локально — репозиторий существует только на вашем компьютере. Если диск сломается — история будет потеряна.

Удалённый репозиторий (remote) — это копия вашего репозитория на сервере. Он решает сразу несколько задач:

  • Резервная копия — проект хранится не только у вас.
  • Совместная работа — другие люди могут клонировать проект, вносить изменения и отправлять их обратно.
  • CI/CD — автоматическая сборка и тестирование (об этом в главе 16).

GitLab — платформа для хранения Git-репозиториев с веб-интерфейсом, системой задач и встроенным CI/CD. Мы будем использовать его до конца курса.

Примечание. Примеры ниже используют публичный сервис gitlab.com. Если ваш преподаватель предоставил собственный сервер GitLab (self-hosted), замените gitlab.com на его адрес во всех командах.

Регистрация на GitLab

  1. Откройте в браузере https://gitlab.com и создайте аккаунт.
  2. Подтвердите email.
  3. Запомните своё имя пользователя (username) — оно будет частью URL вашего репозитория.

Настройка SSH-ключа

В главе 01 мы подключались к серверу по SSH. Тот же принцип используется для безопасной связи с GitLab — без ввода пароля при каждом push и pull.

Шаг 1. Сгенерируйте пару ключей (если ещё не сделали):

ssh-keygen -t ed25519 -C "ivan@example.com"

Нажмите Enter на все вопросы (имя файла по умолчанию, пароль по желанию).

Шаг 2. Скопируйте публичный ключ:

cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ivan@example.com

Шаг 3. Добавьте ключ в GitLab:

  1. Войдите на gitlab.com.
  2. Перейдите в Settings (иконка профиля → Edit profile) → SSH Keys.
  3. Вставьте содержимое публичного ключа в поле «Key».
  4. Нажмите Add key.

Шаг 4. Проверьте подключение:

ssh -T git@gitlab.com
Welcome to GitLab, @username!

Если вы видите приветствие — всё настроено верно.

Создание проекта на GitLab

  1. На gitlab.com нажмите New project → Create blank project.
  2. Имя проекта: wordpress-project.
  3. Видимость: Private (только для вас) или Public (виден всем).
  4. Снимите галочку «Initialize repository with a README» — у нас уже есть локальный репозиторий.
  5. Нажмите 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 -v
origin  git@gitlab.com:<username>/wordpress-project.git (fetch)
origin  git@gitlab.com:<username>/wordpress-project.git (push)

origin — стандартное имя для основного удалённого репозитория.

Отправим все коммиты на сервер:

git push -u origin main
Enumerating 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 pull

git 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> Клонировать удалённый репозиторий

Практические задания

  1. Установите Git и настройте имя и email. Проверьте настройки командой git config --list.

  2. Инициализируйте репозиторий в каталоге ~/wordpress-project. Выполните git status и объясните, что выводит команда.

  3. Создайте файл .gitignore, который исключает каталог logs/, файлы *.swp и архивы *.tar.gz. Убедитесь, что git status больше не показывает logs/.

  4. Добавьте все файлы в индекс и сделайте первый коммит. Посмотрите историю командой git log.

  5. Отредактируйте config/nginx.conf (добавьте комментарий). Посмотрите изменения через git diff, затем добавьте в индекс и зафиксируйте вторым коммитом.

  6. Создайте ветку feature-backup. Переключитесь на неё, внесите изменение в backup.sh, сделайте коммит. Вернитесь в main и выполните слияние. Удалите ветку. Посмотрите историю с git log --oneline --graph.

  7. Создайте конфликт слияния: в main измените строку в config/nginx.conf, создайте ветку и измените ту же строку по-другому. Попробуйте выполнить git merge. Разрешите конфликт вручную и завершите слияние.

  8. Сгенерируйте SSH-ключ (если ещё не сделали), добавьте его в GitLab. Создайте проект на GitLab и отправьте репозиторий командой git push -u origin main.

  9. Клонируйте свой репозиторий в другой каталог (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) в изолированные контейнеры, которые можно запускать на любом сервере одной командой.