specs/010-email-patch-import/spec.md
Ref: Size: 9.5 KiB
# Feature Specification: Email/Format-Patch Import **Feature Branch**: `010-email-patch-import` **Created**: 2026-03-21 **Status**: Draft **Input**: User description: "Support email format-patch based contributions for mailing list style workflow" ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Import a .patch file and create patch DAG entry (Priority: P1) A contributor without push access generates a `.patch` file using `git format-patch` and sends it to a maintainer (via email, file sharing, etc.). The maintainer runs `git collab patch import <file.patch>` which: 1. Parses and validates the patch file. 2. Applies the patch to a temporary branch derived from the current HEAD (or a specified base). 3. Records the resulting commit OID. 4. Creates a patch DAG entry via the existing `patch::create()` infrastructure so that the imported patch appears in `git collab patch list` and is reviewable. **Why this priority**: This is the core feature. Without import, nothing else works. It delivers the fundamental value proposition: enabling contributions without push access. **Independent Test**: Can be fully tested by generating a `.patch` file with `git format-patch`, running `git collab patch import <file>`, and verifying the patch appears in `git collab patch list` with correct title, author metadata, and head commit OID. **Acceptance Scenarios**: 1. **Given** a valid `.patch` file generated by `git format-patch`, **When** the maintainer runs `git collab patch import my.patch`, **Then** the patch is applied to a temp branch `collab/imported/<short-oid>`, a patch DAG entry is created with the commit message as the title, and the patch ID is printed to stdout. 2. **Given** a valid `.patch` file with a commit message containing a subject and body, **When** imported, **Then** the subject becomes the patch title and the body becomes the patch description. 3. **Given** a `.patch` file that does not apply cleanly to the base branch, **When** the maintainer runs `git collab patch import my.patch`, **Then** the command exits with a clear error message indicating the conflict and no DAG entry is created. 4. **Given** a malformed file that is not a valid `git format-patch` output, **When** the maintainer runs `git collab patch import bad.txt`, **Then** the command exits with error "not a valid patch file" and no side effects occur. 5. **Given** the `--base` flag is provided, **When** the maintainer runs `git collab patch import --base develop my.patch`, **Then** the patch is applied against the `develop` branch instead of the default `main`. --- ### User Story 2 - Review imported patches using existing review flow (Priority: P2) Once a patch has been imported, a maintainer or reviewer uses the existing `git collab patch review`, `git collab patch comment`, `git collab patch diff`, and `git collab patch merge` commands exactly as they would for any locally-created patch. The imported patch is indistinguishable from a push-access patch in the review flow. **Why this priority**: Leverages existing infrastructure. Validates that the import creates a fully compatible DAG entry. No new code needed if P1 is done correctly, but must be explicitly verified. **Independent Test**: Import a patch, then run `git collab patch show <id>`, `git collab patch diff <id>`, `git collab patch review <id>`, and `git collab patch merge <id>` to verify each works. **Acceptance Scenarios**: 1. **Given** an imported patch, **When** the reviewer runs `git collab patch show <id>`, **Then** the patch details are displayed including title, author, base, head commit, and body. 2. **Given** an imported patch, **When** the reviewer runs `git collab patch diff <id>`, **Then** the diff between base and the imported commit is shown. 3. **Given** an imported patch that has been approved, **When** the maintainer runs `git collab patch merge <id>`, **Then** the imported commit is merged into the base branch as normal. 4. **Given** an imported patch, **When** the reviewer runs `git collab patch comment <id> --body "looks good"`, **Then** the comment is appended to the patch DAG. --- ### User Story 3 - Multi-patch series import (Priority: P3) A contributor generates a multi-commit patch series with `git format-patch -n`. The maintainer imports the entire series at once with `git collab patch import *.patch` or `git collab patch import --series 0001.patch 0002.patch 0003.patch`. The patches are applied sequentially, and a single patch DAG entry is created whose head commit points to the final commit in the series. The title is derived from the cover letter (if present) or the first patch subject. **Why this priority**: Multi-patch series are common in mailing-list workflows but are an enhancement over the single-patch MVP. Can be deferred without blocking basic usage. **Independent Test**: Generate a 3-commit series with `git format-patch -3`, import all three, and verify a single patch DAG entry is created with the correct head commit pointing to the last applied commit. **Acceptance Scenarios**: 1. **Given** multiple `.patch` files from a series, **When** the maintainer runs `git collab patch import --series 0001.patch 0002.patch 0003.patch`, **Then** all patches are applied sequentially and a single patch DAG entry is created with the head commit being the final applied commit. 2. **Given** a series where the second patch fails to apply, **When** the maintainer runs the import, **Then** the command rolls back all applied patches, prints an error identifying which patch failed, and no DAG entry is created. 3. **Given** a series with a cover letter (`0000-cover-letter.patch`), **When** imported, **Then** the cover letter subject is used as the patch title and its body as the patch description. --- ### Edge Cases - What happens when the patch file path does not exist? The command exits with error "file not found: <path>". - What happens when the patch file is empty (0 bytes)? The command exits with error "not a valid patch file". - What happens when the patch was generated against a very different tree? The apply fails with a conflict error, no DAG entry is created. - What happens when the user imports the same patch twice? Two separate DAG entries are created (idempotency is not enforced; each import is a distinct submission). - What happens when the patch file contains binary diffs? The import handles binary diffs if git2 supports them; otherwise a clear error is returned. - What happens when the base branch does not exist? The command exits with error "base branch '<name>' not found". - What happens when the repository has uncommitted changes? The import operates on a detached temp branch and does not affect the working tree; uncommitted changes are irrelevant. ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: System MUST accept a file path argument pointing to a `.patch` file generated by `git format-patch`. - **FR-002**: System MUST parse the patch file to extract the commit message subject (title), body (description), and diff content. - **FR-003**: System MUST apply the patch to create a real git commit on a temporary branch (`collab/imported/<short-oid>`). - **FR-004**: System MUST create a patch DAG entry using the existing `patch::create()` function with the resulting commit OID. - **FR-005**: System MUST default `--base` to `main` if not specified. - **FR-006**: System MUST validate that the patch file is a valid `git format-patch` output before attempting to apply. - **FR-007**: System MUST NOT modify the working tree or current branch when importing a patch. - **FR-008**: System MUST report the created patch ID to stdout on success. - **FR-009**: System MUST exit with a non-zero status and descriptive error for malformed patches, missing files, apply conflicts, and missing base branches. - **FR-010**: System MUST support the `--series` flag for importing multiple patch files as a single patch DAG entry (P3). - **FR-011**: System MUST roll back any partially-applied commits if a series import fails partway through (P3). ### Key Entities - **PatchFile**: A file on disk containing `git format-patch` output. Key attributes: path, parsed subject, parsed body, raw diff content. - **ImportedPatch**: The result of applying a patch file. Key attributes: commit OID, temp branch name, base ref, title, body. - **PatchDAGEntry**: An existing entity (via `patch::create()`). Links the imported commit to the collab review system under `refs/collab/patches/<id>`. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: A single `.patch` file generated by `git format-patch` can be imported and reviewed end-to-end (import, show, diff, review, merge) without errors. - **SC-002**: Malformed or conflicting patch files produce clear, actionable error messages and no side effects (no orphan branches, no partial DAG entries). - **SC-003**: The imported patch is indistinguishable from a locally-created patch when viewed via `git collab patch list` and `git collab patch show`. - **SC-004**: A multi-patch series (3+ patches) can be imported as a single reviewable unit (P3). - **SC-005**: All new code passes `cargo test` and `cargo clippy` with no warnings. ## Assumptions - Contributors have access to `git format-patch` (standard git tooling). - The maintainer has push access and a local clone of the repository. - Patch files follow the standard `git format-patch` mbox format. - The `git2` crate's `Diff::from_buffer()` and `apply()` APIs can parse and apply format-patch output. - The existing `patch::create()` and DAG infrastructure does not need modification to support externally-created commits.