SourceManager

Struct SourceManager 

Source
pub struct SourceManager { /* private fields */ }
Expand description

Manages multiple source repositories with caching, synchronization, and verification.

SourceManager is the central component for handling source repositories in AGPM. It provides operations for adding, removing, syncing, and verifying sources while maintaining a local cache for efficient access. The manager handles both remote repositories and local file paths with comprehensive error handling and progress reporting.

§Core Responsibilities

  • Source Registry: Maintains a collection of named sources
  • Cache Management: Handles local caching of repository content
  • Synchronization: Keeps cached repositories up-to-date
  • Verification: Ensures repositories are accessible and valid
  • Authentication: Integrates with global configuration for private repositories
  • Progress Reporting: Provides feedback during long-running operations

§Cache Management

The manager maintains a cache directory (typically ~/.agpm/cache/sources/) where each source is stored in a subdirectory named after the repository owner and name. The cache provides:

  • Persistence: Repositories remain cached between operations
  • Efficiency: Avoid re-downloading unchanged repositories
  • Offline Access: Use cached content when network is unavailable
  • Integrity: Validate cache consistency and auto-repair when needed

§Thread Safety

SourceManager is designed for single-threaded use but can be cloned for use across multiple operations. For concurrent access, wrap in appropriate synchronization primitives like Arc and Mutex.

§Examples

§Basic Usage

use agpm_cli::source::{Source, SourceManager};
use anyhow::Result;

// Create a new manager
let mut manager = SourceManager::new()?;

// Add a source
let source = Source::new(
    "community".to_string(),
    "https://github.com/example/agpm-community.git".to_string()
);
manager.add(source)?;

// Sync the repository
let repo = manager.sync("community").await?;
println!("Repository synced to: {:?}", repo.path());

§Loading from Manifest

use agpm_cli::source::SourceManager;
use agpm_cli::manifest::Manifest;
use std::path::Path;

// Load sources from project manifest and global config
let manifest = Manifest::load(Path::new("agpm.toml"))?;
let manager = SourceManager::from_manifest_with_global(&manifest).await?;

println!("Loaded {} sources", manager.list().len());

Implementations§

Source§

impl SourceManager

Source

pub fn new() -> Result<Self>

Creates a new source manager with the default cache directory.

The cache directory is determined by the system configuration, typically ~/.agpm/cache/ on Unix systems or %APPDATA%\agpm\cache\ on Windows.

§Errors

Returns an error if the cache directory cannot be determined or created.

§Examples
use agpm_cli::source::SourceManager;

let manager = SourceManager::new()?;
println!("Manager created with {} sources", manager.list().len());
Source

pub fn new_with_cache(cache_dir: PathBuf) -> Self

Creates a new source manager with a custom cache directory.

This constructor is primarily used for testing and scenarios where a specific cache location is required. For normal usage, prefer SourceManager::new().

§Arguments
  • cache_dir - Custom directory for caching repositories
§Examples
use agpm_cli::source::SourceManager;
use std::path::PathBuf;

let custom_cache = PathBuf::from("/custom/cache/location");
let manager = SourceManager::new_with_cache(custom_cache);
Source

pub fn from_manifest(manifest: &Manifest) -> Result<Self>

Creates a source manager from a manifest file (without global config integration).

This method loads only sources defined in the project manifest, without merging with global configuration. Use from_manifest_with_global() for full integration that includes authentication tokens and private repositories.

This method is primarily for backward compatibility and testing scenarios.

§Arguments
  • manifest - Project manifest containing source definitions
§Errors

Returns an error if the cache directory cannot be determined.

§Examples
use agpm_cli::source::SourceManager;
use agpm_cli::manifest::Manifest;
use std::path::Path;

let manifest = Manifest::load(Path::new("agpm.toml"))?;
let manager = SourceManager::from_manifest(&manifest)?;

println!("Loaded {} sources from manifest", manager.list().len());
Source

pub async fn from_manifest_with_global(manifest: &Manifest) -> Result<Self>

Creates a source manager from manifest with global configuration integration.

This is the recommended method for creating a SourceManager in production use. It merges sources from both the project manifest and global configuration, enabling:

  • Authentication: Access to private repositories with embedded credentials
  • User customization: Global sources that extend project-defined sources
  • Security: Credentials stored safely outside version control
§Source Resolution Priority
  1. Global sources: Loaded first (may contain authentication tokens)
  2. Local sources: Override global sources with same names
  3. Merged result: Final source collection used by the manager
§Arguments
  • manifest - Project manifest containing source definitions
§Errors

Returns an error if:

  • Cache directory cannot be determined
  • Global configuration cannot be loaded (though this is non-fatal)
§Examples
use agpm_cli::source::SourceManager;
use agpm_cli::manifest::Manifest;
use std::path::Path;

