diff --git a/Cargo.lock b/Cargo.lock index d8556d9..711c164 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1328,6 +1328,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "path-slash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cacbb3c4ff353b534a67fb8d7524d00229da4cb1dc8c79f4db96e375ab5b619" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2255,6 +2261,7 @@ dependencies = [ "liquid", "log", "octocrab", + "path-slash", "regex", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index dcab0bc..6b0502b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ hyper = "0.12.27" liquid = "0.21" log = "0.4" octocrab = "0.6" +path-slash = "0.1" regex = "1.3" serde_json = "1.0" serde_yaml = "0.8" diff --git a/src/config.rs b/src/config.rs index a04b412..a6530a0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,6 +25,7 @@ use std::fmt; use std::iter::once; use std::path::{Path, PathBuf}; use std::str::FromStr; +use path_slash::PathBufExt as _; pub const CONFIG_FILENAME: &str = ".versio.yaml"; @@ -358,7 +359,7 @@ impl Project { pub fn changelog(&self) -> Option> { self.changelog.as_ref().map(|changelog| { if let Some(root) = self.root() { - Cow::Owned(PathBuf::from(root).join(changelog).to_string_lossy().to_string()) + Cow::Owned(PathBuf::from_slash(root).join(PathBuf::from_slash(changelog)).to_slash_lossy()) } else { Cow::Borrowed(changelog.as_str()) } @@ -374,7 +375,7 @@ impl Project { } if let Some(log_path) = self.changelog().as_ref() { - let log_path = Path::new(log_path.as_ref()).to_path_buf(); + let log_path = PathBuf::from_slash(log_path.as_ref()); let old_content = extract_old_content(&log_path)?; write.write_file(log_path.clone(), construct_changelog_html(cl, new_vers, old_content)?, self.id())?; Ok(Some(log_path)) @@ -447,10 +448,10 @@ impl Project { Ok(()) } - /// Ensure that we don't have excludes without includes. + /// Ensure that we don't have a version tag without a tag_prefix. fn check_prefix(&self) -> Result<()> { if self.version.is_tag() && self.tag_prefix.is_none() { - bail!("Proj {} has version: tag without tag_prefix, self.id"); + bail!("Proj {} has version: tag without tag_prefix.", self.id); } Ok(()) } @@ -494,7 +495,7 @@ impl Project { if root == "." { pat.to_string() } else { - PathBuf::from(root).join(pat).to_string_lossy().to_string() + PathBuf::from_slash(root).join(PathBuf::from_slash(pat)).to_slash_lossy() } } else { pat.to_string() @@ -699,10 +700,10 @@ fn expand_name(name: &str, sub: &SubExtent) -> String { fn expand_root(root: Option<&String>, sub: &SubExtent) -> Option { match root { Some(root) => match sub.dir() { - Some(subdir) => Some(Path::new(root).join(subdir).to_string_lossy().into_owned()), - None => Some(Path::new(root).to_string_lossy().into_owned()) + Some(subdir) => Some(PathBuf::from_slash(root).join(subdir).to_slash_lossy()), + None => Some(root.clone()) }, - None => sub.dir().as_ref().map(|subdir| Path::new(subdir).to_string_lossy().into_owned()) + None => sub.dir().clone() } } @@ -731,6 +732,7 @@ fn expand_version(version: &Location, sub: &SubExtent) -> Location { fn expand_also(also: &[Location]) -> Vec { also.iter().filter(|l| !l.is_tags()).cloned().collect() } struct SubExtent { + // TODO: ensure `dir` will always be a single-level relative path (i.e. no slashes) dir: Option, majors: Vec, largest: bool, @@ -964,8 +966,8 @@ impl FileLocation { pub fn rooted(&self, root: Option<&String>) -> PathBuf { match root { - Some(root) => PathBuf::from(root).join(&self.file), - None => PathBuf::from(&self.file) + Some(root) => PathBuf::from_slash(root).join(PathBuf::from_slash(&self.file)), + None => PathBuf::from_slash(&self.file) } } } diff --git a/src/git.rs b/src/git.rs index 3f1c1a9..94caf78 100644 --- a/src/git.rs +++ b/src/git.rs @@ -24,6 +24,7 @@ use std::fmt; use std::io::{stdout, Write}; use std::iter::empty; use std::path::{Path, PathBuf}; +use path_slash::PathBufExt as _; pub struct Repo { vcs: GitVcsLevel, @@ -489,10 +490,18 @@ impl<'r> Slice<'r> { Ok(tree.iter().filter_map(|entry| entry.name().map(|n| n.to_string())).filter(|n| filter.is_match(n)).collect()) } + #[cfg(not(target_family = "windows"))] fn object(&self, path: &str) -> Result { Ok(self.repo.repo()?.revparse_single(&format!("{}:{}", self.refspec.tag(), path))?) } + // Always path issues on windows. See https://github.com/JuliaLang/julia/issues/18724 + #[cfg(target_family = "windows")] + fn object(&self, path: &str) -> Result { + let path = path.replace('\\', "/"); + Ok(self.repo.repo()?.revparse_single(&format!("{}:{}", self.refspec.tag(), &path))?) + } + pub fn date(&self) -> Result> { let obj = match self.repo.repo()?.revparse_single(&format!("{}^{{}}", self.refspec.tag())) { Ok(obj) => obj, @@ -1034,7 +1043,7 @@ fn files_from_commit<'a>(repo: &'a Repository, commit: &Commit<'a>) -> Result Result<()> { status_opts.include_ignored(false); status_opts.include_untracked(true); status_opts.exclude_submodules(false); - if repo.statuses(Some(&mut status_opts))?.iter().any(|s| s.status() != Status::CURRENT) { - bail!("Repository is not current."); + + let statuses = repo.statuses(Some(&mut status_opts))?; + let bad_status = statuses.iter().find(|s| s.status() != Status::CURRENT); + if let Some(bad_status) = bad_status { + bail!("Repository is not current: {} = {:?}", bad_status.path().unwrap_or(""), bad_status.status()); } Ok(()) } diff --git a/src/init.rs b/src/init.rs index 7277005..29eb1aa 100644 --- a/src/init.rs +++ b/src/init.rs @@ -12,6 +12,7 @@ use std::ffi::OsStr; use std::fs::OpenOptions; use std::io::Write; use std::path::Path; +use path_slash::PathExt; pub fn init(max_depth: u16) -> Result<()> { if Path::new(CONFIG_FILENAME).exists() { @@ -40,14 +41,14 @@ fn find_project(name: &OsStr, file: &Path) -> Result> { if fname == "package.json" { let name = extract_name(file, |d| JsonScanner::new("name").find(&d))?; let dir = file.parent().unwrap(); - return Ok(Some(ProjSummary::new_file(name, dir.to_string_lossy(), "package.json", "json", "version", &["npm"]))); + return Ok(Some(ProjSummary::new_file(name, dir.to_slash_lossy(), "package.json", "json", "version", &["npm"]))); } if fname == "Cargo.toml" { let name = extract_name(file, |d| TomlScanner::new("package.name").find(&d))?; let dir = file.parent().unwrap(); let mut proj = - ProjSummary::new_file(name, dir.to_string_lossy(), "Cargo.toml", "toml", "package.version", &["cargo"]); + ProjSummary::new_file(name, dir.to_slash_lossy(), "Cargo.toml", "toml", "package.version", &["cargo"]); proj.hook("post_write", "cargo update --workspace"); return Ok(Some(proj)); } @@ -57,13 +58,13 @@ fn find_project(name: &OsStr, file: &Path) -> Result> { let is_subdir = if let Some(parent) = dir.parent() { parent.join("go.mod").exists() } else { false }; if !is_subdir { let name = dir.file_name().and_then(|n| n.to_str()).unwrap_or("project"); - return Ok(Some(ProjSummary::new_tags(name, dir.to_string_lossy(), true, &["go"]))); + return Ok(Some(ProjSummary::new_tags(name, dir.to_slash_lossy(), true, &["go"]))); } } if fname == "pom.xml" { let name = extract_name(file, |d| XmlScanner::new("project.artifactId").find(&d))?; - let dir = file.parent().unwrap().to_string_lossy(); + let dir = file.parent().unwrap().to_slash_lossy(); return Ok(Some(ProjSummary::new_file(name, dir, "pom.xml", "xml", "project.version", &["mvn"]))); } @@ -71,7 +72,7 @@ fn find_project(name: &OsStr, file: &Path) -> Result> { let name_reg = r#"name *= *['"]([^'"]*)['"]"#; let version_reg = r#"version *= *['"](\d+\.\d+\.\d+)['"]"#; let name = extract_name(file, |d| find_reg_data(&d, name_reg))?; - let dir = file.parent().unwrap().to_string_lossy(); + let dir = file.parent().unwrap().to_slash_lossy(); return Ok(Some(ProjSummary::new_file(name, dir, "setup.py", "pattern", version_reg, &["pip"]))); } @@ -81,12 +82,12 @@ fn find_project(name: &OsStr, file: &Path) -> Result> { .filter_map(|e| e.ok().and_then(|e| e.file_name().into_string().ok())) .any(|n| n.ends_with("*.tf")) { - return Ok(Some(ProjSummary::new_tags("terraform", file.to_string_lossy(), false, &["terraform"]))); + return Ok(Some(ProjSummary::new_tags("terraform", file.to_slash_lossy(), false, &["terraform"]))); } if fname == "Dockerfile" { let dir = file.parent().unwrap(); - return Ok(Some(ProjSummary::new_tags("docker", dir.to_string_lossy(), false, &["docker"]))); + return Ok(Some(ProjSummary::new_tags("docker", dir.to_slash_lossy(), false, &["docker"]))); } if let Some(ps) = add_gemspec(fname, file)? { @@ -104,7 +105,7 @@ fn add_gemspec(fname: &str, file: &Path) -> Result> { let name = extract_name(file, |d| find_reg_data(&d, name_reg))?; let mut vers = extract_name(file, |d| find_reg_data(&d, version_reg))?; let dir = file.parent().unwrap(); - let dirn = dir.to_string_lossy(); + let dirn = dir.to_slash_lossy(); if (vers.starts_with('"') && vers.ends_with('"')) || (vers.starts_with('\'') && vers.ends_with('\'')) { vers = vers[1 .. vers.len() - 1].to_string(); @@ -119,16 +120,16 @@ fn add_gemspec(fname: &str, file: &Path) -> Result> { let vers_file = Path::new("lib").join(fname_pref).join("version.rb"); if dir.join(&vers_file).exists() { let version_reg = r#"VERSION *= *['"](\d+\.\d+\.\d+)['"]"#; - let vfn = vers_file.to_string_lossy(); + let vfn = vers_file.to_slash_lossy(); return Ok(Some(ProjSummary::new_file(name, dirn, vfn, "pattern", version_reg, &["gem"]))); } else { - warn!("Couldn't find VERSION file \"{}\". Please edit the .versio.yaml file.", vers_file.to_string_lossy()); + warn!("Couldn't find VERSION file \"{}\". Please edit the .versio.yaml file.", vers_file.to_slash_lossy()); return Ok(Some(ProjSummary::new_file(name, dirn, "EDIT_ME", "pattern", "EDIT_ME", &["gem"]))); } } else { // Still other times, it's too tough to find. warn!("Couldn't find version in \"{}\" from \"{}\". Please edit the .versio.yaml file.", fname, vers); - return Ok(Some(ProjSummary::new_file(name, dir.to_string_lossy(), "EDIT_ME", "pattern", "EDIT_ME", &["gem"]))); + return Ok(Some(ProjSummary::new_file(name, dir.to_slash_lossy(), "EDIT_ME", "pattern", "EDIT_ME", &["gem"]))); } } diff --git a/src/state.rs b/src/state.rs index e2884e0..d98d550 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,6 +11,7 @@ use std::collections::{HashMap, HashSet}; use std::fs::OpenOptions; use std::mem::take; use std::path::{Path, PathBuf}; +use path_slash::{PathExt as _, PathBufExt as _}; pub trait StateRead: FilesRead { fn latest_tag(&self, proj: &ProjectId) -> Option<&String>; @@ -65,7 +66,7 @@ impl FilesRead for CurrentFiles { fn subdirs(&self, root: Option<&String>, regex: &str) -> Result> { let filter = Regex::new(regex)?; let root = root.map(|s| s.as_str()).unwrap_or("."); - Path::new(root) + PathBuf::from_slash(root) .read_dir()? .filter_map(|e| e.map(|e| e.file_name().into_string().ok()).transpose()) .filter(|n| n.as_ref().map(|n| filter.is_match(n)).unwrap_or(true)) @@ -102,7 +103,7 @@ pub struct PrevFiles<'r> { } impl<'r> FilesRead for PrevFiles<'r> { - fn has_file(&self, path: &Path) -> Result { self.slice.has_blob(&path.to_string_lossy().to_string()) } + fn has_file(&self, path: &Path) -> Result { self.slice.has_blob(&path.to_slash_lossy()) } fn read_file(&self, path: &Path) -> Result { read_from_slice(&self.slice, path) } fn subdirs(&self, root: Option<&String>, regex: &str) -> Result> { self.slice.subdirs(root, regex) } @@ -347,8 +348,8 @@ impl PickPath { } pub fn read_from_slice>(slice: &Slice, path: P) -> Result { - let path = path.as_ref().to_string_lossy().to_string(); + let path = path.as_ref().to_slash_lossy(); let blob = slice.blob(&path)?; - let cont: &str = std::str::from_utf8(blob.content()).chain_err(|| format!("Not UTF8: {}", path))?; + let cont: &str = std::str::from_utf8(blob.content()).chain_err(|| format!("Not UTF8 content: {}", path))?; Ok(cont.to_string()) }