Files
Gotcha/crates/gitea/src/pulls.rs
2026-07-31 16:44:16 +02:00

343 lines
9.1 KiB
Rust

use std::collections::BTreeSet;
use crate::{
Client, Error, Result,
domain::{DEFAULT_PAGE_SIZE, Page, PullDetails, RepositoryId},
models,
};
use gitea_openapi::apis;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PullFileSource {
Branch(String),
Commit(String),
Request,
}
pub fn pull_state(pull: &models::PullRequest) -> &str {
if pull.merged.unwrap_or(false) {
"merged"
} else if pull.draft.unwrap_or(false) {
"draft"
} else {
pull.state.as_deref().unwrap_or("unknown")
}
}
pub fn pull_file_source(pull: &models::PullRequest) -> PullFileSource {
if pull.state.as_deref() == Some("open") {
PullFileSource::Branch(
pull.head
.as_ref()
.and_then(|head| head.r#ref.clone().or(head.label.clone()))
.unwrap_or_else(|| "head branch".into()),
)
} else if let Some(sha) = pull.merge_commit_sha.as_deref() {
PullFileSource::Commit(sha.chars().take(8).collect())
} else {
PullFileSource::Request
}
}
impl Client {
pub async fn repository_pulls(
&self,
repository: &RepositoryId,
state: &str,
page: i32,
limit: i32,
) -> Result<Page<models::PullRequest>> {
validate_page(page, limit)?;
apis::repository_api::repo_list_pull_requests(
&self.configuration(),
&repository.owner,
&repository.repository,
None,
Some(state),
None,
None,
None,
None,
Some(page),
Some(limit),
)
.await
.map(|items| Page::from_items(items, limit))
.map_err(Error::generated)
}
pub async fn search_pulls(
&self,
state: &str,
milestone: Option<&str>,
page: i32,
limit: i32,
) -> Result<Page<models::Issue>> {
validate_page(page, limit)?;
let owner = self
.current_user()
.await?
.login
.ok_or_else(|| Error::Generated("The server account has no username.".into()))?;
apis::issue_api::issue_search_issues(
&self.configuration(),
Some(state),
None,
milestone,
None,
None,
Some("pulls"),
None,
None,
None,
None,
None,
None,
None,
Some(&owner),
None,
Some(page),
Some(limit),
)
.await
.map(|items| Page::from_items(items, limit))
.map_err(Error::generated)
}
pub async fn pull_milestones(&self) -> Result<Vec<String>> {
let (open, closed) = tokio::try_join!(
self.all_searched_pulls("open"),
self.all_searched_pulls("closed"),
)?;
Ok(open
.into_iter()
.chain(closed)
.filter_map(|pull| pull.milestone?.title)
.collect::<BTreeSet<_>>()
.into_iter()
.collect())
}
async fn all_searched_pulls(&self, state: &str) -> Result<Vec<models::Issue>> {
let mut pulls = Vec::new();
for page in 1.. {
let batch = self
.search_pulls(state, None, page, DEFAULT_PAGE_SIZE)
.await?;
pulls.extend(batch.items);
if !batch.has_more {
break;
}
}
Ok(pulls)
}
pub async fn pull(
&self,
repository: &RepositoryId,
number: i64,
) -> Result<models::PullRequest> {
positive(number, "pull request number")?;
apis::repository_api::repo_get_pull_request(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
)
.await
.map_err(Error::generated)
}
pub async fn pull_details(
&self,
repository: &RepositoryId,
number: i64,
page: i32,
) -> Result<PullDetails> {
let pull = self.pull(repository, number);
let comments = async {
if page == 1 {
self.issue_comments(repository, number).await
} else {
Ok(Vec::new())
}
};
let files = self.pull_files_page(repository, number, page, DEFAULT_PAGE_SIZE);
let (pull, comments, files) = tokio::try_join!(pull, comments, files)?;
Ok(PullDetails {
pull,
comments,
has_more: files.has_more,
files: files.items,
})
}
pub async fn create_pull(
&self,
repository: &RepositoryId,
option: models::CreatePullRequestOption,
) -> Result<models::PullRequest> {
if [
option.title.as_deref(),
option.head.as_deref(),
option.base.as_deref(),
]
.into_iter()
.any(|value| value.unwrap_or_default().trim().is_empty())
{
return Err(Error::InvalidInput(
"pull request title, head, and base must not be empty".into(),
));
}
apis::repository_api::repo_create_pull_request(
&self.configuration(),
&repository.owner,
&repository.repository,
Some(option),
)
.await
.map_err(Error::generated)
}
pub async fn edit_pull(
&self,
repository: &RepositoryId,
number: i64,
option: models::EditPullRequestOption,
) -> Result<models::PullRequest> {
positive(number, "pull request number")?;
apis::repository_api::repo_edit_pull_request(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
Some(option),
)
.await
.map_err(Error::generated)
}
pub async fn merge_pull(
&self,
repository: &RepositoryId,
number: i64,
option: models::MergePullRequestOption,
) -> Result<()> {
positive(number, "pull request number")?;
apis::repository_api::repo_merge_pull_request(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
Some(option),
)
.await
.map(|_| ())
.map_err(Error::generated)
}
pub async fn pull_commits(
&self,
repository: &RepositoryId,
number: i64,
) -> Result<Vec<models::Commit>> {
positive(number, "pull request number")?;
apis::repository_api::repo_get_pull_request_commits(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
None,
None,
None,
None,
)
.await
.map_err(Error::generated)
}
pub async fn pull_files_page(
&self,
repository: &RepositoryId,
number: i64,
page: i32,
limit: i32,
) -> Result<Page<models::ChangedFile>> {
positive(number, "pull request number")?;
validate_page(page, limit)?;
apis::repository_api::repo_get_pull_request_files(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
None,
None,
Some(page),
Some(limit),
)
.await
.map(|items| Page::from_items(items, limit))
.map_err(Error::generated)
}
pub async fn pull_files(
&self,
repository: &RepositoryId,
number: i64,
) -> Result<Vec<models::ChangedFile>> {
self.pull_files_page(repository, number, 1, DEFAULT_PAGE_SIZE)
.await
.map(|page| page.items)
}
pub async fn pull_reviews(
&self,
repository: &RepositoryId,
number: i64,
) -> Result<Vec<models::PullReview>> {
positive(number, "pull request number")?;
apis::repository_api::repo_list_pull_reviews(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
None,
None,
)
.await
.map_err(Error::generated)
}
pub async fn pull_diff(&self, repository: &RepositoryId, number: i64) -> Result<String> {
positive(number, "pull request number")?;
apis::repository_api::repo_download_pull_diff_or_patch(
&self.configuration(),
&repository.owner,
&repository.repository,
number,
"diff",
Some(false),
)
.await
.map_err(Error::generated)
}
}
fn validate_page(page: i32, limit: i32) -> Result<()> {
if page < 1 || limit < 1 {
return Err(Error::InvalidInput(
"page and limit must be positive".into(),
));
}
Ok(())
}
fn positive(value: i64, name: &str) -> Result<()> {
if value < 1 {
return Err(Error::InvalidInput(format!(
"{name} must be a positive integer"
)));
}
Ok(())
}