a73x

specs/015-gerrit-style-patchsets/plan.md

Ref:   Size: 6.9 KiB

# Implementation Plan: Patch Revisions

**Branch**: `015-gerrit-style-patchsets` | **Date**: 2026-03-21 | **Spec**: [spec.md](spec.md)

## Summary

Add numbered revisions to git-collab patches, following the Radicle model (patch + revisions). Each revision snapshots the branch tip (commit OID + tree OID) in the review DAG. Inline comments and reviews are anchored to specific revisions. Interdiff between any two revisions shows exactly what changed. Revision numbers are derived from topological DAG order during materialization, making concurrent detection safe.

## Technical Context

**Language/Version**: Rust 2021 edition
**Primary Dependencies**: git2 0.19, clap 4 (derive), serde/serde_json 1, chrono 0.4, ed25519-dalek 2
**Storage**: Git object database (collab DAG refs under `.git/refs/collab/`)
**Testing**: cargo test (integration tests in `tests/`, unit tests inline)
**Target Platform**: Linux/macOS/Windows CLI
**Project Type**: CLI tool with TUI dashboard
**Performance Goals**: N/A (CLI tool, operations are fast by nature of git2)
**Constraints**: All data in git objects — no external state, no network dependencies
**Scale/Scope**: Single-repo, multi-collaborator via git remotes

## Constitution Check

*No constitution configured for this project. No gates to check.*

## Design Decisions

### 1. New Event Type: `patch.revision`

Add `PatchRevision` variant to `Action` enum in `src/event.rs`:
- Fields: `commit: String`, `tree: String`, `body: Option<String>`
- Serialized as `"type": "patch.revision"`
- No revision number stored — derived from DAG order during materialization

### 2. Modified `PatchCreate` Event

Add required `commit` and `tree` fields to `Action::PatchCreate` in `src/event.rs`:
- These capture revision 1's snapshot at creation time
- Required fields (not optional) — no backward compat needed

### 3. Revision-Anchored Comments and Reviews

Add required `revision: u32` field to `Action::PatchInlineComment` and `Action::PatchReview`:
- Set at event creation time from the current derived revision number
- Not added to `Action::PatchComment` (thread comments are patch-level)

### 4. PatchState Gains Revision Tracking

In `src/state.rs`, `PatchState` gains:
- `revisions: Vec<Revision>` — ordered list of revision snapshots
- `Revision` struct: `{ number: u32, commit: String, tree: String, body: Option<String>, timestamp: String }`
- During materialization: PatchCreate contributes revision 1 (if `commit`/`tree` present), each `PatchRevision` event contributes subsequent revisions
- Dedup: skip `PatchRevision` events whose `commit` matches an already-seen commit OID

### 5. Auto-Detection on Write Operations

In `src/patch.rs`, before appending events in `comment()`, `review()`, and `merge()`:
1. Materialize current `PatchState` to get the last recorded revision's commit OID
2. Resolve current branch tip via `resolve_head()`
3. If they differ, append a `PatchRevision` event first (with current tip's commit + tree OID)
4. Then append the actual event with the (possibly updated) revision number

Extract this as a helper: `fn auto_detect_revision(repo, ref_name, state, sk) -> Result<u32>`

### 6. `patch revise` Command Repurposed

The `PatchRevise` action is removed and replaced by `PatchRevision`. The CLI command `patch revise`:
- Resolves current branch tip
- Compares to last recorded revision's commit OID
- If different: appends `PatchRevision` event with optional `--body`
- If same: rejects with "no changes since revision N"

### 7. CLI Changes

**Modified commands**:
- `patch comment` — add `--revision N` flag (optional, defaults to latest)
- `patch review` — add `--revision N` flag (optional, defaults to latest)
- `patch show` — add `--revision N` flag (filters inline comments/reviews to that revision)
- `patch diff` — add `--revision N` flag (historical diff: revision N vs merge-base) and `--between N [M]` flag (interdiff; single arg = N to latest)
- `patch revise` — now appends `PatchRevision` event instead of `PatchRevise`; keeps `--body`

**New command**:
- `patch log <id>` — lists all revisions with number, timestamp, commit short OID, file-change summary; supports `--json`

### 8. Interdiff Implementation

In `src/patch.rs`, new function `interdiff(repo, patch, from_rev, to_rev) -> Result<String>`:
- Look up tree OIDs for the two revision numbers from `PatchState.revisions`
- `repo.find_tree(from_tree)` and `repo.find_tree(to_tree)`
- `repo.diff_tree_to_tree(from, to, None)`
- Format as unified diff (same as existing `generate_diff`)

### 9. Historical Diff

In `src/patch.rs`, modify `generate_diff` to accept an optional revision number:
- If provided, use that revision's tree OID instead of the current branch tip
- Merge-base is still computed against the base branch

### 10. Merge Policy Config

In `refs/collab/config` (project-level, synced):
- New key: `merge.require_approval_on_latest` (boolean, default false)
- `patch merge` reads this config before proceeding
- If true, checks that at least one `Approve` review exists with `revision == latest_revision_number`
- Config read/write helpers in a new section of `src/patch.rs` or extracted to a config module

### 11. TUI Updates

In `src/tui/`:
- Patch list view: show revision count (e.g., "r3") alongside status
- Patch detail view: show revision list, annotate inline comments with revision number
- No new TUI interactions for this feature (revision browsing is CLI-only for now)

### 12. No Backward Compatibility Needed

The project is pre-release with no existing patches. The old `PatchRevise` event type is removed entirely. All new fields (`commit`/`tree` on PatchCreate, `revision` on inline comments/reviews) are required, not optional.

## Project Structure

### Documentation (this feature)

```text
specs/015-gerrit-style-patchsets/
├── plan.md              # This file
├── spec.md              # Feature specification
├── research.md          # Phase 0 output
├── data-model.md        # Phase 1 output
├── contracts/           # Phase 1 output (CLI contract)
└── tasks.md             # Phase 2 output
```

### Source Code (repository root)

```text
src/
├── event.rs    # Add PatchRevision variant, modify PatchCreate/PatchInlineComment/PatchReview
├── state.rs    # Add Revision struct, revisions vec to PatchState, dedup logic
├── patch.rs    # Auto-detect helper, interdiff, historical diff, patch log, merge policy
├── cli.rs      # Add --revision, --between flags; add patch log subcommand
├── lib.rs      # Dispatch for new/modified CLI commands
└── tui/
    └── widgets.rs  # Show revision count in patch list, revision annotations in detail

tests/
├── revision_test.rs  # New: revision recording, dedup, interdiff, anchored comments
└── cli_test.rs       # Extended: new flags and commands
```

## Migration Strategy

No migration needed — pre-release project with no existing patches. Breaking changes are acceptable.