lesson ── git-labs ── ~25 мин ── 11 шагов
Цель - руками пройти цикл работы через fork: clone, remote add upstream, ветвиться от свежего upstream, push в свой origin, sync через fetch + rebase + force-with-lease. Без настоящего GitHub: имитируем оригинал и fork двумя локальными bare-репозиториями.
Это тот же сценарий, что в книге, но с автопроверкой каждого шага.
интерактивный sandbox
Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.
stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя
Bare-репозиторий - серверная сторона git, без working tree. Имитируем им центральный сервер и форк.
cd /home/student/work
mkdir -p fork-lab && cd fork-lab
git init --bare original.git # --bare = серверный репо без working tree
git init --bare fork.git
ls -la
Видишь две .git-папки. Это «GitHub-как-будто».
✓ Bare-репозитории на месте.
Создадим временный working-репо, сделаем коммит, запушим в bare.
cd /home/student/work/fork-lab
mkdir seed && cd seed
git init -b main
echo "v1" > version.txt
git add . && git commit -m "init"
# remote = именованный URL для push/fetch; origin - локальный путь до bare-репо
git remote add origin /home/student/work/fork-lab/original.git
git push -u origin main # -u = set-upstream: запомнить связь main -> origin/main
cd .. && rm -rf seed # временный репо больше не нужен
Оригинал теперь содержит один коммит на main.
✓ Оригинал засеян. Дальше - имитируем fork.
На настоящем GitHub fork = «скопируй репо в мой namespace». Здесь - просто клонируем оригинал и пушим в fork.git.
cd /home/student/work/fork-lab
git clone original.git seed2
cd seed2
# set-url = переключить адрес существующего remote, не добавлять новый
git remote set-url origin /home/student/work/fork-lab/fork.git
git push origin main # пушим уже в fork.git, не в original.git
cd .. && rm -rf seed2
Теперь fork.git содержит тот же main, что и original.git. С этого момента они независимы.
✓ Fork имитирован. Дальше клонируем его как разработчик.
cd /home/student/work/fork-lab
git clone fork.git my-work
cd my-work
git remote -v # -v (verbose) = показать URL для fetch и push
Видишь один origin -> fork.git. Это твой свежий клон форка.
✓ Клон есть. Теперь добавим upstream.
cd /home/student/work/fork-lab/my-work
git remote add upstream /home/student/work/fork-lab/original.git
git fetch upstream # тянет refs/remotes/upstream/*, локальные ветки не трогает
git remote -v # теперь два remote: origin и upstream
Теперь два remote: origin (твой fork) и upstream (оригинал).
git fetch upstream загружает refs/remotes/upstream/* без
трогания твоих локальных веток.
✓ Два remote настроены. Теперь имитируем активность в оригинале.
Другой разработчик что-то запушил в оригинал. Имитируем это:
cd /home/student/work/fork-lab
git clone original.git orig-clone
cd orig-clone
echo "v2" > version.txt # перезаписываем v1 -> v2
git commit -am "bump to v2"
git push origin main # пушим в оригинал (origin тут указывает на original.git)
cd .. && rm -rf orig-clone
Оригинал ушёл вперёд. Твой fork - нет, твой clone - нет.
✓ Оригинал на 2 коммита, fork и my-work на 1. Расхождение есть.
cd /home/student/work/fork-lab/my-work
git fetch upstream # обновить refs/remotes/upstream/main
# switch -c <new> <start-point>: новая ветка от upstream/main, а не от local main
git switch -c feat/add-changelog upstream/main
Обрати внимание: ветка стартует от upstream/main, не от main.
main твоего форка отстаёт на v2. upstream/main свежий.
Сделай коммит:
echo "# Changelog" > CHANGELOG.md
git add . && git commit -m "add changelog"
✓ Ветка сделана от upstream, коммит на ней. Дальше - push в origin.
git push -u origin feat/add-changelog # -u = настроить tracking origin/feat/add-changelog
Запомни: пушим в origin (свой fork), не в upstream (оригинал). На настоящем GitHub упсtream обычно read-only для тебя.
✓ Ветка в твоём форке. На GitHub отсюда открывается PR.
Пока ты работал над веткой, в оригинале появился ещё коммит:
cd /home/student/work/fork-lab
git clone original.git orig-clone
cd orig-clone
echo 'docs added' > README.md
git add . && git commit -m "add readme"
git push origin main
cd .. && rm -rf orig-clone
✓ Оригинал ушёл ещё на коммит. Дальше синхронизация.
cd /home/student/work/fork-lab/my-work
git fetch upstream
git rebase upstream/main # переписать твои коммиты поверх свежей upstream/main
Это перепишет твой add changelog поверх свежей upstream/main
(которая сейчас содержит init + v2 + readme).
✓ Rebase прошёл. История теперь линейная.
После rebase SHA коммита изменился. Обычный push отказался бы. Используй force-with-lease:
# --force-with-lease: force-push с проверкой что remote не ушёл вперёд от твоего вида
git push --force-with-lease
--force-with-lease - не просто --force. Он проверяет, что
твой fork остался таким, каким ты его последний раз видел. Если
кто-то успел запушить туда - команда отменится с ошибкой
(stale info).
На public-репо с одним owner'ом разница не заметна. На совместных ветках - спасает от затирания чужих коммитов.
✓ Fork синхронизирован, история линейная. Это и есть «sync fork» из терминала.
Fork - два remote: origin (твоя копия) и upstream (оригинал). Ветка создаётся от upstream/main, push'ится в origin/*, sync через fetch+rebase. Force только with-lease.
команды
git remote add upstream <url>добавить оригинал как upstreamgit fetch upstreamполучить обновления оригиналаgit switch -c feat/x upstream/mainветвиться от свежего upstreamgit rebase upstream/mainдогнать оригинал после долгой работыgit push --force-with-leaseзапушить переписанную ветку безопасноконцепции