aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-vcs/src/downloader/git.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mozart-vcs/src/downloader/git.rs')
-rw-r--r--crates/mozart-vcs/src/downloader/git.rs112
1 files changed, 112 insertions, 0 deletions
diff --git a/crates/mozart-vcs/src/downloader/git.rs b/crates/mozart-vcs/src/downloader/git.rs
new file mode 100644
index 0000000..3bdb9ca
--- /dev/null
+++ b/crates/mozart-vcs/src/downloader/git.rs
@@ -0,0 +1,112 @@
+use std::path::Path;
+
+use anyhow::Result;
+
+use crate::process::ProcessExecutor;
+use crate::util::git::GitUtil;
+
+use super::VcsDownloader;
+
+/// Git downloader using clone/checkout with optional mirror cache.
+///
+/// Corresponds to Composer's `Downloader\GitDownloader`.
+pub struct GitDownloader {
+ git_util: GitUtil,
+}
+
+impl GitDownloader {
+ pub fn new(git_util: GitUtil) -> Self {
+ Self { git_util }
+ }
+}
+
+impl VcsDownloader for GitDownloader {
+ fn download(&self, url: &str, _reference: &str, _target: &Path) -> Result<()> {
+ // Pre-sync the mirror so install can use --reference
+ self.git_util.sync_mirror(url)?;
+ Ok(())
+ }
+
+ fn install(&self, url: &str, reference: &str, target: &Path) -> Result<()> {
+ let target_str = target.to_string_lossy();
+ let mirror_path = self.git_util.mirror_path(url);
+
+ if mirror_path.join("HEAD").exists() {
+ // Clone with mirror reference for efficiency
+ let mirror_str = mirror_path.to_string_lossy().to_string();
+ self.git_util.run_command(
+ &[
+ "git",
+ "clone",
+ "--no-checkout",
+ "--dissociate",
+ "--reference",
+ &mirror_str,
+ "--",
+ url,
+ &target_str,
+ ],
+ url,
+ None,
+ )?;
+ } else {
+ self.git_util.run_command(
+ &["git", "clone", "--no-checkout", "--", url, &target_str],
+ url,
+ None,
+ )?;
+ }
+
+ // Checkout the specific reference
+ let process = ProcessExecutor::new();
+ process.execute_checked(&["git", "checkout", reference, "--force"], Some(target))?;
+
+ Ok(())
+ }
+
+ fn update(&self, url: &str, _old_ref: &str, new_ref: &str, target: &Path) -> Result<()> {
+ let process = ProcessExecutor::new();
+
+ // Update remote URL
+ process.execute_checked(
+ &["git", "remote", "set-url", "origin", "--", url],
+ Some(target),
+ )?;
+
+ // Fetch latest
+ self.git_util
+ .run_command(&["git", "fetch", "origin"], url, Some(target))?;
+
+ // Checkout new reference
+ process.execute_checked(&["git", "checkout", new_ref, "--force"], Some(target))?;
+
+ Ok(())
+ }
+
+ fn remove(&self, target: &Path) -> Result<()> {
+ if target.exists() {
+ std::fs::remove_dir_all(target)?;
+ }
+ Ok(())
+ }
+
+ fn local_changes(&self, target: &Path) -> Result<Option<String>> {
+ let process = ProcessExecutor::new();
+ let output = process.execute(&["git", "status", "--porcelain"], Some(target))?;
+ if output.stdout.trim().is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(output.stdout))
+ }
+ }
+
+ fn commit_logs(&self, from: &str, to: &str, target: &Path) -> Result<String> {
+ let process = ProcessExecutor::new();
+ let range = format!("{from}..{to}");
+ let output = process.execute(
+ &["git", "log", &range, "--oneline", "--no-decorate"],
+ Some(target),
+ )?;
+ Ok(output.stdout)
+ }
+}