Ветки
Ситуация
Вы начали делать страницу расписания. Работы на час, страница пока сырая. В этот момент просят срочно поправить README на рабочей версии сайта. Если вы делаете всё в одной линии истории, сырая страница уйдёт вместе со срочной правкой.
Главная мысль
Ветка — это подвижное имя, которое указывает на последний коммит линии работы. Новый коммит сдвигает текущую ветку вперёд, остальные остаются на месте. Переключение ветки меняет файлы в рабочей папке на снимок из её последнего коммита.
Важное следствие: незакоммиченные изменения не принадлежат ни одной ветке. Они лежат в рабочей папке и при переключении либо переезжают с вами, либо мешают переключиться.
Новые слова
- Ветка (branch) — имя, указывающее на коммит;
main— основная ветка курса. - Текущая ветка — та, в которую попадёт следующий коммит; в
git branchотмечена*. git switch— переключиться на ветку;git switch -c имя— создать ветку и сразу переключиться.
Разбор: страница расписания в отдельной ветке
Папка ~/git-course/site. Исходное состояние — конец главы 3: ветка main, working tree clean.
Если начинаете здесь — setup.md. Хеши — пример.
Создаём ветку и работаем в ней
git branch
* main
git switch -c schedule-page
Switched to a new branch 'schedule-page'
Создаём страницу и коммитим её — уже в новой ветке:
cat > schedule.html <<'EOF'
<!doctype html>
<html lang="ru">
<head><meta charset="utf-8"><title>Расписание</title></head>
<body>
<h1>Расписание</h1>
<ul>
<li>Вторник, 19:00 — Git: основы</li>
<li>Четверг, 19:00 — практика</li>
</ul>
</body>
</html>
EOF
git add schedule.html
git commit -m "Добавить страницу расписания"
[schedule-page 85bf960] Добавить страницу расписания
1 file changed, 11 insertions(+)
create mode 100644 schedule.html
В квадратных скобках — ветка, в которую лёг коммит.
Срочная правка на main
Предскажите: будет ли schedule.html в папке после переключения на main?
git switch main
ls
Switched to branch 'main'
README.md
about.html
index.html
site.conf
Файла нет: в последнем коммите main его не существует. Он не удалён — он в истории ветки schedule-page.
Срочная правка — перечислить страницы в README. sed вставляет строку после второй строки файла:
sed -i '2a Страницы: index.html, about.html.\n' README.md
git add README.md
git commit -m "Перечислить страницы в README"
git log --oneline --graph --all -4
* 64e50a3 Перечислить страницы в README
| * 85bf960 Добавить страницу расписания
|/
* 04549f4 Вернуть порт 8082
* 3c45247 Добавить контакты на страницу «О нас»
--graph --all рисует все ветки: от общего коммита 04549f4 история разошлась на две линии.
Незакоммиченное переезжает с вами
Вернёмся к задаче и начнём ссылку на расписание на главной — но пока не коммитим:
git switch schedule-page
sed -i 's|<p><a href="about.html">О нас</a></p>|&\n<p><a href="schedule.html">Расписание</a></p>|' index.html
git switch main
Switched to branch 'main'
M index.html
Переключение прошло, и правка index.html приехала на main: index.html одинаков в обеих ветках,
Git нечего перезаписывать, и он оставляет вашу правку в папке. Легко потом закоммитить её не в ту ветку.
Вернёмся:
git switch schedule-page
Switched to branch 'schedule-page'
M index.html
Практика: закончить задачу в ветке
Исходное состояние — конец разбора: вы на ветке schedule-page, index.html изменён и не закоммичен.
Если начинаете здесь — setup.md.
-
Допишите в конец README строку о расписании:
printf '\nРасписание занятий — на странице schedule.html.\n' >> README.md -
Предскажите, что ответит
git switch mainтеперь. Выполните и сравните. -
Закончите задачу: сохраните ссылку и строку README одним коммитом «Добавить ссылки на страницу расписания» в ветке
schedule-page. -
Переключитесь на
mainи убедитесь, что там ссылки ещё нет.
Признаки успеха:
git log --oneline --graph --all -5показывает два коммита вschedule-pageи один новый вmain;- на
main:git status—working tree clean,grep -c schedule index.html—0.
Подсказка 1
README в двух ветках уже разный: на main есть строка «Страницы: …». Что сделает Git с вашей правкой README,
если переключение заменит файл версией из main?
Подсказка 2
Git откажется переключаться и подскажет два выхода. Один из них — тот, что нужен для законченной задачи.
Подсказка 3
git add index.html README.md → git commit -m "…" → git switch main.
Решение — в answers/04-branches.md.
Если результат другой
- Коммит попал в
main, а не в ветку. Посмотритеgit branchперед коммитом:*стоит у текущей ветки. Перенести коммит между ветками — тема глав 5 и 7; пока сделайте вывод и проверяйте ветку передgit commit. git switchпишетinvalid reference. Опечатка в имени ветки. Список —git branch.- Переключение отказало, а вы не хотите коммитить сырую работу. Временное сохранение без коммита —
git stash, глава 7.
Проверка понимания
- Вы на ветке
schedule-pageсоздали файл и не закоммитили его. Переключились наmain. Где файл? - Почему Git отказался переключаться из-за README, но пропустил изменённый
index.html?
Ответы — в answers/04-branches.md.
Дальше: 5. Слияние и конфликт.
Подготовка исходного состояния (setup.md)
Блоки работают только в отдельной учебной папке ~/git-course. При неожиданном состоянии они отказывают до записи; ошибка после начала подготовки может оставить часть действий выполненной. Сначала прочитайте сообщение, не удаляйте существующие файлы. Для продолжения после предыдущей главы выбирайте только нужный блок.
Перед главой 4
Конец главы 3: коммит «Вернуть порт 8082». Выполняется после блоков «Перед главой 3».
bash <<'SETUP'
rp_before4() {
# --- Проверки: ничего не меняют ---
fail() { echo "Отказ: $1"; echo "Ничего не изменено."; return 1; }
course_repo() {
[ -d "$1/.git" ] || { fail "$1 — нет такого репозитория Git"; return 1; }
git -C "$1" log --max-parents=0 --format=%s 2>/dev/null | grep -qx 'Первая версия учебного сайта' \
|| { fail "$1 — не учебный репозиторий курса"; return 1; }
}
quiet() { # папка, ожидаемая ветка, разрешённые неотслеживаемые файлы через пробел
local x f
[ "$(git -C "$1" symbolic-ref --short -q HEAD)" = "$2" ] || { fail "в $1 текущая ветка не $2"; return 1; }
for x in MERGE_HEAD REVERT_HEAD CHERRY_PICK_HEAD REBASE_HEAD; do
git -C "$1" rev-parse -q --verify "$x" >/dev/null && { fail "в $1 не завершена операция Git ($x) — завершите или отмените её"; return 1; }
done
for x in rebase-merge rebase-apply; do
[ -e "$(git -C "$1" rev-parse --git-path "$x")" ] && { fail "в $1 не завершён rebase"; return 1; }
done
[ -z "$(git -C "$1" status --porcelain --untracked-files=no)" ] || { fail "в $1 есть незакоммиченные изменения"; return 1; }
for f in $(git -C "$1" ls-files --others --exclude-standard); do
case " $3 " in *" $f "*) ;; *) fail "в $1 лишний неотслеживаемый файл $f"; return 1 ;; esac
done
}
last_is() { [ "$(git -C "$1" log -1 --format=%s)" = "$2" ] || { fail "в $1 последний коммит не «$2»"; return 1; }; }
no_branch() { ! git -C "$1" show-ref --verify -q "refs/heads/$2" || { fail "в $1 уже есть ветка $2"; return 1; }; }
absent() { [ ! -e "$1" ] || { fail "$1 уже существует — переименуйте её сами, если хотите начать заново"; return 1; }; }
command -v git >/dev/null || { fail "не найден git"; return 1; }
course_repo ~/git-course/site || return 1
quiet ~/git-course/site main || return 1
last_is ~/git-course/site "Добавить контакты на страницу «О нас»" || return 1
# --- Подготовка: после первой записи при ошибке скрипт останавливается ---
(
set -e
cd ~/git-course/site
sed -i 's/^PORT=80x2/PORT=8082/' site.conf
git add site.conf
git commit -q -m "Вернуть порт 8082" -m "Опечатка 80x2 попала в коммит «Обновить описание запуска в README»."
)
if [ $? -ne 0 ]; then
echo "Ошибка после начала подготовки: часть шагов могла выполниться."
echo "Посмотрите git status и git log --oneline --all -5. Начать заново: переименуйте ~/git-course"
echo "(mv ~/git-course ~/git-course-old) и выполните блоки этого файла с начала."
return 1
fi
git -C ~/git-course/site log --oneline -1
echo "Готово: конец главы 3."
}
rp_before4 </dev/null
SETUP
Практика главы 4
Конец разбора главы 4: ветка schedule-page с одним коммитом, в main новый коммит README, вы на schedule-page,
index.html изменён и не закоммичен. Выполняется после блока «Перед главой 4». Ветки schedule-page быть не должно.
bash <<'SETUP'
rp_practice4() {
# --- Проверки: ничего не меняют ---
fail() { echo "Отказ: $1"; echo "Ничего не изменено."; return 1; }
course_repo() {
[ -d "$1/.git" ] || { fail "$1 — нет такого репозитория Git"; return 1; }
git -C "$1" log --max-parents=0 --format=%s 2>/dev/null | grep -qx 'Первая версия учебного сайта' \
|| { fail "$1 — не учебный репозиторий курса"; return 1; }
}
quiet() { # папка, ожидаемая ветка, разрешённые неотслеживаемые файлы через пробел
local x f
[ "$(git -C "$1" symbolic-ref --short -q HEAD)" = "$2" ] || { fail "в $1 текущая ветка не $2"; return 1; }
for x in MERGE_HEAD REVERT_HEAD CHERRY_PICK_HEAD REBASE_HEAD; do
git -C "$1" rev-parse -q --verify "$x" >/dev/null && { fail "в $1 не завершена операция Git ($x) — завершите или отмените её"; return 1; }
done
for x in rebase-merge rebase-apply; do
[ -e "$(git -C "$1" rev-parse --git-path "$x")" ] && { fail "в $1 не завершён rebase"; return 1; }
done
[ -z "$(git -C "$1" status --porcelain --untracked-files=no)" ] || { fail "в $1 есть незакоммиченные изменения"; return 1; }
for f in $(git -C "$1" ls-files --others --exclude-standard); do
case " $3 " in *" $f "*) ;; *) fail "в $1 лишний неотслеживаемый файл $f"; return 1 ;; esac
done
}
last_is() { [ "$(git -C "$1" log -1 --format=%s)" = "$2" ] || { fail "в $1 последний коммит не «$2»"; return 1; }; }
no_branch() { ! git -C "$1" show-ref --verify -q "refs/heads/$2" || { fail "в $1 уже есть ветка $2"; return 1; }; }
absent() { [ ! -e "$1" ] || { fail "$1 уже существует — переименуйте её сами, если хотите начать заново"; return 1; }; }
command -v git >/dev/null || { fail "не найден git"; return 1; }
course_repo ~/git-course/site || return 1
quiet ~/git-course/site main || return 1
last_is ~/git-course/site "Вернуть порт 8082" || return 1
no_branch ~/git-course/site schedule-page || return 1
# --- Подготовка: после первой записи при ошибке скрипт останавливается ---
(
set -e
cd ~/git-course/site
git switch -q -c schedule-page
cat > schedule.html <<'EOF'
<!doctype html>
<html lang="ru">
<head><meta charset="utf-8"><title>Расписание</title></head>
<body>
<h1>Расписание</h1>
<ul>
<li>Вторник, 19:00 — Git: основы</li>
<li>Четверг, 19:00 — практика</li>
</ul>
</body>
</html>
EOF
git add schedule.html
git commit -q -m "Добавить страницу расписания"
git switch -q main
sed -i '2a Страницы: index.html, about.html.\n' README.md
git add README.md
git commit -q -m "Перечислить страницы в README"
git switch -q schedule-page
cd ~/git-course/site
sed -i 's|<p><a href="about.html">О нас</a></p>|&\n<p><a href="schedule.html">Расписание</a></p>|' index.html
git status --short
)
if [ $? -ne 0 ]; then
echo "Ошибка после начала подготовки: часть шагов могла выполниться."
echo "Посмотрите git status и git log --oneline --all -5. Начать заново: переименуйте ~/git-course"
echo "(mv ~/git-course ~/git-course-old) и выполните блоки этого файла с начала."
return 1
fi
echo "Готово: вы на ветке schedule-page, index.html изменён."
}
rp_practice4 </dev/null
SETUP
Разбор и проверка понимания
Лист ученика: 04-branches.md. Хеши — из проверочного запуска.
Практика
Папка ~/git-course/site, ветка schedule-page.
printf '\nРасписание занятий — на странице schedule.html.\n' >> README.md
git switch main
error: Your local changes to the following files would be overwritten by checkout:
README.md
Please commit your changes or stash them before you switch branches.
Aborting
README в main и schedule-page различается. Переключение заменило бы файл версией из main, и ваша правка
потерялась бы — поэтому Git отказался и ничего не изменил. Задача закончена — коммитим:
git add index.html README.md
git commit -m "Добавить ссылки на страницу расписания"
git switch main
git status
grep -c schedule index.html
Switched to branch 'main'
On branch main
nothing to commit, working tree clean
0
git log --oneline --graph --all -5
* 64e50a3 Перечислить страницы в README
| * fc46230 Добавить ссылки на страницу расписания
| * 85bf960 Добавить страницу расписания
|/
* 04549f4 Вернуть порт 8082
* 3c45247 Добавить контакты на страницу «О нас»
Воспроизведено: Git 2.43.0, Ubuntu 24.04.
Проверка понимания
- В рабочей папке. Неотслеживаемый файл Git не хранит ни в одной ветке; на
mainон виден как?? имя-файла. Если закоммитить его там, он попадёт вmain. - Git отказывается, только когда переключение затёрло бы вашу правку.
index.htmlв обеих ветках одинаковый — заменять нечего, правка просто остаётся в папке. README разный — заменить файл значило бы потерять правку.
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.