mirror of
https://github.com/starship/starship.git
synced 2025-02-02 11:29:40 +01:00
feat: vcs: introduce the VCS module with Jujutsu as an implementation example
This commit is contained in:
parent
af08ab4ce1
commit
19926e1e0a
185
.github/config-schema.json
vendored
185
.github/config-schema.json
vendored
@ -1859,6 +1859,37 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"vcs": {
|
||||
"default": {
|
||||
"disabled": true,
|
||||
"jujutsu": {
|
||||
"change": {
|
||||
"change_id_length": 7,
|
||||
"disabled": false,
|
||||
"format": "on [$change_id]($style) ",
|
||||
"style": "purple"
|
||||
},
|
||||
"format": "$change$status",
|
||||
"status": {
|
||||
"added": "+",
|
||||
"deleted": "✘",
|
||||
"disabled": false,
|
||||
"format": "[\\[$all_status\\]]($style) ",
|
||||
"modified": "!",
|
||||
"renamed": "»",
|
||||
"style": "yellow"
|
||||
}
|
||||
},
|
||||
"order": [
|
||||
"jujutsu"
|
||||
]
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/VcsConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vcsh": {
|
||||
"default": {
|
||||
"disabled": false,
|
||||
@ -6328,6 +6359,160 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"VcsConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disabled": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"order": {
|
||||
"default": [
|
||||
"jujutsu"
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Vcs"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"jujutsu": {
|
||||
"default": {
|
||||
"change": {
|
||||
"change_id_length": 7,
|
||||
"disabled": false,
|
||||
"format": "on [$change_id]($style) ",
|
||||
"style": "purple"
|
||||
},
|
||||
"format": "$change$status",
|
||||
"status": {
|
||||
"added": "+",
|
||||
"deleted": "✘",
|
||||
"disabled": false,
|
||||
"format": "[\\[$all_status\\]]($style) ",
|
||||
"modified": "!",
|
||||
"renamed": "»",
|
||||
"style": "yellow"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/JjConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Vcs": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jujutsu"
|
||||
]
|
||||
},
|
||||
"JjConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"format"
|
||||
],
|
||||
"properties": {
|
||||
"format": {
|
||||
"type": "string"
|
||||
},
|
||||
"change": {
|
||||
"default": {
|
||||
"change_id_length": 7,
|
||||
"disabled": false,
|
||||
"format": "on [$change_id]($style) ",
|
||||
"style": "purple"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/JjChangeConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"default": {
|
||||
"added": "+",
|
||||
"deleted": "✘",
|
||||
"disabled": false,
|
||||
"format": "[\\[$all_status\\]]($style) ",
|
||||
"modified": "!",
|
||||
"renamed": "»",
|
||||
"style": "yellow"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/JjStatusConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"JjChangeConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"change_id_length",
|
||||
"disabled",
|
||||
"format",
|
||||
"style"
|
||||
],
|
||||
"properties": {
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"format": {
|
||||
"type": "string"
|
||||
},
|
||||
"style": {
|
||||
"type": "string"
|
||||
},
|
||||
"change_id_length": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"JjStatusConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"added",
|
||||
"deleted",
|
||||
"disabled",
|
||||
"format",
|
||||
"modified",
|
||||
"renamed",
|
||||
"style"
|
||||
],
|
||||
"properties": {
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"format": {
|
||||
"type": "string"
|
||||
},
|
||||
"style": {
|
||||
"type": "string"
|
||||
},
|
||||
"added": {
|
||||
"type": "string"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "string"
|
||||
},
|
||||
"modified": {
|
||||
"type": "string"
|
||||
},
|
||||
"renamed": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"VcshConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -4684,6 +4684,128 @@ By default the module will be shown if any of the following conditions are met:
|
||||
format = 'via [V $version](blue bold) '
|
||||
```
|
||||
|
||||
## VCS
|
||||
|
||||
The `vcs` module displays the current active Version Control System (VCS).
|
||||
The module will be shown only if a VCS is currently in use.
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| ----------- | ------------- | ----------------------------------- |
|
||||
| `order` | `["jujutsu"]` | The order in which to search VCSes. |
|
||||
| `jujutsu` | _see below_ | The Jujutsu configuration. |
|
||||
| `mercurial` | _see below_ | The Mercurial configuration. |
|
||||
| `disabled` | `true` | Disables the `vcs` module. |
|
||||
|
||||
Note that an empty `order` has the same effect as disabling the module.
|
||||
|
||||
VCS will be searched for following `order`. The first one that returns valid informations will be the one printed,
|
||||
e.g. in a repository with both `.git` and `.jj` present, `order = ["jujutsu", "git"]` will always print Jujutsu's output.
|
||||
|
||||
### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[vcs]
|
||||
order = [
|
||||
"jujutsu",
|
||||
]
|
||||
```
|
||||
|
||||
### VCS Options: Jujutsu
|
||||
|
||||
| Option | Default | Description |
|
||||
| -------- | ---------------- | ------------------------------------- |
|
||||
| `format` | `$change$status` | The format of the Jujutsu VCS module. |
|
||||
| `change` | _see below_ | The Jujutsu change configuration. |
|
||||
| `status` | _see below_ | The Jujutsu status configuration. |
|
||||
|
||||
#### Variables
|
||||
|
||||
| Variable | Description |
|
||||
| -------- | -------------------------- |
|
||||
| `change` | The Jujutsu change module. |
|
||||
| `status` | The Jujutsu status module. |
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[vcs.jujutsu]
|
||||
format = "$status$change"
|
||||
```
|
||||
|
||||
### VCS Options: Jujutsu: Change
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------------------ | ---------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `change_id_length` | `7` | Minimum size of the unique prefix to use. Pass `0` to always use the shortest possible size. |
|
||||
| `style` | `'purple'` | The style for the module. |
|
||||
| `format` | `'on [$change_id]($style) '` | The format for the module. |
|
||||
| `disabled` | `false` | Disables the `vcs.jujutsu.change` module. |
|
||||
|
||||
#### Variables
|
||||
|
||||
The following variables can be used in `format`:
|
||||
|
||||
| Variable | Description |
|
||||
| ----------- | ----------------------------------- |
|
||||
| `change_id` | The Jujutsu current Change ID. |
|
||||
| style\* | Mirrors the value of option `style` |
|
||||
|
||||
*: This variable can only be used as a part of a style string
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[vcs.jujutsu.change]
|
||||
format = "with [$change_id]($style)"
|
||||
```
|
||||
|
||||
### VCS Options: Jujutsu: Status
|
||||
|
||||
| Option | Default | Description |
|
||||
| ---------- | -------------------------------- | ----------------------------------------- |
|
||||
| `added` | `'+'` | The format of `added` |
|
||||
| `deleted` | `'✘'` | The format of `deleted` |
|
||||
| `modified` | `'!'` | The format of `modified` |
|
||||
| `renamed` | `'»'` | The format of `renamed` |
|
||||
| `style` | `'yellow'` | The style for the module. |
|
||||
| `format` | `'[\\[$all_status\\]]($style) '` | The format for the module. |
|
||||
| `disabled` | `false` | Disables the `vcs.jujutsu.status` module. |
|
||||
|
||||
#### Variables
|
||||
|
||||
The following variables can be used in `format`:
|
||||
|
||||
| Variable | Description |
|
||||
| ------------ | -------------------------------------------------- |
|
||||
| `all_status` | Shortcut for`$deleted$renamed$modified$added` |
|
||||
| `added` | Displays `added` when a new file has been added. |
|
||||
| `deleted` | Displays `deleted` when a file has been deleted. |
|
||||
| `modified` | Displays `modified` when a file has been modified. |
|
||||
| `renamed` | Displays `renamed` when a file has been renamed. |
|
||||
| style\* | Mirrors the value of option `style` |
|
||||
|
||||
*: This variable can only be used as a part of a style string
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[vcs.jujutsu.status]
|
||||
added = "A"
|
||||
deleted = "D"
|
||||
modified = "M"
|
||||
renamed = "R"
|
||||
```
|
||||
|
||||
## VCSH
|
||||
|
||||
The `vcsh` module displays the current active [VCSH](https://github.com/RichiH/vcsh) repository.
|
||||
|
@ -94,6 +94,7 @@ pub mod typst;
|
||||
pub mod username;
|
||||
pub mod v;
|
||||
pub mod vagrant;
|
||||
pub mod vcs;
|
||||
pub mod vcsh;
|
||||
pub mod zig;
|
||||
|
||||
@ -294,6 +295,8 @@ pub struct FullConfig<'a> {
|
||||
#[serde(borrow)]
|
||||
vagrant: vagrant::VagrantConfig<'a>,
|
||||
#[serde(borrow)]
|
||||
vcs: vcs::VcsConfig<'a>,
|
||||
#[serde(borrow)]
|
||||
vcsh: vcsh::VcshConfig<'a>,
|
||||
#[serde(borrow)]
|
||||
vlang: v::VConfig<'a>,
|
||||
|
@ -40,6 +40,7 @@ pub const PROMPT_ORDER: &[&str] = &[
|
||||
"nats",
|
||||
"directory",
|
||||
"vcsh",
|
||||
"vcs",
|
||||
"fossil_branch",
|
||||
"fossil_metrics",
|
||||
"git_branch",
|
||||
|
83
src/configs/vcs/jujutsu.rs
Normal file
83
src/configs/vcs/jujutsu.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(deny_unknown_fields)
|
||||
)]
|
||||
pub struct JjConfig<'a> {
|
||||
pub format: &'a str,
|
||||
#[serde(borrow, default)]
|
||||
pub change: JjChangeConfig<'a>,
|
||||
#[serde(borrow, default)]
|
||||
pub status: JjStatusConfig<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Default for JjConfig<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
format: "$change$status",
|
||||
change: Default::default(),
|
||||
status: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(deny_unknown_fields)
|
||||
)]
|
||||
pub struct JjChangeConfig<'a> {
|
||||
pub disabled: bool,
|
||||
pub format: &'a str,
|
||||
pub style: &'a str,
|
||||
|
||||
pub change_id_length: usize,
|
||||
}
|
||||
|
||||
impl<'a> Default for JjChangeConfig<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
disabled: false,
|
||||
format: "on [$change_id]($style) ",
|
||||
style: "purple",
|
||||
|
||||
change_id_length: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(deny_unknown_fields)
|
||||
)]
|
||||
pub struct JjStatusConfig<'a> {
|
||||
pub disabled: bool,
|
||||
pub format: &'a str,
|
||||
pub style: &'a str,
|
||||
|
||||
pub added: &'a str,
|
||||
pub deleted: &'a str,
|
||||
pub modified: &'a str,
|
||||
pub renamed: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Default for JjStatusConfig<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
disabled: false,
|
||||
format: "[\\[$all_status\\]]($style) ",
|
||||
style: "yellow",
|
||||
|
||||
added: "+",
|
||||
deleted: "✘",
|
||||
modified: "!",
|
||||
renamed: "»",
|
||||
}
|
||||
}
|
||||
}
|
64
src/configs/vcs/mod.rs
Normal file
64
src/configs/vcs/mod.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use indexmap::IndexSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod jujutsu;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(deny_unknown_fields)
|
||||
)]
|
||||
#[serde(default)]
|
||||
pub struct VcsConfig<'a> {
|
||||
pub disabled: bool,
|
||||
pub order: IndexSet<Vcs>,
|
||||
#[serde(borrow, default)]
|
||||
pub jujutsu: jujutsu::JjConfig<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Default for VcsConfig<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
disabled: true,
|
||||
order: [
|
||||
// TODO(poliorcetics): make the default be only Git, avoiding costs for the
|
||||
// vast majority of users.
|
||||
Vcs::Jujutsu,
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
jujutsu: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(deny_unknown_fields)
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Vcs {
|
||||
Jujutsu,
|
||||
}
|
||||
|
||||
impl Vcs {
|
||||
/// Marker file or directory indicating the VCS is active.
|
||||
pub const fn marker(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Jujutsu => ".jj",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Vcs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = match self {
|
||||
Self::Jujutsu => "jujutsu",
|
||||
};
|
||||
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ pub const ALL_MODULES: &[&str] = &[
|
||||
"typst",
|
||||
"username",
|
||||
"vagrant",
|
||||
"vcs",
|
||||
"vcsh",
|
||||
"vlang",
|
||||
"zig",
|
||||
|
@ -89,6 +89,7 @@ mod time;
|
||||
mod username;
|
||||
mod utils;
|
||||
mod vagrant;
|
||||
mod vcs;
|
||||
mod vcsh;
|
||||
mod vlang;
|
||||
mod zig;
|
||||
@ -203,6 +204,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
||||
"username" => username::module(context),
|
||||
"vlang" => vlang::module(context),
|
||||
"vagrant" => vagrant::module(context),
|
||||
"vcs" => vcs::module(context),
|
||||
"vcsh" => vcsh::module(context),
|
||||
"zig" => zig::module(context),
|
||||
env if env.starts_with("env_var.") => {
|
||||
@ -326,6 +328,7 @@ pub fn description(module: &str) -> &'static str {
|
||||
"typst" => "The current installed version of typst",
|
||||
"username" => "The active user's username",
|
||||
"vagrant" => "The currently installed version of Vagrant",
|
||||
"vcs" => "The currently active version control system",
|
||||
"vcsh" => "The currently active VCSH repository",
|
||||
"vlang" => "The currently installed version of V",
|
||||
"zig" => "The currently installed version of Zig",
|
||||
|
152
src/modules/vcs/jujutsu.rs
Normal file
152
src/modules/vcs/jujutsu.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::configs::vcs::jujutsu::{JjChangeConfig, JjConfig, JjStatusConfig};
|
||||
use crate::formatter::string_formatter::StringFormatterError;
|
||||
|
||||
use super::{format_count, Context, Segment, StringFormatter};
|
||||
|
||||
const ALL_STATUS_FORMAT: &str = "$deleted$renamed$modified$added";
|
||||
|
||||
#[derive(Default)]
|
||||
struct Status {
|
||||
added: usize,
|
||||
deleted: usize,
|
||||
modified: usize,
|
||||
renamed: usize,
|
||||
}
|
||||
|
||||
pub(super) fn segments<'a>(
|
||||
context: &Context<'a>,
|
||||
root: &Path,
|
||||
config: &JjConfig<'a>,
|
||||
) -> Option<Result<Vec<Segment>, StringFormatterError>> {
|
||||
// Prints something of the form:
|
||||
//
|
||||
// M src/configs/mod.rs
|
||||
// A src/configs/vcs/jujutsu.rs
|
||||
// vnyuwku
|
||||
let template = format!(
|
||||
"self.diff().summary() ++ '@ ' ++ change_id.shortest({})",
|
||||
config.change.change_id_length
|
||||
);
|
||||
let out = context.exec_cmd(
|
||||
"jj",
|
||||
&[
|
||||
"--repository".as_ref(),
|
||||
root.as_os_str(),
|
||||
"log".as_ref(),
|
||||
"--ignore-working-copy".as_ref(),
|
||||
"--no-graph".as_ref(),
|
||||
"--color".as_ref(),
|
||||
"never".as_ref(),
|
||||
"--revisions".as_ref(),
|
||||
"@".as_ref(), // Only display the current revision
|
||||
"--template".as_ref(),
|
||||
template.as_ref(),
|
||||
],
|
||||
)?;
|
||||
|
||||
let mut status = Status::default();
|
||||
let mut change_id = None;
|
||||
|
||||
for line in out.stdout.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (indic, rest) = line.split_once(' ')?;
|
||||
match indic {
|
||||
"A" => status.added += 1,
|
||||
"D" => status.deleted += 1,
|
||||
"M" => status.modified += 1,
|
||||
"R" => status.renamed += 1,
|
||||
"@" => change_id = Some(rest),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
formatter
|
||||
.map_variables_to_segments(|variable| match variable {
|
||||
"change" => change_segments(context, &config.change, change_id),
|
||||
"status" => status_segments(context, &config.status, &status),
|
||||
_ => None,
|
||||
})
|
||||
.parse(None, Some(context))
|
||||
});
|
||||
|
||||
Some(parsed)
|
||||
}
|
||||
|
||||
fn change_segments(
|
||||
context: &Context,
|
||||
config: &JjChangeConfig,
|
||||
change_id: Option<&str>,
|
||||
) -> Option<Result<Vec<Segment>, StringFormatterError>> {
|
||||
if config.disabled {
|
||||
return None;
|
||||
}
|
||||
let change_id = change_id?;
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
formatter
|
||||
.map_style(|variable: &str| match variable {
|
||||
"style" => Some(Ok(config.style)),
|
||||
_ => None,
|
||||
})
|
||||
.map(|variable| (variable == "change_id").then_some(Ok(change_id)))
|
||||
.parse(None, Some(context))
|
||||
});
|
||||
Some(parsed)
|
||||
}
|
||||
|
||||
fn status_segments(
|
||||
context: &Context,
|
||||
config: &JjStatusConfig,
|
||||
status: &Status,
|
||||
) -> Option<Result<Vec<Segment>, StringFormatterError>> {
|
||||
if config.disabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
formatter
|
||||
.map_meta(|variable, _| match variable {
|
||||
"all_status" => Some(ALL_STATUS_FORMAT),
|
||||
_ => None,
|
||||
})
|
||||
.map_style(|variable: &str| match variable {
|
||||
"style" => Some(Ok(config.style)),
|
||||
_ => None,
|
||||
})
|
||||
.map_variables_to_segments(|variable| {
|
||||
let segments = match variable {
|
||||
"added" => {
|
||||
format_count(config.added, "jujutsu.status.added", context, status.added)
|
||||
}
|
||||
"deleted" => format_count(
|
||||
config.deleted,
|
||||
"jujutsu.status.deleted",
|
||||
context,
|
||||
status.deleted,
|
||||
),
|
||||
"modified" => format_count(
|
||||
config.modified,
|
||||
"jujutsu.status.modified",
|
||||
context,
|
||||
status.modified,
|
||||
),
|
||||
"renamed" => format_count(
|
||||
config.renamed,
|
||||
"jujutsu.status.renamed",
|
||||
context,
|
||||
status.renamed,
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
segments.map(Ok)
|
||||
})
|
||||
.parse(None, Some(context))
|
||||
});
|
||||
Some(parsed)
|
||||
}
|
119
src/modules/vcs/mod.rs
Normal file
119
src/modules/vcs/mod.rs
Normal file
@ -0,0 +1,119 @@
|
||||
//! The Version Control System (VCS) module exposes information from the currently active VCS,
|
||||
//! trying them in a preconfigured order.
|
||||
//!
|
||||
//! This allows exposing information from only one VCS when several are present, as can be the case
|
||||
//! for [colocated Git repos in Jujutsu][coloc].
|
||||
//! It also makes reusing already parsed repository information easier.
|
||||
//!
|
||||
//! [coloc]: https://martinvonz.github.io/jj/latest/git-compatibility/#co-located-jujutsugit-repos
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::configs::vcs::{Vcs, VcsConfig};
|
||||
use crate::formatter::StringFormatter;
|
||||
use crate::segment::Segment;
|
||||
|
||||
use super::{Context, Module, ModuleConfig};
|
||||
|
||||
pub mod jujutsu;
|
||||
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("vcs");
|
||||
let config = VcsConfig::try_load(module.config);
|
||||
|
||||
if config.disabled || config.order.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (root, vcs) = find_vcs_root(context, &config)?;
|
||||
|
||||
let parsed = match vcs {
|
||||
Vcs::Jujutsu => jujutsu::segments(context, root, &config.jujutsu)?,
|
||||
};
|
||||
|
||||
module.set_segments(match parsed {
|
||||
Ok(segments) if segments.is_empty() => return None,
|
||||
Ok(segments) => segments,
|
||||
Err(error) => {
|
||||
log::warn!("Error in module `vcs.{vcs}`:\n{error}");
|
||||
return None;
|
||||
}
|
||||
});
|
||||
|
||||
Some(module)
|
||||
}
|
||||
|
||||
/// Find the closest VCS root marker by searching upwards.
|
||||
fn find_vcs_root<'a>(context: &'a Context, config: &VcsConfig<'a>) -> Option<(&'a Path, Vcs)> {
|
||||
let current_dir = &context.current_dir;
|
||||
|
||||
// We want to avoid reallocations during the search so we pre-allocate a buffer with enough
|
||||
// capacity to hold the longest path + any marker (we could find the length of the longest
|
||||
// marker programmatically but that would actually cost more than preallocating a few bytes too many).
|
||||
let mut buf = PathBuf::with_capacity(current_dir.capacity() + 15);
|
||||
|
||||
for dir in current_dir.ancestors() {
|
||||
// Then for each dir, we do `<dir>`, clearing in case `dir` is not an absolute path for
|
||||
// some reason
|
||||
buf.clear();
|
||||
buf.push(dir);
|
||||
|
||||
for &vcs in &config.order {
|
||||
// Then we push, so it becomes `<dir>/<marker>`
|
||||
buf.push(vcs.marker());
|
||||
|
||||
if buf.exists() {
|
||||
// In case we find it, we return the VCS but also the root dir: we already did the
|
||||
// work of finding it, we can give it as a parameter to the called command or
|
||||
// library so it can avoid doing its own search.
|
||||
return Some((dir, vcs));
|
||||
}
|
||||
|
||||
// Remove the current marker to prepare for the next one
|
||||
buf.pop();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn format_text<F>(
|
||||
format_str: &str,
|
||||
config_path: &str,
|
||||
context: &Context,
|
||||
mapper: F,
|
||||
) -> Option<Vec<Segment>>
|
||||
where
|
||||
F: Fn(&str) -> Option<String> + Send + Sync,
|
||||
{
|
||||
if let Ok(formatter) = StringFormatter::new(format_str) {
|
||||
formatter
|
||||
.map(|variable| mapper(variable).map(Ok))
|
||||
.parse(None, Some(context))
|
||||
.ok()
|
||||
} else {
|
||||
log::warn!("Error parsing format string `vcs.{}`", config_path);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn format_count(
|
||||
format_str: &str,
|
||||
config_path: &str,
|
||||
context: &Context,
|
||||
count: usize,
|
||||
) -> Option<Vec<Segment>> {
|
||||
if count == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
format_text(
|
||||
format_str,
|
||||
config_path,
|
||||
context,
|
||||
|variable| match variable {
|
||||
"count" => Some(count.to_string()),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user