mirror of
https://github.com/nushell/nushell.git
synced 2025-04-25 05:38:20 +02:00
introducing gstat
, a new command to get the git status (#443)
* wip - preliminary checking * updated to latest pluging * i think it's all working now, except bare words * clippy
This commit is contained in:
parent
1fd26727c5
commit
c8b9913718
149
Cargo.lock
generated
149
Cargo.lock
generated
@ -963,6 +963,21 @@ version = "0.26.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "git2"
|
||||||
|
version = "0.13.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "845e007a28f1fcac035715988a234e8ec5458fd825b20a20c7dec74237ef341f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"libgit2-sys",
|
||||||
|
"log",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -1024,6 +1039,17 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "im"
|
name = "im"
|
||||||
version = "15.0.0"
|
version = "15.0.0"
|
||||||
@ -1193,6 +1219,46 @@ version = "0.2.108"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libgit2-sys"
|
||||||
|
version = "0.12.25+1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f68169ef08d6519b2fe133ecc637408d933c0174b23b80bb2f79828966fbaab"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libssh2-sys",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libssh2-sys"
|
||||||
|
version = "0.2.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-sys"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
@ -1596,6 +1662,16 @@ dependencies = [
|
|||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu_plugin_gstat"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"git2",
|
||||||
|
"nu-engine",
|
||||||
|
"nu-plugin",
|
||||||
|
"nu-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu_plugin_inc"
|
name = "nu_plugin_inc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1753,6 +1829,25 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@ -1914,6 +2009,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polars"
|
name = "polars"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@ -2663,6 +2764,21 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "titlecase"
|
name = "titlecase"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -2736,6 +2852,12 @@ dependencies = [
|
|||||||
"version_check 0.9.3",
|
"version_check 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@ -2745,6 +2867,15 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@ -2769,6 +2900,18 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80"
|
checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8-width"
|
name = "utf8-width"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -2790,6 +2933,12 @@ dependencies = [
|
|||||||
"getrandom 0.2.3",
|
"getrandom 0.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -14,6 +14,7 @@ members = [
|
|||||||
"crates/nu-protocol",
|
"crates/nu-protocol",
|
||||||
"crates/nu-plugin",
|
"crates/nu-plugin",
|
||||||
"crates/nu_plugin_inc",
|
"crates/nu_plugin_inc",
|
||||||
|
"crates/nu_plugin_gstat",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu_plugin_example",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
17
crates/nu_plugin_gstat/Cargo.toml
Normal file
17
crates/nu_plugin_gstat/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Nu Project Contributors"]
|
||||||
|
description = "A git status plugin for Nushell"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu_plugin_gstat"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-plugin = { path="../nu-plugin", version = "0.1.0" }
|
||||||
|
nu-protocol = { path="../nu-protocol", version = "0.1.0" }
|
||||||
|
nu-engine = { path="../nu-engine", version = "0.1.0" }
|
||||||
|
|
||||||
|
git2 = "0.13.24"
|
543
crates/nu_plugin_gstat/src/gstat.rs
Normal file
543
crates/nu_plugin_gstat/src/gstat.rs
Normal file
@ -0,0 +1,543 @@
|
|||||||
|
use git2::{Branch, BranchType, Repository};
|
||||||
|
use nu_plugin::LabeledError;
|
||||||
|
use nu_protocol::{Span, Spanned, Value};
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::ops::BitAnd;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
// git status
|
||||||
|
// https://github.com/git/git/blob/9875c515535860450bafd1a177f64f0a478900fa/Documentation/git-status.txt
|
||||||
|
|
||||||
|
// git status borrowed from here and tweaked
|
||||||
|
// https://github.com/glfmn/glitter/blob/master/lib/git.rs
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GStat;
|
||||||
|
|
||||||
|
impl GStat {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usage() -> &'static str {
|
||||||
|
"Usage: gstat"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gstat(
|
||||||
|
&self,
|
||||||
|
value: &Value,
|
||||||
|
path: Option<Spanned<String>>,
|
||||||
|
span: &Span,
|
||||||
|
) -> Result<Value, LabeledError> {
|
||||||
|
// use std::any::Any;
|
||||||
|
// eprintln!("input type: {:?} value: {:#?}", &value.type_id(), &value);
|
||||||
|
// eprintln!("path type: {:?} value: {:#?}", &path.type_id(), &path);
|
||||||
|
|
||||||
|
// This is a flag to let us know if we're using the input value (value)
|
||||||
|
// or using the path specified (path)
|
||||||
|
let mut using_input_value = false;
|
||||||
|
|
||||||
|
// let's get the input value as a string
|
||||||
|
let piped_value = match value.as_string() {
|
||||||
|
Ok(s) => {
|
||||||
|
using_input_value = true;
|
||||||
|
s
|
||||||
|
}
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// now let's get the path string
|
||||||
|
let mut a_path = match path {
|
||||||
|
Some(p) => {
|
||||||
|
// should we check for input and path? nah.
|
||||||
|
using_input_value = false;
|
||||||
|
p
|
||||||
|
}
|
||||||
|
None => Spanned {
|
||||||
|
item: ".".to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there was no path specified and there is a piped in value, let's use the piped in value
|
||||||
|
if a_path.item == "." && piped_value.chars().count() > 0 {
|
||||||
|
a_path.item = piped_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This path has to exist
|
||||||
|
if !std::path::Path::new(&a_path.item).exists() {
|
||||||
|
return Err(LabeledError {
|
||||||
|
label: "error with path".to_string(),
|
||||||
|
msg: format!("path does not exist [{}]", &a_path.item),
|
||||||
|
span: if using_input_value {
|
||||||
|
Some(value.span().expect("unable to get value span"))
|
||||||
|
} else {
|
||||||
|
Some(a_path.span)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = match std::fs::metadata(&a_path.item) {
|
||||||
|
Ok(md) => md,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(LabeledError {
|
||||||
|
label: "error with metadata".to_string(),
|
||||||
|
msg: format!(
|
||||||
|
"unable to get metadata for [{}], error: {}",
|
||||||
|
&a_path.item, e
|
||||||
|
),
|
||||||
|
span: if using_input_value {
|
||||||
|
Some(value.span().expect("unable to get value span"))
|
||||||
|
} else {
|
||||||
|
Some(a_path.span)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This path has to be a directory
|
||||||
|
if !metadata.is_dir() {
|
||||||
|
return Err(LabeledError {
|
||||||
|
label: "error with directory".to_string(),
|
||||||
|
msg: format!("path is not a directory [{}]", &a_path.item),
|
||||||
|
span: if using_input_value {
|
||||||
|
Some(value.span().expect("unable to get value span"))
|
||||||
|
} else {
|
||||||
|
Some(a_path.span)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo_path = match PathBuf::from(&a_path.item).canonicalize() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(LabeledError {
|
||||||
|
label: format!("error canonicalizing [{}]", a_path.item),
|
||||||
|
msg: e.to_string(),
|
||||||
|
span: if using_input_value {
|
||||||
|
Some(value.span().expect("unable to get value span"))
|
||||||
|
} else {
|
||||||
|
Some(a_path.span)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stats = Repository::discover(repo_path).map(|mut repo| (Stats::new(&mut repo)));
|
||||||
|
let stats = match stats {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
// Since we really never want this to fail, lets return an empty record so
|
||||||
|
// that one can check it in a script and do something with it.
|
||||||
|
return Ok(self.create_empty_git_status(span));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
cols.push("idx_added_staged".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.idx_added_staged as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_modified_staged".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.idx_modified_staged as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_deleted_staged".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.idx_deleted_staged as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_renamed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.idx_renamed as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_type_changed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.idx_type_changed as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_untracked".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.wt_untracked as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_modified".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.wt_modified as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_deleted".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.wt_deleted as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_type_changed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.wt_type_changed as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_renamed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.wt_renamed as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("ignored".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.ignored as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("conflicts".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.conflicts as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("ahead".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.ahead as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("behind".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.behind as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("stashes".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: stats.stashes as i64,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("branch".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: stats.branch,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("remote".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: stats.remote,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Leave this in case we want to turn it into a table instead of a list
|
||||||
|
// Ok(Value::List {
|
||||||
|
// vals: vec![Value::Record {
|
||||||
|
// cols,
|
||||||
|
// vals,
|
||||||
|
// span: *span,
|
||||||
|
// }],
|
||||||
|
// span: *span,
|
||||||
|
// })
|
||||||
|
|
||||||
|
Ok(Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: *span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_empty_git_status(&self, span: &Span) -> Value {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
cols.push("idx_added_staged".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_modified_staged".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_deleted_staged".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_renamed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("idx_type_changed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_untracked".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_modified".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_deleted".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_type_changed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("wt_renamed".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("ignored".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("conflicts".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("ahead".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("behind".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("stashes".into());
|
||||||
|
vals.push(Value::Int {
|
||||||
|
val: -1,
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("branch".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: "no_branch".to_string(),
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
cols.push("remote".into());
|
||||||
|
vals.push(Value::String {
|
||||||
|
val: "no_remote".to_string(),
|
||||||
|
span: *span,
|
||||||
|
});
|
||||||
|
|
||||||
|
Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: *span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stats which the interpreter uses to populate the gist expression
|
||||||
|
#[derive(Debug, PartialEq, Eq, Default, Clone)]
|
||||||
|
pub struct Stats {
|
||||||
|
/// Number of files to be added
|
||||||
|
pub idx_added_staged: u16,
|
||||||
|
/// Number of staged changes to files
|
||||||
|
pub idx_modified_staged: u16,
|
||||||
|
/// Number of staged deletions
|
||||||
|
pub idx_deleted_staged: u16,
|
||||||
|
/// Number of renamed files
|
||||||
|
pub idx_renamed: u16,
|
||||||
|
/// Index file type change
|
||||||
|
pub idx_type_changed: u16,
|
||||||
|
|
||||||
|
/// Number of untracked files which are new to the repository
|
||||||
|
pub wt_untracked: u16,
|
||||||
|
/// Number of modified files which have not yet been staged
|
||||||
|
pub wt_modified: u16,
|
||||||
|
/// Number of deleted files
|
||||||
|
pub wt_deleted: u16,
|
||||||
|
/// Working tree file type change
|
||||||
|
pub wt_type_changed: u16,
|
||||||
|
/// Working tree renamed
|
||||||
|
pub wt_renamed: u16,
|
||||||
|
|
||||||
|
// Ignored files
|
||||||
|
pub ignored: u16,
|
||||||
|
/// Number of unresolved conflicts in the repository
|
||||||
|
pub conflicts: u16,
|
||||||
|
|
||||||
|
/// Number of commits ahead of the upstream branch
|
||||||
|
pub ahead: u16,
|
||||||
|
/// Number of commits behind the upstream branch
|
||||||
|
pub behind: u16,
|
||||||
|
/// Number of stashes on the current branch
|
||||||
|
pub stashes: u16,
|
||||||
|
/// The branch name or other stats of the HEAD pointer
|
||||||
|
pub branch: String,
|
||||||
|
/// The of the upstream branch
|
||||||
|
pub remote: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stats {
|
||||||
|
/// Populate stats with the status of the given repository
|
||||||
|
pub fn new(repo: &mut Repository) -> Stats {
|
||||||
|
let mut st: Stats = Default::default();
|
||||||
|
|
||||||
|
st.read_branch(repo);
|
||||||
|
|
||||||
|
let mut opts = git2::StatusOptions::new();
|
||||||
|
|
||||||
|
opts.include_untracked(true)
|
||||||
|
.recurse_untracked_dirs(true)
|
||||||
|
.renames_head_to_index(true);
|
||||||
|
|
||||||
|
if let Ok(statuses) = repo.statuses(Some(&mut opts)) {
|
||||||
|
for status in statuses.iter() {
|
||||||
|
let flags = status.status();
|
||||||
|
|
||||||
|
if check(flags, git2::Status::INDEX_NEW) {
|
||||||
|
st.idx_added_staged += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::INDEX_MODIFIED) {
|
||||||
|
st.idx_modified_staged += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::INDEX_DELETED) {
|
||||||
|
st.idx_deleted_staged += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::INDEX_RENAMED) {
|
||||||
|
st.idx_renamed += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::INDEX_TYPECHANGE) {
|
||||||
|
st.idx_type_changed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if check(flags, git2::Status::WT_NEW) {
|
||||||
|
st.wt_untracked += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::WT_MODIFIED) {
|
||||||
|
st.wt_modified += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::WT_DELETED) {
|
||||||
|
st.wt_deleted += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::WT_TYPECHANGE) {
|
||||||
|
st.wt_type_changed += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::WT_RENAMED) {
|
||||||
|
st.wt_renamed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if check(flags, git2::Status::IGNORED) {
|
||||||
|
st.ignored += 1;
|
||||||
|
}
|
||||||
|
if check(flags, git2::Status::CONFLICTED) {
|
||||||
|
st.conflicts += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = repo.stash_foreach(|_, &_, &_| {
|
||||||
|
st.stashes += 1;
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
st
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the branch-name of the repository
|
||||||
|
///
|
||||||
|
/// If in detached head, grab the first few characters of the commit ID if possible, otherwise
|
||||||
|
/// simply provide HEAD as the branch name. This is to mimic the behaviour of `git status`.
|
||||||
|
fn read_branch(&mut self, repo: &Repository) {
|
||||||
|
self.branch = match repo.head() {
|
||||||
|
Ok(head) => {
|
||||||
|
if let Some(name) = head.shorthand() {
|
||||||
|
// try to use first 8 characters or so of the ID in detached HEAD
|
||||||
|
if name == "HEAD" {
|
||||||
|
if let Ok(commit) = head.peel_to_commit() {
|
||||||
|
let mut id = String::new();
|
||||||
|
for byte in &commit.id().as_bytes()[..4] {
|
||||||
|
write!(&mut id, "{:x}", byte).unwrap();
|
||||||
|
}
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
"HEAD".to_string()
|
||||||
|
}
|
||||||
|
// Grab the branch from the reference
|
||||||
|
} else {
|
||||||
|
let branch = name.to_string();
|
||||||
|
// Since we have a branch name, look for the name of the upstream branch
|
||||||
|
self.read_upstream_name(repo, &branch);
|
||||||
|
branch
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"HEAD".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ref err) if err.code() == git2::ErrorCode::BareRepo => "master".to_string(),
|
||||||
|
Err(_) if repo.is_empty().unwrap_or(false) => "master".to_string(),
|
||||||
|
Err(_) => "HEAD".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read name of the upstream branch
|
||||||
|
fn read_upstream_name(&mut self, repo: &Repository, branch: &str) {
|
||||||
|
// First grab branch from the name
|
||||||
|
self.remote = match repo.find_branch(branch, BranchType::Local) {
|
||||||
|
Ok(branch) => {
|
||||||
|
// Grab the upstream from the branch
|
||||||
|
match branch.upstream() {
|
||||||
|
// Grab the name of the upstream if it's valid UTF-8
|
||||||
|
Ok(upstream) => {
|
||||||
|
// While we have the upstream branch, traverse the graph and count
|
||||||
|
// ahead-behind commits.
|
||||||
|
self.read_ahead_behind(repo, &branch, &upstream);
|
||||||
|
|
||||||
|
match upstream.name() {
|
||||||
|
Ok(Some(name)) => name.to_string(),
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read ahead-behind information between the local and upstream branches
|
||||||
|
fn read_ahead_behind(&mut self, repo: &Repository, local: &Branch, upstream: &Branch) {
|
||||||
|
if let (Some(local), Some(upstream)) = (local.get().target(), upstream.get().target()) {
|
||||||
|
if let Ok((ahead, behind)) = repo.graph_ahead_behind(local, upstream) {
|
||||||
|
self.ahead = ahead as u16;
|
||||||
|
self.behind = behind as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl AddAssign for Stats {
|
||||||
|
// fn add_assign(&mut self, rhs: Self) {
|
||||||
|
// self.untracked += rhs.untracked;
|
||||||
|
// self.added_staged += rhs.added_staged;
|
||||||
|
// self.modified += rhs.modified;
|
||||||
|
// self.modified_staged += rhs.modified_staged;
|
||||||
|
// self.renamed += rhs.renamed;
|
||||||
|
// self.deleted += rhs.deleted;
|
||||||
|
// self.deleted_staged += rhs.deleted_staged;
|
||||||
|
// self.ahead += rhs.ahead;
|
||||||
|
// self.behind += rhs.behind;
|
||||||
|
// self.conflicts += rhs.conflicts;
|
||||||
|
// self.stashes += rhs.stashes;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Check the bits of a flag against the value to see if they are set
|
||||||
|
#[inline]
|
||||||
|
fn check<B>(val: B, flag: B) -> bool
|
||||||
|
where
|
||||||
|
B: BitAnd<Output = B> + PartialEq + Copy,
|
||||||
|
{
|
||||||
|
val & flag == flag
|
||||||
|
}
|
4
crates/nu_plugin_gstat/src/lib.rs
Normal file
4
crates/nu_plugin_gstat/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mod gstat;
|
||||||
|
mod nu;
|
||||||
|
|
||||||
|
pub use gstat::GStat;
|
6
crates/nu_plugin_gstat/src/main.rs
Normal file
6
crates/nu_plugin_gstat/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_gstat::GStat;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
serve_plugin(&mut GStat::new())
|
||||||
|
}
|
28
crates/nu_plugin_gstat/src/nu/mod.rs
Normal file
28
crates/nu_plugin_gstat/src/nu/mod.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use crate::GStat;
|
||||||
|
use nu_plugin::{EvaluatedCall, LabeledError, Plugin};
|
||||||
|
use nu_protocol::{Signature, Span, Spanned, SyntaxShape, Value};
|
||||||
|
|
||||||
|
impl Plugin for GStat {
|
||||||
|
fn signature(&self) -> Vec<Signature> {
|
||||||
|
vec![Signature::build("gstat")
|
||||||
|
.desc("Get the git status of a repo")
|
||||||
|
.optional("path", SyntaxShape::String, "path to repo")]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
call: &EvaluatedCall,
|
||||||
|
input: &Value,
|
||||||
|
) -> Result<Value, LabeledError> {
|
||||||
|
if name != "gstat" {
|
||||||
|
return Ok(Value::Nothing {
|
||||||
|
span: Span::unknown(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo_path: Option<Spanned<String>> = call.opt(0)?;
|
||||||
|
// eprintln!("input value: {:#?}", &input);
|
||||||
|
self.gstat(input, repo_path, &call.head)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user