Skip to content

Commit

Permalink
add: Unknown, Detached and NotPushed states + refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
Nukesor committed May 30, 2022
1 parent eb6ce1a commit ccf241c
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 64 deletions.
14 changes: 9 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ pub struct CliArguments {
#[clap(short, long, parse(from_occurrences))]
pub verbose: u8,

/// Don't run git commands in parallel
/// This is useful in combination with the verbose flag for debugging.
#[clap(short, long)]
pub not_parallel: bool,

#[clap(subcommand)]
pub cmd: SubCommand,
}
Expand Down Expand Up @@ -48,5 +43,14 @@ pub enum SubCommand {
/// Show all repositories and not only those that are somehow interesting
#[clap(short, long)]
all: bool,

/// Don't run repository checks in parallel
/// This is useful in combination with the verbose flag for debugging.
#[clap(short, long)]
not_parallel: bool,

/// The amount of threads that should run in parallel for checking repositories.
#[clap(short, long)]
threads: Option<usize>,
},
}
9 changes: 4 additions & 5 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ pub fn print_status(mut repo_infos: Vec<RepositoryInfo>, show_all: bool) -> Resu
if !show_all {
repo_infos = repo_infos
.into_iter()
.filter(|info| {
!matches!(info.state, RepositoryState::UpToDate)
|| info.stashed != 0
|| matches!(info.state, RepositoryState::LocalChanges)
})
.filter(|info| !matches!(info.state, RepositoryState::UpToDate) || info.stashed != 0)
.collect();
}

Expand Down Expand Up @@ -41,11 +37,14 @@ pub fn print_status(mut repo_infos: Vec<RepositoryInfo>, show_all: bool) -> Resu

pub fn format_state(state: &RepositoryState) -> Cell {
match state {
RepositoryState::Unknown => Cell::new("Unknown").fg(Color::Red),
RepositoryState::Detached => Cell::new("Detached HEAD").fg(Color::Yellow),
RepositoryState::Updated => Cell::new("Updated").fg(Color::Green),
RepositoryState::UpToDate => Cell::new("Up to date").fg(Color::DarkGreen),
RepositoryState::Fetched => Cell::new("Fetched").fg(Color::Yellow),
RepositoryState::NoFastForward => Cell::new("No fast forward").fg(Color::Red),
RepositoryState::LocalChanges => Cell::new("Local changes").fg(Color::Red),
RepositoryState::NotPushed => Cell::new("Unpushed commits").fg(Color::Yellow),
}
}

Expand Down
76 changes: 70 additions & 6 deletions src/git.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;

use anyhow::Result;
use log::info;
use log::{debug, info};

use crate::cmd;
use crate::process::*;
Expand All @@ -23,6 +23,10 @@ pub fn handle_repo(
}
update_repo(&mut repo_info, envs)?;

if matches!(repo_info.state, RepositoryState::UpToDate) {
check_unpushed_commits(&mut repo_info, envs)?;
}

Ok(repo_info)
}

Expand All @@ -47,7 +51,7 @@ pub fn get_stashed_entries(
.trim();

repo_info.stashed = number.parse::<usize>().expect("Couldn't get stash amount");
info!("Found {} stashed entries", repo_info.stashed);
info!("Found {} stashed entries!", repo_info.stashed);

Ok(())
}
Expand All @@ -69,7 +73,7 @@ pub fn check_local_changes(
}

repo_info.state = RepositoryState::LocalChanges;
info!("Found local changes");
info!("Found local changes!");

