lesson ── git-labs ── ~22 мин ── 7 шагов
The goal is to build a trunk-based workflow by hand and see why it looks "flat" compared to GitHub Flow. Each feature lands in main as a small commit behind a flag, then the flag is turned on, then it is removed. No long-lived feature branches.
интерактивный sandbox
Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.
stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя
cd /home/student/work
mkdir -p trunk-lab && cd trunk-lab
git init -b main
cat > app.py <<'EOF'
FLAGS = {}def greet(name):
return f"Hello, {name}"print(greet("world"))EOF
git add . && git commit -m "init: greet"
FLAGS = {} is a placeholder for the flags you will add as you
go. This is a typical pattern.
✓ The base is ready. FLAGS is empty.
The feature is uppercase letters in the greeting. First add it behind a flag that is off: the change lands in main, but the behavior does not change.
cd /home/student/work/trunk-lab
git switch -c feat/uppercase
cat > app.py <<'EOF'
FLAGS = {"uppercase": False}def greet(name):
out = f"Hello, {name}" if FLAGS.get("uppercase"):out = out.upper()
return out
print(greet("world"))EOF
git add . && git commit -m "feat(uppercase): scaffold behind flag, default off"
git switch main
# --no-ff = always create a merge commit, even when a fast-forward is possible
git merge --no-ff feat/uppercase -m "merge feat/uppercase"
git branch -d feat/uppercase # delete it right away: the branch is merged, no longer needed
python3 app.py # output is "Hello, world": flag is off
The key point: the output Hello, world is the same. The feature
is in main, but it is off. The branch lived about 30 seconds.
✓ The feature is in main, the behavior did not change.
cd /home/student/work/trunk-lab
sed -i 's/"uppercase": False/"uppercase": True/' app.py # turn the flag on in place
git commit -am "enable(uppercase): turn on for everyone"
python3 app.py # now "HELLO, WORLD"
The output is now HELLO, WORLD. This is a separate commit: you
can roll back only the activation without touching the feature
code itself.
✓ The flag is on, the behavior changed.
Picture two developers working in parallel. The second feature is a custom greeting.
cd /home/student/work/trunk-lab
git switch -c feat/greeting
# add the key "greeting": False to the FLAGS dict
sed -i 's/FLAGS = {"uppercase": True}/FLAGS = {"uppercase": True, "greeting": False}/' app.py# replace the literal "Hello" with a conditional choice based on the flag
sed -i 's/out = f"Hello, {name}"/g = "Hi" if FLAGS.get("greeting") else "Hello"\n out = f"{g}, {name}"/' app.pygit commit -am "feat(greeting): customizable, behind flag"
git switch main
git merge --no-ff feat/greeting -m "merge feat/greeting"
git branch -d feat/greeting
python3 app.py # "HELLO, WORLD" again: greeting is off
The output is HELLO, WORLD again: greeting is off. But the
feature is already in main.
✓ The second feature is merged, the behavior did not change.
cd /home/student/work/trunk-lab
git log --oneline --graph --all
You see 4 commits, two merge commits from --no-ff (this is a
convention so the features stay markable). But logically
everything moves along main. No parallel long-lived branches.
✓ Four commits, a flat structure.
cd /home/student/work/trunk-lab
sed -i 's/"greeting": False/"greeting": True/' app.py
git commit -am "enable(greeting): GA"
python3 app.py # "HI, WORLD": both features are active
The output is HI, WORLD. Both features are active.
✓ Both features work.
Once the features are stable and there is no reason to turn them off anymore, remove the flags so the code does not accumulate dead code.
cd /home/student/work/trunk-lab
cat > app.py <<'EOF'
def greet(name):
return f"Hi, {name}".upper()print(greet("world"))EOF
git commit -am "cleanup: remove uppercase + greeting flags (both GA)"
python3 app.py
git log --oneline --graph
The output is the same: HI, WORLD. But the code is clean: no
FLAGS, no if. The graph is a linear history of the features.
This is trunk-based in its final form.
✓ The flags are removed, the code is clean. The history is linear.
Trunk-based means one eternal branch (main) and short-lived feature branches that live for hours or a day. Features merge behind flags, are turned on separately, and the flags are removed once they run reliably.
команды
git switch -c feat/x && ... && git switch main && git merge feat/xa short branch, an hour to a day of lifegrep -r FLAGS .find every feature flag so you can clean them upконцепции