A tool to help managing forked repos with their own history
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}