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
impl SourceManager
Sourcepub fn new() -> Result<Self>
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());
Sourcepub fn new_with_cache(cache_dir: PathBuf) -> Self
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);
Sourcepub fn from_manifest(manifest: &Manifest) -> Result<Self>
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());
Sourcepub async fn from_manifest_with_global(manifest: &Manifest) -> Result<Self>
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
- Global sources: Loaded first (may contain authentication tokens)
- Local sources: Override global sources with same names
- 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);
}
Sourcepub fn from_manifest_with_cache(manifest: &Manifest, cache_dir: PathBuf) -> Self
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 definitionscache_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);
Sourcepub fn add(&mut self, source: Source) -> Result<()>
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());
Sourcepub async fn remove(&mut self, name: &str) -> Result<()>
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:
- The source does not exist (
AgpmError::SourceNotFound
) - The cache directory cannot be removed due to filesystem permissions
§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());
Sourcepub fn get(&self, name: &str) -> Option<&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);
}
Sourcepub fn get_mut(&mut self, name: &str) -> Option<&mut 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;
}
Sourcepub fn list(&self) -> Vec<&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);
}
Sourcepub fn list_enabled(&self) -> Vec<&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);
}
Sourcepub fn get_source_url(&self, name: &str) -> Option<String>
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);
}
Sourcepub async fn sync(&mut self, name: &str) -> Result<GitRepo>
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
- Validation: Check that the source exists and is enabled
- Cache Check: Determine if repository is already cached
- Repository Type Detection: Handle remote vs local repositories
- Sync Operation:
- First time: Clone the repository to cache
- Subsequent: Fetch updates from remote
- Invalid cache: Remove corrupted cache and re-clone
- 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 synchronizeprogress
- 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");
Sourcepub async fn sync_by_url(&self, url: &str) -> Result<GitRepo>
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 synchronizeprogress
- 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?;
Sourcepub async fn sync_all(&mut self) -> Result<()>
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
Sourcepub async fn sync_multiple_by_url(
&self,
urls: &[String],
) -> Result<Vec<GitRepo>>
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?;
Sourcepub fn enable(&mut self, name: &str) -> Result<()>
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);
Sourcepub fn disable(&mut self, name: &str) -> Result<()>
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);
Sourcepub fn get_cached_path(&self, url: &str) -> Result<PathBuf>
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);
Sourcepub fn get_cached_path_by_name(&self, name: &str) -> Result<PathBuf>
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);
Sourcepub async fn verify_all(&self) -> Result<()>
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:
- URL validation: Check URL format and structure
- Connectivity test: Verify remote repositories are reachable
- Local path validation: Ensure local repositories exist and are Git repos
- 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
impl Clone for SourceManager
Source§fn clone(&self) -> SourceManager
fn clone(&self) -> SourceManager
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read more