#![allow(clippy::multiple_crate_versions)]
mod commit;
mod config;
mod git;
mod hooks;
mod interactive;
mod output;
mod status;
mod types;
mod workflow;
use colored::Colorize;
fn main() {
if let Err(e) = workflow::run() {
eprintln!("{}", format!("Error: {e:#}").red().bold());
std::process::exit(1);
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::commit::{is_conventional_commit, is_wip_commit};
use crate::git::GitRunner;
use crate::interactive::is_truthy;
use crate::status::{get_repo_status, parse_repo_status};
use crate::types::BlockingState;
struct MockGitRunner {
status_output: String,
}
impl GitRunner for MockGitRunner {
fn run(&self, _args: &[&str]) -> Result<()> {
Ok(())
}
fn run_output(&self, args: &[&str]) -> Result<String> {
if args.contains(&"status") && args.contains(&"--porcelain") {
return Ok(self.status_output.clone());
}
Ok(String::new())
}
fn run_output_full(&self, _args: &[&str]) -> Result<(String, String)> {
Ok((String::new(), String::new()))
}
fn run_status(&self, _args: &[&str]) -> Result<bool> {
Ok(true)
}
}
#[test]
fn test_parse_repo_status_empty() {
let status = parse_repo_status("");
assert!(!status.has_any_changes());
assert_eq!(status.total_files(), 0);
}
#[test]
fn test_parse_repo_status_staged() {
let output = "M src/main.rs\nA src/lib.rs";
let status = parse_repo_status(output);
assert!(status.has_staged());
assert!(!status.has_unstaged());
assert!(!status.has_untracked());
assert_eq!(status.staged.len(), 2);
}
#[test]
fn test_parse_repo_status_unstaged() {
let output = " M src/main.rs\n D src/lib.rs";
let status = parse_repo_status(output);
assert!(!status.has_staged());
assert!(status.has_unstaged());
assert!(!status.has_untracked());
assert_eq!(status.unstaged.len(), 2);
}
#[test]
fn test_parse_repo_status_untracked() {
let output = "?? new_file.rs\n?? another.txt";
let status = parse_repo_status(output);
assert!(!status.has_staged());
assert!(!status.has_unstaged());
assert!(status.has_untracked());
assert_eq!(status.untracked.len(), 2);
}
#[test]
fn test_parse_repo_status_mixed() {
let output = "M staged.rs\n M unstaged.rs\n?? untracked.rs";
let status = parse_repo_status(output);
assert!(status.has_staged());
assert!(status.has_unstaged());
assert!(status.has_untracked());
assert_eq!(status.total_files(), 3);
}
#[test]
fn test_get_repo_status() {
let mock = MockGitRunner {
status_output: "M src/main.rs\n?? test.txt".to_string(),
};
let status = get_repo_status(&mock).unwrap();
assert!(status.has_staged());
assert!(status.has_untracked());
assert_eq!(status.total_files(), 2);
}
#[test]
fn test_is_conventional_commit() {
assert!(is_conventional_commit("feat: add new feature"));
assert!(is_conventional_commit("fix: resolve bug"));
assert!(is_conventional_commit("docs: update readme"));
assert!(is_conventional_commit("feat(scope): add feature"));
assert!(!is_conventional_commit("random commit message"));
assert!(!is_conventional_commit("Feature: new thing"));
}
#[test]
fn test_is_wip_commit() {
assert!(is_wip_commit("WIP: working on feature"));
assert!(is_wip_commit("wip something"));
assert!(is_wip_commit("work in progress"));
assert!(is_wip_commit("fixup! previous commit"));
assert!(!is_wip_commit("feat: complete feature"));
assert!(!is_wip_commit("fix: resolve issue"));
}
#[test]
fn test_is_truthy() {
assert!(is_truthy("1"));
assert!(is_truthy("true"));
assert!(is_truthy("TRUE"));
assert!(is_truthy("yes"));
assert!(is_truthy("YES"));
assert!(is_truthy("on"));
assert!(!is_truthy("0"));
assert!(!is_truthy("false"));
assert!(!is_truthy("no"));
assert!(!is_truthy(""));
}
#[test]
fn test_blocking_state_display() {
assert_eq!(format!("{}", BlockingState::Rebase), "Rebase");
assert_eq!(format!("{}", BlockingState::Merge), "Merge");
assert_eq!(format!("{}", BlockingState::Conflicts), "Merge conflicts");
}
}