lesson ── git-labs ── ~25 мин ── 11 шагов
The goal is to walk the fork workflow by hand: clone, remote add upstream, branch from a fresh upstream, push to your own origin, sync via fetch + rebase + force-with-lease. No real GitHub: you simulate the original and the fork with two local bare repositories.
This is the same scenario as in the book, but with an automatic check on every step.
интерактивный sandbox
Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.
stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя
A bare repository is the server side of git, with no working tree. You use it to simulate the central server and the fork.
cd /home/student/work
mkdir -p fork-lab && cd fork-lab
git init --bare original.git # --bare = server repo with no working tree
git init --bare fork.git
ls -la
You see two .git folders. This is "GitHub, sort of".
✓ Both bare repositories are in place.
Create a temporary working repo, make a commit, push it to the 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 = a named URL for push/fetch; origin is the local path to the bare repo
git remote add origin /home/student/work/fork-lab/original.git
git push -u origin main # -u = set-upstream: remember the link main -> origin/main
cd .. && rm -rf seed # the temporary repo is no longer needed
The original now holds one commit on main.
✓ The original is seeded. Next you simulate the fork.
On real GitHub, fork means "copy the repo into my namespace". Here you just clone the original and push it to fork.git.
cd /home/student/work/fork-lab
git clone original.git seed2
cd seed2
# set-url = change the address of an existing remote, do not add a new one
git remote set-url origin /home/student/work/fork-lab/fork.git
git push origin main # push to fork.git now, not to original.git
cd .. && rm -rf seed2
Now fork.git holds the same main as original.git. From this point on they are independent.
✓ Fork simulated. Next you clone it as a developer.
cd /home/student/work/fork-lab
git clone fork.git my-work
cd my-work
git remote -v # -v (verbose) = show the URLs for fetch and push
You see one origin -> fork.git. This is your fresh clone of the fork.
✓ You have a clone. Now add upstream.
cd /home/student/work/fork-lab/my-work
git remote add upstream /home/student/work/fork-lab/original.git
git fetch upstream # pulls refs/remotes/upstream/*, leaves local branches untouched
git remote -v # two remotes now: origin and upstream
You now have two remotes: origin (your fork) and upstream (the original).
git fetch upstream downloads refs/remotes/upstream/* without
touching your local branches.
✓ Both remotes are set up. Now simulate activity in the original.
Another developer pushed something to the original. Simulate it:
cd /home/student/work/fork-lab
git clone original.git orig-clone
cd orig-clone
echo "v2" > version.txt # overwrite v1 -> v2
git commit -am "bump to v2"
git push origin main # push to the original (origin here points to original.git)
cd .. && rm -rf orig-clone
The original moved ahead. Your fork did not, your clone did not.
✓ The original has 2 commits, the fork and my-work have 1. They have diverged.
cd /home/student/work/fork-lab/my-work
git fetch upstream # refresh refs/remotes/upstream/main
# switch -c <new> <start-point>: new branch from upstream/main, not from local main
git switch -c feat/add-changelog upstream/main
Note: the branch starts from upstream/main, not from main.
Your fork's main lags behind at v2. upstream/main is fresh.
Make a commit:
echo "# Changelog" > CHANGELOG.md
git add . && git commit -m "add changelog"
✓ The branch is made from upstream and has your commit. Next, push to origin.
git push -u origin feat/add-changelog # -u = set up tracking origin/feat/add-changelog
Remember: push to origin (your fork), not to upstream (the original). On real GitHub, upstream is usually read-only for you.
✓ Your branch is in your fork. On GitHub you open a PR from here.
While you worked on the branch, the original gained one more commit:
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
✓ The original moved one more commit ahead. Next, sync.
cd /home/student/work/fork-lab/my-work
git fetch upstream
git rebase upstream/main # replay your commits on top of the fresh upstream/main
This replays your add changelog on top of the fresh upstream/main
(which now holds init + v2 + readme).
✓ Rebase finished. The history is now linear.
After the rebase the commit's SHA changed. A plain push would refuse. Use force-with-lease:
# --force-with-lease: force-push with a check that the remote has not moved ahead of your view
git push --force-with-lease
--force-with-lease is more than just --force. It checks that
your fork is still what you last saw. If someone managed to push
there, the command aborts with a (stale info) error.
On a public repo with a single owner the difference is invisible. On shared branches it saves you from clobbering other people's commits.
✓ Your fork is synced and the history is linear. That is "sync fork", done from the terminal.
A fork means two remotes: origin (your copy) and upstream (the original). A branch is created from upstream/main, pushed to origin/*, synced via fetch+rebase. Force only with with-lease.
команды
git remote add upstream <url>add the original as upstreamgit fetch upstreamget updates from the originalgit switch -c feat/x upstream/mainbranch from a fresh upstreamgit rebase upstream/maincatch up with the original after long workgit push --force-with-leasepush a rewritten branch safelyконцепции