Ветки и merge до первой конфликтной ситуации
Ветка в Git - это подвижный указатель на коммит. Когда ты делаешь новый коммит, текущая ветка переезжает на него.
main
|
v
A -- B -- C
Если сейчас активна ветка main, и ты создаёшь новый коммит D, указатель main сдвинется:
A -- B -- C -- D
main -> D
Ветка не копирует весь проект. Это лёгкая ссылка на коммит, поэтому создавать ветки в Git быстро и нормально.
HEAD: где ты находишься
HEAD - это указатель на текущую позицию. В обычной работе HEAD указывает на текущую ветку, а ветка указывает на коммит.
HEAD -> main -> C
Посмотреть текущую ветку:
git branch
git status
В выводе git branch текущая ветка отмечена *.
Зачем нужны ветки
Ветки позволяют вести несколько линий работы:
main- стабильное состояние;feature/login- новая функциональность;feature/api- другая функциональность;hotfix/nginx-config- срочное исправление.
Без веток все изменения сразу попадают в одну линию истории. В командной работе это быстро приводит к хаосу: незаконченная фича может сломать main, а срочный hotfix смешается с экспериментами.
С ветками можно изолировать работу:
main: A -- B
\
feature/login: C -- D
Пока фича не готова, main остаётся на коммите B.
Создание и переключение веток
Создать ветку, но не переключаться:
git branch feature/login
Переключиться на ветку:
git switch feature/login
Создать и сразу переключиться:
git switch -c feature/api
Вернуться в main:
git switch main
Старые материалы часто используют git checkout -b feature/api. Это тоже работает, но в новых курсах лучше привыкать к git switch: команда читается яснее и отвечает только за переключение веток.
Коммит в ветке
После переключения все новые коммиты будут двигать текущую ветку.
git switch -c feature/login
echo "# login module" > login.py
git add login.py
git commit -m "add login"
История:
main: A -- B
\
feature/login: C
Если теперь переключиться в main, файл login.py исчезнет из working tree, потому что в main такого файла ещё нет. Это нормально: Git приводит файлы на диске к состоянию выбранной ветки.
Что делает merge
git merge соединяет изменения из одной ветки в другую. Важно: merge выполняется в ветку, на которой ты сейчас находишься.
Чтобы слить feature/login в main:
git switch main
git merge feature/login
Сначала переходишь на ветку-приёмник, потом сливаешь ветку-источник.
Fast-forward merge
Fast-forward происходит, когда целевая ветка не имеет новых коммитов с момента создания feature-ветки.
До merge:
main: A -- B
\
feature/login: C
После fast-forward:
main: A -- B -- C
feature/login: ^
Git просто передвигает указатель main на коммит C. Отдельный merge commit не нужен, потому что истории не расходились в две активные линии.
Merge commit
Если обе ветки успели получить новые коммиты, Git создаёт merge commit.
main: A -- B -- D
\ \
feature/login: C --- M
Коммит M имеет двух родителей: один из main, другой из feature/login. Такой коммит фиксирует факт слияния.
Посмотреть структуру:
git log --oneline --graph --all --decorate
--onelineделает каждый коммит одной строкой;--graphрисует ASCII-граф;--allпоказывает все ветки;--decorateпоказывает имена веток и тегов рядом с коммитами.
Удаление слитой ветки
После merge локальную feature-ветку часто удаляют:
git branch -d feature/login
-d удаляет только ветку, которая уже слита. Это защитный режим.
Если ветка не слита, Git предупредит. Есть принудительный режим:
git branch -D feature/experiment
Но на старте лучше не использовать -D без явной причины: можно потерять указатель на полезные коммиты.
Нельзя удалить ветку, на которой ты сейчас находишься. Сначала переключись:
git switch main
git branch -d feature/login
Когда возникает конфликт
Merge conflict возникает, когда Git не может автоматически совместить изменения. Чаще всего это одна и та же строка одного файла, изменённая по-разному в двух ветках.
Пример:
main меняет app.txt: mode=production
branch-b меняет там же: mode=debug
Git не знает, какой вариант правильный, поэтому останавливает merge и просит человека принять решение.
Как выглядит конфликт
В конфликтном файле появляются маркеры:
<<<<<<< HEAD
версия из текущей ветки
=======
версия из ветки, которую сливаешь
>>>>>>> branch-b
HEAD - это текущая ветка, то есть ветка-приёмник merge. Нижняя часть - incoming change из ветки, которую ты сливаешь.
Разрешить конфликт - значит отредактировать файл вручную: убрать маркеры и оставить итоговое содержимое.
Например, если нужно оставить только Resolved version.
файл после правки должен содержать именно это, без <<<<<<<, ======= и >>>>>>>.
Порядок разрешения конфликта
Типичный порядок:
git status
# открыть конфликтный файл и отредактировать
git add app.txt
git commit -m "resolve conflict"
Что происходит:
git statusпоказывает, какие файлы конфликтуют.- Ты редактируешь файл до правильного финального состояния.
git add app.txtсообщает Git, что конфликт в этом файле решён.git commitзавершает merge и создаёт merge commit.
После завершения полезно посмотреть граф:
git log --oneline --graph --all
Конфликт - не ошибка Git. Это ситуация, где автоматическое объединение было бы опаснее, чем остановка и ручной выбор.
