aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-core/src/vcs/driver/git.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-10 20:01:21 +0900
committernsfisis <nsfisis@gmail.com>2026-05-10 20:01:21 +0900
commit24bb31c109332ae982b7091ffcd5183442ce6f6f (patch)
treece590a34053bd48506310c0a0c26395605973d45 /crates/mozart-core/src/vcs/driver/git.rs
parentdd13f29a3535bf15bb2494da4c67b5e2c61bbda5 (diff)
downloadphp-mozart-24bb31c109332ae982b7091ffcd5183442ce6f6f.tar.gz
php-mozart-24bb31c109332ae982b7091ffcd5183442ce6f6f.tar.zst
php-mozart-24bb31c109332ae982b7091ffcd5183442ce6f6f.zip
refactor(vcs): move VCS drivers under repository module
Mirror Composer's structure where VCS drivers live under Repository/Vcs/. Rename VcsDriver trait to VcsDriverInterface and BitbucketDriver to GitBitbucketDriver to match Composer naming, and add stubs for FossilDriver, PerforceDriver, and the VcsDriver base.
Diffstat (limited to 'crates/mozart-core/src/vcs/driver/git.rs')
-rw-r--r--crates/mozart-core/src/vcs/driver/git.rs275
1 files changed, 0 insertions, 275 deletions
diff --git a/crates/mozart-core/src/vcs/driver/git.rs b/crates/mozart-core/src/vcs/driver/git.rs
deleted file mode 100644
index 7d6643f..0000000
--- a/crates/mozart-core/src/vcs/driver/git.rs
+++ /dev/null
@@ -1,275 +0,0 @@
-use super::super::process::ProcessExecutor;
-use super::super::util::git::GitUtil;
-use super::{DistReference, DriverConfig, SourceReference, VcsDriver};
-use anyhow::Result;
-use indexmap::IndexMap;
-use std::collections::BTreeMap;
-use std::path::{Path, PathBuf};
-
-/// Git VCS driver.
-///
-/// Corresponds to Composer's `Repository\Vcs\GitDriver`.
-pub struct GitDriver {
- url: String,
- repo_dir: Option<PathBuf>,
- root_identifier: Option<String>,
- tags: Option<BTreeMap<String, String>>,
- branches: Option<BTreeMap<String, String>>,
- info_cache: IndexMap<String, Option<serde_json::Value>>,
- git_util: GitUtil,
- is_local: bool,
-}
-
-impl GitDriver {
- pub fn new(url: &str, config: DriverConfig) -> Self {
- let is_local = Self::is_local_path(url);
- let process = ProcessExecutor::new();
- let git_util = GitUtil::new(process, config.cache_vcs_dir.clone());
- Self {
- url: url.to_string(),
- repo_dir: if is_local {
- Some(PathBuf::from(url))
- } else {
- None
- },
- root_identifier: None,
- tags: None,
- branches: None,
- info_cache: IndexMap::new(),
- git_util,
- is_local,
- }
- }
-
- /// Check if a URL is supported by the Git driver.
- pub fn supports(url: &str) -> bool {
- if Self::is_local_path(url) {
- return Path::new(url).join(".git").is_dir() || url.ends_with(".git");
- }
- url.starts_with("git://")
- || url.starts_with("git@")
- || url.ends_with(".git")
- || url.contains("git.")
- }
-
- fn is_local_path(url: &str) -> bool {
- !url.contains("://") && !url.starts_with("git@") && Path::new(url).exists()
- }
-
- fn get_repo_dir(&self) -> Result<&Path> {
- self.repo_dir
- .as_deref()
- .ok_or_else(|| anyhow::anyhow!("GitDriver not initialized"))
- }
-
- fn parse_branches(output: &str) -> BTreeMap<String, String> {
- let mut branches = BTreeMap::new();
- for line in output.lines() {
- let line = line.trim();
- if line.is_empty() || line.contains("HEAD detached") || line.contains("->") {
- continue;
- }
- // Remove leading "* " for current branch
- let line = line.strip_prefix("* ").unwrap_or(line);
- // Format: "branch_name commit_hash ..."
- let parts: Vec<&str> = line.split_whitespace().collect();
- if parts.len() >= 2 {
- branches.insert(parts[0].to_string(), parts[1].to_string());
- }
- }
- branches
- }
-
- fn parse_tags(output: &str) -> BTreeMap<String, String> {
- let mut tags = BTreeMap::new();
- // First pass: collect dereferenced tags (^{})
- let mut dereferenced = IndexMap::new();
- for line in output.lines() {
- let line = line.trim();
- if line.is_empty() {
- continue;
- }
- // Format: "commit_hash refs/tags/tag_name" or "commit_hash refs/tags/tag_name^{}"
- let parts: Vec<&str> = line.split_whitespace().collect();
- if parts.len() >= 2 {
- let hash = parts[0];
- let refname = parts[1];
- if let Some(tag_name) = refname.strip_prefix("refs/tags/")
- && let Some(tag_name) = tag_name.strip_suffix("^{}")
- {
- // Dereferenced tag - this is the actual commit
- dereferenced.insert(tag_name.to_string(), hash.to_string());
- }
- }
- }
- // Second pass: collect all tags, preferring dereferenced values
- for line in output.lines() {
- let line = line.trim();
- if line.is_empty() {
- continue;
- }
- let parts: Vec<&str> = line.split_whitespace().collect();
- if parts.len() >= 2 {
- let hash = parts[0];
- let refname = parts[1];
- if let Some(tag_name) = refname.strip_prefix("refs/tags/") {
- if tag_name.ends_with("^{}") {
- continue; // Skip dereferenced entries themselves
- }
- let resolved = dereferenced
- .get(tag_name)
- .cloned()
- .unwrap_or_else(|| hash.to_string());
- tags.insert(tag_name.to_string(), resolved);
- }
- }
- }
- tags
- }
-}
-
-impl VcsDriver for GitDriver {
- async fn initialize(&mut self) -> Result<()> {
- if self.is_local {
- // Local repo: use directly (or its .git subdir)
- let path = Path::new(&self.url);
- if path.join(".git").is_dir() {
- self.repo_dir = Some(path.join(".git"));
- } else {
- self.repo_dir = Some(path.to_path_buf());
- }
- } else {
- // Remote repo: sync mirror
- let mirror_dir = self.git_util.sync_mirror(&self.url)?;
- self.repo_dir = Some(mirror_dir);
- }
-
- // Determine root identifier (default branch)
- let repo_dir = self.repo_dir.clone().unwrap();
- if let Ok(Some(branch)) = self.git_util.get_default_branch(&repo_dir) {
- self.root_identifier = Some(branch);
- } else {
- // Fallback: try common branch names
- let process = ProcessExecutor::new();
- for name in &["main", "master"] {
- let output =
- process.execute(&["git", "rev-parse", "--verify", name], Some(&repo_dir))?;
- if output.status == 0 {
- self.root_identifier = Some(name.to_string());
- break;
- }
- }
- }
-
- if self.root_identifier.is_none() {
- self.root_identifier = Some("master".to_string());
- }
-
- Ok(())
- }
-
- fn root_identifier(&self) -> &str {
- self.root_identifier.as_deref().unwrap_or("master")
- }
-
- async fn branches(&mut self) -> Result<&BTreeMap<String, String>> {
- if self.branches.is_none() {
- let repo_dir = self.get_repo_dir()?.to_path_buf();
- let process = ProcessExecutor::new();
- let output = process.execute_checked(
- &["git", "branch", "--no-color", "--no-abbrev", "-v"],
- Some(&repo_dir),
- )?;
- self.branches = Some(Self::parse_branches(&output.stdout));
- }
- Ok(self.branches.as_ref().unwrap())
- }
-
- async fn tags(&mut self) -> Result<&BTreeMap<String, String>> {
- if self.tags.is_none() {
- let repo_dir = self.get_repo_dir()?.to_path_buf();
- let process = ProcessExecutor::new();
- let output = process.execute(
- &["git", "show-ref", "--tags", "--dereference"],
- Some(&repo_dir),
- )?;
- self.tags = Some(if output.status == 0 {
- Self::parse_tags(&output.stdout)
- } else {
- BTreeMap::new()
- });
- }
- Ok(self.tags.as_ref().unwrap())
- }
-
- async fn composer_information(
- &mut self,
- identifier: &str,
- ) -> Result<Option<serde_json::Value>> {
- if let Some(cached) = self.info_cache.get(identifier) {
- return Ok(cached.clone());
- }
-
- let content = self.file_content("composer.json", identifier).await?;
- let value = match content {
- Some(c) => serde_json::from_str(&c).ok(),
- None => None,
- };
-
- self.info_cache
- .insert(identifier.to_string(), value.clone());
- Ok(value)
- }
-
- async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> {
- let repo_dir = self.get_repo_dir()?;
- let process = ProcessExecutor::new();
- let resource = format!("{identifier}:{file}");
- let output = process.execute(&["git", "show", &resource], Some(repo_dir))?;
- if output.status == 0 {
- Ok(Some(output.stdout))
- } else {
- Ok(None)
- }
- }
-
- async fn change_date(&self, identifier: &str) -> Result<Option<String>> {
- let repo_dir = self.get_repo_dir()?;
- let process = ProcessExecutor::new();
- let output = process.execute(
- &["git", "log", "-1", "--format=%aI", identifier],
- Some(repo_dir),
- )?;
- if output.status == 0 {
- let date = output.stdout.trim().to_string();
- if date.is_empty() {
- Ok(None)
- } else {
- Ok(Some(date))
- }
- } else {
- Ok(None)
- }
- }
-
- async fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> {
- // Plain git repos don't provide dist archives
- Ok(None)
- }
-
- fn source(&self, identifier: &str) -> SourceReference {
- SourceReference {
- source_type: "git".to_string(),
- url: self.url.clone(),
- reference: identifier.to_string(),
- }
- }
-
- fn url(&self) -> &str {
- &self.url
- }
-
- async fn cleanup(&mut self) -> Result<()> {
- Ok(())
- }
-}