1use rustic_git::{GitError, Repository, Result};
12use std::{env, fs};
13
14fn main() -> Result<()> {
15 println!("Rustic Git - Error Handling Example\n");
16
17 let base_path = env::temp_dir().join("rustic_git_error_example");
18 let repo_path = base_path.join("test_repo");
19
20 if base_path.exists() {
22 fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
23 }
24 fs::create_dir_all(&base_path).expect("Failed to create base directory");
25
26 println!("=== GitError Types and Handling ===\n");
27
28 demonstrate_repository_errors(&repo_path)?;
30 demonstrate_file_operation_errors(&repo_path)?;
31 demonstrate_git_command_errors(&repo_path)?;
32 demonstrate_error_recovery_patterns(&repo_path)?;
33 demonstrate_error_propagation_strategies(&base_path)?;
34
35 println!("Cleaning up error handling examples...");
37 fs::remove_dir_all(&base_path)?;
38 println!("Error handling example completed successfully!");
39
40 Ok(())
41}
42
43fn demonstrate_repository_errors(repo_path: &std::path::Path) -> Result<()> {
45 println!("Repository Error Scenarios:\n");
46
47 println!("1. Attempting to open non-existent repository:");
49 match Repository::open("/definitely/does/not/exist") {
50 Ok(_) => println!(" Unexpectedly succeeded"),
51 Err(GitError::IoError(msg)) => {
52 println!(" IoError caught: {}", msg);
53 println!(" This typically happens when the path doesn't exist");
54 }
55 Err(GitError::CommandFailed(msg)) => {
56 println!(" CommandFailed caught: {}", msg);
57 println!(" Git command failed - path exists but isn't a repo");
58 }
59 }
60
61 let fake_repo_path = repo_path.with_extension("fake.txt");
63 fs::write(&fake_repo_path, "This is not a git repository")?;
64
65 println!("\n2. Attempting to open regular file as repository:");
66 match Repository::open(&fake_repo_path) {
67 Ok(_) => println!(" Unexpectedly succeeded"),
68 Err(GitError::CommandFailed(msg)) => {
69 println!(" CommandFailed caught: {}", msg);
70 println!(" Git recognized the path but it's not a repository");
71 }
72 Err(GitError::IoError(msg)) => {
73 println!(" IoError caught: {}", msg);
74 }
75 }
76
77 fs::remove_file(&fake_repo_path)?;
78
79 println!("\n3. Attempting to initialize repository with problematic path:");
81
82 match Repository::init("/root/definitely_no_permission", false) {
84 Ok(_) => println!(" Unexpectedly succeeded (you might be running as root!)"),
85 Err(GitError::IoError(msg)) => {
86 println!(" IoError caught: {}", msg);
87 println!(" Likely a permission issue");
88 }
89 Err(GitError::CommandFailed(msg)) => {
90 println!(" CommandFailed caught: {}", msg);
91 println!(" Git init command failed");
92 }
93 }
94
95 println!();
96 Ok(())
97}
98
99fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
101 println!("File Operation Error Scenarios:\n");
102
103 let repo = Repository::init(repo_path, false)?;
105
106 fs::write(repo_path.join("test.txt"), "Test content")?;
108 repo.add(&["test.txt"])?;
109 repo.commit("Initial commit")?;
110
111 println!("1. Attempting to add non-existent files:");
113 match repo.add(&["does_not_exist.txt", "also_missing.txt"]) {
114 Ok(_) => println!(" Unexpectedly succeeded"),
115 Err(GitError::CommandFailed(msg)) => {
116 println!(" CommandFailed caught: {}", msg);
117 println!(" Git add failed because files don't exist");
118 }
119 Err(GitError::IoError(msg)) => {
120 println!(" IoError caught: {}", msg);
121 }
122 }
123
124 println!("\n2. Adding mix of valid and invalid files:");
126 fs::write(repo_path.join("valid.txt"), "Valid file")?;
127
128 match repo.add(&["valid.txt", "invalid.txt"]) {
129 Ok(_) => {
130 println!(" Partially succeeded - some Git versions allow this");
131 let status = repo.status()?;
133 println!(" {} files staged despite error", status.entries.len());
134 }
135 Err(GitError::CommandFailed(msg)) => {
136 println!(" CommandFailed caught: {}", msg);
137 println!(" Entire add operation failed due to invalid file");
138
139 println!(" Recovery: Adding valid files individually...");
141 match repo.add(&["valid.txt"]) {
142 Ok(_) => println!(" Successfully added valid.txt"),
143 Err(e) => println!(" Recovery failed: {:?}", e),
144 }
145 }
146 Err(GitError::IoError(msg)) => {
147 println!(" IoError caught: {}", msg);
148 }
149 }
150
151 println!();
152 Ok(())
153}
154
155fn demonstrate_git_command_errors(repo_path: &std::path::Path) -> Result<()> {
157 println!("Git Command Error Scenarios:\n");
158
159 let repo = Repository::open(repo_path)?;
160
161 println!("1. Attempting commit with no staged changes:");
163 match repo.commit("Empty commit attempt") {
164 Ok(hash) => {
165 println!(" Unexpectedly succeeded: {}", hash.short());
166 println!(" Some Git configurations allow empty commits");
167 }
168 Err(GitError::CommandFailed(msg)) => {
169 println!(" CommandFailed caught: {}", msg);
170 println!(" Git requires changes to commit (normal behavior)");
171 }
172 Err(GitError::IoError(msg)) => {
173 println!(" IoError caught: {}", msg);
174 }
175 }
176
177 println!("\n2. Testing commit message edge cases:");
179
180 fs::write(
182 repo_path.join("commit_test.txt"),
183 "Content for commit testing",
184 )?;
185 repo.add(&["commit_test.txt"])?;
186
187 let very_long_message = "A ".repeat(1000) + "very long commit message";
189 match repo.commit(&very_long_message) {
190 Ok(hash) => {
191 println!(" Long commit message succeeded: {}", hash.short());
192 println!(" Git handled the long message fine");
193 }
194 Err(GitError::CommandFailed(msg)) => {
195 println!(" Long commit message failed: {}", msg);
196 }
197 Err(GitError::IoError(msg)) => {
198 println!(" IoError with long message: {}", msg);
199 }
200 }
201
202 println!();
203 Ok(())
204}
205
206fn demonstrate_error_recovery_patterns(repo_path: &std::path::Path) -> Result<()> {
208 println!("Error Recovery Patterns:\n");
209
210 let repo = Repository::open(repo_path)?;
211
212 println!("1. Retry Pattern - Graceful degradation:");
214
215 let files_to_add = ["missing1.txt", "missing2.txt", "missing3.txt"];
217
218 println!(" Attempting to add specific files...");
219 match repo.add(&files_to_add) {
220 Ok(_) => println!(" Specific files added successfully"),
221 Err(e) => {
222 println!(" Specific files failed: {:?}", e);
223 println!(" Falling back to add_all()...");
224
225 match repo.add_all() {
226 Ok(_) => {
227 let status = repo.status()?;
228 println!(
229 " add_all() succeeded, {} files staged",
230 status.entries.len()
231 );
232 }
233 Err(fallback_error) => {
234 println!(" Fallback also failed: {:?}", fallback_error);
235 }
236 }
237 }
238 }
239
240 println!("\n2. Partial Success Pattern:");
242
243 fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245 fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246 let mixed_files = ["good1.txt", "bad1.txt", "good2.txt"];
249
250 println!(" Attempting to add mixed valid/invalid files...");
251 match repo.add(&mixed_files) {
252 Ok(_) => println!(" All files added (unexpected success)"),
253 Err(GitError::CommandFailed(msg)) => {
254 println!(" Batch add failed: {}", msg);
255 println!(" Recovery: Adding files individually...");
256
257 let mut successful_adds = 0;
258 let mut failed_adds = 0;
259
260 for file in &mixed_files {
261 match repo.add(&[file]) {
262 Ok(_) => {
263 successful_adds += 1;
264 println!(" Added: {}", file);
265 }
266 Err(_) => {
267 failed_adds += 1;
268 println!(" Failed: {}", file);
269 }
270 }
271 }
272
273 println!(
274 " Results: {} succeeded, {} failed",
275 successful_adds, failed_adds
276 );
277 }
278 Err(GitError::IoError(msg)) => {
279 println!(" IoError during batch add: {}", msg);
280 }
281 }
282
283 println!("\n3. Preventive Pattern - Check before operation:");
285
286 println!(" Checking repository status before commit...");
287 let status = repo.status()?;
288
289 if status.is_clean() {
290 println!(" Repository is clean - no commit needed");
291 } else {
292 println!(" Repository has {} changes", status.entries.len());
293
294 for entry in &status.entries {
296 println!(
297 " Index {:?}, Worktree {:?}: {}",
298 entry.index_status,
299 entry.worktree_status,
300 entry.path.display()
301 );
302 }
303
304 match repo.commit("Commit after status check") {
306 Ok(hash) => println!(" Safe commit succeeded: {}", hash.short()),
307 Err(e) => println!(" Even safe commit failed: {:?}", e),
308 }
309 }
310
311 println!();
312 Ok(())
313}
314
315fn demonstrate_error_propagation_strategies(base_path: &std::path::Path) -> Result<()> {
317 println!("Error Propagation Strategies:\n");
318
319 println!("1. Early Return Strategy (using ?):");
321 match workflow_with_early_return(base_path) {
322 Ok(message) => println!(" Workflow completed: {}", message),
323 Err(e) => println!(" Workflow failed early: {:?}", e),
324 }
325
326 println!("\n2. Error Collection Strategy:");
328 let results = workflow_with_error_collection(base_path);
329
330 let successful = results.iter().filter(|r| r.is_ok()).count();
331 let failed = results.iter().filter(|r| r.is_err()).count();
332
333 println!(
334 " Operations: {} succeeded, {} failed",
335 successful, failed
336 );
337
338 for (i, result) in results.iter().enumerate() {
339 match result {
340 Ok(msg) => println!(" Step {}: {}", i + 1, msg),
341 Err(e) => println!(" Step {}: {:?}", i + 1, e),
342 }
343 }
344
345 println!("\n3. Error Context Strategy:");
347 match workflow_with_context(base_path) {
348 Ok(message) => println!(" Contextual workflow: {}", message),
349 Err(e) => println!(" Contextual workflow failed: {:?}", e),
350 }
351
352 println!();
353 Ok(())
354}
355
356fn workflow_with_early_return(base_path: &std::path::Path) -> Result<String> {
358 let repo_path = base_path.join("early_return_test");
359
360 let repo = Repository::init(&repo_path, false)?;
362
363 fs::write(repo_path.join("file1.txt"), "Content 1")?;
364 repo.add(&["file1.txt"])?;
365
366 let hash = repo.commit("Early return workflow commit")?;
367
368 fs::remove_dir_all(&repo_path)?;
370
371 Ok(format!("Completed with commit {}", hash.short()))
372}
373
374fn workflow_with_error_collection(base_path: &std::path::Path) -> Vec<Result<String>> {
376 let repo_path = base_path.join("error_collection_test");
377 let mut results = Vec::new();
378
379 results.push(Repository::init(&repo_path, false).map(|_| "Repository initialized".to_string()));
381
382 let files_to_create = ["good.txt", "another_good.txt"];
384
385 for file in &files_to_create {
386 results.push(
387 fs::write(repo_path.join(file), "Content")
388 .map_err(GitError::from)
389 .map(|_| format!("Created {}", file)),
390 );
391 }
392
393 if let Ok(repo) = Repository::open(&repo_path) {
395 results.push(
396 repo.add(&files_to_create)
397 .map(|_| "Files added to staging".to_string()),
398 );
399
400 results.push(
401 repo.commit("Error collection workflow")
402 .map(|hash| format!("Committed: {}", hash.short())),
403 );
404 } else {
405 results.push(Err(GitError::CommandFailed(
406 "Could not open repo for adding files".to_string(),
407 )));
408 results.push(Err(GitError::CommandFailed(
409 "Could not open repo for commit".to_string(),
410 )));
411 }
412
413 let _ = fs::remove_dir_all(&repo_path);
415
416 results
417}
418
419fn workflow_with_context(base_path: &std::path::Path) -> Result<String> {
421 let repo_path = base_path.join("context_test");
422
423 let repo = Repository::init(&repo_path, false).inspect_err(|_e| {
425 eprintln!(
426 "Context: Failed to initialize repository at {}",
427 repo_path.display()
428 );
429 })?;
430
431 fs::write(repo_path.join("context_file.txt"), "Content with context").map_err(|e| {
433 eprintln!("Context: Failed to create context_file.txt");
434 GitError::from(e)
435 })?;
436
437 repo.add(&["context_file.txt"]).inspect_err(|_e| {
439 eprintln!("Context: Failed to stage context_file.txt");
440 })?;
441
442 let hash = repo.commit("Context workflow commit").inspect_err(|_e| {
444 eprintln!("Context: Failed to create commit");
445 })?;
446
447 fs::remove_dir_all(&repo_path)?;
449
450 Ok(format!("Context workflow completed: {}", hash.short()))
451}