- Introduction
- Git Configuration for Graphite
- Complex Stack Examples
- Managing Partially Merged Stacks
- Conflict Resolution Strategies
- Advanced Workflows
- Automation and Scripts
- Team Collaboration
- Performance Optimization
- Real-World Scenarios
This advanced guide covers complex scenarios, best practices, and deep dives into Graphite's stacked PR workflow. It assumes familiarity with basic Graphite concepts and commands.
# Enable rerere (Reuse Recorded Resolution)
git config rerere.enabled true
git config rerere.autoupdate true
# Set up helpful aliases
git config alias.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
# Configure merge strategy
git config pull.rebase true
git config rebase.autoStash true
# Graphite-specific settings
gt repo init --trunk mainGit rerere (reuse recorded resolution) is crucial for Graphite workflows:
- Automatically remembers how you resolved conflicts
- Applies the same resolution when encountering identical conflicts
- Essential when restacking multiple branches with similar conflicts
# Step 1: Database schema changes
gt branch create "feat/user-system-db-schema"
echo "CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255));" > migrations/001_users.sql
git add migrations/001_users.sql
git commit -m "Add users table schema"
gt pr create --title "Add users database schema" --body "Foundation for user system"
# Step 2: Model layer
gt branch create "feat/user-system-models" --parent "feat/user-system-db-schema"
cat > models/user.py << EOF
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
id: Optional[int] = None
email: str
EOF
git add models/user.py
git commit -m "Add User model"
gt pr create --title "Add User model" --body "Basic user model implementation"
# Step 3: Repository layer
gt branch create "feat/user-system-repository" --parent "feat/user-system-models"
cat > repositories/user_repository.py << EOF
from models.user import User
from typing import Optional, List
class UserRepository:
async def create(self, user: User) -> User:
# Implementation here
pass
async def find_by_email(self, email: str) -> Optional[User]:
# Implementation here
pass
EOF
git add repositories/user_repository.py
git commit -m "Add UserRepository"
gt pr create --title "Add UserRepository" --body "Repository pattern for user data access"
# Step 4: Service layer
gt branch create "feat/user-system-service" --parent "feat/user-system-repository"
cat > services/user_service.py << EOF
from repositories.user_repository import UserRepository
from models.user import User
class UserService:
def __init__(self, repo: UserRepository):
self.repo = repo
async def register_user(self, email: str) -> User:
# Business logic here
user = User(email=email)
return await self.repo.create(user)
EOF
git add services/user_service.py
git commit -m "Add UserService with registration logic"
gt pr create --title "Add UserService" --body "Business logic for user operations"
# Step 5: API endpoints
gt branch create "feat/user-system-api" --parent "feat/user-system-service"
cat > api/users.py << EOF
from fastapi import APIRouter, Depends
from services.user_service import UserService
router = APIRouter()
@router.post("/users")
async def create_user(email: str, service: UserService = Depends()):
return await service.register_user(email)
EOF
git add api/users.py
git commit -m "Add user registration API endpoint"
gt pr create --title "Add user API endpoints" --body "REST API for user operations"
# View the complete stack
gt stack# Starting with a monolithic function that needs refactoring
gt branch create "refactor/split-payment-processing-step1"
# Step 1: Extract validation logic
cat > validators/payment_validator.py << EOF
def validate_credit_card(card_number: str) -> bool:
# Luhn algorithm implementation
return len(card_number) == 16 and card_number.isdigit()
def validate_amount(amount: float) -> bool:
return amount > 0 and amount <= 10000
EOF
git add validators/payment_validator.py
git commit -m "Extract payment validation logic"
gt pr create --title "Extract payment validation" --body "Step 1: Separate validation concerns"
# Step 2: Extract payment gateway interface
gt branch create "refactor/split-payment-processing-step2" --parent "refactor/split-payment-processing-step1"
cat > gateways/payment_gateway.py << EOF
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
async def charge(self, amount: float, token: str) -> dict:
pass
class StripeGateway(PaymentGateway):
async def charge(self, amount: float, token: str) -> dict:
# Stripe-specific implementation
pass
EOF
git add gateways/payment_gateway.py
git commit -m "Create payment gateway abstraction"
gt pr create --title "Add payment gateway interface" --body "Step 2: Abstract payment provider logic"
# Step 3: Refactor main payment processor
gt branch create "refactor/split-payment-processing-step3" --parent "refactor/split-payment-processing-step2"
cat > processors/payment_processor.py << EOF
from validators.payment_validator import validate_credit_card, validate_amount
from gateways.payment_gateway import PaymentGateway
class PaymentProcessor:
def __init__(self, gateway: PaymentGateway):
self.gateway = gateway
async def process_payment(self, card_number: str, amount: float) -> dict:
if not validate_credit_card(card_number):
raise ValueError("Invalid card number")
if not validate_amount(amount):
raise ValueError("Invalid amount")
token = self._tokenize_card(card_number)
return await self.gateway.charge(amount, token)
def _tokenize_card(self, card_number: str) -> str:
# Tokenization logic
return f"tok_{card_number[-4:]}"
EOF
git add processors/payment_processor.py
git commit -m "Refactor payment processor to use new components"
gt pr create --title "Complete payment processing refactor" --body "Step 3: Wire everything together"
# Step 4: Add tests for the refactored code
gt branch create "refactor/split-payment-processing-tests" --parent "refactor/split-payment-processing-step3"
cat > tests/test_payment_processor.py << EOF
import pytest
from processors.payment_processor import PaymentProcessor
from gateways.payment_gateway import PaymentGateway
class MockGateway(PaymentGateway):
async def charge(self, amount: float, token: str) -> dict:
return {"status": "success", "transaction_id": "123"}
@pytest.mark.asyncio
async def test_valid_payment():
processor = PaymentProcessor(MockGateway())
result = await processor.process_payment("4111111111111111", 100.0)
assert result["status"] == "success"
@pytest.mark.asyncio
async def test_invalid_card():
processor = PaymentProcessor(MockGateway())
with pytest.raises(ValueError, match="Invalid card number"):
await processor.process_payment("invalid", 100.0)
EOF
git add tests/test_payment_processor.py
git commit -m "Add comprehensive tests for payment processing"
gt pr create --title "Add payment processing tests" --body "Step 4: Ensure refactoring maintains functionality"When a PR in the middle of your stack gets merged before the ones below it:
# Initial stack state
# main
# βββ feat/api-base (#101) [OPEN]
# βββ feat/api-auth (#102) [MERGED] β This got merged first
# βββ feat/api-endpoints (#103) [OPEN]
# Step 1: Sync with remote to get the latest main
gt repo sync
# Step 2: Graphite detects the merged PR and offers to restack
gt stack restack
# Graphite will:
# 1. Update main with the merged changes
# 2. Rebase feat/api-base onto the new main
# 3. Rebase feat/api-endpoints onto feat/api-base
# If there are conflicts during restacking:
gt stack fix
# Example conflict resolution flow:
# Graphite: Conflict detected in feat/api-base
# 1. Resolve conflicts in your editor
# 2. Stage resolved files: git add <files>
# 3. Continue: gt stack fix --continue# After reviewers approve and merge PRs #101 and #102 with squash commits
# Original stack:
# main
# βββ feat/logging-base (#101) [MERGED with changes]
# βββ feat/logging-impl (#102) [MERGED with changes]
# βββ feat/logging-tests (#103) [OPEN]
# βββ feat/logging-docs (#104) [OPEN]
# Step 1: Sync and let Graphite detect merged PRs
gt repo sync
# Graphite output:
# Detected merged PRs: #101, #102
# Would you like to restack the remaining branches? [Y/n]
# Step 2: Restack remaining branches
gt stack restack
# If the squashed commits differ significantly from your original commits:
# You might encounter conflicts. Use rerere to help:
git rerere diff # Shows recorded resolutions
# Step 3: Update PR descriptions for remaining PRs
gt pr update --body "Updated after base PRs were merged"
# Step 4: Check the new stack state
gt stack
# main
# βββ feat/logging-tests (#103) [OPEN] β Now based on main
# βββ feat/logging-docs (#104) [OPEN]# PR #102 has review comments that need addressing
gt checkout feat/api-auth
# Make the requested changes
vim src/auth.py # Address review comments
git add src/auth.py
git commit -m "Address review: Add input validation"
# Update the PR
gt pr update
# Push changes and update upstack branches
gt repo sync
# The changes automatically propagate to dependent branches
# Graphite handles rebasing feat/api-endpoints onto the updated feat/api-auth# When dealing with complex conflicts across multiple branches
gt stack fix --one-at-a-time
# This allows you to:
# 1. Fix conflicts in one branch
# 2. Test that branch
# 3. Move to the next branch
# 4. Repeat until all conflicts are resolved# For similar conflicts across multiple branches
gt stack fix
# Use git rerere to record resolutions
git add .
git rerere # Records the resolution
# Continue with the fix
gt stack fix --continue
# Future similar conflicts will be auto-resolved# Before creating a new branch in the stack
# Check for potential conflicts
git diff main..HEAD --name-only | grep -E "(config|settings)"
# If files that commonly cause conflicts are modified,
# consider creating a separate base branch for those changes
gt branch create "config/update-settings"
# Make config changes
git add config/
git commit -m "Update configuration files"
gt pr create
# Then base your feature stack on this branch
gt branch create "feat/new-feature" --parent "config/update-settings"# Create two parallel tracks that merge at the end
# Track 1: Backend changes
gt branch create "feat/backend-models"
# ... make changes ...
gt pr create
gt branch create "feat/backend-api" --parent "feat/backend-models"
# ... make changes ...
gt pr create
# Track 2: Frontend changes (parallel to backend)
gt checkout main
gt branch create "feat/frontend-components"
# ... make changes ...
gt pr create
gt branch create "feat/frontend-views" --parent "feat/frontend-components"
# ... make changes ...
gt pr create
# Convergence point: Integration
gt branch create "feat/integration" --parent "feat/backend-api"
# Manually merge frontend changes
git merge feat/frontend-views --no-ff
git commit -m "Integrate frontend and backend changes"
gt pr create
# View the complex stack
gt stack --all# Base: Add feature flag infrastructure
gt branch create "feat/add-feature-flags"
cat > feature_flags.py << EOF
FLAGS = {
"new_payment_flow": False,
"enhanced_analytics": False,
}
EOF
git add feature_flags.py
git commit -m "Add feature flag system"
gt pr create
# Implementation behind flag
gt branch create "feat/new-payment-impl" --parent "feat/add-feature-flags"
cat > payment_v2.py << EOF
from feature_flags import FLAGS
def process_payment(amount):
if FLAGS["new_payment_flow"]:
return _new_payment_flow(amount)
return _legacy_payment_flow(amount)
EOF
git add payment_v2.py
git commit -m "Implement new payment flow behind flag"
gt pr create
# Enable flag
gt branch create "feat/enable-payment-flag" --parent "feat/new-payment-impl"
sed -i '' 's/"new_payment_flow": False/"new_payment_flow": True/' feature_flags.py
git add feature_flags.py
git commit -m "Enable new payment flow"
gt pr create --draft # Keep as draft until ready to enable# Current stack has 5 PRs, but you need to hotfix PR #2
# Save current stack state
gt stack --format json > stack-backup.json
# Create hotfix branch from the problematic PR
gt checkout feat/problematic-branch
gt branch create "hotfix/urgent-fix"
# Make the fix
vim src/critical_file.py
git add src/critical_file.py
git commit -m "Fix critical bug in data processing"
# Create PR directly against main (bypass stack)
gt pr create --base main --no-stack
# After hotfix is merged, restore stack
gt repo sync
gt stack restack#!/bin/bash
# save as ~/.graphite/scripts/auto-restack.sh
set -e
echo "π Starting auto-restack process..."
# Fetch latest changes
gt repo sync
# Check for merged PRs
MERGED_COUNT=$(gt stack --format json | jq '[.[] | select(.pr_state == "MERGED")] | length')
if [ "$MERGED_COUNT" -gt 0 ]; then
echo "π Found $MERGED_COUNT merged PRs"
# Backup current state
gt stack --format json > ~/.graphite/stack-backup-$(date +%Y%m%d-%H%M%S).json
# Attempt restack
if gt stack restack --no-interactive; then
echo "β
Restack successful!"
# Run tests on each affected branch
for branch in $(gt stack --format json | jq -r '.[].name'); do
echo "π§ͺ Testing $branch..."
gt checkout "$branch"
if npm test; then
echo "β
Tests passed for $branch"
else
echo "β Tests failed for $branch"
exit 1
fi
done
else
echo "β Restack failed, manual intervention required"
exit 1
fi
else
echo "βΉοΈ No merged PRs found, stack is up to date"
fi#!/bin/bash
# save as ~/.graphite/scripts/generate-pr-description.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PARENT=$(gt branch info --json | jq -r '.parent // "main"')
echo "## Summary"
echo ""
echo "### Changes"
git log --oneline "$PARENT".."$BRANCH" | sed 's/^/- /'
echo ""
echo "### Files Modified"
git diff --stat "$PARENT".."$BRANCH" | head -n -1
echo ""
echo "### Stack Position"
gt stack | grep -A2 -B2 "$BRANCH" || echo "Branch: $BRANCH"
echo ""
echo "### Testing"
echo "- [ ] Unit tests pass"
echo "- [ ] Integration tests pass"
echo "- [ ] Manual testing completed"# .graphite/team-config.yml
stack_conventions:
branch_naming:
pattern: "{type}/{ticket}-{description}"
types: ["feat", "fix", "refactor", "docs", "test"]
pr_size_limits:
max_lines_changed: 400
max_files_changed: 20
required_stack_depth:
min: 2
max: 7
auto_restack:
enabled: true
on_pr_merge: true
run_tests: true# Developer A starts the foundation
gt branch create "feat/JIRA-123-api-foundation"
# ... implements base API structure ...
gt pr create --reviewers "developerB,developerC"
# Developer B can start working on top while A's PR is in review
gt checkout feat/JIRA-123-api-foundation
gt branch create "feat/JIRA-124-api-auth"
# ... implements authentication ...
gt pr create --reviewers "developerA,developerC"
# Developer C can work on a parallel feature
gt checkout feat/JIRA-123-api-foundation
gt branch create "feat/JIRA-125-api-validation"
# ... implements validation ...
gt pr create --reviewers "developerA,developerB"
# Handling updates from Developer A
# When A pushes updates to the foundation:
gt repo sync
gt stack restack # Automatically rebases B and C's work# For stacks with 10+ PRs, use targeted operations
# Update only a specific part of the stack
gt stack restack --only "feat/specific-branch"
# Skip CI for intermediate branches
gt pr create --no-ci --title "[SKIP CI] Intermediate change"
# Batch PR creation
for i in {1..5}; do
gt branch create "feat/part-$i" --parent "feat/part-$((i-1))"
# make changes
git commit -m "Part $i implementation"
done
gt pr create --all # Creates all PRs at once# Pre-record common conflict resolutions
# Create a rerere training script
cat > train-rerere.sh << 'EOF'
#!/bin/bash
# Common conflict patterns in your codebase
# Pattern 1: Import conflicts
echo "Training rerere for import conflicts..."
git checkout -b rerere-train-1
echo "import { A } from './a';" > test.js
git add test.js && git commit -m "Add import A"
git checkout -b rerere-train-2
echo "import { B } from './b';" > test.js
git add test.js && git commit -m "Add import B"
git checkout rerere-train-1
git merge rerere-train-2 # Creates conflict
# Resolve the conflict
echo -e "import { A } from './a';\nimport { B } from './b';" > test.js
git add test.js
git commit -m "Merge imports"
# Clean up
git checkout main
git branch -D rerere-train-1 rerere-train-2
EOF
chmod +x train-rerere.sh
./train-rerere.sh# Stack: A -> B -> C -> D -> E
# Problem: PR B introduced a critical bug after merge
# Step 1: Create revert PR for B
gt pr create --revert PR_NUMBER_OF_B
# Step 2: Rebase remaining stack without B's changes
gt checkout C
git rebase --onto A B C
gt checkout D
git rebase --onto C B@{1} D
gt checkout E
git rebase --onto D C@{1} E
# Step 3: Force update the PRs
gt pr update --force# Team A's stack: auth-base -> auth-impl -> auth-tests
# Team B's stack: ui-base -> ui-components -> ui-integration
# Need to create integration PR
# Step 1: Create integration branch
gt checkout auth-tests
gt branch create "feat/auth-ui-integration"
# Step 2: Merge UI changes
git merge ui-integration --no-ff -m "Merge UI components for auth"
# Step 3: Integration code
cat > integration/auth_ui_connector.py << EOF
from auth.service import AuthService
from ui.components import LoginForm
class AuthUIConnector:
def __init__(self):
self.auth_service = AuthService()
self.login_form = LoginForm()
def wire_authentication(self):
self.login_form.on_submit = self.auth_service.authenticate
EOF
git add integration/
git commit -m "Wire auth backend to UI components"
gt pr create --reviewers "teamA-lead,teamB-lead"# Migrating from OldAPI to NewAPI across the codebase
# Step 1: Add new API alongside old
gt branch create "migrate/add-new-api"
cp -r src/old_api src/new_api
# ... modify new_api implementation ...
git add src/new_api
git commit -m "Add NewAPI implementation"
gt pr create
# Step 2: Add compatibility layer
gt branch create "migrate/compatibility-layer" --parent "migrate/add-new-api"
cat > src/api_compat.py << EOF
from src.old_api import OldAPI
from src.new_api import NewAPI
class APICompat:
def __init__(self, use_new=False):
self.impl = NewAPI() if use_new else OldAPI()
def __getattr__(self, name):
return getattr(self.impl, name)
EOF
git add src/api_compat.py
git commit -m "Add API compatibility layer"
gt pr create
# Step 3-N: Migrate each component
components=("auth" "payment" "user" "admin")
for comp in "${components[@]}"; do
parent=$(gt branch current)
gt branch create "migrate/${comp}-to-new-api" --parent "$parent"
# Update imports
find "src/${comp}" -name "*.py" -exec sed -i '' 's/from src.old_api/from src.api_compat/g' {} +
git add "src/${comp}"
git commit -m "Migrate ${comp} to use compatibility layer"
gt pr create
done
# Final step: Remove old API
gt branch create "migrate/remove-old-api" --parent $(gt branch current)
rm -rf src/old_api
sed -i '' 's/use_new=False/use_new=True/g' src/api_compat.py
git add -A
git commit -m "Remove OldAPI and default to NewAPI"
gt pr createThis advanced guide demonstrates:
- Complex Stack Patterns: Building multi-layered features with clear dependencies
- Merge Management: Handling partial merges and automatic restacking
- Conflict Resolution: Using git rerere and strategic approaches
- Team Workflows: Coordinating multiple developers on interconnected features
- Automation: Scripts and tools to streamline stack management
- Real-World Solutions: Practical approaches to common challenges
Remember:
- Always run
gt repo syncbefore starting work - Use
gt stack fixfor complex conflict scenarios - Enable git rerere for repeated conflict patterns
- Keep individual PRs focused and reviewable
- Automate repetitive tasks with scripts
- Communicate stack dependencies clearly in PR descriptions