pray-core 1.22.0

Core library for Prayfile, the package manager for the language placed before inference
Documentation
use super::super::{source_map, ResolvedPackage, ResolvedProject};
use super::input_output::{
    content_file_bytes, local_content_for_refresh, should_materialize_empty_fork,
};
use super::lock::ensure_locked_upstream_matches;
use super::resolution_context::UpstreamResolutionContext;
use crate::config::load_user_config;
use crate::lockfile::Lockfile;
use crate::package_upstream::{try_merge_content_files, LockedUpstream};
use crate::resolve_context::ResolveOptions;
use crate::resolve_git_sources::prepare_git_sources;
use crate::PrayResult;
use std::collections::BTreeMap;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathUpstreamConflict {
    pub package_name: String,
    pub upstream_name: String,
    pub old_version: String,
    pub new_version: String,
    pub paths: Vec<String>,
}

pub fn plan_path_upstream_conflicts(
    project: &ResolvedProject,
    previous: Option<&Lockfile>,
    selected: Option<&str>,
    options: &ResolveOptions,
) -> PrayResult<Vec<PathUpstreamConflict>> {
    let user_config = load_user_config()?;
    let git_sources = prepare_git_sources(
        &project.project_root,
        &project.manifest.sources,
        previous,
        options,
    )?;
    let sources = source_map(&project.manifest.sources);
    let mut conflicts = Vec::new();
    for package in &project.packages {
        if selected.is_some_and(|name| name != package.declaration.name) {
            continue;
        }
        if let Some(conflict) = plan_one_path_upstream_conflict(
            project,
            package,
            previous,
            &sources,
            &git_sources,
            &user_config,
            options,
        )? {
            conflicts.push(conflict);
        }
    }
    Ok(conflicts)
}

fn plan_one_path_upstream_conflict(
    project: &ResolvedProject,
    package: &ResolvedPackage,
    previous: Option<&Lockfile>,
    sources: &BTreeMap<String, crate::manifest::ManifestSource>,
    git_sources: &crate::resolve_git_sources::GitSourceSet,
    user_config: &crate::config::PrayConfig,
    options: &ResolveOptions,
) -> PrayResult<Option<PathUpstreamConflict>> {
    let Some(new_upstream) = &package.upstream else {
        return Ok(None);
    };
    if package.declaration.path.is_none() {
        return Ok(None);
    }
    let local_content = local_content_for_refresh(&package.root, &package.spec)?;
    let old_upstream = previous.and_then(|lockfile| {
        lockfile
            .package
            .iter()
            .find(|entry| entry.name == package.declaration.name)
            .and_then(|entry| entry.upstream.clone())
    });
    if should_materialize_empty_fork(&local_content, &package.spec, old_upstream.is_some()) {
        return Ok(None);
    }
    let Some(old_upstream) = old_upstream else {
        return Ok(None);
    };
    if old_upstream.version == new_upstream.version
        && old_upstream.tree_hash == new_upstream.tree_hash
    {
        return Ok(None);
    }
    let resolution_context = UpstreamResolutionContext::new(
        &project.project_root,
        sources,
        git_sources,
        user_config,
        previous,
        options,
    );
    let old_package = resolution_context.resolve(
        &old_upstream.name,
        &format!("= {}", old_upstream.version),
        old_upstream.source.as_deref(),
    )?;
    let resolved_old = LockedUpstream {
        name: old_upstream.name.clone(),
        version: old_package.spec.version.clone(),
        source: old_upstream.source.clone(),
        tree_hash: old_package.tree_hash.clone(),
        artifact_hash: old_package.artifact_hash.clone(),
    };
    ensure_locked_upstream_matches(&old_upstream, &resolved_old)?;
    let new_package = resolution_context.resolve(
        &new_upstream.name,
        &format!("= {}", new_upstream.version),
        new_upstream.source.as_deref(),
    )?;
    let old_content = content_file_bytes(&old_package.root, &old_package.spec)?;
    let new_content = content_file_bytes(&new_package.root, &new_package.spec)?;
    match try_merge_content_files(&old_content, &new_content, &local_content) {
        Ok(_) => Ok(None),
        Err(paths) => Ok(Some(PathUpstreamConflict {
            package_name: package.declaration.name.clone(),
            upstream_name: old_upstream.name.clone(),
            old_version: old_upstream.version.clone(),
            new_version: new_upstream.version.clone(),
            paths,
        })),
    }
}