Git хранит историю проекта как цепочку коммитов — снимков всех файлов в момент сохранения. Рабочий цикл: изменить файлы, отметить нужные командой git add, зафиксировать git commit. Новую задачу делают в отдельной ветке и сливают обратно git merge; если обе ветки изменили одну строку, Git останавливается на конфликте, и вы сами выбираете итоговый текст.
После материала вы сможете
- создать репозиторий, настроить
.gitignoreи сделать осмысленные коммиты черезgit addиgit commit - создать ветку, переключиться на неё и слить обратно, отличая fast-forward от коммита слияния
- разрешить конфликт слияния: найти маркеры, оставить итоговый текст и завершить merge
- выбрать безопасную отмену:
restore,revertилиreset --soft
Что знать заранее
Проверено на
Git 2.55 (Git for Windows, Git Bash)Git 2.45 (Alpine Linux 3.20)
Сверено с документацией: 11 октября 2026 г.
Содержание
- Зачем контроль версий
- Рабочая папка, индекс, коммит
- Первый репозиторий: init, status, add, commit, log
- .gitignore и что нельзя коммитить
- Ветки: создание, переключение и fast-forward
- Слияние с конфликтом: как его разрешить
- Отмена изменений: restore, revert, reset — без потерь
- Практика: репозиторий с намеренным конфликтом
Зачем контроль версий
Без системы контроля версий история проекта живёт в папках вида site-final, site-final-2, site-final-ПРАВКИ. Непонятно, что изменилось, кто это сделал и как вернуть вчерашнюю рабочую версию. Git решает эти задачи: каждое сохранение — коммит — получает автора, дату, описание и уникальный идентификатор, а к любому коммиту можно вернуться.
Pro Git, официальная книга проекта, подчёркивает, чем Git отличается от многих старых систем: он хранит не список правок, а снимки (snapshots) — при каждом коммите Git как бы фотографирует состояние всех файлов. Неизменённые файлы не копируются заново, Git ссылается на уже сохранённую версию. Почти все операции выполняются локально, поэтому работать с историей можно без интернета.
- История: кто, когда и зачем изменил каждую строку.
- Безопасные эксперименты: новая функция разрабатывается в ветке и не ломает рабочую версию.
- Командная работа: изменения нескольких людей объединяются автоматически, а спорные места Git показывает явно.
- Отмена: ошибочный коммит можно отменить, не теряя остальную работу.
Рабочая папка, индекс, коммит
Главное, что нужно запомнить, — у файла в Git три состояния. По Pro Git: изменённый (modified) — вы поменяли файл, но не зафиксировали; подготовленный (staged) — вы отметили текущую версию файла для следующего коммита; зафиксированный (committed) — данные надёжно сохранены в локальной базе. Отсюда три места, между которыми движутся изменения.
Рабочая папка
- Что там лежит
- Файлы, которые вы редактируете
- Команда, чтобы увидеть
git status,git diff
Индекс
- Что там лежит
- Версии файлов, которые попадут в следующий коммит
- Команда, чтобы увидеть
git diff --staged
Репозиторий
- Что там лежит
- Все коммиты в папке
.git - Команда, чтобы увидеть
git log
Индекс позволяет собирать коммит осознанно: из пяти изменённых файлов отметить два, относящихся к одной задаче. Перед первым коммитом Git нужно представиться — имя и почта попадают в каждый коммит. Флаг --global сохраняет настройки для всех репозиториев пользователя; последняя строка делает main именем первой ветки по умолчанию (в документации git init сказано, что без настройки сейчас используется master, а в Git 3.0 станет main).
git config --global user.name "Aigerim Dev"
git config --global user.email "aigerim@example.com"
git config --global init.defaultBranch maingit config --global --list показывает три значения.Первый репозиторий: init, status, add, commit, log
Сделаем репозиторий для лендинга кофейни. git init создаёт в текущей папке скрытый каталог .git — это и есть репозиторий. В папке два файла: README.md и страница index.html:
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Coffee Point</title></head>
<body>
<h1>Coffee Point</h1>
<p>Open daily 8:00-22:00</p>
</body>
</html>$ git --version
git version 2.55.0.windows.1
$ mkdir landing && cd landing
$ git init -b main
Initialized empty Git repository in C:/Users/you/landing/.git/
$ git config user.name "Aigerim Dev"
$ git config user.email "aigerim@example.com"
$ printf '# Coffee Point landing\n' > README.md
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
index.html
nothing added to commit but untracked files present (use "git add" to track)
$ git add index.html README.md
$ git status --short
A README.md
A index.html
$ git commit -m "Add landing page skeleton"
[main (root-commit) 01210cb] Add landing page skeleton
2 files changed, 9 insertions(+)
create mode 100644 README.md
create mode 100644 index.html
$ git log --oneline
01210cb Add landing page skeleton--global.git status
Главная команда новичка: на какой вы ветке, что изменено, что подготовлено. Новые файлы Git называет untracked — он их видит, но не отслеживает.
git add
Переносит текущую версию файлов в индекс.
git status --shortпоказывает это компактно:??— неотслеживаемый файл,A— добавлен в индекс,M— изменён. Левая колонка — индекс, правая — рабочая папка.git commit -m
Фиксирует содержимое индекса. Git печатает ветку, короткий хеш коммита (
01210cb) и статистику.root-commit— первый коммит без родителя.git log --oneline
История по одному коммиту в строке: хеш и сообщение. Без флага
--onelineвыводятся автор и дата.
.gitignore и что нельзя коммитить
В любом проекте появляются файлы, которым не место в истории: установленные зависимости (node_modules), сборки, логи и, главное, локальные настройки с секретами (.env). Файл .gitignore в корне репозитория перечисляет шаблоны путей, которые Git должен игнорировать.
$ printf 'API_KEY=YOUR_API_KEY\n' > .env
$ mkdir node_modules && touch node_modules/cache.js
$ printf 'debug\n' > npm-debug.log
$ git status --short
?? .env
?? node_modules/
?? npm-debug.log
$ printf 'node_modules/\n.env\n*.log\n' > .gitignore
$ git status --short
?? .gitignore
$ git add .gitignore
$ git commit -m "Ignore dependencies, logs and local secrets"
[main 8e7365c] Ignore dependencies, logs and local secrets
1 file changed, 3 insertions(+)
create mode 100644 .gitignore
$ git check-ignore -v .env npm-debug.log
.gitignore:2:.env .env
.gitignore:3:*.log npm-debug.loggit check-ignore -v показывает, какое правило из какой строки сработало.Тот же .gitignore с комментариями — так его удобнее поддерживать:
# dependencies — installed by npm install
node_modules/
# local settings and secrets
.env
# logs
*.loggit check-ignore -v находит правила для node_modules/, .env и *.log.node_modules/
- Что игнорирует
- Папку с таким именем на любом уровне (косая черта в конце — только папки)
*.log
- Что игнорирует
- Все файлы с расширением .log;
*не захватывает/
/build
- Что игнорирует
- Только
buildв корне — косая черта в начале привязывает шаблон к папке .gitignore
!keep.log
- Что игнорирует
- Возвращает файл, исключённый предыдущим правилом
# текст
- Что игнорирует
- Комментарий
Ветки: создание, переключение и fast-forward
Ветка в Git — подвижный указатель на коммит. Новую работу ведут в отдельной ветке: основная (main) остаётся рабочей, а незаконченная функция не мешает остальным. git switch -c имя создаёт ветку и переключается на неё (то же самое делают две команды: git branch имя и git switch имя). По Pro Git, начиная с версии 2.23 для переключения и создания веток можно использовать git switch вместо прежнего git checkout.
$ git switch -c feature/footer
Switched to a new branch 'feature/footer'
$ git diff
diff --git a/index.html b/index.html
index 8e73a21..4683878 100644
--- a/index.html
+++ b/index.html
@@ -4,5 +4,6 @@
<body>
<h1>Coffee Point</h1>
<p>Open daily 8:00-22:00</p>
+ <footer>Almaty, Abay Ave 1</footer>
</body>
</html>
$ git commit -am "Add footer with address"
[feature/footer 47ae5bd] Add footer with address
1 file changed, 1 insertion(+)
$ git switch main
Switched to branch 'main'
$ git merge feature/footer
Updating 8e7365c..47ae5bd
Fast-forward
index.html | 1 +
1 file changed, 1 insertion(+)
$ git branch -d feature/footer
Deleted branch feature/footer (was 47ae5bd).git diff).git diff показывает неподготовленные изменения: строки с + добавлены, с - удалены. git commit -am подготавливает все уже отслеживаемые изменённые файлы и сразу коммитит — новые файлы этот флаг не добавляет. После переключения на main слияние прошло как fast-forward: в main не было своих новых коммитов, и Git просто передвинул указатель вперёд. Слитую ветку удаляют git branch -d — по документации, ветка должна быть полностью слита, иначе Git откажется её удалять.
Слияние с конфликтом: как его разрешить
Если обе ветки получили свои коммиты, Git выполняет трёхстороннее слияние: сравнивает два последних снимка с их общим предком и создаёт коммит слияния с двумя родителями. Когда ветки изменили одну и ту же строку по-разному, автоматически выбрать нельзя — это конфликт. Воспроизведём его: в ветке feature/hours поменяем часы работы, а в main — ту же строку иначе.
$ git branch feature/hours
$ git switch feature/hours
Switched to branch 'feature/hours'
$ sed -i 's/8:00-22:00/9:00-23:00/' index.html
$ git commit -am "Change opening hours to 9-23"
[feature/hours bd9d4cd] Change opening hours to 9-23
1 file changed, 1 insertion(+), 1 deletion(-)
$ git switch main
Switched to branch 'main'
$ sed -i 's/8:00-22:00/8:00-22:00, weekends till 23:00/' index.html
$ git commit -am "Mention weekend hours"
[main 437a99b] Mention weekend hours
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --all
* bd9d4cd Change opening hours to 9-23
| * 437a99b Mention weekend hours
|/
* 47ae5bd Add footer with address
* 8e7365c Ignore dependencies, logs and local secrets
* 01210cb Add landing page skeleton
$ git merge feature/hours
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
$ cat index.html
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Coffee Point</title></head>
<body>
<h1>Coffee Point</h1>
<<<<<<< HEAD
<p>Open daily 8:00-22:00, weekends till 23:00</p>
=======
<p>Open daily 9:00-23:00</p>
>>>>>>> feature/hours
<footer>Almaty, Abay Ave 1</footer>
</body>
</html>Git остановился посреди слияния: файл помечен как both modified, а в нём появились маркеры. Между <<<<<<< HEAD и ======= — версия текущей ветки (main), между ======= и >>>>>>> feature/hours — версия сливаемой ветки. Разрешить конфликт — значит оставить итоговый текст и удалить все три маркера. Здесь правильный результат объединяет обе правки: новые часы и упоминание выходных.
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Coffee Point</title></head>
<body>
<h1>Coffee Point</h1>
<p>Open daily 9:00-23:00, weekends till 23:00</p>
<footer>Almaty, Abay Ave 1</footer>
</body>
</html>$ git diff
diff --cc index.html
index d216469,5d082e3..0000000
--- a/index.html
+++ b/index.html
@@@ -3,7 -3,7 +3,7 @@@
<head><meta charset="utf-8"><title>Coffee Point</title></head>
<body>
<h1>Coffee Point</h1>
- <p>Open daily 8:00-22:00, weekends till 23:00</p>
- <p>Open daily 9:00-23:00</p>
++ <p>Open daily 9:00-23:00, weekends till 23:00</p>
<footer>Almaty, Abay Ave 1</footer>
</body>
</html>
$ git add index.html
$ git status --short
M index.html
$ git commit --no-edit
[main c954b42] Merge branch 'feature/hours'
$ git log --oneline --graph
* c954b42 Merge branch 'feature/hours'
|\
| * bd9d4cd Change opening hours to 9-23
* | 437a99b Mention weekend hours
|/
* 47ae5bd Add footer with address
* 8e7365c Ignore dependencies, logs and local secrets
* 01210cb Add landing page skeleton
$ git branch -d feature/hours
Deleted branch feature/hours (was bd9d4cd).git diff во время слияния показывает комбинированный формат: две колонки знаков — относительно каждой из веток.Найдите файлы с конфликтом
git statusперечисляет их в разделе Unmerged paths.Отредактируйте каждый файл
Оставьте нужный вариант или объедините оба, удалите маркеры
<<<<<<<,=======,>>>>>>>. Многие редакторы кода подсвечивают такие блоки и помогают выбрать вариант.Отметьте как разрешённое
git add файл— по Pro Git, добавление в индекс и означает «конфликт разрешён».Завершите слияние
git commit(с--no-edit— со стандартным сообщениемMerge branch …). Передумали —git merge --abortвернёт всё к состоянию до слияния.
Отмена изменений: restore, revert, reset — без потерь
Для отмены есть несколько команд, и выбор зависит от того, где находится ошибка: в рабочей папке, в индексе или уже в коммите. Начиная с Git 2.23 для файлов есть отдельная команда git restore — её же подсказывает git status.
$ printf 'oops\n' >> README.md
$ git status --short
M README.md
$ git restore README.md
$ git status --short
$ printf 'draft\n' > notes.txt
$ git add notes.txt
$ git status --short
A notes.txt
$ git restore --staged notes.txt
$ git status --short
?? notes.txt
$ rm notes.txt
$ sed -i 's# <footer># <p class="promo">-50% today</p>\n <footer>#' index.html
$ git commit -am "Add promo banner"
[main c8a8b36] Add promo banner
1 file changed, 1 insertion(+)
$ git revert --no-edit HEAD
[main 6bc2589] Revert "Add promo banner"
Date: Sun Oct 11 16:11:34 2026 -0600
1 file changed, 1 deletion(-)
$ git log --oneline -3
6bc2589 Revert "Add promo banner"
c8a8b36 Add promo banner
c954b42 Merge branch 'feature/hours'
$ grep -c promo index.html
0
$ printf 'typo\n' >> README.md
$ git commit -am "WIP"
[main 7ca022b] WIP
1 file changed, 1 insertion(+)
$ git reset --soft HEAD~1
$ git status --short
M README.md
$ git log --oneline -1
6bc2589 Revert "Add promo banner"
$ git restore --staged README.md && git restore README.md
$ git status
On branch main
nothing to commit, working tree cleanИспортили файл, ещё не делали add
- Команда
git restore файл- Что происходит
- Файл возвращается к версии из индекса; правки в рабочей папке теряются
Лишний файл попал в индекс
- Команда
git restore --staged файл- Что происходит
- Файл убирается из индекса, содержимое на диске остаётся
Ошибочный коммит уже в общей истории
- Команда
git revert <коммит>- Что происходит
- Создаётся новый коммит, отменяющий изменения; история не переписывается
Последний коммит ещё никому не отправлен
- Команда
git reset --soft HEAD~1- Что происходит
- Коммит снимается, его изменения остаются подготовленными в индексе
Практика: репозиторий с намеренным конфликтом
Скрипт ниже создаёт учебный репозиторий: файл настроек сайта, ветку feature/hours и разные правки одной строки в обеих ветках. Сохраните его как conflict-lab.sh и запустите bash conflict-lab.sh в bash, zsh или Git Bash. Флаг -i.bak у sed нужен, чтобы команда одинаково работала в GNU sed (Linux, Git Bash) и BSD sed (macOS).
#!/usr/bin/env bash
# conflict-lab.sh — creates a practice repo with two branches and a deliberate merge conflict
set -e
dir="${1:-git-conflict-lab}"
mkdir "$dir" && cd "$dir"
git init -q -b main
git config user.name "Git Learner"
git config user.email "learner@example.com"
printf 'title: Coffee Point\ncity: Almaty\nhours: 8:00-22:00\n' > site.yml
git add site.yml
git commit -q -m "Add site settings"
git switch -q -c feature/hours
sed -i.bak 's/8:00-22:00/9:00-23:00/' site.yml && rm site.yml.bak
git commit -q -am "Change hours on feature branch"
git switch -q main
sed -i.bak 's/8:00-22:00/8:00-21:00/' site.yml && rm site.yml.bak
git commit -q -am "Change hours on main"
echo "Ready: cd $dir && git merge feature/hours"Следующий шаг — удалённый репозиторий: git push, pull request и код-ревью на GitHub.
Как проверено4 · пройденосверено 11 октября 2026 г.
- запущено, работает
Основной сценарий: init, status, add, commit, log, .gitignore + check-ignore, switch -c, fast-forward merge, конфликт и его разрешение, restore / restore --staged / revert / reset --soft (shellsession-блоки, index.html)
реальный git 2.55.0.windows.1 в Git Bash во временной папке, изолированный конфиг (GIT_CONFIG_NOSYSTEM=1, GIT_CONFIG_GLOBAL=/dev/null); блоки кода вырезаны скриптом из транскрипта (code.mjs), содержимое index.html сверено с командами printf из транскрипта; в выводе сокращён только путь временной папки в строке git init (помечено)
- запущено, работает
Тот же сценарий на Linux
docker run --rm alpine:3.20 + apk git 2.45.4: вывод совпал с Git Bash, кроме хешей, дат, путей и порядка двух строк графа с одинаковой меткой времени
- запущено, работает
git config --global (3 строки) и .gitignore с комментариями
Git 2.55 с GIT_CONFIG_GLOBAL на временный файл: git config --global --list показал значения, git init без -b создал ветку main; git check-ignore -v нашёл правила для node_modules/, .env, *.log
- запущено, работает
conflict-lab.sh и упражнения (разрешение конфликта, .gitignore с !keep.log)
Git Bash (Git 2.55) и alpine:3.20 (Git 2.45, bash) — одинаковый результат; дополнительно проверены git branch -d на неслитой ветке (ошибка not fully merged) и git merge --abort
«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.