1use rustic_git::{IndexStatus, Repository, Result, WorktreeStatus};
12use std::{env, fs};
13
14fn main() -> Result<()> {
15 println!("Rustic Git - Staging Operations Example\n");
16
17 let repo_path = env::temp_dir().join("rustic_git_staging_example");
18
19 if repo_path.exists() {
21 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
22 }
23
24 println!("Setting up repository with initial files...");
26 let repo = Repository::init(&repo_path, false)?;
27
28 fs::create_dir_all(repo_path.join("src"))?;
30 fs::create_dir_all(repo_path.join("docs"))?;
31
32 fs::write(
33 repo_path.join("README.md"),
34 "# Staging Demo\nOriginal content",
35 )?;
36 fs::write(
37 repo_path.join("src/main.rs"),
38 "fn main() { println!(\"v1\"); }",
39 )?;
40 fs::write(
41 repo_path.join("src/lib.rs"),
42 "pub fn version() -> &'static str { \"1.0\" }",
43 )?;
44
45 repo.add_all()?;
47 let _initial_hash = repo.commit("Initial commit with basic files")?;
48 println!("Created initial repository with 3 files\n");
49
50 println!("=== Staging Specific Files with add() ===\n");
51
52 println!("Creating new files and modifying existing ones...");
54 fs::write(repo_path.join("new_file1.txt"), "New file 1 content")?;
55 fs::write(repo_path.join("new_file2.txt"), "New file 2 content")?;
56 fs::write(repo_path.join("docs/guide.md"), "# User Guide")?;
57
58 fs::write(
60 repo_path.join("README.md"),
61 "# Staging Demo\nUpdated content!",
62 )?;
63 fs::write(
64 repo_path.join("src/main.rs"),
65 "fn main() { println!(\"v2 - updated!\"); }",
66 )?;
67
68 println!("Created 3 new files and modified 2 existing files");
69
70 println!("\nStatus before staging:");
72 let status_before = repo.status()?;
73 display_status_breakdown(&status_before);
74
75 println!("\nUsing add() to stage specific files:");
77
78 repo.add(&["README.md"])?;
80 println!(" Staged README.md");
81
82 let status_after_readme = repo.status()?;
83 display_status_changes(
84 &status_before,
85 &status_after_readme,
86 "after staging README.md",
87 );
88
89 repo.add(&["new_file1.txt", "src/main.rs"])?;
91 println!(" Staged new_file1.txt and src/main.rs");
92
93 let status_after_multiple = repo.status()?;
94 display_status_changes(
95 &status_after_readme,
96 &status_after_multiple,
97 "after staging multiple files",
98 );
99
100 use std::path::Path as StdPath;
102 repo.add(&[StdPath::new("docs/guide.md")])?;
103 println!(" Staged docs/guide.md using Path object");
104
105 let status_after_path = repo.status()?;
106 display_status_changes(
107 &status_after_multiple,
108 &status_after_path,
109 "after staging with Path object",
110 );
111
112 println!();
113
114 println!("=== Staging All Changes with add_all() ===\n");
115
116 println!("Creating additional files for add_all() demo...");
118 fs::write(
119 repo_path.join("config.toml"),
120 "[package]\nname = \"example\"",
121 )?;
122 fs::write(repo_path.join("src/utils.rs"), "pub fn helper() {}")?;
123 fs::create_dir_all(repo_path.join("tests"))?;
124 fs::write(
125 repo_path.join("tests/integration.rs"),
126 "#[test]\nfn test_basic() {}",
127 )?;
128
129 println!("Created 3 more files");
130
131 let status_before_add_all = repo.status()?;
132 println!("\nStatus before add_all():");
133 display_status_breakdown(&status_before_add_all);
134
135 println!("\nUsing add_all() to stage all remaining changes:");
137 repo.add_all()?;
138 println!(" Staged all changes with add_all()");
139
140 let status_after_add_all = repo.status()?;
141 display_status_changes(
142 &status_before_add_all,
143 &status_after_add_all,
144 "after add_all()",
145 );
146
147 let _commit_hash = repo.commit("Add all new files and modifications")?;
149 println!(" Committed all staged changes\n");
150
151 println!("=== Staging Tracked Changes with add_update() ===\n");
152
153 println!("Setting up files for add_update() demonstration...");
155
156 fs::write(repo_path.join("untracked1.txt"), "This is untracked")?;
158 fs::write(repo_path.join("untracked2.txt"), "Another untracked file")?;
159
160 fs::write(
162 repo_path.join("README.md"),
163 "# Staging Demo\nContent updated again for add_update demo!",
164 )?;
165 fs::write(
166 repo_path.join("src/lib.rs"),
167 "pub fn version() -> &'static str { \"2.0\" }",
168 )?;
169 fs::write(
170 repo_path.join("config.toml"),
171 "[package]\nname = \"example\"\nversion = \"0.2.0\"",
172 )?;
173
174 println!("Created 2 untracked files and modified 3 tracked files");
175
176 let status_before_add_update = repo.status()?;
177 println!("\nStatus before add_update():");
178 display_status_breakdown(&status_before_add_update);
179
180 println!("\nUsing add_update() to stage only tracked file modifications:");
182 repo.add_update()?;
183 println!(" Used add_update() - should stage modified tracked files only");
184
185 let status_after_add_update = repo.status()?;
186 display_status_changes(
187 &status_before_add_update,
188 &status_after_add_update,
189 "after add_update()",
190 );
191
192 let remaining_untracked: Vec<_> = status_after_add_update.untracked_entries().collect();
194 if !remaining_untracked.is_empty() {
195 println!(" Untracked files remain untracked (as expected):");
196 for entry in remaining_untracked {
197 println!(" - {}", entry.path.display());
198 }
199 }
200
201 println!();
202
203 println!("=== Error Handling in Staging Operations ===\n");
204
205 println!("Testing error conditions:");
207
208 match repo.add(&["nonexistent_file.txt"]) {
210 Ok(_) => println!(" Unexpectedly succeeded adding non-existent file"),
211 Err(e) => println!(" Expected error for non-existent file: {:?}", e),
212 }
213
214 match repo.add(&[] as &[&str]) {
216 Ok(_) => println!(" Empty add() succeeded (no-op)"),
217 Err(e) => println!(" Empty add() failed: {:?}", e),
218 }
219
220 println!();
221
222 println!("=== Final Repository State ===\n");
223
224 let final_status = repo.status()?;
225 println!("Final repository summary:");
226 display_status_breakdown(&final_status);
227
228 if final_status.has_changes() {
229 let staged_count = final_status.staged_files().count();
230 let untracked_count = final_status.untracked_entries().count();
231
232 println!("\nRepository state:");
233 println!(" {} files staged and ready to commit", staged_count);
234 println!(" {} untracked files not yet added", untracked_count);
235
236 if staged_count > 0 {
237 println!("\n You could now commit with: repo.commit(\"Your message\")?");
238 }
239 }
240
241 println!("\nCleaning up example repository...");
243 fs::remove_dir_all(&repo_path)?;
244 println!("Staging operations example completed!");
245
246 Ok(())
247}
248
249fn display_status_breakdown(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean");
253 return;
254 }
255
256 let mut index_counts = std::collections::HashMap::new();
257 let mut worktree_counts = std::collections::HashMap::new();
258
259 for entry in &status.entries {
260 if !matches!(entry.index_status, IndexStatus::Clean) {
261 *index_counts.entry(&entry.index_status).or_insert(0) += 1;
262 }
263 if !matches!(entry.worktree_status, WorktreeStatus::Clean) {
264 *worktree_counts.entry(&entry.worktree_status).or_insert(0) += 1;
265 }
266 }
267
268 println!(" Index status:");
269 for (index_status, count) in &index_counts {
270 let marker = match index_status {
271 IndexStatus::Modified => "[M]",
272 IndexStatus::Added => "[A]",
273 IndexStatus::Deleted => "[D]",
274 IndexStatus::Renamed => "[R]",
275 IndexStatus::Copied => "[C]",
276 IndexStatus::Clean => "[ ]",
277 };
278 println!(" {} {:?}: {} files", marker, index_status, count);
279 }
280
281 println!(" Worktree status:");
282 for (worktree_status, count) in &worktree_counts {
283 let marker = match worktree_status {
284 WorktreeStatus::Modified => "[M]",
285 WorktreeStatus::Deleted => "[D]",
286 WorktreeStatus::Untracked => "[?]",
287 WorktreeStatus::Ignored => "[I]",
288 WorktreeStatus::Clean => "[ ]",
289 };
290 println!(" {} {:?}: {} files", marker, worktree_status, count);
291 }
292}
293
294fn display_status_changes(
296 before: &rustic_git::GitStatus,
297 after: &rustic_git::GitStatus,
298 description: &str,
299) {
300 println!("\n Status changes {}:", description);
301
302 let before_count = before.entries.len();
303 let after_count = after.entries.len();
304
305 if before_count == after_count {
306 println!(" Total files unchanged ({} files)", after_count);
307 } else {
308 println!(
309 " Total files: {} → {} ({:+})",
310 before_count,
311 after_count,
312 after_count as i32 - before_count as i32
313 );
314 }
315
316 let before_staged = before.staged_files().count();
318 let after_staged = after.staged_files().count();
319 let before_untracked = before.untracked_entries().count();
320 let after_untracked = after.untracked_entries().count();
321
322 if before_staged != after_staged {
323 println!(
324 " Staged files: {} → {} ({:+})",
325 before_staged,
326 after_staged,
327 after_staged as i32 - before_staged as i32
328 );
329 }
330
331 if before_untracked != after_untracked {
332 println!(
333 " Untracked files: {} → {} ({:+})",
334 before_untracked,
335 after_untracked,
336 after_untracked as i32 - before_untracked as i32
337 );
338 }
339}