1use crate::backend::Backend;
8use crate::error::{WtgError, WtgResult};
9use crate::git::{CommitInfo, FileInfo, TagInfo};
10use crate::github::{ExtendedIssueInfo, PullRequestInfo};
11use crate::parse_input::Query;
12
13#[derive(Debug, Clone)]
19pub enum EntryPoint {
20 Commit(String), IssueNumber(u64), PullRequestNumber(u64), FilePath { branch: String, path: String }, Tag(String), }
26
27#[derive(Debug, Clone)]
29pub struct IssueInfo {
30 pub number: u64,
31 pub title: String,
32 pub body: Option<String>,
33 pub state: octocrab::models::IssueState,
34 pub url: String,
35 pub author: Option<String>,
36 pub author_url: Option<String>,
37}
38
39impl From<&ExtendedIssueInfo> for IssueInfo {
40 fn from(ext_info: &ExtendedIssueInfo) -> Self {
41 Self {
42 number: ext_info.number,
43 title: ext_info.title.clone(),
44 body: ext_info.body.clone(),
45 state: ext_info.state.clone(),
46 url: ext_info.url.clone(),
47 author: ext_info.author.clone(),
48 author_url: ext_info.author_url.clone(),
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct EnrichedInfo {
56 pub entry_point: EntryPoint,
57
58 pub commit: Option<CommitInfo>,
60
61 pub pr: Option<PullRequestInfo>,
63
64 pub issue: Option<IssueInfo>,
66
67 pub release: Option<TagInfo>,
69}
70
71#[derive(Debug, Clone)]
73pub struct FileResult {
74 pub file_info: FileInfo,
75 pub commit_url: Option<String>,
76 pub author_urls: Vec<Option<String>>,
77 pub release: Option<TagInfo>,
78}
79
80#[derive(Debug, Clone)]
81pub enum IdentifiedThing {
82 Enriched(Box<EnrichedInfo>),
83 File(Box<FileResult>),
84 TagOnly(Box<TagInfo>, Option<String>), }
86
87pub async fn resolve(backend: &dyn Backend, query: &Query) -> WtgResult<IdentifiedThing> {
93 match query {
94 Query::GitCommit(hash) => resolve_commit(backend, hash).await,
95 Query::Pr(number) => resolve_pr(backend, *number).await,
96 Query::Issue(number) => resolve_issue(backend, *number).await,
97 Query::IssueOrPr(number) => {
98 if let Ok(result) = resolve_pr(backend, *number).await {
100 return Ok(result);
101 }
102 if let Ok(result) = resolve_issue(backend, *number).await {
103 return Ok(result);
104 }
105 Err(WtgError::NotFound(format!("#{number}")))
106 }
107 Query::FilePath { branch, path } => {
108 resolve_file(backend, branch, &path.to_string_lossy()).await
109 }
110 Query::Tag(tag) => resolve_tag(backend, tag).await,
111 }
112}
113
114async fn resolve_commit(backend: &dyn Backend, hash: &str) -> WtgResult<IdentifiedThing> {
116 let commit = backend.find_commit(hash).await?;
117 let commit = backend.enrich_commit(commit).await;
118 let release = backend
119 .find_release_for_commit(&commit.hash, Some(commit.date))
120 .await;
121
122 Ok(IdentifiedThing::Enriched(Box::new(EnrichedInfo {
123 entry_point: EntryPoint::Commit(hash.to_string()),
124 commit: Some(commit),
125 pr: None,
126 issue: None,
127 release,
128 })))
129}
130
131async fn resolve_pr(backend: &dyn Backend, number: u64) -> WtgResult<IdentifiedThing> {
133 let pr = backend.fetch_pr(number).await?;
134
135 let commit = backend.find_commit_for_pr(&pr).await.ok();
136 let commit = match commit {
137 Some(c) => Some(backend.enrich_commit(c).await),
138 None => None,
139 };
140
141 let release = if let Some(ref c) = commit {
142 backend.find_release_for_commit(&c.hash, Some(c.date)).await
143 } else {
144 None
145 };
146
147 Ok(IdentifiedThing::Enriched(Box::new(EnrichedInfo {
148 entry_point: EntryPoint::PullRequestNumber(number),
149 commit,
150 pr: Some(pr),
151 issue: None,
152 release,
153 })))
154}
155
156async fn resolve_issue(backend: &dyn Backend, number: u64) -> WtgResult<IdentifiedThing> {
160 let ext_issue = backend.fetch_issue(number).await?;
161 let display_issue = (&ext_issue).into();
162
163 let closing_pr = ext_issue.closing_prs.into_iter().next();
165
166 let (commit, release) = if let Some(ref pr) = closing_pr {
167 if let Some(merge_sha) = &pr.merge_commit_sha {
168 let cross_backend = backend.backend_for_pr(pr).await;
170 let effective_backend: &dyn Backend =
171 cross_backend.as_ref().map_or(backend, |b| b.as_ref());
172
173 let commit = effective_backend.find_commit(merge_sha).await.ok();
174 let commit = match commit {
175 Some(c) => Some(effective_backend.enrich_commit(c).await),
176 None => None,
177 };
178
179 let release = if let Some(ref c) = commit {
180 let hash = &c.hash;
181 let date = Some(c.date);
182 if cross_backend.is_some() {
184 match backend.find_release_for_commit(hash, date).await {
185 Some(r) => Some(r),
186 None => effective_backend.find_release_for_commit(hash, date).await,
187 }
188 } else {
189 backend.find_release_for_commit(hash, date).await
190 }
191 } else {
192 None
193 };
194
195 (commit, release)
196 } else {
197 (None, None)
198 }
199 } else {
200 (None, None)
201 };
202
203 Ok(IdentifiedThing::Enriched(Box::new(EnrichedInfo {
204 entry_point: EntryPoint::IssueNumber(number),
205 commit,
206 pr: closing_pr,
207 issue: Some(display_issue),
208 release,
209 })))
210}
211
212async fn resolve_file(
214 backend: &dyn Backend,
215 branch: &str,
216 path: &str,
217) -> WtgResult<IdentifiedThing> {
218 let file_info = backend.find_file(branch, path).await?;
219 let commit_url = backend.commit_url(&file_info.last_commit.hash);
220
221 let author_urls: Vec<Option<String>> = file_info
223 .previous_authors
224 .iter()
225 .map(|(_, _, email)| backend.author_url_from_email(email))
226 .collect();
227
228 let release = backend
229 .find_release_for_commit(
230 &file_info.last_commit.hash,
231 Some(file_info.last_commit.date),
232 )
233 .await;
234
235 Ok(IdentifiedThing::File(Box::new(FileResult {
236 file_info,
237 commit_url,
238 author_urls,
239 release,
240 })))
241}
242
243async fn resolve_tag(backend: &dyn Backend, name: &str) -> WtgResult<IdentifiedThing> {
245 let tag = backend.find_tag(name).await?;
246 let url = backend.tag_url(name);
247 Ok(IdentifiedThing::TagOnly(Box::new(tag), url))
248}