let manifest = Manifest::load(Path::new("agpm.toml"))?;
let manager = SourceManager::from_manifest_with_global(&manifest).await?;

// Manager now includes both project and global sources
for source in manager.list() {
    println!("Available source: {} -> {}", source.name, source.url);
}
Source

pub fn from_manifest_with_cache(manifest: &Manifest, cache_dir: PathBuf) -> Self

Creates a source manager from manifest with a custom cache directory.

This method is primarily used for testing where a specific cache location is needed. It loads only sources from the manifest without global configuration integration.

§Arguments
  • manifest - Project manifest containing source definitions
  • cache_dir - Custom directory for caching repositories
§Examples
use agpm_cli::source::SourceManager;
use agpm_cli::manifest::Manifest;
use std::path::{Path, PathBuf};

let manifest = Manifest::load(Path::new("agpm.toml"))?;
let custom_cache = PathBuf::from("/tmp/test-cache");
let manager = SourceManager::from_manifest_with_cache(&manifest, custom_cache);
Source

pub fn add(&mut self, source: Source) -> Result<()>

Adds a new source to the manager.

The source name must be unique within this manager. Adding a source with an existing name will return an error.

§Arguments
  • source - The source to add to the manager
§Errors

Returns AgpmError::ConfigError if a source with the same name already exists.

§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;

let source = Source::new(
    "community".to_string(),
    "https://github.com/example/agpm-community.git".to_string()
);

manager.add(source)?;
assert!(manager.get("community").is_some());
Source

pub async fn remove(&mut self, name: &str) -> Result<()>

Removes a source from the manager and cleans up its cache.

This operation permanently removes the source from the manager and deletes its cached repository data from disk. This cannot be undone, though the repository can be re-added and will be cloned again on next sync.

§Arguments
  • name - Name of the source to remove
§Errors

Returns an error if:

§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;

// Add and then remove a source
let source = Source::new("temp".to_string(), "https://github.com/temp/repo.git".to_string());
manager.add(source)?;
manager.remove("temp").await?;

assert!(manager.get("temp").is_none());
Source

pub fn get(&self, name: &str) -> Option<&Source>

Gets a reference to a source by name.

Returns None if no source with the given name exists.

§Arguments
  • name - Name of the source to retrieve
§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new("test".to_string(), "https://github.com/test/repo.git".to_string());
manager.add(source)?;

if let Some(source) = manager.get("test") {
    println!("Found source: {} -> {}", source.name, source.url);
}
Source

pub fn get_mut(&mut self, name: &str) -> Option<&mut Source>

Gets a mutable reference to a source by name.

Returns None if no source with the given name exists. Use this method when you need to modify source properties like description or enabled status.

§Arguments
  • name - Name of the source to retrieve
§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new("test".to_string(), "https://github.com/test/repo.git".to_string());
manager.add(source)?;

if let Some(source) = manager.get_mut("test") {
    source.description = Some("Updated description".to_string());
    source.enabled = false;
}
Source

pub fn list(&self) -> Vec<&Source>

Returns a list of all sources managed by this manager.

The returned vector contains references to all sources, both enabled and disabled. For only enabled sources, use list_enabled().

§Examples
use agpm_cli::source::{Source, SourceManager};

let manager = SourceManager::new()?;

for source in manager.list() {
    println!("Source: {} -> {} (enabled: {})",
        source.name, source.url, source.enabled);
}
Source

pub fn list_enabled(&self) -> Vec<&Source>

Returns a list of enabled sources managed by this manager.

Only sources with enabled: true are included in the result. This is useful for operations that should only work with active sources.

§Examples
use agpm_cli::source::{Source, SourceManager};

let manager = SourceManager::new()?;

println!("Enabled sources: {}", manager.list_enabled().len());
for source in manager.list_enabled() {
    println!("  {} -> {}", source.name, source.url);
}
Source

pub fn get_source_url(&self, name: &str) -> Option<String>

Gets the URL of a source by name.

Returns the repository URL for the named source, or None if the source doesn’t exist. This is useful for logging and debugging purposes.

§Arguments
  • name - Name of the source to get the URL for
§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new("test".to_string(), "https://github.com/test/repo.git".to_string());
manager.add(source)?;

if let Some(url) = manager.get_source_url("test") {
    println!("Source URL: {}", url);
}
Source

pub async fn sync(&mut self, name: &str) -> Result<GitRepo>

Synchronizes a source repository to the local cache.

This is the core method for ensuring a source repository is available locally. It handles both initial cloning and subsequent updates, with intelligent caching and error recovery.

