aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-16 00:42:39 +0900
committernsfisis <nsfisis@gmail.com>2026-05-16 10:00:40 +0900
commit7c58ca16cb5bc4e14ff5c8c192c67e8a47afeaa1 (patch)
treece32dd3cd5f7f8e290b7774cb2db86f362e2e1ca /crates/shirabe
parentb0b97e85720d62eb21fc63f986a5de40c796c869 (diff)
downloadphp-shirabe-7c58ca16cb5bc4e14ff5c8c192c67e8a47afeaa1.tar.gz
php-shirabe-7c58ca16cb5bc4e14ff5c8c192c67e8a47afeaa1.tar.zst
php-shirabe-7c58ca16cb5bc4e14ff5c8c192c67e8a47afeaa1.zip
feat(port): port HgDriver.php
Diffstat (limited to 'crates/shirabe')
-rw-r--r--crates/shirabe/src/repository/vcs/hg_driver.rs260
1 files changed, 260 insertions, 0 deletions
diff --git a/crates/shirabe/src/repository/vcs/hg_driver.rs b/crates/shirabe/src/repository/vcs/hg_driver.rs
index 3afad78..7dc85e2 100644
--- a/crates/shirabe/src/repository/vcs/hg_driver.rs
+++ b/crates/shirabe/src/repository/vcs/hg_driver.rs
@@ -1 +1,261 @@
//! ref: composer/src/Composer/Repository/Vcs/HgDriver.php
+
+use chrono::{DateTime, Utc};
+use indexmap::IndexMap;
+use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_php_shim::{dirname, is_dir, is_writable, RuntimeException};
+use crate::cache::Cache;
+use crate::config::Config;
+use crate::io::io_interface::IOInterface;
+use crate::repository::vcs::vcs_driver::VcsDriver;
+use crate::util::filesystem::Filesystem;
+use crate::util::hg::Hg as HgUtils;
+use crate::util::url::Url;
+
+#[derive(Debug)]
+pub struct HgDriver {
+ pub(crate) inner: VcsDriver,
+ pub(crate) tags: Option<IndexMap<String, String>>,
+ pub(crate) branches: Option<IndexMap<String, String>>,
+ pub(crate) root_identifier: Option<String>,
+ pub(crate) repo_dir: String,
+}
+
+impl HgDriver {
+ pub fn initialize(&mut self) -> anyhow::Result<()> {
+ if Filesystem::is_local_path(&self.inner.url) {
+ self.repo_dir = self.inner.url.clone();
+ } else {
+ let cache_vcs_dir = self.inner.config.get("cache-vcs-dir").as_string().unwrap_or("").to_string();
+ if !Cache::is_usable(&cache_vcs_dir) {
+ return Err(RuntimeException {
+ message: "HgDriver requires a usable cache directory, and it looks like you set it to be disabled".to_string(),
+ code: 0,
+ }.into());
+ }
+
+ let sanitized = Preg::replace(r"{[^a-z0-9]}i", "-", Url::sanitize(self.inner.url.clone()));
+ self.repo_dir = format!("{}/{}/", cache_vcs_dir, sanitized);
+
+ let fs = Filesystem::new();
+ fs.ensure_directory_exists(&cache_vcs_dir)?;
+
+ if !is_writable(&dirname(&self.repo_dir)) {
+ return Err(RuntimeException {
+ message: format!(
+ "Can not clone {} to access package information. The \"{}\" directory is not writable by the current user.",
+ self.inner.url, cache_vcs_dir
+ ),
+ code: 0,
+ }.into());
+ }
+
+ self.inner.config.prohibit_url_by_config(&self.inner.url, &*self.inner.io)?;
+
+ let hg_utils = HgUtils::new(&*self.inner.io, &self.inner.config, &self.inner.process);
+
+ if is_dir(&self.repo_dir) && self.inner.process.execute(&["hg", "summary"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.repo_dir.clone())) == 0 {
+ if self.inner.process.execute(&["hg", "pull"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.repo_dir.clone())) != 0 {
+ self.inner.io.write_error(
+ format!("<error>Failed to update {}, package information from this repository may be outdated ({})</error>", self.inner.url, self.inner.process.get_error_output()).into(),
+ true,
+ crate::io::io_interface::IOInterface::NORMAL,
+ );
+ }
+ } else {
+ let fs2 = Filesystem::new();
+ fs2.remove_directory(&self.repo_dir)?;
+
+ let repo_dir = self.repo_dir.clone();
+ let command = move |url: String| -> Vec<String> {
+ vec!["hg".to_string(), "clone".to_string(), "--noupdate".to_string(), "--".to_string(), url, repo_dir.clone()]
+ };
+
+ hg_utils.run_command(command, self.inner.url.clone(), None)?;
+ }
+ }
+
+ self.get_tags()?;
+ self.get_branches()?;
+
+ Ok(())
+ }
+
+ pub fn get_root_identifier(&mut self) -> anyhow::Result<String> {
+ if self.root_identifier.is_none() {
+ let mut output = String::new();
+ self.inner.process.execute(
+ &["hg", "tip", "--template", "{node}"].map(|s| s.to_string()).to_vec(),
+ &mut output,
+ Some(self.repo_dir.clone()),
+ );
+ let lines = self.inner.process.split_lines(&output);
+ self.root_identifier = lines.into_iter().next();
+ }
+
+ Ok(self.root_identifier.clone().unwrap_or_default())
+ }
+
+ pub fn get_url(&self) -> String {
+ self.inner.url.clone()
+ }
+
+ pub fn get_source(&self, identifier: &str) -> IndexMap<String, String> {
+ let mut map = IndexMap::new();
+ map.insert("type".to_string(), "hg".to_string());
+ map.insert("url".to_string(), self.get_url());
+ map.insert("reference".to_string(), identifier.to_string());
+ map
+ }
+
+ pub fn get_dist(&self, _identifier: &str) -> Option<IndexMap<String, String>> {
+ None
+ }
+
+ pub fn get_file_content(&self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> {
+ if identifier.starts_with('-') {
+ return Err(RuntimeException {
+ message: format!("Invalid hg identifier detected. Identifier must not start with a -, given: {}", identifier),
+ code: 0,
+ }.into());
+ }
+
+ let resource = vec![
+ "hg".to_string(), "cat".to_string(), "-r".to_string(), identifier.to_string(),
+ "--".to_string(), file.to_string(),
+ ];
+ let mut content = String::new();
+ self.inner.process.execute(&resource, &mut content, Some(self.repo_dir.clone()));
+
+ if content.trim().is_empty() {
+ return Ok(None);
+ }
+
+ Ok(Some(content))
+ }
+
+ pub fn get_change_date(&self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> {
+ if identifier.starts_with('-') {
+ return Err(RuntimeException {
+ message: format!("Invalid hg identifier detected. Identifier must not start with a -, given: {}", identifier),
+ code: 0,
+ }.into());
+ }
+
+ let mut output = String::new();
+ self.inner.process.execute(
+ &["hg", "log", "--template", "{date|rfc3339date}", "-r", identifier]
+ .map(|s| s.to_string())
+ .to_vec(),
+ &mut output,
+ Some(self.repo_dir.clone()),
+ );
+
+ let date = DateTime::parse_from_rfc3339(output.trim())
+ .map(|d| d.with_timezone(&Utc))?;
+ Ok(Some(date))
+ }
+
+ pub fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> {
+ if self.tags.is_none() {
+ let mut tags: IndexMap<String, String> = IndexMap::new();
+ let mut output = String::new();
+ self.inner.process.execute(
+ &["hg", "tags"].map(|s| s.to_string()).to_vec(),
+ &mut output,
+ Some(self.repo_dir.clone()),
+ );
+ for tag in self.inner.process.split_lines(&output) {
+ if !tag.is_empty() {
+ if let Some(m) = Preg::match_(r"^([^\s]+)\s+\d+:(.*)$", &tag) {
+ tags.insert(
+ m.get("1").cloned().unwrap_or_default(),
+ m.get("2").cloned().unwrap_or_default(),
+ );
+ }
+ }
+ }
+ tags.shift_remove("tip");
+
+ self.tags = Some(tags);
+ }
+
+ Ok(self.tags.clone().unwrap_or_default())
+ }
+
+ pub fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> {
+ if self.branches.is_none() {
+ let mut branches: IndexMap<String, String> = IndexMap::new();
+ let mut bookmarks: IndexMap<String, String> = IndexMap::new();
+
+ let mut output = String::new();
+ self.inner.process.execute(
+ &["hg", "branches"].map(|s| s.to_string()).to_vec(),
+ &mut output,
+ Some(self.repo_dir.clone()),
+ );
+ for branch in self.inner.process.split_lines(&output) {
+ if !branch.is_empty() {
+ if let Some(m) = Preg::match_(r"^([^\s]+)\s+\d+:([a-f0-9]+)", &branch) {
+ let name = m.get("1").cloned().unwrap_or_default();
+ if !name.starts_with('-') {
+ branches.insert(name, m.get("2").cloned().unwrap_or_default());
+ }
+ }
+ }
+ }
+
+ output.clear();
+ self.inner.process.execute(
+ &["hg", "bookmarks"].map(|s| s.to_string()).to_vec(),
+ &mut output,
+ Some(self.repo_dir.clone()),
+ );
+ for branch in self.inner.process.split_lines(&output) {
+ if !branch.is_empty() {
+ if let Some(m) = Preg::match_(r"^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$", &branch) {
+ let name = m.get("1").cloned().unwrap_or_default();
+ if !name.starts_with('-') {
+ bookmarks.insert(name, m.get("2").cloned().unwrap_or_default());
+ }
+ }
+ }
+ }
+
+ // Branches will have preference over bookmarks
+ bookmarks.extend(branches);
+ self.branches = Some(bookmarks);
+ }
+
+ Ok(self.branches.clone().unwrap_or_default())
+ }
+
+ pub fn supports(io: &dyn IOInterface, config: &Config, url: &str, deep: bool) -> bool {
+ if Preg::is_match(r"#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i", url).unwrap_or(false) {
+ return true;
+ }
+
+ if Filesystem::is_local_path(url) {
+ let url = Filesystem::get_platform_path(url);
+ if !is_dir(&url) {
+ return false;
+ }
+
+ let process = crate::util::process_executor::ProcessExecutor::new(io);
+ let mut output = String::new();
+ if process.execute(&["hg", "summary"].map(|s| s.to_string()).to_vec(), &mut output, Some(url)) == 0 {
+ return true;
+ }
+ }
+
+ if !deep {
+ return false;
+ }
+
+ let process = crate::util::process_executor::ProcessExecutor::new(io);
+ let mut ignored = String::new();
+ let exit = process.execute(&["hg", "identify", "--", url].map(|s| s.to_string()).to_vec(), &mut ignored, None);
+
+ exit == 0
+ }
+}