main.rs (7774B)
1 use dialoguer::{theme::ColorfulTheme, Select, Input}; 2 use std::process::{Command, Output}; 3 use std::env; 4 #[cfg(all(test, unix))] 5 use std::os::unix::process::ExitStatusExt; 6 #[cfg(all(test, windows))] 7 use std::os::windows::process::ExitStatusExt; 8 9 /// Main entry point for UniShell - Git Operations Terminal 10 fn main() { 11 loop { 12 println!("Welcome to UniShell - Git Operations Terminal"); 13 14 let options = vec![ 15 "Status", 16 "Add Files", 17 "Commit Changes", 18 "Push Changes", 19 "Pull Changes", 20 "Switch Branch", 21 "Initialize Repository", 22 "Clone Repository", 23 "Pull Specific Folder", 24 "Include Submodules", 25 "Remove Folder", 26 "Restore Folder", 27 "Exit", 28 ]; 29 30 let selection = Select::with_theme(&ColorfulTheme::default()) 31 .with_prompt("Choose a Git operation") 32 .items(&options) 33 .default(0) 34 .interact() 35 .unwrap(); 36 37 match selection { 38 0 => git_status(), 39 1 => git_add(), 40 2 => git_commit(), 41 3 => git_push(), 42 4 => git_pull(), 43 5 => git_switch_branch(), 44 6 => git_init(), 45 7 => git_clone(), 46 8 => git_pull_folder(), 47 9 => git_include_submodules(), 48 10 => git_remove_folder(), 49 11 => git_restore_folder(), 50 12 => { 51 println!("Exiting UniShell. Goodbye!"); 52 break; 53 } 54 _ => unreachable!(), 55 } 56 } 57 } 58 59 /// Runs `git status` and prints the output 60 fn git_status() { 61 println!("Running `git status`..."); 62 let output = Command::new("git") 63 .arg("status") 64 .output() 65 .expect("Failed to execute git status"); 66 println!("{}", String::from_utf8_lossy(&output.stdout)); 67 } 68 69 /// Returns the executable used for git operations (overridden in tests) 70 fn git_bin() -> String { 71 env::var("GIT_BIN").unwrap_or_else(|_| "git".to_string()) 72 } 73 74 /// Adds specified files to the staging area and returns the command output 75 fn git_add_files(files: &str) -> Output { 76 println!("Running `git add {}`...", files); 77 Command::new(git_bin()) 78 .arg("add") 79 .arg(files) 80 .output() 81 .expect("Failed to execute git add") 82 } 83 84 /// Commits with the provided message and returns the command output 85 fn git_commit_message(message: &str) -> Output { 86 println!("Running `git commit -m \"{}\"`...", message); 87 Command::new(git_bin()) 88 .arg("commit") 89 .arg("-m") 90 .arg(message) 91 .output() 92 .expect("Failed to execute git commit") 93 } 94 95 /// Adds files to the staging area 96 fn git_add() { 97 let files: String = Input::new() 98 .with_prompt("Enter files to add (use '.' to add all)") 99 .interact_text() 100 .unwrap(); 101 102 git_add_files(&files); 103 } 104 105 /// Commits changes with a message 106 fn git_commit() { 107 let message: String = Input::new() 108 .with_prompt("Enter commit message") 109 .interact_text() 110 .unwrap(); 111 112 git_commit_message(&message); 113 } 114 115 /// Pushes changes to the remote repository 116 fn git_push() { 117 println!("Running `git push`..."); 118 Command::new("git") 119 .arg("push") 120 .status() 121 .expect("Failed to execute git push"); 122 } 123 124 /// Pulls changes from the remote repository 125 fn git_pull() { 126 println!("Running `git pull`..."); 127 Command::new("git") 128 .arg("pull") 129 .status() 130 .expect("Failed to execute git pull"); 131 } 132 133 /// Switches to a specified branch 134 fn git_switch_branch() { 135 let branch: String = Input::new() 136 .with_prompt("Enter branch name to switch to") 137 .interact_text() 138 .unwrap(); 139 140 println!("Running `git checkout {}`...", branch); 141 Command::new("git") 142 .arg("checkout") 143 .arg(branch) 144 .status() 145 .expect("Failed to execute git checkout"); 146 } 147 148 /// Initializes a new Git repository 149 fn git_init() { 150 println!("Running `git init`..."); 151 Command::new("git") 152 .arg("init") 153 .status() 154 .expect("Failed to execute git init"); 155 } 156 157 /// Clones a repository from a given URL 158 fn git_clone() { 159 let repo_url: String = Input::new() 160 .with_prompt("Enter repository URL to clone") 161 .interact_text() 162 .unwrap(); 163 164 println!("Running `git clone {}`...", repo_url); 165 Command::new("git") 166 .arg("clone") 167 .arg(repo_url) 168 .status() 169 .expect("Failed to execute git clone"); 170 } 171 172 /// Pulls a specific folder using sparse-checkout 173 fn git_pull_folder() { 174 let folder: String = Input::new() 175 .with_prompt("Enter folder path to pull") 176 .interact_text() 177 .unwrap(); 178 179 println!("Running `git sparse-checkout set {}` and pulling...", folder); 180 Command::new("git") 181 .arg("sparse-checkout") 182 .arg("set") 183 .arg(&folder) 184 .status() 185 .expect("Failed to set sparse-checkout folder"); 186 187 Command::new("git") 188 .arg("pull") 189 .status() 190 .expect("Failed to pull specific folder"); 191 } 192 193 /// Includes submodules in the repository 194 fn git_include_submodules() { 195 println!("Running `git submodule update --init --recursive`..."); 196 Command::new("git") 197 .arg("submodule") 198 .arg("update") 199 .arg("--init") 200 .arg("--recursive") 201 .status() 202 .expect("Failed to include submodules"); 203 } 204 205 /// Removes a folder from the repository 206 fn git_remove_folder() { 207 let folder: String = Input::new() 208 .with_prompt("Enter folder path to remove") 209 .interact_text() 210 .unwrap(); 211 212 println!("Removing folder `{}`...", folder); 213 Command::new("rm") 214 .arg("-rf") 215 .arg(&folder) 216 .status() 217 .expect("Failed to remove folder"); 218 } 219 220 /// Restores a folder to its previous state 221 fn git_restore_folder() { 222 let folder: String = Input::new() 223 .with_prompt("Enter folder path to restore") 224 .interact_text() 225 .unwrap(); 226 227 println!("Restoring folder `{}`...", folder); 228 Command::new("git") 229 .arg("checkout") 230 .arg("--") 231 .arg(&folder) 232 .status() 233 .expect("Failed to restore folder"); 234 } 235 236 #[cfg(test)] 237 mod tests { 238 use super::*; 239 240 fn mock_command_output(stdout: &str) -> Output { 241 Output { 242 status: std::process::ExitStatus::from_raw(0), 243 stdout: stdout.as_bytes().to_vec(), 244 stderr: Vec::new(), 245 } 246 } 247 248 #[test] 249 fn test_git_status() { 250 let output = mock_command_output("On branch main\nYour branch is up to date."); 251 assert_eq!( 252 String::from_utf8_lossy(&output.stdout), 253 "On branch main\nYour branch is up to date." 254 ); 255 } 256 257 #[test] 258 fn test_git_add() { 259 unsafe { std::env::set_var("GIT_BIN", "echo") }; 260 let output = git_add_files("test_file.txt"); 261 assert_eq!( 262 String::from_utf8_lossy(&output.stdout).trim(), 263 "add test_file.txt" 264 ); 265 unsafe { std::env::remove_var("GIT_BIN") }; 266 } 267 268 #[test] 269 fn test_git_commit() { 270 unsafe { std::env::set_var("GIT_BIN", "echo") }; 271 let output = git_commit_message("Initial commit"); 272 assert_eq!( 273 String::from_utf8_lossy(&output.stdout).trim(), 274 "commit -m Initial commit" 275 ); 276 unsafe { std::env::remove_var("GIT_BIN") }; 277 } 278 279 #[test] 280 fn test_git_push() { 281 // Mocking command execution 282 let output = mock_command_output("Everything up-to-date"); 283 assert_eq!( 284 String::from_utf8_lossy(&output.stdout), 285 "Everything up-to-date" 286 ); 287 } 288 }