A Git Workflow for Feature Flags, Safe Rollbacks, and Reproducible Releases
A Git Workflow for Feature Flags, Safe Rollbacks, and Reproducible Releases
A strong Git workflow is not just about branches; it is about making every change easy to ship, easy to undo, and easy to understand. This guide shows a practical version-control process built around feature flags, small commits, release tags, and safe rollback commands.
Why this workflow works
The core idea is simple: keep changes small, merge often, and make production changes reversible without rewriting shared history. Trunk-based development supports frequent merges to a single main branch and is commonly used to reduce merge conflict pain and keep code releasable.
Feature flags let you merge code before you are ready to expose it to users, which makes Git history reflect integration progress instead of only launch dates. When something goes wrong in production, git revert is the safer undo tool for public branches because it preserves history by creating a new commit that reverses the bad change.
The branch model
Use three kinds of branches:
-
mainfor always-releasable code. - Short-lived
feature/*branches for work in progress. - Optional
release/*branches only when you need a stabilization window before a tagged release.
A good rule is to keep feature branches alive for hours or days, not weeks. Long-lived branches increase merge drift, while trunk-based development encourages small, frequent integration to keep the mainline healthy.
Day-to-day process
- Start from the latest
main. - Create a short-lived feature branch.
- Make small commits.
- Open a pull request early.
- Merge as soon as the branch is green and reviewed.
- Delete the branch after merge.
A typical sequence looks like this:
git checkout main
git pull origin main
git checkout -b feature/payment-banner
Then commit in small steps:
git add src/banner.tsx
git commit -m "feat(banner): add payment promo copy"
git add src/banner.tsx src/flags.ts
git commit -m "feat(flags): gate banner behind launch flag"
This style of commit history makes reviews easier and helps tools infer release notes from conventional commit types.
Using feature flags well
Feature flags should separate deployment from exposure. You can merge the code, deploy it safely, and keep the feature hidden until product or QA turns it on. That reduces the pressure to hold back merged work just because a launch is not ready yet.
A simple pattern in JavaScript might look like this:
export function isPaymentBannerEnabled(userId: string) {
return userId.startsWith("beta_");
}
export function HomePage({ userId }: { userId: string }) {
return (
<>
{isPaymentBannerEnabled(userId) ? <PaymentBanner /> : null}
<MainContent />
</>
);
}
Keep flags named clearly and remove them after launch, because old flags create hidden complexity and make code harder to reason about over time.
Releasing safely
When you are ready to ship, tag the exact commit that went to production. Tags create a stable reference point for rollbacks, audits, and release notes. Git tags are especially useful when combined with semantic commit messages and automated release tooling.
A release sequence might look like this:
git checkout main
git pull origin main
git tag -a v1.8.0 -m "Release 1.8.0"
git push origin v1.8.0
If you need a stabilization phase, create a release branch from main, apply only bug fixes there, and merge those fixes back afterward so the history stays consistent. Release branches are usually short-lived and used for final testing, documentation updates, and bug fixes.
Undoing mistakes
Use git revert when the bad change is already shared with others. It creates a new commit that reverses the earlier one, which keeps the branch history intact and avoids disrupting teammates.
Examples:
git revert 4f2c91a
git revert no-edit 4f2c91a
Use git reset only on private work when you truly want to rewrite local history. Git documentation and tutorials consistently treat reset as a history-changing operation, while revert is the safer public-branch option.
For uncommitted work, git stash is the escape hatch:
git stash push -m "wip: checkout flow"
git stash list
git stash pop
That is useful when you need to switch tasks quickly without committing half-finished changes.
A practical team policy
A team workflow can be written as a few rules:
- Merge only small, reviewable pull requests.
- Require CI to pass before merge.
- Use conventional commits for readable history.
- Tag every production release.
- Prefer
revertoverreseton shared branches. - Keep feature flags temporary and visible in code review.
This policy keeps development moving without turning Git history into a mystery. It also makes incidents easier to fix because you can trace, tag, and undo changes with less guesswork.
Example workflow in practice
Imagine you are adding a new checkout warning banner. You create feature/checkout-warning, implement the UI behind a flag, commit in small chunks, and merge it to main once CI passes. Later, if the banner causes a production issue, you can disable the flag immediately and then revert the offending commit cleanly on the shared branch.
That gives you three layers of safety: hide the feature, revert the code, and tag the stable release. In real teams, that combination is often more reliable than relying on branching alone.
Command cheat sheet
### Start work
git checkout main
git pull origin main
git checkout -b feature/my-change
### Commit work
git add .
git commit -m "feat(scope): describe change"
### Ship safely
git checkout main
git pull origin main
git tag -a v1.0.0 -m "Release 1.0.0"
git push origin v1.0.0
### Undo a shared mistake
git revert <commit-sha>
### Temporarily set work aside
git stash push -m "wip"
Use this workflow when you want Git to help you ship continuously without losing control of releases or recoverability. The result is a codebase that is easier to review, easier to deploy, and easier to repair.
-
Rizwan Saleem | https://rizwansaleem.co
Top comments (0)