Git workflow automation with conventional commits, merge vs rebase decision trees, worktrees, SSH signing, and monorepo sparse checkout patterns.
# BAD: vague, no type
git commit -m "changes"
# GOOD: clear type, scope, imperative mood
git commit -m "feat(auth): add OAuth2 login flow"
git commit -m "fix(api): handle null response in getUserData"
git commit -m "docs(readme): update installation steps"
Format: type(scope): description
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
Breaking changes:
git commit -m "feat(api)!: remove deprecated v1 endpoints"
# Or in body:
git commit -m "feat(api): migrate to v2 endpoints
BREAKING CHANGE: v1 endpoints removed, use v2"
# Install commitlint
npm install --save-dev @commitlint/config-conventional @commitlint/cli
# Create config
cat > commitlint.config.js <<'EOF'
module.exports = { extends: ['@commitlint/config-conventional'] };
EOF
# Hook with Husky
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
Question: Is the branch shared with others?
│
├─ YES → USE MERGE
│ └─ git merge feature-branch
│ (preserves branch history, safer for collaboration)
│
└─ NO → Ask: Do you want linear history?
│
├─ YES → USE REBASE
│ └─ git rebase main
│ (clean history, better for bisect)
│
└─ NO → USE MERGE
└─ git merge --no-ff feature-branch
(preserves feature branch context)
Rebase workflow for feature branches:
# Daily sync (before starting work)
git checkout feature-branch
git fetch origin
git rebase origin/main
# Interactive cleanup before PR
git rebase -i origin/main
# Squash "fix typo", "wip" commits
# After PR approval
git checkout main
git merge --ff-only feature-branch # or squash in GitHub UI
When rebase conflicts occur:
# BAD: panic and force push to main
git push --force origin main # NEVER DO THIS
# GOOD: resolve, test, continue
git rebase main
# Fix conflicts in files
git add resolved-file.js
git rebase --continue
npm test # Always test after resolving
git push --force-with-lease origin feature-branch
Abort and recover:
# Rebase went wrong? Abort it
git rebase --abort
# Already committed a bad rebase? Find previous state
git reflog
# Outputs: abc123 HEAD@{1}: rebase finished
# def456 HEAD@{2}: checkout: moving from main to feature
git reset --hard def456 # Go back before rebase
# BAD: stashing and switching repeatedly
git stash
git checkout hotfix-branch
# fix bug
git checkout main
git stash pop
# GOOD: use worktrees
git worktree add ../myproject-hotfix hotfix-branch
cd ../myproject-hotfix
# work on hotfix while main branch untouched in ../myproject
# List all worktrees
git worktree list
# Remove when done
git worktree remove ../myproject-hotfix
Use case: review PR while keeping main branch clean:
git worktree add ../myproject-pr-review pr/123
cd ../myproject-pr-review
npm install
npm test
# Review done
cd ../myproject
git worktree remove ../myproject-pr-review
# Generate signing key (Ed25519)
ssh-keygen -t ed25519 -C "your.email@example.com" -f ~/.ssh/git_signing
# Add public key to GitHub: Settings → SSH and GPG keys → New SSH key
# Paste contents of ~/.ssh/git_signing.pub, select "Signing Key"
# Configure git to use SSH signing
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/git_signing.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
# Test it
git commit --allow-empty -m "test: verify SSH signing"
git log --show-signature
# Should show: "Good signature with ED25519 key..."
Per-repository signing:
cd my-project
git config user.signingkey ~/.ssh/git_signing_work.pub # Different key for work
Problem: Monorepo with 50 packages, you only work on 2.
# BAD: clone entire 5GB repo
git clone https://github.com/company/monorepo.git
# Downloads 5GB, slow npm install across all packages
# GOOD: sparse checkout
git clone --filter=blob:none --sparse https://github.com/company/monorepo.git
cd monorepo
git sparse-checkout init --cone
git sparse-checkout set packages/frontend packages/api
# Only these directories are checked out:
# packages/frontend/
# packages/api/
# Root files (package.json, etc.)
Add more packages later:
git sparse-checkout add packages/shared-ui
Full monorepo strategy:
# 1. Initialize sparse checkout
git clone --filter=blob:none --sparse https://github.com/company/monorepo.git
cd monorepo
# 2. Set up workspace
git sparse-checkout set packages/my-app libs/shared-utils
# 3. Install dependencies (only for checked out packages)
npm install # or pnpm/yarn
# 4. Configure CODEOWNERS (in repo root)
cat > .github/CODEOWNERS <<'EOF'
/packages/frontend/ @frontend-team
/packages/api/ @backend-team
/libs/shared-utils/ @platform-team
EOF
# 5. Tag releases per package
git tag @company/frontend@1.2.0
git tag @company/api@2.1.5
git push origin --tags
# List conflicted files
git diff --name-only --diff-filter=U
# BAD: manually edit without understanding
vim conflicted.js # Hope for the best
# GOOD: use strategy for simple conflicts
git checkout --theirs package-lock.json # Take their version
git checkout --ours config/local.json # Keep our version
git add package-lock.json config/local.json
# For complex conflicts, use mergetool
git mergetool --tool=vimdiff # or meld, kdiff3, vscode
Test after every conflict resolution:
git rebase --continue
npm test || (git rebase --abort && echo "Tests failed, fix conflicts")
# Install lefthook (faster, polyglot alternative to Husky)
go install github.com/evilmartians/lefthook@latest
# Or: npm install lefthook --save-dev
# Create lefthook.yml
cat > lefthook.yml <<'EOF'
pre-commit:
parallel: true
commands:
lint:
glob: "*.{js,ts}"
run: eslint --fix {staged_files}
format:
glob: "*.{js,ts,json,md}"
run: prettier --write {staged_files}
types:
glob: "*.{ts,tsx}"
run: tsc --noEmit
commit-msg:
commands:
commitlint:
run: npx commitlint --edit {1}
pre-push:
commands:
test:
run: npm test
check-secrets:
run: gitleaks detect --no-git --verbose
EOF
# Install hooks
lefthook install
# 1. Create short-lived feature branch
git checkout -b feat/user-profile
# 2. Work in small increments (1-2 days max)
git commit -m "feat(profile): add avatar upload"
git commit -m "test(profile): add avatar validation tests"
# 3. Rebase daily
git fetch origin
git rebase origin/main
# 4. Open PR when ready (same day or next day)
gh pr create --title "Add user profile avatar upload"
# 5. Squash merge to main
gh pr merge --squash
# Long-lived branches: main, develop
# Supporting: feature/*, release/*, hotfix/*
# Start feature
git checkout develop
git checkout -b feature/payment-integration
# Finish feature
git checkout develop
git merge --no-ff feature/payment-integration
git branch -d feature/payment-integration
# Create release branch
git checkout -b release/1.2.0 develop
# Fix bugs in release/1.2.0
# Merge to main and develop
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release v1.2.0"
git checkout develop
git merge --no-ff release/1.2.0
git branch -d release/1.2.0
Only use Git Flow if:
# Lost commits after reset
git reflog
git cherry-pick abc123 # Restore lost commit
# Committed to wrong branch
git log # Copy commit hash
git checkout correct-branch
git cherry-pick abc123
git checkout wrong-branch
git reset --hard HEAD~1 # Remove from wrong branch
# Remove file from history (leaked secret)
git filter-repo --path secrets.env --invert-paths
# Note: force push required, coordinate with team
# Undo last commit (keep changes staged)
git reset --soft HEAD~1
# Undo last commit (keep changes unstaged)
git reset HEAD~1
# Undo last commit (discard changes - CAREFUL)
git reset --hard HEAD~1
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer