Add status command
This commit is contained in:
172
Cargo.lock
generated
172
Cargo.lock
generated
@@ -80,6 +80,18 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "comfy-table"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c42350b81f044f576ff88ac750419f914abb46a03831bb1747134344ee7a4e64"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@@ -95,6 +107,31 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-next"
|
name = "dirs-next"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -148,6 +185,7 @@ name = "git-repo-manager"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"comfy-table",
|
||||||
"console",
|
"console",
|
||||||
"git2",
|
"git2",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -216,6 +254,15 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
@@ -277,6 +324,15 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -298,6 +354,37 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.7.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"ntapi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -332,6 +419,31 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"instant",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -422,6 +534,12 @@ version = "0.6.25"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.130"
|
version = "1.0.130"
|
||||||
@@ -451,12 +569,66 @@ dependencies = [
|
|||||||
"dirs-next",
|
"dirs-next",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.81"
|
version = "1.0.81"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "git-repo-manager"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"Hannes Körber <hannes@hkoerber.de>",
|
"Hannes Körber <hannes@hkoerber.de>",
|
||||||
]
|
]
|
||||||
description = """
|
description = """
|
||||||
Manage multiple git repositories.
|
Manage multiple git repositories.
|
||||||
@@ -46,3 +46,6 @@ version = "0.15.0"
|
|||||||
|
|
||||||
[dependencies.regex]
|
[dependencies.regex]
|
||||||
version = "1.5"
|
version = "1.5"
|
||||||
|
|
||||||
|
[dependencies.comfy-table]
|
||||||
|
version = "5.0"
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -53,6 +53,32 @@ $ grm find ~/your/project/root > config.toml
|
|||||||
|
|
||||||
This will detect all repositories and remotes and write them to `config.toml`.
|
This will detect all repositories and remotes and write them to `config.toml`.
|
||||||
|
|
||||||
|
### Show the state of your projects
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm status --config example.config.toml
|
||||||
|
+------------------+------------+----------------------------------+--------+---------+
|
||||||
|
| Repo | Status | Branches | HEAD | Remotes |
|
||||||
|
+=====================================================================================+
|
||||||
|
| git-repo-manager | | branch: master <origin/master> ✔ | master | github |
|
||||||
|
| | | | | origin |
|
||||||
|
|------------------+------------+----------------------------------+--------+---------|
|
||||||
|
| dotfiles | No changes | branch: master <origin/master> ✔ | master | origin |
|
||||||
|
+------------------+------------+----------------------------------+--------+---------+
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `status` without `--config` to check the current directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd ./dotfiles
|
||||||
|
$ grm status
|
||||||
|
+----------+------------+----------------------------------+--------+---------+
|
||||||
|
| Repo | Status | Branches | HEAD | Remotes |
|
||||||
|
+=============================================================================+
|
||||||
|
| dotfiles | No changes | branch: master <origin/master> ✔ | master | origin |
|
||||||
|
+----------+------------+----------------------------------+--------+---------+
|
||||||
|
```
|
||||||
|
|
||||||
# Why?
|
# Why?
|
||||||
|
|
||||||
I have a **lot** of repositories on my machines. My own stuff, forks, quick
|
I have a **lot** of repositories on my machines. My own stuff, forks, quick
|
||||||
|
|||||||
13
src/cmd.rs
13
src/cmd.rs
@@ -26,6 +26,8 @@ pub enum SubCommand {
|
|||||||
Sync(Sync),
|
Sync(Sync),
|
||||||
#[clap(about = "Generate a repository configuration from an existing file tree")]
|
#[clap(about = "Generate a repository configuration from an existing file tree")]
|
||||||
Find(Find),
|
Find(Find),
|
||||||
|
#[clap(about = "Show status of configured repositories")]
|
||||||
|
Status(OptionalConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@@ -40,6 +42,17 @@ pub struct Sync {
|
|||||||
pub config: String,
|
pub config: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[clap()]
|
||||||
|
pub struct OptionalConfig {
|
||||||
|
#[clap(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
about = "Path to the configuration file"
|
||||||
|
)]
|
||||||
|
pub config: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct Find {
|
pub struct Find {
|
||||||
#[clap(about = "The path to search through")]
|
#[clap(about = "The path to search through")]
|
||||||
|
|||||||
163
src/lib.rs
163
src/lib.rs
@@ -10,7 +10,12 @@ mod repo;
|
|||||||
use config::{Config, Tree};
|
use config::{Config, Tree};
|
||||||
use output::*;
|
use output::*;
|
||||||
|
|
||||||
use repo::{clone_repo, detect_remote_type, init_repo, open_repo, Remote, Repo};
|
use comfy_table::{Table, Cell};
|
||||||
|
|
||||||
|
use repo::{
|
||||||
|
clone_repo, detect_remote_type, get_repo_status, init_repo, open_repo, Remote, Repo,
|
||||||
|
RepoErrorKind, RemoteTrackingStatus
|
||||||
|
};
|
||||||
|
|
||||||
fn path_as_string(path: &Path) -> String {
|
fn path_as_string(path: &Path) -> String {
|
||||||
path.to_path_buf().into_os_string().into_string().unwrap()
|
path.to_path_buf().into_os_string().into_string().unwrap()
|
||||||
@@ -358,6 +363,137 @@ fn find_in_tree(path: &Path) -> Option<Tree> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_table_header(table: &mut Table) {
|
||||||
|
table
|
||||||
|
.load_preset(comfy_table::presets::UTF8_FULL)
|
||||||
|
.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS)
|
||||||
|
.set_header(vec![
|
||||||
|
Cell::new("Repo"),
|
||||||
|
Cell::new("Status"),
|
||||||
|
Cell::new("Branches"),
|
||||||
|
Cell::new("HEAD"),
|
||||||
|
Cell::new("Remotes"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_repo_status(table: &mut Table, repo_name: &String, repo_handle: &git2::Repository) {
|
||||||
|
let repo_status = get_repo_status(repo_handle);
|
||||||
|
|
||||||
|
table.add_row(vec![
|
||||||
|
repo_name,
|
||||||
|
&match repo_status.changes {
|
||||||
|
Some(changes) => {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if changes.files_new > 0 {
|
||||||
|
out.push(format!("New: {}\n", changes.files_new))
|
||||||
|
}
|
||||||
|
if changes.files_modified > 0 {
|
||||||
|
out.push(format!("Modified: {}\n", changes.files_modified))
|
||||||
|
}
|
||||||
|
if changes.files_deleted > 0 {
|
||||||
|
out.push(format!("Deleted: {}\n", changes.files_deleted))
|
||||||
|
}
|
||||||
|
out.into_iter().collect::<String>().trim().to_string()
|
||||||
|
},
|
||||||
|
None => String::from("No changes"),
|
||||||
|
},
|
||||||
|
&repo_status.branches.iter().map(|(branch_name, remote_branch)| {
|
||||||
|
format!("branch: {}{}\n",
|
||||||
|
&branch_name,
|
||||||
|
&match remote_branch {
|
||||||
|
None => String::from(" <!local>"),
|
||||||
|
Some((remote_branch_name, remote_tracking_status)) => {
|
||||||
|
format!(" <{}>{}",
|
||||||
|
remote_branch_name,
|
||||||
|
&match remote_tracking_status {
|
||||||
|
RemoteTrackingStatus::UpToDate => String::from(" \u{2714}"),
|
||||||
|
RemoteTrackingStatus::Ahead(d) => format!(" [+{}]", &d),
|
||||||
|
RemoteTrackingStatus::Behind(d) => format!(" [-{}]", &d),
|
||||||
|
RemoteTrackingStatus::Diverged(d1, d2) => format!(" [-{}/+{}]", &d1,&d2),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}).collect::<String>().trim().to_string(),
|
||||||
|
&match repo_status.head {
|
||||||
|
Some(head) => head,
|
||||||
|
None => String::from("Empty"),
|
||||||
|
},
|
||||||
|
&repo_status.remotes.iter().map(|r| format!("{}\n", r)).collect::<String>().trim().to_string(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_single_repo_status(path: &PathBuf) {
|
||||||
|
let mut table = Table::new();
|
||||||
|
add_table_header(&mut table);
|
||||||
|
|
||||||
|
let repo_handle = open_repo(path);
|
||||||
|
|
||||||
|
if let Err(error) = repo_handle {
|
||||||
|
if error.kind == RepoErrorKind::NotFound {
|
||||||
|
print_error(&"Directory is not a git directory".to_string());
|
||||||
|
} else {
|
||||||
|
print_error(&format!("Opening repository failed: {}", error));
|
||||||
|
}
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
let repo_name = match path.file_name() {
|
||||||
|
None => {
|
||||||
|
print_warning("Cannot detect repo name. Are you working in /?");
|
||||||
|
String::from("unknown")
|
||||||
|
},
|
||||||
|
Some(file_name) => match file_name.to_str() {
|
||||||
|
None => {
|
||||||
|
print_warning("Name of current directory is not valid UTF-8");
|
||||||
|
String::from("invalid")
|
||||||
|
},
|
||||||
|
Some(name) => name.to_string(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add_repo_status(&mut table, &repo_name, &repo_handle.unwrap());
|
||||||
|
|
||||||
|
println!("{}", table);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_status(config: Config) {
|
||||||
|
for tree in config.trees {
|
||||||
|
let repos = tree.repos.unwrap_or_default();
|
||||||
|
|
||||||
|
let root_path = expand_path(Path::new(&tree.root));
|
||||||
|
|
||||||
|
let mut table = Table::new();
|
||||||
|
add_table_header(&mut table);
|
||||||
|
|
||||||
|
for repo in &repos {
|
||||||
|
let repo_path = root_path.join(&repo.name);
|
||||||
|
|
||||||
|
if !repo_path.exists() {
|
||||||
|
print_repo_error(&repo.name, &"Repository does not exist. Run sync?".to_string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo_handle = open_repo(&repo_path);
|
||||||
|
|
||||||
|
if let Err(error) = repo_handle {
|
||||||
|
if error.kind == RepoErrorKind::NotFound {
|
||||||
|
print_repo_error(&repo.name, &"No git repository found. Run sync?".to_string());
|
||||||
|
} else {
|
||||||
|
print_repo_error(&repo.name, &format!("Opening repository failed: {}", error));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let repo_handle = repo_handle.unwrap();
|
||||||
|
|
||||||
|
add_repo_status(&mut table, &repo.name, &repo_handle);
|
||||||
|
}
|
||||||
|
println!("{}", table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let opts = cmd::parse();
|
let opts = cmd::parse();
|
||||||
|
|
||||||
@@ -372,6 +508,31 @@ pub fn run() {
|
|||||||
};
|
};
|
||||||
sync_trees(config);
|
sync_trees(config);
|
||||||
}
|
}
|
||||||
|
cmd::SubCommand::Status(args) => {
|
||||||
|
match &args.config {
|
||||||
|
Some(config_path) => {
|
||||||
|
let config = match config::read_config(config_path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(&e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
show_status(config);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let dir = match std::env::current_dir(){
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(&format!("Could not open current directory: {}", e));
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
show_single_repo_status(&dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
cmd::SubCommand::Find(find) => {
|
cmd::SubCommand::Find(find) => {
|
||||||
let path = Path::new(&find.path);
|
let path = Path::new(&find.path);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
|
|||||||
165
src/repo.rs
165
src/repo.rs
@@ -53,6 +53,44 @@ pub struct Repo {
|
|||||||
pub remotes: Option<Vec<Remote>>,
|
pub remotes: Option<Vec<Remote>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RepoChanges {
|
||||||
|
pub files_new: usize,
|
||||||
|
pub files_modified: usize,
|
||||||
|
pub files_deleted: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SubmoduleStatus {
|
||||||
|
Clean,
|
||||||
|
Uninitialized,
|
||||||
|
Changed,
|
||||||
|
OutOfDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RemoteTrackingStatus {
|
||||||
|
UpToDate,
|
||||||
|
Ahead(usize),
|
||||||
|
Behind(usize),
|
||||||
|
Diverged(usize, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RepoStatus {
|
||||||
|
pub operation: Option<git2::RepositoryState>,
|
||||||
|
|
||||||
|
pub empty: bool,
|
||||||
|
|
||||||
|
pub remotes: Vec<String>,
|
||||||
|
|
||||||
|
pub head: Option<String>,
|
||||||
|
|
||||||
|
pub changes: Option<RepoChanges>,
|
||||||
|
|
||||||
|
pub worktrees: usize,
|
||||||
|
|
||||||
|
pub submodules: Vec<(String, SubmoduleStatus)>,
|
||||||
|
|
||||||
|
pub branches: Vec<(String, Option<(String, RemoteTrackingStatus)>)>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -178,3 +216,130 @@ pub fn clone_repo(remote: &Remote, path: &Path) -> Result<(), Box<dyn std::error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_repo_status(repo: &git2::Repository) -> RepoStatus {
|
||||||
|
let operation = match repo.state() {
|
||||||
|
git2::RepositoryState::Clean => None,
|
||||||
|
state => Some(state),
|
||||||
|
};
|
||||||
|
|
||||||
|
let empty = repo.is_empty().unwrap();
|
||||||
|
|
||||||
|
let remotes = repo
|
||||||
|
.remotes()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|repo_name| repo_name.unwrap().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let head = match empty {
|
||||||
|
true => None,
|
||||||
|
false => Some(repo.head().unwrap().shorthand().unwrap().to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let statuses = repo.statuses(None).unwrap();
|
||||||
|
|
||||||
|
let changes = match statuses.is_empty() {
|
||||||
|
true => None,
|
||||||
|
false => {
|
||||||
|
let mut files_new = 0;
|
||||||
|
let mut files_modified = 0;
|
||||||
|
let mut files_deleted = 0;
|
||||||
|
for status in statuses.iter() {
|
||||||
|
let status_bits = status.status();
|
||||||
|
if status_bits.intersects(
|
||||||
|
git2::Status::INDEX_MODIFIED
|
||||||
|
| git2::Status::INDEX_RENAMED
|
||||||
|
| git2::Status::INDEX_TYPECHANGE
|
||||||
|
| git2::Status::WT_MODIFIED
|
||||||
|
| git2::Status::WT_RENAMED
|
||||||
|
| git2::Status::WT_TYPECHANGE,
|
||||||
|
) {
|
||||||
|
files_modified += 1;
|
||||||
|
} else if status_bits.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) {
|
||||||
|
files_new += 1;
|
||||||
|
} else if status_bits
|
||||||
|
.intersects(git2::Status::INDEX_DELETED | git2::Status::WT_DELETED)
|
||||||
|
{
|
||||||
|
files_deleted += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(RepoChanges {
|
||||||
|
files_new,
|
||||||
|
files_modified,
|
||||||
|
files_deleted,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let worktrees = repo.worktrees().unwrap().len();
|
||||||
|
|
||||||
|
let mut submodules = Vec::new();
|
||||||
|
for submodule in repo.submodules().unwrap() {
|
||||||
|
let submodule_name = submodule.name().unwrap().to_string();
|
||||||
|
|
||||||
|
let submodule_status;
|
||||||
|
let status = repo
|
||||||
|
.submodule_status(submodule.name().unwrap(), git2::SubmoduleIgnore::None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if status.intersects(
|
||||||
|
git2::SubmoduleStatus::WD_INDEX_MODIFIED
|
||||||
|
| git2::SubmoduleStatus::WD_WD_MODIFIED
|
||||||
|
| git2::SubmoduleStatus::WD_UNTRACKED,
|
||||||
|
) {
|
||||||
|
submodule_status = SubmoduleStatus::Changed;
|
||||||
|
} else if status.is_wd_uninitialized() {
|
||||||
|
submodule_status = SubmoduleStatus::Uninitialized;
|
||||||
|
} else if status.is_wd_modified() {
|
||||||
|
submodule_status = SubmoduleStatus::OutOfDate;
|
||||||
|
} else {
|
||||||
|
submodule_status = SubmoduleStatus::Clean;
|
||||||
|
}
|
||||||
|
|
||||||
|
submodules.push((submodule_name, submodule_status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut branches = Vec::new();
|
||||||
|
for (local_branch, _) in repo
|
||||||
|
.branches(Some(git2::BranchType::Local))
|
||||||
|
.unwrap()
|
||||||
|
.map(|branch_name| branch_name.unwrap())
|
||||||
|
{
|
||||||
|
let branch_name = local_branch.name().unwrap().unwrap().to_string();
|
||||||
|
let remote_branch = match local_branch.upstream() {
|
||||||
|
Ok(remote_branch) => {
|
||||||
|
let remote_branch_name = remote_branch.name().unwrap().unwrap().to_string();
|
||||||
|
|
||||||
|
let (ahead, behind) = repo
|
||||||
|
.graph_ahead_behind(
|
||||||
|
local_branch.get().peel_to_commit().unwrap().id(),
|
||||||
|
remote_branch.get().peel_to_commit().unwrap().id(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let remote_tracking_status = match (ahead, behind) {
|
||||||
|
(0, 0) => RemoteTrackingStatus::UpToDate,
|
||||||
|
(0, d) => RemoteTrackingStatus::Behind(d),
|
||||||
|
(d, 0) => RemoteTrackingStatus::Ahead(d),
|
||||||
|
(d1, d2) => RemoteTrackingStatus::Diverged(d1, d2),
|
||||||
|
};
|
||||||
|
Some((remote_branch_name, remote_tracking_status))
|
||||||
|
}
|
||||||
|
// Err => no remote branch
|
||||||
|
Err(_) => None,
|
||||||
|
};
|
||||||
|
branches.push((branch_name, remote_branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoStatus {
|
||||||
|
operation,
|
||||||
|
empty,
|
||||||
|
remotes,
|
||||||
|
head,
|
||||||
|
changes,
|
||||||
|
worktrees,
|
||||||
|
submodules,
|
||||||
|
branches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user