Git workflow patterns including conventional commits, branch naming, PR descriptions, merge strategies, and advanced operations like bisect, reflog, and interactive rebase
Every commit message MUST follow the Conventional Commits specification:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
| Type | When to use |
|---|---|
feat | New feature visible to users |
fix | Bug fix visible to users |
docs | Documentation-only changes |
style | Formatting, whitespace (no logic change) |
refactor | Code restructuring without behavior change |
perf | Performance improvement |
test | Adding or correcting tests |
build | Build system or external dependency changes |
ci | CI configuration and scripts |
chore | Maintenance tasks that don't modify src or tests |
Append ! after the type/scope, and include a BREAKING CHANGE: footer:
feat(api)!: remove deprecated /v1/users endpoint
BREAKING CHANGE: The /v1/users endpoint has been removed. Use /v2/users instead.
<type>/<ticket-id>-<short-description>
feat/PROJ-123-user-authfix/PROJ-456-null-pointer-crashchore/PROJ-789-upgrade-dependenciesNever work directly on main or develop. Always branch.
Structure every PR description with:
Keep the PR title under 70 characters. Use imperative mood ("Add feature" not "Added feature").
Anti-pattern: Never merge main into your feature branch. Rebase onto main instead:
git fetch origin
git rebase origin/main
Use descriptive stash messages:
git stash push -m "WIP: auth middleware token refresh logic"
Prefer git stash push over git stash save (deprecated).
To stash only specific files:
git stash push -m "partial work" -- path/to/file.ts
List and apply by index or message:
git stash list
git stash apply stash@{2}
git stash pop # apply + drop
Clean up old stashes regularly. Stashes are not a substitute for branches.
Use bisect to find the commit that introduced a bug:
git bisect start
git bisect bad # current commit is broken
git bisect good <known-good> # last known working commit
# Git checks out a middle commit. Test it, then:
git bisect good # or
git bisect bad
# Repeat until Git identifies the culprit
git bisect reset # return to original HEAD
Automate with a test script:
git bisect start HEAD v1.2.0
git bisect run ./test-script.sh
The reflog is your safety net. It records every HEAD movement:
git reflog # see recent HEAD changes
git checkout HEAD@{3} # go back 3 moves
git branch recovery-branch HEAD@{5} # recover lost commits
Reflog entries expire after 90 days (30 for unreachable commits). Do not rely on it as permanent storage.
Apply a specific commit to the current branch:
git cherry-pick <commit-hash>
git cherry-pick -x <commit-hash> # adds "cherry picked from" to message
For a range of commits:
git cherry-pick A..B # excludes A, includes B
git cherry-pick A^..B # includes both A and B
Anti-pattern: Do not cherry-pick as a routine workflow. It creates duplicate commits. Prefer merging or rebasing.
Clean up history before opening a PR:
git rebase -i origin/main
Common operations in the interactive editor:
pick -- keep commit as-isreword -- change commit messagesquash -- combine with previous commit, edit messagefixup -- combine with previous commit, discard messagedrop -- remove commit entirelyedit -- pause to amend the commitRule: Never rebase commits that have been pushed to a shared branch.
eslint, ruff, golangci-lint)prettier, black, gofmt)detect-secrets or gitleaks)lint-staged to only check staged filesTODO or FIXME markers that should be resolvedUse husky (Node.js) or pre-commit (Python) to manage hooks in version control. Never rely on manually installed local hooks.
// package.json (husky)
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
.env, and secrets to .gitignoregit pull --rebase before pushgit add . blindly -- review git status and stage specific files