Skip to content

feat(gmail): add gmail extract-attachments command - #1512

Merged
newhoggy merged 2 commits into
mainfrom
issue-1510-gmail-extract-attachments
Aug 6, 2026
Merged

feat(gmail): add gmail extract-attachments command#1512
newhoggy merged 2 commits into
mainfrom
issue-1510-gmail-extract-attachments

Conversation

@newhoggy

@newhoggy newhoggy commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1510.

  • Adds omni-dev gmail extract-attachments --output-dir PATH [--dry-run] [--quiet] [-o FORMAT], retroactively extracting attachments for messages gmail sync/sync-all already archived, without re-fetching from Gmail.
  • gmail sync --extract-attachments only ever extracted attachments for messages fetched during that run — presence-on-disk is the archive's idempotence mechanism (ADR-0064), so turning the flag on never backfilled already-archived mail, even under --full. ADR-0065 named this gap explicitly; until now the only fix was deleting the affected .eml files and re-running --full --extract-attachments.
  • Purely local and offline: reads the manifest and .eml files already under --output-dir, never resolves a GmailClient — dispatched alongside sync-all's no-client special case in GmailCommand::execute, before credential resolution. --account is rejected the same way sync-all rejects it.
  • Reuses gmail sync --extract-attachments's extraction path verbatim against on-disk bytes: trusts attachment_count > 0 as a fast-path filter (the same cheap heuristic scan sync always runs), skips any record whose attachments/ directory already exists (idempotent, safe to re-run), and calls the exact same MIME-parse-and-write helpers fetch_and_write_one uses (manifest_path/write_atomic promoted to pub(crate) — no new writing logic needed).
  • Per-message failures accumulate into a report and never abort the batch (ExtractAttachmentsReport, mirroring SyncReport's compute-render-decide shape from ADR-0064 Decision 4); a real-parser/heuristic disagreement is a silent no-op, not an error.
  • --dry-run parses every candidate for an accurate count rather than echoing the heuristic, and writes nothing.
  • See ADR-0065's amended Consequences section and docs/gmail.md#extract-attachments for the operator-facing docs.

Commit-by-commit:

  1. feat(gmail): add gmail extract-attachments command — the engine, report types, CLI wiring, and reused sync/manifest helpers.
  2. docs(docs,release): document gmail extract-attachments, amend ADR-0065 — ADR amendment, docs, changelog.

Test plan

  • cargo build
  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test (full suite) — all pass except the two pre-existing, unrelated daemon_test.rs lifecycle-flake tests (zero files touched by this branch overlap with daemon)
  • cargo insta test --test integration_testhelp_all_output snapshot reviewed and accepted for the new subcommand
  • New unit/CLI tests covering candidate filtering, idempotence, --dry-run accuracy, per-message error accumulation, heuristic/parser disagreement, and CLI dispatch routing (no client resolved, --account rejected)

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Coverage

Total: 97.31% 🔴 0 pp vs main

Comparing 4a657b9..3a67d71 (merge-base → PR head)

File Before After Δ
src/cli/gmail/extract_attachments.rs 95.02% 🆕 new
src/cli/gmail/extract_attachments/engine.rs 98.01% 🆕 new
src/cli/gmail/extract_attachments/report.rs 92.31% 🆕 new
src/cli/gmail.rs 98.33% 98.23% 🔴 -0.1 pp
src/cli/gmail/sync/engine.rs 97.58% 97.65% 🟢 0.07 pp
src/cli/gmail/sync/manifest.rs 97.96% 98.24% 🟢 0.28 pp

Patch coverage

Patch: 96.9% (562/580 new lines covered)

File Patch Uncovered new lines
src/cli/gmail.rs 97.67% (42/43) 151
src/cli/gmail/extract_attachments.rs 95.02% (191/201) 111-113, 129-132, 134-135, 138
src/cli/gmail/extract_attachments/engine.rs 98.01% (197/201) 92-95
src/cli/gmail/extract_attachments/report.rs 92.31% (36/39) 28-30
src/cli/gmail/sync/engine.rs 100% (65/65)
src/cli/gmail/sync/manifest.rs 100% (31/31)
Uncovered new lines (18)
  • src/cli/gmail.rs:151
  • src/cli/gmail/extract_attachments.rs:111
  • src/cli/gmail/extract_attachments.rs:112
  • src/cli/gmail/extract_attachments.rs:113
  • src/cli/gmail/extract_attachments.rs:129
  • src/cli/gmail/extract_attachments.rs:130
  • src/cli/gmail/extract_attachments.rs:131
  • src/cli/gmail/extract_attachments.rs:132
  • src/cli/gmail/extract_attachments.rs:134
  • src/cli/gmail/extract_attachments.rs:135
  • src/cli/gmail/extract_attachments.rs:138
  • src/cli/gmail/extract_attachments/engine.rs:92
  • src/cli/gmail/extract_attachments/engine.rs:93
  • src/cli/gmail/extract_attachments/engine.rs:94
  • src/cli/gmail/extract_attachments/engine.rs:95
  • src/cli/gmail/extract_attachments/report.rs:28
  • src/cli/gmail/extract_attachments/report.rs:29
  • src/cli/gmail/extract_attachments/report.rs:30

📦 Full per-file coverage summary · run summary

@newhoggy
newhoggy force-pushed the issue-1510-gmail-extract-attachments branch 2 times, most recently from 212368d to 3f7cb8a Compare August 6, 2026 12:28
Retroactively extracts attachments for messages `gmail sync`/`sync-all`
already archived, without re-fetching from Gmail. Presence-on-disk is
the archive's idempotence mechanism (ADR-0064), so turning on
`--extract-attachments` never backfilled already-archived mail even
under `--full` — a gap ADR-0065 documented and, until now, the only
fix was deleting the affected `.eml` files and re-running
`--full --extract-attachments`.

- New `src/cli/gmail/extract_attachments/` module (`engine.rs` +
  `report.rs`), mirroring `sync.rs`'s own file+dir split and
  ADR-0064's compute-render-decide report pattern. For each
  non-deleted manifest record it trusts `attachment_count > 0` as a
  fast-path filter, skips a record whose `attachments/` directory
  already exists (idempotent, safe to re-run), and otherwise re-runs
  the exact same MIME-parse-and-write path `gmail sync
  --extract-attachments` uses against the on-disk `.eml` bytes. A
  missing/unreadable `.eml` accumulates a per-message error without
  aborting the batch; a real-parser/heuristic disagreement (finds
  nothing) is a silent no-op.
- `sync/engine.rs`'s `manifest_path`/`write_atomic` helpers promoted
  to `pub(crate)` so no new writing logic was needed, just a new
  caller.
- Two small additive `Manifest` API changes:
  `ManifestRecord::internal_date_utc()` (mirrors
  `Message::internal_date_utc`) and `Manifest::records_not_deleted()`
  (the full-record counterpart of the existing id-only
  `ids_not_deleted`).
- Dispatched in `GmailCommand::execute`/`GmailSubcommands`
  (`src/cli/gmail.rs`) alongside `sync-all`'s no-client special case,
  before credential resolution — purely local and offline, so
  `--account` is rejected the same way `sync-all` rejects it.
- `--dry-run` parses every candidate for an accurate count rather than
  echoing the heuristic, and writes nothing; `-o
  table|json|yaml|yamls|jsonl` output mirrors `sync`'s own report
  formats.
- ADR-0065's "No retroactive backfill, for free" consequence gains a
  backward-reference noting the gap is now closed by
  `gmail extract-attachments` (#1510), mirroring how ADR-0064 already
  backward-references ADR-0065 for the same kind of follow-up.
- docs/gmail.md gains a new "Extract attachments" section (Table of
  Contents included) following the Sync/Sync all accounts style; the
  `--extract-attachments` paragraph's old delete-and-refetch advice
  now points at the new command instead.
- CHANGELOG.md's Unreleased/Added section gains an entry for #1510,
  matching the existing Gmail feature entries.
@newhoggy
newhoggy force-pushed the issue-1510-gmail-extract-attachments branch from 3f7cb8a to 3a67d71 Compare August 6, 2026 13:02
@newhoggy
newhoggy merged commit 405da8d into main Aug 6, 2026
18 checks passed
@newhoggy
newhoggy deleted the issue-1510-gmail-extract-attachments branch August 6, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(gmail): retroactively extract attachments for already-archived messages, without re-fetching

1 participant