mirror of
https://github.com/Llewellynvdm/starship.git
synced 2024-11-28 07:46:28 +00:00
feat(fossil): detection of Fossil check-outs in subdirectories (#4910)
* Move PathExt::device_id() outside modules module * Add upwards_sibling_scan-function * Fix Fossil check-out detection in subdirectories * Use shared upwards scanning function in hg_branch * Let the caller specify if they're looking for a file or a folder * fix merge --------- Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
This commit is contained in:
parent
aef799bfb0
commit
4bca74eca2
@ -1,7 +1,7 @@
|
|||||||
use crate::config::{ModuleConfig, StarshipConfig};
|
use crate::config::{ModuleConfig, StarshipConfig};
|
||||||
use crate::configs::StarshipRootConfig;
|
use crate::configs::StarshipRootConfig;
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::utils::{create_command, exec_timeout, read_file, CommandOutput};
|
use crate::utils::{create_command, exec_timeout, read_file, CommandOutput, PathExt};
|
||||||
|
|
||||||
use crate::modules;
|
use crate::modules;
|
||||||
use crate::utils::{self, home_dir};
|
use crate::utils::{self, home_dir};
|
||||||
@ -248,6 +248,16 @@ impl<'a> Context<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Begins an ancestor scan at the current directory, see [`ScanAncestors`] for available
|
||||||
|
/// methods.
|
||||||
|
pub fn begin_ancestor_scan(&'a self) -> ScanAncestors<'a> {
|
||||||
|
ScanAncestors {
|
||||||
|
path: &self.current_dir,
|
||||||
|
files: &[],
|
||||||
|
folders: &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Will lazily get repo root and branch when a module requests it.
|
/// Will lazily get repo root and branch when a module requests it.
|
||||||
pub fn get_repo(&self) -> Result<&Repo, Box<gix::discover::Error>> {
|
pub fn get_repo(&self) -> Result<&Repo, Box<gix::discover::Error>> {
|
||||||
self.repo
|
self.repo
|
||||||
@ -607,6 +617,48 @@ impl<'a> ScanDir<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scans the ancestors of a given path until a directory containing one of the given files or
|
||||||
|
/// folders is found.
|
||||||
|
pub struct ScanAncestors<'a> {
|
||||||
|
path: &'a Path,
|
||||||
|
files: &'a [&'a str],
|
||||||
|
folders: &'a [&'a str],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ScanAncestors<'a> {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn set_files(mut self, files: &'a [&'a str]) -> Self {
|
||||||
|
self.files = files;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn set_folders(mut self, folders: &'a [&'a str]) -> Self {
|
||||||
|
self.folders = folders;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scans upwards starting from the initial path until a directory containing one of the given
|
||||||
|
/// files or folders is found.
|
||||||
|
///
|
||||||
|
/// The scan does not cross device boundaries.
|
||||||
|
pub fn scan(&self) -> Option<&'a Path> {
|
||||||
|
let initial_device_id = self.path.device_id();
|
||||||
|
for dir in self.path.ancestors() {
|
||||||
|
if initial_device_id != dir.device_id() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.files.iter().any(|name| dir.join(name).is_file())
|
||||||
|
|| self.folders.iter().any(|name| dir.join(name).is_dir())
|
||||||
|
{
|
||||||
|
return Some(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_current_branch(repository: &Repository) -> Option<String> {
|
fn get_current_branch(repository: &Repository) -> Option<String> {
|
||||||
let name = repository.head_name().ok()??;
|
let name = repository.head_name().ok()??;
|
||||||
let shorthand = name.shorten();
|
let shorthand = name.shorten();
|
||||||
|
@ -22,15 +22,11 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||||||
} else {
|
} else {
|
||||||
".fslckout"
|
".fslckout"
|
||||||
};
|
};
|
||||||
|
// See if we're in a check-out by scanning upwards for a directory containing the checkout_db file
|
||||||
let is_checkout = context
|
context
|
||||||
.try_begin_scan()?
|
.begin_ancestor_scan()
|
||||||
.set_files(&[checkout_db])
|
.set_files(&[checkout_db])
|
||||||
.is_match();
|
.scan()?;
|
||||||
|
|
||||||
if !is_checkout {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = if config.truncation_length <= 0 {
|
let len = if config.truncation_length <= 0 {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@ -143,6 +139,18 @@ mod tests {
|
|||||||
tempdir.close()
|
tempdir.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fossil_branch_subdir() -> io::Result<()> {
|
||||||
|
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
||||||
|
let checkout_dir = tempdir.path();
|
||||||
|
expect_fossil_branch_with_config(
|
||||||
|
&checkout_dir.join("subdir"),
|
||||||
|
None,
|
||||||
|
&[Expect::BranchName("topic-branch"), Expect::NoTruncation],
|
||||||
|
);
|
||||||
|
tempdir.close()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fossil_branch_configured() -> io::Result<()> {
|
fn test_fossil_branch_configured() -> io::Result<()> {
|
||||||
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::io::{Error, ErrorKind};
|
use std::io::Error;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::utils::truncate::truncate_text;
|
use super::utils::truncate::truncate_text;
|
||||||
@ -6,7 +6,6 @@ use super::{Context, Module, ModuleConfig};
|
|||||||
|
|
||||||
use crate::configs::hg_branch::HgBranchConfig;
|
use crate::configs::hg_branch::HgBranchConfig;
|
||||||
use crate::formatter::StringFormatter;
|
use crate::formatter::StringFormatter;
|
||||||
use crate::modules::utils::path::PathExt;
|
|
||||||
use crate::utils::read_file;
|
use crate::utils::read_file;
|
||||||
|
|
||||||
/// Creates a module with the Hg bookmark or branch in the current directory
|
/// Creates a module with the Hg bookmark or branch in the current directory
|
||||||
@ -32,7 +31,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||||||
config.truncation_length as usize
|
config.truncation_length as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
let repo_root = get_hg_repo_root(context).ok()?;
|
let repo_root = context.begin_ancestor_scan().set_folders(&[".hg"]).scan()?;
|
||||||
let branch_name = get_hg_current_bookmark(repo_root).unwrap_or_else(|_| {
|
let branch_name = get_hg_current_bookmark(repo_root).unwrap_or_else(|_| {
|
||||||
get_hg_branch_name(repo_root).unwrap_or_else(|_| String::from("default"))
|
get_hg_branch_name(repo_root).unwrap_or_else(|_| String::from("default"))
|
||||||
});
|
});
|
||||||
@ -73,20 +72,6 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|||||||
Some(module)
|
Some(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hg_repo_root<'a>(ctx: &'a Context) -> Result<&'a Path, Error> {
|
|
||||||
let dir = ctx.current_dir.as_path();
|
|
||||||
let dev_id = dir.device_id();
|
|
||||||
for root_dir in dir.ancestors() {
|
|
||||||
if dev_id != root_dir.device_id() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if root_dir.join(".hg").is_dir() {
|
|
||||||
return Ok(root_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::new(ErrorKind::Other, "No .hg found!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_hg_branch_name(hg_root: &Path) -> Result<String, Error> {
|
fn get_hg_branch_name(hg_root: &Path) -> Result<String, Error> {
|
||||||
match read_file(hg_root.join(".hg").join("branch")) {
|
match read_file(hg_root.join(".hg").join("branch")) {
|
||||||
Ok(b) => Ok(b.trim().to_string()),
|
Ok(b) => Ok(b.trim().to_string()),
|
||||||
|
@ -13,8 +13,6 @@ pub trait PathExt {
|
|||||||
/// E.g. `\\?\UNC\server\share\foo` => `\foo`
|
/// E.g. `\\?\UNC\server\share\foo` => `\foo`
|
||||||
/// E.g. `/foo/bar` => `/foo/bar`
|
/// E.g. `/foo/bar` => `/foo/bar`
|
||||||
fn without_prefix(&self) -> &Path;
|
fn without_prefix(&self) -> &Path;
|
||||||
/// Get device / volume info
|
|
||||||
fn device_id(&self) -> Option<u64>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -82,11 +80,6 @@ impl PathExt for Path {
|
|||||||
let (_, path) = normalize::normalize_path(self);
|
let (_, path) = normalize::normalize_path(self);
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_id(&self) -> Option<u64> {
|
|
||||||
// Maybe it should use unimplemented!
|
|
||||||
Some(42u64)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Windows path prefixes are only parsed on Windows.
|
// NOTE: Windows path prefixes are only parsed on Windows.
|
||||||
@ -107,24 +100,6 @@ impl PathExt for Path {
|
|||||||
fn without_prefix(&self) -> &Path {
|
fn without_prefix(&self) -> &Path {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn device_id(&self) -> Option<u64> {
|
|
||||||
use std::os::linux::fs::MetadataExt;
|
|
||||||
match self.metadata() {
|
|
||||||
Ok(m) => Some(m.st_dev()),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "linux")))]
|
|
||||||
fn device_id(&self) -> Option<u64> {
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
match self.metadata() {
|
|
||||||
Ok(m) => Some(m.dev()),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -182,6 +182,7 @@ pub fn fixture_repo(provider: FixtureProvider) -> io::Result<TempDir> {
|
|||||||
".fslckout"
|
".fslckout"
|
||||||
};
|
};
|
||||||
let path = tempfile::tempdir()?;
|
let path = tempfile::tempdir()?;
|
||||||
|
fs::create_dir(path.path().join("subdir"))?;
|
||||||
fs::OpenOptions::new()
|
fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
|
34
src/utils.rs
34
src/utils.rs
@ -644,6 +644,40 @@ pub fn encode_to_hex(slice: &[u8]) -> String {
|
|||||||
String::from_utf8(dst).unwrap()
|
String::from_utf8(dst).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PathExt {
|
||||||
|
/// Get device / volume info
|
||||||
|
fn device_id(&self) -> Option<u64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
impl PathExt for Path {
|
||||||
|
fn device_id(&self) -> Option<u64> {
|
||||||
|
// Maybe it should use unimplemented!
|
||||||
|
Some(42u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
impl PathExt for Path {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn device_id(&self) -> Option<u64> {
|
||||||
|
use std::os::linux::fs::MetadataExt;
|
||||||
|
match self.metadata() {
|
||||||
|
Ok(m) => Some(m.st_dev()),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, not(target_os = "linux")))]
|
||||||
|
fn device_id(&self) -> Option<u64> {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
match self.metadata() {
|
||||||
|
Ok(m) => Some(m.dev()),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user