Ok(())
}
Expand All @@ -80,7 +84,7 @@ pub fn fetch_repo(repo_info: &mut RepositoryInfo, envs: &HashMap<String, String>
.env(envs.clone());
let capture_data = fetch.run()?;
if String::from_utf8_lossy(&capture_data.stdout).contains("Receiving objects: 100%") {
info!("Got new changes from remote");
info!("Got new changes from remote!");
repo_info.state = RepositoryState::Fetched;
} else {
info!("Everything is up to date");
Expand All @@ -100,15 +104,75 @@ pub fn update_repo(repo_info: &mut RepositoryInfo, envs: &HashMap<String, String
if stdout.contains("Updating") {
info!("Fast forward succeeded");
repo_info.state = RepositoryState::Updated;
} else if stdout.contains("Already up to date") {
} else if stdout.contains("up to date") {
info!("Already up to date");
repo_info.state = RepositoryState::UpToDate;
} else if stdout.contains("fatal:") {
info!("Fast forward not possible");
info!("Fast forward not possible!");
repo_info.state = RepositoryState::NoFastForward;
} else {
info!("Couldn't get state from output: {}", stdout);
repo_info.state = RepositoryState::Unknown;
}

Ok(())
}

/// Check whether the current branch has some commits that're newer than the remotes.
/// If the current HEAD isn't on a branch, the repository enters the `Detached` state.
pub fn check_unpushed_commits(
repo_info: &mut RepositoryInfo,
envs: &HashMap<String, String>,
) -> Result<()> {
// Get all remotes for this repository
let capture_data = cmd!("git remote")
.cwd(repo_info.path.clone())
.env(envs.clone())
.run()?;
let remotes = String::from_utf8_lossy(&capture_data.stdout);
let remotes = remotes.trim();

let capture_data = cmd!("git rev-parse --abbrev-ref HEAD")
.cwd(repo_info.path.clone())
.env(envs.clone())
.run()?;
let current_branch = String::from_utf8_lossy(&capture_data.stdout);
let current_branch = current_branch.trim();

// The repository is in a detached state. Return early.
if current_branch == "HEAD" {
repo_info.state = RepositoryState::Detached;
return Ok(());
}

// Get the hash of the local commit
let capture_data = cmd!("git rev-parse HEAD")
.cwd(repo_info.path.clone())
.env(envs.clone())
.run()?;
let local_hash = String::from_utf8_lossy(&capture_data.stdout);
let local_hash = local_hash.trim();

// Check if all remotes have been pushed.
for remote in remotes.lines() {
debug!("Checking {remote}/{current_branch}");
let capture_data = cmd!("git rev-parse {remote}/{current_branch}")
.cwd(repo_info.path.clone())
.env(envs.clone())
.run()?;
let remote_hash = String::from_utf8_lossy(&capture_data.stdout);
let remote_hash = remote_hash.trim();

// The hashes differ. Since the branch is already UpToDate at this state,
// this (most likely) means the rpeository has unpushed changes.
debug!("Local hash: {local_hash}, Remote: {remote_hash}");
if local_hash != remote_hash {
info!("Found unpushed commits!");
repo_info.state = RepositoryState::NotPushed;
return Ok(());
}
}

info!("No unpushed commits");
Ok(())
}
111 changes: 64 additions & 47 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::collections::HashMap;
use std::env::vars;
use std::time::Instant;
use std::{collections::HashMap, path::PathBuf};

use anyhow::Result;
use clap::Parser;
use indicatif::{ProgressBar, ProgressStyle};
use log::error;
use log::{debug, error};
use rayon::prelude::*;
use simplelog::{Config, LevelFilter, SimpleLogger};

Expand Down Expand Up @@ -36,48 +37,55 @@ fn main() -> Result<()> {

let mut state = State::load()?;

let show_all;
match opt.cmd {
SubCommand::Add { repos } => {
for path in repos {
// Check if the directory to add actually exists
if !path.exists() || !path.is_dir() {
error!("Cannot find repository at {:?}", path);
}

// Store the absolute path.
let real_path = std::fs::canonicalize(&path)?;
if !state.repositories.contains(&real_path) {
println!("Added repository: {:?}", &real_path);
state.repositories.push(real_path);
}
}
state.save()?;
return Ok(());
}
SubCommand::Watch { directories } => {
for path in directories {
// Check if the directory to add actually exists
if !path.exists() || !path.is_dir() {
error!("Cannot find directory at {:?}", path);
}

// Store the absolute path.
let real_path = std::fs::canonicalize(&path)?;
if !state.watched.contains(&real_path) {
println!("Watching folder: {:?}", &real_path);
state.watched.push(real_path);
}
}
SubCommand::Add { repos } => add(state, repos),
SubCommand::Watch { directories } => watch(state, directories),
SubCommand::Update {
all,
not_parallel,
threads,
} => {
state.scan()?;
return Ok(());
update(state, all, !not_parallel, threads)
}
SubCommand::Update { all } => {
state.scan()?;
show_all = all;
}
}

fn add(mut state: State, repos: Vec<PathBuf>) -> Result<()> {
for path in repos {
// Check if the directory to add actually exists
if !path.exists() || !path.is_dir() {
error!("Cannot find repository at {:?}", path);
}
};

// Store the absolute path.
let real_path = std::fs::canonicalize(&path)?;
if !state.repositories.contains(&real_path) {
println!("Added repository: {:?}", &real_path);
state.repositories.push(real_path);
}
}
state.save()
}

fn watch(mut state: State, directories: Vec<PathBuf>) -> Result<()> {
for path in directories {
// Check if the directory to add actually exists
if !path.exists() || !path.is_dir() {
error!("Cannot find directory at {:?}", path);
}

// Store the absolute path.
let real_path = std::fs::canonicalize(&path)?;
if !state.watched.contains(&real_path) {
println!("Watching folder: {:?}", &real_path);
state.watched.push(real_path);
}
}
state.scan()
}

fn update(state: State, show_all: bool, parallel: bool, threads: Option<usize>) -> Result<()> {
// We create a struct for our internal representation for each repository
let mut repo_infos: Vec<RepositoryInfo> = Vec::new();
for path in state.repositories.iter() {
Expand All @@ -92,28 +100,37 @@ fn main() -> Result<()> {
}

let mut results: Vec<Result<RepositoryInfo>>;
if opt.not_parallel {
results = Vec::new();
for repo_info in repo_infos.into_iter() {
results.push(handle_repo(repo_info, &envs));
}
} else {
if parallel {
// Set up the styling for the progress bar.
let style = ProgressStyle::default_bar().template("{msg}: {wide_bar} {pos}/{len}");
let bar = ProgressBar::new(repo_infos.len() as u64);

// Set the amount of threads, if specified.
if let Some(threads) = threads {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build_global()
.unwrap();
}

bar.set_style(style);
bar.set_message("Checking repositories");
results = repo_infos
.into_par_iter()
// Commend above and uncomment below for debug
//.into_iter()
.map(|repo_info| {
bar.inc(1);
handle_repo(repo_info, &envs)
})
.collect();

bar.finish_with_message("All done: ");
} else {
results = Vec::new();
for repo_info in repo_infos.into_iter() {
let start = Instant::now();
results.push(handle_repo(repo_info, &envs));
debug!("Check took {}ms", start.elapsed().as_millis());
}
}

let mut repo_infos = Vec::new();
Expand Down
5 changes: 4 additions & 1 deletion src/repository_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ use strum_macros::Display;

#[derive(Display)]
pub enum RepositoryState {
Unknown,
Detached,
UpToDate,
Fetched,
Updated,
NoFastForward,
LocalChanges,
NotPushed,
}

pub struct RepositoryInfo {
Expand All @@ -21,7 +24,7 @@ impl RepositoryInfo {
pub fn new(path: PathBuf) -> RepositoryInfo {
RepositoryInfo {
path,
state: RepositoryState::UpToDate,
state: RepositoryState::Unknown,
stashed: 0,
}
}
Expand Down

0 comments on commit ccf241c

Please sign in to comment.