DEV Community

Rizwan Saleem
Rizwan Saleem

Posted on

A Git Workflow for Feature Flags, Safe Rollbacks, and Reproducible Releases

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:

  • main for 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

  1. Start from the latest main.
  2. Create a short-lived feature branch.
  3. Make small commits.
  4. Open a pull request early.
  5. Merge as soon as the branch is green and reviewed.
  6. Delete the branch after merge.

A typical sequence looks like this:

git checkout main
git pull origin main
git checkout -b feature/payment-banner
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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_");
}
Enter fullscreen mode Exit fullscreen mode
export function HomePage({ userId }: { userId: string }) {
  return (
    <>
      {isPaymentBannerEnabled(userId) ? <PaymentBanner /> : null}
      <MainContent />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 revert over reset on 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"
Enter fullscreen mode Exit fullscreen mode

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)