aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-core/src/vcs/driver/hg.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-10 00:32:08 +0900
committernsfisis <nsfisis@gmail.com>2026-05-10 00:32:08 +0900
commit8cc1ba8a02c0318b65658f1634de378c780392b9 (patch)
treefdd5cb61e488018891a486b25991b87c84220bb8 /crates/mozart-core/src/vcs/driver/hg.rs
parent72b2e877c01e67ba7edd37e34ac2eadb7a1c62c4 (diff)
downloadphp-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.tar.gz
php-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.tar.zst
php-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.zip
refactor(workspace): consolidate crates into mozart-core
Merged mozart-archiver, mozart-autoload, mozart-registry, mozart-sat-resolver, and mozart-vcs into mozart-core to align the source layout with Composer's structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-core/src/vcs/driver/hg.rs')
-rw-r--r--crates/mozart-core/src/vcs/driver/hg.rs202
1 files changed, 202 insertions, 0 deletions
diff --git a/crates/mozart-core/src/vcs/driver/hg.rs b/crates/mozart-core/src/vcs/driver/hg.rs
new file mode 100644
index 0000000..e2c3fcd
--- /dev/null
+++ b/crates/mozart-core/src/vcs/driver/hg.rs
@@ -0,0 +1,202 @@
+use super::super::process::ProcessExecutor;
+use super::super::util::hg::HgUtil;
+use super::{DistReference, DriverConfig, SourceReference, VcsDriver};
+use anyhow::Result;
+use indexmap::IndexMap;
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+
+/// Mercurial VCS driver.
+///
+/// Corresponds to Composer's `Repository\Vcs\HgDriver`.
+pub struct HgDriver {
+ 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>>,
+ hg_util: HgUtil,
+ config: DriverConfig,
+}
+
+impl HgDriver {
+ pub fn new(url: &str, config: DriverConfig) -> Self {
+ let process = ProcessExecutor::new();
+ Self {
+ url: url.to_string(),
+ repo_dir: None,
+ root_identifier: None,
+ tags: None,
+ branches: None,
+ info_cache: IndexMap::new(),
+ hg_util: HgUtil::new(process),
+ config,
+ }
+ }
+
+ pub fn supports(url: &str) -> bool {
+ url.starts_with("hg://") || url.contains("hg.") || url.ends_with(".hg")
+ }
+
+ fn get_repo_dir(&self) -> Result<&PathBuf> {
+ self.repo_dir
+ .as_ref()
+ .ok_or_else(|| anyhow::anyhow!("HgDriver not initialized"))
+ }
+}
+
+impl VcsDriver for HgDriver {
+ async fn initialize(&mut self) -> Result<()> {
+ let cache_dir = &self.config.cache_vcs_dir;
+ std::fs::create_dir_all(cache_dir)?;
+ let repo_dir = cache_dir.join(super::super::util::git::GitUtil::sanitize_url(&self.url));
+
+ if repo_dir.join(".hg").is_dir() {
+ // Update existing clone
+ self.hg_util.execute(&["pull"], Some(&repo_dir))?;
+ } else {
+ // Clone without checkout
+ let dir_str = repo_dir.to_string_lossy().to_string();
+ self.hg_util
+ .execute(&["clone", "--noupdate", &self.url, &dir_str], None)?;
+ }
+
+ self.repo_dir = Some(repo_dir.clone());
+
+ // Get default branch
+ let output = self.hg_util.execute(
+ &["log", "-r", "default", "--template", "{node|short}"],
+ Some(&repo_dir),
+ );
+ self.root_identifier = match output {
+ Ok(o) if !o.stdout.trim().is_empty() => Some("default".to_string()),
+ _ => Some("tip".to_string()),
+ };
+
+ Ok(())
+ }
+
+ fn root_identifier(&self) -> &str {
+ self.root_identifier.as_deref().unwrap_or("default")
+ }
+
+ async fn branches(&mut self) -> Result<&BTreeMap<String, String>> {
+ if self.branches.is_none() {
+ let repo_dir = self.get_repo_dir()?.clone();
+ let mut branches = BTreeMap::new();
+
+ // Named branches
+ let output = self.hg_util.execute(&["branches", "-q"], Some(&repo_dir))?;
+ for name in ProcessExecutor::split_lines(&output.stdout) {
+ let name = name.trim();
+ let rev_output = self.hg_util.execute(
+ &["log", "-r", name, "--template", "{node}"],
+ Some(&repo_dir),
+ )?;
+ branches.insert(name.to_string(), rev_output.stdout.trim().to_string());
+ }
+
+ // Bookmarks
+ let output = self
+ .hg_util
+ .execute_unchecked(&["bookmarks", "-q"], Some(&repo_dir))?;
+ if output.status == 0 {
+ for name in ProcessExecutor::split_lines(&output.stdout) {
+ let name = name.trim();
+ if !branches.contains_key(name) {
+ let rev_output = self.hg_util.execute(
+ &["log", "-r", name, "--template", "{node}"],
+ Some(&repo_dir),
+ )?;
+ branches.insert(name.to_string(), rev_output.stdout.trim().to_string());
+ }
+ }
+ }
+
+ self.branches = Some(branches);
+ }
+ 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()?.clone();
+ let output = self.hg_util.execute(&["tags", "-q"], Some(&repo_dir))?;
+ let mut tags = BTreeMap::new();
+ for name in ProcessExecutor::split_lines(&output.stdout) {
+ let name = name.trim();
+ if name == "tip" {
+ continue; // Skip the "tip" pseudo-tag
+ }
+ let rev_output = self.hg_util.execute(
+ &["log", "-r", name, "--template", "{node}"],
+ Some(&repo_dir),
+ )?;
+ tags.insert(name.to_string(), rev_output.stdout.trim().to_string());
+ }
+ self.tags = Some(tags);
+ }
+ 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 = content.and_then(|c| serde_json::from_str(&c).ok());
+ 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 output = self
+ .hg_util
+ .execute_unchecked(&["cat", "-r", identifier, "--", file], 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 output = self.hg_util.execute(
+ &["log", "-r", identifier, "--template", "{date|isodatesec}"],
+ Some(repo_dir),
+ )?;
+ let date = output.stdout.trim().to_string();
+ if date.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(date))
+ }
+ }
+
+ async fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> {
+ Ok(None)
+ }
+
+ fn source(&self, identifier: &str) -> SourceReference {
+ SourceReference {
+ source_type: "hg".to_string(),
+ url: self.url.clone(),
+ reference: identifier.to_string(),
+ }
+ }
+
+ fn url(&self) -> &str {
+ &self.url
+ }
+
+ async fn cleanup(&mut self) -> Result<()> {
+ Ok(())
+ }
+}