§Synchronization Process
  1. Validation: Check that the source exists and is enabled
  2. Cache Check: Determine if repository is already cached
  3. Repository Type Detection: Handle remote vs local repositories
  4. Sync Operation:
    • First time: Clone the repository to cache
    • Subsequent: Fetch updates from remote
    • Invalid cache: Remove corrupted cache and re-clone
  5. Cache Update: Update source’s local_path with cache location
§Repository Types Supported
§Remote Repositories
  • HTTPS: https://github.com/owner/repo.git
  • SSH: git@github.com:owner/repo.git
§Local Repositories
  • Absolute paths: /absolute/path/to/repo
  • Relative paths: ../relative/path or ./local-path
  • File URLs: file:///absolute/path/to/repo
§Authentication

Authentication is handled transparently through URLs with embedded credentials from the global configuration. Private repositories should have their authentication tokens configured in ~/.agpm/config.toml.

§Error Handling

The method provides comprehensive error handling for common scenarios:

  • Source not found: Clear error with source name
  • Disabled source: Prevents operations on disabled sources
  • Network failures: Graceful handling with context
  • Invalid repositories: Validation of Git repository structure
  • Cache corruption: Automatic cleanup and re-cloning
§Arguments
  • name - Name of the source to synchronize
  • progress - Optional progress bar for user feedback during long operations
§Returns

Returns a GitRepo instance pointing to the synchronized repository cache.

§Errors

Returns an error if:

  • Source doesn’t exist (AgpmError::SourceNotFound)
  • Source is disabled (AgpmError::ConfigError)
  • Repository is not accessible (network, permissions, etc.)
  • Local path doesn’t exist or isn’t a Git repository
  • Cache directory cannot be created
§Examples
§Basic Synchronization
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new(
    "community".to_string(),
    "https://github.com/example/agpm-community.git".to_string()
);
manager.add(source)?;

// Sync without progress feedback
let repo = manager.sync("community").await?;
println!("Repository available at: {:?}", repo.path());
§Synchronization with Progress
use agpm_cli::source::{Source, SourceManager};
use indicatif::ProgressBar;

let mut manager = SourceManager::new()?;
let source = Source::new(
    "large-repo".to_string(),
    "https://github.com/example/large-repository.git".to_string()
);
manager.add(source)?;

// Sync repository
let progress = ProgressBar::new(100);
progress.set_message("Syncing large repository...");

let repo = manager.sync("large-repo").await?;
progress.finish_with_message("Repository synced successfully");
Source

pub async fn sync_by_url(&self, url: &str) -> Result<GitRepo>

Synchronizes a repository by URL without adding it as a named source.

This method is used for direct Git dependencies that are referenced by URL rather than by source name. It’s particularly useful for one-off repository access or when dealing with dependencies that don’t need to be permanently registered.

§Key Differences from sync()
  • No source registration: Repository is not added to the manager’s source list
  • URL-based caching: Cache directory is derived from the URL structure
  • Direct access: Bypasses source name resolution and enablement checks
  • Temporary usage: Ideal for short-lived or one-time repository access
§Cache Management

The cache directory is generated using the same pattern as named sources: {cache_dir}/sources/{owner}_{repository} where owner and repository are parsed from the Git URL.

§Repository Types

Supports the same repository types as sync():

  • Remote HTTPS/SSH repositories
  • Local file paths and file:// URLs
  • Proper validation for all repository types
§Arguments
  • url - Repository URL or local path to synchronize
  • progress - Optional progress bar for user feedback
§Returns

Returns a GitRepo instance pointing to the cached repository.

§Errors

Returns an error if:

  • Repository URL is invalid or inaccessible
  • Local path doesn’t exist or isn’t a Git repository
  • Network connectivity issues for remote repositories
  • Filesystem permission issues
§Examples
§Direct Repository Access
use agpm_cli::source::SourceManager;

let mut manager = SourceManager::new()?;

// Sync a repository directly by URL
let repo = manager.sync_by_url(
    "https://github.com/example/direct-dependency.git"
).await?;

println!("Direct repository available at: {:?}", repo.path());
§Local Repository Access
use agpm_cli::source::SourceManager;
use std::env;

let mut manager = SourceManager::new()?;

// Access a local development repository
let local_path = env::temp_dir().join("development").join("repo");
let repo = manager.sync_by_url(
    &local_path.to_string_lossy()
).await?;
Source

pub async fn sync_all(&mut self) -> Result<()>

Synchronizes all enabled sources by fetching latest changes

This method iterates through all enabled sources and synchronizes each one by fetching the latest changes from their remote repositories.

§Arguments
  • progress - Optional progress bar for displaying sync progress
§Returns

Returns Ok(()) if all sources sync successfully

§Errors

Returns an error if any source fails to sync

Source

pub async fn sync_multiple_by_url( &self, urls: &[String], ) -> Result<Vec<GitRepo>>

Sync multiple sources by URL in parallel

