use std::env; use std::ffi::OsString; use std::path::{Path, PathBuf}; pub struct ParsedArgv { pub effective_cwd: PathBuf, pub has_git_dir_or_work_tree: bool, pub subcommand: Option, pub is_version_query: bool, } const BOOL_TOP_FLAGS: &[&str] = &[ "-p", "-P", "--paginate", "--no-pager", "--bare", "--no-replace-objects", "--html-path", "--man-path", "--info-path", ]; pub fn parse_top_level(argv: &[OsString]) -> ParsedArgv { let mut effective_cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let mut has_git_dir_or_work_tree = false; let mut subcommand: Option = None; let mut is_version_query = false; let mut i = 0; while i < argv.len() { let arg = &argv[i]; if arg == "-C" { if let Some(p) = argv.get(i + 1) { effective_cwd = effective_cwd.join(Path::new(p)); i += 2; continue; } break; } if arg == "-c" { i += 2; continue; } if arg == "--git-dir" || arg == "--work-tree" { has_git_dir_or_work_tree = true; i += 2; continue; } let Some(s) = arg.to_str() else { break }; if s.starts_with("--git-dir=") || s.starts_with("--work-tree=") { has_git_dir_or_work_tree = true; i += 1; continue; } if s.starts_with("--namespace=") || s.starts_with("--config-env=") || s.starts_with("--exec-path=") || s == "--exec-path" { i += 1; continue; } if BOOL_TOP_FLAGS.contains(&s) { i += 1; continue; } if s == "--version" || s == "-v" { is_version_query = true; i += 1; continue; } if s == "--help" || s == "-h" { subcommand = Some("help".to_string()); break; } if !s.starts_with('-') { subcommand = Some(s.to_string()); break; } // Unknown leading flag — bail out; it presumably belongs to a subcommand // we couldn't identify. break; } ParsedArgv { effective_cwd, has_git_dir_or_work_tree, subcommand, is_version_query, } } #[cfg(test)] mod tests { use super::*; fn parse(args: &[&str]) -> ParsedArgv { let owned: Vec = args.iter().map(OsString::from).collect(); parse_top_level(&owned) } #[test] fn bare_subcommand() { let p = parse(&["status"]); assert_eq!(p.subcommand.as_deref(), Some("status")); assert!(!p.is_version_query); assert!(!p.has_git_dir_or_work_tree); } #[test] fn version_query() { let p = parse(&["--version"]); assert!(p.is_version_query); let p = parse(&["-v"]); assert!(p.is_version_query); } #[test] fn dash_capital_c_changes_cwd() { let p = parse(&["-C", "/tmp", "status"]); assert_eq!(p.subcommand.as_deref(), Some("status")); assert_eq!(p.effective_cwd, PathBuf::from("/tmp")); } #[test] fn git_dir_forces_passthrough_flag() { let p = parse(&["--git-dir=/foo", "status"]); assert!(p.has_git_dir_or_work_tree); let p = parse(&["--work-tree", "/foo", "status"]); assert!(p.has_git_dir_or_work_tree); } #[test] fn config_kv_consumes_two_args() { let p = parse(&["-c", "user.name=Foo", "log"]); assert_eq!(p.subcommand.as_deref(), Some("log")); } #[test] fn paginate_skipped() { let p = parse(&["--paginate", "log"]); assert_eq!(p.subcommand.as_deref(), Some("log")); } #[test] fn help_short_and_long() { assert_eq!(parse(&["--help"]).subcommand.as_deref(), Some("help")); assert_eq!(parse(&["-h"]).subcommand.as_deref(), Some("help")); } }