diff --git a/README.en.md b/README.en.md index 3b61827..3cfb668 100644 --- a/README.en.md +++ b/README.en.md @@ -60,6 +60,7 @@ gitea-pr-review render-md --in pr.json --out pr.md # `#` > Numbering: Review `.`; Comment `..`; Reply `...` +> fetched at: diff --git a/README.md b/README.md index defce9a..a9429b9 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ gitea-pr-review render-md --in pr.json --out pr.md # `#` > 编号规则:Review `.`;Comment `..`;Reply `...` +> fetched at: diff --git a/src/lib.rs b/src/lib.rs index efc50f7..58eb191 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use std::env; use std::path::Path; +use chrono::Utc; use clap::Parser; pub mod cli; @@ -35,7 +36,8 @@ pub fn run() -> anyhow::Result<()> { let client = GiteaClient::new(base_url, token); let bundle = client.fetch_pr_bundle(&repo, args.pr_index)?; - let doc = normalize_bundle(&repo, bundle); + let fetched_at = Utc::now().to_rfc3339(); + let doc = normalize_bundle(&repo, fetched_at, bundle); let rendered = match args.format { OutputFormat::Markdown => render_markdown(&doc), diff --git a/src/model.rs b/src/model.rs index 5b54f33..f4318b4 100644 --- a/src/model.rs +++ b/src/model.rs @@ -15,6 +15,7 @@ pub struct PrMeta { pub pr_index: i64, pub title: String, pub description: Option, + pub fetched_at: Option, pub state: String, pub author: String, pub base_branch: String, diff --git a/src/normalize.rs b/src/normalize.rs index abd1136..0f4a269 100644 --- a/src/normalize.rs +++ b/src/normalize.rs @@ -9,7 +9,7 @@ use crate::model::{ ReviewItem, }; -pub fn normalize_bundle(repo: &str, bundle: PullBundleDto) -> PrReviewDocument { +pub fn normalize_bundle(repo: &str, fetched_at: String, bundle: PullBundleDto) -> PrReviewDocument { let PullBundleDto { pull, reviews, @@ -33,6 +33,7 @@ pub fn normalize_bundle(repo: &str, bundle: PullBundleDto) -> PrReviewDocument { pr_index: pull.number, title: pull.title, description: pull.body, + fetched_at: Some(fetched_at), state: pull.state, author: pull.user.login, base_branch: pull.base.ref_name, diff --git a/src/render/markdown.rs b/src/render/markdown.rs index 7698532..8e7c1ad 100644 --- a/src/render/markdown.rs +++ b/src/render/markdown.rs @@ -65,6 +65,8 @@ pub fn render_markdown(doc: &PrReviewDocument) -> String { out.push_str( "> 编号规则:Review `.`;Comment `..`;Reply `...`\n\n", ); + let fetched_at = doc.meta.fetched_at.as_deref().unwrap_or("unknown"); + out.push_str(&format!("> fetched at: {fetched_at}\n\n")); let pr_description = doc .meta .description diff --git a/tests/e2e_smoke_tests.rs b/tests/e2e_smoke_tests.rs index ad7f3e4..ad601b6 100644 --- a/tests/e2e_smoke_tests.rs +++ b/tests/e2e_smoke_tests.rs @@ -13,6 +13,7 @@ fn render_md_reads_json_and_outputs_markdown_to_stdout() { "pr_index": 1, "title": "t", "description": "PR body", + "fetched_at": "2026-04-08T12:34:56Z", "state": "open", "author": "a", "base_branch": "main", @@ -56,6 +57,7 @@ fn render_md_writes_to_out_file_when_requested() { "pr_index": 2, "title": "t2", "description": "PR body", + "fetched_at": "2026-04-08T12:34:56Z", "state": "open", "author": "a", "base_branch": "main", @@ -220,6 +222,7 @@ fn fetch_writes_markdown_to_stdout_by_default() { let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); assert!(stdout.contains("# org/repo `#7` Fix parser")); + assert!(stdout.contains("> fetched at: ")); assert!(stdout.contains("## Commits")); assert!(stdout.contains("## Diff Stat")); assert!(stdout.contains("## Review 7.1 (COMMENT)")); @@ -288,5 +291,6 @@ fn fetch_writes_json_to_out_file_when_requested() { let written = std::fs::read_to_string(output.path()).unwrap(); assert!(written.contains("\"repo\": \"org/repo\"")); assert!(written.contains("\"pr_index\": 8")); + assert!(written.contains("\"fetched_at\":")); assert!(written.contains("\"head_branch\": \"feature/y\"")); } diff --git a/tests/normalize_tests.rs b/tests/normalize_tests.rs index 0808f12..021441b 100644 --- a/tests/normalize_tests.rs +++ b/tests/normalize_tests.rs @@ -141,10 +141,11 @@ fn normalize_groups_replies_and_sorts_threads_by_time() { ], }; - let doc = normalize_bundle("org/repo", bundle); + let doc = normalize_bundle("org/repo", "2026-04-08T12:34:56Z".into(), bundle); assert_eq!(doc.meta.repo, "org/repo"); assert_eq!(doc.meta.pr_index, 42); + assert_eq!(doc.meta.fetched_at.as_deref(), Some("2026-04-08T12:34:56Z")); assert_eq!(doc.meta.base_branch, "main"); assert_eq!(doc.meta.head_branch, "feature/x"); @@ -241,7 +242,7 @@ fn normalize_does_not_merge_unrelated_comments_on_same_file() { files: vec![], }; - let doc = normalize_bundle("org/repo", bundle); + let doc = normalize_bundle("org/repo", "2026-04-08T12:34:56Z".into(), bundle); assert_eq!(doc.threads.len(), 2); assert_eq!(doc.threads[0].root_comment.id, 10); @@ -297,7 +298,7 @@ fn normalize_uses_non_zero_position_when_line_is_missing() { files: vec![], }; - let doc = normalize_bundle("org/repo", bundle); + let doc = normalize_bundle("org/repo", "2026-04-08T12:34:56Z".into(), bundle); assert_eq!(doc.threads.len(), 1); assert_eq!(doc.threads[0].root_comment.path.as_deref(), Some("src/main.rs")); assert_eq!(doc.threads[0].root_comment.line, Some(41)); diff --git a/tests/render_json_tests.rs b/tests/render_json_tests.rs index fb9df36..a76d0de 100644 --- a/tests/render_json_tests.rs +++ b/tests/render_json_tests.rs @@ -10,6 +10,7 @@ fn model_json_roundtrip() { pr_index: 9, title: "feat: x".into(), description: Some("desc".into()), + fetched_at: Some("2026-04-08T12:34:56Z".into()), state: "open".into(), author: "alice".into(), base_branch: "main".into(), @@ -50,5 +51,6 @@ fn model_json_roundtrip() { let encoded = serde_json::to_string(&doc).unwrap(); let decoded: PrReviewDocument = serde_json::from_str(&encoded).unwrap(); assert_eq!(decoded.meta.repo, "org/repo"); + assert_eq!(decoded.meta.fetched_at.as_deref(), Some("2026-04-08T12:34:56Z")); assert_eq!(decoded.threads[0].root_comment.body, "hello"); } diff --git a/tests/render_markdown_tests.rs b/tests/render_markdown_tests.rs index 67dcb9b..cf21521 100644 --- a/tests/render_markdown_tests.rs +++ b/tests/render_markdown_tests.rs @@ -12,6 +12,7 @@ fn render_markdown_includes_expected_sections_and_preserves_comment_markdown() { pr_index: 7, title: "Fix parser".into(), description: Some("PR body".into()), + fetched_at: Some("2026-04-08T12:34:56Z".into()), state: "open".into(), author: "alice".into(), base_branch: "main".into(), @@ -72,6 +73,7 @@ fn render_markdown_includes_expected_sections_and_preserves_comment_markdown() { assert!(md.contains("# org/repo `#7` Fix parser")); assert!(md.contains("## Metadata")); + assert!(md.contains("> fetched at: 2026-04-08T12:34:56Z")); assert!(md.contains("## Commits")); assert!(md.contains("## Diff Stat")); assert!(md.contains("> 编号规则:Review `.`;Comment `..`;Reply `...`")); @@ -99,6 +101,7 @@ fn render_markdown_uses_minimal_fence_for_plain_text() { pr_index: 1, title: "T".into(), description: None, + fetched_at: None, state: "open".into(), author: "alice".into(), base_branch: "main".into(),