A tool to help managing forked repos with their own history
at main 114 lines 3.5 kB view raw
1use anyhow::{bail, Context, Result}; 2use std::fs::{self, OpenOptions}; 3use std::io::Write; 4use std::path::Path; 5 6use crate::config::{self, Config, Upstream}; 7use crate::git::{self, SOURCE_DIR}; 8use crate::patch; 9 10pub fn run(url: Option<String>, branch: &str, depth: Option<usize>) -> Result<()> { 11 // Determine the URL - either from argument or existing config 12 let url = match url { 13 Some(u) => u, 14 None => { 15 if Config::exists() { 16 let config = Config::load()?; 17 config.upstream.url 18 } else { 19 bail!("No URL provided and no forkme.toml found. Use --url to specify upstream repository."); 20 } 21 } 22 }; 23 24 // Check if source directory already exists 25 if Path::new(SOURCE_DIR).exists() { 26 bail!( 27 "Source directory '{}' already exists. Remove it first if you want to reinitialize.", 28 SOURCE_DIR 29 ); 30 } 31 32 // Create/update the config file 33 let config = Config { 34 upstream: Upstream { 35 url: url.clone(), 36 branch: branch.into(), 37 }, 38 }; 39 config 40 .save() 41 .with_context(|| "Failed to save forkme.toml")?; 42 println!("Created forkme.toml"); 43 44 // Clone the repository 45 let repo = git::clone_repo(&url, branch, depth)?; 46 47 // Create the forkme branch - from locked commit if available 48 if let Some(locked_sha) = config::load_lock()? { 49 let oid = git::resolve_commit(&repo, &locked_sha)?; 50 git::create_forkme_branch_at(&repo, oid)?; 51 println!("Using locked upstream revision: {}", &locked_sha[..12]); 52 } else { 53 git::create_forkme_branch(&repo, branch)?; 54 } 55 56 // Create patches directory 57 patch::ensure_patches_dir()?; 58 println!("Created patches/ directory"); 59 60 // Add source/ to .gitignore if not already there 61 add_to_gitignore(SOURCE_DIR)?; 62 63 // Apply any existing patches 64 let patches = patch::list_patches()?; 65 if !patches.is_empty() { 66 println!("Found {} existing patches, applying...", patches.len()); 67 super::apply::apply_patches(&repo)?; 68 } 69 70 println!("\nInitialization complete!"); 71 println!("You can now work in the {} directory.", SOURCE_DIR); 72 println!("Use 'forkme sync' to save your changes as patches."); 73 74 Ok(()) 75} 76 77fn add_to_gitignore(entry: &str) -> Result<()> { 78 let gitignore_path = Path::new(".gitignore"); 79 let entry_line = format!("{}/", entry); 80 81 // Check if .gitignore exists and if entry is already there 82 if gitignore_path.exists() { 83 let content = fs::read_to_string(gitignore_path)?; 84 for line in content.lines() { 85 let trimmed = line.trim(); 86 if trimmed == entry 87 || trimmed == entry_line.trim_end_matches('/') 88 || trimmed == entry_line 89 { 90 println!(".gitignore already contains {}", entry); 91 return Ok(()); 92 } 93 } 94 } 95 96 // Append to .gitignore 97 let mut file = OpenOptions::new() 98 .create(true) 99 .append(true) 100 .open(gitignore_path)?; 101 102 // Add newline before if file doesn't end with one 103 if gitignore_path.exists() { 104 let content = fs::read_to_string(gitignore_path)?; 105 if !content.is_empty() && !content.ends_with('\n') { 106 writeln!(file)?; 107 } 108 } 109 110 writeln!(file, "{}", entry_line)?; 111 println!("Added {} to .gitignore", entry_line); 112 113 Ok(()) 114}