Opinionated Android 15+ Linux Terminal Setup
android linux command-line-tools

add support for git branch/commit hash/tags when specifying remote configuration/dotfile repo

Changed files
+148 -46
src
+14
src/apply.rs
··· 6 6 use crate::{ 7 7 command::{run_command, run_command_without_local_path}, 8 8 config::SshConfig, 9 + git::extract_version, 9 10 }; 10 11 11 12 #[derive(Debug)] ··· 419 420 repo.to_string() 420 421 }; 421 422 423 + let (repo, version) = extract_version(&repo); 424 + 422 425 let home = dirs::home_dir().ok_or_else(|| Error::msg("Failed to get home directory"))?; 423 426 424 427 if !Path::new(&home.join(".dotfiles")).exists() { 425 428 run_command("bash", &["-c", &format!("git clone {} ~/.dotfiles", repo)]) 426 429 .context("Failed to clone dotfiles repository")?; 427 430 } else { 431 + run_command("bash", &["-c", "git -C ~/.dotfiles pull"]) 432 + .context("Failed to update dotfiles repository")?; 433 + } 434 + 435 + if let Some(version) = version { 436 + run_command("bash", &["-c", "git -C ~/.dotfiles fetch --all"])?; 437 + run_command( 438 + "bash", 439 + &["-c", &format!("git -C ~/.dotfiles checkout {}", version)], 440 + ) 441 + .context("Failed to checkout dotfiles version")?; 428 442 run_command("bash", &["-c", "git -C ~/.dotfiles pull"]) 429 443 .context("Failed to update dotfiles repository")?; 430 444 }
+73 -45
src/cmd/setup.rs
··· 2 2 3 3 use anyhow::{Context, Error}; 4 4 use owo_colors::OwoColorize; 5 - use url::Url; 6 5 7 6 use crate::{ 8 - command::run_command, config::Configuration, consts::CONFIG_FILE, diff::compare_configurations, 7 + command::run_command, 8 + config::Configuration, 9 + consts::CONFIG_FILE, 10 + diff::compare_configurations, 11 + git::{extract_repo_name, extract_version}, 9 12 }; 10 13 11 14 pub fn setup(dry_run: bool, no_confirm: bool, config_path: &str) -> Result<(), Error> { 12 15 let mut cfg = Configuration::default(); 13 16 14 17 let repo_url = parse_config_path(config_path)?; 18 + let (repo_url, version) = match repo_url.starts_with("https://") { 19 + true => extract_version(&repo_url), 20 + false => (repo_url, None), 21 + }; 15 22 16 - let toml_config = match clone_repo(&repo_url) { 23 + let toml_config = match clone_repo(&repo_url, version) { 17 24 Ok(toml_config) => toml_config, 18 25 Err(err) => { 19 26 if !repo_url.starts_with("https://") { ··· 92 99 fn parse_config_path(config_path: &str) -> Result<String, Error> { 93 100 if config_path.starts_with("github:") { 94 101 let repo = &config_path["github:".len()..]; 95 - return Ok(format!("https://github.com/{}.git", repo)); 102 + return Ok(format!("https://github.com/{}", repo)); 96 103 } 97 104 98 105 if config_path.starts_with("tangled:") { ··· 103 110 Ok(config_path.to_string()) 104 111 } 105 112 106 - fn clone_repo(repo_url: &str) -> Result<String, Error> { 113 + fn clone_repo(repo_url: &str, version: Option<String>) -> Result<String, Error> { 107 114 if !repo_url.starts_with("https://") { 108 115 return Err(anyhow::anyhow!( 109 116 "Unsupported repository URL. Only HTTPS URLs are supported." ··· 127 134 run_command("git", &["-C", dest.to_str().unwrap(), "pull"])?; 128 135 } 129 136 false => { 130 - run_command( 131 - "git", 132 - &["clone", "--depth", "1", repo_url, dest.to_str().unwrap()], 133 - )?; 137 + run_command("git", &["clone", repo_url, dest.to_str().unwrap()])?; 134 138 } 135 139 } 136 140 141 + if version.is_some() { 142 + run_command("git", &["-C", dest.to_str().unwrap(), "fetch", "--all"])?; 143 + run_command( 144 + "git", 145 + &[ 146 + "-C", 147 + dest.to_str().unwrap(), 148 + "checkout", 149 + version.as_ref().unwrap(), 150 + ], 151 + )?; 152 + } 153 + 137 154 if !dest.join("oh-my-droid.toml").exists() { 138 155 return Err(anyhow::anyhow!( 139 156 "The repository does not contain an oh-my-droid.toml configuration file." ··· 143 160 Ok(dest.join("oh-my-droid.toml").to_str().unwrap().to_string()) 144 161 } 145 162 146 - fn extract_repo_name(url: &str) -> Option<String> { 147 - let parsed = Url::parse(url).ok()?; 148 - let mut segments = parsed.path_segments()?; 149 - let username = segments.next()?; 150 - let mut repo = segments.next()?; 151 - 152 - if let Some(stripped) = repo.strip_suffix(".git") { 153 - repo = stripped; 154 - } 155 - 156 - Some(format!("{}-{}", username, repo)) 157 - } 158 - 159 163 #[cfg(test)] 160 164 mod tests { 165 + use crate::git::extract_version; 166 + 161 167 use super::*; 162 168 163 169 #[test] 164 - fn test_extract_repo_name() { 165 - let url = "https://github.com/tsirysndr/pkgs.git"; 166 - let expected = Some("tsirysndr-pkgs".into()); 167 - let result = extract_repo_name(url); 170 + fn test_parse_config_path_github() { 171 + let path = "github:tsirysndr/pkgs"; 172 + let expected = Some("https://github.com/tsirysndr/pkgs".into()); 173 + let result = parse_config_path(path).ok(); 168 174 assert_eq!(result, expected); 169 175 } 170 176 171 177 #[test] 172 - fn test_extract_repo_name_no_git() { 173 - let url = "https://github.com/tsirysndr/pkgs"; 174 - let expected = Some("tsirysndr-pkgs".into()); 175 - let result = extract_repo_name(url); 178 + fn test_parse_config_path_tangled() { 179 + let path = "tangled:@tsirysandratraina/pkgs"; 180 + let expected = Some("https://tangled.sh/@tsirysandratraina/pkgs".into()); 181 + let result = parse_config_path(path).ok(); 182 + assert_eq!(result, expected); 183 + } 184 + 185 + #[test] 186 + fn test_parse_config_path_git_url() { 187 + let path = "https://github.com/tsirysndr/pkgs@main"; 188 + let expected = Some("https://github.com/tsirysndr/pkgs@main".into()); 189 + let result = parse_config_path(path).ok(); 176 190 assert_eq!(result, expected); 177 191 } 178 192 179 193 #[test] 180 - fn test_extract_repo_name_invalid_url() { 181 - let url = "invalid-url"; 182 - let expected = None; 183 - let result = extract_repo_name(url); 194 + fn test_extract_version() { 195 + let url = "https://tangled.sh/@tsirysandratraina/pkgs@main"; 196 + let expected = ( 197 + "https://tangled.sh/@tsirysandratraina/pkgs".into(), 198 + Some("main".into()), 199 + ); 200 + let result = extract_version(url); 201 + assert_eq!(result, expected); 202 + } 203 + 204 + #[test] 205 + fn test_extract_version_2() { 206 + let url = "https://tangled.sh/@tsirysandratraina/pkgs"; 207 + let expected = ("https://tangled.sh/@tsirysandratraina/pkgs".into(), None); 208 + let result = extract_version(url); 184 209 assert_eq!(result, expected); 185 210 } 186 211 187 212 #[test] 188 - fn test_parse_config_path_github() { 189 - let path = "github:tsirysndr/pkgs"; 190 - let expected = Some("https://github.com/tsirysndr/pkgs.git".into()); 191 - let result = parse_config_path(path).ok(); 213 + fn test_extract_version_3() { 214 + let url = "https://github.com/tsirysndr/pkgs"; 215 + let expected = ("https://github.com/tsirysndr/pkgs".into(), None); 216 + let result = extract_version(url); 192 217 assert_eq!(result, expected); 193 218 } 194 219 195 220 #[test] 196 - fn test_parse_config_path_tangled() { 197 - let path = "tangled:@tsirysandratraina/pkgs"; 198 - let expected = Some("https://tangled.sh/@tsirysandratraina/pkgs".into()); 199 - let result = parse_config_path(path).ok(); 221 + fn test_extract_version_4() { 222 + let url = "https://github.com/tsirysndr/pkgs@main"; 223 + let expected = ( 224 + "https://github.com/tsirysndr/pkgs".into(), 225 + Some("main".into()), 226 + ); 227 + let result = extract_version(url); 200 228 assert_eq!(result, expected); 201 229 } 202 230 203 231 #[test] 204 - fn test_parse_config_path_git_url() { 205 - let path = "https://github.com/tsirysndr/pkgs.git"; 206 - let expected = Some("https://github.com/tsirysndr/pkgs.git".into()); 232 + fn test_parse_config_path_github_with_branch() { 233 + let path = "github:tsirysndr/pkgs@main"; 234 + let expected = Some("https://github.com/tsirysndr/pkgs@main".into()); 207 235 let result = parse_config_path(path).ok(); 208 236 assert_eq!(result, expected); 209 237 }
+59
src/git.rs
··· 1 + use url::Url; 2 + 3 + pub fn extract_repo_name(url: &str) -> Option<String> { 4 + let parsed = Url::parse(url).ok()?; 5 + let mut segments = parsed.path_segments()?; 6 + let username = segments.next()?; 7 + let mut repo = segments.next()?; 8 + 9 + if let Some(stripped) = repo.strip_suffix(".git") { 10 + repo = stripped; 11 + } 12 + 13 + Some(format!("{}-{}", username, repo)) 14 + } 15 + 16 + pub fn extract_version(url: &str) -> (String, Option<String>) { 17 + let repo_name = url.replace("https://tangled.sh/", "").replace("@", ""); 18 + match url.rfind('@') { 19 + Some(idx) => { 20 + let (repo, version) = url.split_at(idx); 21 + let version = &version[1..]; 22 + match version == repo_name { 23 + true => (url.to_string(), None), 24 + false => (repo.to_string(), Some(version.to_string())), 25 + } 26 + } 27 + None => (url.to_string(), None), 28 + } 29 + } 30 + 31 + #[cfg(test)] 32 + mod tests { 33 + 34 + use super::*; 35 + 36 + #[test] 37 + fn test_extract_repo_name() { 38 + let url = "https://github.com/tsirysndr/pkgs.git"; 39 + let expected = Some("tsirysndr-pkgs".into()); 40 + let result = extract_repo_name(url); 41 + assert_eq!(result, expected); 42 + } 43 + 44 + #[test] 45 + fn test_extract_repo_name_no_git() { 46 + let url = "https://github.com/tsirysndr/pkgs"; 47 + let expected = Some("tsirysndr-pkgs".into()); 48 + let result = extract_repo_name(url); 49 + assert_eq!(result, expected); 50 + } 51 + 52 + #[test] 53 + fn test_extract_repo_name_invalid_url() { 54 + let url = "invalid-url"; 55 + let expected = None; 56 + let result = extract_repo_name(url); 57 + assert_eq!(result, expected); 58 + } 59 + }
+2 -1
src/main.rs
··· 1 1 use anyhow::Error; 2 - use clap::{Command, arg}; 2 + use clap::{arg, Command}; 3 3 use owo_colors::OwoColorize; 4 4 5 5 use crate::{ ··· 13 13 pub mod config; 14 14 pub mod consts; 15 15 pub mod diff; 16 + pub mod git; 16 17 17 18 fn cli() -> Command { 18 19 let banner = format!(