Git Basics for Mobile Teams
A cookbook for a branching model that respects store release freezes - trunk-based integration on main, short-lived feature branches, and release lanes that do not collide with App Store or Play review windows.
Search across all documentation pages
A cookbook for a branching model that respects store release freezes - trunk-based integration on main, short-lived feature branches, and release lanes that do not collide with App Store or Play review windows.
Quick-reference recipe card - copy-paste ready.
# Daily feature work
git checkout main && git pull --ff-only
git checkout -b feat/SHIP-412-checkout-summary
# ... commits ...
git push -u origin feat/SHIP-412-checkout-summary
# Open PR → squash merge to main after review + CI green# Release cut (Tuesday - before Thursday freeze)
git checkout main && git pull --ff-only
git checkout -b release/2.6.0
git push -u origin release/2.6.0
# Only bugfixes cherry-picked or PRs targeting release/2.6.0 until tag# Tag after QA sign-off (post-review)
git checkout release/2.6.0
git tag -a v2.6.0 -m "Release 2.6.0 - build_id abc123"
git push origin v2.6.0Branch lanes (mobile)
main ← integration; always green CI
feat/* ← days, squash merge
release/X.Y ← weeks; freeze during store review
hotfix/X.Y.Z ← hours; from tag, cherry-pick to main
ota/production ← optional long-lived for channel-only JS (see GitHub Flow page)When to reach for this:
When to avoid:
main + tags may be enough.develop lasting months - mobile store review punishes drift.End-to-end feature → release → freeze → tag for an Expo SDK 57 app shipping through EAS.
Step 1 - Bootstrap repo hygiene
npx create-expo-app@latest ShipApp --template blank-typescript
cd ShipApp
git init# .gitignore - mobile essentials (do NOT ignore lockfile)
node_modules/
.expo/
dist/
*.jks
*.p8
*.p12
*.mobileprovision
ios/Pods/
android/.gradle/
android/app/build/{
"scripts": {
"lint": "expo lint",
"typecheck": "tsc --noEmit",
"test": "jest",
"format:check": "prettier --check .",
"prepare": "husky"
}
}# .husky/pre-commit
npm run format:check && npm run lint && npm run typecheckpackage-lock.json - CI uses npm ci (Mobile CI/CD Basics)Step 2 - Feature branch with ticket-scoped commits
git checkout main && git pull --ff-only
git checkout -b feat/SHIP-412-checkout-summary
git add src/screens/CheckoutSummary.tsx
git commit -m "feat(checkout): add order summary sheet [SHIP-412]"
git push -u origin feat/SHIP-412-checkout-summaryPR targets main. Squash merge preserves one commit per feature - easier cherry-picks to release/* later.
Step 3 - Cut release branch before freeze
<!-- docs/release-calendar.md (excerpt) -->
| Week | Event | Git rule |
|------|--------------------------------|-----------------------------------|
| W12 | Cut release/2.6.0 Tue 10:00 | New features → main only |
| W12 | Code freeze Thu 18:00 UTC | release/2.6.0: bugfixes only |
| W12 | Submit Fri after Maestro | Tag v2.6.0 from release branch |
| W13 | Store review in flight | No native changes on release/2.6.0|git checkout main && git pull --ff-only
git checkout -b release/2.6.0
git push -u origin release/2.6.0Branch protection on release/2.6.0:
quality from GitHub Actions Integration)Step 4 - Cherry-pick a bugfix during freeze
A critical fix lands on main after the cut. Cherry-pick - do not merge all of main.
git checkout release/2.6.0 && git pull --ff-only
git cherry-pick <sha-from-main>
git push origin release/2.6.0# Verify the cherry-pick is the only diff vs tag candidate
git log --oneline main..release/2.6.0
git diff main...release/2.6.0 --statStep 5 - Tag triggers release pipeline
git checkout release/2.6.0
git tag -a v2.6.0 -m "Release 2.6.0 - QA build_id abc123"
git push origin v2.6.0Tag v* triggers eas build in CI - same artifact QA tested (Automated Store Promotion).
Step 6 - Merge release back to main
After store approval and staged rollout:
git checkout main && git pull --ff-only
git merge --ff-only release/2.6.0
git push origin main
git branch -d release/2.6.0
git push origin --delete release/2.6.0| Rule | Web habit | Mobile reality |
|---|---|---|
| Lockfile | Sometimes ignored | Always commit - reproducible native builds |
ios/ / android/ | N/A | Commit after prebuild; review native diffs on SDK bumps |
Long develop branch | Common | Avoid - OTA and store binaries diverge from main |
| Merge Friday 5 p.m. | Acceptable | Risky - no on-call for broken store binary |
Force-push main | Rare disaster | Never - breaks release tags and OTA audit trail |
# Inspect what changed in native folders before merging SDK bump
git diff main -- ios/ android/ app.json eas.json
npx expo-doctormain into release/* during review - Pulls in un-QA'd features; store rejection risk. Fix: Cherry-pick only listed SHAs.main - Rebase before PR; outdated runtimeVersion conflicts surface late. Fix: git fetch && git rebase origin/main weekly..env committed "just for CI" - Secrets in Git forever. Fix: EAS environment variables (Environments and EAS Environment Variables).git lfs track "*.psd" or host in CDN.| Alternative | Use When | Don't Use When |
|---|---|---|
Trunk-only (main + tags) | Solo dev, weekly store releases | Parallel QA on next version during review |
GitFlow develop | Legacy enterprise mandate | Greenfield mobile - prefer release branches |
Monorepo apps/mobile | Shared packages | Without path filters in CI - burns minutes |
| Fork-based OSS workflow | External contributors | Internal team - branch on same repo |
Target 1–3 days. Longer branches conflict with app.json version bumps, native folder changes, and EAS profile edits. Rebase or merge main daily.
Bugfixes with QA evidence, copy tweaks that do not change native code, and security patches. No new screens, permissions, or Expo modules - those need a new store binary and reset review clock.
JS-only hotfixes on the same runtimeVersion can publish from main to the production channel after soak. Store-bound code must match the tagged release/* commit QA signed off on. See Release Channels & Branches.
Squash for feat/* → main - one ticket, one commit. Merge commit optional for release/* → main if you want to preserve release boundary. Never squash a release branch without team agreement - loses cherry-pick SHAs.
Use a dedicated long-lived spike branch or git worktrees - do not block main for two weeks while expo upgrade runs.
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026