Executes all sync operations concurrently using tokio tasks. Each sync operation uses file-level locking via CacheLock to ensure thread safety, preventing concurrent modifications to the same repository.

§Performance

This method provides significant performance improvements when syncing multiple repositories, especially over network connections. All sync operations execute concurrently, limited only by system resources and network bandwidth.

§Thread Safety
  • Each repository sync acquires a file-based lock to prevent concurrent access
  • Different repositories can sync simultaneously without blocking each other
  • Lock contention only occurs if the same repository is synced multiple times
§Examples
let urls = vec![
    "https://github.com/example/repo1.git".to_string(),
    "https://github.com/example/repo2.git".to_string(),
    "https://github.com/example/repo3.git".to_string(),
];
let repos = manager.sync_multiple_by_url(&urls).await?;
Source

pub fn enable(&mut self, name: &str) -> Result<()>

Enables a source for use in operations.

Enabled sources are included in operations like sync_all() and verify_all(). Sources are enabled by default when created.

§Arguments
  • name - Name of the source to enable
§Errors

Returns AgpmError::SourceNotFound if no source with the given name exists.

§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new("test".to_string(), "https://github.com/test/repo.git".to_string());
manager.add(source)?;

// Disable then re-enable
manager.disable("test")?;
manager.enable("test")?;

assert!(manager.get("test").unwrap().enabled);
Source

pub fn disable(&mut self, name: &str) -> Result<()>

Disables a source to exclude it from operations.

Disabled sources are excluded from bulk operations like sync_all() and verify_all(), and cannot be synced individually. This is useful for temporarily disabling problematic sources without removing them entirely.

§Arguments
  • name - Name of the source to disable
§Errors

Returns AgpmError::SourceNotFound if no source with the given name exists.

§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new("test".to_string(), "https://github.com/test/repo.git".to_string());
manager.add(source)?;

// Disable the source
manager.disable("test")?;

assert!(!manager.get("test").unwrap().enabled);
assert_eq!(manager.list_enabled().len(), 0);
Source

pub fn get_cached_path(&self, url: &str) -> Result<PathBuf>

Gets the cache directory path for a source by URL.

Searches through managed sources to find one with a matching URL and returns its cache directory path. This is useful when you have a URL and need to determine where its cached content would be stored.

§Arguments
  • url - Repository URL to look up
§Returns

PathBuf pointing to the cache directory for the source.

§Errors

Returns AgpmError::SourceNotFound if no source with the given URL exists.

§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let url = "https://github.com/example/repo.git".to_string();
let source = Source::new("example".to_string(), url.clone());
manager.add(source)?;

let cache_path = manager.get_cached_path(&url)?;
println!("Cache path: {:?}", cache_path);
Source

pub fn get_cached_path_by_name(&self, name: &str) -> Result<PathBuf>

Gets the cache directory path for a source by name.

Returns the cache directory path where the named source’s repository content is or would be stored.

§Arguments
  • name - Name of the source to get the cache path for
§Returns

PathBuf pointing to the cache directory for the source.

§Errors

Returns AgpmError::SourceNotFound if no source with the given name exists.

§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;
let source = Source::new(
    "community".to_string(),
    "https://github.com/example/agpm-community.git".to_string()
);
manager.add(source)?;

let cache_path = manager.get_cached_path_by_name("community")?;
println!("Community cache: {:?}", cache_path);
Source

pub async fn verify_all(&self) -> Result<()>

Verifies that all enabled sources are accessible.

This method performs lightweight verification checks on all enabled sources without performing full synchronization. It’s useful for validating source configurations and network connectivity before attempting operations.

§Verification Process

For each enabled source:

  1. URL validation: Check URL format and structure
  2. Connectivity test: Verify remote repositories are reachable
  3. Local path validation: Ensure local repositories exist and are Git repos
  4. Authentication check: Validate credentials for private repositories
§Performance Characteristics
  • Lightweight: No cloning or downloading of repository content
  • Fast: Quick network checks rather than full Git operations
  • Sequential: Sources verified one at a time for clear error reporting
§Arguments
  • progress - Optional progress bar for user feedback
§Errors

Returns an error if any enabled source fails verification:

  • Network connectivity issues
  • Authentication failures
  • Invalid repository URLs
  • Local paths that don’t exist or aren’t Git repositories
§Examples
use agpm_cli::source::{Source, SourceManager};

let mut manager = SourceManager::new()?;

// Add some sources
manager.add(Source::new(
    "community".to_string(),
    "https://github.com/example/agpm-community.git".to_string()
))?;

// Verify all sources
manager.verify_all().await?;

println!("All sources verified successfully");

Trait Implementations§

Source§

impl Clone for SourceManager

Source§

fn clone(&self) -> SourceManager

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SourceManager

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,