use std::collections::HashSet;
use sqlx::SqlitePool;
use crate::error::CoreError;
use crate::ingest::{ImportProgress, ProgressCallback};
pub mod auth;
mod client;
mod parse;
mod schema;
use client::GitlabClient;
use schema::{Discussion, MergeRequest};
pub struct ImportOptions {
pub host: String,
pub project_path: String,
pub project_id: String,
pub token: String,
pub max_mrs: usize,
pub mr_iids: Vec<i32>,
pub exclude_mrs: HashSet<i32>,
pub since: Option<String>,
}
pub async fn import_mr_reviews(
db: &SqlitePool,
opts: ImportOptions,
on_progress: Option<ProgressCallback>,
) -> Result<ImportProgress, CoreError> {
crate::ingest::provider::validate_gitlab_project_path(&opts.project_path)?;
if let Some(since) = opts.since.as_deref() {
crate::ingest::validate_since_date(since)?;
}
let client = GitlabClient::new(&opts.host, &opts.token)?;
let mut progress = ImportProgress {
prs_fetched: 0,
prs_total: 0,
comments_imported: 0,
comments_skipped: 0,
prs_missing: 0,
missing_pr_numbers: Vec::new(),
};
let merge_requests = if opts.mr_iids.is_empty() {
let updated_after = opts.since.as_deref().map(parse::updated_after_param);
let listed = client
.list_merged_merge_requests(&opts.project_path, updated_after.as_deref(), opts.max_mrs)
.await?;
listed
.into_iter()
.filter(|mr| !iid_excluded(mr.iid, &opts.exclude_mrs))
.collect()
} else {
let mut collected: Vec<MergeRequest> = Vec::new();
let mut seen = HashSet::new();
for iid in &opts.mr_iids {
if !seen.insert(*iid) {
continue;
}
if opts.exclude_mrs.contains(iid) {
continue;
}
if let Some(mr) = client.get_merge_request(&opts.project_path, *iid).await? {
collected.push(mr);
} else {
progress.prs_missing += 1;
progress.missing_pr_numbers.push(*iid);
}
}
collected
};
let mut with_discussions: Vec<(MergeRequest, Vec<Discussion>)> = Vec::new();
for mr in merge_requests {
let discussions = client.list_discussions(&opts.project_path, mr.iid).await?;
if parse::has_importable_notes(&discussions) {
with_discussions.push((mr, discussions));
}
}
progress.prs_total = with_discussions.len();
if let Some(ref cb) = on_progress {
cb(&progress);
}
for (mr, discussions) in &with_discussions {
parse::persist_merge_request(db, &opts, mr, discussions, &mut progress).await?;
progress.prs_fetched += 1;
if let Some(ref cb) = on_progress {
cb(&progress);
}
}
Ok(progress)
}
pub async fn verify_project_access(
host: &str,
token: &str,
project_path: &str,
) -> Result<(), CoreError> {
crate::ingest::provider::validate_gitlab_project_path(project_path)?;
let client = GitlabClient::new(host, token)?;
client.check_project_access(project_path).await
}
fn iid_excluded(iid: i64, exclude: &HashSet<i32>) -> bool {
exclude.iter().any(|excluded| i64::from(*excluded) == iid)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iid_exclusion_compares_across_integer_widths() {
let exclude: HashSet<i32> = std::iter::once(42).collect();
assert!(iid_excluded(42, &exclude));
assert!(!iid_excluded(43, &exclude));
assert!(!iid_excluded(i64::from(i32::MAX) + 1, &exclude));
}
}