Stage 2 · Tools
Git Internals & Advanced
Capstone: Full Team Workflow
Simulate a complete product cycle — from branch to tagged release.
Scenario
You are building a CLI tool called taskctl. The team is releasing v1.0.0. This capstone walks through the entire lifecycle: feature development, review, release tagging, and a production hotfix.
Follow each phase step by step in your terminal.
Phase 1: Feature Development
mkdir taskctl && cd taskctl
git init
git commit --allow-empty -m "chore: initial commit"
git branch -M mainStart with an empty initial commit on main.
git switch -c feat/add-task-command
cat > main.go << 'EOF'
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: taskctl <command>")
os.Exit(1)
}
fmt.Printf("Running: %s\n", os.Args[1])
}
EOF
git add main.go
git commit -m "feat: add basic CLI entry point"Conventional commit format: type(scope): description.
# Add the add command
cat > cmd_add.go << 'EOF'
package main
import "fmt"
func addTask(name string) string {
return fmt.Sprintf("Added: %s", name)
}
EOF
git add cmd_add.go
git commit -m "feat(cmd): implement add command"
# Add tests
cat > cmd_add_test.go << 'EOF'
package main
import "testing"
func TestAddTask(t *testing.T) {
result := addTask("buy milk")
if result != "Added: buy milk" {
t.Errorf("expected 'Added: buy milk', got '%s'", result)
}
}
EOF
git add cmd_add_test.go
git commit -m "test(cmd): add tests for add command"Separate commits for feature and tests keep history granular.
Small, focused commits are easier to review, revert, and understand. Aim for one logical change per commit.
Phase 2: Code Review & Merge
# Push the feature branch
git push -u origin feat/add-task-command
# Simulate review: reviewer leaves a comment
git switch feat/add-task-command
echo "// TODO: handle empty name" >> cmd_add.go
git add cmd_add.go
git commit -m "fix(cmd): handle empty task name"
git pushPush, get feedback, address it, push again.
# Simulate GitHub squash merge
git switch main
git merge --squash feat/add-task-command
git commit -m "feat: implement add command with tests"
git push
git branch -d feat/add-task-commandSquash merge keeps main history clean — one commit per feature.
Squash keeps a clean linear history. Merge commits preserve the full feature branch detail. Choose based on your team's preference.
Phase 3: Release
# Create annotated tag
git tag -a v1.0.0 -m "Release v1.0.0: initial CLI with add command"
git push --tags
# Verify
git log --oneline --decorate -5Annotated tags capture who, when, and why — unlike lightweight tags.
git switch -c release/v1.0
git log --oneline main..release/v1.0 # nothing extra
git switch mainRelease branches are useful for cherry-picking fixes without pulling in new features.
Phase 4: Hotfix
# Branch from main
git switch main
git switch -c hotfix/fix-empty-name-crash
# Fix the bug
cat > cmd_add.go << 'EOF'
package main
import "fmt"
func addTask(name string) string {
if name == "" {
return "Error: task name cannot be empty"
}
return fmt.Sprintf("Added: %s", name)
}
EOF
git add cmd_add.go
git commit -m "fix: prevent crash on empty task name"
git push -u origin hotfix/fix-empty-name-crashHotfix branches are short-lived — fix, merge, tag.
# Merge to main
git switch main
git merge hotfix/fix-empty-name-crash
git push
# Tag patch release
git tag -a v1.0.1 -m "Hotfix: fix empty task name crash"
git push --tags
# Clean up
git branch -d hotfix/fix-empty-name-crashv1.0.1 is the patch. Semver: MAJOR.MINOR.PATCH — bug fixes bump PATCH.
git log --oneline --graph --all --decorateYour history now shows features, a squash merge, a tagged release, and a hotfix.
Summary
This capstone covered a complete development lifecycle:
- Feature branch with conventional commits
- Code review feedback loop
- Squash merge to main
- Annotated release tag (v1.0.0)
- Hotfix branch for production bug
- Patch release tag (v1.0.1)
Whether you are a solo developer or on a 100-person team, this pattern — branch, commit, review, merge, tag, hotfix — is the foundation of professional software delivery.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.