Show commit details above changed files
This commit is contained in:
@@ -316,14 +316,14 @@ pub async fn load_pull(
|
||||
.map_err(message)
|
||||
}
|
||||
|
||||
pub async fn load_commit_files(
|
||||
pub async fn load_commit(
|
||||
server: &Server,
|
||||
owner: &str,
|
||||
repository: &str,
|
||||
sha: &str,
|
||||
) -> Result<Vec<models::CommitAffectedFiles>, String> {
|
||||
) -> Result<models::Commit, String> {
|
||||
client(server)?
|
||||
.commit_files(&scope(owner, repository)?, sha)
|
||||
.commit(&scope(owner, repository)?, sha)
|
||||
.await
|
||||
.map_err(message)
|
||||
}
|
||||
|
||||
@@ -726,14 +726,16 @@ impl GotchaCore {
|
||||
Ok(repository_file_page(&path, data))
|
||||
}
|
||||
|
||||
pub async fn commit_files(
|
||||
pub async fn commit_details(
|
||||
&self,
|
||||
owner: String,
|
||||
repository: String,
|
||||
sha: String,
|
||||
) -> Result<Vec<FileRow>, GotchaError> {
|
||||
Ok(commit_file_rows(
|
||||
load_commit_files(&self.server()?, &owner, &repository, &sha).await?,
|
||||
branch: Option<String>,
|
||||
) -> Result<CommitDetailsPage, GotchaError> {
|
||||
Ok(commit_details_page(
|
||||
load_commit(&self.server()?, &owner, &repository, &sha).await?,
|
||||
branch,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,20 @@ pub struct CommitPage {
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct CommitMetadataRow {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct CommitDetailsPage {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub metadata: Vec<CommitMetadataRow>,
|
||||
pub files: Vec<FileRow>,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct FileRow {
|
||||
pub path: String,
|
||||
@@ -716,6 +730,97 @@ pub fn commit_file_rows(files: Vec<models::CommitAffectedFiles>) -> Vec<FileRow>
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn commit_details_page(commit: models::Commit, branch: Option<String>) -> CommitDetailsPage {
|
||||
let details = commit.commit.as_deref();
|
||||
let message = details
|
||||
.and_then(|details| details.message.as_deref())
|
||||
.unwrap_or("Commit");
|
||||
let mut lines = message.lines();
|
||||
let title = lines.next().unwrap_or("Commit").to_string();
|
||||
let description = lines.collect::<Vec<_>>().join("\n").trim().to_string();
|
||||
let author = details
|
||||
.and_then(|details| details.author.as_deref())
|
||||
.and_then(commit_user)
|
||||
.or_else(|| {
|
||||
commit
|
||||
.author
|
||||
.as_deref()
|
||||
.and_then(|author| author.login.clone())
|
||||
});
|
||||
let committer = details
|
||||
.and_then(|details| details.committer.as_deref())
|
||||
.and_then(commit_user)
|
||||
.or_else(|| {
|
||||
commit
|
||||
.committer
|
||||
.as_deref()
|
||||
.and_then(|committer| committer.login.clone())
|
||||
});
|
||||
let date = details
|
||||
.and_then(|details| details.committer.as_deref())
|
||||
.and_then(|committer| committer.date.as_deref())
|
||||
.or_else(|| {
|
||||
details
|
||||
.and_then(|details| details.author.as_deref())
|
||||
.and_then(|author| author.date.as_deref())
|
||||
})
|
||||
.or(commit.created.as_deref());
|
||||
let files = commit.files.unwrap_or_default();
|
||||
let mut metadata = Vec::new();
|
||||
if let Some(author) = author {
|
||||
metadata.push(metadata_row("Author", author));
|
||||
}
|
||||
if let Some(committer) = committer {
|
||||
metadata.push(metadata_row("Committer", committer));
|
||||
}
|
||||
if date.is_some() {
|
||||
metadata.push(metadata_row("Committed", compact_date(date)));
|
||||
}
|
||||
if let Some(branch) = branch.filter(|branch| !branch.is_empty()) {
|
||||
metadata.push(metadata_row("Branch", branch));
|
||||
}
|
||||
metadata.push(metadata_row(
|
||||
"Commit",
|
||||
commit.sha.clone().unwrap_or_else(|| "unknown".into()),
|
||||
));
|
||||
let verification = details.and_then(|details| details.verification.as_deref());
|
||||
metadata.push(metadata_row(
|
||||
"Signature",
|
||||
match verification.filter(|verification| {
|
||||
verification
|
||||
.signature
|
||||
.as_deref()
|
||||
.is_some_and(|signature| !signature.is_empty())
|
||||
}) {
|
||||
Some(verification) if verification.verified.unwrap_or(false) => "Verified".into(),
|
||||
Some(_) => "Unverified".into(),
|
||||
None => "Unsigned".into(),
|
||||
},
|
||||
));
|
||||
CommitDetailsPage {
|
||||
title,
|
||||
description,
|
||||
metadata,
|
||||
files: commit_file_rows(files),
|
||||
}
|
||||
}
|
||||
|
||||
fn commit_user(user: &models::CommitUser) -> Option<String> {
|
||||
match (user.name.as_deref(), user.email.as_deref()) {
|
||||
(Some(name), Some(email)) => Some(format!("{name} <{email}>")),
|
||||
(Some(name), None) => Some(name.into()),
|
||||
(None, Some(email)) => Some(email.into()),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata_row(label: &str, value: String) -> CommitMetadataRow {
|
||||
CommitMetadataRow {
|
||||
label: label.into(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repository_content_rows(
|
||||
contents: Vec<models::ContentsResponse>,
|
||||
) -> Vec<RepositoryContentRow> {
|
||||
@@ -1215,6 +1320,66 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presents_complete_commit_details() {
|
||||
let page = commit_details_page(
|
||||
models::Commit {
|
||||
sha: Some("0123456789abcdef".into()),
|
||||
commit: Some(Box::new(models::RepoCommit {
|
||||
message: Some("Add details\n\nExplain the change.".into()),
|
||||
author: Some(Box::new(models::CommitUser {
|
||||
name: Some("Ada".into()),
|
||||
email: Some("ada@example.com".into()),
|
||||
date: Some("2026-08-01T10:00:00Z".into()),
|
||||
})),
|
||||
committer: Some(Box::new(models::CommitUser {
|
||||
name: Some("Grace".into()),
|
||||
date: Some("2026-08-02T11:00:00Z".into()),
|
||||
..Default::default()
|
||||
})),
|
||||
verification: Some(Box::new(models::PayloadCommitVerification {
|
||||
verified: Some(true),
|
||||
signature: Some("signed".into()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})),
|
||||
files: Some(vec![models::CommitAffectedFiles {
|
||||
filename: Some("README.md".into()),
|
||||
status: Some("modified".into()),
|
||||
}]),
|
||||
..Default::default()
|
||||
},
|
||||
Some("main".into()),
|
||||
);
|
||||
|
||||
assert_eq!(page.title, "Add details");
|
||||
assert_eq!(page.description, "Explain the change.");
|
||||
assert_eq!(page.files[0].path, "README.md");
|
||||
assert_eq!(
|
||||
page.metadata
|
||||
.iter()
|
||||
.map(|row| (row.label.as_str(), row.value.as_str()))
|
||||
.collect::<Vec<_>>(),
|
||||
[
|
||||
("Author", "Ada <ada@example.com>"),
|
||||
("Committer", "Grace"),
|
||||
("Committed", "2026-08-02"),
|
||||
("Branch", "main"),
|
||||
("Commit", "0123456789abcdef"),
|
||||
("Signature", "Verified"),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
commit_details_page(models::Commit::default(), None)
|
||||
.metadata
|
||||
.last()
|
||||
.unwrap()
|
||||
.value,
|
||||
"Unsigned"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maps_heat_levels_and_labels() {
|
||||
let heatmap = vec![
|
||||
|
||||
@@ -189,22 +189,17 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn commit_files(
|
||||
&self,
|
||||
repository: &RepositoryId,
|
||||
sha: &str,
|
||||
) -> Result<Vec<models::CommitAffectedFiles>> {
|
||||
pub async fn commit(&self, repository: &RepositoryId, sha: &str) -> Result<models::Commit> {
|
||||
apis::repository_api::repo_get_single_commit(
|
||||
&self.configuration(),
|
||||
&repository.owner,
|
||||
&repository.repository,
|
||||
sha,
|
||||
Some(false),
|
||||
Some(true),
|
||||
None,
|
||||
Some(true),
|
||||
)
|
||||
.await
|
||||
.map(|commit| commit.files.unwrap_or_default())
|
||||
.map_err(Error::generated)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user