diff --git a/compiler/crates/relay-compiler/src/build_project.rs b/compiler/crates/relay-compiler/src/build_project.rs index a4a57edd78189..7f8d20f5666c4 100644 --- a/compiler/crates/relay-compiler/src/build_project.rs +++ b/compiler/crates/relay-compiler/src/build_project.rs @@ -67,7 +67,7 @@ use rustc_hash::FxHashSet; use schema::SDLSchema; use schema_diff::check::IncrementalBuildSchemaChange; use schema_diff::check::SchemaChangeSafety; -pub use source_control::add_to_mercurial; +pub use source_control::source_control_for_root; pub use validate::validate; pub use validate::AdditionalValidations; diff --git a/compiler/crates/relay-compiler/src/build_project/artifact_writer.rs b/compiler/crates/relay-compiler/src/build_project/artifact_writer.rs index 8d1d8e3a2ef42..83578809f02d7 100644 --- a/compiler/crates/relay-compiler/src/build_project/artifact_writer.rs +++ b/compiler/crates/relay-compiler/src/build_project/artifact_writer.rs @@ -23,6 +23,7 @@ use serde::Serializer; use sha1::Digest; use sha1::Sha1; +use crate::build_project::source_control::SourceControl; use crate::errors::BuildProjectError; use crate::errors::Error; @@ -40,22 +41,23 @@ pub trait ArtifactWriter { fn finalize(&self) -> crate::errors::Result<()>; } -type SourceControlFn = - fn(&PathBuf, &Mutex>, &Mutex>) -> crate::errors::Result<()>; #[derive(Default)] pub struct ArtifactFileWriter { added: Mutex>, removed: Mutex>, - source_control_fn: Option, + source_control: Option>, root_dir: PathBuf, } impl ArtifactFileWriter { - pub fn new(source_control_fn: Option, root_dir: PathBuf) -> Self { + pub fn new( + source_control: Option>, + root_dir: PathBuf, + ) -> Self { Self { added: Default::default(), removed: Default::default(), - source_control_fn, + source_control, root_dir, } } @@ -112,8 +114,9 @@ impl ArtifactWriter for ArtifactFileWriter { } fn finalize(&self) -> crate::errors::Result<()> { - if let Some(source_control_fn) = self.source_control_fn { - (source_control_fn)(&self.root_dir, &self.added, &self.removed) + if let Some(source_control) = &self.source_control { + source_control.add_files(&self.root_dir, &self.added)?; + source_control.remove_files(&self.root_dir, &self.removed) } else { Ok(()) } diff --git a/compiler/crates/relay-compiler/src/build_project/source_control.rs b/compiler/crates/relay-compiler/src/build_project/source_control.rs index 1e7421d34f2ab..e7ab5339d1715 100644 --- a/compiler/crates/relay-compiler/src/build_project/source_control.rs +++ b/compiler/crates/relay-compiler/src/build_project/source_control.rs @@ -5,25 +5,41 @@ * LICENSE file in the root directory of this source tree. */ +use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; use std::sync::Mutex; +use log::debug; use log::info; -pub fn add_to_mercurial( - root_dir: &PathBuf, - added: &Mutex>, - removed: &Mutex>, -) -> crate::errors::Result<()> { - { +pub trait SourceControl { + fn add_files(&self, root_dir: &Path, added: &Mutex>) -> crate::errors::Result<()>; + + fn remove_files( + &self, + root_dir: &Path, + removed: &Mutex>, + ) -> crate::errors::Result<()>; +} + +trait SourceControlStartAndStopCommands { + fn start_tracking_command() -> Command; + + fn stop_tracking_command() -> Command; +} + +impl SourceControl for T +where + T: SourceControlStartAndStopCommands, +{ + fn add_files(&self, root_dir: &Path, added: &Mutex>) -> crate::errors::Result<()> { let mut added = added.lock().unwrap(); if !added.is_empty() { for added_files in added.chunks(100) { - if Command::new("hg") + if Self::start_tracking_command() .current_dir(root_dir) - .arg("add") .args(added_files) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -31,19 +47,24 @@ pub fn add_to_mercurial( .spawn() .is_err() { - info!("Failed to run `hg add`."); + info!("Failed to run source control 'add' operation."); } } added.clear(); } + Ok(()) } - { + + fn remove_files( + &self, + root_dir: &Path, + removed: &Mutex>, + ) -> crate::errors::Result<()> { let mut removed = removed.lock().unwrap(); if !removed.is_empty() { for removed_files in removed.chunks(100) { - if Command::new("hg") + if Self::stop_tracking_command() .current_dir(root_dir) - .arg("forget") .args(removed_files) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -51,11 +72,92 @@ pub fn add_to_mercurial( .spawn() .is_err() { - info!("Failed to run `hg forget`."); + info!("Failed to run source control 'remove' operation."); } } removed.clear(); } + Ok(()) + } +} + +/// Sapling is Meta's fork of Mercurial. +/// Inside Meta, it is available as both +/// `sl`, and `hg`. +struct Sapling; + +impl SourceControlStartAndStopCommands for Sapling { + fn start_tracking_command() -> Command { + let mut command = Command::new("sl"); + command.arg("add"); + command + } + + fn stop_tracking_command() -> Command { + let mut command = Command::new("sl"); + command.arg("forget"); + command + } +} + +struct Git; + +impl SourceControlStartAndStopCommands for Git { + fn start_tracking_command() -> Command { + let mut command = Command::new("git"); + command.arg("add"); + command } - Ok(()) + + fn stop_tracking_command() -> Command { + let mut command = Command::new("git"); + command.arg("rm").arg("--cached"); + command + } +} + +pub fn source_control_for_root(root_dir: &PathBuf) -> Option> { + let check_git = Command::new("git") + .arg("status") + .current_dir(root_dir) + .output(); + + if let Ok(check_git) = check_git { + if check_git.status.success() { + debug!("Enabling git source control integration"); + return Some(Box::new(Git)); + } + } + + // Warning: `sl` can support git repos, so it's important that we + // check the native `git` command first due to differences in + // staging behavior between the two. + let check_sapling = Command::new("sl") + .arg("root") + .current_dir(root_dir) + .output(); + + if let Ok(check_sapling) = check_sapling { + if check_sapling.status.success() { + let possible_steam_locomotive_check = Command::new("sl").arg("--version").output(); + + // The "Steam Locomotive" command also uses `sl` and doesn't have an easy way to detect + // if it is actually the `sl` command (it exits with code 0 if run as `sl root`), so we + // need to do some additional checking to make sure we can enable Sapling integration: + if let Ok(output) = possible_steam_locomotive_check { + if output.status.success() + && String::from_utf8_lossy(&output.stdout).contains("Sapling") + { + debug!("Enabling Sapling source control integration"); + return Some(Box::new(Sapling)); + } else { + debug!( + "The `sl` command is not Sapling, so Sapling source control integration is disabled" + ); + } + } + } + } + + None } diff --git a/compiler/crates/relay-compiler/src/config.rs b/compiler/crates/relay-compiler/src/config.rs index d202f9e32a79c..c4009a5c5c75d 100644 --- a/compiler/crates/relay-compiler/src/config.rs +++ b/compiler/crates/relay-compiler/src/config.rs @@ -65,6 +65,7 @@ use crate::errors::ConfigValidationError; use crate::errors::Error; use crate::errors::Result; use crate::saved_state::SavedStateLoader; +use crate::source_control_for_root; use crate::status_reporter::ConsoleStatusReporter; use crate::status_reporter::StatusReporter; @@ -418,7 +419,10 @@ impl Config { let config = Self { name: config_file.name, - artifact_writer: Box::new(ArtifactFileWriter::new(None, root_dir.clone())), + artifact_writer: Box::new(ArtifactFileWriter::new( + source_control_for_root(&root_dir), + root_dir.clone(), + )), status_reporter: Box::new(ConsoleStatusReporter::new( root_dir.clone(), is_multi_project, diff --git a/compiler/crates/relay-compiler/src/lib.rs b/compiler/crates/relay-compiler/src/lib.rs index 795f549dcaaf8..0764ed576809b 100644 --- a/compiler/crates/relay-compiler/src/lib.rs +++ b/compiler/crates/relay-compiler/src/lib.rs @@ -26,7 +26,6 @@ pub mod status_reporter; mod utils; pub use artifact_map::ArtifactSourceKey; -pub use build_project::add_to_mercurial; pub use build_project::artifact_writer::ArtifactDifferenceShardedWriter; pub use build_project::artifact_writer::ArtifactDifferenceWriter; pub use build_project::artifact_writer::ArtifactFileWriter; @@ -40,6 +39,7 @@ pub use build_project::find_duplicates; pub use build_project::generate_artifacts; pub use build_project::generate_extra_artifacts::GenerateExtraArtifactsFn; pub use build_project::get_artifacts_file_hash_map::GetArtifactsFileHashMapFn; +pub use build_project::source_control_for_root; pub use build_project::transform_program; pub use build_project::validate; pub use build_project::validate_program;