# Incremental Commit Protocol **Purpose**: Ensure clean, revertible git history through disciplined incremental commits **When to Use**: During ALL refactoring work **Origin**: Iteration 1 - Problem E3 (No Incremental Commit Discipline) --- ## Core Principle **Every refactoring step = One commit with passing tests** **Benefits**: - **Rollback**: Can revert any single change easily - **Review**: Small commits easier to review - **Bisect**: Can use `git bisect` to find which change caused issue - **Collaboration**: Easy to cherry-pick or rebase individual changes - **Safety**: Never have large uncommitted work at risk of loss --- ## Commit Frequency Rule **COMMIT AFTER**: - Every refactoring step (Extract Method, Rename, Simplify Conditional) - Every test addition - Every passing test run after code change - Approximately every 5-10 minutes of work - Before taking a break or switching context **DO NOT COMMIT**: - While tests are failing (except for WIP commits on feature branches) - Large batches of changes (>200 lines in single commit) - Multiple unrelated changes together --- ## Commit Message Convention ### Format ``` (): [optional body] [optional footer] ``` ### Types for Refactoring | Type | When to Use | Example | |------|-------------|---------| | `refactor` | Restructuring code without behavior change | `refactor(sequences): extract collectTimestamps helper` | | `test` | Adding or modifying tests | `test(sequences): add edge cases for calculateTimeSpan` | | `docs` | Adding/updating GoDoc comments | `docs(sequences): document calculateTimeSpan parameters` | | `style` | Formatting, naming (no logic change) | `style(sequences): rename ts to timestamp` | | `perf` | Performance improvement | `perf(sequences): optimize timestamp collection loop` | ### Scope **Use package or file name**: - `sequences` (for internal/query/sequences.go) - `context` (for internal/query/context.go) - `file_access` (for internal/query/file_access.go) - `query` (for changes across multiple files in package) ### Subject Line Rules **Format**: ` []` **Verbs**: - `extract`: Extract Method pattern - `inline`: Inline Method pattern - `simplify`: Simplify Conditionals pattern - `rename`: Rename pattern - `move`: Move Method/Field pattern - `add`: Add tests, documentation - `remove`: Remove dead code, duplication - `update`: Update existing code/tests **Examples**: - ✅ `refactor(sequences): extract collectTimestamps helper` - ✅ `refactor(sequences): simplify timestamp filtering logic` - ✅ `refactor(sequences): rename ts to timestamp for clarity` - ✅ `test(sequences): add edge cases for empty occurrences` - ✅ `docs(sequences): document calculateSequenceTimeSpan return value` **Avoid**: - ❌ `fix bugs` (vague, no scope) - ❌ `refactor calculateSequenceTimeSpan` (no scope, unclear what changed) - ❌ `WIP` (not descriptive, avoid on main branch) - ❌ `refactor: various changes` (not specific) ### Body (Optional but Recommended) **When to add body**: - Change is not obvious from subject - Multiple related changes in one commit - Need to explain WHY (not WHAT) **Example**: ``` refactor(sequences): extract collectTimestamps helper Reduces complexity of calculateSequenceTimeSpan from 10 to 7. Extracted timestamp collection logic to dedicated helper for clarity. All tests pass, coverage maintained at 85%. ``` ### Footer (For Tracking) **Pattern**: `Pattern: ` **Examples**: ``` refactor(sequences): extract collectTimestamps helper Pattern: Extract Method ``` ``` test(sequences): add edge cases for calculateTimeSpan Pattern: Characterization Tests ``` --- ## Commit Workflow (Step-by-Step) ### Before Starting Refactoring **1. Ensure Clean Baseline** ```bash git status ``` **Checklist**: - [ ] No uncommitted changes: `nothing to commit, working tree clean` - [ ] If dirty: Stash or commit before starting: `git stash` or `git commit` **2. Create Refactoring Branch** (optional but recommended) ```bash git checkout -b refactor/calculate-sequence-timespan ``` **Checklist**: - [ ] Branch created: `refactor/` - [ ] On correct branch: `git branch` shows current branch --- ### During Refactoring (Per Step) **For Each Refactoring Step**: #### 1. Make Single Change - Focused, minimal change (e.g., extract one helper method) - No unrelated changes in same commit #### 2. Run Tests ```bash go test ./internal/query/... -v ``` **Checklist**: - [ ] All tests pass: PASS / FAIL - [ ] If FAIL: Fix issue before committing #### 3. Stage Changes ```bash git add internal/query/sequences.go internal/query/sequences_test.go ``` **Checklist**: - [ ] Only relevant files staged: `git status` shows green files - [ ] No unintended files: Review `git diff --cached` **Review Staged Changes**: ```bash git diff --cached ``` **Verify**: - [ ] Changes are what you intended - [ ] No debug code, commented code, or temporary changes - [ ] No unrelated changes sneaked in #### 4. Commit with Descriptive Message ```bash git commit -m "refactor(sequences): extract collectTimestamps helper" ``` **Or with body**: ```bash git commit -m "refactor(sequences): extract collectTimestamps helper Reduces complexity from 10 to 7. Extracts timestamp collection logic to dedicated helper. Pattern: Extract Method" ``` **Checklist**: - [ ] Commit message follows convention - [ ] Commit hash: _______________ (from `git log -1 --oneline`) - [ ] Commit is small (<200 lines): `git show --stat` #### 5. Verify Commit ```bash git log -1 --stat ``` **Checklist**: - [ ] Commit message correct - [ ] Files changed correct - [ ] Line count reasonable (<200 insertions + deletions) **Repeat for each refactoring step** --- ### After Refactoring Complete **1. Review Commit History** ```bash git log --oneline ``` **Checklist**: - [ ] Each commit is small, focused - [ ] Each commit message is descriptive - [ ] Commits tell a story of refactoring progression - [ ] No "fix typo" or "oops" commits (if any, squash them) **2. Run Final Test Suite** ```bash go test ./... -v ``` **Checklist**: - [ ] All tests pass - [ ] Test coverage: `go test -cover ./internal/query/...` - [ ] Coverage ≥85%: YES / NO **3. Verify Each Commit Independently** (optional but good practice) ```bash git rebase -i HEAD~N # N = number of commits # For each commit: git checkout go test ./internal/query/... ``` **Checklist**: - [ ] Each commit has passing tests: YES / NO - [ ] Each commit is a valid state: YES / NO - [ ] If any commit fails tests: Reorder or squash commits --- ## Commit Size Guidelines ### Ideal Commit Size | Metric | Target | Max | |--------|--------|-----| | **Lines changed** | 20-50 | 200 | | **Files changed** | 1-2 | 5 | | **Time to review** | 2-5 min | 15 min | | **Complexity change** | -1 to -3 | -5 | **Rationale**: - Small commits easier to review - Small commits easier to revert - Small commits easier to understand in history ### When Commit is Too Large **Signs**: - >200 lines changed - >5 files changed - Commit message says "and" (doing multiple things) - Hard to write descriptive subject (too complex) **Fix**: - Break into multiple smaller commits: ```bash git reset HEAD~1 # Undo last commit, keep changes # Stage and commit parts separately git add git commit -m "refactor: " git add git commit -m "refactor: " ``` - Or use interactive staging: ```bash git add -p # Stage hunks interactively git commit -m "refactor: " ``` --- ## Rollback Scenarios ### Scenario 1: Last Commit Was Mistake **Undo last commit, keep changes**: ```bash git reset HEAD~1 ``` **Checklist**: - [ ] Commit removed from history: `git log` - [ ] Changes still in working directory: `git status` - [ ] Can re-commit differently: `git add` + `git commit` **Undo last commit, discard changes**: ```bash git reset --hard HEAD~1 ``` **WARNING**: This DELETES changes permanently - [ ] Confirm you want to lose changes: YES / NO - [ ] Backup created if needed: YES / NO / N/A --- ### Scenario 2: Need to Revert Specific Commit **Revert a commit** (keeps history, creates new commit undoing changes): ```bash git revert ``` **Checklist**: - [ ] Commit hash identified: _______________ - [ ] Revert commit created: `git log -1` - [ ] Tests pass after revert: PASS / FAIL **Example**: ```bash # Revert the "extract helper" commit git log --oneline # Find commit hash git revert abc123 # Revert that commit git commit -m "revert: extract collectTimestamps helper Tests failed due to nil pointer. Rolling back to investigate. Pattern: Rollback" ``` --- ### Scenario 3: Multiple Commits Need Rollback **Revert range of commits**: ```bash git revert .. ``` **Or reset to earlier state**: ```bash git reset --hard ``` **Checklist**: - [ ] Identified rollback point: - [ ] Confirmed losing commits OK: YES / NO - [ ] Branch backed up if needed: `git branch backup-$(date +%Y%m%d)` - [ ] Tests pass after rollback: PASS / FAIL --- ## Clean History Practices ### Practice 1: Squash Fixup Commits **Scenario**: Made small "oops" commits (typo fix, forgot file) **Before Pushing** (local history only): ```bash git rebase -i HEAD~N # N = number of commits to review # Mark fixup commits as "fixup" or "squash" # Save and close ``` **Example**: ``` pick abc123 refactor: extract collectTimestamps helper fixup def456 fix: forgot to commit test file pick ghi789 refactor: extract findMinMax helper fixup jkl012 fix: typo in variable name ``` **After rebase**: ``` abc123 refactor: extract collectTimestamps helper ghi789 refactor: extract findMinMax helper ``` **Checklist**: - [ ] Fixup commits squashed: YES / NO - [ ] History clean: `git log --oneline` - [ ] Tests still pass: PASS / FAIL --- ### Practice 2: Reorder Commits Logically **Scenario**: Commits out of logical order (test commit before code commit) **Reorder with Interactive Rebase**: ```bash git rebase -i HEAD~N # Reorder lines to desired sequence # Save and close ``` **Example**: ``` # Before: pick abc123 refactor: extract helper pick def456 test: add edge case tests pick ghi789 docs: add GoDoc comments # After (logical order): pick def456 test: add edge case tests pick abc123 refactor: extract helper pick ghi789 docs: add GoDoc comments ``` **Checklist**: - [ ] Commits reordered logically: YES / NO - [ ] Each commit still has passing tests: VERIFY - [ ] History makes sense: `git log --oneline` --- ## Git Hooks for Enforcement ### Pre-Commit Hook (Prevent Committing Failing Tests) **Create `.git/hooks/pre-commit`**: ```bash #!/bin/bash # Run tests before allowing commit go test ./... > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "❌ Tests failing. Fix tests before committing." echo "Run 'go test ./...' to see failures." echo "" echo "To commit anyway (NOT RECOMMENDED):" echo " git commit --no-verify" exit 1 fi echo "✅ Tests pass. Proceeding with commit." exit 0 ``` **Make executable**: ```bash chmod +x .git/hooks/pre-commit ``` **Checklist**: - [ ] Pre-commit hook installed: YES / NO - [ ] Hook prevents failing test commits: VERIFY - [ ] Hook can be bypassed if needed: `--no-verify` works --- ### Commit-Msg Hook (Enforce Commit Message Convention) **Create `.git/hooks/commit-msg`**: ```bash #!/bin/bash # Validate commit message format commit_msg_file=$1 commit_msg=$(cat "$commit_msg_file") # Pattern: type(scope): subject pattern="^(refactor|test|docs|style|perf)\([a-z_]+\): .{10,}" if ! echo "$commit_msg" | grep -qE "$pattern"; then echo "❌ Invalid commit message format." echo "" echo "Required format: type(scope): subject" echo " Types: refactor, test, docs, style, perf" echo " Scope: package or file name (lowercase)" echo " Subject: descriptive (min 10 chars)" echo "" echo "Example: refactor(sequences): extract collectTimestamps helper" echo "" echo "Your message:" echo "$commit_msg" exit 1 fi echo "✅ Commit message format valid." exit 0 ``` **Make executable**: ```bash chmod +x .git/hooks/commit-msg ``` **Checklist**: - [ ] Commit-msg hook installed: YES / NO - [ ] Hook enforces convention: VERIFY - [ ] Can be bypassed if needed: `--no-verify` works --- ## Commit Statistics (Track Over Time) **Refactoring Session**: ___ (e.g., calculateSequenceTimeSpan - 2025-10-19) | Metric | Value | |--------|-------| | **Total commits** | ___ | | **Commits with passing tests** | ___ | | **Average commit size** | ___ lines | | **Largest commit** | ___ lines | | **Smallest commit** | ___ lines | | **Rollbacks needed** | ___ | | **Fixup commits** | ___ | | **Commits per hour** | ___ | **Commit Discipline Score**: (Commits with passing tests) / (Total commits) × 100% = ___% **Target**: 100% commit discipline (every commit has passing tests) --- ## Example Commit Sequence **Refactoring**: calculateSequenceTimeSpan (Complexity 10 → <8) ```bash # Baseline abc123 test: add edge cases for calculateSequenceTimeSpan def456 refactor(sequences): extract collectOccurrenceTimestamps helper ghi789 test: add unit tests for collectOccurrenceTimestamps jkl012 refactor(sequences): extract findMinMaxTimestamps helper mno345 test: add unit tests for findMinMaxTimestamps pqr678 refactor(sequences): simplify calculateSequenceTimeSpan using helpers stu901 docs(sequences): add GoDoc for calculateSequenceTimeSpan vwx234 test(sequences): verify complexity reduced to 6 ``` **Statistics**: - Total commits: 8 - Average size: ~30 lines - Largest commit: def456 (extract helper, 45 lines) - All commits with passing tests: 8/8 (100%) - Complexity progression: 10 → 7 (def456) → 6 (pqr678) --- ## Notes - **Discipline**: Commit after EVERY refactoring step - **Small**: Keep commits <200 lines - **Passing**: Every commit must have passing tests - **Descriptive**: Subject line tells what changed - **Revertible**: Each commit can be reverted independently - **Story**: Commit history tells story of refactoring progression --- **Version**: 1.0 (Iteration 1) **Next Review**: Iteration 2 (refine based on usage data) **Automation**: See git hooks section for automated enforcement