use super::super::{Credentials, RevisionControlAPI, Status};
use crate::utils::command::log_stdout_and_stderr;
use crate::utils::file::with_dir;
use crate::{Outcome, Result};
use chrono::offset::Utc;
use regex::Regex;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
#[derive(Debug)]
pub struct Designsync {
pub local: PathBuf,
pub remote: String,
}
impl Designsync {
pub fn new(local: &Path, remote: &str, _credentials: Option<Credentials>) -> Self {
Designsync {
local: local.to_path_buf(),
remote: remote.to_string(),
}
}
}
impl RevisionControlAPI for Designsync {
fn system(&self) -> &str {
"DesignSync"
}
fn populate(&self, version: &str) -> Result<()> {
log_info!("Populating {}", &self.local.display());
fs::create_dir_all(&self.local)?;
self.set_vault()?;
self.pop(true, Some(Path::new(".")), Some(version))
}
fn checkout(&self, force: bool, path: Option<&Path>, version: &str) -> Result<bool> {
self.pop(force, path, Some(version))?;
Ok(false)
}
fn revert(&self, _path: Option<&Path>) -> Result<()> {
let timestamp = format!("ts{}", Utc::now().timestamp_millis());
self._tag(×tamp, false, None, true)?;
self.with_selector(×tamp, || {
self.pop(true, None, None)?;
Ok(())
})?;
self.delete_tag(×tamp)?;
Ok(())
}
fn status(&self, _path: Option<&Path>) -> Result<Status> {
with_dir(&self.local, || {
let mut status = Status::default();
let mut process = Command::new("dssc")
.args(&["compare", "-rec", "-path", "-report", "silent", "."])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
lazy_static! {
static ref NEW_FILE_REGEX: Regex =
Regex::new(r"\s*Unmanaged\s+First only\s+(\S+)").unwrap();
static ref DELETED_FILE_REGEX: Regex =
Regex::new(r"\s*\d+\.*\d*\s*\(Reference\)\s+\d+\.*\d*\s*Different \w+\s*(\S+)")
.unwrap();
static ref MODIFIED_FILE_REGEX: Regex = Regex::new(
r"\s*\d+\.*\d*\s*\(Locally Modified\)\s+\d+\.*\d*\s*Different \w+\s*(\S+)"
)
.unwrap();
}
log_stdout_and_stderr(
&mut process,
Some(&mut |line: &str| {
log_debug!("{}", line);
if let Some(captures) = NEW_FILE_REGEX.captures(&line) {
status.added.push(self.local.join(&captures[1]));
} else if let Some(captures) = DELETED_FILE_REGEX.captures(&line) {
status.removed.push(self.local.join(&captures[1]));
} else if let Some(captures) = MODIFIED_FILE_REGEX.captures(&line) {
status.changed.push(self.local.join(&captures[1]));
}
}),
None,
);
if process.wait()?.success() {
Ok(status)
} else {
bail!(
"Something went wrong reporting the dssc status for '{}', see log for details",
self.local.display()
)
}
})
}
fn tag(&self, tagname: &str, force: bool, _message: Option<&str>) -> Result<()> {
self._tag(tagname, force, _message, true)
}
fn is_initialized(&self) -> Result<bool> {
todo!("Initializing with DesignSync is not supported yet!");
}
fn init(&self) -> Result<Outcome> {
todo!("Initializing with DesignSync is not supported yet!");
}
fn checkin(
&self,
_files_or_dirs: Option<Vec<&Path>>,
_msg: &str,
_dry_run: bool,
) -> Result<Outcome> {
todo!("Checkins with DesignSync is not supported yet!");
}
}
impl Designsync {
fn set_vault(&self) -> Result<()> {
with_dir(&self.local, || {
let mut process = Command::new("dssc")
.args(&["setvault", &self.remote, "."])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
log_stdout_and_stderr(&mut process, None, None);
if process.wait()?.success() {
Ok(())
} else {
bail!(
"Something went wrong setting the vault in '{}', see log for details",
self.local.display()
)
}
})
}
fn get_selector(&self) -> Result<String> {
let mut process = Command::new("dssc")
.args(&["url", "selector", &format!("{}", self.local.display())])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut selector = "".to_string();
log_stdout_and_stderr(
&mut process,
Some(&mut |line: &str| {
log_debug!("{}", line);
selector = line.to_string();
}),
None,
);
if process.wait()?.success() {
log_debug!("get_selector returned '{}'", &selector);
Ok(selector)
} else {
bail!(
"Something went wrong setting the selector in '{}', see log for details",
self.local.display()
)
}
}
fn with_selector<T, F>(&self, selector: &str, mut f: F) -> Result<T>
where
F: FnMut() -> Result<T>,
{
let existing = self.get_selector()?;
let r = self.set_selector(selector);
if r.is_err() {
let _ = self.set_selector(&existing);
bail!("{:?}", r);
}
let result = f();
self.set_selector(&existing)?;
result
}
fn set_selector(&self, selector: &str) -> Result<()> {
log_debug!("Setting workspace selector to '{}'", selector);
let mut process = Command::new("dssc")
.args(&[
"setselector",
selector,
&format!("{}", self.local.display()),
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
log_stdout_and_stderr(&mut process, None, None);
if process.wait()?.success() {
Ok(())
} else {
bail!(
"Something went wrong setting the selector to '{}' in '{}', see log for details",
selector,
self.local.display()
)
}
}
fn _tag(
&self,
tagname: &str,
force: bool,
_message: Option<&str>,
modified: bool,
) -> Result<()> {
with_dir(&self.local, || {
let replace = match force {
false => "",
true => "-replace",
};
let modified = match modified {
false => "",
true => "-modified",
};
let mut process = Command::new("dssc")
.args(&["tag", tagname, replace, modified, "-rec", "."])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut err_msg: Option<String> = None;
lazy_static! {
static ref TAG_EXISTS_REGEX: Regex =
Regex::new(r"This tag is already in use").unwrap();
}
log_stdout_and_stderr(
&mut process,
Some(&mut |line: &str| {
log_debug!("{}", line);
if TAG_EXISTS_REGEX.is_match(&line) {
if err_msg.is_none() {
err_msg = Some("tag already exists".to_string());
}
}
}),
None,
);
let p = process.wait()?;
if let Some(msg) = err_msg {
bail!("{}", msg)
} else if p.success() {
Ok(())
} else {
bail!(
"Something went wrong tagging '{}', see log for details",
self.local.display()
)
}
})
}
fn delete_tag(&self, tagname: &str) -> Result<()> {
with_dir(&self.local, || {
let mut process = Command::new("dssc")
.args(&["tag", "-delete", "-rec", tagname, "."])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
log_stdout_and_stderr(&mut process, None, None);
let p = process.wait()?;
if p.success() {
Ok(())
} else {
bail!(
"Something went wrong deleting tag '{}', see log for details",
tagname
)
}
})
}
fn pop(&self, force: bool, path: Option<&Path>, version: Option<&str>) -> Result<()> {
let path = path.unwrap_or(Path::new("."));
if let Some(version) = version {
self.set_selector(version)?;
}
with_dir(&self.local, || {
let mut args = vec!["pop", "-rec", "-uni"];
if force {
args.push("-force");
} else {
args.push("-merge");
}
args.push(path.to_str().unwrap());
log_debug!("Running DesignSync command: dssc {}", args.join(" "));
let mut process = Command::new("dssc")
.args(&args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
log_stdout_and_stderr(&mut process, None, None);
if process.wait()?.success() {
Ok(())
} else {
bail!(
"Something went wrong populating '{}', see log for details",
self.remote
)
}
})
}
}