pub struct GitRepo { /* private fields */ }
Expand description
A Git repository handle providing async operations via CLI commands.
GitRepo
represents a local Git repository and provides methods for common
Git operations such as cloning, fetching, checking out specific references,
and querying repository state. All operations are performed asynchronously
using the system’s git
command rather than an embedded Git library.
§Design Principles
- CLI-based: Uses system
git
command for maximum compatibility - Async: All operations are non-blocking and support cancellation
- Progress-aware: Integration with progress reporting for long operations
- Error-rich: Detailed error information with context and suggestions
- Cross-platform: Tested on Windows, macOS, and Linux
§Repository State
The struct holds minimal state (just the repository path) and queries Git directly for current information. This ensures consistency with external Git operations and avoids state synchronization issues.
§Examples
use agpm_cli::git::GitRepo;
use std::path::Path;
// Create handle for existing repository
let repo = GitRepo::new("/path/to/existing/repo");
// Verify it's a valid Git repository
if repo.is_git_repo() {
let tags = repo.list_tags().await?;
repo.checkout("main").await?;
}
§Thread Safety
GitRepo
is Send
and Sync
, allowing it to be used across async tasks.
However, concurrent Git operations on the same repository may conflict
at the Git level (e.g., simultaneous checkouts).
Implementations§
Source§impl GitRepo
impl GitRepo
Sourcepub fn new(path: impl AsRef<Path>) -> Self
pub fn new(path: impl AsRef<Path>) -> Self
Creates a new GitRepo
instance for an existing local repository.
This constructor does not verify that the path contains a valid Git repository.
Use is_git_repo
or ensure_valid_git_repo
to validate
the repository before performing Git operations.
§Arguments
path
- The filesystem path to the Git repository root directory
§Examples
use agpm_cli::git::GitRepo;
use std::path::Path;
// Create repository handle
let repo = GitRepo::new("/path/to/repo");
// Verify it's valid before operations
if repo.is_git_repo() {
println!("Valid Git repository at: {:?}", repo.path());
}
§See Also
clone
- For creating repositories by cloning from remoteis_git_repo
- For validating repository state
Sourcepub async fn clone(url: &str, target: impl AsRef<Path>) -> Result<Self>
pub async fn clone(url: &str, target: impl AsRef<Path>) -> Result<Self>
Clones a Git repository from a remote URL to a local path.
This method performs a full clone operation, downloading the entire repository history to the target directory. The operation is async and supports progress reporting for large repositories.
§Arguments
url
- The remote repository URL (HTTPS, SSH, or file://)target
- The local directory where the repository will be clonedprogress
- Optional progress bar for user feedback
§Authentication
Authentication can be provided in several ways:
- HTTPS with tokens:
https://token:value@github.com/user/repo.git
- SSH keys: Handled by system SSH agent and Git configuration
- Credential helpers: System Git credential managers
§Supported URL Formats
https://github.com/user/repo.git
- HTTPSgit@github.com:user/repo.git
- SSHfile:///path/to/repo.git
- Local file systemhttps://user:token@github.com/user/repo.git
- HTTPS with auth
§Examples
use agpm_cli::git::GitRepo;
use std::env;
let temp_dir = env::temp_dir();
// Clone public repository
let repo = GitRepo::clone(
"https://github.com/rust-lang/git2-rs.git",
temp_dir.join("git2-rs")
).await?;
// Clone another repository
let repo = GitRepo::clone(
"https://github.com/example/repository.git",
temp_dir.join("example-repo")
).await?;
§Errors
Returns AgpmError::GitCloneFailed
if:
- The URL is invalid or unreachable
- Authentication fails
- The target directory already exists and is not empty
- Network connectivity issues
- Insufficient disk space
§Security
URLs are validated and sanitized before passing to Git. Authentication tokens in URLs are never logged or exposed in error messages.
Sourcepub async fn fetch(&self, auth_url: Option<&str>) -> Result<()>
pub async fn fetch(&self, auth_url: Option<&str>) -> Result<()>
Fetches updates from the remote repository without modifying the working tree.
This operation downloads new commits, branches, and tags from the remote
repository but does not modify the current branch or working directory.
It’s equivalent to git fetch --all --tags
.
§Arguments
auth_url
- Optional URL with authentication for private repositoriesprogress
- Optional progress bar for network operation feedback
§Authentication URL
The auth_url
parameter allows fetching from repositories that require
different authentication than the original clone URL. This is useful when:
- Using rotating tokens or credentials
- Accessing private repositories through different auth methods
- Working with multiple authentication contexts
§Local Repository Optimization
For local repositories (file:// URLs), fetch is automatically skipped as local repositories don’t require network synchronization.
§Examples
use agpm_cli::git::GitRepo;
use std::env;
let temp_dir = env::temp_dir();
let repo_path = temp_dir.join("repo");
let repo = GitRepo::new(&repo_path);
// Basic fetch from configured remote
repo.fetch(None).await?;
// Fetch with authentication
let auth_url = "https://token:ghp_xxxx@github.com/user/repo.git";
repo.fetch(Some(auth_url)).await?;
§Errors
Returns AgpmError::GitCommandError
if:
- Network connectivity fails
- Authentication is rejected
- The remote repository is unavailable
- The local repository is in an invalid state
§Performance
Fetch operations are optimized to:
- Skip unnecessary work for local repositories
- Provide progress feedback for large transfers
- Use efficient Git transfer protocols
Sourcepub async fn checkout(&self, ref_name: &str) -> Result<()>
pub async fn checkout(&self, ref_name: &str) -> Result<()>
Checks out a specific Git reference (branch, tag, or commit hash).
This operation switches the repository’s working directory to match the specified reference. It performs a hard reset before checkout to ensure a clean state, discarding any local modifications.
§Arguments
ref_name
- The Git reference to checkout (branch, tag, or commit)
§Reference Resolution Strategy
The method attempts to resolve references in the following order:
- Direct reference: Exact match for tags, branches, or commit hashes
- Remote branch: Tries
origin/{ref_name}
for remote branches - Error: If neither resolution succeeds, returns an error
§Supported Reference Types
- Tags:
v1.0.0
,release-2023-01
, etc. - Branches:
main
,develop
,feature/new-ui
, etc. - Commit hashes:
abc123def
,1234567890abcdef
(full or abbreviated) - Remote branches: Automatically tries
origin/{branch_name}
§State Management
Before checkout, the method performs:
- Hard reset:
git reset --hard HEAD
to discard local changes - Clean checkout: Switches to the target reference
- Detached HEAD: For tags/commits (normal Git behavior)
§Examples
use agpm_cli::git::GitRepo;
let repo = GitRepo::new("/path/to/repo");
// Checkout a specific version tag
repo.checkout("v1.2.3").await?;
// Checkout a branch
repo.checkout("main").await?;
// Checkout a commit hash
repo.checkout("abc123def456").await?;
// Checkout remote branch
repo.checkout("feature/experimental").await?;
§Data Loss Warning
This operation discards uncommitted changes. The hard reset before checkout ensures a clean state but will permanently lose any local modifications. This behavior is intentional for AGPM’s package management use case where clean, reproducible states are required.
§Errors
Returns AgpmError::GitCheckoutFailed
if:
- The reference doesn’t exist in the repository
- The repository is in an invalid state
- File system permissions prevent checkout
- The working directory is locked by another process
§Performance
Checkout operations are optimized for:
- Fast switching between cached references
- Minimal file system operations
- Efficient handling of large repositories
Lists all tags in the repository, sorted by Git’s default ordering.
This method retrieves all Git tags from the local repository using
git tag -l
. Tags are returned as strings in Git’s natural ordering,
which may not be semantic version order.
§Return Value
Returns a Vec<String>
containing all tag names. Empty if no tags exist.
Tags are returned exactly as they appear in Git (no prefix stripping).
§Repository Validation
The method validates that:
- The repository path exists on the filesystem
- The directory contains a
.git
subdirectory - The repository is in a valid state for Git operations
§Examples
use agpm_cli::git::GitRepo;
let repo = GitRepo::new("/path/to/repo");
// Get all available tags
let tags = repo.list_tags().await?;
for tag in tags {
println!("Available version: {}", tag);
}
// Check for specific tag
let tags = repo.list_tags().await?;
if tags.contains(&"v1.0.0".to_string()) {
repo.checkout("v1.0.0").await?;
}
§Version Parsing
For semantic version ordering, consider using the semver
crate:
use semver::Version;
use agpm_cli::git::GitRepo;
let repo = GitRepo::new("/path/to/repo");
let tags = repo.list_tags().await?;
// Parse and sort semantic versions
let mut versions: Vec<Version> = tags
.iter()
.filter_map(|tag| tag.strip_prefix('v'))
.filter_map(|v| Version::parse(v).ok())
.collect();
versions.sort();
§Errors
Returns AgpmError::GitCommandError
if:
- The repository path doesn’t exist
- The directory is not a valid Git repository
- Git command execution fails
- File system permissions prevent access
§Performance
This operation is relatively fast as it only reads Git’s tag database without network access. For repositories with thousands of tags, consider filtering or pagination if memory usage is a concern.
Sourcepub async fn get_remote_url(&self) -> Result<String>
pub async fn get_remote_url(&self) -> Result<String>
Retrieves the URL of the remote ‘origin’ repository.
This method queries the Git repository for the URL associated with the ‘origin’ remote, which is typically the source repository from which the local repository was cloned.
§Return Value
Returns the origin URL as configured in the repository’s Git configuration. The URL format depends on how the repository was cloned:
- HTTPS:
https://github.com/user/repo.git
- SSH:
git@github.com:user/repo.git
- File:
file:///path/to/repo.git
§Authentication Handling
The returned URL reflects the repository’s configured origin, which may or may not include authentication information depending on the original clone method and Git configuration.
§Examples
use agpm_cli::git::GitRepo;
let repo = GitRepo::new("/path/to/repo");
// Get the origin URL
let url = repo.get_remote_url().await?;
println!("Repository origin: {}", url);
// Check if it's a specific platform
if url.contains("github.com") {
println!("This is a GitHub repository");
}
§URL Processing
For processing the URL further, consider using parse_git_url
:
use agpm_cli::git::{GitRepo, parse_git_url};
let repo = GitRepo::new("/path/to/repo");
let url = repo.get_remote_url().await?;
// Parse into owner and repository name
let (owner, name) = parse_git_url(&url)?;
println!("Owner: {}, Repository: {}", owner, name);
§Errors
Returns AgpmError::GitCommandError
if:
- No ‘origin’ remote is configured
- The repository is not a valid Git repository
- Git command execution fails
- File system access is denied
§Security
The returned URL may contain authentication information if it was configured that way. Be cautious when logging or displaying URLs that might contain sensitive tokens or credentials.
Sourcepub fn is_git_repo(&self) -> bool
pub fn is_git_repo(&self) -> bool
Checks if the directory contains a valid Git repository.\n ///
This method detects both regular and bare Git repositories:\n /// - Regular repositories: Have a .git
subdirectory\n /// - Bare repositories: Have a HEAD
file in the root\n ///
Bare repositories are commonly used for:\n /// - Serving repositories (like GitHub/GitLab)\n /// - Cache storage in package managers\n /// - Worktree sources for parallel operations\n ///
§Return Value\n ///
true
if the directory is a valid Git repository (regular or bare)\n /// -false
if neither.git
directory norHEAD
file exists\n ///
§Performance\n ///
This method is intentionally synchronous and lightweight for efficiency.\n /// It performs at most two filesystem checks without spawning async tasks or\n /// executing Git commands.\n ///
§Examples\n ///
use agpm_cli::git::GitRepo;
// Regular repository
let repo = GitRepo::new("/path/to/regular/repo");
if repo.is_git_repo() {
println!("Valid Git repository detected");
}
// Bare repository
let bare_repo = GitRepo::new("/path/to/repo.git");
if bare_repo.is_git_repo() {
println!("Valid bare Git repository detected");
}
// Use before async operations
let repo = GitRepo::new("/path/to/repo");
if repo.is_git_repo() {
let tags = repo.list_tags().await?;
// Process tags...
}
§Validation Scope
This method only checks for the presence of Git repository markers. It does not:
- Validate Git repository integrity
- Check for repository corruption
- Verify specific Git version compatibility
- Test network connectivity to remotes
For more thorough validation, use Git operations that will fail with\n /// detailed error information if the repository is corrupted.
§Alternative
For error-based validation with detailed context, use ensure_valid_git_repo
:
use agpm_cli::git::ensure_valid_git_repo;
use std::path::Path;
let path = Path::new("/path/to/repo");
ensure_valid_git_repo(path)?; // Returns detailed error if invalid
Sourcepub fn path(&self) -> &Path
pub fn path(&self) -> &Path
Returns the filesystem path to the Git repository.
This method provides access to the repository’s root directory path
as configured when the GitRepo
instance was created.
§Return Value
Returns a reference to the Path
representing the repository’s
root directory (the directory containing the .git
subdirectory).
§Examples
use agpm_cli::git::GitRepo;
use std::path::Path;
let repo = GitRepo::new("/home/user/my-project");
let path = repo.path();
println!("Repository path: {}", path.display());
assert_eq!(path, Path::new("/home/user/my-project"));
// Use for file operations within the repository
let readme_path = path.join("README.md");
if readme_path.exists() {
println!("Repository has a README file");
}
§File System Operations
The returned path can be used for various filesystem operations:
use agpm_cli::git::GitRepo;
let repo = GitRepo::new("/path/to/repo");
let repo_path = repo.path();
// Check repository contents
for entry in std::fs::read_dir(repo_path)? {
let entry = entry?;
println!("Found: {}", entry.file_name().to_string_lossy());
}
// Access specific files
let manifest_path = repo_path.join("Cargo.toml");
if manifest_path.exists() {
println!("Rust project detected");
}
§Path Validity
The returned path reflects the value provided during construction and
may not exist or may not be a valid Git repository. Use is_git_repo
to validate the repository state.
Sourcepub async fn verify_url(url: &str) -> Result<()>
pub async fn verify_url(url: &str) -> Result<()>
Verifies that a Git repository URL is accessible without performing a full clone.
This static method performs a lightweight check to determine if a repository
URL is valid and accessible. It uses git ls-remote
for remote repositories
or filesystem checks for local paths.
§Arguments
url
- The repository URL to verify
§Verification Methods
- Local repositories (
file://
URLs): Checks if the path exists - Remote repositories: Uses
git ls-remote --heads
to test connectivity - Authentication: Leverages system Git configuration and credential helpers
§Supported URL Types
https://github.com/user/repo.git
- HTTPS with optional authenticationgit@github.com:user/repo.git
- SSH with key-based authenticationfile:///path/to/repo
- Local filesystem repositorieshttps://token:value@host.com/repo.git
- HTTPS with embedded credentials
§Examples
use agpm_cli::git::GitRepo;
// Verify public repository
GitRepo::verify_url("https://github.com/rust-lang/git2-rs.git").await?;
// Verify before cloning
let url = "https://github.com/user/private-repo.git";
match GitRepo::verify_url(url).await {
Ok(_) => {
let repo = GitRepo::clone(url, "/tmp/repo").await?;
println!("Repository cloned successfully");
}
Err(e) => {
eprintln!("Repository not accessible: {}", e);
}
}
// Verify local repository
GitRepo::verify_url("file:///home/user/local-repo").await?;
§Performance Benefits
This method is much faster than attempting a full clone because it:
- Only queries repository metadata (refs and heads)
- Transfers minimal data over the network
- Avoids creating local filesystem structures
- Provides quick feedback on accessibility
§Authentication Testing
The verification process tests the complete authentication chain:
- Credential helper invocation
- SSH key validation (for SSH URLs)
- Token validation (for HTTPS URLs)
- Network connectivity and DNS resolution
§Use Cases
- Pre-flight checks: Validate URLs before expensive clone operations
- Dependency validation: Ensure all repository sources are accessible
- Configuration testing: Verify authentication setup
- Network diagnostics: Test connectivity to repository hosts
§Errors
Returns an error if:
- Network issues: DNS resolution, connectivity, timeouts
- Authentication failures: Invalid credentials, expired tokens
- Repository issues: Repository doesn’t exist, access denied
- Local path issues: File doesn’t exist (for
file://
URLs) - URL format issues: Malformed or unsupported URL schemes
§Security
This method respects the same security boundaries as Git operations:
- Uses system Git configuration and security settings
- Never bypasses authentication requirements
- Doesn’t cache or expose authentication credentials
- Follows Git’s SSL/TLS verification policies
Sourcepub async fn clone_bare(url: &str, target: impl AsRef<Path>) -> Result<Self>
pub async fn clone_bare(url: &str, target: impl AsRef<Path>) -> Result<Self>
Clone a repository as a bare repository (no working directory).
Bare repositories are optimized for use as a source for worktrees, allowing multiple concurrent checkouts without conflicts.
§Arguments
url
- The remote repository URLtarget
- The local directory where the bare repository will be storedprogress
- Optional progress bar for user feedback
§Returns
Returns a new GitRepo
instance pointing to the bare repository
§Examples
use agpm_cli::git::GitRepo;
use std::env;
let temp_dir = env::temp_dir();
let bare_repo = GitRepo::clone_bare(
"https://github.com/example/repo.git",
temp_dir.join("repo.git")
).await?;
Sourcepub async fn clone_bare_with_context(
url: &str,
target: impl AsRef<Path>,
context: Option<&str>,
) -> Result<Self>
pub async fn clone_bare_with_context( url: &str, target: impl AsRef<Path>, context: Option<&str>, ) -> Result<Self>
Clone a repository as a bare repository with logging context.
Bare repositories are optimized for use as a source for worktrees, allowing multiple concurrent checkouts without conflicts.
§Arguments
url
- The remote repository URLtarget
- The local directory where the bare repository will be storedprogress
- Optional progress bar for user feedbackcontext
- Optional context for logging (e.g., dependency name)
§Returns
Returns a new GitRepo
instance pointing to the bare repository
Sourcepub async fn create_worktree(
&self,
worktree_path: impl AsRef<Path>,
reference: Option<&str>,
) -> Result<Self>
pub async fn create_worktree( &self, worktree_path: impl AsRef<Path>, reference: Option<&str>, ) -> Result<Self>
Create a new worktree from this repository.
Worktrees allow multiple working directories to be checked out from a single repository, enabling parallel operations on different versions.
§Arguments
worktree_path
- The path where the worktree will be createdreference
- Optional Git reference (branch/tag/commit) to checkout
§Returns
Returns a new GitRepo
instance pointing to the worktree
§Examples
use agpm_cli::git::GitRepo;
let bare_repo = GitRepo::new("/path/to/bare.git");
// Create worktree with specific version
let worktree = bare_repo.create_worktree(
"/tmp/worktree1",
Some("v1.0.0")
).await?;
// Create worktree with default branch
let worktree2 = bare_repo.create_worktree(
"/tmp/worktree2",
None
).await?;
Sourcepub async fn create_worktree_with_context(
&self,
worktree_path: impl AsRef<Path>,
reference: Option<&str>,
context: Option<&str>,
) -> Result<Self>
pub async fn create_worktree_with_context( &self, worktree_path: impl AsRef<Path>, reference: Option<&str>, context: Option<&str>, ) -> Result<Self>
Create a new worktree from this repository with logging context.
Worktrees allow multiple working directories to be checked out from a single repository, enabling parallel operations on different versions.
§Arguments
worktree_path
- The path where the worktree will be createdreference
- Optional Git reference (branch/tag/commit) to checkoutcontext
- Optional context for logging (e.g., dependency name)
§Returns
Returns a new GitRepo
instance pointing to the worktree
Sourcepub async fn remove_worktree(
&self,
worktree_path: impl AsRef<Path>,
) -> Result<()>
pub async fn remove_worktree( &self, worktree_path: impl AsRef<Path>, ) -> Result<()>
Remove a worktree associated with this repository.
This removes the worktree and its administrative files, but preserves the bare repository for future use.
§Arguments
worktree_path
- The path to the worktree to remove
§Examples
use agpm_cli::git::GitRepo;
let bare_repo = GitRepo::new("/path/to/bare.git");
bare_repo.remove_worktree("/tmp/worktree1").await?;
Sourcepub async fn list_worktrees(&self) -> Result<Vec<PathBuf>>
pub async fn list_worktrees(&self) -> Result<Vec<PathBuf>>
List all worktrees associated with this repository.
Returns a list of paths to existing worktrees.
§Examples
use agpm_cli::git::GitRepo;
let bare_repo = GitRepo::new("/path/to/bare.git");
let worktrees = bare_repo.list_worktrees().await?;
for worktree in worktrees {
println!("Worktree: {}", worktree.display());
}
Sourcepub async fn prune_worktrees(&self) -> Result<()>
pub async fn prune_worktrees(&self) -> Result<()>
Prune stale worktree administrative files.
This cleans up worktree entries that no longer have a corresponding working directory on disk.
§Examples
use agpm_cli::git::GitRepo;
let bare_repo = GitRepo::new("/path/to/bare.git");
bare_repo.prune_worktrees().await?;
Sourcepub async fn is_bare(&self) -> Result<bool>
pub async fn is_bare(&self) -> Result<bool>
Check if this repository is a bare repository.
Bare repositories don’t have a working directory and are optimized for use as a source for worktrees.
§Examples
use agpm_cli::git::GitRepo;
let repo = GitRepo::new("/path/to/repo.git");
if repo.is_bare().await? {
println!("This is a bare repository");
}
Sourcepub async fn get_current_commit(&self) -> Result<String>
pub async fn get_current_commit(&self) -> Result<String>
Get the current commit SHA of the repository.
Returns the full 40-character SHA-1 hash of the current HEAD commit. This is useful for recording exact versions in lockfiles.
§Returns
The full commit hash as a string.
§Errors
Returns an error if:
- The repository is not valid
- HEAD is not pointing to a valid commit
- Git command fails
§Examples
let repo = GitRepo::new("/path/to/repo");
let commit = repo.get_current_commit().await?;
println!("Current commit: {}", commit);
Sourcepub async fn resolve_to_sha(&self, ref_spec: Option<&str>) -> Result<String>
pub async fn resolve_to_sha(&self, ref_spec: Option<&str>) -> Result<String>
Resolves a Git reference (tag, branch, commit) to its full SHA-1 hash.
This method is central to AGPM’s optimization strategy - by resolving all version specifications to SHAs upfront, we can:
- Create worktrees keyed by SHA for maximum reuse
- Avoid redundant checkouts for the same commit
- Ensure deterministic, reproducible installations
§Arguments
ref_spec
- The Git reference to resolve (tag, branch, short/full SHA, or None for HEAD)
§Returns
Returns the full 40-character SHA-1 hash of the resolved reference.
§Resolution Strategy
- If
ref_spec
is None or “HEAD”, resolves to current HEAD commit - If already a full SHA (40 hex chars), returns it unchanged
- Otherwise uses
git rev-parse
to resolve:- Tags (e.g., “v1.0.0”)
- Branches (e.g., “main”, “origin/main”)
- Short SHAs (e.g., “abc123”)
- Symbolic refs (e.g., “HEAD~1”)
§Examples
let repo = GitRepo::new("/path/to/repo");
// Resolve a tag
let sha = repo.resolve_to_sha(Some("v1.2.3")).await?;
assert_eq!(sha.len(), 40);
// Resolve HEAD
let head_sha = repo.resolve_to_sha(None).await?;
// Already a full SHA - returned as-is
let full_sha = "a".repeat(40);
let resolved = repo.resolve_to_sha(Some(&full_sha)).await?;
assert_eq!(resolved, full_sha);
§Errors
Returns an error if:
- The reference doesn’t exist in the repository
- The repository is invalid or corrupted
- Git command execution fails
pub async fn get_current_branch(&self) -> Result<String>
Sourcepub async fn get_default_branch(&self) -> Result<String>
pub async fn get_default_branch(&self) -> Result<String>
Gets the default branch name for the repository.
For bare repositories, this queries refs/remotes/origin/HEAD
to find
the default branch. For non-bare repositories, it returns the current branch.
§Returns
The default branch name (e.g., “main”, “master”) without the “refs/heads/” prefix.
§Errors
Returns an error if Git commands fail or the default branch cannot be determined.