diff --git a/assets/icons/github/check-failed.svg b/assets/icons/github/check-failed.svg
new file mode 100644
index 00000000..7cbf8d98
--- /dev/null
+++ b/assets/icons/github/check-failed.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/github/check-passed.svg b/assets/icons/github/check-passed.svg
new file mode 100644
index 00000000..1ef4c564
--- /dev/null
+++ b/assets/icons/github/check-passed.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/github/check-pending.svg b/assets/icons/github/check-pending.svg
new file mode 100644
index 00000000..af65af00
--- /dev/null
+++ b/assets/icons/github/check-pending.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/github/check-skipped.svg b/assets/icons/github/check-skipped.svg
new file mode 100644
index 00000000..90fc554b
--- /dev/null
+++ b/assets/icons/github/check-skipped.svg
@@ -0,0 +1 @@
+
diff --git a/crates/tty7-core/src/core/github/api.rs b/crates/tty7-core/src/core/github/api.rs
index 66e4f67d..247c43cc 100644
--- a/crates/tty7-core/src/core/github/api.rs
+++ b/crates/tty7-core/src/core/github/api.rs
@@ -8,7 +8,8 @@
use serde::de::DeserializeOwned;
use super::model::{
- Comment, Detail, Item, Kind, PrFile, RawComment, RawFile, RawIssue, RawPull, StateFilter,
+ Checks, Comment, Detail, Item, ItemState, Kind, PrFile, RawCheckRuns, RawCombinedStatus,
+ RawComment, RawFile, RawIssue, RawPull, RawReview, StateFilter,
};
use super::remote::RepoSlug;
@@ -267,12 +268,32 @@ pub fn detail(t: &dyn Transport, slug: &RepoSlug, number: u64) -> Result(&t.get(&format!("{base}/pulls/{number}"))?)?.into_item();
+ let mut raw: RawPull = decode(&t.get(&format!("{base}/pulls/{number}"))?)?;
+ let teams = raw.requested_team_names(&slug.owner);
+ let requested = std::mem::take(&mut raw.requested_reviewers);
+ let (pr_item, info) = raw.into_item();
// `/pulls/{n}` is the one that knows about drafts; the issue's
// labels and comment count are kept, which `/pulls` omits.
item.state = pr_item.state;
+ // Checks and reviews are the panel's summary, not the pull request
+ // itself: one that cannot be read is left out rather than failing
+ // the whole view (an old commit's checks can be gone, a token can be
+ // scoped away from them).
+ if !info.head_sha.is_empty() {
+ checks_out = checks(t, slug, &info.head_sha)
+ .inspect_err(|e| log::warn!("github: checks of {}#{number}: {e}", slug.full()))
+ .ok();
+ }
+ reviewers_out = t
+ .get(&format!(
+ "{base}/pulls/{number}/reviews?per_page={DETAIL_PAGE}"
+ ))
+ .and_then(|r| decode::>(&r))
+ .inspect_err(|e| log::warn!("github: reviews of {}#{number}: {e}", slug.full()))
+ .ok()
+ .map(|reviews| super::model::reviewers(&item.author, reviews, requested, teams));
pull = Some(info);
let reply = t.get(&format!(
"{base}/pulls/{number}/files?per_page={DETAIL_PAGE}"
@@ -294,6 +315,50 @@ pub fn detail(t: &dyn Transport, slug: &RepoSlug, number: u64) -> Result Result {
+ let base = repo_path(slug);
+ let sha = super::remote::escape_path(sha);
+ let runs: RawCheckRuns = decode(&t.get(&format!(
+ "{base}/commits/{sha}/check-runs?per_page={DETAIL_PAGE}"
+ ))?)?;
+ let statuses: RawCombinedStatus = decode(&t.get(&format!(
+ "{base}/commits/{sha}/status?per_page={DETAIL_PAGE}"
+ ))?)?;
+ Ok(super::model::checks(runs, statuses))
+}
+
+/// The pull request branch `branch` of `head_owner`'s fork (or of the
+/// repository itself) opened against `slug`: the open one if there is one,
+/// else the most recently updated. `None` when the branch has none.
+pub fn pull_for_branch(
+ t: &dyn Transport,
+ slug: &RepoSlug,
+ head_owner: &str,
+ branch: &str,
+) -> Result