Git Workflow
Branch Strategy
Pivot uses a linear promotion workflow:
development → main → production
development → A → B → C → D → E → F → G (most commits)
main → A → B → C → D → E (staging, tested subset)
production → A → B → C (live)
Feature Branch Workflow
Working on Features
When developing new features, developers create feature branches off development:
development: A → B → C → Dfeature-branch: A → B → C → Feature work → More work
This creates divergence - both branches have different commits. This is normal and expected for feature branches!
Updating Your Feature Branch (IMPORTANT)
When development has moved forward and you need to incorporate those changes into your feature branch, you MUST use rebase, not merge.
# ✅ Always use rebase
git checkout feature-branch
git rebase development
git push --force-with-lease
GitHub branch protection enforces linear history - PRs with merge commits will be blocked.
For detailed explanations, visual diagrams, common mistakes and how to fix them, see the Git Rebase Guide.
Merging Feature Branches
When merging a feature branch into development via Pull Request, you have options:
Option 1: Squash Merge (Recommended)
All feature commits get squashed into a single commit on development:
Result: ✅ Clean, linear history. Feature commits become one new commit with a new hash.
Option 2: Merge Commit
Creates a merge commit joining both histories:
Result: ⚠️ More complex history with merge commits.
Option 3: Rebase + Fast-Forward
Rebase feature commits onto development, then fast-forward:
Result: ✅ Linear history, but feature commits get new hashes (F1*, F2*) because they were replayed on top of D.
Key Point: Feature Branches Are Different
- Feature branches SHOULD diverge from development - that's normal!
- Merging feature branches creates new commits or changes hashes - that's expected!
- Environment branches (main, production) should NEVER diverge - only fast-forward!
Environment Promotion (main, production)
For promoting between environments, we use a different strategy:
Fast-Forward vs Rebase vs Merge
Scenario 1: No Divergence (Normal Workflow)
When all commits happen in development first, and main just follows:
Before merge:
development: A → B → C → D → E → F (all work happens here)main: A → B → C
After fast-forward main to development:
development: A → B → C → D → E → Fmain: A → B → C → D → E → F (same commits, same hashes)
Result: ✅ Commits D, E, F keep their exact same Git hashes
Fast-forward and rebase produce identical results - they just move the pointer forward.
# These do the same thing when no divergence:
git merge --ff-only development # Fast-forward
git rebase development # Rebase (no-op, then fast-forward)
This is what should always happen in our workflow since we never commit directly to main or production.
Scenario 2: Divergence (What to Avoid)
When you accidentally commit directly to main (or have commits on both branches), they have different histories - this is called divergence:
Before rebase (branches diverged):
development: A → B → C → READMEmain: A → B → C → Fix (hash: 5aea767) ← committed directly to main!
After rebasing main onto development:
development: A → B → C → README → Fixmain: A → B → C → README → Fix (hash changed!)
Result: ⚠️ Commit "Fix" gets a new hash (5aea767 → b577685)
The hash changes because the commit was replayed with a new parent (README), creating a new commit object.
Merge commits create complicated Git history and should be avoided.
Best Practice
✅ Always use fast-forward or rebase (they're the same when no divergence)
- Keeps linear history
- Simple, clean Git log
- Easy to understand
❌ Merge commits only needed when history has diverged
- Creates complicated branching structure
- Should be avoided in our linear promotion workflow
- Indicates someone committed directly to main/production (which shouldn't happen)
Why No PRs for Environment Promotion
We don't use Pull Requests when promoting code between environment branches (development → main → production). Here's why:
PRs Add No Value for Fast-Forward
| What PRs provide | Why it doesn't apply here |
|---|---|
| Code review | Code was already reviewed in the feature PR |
| Discussion | Nothing to discuss - same commits, same hashes |
| Approval gate | What would you approve? "Yes, move the pointer"? |
| CI checks | Configure CI to trigger on push, not PR |
The Real Gate is Development
The meaningful review happens when feature branches merge to development. Once code is in development:
- It's been reviewed
- It's been tested
- It's "on the train" to production
Adding a PR for promotion is like checking your ID twice at the same door.
What About Audit Trail?
Git log already shows exactly when main or production moved forward:
git log --oneline main
# Shows every commit with timestamps
What About Compliance?
Some organizations require PRs for compliance reasons. If that's a hard requirement, do it - but recognize it's checkbox theater, not adding real safety.
The Clean Approach
Instead of creating PRs, just push:
# Option 1: Merge locally and push
git checkout main
git merge --ff-only development
git push origin main
# Option 2: Push directly (even simpler)
git push origin development:main
This is faster, cleaner, and the git history stays linear without merge commits.
Commands
Merging development → main
git checkout main
git merge --ff-only development # Fails if diverged (good safety check)
If fast-forward fails, investigate why main diverged (normally this shouldn't happen).
Merging main → production
git checkout production
git merge --ff-only main # Fails if diverged (good safety check)
Summary
As long as we follow the rule:
- Never commit directly to main or production
- All work happens in development first
- Promote by moving the head pointer forward
Then:
- Fast-forward and rebase produce identical results
- All commit hashes stay the same
- History stays clean and linear
In Pivot's workflow:
- Fast-forward = Rebase (same result when no divergence)
- Merge commits = Complicated history (only when diverged)
- Changes only flow one direction: development → main → production
- Branches diverge when they have different histories (commits on both branches)
Hotfix Process
Sometimes bugs need to be fixed faster than the normal train allows. We have three levels of hotfix urgency:
Standard Hotfix (Recommended)
Use when: Bug is important but can wait for normal testing flow.
development → (PR) → main → (cherry-pick after testing) → production
Steps:
- Create a feature branch from
development - Fix the bug and open a PR to
development - After PR is merged, cherry-pick the commit to
main - Test the fix in staging
- Cherry-pick the commit to
production
# After PR merges to development
git checkout main
git cherry-pick <commit-hash>
git push origin main
# After testing in staging
git checkout production
git cherry-pick <commit-hash>
git push origin production
Urgent Hotfix
Use when: Bug is critical and needs to go to production quickly, but you still have time to test in staging first.
main → (PR) → production → (back-merge to development)
Steps:
- Create a feature branch from
main(staging) - Fix the bug and open a PR directly to
main - Test the fix in staging
- Cherry-pick the commit to
production - Back-merge: merge
mainintodevelopmentto sync
# After testing fix in staging
git checkout production
git cherry-pick <commit-hash>
git push origin production
# Sync development with main
git checkout development
git merge main
git push origin development
Emergency Hotfix
Use when: Production is on fire. No time to test in staging. Fix it NOW.
production → (PR) → back-merge to main → back-merge to development
Steps:
- Create a feature branch from
production - Fix the bug and open a PR directly to
production - After fix is live and verified, back-merge to sync all branches
# After fix is live in production
git checkout main
git merge production
git push origin main
git checkout development
git merge main
git push origin development
Emergency hotfixes bypass staging testing. Only use this when production is critically broken and every minute counts.
Hotfix Summary
| Level | Speed | Testing | Target Branch | Risk |
|---|---|---|---|---|
| Standard | Normal | Full flow | development | Lowest |
| Urgent | Fast | Staging only | main | Medium |
| Emergency | Immediate | None | production | Highest |
After Any Hotfix
Always ensure branches are synchronized after hotfixes:
production ⊆ main ⊆ development
All commits in production should exist in main, and all commits in main should exist in development. Run these checks:
# Check if production is subset of main
git log production --not main --oneline # Should be empty
# Check if main is subset of development
git log main --not development --oneline # Should be empty
If either shows commits, you need to back-merge.
Related Documentation
- Git Rebase Guide - Detailed guide on merge vs rebase with visual examples
- Monday.com Development Workflow - How development integrates with Monday.com task tracking