quinjet 0.0.72

A fast, live, keyboard-first Git source-control interface for the terminal
use std::sync::OnceLock;

#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use super::*;

#[derive(Deserialize, Serialize)]
struct CachedComparison {
    merge_base: String,
    index: PullRequestDiffIndex,
}

pub(super) fn load(
    repository: &Repository,
    pull_request: &PullRequest,
    exact_base: bool,
) -> Option<PreparedPullRequest> {
    let key = key(pull_request, exact_base)?;
    let data = cache_read_bounded(&key, CacheLife::Immutable, MAX_PR_PATH_BYTES)?;
    let cached: CachedComparison = serde_json::from_slice(&data).ok()?;
    if !is_commit_oid(&cached.merge_base) || cached.index.files.len() > MAX_PR_PATHS {
        return None;
    }
    let mut pull_request = pull_request.clone();
    if exact_base {
        pull_request.changed_files = cached.index.total_files;
        pull_request.additions = cached
            .index
            .files
            .iter()
            .filter_map(|file| file.counts)
            .map(|counts| counts.additions)
            .sum();
        pull_request.deletions = cached
            .index
            .files
            .iter()
            .filter_map(|file| file.counts)
            .map(|counts| counts.deletions)
            .sum();
    }
    let head = pull_request.head_oid.clone();
    Some(PreparedPullRequest {
        repository: PreparedRepository::Deferred(DeferredRepository {
            source: repository.root().to_path_buf(),
            ready: OnceLock::new(),
        }),
        pull_request,
        merge_base: cached.merge_base,
        head,
        index: cached.index,
        diff_highlighting: repository.diff_highlighting,
    })
}

pub(super) fn save(prepared: &PreparedPullRequest, exact_base: bool) {
    let Some(key) = key(&prepared.pull_request, exact_base) else {
        return;
    };
    if prepared.head != prepared.pull_request.head_oid {
        return;
    }
    let cached = CachedComparison {
        merge_base: prepared.merge_base.clone(),
        index: prepared.index.clone(),
    };
    if let Ok(data) = serde_json::to_vec(&cached) {
        cache_write_bounded(&key, &data, MAX_PR_PATH_BYTES);
    }
}

fn key(pull_request: &PullRequest, exact_base: bool) -> Option<String> {
    (is_commit_oid(&pull_request.base_oid) && is_commit_oid(&pull_request.head_oid)).then(|| {
        format!(
            "pr-comparison-v1\n{}\n{}\n{}\n{exact_base}",
            pull_request.base_repository.url.trim_end_matches('/'),
            pull_request.base_oid,
            pull_request.head_oid,
        )
    })
}

pub(super) struct DeferredRepository {
    pub(super) source: PathBuf,
    ready: OnceLock<TemporaryBareRepository>,
}

impl DeferredRepository {
    pub(super) fn resolve(
        &self,
        pull_request: &PullRequest,
        merge_base: &str,
        head: &str,
    ) -> Result<&Path> {
        if let Some(repository) = self.ready.get() {
            return Ok(&repository.path);
        }
        let opened = Repository {
            root: self.source.clone(),
            github_cli: None,
            diff_highlighting: true,
        };
        if opened.has_commit(merge_base) && opened.has_commit(head) {
            return Ok(&self.source);
        }
        let temporary = TemporaryBareRepository::new()?;
        temporary.borrow_local_objects(&opened);
        let (fetched_base, fetched_head) =
            fetch_pull_request(&temporary.path, pull_request, Some(merge_base), &mut |_| {})?;
        if fetched_head != head || fetched_base != merge_base {
            bail!("The pull-request comparison changed; refresh before loading uncached files");
        }
        drop(self.ready.set(temporary));
        self.ready
            .get()
            .map(|repository| repository.path.as_path())
            .context("the pull-request repository was not prepared")
    }
}