feat(hg_branch): Add support for mercurial topics and find hg root dir (#4771)

* feat(hg_branch): Add support for mercurial topics and find hg root dir

* Fix clippy errors

* Use crate::utils::read_file

* Update config-schema.json

* Extend PathExt to retrieve device ID of Path

* Break hg root search when switching to another device

* Fix clippy and formatting errors

* Update docs/config/README.md

Co-authored-by: David Knaack <davidkna@users.noreply.github.com>

* Update src/modules/utils/path.rs

Co-authored-by: David Knaack <davidkna@users.noreply.github.com>

* Update src/configs/hg_branch.rs

Co-authored-by: David Knaack <davidkna@users.noreply.github.com>

* Update hg_branch description

* Revert to lazy loading, use truncate_text from utils and use fake topic

* Format code and fix clippy error

* Revert to previous test string as topic is optional in the config

* Fix doc formatting

* Stub device_id for windows

* Update config-schema.json

* Update src/modules/hg_branch.rs

Co-authored-by: David Knaack <davidkna@users.noreply.github.com>

* Do not use unwrap in device_id

* Fix formatter error

* Use dev under non linux unixes

Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
This commit is contained in:
Nicolas Évrard 2022-12-31 15:53:55 +01:00 committed by GitHub
parent 8a8e09dd50
commit 8d2256ab1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 48 deletions

View File

@ -750,7 +750,7 @@
"hg_branch": {
"default": {
"disabled": true,
"format": "on [$symbol$branch]($style) ",
"format": "on [$symbol$branch(:$topic)]($style) ",
"style": "bold purple",
"symbol": " ",
"truncation_length": 9223372036854775807,
@ -3537,7 +3537,7 @@
"type": "string"
},
"format": {
"default": "on [$symbol$branch]($style) ",
"default": "on [$symbol$branch(:$topic)]($style) ",
"type": "string"
},
"truncation_length": {

View File

@ -2643,26 +2643,27 @@ style = 'bold dimmed green'
## Mercurial Branch
The `hg_branch` module shows the active branch of the repo in your current directory.
The `hg_branch` module shows the active branch and topic of the repo in your current directory.
### Options
| Option | Default | Description |
| ------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
| `symbol` | `' '` | The symbol used before the hg bookmark or branch name of the repo in your current directory. |
| `style` | `'bold purple'` | The style for the module. |
| `format` | `'on [$symbol$branch]($style) '` | The format for the module. |
| `truncation_length` | `2^63 - 1` | Truncates the hg branch name to `N` graphemes |
| `truncation_symbol` | `'…'` | The symbol used to indicate a branch name was truncated. |
| `disabled` | `true` | Disables the `hg_branch` module. |
| Option | Default | Description |
| ------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- |
| `symbol` | `' '` | The symbol used before the hg bookmark or branch name of the repo in your current directory. |
| `style` | `'bold purple'` | The style for the module. |
| `format` | `'on [$symbol$branch(:$topic)]($style) '` | The format for the module. |
| `truncation_length` | `2^63 - 1` | Truncates the hg branch / topic name to `N` graphemes |
| `truncation_symbol` | `'…'` | The symbol used to indicate a branch name was truncated. |
| `disabled` | `true` | Disables the `hg_branch` module. |
### Variables
| Variable | Example | Description |
| -------- | -------- | ------------------------------------ |
| branch | `master` | The active mercurial branch |
| symbol | | Mirrors the value of option `symbol` |
| style\* | | Mirrors the value of option `style` |
| Variable | Example | Description |
| -------- | --------- | ------------------------------------ |
| branch | `master` | The active mercurial branch |
| topic | `feature` | The active mercurial topic |
| symbol | | Mirrors the value of option `symbol` |
| style\* | | Mirrors the value of option `style` |
*: This variable can only be used as a part of a style string

View File

@ -21,7 +21,7 @@ impl<'a> Default for HgBranchConfig<'a> {
HgBranchConfig {
symbol: "",
style: "bold purple",
format: "on [$symbol$branch]($style) ",
format: "on [$symbol$branch(:$topic)]($style) ",
truncation_length: std::i64::MAX,
truncation_symbol: "",
disabled: true,

View File

@ -1,20 +1,18 @@
use unicode_segmentation::UnicodeSegmentation;
use std::io::{Error, ErrorKind};
use std::path::Path;
use super::utils::truncate::truncate_text;
use super::{Context, Module, ModuleConfig};
use crate::configs::hg_branch::HgBranchConfig;
use crate::formatter::StringFormatter;
use crate::modules::utils::path::PathExt;
use crate::utils::read_file;
/// Creates a module with the Hg bookmark or branch in the current directory
///
/// Will display the bookmark or branch name if the current directory is an hg repo
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let is_hg_repo = context.try_begin_scan()?.set_folders(&[".hg"]).is_match();
if !is_hg_repo {
return None;
}
let mut module = context.new_module("hg_branch");
let config: HgBranchConfig = HgBranchConfig::try_load(module.config);
@ -34,16 +32,16 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
config.truncation_length as usize
};
let branch_name =
get_hg_current_bookmark(context).unwrap_or_else(|| get_hg_branch_name(context));
let repo_root = get_hg_repo_root(context).ok()?;
let branch_name = get_hg_current_bookmark(repo_root).unwrap_or_else(|_| {
get_hg_branch_name(repo_root).unwrap_or_else(|_| String::from("default"))
});
let truncated_graphemes = get_graphemes(&branch_name, len);
// The truncation symbol should only be added if we truncated
let truncated_and_symbol = if len < graphemes_len(&branch_name) {
let truncation_symbol = get_graphemes(config.truncation_symbol, 1);
truncated_graphemes + truncation_symbol.as_str()
let branch_graphemes = truncate_text(&branch_name, len, config.truncation_symbol);
let topic_graphemes = if let Ok(topic) = get_hg_topic_name(repo_root) {
truncate_text(&topic, len, config.truncation_symbol)
} else {
truncated_graphemes
String::from("")
};
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
@ -57,7 +55,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
_ => None,
})
.map(|variable| match variable {
"branch" => Some(Ok(truncated_and_symbol.as_str())),
"branch" => Some(Ok(branch_graphemes.as_str())),
"topic" => Some(Ok(topic_graphemes.as_str())),
_ => None,
})
.parse(None, Some(context))
@ -74,26 +73,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(module)
}
fn get_hg_branch_name(ctx: &Context) -> String {
std::fs::read_to_string(ctx.current_dir.join(".hg").join("branch"))
.map_or_else(|_| "default".to_string(), |s| s.trim().into())
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_current_bookmark(ctx: &Context) -> Option<String> {
std::fs::read_to_string(ctx.current_dir.join(".hg").join("bookmarks.current"))
.map(|s| s.trim().into())
.ok()
fn get_hg_branch_name(hg_root: &Path) -> Result<String, Error> {
match read_file(hg_root.join(".hg").join("branch")) {
Ok(b) => Ok(b.trim().to_string()),
Err(e) => Err(e),
}
}
fn get_graphemes(text: &str, length: usize) -> String {
UnicodeSegmentation::graphemes(text, true)
.take(length)
.collect::<Vec<&str>>()
.concat()
fn get_hg_current_bookmark(hg_root: &Path) -> Result<String, Error> {
read_file(hg_root.join(".hg").join("bookmarks.current"))
}
fn graphemes_len(text: &str) -> usize {
UnicodeSegmentation::graphemes(text, true).count()
fn get_hg_topic_name(hg_root: &Path) -> Result<String, Error> {
read_file(hg_root.join(".hg").join("topic"))
}
#[cfg(test)]
@ -186,6 +192,26 @@ mod tests {
tempdir.close()
}
#[test]
#[ignore]
fn test_hg_topic() -> io::Result<()> {
let tempdir = fixture_repo(FixtureProvider::Hg)?;
let repo_dir = tempdir.path();
fs::write(repo_dir.join(".hg").join("topic"), "feature")?;
let actual = ModuleRenderer::new("hg_branch")
.path(repo_dir.to_str().unwrap())
.config(toml::toml! {
[hg_branch]
format = "$topic"
disabled = false
})
.collect();
assert_eq!(Some(String::from("feature")), actual);
tempdir.close()
}
#[test]
#[ignore]
fn test_default_truncation_symbol() -> io::Result<()> {

View File

@ -249,7 +249,7 @@ pub fn description(module: &str) -> &'static str {
"haskell" => "The selected version of the Haskell toolchain",
"haxe" => "The currently installed version of Haxe",
"helm" => "The currently installed version of Helm",
"hg_branch" => "The active branch of the repo in your current directory",
"hg_branch" => "The active branch and topic of the repo in your current directory",
"hostname" => "The system hostname",
"java" => "The currently installed version of Java",
"jobs" => "The current number of jobs running",

View File

@ -13,6 +13,8 @@ pub trait PathExt {
/// E.g. `\\?\UNC\server\share\foo` => `\foo`
/// E.g. `/foo/bar` => `/foo/bar`
fn without_prefix(&self) -> &Path;
/// Get device / volume info
fn device_id(&self) -> Option<u64>;
}
#[cfg(windows)]
@ -80,6 +82,11 @@ impl PathExt for Path {
let (_, path) = normalize::normalize_path(self);
path
}
fn device_id(&self) -> Option<u64> {
// Maybe it should use unimplemented!
Some(42u64)
}
}
// NOTE: Windows path prefixes are only parsed on Windows.
@ -100,6 +107,24 @@ impl PathExt for Path {
fn without_prefix(&self) -> &Path {
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)]