use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::client::issue::IssueUser;
use crate::client::repository::RepositoriesClient;
use crate::error::ApiError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FullCommit {
pub sha: String,
pub node_id: String,
pub commit: CommitDetails,
pub author: Option<IssueUser>,
pub committer: Option<IssueUser>,
pub parents: Vec<CommitReference>,
pub url: String,
pub html_url: String,
pub comment_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitDetails {
pub author: GitSignature,
pub committer: GitSignature,
pub message: String,
pub tree: CommitReference,
pub verification: Option<Verification>,
pub comment_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitSignature {
pub name: String,
pub email: String,
pub date: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitReference {
pub sha: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Verification {
pub verified: bool,
pub reason: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comparison {
pub base_commit: FullCommit,
pub merge_base_commit: FullCommit,
pub head_commit: FullCommit,
pub status: String,
pub ahead_by: u32,
pub behind_by: u32,
pub total_commits: u32,
pub commits: Vec<FullCommit>,
pub files: Vec<FileChange>,
pub html_url: String,
pub permalink_url: String,
pub diff_url: String,
pub patch_url: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileChange {
pub filename: String,
pub status: String,
pub additions: u32,
pub deletions: u32,
pub changes: u32,
pub blob_url: String,
pub raw_url: String,
pub contents_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub patch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_filename: Option<String>,
}
impl RepositoriesClient {
pub async fn get_commit(
&self,
owner: &str,
repo: &str,
ref_name: &str,
) -> Result<FullCommit, ApiError> {
let path = format!(
"/repos/{}/{}/commits/{}",
owner,
repo,
urlencoding::encode(ref_name)
);
let response = self.client.get(&path).await?;
response.json().await.map_err(ApiError::from)
}
#[allow(clippy::too_many_arguments)]
pub async fn list_commits(
&self,
owner: &str,
repo: &str,
sha: Option<&str>,
path: Option<&str>,
author: Option<&str>,
since: Option<DateTime<Utc>>,
until: Option<DateTime<Utc>>,
per_page: Option<u32>,
page: Option<u32>,
) -> Result<Vec<FullCommit>, ApiError> {
let base = format!("/repos/{}/{}/commits", owner, repo);
let mut query_params: Vec<String> = Vec::new();
if let Some(s) = sha {
query_params.push(format!("sha={}", urlencoding::encode(s)));
}
if let Some(p) = path {
query_params.push(format!("path={}", urlencoding::encode(p)));
}
if let Some(a) = author {
query_params.push(format!("author={}", urlencoding::encode(a)));
}
if let Some(s) = since {
query_params.push(format!("since={}", urlencoding::encode(&s.to_rfc3339())));
}
if let Some(u) = until {
query_params.push(format!("until={}", urlencoding::encode(&u.to_rfc3339())));
}
if let Some(pp) = per_page {
let clamped = pp.clamp(1, 100);
query_params.push(format!("per_page={}", clamped));
}
if let Some(pg) = page {
query_params.push(format!("page={}", pg));
}
let request_path = if query_params.is_empty() {
base
} else {
format!("{}?{}", base, query_params.join("&"))
};
let response = self.client.get(&request_path).await?;
response.json().await.map_err(ApiError::from)
}
pub async fn compare(
&self,
owner: &str,
repo: &str,
base: &str,
head: &str,
) -> Result<Comparison, ApiError> {
let path = format!(
"/repos/{}/{}/compare/{}...{}",
owner,
repo,
urlencoding::encode(base),
urlencoding::encode(head)
);
let response = self.client.get(&path).await?;
response.json().await.map_err(ApiError::from)
}
}
#[cfg(test)]
#[path = "commit_tests.rs"]
mod tests;