Repository

Struct Repository 

Source
pub struct Repository { /* private fields */ }

Implementations§

Source§

impl Repository

Source

pub fn add<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()>

Add specific files or paths to the staging area.

§Arguments
  • paths - The file paths to add to the staging area
§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/merge_operations.rs (line 52)
45fn demonstrate_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
46    println!("--- Demonstrating Fast-Forward Merge ---\n");
47
48    // Create initial commit
49    println!("1. Creating initial commit on master...");
50    let file1_path = temp_dir.join("README.md");
51    fs::write(&file1_path, "# Project\n\nInitial content")?;
52    repo.add(&["README.md"])?;
53    let initial_commit = repo.commit("Initial commit")?;
54    println!("   Created commit: {}", initial_commit);
55
56    // Create feature branch and add commits
57    println!("\n2. Creating feature branch and adding commits...");
58    repo.checkout_new("feature/fast-forward", None)?;
59
60    let file2_path = temp_dir.join("feature.txt");
61    fs::write(&file2_path, "New feature implementation")?;
62    repo.add(&["feature.txt"])?;
63    let feature_commit = repo.commit("Add new feature")?;
64    println!("   Feature commit: {}", feature_commit);
65
66    // Switch back to master
67    println!("\n3. Switching back to master...");
68    let branches = repo.branches()?;
69    let master_branch = branches.find("master").unwrap();
70    repo.checkout(master_branch)?;
71    println!("   Switched to master");
72
73    // Perform fast-forward merge
74    println!("\n4. Performing fast-forward merge...");
75    let merge_status = repo.merge("feature/fast-forward")?;
76
77    match merge_status {
78        MergeStatus::FastForward(hash) => {
79            println!("   ✓ Fast-forward merge completed!");
80            println!("   New HEAD: {}", hash);
81            println!("   Both files are now present on master");
82        }
83        _ => println!("   Unexpected merge result: {:?}", merge_status),
84    }
85
86    println!("   Files in repository:");
87    for file in ["README.md", "feature.txt"] {
88        if temp_dir.join(file).exists() {
89            println!("     ✓ {}", file);
90        }
91    }
92
93    Ok(())
94}
95
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
More examples
Hide additional examples
examples/error_handling.rs (line 108)
100fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
101    println!("File Operation Error Scenarios:\n");
102
103    // Set up a valid repository first
104    let repo = Repository::init(repo_path, false)?;
105
106    // Create some test files
107    fs::write(repo_path.join("test.txt"), "Test content")?;
108    repo.add(&["test.txt"])?;
109    repo.commit("Initial commit")?;
110
111    // 1. Adding non-existent files
112    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    // 2. Mixed valid and invalid files
125    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            // Check what actually got staged
132            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            // Try recovery: add valid files individually
140            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
155/// Demonstrate Git command related errors
156fn 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    // 1. Empty commit (no staged changes)
162    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    // 2. Commit with problematic message
178    println!("\n2. Testing commit message edge cases:");
179
180    // Stage a file for testing
181    fs::write(
182        repo_path.join("commit_test.txt"),
183        "Content for commit testing",
184    )?;
185    repo.add(&["commit_test.txt"])?;
186
187    // Very long commit message
188    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
206/// Demonstrate error recovery patterns
207fn 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    // Pattern 1: Retry with different approach
213    println!("1. Retry Pattern - Graceful degradation:");
214
215    // Try to add specific files, fall back to add_all on failure
216    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    // Pattern 2: Partial success handling
241    println!("\n2. Partial Success Pattern:");
242
243    // Create some files with known issues
244    fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245    fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246    // Don't create bad1.txt - it will be missing
247
248    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    // Pattern 3: Status checking before operations
284    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        // Show what would be committed
295        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        // Safe commit since we know there are changes
305        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
315/// Demonstrate error propagation strategies
316fn demonstrate_error_propagation_strategies(base_path: &std::path::Path) -> Result<()> {
317    println!("Error Propagation Strategies:\n");
318
319    // Strategy 1: Early return with ?
320    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    // Strategy 2: Collect all errors
327    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    // Strategy 3: Error context enrichment
346    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
356/// Workflow that returns early on first error
357fn workflow_with_early_return(base_path: &std::path::Path) -> Result<String> {
358    let repo_path = base_path.join("early_return_test");
359
360    // This will propagate any error immediately
361    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    // Clean up
369    fs::remove_dir_all(&repo_path)?;
370
371    Ok(format!("Completed with commit {}", hash.short()))
372}
373
374/// Workflow that collects all errors instead of failing fast
375fn 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    // Step 1: Initialize repo
380    results.push(Repository::init(&repo_path, false).map(|_| "Repository initialized".to_string()));
381
382    // Step 2: Add files (some may fail)
383    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    // Step 3: Try to add files (continue even if repo init failed)
394    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    // Cleanup (don't add to results as it's not part of main workflow)
414    let _ = fs::remove_dir_all(&repo_path);
415
416    results
417}
418
419/// Workflow with enhanced error context
420fn workflow_with_context(base_path: &std::path::Path) -> Result<String> {
421    let repo_path = base_path.join("context_test");
422
423    // Add context to errors
424    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    // Create file with context
432    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    // Add with context
438    repo.add(&["context_file.txt"]).inspect_err(|_e| {
439        eprintln!("Context: Failed to stage context_file.txt");
440    })?;
441
442    // Commit with context
443    let hash = repo.commit("Context workflow commit").inspect_err(|_e| {
444        eprintln!("Context: Failed to create commit");
445    })?;
446
447    // Clean up
448    fs::remove_dir_all(&repo_path)?;
449
450    Ok(format!("Context workflow completed: {}", hash.short()))
451}
examples/reset_operations.rs (line 52)
43fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44    println!("--- Demonstrating Reset Modes ---\n");
45
46    // Create initial commits
47    println!("1. Creating initial commits...");
48
49    // First commit
50    let file1_path = temp_dir.join("file1.txt");
51    fs::write(&file1_path, "Initial content")?;
52    repo.add(&["file1.txt"])?;
53    let first_commit = repo.commit("Initial commit")?;
54    println!("   Created first commit: {}", first_commit);
55
56    // Second commit
57    let file2_path = temp_dir.join("file2.txt");
58    fs::write(&file2_path, "Second file content")?;
59    repo.add(&["file2.txt"])?;
60    let second_commit = repo.commit("Add file2.txt")?;
61    println!("   Created second commit: {}", second_commit);
62
63    // Third commit
64    fs::write(&file1_path, "Modified content")?;
65    repo.add(&["file1.txt"])?;
66    let third_commit = repo.commit("Modify file1.txt")?;
67    println!("   Created third commit: {}", third_commit);
68
69    // Show current status
70    println!("\n2. Current repository state:");
71    show_repo_state(repo)?;
72
73    // Demonstrate soft reset
74    println!("\n3. Performing soft reset to second commit...");
75    repo.reset_soft(&second_commit.to_string())?;
76
77    println!("   After soft reset:");
78    show_repo_state(repo)?;
79    println!("   Note: Changes are still staged, working directory unchanged");
80
81    // Reset back to third commit for next demonstration
82    repo.reset_hard(&third_commit.to_string())?;
83
84    // Demonstrate mixed reset (default)
85    println!("\n4. Performing mixed reset to second commit...");
86    repo.reset_mixed(&second_commit.to_string())?;
87
88    println!("   After mixed reset:");
89    show_repo_state(repo)?;
90    println!("   Note: Changes are unstaged but preserved in working directory");
91
92    // Reset back to third commit for next demonstration
93    repo.reset_hard(&third_commit.to_string())?;
94
95    // Demonstrate hard reset
96    println!("\n5. Performing hard reset to first commit...");
97    repo.reset_hard(&first_commit.to_string())?;
98
99    println!("   After hard reset:");
100    show_repo_state(repo)?;
101    println!("   Note: All changes discarded, working directory matches commit");
102
103    // Demonstrate reset_with_mode for flexibility
104    println!("\n6. Using reset_with_mode for explicit control...");
105
106    // Recreate second commit for demo
107    fs::write(&file2_path, "Recreated second file")?;
108    repo.add(&["file2.txt"])?;
109    let _new_commit = repo.commit("Recreate file2.txt")?;
110
111    repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112    println!("   Used ResetMode::Mixed explicitly");
113    show_repo_state(repo)?;
114
115    Ok(())
116}
117
118fn demonstrate_file_resets(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
119    println!("\n--- Demonstrating File-Specific Resets ---\n");
120
121    // Create some files and stage them
122    println!("1. Creating and staging multiple files...");
123
124    let file_a = temp_dir.join("fileA.txt");
125    let file_b = temp_dir.join("fileB.txt");
126
127    fs::write(&file_a, "Content A")?;
128    fs::write(&file_b, "Content B")?;
129
130    repo.add(&["fileA.txt", "fileB.txt"])?;
131    println!("   Staged fileA.txt and fileB.txt");
132
133    show_repo_state(repo)?;
134
135    // Reset a single file (using existing reset_file from files.rs)
136    println!("\n2. Resetting single file (fileA.txt)...");
137    repo.reset_file("fileA.txt")?;
138
139    println!("   After resetting fileA.txt:");
140    show_repo_state(repo)?;
141    println!("   Note: fileA.txt is unstaged, fileB.txt remains staged");
142
143    // Demonstrate HEAD reset (unstage all changes)
144    println!("\n3. Performing mixed reset to HEAD (unstage all)...");
145    repo.reset_mixed("HEAD")?;
146
147    println!("   After reset HEAD:");
148    show_repo_state(repo)?;
149    println!("   Note: All staged changes are now unstaged");
150
151    Ok(())
152}
examples/basic_usage.rs (line 79)
16fn main() -> Result<()> {
17    println!("Rustic Git - Basic Usage Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing new repository at: {}", repo_path.display());
28
29    // Initialize a new repository
30    let repo = Repository::init(&repo_path, false)?;
31    println!("Repository initialized successfully\n");
32
33    // Create some example files
34    println!("Creating example files...");
35    fs::create_dir_all(repo_path.join("src"))?;
36
37    fs::write(
38        repo_path.join("README.md"),
39        "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40    )?;
41
42    fs::write(
43        repo_path.join("src/main.rs"),
44        r#"fn main() {
45    println!("Hello from rustic-git example!");
46}
47"#,
48    )?;
49
50    fs::write(
51        repo_path.join("src/lib.rs"),
52        "// Library code goes here\npub fn hello() -> &'static str {\n    \"Hello, World!\"\n}\n",
53    )?;
54
55    println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57    // Check repository status
58    println!("Checking repository status...");
59    let status = repo.status()?;
60
61    if status.is_clean() {
62        println!("   Repository is clean (no changes)");
63    } else {
64        println!("   Repository has changes:");
65        println!("   Unstaged files: {}", status.unstaged_files().count());
66        println!("   Untracked files: {}", status.untracked_entries().count());
67
68        // Show untracked files
69        for entry in status.untracked_entries() {
70            println!("      - {}", entry.path.display());
71        }
72    }
73    println!();
74
75    // Stage specific files first
76    println!("Staging files...");
77
78    // Stage README.md first
79    repo.add(&["README.md"])?;
80    println!("Staged README.md");
81
82    // Stage all remaining files
83    repo.add_all()?;
84    println!("Staged all remaining files");
85
86    // Check status after staging
87    let status_after_staging = repo.status()?;
88    println!("\nStatus after staging:");
89    if status_after_staging.is_clean() {
90        println!("   Repository is clean (all changes staged)");
91    } else {
92        println!(
93            "   Files staged for commit: {}",
94            status_after_staging.entries.len()
95        );
96        for entry in &status_after_staging.entries {
97            println!(
98                "      Index {:?}, Worktree {:?}: {}",
99                entry.index_status,
100                entry.worktree_status,
101                entry.path.display()
102            );
103        }
104    }
105    println!();
106
107    // Create a commit
108    println!("Creating commit...");
109    let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111    println!("Commit created successfully!");
112    println!("   Full hash: {}", hash);
113    println!("   Short hash: {}", hash.short());
114    println!();
115
116    // Verify final status
117    println!("Final repository status:");
118    let final_status = repo.status()?;
119    if final_status.is_clean() {
120        println!("   Repository is clean - all changes committed!");
121    } else {
122        println!("   Repository still has uncommitted changes");
123    }
124    println!();
125
126    // Clean up
127    println!("Cleaning up example repository...");
128    fs::remove_dir_all(&repo_path)?;
129    println!("Example completed successfully!");
130
131    Ok(())
132}
examples/branch_operations.rs (line 18)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
examples/config_operations.rs (line 87)
14fn main() -> Result<()> {
15    println!("Rustic Git - Repository Configuration Operations Example\n");
16
17    // Use a temporary directory for this example
18    let repo_path = env::temp_dir().join("rustic_git_config_example");
19
20    // Clean up any previous run
21    if repo_path.exists() {
22        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
23    }
24
25    println!("Initializing new repository at: {}", repo_path.display());
26
27    // Initialize a new repository
28    let repo = Repository::init(&repo_path, false)?;
29
30    // ==================== USER CONFIGURATION ====================
31
32    println!("\n[CONFIG] Configuring git user settings...");
33
34    // Set user configuration (convenience method)
35    repo.config()
36        .set_user("Alice Developer", "alice@example.com")?;
37    println!("Set user configuration");
38
39    // Verify user configuration
40    let (name, email) = repo.config().get_user()?;
41    println!("Current user: {} <{}>", name, email);
42
43    // ==================== GENERAL CONFIGURATION ====================
44
45    println!("\n[CONFIG] Setting repository configuration values...");
46
47    // Set various git configuration values
48    repo.config().set("core.autocrlf", "false")?;
49    repo.config().set("core.ignorecase", "true")?;
50    repo.config().set("pull.rebase", "true")?;
51    repo.config().set("push.default", "simple")?;
52    repo.config().set("branch.autosetupmerge", "always")?;
53
54    println!("Set core configuration values");
55
56    // Get and display configuration values
57    println!("\n[CONFIG] Current repository configuration:");
58
59    let configs = [
60        "core.autocrlf",
61        "core.ignorecase",
62        "pull.rebase",
63        "push.default",
64        "branch.autosetupmerge",
65    ];
66
67    for config_key in &configs {
68        match repo.config().get(config_key) {
69            Ok(value) => println!("  {} = {}", config_key, value),
70            Err(_) => println!("  {} = <not set>", config_key),
71        }
72    }
73
74    // ==================== CONFIGURATION WITH COMMITS ====================
75
76    println!("\n[COMMIT] Testing configuration with commit operations...");
77
78    // Create a test file
79    let test_file_path = repo_path.join("test.txt");
80    fs::write(
81        &test_file_path,
82        "Hello from rustic-git configuration example!",
83    )?;
84    println!("Created test file: test.txt");
85
86    // Stage the file
87    repo.add(&["test.txt"])?;
88    println!("Staged test.txt");
89
90    // Create a commit (this will use our configured user)
91    let commit_hash = repo.commit("Add test file with configuration example")?;
92    println!("Created commit: {}", commit_hash.short());
93
94    // ==================== CONFIGURATION MODIFICATION ====================
95
96    println!("\n[UPDATE] Modifying configuration values...");
97
98    // Change some configuration values
99    repo.config().set("core.autocrlf", "true")?;
100    repo.config()
101        .set("user.email", "alice.developer@newcompany.com")?;
102
103    println!("Updated configuration values");
104
105    // Display updated values
106    let autocrlf = repo.config().get("core.autocrlf")?;
107    let (updated_name, updated_email) = repo.config().get_user()?;
108
109    println!("Updated configuration:");
110    println!("  core.autocrlf = {}", autocrlf);
111    println!("  user: {} <{}>", updated_name, updated_email);
112
113    // ==================== CONFIGURATION REMOVAL ====================
114
115    println!("\n[REMOVE] Removing configuration values...");
116
117    // Remove a configuration value
118    repo.config().unset("branch.autosetupmerge")?;
119    println!("Removed branch.autosetupmerge");
120
121    // Try to get the removed value (should fail)
122    match repo.config().get("branch.autosetupmerge") {
123        Ok(value) => println!("Unexpected: branch.autosetupmerge = {}", value),
124        Err(_) => println!("Confirmed: branch.autosetupmerge is not set"),
125    }
126
127    // ==================== ADVANCED CONFIGURATION ====================
128
129    println!("\n[ADVANCED] Setting advanced configuration...");
130
131    // Set some advanced git configuration
132    repo.config().set("diff.tool", "vimdiff")?;
133    repo.config().set("merge.tool", "vimdiff")?;
134    repo.config().set("alias.st", "status")?;
135    repo.config().set("alias.co", "checkout")?;
136    repo.config().set("alias.br", "branch")?;
137    repo.config().set("alias.ci", "commit")?;
138
139    println!("Set advanced configuration (diff/merge tools and aliases)");
140
141    // Display all custom configuration
142    println!("\n[SUMMARY] Complete repository configuration summary:");
143
144    let all_configs = [
145        ("User", vec![("user.name", ""), ("user.email", "")]),
146        ("Core", vec![("core.autocrlf", ""), ("core.ignorecase", "")]),
147        ("Workflow", vec![("pull.rebase", ""), ("push.default", "")]),
148        ("Tools", vec![("diff.tool", ""), ("merge.tool", "")]),
149        (
150            "Aliases",
151            vec![
152                ("alias.st", ""),
153                ("alias.co", ""),
154                ("alias.br", ""),
155                ("alias.ci", ""),
156            ],
157        ),
158    ];
159
160    for (category, configs) in &all_configs {
161        println!("\n  {}:", category);
162        for (key, _) in configs {
163            match repo.config().get(key) {
164                Ok(value) => println!("    {} = {}", key, value),
165                Err(_) => println!("    {} = <not set>", key),
166            }
167        }
168    }
169
170    // ==================== PRACTICAL EXAMPLE ====================
171
172    println!("\n[TEAM] Practical example: Setting up repository for a team...");
173
174    // Configure repository for team development
175    repo.config().set("user.name", "Team Member")?;
176    repo.config().set("user.email", "team@company.com")?;
177    repo.config().set("core.autocrlf", "input")?;
178    repo.config().set("core.safecrlf", "true")?;
179    repo.config().set("pull.rebase", "true")?;
180    repo.config().set("push.default", "current")?;
181    repo.config().set("init.defaultBranch", "main")?;
182
183    println!("Configured repository for team development");
184
185    // Create another commit with the team configuration
186    fs::write(
187        repo_path.join("team.md"),
188        "# Team Development\n\nThis repository is configured for team development.",
189    )?;
190    repo.add(&["team.md"])?;
191    let team_commit = repo.commit("Add team development documentation")?;
192
193    println!("Created team commit: {}", team_commit.short());
194
195    // Final verification
196    let (final_name, final_email) = repo.config().get_user()?;
197    println!("\n[FINAL] Final repository configuration:");
198    println!("  User: {} <{}>", final_name, final_email);
199    println!("  Repository configured for team development workflow");
200
201    // ==================== CLEANUP ====================
202
203    println!("\n[CLEANUP] Cleaning up...");
204    fs::remove_dir_all(&repo_path).expect("Failed to clean up example");
205    println!("Example completed successfully!");
206
207    Ok(())
208}
Source

pub fn add_all(&self) -> Result<()>

Add all changes to the staging area (equivalent to git add .).

§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/basic_usage.rs (line 83)
16fn main() -> Result<()> {
17    println!("Rustic Git - Basic Usage Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing new repository at: {}", repo_path.display());
28
29    // Initialize a new repository
30    let repo = Repository::init(&repo_path, false)?;
31    println!("Repository initialized successfully\n");
32
33    // Create some example files
34    println!("Creating example files...");
35    fs::create_dir_all(repo_path.join("src"))?;
36
37    fs::write(
38        repo_path.join("README.md"),
39        "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40    )?;
41
42    fs::write(
43        repo_path.join("src/main.rs"),
44        r#"fn main() {
45    println!("Hello from rustic-git example!");
46}
47"#,
48    )?;
49
50    fs::write(
51        repo_path.join("src/lib.rs"),
52        "// Library code goes here\npub fn hello() -> &'static str {\n    \"Hello, World!\"\n}\n",
53    )?;
54
55    println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57    // Check repository status
58    println!("Checking repository status...");
59    let status = repo.status()?;
60
61    if status.is_clean() {
62        println!("   Repository is clean (no changes)");
63    } else {
64        println!("   Repository has changes:");
65        println!("   Unstaged files: {}", status.unstaged_files().count());
66        println!("   Untracked files: {}", status.untracked_entries().count());
67
68        // Show untracked files
69        for entry in status.untracked_entries() {
70            println!("      - {}", entry.path.display());
71        }
72    }
73    println!();
74
75    // Stage specific files first
76    println!("Staging files...");
77
78    // Stage README.md first
79    repo.add(&["README.md"])?;
80    println!("Staged README.md");
81
82    // Stage all remaining files
83    repo.add_all()?;
84    println!("Staged all remaining files");
85
86    // Check status after staging
87    let status_after_staging = repo.status()?;
88    println!("\nStatus after staging:");
89    if status_after_staging.is_clean() {
90        println!("   Repository is clean (all changes staged)");
91    } else {
92        println!(
93            "   Files staged for commit: {}",
94            status_after_staging.entries.len()
95        );
96        for entry in &status_after_staging.entries {
97            println!(
98                "      Index {:?}, Worktree {:?}: {}",
99                entry.index_status,
100                entry.worktree_status,
101                entry.path.display()
102            );
103        }
104    }
105    println!();
106
107    // Create a commit
108    println!("Creating commit...");
109    let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111    println!("Commit created successfully!");
112    println!("   Full hash: {}", hash);
113    println!("   Short hash: {}", hash.short());
114    println!();
115
116    // Verify final status
117    println!("Final repository status:");
118    let final_status = repo.status()?;
119    if final_status.is_clean() {
120        println!("   Repository is clean - all changes committed!");
121    } else {
122        println!("   Repository still has uncommitted changes");
123    }
124    println!();
125
126    // Clean up
127    println!("Cleaning up example repository...");
128    fs::remove_dir_all(&repo_path)?;
129    println!("Example completed successfully!");
130
131    Ok(())
132}
More examples
Hide additional examples
examples/error_handling.rs (line 225)
207fn 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    // Pattern 1: Retry with different approach
213    println!("1. Retry Pattern - Graceful degradation:");
214
215    // Try to add specific files, fall back to add_all on failure
216    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    // Pattern 2: Partial success handling
241    println!("\n2. Partial Success Pattern:");
242
243    // Create some files with known issues
244    fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245    fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246    // Don't create bad1.txt - it will be missing
247
248    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    // Pattern 3: Status checking before operations
284    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        // Show what would be committed
295        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        // Safe commit since we know there are changes
305        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}
examples/staging_operations.rs (line 46)
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    // Clean up any previous run
20    if repo_path.exists() {
21        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
22    }
23
24    // Initialize repository and create initial commit
25    println!("Setting up repository with initial files...");
26    let repo = Repository::init(&repo_path, false)?;
27
28    // Create initial files
29    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    // Create initial commit so we can demonstrate staging tracked file changes
46    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    // Create some new files and modify existing ones
53    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    // Modify existing files
59    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    // Show status before staging
71    println!("\nStatus before staging:");
72    let status_before = repo.status()?;
73    display_status_breakdown(&status_before);
74
75    // Stage specific files using add()
76    println!("\nUsing add() to stage specific files:");
77
78    // Stage just the README.md
79    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    // Stage multiple specific files
90    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    // Stage using Path objects (alternative syntax)
101    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    // Create more files to demonstrate add_all()
117    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    // Use add_all() to stage everything remaining
136    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    // Create a commit to set up for add_update() demo
148    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    // Create new untracked files and modify existing tracked files
154    println!("Setting up files for add_update() demonstration...");
155
156    // Create new untracked files (these should NOT be staged by add_update)
157    fs::write(repo_path.join("untracked1.txt"), "This is untracked")?;
158    fs::write(repo_path.join("untracked2.txt"), "Another untracked file")?;
159
160    // Modify existing tracked files (these SHOULD be staged by add_update)
161    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    // Use add_update() to stage only tracked file changes
181    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    // Verify that untracked files are still untracked
193    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    // Demonstrate error handling
206    println!("Testing error conditions:");
207
208    // Try to add non-existent files
209    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    // Try to add empty array (should succeed but do nothing)
215    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    // Clean up
242    println!("\nCleaning up example repository...");
243    fs::remove_dir_all(&repo_path)?;
244    println!("Staging operations example completed!");
245
246    Ok(())
247}
examples/diff_operations.rs (line 50)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
examples/commit_workflows.rs (line 62)
15fn main() -> Result<()> {
16    println!("Rustic Git - Commit Workflows Example\n");
17
18    let repo_path = env::temp_dir().join("rustic_git_commit_example");
19
20    // Clean up any previous run
21    if repo_path.exists() {
22        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
23    }
24
25    // Initialize repository
26    println!("Setting up repository for commit demonstrations...");
27    let repo = Repository::init(&repo_path, false)?;
28    println!("Repository initialized\n");
29
30    println!("=== Basic Commit Operations ===\n");
31
32    // Create initial files
33    println!("Creating initial project files...");
34    fs::create_dir_all(repo_path.join("src"))?;
35
36    fs::write(
37        repo_path.join("README.md"),
38        "# Commit Demo Project\n\nThis project demonstrates commit workflows with rustic-git.\n",
39    )?;
40
41    fs::write(
42        repo_path.join("src/main.rs"),
43        r#"fn main() {
44    println!("Hello, Commit Demo!");
45}
46"#,
47    )?;
48
49    fs::write(
50        repo_path.join("Cargo.toml"),
51        r#"[package]
52name = "commit-demo"
53version = "0.1.0"
54edition = "2021"
55"#,
56    )?;
57
58    println!("Created README.md, src/main.rs, and Cargo.toml");
59
60    // Stage and commit with basic commit()
61    println!("\nStaging files for first commit...");
62    repo.add_all()?;
63
64    println!("Creating first commit with basic commit() method:");
65    let first_hash = repo.commit("Initial commit: Add project structure")?;
66
67    println!("First commit created!");
68    display_hash_info(&first_hash, "First commit");
69    println!();
70
71    println!("=== Hash Type Demonstrations ===\n");
72
73    println!("Hash type methods and usage:");
74
75    // Demonstrate different ways to work with Hash
76    let hash_as_string: String = first_hash.to_string();
77    let hash_as_str: &str = first_hash.as_str();
78    let short_hash: &str = first_hash.short();
79
80    println!("   Hash conversions:");
81    println!("      as_str(): '{}'", hash_as_str);
82    println!("      short(): '{}'", short_hash);
83    println!("      to_string(): '{}'", hash_as_string);
84    println!("      Display: '{}'", first_hash);
85
86    // Demonstrate Hash equality and cloning
87    let cloned_hash = first_hash.clone();
88    println!("\n   Hash operations:");
89    println!("      Original == Clone: {}", first_hash == cloned_hash);
90    println!(
91        "      Hash length: {} characters",
92        first_hash.as_str().len()
93    );
94    println!(
95        "      Short hash length: {} characters",
96        first_hash.short().len()
97    );
98
99    // Create Hash from different sources for demonstration
100    let hash_from_string: Hash = "1234567890abcdef".to_string().into();
101    let hash_from_str: Hash = "fedcba0987654321".into();
102
103    println!("      Hash from String: {}", hash_from_string.short());
104    println!("      Hash from &str: {}", hash_from_str.short());
105    println!();
106
107    println!("=== Commits with Custom Authors ===\n");
108
109    // Create more files to commit with custom author
110    println!("Adding features for custom author commit...");
111    fs::create_dir_all(repo_path.join("tests"))?;
112
113    fs::write(
114        repo_path.join("src/lib.rs"),
115        r#"//! Commit demo library
116
117pub fn greet(name: &str) -> String {
118    format!("Hello, {}! This is a commit demo.", name)
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_greet() {
127        assert_eq!(greet("Alice"), "Hello, Alice! This is a commit demo.");
128    }
129}
130"#,
131    )?;
132
133    fs::write(
134        repo_path.join("tests/integration_test.rs"),
135        r#"use commit_demo::greet;
136
137#[test]
138fn test_integration() {
139    let result = greet("Integration");
140    assert!(result.contains("Integration"));
141    assert!(result.contains("commit demo"));
142}
143"#,
144    )?;
145
146    println!("Created src/lib.rs and tests/integration_test.rs");
147
148    // Stage and commit with custom author
149    repo.add_all()?;
150    println!("\nCreating commit with custom author:");
151    let second_hash = repo.commit_with_author(
152        "Add library code and tests\n\n- Implement greet function with proper documentation\n- Add unit tests and integration tests\n- Prepare for version 0.2.0 release",
153        "Jane Developer <jane.dev@example.com>"
154    )?;
155
156    println!("Commit with custom author created!");
157    display_hash_info(&second_hash, "Second commit (custom author)");
158    println!();
159
160    println!("=== Multiple Commit Workflow ===\n");
161
162    // Demonstrate a series of commits
163    let mut commit_hashes = vec![first_hash, second_hash];
164
165    // Commit 3: Update version
166    println!("Step 1: Update version information...");
167    fs::write(
168        repo_path.join("Cargo.toml"),
169        r#"[package]
170name = "commit-demo"
171version = "0.2.0"
172edition = "2021"
173description = "A demo project for commit workflows"
174"#,
175    )?;
176
177    repo.add(&["Cargo.toml"])?;
178    let third_hash = repo.commit("Bump version to 0.2.0 and add description")?;
179    commit_hashes.push(third_hash);
180
181    // Commit 4: Add documentation
182    println!("Step 2: Add documentation...");
183    fs::write(
184        repo_path.join("CHANGELOG.md"),
185        r#"# Changelog
186
187## [0.2.0] - 2024-01-01
188
189### Added
190- Library functionality with greet function
191- Comprehensive test suite
192- Project documentation
193
194## [0.1.0] - 2024-01-01
195
196### Added
197- Initial project structure
198- Basic Cargo configuration
199"#,
200    )?;
201
202    repo.add(&["CHANGELOG.md"])?;
203    let fourth_hash = repo.commit_with_author(
204        "docs: Add CHANGELOG with version history",
205        "Doc Writer <docs@example.com>",
206    )?;
207    commit_hashes.push(fourth_hash);
208
209    // Commit 5: Final polish
210    println!("Step 3: Final polish...");
211    fs::write(
212        repo_path.join("README.md"),
213        r#"# Commit Demo Project
214
215This project demonstrates commit workflows with rustic-git.
216
217## Features
218
219- Clean, type-safe Git operations
220- Comprehensive commit history
221- Multiple author support
222- Hash management utilities
223
224## Usage
225
226```rust
227use commit_demo::greet;
228
229fn main() {
230    println!("{}", greet("World"));
231}
232```
233
234## Version
235
236Current version: 0.2.0
237
238See CHANGELOG.md for version history.
239"#,
240    )?;
241
242    repo.add(&["README.md"])?;
243    let fifth_hash = repo.commit("docs: Enhance README with usage examples and features")?;
244    commit_hashes.push(fifth_hash);
245
246    println!("\nComplete commit history created!");
247
248    // Display all commits
249    println!("\n=== Commit History Summary ===\n");
250
251    for (i, hash) in commit_hashes.iter().enumerate() {
252        println!("{}. Commit {}", i + 1, i + 1);
253        display_hash_info(hash, &format!("Commit {}", i + 1));
254        println!();
255    }
256
257    // Compare hashes
258    println!("Hash comparisons:");
259    println!(
260        "   First commit == Last commit: {}",
261        commit_hashes[0] == commit_hashes[4]
262    );
263    println!("   All hashes unique: {}", all_unique(&commit_hashes));
264
265    // Show short hashes for all commits
266    println!("\nAll commit short hashes:");
267    for (i, hash) in commit_hashes.iter().enumerate() {
268        println!("   {}: {}", i + 1, hash.short());
269    }
270    println!();
271
272    println!("=== Error Handling for Commits ===\n");
273
274    // Try to commit with nothing staged (should fail)
275    println!("Testing commit with no staged changes:");
276    match repo.commit("This should fail - no changes") {
277        Ok(_hash) => println!("   Unexpectedly succeeded with empty commit"),
278        Err(e) => {
279            println!("   Expected error for empty commit: {:?}", e);
280            println!("   This is normal behavior - Git requires changes to commit");
281        }
282    }
283
284    // Try commit with custom author but no changes (should also fail)
285    println!("\nTesting custom author commit with no changes:");
286    match repo.commit_with_author("This should also fail", "Test Author <test@example.com>") {
287        Ok(_hash) => println!("   Unexpectedly succeeded with empty custom author commit"),
288        Err(e) => {
289            println!(
290                "   Expected error for empty commit with custom author: {:?}",
291                e
292            );
293        }
294    }
295
296    // Test commit with empty message (Git might handle this differently)
297    println!("\nTesting commit with empty message:");
298
299    // Create a change to commit
300    fs::write(repo_path.join("temp_for_empty_message.txt"), "temp content")?;
301    repo.add(&["temp_for_empty_message.txt"])?;
302
303    match repo.commit("") {
304        Ok(hash) => {
305            println!("   Commit with empty message succeeded: {}", hash.short());
306            println!("   Some Git configurations allow empty commit messages");
307        }
308        Err(e) => {
309            println!("   Empty commit message rejected: {:?}", e);
310        }
311    }
312
313    println!();
314
315    println!("=== Final Repository State ===\n");
316
317    let final_status = repo.status()?;
318    if final_status.is_clean() {
319        println!("Repository is clean - all changes committed!");
320    } else {
321        println!(
322            "Repository has {} uncommitted changes",
323            final_status.entries.len()
324        );
325    }
326
327    println!("\nWorkflow summary:");
328    println!("   Total commits created: {}", commit_hashes.len());
329    println!("   Hash examples demonstrated: [OK]");
330    println!("   Custom author commits: [OK]");
331    println!("   Error handling tested: [OK]");
332
333    // Clean up
334    println!("\nCleaning up example repository...");
335    fs::remove_dir_all(&repo_path)?;
336    println!("Commit workflows example completed!");
337
338    Ok(())
339}
Source

pub fn add_update(&self) -> Result<()>

Add all tracked files that have been modified (equivalent to git add -u).

§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/staging_operations.rs (line 182)
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    // Clean up any previous run
20    if repo_path.exists() {
21        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
22    }
23
24    // Initialize repository and create initial commit
25    println!("Setting up repository with initial files...");
26    let repo = Repository::init(&repo_path, false)?;
27
28    // Create initial files
29    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    // Create initial commit so we can demonstrate staging tracked file changes
46    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    // Create some new files and modify existing ones
53    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    // Modify existing files
59    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    // Show status before staging
71    println!("\nStatus before staging:");
72    let status_before = repo.status()?;
73    display_status_breakdown(&status_before);
74
75    // Stage specific files using add()
76    println!("\nUsing add() to stage specific files:");
77
78    // Stage just the README.md
79    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    // Stage multiple specific files
90    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    // Stage using Path objects (alternative syntax)
101    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    // Create more files to demonstrate add_all()
117    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    // Use add_all() to stage everything remaining
136    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    // Create a commit to set up for add_update() demo
148    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    // Create new untracked files and modify existing tracked files
154    println!("Setting up files for add_update() demonstration...");
155
156    // Create new untracked files (these should NOT be staged by add_update)
157    fs::write(repo_path.join("untracked1.txt"), "This is untracked")?;
158    fs::write(repo_path.join("untracked2.txt"), "Another untracked file")?;
159
160    // Modify existing tracked files (these SHOULD be staged by add_update)
161    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    // Use add_update() to stage only tracked file changes
181    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    // Verify that untracked files are still untracked
193    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    // Demonstrate error handling
206    println!("Testing error conditions:");
207
208    // Try to add non-existent files
209    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    // Try to add empty array (should succeed but do nothing)
215    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    // Clean up
242    println!("\nCleaning up example repository...");
243    fs::remove_dir_all(&repo_path)?;
244    println!("Staging operations example completed!");
245
246    Ok(())
247}
Source§

impl Repository

Source

pub fn branches(&self) -> Result<BranchList>

List all branches in the repository

Examples found in repository?
examples/merge_operations.rs (line 68)
45fn demonstrate_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
46    println!("--- Demonstrating Fast-Forward Merge ---\n");
47
48    // Create initial commit
49    println!("1. Creating initial commit on master...");
50    let file1_path = temp_dir.join("README.md");
51    fs::write(&file1_path, "# Project\n\nInitial content")?;
52    repo.add(&["README.md"])?;
53    let initial_commit = repo.commit("Initial commit")?;
54    println!("   Created commit: {}", initial_commit);
55
56    // Create feature branch and add commits
57    println!("\n2. Creating feature branch and adding commits...");
58    repo.checkout_new("feature/fast-forward", None)?;
59
60    let file2_path = temp_dir.join("feature.txt");
61    fs::write(&file2_path, "New feature implementation")?;
62    repo.add(&["feature.txt"])?;
63    let feature_commit = repo.commit("Add new feature")?;
64    println!("   Feature commit: {}", feature_commit);
65
66    // Switch back to master
67    println!("\n3. Switching back to master...");
68    let branches = repo.branches()?;
69    let master_branch = branches.find("master").unwrap();
70    repo.checkout(master_branch)?;
71    println!("   Switched to master");
72
73    // Perform fast-forward merge
74    println!("\n4. Performing fast-forward merge...");
75    let merge_status = repo.merge("feature/fast-forward")?;
76
77    match merge_status {
78        MergeStatus::FastForward(hash) => {
79            println!("   ✓ Fast-forward merge completed!");
80            println!("   New HEAD: {}", hash);
81            println!("   Both files are now present on master");
82        }
83        _ => println!("   Unexpected merge result: {:?}", merge_status),
84    }
85
86    println!("   Files in repository:");
87    for file in ["README.md", "feature.txt"] {
88        if temp_dir.join(file).exists() {
89            println!("     ✓ {}", file);
90        }
91    }
92
93    Ok(())
94}
95
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
More examples
Hide additional examples
examples/branch_operations.rs (line 23)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
Source

pub fn current_branch(&self) -> Result<Option<Branch>>

Get the current branch

Examples found in repository?
examples/branch_operations.rs (line 30)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
Source

pub fn create_branch( &self, name: &str, start_point: Option<&str>, ) -> Result<Branch>

Create a new branch

Examples found in repository?
examples/branch_operations.rs (line 40)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
Source

pub fn delete_branch(&self, branch: &Branch, force: bool) -> Result<()>

Delete a branch

Examples found in repository?
examples/branch_operations.rs (line 129)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
Source

pub fn checkout(&self, branch: &Branch) -> Result<()>

Switch to an existing branch

Examples found in repository?
examples/merge_operations.rs (line 70)
45fn demonstrate_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
46    println!("--- Demonstrating Fast-Forward Merge ---\n");
47
48    // Create initial commit
49    println!("1. Creating initial commit on master...");
50    let file1_path = temp_dir.join("README.md");
51    fs::write(&file1_path, "# Project\n\nInitial content")?;
52    repo.add(&["README.md"])?;
53    let initial_commit = repo.commit("Initial commit")?;
54    println!("   Created commit: {}", initial_commit);
55
56    // Create feature branch and add commits
57    println!("\n2. Creating feature branch and adding commits...");
58    repo.checkout_new("feature/fast-forward", None)?;
59
60    let file2_path = temp_dir.join("feature.txt");
61    fs::write(&file2_path, "New feature implementation")?;
62    repo.add(&["feature.txt"])?;
63    let feature_commit = repo.commit("Add new feature")?;
64    println!("   Feature commit: {}", feature_commit);
65
66    // Switch back to master
67    println!("\n3. Switching back to master...");
68    let branches = repo.branches()?;
69    let master_branch = branches.find("master").unwrap();
70    repo.checkout(master_branch)?;
71    println!("   Switched to master");
72
73    // Perform fast-forward merge
74    println!("\n4. Performing fast-forward merge...");
75    let merge_status = repo.merge("feature/fast-forward")?;
76
77    match merge_status {
78        MergeStatus::FastForward(hash) => {
79            println!("   ✓ Fast-forward merge completed!");
80            println!("   New HEAD: {}", hash);
81            println!("   Both files are now present on master");
82        }
83        _ => println!("   Unexpected merge result: {:?}", merge_status),
84    }
85
86    println!("   Files in repository:");
87    for file in ["README.md", "feature.txt"] {
88        if temp_dir.join(file).exists() {
89            println!("     ✓ {}", file);
90        }
91    }
92
93    Ok(())
94}
95
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
More examples
Hide additional examples
examples/branch_operations.rs (line 75)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
Source

pub fn checkout_new( &self, name: &str, start_point: Option<&str>, ) -> Result<Branch>

Create a new branch and switch to it

Examples found in repository?
examples/merge_operations.rs (line 58)
45fn demonstrate_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
46    println!("--- Demonstrating Fast-Forward Merge ---\n");
47
48    // Create initial commit
49    println!("1. Creating initial commit on master...");
50    let file1_path = temp_dir.join("README.md");
51    fs::write(&file1_path, "# Project\n\nInitial content")?;
52    repo.add(&["README.md"])?;
53    let initial_commit = repo.commit("Initial commit")?;
54    println!("   Created commit: {}", initial_commit);
55
56    // Create feature branch and add commits
57    println!("\n2. Creating feature branch and adding commits...");
58    repo.checkout_new("feature/fast-forward", None)?;
59
60    let file2_path = temp_dir.join("feature.txt");
61    fs::write(&file2_path, "New feature implementation")?;
62    repo.add(&["feature.txt"])?;
63    let feature_commit = repo.commit("Add new feature")?;
64    println!("   Feature commit: {}", feature_commit);
65
66    // Switch back to master
67    println!("\n3. Switching back to master...");
68    let branches = repo.branches()?;
69    let master_branch = branches.find("master").unwrap();
70    repo.checkout(master_branch)?;
71    println!("   Switched to master");
72
73    // Perform fast-forward merge
74    println!("\n4. Performing fast-forward merge...");
75    let merge_status = repo.merge("feature/fast-forward")?;
76
77    match merge_status {
78        MergeStatus::FastForward(hash) => {
79            println!("   ✓ Fast-forward merge completed!");
80            println!("   New HEAD: {}", hash);
81            println!("   Both files are now present on master");
82        }
83        _ => println!("   Unexpected merge result: {:?}", merge_status),
84    }
85
86    println!("   Files in repository:");
87    for file in ["README.md", "feature.txt"] {
88        if temp_dir.join(file).exists() {
89            println!("     ✓ {}", file);
90        }
91    }
92
93    Ok(())
94}
95
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
More examples
Hide additional examples
examples/branch_operations.rs (line 55)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
Source§

impl Repository

Source

pub fn commit(&self, message: &str) -> Result<Hash>

Create a commit with the given message.

§Arguments
  • message - The commit message
§Returns

A Result containing the Hash of the new commit or a GitError.

Examples found in repository?
examples/merge_operations.rs (line 53)
45fn demonstrate_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
46    println!("--- Demonstrating Fast-Forward Merge ---\n");
47
48    // Create initial commit
49    println!("1. Creating initial commit on master...");
50    let file1_path = temp_dir.join("README.md");
51    fs::write(&file1_path, "# Project\n\nInitial content")?;
52    repo.add(&["README.md"])?;
53    let initial_commit = repo.commit("Initial commit")?;
54    println!("   Created commit: {}", initial_commit);
55
56    // Create feature branch and add commits
57    println!("\n2. Creating feature branch and adding commits...");
58    repo.checkout_new("feature/fast-forward", None)?;
59
60    let file2_path = temp_dir.join("feature.txt");
61    fs::write(&file2_path, "New feature implementation")?;
62    repo.add(&["feature.txt"])?;
63    let feature_commit = repo.commit("Add new feature")?;
64    println!("   Feature commit: {}", feature_commit);
65
66    // Switch back to master
67    println!("\n3. Switching back to master...");
68    let branches = repo.branches()?;
69    let master_branch = branches.find("master").unwrap();
70    repo.checkout(master_branch)?;
71    println!("   Switched to master");
72
73    // Perform fast-forward merge
74    println!("\n4. Performing fast-forward merge...");
75    let merge_status = repo.merge("feature/fast-forward")?;
76
77    match merge_status {
78        MergeStatus::FastForward(hash) => {
79            println!("   ✓ Fast-forward merge completed!");
80            println!("   New HEAD: {}", hash);
81            println!("   Both files are now present on master");
82        }
83        _ => println!("   Unexpected merge result: {:?}", merge_status),
84    }
85
86    println!("   Files in repository:");
87    for file in ["README.md", "feature.txt"] {
88        if temp_dir.join(file).exists() {
89            println!("     ✓ {}", file);
90        }
91    }
92
93    Ok(())
94}
95
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
More examples
Hide additional examples
examples/error_handling.rs (line 109)
100fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
101    println!("File Operation Error Scenarios:\n");
102
103    // Set up a valid repository first
104    let repo = Repository::init(repo_path, false)?;
105
106    // Create some test files
107    fs::write(repo_path.join("test.txt"), "Test content")?;
108    repo.add(&["test.txt"])?;
109    repo.commit("Initial commit")?;
110
111    // 1. Adding non-existent files
112    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    // 2. Mixed valid and invalid files
125    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            // Check what actually got staged
132            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            // Try recovery: add valid files individually
140            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
155/// Demonstrate Git command related errors
156fn 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    // 1. Empty commit (no staged changes)
162    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    // 2. Commit with problematic message
178    println!("\n2. Testing commit message edge cases:");
179
180    // Stage a file for testing
181    fs::write(
182        repo_path.join("commit_test.txt"),
183        "Content for commit testing",
184    )?;
185    repo.add(&["commit_test.txt"])?;
186
187    // Very long commit message
188    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
206/// Demonstrate error recovery patterns
207fn 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    // Pattern 1: Retry with different approach
213    println!("1. Retry Pattern - Graceful degradation:");
214
215    // Try to add specific files, fall back to add_all on failure
216    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    // Pattern 2: Partial success handling
241    println!("\n2. Partial Success Pattern:");
242
243    // Create some files with known issues
244    fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245    fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246    // Don't create bad1.txt - it will be missing
247
248    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    // Pattern 3: Status checking before operations
284    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        // Show what would be committed
295        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        // Safe commit since we know there are changes
305        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
315/// Demonstrate error propagation strategies
316fn demonstrate_error_propagation_strategies(base_path: &std::path::Path) -> Result<()> {
317    println!("Error Propagation Strategies:\n");
318
319    // Strategy 1: Early return with ?
320    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    // Strategy 2: Collect all errors
327    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    // Strategy 3: Error context enrichment
346    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
356/// Workflow that returns early on first error
357fn workflow_with_early_return(base_path: &std::path::Path) -> Result<String> {
358    let repo_path = base_path.join("early_return_test");
359
360    // This will propagate any error immediately
361    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    // Clean up
369    fs::remove_dir_all(&repo_path)?;
370
371    Ok(format!("Completed with commit {}", hash.short()))
372}
373
374/// Workflow that collects all errors instead of failing fast
375fn 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    // Step 1: Initialize repo
380    results.push(Repository::init(&repo_path, false).map(|_| "Repository initialized".to_string()));
381
382    // Step 2: Add files (some may fail)
383    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    // Step 3: Try to add files (continue even if repo init failed)
394    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    // Cleanup (don't add to results as it's not part of main workflow)
414    let _ = fs::remove_dir_all(&repo_path);
415
416    results
417}
418
419/// Workflow with enhanced error context
420fn workflow_with_context(base_path: &std::path::Path) -> Result<String> {
421    let repo_path = base_path.join("context_test");
422
423    // Add context to errors
424    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    // Create file with context
432    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    // Add with context
438    repo.add(&["context_file.txt"]).inspect_err(|_e| {
439        eprintln!("Context: Failed to stage context_file.txt");
440    })?;
441
442    // Commit with context
443    let hash = repo.commit("Context workflow commit").inspect_err(|_e| {
444        eprintln!("Context: Failed to create commit");
445    })?;
446
447    // Clean up
448    fs::remove_dir_all(&repo_path)?;
449
450    Ok(format!("Context workflow completed: {}", hash.short()))
451}
examples/reset_operations.rs (line 53)
43fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44    println!("--- Demonstrating Reset Modes ---\n");
45
46    // Create initial commits
47    println!("1. Creating initial commits...");
48
49    // First commit
50    let file1_path = temp_dir.join("file1.txt");
51    fs::write(&file1_path, "Initial content")?;
52    repo.add(&["file1.txt"])?;
53    let first_commit = repo.commit("Initial commit")?;
54    println!("   Created first commit: {}", first_commit);
55
56    // Second commit
57    let file2_path = temp_dir.join("file2.txt");
58    fs::write(&file2_path, "Second file content")?;
59    repo.add(&["file2.txt"])?;
60    let second_commit = repo.commit("Add file2.txt")?;
61    println!("   Created second commit: {}", second_commit);
62
63    // Third commit
64    fs::write(&file1_path, "Modified content")?;
65    repo.add(&["file1.txt"])?;
66    let third_commit = repo.commit("Modify file1.txt")?;
67    println!("   Created third commit: {}", third_commit);
68
69    // Show current status
70    println!("\n2. Current repository state:");
71    show_repo_state(repo)?;
72
73    // Demonstrate soft reset
74    println!("\n3. Performing soft reset to second commit...");
75    repo.reset_soft(&second_commit.to_string())?;
76
77    println!("   After soft reset:");
78    show_repo_state(repo)?;
79    println!("   Note: Changes are still staged, working directory unchanged");
80
81    // Reset back to third commit for next demonstration
82    repo.reset_hard(&third_commit.to_string())?;
83
84    // Demonstrate mixed reset (default)
85    println!("\n4. Performing mixed reset to second commit...");
86    repo.reset_mixed(&second_commit.to_string())?;
87
88    println!("   After mixed reset:");
89    show_repo_state(repo)?;
90    println!("   Note: Changes are unstaged but preserved in working directory");
91
92    // Reset back to third commit for next demonstration
93    repo.reset_hard(&third_commit.to_string())?;
94
95    // Demonstrate hard reset
96    println!("\n5. Performing hard reset to first commit...");
97    repo.reset_hard(&first_commit.to_string())?;
98
99    println!("   After hard reset:");
100    show_repo_state(repo)?;
101    println!("   Note: All changes discarded, working directory matches commit");
102
103    // Demonstrate reset_with_mode for flexibility
104    println!("\n6. Using reset_with_mode for explicit control...");
105
106    // Recreate second commit for demo
107    fs::write(&file2_path, "Recreated second file")?;
108    repo.add(&["file2.txt"])?;
109    let _new_commit = repo.commit("Recreate file2.txt")?;
110
111    repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112    println!("   Used ResetMode::Mixed explicitly");
113    show_repo_state(repo)?;
114
115    Ok(())
116}
examples/basic_usage.rs (line 109)
16fn main() -> Result<()> {
17    println!("Rustic Git - Basic Usage Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing new repository at: {}", repo_path.display());
28
29    // Initialize a new repository
30    let repo = Repository::init(&repo_path, false)?;
31    println!("Repository initialized successfully\n");
32
33    // Create some example files
34    println!("Creating example files...");
35    fs::create_dir_all(repo_path.join("src"))?;
36
37    fs::write(
38        repo_path.join("README.md"),
39        "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40    )?;
41
42    fs::write(
43        repo_path.join("src/main.rs"),
44        r#"fn main() {
45    println!("Hello from rustic-git example!");
46}
47"#,
48    )?;
49
50    fs::write(
51        repo_path.join("src/lib.rs"),
52        "// Library code goes here\npub fn hello() -> &'static str {\n    \"Hello, World!\"\n}\n",
53    )?;
54
55    println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57    // Check repository status
58    println!("Checking repository status...");
59    let status = repo.status()?;
60
61    if status.is_clean() {
62        println!("   Repository is clean (no changes)");
63    } else {
64        println!("   Repository has changes:");
65        println!("   Unstaged files: {}", status.unstaged_files().count());
66        println!("   Untracked files: {}", status.untracked_entries().count());
67
68        // Show untracked files
69        for entry in status.untracked_entries() {
70            println!("      - {}", entry.path.display());
71        }
72    }
73    println!();
74
75    // Stage specific files first
76    println!("Staging files...");
77
78    // Stage README.md first
79    repo.add(&["README.md"])?;
80    println!("Staged README.md");
81
82    // Stage all remaining files
83    repo.add_all()?;
84    println!("Staged all remaining files");
85
86    // Check status after staging
87    let status_after_staging = repo.status()?;
88    println!("\nStatus after staging:");
89    if status_after_staging.is_clean() {
90        println!("   Repository is clean (all changes staged)");
91    } else {
92        println!(
93            "   Files staged for commit: {}",
94            status_after_staging.entries.len()
95        );
96        for entry in &status_after_staging.entries {
97            println!(
98                "      Index {:?}, Worktree {:?}: {}",
99                entry.index_status,
100                entry.worktree_status,
101                entry.path.display()
102            );
103        }
104    }
105    println!();
106
107    // Create a commit
108    println!("Creating commit...");
109    let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111    println!("Commit created successfully!");
112    println!("   Full hash: {}", hash);
113    println!("   Short hash: {}", hash.short());
114    println!();
115
116    // Verify final status
117    println!("Final repository status:");
118    let final_status = repo.status()?;
119    if final_status.is_clean() {
120        println!("   Repository is clean - all changes committed!");
121    } else {
122        println!("   Repository still has uncommitted changes");
123    }
124    println!();
125
126    // Clean up
127    println!("Cleaning up example repository...");
128    fs::remove_dir_all(&repo_path)?;
129    println!("Example completed successfully!");
130
131    Ok(())
132}
examples/branch_operations.rs (line 19)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
examples/config_operations.rs (line 91)
14fn main() -> Result<()> {
15    println!("Rustic Git - Repository Configuration Operations Example\n");
16
17    // Use a temporary directory for this example
18    let repo_path = env::temp_dir().join("rustic_git_config_example");
19
20    // Clean up any previous run
21    if repo_path.exists() {
22        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
23    }
24
25    println!("Initializing new repository at: {}", repo_path.display());
26
27    // Initialize a new repository
28    let repo = Repository::init(&repo_path, false)?;
29
30    // ==================== USER CONFIGURATION ====================
31
32    println!("\n[CONFIG] Configuring git user settings...");
33
34    // Set user configuration (convenience method)
35    repo.config()
36        .set_user("Alice Developer", "alice@example.com")?;
37    println!("Set user configuration");
38
39    // Verify user configuration
40    let (name, email) = repo.config().get_user()?;
41    println!("Current user: {} <{}>", name, email);
42
43    // ==================== GENERAL CONFIGURATION ====================
44
45    println!("\n[CONFIG] Setting repository configuration values...");
46
47    // Set various git configuration values
48    repo.config().set("core.autocrlf", "false")?;
49    repo.config().set("core.ignorecase", "true")?;
50    repo.config().set("pull.rebase", "true")?;
51    repo.config().set("push.default", "simple")?;
52    repo.config().set("branch.autosetupmerge", "always")?;
53
54    println!("Set core configuration values");
55
56    // Get and display configuration values
57    println!("\n[CONFIG] Current repository configuration:");
58
59    let configs = [
60        "core.autocrlf",
61        "core.ignorecase",
62        "pull.rebase",
63        "push.default",
64        "branch.autosetupmerge",
65    ];
66
67    for config_key in &configs {
68        match repo.config().get(config_key) {
69            Ok(value) => println!("  {} = {}", config_key, value),
70            Err(_) => println!("  {} = <not set>", config_key),
71        }
72    }
73
74    // ==================== CONFIGURATION WITH COMMITS ====================
75
76    println!("\n[COMMIT] Testing configuration with commit operations...");
77
78    // Create a test file
79    let test_file_path = repo_path.join("test.txt");
80    fs::write(
81        &test_file_path,
82        "Hello from rustic-git configuration example!",
83    )?;
84    println!("Created test file: test.txt");
85
86    // Stage the file
87    repo.add(&["test.txt"])?;
88    println!("Staged test.txt");
89
90    // Create a commit (this will use our configured user)
91    let commit_hash = repo.commit("Add test file with configuration example")?;
92    println!("Created commit: {}", commit_hash.short());
93
94    // ==================== CONFIGURATION MODIFICATION ====================
95
96    println!("\n[UPDATE] Modifying configuration values...");
97
98    // Change some configuration values
99    repo.config().set("core.autocrlf", "true")?;
100    repo.config()
101        .set("user.email", "alice.developer@newcompany.com")?;
102
103    println!("Updated configuration values");
104
105    // Display updated values
106    let autocrlf = repo.config().get("core.autocrlf")?;
107    let (updated_name, updated_email) = repo.config().get_user()?;
108
109    println!("Updated configuration:");
110    println!("  core.autocrlf = {}", autocrlf);
111    println!("  user: {} <{}>", updated_name, updated_email);
112
113    // ==================== CONFIGURATION REMOVAL ====================
114
115    println!("\n[REMOVE] Removing configuration values...");
116
117    // Remove a configuration value
118    repo.config().unset("branch.autosetupmerge")?;
119    println!("Removed branch.autosetupmerge");
120
121    // Try to get the removed value (should fail)
122    match repo.config().get("branch.autosetupmerge") {
123        Ok(value) => println!("Unexpected: branch.autosetupmerge = {}", value),
124        Err(_) => println!("Confirmed: branch.autosetupmerge is not set"),
125    }
126
127    // ==================== ADVANCED CONFIGURATION ====================
128
129    println!("\n[ADVANCED] Setting advanced configuration...");
130
131    // Set some advanced git configuration
132    repo.config().set("diff.tool", "vimdiff")?;
133    repo.config().set("merge.tool", "vimdiff")?;
134    repo.config().set("alias.st", "status")?;
135    repo.config().set("alias.co", "checkout")?;
136    repo.config().set("alias.br", "branch")?;
137    repo.config().set("alias.ci", "commit")?;
138
139    println!("Set advanced configuration (diff/merge tools and aliases)");
140
141    // Display all custom configuration
142    println!("\n[SUMMARY] Complete repository configuration summary:");
143
144    let all_configs = [
145        ("User", vec![("user.name", ""), ("user.email", "")]),
146        ("Core", vec![("core.autocrlf", ""), ("core.ignorecase", "")]),
147        ("Workflow", vec![("pull.rebase", ""), ("push.default", "")]),
148        ("Tools", vec![("diff.tool", ""), ("merge.tool", "")]),
149        (
150            "Aliases",
151            vec![
152                ("alias.st", ""),
153                ("alias.co", ""),
154                ("alias.br", ""),
155                ("alias.ci", ""),
156            ],
157        ),
158    ];
159
160    for (category, configs) in &all_configs {
161        println!("\n  {}:", category);
162        for (key, _) in configs {
163            match repo.config().get(key) {
164                Ok(value) => println!("    {} = {}", key, value),
165                Err(_) => println!("    {} = <not set>", key),
166            }
167        }
168    }
169
170    // ==================== PRACTICAL EXAMPLE ====================
171
172    println!("\n[TEAM] Practical example: Setting up repository for a team...");
173
174    // Configure repository for team development
175    repo.config().set("user.name", "Team Member")?;
176    repo.config().set("user.email", "team@company.com")?;
177    repo.config().set("core.autocrlf", "input")?;
178    repo.config().set("core.safecrlf", "true")?;
179    repo.config().set("pull.rebase", "true")?;
180    repo.config().set("push.default", "current")?;
181    repo.config().set("init.defaultBranch", "main")?;
182
183    println!("Configured repository for team development");
184
185    // Create another commit with the team configuration
186    fs::write(
187        repo_path.join("team.md"),
188        "# Team Development\n\nThis repository is configured for team development.",
189    )?;
190    repo.add(&["team.md"])?;
191    let team_commit = repo.commit("Add team development documentation")?;
192
193    println!("Created team commit: {}", team_commit.short());
194
195    // Final verification
196    let (final_name, final_email) = repo.config().get_user()?;
197    println!("\n[FINAL] Final repository configuration:");
198    println!("  User: {} <{}>", final_name, final_email);
199    println!("  Repository configured for team development workflow");
200
201    // ==================== CLEANUP ====================
202
203    println!("\n[CLEANUP] Cleaning up...");
204    fs::remove_dir_all(&repo_path).expect("Failed to clean up example");
205    println!("Example completed successfully!");
206
207    Ok(())
208}
Source

pub fn commit_with_author(&self, message: &str, author: &str) -> Result<Hash>

Create a commit with the given message and author.

§Arguments
  • message - The commit message
  • author - The author in format “Name email@example.com
§Returns

A Result containing the Hash of the new commit or a GitError.

Examples found in repository?
examples/commit_workflows.rs (lines 151-154)
15fn main() -> Result<()> {
16    println!("Rustic Git - Commit Workflows Example\n");
17
18    let repo_path = env::temp_dir().join("rustic_git_commit_example");
19
20    // Clean up any previous run
21    if repo_path.exists() {
22        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
23    }
24
25    // Initialize repository
26    println!("Setting up repository for commit demonstrations...");
27    let repo = Repository::init(&repo_path, false)?;
28    println!("Repository initialized\n");
29
30    println!("=== Basic Commit Operations ===\n");
31
32    // Create initial files
33    println!("Creating initial project files...");
34    fs::create_dir_all(repo_path.join("src"))?;
35
36    fs::write(
37        repo_path.join("README.md"),
38        "# Commit Demo Project\n\nThis project demonstrates commit workflows with rustic-git.\n",
39    )?;
40
41    fs::write(
42        repo_path.join("src/main.rs"),
43        r#"fn main() {
44    println!("Hello, Commit Demo!");
45}
46"#,
47    )?;
48
49    fs::write(
50        repo_path.join("Cargo.toml"),
51        r#"[package]
52name = "commit-demo"
53version = "0.1.0"
54edition = "2021"
55"#,
56    )?;
57
58    println!("Created README.md, src/main.rs, and Cargo.toml");
59
60    // Stage and commit with basic commit()
61    println!("\nStaging files for first commit...");
62    repo.add_all()?;
63
64    println!("Creating first commit with basic commit() method:");
65    let first_hash = repo.commit("Initial commit: Add project structure")?;
66
67    println!("First commit created!");
68    display_hash_info(&first_hash, "First commit");
69    println!();
70
71    println!("=== Hash Type Demonstrations ===\n");
72
73    println!("Hash type methods and usage:");
74
75    // Demonstrate different ways to work with Hash
76    let hash_as_string: String = first_hash.to_string();
77    let hash_as_str: &str = first_hash.as_str();
78    let short_hash: &str = first_hash.short();
79
80    println!("   Hash conversions:");
81    println!("      as_str(): '{}'", hash_as_str);
82    println!("      short(): '{}'", short_hash);
83    println!("      to_string(): '{}'", hash_as_string);
84    println!("      Display: '{}'", first_hash);
85
86    // Demonstrate Hash equality and cloning
87    let cloned_hash = first_hash.clone();
88    println!("\n   Hash operations:");
89    println!("      Original == Clone: {}", first_hash == cloned_hash);
90    println!(
91        "      Hash length: {} characters",
92        first_hash.as_str().len()
93    );
94    println!(
95        "      Short hash length: {} characters",
96        first_hash.short().len()
97    );
98
99    // Create Hash from different sources for demonstration
100    let hash_from_string: Hash = "1234567890abcdef".to_string().into();
101    let hash_from_str: Hash = "fedcba0987654321".into();
102
103    println!("      Hash from String: {}", hash_from_string.short());
104    println!("      Hash from &str: {}", hash_from_str.short());
105    println!();
106
107    println!("=== Commits with Custom Authors ===\n");
108
109    // Create more files to commit with custom author
110    println!("Adding features for custom author commit...");
111    fs::create_dir_all(repo_path.join("tests"))?;
112
113    fs::write(
114        repo_path.join("src/lib.rs"),
115        r#"//! Commit demo library
116
117pub fn greet(name: &str) -> String {
118    format!("Hello, {}! This is a commit demo.", name)
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_greet() {
127        assert_eq!(greet("Alice"), "Hello, Alice! This is a commit demo.");
128    }
129}
130"#,
131    )?;
132
133    fs::write(
134        repo_path.join("tests/integration_test.rs"),
135        r#"use commit_demo::greet;
136
137#[test]
138fn test_integration() {
139    let result = greet("Integration");
140    assert!(result.contains("Integration"));
141    assert!(result.contains("commit demo"));
142}
143"#,
144    )?;
145
146    println!("Created src/lib.rs and tests/integration_test.rs");
147
148    // Stage and commit with custom author
149    repo.add_all()?;
150    println!("\nCreating commit with custom author:");
151    let second_hash = repo.commit_with_author(
152        "Add library code and tests\n\n- Implement greet function with proper documentation\n- Add unit tests and integration tests\n- Prepare for version 0.2.0 release",
153        "Jane Developer <jane.dev@example.com>"
154    )?;
155
156    println!("Commit with custom author created!");
157    display_hash_info(&second_hash, "Second commit (custom author)");
158    println!();
159
160    println!("=== Multiple Commit Workflow ===\n");
161
162    // Demonstrate a series of commits
163    let mut commit_hashes = vec![first_hash, second_hash];
164
165    // Commit 3: Update version
166    println!("Step 1: Update version information...");
167    fs::write(
168        repo_path.join("Cargo.toml"),
169        r#"[package]
170name = "commit-demo"
171version = "0.2.0"
172edition = "2021"
173description = "A demo project for commit workflows"
174"#,
175    )?;
176
177    repo.add(&["Cargo.toml"])?;
178    let third_hash = repo.commit("Bump version to 0.2.0 and add description")?;
179    commit_hashes.push(third_hash);
180
181    // Commit 4: Add documentation
182    println!("Step 2: Add documentation...");
183    fs::write(
184        repo_path.join("CHANGELOG.md"),
185        r#"# Changelog
186
187## [0.2.0] - 2024-01-01
188
189### Added
190- Library functionality with greet function
191- Comprehensive test suite
192- Project documentation
193
194## [0.1.0] - 2024-01-01
195
196### Added
197- Initial project structure
198- Basic Cargo configuration
199"#,
200    )?;
201
202    repo.add(&["CHANGELOG.md"])?;
203    let fourth_hash = repo.commit_with_author(
204        "docs: Add CHANGELOG with version history",
205        "Doc Writer <docs@example.com>",
206    )?;
207    commit_hashes.push(fourth_hash);
208
209    // Commit 5: Final polish
210    println!("Step 3: Final polish...");
211    fs::write(
212        repo_path.join("README.md"),
213        r#"# Commit Demo Project
214
215This project demonstrates commit workflows with rustic-git.
216
217## Features
218
219- Clean, type-safe Git operations
220- Comprehensive commit history
221- Multiple author support
222- Hash management utilities
223
224## Usage
225
226```rust
227use commit_demo::greet;
228
229fn main() {
230    println!("{}", greet("World"));
231}
232```
233
234## Version
235
236Current version: 0.2.0
237
238See CHANGELOG.md for version history.
239"#,
240    )?;
241
242    repo.add(&["README.md"])?;
243    let fifth_hash = repo.commit("docs: Enhance README with usage examples and features")?;
244    commit_hashes.push(fifth_hash);
245
246    println!("\nComplete commit history created!");
247
248    // Display all commits
249    println!("\n=== Commit History Summary ===\n");
250
251    for (i, hash) in commit_hashes.iter().enumerate() {
252        println!("{}. Commit {}", i + 1, i + 1);
253        display_hash_info(hash, &format!("Commit {}", i + 1));
254        println!();
255    }
256
257    // Compare hashes
258    println!("Hash comparisons:");
259    println!(
260        "   First commit == Last commit: {}",
261        commit_hashes[0] == commit_hashes[4]
262    );
263    println!("   All hashes unique: {}", all_unique(&commit_hashes));
264
265    // Show short hashes for all commits
266    println!("\nAll commit short hashes:");
267    for (i, hash) in commit_hashes.iter().enumerate() {
268        println!("   {}: {}", i + 1, hash.short());
269    }
270    println!();
271
272    println!("=== Error Handling for Commits ===\n");
273
274    // Try to commit with nothing staged (should fail)
275    println!("Testing commit with no staged changes:");
276    match repo.commit("This should fail - no changes") {
277        Ok(_hash) => println!("   Unexpectedly succeeded with empty commit"),
278        Err(e) => {
279            println!("   Expected error for empty commit: {:?}", e);
280            println!("   This is normal behavior - Git requires changes to commit");
281        }
282    }
283
284    // Try commit with custom author but no changes (should also fail)
285    println!("\nTesting custom author commit with no changes:");
286    match repo.commit_with_author("This should also fail", "Test Author <test@example.com>") {
287        Ok(_hash) => println!("   Unexpectedly succeeded with empty custom author commit"),
288        Err(e) => {
289            println!(
290                "   Expected error for empty commit with custom author: {:?}",
291                e
292            );
293        }
294    }
295
296    // Test commit with empty message (Git might handle this differently)
297    println!("\nTesting commit with empty message:");
298
299    // Create a change to commit
300    fs::write(repo_path.join("temp_for_empty_message.txt"), "temp content")?;
301    repo.add(&["temp_for_empty_message.txt"])?;
302
303    match repo.commit("") {
304        Ok(hash) => {
305            println!("   Commit with empty message succeeded: {}", hash.short());
306            println!("   Some Git configurations allow empty commit messages");
307        }
308        Err(e) => {
309            println!("   Empty commit message rejected: {:?}", e);
310        }
311    }
312
313    println!();
314
315    println!("=== Final Repository State ===\n");
316
317    let final_status = repo.status()?;
318    if final_status.is_clean() {
319        println!("Repository is clean - all changes committed!");
320    } else {
321        println!(
322            "Repository has {} uncommitted changes",
323            final_status.entries.len()
324        );
325    }
326
327    println!("\nWorkflow summary:");
328    println!("   Total commits created: {}", commit_hashes.len());
329    println!("   Hash examples demonstrated: [OK]");
330    println!("   Custom author commits: [OK]");
331    println!("   Error handling tested: [OK]");
332
333    // Clean up
334    println!("\nCleaning up example repository...");
335    fs::remove_dir_all(&repo_path)?;
336    println!("Commit workflows example completed!");
337
338    Ok(())
339}
Source§

impl Repository

Source

pub fn diff(&self) -> Result<DiffOutput>

Get diff between working directory and index (staged changes)

Shows changes that are not yet staged for commit.

§Returns

A Result containing the DiffOutput or a GitError.

§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let diff = repo.diff()?;
println!("Unstaged changes: {}", diff);
Examples found in repository?
examples/diff_operations.rs (line 58)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
Source

pub fn diff_staged(&self) -> Result<DiffOutput>

Get diff between index and HEAD (staged changes)

Shows changes that are staged for commit.

§Returns

A Result containing the DiffOutput or a GitError.

§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let diff = repo.diff_staged()?;
println!("Staged changes: {}", diff);
Examples found in repository?
examples/diff_operations.rs (line 84)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
Source

pub fn diff_head(&self) -> Result<DiffOutput>

Get diff between working directory and HEAD

Shows all changes (both staged and unstaged) compared to the last commit.

§Returns

A Result containing the DiffOutput or a GitError.

Examples found in repository?
examples/diff_operations.rs (line 188)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
Source

pub fn diff_commits(&self, from: &Hash, to: &Hash) -> Result<DiffOutput>

Get diff between two commits

§Arguments
  • from - The starting commit hash
  • to - The ending commit hash
§Returns

A Result containing the DiffOutput or a GitError.

§Example
use rustic_git::{Repository, Hash};

let repo = Repository::open(".")?;
let from = Hash::from("abc123");
let to = Hash::from("def456");
let diff = repo.diff_commits(&from, &to)?;
println!("Changes between commits: {}", diff);
Examples found in repository?
examples/diff_operations.rs (line 125)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
Source

pub fn diff_with_options(&self, options: &DiffOptions) -> Result<DiffOutput>

Get diff with custom options

§Arguments
  • options - The diff options to use
§Returns

A Result containing the DiffOutput or a GitError.

§Example
use rustic_git::{Repository, DiffOptions};

let repo = Repository::open(".")?;
let options = DiffOptions::new()
    .ignore_whitespace()
    .context_lines(5);
let diff = repo.diff_with_options(&options)?;
println!("Diff with options: {}", diff);
Examples found in repository?
examples/diff_operations.rs (line 101)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
Source§

impl Repository

Source

pub fn checkout_file<P: AsRef<Path>>(&self, path: P) -> Result<()>

Restore file from HEAD, discarding local changes

This is equivalent to git checkout HEAD -- <file> and will restore the file to its state in the last commit, discarding any local changes.

§Arguments
  • path - Path to the file to restore
§Example
use rustic_git::Repository;

// Restore a modified file to its last committed state
repo.checkout_file("modified_file.txt")?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 99)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn restore<P: AsRef<Path>>( &self, paths: &[P], options: RestoreOptions, ) -> Result<()>

Restore files with advanced options

This provides access to git’s restore command with full control over source, staging area, and working tree restoration.

§Arguments
  • paths - Paths to restore
  • options - Restore options
§Example
use rustic_git::{Repository, RestoreOptions};

// Restore from a specific commit
let options = RestoreOptions::new()
    .with_source("HEAD~1")
    .with_worktree();
repo.restore(&["file.txt"], options)?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 130)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn reset_file<P: AsRef<Path>>(&self, path: P) -> Result<()>

Unstage a specific file, removing it from the staging area

This is equivalent to git reset HEAD -- <file> and removes the file from the staging area while keeping changes in the working directory.

§Arguments
  • path - Path to the file to unstage
§Example
use rustic_git::Repository;

// Unstage a previously staged file
repo.reset_file("staged_file.txt")?;
Examples found in repository?
examples/reset_operations.rs (line 137)
118fn demonstrate_file_resets(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
119    println!("\n--- Demonstrating File-Specific Resets ---\n");
120
121    // Create some files and stage them
122    println!("1. Creating and staging multiple files...");
123
124    let file_a = temp_dir.join("fileA.txt");
125    let file_b = temp_dir.join("fileB.txt");
126
127    fs::write(&file_a, "Content A")?;
128    fs::write(&file_b, "Content B")?;
129
130    repo.add(&["fileA.txt", "fileB.txt"])?;
131    println!("   Staged fileA.txt and fileB.txt");
132
133    show_repo_state(repo)?;
134
135    // Reset a single file (using existing reset_file from files.rs)
136    println!("\n2. Resetting single file (fileA.txt)...");
137    repo.reset_file("fileA.txt")?;
138
139    println!("   After resetting fileA.txt:");
140    show_repo_state(repo)?;
141    println!("   Note: fileA.txt is unstaged, fileB.txt remains staged");
142
143    // Demonstrate HEAD reset (unstage all changes)
144    println!("\n3. Performing mixed reset to HEAD (unstage all)...");
145    repo.reset_mixed("HEAD")?;
146
147    println!("   After reset HEAD:");
148    show_repo_state(repo)?;
149    println!("   Note: All staged changes are now unstaged");
150
151    Ok(())
152}
More examples
Hide additional examples
examples/file_lifecycle_operations.rs (line 159)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn rm<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()>

Remove files from the repository

This removes files from both the working directory and the repository, equivalent to git rm <files>.

§Arguments
  • paths - Paths to remove
§Example
use rustic_git::Repository;

// Remove files from repository
repo.rm(&["unwanted_file.txt", "old_dir/"])?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 199)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn rm_with_options<P: AsRef<Path>>( &self, paths: &[P], options: RemoveOptions, ) -> Result<()>

Remove files with advanced options

This provides full control over file removal with options for force, recursive, cached-only removal, etc.

§Arguments
  • paths - Paths to remove
  • options - Remove options
§Example
use rustic_git::{Repository, RemoveOptions};

// Remove from index only, keep files in working tree
let options = RemoveOptions::new().with_cached();
repo.rm_with_options(&["keep_local.txt"], options)?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 206)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn mv<P: AsRef<Path>, Q: AsRef<Path>>( &self, source: P, destination: Q, ) -> Result<()>

Move or rename a file or directory

This is equivalent to git mv <source> <destination> and will move the file both in the working directory and in the repository.

§Arguments
  • source - Source path
  • destination - Destination path
§Example
use rustic_git::Repository;

// Rename a file
repo.mv("old_name.txt", "new_name.txt")?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 242)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn mv_with_options<P: AsRef<Path>, Q: AsRef<Path>>( &self, source: P, destination: Q, options: MoveOptions, ) -> Result<()>

Move files with advanced options

This provides full control over file moving with options for force, verbose output, and dry run mode.

§Arguments
  • source - Source path
  • destination - Destination path
  • options - Move options
§Example
use rustic_git::{Repository, MoveOptions};

// Force move even if destination exists
let options = MoveOptions::new().with_force();
repo.mv_with_options("source.txt", "existing.txt", options)?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 265)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn ignore_add(&self, patterns: &[&str]) -> Result<()>

Add patterns to .gitignore file

This adds the specified patterns to the repository’s .gitignore file, creating the file if it doesn’t exist.

§Arguments
  • patterns - Patterns to add to .gitignore
§Example
use rustic_git::Repository;

// Add patterns to .gitignore
repo.ignore_add(&["*.tmp", "build/", "node_modules/"])?;
Examples found in repository?
examples/file_lifecycle_operations.rs (lines 282-289)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn ignore_check<P: AsRef<Path>>(&self, path: P) -> Result<bool>

Check if a file is ignored by .gitignore patterns

This uses git check-ignore to determine if a file would be ignored by the current .gitignore patterns.

§Arguments
  • path - Path to check
§Returns
  • Ok(true) if the file is ignored
  • Ok(false) if the file is not ignored
  • Err(GitError) if the command fails
§Example
use rustic_git::Repository;

// Check if a file is ignored
let is_ignored = repo.ignore_check("temp_file.tmp")?;
Examples found in repository?
examples/file_lifecycle_operations.rs (line 316)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source

pub fn ignore_list(&self) -> Result<Vec<String>>

List current ignore patterns from .gitignore

This reads the .gitignore file and returns all non-empty, non-comment lines.

§Returns
  • Vector of ignore patterns
§Example
use rustic_git::Repository;

// List all ignore patterns
let patterns = repo.ignore_list()?;
for pattern in patterns {
    println!("Ignoring: {}", pattern);
}
Examples found in repository?
examples/file_lifecycle_operations.rs (line 277)
16fn main() -> Result<()> {
17    println!("Rustic Git - File Lifecycle Operations Example\n");
18
19    let base_path = env::temp_dir().join("rustic_git_files_example");
20    let repo_path = base_path.join("main_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for file lifecycle demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Set up git configuration for commits
36    repo.config().set_user("Demo User", "demo@example.com")?;
37
38    // Create initial project structure
39    fs::create_dir_all(repo_path.join("src"))?;
40    fs::create_dir_all(repo_path.join("docs"))?;
41    fs::create_dir_all(repo_path.join("tests"))?;
42
43    let files = [
44        (
45            "README.md",
46            "# File Lifecycle Demo\n\nDemonstrating rustic-git file management capabilities.",
47        ),
48        (
49            "src/main.rs",
50            "fn main() {\n    println!(\"Hello, world!\");\n}",
51        ),
52        (
53            "src/lib.rs",
54            "//! Library module\n\npub fn greet() {\n    println!(\"Hello from lib!\");\n}",
55        ),
56        ("docs/guide.md", "# User Guide\n\nThis is the user guide."),
57        (
58            "tests/integration.rs",
59            "#[test]\nfn test_basic() {\n    assert_eq!(2 + 2, 4);\n}",
60        ),
61    ];
62
63    for (path, content) in &files {
64        fs::write(repo_path.join(path), content)?;
65    }
66
67    repo.add(&files.iter().map(|(path, _)| *path).collect::<Vec<_>>())?;
68    let initial_commit = repo.commit("Initial project setup")?;
69    println!("Created initial commit: {}\n", initial_commit.short());
70
71    println!("=== File Restoration Operations ===\n");
72
73    // Modify some files
74    println!("Modifying files to demonstrate restoration...");
75    fs::write(
76        repo_path.join("README.md"),
77        "# Modified README\n\nThis content has been changed.",
78    )?;
79    fs::write(
80        repo_path.join("src/main.rs"),
81        "fn main() {\n    println!(\"Modified main!\");\n    println!(\"Added new line!\");\n}",
82    )?;
83
84    println!("   Modified README.md and src/main.rs");
85
86    // Show current status
87    let status = repo.status()?;
88    println!(
89        "   Files with modifications: {}",
90        status.unstaged_files().count()
91    );
92    for entry in status.unstaged_files() {
93        println!("     - {}", entry.path.display());
94    }
95    println!();
96
97    // Restore single file with checkout_file
98    println!("Restoring README.md using checkout_file():");
99    repo.checkout_file("README.md")?;
100    let restored_content = fs::read_to_string(repo_path.join("README.md"))?;
101    println!("   ✓ README.md restored to original state");
102    println!(
103        "   Content preview: {:?}",
104        restored_content.lines().next().unwrap_or("")
105    );
106    println!();
107
108    // Demonstrate advanced restore with options
109    println!("Creating second commit for restore demonstration...");
110    fs::write(
111        repo_path.join("src/advanced.rs"),
112        "//! Advanced module\n\npub fn advanced_function() {\n    println!(\"Advanced functionality\");\n}",
113    )?;
114    repo.add(&["src/advanced.rs"])?;
115    let second_commit = repo.commit("Add advanced module")?;
116    println!("   Second commit: {}", second_commit.short());
117
118    // Modify the advanced file
119    fs::write(
120        repo_path.join("src/advanced.rs"),
121        "//! HEAVILY MODIFIED\n\npub fn broken_function() {\n    panic!(\"This is broken!\");\n}",
122    )?;
123    println!("   Modified src/advanced.rs");
124
125    // Restore from specific commit using restore with options
126    println!("Restoring src/advanced.rs from specific commit using restore():");
127    let restore_options = RestoreOptions::new()
128        .with_source(format!("{}", second_commit))
129        .with_worktree();
130    repo.restore(&["src/advanced.rs"], restore_options)?;
131
132    let restored_advanced = fs::read_to_string(repo_path.join("src/advanced.rs"))?;
133    println!("   ✓ File restored from commit {}", second_commit.short());
134    println!(
135        "   Content preview: {:?}",
136        restored_advanced.lines().next().unwrap_or("")
137    );
138    println!();
139
140    println!("=== Staging Area Operations ===\n");
141
142    // Modify and stage files
143    println!("Demonstrating staging area manipulation...");
144    fs::write(
145        repo_path.join("src/lib.rs"),
146        "//! STAGED CHANGES\n\npub fn new_function() {\n    println!(\"This will be staged\");\n}",
147    )?;
148    repo.add(&["src/lib.rs"])?;
149    println!("   Modified and staged src/lib.rs");
150
151    let status = repo.status()?;
152    println!("   Staged files: {}", status.staged_files().count());
153    for entry in status.staged_files() {
154        println!("     - {}", entry.path.display());
155    }
156
157    // Unstage the file
158    println!("Unstaging src/lib.rs using reset_file():");
159    repo.reset_file("src/lib.rs")?;
160
161    let status_after_reset = repo.status()?;
162    println!("   ✓ File unstaged (now in modified files)");
163    println!(
164        "   Staged files: {}",
165        status_after_reset.staged_files().count()
166    );
167    println!(
168        "   Modified files: {}",
169        status_after_reset.unstaged_files().count()
170    );
171    println!();
172
173    println!("=== File Removal Operations ===\n");
174
175    // Create files for removal demonstration
176    println!("Creating files for removal demonstration...");
177    fs::write(repo_path.join("temp_file.txt"), "This is a temporary file")?;
178    fs::write(
179        repo_path.join("docs/old_doc.md"),
180        "# Old Documentation\n\nThis document is outdated.",
181    )?;
182    fs::create_dir_all(repo_path.join("old_directory"))?;
183    fs::write(
184        repo_path.join("old_directory/nested_file.txt"),
185        "Nested content",
186    )?;
187
188    // Add and commit these files
189    repo.add(&[
190        "temp_file.txt",
191        "docs/old_doc.md",
192        "old_directory/nested_file.txt",
193    ])?;
194    repo.commit("Add files for removal demo")?;
195    println!("   Created and committed files for removal");
196
197    // Basic file removal
198    println!("Removing temp_file.txt using rm():");
199    repo.rm(&["temp_file.txt"])?;
200    println!("   ✓ temp_file.txt removed from repository and working tree");
201    assert!(!repo_path.join("temp_file.txt").exists());
202
203    // Remove from index only (keep in working tree)
204    println!("Removing docs/old_doc.md from index only using rm_with_options():");
205    let cached_remove_options = RemoveOptions::new().with_cached();
206    repo.rm_with_options(&["docs/old_doc.md"], cached_remove_options)?;
207
208    println!("   ✓ File removed from index but kept in working tree");
209    assert!(repo_path.join("docs/old_doc.md").exists());
210    let content = fs::read_to_string(repo_path.join("docs/old_doc.md"))?;
211    println!(
212        "   Working tree content still available: {:?}",
213        content.lines().next().unwrap_or("")
214    );
215
216    // Recursive removal
217    println!("Removing old_directory/ recursively:");
218    let recursive_options = RemoveOptions::new().with_recursive();
219    repo.rm_with_options(&["old_directory/"], recursive_options)?;
220    println!("   ✓ Directory and contents removed recursively");
221    assert!(!repo_path.join("old_directory").exists());
222    println!();
223
224    println!("=== File Move/Rename Operations ===\n");
225
226    // Create files for move demonstration
227    println!("Creating files for move/rename demonstration...");
228    fs::write(repo_path.join("old_name.txt"), "This file will be renamed")?;
229    fs::create_dir_all(repo_path.join("source_dir"))?;
230    fs::write(
231        repo_path.join("source_dir/movable.txt"),
232        "This file will be moved",
233    )?;
234    fs::create_dir_all(repo_path.join("target_dir"))?;
235
236    repo.add(&["old_name.txt", "source_dir/movable.txt"])?;
237    repo.commit("Add files for move demo")?;
238    println!("   Created files for move demonstration");
239
240    // Simple rename
241    println!("Renaming old_name.txt to new_name.txt using mv():");
242    repo.mv("old_name.txt", "new_name.txt")?;
243
244    assert!(!repo_path.join("old_name.txt").exists());
245    assert!(repo_path.join("new_name.txt").exists());
246    let content = fs::read_to_string(repo_path.join("new_name.txt"))?;
247    println!("   ✓ File renamed successfully");
248    println!("   Content preserved: {:?}", content.trim());
249
250    // Move file to different directory
251    println!("Moving source_dir/movable.txt to target_dir/ using mv():");
252    repo.mv("source_dir/movable.txt", "target_dir/movable.txt")?;
253
254    assert!(!repo_path.join("source_dir/movable.txt").exists());
255    assert!(repo_path.join("target_dir/movable.txt").exists());
256    println!("   ✓ File moved to different directory");
257
258    // Demonstrate move with options (dry run)
259    fs::write(repo_path.join("test_move.txt"), "Test content for dry run")?;
260    repo.add(&["test_move.txt"])?;
261    repo.commit("Add test file for dry run demo")?;
262
263    println!("Demonstrating dry run move (won't actually move):");
264    let dry_run_options = MoveOptions::new().with_dry_run().with_verbose();
265    repo.mv_with_options("test_move.txt", "would_be_moved.txt", dry_run_options)?;
266
267    // File should still exist at original location
268    assert!(repo_path.join("test_move.txt").exists());
269    assert!(!repo_path.join("would_be_moved.txt").exists());
270    println!("   ✓ Dry run completed - no actual move performed");
271    println!();
272
273    println!("=== .gitignore Management ===\n");
274
275    // Initially no ignore patterns
276    println!("Checking initial .gitignore state:");
277    let initial_patterns = repo.ignore_list()?;
278    println!("   Initial ignore patterns: {}", initial_patterns.len());
279
280    // Add ignore patterns
281    println!("Adding ignore patterns...");
282    repo.ignore_add(&[
283        "*.tmp",
284        "*.log",
285        "build/",
286        "node_modules/",
287        ".DS_Store",
288        "*.secret",
289    ])?;
290    println!("   Added 6 ignore patterns to .gitignore");
291
292    // List current patterns
293    let patterns = repo.ignore_list()?;
294    println!("   Current ignore patterns: {}", patterns.len());
295    for (i, pattern) in patterns.iter().enumerate() {
296        println!("     {}. {}", i + 1, pattern);
297    }
298
299    // Create test files to check ignore status
300    println!("\nCreating test files to check ignore status...");
301    let test_files = [
302        ("regular_file.txt", false),
303        ("temp_file.tmp", true),
304        ("debug.log", true),
305        ("important.secret", true),
306        ("normal.md", false),
307    ];
308
309    for (filename, _) in &test_files {
310        fs::write(repo_path.join(filename), "test content")?;
311    }
312
313    // Check ignore status for each file
314    println!("Checking ignore status for test files:");
315    for (filename, expected_ignored) in &test_files {
316        let is_ignored = repo.ignore_check(filename)?;
317        let status_symbol = if is_ignored { "🚫" } else { "✅" };
318        println!(
319            "   {} {} - {}",
320            status_symbol,
321            filename,
322            if is_ignored { "IGNORED" } else { "TRACKED" }
323        );
324
325        // Verify expectation
326        assert_eq!(
327            is_ignored, *expected_ignored,
328            "Ignore status mismatch for {}",
329            filename
330        );
331    }
332    println!();
333
334    println!("=== Error Handling and Edge Cases ===\n");
335
336    // Test error cases
337    println!("Testing error conditions:");
338
339    // Try to checkout non-existent file
340    println!("   Attempting to checkout non-existent file:");
341    match repo.checkout_file("nonexistent.txt") {
342        Ok(_) => println!("     Unexpected success"),
343        Err(e) => println!("     ✓ Expected error: {}", e),
344    }
345
346    // Try to reset non-existent file
347    println!("   Attempting to reset non-staged file:");
348    match repo.reset_file("new_name.txt") {
349        Ok(_) => println!("     ✓ Reset succeeded (file not staged, no error)"),
350        Err(e) => println!("     Error: {}", e),
351    }
352
353    // Try to remove non-existent file
354    println!("   Attempting to remove non-existent file:");
355    match repo.rm(&["definitely_not_here.txt"]) {
356        Ok(_) => println!("     Unexpected success"),
357        Err(e) => println!("     ✓ Expected error: {}", e),
358    }
359
360    // Try to remove with ignore-unmatch option
361    println!("   Attempting to remove with ignore-unmatch option:");
362    let ignore_unmatch_options = RemoveOptions::new().with_ignore_unmatch();
363    match repo.rm_with_options(&["also_not_here.txt"], ignore_unmatch_options) {
364        Ok(_) => println!("     ✓ Succeeded with ignore-unmatch (no error)"),
365        Err(e) => println!("     Error: {}", e),
366    }
367
368    // Try to move to existing file without force
369    fs::write(repo_path.join("existing_target.txt"), "existing content")?;
370    repo.add(&["existing_target.txt"])?;
371    repo.commit("Add existing target")?;
372
373    println!("   Attempting to move to existing file without force:");
374    match repo.mv("test_move.txt", "existing_target.txt") {
375        Ok(_) => println!("     Unexpected success (git may have overwritten)"),
376        Err(e) => println!("     ✓ Expected error: {}", e),
377    }
378    println!();
379
380    println!("=== Advanced Restore Operations ===\n");
381
382    // Demonstrate restore with staged and worktree options
383    println!("Demonstrating advanced restore with staging area...");
384
385    // Modify file and stage it
386    fs::write(repo_path.join("new_name.txt"), "staged changes")?;
387    repo.add(&["new_name.txt"])?;
388
389    // Modify it again in working tree
390    fs::write(repo_path.join("new_name.txt"), "working tree changes")?;
391
392    println!("   File has both staged and working tree changes");
393
394    // Restore only staged area
395    println!("   Restoring staged changes only:");
396    let staged_restore = RestoreOptions::new().with_staged();
397    repo.restore(&["new_name.txt"], staged_restore)?;
398
399    let content_after_staged_restore = fs::read_to_string(repo_path.join("new_name.txt"))?;
400    println!("     ✓ Staged changes restored, working tree preserved");
401    println!(
402        "     Working tree content: {:?}",
403        content_after_staged_restore.trim()
404    );
405
406    // Restore working tree
407    println!("   Restoring working tree:");
408    let worktree_restore = RestoreOptions::new().with_worktree();
409    repo.restore(&["new_name.txt"], worktree_restore)?;
410
411    let final_content = fs::read_to_string(repo_path.join("new_name.txt"))?;
412    println!("     ✓ Working tree restored to committed state");
413    println!("     Final content: {:?}", final_content.trim());
414    println!();
415
416    println!("=== Repository State Summary ===\n");
417
418    let final_status = repo.status()?;
419    println!("Final repository state:");
420    println!("   Clean repository: {}", final_status.is_clean());
421    println!("   Staged files: {}", final_status.staged_files().count());
422    println!(
423        "   Modified files: {}",
424        final_status.unstaged_files().count()
425    );
426    println!(
427        "   Untracked files: {}",
428        final_status.untracked_entries().count()
429    );
430
431    if !final_status.is_clean() {
432        println!("\n   Remaining changes:");
433        for entry in final_status.staged_files() {
434            println!("     Staged: {}", entry.path.display());
435        }
436        for entry in final_status.unstaged_files() {
437            println!("     Modified: {}", entry.path.display());
438        }
439        for entry in final_status.untracked_entries() {
440            println!("     Untracked: {}", entry.path.display());
441        }
442    }
443
444    // Show .gitignore content
445    let final_patterns = repo.ignore_list()?;
446    println!("\n   .gitignore patterns: {}", final_patterns.len());
447    for pattern in final_patterns {
448        println!("     - {}", pattern);
449    }
450
451    println!("\n=== Summary ===\n");
452
453    println!("File lifecycle operations demonstration completed!");
454    println!("  Repository: {}", repo_path.display());
455
456    println!("\nOperations demonstrated:");
457    println!("  ✓ File restoration from HEAD (checkout_file)");
458    println!("  ✓ Advanced file restoration with options (restore)");
459    println!("  ✓ Unstaging files (reset_file)");
460    println!("  ✓ File removal with various options (rm, rm_with_options)");
461    println!("  ✓ File moving and renaming (mv, mv_with_options)");
462    println!("  ✓ .gitignore pattern management (ignore_add, ignore_list, ignore_check)");
463    println!("  ✓ Staged vs working tree restoration");
464    println!("  ✓ Error handling for invalid operations");
465    println!("  ✓ Dry run and verbose options");
466    println!("  ✓ Recursive and cached removal options");
467
468    // Clean up
469    println!("\nCleaning up example repositories...");
470    fs::remove_dir_all(&base_path)?;
471    println!("File lifecycle operations example completed!");
472
473    Ok(())
474}
Source§

impl Repository

Source

pub fn log(&self) -> Result<CommitLog>

Get commit history with default options

Examples found in repository?
examples/commit_history.rs (line 82)
5fn main() -> Result<()> {
6    let test_path = env::temp_dir().join("rustic_git_commit_history_example");
7
8    // Clean up if exists
9    if test_path.exists() {
10        fs::remove_dir_all(&test_path).unwrap();
11    }
12
13    // Create a test repository
14    let repo = Repository::init(&test_path, false)?;
15    println!("Created repository at: {}", test_path.display());
16
17    // Create several commits to build history
18    println!("\n=== Building Commit History ===");
19
20    // First commit
21    fs::write(
22        test_path.join("README.md"),
23        "# Commit History Demo\n\nA demonstration of rustic-git log functionality.",
24    )
25    .unwrap();
26    repo.add(&["README.md"])?;
27    let commit1 = repo.commit("Initial commit - add README")?;
28    println!("Created commit 1: {} - Initial commit", commit1.short());
29
30    // Second commit
31    fs::create_dir_all(test_path.join("src")).unwrap();
32    fs::write(
33        test_path.join("src/main.rs"),
34        "fn main() {\n    println!(\"Hello, world!\");\n}",
35    )
36    .unwrap();
37    repo.add(&["src/main.rs"])?;
38    let commit2 = repo.commit("Add main.rs with Hello World")?;
39    println!("Created commit 2: {} - Add main.rs", commit2.short());
40
41    // Third commit
42    fs::write(
43        test_path.join("src/lib.rs"),
44        "pub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}",
45    )
46    .unwrap();
47    repo.add(&["src/lib.rs"])?;
48    let commit3 = repo.commit("Add library module with greet function")?;
49    println!("Created commit 3: {} - Add lib.rs", commit3.short());
50
51    // Fourth commit
52    fs::write(
53        test_path.join("Cargo.toml"),
54        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
55    )
56    .unwrap();
57    repo.add(&["Cargo.toml"])?;
58    let commit4 = repo.commit("Add Cargo.toml configuration")?;
59    println!("Created commit 4: {} - Add Cargo.toml", commit4.short());
60
61    // Fifth commit - bug fix
62    fs::write(
63        test_path.join("src/main.rs"),
64        "fn main() {\n    println!(\"Hello, rustic-git!\");\n}",
65    )
66    .unwrap();
67    repo.add(&["src/main.rs"])?;
68    let commit5 = repo.commit("Fix greeting message in main")?;
69    println!("Created commit 5: {} - Fix greeting", commit5.short());
70
71    // Sixth commit - documentation
72    fs::write(test_path.join("README.md"), "# Commit History Demo\n\nA demonstration of rustic-git log functionality.\n\n## Features\n\n- Greeting functionality\n- Command line interface\n").unwrap();
73    repo.add(&["README.md"])?;
74    let commit6 = repo.commit("Update README with features section")?;
75    println!("Created commit 6: {} - Update README", commit6.short());
76
77    println!("Built commit history with 6 commits");
78
79    // Basic log operations
80    println!("\n=== Basic Log Operations ===");
81
82    let all_commits = repo.log()?;
83    println!("Total commits in repository: {}", all_commits.len());
84
85    println!("\nAll commits (most recent first):");
86    for (i, commit) in all_commits.iter().enumerate() {
87        println!("  {}. {}", i + 1, commit);
88    }
89
90    // Recent commits
91    println!("\n=== Recent Commits ===");
92    let recent = repo.recent_commits(3)?;
93    println!("Last 3 commits:");
94    for commit in recent.iter() {
95        println!("  {} - {}", commit.hash.short(), commit.message.subject);
96        if let Some(body) = &commit.message.body {
97            println!("    Body: {}", body);
98        }
99    }
100
101    // Advanced filtering with LogOptions
102    println!("\n=== Advanced Filtering ===");
103
104    // Filter by message content
105    let fix_commits = all_commits.with_message_containing("fix");
106    println!("Commits with 'fix' in message:");
107    for commit in fix_commits {
108        println!("  {} - {}", commit.hash.short(), commit.message.subject);
109    }
110
111    // Filter by date (recent commits)
112    let now = Utc::now();
113    let recent_commits = all_commits.since(now - Duration::minutes(5));
114    println!("\nCommits from last 5 minutes: {}", recent_commits.count());
115
116    // Using LogOptions for advanced queries
117    println!("\n=== LogOptions Advanced Queries ===");
118
119    // Get commits with grep
120    let opts = LogOptions::new().max_count(10).grep("README".to_string());
121    let readme_commits = repo.log_with_options(&opts)?;
122    println!("Commits mentioning 'README': {}", readme_commits.len());
123    for commit in readme_commits.iter() {
124        println!("  {} - {}", commit.hash.short(), commit.message.subject);
125    }
126
127    // Get commits affecting specific paths
128    println!("\n=== Path-Specific History ===");
129    let src_commits = repo.log_for_paths(&["src/"])?;
130    println!("Commits affecting src/ directory: {}", src_commits.len());
131    for commit in src_commits.iter() {
132        println!("  {} - {}", commit.hash.short(), commit.message.subject);
133    }
134
135    // Show detailed commit information
136    println!("\n=== Detailed Commit Information ===");
137
138    let commit_details = repo.show_commit(&commit3)?;
139    println!("Detailed info for commit {}:", commit3.short());
140    println!("  Author: {}", commit_details.commit.author);
141    println!("  Committer: {}", commit_details.commit.committer);
142    println!(
143        "  Timestamp: {}",
144        commit_details
145            .commit
146            .timestamp
147            .format("%Y-%m-%d %H:%M:%S UTC")
148    );
149    println!("  Message: {}", commit_details.commit.message.subject);
150    println!("  Parents: {}", commit_details.commit.parents.len());
151    for parent in commit_details.commit.parents.iter() {
152        println!("    - {}", parent.short());
153    }
154    println!("  Files changed: {}", commit_details.files_changed.len());
155    for file in &commit_details.files_changed {
156        println!("    - {}", file.display());
157    }
158    println!(
159        "  Changes: +{} -{}",
160        commit_details.insertions, commit_details.deletions
161    );
162
163    // Commit analysis
164    println!("\n=== Commit Analysis ===");
165
166    let merge_commits: Vec<_> = all_commits.merges_only().collect();
167    let regular_commits: Vec<_> = all_commits.no_merges().collect();
168
169    println!("Repository statistics:");
170    println!("  Total commits: {}", all_commits.len());
171    println!("  Merge commits: {}", merge_commits.len());
172    println!("  Regular commits: {}", regular_commits.len());
173
174    if let Some(first_commit) = all_commits.first() {
175        println!(
176            "  Most recent: {} ({})",
177            first_commit.hash.short(),
178            first_commit.message.subject
179        );
180    }
181
182    if let Some(last_commit) = all_commits.last() {
183        println!(
184            "  Oldest: {} ({})",
185            last_commit.hash.short(),
186            last_commit.message.subject
187        );
188    }
189
190    // Search operations
191    println!("\n=== Search Operations ===");
192
193    // Find by hash
194    if let Some(found) = all_commits.find_by_hash(&commit2) {
195        println!("Found commit by full hash: {}", found.message.subject);
196    }
197
198    // Find by short hash
199    if let Some(found) = all_commits.find_by_short_hash(commit4.short()) {
200        println!("Found commit by short hash: {}", found.message.subject);
201    }
202
203    // Commit range operations
204    println!("\n=== Commit Range Operations ===");
205
206    let range_commits = repo.log_range(&commit2, &commit5)?;
207    println!(
208        "Commits in range {}..{}: {}",
209        commit2.short(),
210        commit5.short(),
211        range_commits.len()
212    );
213    for commit in range_commits.iter() {
214        println!("  {} - {}", commit.hash.short(), commit.message.subject);
215    }
216
217    // Advanced LogOptions demonstration
218    println!("\n=== Advanced LogOptions Usage ===");
219
220    let advanced_opts = LogOptions::new()
221        .max_count(5)
222        .no_merges(true)
223        .paths(vec!["src/main.rs".into()]);
224
225    let filtered_commits = repo.log_with_options(&advanced_opts)?;
226    println!(
227        "Non-merge commits affecting src/main.rs (max 5): {}",
228        filtered_commits.len()
229    );
230    for commit in filtered_commits.iter() {
231        println!("  {} - {}", commit.hash.short(), commit.message.subject);
232    }
233
234    // Commit message analysis
235    println!("\n=== Commit Message Analysis ===");
236
237    let total_commits = all_commits.len();
238    let commits_with_body: Vec<_> = all_commits
239        .iter()
240        .filter(|c| c.message.body.is_some())
241        .collect();
242
243    println!("Message statistics:");
244    println!("  Total commits: {}", total_commits);
245    println!("  Commits with body text: {}", commits_with_body.len());
246    println!(
247        "  Commits with subject only: {}",
248        total_commits - commits_with_body.len()
249    );
250
251    // Display commit types by analyzing subjects
252    let fix_count = all_commits
253        .iter()
254        .filter(|c| c.message.subject.to_lowercase().contains("fix"))
255        .count();
256    let add_count = all_commits
257        .iter()
258        .filter(|c| c.message.subject.to_lowercase().contains("add"))
259        .count();
260    let update_count = all_commits
261        .iter()
262        .filter(|c| c.message.subject.to_lowercase().contains("update"))
263        .count();
264
265    println!("  Commit types:");
266    println!("    - Fix commits: {}", fix_count);
267    println!("    - Add commits: {}", add_count);
268    println!("    - Update commits: {}", update_count);
269    println!(
270        "    - Other commits: {}",
271        total_commits - fix_count - add_count - update_count
272    );
273
274    // Timeline view
275    println!("\n=== Timeline View ===");
276
277    println!("Commit timeline (oldest to newest):");
278    let commits: Vec<_> = all_commits.iter().collect();
279    for commit in commits.iter().rev() {
280        let commit_type = if commit.is_merge() { "MERGE" } else { "COMMIT" };
281        println!(
282            "  {} {} {} - {}",
283            commit.timestamp.format("%H:%M:%S"),
284            commit_type,
285            commit.hash.short(),
286            commit.message.subject
287        );
288    }
289
290    // Summary
291    println!("\n=== Summary ===");
292
293    println!("Commit history demonstration completed!");
294    println!("  Repository: {}", test_path.display());
295    println!("  Total commits analyzed: {}", all_commits.len());
296    println!("  Hash examples:");
297    for commit in all_commits.iter().take(3) {
298        println!("    - Full: {}", commit.hash.as_str());
299        println!("      Short: {}", commit.hash.short());
300    }
301
302    // Clean up
303    fs::remove_dir_all(&test_path).unwrap();
304    println!("\nCleaned up test repository");
305
306    Ok(())
307}
Source

pub fn recent_commits(&self, count: usize) -> Result<CommitLog>

Get recent N commits

Examples found in repository?
examples/merge_operations.rs (line 145)
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
More examples
Hide additional examples
examples/commit_history.rs (line 92)
5fn main() -> Result<()> {
6    let test_path = env::temp_dir().join("rustic_git_commit_history_example");
7
8    // Clean up if exists
9    if test_path.exists() {
10        fs::remove_dir_all(&test_path).unwrap();
11    }
12
13    // Create a test repository
14    let repo = Repository::init(&test_path, false)?;
15    println!("Created repository at: {}", test_path.display());
16
17    // Create several commits to build history
18    println!("\n=== Building Commit History ===");
19
20    // First commit
21    fs::write(
22        test_path.join("README.md"),
23        "# Commit History Demo\n\nA demonstration of rustic-git log functionality.",
24    )
25    .unwrap();
26    repo.add(&["README.md"])?;
27    let commit1 = repo.commit("Initial commit - add README")?;
28    println!("Created commit 1: {} - Initial commit", commit1.short());
29
30    // Second commit
31    fs::create_dir_all(test_path.join("src")).unwrap();
32    fs::write(
33        test_path.join("src/main.rs"),
34        "fn main() {\n    println!(\"Hello, world!\");\n}",
35    )
36    .unwrap();
37    repo.add(&["src/main.rs"])?;
38    let commit2 = repo.commit("Add main.rs with Hello World")?;
39    println!("Created commit 2: {} - Add main.rs", commit2.short());
40
41    // Third commit
42    fs::write(
43        test_path.join("src/lib.rs"),
44        "pub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}",
45    )
46    .unwrap();
47    repo.add(&["src/lib.rs"])?;
48    let commit3 = repo.commit("Add library module with greet function")?;
49    println!("Created commit 3: {} - Add lib.rs", commit3.short());
50
51    // Fourth commit
52    fs::write(
53        test_path.join("Cargo.toml"),
54        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
55    )
56    .unwrap();
57    repo.add(&["Cargo.toml"])?;
58    let commit4 = repo.commit("Add Cargo.toml configuration")?;
59    println!("Created commit 4: {} - Add Cargo.toml", commit4.short());
60
61    // Fifth commit - bug fix
62    fs::write(
63        test_path.join("src/main.rs"),
64        "fn main() {\n    println!(\"Hello, rustic-git!\");\n}",
65    )
66    .unwrap();
67    repo.add(&["src/main.rs"])?;
68    let commit5 = repo.commit("Fix greeting message in main")?;
69    println!("Created commit 5: {} - Fix greeting", commit5.short());
70
71    // Sixth commit - documentation
72    fs::write(test_path.join("README.md"), "# Commit History Demo\n\nA demonstration of rustic-git log functionality.\n\n## Features\n\n- Greeting functionality\n- Command line interface\n").unwrap();
73    repo.add(&["README.md"])?;
74    let commit6 = repo.commit("Update README with features section")?;
75    println!("Created commit 6: {} - Update README", commit6.short());
76
77    println!("Built commit history with 6 commits");
78
79    // Basic log operations
80    println!("\n=== Basic Log Operations ===");
81
82    let all_commits = repo.log()?;
83    println!("Total commits in repository: {}", all_commits.len());
84
85    println!("\nAll commits (most recent first):");
86    for (i, commit) in all_commits.iter().enumerate() {
87        println!("  {}. {}", i + 1, commit);
88    }
89
90    // Recent commits
91    println!("\n=== Recent Commits ===");
92    let recent = repo.recent_commits(3)?;
93    println!("Last 3 commits:");
94    for commit in recent.iter() {
95        println!("  {} - {}", commit.hash.short(), commit.message.subject);
96        if let Some(body) = &commit.message.body {
97            println!("    Body: {}", body);
98        }
99    }
100
101    // Advanced filtering with LogOptions
102    println!("\n=== Advanced Filtering ===");
103
104    // Filter by message content
105    let fix_commits = all_commits.with_message_containing("fix");
106    println!("Commits with 'fix' in message:");
107    for commit in fix_commits {
108        println!("  {} - {}", commit.hash.short(), commit.message.subject);
109    }
110
111    // Filter by date (recent commits)
112    let now = Utc::now();
113    let recent_commits = all_commits.since(now - Duration::minutes(5));
114    println!("\nCommits from last 5 minutes: {}", recent_commits.count());
115
116    // Using LogOptions for advanced queries
117    println!("\n=== LogOptions Advanced Queries ===");
118
119    // Get commits with grep
120    let opts = LogOptions::new().max_count(10).grep("README".to_string());
121    let readme_commits = repo.log_with_options(&opts)?;
122    println!("Commits mentioning 'README': {}", readme_commits.len());
123    for commit in readme_commits.iter() {
124        println!("  {} - {}", commit.hash.short(), commit.message.subject);
125    }
126
127    // Get commits affecting specific paths
128    println!("\n=== Path-Specific History ===");
129    let src_commits = repo.log_for_paths(&["src/"])?;
130    println!("Commits affecting src/ directory: {}", src_commits.len());
131    for commit in src_commits.iter() {
132        println!("  {} - {}", commit.hash.short(), commit.message.subject);
133    }
134
135    // Show detailed commit information
136    println!("\n=== Detailed Commit Information ===");
137
138    let commit_details = repo.show_commit(&commit3)?;
139    println!("Detailed info for commit {}:", commit3.short());
140    println!("  Author: {}", commit_details.commit.author);
141    println!("  Committer: {}", commit_details.commit.committer);
142    println!(
143        "  Timestamp: {}",
144        commit_details
145            .commit
146            .timestamp
147            .format("%Y-%m-%d %H:%M:%S UTC")
148    );
149    println!("  Message: {}", commit_details.commit.message.subject);
150    println!("  Parents: {}", commit_details.commit.parents.len());
151    for parent in commit_details.commit.parents.iter() {
152        println!("    - {}", parent.short());
153    }
154    println!("  Files changed: {}", commit_details.files_changed.len());
155    for file in &commit_details.files_changed {
156        println!("    - {}", file.display());
157    }
158    println!(
159        "  Changes: +{} -{}",
160        commit_details.insertions, commit_details.deletions
161    );
162
163    // Commit analysis
164    println!("\n=== Commit Analysis ===");
165
166    let merge_commits: Vec<_> = all_commits.merges_only().collect();
167    let regular_commits: Vec<_> = all_commits.no_merges().collect();
168
169    println!("Repository statistics:");
170    println!("  Total commits: {}", all_commits.len());
171    println!("  Merge commits: {}", merge_commits.len());
172    println!("  Regular commits: {}", regular_commits.len());
173
174    if let Some(first_commit) = all_commits.first() {
175        println!(
176            "  Most recent: {} ({})",
177            first_commit.hash.short(),
178            first_commit.message.subject
179        );
180    }
181
182    if let Some(last_commit) = all_commits.last() {
183        println!(
184            "  Oldest: {} ({})",
185            last_commit.hash.short(),
186            last_commit.message.subject
187        );
188    }
189
190    // Search operations
191    println!("\n=== Search Operations ===");
192
193    // Find by hash
194    if let Some(found) = all_commits.find_by_hash(&commit2) {
195        println!("Found commit by full hash: {}", found.message.subject);
196    }
197
198    // Find by short hash
199    if let Some(found) = all_commits.find_by_short_hash(commit4.short()) {
200        println!("Found commit by short hash: {}", found.message.subject);
201    }
202
203    // Commit range operations
204    println!("\n=== Commit Range Operations ===");
205
206    let range_commits = repo.log_range(&commit2, &commit5)?;
207    println!(
208        "Commits in range {}..{}: {}",
209        commit2.short(),
210        commit5.short(),
211        range_commits.len()
212    );
213    for commit in range_commits.iter() {
214        println!("  {} - {}", commit.hash.short(), commit.message.subject);
215    }
216
217    // Advanced LogOptions demonstration
218    println!("\n=== Advanced LogOptions Usage ===");
219
220    let advanced_opts = LogOptions::new()
221        .max_count(5)
222        .no_merges(true)
223        .paths(vec!["src/main.rs".into()]);
224
225    let filtered_commits = repo.log_with_options(&advanced_opts)?;
226    println!(
227        "Non-merge commits affecting src/main.rs (max 5): {}",
228        filtered_commits.len()
229    );
230    for commit in filtered_commits.iter() {
231        println!("  {} - {}", commit.hash.short(), commit.message.subject);
232    }
233
234    // Commit message analysis
235    println!("\n=== Commit Message Analysis ===");
236
237    let total_commits = all_commits.len();
238    let commits_with_body: Vec<_> = all_commits
239        .iter()
240        .filter(|c| c.message.body.is_some())
241        .collect();
242
243    println!("Message statistics:");
244    println!("  Total commits: {}", total_commits);
245    println!("  Commits with body text: {}", commits_with_body.len());
246    println!(
247        "  Commits with subject only: {}",
248        total_commits - commits_with_body.len()
249    );
250
251    // Display commit types by analyzing subjects
252    let fix_count = all_commits
253        .iter()
254        .filter(|c| c.message.subject.to_lowercase().contains("fix"))
255        .count();
256    let add_count = all_commits
257        .iter()
258        .filter(|c| c.message.subject.to_lowercase().contains("add"))
259        .count();
260    let update_count = all_commits
261        .iter()
262        .filter(|c| c.message.subject.to_lowercase().contains("update"))
263        .count();
264
265    println!("  Commit types:");
266    println!("    - Fix commits: {}", fix_count);
267    println!("    - Add commits: {}", add_count);
268    println!("    - Update commits: {}", update_count);
269    println!(
270        "    - Other commits: {}",
271        total_commits - fix_count - add_count - update_count
272    );
273
274    // Timeline view
275    println!("\n=== Timeline View ===");
276
277    println!("Commit timeline (oldest to newest):");
278    let commits: Vec<_> = all_commits.iter().collect();
279    for commit in commits.iter().rev() {
280        let commit_type = if commit.is_merge() { "MERGE" } else { "COMMIT" };
281        println!(
282            "  {} {} {} - {}",
283            commit.timestamp.format("%H:%M:%S"),
284            commit_type,
285            commit.hash.short(),
286            commit.message.subject
287        );
288    }
289
290    // Summary
291    println!("\n=== Summary ===");
292
293    println!("Commit history demonstration completed!");
294    println!("  Repository: {}", test_path.display());
295    println!("  Total commits analyzed: {}", all_commits.len());
296    println!("  Hash examples:");
297    for commit in all_commits.iter().take(3) {
298        println!("    - Full: {}", commit.hash.as_str());
299        println!("      Short: {}", commit.hash.short());
300    }
301
302    // Clean up
303    fs::remove_dir_all(&test_path).unwrap();
304    println!("\nCleaned up test repository");
305
306    Ok(())
307}
Source

pub fn log_with_options(&self, options: &LogOptions) -> Result<CommitLog>

Get commit history with custom options

Examples found in repository?
examples/commit_history.rs (line 121)
5fn main() -> Result<()> {
6    let test_path = env::temp_dir().join("rustic_git_commit_history_example");
7
8    // Clean up if exists
9    if test_path.exists() {
10        fs::remove_dir_all(&test_path).unwrap();
11    }
12
13    // Create a test repository
14    let repo = Repository::init(&test_path, false)?;
15    println!("Created repository at: {}", test_path.display());
16
17    // Create several commits to build history
18    println!("\n=== Building Commit History ===");
19
20    // First commit
21    fs::write(
22        test_path.join("README.md"),
23        "# Commit History Demo\n\nA demonstration of rustic-git log functionality.",
24    )
25    .unwrap();
26    repo.add(&["README.md"])?;
27    let commit1 = repo.commit("Initial commit - add README")?;
28    println!("Created commit 1: {} - Initial commit", commit1.short());
29
30    // Second commit
31    fs::create_dir_all(test_path.join("src")).unwrap();
32    fs::write(
33        test_path.join("src/main.rs"),
34        "fn main() {\n    println!(\"Hello, world!\");\n}",
35    )
36    .unwrap();
37    repo.add(&["src/main.rs"])?;
38    let commit2 = repo.commit("Add main.rs with Hello World")?;
39    println!("Created commit 2: {} - Add main.rs", commit2.short());
40
41    // Third commit
42    fs::write(
43        test_path.join("src/lib.rs"),
44        "pub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}",
45    )
46    .unwrap();
47    repo.add(&["src/lib.rs"])?;
48    let commit3 = repo.commit("Add library module with greet function")?;
49    println!("Created commit 3: {} - Add lib.rs", commit3.short());
50
51    // Fourth commit
52    fs::write(
53        test_path.join("Cargo.toml"),
54        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
55    )
56    .unwrap();
57    repo.add(&["Cargo.toml"])?;
58    let commit4 = repo.commit("Add Cargo.toml configuration")?;
59    println!("Created commit 4: {} - Add Cargo.toml", commit4.short());
60
61    // Fifth commit - bug fix
62    fs::write(
63        test_path.join("src/main.rs"),
64        "fn main() {\n    println!(\"Hello, rustic-git!\");\n}",
65    )
66    .unwrap();
67    repo.add(&["src/main.rs"])?;
68    let commit5 = repo.commit("Fix greeting message in main")?;
69    println!("Created commit 5: {} - Fix greeting", commit5.short());
70
71    // Sixth commit - documentation
72    fs::write(test_path.join("README.md"), "# Commit History Demo\n\nA demonstration of rustic-git log functionality.\n\n## Features\n\n- Greeting functionality\n- Command line interface\n").unwrap();
73    repo.add(&["README.md"])?;
74    let commit6 = repo.commit("Update README with features section")?;
75    println!("Created commit 6: {} - Update README", commit6.short());
76
77    println!("Built commit history with 6 commits");
78
79    // Basic log operations
80    println!("\n=== Basic Log Operations ===");
81
82    let all_commits = repo.log()?;
83    println!("Total commits in repository: {}", all_commits.len());
84
85    println!("\nAll commits (most recent first):");
86    for (i, commit) in all_commits.iter().enumerate() {
87        println!("  {}. {}", i + 1, commit);
88    }
89
90    // Recent commits
91    println!("\n=== Recent Commits ===");
92    let recent = repo.recent_commits(3)?;
93    println!("Last 3 commits:");
94    for commit in recent.iter() {
95        println!("  {} - {}", commit.hash.short(), commit.message.subject);
96        if let Some(body) = &commit.message.body {
97            println!("    Body: {}", body);
98        }
99    }
100
101    // Advanced filtering with LogOptions
102    println!("\n=== Advanced Filtering ===");
103
104    // Filter by message content
105    let fix_commits = all_commits.with_message_containing("fix");
106    println!("Commits with 'fix' in message:");
107    for commit in fix_commits {
108        println!("  {} - {}", commit.hash.short(), commit.message.subject);
109    }
110
111    // Filter by date (recent commits)
112    let now = Utc::now();
113    let recent_commits = all_commits.since(now - Duration::minutes(5));
114    println!("\nCommits from last 5 minutes: {}", recent_commits.count());
115
116    // Using LogOptions for advanced queries
117    println!("\n=== LogOptions Advanced Queries ===");
118
119    // Get commits with grep
120    let opts = LogOptions::new().max_count(10).grep("README".to_string());
121    let readme_commits = repo.log_with_options(&opts)?;
122    println!("Commits mentioning 'README': {}", readme_commits.len());
123    for commit in readme_commits.iter() {
124        println!("  {} - {}", commit.hash.short(), commit.message.subject);
125    }
126
127    // Get commits affecting specific paths
128    println!("\n=== Path-Specific History ===");
129    let src_commits = repo.log_for_paths(&["src/"])?;
130    println!("Commits affecting src/ directory: {}", src_commits.len());
131    for commit in src_commits.iter() {
132        println!("  {} - {}", commit.hash.short(), commit.message.subject);
133    }
134
135    // Show detailed commit information
136    println!("\n=== Detailed Commit Information ===");
137
138    let commit_details = repo.show_commit(&commit3)?;
139    println!("Detailed info for commit {}:", commit3.short());
140    println!("  Author: {}", commit_details.commit.author);
141    println!("  Committer: {}", commit_details.commit.committer);
142    println!(
143        "  Timestamp: {}",
144        commit_details
145            .commit
146            .timestamp
147            .format("%Y-%m-%d %H:%M:%S UTC")
148    );
149    println!("  Message: {}", commit_details.commit.message.subject);
150    println!("  Parents: {}", commit_details.commit.parents.len());
151    for parent in commit_details.commit.parents.iter() {
152        println!("    - {}", parent.short());
153    }
154    println!("  Files changed: {}", commit_details.files_changed.len());
155    for file in &commit_details.files_changed {
156        println!("    - {}", file.display());
157    }
158    println!(
159        "  Changes: +{} -{}",
160        commit_details.insertions, commit_details.deletions
161    );
162
163    // Commit analysis
164    println!("\n=== Commit Analysis ===");
165
166    let merge_commits: Vec<_> = all_commits.merges_only().collect();
167    let regular_commits: Vec<_> = all_commits.no_merges().collect();
168
169    println!("Repository statistics:");
170    println!("  Total commits: {}", all_commits.len());
171    println!("  Merge commits: {}", merge_commits.len());
172    println!("  Regular commits: {}", regular_commits.len());
173
174    if let Some(first_commit) = all_commits.first() {
175        println!(
176            "  Most recent: {} ({})",
177            first_commit.hash.short(),
178            first_commit.message.subject
179        );
180    }
181
182    if let Some(last_commit) = all_commits.last() {
183        println!(
184            "  Oldest: {} ({})",
185            last_commit.hash.short(),
186            last_commit.message.subject
187        );
188    }
189
190    // Search operations
191    println!("\n=== Search Operations ===");
192
193    // Find by hash
194    if let Some(found) = all_commits.find_by_hash(&commit2) {
195        println!("Found commit by full hash: {}", found.message.subject);
196    }
197
198    // Find by short hash
199    if let Some(found) = all_commits.find_by_short_hash(commit4.short()) {
200        println!("Found commit by short hash: {}", found.message.subject);
201    }
202
203    // Commit range operations
204    println!("\n=== Commit Range Operations ===");
205
206    let range_commits = repo.log_range(&commit2, &commit5)?;
207    println!(
208        "Commits in range {}..{}: {}",
209        commit2.short(),
210        commit5.short(),
211        range_commits.len()
212    );
213    for commit in range_commits.iter() {
214        println!("  {} - {}", commit.hash.short(), commit.message.subject);
215    }
216
217    // Advanced LogOptions demonstration
218    println!("\n=== Advanced LogOptions Usage ===");
219
220    let advanced_opts = LogOptions::new()
221        .max_count(5)
222        .no_merges(true)
223        .paths(vec!["src/main.rs".into()]);
224
225    let filtered_commits = repo.log_with_options(&advanced_opts)?;
226    println!(
227        "Non-merge commits affecting src/main.rs (max 5): {}",
228        filtered_commits.len()
229    );
230    for commit in filtered_commits.iter() {
231        println!("  {} - {}", commit.hash.short(), commit.message.subject);
232    }
233
234    // Commit message analysis
235    println!("\n=== Commit Message Analysis ===");
236
237    let total_commits = all_commits.len();
238    let commits_with_body: Vec<_> = all_commits
239        .iter()
240        .filter(|c| c.message.body.is_some())
241        .collect();
242
243    println!("Message statistics:");
244    println!("  Total commits: {}", total_commits);
245    println!("  Commits with body text: {}", commits_with_body.len());
246    println!(
247        "  Commits with subject only: {}",
248        total_commits - commits_with_body.len()
249    );
250
251    // Display commit types by analyzing subjects
252    let fix_count = all_commits
253        .iter()
254        .filter(|c| c.message.subject.to_lowercase().contains("fix"))
255        .count();
256    let add_count = all_commits
257        .iter()
258        .filter(|c| c.message.subject.to_lowercase().contains("add"))
259        .count();
260    let update_count = all_commits
261        .iter()
262        .filter(|c| c.message.subject.to_lowercase().contains("update"))
263        .count();
264
265    println!("  Commit types:");
266    println!("    - Fix commits: {}", fix_count);
267    println!("    - Add commits: {}", add_count);
268    println!("    - Update commits: {}", update_count);
269    println!(
270        "    - Other commits: {}",
271        total_commits - fix_count - add_count - update_count
272    );
273
274    // Timeline view
275    println!("\n=== Timeline View ===");
276
277    println!("Commit timeline (oldest to newest):");
278    let commits: Vec<_> = all_commits.iter().collect();
279    for commit in commits.iter().rev() {
280        let commit_type = if commit.is_merge() { "MERGE" } else { "COMMIT" };
281        println!(
282            "  {} {} {} - {}",
283            commit.timestamp.format("%H:%M:%S"),
284            commit_type,
285            commit.hash.short(),
286            commit.message.subject
287        );
288    }
289
290    // Summary
291    println!("\n=== Summary ===");
292
293    println!("Commit history demonstration completed!");
294    println!("  Repository: {}", test_path.display());
295    println!("  Total commits analyzed: {}", all_commits.len());
296    println!("  Hash examples:");
297    for commit in all_commits.iter().take(3) {
298        println!("    - Full: {}", commit.hash.as_str());
299        println!("      Short: {}", commit.hash.short());
300    }
301
302    // Clean up
303    fs::remove_dir_all(&test_path).unwrap();
304    println!("\nCleaned up test repository");
305
306    Ok(())
307}
Source

pub fn log_range(&self, from: &Hash, to: &Hash) -> Result<CommitLog>

Get commits in a range between two commits

Examples found in repository?
examples/commit_history.rs (line 206)
5fn main() -> Result<()> {
6    let test_path = env::temp_dir().join("rustic_git_commit_history_example");
7
8    // Clean up if exists
9    if test_path.exists() {
10        fs::remove_dir_all(&test_path).unwrap();
11    }
12
13    // Create a test repository
14    let repo = Repository::init(&test_path, false)?;
15    println!("Created repository at: {}", test_path.display());
16
17    // Create several commits to build history
18    println!("\n=== Building Commit History ===");
19
20    // First commit
21    fs::write(
22        test_path.join("README.md"),
23        "# Commit History Demo\n\nA demonstration of rustic-git log functionality.",
24    )
25    .unwrap();
26    repo.add(&["README.md"])?;
27    let commit1 = repo.commit("Initial commit - add README")?;
28    println!("Created commit 1: {} - Initial commit", commit1.short());
29
30    // Second commit
31    fs::create_dir_all(test_path.join("src")).unwrap();
32    fs::write(
33        test_path.join("src/main.rs"),
34        "fn main() {\n    println!(\"Hello, world!\");\n}",
35    )
36    .unwrap();
37    repo.add(&["src/main.rs"])?;
38    let commit2 = repo.commit("Add main.rs with Hello World")?;
39    println!("Created commit 2: {} - Add main.rs", commit2.short());
40
41    // Third commit
42    fs::write(
43        test_path.join("src/lib.rs"),
44        "pub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}",
45    )
46    .unwrap();
47    repo.add(&["src/lib.rs"])?;
48    let commit3 = repo.commit("Add library module with greet function")?;
49    println!("Created commit 3: {} - Add lib.rs", commit3.short());
50
51    // Fourth commit
52    fs::write(
53        test_path.join("Cargo.toml"),
54        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
55    )
56    .unwrap();
57    repo.add(&["Cargo.toml"])?;
58    let commit4 = repo.commit("Add Cargo.toml configuration")?;
59    println!("Created commit 4: {} - Add Cargo.toml", commit4.short());
60
61    // Fifth commit - bug fix
62    fs::write(
63        test_path.join("src/main.rs"),
64        "fn main() {\n    println!(\"Hello, rustic-git!\");\n}",
65    )
66    .unwrap();
67    repo.add(&["src/main.rs"])?;
68    let commit5 = repo.commit("Fix greeting message in main")?;
69    println!("Created commit 5: {} - Fix greeting", commit5.short());
70
71    // Sixth commit - documentation
72    fs::write(test_path.join("README.md"), "# Commit History Demo\n\nA demonstration of rustic-git log functionality.\n\n## Features\n\n- Greeting functionality\n- Command line interface\n").unwrap();
73    repo.add(&["README.md"])?;
74    let commit6 = repo.commit("Update README with features section")?;
75    println!("Created commit 6: {} - Update README", commit6.short());
76
77    println!("Built commit history with 6 commits");
78
79    // Basic log operations
80    println!("\n=== Basic Log Operations ===");
81
82    let all_commits = repo.log()?;
83    println!("Total commits in repository: {}", all_commits.len());
84
85    println!("\nAll commits (most recent first):");
86    for (i, commit) in all_commits.iter().enumerate() {
87        println!("  {}. {}", i + 1, commit);
88    }
89
90    // Recent commits
91    println!("\n=== Recent Commits ===");
92    let recent = repo.recent_commits(3)?;
93    println!("Last 3 commits:");
94    for commit in recent.iter() {
95        println!("  {} - {}", commit.hash.short(), commit.message.subject);
96        if let Some(body) = &commit.message.body {
97            println!("    Body: {}", body);
98        }
99    }
100
101    // Advanced filtering with LogOptions
102    println!("\n=== Advanced Filtering ===");
103
104    // Filter by message content
105    let fix_commits = all_commits.with_message_containing("fix");
106    println!("Commits with 'fix' in message:");
107    for commit in fix_commits {
108        println!("  {} - {}", commit.hash.short(), commit.message.subject);
109    }
110
111    // Filter by date (recent commits)
112    let now = Utc::now();
113    let recent_commits = all_commits.since(now - Duration::minutes(5));
114    println!("\nCommits from last 5 minutes: {}", recent_commits.count());
115
116    // Using LogOptions for advanced queries
117    println!("\n=== LogOptions Advanced Queries ===");
118
119    // Get commits with grep
120    let opts = LogOptions::new().max_count(10).grep("README".to_string());
121    let readme_commits = repo.log_with_options(&opts)?;
122    println!("Commits mentioning 'README': {}", readme_commits.len());
123    for commit in readme_commits.iter() {
124        println!("  {} - {}", commit.hash.short(), commit.message.subject);
125    }
126
127    // Get commits affecting specific paths
128    println!("\n=== Path-Specific History ===");
129    let src_commits = repo.log_for_paths(&["src/"])?;
130    println!("Commits affecting src/ directory: {}", src_commits.len());
131    for commit in src_commits.iter() {
132        println!("  {} - {}", commit.hash.short(), commit.message.subject);
133    }
134
135    // Show detailed commit information
136    println!("\n=== Detailed Commit Information ===");
137
138    let commit_details = repo.show_commit(&commit3)?;
139    println!("Detailed info for commit {}:", commit3.short());
140    println!("  Author: {}", commit_details.commit.author);
141    println!("  Committer: {}", commit_details.commit.committer);
142    println!(
143        "  Timestamp: {}",
144        commit_details
145            .commit
146            .timestamp
147            .format("%Y-%m-%d %H:%M:%S UTC")
148    );
149    println!("  Message: {}", commit_details.commit.message.subject);
150    println!("  Parents: {}", commit_details.commit.parents.len());
151    for parent in commit_details.commit.parents.iter() {
152        println!("    - {}", parent.short());
153    }
154    println!("  Files changed: {}", commit_details.files_changed.len());
155    for file in &commit_details.files_changed {
156        println!("    - {}", file.display());
157    }
158    println!(
159        "  Changes: +{} -{}",
160        commit_details.insertions, commit_details.deletions
161    );
162
163    // Commit analysis
164    println!("\n=== Commit Analysis ===");
165
166    let merge_commits: Vec<_> = all_commits.merges_only().collect();
167    let regular_commits: Vec<_> = all_commits.no_merges().collect();
168
169    println!("Repository statistics:");
170    println!("  Total commits: {}", all_commits.len());
171    println!("  Merge commits: {}", merge_commits.len());
172    println!("  Regular commits: {}", regular_commits.len());
173
174    if let Some(first_commit) = all_commits.first() {
175        println!(
176            "  Most recent: {} ({})",
177            first_commit.hash.short(),
178            first_commit.message.subject
179        );
180    }
181
182    if let Some(last_commit) = all_commits.last() {
183        println!(
184            "  Oldest: {} ({})",
185            last_commit.hash.short(),
186            last_commit.message.subject
187        );
188    }
189
190    // Search operations
191    println!("\n=== Search Operations ===");
192
193    // Find by hash
194    if let Some(found) = all_commits.find_by_hash(&commit2) {
195        println!("Found commit by full hash: {}", found.message.subject);
196    }
197
198    // Find by short hash
199    if let Some(found) = all_commits.find_by_short_hash(commit4.short()) {
200        println!("Found commit by short hash: {}", found.message.subject);
201    }
202
203    // Commit range operations
204    println!("\n=== Commit Range Operations ===");
205
206    let range_commits = repo.log_range(&commit2, &commit5)?;
207    println!(
208        "Commits in range {}..{}: {}",
209        commit2.short(),
210        commit5.short(),
211        range_commits.len()
212    );
213    for commit in range_commits.iter() {
214        println!("  {} - {}", commit.hash.short(), commit.message.subject);
215    }
216
217    // Advanced LogOptions demonstration
218    println!("\n=== Advanced LogOptions Usage ===");
219
220    let advanced_opts = LogOptions::new()
221        .max_count(5)
222        .no_merges(true)
223        .paths(vec!["src/main.rs".into()]);
224
225    let filtered_commits = repo.log_with_options(&advanced_opts)?;
226    println!(
227        "Non-merge commits affecting src/main.rs (max 5): {}",
228        filtered_commits.len()
229    );
230    for commit in filtered_commits.iter() {
231        println!("  {} - {}", commit.hash.short(), commit.message.subject);
232    }
233
234    // Commit message analysis
235    println!("\n=== Commit Message Analysis ===");
236
237    let total_commits = all_commits.len();
238    let commits_with_body: Vec<_> = all_commits
239        .iter()
240        .filter(|c| c.message.body.is_some())
241        .collect();
242
243    println!("Message statistics:");
244    println!("  Total commits: {}", total_commits);
245    println!("  Commits with body text: {}", commits_with_body.len());
246    println!(
247        "  Commits with subject only: {}",
248        total_commits - commits_with_body.len()
249    );
250
251    // Display commit types by analyzing subjects
252    let fix_count = all_commits
253        .iter()
254        .filter(|c| c.message.subject.to_lowercase().contains("fix"))
255        .count();
256    let add_count = all_commits
257        .iter()
258        .filter(|c| c.message.subject.to_lowercase().contains("add"))
259        .count();
260    let update_count = all_commits
261        .iter()
262        .filter(|c| c.message.subject.to_lowercase().contains("update"))
263        .count();
264
265    println!("  Commit types:");
266    println!("    - Fix commits: {}", fix_count);
267    println!("    - Add commits: {}", add_count);
268    println!("    - Update commits: {}", update_count);
269    println!(
270        "    - Other commits: {}",
271        total_commits - fix_count - add_count - update_count
272    );
273
274    // Timeline view
275    println!("\n=== Timeline View ===");
276
277    println!("Commit timeline (oldest to newest):");
278    let commits: Vec<_> = all_commits.iter().collect();
279    for commit in commits.iter().rev() {
280        let commit_type = if commit.is_merge() { "MERGE" } else { "COMMIT" };
281        println!(
282            "  {} {} {} - {}",
283            commit.timestamp.format("%H:%M:%S"),
284            commit_type,
285            commit.hash.short(),
286            commit.message.subject
287        );
288    }
289
290    // Summary
291    println!("\n=== Summary ===");
292
293    println!("Commit history demonstration completed!");
294    println!("  Repository: {}", test_path.display());
295    println!("  Total commits analyzed: {}", all_commits.len());
296    println!("  Hash examples:");
297    for commit in all_commits.iter().take(3) {
298        println!("    - Full: {}", commit.hash.as_str());
299        println!("      Short: {}", commit.hash.short());
300    }
301
302    // Clean up
303    fs::remove_dir_all(&test_path).unwrap();
304    println!("\nCleaned up test repository");
305
306    Ok(())
307}
Source

pub fn log_for_paths(&self, paths: &[impl AsRef<Path>]) -> Result<CommitLog>

Get commits that affected specific paths

Examples found in repository?
examples/commit_history.rs (line 129)
5fn main() -> Result<()> {
6    let test_path = env::temp_dir().join("rustic_git_commit_history_example");
7
8    // Clean up if exists
9    if test_path.exists() {
10        fs::remove_dir_all(&test_path).unwrap();
11    }
12
13    // Create a test repository
14    let repo = Repository::init(&test_path, false)?;
15    println!("Created repository at: {}", test_path.display());
16
17    // Create several commits to build history
18    println!("\n=== Building Commit History ===");
19
20    // First commit
21    fs::write(
22        test_path.join("README.md"),
23        "# Commit History Demo\n\nA demonstration of rustic-git log functionality.",
24    )
25    .unwrap();
26    repo.add(&["README.md"])?;
27    let commit1 = repo.commit("Initial commit - add README")?;
28    println!("Created commit 1: {} - Initial commit", commit1.short());
29
30    // Second commit
31    fs::create_dir_all(test_path.join("src")).unwrap();
32    fs::write(
33        test_path.join("src/main.rs"),
34        "fn main() {\n    println!(\"Hello, world!\");\n}",
35    )
36    .unwrap();
37    repo.add(&["src/main.rs"])?;
38    let commit2 = repo.commit("Add main.rs with Hello World")?;
39    println!("Created commit 2: {} - Add main.rs", commit2.short());
40
41    // Third commit
42    fs::write(
43        test_path.join("src/lib.rs"),
44        "pub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}",
45    )
46    .unwrap();
47    repo.add(&["src/lib.rs"])?;
48    let commit3 = repo.commit("Add library module with greet function")?;
49    println!("Created commit 3: {} - Add lib.rs", commit3.short());
50
51    // Fourth commit
52    fs::write(
53        test_path.join("Cargo.toml"),
54        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
55    )
56    .unwrap();
57    repo.add(&["Cargo.toml"])?;
58    let commit4 = repo.commit("Add Cargo.toml configuration")?;
59    println!("Created commit 4: {} - Add Cargo.toml", commit4.short());
60
61    // Fifth commit - bug fix
62    fs::write(
63        test_path.join("src/main.rs"),
64        "fn main() {\n    println!(\"Hello, rustic-git!\");\n}",
65    )
66    .unwrap();
67    repo.add(&["src/main.rs"])?;
68    let commit5 = repo.commit("Fix greeting message in main")?;
69    println!("Created commit 5: {} - Fix greeting", commit5.short());
70
71    // Sixth commit - documentation
72    fs::write(test_path.join("README.md"), "# Commit History Demo\n\nA demonstration of rustic-git log functionality.\n\n## Features\n\n- Greeting functionality\n- Command line interface\n").unwrap();
73    repo.add(&["README.md"])?;
74    let commit6 = repo.commit("Update README with features section")?;
75    println!("Created commit 6: {} - Update README", commit6.short());
76
77    println!("Built commit history with 6 commits");
78
79    // Basic log operations
80    println!("\n=== Basic Log Operations ===");
81
82    let all_commits = repo.log()?;
83    println!("Total commits in repository: {}", all_commits.len());
84
85    println!("\nAll commits (most recent first):");
86    for (i, commit) in all_commits.iter().enumerate() {
87        println!("  {}. {}", i + 1, commit);
88    }
89
90    // Recent commits
91    println!("\n=== Recent Commits ===");
92    let recent = repo.recent_commits(3)?;
93    println!("Last 3 commits:");
94    for commit in recent.iter() {
95        println!("  {} - {}", commit.hash.short(), commit.message.subject);
96        if let Some(body) = &commit.message.body {
97            println!("    Body: {}", body);
98        }
99    }
100
101    // Advanced filtering with LogOptions
102    println!("\n=== Advanced Filtering ===");
103
104    // Filter by message content
105    let fix_commits = all_commits.with_message_containing("fix");
106    println!("Commits with 'fix' in message:");
107    for commit in fix_commits {
108        println!("  {} - {}", commit.hash.short(), commit.message.subject);
109    }
110
111    // Filter by date (recent commits)
112    let now = Utc::now();
113    let recent_commits = all_commits.since(now - Duration::minutes(5));
114    println!("\nCommits from last 5 minutes: {}", recent_commits.count());
115
116    // Using LogOptions for advanced queries
117    println!("\n=== LogOptions Advanced Queries ===");
118
119    // Get commits with grep
120    let opts = LogOptions::new().max_count(10).grep("README".to_string());
121    let readme_commits = repo.log_with_options(&opts)?;
122    println!("Commits mentioning 'README': {}", readme_commits.len());
123    for commit in readme_commits.iter() {
124        println!("  {} - {}", commit.hash.short(), commit.message.subject);
125    }
126
127    // Get commits affecting specific paths
128    println!("\n=== Path-Specific History ===");
129    let src_commits = repo.log_for_paths(&["src/"])?;
130    println!("Commits affecting src/ directory: {}", src_commits.len());
131    for commit in src_commits.iter() {
132        println!("  {} - {}", commit.hash.short(), commit.message.subject);
133    }
134
135    // Show detailed commit information
136    println!("\n=== Detailed Commit Information ===");
137
138    let commit_details = repo.show_commit(&commit3)?;
139    println!("Detailed info for commit {}:", commit3.short());
140    println!("  Author: {}", commit_details.commit.author);
141    println!("  Committer: {}", commit_details.commit.committer);
142    println!(
143        "  Timestamp: {}",
144        commit_details
145            .commit
146            .timestamp
147            .format("%Y-%m-%d %H:%M:%S UTC")
148    );
149    println!("  Message: {}", commit_details.commit.message.subject);
150    println!("  Parents: {}", commit_details.commit.parents.len());
151    for parent in commit_details.commit.parents.iter() {
152        println!("    - {}", parent.short());
153    }
154    println!("  Files changed: {}", commit_details.files_changed.len());
155    for file in &commit_details.files_changed {
156        println!("    - {}", file.display());
157    }
158    println!(
159        "  Changes: +{} -{}",
160        commit_details.insertions, commit_details.deletions
161    );
162
163    // Commit analysis
164    println!("\n=== Commit Analysis ===");
165
166    let merge_commits: Vec<_> = all_commits.merges_only().collect();
167    let regular_commits: Vec<_> = all_commits.no_merges().collect();
168
169    println!("Repository statistics:");
170    println!("  Total commits: {}", all_commits.len());
171    println!("  Merge commits: {}", merge_commits.len());
172    println!("  Regular commits: {}", regular_commits.len());
173
174    if let Some(first_commit) = all_commits.first() {
175        println!(
176            "  Most recent: {} ({})",
177            first_commit.hash.short(),
178            first_commit.message.subject
179        );
180    }
181
182    if let Some(last_commit) = all_commits.last() {
183        println!(
184            "  Oldest: {} ({})",
185            last_commit.hash.short(),
186            last_commit.message.subject
187        );
188    }
189
190    // Search operations
191    println!("\n=== Search Operations ===");
192
193    // Find by hash
194    if let Some(found) = all_commits.find_by_hash(&commit2) {
195        println!("Found commit by full hash: {}", found.message.subject);
196    }
197
198    // Find by short hash
199    if let Some(found) = all_commits.find_by_short_hash(commit4.short()) {
200        println!("Found commit by short hash: {}", found.message.subject);
201    }
202
203    // Commit range operations
204    println!("\n=== Commit Range Operations ===");
205
206    let range_commits = repo.log_range(&commit2, &commit5)?;
207    println!(
208        "Commits in range {}..{}: {}",
209        commit2.short(),
210        commit5.short(),
211        range_commits.len()
212    );
213    for commit in range_commits.iter() {
214        println!("  {} - {}", commit.hash.short(), commit.message.subject);
215    }
216
217    // Advanced LogOptions demonstration
218    println!("\n=== Advanced LogOptions Usage ===");
219
220    let advanced_opts = LogOptions::new()
221        .max_count(5)
222        .no_merges(true)
223        .paths(vec!["src/main.rs".into()]);
224
225    let filtered_commits = repo.log_with_options(&advanced_opts)?;
226    println!(
227        "Non-merge commits affecting src/main.rs (max 5): {}",
228        filtered_commits.len()
229    );
230    for commit in filtered_commits.iter() {
231        println!("  {} - {}", commit.hash.short(), commit.message.subject);
232    }
233
234    // Commit message analysis
235    println!("\n=== Commit Message Analysis ===");
236
237    let total_commits = all_commits.len();
238    let commits_with_body: Vec<_> = all_commits
239        .iter()
240        .filter(|c| c.message.body.is_some())
241        .collect();
242
243    println!("Message statistics:");
244    println!("  Total commits: {}", total_commits);
245    println!("  Commits with body text: {}", commits_with_body.len());
246    println!(
247        "  Commits with subject only: {}",
248        total_commits - commits_with_body.len()
249    );
250
251    // Display commit types by analyzing subjects
252    let fix_count = all_commits
253        .iter()
254        .filter(|c| c.message.subject.to_lowercase().contains("fix"))
255        .count();
256    let add_count = all_commits
257        .iter()
258        .filter(|c| c.message.subject.to_lowercase().contains("add"))
259        .count();
260    let update_count = all_commits
261        .iter()
262        .filter(|c| c.message.subject.to_lowercase().contains("update"))
263        .count();
264
265    println!("  Commit types:");
266    println!("    - Fix commits: {}", fix_count);
267    println!("    - Add commits: {}", add_count);
268    println!("    - Update commits: {}", update_count);
269    println!(
270        "    - Other commits: {}",
271        total_commits - fix_count - add_count - update_count
272    );
273
274    // Timeline view
275    println!("\n=== Timeline View ===");
276
277    println!("Commit timeline (oldest to newest):");
278    let commits: Vec<_> = all_commits.iter().collect();
279    for commit in commits.iter().rev() {
280        let commit_type = if commit.is_merge() { "MERGE" } else { "COMMIT" };
281        println!(
282            "  {} {} {} - {}",
283            commit.timestamp.format("%H:%M:%S"),
284            commit_type,
285            commit.hash.short(),
286            commit.message.subject
287        );
288    }
289
290    // Summary
291    println!("\n=== Summary ===");
292
293    println!("Commit history demonstration completed!");
294    println!("  Repository: {}", test_path.display());
295    println!("  Total commits analyzed: {}", all_commits.len());
296    println!("  Hash examples:");
297    for commit in all_commits.iter().take(3) {
298        println!("    - Full: {}", commit.hash.as_str());
299        println!("      Short: {}", commit.hash.short());
300    }
301
302    // Clean up
303    fs::remove_dir_all(&test_path).unwrap();
304    println!("\nCleaned up test repository");
305
306    Ok(())
307}
Source

pub fn show_commit(&self, hash: &Hash) -> Result<CommitDetails>

Get detailed information about a specific commit

Examples found in repository?
examples/commit_history.rs (line 138)
5fn main() -> Result<()> {
6    let test_path = env::temp_dir().join("rustic_git_commit_history_example");
7
8    // Clean up if exists
9    if test_path.exists() {
10        fs::remove_dir_all(&test_path).unwrap();
11    }
12
13    // Create a test repository
14    let repo = Repository::init(&test_path, false)?;
15    println!("Created repository at: {}", test_path.display());
16
17    // Create several commits to build history
18    println!("\n=== Building Commit History ===");
19
20    // First commit
21    fs::write(
22        test_path.join("README.md"),
23        "# Commit History Demo\n\nA demonstration of rustic-git log functionality.",
24    )
25    .unwrap();
26    repo.add(&["README.md"])?;
27    let commit1 = repo.commit("Initial commit - add README")?;
28    println!("Created commit 1: {} - Initial commit", commit1.short());
29
30    // Second commit
31    fs::create_dir_all(test_path.join("src")).unwrap();
32    fs::write(
33        test_path.join("src/main.rs"),
34        "fn main() {\n    println!(\"Hello, world!\");\n}",
35    )
36    .unwrap();
37    repo.add(&["src/main.rs"])?;
38    let commit2 = repo.commit("Add main.rs with Hello World")?;
39    println!("Created commit 2: {} - Add main.rs", commit2.short());
40
41    // Third commit
42    fs::write(
43        test_path.join("src/lib.rs"),
44        "pub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}",
45    )
46    .unwrap();
47    repo.add(&["src/lib.rs"])?;
48    let commit3 = repo.commit("Add library module with greet function")?;
49    println!("Created commit 3: {} - Add lib.rs", commit3.short());
50
51    // Fourth commit
52    fs::write(
53        test_path.join("Cargo.toml"),
54        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
55    )
56    .unwrap();
57    repo.add(&["Cargo.toml"])?;
58    let commit4 = repo.commit("Add Cargo.toml configuration")?;
59    println!("Created commit 4: {} - Add Cargo.toml", commit4.short());
60
61    // Fifth commit - bug fix
62    fs::write(
63        test_path.join("src/main.rs"),
64        "fn main() {\n    println!(\"Hello, rustic-git!\");\n}",
65    )
66    .unwrap();
67    repo.add(&["src/main.rs"])?;
68    let commit5 = repo.commit("Fix greeting message in main")?;
69    println!("Created commit 5: {} - Fix greeting", commit5.short());
70
71    // Sixth commit - documentation
72    fs::write(test_path.join("README.md"), "# Commit History Demo\n\nA demonstration of rustic-git log functionality.\n\n## Features\n\n- Greeting functionality\n- Command line interface\n").unwrap();
73    repo.add(&["README.md"])?;
74    let commit6 = repo.commit("Update README with features section")?;
75    println!("Created commit 6: {} - Update README", commit6.short());
76
77    println!("Built commit history with 6 commits");
78
79    // Basic log operations
80    println!("\n=== Basic Log Operations ===");
81
82    let all_commits = repo.log()?;
83    println!("Total commits in repository: {}", all_commits.len());
84
85    println!("\nAll commits (most recent first):");
86    for (i, commit) in all_commits.iter().enumerate() {
87        println!("  {}. {}", i + 1, commit);
88    }
89
90    // Recent commits
91    println!("\n=== Recent Commits ===");
92    let recent = repo.recent_commits(3)?;
93    println!("Last 3 commits:");
94    for commit in recent.iter() {
95        println!("  {} - {}", commit.hash.short(), commit.message.subject);
96        if let Some(body) = &commit.message.body {
97            println!("    Body: {}", body);
98        }
99    }
100
101    // Advanced filtering with LogOptions
102    println!("\n=== Advanced Filtering ===");
103
104    // Filter by message content
105    let fix_commits = all_commits.with_message_containing("fix");
106    println!("Commits with 'fix' in message:");
107    for commit in fix_commits {
108        println!("  {} - {}", commit.hash.short(), commit.message.subject);
109    }
110
111    // Filter by date (recent commits)
112    let now = Utc::now();
113    let recent_commits = all_commits.since(now - Duration::minutes(5));
114    println!("\nCommits from last 5 minutes: {}", recent_commits.count());
115
116    // Using LogOptions for advanced queries
117    println!("\n=== LogOptions Advanced Queries ===");
118
119    // Get commits with grep
120    let opts = LogOptions::new().max_count(10).grep("README".to_string());
121    let readme_commits = repo.log_with_options(&opts)?;
122    println!("Commits mentioning 'README': {}", readme_commits.len());
123    for commit in readme_commits.iter() {
124        println!("  {} - {}", commit.hash.short(), commit.message.subject);
125    }
126
127    // Get commits affecting specific paths
128    println!("\n=== Path-Specific History ===");
129    let src_commits = repo.log_for_paths(&["src/"])?;
130    println!("Commits affecting src/ directory: {}", src_commits.len());
131    for commit in src_commits.iter() {
132        println!("  {} - {}", commit.hash.short(), commit.message.subject);
133    }
134
135    // Show detailed commit information
136    println!("\n=== Detailed Commit Information ===");
137
138    let commit_details = repo.show_commit(&commit3)?;
139    println!("Detailed info for commit {}:", commit3.short());
140    println!("  Author: {}", commit_details.commit.author);
141    println!("  Committer: {}", commit_details.commit.committer);
142    println!(
143        "  Timestamp: {}",
144        commit_details
145            .commit
146            .timestamp
147            .format("%Y-%m-%d %H:%M:%S UTC")
148    );
149    println!("  Message: {}", commit_details.commit.message.subject);
150    println!("  Parents: {}", commit_details.commit.parents.len());
151    for parent in commit_details.commit.parents.iter() {
152        println!("    - {}", parent.short());
153    }
154    println!("  Files changed: {}", commit_details.files_changed.len());
155    for file in &commit_details.files_changed {
156        println!("    - {}", file.display());
157    }
158    println!(
159        "  Changes: +{} -{}",
160        commit_details.insertions, commit_details.deletions
161    );
162
163    // Commit analysis
164    println!("\n=== Commit Analysis ===");
165
166    let merge_commits: Vec<_> = all_commits.merges_only().collect();
167    let regular_commits: Vec<_> = all_commits.no_merges().collect();
168
169    println!("Repository statistics:");
170    println!("  Total commits: {}", all_commits.len());
171    println!("  Merge commits: {}", merge_commits.len());
172    println!("  Regular commits: {}", regular_commits.len());
173
174    if let Some(first_commit) = all_commits.first() {
175        println!(
176            "  Most recent: {} ({})",
177            first_commit.hash.short(),
178            first_commit.message.subject
179        );
180    }
181
182    if let Some(last_commit) = all_commits.last() {
183        println!(
184            "  Oldest: {} ({})",
185            last_commit.hash.short(),
186            last_commit.message.subject
187        );
188    }
189
190    // Search operations
191    println!("\n=== Search Operations ===");
192
193    // Find by hash
194    if let Some(found) = all_commits.find_by_hash(&commit2) {
195        println!("Found commit by full hash: {}", found.message.subject);
196    }
197
198    // Find by short hash
199    if let Some(found) = all_commits.find_by_short_hash(commit4.short()) {
200        println!("Found commit by short hash: {}", found.message.subject);
201    }
202
203    // Commit range operations
204    println!("\n=== Commit Range Operations ===");
205
206    let range_commits = repo.log_range(&commit2, &commit5)?;
207    println!(
208        "Commits in range {}..{}: {}",
209        commit2.short(),
210        commit5.short(),
211        range_commits.len()
212    );
213    for commit in range_commits.iter() {
214        println!("  {} - {}", commit.hash.short(), commit.message.subject);
215    }
216
217    // Advanced LogOptions demonstration
218    println!("\n=== Advanced LogOptions Usage ===");
219
220    let advanced_opts = LogOptions::new()
221        .max_count(5)
222        .no_merges(true)
223        .paths(vec!["src/main.rs".into()]);
224
225    let filtered_commits = repo.log_with_options(&advanced_opts)?;
226    println!(
227        "Non-merge commits affecting src/main.rs (max 5): {}",
228        filtered_commits.len()
229    );
230    for commit in filtered_commits.iter() {
231        println!("  {} - {}", commit.hash.short(), commit.message.subject);
232    }
233
234    // Commit message analysis
235    println!("\n=== Commit Message Analysis ===");
236
237    let total_commits = all_commits.len();
238    let commits_with_body: Vec<_> = all_commits
239        .iter()
240        .filter(|c| c.message.body.is_some())
241        .collect();
242
243    println!("Message statistics:");
244    println!("  Total commits: {}", total_commits);
245    println!("  Commits with body text: {}", commits_with_body.len());
246    println!(
247        "  Commits with subject only: {}",
248        total_commits - commits_with_body.len()
249    );
250
251    // Display commit types by analyzing subjects
252    let fix_count = all_commits
253        .iter()
254        .filter(|c| c.message.subject.to_lowercase().contains("fix"))
255        .count();
256    let add_count = all_commits
257        .iter()
258        .filter(|c| c.message.subject.to_lowercase().contains("add"))
259        .count();
260    let update_count = all_commits
261        .iter()
262        .filter(|c| c.message.subject.to_lowercase().contains("update"))
263        .count();
264
265    println!("  Commit types:");
266    println!("    - Fix commits: {}", fix_count);
267    println!("    - Add commits: {}", add_count);
268    println!("    - Update commits: {}", update_count);
269    println!(
270        "    - Other commits: {}",
271        total_commits - fix_count - add_count - update_count
272    );
273
274    // Timeline view
275    println!("\n=== Timeline View ===");
276
277    println!("Commit timeline (oldest to newest):");
278    let commits: Vec<_> = all_commits.iter().collect();
279    for commit in commits.iter().rev() {
280        let commit_type = if commit.is_merge() { "MERGE" } else { "COMMIT" };
281        println!(
282            "  {} {} {} - {}",
283            commit.timestamp.format("%H:%M:%S"),
284            commit_type,
285            commit.hash.short(),
286            commit.message.subject
287        );
288    }
289
290    // Summary
291    println!("\n=== Summary ===");
292
293    println!("Commit history demonstration completed!");
294    println!("  Repository: {}", test_path.display());
295    println!("  Total commits analyzed: {}", all_commits.len());
296    println!("  Hash examples:");
297    for commit in all_commits.iter().take(3) {
298        println!("    - Full: {}", commit.hash.as_str());
299        println!("      Short: {}", commit.hash.short());
300    }
301
302    // Clean up
303    fs::remove_dir_all(&test_path).unwrap();
304    println!("\nCleaned up test repository");
305
306    Ok(())
307}
Source§

impl Repository

Source

pub fn merge(&self, branch: &str) -> Result<MergeStatus>

Merge the specified branch into the current branch.

Performs a merge using default options (allow fast-forward, no custom message).

§Arguments
  • branch - The name of the branch to merge into the current branch
§Returns

A Result containing the MergeStatus which indicates the outcome of the merge.

§Examples
use rustic_git::{Repository, MergeStatus};

let repo = Repository::open(".")?;
match repo.merge("feature-branch")? {
    MergeStatus::Success(hash) => println!("Merge commit: {}", hash),
    MergeStatus::FastForward(hash) => println!("Fast-forwarded to: {}", hash),
    MergeStatus::UpToDate => println!("Already up to date"),
    MergeStatus::Conflicts(files) => println!("Conflicts in: {:?}", files),
}
Examples found in repository?
examples/merge_operations.rs (line 75)
45fn demonstrate_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
46    println!("--- Demonstrating Fast-Forward Merge ---\n");
47
48    // Create initial commit
49    println!("1. Creating initial commit on master...");
50    let file1_path = temp_dir.join("README.md");
51    fs::write(&file1_path, "# Project\n\nInitial content")?;
52    repo.add(&["README.md"])?;
53    let initial_commit = repo.commit("Initial commit")?;
54    println!("   Created commit: {}", initial_commit);
55
56    // Create feature branch and add commits
57    println!("\n2. Creating feature branch and adding commits...");
58    repo.checkout_new("feature/fast-forward", None)?;
59
60    let file2_path = temp_dir.join("feature.txt");
61    fs::write(&file2_path, "New feature implementation")?;
62    repo.add(&["feature.txt"])?;
63    let feature_commit = repo.commit("Add new feature")?;
64    println!("   Feature commit: {}", feature_commit);
65
66    // Switch back to master
67    println!("\n3. Switching back to master...");
68    let branches = repo.branches()?;
69    let master_branch = branches.find("master").unwrap();
70    repo.checkout(master_branch)?;
71    println!("   Switched to master");
72
73    // Perform fast-forward merge
74    println!("\n4. Performing fast-forward merge...");
75    let merge_status = repo.merge("feature/fast-forward")?;
76
77    match merge_status {
78        MergeStatus::FastForward(hash) => {
79            println!("   ✓ Fast-forward merge completed!");
80            println!("   New HEAD: {}", hash);
81            println!("   Both files are now present on master");
82        }
83        _ => println!("   Unexpected merge result: {:?}", merge_status),
84    }
85
86    println!("   Files in repository:");
87    for file in ["README.md", "feature.txt"] {
88        if temp_dir.join(file).exists() {
89            println!("     ✓ {}", file);
90        }
91    }
92
93    Ok(())
94}
95
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
Source

pub fn merge_with_options( &self, branch: &str, options: MergeOptions, ) -> Result<MergeStatus>

Merge the specified branch with custom options.

Provides full control over merge behavior including fast-forward mode, merge strategy, and commit message.

§Arguments
  • branch - The name of the branch to merge into the current branch
  • options - Merge options controlling the merge behavior
§Returns

A Result containing the MergeStatus which indicates the outcome of the merge.

§Examples
use rustic_git::{Repository, MergeOptions, FastForwardMode};

let repo = Repository::open(".")?;
let options = MergeOptions::new()
    .with_fast_forward(FastForwardMode::Never)
    .with_message("Merge feature into main".to_string());

let status = repo.merge_with_options("feature-branch", options)?;
Examples found in repository?
examples/merge_operations.rs (line 132)
96fn demonstrate_no_fast_forward_merge(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
97    println!("\n--- Demonstrating No-Fast-Forward Merge ---\n");
98
99    // Add a commit to master to prevent fast-forward
100    println!("1. Adding commit to master...");
101    let readme_path = temp_dir.join("README.md");
102    fs::write(
103        &readme_path,
104        "# Project\n\nInitial content\n\n## Updates\nAdded documentation",
105    )?;
106    repo.add(&["README.md"])?;
107    let master_commit = repo.commit("Update documentation")?;
108    println!("   Master commit: {}", master_commit);
109
110    // Create another feature branch
111    println!("\n2. Creating another feature branch...");
112    repo.checkout_new("feature/no-ff", None)?;
113
114    let config_path = temp_dir.join("config.yaml");
115    fs::write(&config_path, "app:\n  name: example\n  version: 1.0")?;
116    repo.add(&["config.yaml"])?;
117    let config_commit = repo.commit("Add configuration file")?;
118    println!("   Config commit: {}", config_commit);
119
120    // Switch back to master
121    println!("\n3. Switching back to master...");
122    let branches = repo.branches()?;
123    let master_branch = branches.find("master").unwrap();
124    repo.checkout(master_branch)?;
125
126    // Perform no-fast-forward merge
127    println!("\n4. Performing no-fast-forward merge...");
128    let options = MergeOptions::new()
129        .with_fast_forward(FastForwardMode::Never)
130        .with_message("Merge feature/no-ff into master".to_string());
131
132    let merge_status = repo.merge_with_options("feature/no-ff", options)?;
133
134    match merge_status {
135        MergeStatus::Success(hash) => {
136            println!("   ✓ Merge commit created!");
137            println!("   Merge commit: {}", hash);
138            println!("   Created explicit merge commit preserving branch history");
139        }
140        _ => println!("   Unexpected merge result: {:?}", merge_status),
141    }
142
143    // Show the commit history
144    println!("\n5. Recent commit history:");
145    let commits = repo.recent_commits(3)?;
146    for (i, commit) in commits.iter().enumerate() {
147        println!(
148            "   {}: {} - {}",
149            i + 1,
150            commit.hash.short(),
151            commit.message.subject
152        );
153    }
154
155    Ok(())
156}
157
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
235
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
Source

pub fn merge_in_progress(&self) -> Result<bool>

Check if a merge is currently in progress.

Returns true if there is an ongoing merge that needs to be completed or aborted.

§Returns

A Result containing a boolean indicating whether a merge is in progress.

Examples found in repository?
examples/merge_operations.rs (line 202)
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
Source

pub fn abort_merge(&self) -> Result<()>

Abort an in-progress merge.

Cancels the current merge operation and restores the repository to the state before the merge was started. This is useful when merge conflicts occur and you want to cancel the merge instead of resolving conflicts.

§Returns

A Result indicating success or failure of the abort operation.

§Examples
use rustic_git::Repository;

let repo = Repository::open(".")?;
if repo.merge_in_progress()? {
    repo.abort_merge()?;
    println!("Merge aborted");
}
Examples found in repository?
examples/merge_operations.rs (line 222)
158fn demonstrate_merge_conflicts(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
159    println!("\n--- Demonstrating Merge Conflicts ---\n");
160
161    // Create conflicting branch
162    println!("1. Creating branch with conflicting changes...");
163    repo.checkout_new("feature/conflict", None)?;
164
165    // Modify the same file differently
166    let readme_path = temp_dir.join("README.md");
167    fs::write(
168        &readme_path,
169        "# Project\n\nFeature branch changes\n\n## Updates\nAdded documentation",
170    )?;
171    repo.add(&["README.md"])?;
172    let feature_commit = repo.commit("Update README from feature branch")?;
173    println!("   Feature commit: {}", feature_commit);
174
175    // Switch back to master and make conflicting change
176    println!("\n2. Making conflicting change on master...");
177    let branches = repo.branches()?;
178    let master_branch = branches.find("master").unwrap();
179    repo.checkout(master_branch)?;
180
181    fs::write(
182        &readme_path,
183        "# Project\n\nMaster branch changes\n\n## Updates\nAdded documentation",
184    )?;
185    repo.add(&["README.md"])?;
186    let master_conflict_commit = repo.commit("Update README from master")?;
187    println!("   Master commit: {}", master_conflict_commit);
188
189    // Attempt merge (will have conflicts)
190    println!("\n3. Attempting merge (will have conflicts)...");
191    let merge_status = repo.merge("feature/conflict")?;
192
193    match merge_status {
194        MergeStatus::Conflicts(files) => {
195            println!("   ⚠️  Merge conflicts detected!");
196            println!("   Conflicted files:");
197            for file in &files {
198                println!("     - {}", file.display());
199            }
200
201            // Check merge in progress
202            if repo.merge_in_progress()? {
203                println!("   ✓ Merge in progress status detected");
204            }
205
206            // Show conflict markers in file
207            println!("\n4. Conflict markers in README.md:");
208            let content = fs::read_to_string(&readme_path)?;
209            for (i, line) in content.lines().enumerate() {
210                if line.starts_with("<<<<<<< ")
211                    || line.starts_with("======= ")
212                    || line.starts_with(">>>>>>> ")
213                {
214                    println!("     {}: {} <-- conflict marker", i + 1, line);
215                } else {
216                    println!("     {}: {}", i + 1, line);
217                }
218            }
219
220            // Abort the merge
221            println!("\n5. Aborting merge...");
222            repo.abort_merge()?;
223            println!("   ✓ Merge aborted successfully");
224
225            // Verify merge is no longer in progress
226            if !repo.merge_in_progress()? {
227                println!("   ✓ Repository is back to clean state");
228            }
229        }
230        _ => println!("   Unexpected merge result: {:?}", merge_status),
231    }
232
233    Ok(())
234}
Source§

impl Repository

Source

pub fn add_remote(&self, name: &str, url: &str) -> Result<()>

Add a new remote to the repository

§Arguments
  • name - The name for the remote (e.g., “origin”)
  • url - The URL for the remote repository
§Example
use rustic_git::Repository;
use std::{env, fs};

let test_path = env::temp_dir().join("remote_add_test");
if test_path.exists() {
    fs::remove_dir_all(&test_path).unwrap();
}

let repo = Repository::init(&test_path, false)?;
repo.add_remote("origin", "https://github.com/user/repo.git")?;

// Clean up
fs::remove_dir_all(&test_path).unwrap();
Examples found in repository?
examples/remote_operations.rs (line 57)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn remove_remote(&self, name: &str) -> Result<()>

Remove a remote from the repository

§Arguments
  • name - The name of the remote to remove
§Example
use rustic_git::Repository;
use std::{env, fs};

let test_path = env::temp_dir().join("remote_remove_test");
if test_path.exists() {
    fs::remove_dir_all(&test_path).unwrap();
}

let repo = Repository::init(&test_path, false)?;
repo.add_remote("origin", "https://github.com/user/repo.git")?;
repo.remove_remote("origin")?;

// Clean up
fs::remove_dir_all(&test_path).unwrap();
Examples found in repository?
examples/remote_operations.rs (line 193)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn rename_remote(&self, old_name: &str, new_name: &str) -> Result<()>

Rename a remote

§Arguments
  • old_name - The current name of the remote
  • new_name - The new name for the remote
§Example
use rustic_git::Repository;
use std::{env, fs};

let test_path = env::temp_dir().join("remote_rename_test");
if test_path.exists() {
    fs::remove_dir_all(&test_path).unwrap();
}

let repo = Repository::init(&test_path, false)?;
repo.add_remote("origin", "https://github.com/user/repo.git")?;
repo.rename_remote("origin", "upstream")?;

// Clean up
fs::remove_dir_all(&test_path).unwrap();
Examples found in repository?
examples/remote_operations.rs (line 91)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn get_remote_url(&self, name: &str) -> Result<String>

Get the URL for a specific remote

§Arguments
  • name - The name of the remote
§Returns

The fetch URL for the remote

§Example
use rustic_git::Repository;
use std::{env, fs};

let test_path = env::temp_dir().join("remote_url_test");
if test_path.exists() {
    fs::remove_dir_all(&test_path).unwrap();
}

let repo = Repository::init(&test_path, false)?;
let url = "https://github.com/user/repo.git";
repo.add_remote("origin", url)?;
let fetched_url = repo.get_remote_url("origin")?;
assert_eq!(fetched_url, url);

// Clean up
fs::remove_dir_all(&test_path).unwrap();
Examples found in repository?
examples/remote_operations.rs (line 82)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn list_remotes(&self) -> Result<RemoteList>

List all remotes in the repository

§Returns

A RemoteList containing all remotes with their URLs

§Example
use rustic_git::Repository;
use std::{env, fs};

let test_path = env::temp_dir().join("remote_list_test");
if test_path.exists() {
    fs::remove_dir_all(&test_path).unwrap();
}

let repo = Repository::init(&test_path, false)?;
repo.add_remote("origin", "https://github.com/user/repo.git")?;
repo.add_remote("upstream", "https://github.com/original/repo.git")?;

let remotes = repo.list_remotes()?;
assert_eq!(remotes.len(), 2);
assert!(remotes.find("origin").is_some());
assert!(remotes.find("upstream").is_some());

// Clean up
fs::remove_dir_all(&test_path).unwrap();
Examples found in repository?
examples/remote_operations.rs (line 48)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn fetch(&self, remote: &str) -> Result<()>

Fetch changes from a remote repository

§Arguments
  • remote - The name of the remote to fetch from
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
repo.fetch("origin")?;
Examples found in repository?
examples/remote_operations.rs (line 116)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn fetch_with_options( &self, remote: &str, options: FetchOptions, ) -> Result<()>

Fetch changes from a remote repository with custom options

§Arguments
  • remote - The name of the remote to fetch from
  • options - Fetch options to customize the operation
§Example
use rustic_git::{Repository, FetchOptions};

let repo = Repository::open(".")?;
let options = FetchOptions::new().with_prune().with_tags();
repo.fetch_with_options("origin", options)?;
Examples found in repository?
examples/remote_operations.rs (line 123)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn push(&self, remote: &str, branch: &str) -> Result<()>

Push changes to a remote repository

§Arguments
  • remote - The name of the remote to push to
  • branch - The name of the branch to push
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
repo.push("origin", "main")?;
Examples found in repository?
examples/remote_operations.rs (line 139)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn push_with_options( &self, remote: &str, branch: &str, options: PushOptions, ) -> Result<()>

Push changes to a remote repository with custom options

§Arguments
  • remote - The name of the remote to push to
  • branch - The name of the branch to push
  • options - Push options to customize the operation
§Example
use rustic_git::{Repository, PushOptions};

let repo = Repository::open(".")?;
let options = PushOptions::new().with_set_upstream();
repo.push_with_options("origin", "main", options)?;
Examples found in repository?
examples/remote_operations.rs (line 146)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source

pub fn clone<P: AsRef<Path>>(url: &str, path: P) -> Result<Repository>

Clone a remote repository to a local path

§Arguments
  • url - The URL of the remote repository to clone
  • path - The local path where the repository should be cloned
§Returns

A Repository instance pointing to the cloned repository

§Example
use rustic_git::Repository;

let repo = Repository::clone("https://github.com/user/repo.git", "./local-repo")?;
Examples found in repository?
examples/remote_operations.rs (line 167)
15fn main() -> Result<()> {
16    println!("Rustic Git - Remote Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_remote_example");
19    let repo_path = base_path.join("main_repo");
20    let clone_path = base_path.join("cloned_repo");
21
22    // Clean up any previous runs
23    if base_path.exists() {
24        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
25    }
26    fs::create_dir_all(&base_path)?;
27
28    println!("=== Repository Setup ===\n");
29
30    // Initialize repository
31    println!("Initializing repository for remote demonstrations...");
32    let repo = Repository::init(&repo_path, false)?;
33    println!("Repository initialized at: {}", repo_path.display());
34
35    // Create initial commit so we have something to work with
36    fs::write(
37        repo_path.join("README.md"),
38        "# Remote Operations Demo\n\nDemonstrating rustic-git remote management capabilities.",
39    )?;
40    repo.add(&["README.md"])?;
41    repo.commit("Initial commit for remote operations demo")?;
42    println!("Created initial commit\n");
43
44    println!("=== Basic Remote Management ===\n");
45
46    // Check initial remote state
47    println!("Checking initial remote state:");
48    let remotes = repo.list_remotes()?;
49    println!("   Initial remotes count: {}", remotes.len());
50    if remotes.is_empty() {
51        println!("   No remotes configured (as expected)");
52    }
53    println!();
54
55    // Add remotes
56    println!("Adding remotes...");
57    repo.add_remote("origin", "https://github.com/user/demo-repo.git")?;
58    println!("   Added 'origin' remote");
59
60    repo.add_remote("upstream", "https://github.com/original/demo-repo.git")?;
61    println!("   Added 'upstream' remote");
62
63    repo.add_remote("fork", "git@github.com:user/fork-repo.git")?;
64    println!("   Added 'fork' remote (SSH URL)");
65    println!();
66
67    // List remotes
68    println!("Listing all remotes:");
69    let remotes = repo.list_remotes()?;
70    println!("   Total remotes: {}", remotes.len());
71
72    for remote in remotes.iter() {
73        println!("   {} -> {}", remote.name, remote.fetch_url);
74        if let Some(push_url) = &remote.push_url {
75            println!("     Push URL: {}", push_url);
76        }
77    }
78    println!();
79
80    // Get specific remote URLs
81    println!("Getting specific remote URLs:");
82    let origin_url = repo.get_remote_url("origin")?;
83    println!("   Origin URL: {}", origin_url);
84
85    let upstream_url = repo.get_remote_url("upstream")?;
86    println!("   Upstream URL: {}", upstream_url);
87    println!();
88
89    // Rename a remote
90    println!("Renaming 'fork' remote to 'my-fork'...");
91    repo.rename_remote("fork", "my-fork")?;
92    println!("   Remote renamed successfully");
93
94    // Verify rename
95    let remotes = repo.list_remotes()?;
96    let renamed_remote = remotes.find("my-fork");
97    match renamed_remote {
98        Some(remote) => println!(
99            "   Found renamed remote: {} -> {}",
100            remote.name, remote.fetch_url
101        ),
102        None => println!("   Error: Could not find renamed remote"),
103    }
104
105    // Verify old name is gone
106    if remotes.find("fork").is_none() {
107        println!("   Confirmed: old 'fork' remote no longer exists");
108    }
109    println!();
110
111    println!("=== Remote Operations with Options ===\n");
112
113    // Demonstrate fetch options
114    println!("Fetch operations (simulated - no actual network calls):");
115    println!("   Basic fetch from origin:");
116    match repo.fetch("origin") {
117        Ok(_) => println!("     ✓ Fetch completed successfully"),
118        Err(e) => println!("     ⚠ Fetch failed (expected): {}", e),
119    }
120
121    println!("   Fetch with options (prune + tags):");
122    let fetch_options = FetchOptions::new().with_prune().with_tags();
123    match repo.fetch_with_options("origin", fetch_options) {
124        Ok(_) => println!("     ✓ Fetch with options completed successfully"),
125        Err(e) => println!("     ⚠ Fetch with options failed (expected): {}", e),
126    }
127
128    println!("   Fetch all remotes:");
129    let fetch_all_options = FetchOptions::new().with_all_remotes();
130    match repo.fetch_with_options("", fetch_all_options) {
131        Ok(_) => println!("     ✓ Fetch all completed successfully"),
132        Err(e) => println!("     ⚠ Fetch all failed (expected): {}", e),
133    }
134    println!();
135
136    // Demonstrate push options
137    println!("Push operations (simulated - no actual network calls):");
138    println!("   Basic push to origin:");
139    match repo.push("origin", "main") {
140        Ok(_) => println!("     ✓ Push completed successfully"),
141        Err(e) => println!("     ⚠ Push failed (expected): {}", e),
142    }
143
144    println!("   Push with upstream tracking:");
145    let push_options = PushOptions::new().with_set_upstream();
146    match repo.push_with_options("origin", "main", push_options) {
147        Ok(_) => println!("     ✓ Push with upstream completed successfully"),
148        Err(e) => println!("     ⚠ Push with upstream failed (expected): {}", e),
149    }
150
151    println!("   Force push with tags:");
152    let force_push_options = PushOptions::new().with_force().with_tags();
153    match repo.push_with_options("my-fork", "feature-branch", force_push_options) {
154        Ok(_) => println!("     ✓ Force push with tags completed successfully"),
155        Err(e) => println!("     ⚠ Force push with tags failed (expected): {}", e),
156    }
157    println!();
158
159    println!("=== Clone Operations ===\n");
160
161    // Note: We can't actually clone from the URLs we added since they're fake,
162    // but we can demonstrate the API and show how it would work
163    println!("Clone operation demonstration:");
164    println!("   Attempting to clone a repository...");
165
166    // This will fail since the URL doesn't exist, but demonstrates the API
167    match Repository::clone("https://github.com/nonexistent/fake-repo.git", &clone_path) {
168        Ok(_repo) => {
169            println!("     ✓ Clone completed successfully");
170            println!("     Cloned repository location: {}", clone_path.display());
171        }
172        Err(e) => {
173            println!("     ⚠ Clone failed (expected for demo): {}", e);
174            println!("     In real usage, provide a valid repository URL");
175        }
176    }
177    println!();
178
179    println!("=== Error Handling and Edge Cases ===\n");
180
181    // Test error cases
182    println!("Testing error conditions:");
183
184    // Try to get URL for non-existent remote
185    println!("   Getting URL for non-existent remote:");
186    match repo.get_remote_url("nonexistent") {
187        Ok(url) => println!("     Unexpected success: {}", url),
188        Err(e) => println!("     ✓ Expected error: {}", e),
189    }
190
191    // Try to remove non-existent remote
192    println!("   Removing non-existent remote:");
193    match repo.remove_remote("nonexistent") {
194        Ok(_) => println!("     Unexpected success"),
195        Err(e) => println!("     ✓ Expected error: {}", e),
196    }
197
198    // Try to add duplicate remote
199    println!("   Adding duplicate remote:");
200    match repo.add_remote("origin", "https://github.com/duplicate/repo.git") {
201        Ok(_) => println!("     Unexpected success (git allows URL changes)"),
202        Err(e) => println!("     Error: {}", e),
203    }
204    println!();
205
206    println!("=== Remote Cleanup Operations ===\n");
207
208    // Remove remotes one by one
209    println!("Removing remotes:");
210
211    println!("   Removing 'upstream' remote...");
212    repo.remove_remote("upstream")?;
213
214    println!("   Removing 'my-fork' remote...");
215    repo.remove_remote("my-fork")?;
216
217    println!("   Removing 'origin' remote...");
218    repo.remove_remote("origin")?;
219
220    // Verify all remotes are gone
221    let final_remotes = repo.list_remotes()?;
222    println!("   Final remote count: {}", final_remotes.len());
223
224    if final_remotes.is_empty() {
225        println!("   ✓ All remotes successfully removed");
226    } else {
227        println!("   ⚠ Some remotes still remain:");
228        for remote in final_remotes.iter() {
229            println!("     - {}", remote.name);
230        }
231    }
232    println!();
233
234    println!("=== Advanced Remote Information ===\n");
235
236    // Re-add a remote for advanced operations demo
237    repo.add_remote("demo", "https://github.com/demo/advanced-repo.git")?;
238
239    // Show comprehensive remote information
240    let remotes = repo.list_remotes()?;
241    for remote in remotes.iter() {
242        println!("Remote Details:");
243        println!("   Name: {}", remote.name);
244        println!("   Fetch URL: {}", remote.fetch_url);
245        println!("   Push URL: {}", remote.push_url());
246        println!("   Uses separate push URL: {}", remote.push_url.is_some());
247
248        // Validate URL format
249        if remote.fetch_url.starts_with("https://") {
250            println!("   Protocol: HTTPS");
251        } else if remote.fetch_url.starts_with("git@") {
252            println!("   Protocol: SSH");
253        } else if remote.fetch_url.starts_with("git://") {
254            println!("   Protocol: Git");
255        } else {
256            println!("   Protocol: Other/Local");
257        }
258    }
259    println!();
260
261    println!("=== Summary ===\n");
262
263    println!("Remote operations demonstration completed!");
264    println!("  Repository: {}", repo_path.display());
265
266    let final_remotes = repo.list_remotes()?;
267    println!("  Final remotes configured: {}", final_remotes.len());
268
269    for remote in final_remotes.iter() {
270        println!("    - {} ({})", remote.name, remote.fetch_url);
271    }
272
273    println!("\nOperations demonstrated:");
274    println!("  ✓ Adding remotes with different URL formats");
275    println!("  ✓ Listing and inspecting remotes");
276    println!("  ✓ Getting specific remote URLs");
277    println!("  ✓ Renaming remotes");
278    println!("  ✓ Removing remotes");
279    println!("  ✓ Fetch operations with options");
280    println!("  ✓ Push operations with options");
281    println!("  ✓ Clone API demonstration");
282    println!("  ✓ Error handling for invalid operations");
283    println!("  ✓ Remote information analysis");
284
285    // Clean up
286    println!("\nCleaning up example repositories...");
287    fs::remove_dir_all(&base_path)?;
288    println!("Remote operations example completed!");
289
290    Ok(())
291}
Source§

impl Repository

Source

pub fn reset_soft(&self, commit: &str) -> Result<()>

Perform a soft reset to the specified commit.

Moves HEAD to the specified commit but keeps both the index and working directory unchanged. Previously staged changes remain staged.

§Arguments
  • commit - The commit hash, reference, or “HEAD~N” to reset to
§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/reset_operations.rs (line 75)
43fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44    println!("--- Demonstrating Reset Modes ---\n");
45
46    // Create initial commits
47    println!("1. Creating initial commits...");
48
49    // First commit
50    let file1_path = temp_dir.join("file1.txt");
51    fs::write(&file1_path, "Initial content")?;
52    repo.add(&["file1.txt"])?;
53    let first_commit = repo.commit("Initial commit")?;
54    println!("   Created first commit: {}", first_commit);
55
56    // Second commit
57    let file2_path = temp_dir.join("file2.txt");
58    fs::write(&file2_path, "Second file content")?;
59    repo.add(&["file2.txt"])?;
60    let second_commit = repo.commit("Add file2.txt")?;
61    println!("   Created second commit: {}", second_commit);
62
63    // Third commit
64    fs::write(&file1_path, "Modified content")?;
65    repo.add(&["file1.txt"])?;
66    let third_commit = repo.commit("Modify file1.txt")?;
67    println!("   Created third commit: {}", third_commit);
68
69    // Show current status
70    println!("\n2. Current repository state:");
71    show_repo_state(repo)?;
72
73    // Demonstrate soft reset
74    println!("\n3. Performing soft reset to second commit...");
75    repo.reset_soft(&second_commit.to_string())?;
76
77    println!("   After soft reset:");
78    show_repo_state(repo)?;
79    println!("   Note: Changes are still staged, working directory unchanged");
80
81    // Reset back to third commit for next demonstration
82    repo.reset_hard(&third_commit.to_string())?;
83
84    // Demonstrate mixed reset (default)
85    println!("\n4. Performing mixed reset to second commit...");
86    repo.reset_mixed(&second_commit.to_string())?;
87
88    println!("   After mixed reset:");
89    show_repo_state(repo)?;
90    println!("   Note: Changes are unstaged but preserved in working directory");
91
92    // Reset back to third commit for next demonstration
93    repo.reset_hard(&third_commit.to_string())?;
94
95    // Demonstrate hard reset
96    println!("\n5. Performing hard reset to first commit...");
97    repo.reset_hard(&first_commit.to_string())?;
98
99    println!("   After hard reset:");
100    show_repo_state(repo)?;
101    println!("   Note: All changes discarded, working directory matches commit");
102
103    // Demonstrate reset_with_mode for flexibility
104    println!("\n6. Using reset_with_mode for explicit control...");
105
106    // Recreate second commit for demo
107    fs::write(&file2_path, "Recreated second file")?;
108    repo.add(&["file2.txt"])?;
109    let _new_commit = repo.commit("Recreate file2.txt")?;
110
111    repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112    println!("   Used ResetMode::Mixed explicitly");
113    show_repo_state(repo)?;
114
115    Ok(())
116}
117
118fn demonstrate_file_resets(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
119    println!("\n--- Demonstrating File-Specific Resets ---\n");
120
121    // Create some files and stage them
122    println!("1. Creating and staging multiple files...");
123
124    let file_a = temp_dir.join("fileA.txt");
125    let file_b = temp_dir.join("fileB.txt");
126
127    fs::write(&file_a, "Content A")?;
128    fs::write(&file_b, "Content B")?;
129
130    repo.add(&["fileA.txt", "fileB.txt"])?;
131    println!("   Staged fileA.txt and fileB.txt");
132
133    show_repo_state(repo)?;
134
135    // Reset a single file (using existing reset_file from files.rs)
136    println!("\n2. Resetting single file (fileA.txt)...");
137    repo.reset_file("fileA.txt")?;
138
139    println!("   After resetting fileA.txt:");
140    show_repo_state(repo)?;
141    println!("   Note: fileA.txt is unstaged, fileB.txt remains staged");
142
143    // Demonstrate HEAD reset (unstage all changes)
144    println!("\n3. Performing mixed reset to HEAD (unstage all)...");
145    repo.reset_mixed("HEAD")?;
146
147    println!("   After reset HEAD:");
148    show_repo_state(repo)?;
149    println!("   Note: All staged changes are now unstaged");
150
151    Ok(())
152}
153
154fn demonstrate_error_handling(repo: &Repository) -> Result<()> {
155    println!("\n--- Demonstrating Error Handling ---\n");
156
157    // Try to reset to invalid commit
158    println!("1. Attempting reset to invalid commit hash...");
159    match repo.reset_mixed("invalid_commit_hash") {
160        Ok(_) => println!("   Unexpected success!"),
161        Err(e) => println!("   Expected error: {}", e),
162    }
163
164    // Try to reset to non-existent reference
165    println!("\n2. Attempting reset to non-existent reference...");
166    match repo.reset_soft("nonexistent-branch") {
167        Ok(_) => println!("   Unexpected success!"),
168        Err(e) => println!("   Expected error: {}", e),
169    }
170
171    println!("\n   Error handling works correctly!");
172    Ok(())
173}
Source

pub fn reset_mixed(&self, commit: &str) -> Result<()>

Perform a mixed reset to the specified commit (default reset behavior).

Moves HEAD to the specified commit and resets the index to match, but leaves the working directory unchanged. Previously staged changes become unstaged but remain in the working directory.

§Arguments
  • commit - The commit hash, reference, or “HEAD~N” to reset to
§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/reset_operations.rs (line 86)
43fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44    println!("--- Demonstrating Reset Modes ---\n");
45
46    // Create initial commits
47    println!("1. Creating initial commits...");
48
49    // First commit
50    let file1_path = temp_dir.join("file1.txt");
51    fs::write(&file1_path, "Initial content")?;
52    repo.add(&["file1.txt"])?;
53    let first_commit = repo.commit("Initial commit")?;
54    println!("   Created first commit: {}", first_commit);
55
56    // Second commit
57    let file2_path = temp_dir.join("file2.txt");
58    fs::write(&file2_path, "Second file content")?;
59    repo.add(&["file2.txt"])?;
60    let second_commit = repo.commit("Add file2.txt")?;
61    println!("   Created second commit: {}", second_commit);
62
63    // Third commit
64    fs::write(&file1_path, "Modified content")?;
65    repo.add(&["file1.txt"])?;
66    let third_commit = repo.commit("Modify file1.txt")?;
67    println!("   Created third commit: {}", third_commit);
68
69    // Show current status
70    println!("\n2. Current repository state:");
71    show_repo_state(repo)?;
72
73    // Demonstrate soft reset
74    println!("\n3. Performing soft reset to second commit...");
75    repo.reset_soft(&second_commit.to_string())?;
76
77    println!("   After soft reset:");
78    show_repo_state(repo)?;
79    println!("   Note: Changes are still staged, working directory unchanged");
80
81    // Reset back to third commit for next demonstration
82    repo.reset_hard(&third_commit.to_string())?;
83
84    // Demonstrate mixed reset (default)
85    println!("\n4. Performing mixed reset to second commit...");
86    repo.reset_mixed(&second_commit.to_string())?;
87
88    println!("   After mixed reset:");
89    show_repo_state(repo)?;
90    println!("   Note: Changes are unstaged but preserved in working directory");
91
92    // Reset back to third commit for next demonstration
93    repo.reset_hard(&third_commit.to_string())?;
94
95    // Demonstrate hard reset
96    println!("\n5. Performing hard reset to first commit...");
97    repo.reset_hard(&first_commit.to_string())?;
98
99    println!("   After hard reset:");
100    show_repo_state(repo)?;
101    println!("   Note: All changes discarded, working directory matches commit");
102
103    // Demonstrate reset_with_mode for flexibility
104    println!("\n6. Using reset_with_mode for explicit control...");
105
106    // Recreate second commit for demo
107    fs::write(&file2_path, "Recreated second file")?;
108    repo.add(&["file2.txt"])?;
109    let _new_commit = repo.commit("Recreate file2.txt")?;
110
111    repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112    println!("   Used ResetMode::Mixed explicitly");
113    show_repo_state(repo)?;
114
115    Ok(())
116}
117
118fn demonstrate_file_resets(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
119    println!("\n--- Demonstrating File-Specific Resets ---\n");
120
121    // Create some files and stage them
122    println!("1. Creating and staging multiple files...");
123
124    let file_a = temp_dir.join("fileA.txt");
125    let file_b = temp_dir.join("fileB.txt");
126
127    fs::write(&file_a, "Content A")?;
128    fs::write(&file_b, "Content B")?;
129
130    repo.add(&["fileA.txt", "fileB.txt"])?;
131    println!("   Staged fileA.txt and fileB.txt");
132
133    show_repo_state(repo)?;
134
135    // Reset a single file (using existing reset_file from files.rs)
136    println!("\n2. Resetting single file (fileA.txt)...");
137    repo.reset_file("fileA.txt")?;
138
139    println!("   After resetting fileA.txt:");
140    show_repo_state(repo)?;
141    println!("   Note: fileA.txt is unstaged, fileB.txt remains staged");
142
143    // Demonstrate HEAD reset (unstage all changes)
144    println!("\n3. Performing mixed reset to HEAD (unstage all)...");
145    repo.reset_mixed("HEAD")?;
146
147    println!("   After reset HEAD:");
148    show_repo_state(repo)?;
149    println!("   Note: All staged changes are now unstaged");
150
151    Ok(())
152}
153
154fn demonstrate_error_handling(repo: &Repository) -> Result<()> {
155    println!("\n--- Demonstrating Error Handling ---\n");
156
157    // Try to reset to invalid commit
158    println!("1. Attempting reset to invalid commit hash...");
159    match repo.reset_mixed("invalid_commit_hash") {
160        Ok(_) => println!("   Unexpected success!"),
161        Err(e) => println!("   Expected error: {}", e),
162    }
163
164    // Try to reset to non-existent reference
165    println!("\n2. Attempting reset to non-existent reference...");
166    match repo.reset_soft("nonexistent-branch") {
167        Ok(_) => println!("   Unexpected success!"),
168        Err(e) => println!("   Expected error: {}", e),
169    }
170
171    println!("\n   Error handling works correctly!");
172    Ok(())
173}
Source

pub fn reset_hard(&self, commit: &str) -> Result<()>

Perform a hard reset to the specified commit.

Moves HEAD to the specified commit and resets both the index and working directory to match. WARNING: This discards all uncommitted changes permanently.

§Arguments
  • commit - The commit hash, reference, or “HEAD~N” to reset to
§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/reset_operations.rs (line 82)
43fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44    println!("--- Demonstrating Reset Modes ---\n");
45
46    // Create initial commits
47    println!("1. Creating initial commits...");
48
49    // First commit
50    let file1_path = temp_dir.join("file1.txt");
51    fs::write(&file1_path, "Initial content")?;
52    repo.add(&["file1.txt"])?;
53    let first_commit = repo.commit("Initial commit")?;
54    println!("   Created first commit: {}", first_commit);
55
56    // Second commit
57    let file2_path = temp_dir.join("file2.txt");
58    fs::write(&file2_path, "Second file content")?;
59    repo.add(&["file2.txt"])?;
60    let second_commit = repo.commit("Add file2.txt")?;
61    println!("   Created second commit: {}", second_commit);
62
63    // Third commit
64    fs::write(&file1_path, "Modified content")?;
65    repo.add(&["file1.txt"])?;
66    let third_commit = repo.commit("Modify file1.txt")?;
67    println!("   Created third commit: {}", third_commit);
68
69    // Show current status
70    println!("\n2. Current repository state:");
71    show_repo_state(repo)?;
72
73    // Demonstrate soft reset
74    println!("\n3. Performing soft reset to second commit...");
75    repo.reset_soft(&second_commit.to_string())?;
76
77    println!("   After soft reset:");
78    show_repo_state(repo)?;
79    println!("   Note: Changes are still staged, working directory unchanged");
80
81    // Reset back to third commit for next demonstration
82    repo.reset_hard(&third_commit.to_string())?;
83
84    // Demonstrate mixed reset (default)
85    println!("\n4. Performing mixed reset to second commit...");
86    repo.reset_mixed(&second_commit.to_string())?;
87
88    println!("   After mixed reset:");
89    show_repo_state(repo)?;
90    println!("   Note: Changes are unstaged but preserved in working directory");
91
92    // Reset back to third commit for next demonstration
93    repo.reset_hard(&third_commit.to_string())?;
94
95    // Demonstrate hard reset
96    println!("\n5. Performing hard reset to first commit...");
97    repo.reset_hard(&first_commit.to_string())?;
98
99    println!("   After hard reset:");
100    show_repo_state(repo)?;
101    println!("   Note: All changes discarded, working directory matches commit");
102
103    // Demonstrate reset_with_mode for flexibility
104    println!("\n6. Using reset_with_mode for explicit control...");
105
106    // Recreate second commit for demo
107    fs::write(&file2_path, "Recreated second file")?;
108    repo.add(&["file2.txt"])?;
109    let _new_commit = repo.commit("Recreate file2.txt")?;
110
111    repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112    println!("   Used ResetMode::Mixed explicitly");
113    show_repo_state(repo)?;
114
115    Ok(())
116}
Source

pub fn reset_with_mode(&self, commit: &str, mode: ResetMode) -> Result<()>

Perform a reset with the specified mode.

This is a flexible method that allows you to specify the reset mode explicitly.

§Arguments
  • commit - The commit hash, reference, or “HEAD~N” to reset to
  • mode - The reset mode (Soft, Mixed, or Hard)
§Returns

A Result indicating success or a GitError if the operation fails.

Examples found in repository?
examples/reset_operations.rs (line 111)
43fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44    println!("--- Demonstrating Reset Modes ---\n");
45
46    // Create initial commits
47    println!("1. Creating initial commits...");
48
49    // First commit
50    let file1_path = temp_dir.join("file1.txt");
51    fs::write(&file1_path, "Initial content")?;
52    repo.add(&["file1.txt"])?;
53    let first_commit = repo.commit("Initial commit")?;
54    println!("   Created first commit: {}", first_commit);
55
56    // Second commit
57    let file2_path = temp_dir.join("file2.txt");
58    fs::write(&file2_path, "Second file content")?;
59    repo.add(&["file2.txt"])?;
60    let second_commit = repo.commit("Add file2.txt")?;
61    println!("   Created second commit: {}", second_commit);
62
63    // Third commit
64    fs::write(&file1_path, "Modified content")?;
65    repo.add(&["file1.txt"])?;
66    let third_commit = repo.commit("Modify file1.txt")?;
67    println!("   Created third commit: {}", third_commit);
68
69    // Show current status
70    println!("\n2. Current repository state:");
71    show_repo_state(repo)?;
72
73    // Demonstrate soft reset
74    println!("\n3. Performing soft reset to second commit...");
75    repo.reset_soft(&second_commit.to_string())?;
76
77    println!("   After soft reset:");
78    show_repo_state(repo)?;
79    println!("   Note: Changes are still staged, working directory unchanged");
80
81    // Reset back to third commit for next demonstration
82    repo.reset_hard(&third_commit.to_string())?;
83
84    // Demonstrate mixed reset (default)
85    println!("\n4. Performing mixed reset to second commit...");
86    repo.reset_mixed(&second_commit.to_string())?;
87
88    println!("   After mixed reset:");
89    show_repo_state(repo)?;
90    println!("   Note: Changes are unstaged but preserved in working directory");
91
92    // Reset back to third commit for next demonstration
93    repo.reset_hard(&third_commit.to_string())?;
94
95    // Demonstrate hard reset
96    println!("\n5. Performing hard reset to first commit...");
97    repo.reset_hard(&first_commit.to_string())?;
98
99    println!("   After hard reset:");
100    show_repo_state(repo)?;
101    println!("   Note: All changes discarded, working directory matches commit");
102
103    // Demonstrate reset_with_mode for flexibility
104    println!("\n6. Using reset_with_mode for explicit control...");
105
106    // Recreate second commit for demo
107    fs::write(&file2_path, "Recreated second file")?;
108    repo.add(&["file2.txt"])?;
109    let _new_commit = repo.commit("Recreate file2.txt")?;
110
111    repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112    println!("   Used ResetMode::Mixed explicitly");
113    show_repo_state(repo)?;
114
115    Ok(())
116}
Source§

impl Repository

Source

pub fn stash_list(&self) -> Result<StashList>

List all stashes in the repository

Returns a StashList containing all stashes sorted by recency (most recent first).

§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let stashes = repo.stash_list()?;

println!("Found {} stashes:", stashes.len());
for stash in stashes.iter() {
    println!("  {}: {}", stash.index, stash.message);
}
Examples found in repository?
examples/stash_operations.rs (line 152)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_save(&self, message: &str) -> Result<Stash>

Save current changes to a new stash with a message

This is equivalent to git stash push -m "message".

§Arguments
  • message - A descriptive message for the stash
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let stash = repo.stash_save("Work in progress on feature X")?;
println!("Created stash: {}", stash.message);
Examples found in repository?
examples/stash_operations.rs (line 88)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_push(&self, message: &str, options: StashOptions) -> Result<Stash>

Create a stash with advanced options

§Arguments
  • message - A descriptive message for the stash
  • options - Stash creation options
§Example
use rustic_git::{Repository, StashOptions};

let repo = Repository::open(".")?;

// Stash including untracked files
let options = StashOptions::new()
    .with_untracked()
    .with_keep_index();
let stash = repo.stash_push("WIP with untracked files", options)?;
Examples found in repository?
examples/stash_operations.rs (lines 123-126)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_apply( &self, index: usize, options: StashApplyOptions, ) -> Result<()>

Apply a stash without removing it from the stash list

§Arguments
  • index - The stash index to apply (0 is most recent)
  • options - Apply options
§Example
use rustic_git::{Repository, StashApplyOptions};

let repo = Repository::open(".")?;
let options = StashApplyOptions::new().with_index();
repo.stash_apply(0, options)?; // Apply most recent stash
Examples found in repository?
examples/stash_operations.rs (line 209)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_pop(&self, index: usize, options: StashApplyOptions) -> Result<()>

Apply a stash and remove it from the stash list

§Arguments
  • index - The stash index to pop (0 is most recent)
  • options - Apply options
§Example
use rustic_git::{Repository, StashApplyOptions};

let repo = Repository::open(".")?;
repo.stash_pop(0, StashApplyOptions::new())?; // Pop most recent stash
Examples found in repository?
examples/stash_operations.rs (line 240)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_show(&self, index: usize) -> Result<String>

Show the contents of a stash

§Arguments
  • index - The stash index to show (0 is most recent)
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let stash_info = repo.stash_show(0)?;
println!("Stash contents:\n{}", stash_info);
Examples found in repository?
examples/stash_operations.rs (line 198)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_drop(&self, index: usize) -> Result<()>

Delete a specific stash

§Arguments
  • index - The stash index to delete
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
repo.stash_drop(1)?; // Delete second most recent stash
Examples found in repository?
examples/stash_operations.rs (line 295)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source

pub fn stash_clear(&self) -> Result<()>

Clear all stashes

§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
repo.stash_clear()?; // Remove all stashes
Examples found in repository?
examples/stash_operations.rs (line 309)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}
Source§

impl Repository

Source

pub fn status(&self) -> Result<GitStatus>

Get the status of the repository.

§Returns

A Result containing the GitStatus or a GitError.

Examples found in repository?
examples/reset_operations.rs (line 176)
175fn show_repo_state(repo: &Repository) -> Result<()> {
176    let status = repo.status()?;
177
178    let staged_count = status.staged_files().count();
179    let unstaged_count = status.unstaged_files().count();
180    let untracked_count = status.untracked_entries().count();
181
182    println!("   Repository state:");
183    println!("     - Staged files: {}", staged_count);
184    println!("     - Modified files: {}", unstaged_count);
185    println!("     - Untracked files: {}", untracked_count);
186
187    if staged_count > 0 {
188        println!("     - Staged:");
189        for file in status.staged_files().take(5) {
190            println!("       * {}", file.path.display());
191        }
192    }
193
194    if unstaged_count > 0 {
195        println!("     - Modified:");
196        for file in status.unstaged_files().take(5) {
197            println!("       * {}", file.path.display());
198        }
199    }
200
201    if untracked_count > 0 {
202        println!("     - Untracked:");
203        for file in status.untracked_entries().take(5) {
204            println!("       * {}", file.path.display());
205        }
206    }
207
208    Ok(())
209}
More examples
Hide additional examples
examples/merge_operations.rs (line 278)
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237    println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239    // Create a simple feature branch
240    println!("1. Creating simple feature branch...");
241    repo.checkout_new("feature/simple", None)?;
242
243    let simple_path = temp_dir.join("simple.txt");
244    fs::write(&simple_path, "Simple feature content")?;
245    repo.add(&["simple.txt"])?;
246    repo.commit("Add simple feature")?;
247
248    // Switch back to master
249    let branches = repo.branches()?;
250    let master_branch = branches.find("master").unwrap();
251    repo.checkout(master_branch)?;
252
253    // Test merge with different options
254    println!("\n2. Testing merge with custom options...");
255    let options = MergeOptions::new()
256        .with_fast_forward(FastForwardMode::Auto)
257        .with_message("Integrate simple feature".to_string());
258
259    let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261    match merge_status {
262        MergeStatus::FastForward(hash) => {
263            println!("   ✓ Fast-forward merge completed: {}", hash);
264        }
265        MergeStatus::Success(hash) => {
266            println!("   ✓ Merge commit created: {}", hash);
267        }
268        MergeStatus::UpToDate => {
269            println!("   ✓ Already up to date");
270        }
271        MergeStatus::Conflicts(_) => {
272            println!("   ⚠️  Unexpected conflicts");
273        }
274    }
275
276    // Show final repository state
277    println!("\n3. Final repository state:");
278    let status = repo.status()?;
279    println!(
280        "   Working directory clean: {}",
281        status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282    );
283
284    let commits = repo.recent_commits(5)?;
285    println!("   Recent commits:");
286    for (i, commit) in commits.iter().enumerate() {
287        println!(
288            "     {}: {} - {}",
289            i + 1,
290            commit.hash.short(),
291            commit.message.subject
292        );
293    }
294
295    Ok(())
296}
examples/error_handling.rs (line 132)
100fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
101    println!("File Operation Error Scenarios:\n");
102
103    // Set up a valid repository first
104    let repo = Repository::init(repo_path, false)?;
105
106    // Create some test files
107    fs::write(repo_path.join("test.txt"), "Test content")?;
108    repo.add(&["test.txt"])?;
109    repo.commit("Initial commit")?;
110
111    // 1. Adding non-existent files
112    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    // 2. Mixed valid and invalid files
125    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            // Check what actually got staged
132            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            // Try recovery: add valid files individually
140            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
155/// Demonstrate Git command related errors
156fn 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    // 1. Empty commit (no staged changes)
162    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    // 2. Commit with problematic message
178    println!("\n2. Testing commit message edge cases:");
179
180    // Stage a file for testing
181    fs::write(
182        repo_path.join("commit_test.txt"),
183        "Content for commit testing",
184    )?;
185    repo.add(&["commit_test.txt"])?;
186
187    // Very long commit message
188    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
206/// Demonstrate error recovery patterns
207fn 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    // Pattern 1: Retry with different approach
213    println!("1. Retry Pattern - Graceful degradation:");
214
215    // Try to add specific files, fall back to add_all on failure
216    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    // Pattern 2: Partial success handling
241    println!("\n2. Partial Success Pattern:");
242
243    // Create some files with known issues
244    fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245    fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246    // Don't create bad1.txt - it will be missing
247
248    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    // Pattern 3: Status checking before operations
284    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        // Show what would be committed
295        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        // Safe commit since we know there are changes
305        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}
examples/basic_usage.rs (line 59)
16fn main() -> Result<()> {
17    println!("Rustic Git - Basic Usage Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing new repository at: {}", repo_path.display());
28
29    // Initialize a new repository
30    let repo = Repository::init(&repo_path, false)?;
31    println!("Repository initialized successfully\n");
32
33    // Create some example files
34    println!("Creating example files...");
35    fs::create_dir_all(repo_path.join("src"))?;
36
37    fs::write(
38        repo_path.join("README.md"),
39        "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40    )?;
41
42    fs::write(
43        repo_path.join("src/main.rs"),
44        r#"fn main() {
45    println!("Hello from rustic-git example!");
46}
47"#,
48    )?;
49
50    fs::write(
51        repo_path.join("src/lib.rs"),
52        "// Library code goes here\npub fn hello() -> &'static str {\n    \"Hello, World!\"\n}\n",
53    )?;
54
55    println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57    // Check repository status
58    println!("Checking repository status...");
59    let status = repo.status()?;
60
61    if status.is_clean() {
62        println!("   Repository is clean (no changes)");
63    } else {
64        println!("   Repository has changes:");
65        println!("   Unstaged files: {}", status.unstaged_files().count());
66        println!("   Untracked files: {}", status.untracked_entries().count());
67
68        // Show untracked files
69        for entry in status.untracked_entries() {
70            println!("      - {}", entry.path.display());
71        }
72    }
73    println!();
74
75    // Stage specific files first
76    println!("Staging files...");
77
78    // Stage README.md first
79    repo.add(&["README.md"])?;
80    println!("Staged README.md");
81
82    // Stage all remaining files
83    repo.add_all()?;
84    println!("Staged all remaining files");
85
86    // Check status after staging
87    let status_after_staging = repo.status()?;
88    println!("\nStatus after staging:");
89    if status_after_staging.is_clean() {
90        println!("   Repository is clean (all changes staged)");
91    } else {
92        println!(
93            "   Files staged for commit: {}",
94            status_after_staging.entries.len()
95        );
96        for entry in &status_after_staging.entries {
97            println!(
98                "      Index {:?}, Worktree {:?}: {}",
99                entry.index_status,
100                entry.worktree_status,
101                entry.path.display()
102            );
103        }
104    }
105    println!();
106
107    // Create a commit
108    println!("Creating commit...");
109    let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111    println!("Commit created successfully!");
112    println!("   Full hash: {}", hash);
113    println!("   Short hash: {}", hash.short());
114    println!();
115
116    // Verify final status
117    println!("Final repository status:");
118    let final_status = repo.status()?;
119    if final_status.is_clean() {
120        println!("   Repository is clean - all changes committed!");
121    } else {
122        println!("   Repository still has uncommitted changes");
123    }
124    println!();
125
126    // Clean up
127    println!("Cleaning up example repository...");
128    fs::remove_dir_all(&repo_path)?;
129    println!("Example completed successfully!");
130
131    Ok(())
132}
examples/repository_operations.rs (line 71)
15fn main() -> Result<()> {
16    println!("Rustic Git - Repository Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_repo_example");
19    let regular_repo_path = base_path.join("regular");
20    let bare_repo_path = base_path.join("bare");
21    let nonexistent_path = base_path.join("nonexistent");
22
23    // Clean up any previous runs
24    if base_path.exists() {
25        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
26    }
27    fs::create_dir_all(&base_path)?;
28
29    println!("=== Repository Initialization ===\n");
30
31    // 1. Initialize a regular repository
32    println!("Initializing regular repository...");
33    let regular_repo = Repository::init(&regular_repo_path, false)?;
34    println!(
35        "Regular repository created at: {}",
36        regular_repo_path.display()
37    );
38    println!("   Repository path: {:?}", regular_repo.repo_path());
39
40    // Verify it's a git repo by checking for .git directory
41    if regular_repo_path.join(".git").exists() {
42        println!("   .git directory found");
43    }
44    println!();
45
46    // 2. Initialize a bare repository
47    println!("Initializing bare repository...");
48    let bare_repo = Repository::init(&bare_repo_path, true)?;
49    println!("Bare repository created at: {}", bare_repo_path.display());
50    println!("   Repository path: {:?}", bare_repo.repo_path());
51
52    // Verify bare repo structure (has HEAD, objects, etc. directly)
53    if bare_repo_path.join("HEAD").exists() {
54        println!("   HEAD file found (bare repository structure)");
55    }
56    if bare_repo_path.join("objects").exists() {
57        println!("   objects directory found");
58    }
59    println!();
60
61    println!("=== Repository Opening ===\n");
62
63    // 3. Open the existing regular repository
64    println!("Opening existing regular repository...");
65    match Repository::open(&regular_repo_path) {
66        Ok(opened_repo) => {
67            println!("Successfully opened regular repository");
68            println!("   Repository path: {:?}", opened_repo.repo_path());
69
70            // Test that we can perform operations on the opened repo
71            let status = opened_repo.status()?;
72            println!("   Repository status: {} files", status.entries.len());
73        }
74        Err(e) => {
75            println!("Failed to open regular repository: {:?}", e);
76        }
77    }
78    println!();
79
80    // 4. Open the existing bare repository
81    println!("Opening existing bare repository...");
82    match Repository::open(&bare_repo_path) {
83        Ok(opened_bare) => {
84            println!("Successfully opened bare repository");
85            println!("   Repository path: {:?}", opened_bare.repo_path());
86
87            // Note: status operations might behave differently on bare repos
88            match opened_bare.status() {
89                Ok(status) => println!("   Bare repository status: {} files", status.entries.len()),
90                Err(e) => println!(
91                    "   Note: Status check on bare repo failed (expected): {:?}",
92                    e
93                ),
94            }
95        }
96        Err(e) => {
97            println!("Failed to open bare repository: {:?}", e);
98        }
99    }
100    println!();
101
102    println!("=== Error Handling ===\n");
103
104    // 5. Try to open a non-existent repository
105    println!("Attempting to open non-existent repository...");
106    match Repository::open(&nonexistent_path) {
107        Ok(_repo) => {
108            println!("Unexpectedly succeeded opening non-existent repo");
109        }
110        Err(GitError::CommandFailed(msg)) => {
111            println!("Expected error caught: CommandFailed");
112            println!("   Error message: {}", msg);
113        }
114        Err(GitError::IoError(msg)) => {
115            println!("Expected error caught: IoError");
116            println!("   Error message: {}", msg);
117        }
118    }
119    println!();
120
121    // 6. Try to open a regular file as a repository
122    let fake_repo_path = base_path.join("fake.txt");
123    fs::write(&fake_repo_path, "This is not a git repository")?;
124
125    println!("Attempting to open regular file as repository...");
126    match Repository::open(&fake_repo_path) {
127        Ok(_repo) => {
128            println!("Unexpectedly succeeded opening regular file as repo");
129        }
130        Err(GitError::CommandFailed(msg)) => {
131            println!("Expected error caught: CommandFailed");
132            println!("   Error message: {}", msg);
133        }
134        Err(GitError::IoError(msg)) => {
135            println!("Expected error caught: IoError");
136            println!("   Error message: {}", msg);
137        }
138    }
139    println!();
140
141    println!("=== Repository Information ===\n");
142
143    // 7. Compare regular vs bare repository information
144    println!("Comparing repository types:");
145
146    let regular_path = regular_repo.repo_path();
147    let bare_path = bare_repo.repo_path();
148
149    println!("   Regular repo path: {:?}", regular_path);
150    println!("   Bare repo path: {:?}", bare_path);
151
152    // Show directory contents
153    if let Ok(entries) = fs::read_dir(regular_path) {
154        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
155        files.sort_by_key(|e| e.file_name());
156
157        println!("   Regular repo contents:");
158        for entry in files {
159            if let Some(name) = entry.file_name().to_str() {
160                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
161                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
162                println!("     {} {}", marker, name);
163            }
164        }
165    }
166
167    if let Ok(entries) = fs::read_dir(bare_path) {
168        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
169        files.sort_by_key(|e| e.file_name());
170
171        println!("   Bare repo contents:");
172        for entry in files {
173            if let Some(name) = entry.file_name().to_str() {
174                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
175                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
176                println!("     {} {}", marker, name);
177            }
178        }
179    }
180    println!();
181
182    // Clean up
183    println!("Cleaning up example repositories...");
184    fs::remove_dir_all(&base_path)?;
185    println!("Repository operations example completed!");
186
187    Ok(())
188}
examples/status_checking.rs (line 32)
15fn main() -> Result<()> {
16    println!("Rustic Git - Status Checking Example\n");
17
18    let repo_path = env::temp_dir().join("rustic_git_status_example");
19
20    // Clean up any previous run
21    if repo_path.exists() {
22        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
23    }
24
25    // Initialize repository
26    println!("Setting up repository for status demonstration...");
27    let repo = Repository::init(&repo_path, false)?;
28
29    println!("=== Clean Repository Status ===\n");
30
31    // Check initial status (should be clean)
32    let status = repo.status()?;
33    println!("Initial repository status:");
34    display_status_summary(&status);
35    println!();
36
37    println!("=== Creating Files with Different States ===\n");
38
39    // Create various types of files to demonstrate different statuses
40    println!("Creating test files...");
41
42    // Create some files that will be untracked
43    fs::write(repo_path.join("untracked1.txt"), "This file is untracked")?;
44    fs::write(repo_path.join("untracked2.txt"), "Another untracked file")?;
45
46    // Create a .gitignore to demonstrate ignored files
47    fs::write(repo_path.join(".gitignore"), "*.log\n*.tmp\n/temp/\n")?;
48
49    // Create files that will be ignored
50    fs::write(repo_path.join("debug.log"), "Log file content")?;
51    fs::write(repo_path.join("cache.tmp"), "Temporary file")?;
52    fs::create_dir_all(repo_path.join("temp"))?;
53    fs::write(repo_path.join("temp/data.txt"), "Temp data")?;
54
55    println!("Created test files");
56
57    // Check status after creating untracked files
58    println!("\nStatus after creating untracked files:");
59    let status_untracked = repo.status()?;
60    display_status_summary(&status_untracked);
61    display_detailed_status(&status_untracked);
62    println!();
63
64    println!("=== Staging Files to Show 'Added' Status ===\n");
65
66    // Stage some files to show "Added" status
67    repo.add(&["untracked1.txt", ".gitignore"])?;
68    println!("Staged untracked1.txt and .gitignore");
69
70    let status_added = repo.status()?;
71    println!("\nStatus after staging files:");
72    display_status_summary(&status_added);
73    display_detailed_status(&status_added);
74    println!();
75
76    println!("=== Creating Initial Commit ===\n");
77
78    // Commit the staged files so we can demonstrate modified/deleted states
79    let _hash = repo.commit("Initial commit with basic files")?;
80    println!("Created initial commit");
81
82    let status_after_commit = repo.status()?;
83    println!("\nStatus after commit:");
84    display_status_summary(&status_after_commit);
85    if !status_after_commit.is_clean() {
86        display_detailed_status(&status_after_commit);
87    }
88    println!();
89
90    println!("=== Modifying Files to Show 'Modified' Status ===\n");
91
92    // Modify existing tracked files
93    fs::write(
94        repo_path.join("untracked1.txt"),
95        "This file has been MODIFIED!",
96    )?;
97    fs::write(
98        repo_path.join(".gitignore"),
99        "*.log\n*.tmp\n/temp/\n# Added comment\n",
100    )?;
101    println!("Modified untracked1.txt and .gitignore");
102
103    let status_modified = repo.status()?;
104    println!("\nStatus after modifying files:");
105    display_status_summary(&status_modified);
106    display_detailed_status(&status_modified);
107    println!();
108
109    println!("=== Demonstrating All Status Query Methods ===\n");
110
111    // Stage one of the modified files to show mixed states
112    repo.add(&["untracked1.txt"])?;
113    println!("Staged untracked1.txt (now shows as Added)");
114
115    let status_mixed = repo.status()?;
116    println!("\nMixed status demonstration:");
117    display_status_summary(&status_mixed);
118
119    // Demonstrate different query methods
120    println!("\nUsing different status query methods:");
121
122    println!("   All files ({} total):", status_mixed.entries.len());
123    for entry in &status_mixed.entries {
124        println!(
125            "      Index {:?}, Worktree {:?}: {}",
126            entry.index_status,
127            entry.worktree_status,
128            entry.path.display()
129        );
130    }
131
132    // Query by specific status
133    let unstaged_files: Vec<_> = status_mixed.unstaged_files().collect();
134    if !unstaged_files.is_empty() {
135        println!("\n   Unstaged files ({}):", unstaged_files.len());
136        for entry in &unstaged_files {
137            println!("      - {}", entry.path.display());
138        }
139    }
140
141    let untracked_files: Vec<_> = status_mixed.untracked_entries().collect();
142    if !untracked_files.is_empty() {
143        println!("\n   Untracked files ({}):", untracked_files.len());
144        for entry in &untracked_files {
145            println!("      - {}", entry.path.display());
146        }
147    }
148
149    // Query by IndexStatus enum
150    let added_files: Vec<_> = status_mixed
151        .files_with_index_status(IndexStatus::Added)
152        .collect();
153    if !added_files.is_empty() {
154        println!("\n   Added files ({}):", added_files.len());
155        for entry in &added_files {
156            println!("      - {}", entry.path.display());
157        }
158    }
159
160    println!();
161
162    println!("=== File Status Filtering Examples ===\n");
163
164    // Demonstrate filtering capabilities
165    println!("Filtering examples:");
166
167    // Count files by status
168    let mut index_status_counts = std::collections::HashMap::new();
169    let mut worktree_status_counts = std::collections::HashMap::new();
170
171    for entry in &status_mixed.entries {
172        if !matches!(entry.index_status, IndexStatus::Clean) {
173            *index_status_counts
174                .entry(format!("{:?}", entry.index_status))
175                .or_insert(0) += 1;
176        }
177        if !matches!(entry.worktree_status, WorktreeStatus::Clean) {
178            *worktree_status_counts
179                .entry(format!("{:?}", entry.worktree_status))
180                .or_insert(0) += 1;
181        }
182    }
183
184    println!("   Index status counts:");
185    for (status, count) in &index_status_counts {
186        println!("      {}: {} files", status, count);
187    }
188
189    println!("   Worktree status counts:");
190    for (status, count) in &worktree_status_counts {
191        println!("      {}: {} files", status, count);
192    }
193
194    // Filter for specific patterns
195    let txt_files: Vec<_> = status_mixed
196        .entries
197        .iter()
198        .filter(|entry| entry.path.to_string_lossy().ends_with(".txt"))
199        .collect();
200
201    if !txt_files.is_empty() {
202        println!("\n   .txt files:");
203        for entry in txt_files {
204            println!(
205                "      Index {:?}, Worktree {:?}: {}",
206                entry.index_status,
207                entry.worktree_status,
208                entry.path.display()
209            );
210        }
211    }
212
213    println!();
214
215    println!("=== Repository State Checking ===\n");
216
217    println!("Repository state summary:");
218    println!("   Total files tracked: {}", status_mixed.entries.len());
219    println!("   Is clean: {}", status_mixed.is_clean());
220    println!("   Has changes: {}", status_mixed.has_changes());
221
222    if status_mixed.has_changes() {
223        println!("   Repository needs attention!");
224
225        let unstaged_count = status_mixed.unstaged_files().count();
226        if unstaged_count > 0 {
227            println!("      - {} files need to be staged", unstaged_count);
228        }
229
230        let untracked_count = status_mixed.untracked_entries().count();
231        if untracked_count > 0 {
232            println!("      - {} untracked files to consider", untracked_count);
233        }
234
235        let staged_count = status_mixed.staged_files().count();
236        if staged_count > 0 {
237            println!("      - {} files ready to commit", staged_count);
238        }
239    }
240
241    // Clean up
242    println!("\nCleaning up example repository...");
243    fs::remove_dir_all(&repo_path)?;
244    println!("Status checking example completed!");
245
246    Ok(())
247}
Source§

impl Repository

Source

pub fn tags(&self) -> Result<TagList>

List all tags in the repository

Returns a TagList containing all tags sorted by name.

§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let tags = repo.tags()?;

println!("Found {} tags:", tags.len());
for tag in tags.iter() {
    println!("  {} ({}) -> {}", tag.name, tag.tag_type, tag.hash.short());
}
Examples found in repository?
examples/tag_operations.rs (line 129)
16fn main() -> Result<()> {
17    println!("Rustic Git - Tag Operations Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_tag_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing repository at: {}", repo_path.display());
28
29    // Initialize repository and configure user
30    let repo = Repository::init(&repo_path, false)?;
31    repo.config()
32        .set_user("Tag Demo User", "tags@example.com")?;
33
34    // Create some commits to tag
35    println!("\nCreating initial commits...");
36
37    // First commit
38    fs::write(
39        repo_path.join("README.md"),
40        "# Tag Demo Project\n\nDemonstrating Git tag operations.\n",
41    )?;
42    repo.add(&["README.md"])?;
43    let first_commit_hash = repo.commit("Initial commit: Add README")?;
44    println!("Created commit: {}", first_commit_hash.short());
45
46    // Second commit
47    fs::create_dir_all(repo_path.join("src"))?;
48    fs::write(
49        repo_path.join("src/main.rs"),
50        "fn main() {\n    println!(\"Hello, tags!\");\n}\n",
51    )?;
52    repo.add(&["src/main.rs"])?;
53    let second_commit_hash = repo.commit("Add main.rs with hello world")?;
54    println!("Created commit: {}", second_commit_hash.short());
55
56    // Third commit
57    fs::write(
58        repo_path.join("src/lib.rs"),
59        "//! Tag demo library\n\npub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}\n",
60    )?;
61    repo.add(&["src/lib.rs"])?;
62    let third_commit_hash = repo.commit("Add library with greet function")?;
63    println!("Created commit: {}", third_commit_hash.short());
64
65    // Demonstrate tag creation
66    println!("\n=== Creating Tags ===");
67
68    // Create lightweight tags
69    println!("\n1. Creating lightweight tags:");
70
71    let v0_1_0 = repo.create_tag("v0.1.0", Some(&first_commit_hash))?;
72    println!(
73        "Created lightweight tag: {} -> {} ({})",
74        v0_1_0.name,
75        v0_1_0.hash.short(),
76        v0_1_0.tag_type
77    );
78
79    let v0_2_0 = repo.create_tag("v0.2.0", Some(&second_commit_hash))?;
80    println!(
81        "Created lightweight tag: {} -> {} ({})",
82        v0_2_0.name,
83        v0_2_0.hash.short(),
84        v0_2_0.tag_type
85    );
86
87    // Create annotated tags
88    println!("\n2. Creating annotated tags:");
89
90    let options =
91        TagOptions::new().with_message("First stable release with basic functionality".to_string());
92    let v1_0_0 = repo.create_tag_with_options("v1.0.0", Some(&third_commit_hash), options)?;
93    println!(
94        "Created annotated tag: {} -> {} ({})",
95        v1_0_0.name,
96        v1_0_0.hash.short(),
97        v1_0_0.tag_type
98    );
99    if let Some(message) = &v1_0_0.message {
100        println!("  Message: {}", message);
101    }
102
103    // Tag current HEAD
104    let latest_options = TagOptions::new().with_message("Latest development version".to_string());
105    let latest_tag = repo.create_tag_with_options("latest", None, latest_options)?;
106    println!(
107        "Created annotated tag on HEAD: {} -> {} ({})",
108        latest_tag.name,
109        latest_tag.hash.short(),
110        latest_tag.tag_type
111    );
112
113    // Create some feature tags
114    println!("\n3. Creating feature and release candidate tags:");
115
116    let feature_options = TagOptions::new().with_message("Feature branch snapshot".to_string());
117    repo.create_tag_with_options("feature/demo", None, feature_options)?;
118
119    let rc_options = TagOptions::new().with_message("Release candidate for v1.1.0".to_string());
120    repo.create_tag_with_options("v1.1.0-rc1", None, rc_options)?;
121
122    // Create a couple more version tags
123    repo.create_tag("v0.3.0", None)?;
124    repo.create_tag("v0.9.0", None)?;
125
126    // Demonstrate tag listing and filtering
127    println!("\n=== Tag Listing and Filtering ===");
128
129    let tags = repo.tags()?;
130    println!("\nAll tags ({} total):", tags.len());
131    for tag in tags.iter() {
132        let type_marker = match tag.tag_type {
133            TagType::Lightweight => "L",
134            TagType::Annotated => "A",
135        };
136        println!("  [{}] {} -> {}", type_marker, tag.name, tag.hash.short());
137        if let Some(message) = &tag.message {
138            println!("      Message: {}", message.lines().next().unwrap_or(""));
139        }
140    }
141
142    // Filter by type
143    println!("\nLightweight tags ({} total):", tags.lightweight_count());
144    for tag in tags.lightweight() {
145        println!("  {} -> {}", tag.name, tag.hash.short());
146    }
147
148    println!("\nAnnotated tags ({} total):", tags.annotated_count());
149    for tag in tags.annotated() {
150        println!("  {} -> {}", tag.name, tag.hash.short());
151        if let Some(message) = &tag.message {
152            println!("    Message: {}", message.lines().next().unwrap_or(""));
153        }
154    }
155
156    // Search and filtering
157    println!("\n=== Tag Searching ===");
158
159    // Find specific tag
160    if let Some(tag) = tags.find("v1.0.0") {
161        println!("\nFound tag 'v1.0.0':");
162        println!("  Type: {}", tag.tag_type);
163        println!("  Hash: {}", tag.hash.short());
164        if let Some(message) = &tag.message {
165            println!("  Message: {}", message);
166        }
167    }
168
169    // Find version tags
170    let version_tags: Vec<_> = tags.find_containing("v").collect();
171    println!(
172        "\nVersion tags (containing 'v'): {} found",
173        version_tags.len()
174    );
175    for tag in &version_tags {
176        println!("  {}", tag.name);
177    }
178
179    // Find release candidates
180    let rc_tags: Vec<_> = tags.find_containing("rc").collect();
181    println!("\nRelease candidate tags: {} found", rc_tags.len());
182    for tag in &rc_tags {
183        println!("  {}", tag.name);
184    }
185
186    // Find tags for specific commit
187    let tags_for_third_commit: Vec<_> = tags.for_commit(&third_commit_hash).collect();
188    println!(
189        "\nTags pointing to commit {}: {} found",
190        third_commit_hash.short(),
191        tags_for_third_commit.len()
192    );
193    for tag in &tags_for_third_commit {
194        println!("  {}", tag.name);
195    }
196
197    // Demonstrate tag details
198    println!("\n=== Tag Details ===");
199
200    let detailed_tag = repo.show_tag("v1.0.0")?;
201    println!("\nDetailed information for 'v1.0.0':");
202    println!("  Name: {}", detailed_tag.name);
203    println!("  Type: {}", detailed_tag.tag_type);
204    println!("  Commit: {}", detailed_tag.hash);
205    println!("  Short hash: {}", detailed_tag.hash.short());
206
207    if let Some(message) = &detailed_tag.message {
208        println!("  Message: {}", message);
209    }
210
211    if let Some(tagger) = &detailed_tag.tagger {
212        println!("  Tagger: {}", tagger);
213        println!(
214            "  Tagged at: {}",
215            tagger.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
216        );
217    }
218
219    if let Some(timestamp) = &detailed_tag.timestamp {
220        println!("  Timestamp: {}", timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
221    }
222
223    // Demonstrate tag operations
224    println!("\n=== Tag Operations ===");
225
226    // Create and force overwrite a tag
227    println!("\n1. Testing tag overwrite:");
228
229    // This should fail (tag already exists)
230    match repo.create_tag("latest", None) {
231        Ok(_) => println!("  ERROR: Should have failed to create existing tag"),
232        Err(e) => println!("  Expected error creating existing tag: {}", e),
233    }
234
235    // Force overwrite
236    let force_options = TagOptions::new()
237        .with_force()
238        .with_message("Forcefully updated latest tag".to_string());
239
240    match repo.create_tag_with_options("latest", None, force_options) {
241        Ok(tag) => println!("  Successfully force-created tag: {}", tag.name),
242        Err(e) => println!("  Error force-creating tag: {}", e),
243    }
244
245    // Tag deletion
246    println!("\n2. Testing tag deletion:");
247
248    // Create a temporary tag to delete
249    repo.create_tag("temp-tag", None)?;
250    println!("  Created temporary tag: temp-tag");
251
252    // Verify it exists
253    let tags_before = repo.tags()?;
254    let temp_exists_before = tags_before.find("temp-tag").is_some();
255    println!("  Temp tag exists before deletion: {}", temp_exists_before);
256
257    // Delete it
258    repo.delete_tag("temp-tag")?;
259    println!("  Deleted temp-tag");
260
261    // Verify it's gone
262    let tags_after = repo.tags()?;
263    let temp_exists_after = tags_after.find("temp-tag").is_some();
264    println!("  Temp tag exists after deletion: {}", temp_exists_after);
265
266    // Summary
267    println!("\n=== Summary ===");
268    let final_tags = repo.tags()?;
269    println!("\nFinal repository state:");
270    println!("  Total tags: {}", final_tags.len());
271    println!("  Lightweight tags: {}", final_tags.lightweight_count());
272    println!("  Annotated tags: {}", final_tags.annotated_count());
273
274    println!("\nTag creation options demonstrated:");
275    println!("  ✓ Lightweight tags (simple references)");
276    println!("  ✓ Annotated tags (with messages and metadata)");
277    println!("  ✓ Tags on specific commits");
278    println!("  ✓ Tags on current HEAD");
279    println!("  ✓ Force tag creation/overwrite");
280
281    println!("\nTag listing and filtering demonstrated:");
282    println!("  ✓ List all tags");
283    println!("  ✓ Filter by tag type (lightweight/annotated)");
284    println!("  ✓ Search by name patterns");
285    println!("  ✓ Find tags by commit hash");
286    println!("  ✓ Show detailed tag information");
287
288    println!("\nTag management demonstrated:");
289    println!("  ✓ Tag creation with options");
290    println!("  ✓ Tag deletion");
291    println!("  ✓ Error handling for duplicate tags");
292
293    // Clean up
294    println!("\nCleaning up example repository...");
295    fs::remove_dir_all(&repo_path)?;
296    println!("Tag operations example completed successfully!");
297
298    Ok(())
299}
Source

pub fn create_tag(&self, name: &str, target: Option<&Hash>) -> Result<Tag>

Create a lightweight tag pointing to the current HEAD or specified commit

§Arguments
  • name - The name of the tag to create
  • target - Optional commit hash to tag (defaults to HEAD)
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;

// Tag current HEAD
let tag = repo.create_tag("v1.0.0", None)?;

// Tag specific commit
let commits = repo.recent_commits(1)?;
if let Some(commit) = commits.iter().next() {
    let tag = repo.create_tag("v0.9.0", Some(&commit.hash))?;
}
Examples found in repository?
examples/tag_operations.rs (line 71)
16fn main() -> Result<()> {
17    println!("Rustic Git - Tag Operations Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_tag_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing repository at: {}", repo_path.display());
28
29    // Initialize repository and configure user
30    let repo = Repository::init(&repo_path, false)?;
31    repo.config()
32        .set_user("Tag Demo User", "tags@example.com")?;
33
34    // Create some commits to tag
35    println!("\nCreating initial commits...");
36
37    // First commit
38    fs::write(
39        repo_path.join("README.md"),
40        "# Tag Demo Project\n\nDemonstrating Git tag operations.\n",
41    )?;
42    repo.add(&["README.md"])?;
43    let first_commit_hash = repo.commit("Initial commit: Add README")?;
44    println!("Created commit: {}", first_commit_hash.short());
45
46    // Second commit
47    fs::create_dir_all(repo_path.join("src"))?;
48    fs::write(
49        repo_path.join("src/main.rs"),
50        "fn main() {\n    println!(\"Hello, tags!\");\n}\n",
51    )?;
52    repo.add(&["src/main.rs"])?;
53    let second_commit_hash = repo.commit("Add main.rs with hello world")?;
54    println!("Created commit: {}", second_commit_hash.short());
55
56    // Third commit
57    fs::write(
58        repo_path.join("src/lib.rs"),
59        "//! Tag demo library\n\npub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}\n",
60    )?;
61    repo.add(&["src/lib.rs"])?;
62    let third_commit_hash = repo.commit("Add library with greet function")?;
63    println!("Created commit: {}", third_commit_hash.short());
64
65    // Demonstrate tag creation
66    println!("\n=== Creating Tags ===");
67
68    // Create lightweight tags
69    println!("\n1. Creating lightweight tags:");
70
71    let v0_1_0 = repo.create_tag("v0.1.0", Some(&first_commit_hash))?;
72    println!(
73        "Created lightweight tag: {} -> {} ({})",
74        v0_1_0.name,
75        v0_1_0.hash.short(),
76        v0_1_0.tag_type
77    );
78
79    let v0_2_0 = repo.create_tag("v0.2.0", Some(&second_commit_hash))?;
80    println!(
81        "Created lightweight tag: {} -> {} ({})",
82        v0_2_0.name,
83        v0_2_0.hash.short(),
84        v0_2_0.tag_type
85    );
86
87    // Create annotated tags
88    println!("\n2. Creating annotated tags:");
89
90    let options =
91        TagOptions::new().with_message("First stable release with basic functionality".to_string());
92    let v1_0_0 = repo.create_tag_with_options("v1.0.0", Some(&third_commit_hash), options)?;
93    println!(
94        "Created annotated tag: {} -> {} ({})",
95        v1_0_0.name,
96        v1_0_0.hash.short(),
97        v1_0_0.tag_type
98    );
99    if let Some(message) = &v1_0_0.message {
100        println!("  Message: {}", message);
101    }
102
103    // Tag current HEAD
104    let latest_options = TagOptions::new().with_message("Latest development version".to_string());
105    let latest_tag = repo.create_tag_with_options("latest", None, latest_options)?;
106    println!(
107        "Created annotated tag on HEAD: {} -> {} ({})",
108        latest_tag.name,
109        latest_tag.hash.short(),
110        latest_tag.tag_type
111    );
112
113    // Create some feature tags
114    println!("\n3. Creating feature and release candidate tags:");
115
116    let feature_options = TagOptions::new().with_message("Feature branch snapshot".to_string());
117    repo.create_tag_with_options("feature/demo", None, feature_options)?;
118
119    let rc_options = TagOptions::new().with_message("Release candidate for v1.1.0".to_string());
120    repo.create_tag_with_options("v1.1.0-rc1", None, rc_options)?;
121
122    // Create a couple more version tags
123    repo.create_tag("v0.3.0", None)?;
124    repo.create_tag("v0.9.0", None)?;
125
126    // Demonstrate tag listing and filtering
127    println!("\n=== Tag Listing and Filtering ===");
128
129    let tags = repo.tags()?;
130    println!("\nAll tags ({} total):", tags.len());
131    for tag in tags.iter() {
132        let type_marker = match tag.tag_type {
133            TagType::Lightweight => "L",
134            TagType::Annotated => "A",
135        };
136        println!("  [{}] {} -> {}", type_marker, tag.name, tag.hash.short());
137        if let Some(message) = &tag.message {
138            println!("      Message: {}", message.lines().next().unwrap_or(""));
139        }
140    }
141
142    // Filter by type
143    println!("\nLightweight tags ({} total):", tags.lightweight_count());
144    for tag in tags.lightweight() {
145        println!("  {} -> {}", tag.name, tag.hash.short());
146    }
147
148    println!("\nAnnotated tags ({} total):", tags.annotated_count());
149    for tag in tags.annotated() {
150        println!("  {} -> {}", tag.name, tag.hash.short());
151        if let Some(message) = &tag.message {
152            println!("    Message: {}", message.lines().next().unwrap_or(""));
153        }
154    }
155
156    // Search and filtering
157    println!("\n=== Tag Searching ===");
158
159    // Find specific tag
160    if let Some(tag) = tags.find("v1.0.0") {
161        println!("\nFound tag 'v1.0.0':");
162        println!("  Type: {}", tag.tag_type);
163        println!("  Hash: {}", tag.hash.short());
164        if let Some(message) = &tag.message {
165            println!("  Message: {}", message);
166        }
167    }
168
169    // Find version tags
170    let version_tags: Vec<_> = tags.find_containing("v").collect();
171    println!(
172        "\nVersion tags (containing 'v'): {} found",
173        version_tags.len()
174    );
175    for tag in &version_tags {
176        println!("  {}", tag.name);
177    }
178
179    // Find release candidates
180    let rc_tags: Vec<_> = tags.find_containing("rc").collect();
181    println!("\nRelease candidate tags: {} found", rc_tags.len());
182    for tag in &rc_tags {
183        println!("  {}", tag.name);
184    }
185
186    // Find tags for specific commit
187    let tags_for_third_commit: Vec<_> = tags.for_commit(&third_commit_hash).collect();
188    println!(
189        "\nTags pointing to commit {}: {} found",
190        third_commit_hash.short(),
191        tags_for_third_commit.len()
192    );
193    for tag in &tags_for_third_commit {
194        println!("  {}", tag.name);
195    }
196
197    // Demonstrate tag details
198    println!("\n=== Tag Details ===");
199
200    let detailed_tag = repo.show_tag("v1.0.0")?;
201    println!("\nDetailed information for 'v1.0.0':");
202    println!("  Name: {}", detailed_tag.name);
203    println!("  Type: {}", detailed_tag.tag_type);
204    println!("  Commit: {}", detailed_tag.hash);
205    println!("  Short hash: {}", detailed_tag.hash.short());
206
207    if let Some(message) = &detailed_tag.message {
208        println!("  Message: {}", message);
209    }
210
211    if let Some(tagger) = &detailed_tag.tagger {
212        println!("  Tagger: {}", tagger);
213        println!(
214            "  Tagged at: {}",
215            tagger.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
216        );
217    }
218
219    if let Some(timestamp) = &detailed_tag.timestamp {
220        println!("  Timestamp: {}", timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
221    }
222
223    // Demonstrate tag operations
224    println!("\n=== Tag Operations ===");
225
226    // Create and force overwrite a tag
227    println!("\n1. Testing tag overwrite:");
228
229    // This should fail (tag already exists)
230    match repo.create_tag("latest", None) {
231        Ok(_) => println!("  ERROR: Should have failed to create existing tag"),
232        Err(e) => println!("  Expected error creating existing tag: {}", e),
233    }
234
235    // Force overwrite
236    let force_options = TagOptions::new()
237        .with_force()
238        .with_message("Forcefully updated latest tag".to_string());
239
240    match repo.create_tag_with_options("latest", None, force_options) {
241        Ok(tag) => println!("  Successfully force-created tag: {}", tag.name),
242        Err(e) => println!("  Error force-creating tag: {}", e),
243    }
244
245    // Tag deletion
246    println!("\n2. Testing tag deletion:");
247
248    // Create a temporary tag to delete
249    repo.create_tag("temp-tag", None)?;
250    println!("  Created temporary tag: temp-tag");
251
252    // Verify it exists
253    let tags_before = repo.tags()?;
254    let temp_exists_before = tags_before.find("temp-tag").is_some();
255    println!("  Temp tag exists before deletion: {}", temp_exists_before);
256
257    // Delete it
258    repo.delete_tag("temp-tag")?;
259    println!("  Deleted temp-tag");
260
261    // Verify it's gone
262    let tags_after = repo.tags()?;
263    let temp_exists_after = tags_after.find("temp-tag").is_some();
264    println!("  Temp tag exists after deletion: {}", temp_exists_after);
265
266    // Summary
267    println!("\n=== Summary ===");
268    let final_tags = repo.tags()?;
269    println!("\nFinal repository state:");
270    println!("  Total tags: {}", final_tags.len());
271    println!("  Lightweight tags: {}", final_tags.lightweight_count());
272    println!("  Annotated tags: {}", final_tags.annotated_count());
273
274    println!("\nTag creation options demonstrated:");
275    println!("  ✓ Lightweight tags (simple references)");
276    println!("  ✓ Annotated tags (with messages and metadata)");
277    println!("  ✓ Tags on specific commits");
278    println!("  ✓ Tags on current HEAD");
279    println!("  ✓ Force tag creation/overwrite");
280
281    println!("\nTag listing and filtering demonstrated:");
282    println!("  ✓ List all tags");
283    println!("  ✓ Filter by tag type (lightweight/annotated)");
284    println!("  ✓ Search by name patterns");
285    println!("  ✓ Find tags by commit hash");
286    println!("  ✓ Show detailed tag information");
287
288    println!("\nTag management demonstrated:");
289    println!("  ✓ Tag creation with options");
290    println!("  ✓ Tag deletion");
291    println!("  ✓ Error handling for duplicate tags");
292
293    // Clean up
294    println!("\nCleaning up example repository...");
295    fs::remove_dir_all(&repo_path)?;
296    println!("Tag operations example completed successfully!");
297
298    Ok(())
299}
Source

pub fn create_tag_with_options( &self, name: &str, target: Option<&Hash>, options: TagOptions, ) -> Result<Tag>

Create a tag with custom options

§Arguments
  • name - The name of the tag to create
  • target - Optional commit hash to tag (defaults to HEAD)
  • options - Tag creation options
§Example
use rustic_git::{Repository, TagOptions};

let repo = Repository::open(".")?;

// Create annotated tag with message
let options = TagOptions::new()
    .with_message("Release version 1.0.0".to_string());
let tag = repo.create_tag_with_options("v1.0.0", None, options)?;

// Create and force overwrite existing tag
let options = TagOptions::new().with_force();
let tag = repo.create_tag_with_options("latest", None, options)?;
Examples found in repository?
examples/tag_operations.rs (line 92)
16fn main() -> Result<()> {
17    println!("Rustic Git - Tag Operations Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_tag_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing repository at: {}", repo_path.display());
28
29    // Initialize repository and configure user
30    let repo = Repository::init(&repo_path, false)?;
31    repo.config()
32        .set_user("Tag Demo User", "tags@example.com")?;
33
34    // Create some commits to tag
35    println!("\nCreating initial commits...");
36
37    // First commit
38    fs::write(
39        repo_path.join("README.md"),
40        "# Tag Demo Project\n\nDemonstrating Git tag operations.\n",
41    )?;
42    repo.add(&["README.md"])?;
43    let first_commit_hash = repo.commit("Initial commit: Add README")?;
44    println!("Created commit: {}", first_commit_hash.short());
45
46    // Second commit
47    fs::create_dir_all(repo_path.join("src"))?;
48    fs::write(
49        repo_path.join("src/main.rs"),
50        "fn main() {\n    println!(\"Hello, tags!\");\n}\n",
51    )?;
52    repo.add(&["src/main.rs"])?;
53    let second_commit_hash = repo.commit("Add main.rs with hello world")?;
54    println!("Created commit: {}", second_commit_hash.short());
55
56    // Third commit
57    fs::write(
58        repo_path.join("src/lib.rs"),
59        "//! Tag demo library\n\npub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}\n",
60    )?;
61    repo.add(&["src/lib.rs"])?;
62    let third_commit_hash = repo.commit("Add library with greet function")?;
63    println!("Created commit: {}", third_commit_hash.short());
64
65    // Demonstrate tag creation
66    println!("\n=== Creating Tags ===");
67
68    // Create lightweight tags
69    println!("\n1. Creating lightweight tags:");
70
71    let v0_1_0 = repo.create_tag("v0.1.0", Some(&first_commit_hash))?;
72    println!(
73        "Created lightweight tag: {} -> {} ({})",
74        v0_1_0.name,
75        v0_1_0.hash.short(),
76        v0_1_0.tag_type
77    );
78
79    let v0_2_0 = repo.create_tag("v0.2.0", Some(&second_commit_hash))?;
80    println!(
81        "Created lightweight tag: {} -> {} ({})",
82        v0_2_0.name,
83        v0_2_0.hash.short(),
84        v0_2_0.tag_type
85    );
86
87    // Create annotated tags
88    println!("\n2. Creating annotated tags:");
89
90    let options =
91        TagOptions::new().with_message("First stable release with basic functionality".to_string());
92    let v1_0_0 = repo.create_tag_with_options("v1.0.0", Some(&third_commit_hash), options)?;
93    println!(
94        "Created annotated tag: {} -> {} ({})",
95        v1_0_0.name,
96        v1_0_0.hash.short(),
97        v1_0_0.tag_type
98    );
99    if let Some(message) = &v1_0_0.message {
100        println!("  Message: {}", message);
101    }
102
103    // Tag current HEAD
104    let latest_options = TagOptions::new().with_message("Latest development version".to_string());
105    let latest_tag = repo.create_tag_with_options("latest", None, latest_options)?;
106    println!(
107        "Created annotated tag on HEAD: {} -> {} ({})",
108        latest_tag.name,
109        latest_tag.hash.short(),
110        latest_tag.tag_type
111    );
112
113    // Create some feature tags
114    println!("\n3. Creating feature and release candidate tags:");
115
116    let feature_options = TagOptions::new().with_message("Feature branch snapshot".to_string());
117    repo.create_tag_with_options("feature/demo", None, feature_options)?;
118
119    let rc_options = TagOptions::new().with_message("Release candidate for v1.1.0".to_string());
120    repo.create_tag_with_options("v1.1.0-rc1", None, rc_options)?;
121
122    // Create a couple more version tags
123    repo.create_tag("v0.3.0", None)?;
124    repo.create_tag("v0.9.0", None)?;
125
126    // Demonstrate tag listing and filtering
127    println!("\n=== Tag Listing and Filtering ===");
128
129    let tags = repo.tags()?;
130    println!("\nAll tags ({} total):", tags.len());
131    for tag in tags.iter() {
132        let type_marker = match tag.tag_type {
133            TagType::Lightweight => "L",
134            TagType::Annotated => "A",
135        };
136        println!("  [{}] {} -> {}", type_marker, tag.name, tag.hash.short());
137        if let Some(message) = &tag.message {
138            println!("      Message: {}", message.lines().next().unwrap_or(""));
139        }
140    }
141
142    // Filter by type
143    println!("\nLightweight tags ({} total):", tags.lightweight_count());
144    for tag in tags.lightweight() {
145        println!("  {} -> {}", tag.name, tag.hash.short());
146    }
147
148    println!("\nAnnotated tags ({} total):", tags.annotated_count());
149    for tag in tags.annotated() {
150        println!("  {} -> {}", tag.name, tag.hash.short());
151        if let Some(message) = &tag.message {
152            println!("    Message: {}", message.lines().next().unwrap_or(""));
153        }
154    }
155
156    // Search and filtering
157    println!("\n=== Tag Searching ===");
158
159    // Find specific tag
160    if let Some(tag) = tags.find("v1.0.0") {
161        println!("\nFound tag 'v1.0.0':");
162        println!("  Type: {}", tag.tag_type);
163        println!("  Hash: {}", tag.hash.short());
164        if let Some(message) = &tag.message {
165            println!("  Message: {}", message);
166        }
167    }
168
169    // Find version tags
170    let version_tags: Vec<_> = tags.find_containing("v").collect();
171    println!(
172        "\nVersion tags (containing 'v'): {} found",
173        version_tags.len()
174    );
175    for tag in &version_tags {
176        println!("  {}", tag.name);
177    }
178
179    // Find release candidates
180    let rc_tags: Vec<_> = tags.find_containing("rc").collect();
181    println!("\nRelease candidate tags: {} found", rc_tags.len());
182    for tag in &rc_tags {
183        println!("  {}", tag.name);
184    }
185
186    // Find tags for specific commit
187    let tags_for_third_commit: Vec<_> = tags.for_commit(&third_commit_hash).collect();
188    println!(
189        "\nTags pointing to commit {}: {} found",
190        third_commit_hash.short(),
191        tags_for_third_commit.len()
192    );
193    for tag in &tags_for_third_commit {
194        println!("  {}", tag.name);
195    }
196
197    // Demonstrate tag details
198    println!("\n=== Tag Details ===");
199
200    let detailed_tag = repo.show_tag("v1.0.0")?;
201    println!("\nDetailed information for 'v1.0.0':");
202    println!("  Name: {}", detailed_tag.name);
203    println!("  Type: {}", detailed_tag.tag_type);
204    println!("  Commit: {}", detailed_tag.hash);
205    println!("  Short hash: {}", detailed_tag.hash.short());
206
207    if let Some(message) = &detailed_tag.message {
208        println!("  Message: {}", message);
209    }
210
211    if let Some(tagger) = &detailed_tag.tagger {
212        println!("  Tagger: {}", tagger);
213        println!(
214            "  Tagged at: {}",
215            tagger.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
216        );
217    }
218
219    if let Some(timestamp) = &detailed_tag.timestamp {
220        println!("  Timestamp: {}", timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
221    }
222
223    // Demonstrate tag operations
224    println!("\n=== Tag Operations ===");
225
226    // Create and force overwrite a tag
227    println!("\n1. Testing tag overwrite:");
228
229    // This should fail (tag already exists)
230    match repo.create_tag("latest", None) {
231        Ok(_) => println!("  ERROR: Should have failed to create existing tag"),
232        Err(e) => println!("  Expected error creating existing tag: {}", e),
233    }
234
235    // Force overwrite
236    let force_options = TagOptions::new()
237        .with_force()
238        .with_message("Forcefully updated latest tag".to_string());
239
240    match repo.create_tag_with_options("latest", None, force_options) {
241        Ok(tag) => println!("  Successfully force-created tag: {}", tag.name),
242        Err(e) => println!("  Error force-creating tag: {}", e),
243    }
244
245    // Tag deletion
246    println!("\n2. Testing tag deletion:");
247
248    // Create a temporary tag to delete
249    repo.create_tag("temp-tag", None)?;
250    println!("  Created temporary tag: temp-tag");
251
252    // Verify it exists
253    let tags_before = repo.tags()?;
254    let temp_exists_before = tags_before.find("temp-tag").is_some();
255    println!("  Temp tag exists before deletion: {}", temp_exists_before);
256
257    // Delete it
258    repo.delete_tag("temp-tag")?;
259    println!("  Deleted temp-tag");
260
261    // Verify it's gone
262    let tags_after = repo.tags()?;
263    let temp_exists_after = tags_after.find("temp-tag").is_some();
264    println!("  Temp tag exists after deletion: {}", temp_exists_after);
265
266    // Summary
267    println!("\n=== Summary ===");
268    let final_tags = repo.tags()?;
269    println!("\nFinal repository state:");
270    println!("  Total tags: {}", final_tags.len());
271    println!("  Lightweight tags: {}", final_tags.lightweight_count());
272    println!("  Annotated tags: {}", final_tags.annotated_count());
273
274    println!("\nTag creation options demonstrated:");
275    println!("  ✓ Lightweight tags (simple references)");
276    println!("  ✓ Annotated tags (with messages and metadata)");
277    println!("  ✓ Tags on specific commits");
278    println!("  ✓ Tags on current HEAD");
279    println!("  ✓ Force tag creation/overwrite");
280
281    println!("\nTag listing and filtering demonstrated:");
282    println!("  ✓ List all tags");
283    println!("  ✓ Filter by tag type (lightweight/annotated)");
284    println!("  ✓ Search by name patterns");
285    println!("  ✓ Find tags by commit hash");
286    println!("  ✓ Show detailed tag information");
287
288    println!("\nTag management demonstrated:");
289    println!("  ✓ Tag creation with options");
290    println!("  ✓ Tag deletion");
291    println!("  ✓ Error handling for duplicate tags");
292
293    // Clean up
294    println!("\nCleaning up example repository...");
295    fs::remove_dir_all(&repo_path)?;
296    println!("Tag operations example completed successfully!");
297
298    Ok(())
299}
Source

pub fn delete_tag(&self, name: &str) -> Result<()>

Delete a tag

§Arguments
  • name - The name of the tag to delete
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
repo.delete_tag("v0.1.0")?;
Examples found in repository?
examples/tag_operations.rs (line 258)
16fn main() -> Result<()> {
17    println!("Rustic Git - Tag Operations Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_tag_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing repository at: {}", repo_path.display());
28
29    // Initialize repository and configure user
30    let repo = Repository::init(&repo_path, false)?;
31    repo.config()
32        .set_user("Tag Demo User", "tags@example.com")?;
33
34    // Create some commits to tag
35    println!("\nCreating initial commits...");
36
37    // First commit
38    fs::write(
39        repo_path.join("README.md"),
40        "# Tag Demo Project\n\nDemonstrating Git tag operations.\n",
41    )?;
42    repo.add(&["README.md"])?;
43    let first_commit_hash = repo.commit("Initial commit: Add README")?;
44    println!("Created commit: {}", first_commit_hash.short());
45
46    // Second commit
47    fs::create_dir_all(repo_path.join("src"))?;
48    fs::write(
49        repo_path.join("src/main.rs"),
50        "fn main() {\n    println!(\"Hello, tags!\");\n}\n",
51    )?;
52    repo.add(&["src/main.rs"])?;
53    let second_commit_hash = repo.commit("Add main.rs with hello world")?;
54    println!("Created commit: {}", second_commit_hash.short());
55
56    // Third commit
57    fs::write(
58        repo_path.join("src/lib.rs"),
59        "//! Tag demo library\n\npub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}\n",
60    )?;
61    repo.add(&["src/lib.rs"])?;
62    let third_commit_hash = repo.commit("Add library with greet function")?;
63    println!("Created commit: {}", third_commit_hash.short());
64
65    // Demonstrate tag creation
66    println!("\n=== Creating Tags ===");
67
68    // Create lightweight tags
69    println!("\n1. Creating lightweight tags:");
70
71    let v0_1_0 = repo.create_tag("v0.1.0", Some(&first_commit_hash))?;
72    println!(
73        "Created lightweight tag: {} -> {} ({})",
74        v0_1_0.name,
75        v0_1_0.hash.short(),
76        v0_1_0.tag_type
77    );
78
79    let v0_2_0 = repo.create_tag("v0.2.0", Some(&second_commit_hash))?;
80    println!(
81        "Created lightweight tag: {} -> {} ({})",
82        v0_2_0.name,
83        v0_2_0.hash.short(),
84        v0_2_0.tag_type
85    );
86
87    // Create annotated tags
88    println!("\n2. Creating annotated tags:");
89
90    let options =
91        TagOptions::new().with_message("First stable release with basic functionality".to_string());
92    let v1_0_0 = repo.create_tag_with_options("v1.0.0", Some(&third_commit_hash), options)?;
93    println!(
94        "Created annotated tag: {} -> {} ({})",
95        v1_0_0.name,
96        v1_0_0.hash.short(),
97        v1_0_0.tag_type
98    );
99    if let Some(message) = &v1_0_0.message {
100        println!("  Message: {}", message);
101    }
102
103    // Tag current HEAD
104    let latest_options = TagOptions::new().with_message("Latest development version".to_string());
105    let latest_tag = repo.create_tag_with_options("latest", None, latest_options)?;
106    println!(
107        "Created annotated tag on HEAD: {} -> {} ({})",
108        latest_tag.name,
109        latest_tag.hash.short(),
110        latest_tag.tag_type
111    );
112
113    // Create some feature tags
114    println!("\n3. Creating feature and release candidate tags:");
115
116    let feature_options = TagOptions::new().with_message("Feature branch snapshot".to_string());
117    repo.create_tag_with_options("feature/demo", None, feature_options)?;
118
119    let rc_options = TagOptions::new().with_message("Release candidate for v1.1.0".to_string());
120    repo.create_tag_with_options("v1.1.0-rc1", None, rc_options)?;
121
122    // Create a couple more version tags
123    repo.create_tag("v0.3.0", None)?;
124    repo.create_tag("v0.9.0", None)?;
125
126    // Demonstrate tag listing and filtering
127    println!("\n=== Tag Listing and Filtering ===");
128
129    let tags = repo.tags()?;
130    println!("\nAll tags ({} total):", tags.len());
131    for tag in tags.iter() {
132        let type_marker = match tag.tag_type {
133            TagType::Lightweight => "L",
134            TagType::Annotated => "A",
135        };
136        println!("  [{}] {} -> {}", type_marker, tag.name, tag.hash.short());
137        if let Some(message) = &tag.message {
138            println!("      Message: {}", message.lines().next().unwrap_or(""));
139        }
140    }
141
142    // Filter by type
143    println!("\nLightweight tags ({} total):", tags.lightweight_count());
144    for tag in tags.lightweight() {
145        println!("  {} -> {}", tag.name, tag.hash.short());
146    }
147
148    println!("\nAnnotated tags ({} total):", tags.annotated_count());
149    for tag in tags.annotated() {
150        println!("  {} -> {}", tag.name, tag.hash.short());
151        if let Some(message) = &tag.message {
152            println!("    Message: {}", message.lines().next().unwrap_or(""));
153        }
154    }
155
156    // Search and filtering
157    println!("\n=== Tag Searching ===");
158
159    // Find specific tag
160    if let Some(tag) = tags.find("v1.0.0") {
161        println!("\nFound tag 'v1.0.0':");
162        println!("  Type: {}", tag.tag_type);
163        println!("  Hash: {}", tag.hash.short());
164        if let Some(message) = &tag.message {
165            println!("  Message: {}", message);
166        }
167    }
168
169    // Find version tags
170    let version_tags: Vec<_> = tags.find_containing("v").collect();
171    println!(
172        "\nVersion tags (containing 'v'): {} found",
173        version_tags.len()
174    );
175    for tag in &version_tags {
176        println!("  {}", tag.name);
177    }
178
179    // Find release candidates
180    let rc_tags: Vec<_> = tags.find_containing("rc").collect();
181    println!("\nRelease candidate tags: {} found", rc_tags.len());
182    for tag in &rc_tags {
183        println!("  {}", tag.name);
184    }
185
186    // Find tags for specific commit
187    let tags_for_third_commit: Vec<_> = tags.for_commit(&third_commit_hash).collect();
188    println!(
189        "\nTags pointing to commit {}: {} found",
190        third_commit_hash.short(),
191        tags_for_third_commit.len()
192    );
193    for tag in &tags_for_third_commit {
194        println!("  {}", tag.name);
195    }
196
197    // Demonstrate tag details
198    println!("\n=== Tag Details ===");
199
200    let detailed_tag = repo.show_tag("v1.0.0")?;
201    println!("\nDetailed information for 'v1.0.0':");
202    println!("  Name: {}", detailed_tag.name);
203    println!("  Type: {}", detailed_tag.tag_type);
204    println!("  Commit: {}", detailed_tag.hash);
205    println!("  Short hash: {}", detailed_tag.hash.short());
206
207    if let Some(message) = &detailed_tag.message {
208        println!("  Message: {}", message);
209    }
210
211    if let Some(tagger) = &detailed_tag.tagger {
212        println!("  Tagger: {}", tagger);
213        println!(
214            "  Tagged at: {}",
215            tagger.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
216        );
217    }
218
219    if let Some(timestamp) = &detailed_tag.timestamp {
220        println!("  Timestamp: {}", timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
221    }
222
223    // Demonstrate tag operations
224    println!("\n=== Tag Operations ===");
225
226    // Create and force overwrite a tag
227    println!("\n1. Testing tag overwrite:");
228
229    // This should fail (tag already exists)
230    match repo.create_tag("latest", None) {
231        Ok(_) => println!("  ERROR: Should have failed to create existing tag"),
232        Err(e) => println!("  Expected error creating existing tag: {}", e),
233    }
234
235    // Force overwrite
236    let force_options = TagOptions::new()
237        .with_force()
238        .with_message("Forcefully updated latest tag".to_string());
239
240    match repo.create_tag_with_options("latest", None, force_options) {
241        Ok(tag) => println!("  Successfully force-created tag: {}", tag.name),
242        Err(e) => println!("  Error force-creating tag: {}", e),
243    }
244
245    // Tag deletion
246    println!("\n2. Testing tag deletion:");
247
248    // Create a temporary tag to delete
249    repo.create_tag("temp-tag", None)?;
250    println!("  Created temporary tag: temp-tag");
251
252    // Verify it exists
253    let tags_before = repo.tags()?;
254    let temp_exists_before = tags_before.find("temp-tag").is_some();
255    println!("  Temp tag exists before deletion: {}", temp_exists_before);
256
257    // Delete it
258    repo.delete_tag("temp-tag")?;
259    println!("  Deleted temp-tag");
260
261    // Verify it's gone
262    let tags_after = repo.tags()?;
263    let temp_exists_after = tags_after.find("temp-tag").is_some();
264    println!("  Temp tag exists after deletion: {}", temp_exists_after);
265
266    // Summary
267    println!("\n=== Summary ===");
268    let final_tags = repo.tags()?;
269    println!("\nFinal repository state:");
270    println!("  Total tags: {}", final_tags.len());
271    println!("  Lightweight tags: {}", final_tags.lightweight_count());
272    println!("  Annotated tags: {}", final_tags.annotated_count());
273
274    println!("\nTag creation options demonstrated:");
275    println!("  ✓ Lightweight tags (simple references)");
276    println!("  ✓ Annotated tags (with messages and metadata)");
277    println!("  ✓ Tags on specific commits");
278    println!("  ✓ Tags on current HEAD");
279    println!("  ✓ Force tag creation/overwrite");
280
281    println!("\nTag listing and filtering demonstrated:");
282    println!("  ✓ List all tags");
283    println!("  ✓ Filter by tag type (lightweight/annotated)");
284    println!("  ✓ Search by name patterns");
285    println!("  ✓ Find tags by commit hash");
286    println!("  ✓ Show detailed tag information");
287
288    println!("\nTag management demonstrated:");
289    println!("  ✓ Tag creation with options");
290    println!("  ✓ Tag deletion");
291    println!("  ✓ Error handling for duplicate tags");
292
293    // Clean up
294    println!("\nCleaning up example repository...");
295    fs::remove_dir_all(&repo_path)?;
296    println!("Tag operations example completed successfully!");
297
298    Ok(())
299}
Source

pub fn show_tag(&self, name: &str) -> Result<Tag>

Show detailed information about a specific tag

§Arguments
  • name - The name of the tag to show
§Example
use rustic_git::Repository;

let repo = Repository::open(".")?;
let tag = repo.show_tag("v1.0.0")?;

println!("Tag: {} ({})", tag.name, tag.tag_type);
println!("Commit: {}", tag.hash.short());
if let Some(message) = &tag.message {
    println!("Message: {}", message);
}
Examples found in repository?
examples/tag_operations.rs (line 200)
16fn main() -> Result<()> {
17    println!("Rustic Git - Tag Operations Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_tag_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing repository at: {}", repo_path.display());
28
29    // Initialize repository and configure user
30    let repo = Repository::init(&repo_path, false)?;
31    repo.config()
32        .set_user("Tag Demo User", "tags@example.com")?;
33
34    // Create some commits to tag
35    println!("\nCreating initial commits...");
36
37    // First commit
38    fs::write(
39        repo_path.join("README.md"),
40        "# Tag Demo Project\n\nDemonstrating Git tag operations.\n",
41    )?;
42    repo.add(&["README.md"])?;
43    let first_commit_hash = repo.commit("Initial commit: Add README")?;
44    println!("Created commit: {}", first_commit_hash.short());
45
46    // Second commit
47    fs::create_dir_all(repo_path.join("src"))?;
48    fs::write(
49        repo_path.join("src/main.rs"),
50        "fn main() {\n    println!(\"Hello, tags!\");\n}\n",
51    )?;
52    repo.add(&["src/main.rs"])?;
53    let second_commit_hash = repo.commit("Add main.rs with hello world")?;
54    println!("Created commit: {}", second_commit_hash.short());
55
56    // Third commit
57    fs::write(
58        repo_path.join("src/lib.rs"),
59        "//! Tag demo library\n\npub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}\n",
60    )?;
61    repo.add(&["src/lib.rs"])?;
62    let third_commit_hash = repo.commit("Add library with greet function")?;
63    println!("Created commit: {}", third_commit_hash.short());
64
65    // Demonstrate tag creation
66    println!("\n=== Creating Tags ===");
67
68    // Create lightweight tags
69    println!("\n1. Creating lightweight tags:");
70
71    let v0_1_0 = repo.create_tag("v0.1.0", Some(&first_commit_hash))?;
72    println!(
73        "Created lightweight tag: {} -> {} ({})",
74        v0_1_0.name,
75        v0_1_0.hash.short(),
76        v0_1_0.tag_type
77    );
78
79    let v0_2_0 = repo.create_tag("v0.2.0", Some(&second_commit_hash))?;
80    println!(
81        "Created lightweight tag: {} -> {} ({})",
82        v0_2_0.name,
83        v0_2_0.hash.short(),
84        v0_2_0.tag_type
85    );
86
87    // Create annotated tags
88    println!("\n2. Creating annotated tags:");
89
90    let options =
91        TagOptions::new().with_message("First stable release with basic functionality".to_string());
92    let v1_0_0 = repo.create_tag_with_options("v1.0.0", Some(&third_commit_hash), options)?;
93    println!(
94        "Created annotated tag: {} -> {} ({})",
95        v1_0_0.name,
96        v1_0_0.hash.short(),
97        v1_0_0.tag_type
98    );
99    if let Some(message) = &v1_0_0.message {
100        println!("  Message: {}", message);
101    }
102
103    // Tag current HEAD
104    let latest_options = TagOptions::new().with_message("Latest development version".to_string());
105    let latest_tag = repo.create_tag_with_options("latest", None, latest_options)?;
106    println!(
107        "Created annotated tag on HEAD: {} -> {} ({})",
108        latest_tag.name,
109        latest_tag.hash.short(),
110        latest_tag.tag_type
111    );
112
113    // Create some feature tags
114    println!("\n3. Creating feature and release candidate tags:");
115
116    let feature_options = TagOptions::new().with_message("Feature branch snapshot".to_string());
117    repo.create_tag_with_options("feature/demo", None, feature_options)?;
118
119    let rc_options = TagOptions::new().with_message("Release candidate for v1.1.0".to_string());
120    repo.create_tag_with_options("v1.1.0-rc1", None, rc_options)?;
121
122    // Create a couple more version tags
123    repo.create_tag("v0.3.0", None)?;
124    repo.create_tag("v0.9.0", None)?;
125
126    // Demonstrate tag listing and filtering
127    println!("\n=== Tag Listing and Filtering ===");
128
129    let tags = repo.tags()?;
130    println!("\nAll tags ({} total):", tags.len());
131    for tag in tags.iter() {
132        let type_marker = match tag.tag_type {
133            TagType::Lightweight => "L",
134            TagType::Annotated => "A",
135        };
136        println!("  [{}] {} -> {}", type_marker, tag.name, tag.hash.short());
137        if let Some(message) = &tag.message {
138            println!("      Message: {}", message.lines().next().unwrap_or(""));
139        }
140    }
141
142    // Filter by type
143    println!("\nLightweight tags ({} total):", tags.lightweight_count());
144    for tag in tags.lightweight() {
145        println!("  {} -> {}", tag.name, tag.hash.short());
146    }
147
148    println!("\nAnnotated tags ({} total):", tags.annotated_count());
149    for tag in tags.annotated() {
150        println!("  {} -> {}", tag.name, tag.hash.short());
151        if let Some(message) = &tag.message {
152            println!("    Message: {}", message.lines().next().unwrap_or(""));
153        }
154    }
155
156    // Search and filtering
157    println!("\n=== Tag Searching ===");
158
159    // Find specific tag
160    if let Some(tag) = tags.find("v1.0.0") {
161        println!("\nFound tag 'v1.0.0':");
162        println!("  Type: {}", tag.tag_type);
163        println!("  Hash: {}", tag.hash.short());
164        if let Some(message) = &tag.message {
165            println!("  Message: {}", message);
166        }
167    }
168
169    // Find version tags
170    let version_tags: Vec<_> = tags.find_containing("v").collect();
171    println!(
172        "\nVersion tags (containing 'v'): {} found",
173        version_tags.len()
174    );
175    for tag in &version_tags {
176        println!("  {}", tag.name);
177    }
178
179    // Find release candidates
180    let rc_tags: Vec<_> = tags.find_containing("rc").collect();
181    println!("\nRelease candidate tags: {} found", rc_tags.len());
182    for tag in &rc_tags {
183        println!("  {}", tag.name);
184    }
185
186    // Find tags for specific commit
187    let tags_for_third_commit: Vec<_> = tags.for_commit(&third_commit_hash).collect();
188    println!(
189        "\nTags pointing to commit {}: {} found",
190        third_commit_hash.short(),
191        tags_for_third_commit.len()
192    );
193    for tag in &tags_for_third_commit {
194        println!("  {}", tag.name);
195    }
196
197    // Demonstrate tag details
198    println!("\n=== Tag Details ===");
199
200    let detailed_tag = repo.show_tag("v1.0.0")?;
201    println!("\nDetailed information for 'v1.0.0':");
202    println!("  Name: {}", detailed_tag.name);
203    println!("  Type: {}", detailed_tag.tag_type);
204    println!("  Commit: {}", detailed_tag.hash);
205    println!("  Short hash: {}", detailed_tag.hash.short());
206
207    if let Some(message) = &detailed_tag.message {
208        println!("  Message: {}", message);
209    }
210
211    if let Some(tagger) = &detailed_tag.tagger {
212        println!("  Tagger: {}", tagger);
213        println!(
214            "  Tagged at: {}",
215            tagger.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
216        );
217    }
218
219    if let Some(timestamp) = &detailed_tag.timestamp {
220        println!("  Timestamp: {}", timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
221    }
222
223    // Demonstrate tag operations
224    println!("\n=== Tag Operations ===");
225
226    // Create and force overwrite a tag
227    println!("\n1. Testing tag overwrite:");
228
229    // This should fail (tag already exists)
230    match repo.create_tag("latest", None) {
231        Ok(_) => println!("  ERROR: Should have failed to create existing tag"),
232        Err(e) => println!("  Expected error creating existing tag: {}", e),
233    }
234
235    // Force overwrite
236    let force_options = TagOptions::new()
237        .with_force()
238        .with_message("Forcefully updated latest tag".to_string());
239
240    match repo.create_tag_with_options("latest", None, force_options) {
241        Ok(tag) => println!("  Successfully force-created tag: {}", tag.name),
242        Err(e) => println!("  Error force-creating tag: {}", e),
243    }
244
245    // Tag deletion
246    println!("\n2. Testing tag deletion:");
247
248    // Create a temporary tag to delete
249    repo.create_tag("temp-tag", None)?;
250    println!("  Created temporary tag: temp-tag");
251
252    // Verify it exists
253    let tags_before = repo.tags()?;
254    let temp_exists_before = tags_before.find("temp-tag").is_some();
255    println!("  Temp tag exists before deletion: {}", temp_exists_before);
256
257    // Delete it
258    repo.delete_tag("temp-tag")?;
259    println!("  Deleted temp-tag");
260
261    // Verify it's gone
262    let tags_after = repo.tags()?;
263    let temp_exists_after = tags_after.find("temp-tag").is_some();
264    println!("  Temp tag exists after deletion: {}", temp_exists_after);
265
266    // Summary
267    println!("\n=== Summary ===");
268    let final_tags = repo.tags()?;
269    println!("\nFinal repository state:");
270    println!("  Total tags: {}", final_tags.len());
271    println!("  Lightweight tags: {}", final_tags.lightweight_count());
272    println!("  Annotated tags: {}", final_tags.annotated_count());
273
274    println!("\nTag creation options demonstrated:");
275    println!("  ✓ Lightweight tags (simple references)");
276    println!("  ✓ Annotated tags (with messages and metadata)");
277    println!("  ✓ Tags on specific commits");
278    println!("  ✓ Tags on current HEAD");
279    println!("  ✓ Force tag creation/overwrite");
280
281    println!("\nTag listing and filtering demonstrated:");
282    println!("  ✓ List all tags");
283    println!("  ✓ Filter by tag type (lightweight/annotated)");
284    println!("  ✓ Search by name patterns");
285    println!("  ✓ Find tags by commit hash");
286    println!("  ✓ Show detailed tag information");
287
288    println!("\nTag management demonstrated:");
289    println!("  ✓ Tag creation with options");
290    println!("  ✓ Tag deletion");
291    println!("  ✓ Error handling for duplicate tags");
292
293    // Clean up
294    println!("\nCleaning up example repository...");
295    fs::remove_dir_all(&repo_path)?;
296    println!("Tag operations example completed successfully!");
297
298    Ok(())
299}
Source§

impl Repository

Source

pub fn ensure_git() -> Result<()>

Ensure that Git is available in the system PATH.

This function checks if the git command is available in the system PATH. The result is cached, so subsequent calls are very fast. If Git is not found, it returns a GitError::CommandFailed with an appropriate error message.

§Returns

A Result containing either Ok(()) if Git is available or a GitError.

Source

pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>

Open an existing Git repository at the specified path.

§Arguments
  • path - The path to an existing Git repository.
§Returns

A Result containing either the opened Repository instance or a GitError.

Examples found in repository?
examples/error_handling.rs (line 49)
44fn demonstrate_repository_errors(repo_path: &std::path::Path) -> Result<()> {
45    println!("Repository Error Scenarios:\n");
46
47    // 1. Opening non-existent repository
48    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    // 2. Opening a file as a repository
62    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    // 3. Initializing repository with invalid path
80    println!("\n3. Attempting to initialize repository with problematic path:");
81
82    // Try to initialize in a location that might cause issues
83    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
99/// Demonstrate file operation related errors
100fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
101    println!("File Operation Error Scenarios:\n");
102
103    // Set up a valid repository first
104    let repo = Repository::init(repo_path, false)?;
105
106    // Create some test files
107    fs::write(repo_path.join("test.txt"), "Test content")?;
108    repo.add(&["test.txt"])?;
109    repo.commit("Initial commit")?;
110
111    // 1. Adding non-existent files
112    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    // 2. Mixed valid and invalid files
125    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            // Check what actually got staged
132            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            // Try recovery: add valid files individually
140            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
155/// Demonstrate Git command related errors
156fn 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    // 1. Empty commit (no staged changes)
162    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    // 2. Commit with problematic message
178    println!("\n2. Testing commit message edge cases:");
179
180    // Stage a file for testing
181    fs::write(
182        repo_path.join("commit_test.txt"),
183        "Content for commit testing",
184    )?;
185    repo.add(&["commit_test.txt"])?;
186
187    // Very long commit message
188    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
206/// Demonstrate error recovery patterns
207fn 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    // Pattern 1: Retry with different approach
213    println!("1. Retry Pattern - Graceful degradation:");
214
215    // Try to add specific files, fall back to add_all on failure
216    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    // Pattern 2: Partial success handling
241    println!("\n2. Partial Success Pattern:");
242
243    // Create some files with known issues
244    fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245    fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246    // Don't create bad1.txt - it will be missing
247
248    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    // Pattern 3: Status checking before operations
284    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        // Show what would be committed
295        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        // Safe commit since we know there are changes
305        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
315/// Demonstrate error propagation strategies
316fn demonstrate_error_propagation_strategies(base_path: &std::path::Path) -> Result<()> {
317    println!("Error Propagation Strategies:\n");
318
319    // Strategy 1: Early return with ?
320    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    // Strategy 2: Collect all errors
327    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    // Strategy 3: Error context enrichment
346    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
356/// Workflow that returns early on first error
357fn workflow_with_early_return(base_path: &std::path::Path) -> Result<String> {
358    let repo_path = base_path.join("early_return_test");
359
360    // This will propagate any error immediately
361    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    // Clean up
369    fs::remove_dir_all(&repo_path)?;
370
371    Ok(format!("Completed with commit {}", hash.short()))
372}
373
374/// Workflow that collects all errors instead of failing fast
375fn 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    // Step 1: Initialize repo
380    results.push(Repository::init(&repo_path, false).map(|_| "Repository initialized".to_string()));
381
382    // Step 2: Add files (some may fail)
383    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    // Step 3: Try to add files (continue even if repo init failed)
394    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    // Cleanup (don't add to results as it's not part of main workflow)
414    let _ = fs::remove_dir_all(&repo_path);
415
416    results
417}
More examples
Hide additional examples
examples/repository_operations.rs (line 65)
15fn main() -> Result<()> {
16    println!("Rustic Git - Repository Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_repo_example");
19    let regular_repo_path = base_path.join("regular");
20    let bare_repo_path = base_path.join("bare");
21    let nonexistent_path = base_path.join("nonexistent");
22
23    // Clean up any previous runs
24    if base_path.exists() {
25        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
26    }
27    fs::create_dir_all(&base_path)?;
28
29    println!("=== Repository Initialization ===\n");
30
31    // 1. Initialize a regular repository
32    println!("Initializing regular repository...");
33    let regular_repo = Repository::init(&regular_repo_path, false)?;
34    println!(
35        "Regular repository created at: {}",
36        regular_repo_path.display()
37    );
38    println!("   Repository path: {:?}", regular_repo.repo_path());
39
40    // Verify it's a git repo by checking for .git directory
41    if regular_repo_path.join(".git").exists() {
42        println!("   .git directory found");
43    }
44    println!();
45
46    // 2. Initialize a bare repository
47    println!("Initializing bare repository...");
48    let bare_repo = Repository::init(&bare_repo_path, true)?;
49    println!("Bare repository created at: {}", bare_repo_path.display());
50    println!("   Repository path: {:?}", bare_repo.repo_path());
51
52    // Verify bare repo structure (has HEAD, objects, etc. directly)
53    if bare_repo_path.join("HEAD").exists() {
54        println!("   HEAD file found (bare repository structure)");
55    }
56    if bare_repo_path.join("objects").exists() {
57        println!("   objects directory found");
58    }
59    println!();
60
61    println!("=== Repository Opening ===\n");
62
63    // 3. Open the existing regular repository
64    println!("Opening existing regular repository...");
65    match Repository::open(&regular_repo_path) {
66        Ok(opened_repo) => {
67            println!("Successfully opened regular repository");
68            println!("   Repository path: {:?}", opened_repo.repo_path());
69
70            // Test that we can perform operations on the opened repo
71            let status = opened_repo.status()?;
72            println!("   Repository status: {} files", status.entries.len());
73        }
74        Err(e) => {
75            println!("Failed to open regular repository: {:?}", e);
76        }
77    }
78    println!();
79
80    // 4. Open the existing bare repository
81    println!("Opening existing bare repository...");
82    match Repository::open(&bare_repo_path) {
83        Ok(opened_bare) => {
84            println!("Successfully opened bare repository");
85            println!("   Repository path: {:?}", opened_bare.repo_path());
86
87            // Note: status operations might behave differently on bare repos
88            match opened_bare.status() {
89                Ok(status) => println!("   Bare repository status: {} files", status.entries.len()),
90                Err(e) => println!(
91                    "   Note: Status check on bare repo failed (expected): {:?}",
92                    e
93                ),
94            }
95        }
96        Err(e) => {
97            println!("Failed to open bare repository: {:?}", e);
98        }
99    }
100    println!();
101
102    println!("=== Error Handling ===\n");
103
104    // 5. Try to open a non-existent repository
105    println!("Attempting to open non-existent repository...");
106    match Repository::open(&nonexistent_path) {
107        Ok(_repo) => {
108            println!("Unexpectedly succeeded opening non-existent repo");
109        }
110        Err(GitError::CommandFailed(msg)) => {
111            println!("Expected error caught: CommandFailed");
112            println!("   Error message: {}", msg);
113        }
114        Err(GitError::IoError(msg)) => {
115            println!("Expected error caught: IoError");
116            println!("   Error message: {}", msg);
117        }
118    }
119    println!();
120
121    // 6. Try to open a regular file as a repository
122    let fake_repo_path = base_path.join("fake.txt");
123    fs::write(&fake_repo_path, "This is not a git repository")?;
124
125    println!("Attempting to open regular file as repository...");
126    match Repository::open(&fake_repo_path) {
127        Ok(_repo) => {
128            println!("Unexpectedly succeeded opening regular file as repo");
129        }
130        Err(GitError::CommandFailed(msg)) => {
131            println!("Expected error caught: CommandFailed");
132            println!("   Error message: {}", msg);
133        }
134        Err(GitError::IoError(msg)) => {
135            println!("Expected error caught: IoError");
136            println!("   Error message: {}", msg);
137        }
138    }
139    println!();
140
141    println!("=== Repository Information ===\n");
142
143    // 7. Compare regular vs bare repository information
144    println!("Comparing repository types:");
145
146    let regular_path = regular_repo.repo_path();
147    let bare_path = bare_repo.repo_path();
148
149    println!("   Regular repo path: {:?}", regular_path);
150    println!("   Bare repo path: {:?}", bare_path);
151
152    // Show directory contents
153    if let Ok(entries) = fs::read_dir(regular_path) {
154        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
155        files.sort_by_key(|e| e.file_name());
156
157        println!("   Regular repo contents:");
158        for entry in files {
159            if let Some(name) = entry.file_name().to_str() {
160                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
161                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
162                println!("     {} {}", marker, name);
163            }
164        }
165    }
166
167    if let Ok(entries) = fs::read_dir(bare_path) {
168        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
169        files.sort_by_key(|e| e.file_name());
170
171        println!("   Bare repo contents:");
172        for entry in files {
173            if let Some(name) = entry.file_name().to_str() {
174                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
175                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
176                println!("     {} {}", marker, name);
177            }
178        }
179    }
180    println!();
181
182    // Clean up
183    println!("Cleaning up example repositories...");
184    fs::remove_dir_all(&base_path)?;
185    println!("Repository operations example completed!");
186
187    Ok(())
188}
Source

pub fn init<P: AsRef<Path>>(path: P, bare: bool) -> Result<Self>

Initialize a new Git repository at the specified path.

§Arguments
  • path - The path where the repository should be initialized.
  • bare - Whether the repository should be bare or not.
§Returns

A Result containing either the initialized Repository instance or a GitError.

Examples found in repository?
examples/reset_operations.rs (line 27)
13fn main() -> Result<()> {
14    println!("=== Reset Operations Demo ===\n");
15
16    // Create a temporary directory for our example
17    let temp_dir = env::temp_dir().join("rustic_git_reset_demo");
18
19    // Clean up if exists
20    if temp_dir.exists() {
21        fs::remove_dir_all(&temp_dir)?;
22    }
23
24    println!("Working in temporary directory: {:?}\n", temp_dir);
25
26    // Initialize a new repository
27    let repo = Repository::init(&temp_dir, false)?;
28
29    // Configure user for commits
30    repo.config().set_user("Example User", "example@test.com")?;
31
32    demonstrate_reset_modes(&repo, &temp_dir)?;
33    demonstrate_file_resets(&repo, &temp_dir)?;
34    demonstrate_error_handling(&repo)?;
35
36    println!("\n=== Reset Operations Demo Complete ===");
37
38    // Clean up
39    fs::remove_dir_all(&temp_dir)?;
40    Ok(())
41}
More examples
Hide additional examples
examples/merge_operations.rs (line 28)
14fn main() -> Result<()> {
15    println!("=== Merge Operations Demo ===\n");
16
17    // Create a temporary directory for our example
18    let temp_dir = env::temp_dir().join("rustic_git_merge_demo");
19
20    // Clean up if exists
21    if temp_dir.exists() {
22        fs::remove_dir_all(&temp_dir)?;
23    }
24
25    println!("Working in temporary directory: {:?}\n", temp_dir);
26
27    // Initialize a new repository
28    let repo = Repository::init(&temp_dir, false)?;
29
30    // Configure user for commits
31    repo.config().set_user("Example User", "example@test.com")?;
32
33    demonstrate_fast_forward_merge(&repo, &temp_dir)?;
34    demonstrate_no_fast_forward_merge(&repo, &temp_dir)?;
35    demonstrate_merge_conflicts(&repo, &temp_dir)?;
36    demonstrate_merge_status_and_abort(&repo, &temp_dir)?;
37
38    println!("\n=== Merge Operations Demo Complete ===");
39
40    // Clean up
41    fs::remove_dir_all(&temp_dir)?;
42    Ok(())
43}
examples/error_handling.rs (line 83)
44fn demonstrate_repository_errors(repo_path: &std::path::Path) -> Result<()> {
45    println!("Repository Error Scenarios:\n");
46
47    // 1. Opening non-existent repository
48    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    // 2. Opening a file as a repository
62    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    // 3. Initializing repository with invalid path
80    println!("\n3. Attempting to initialize repository with problematic path:");
81
82    // Try to initialize in a location that might cause issues
83    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
99/// Demonstrate file operation related errors
100fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
101    println!("File Operation Error Scenarios:\n");
102
103    // Set up a valid repository first
104    let repo = Repository::init(repo_path, false)?;
105
106    // Create some test files
107    fs::write(repo_path.join("test.txt"), "Test content")?;
108    repo.add(&["test.txt"])?;
109    repo.commit("Initial commit")?;
110
111    // 1. Adding non-existent files
112    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    // 2. Mixed valid and invalid files
125    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            // Check what actually got staged
132            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            // Try recovery: add valid files individually
140            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
155/// Demonstrate Git command related errors
156fn 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    // 1. Empty commit (no staged changes)
162    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    // 2. Commit with problematic message
178    println!("\n2. Testing commit message edge cases:");
179
180    // Stage a file for testing
181    fs::write(
182        repo_path.join("commit_test.txt"),
183        "Content for commit testing",
184    )?;
185    repo.add(&["commit_test.txt"])?;
186
187    // Very long commit message
188    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
206/// Demonstrate error recovery patterns
207fn 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    // Pattern 1: Retry with different approach
213    println!("1. Retry Pattern - Graceful degradation:");
214
215    // Try to add specific files, fall back to add_all on failure
216    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    // Pattern 2: Partial success handling
241    println!("\n2. Partial Success Pattern:");
242
243    // Create some files with known issues
244    fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245    fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246    // Don't create bad1.txt - it will be missing
247
248    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    // Pattern 3: Status checking before operations
284    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        // Show what would be committed
295        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        // Safe commit since we know there are changes
305        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
315/// Demonstrate error propagation strategies
316fn demonstrate_error_propagation_strategies(base_path: &std::path::Path) -> Result<()> {
317    println!("Error Propagation Strategies:\n");
318
319    // Strategy 1: Early return with ?
320    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    // Strategy 2: Collect all errors
327    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    // Strategy 3: Error context enrichment
346    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
356/// Workflow that returns early on first error
357fn workflow_with_early_return(base_path: &std::path::Path) -> Result<String> {
358    let repo_path = base_path.join("early_return_test");
359
360    // This will propagate any error immediately
361    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    // Clean up
369    fs::remove_dir_all(&repo_path)?;
370
371    Ok(format!("Completed with commit {}", hash.short()))
372}
373
374/// Workflow that collects all errors instead of failing fast
375fn 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    // Step 1: Initialize repo
380    results.push(Repository::init(&repo_path, false).map(|_| "Repository initialized".to_string()));
381
382    // Step 2: Add files (some may fail)
383    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    // Step 3: Try to add files (continue even if repo init failed)
394    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    // Cleanup (don't add to results as it's not part of main workflow)
414    let _ = fs::remove_dir_all(&repo_path);
415
416    results
417}
418
419/// Workflow with enhanced error context
420fn workflow_with_context(base_path: &std::path::Path) -> Result<String> {
421    let repo_path = base_path.join("context_test");
422
423    // Add context to errors
424    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    // Create file with context
432    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    // Add with context
438    repo.add(&["context_file.txt"]).inspect_err(|_e| {
439        eprintln!("Context: Failed to stage context_file.txt");
440    })?;
441
442    // Commit with context
443    let hash = repo.commit("Context workflow commit").inspect_err(|_e| {
444        eprintln!("Context: Failed to create commit");
445    })?;
446
447    // Clean up
448    fs::remove_dir_all(&repo_path)?;
449
450    Ok(format!("Context workflow completed: {}", hash.short()))
451}
examples/basic_usage.rs (line 30)
16fn main() -> Result<()> {
17    println!("Rustic Git - Basic Usage Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing new repository at: {}", repo_path.display());
28
29    // Initialize a new repository
30    let repo = Repository::init(&repo_path, false)?;
31    println!("Repository initialized successfully\n");
32
33    // Create some example files
34    println!("Creating example files...");
35    fs::create_dir_all(repo_path.join("src"))?;
36
37    fs::write(
38        repo_path.join("README.md"),
39        "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40    )?;
41
42    fs::write(
43        repo_path.join("src/main.rs"),
44        r#"fn main() {
45    println!("Hello from rustic-git example!");
46}
47"#,
48    )?;
49
50    fs::write(
51        repo_path.join("src/lib.rs"),
52        "// Library code goes here\npub fn hello() -> &'static str {\n    \"Hello, World!\"\n}\n",
53    )?;
54
55    println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57    // Check repository status
58    println!("Checking repository status...");
59    let status = repo.status()?;
60
61    if status.is_clean() {
62        println!("   Repository is clean (no changes)");
63    } else {
64        println!("   Repository has changes:");
65        println!("   Unstaged files: {}", status.unstaged_files().count());
66        println!("   Untracked files: {}", status.untracked_entries().count());
67
68        // Show untracked files
69        for entry in status.untracked_entries() {
70            println!("      - {}", entry.path.display());
71        }
72    }
73    println!();
74
75    // Stage specific files first
76    println!("Staging files...");
77
78    // Stage README.md first
79    repo.add(&["README.md"])?;
80    println!("Staged README.md");
81
82    // Stage all remaining files
83    repo.add_all()?;
84    println!("Staged all remaining files");
85
86    // Check status after staging
87    let status_after_staging = repo.status()?;
88    println!("\nStatus after staging:");
89    if status_after_staging.is_clean() {
90        println!("   Repository is clean (all changes staged)");
91    } else {
92        println!(
93            "   Files staged for commit: {}",
94            status_after_staging.entries.len()
95        );
96        for entry in &status_after_staging.entries {
97            println!(
98                "      Index {:?}, Worktree {:?}: {}",
99                entry.index_status,
100                entry.worktree_status,
101                entry.path.display()
102            );
103        }
104    }
105    println!();
106
107    // Create a commit
108    println!("Creating commit...");
109    let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111    println!("Commit created successfully!");
112    println!("   Full hash: {}", hash);
113    println!("   Short hash: {}", hash.short());
114    println!();
115
116    // Verify final status
117    println!("Final repository status:");
118    let final_status = repo.status()?;
119    if final_status.is_clean() {
120        println!("   Repository is clean - all changes committed!");
121    } else {
122        println!("   Repository still has uncommitted changes");
123    }
124    println!();
125
126    // Clean up
127    println!("Cleaning up example repository...");
128    fs::remove_dir_all(&repo_path)?;
129    println!("Example completed successfully!");
130
131    Ok(())
132}
examples/branch_operations.rs (line 13)
4fn main() -> Result<()> {
5    let test_path = env::temp_dir().join("rustic_git_branch_example");
6
7    // Clean up if exists
8    if test_path.exists() {
9        fs::remove_dir_all(&test_path).unwrap();
10    }
11
12    // Create a test repository
13    let repo = Repository::init(&test_path, false)?;
14    println!("Created repository at: {}", test_path.display());
15
16    // Create initial commit so we have a valid HEAD
17    fs::write(test_path.join("README.md"), "# Branch Operations Demo\n").unwrap();
18    repo.add(&["README.md"])?;
19    repo.commit("Initial commit")?;
20    println!("Created initial commit");
21
22    // List all branches
23    let branches = repo.branches()?;
24    println!("\n=== Initial Branches ===");
25    for branch in branches.iter() {
26        println!("  {}", branch);
27    }
28
29    // Get current branch
30    if let Some(current) = repo.current_branch()? {
31        println!(
32            "\nCurrent branch: {} ({})",
33            current.name,
34            current.commit_hash.short()
35        );
36    }
37
38    // Create new branches
39    println!("\n=== Creating Branches ===");
40    let feature_branch = repo.create_branch("feature/new-api", None)?;
41    println!("Created branch: {}", feature_branch.name);
42
43    let bugfix_branch = repo.create_branch("bugfix/issue-123", Some("HEAD"))?;
44    println!("Created branch: {}", bugfix_branch.name);
45
46    // List branches again
47    let branches = repo.branches()?;
48    println!("\n=== After Creating Branches ===");
49    for branch in branches.local() {
50        println!("  {} (local)", branch);
51    }
52
53    // Create and checkout a new branch
54    println!("\n=== Creating and Checking Out Branch ===");
55    let dev_branch = repo.checkout_new("develop", None)?;
56    println!("Created and checked out: {}", dev_branch.name);
57
58    // Make a commit on the new branch
59    fs::write(test_path.join("feature.txt"), "New feature code\n").unwrap();
60    repo.add(&["feature.txt"])?;
61    repo.commit("Add new feature")?;
62    println!("Made commit on develop branch");
63
64    // Show current branch after checkout
65    if let Some(current) = repo.current_branch()? {
66        println!(
67            "Now on branch: {} ({})",
68            current.name,
69            current.commit_hash.short()
70        );
71    }
72
73    // Switch back to master branch
74    let main_branch = branches.find("master").unwrap().clone();
75    repo.checkout(&main_branch)?;
76    println!("\nSwitched back to master branch");
77
78    // List all branches with details
79    let final_branches = repo.branches()?;
80    println!("\n=== Final Branch List ===");
81    println!("Total branches: {}", final_branches.len());
82    println!("Local branches: {}", final_branches.local_count());
83
84    for branch in final_branches.iter() {
85        let marker = if branch.is_current { "*" } else { " " };
86        let branch_type = if branch.is_local() { "local" } else { "remote" };
87        println!(
88            "  {}{} ({}) {}",
89            marker,
90            branch.name,
91            branch_type,
92            branch.commit_hash.short()
93        );
94
95        if let Some(upstream) = &branch.upstream {
96            println!("    └── tracks: {}", upstream);
97        }
98    }
99
100    // Demonstrate branch searching
101    println!("\n=== Branch Search Examples ===");
102
103    if let Some(branch) = final_branches.find("develop") {
104        println!("Found branch by name: {}", branch.name);
105    }
106
107    if let Some(branch) = final_branches.find_by_short_name("new-api") {
108        println!("Found branch by short name: {}", branch.name);
109    }
110
111    // Demonstrate branch filtering
112    println!("\n=== Branch Filtering ===");
113
114    println!("Local branches:");
115    for branch in final_branches.local() {
116        println!("  - {}", branch.name);
117    }
118
119    if final_branches.remote_count() > 0 {
120        println!("Remote branches:");
121        for branch in final_branches.remote() {
122            println!("  - {}", branch.name);
123        }
124    }
125
126    // Delete a branch (switch away first if it's current)
127    println!("\n=== Branch Deletion ===");
128    let bugfix = final_branches.find("bugfix/issue-123").unwrap().clone();
129    repo.delete_branch(&bugfix, false)?;
130    println!("Deleted branch: {}", bugfix.name);
131
132    // Show final state
133    let final_branches = repo.branches()?;
134    println!("\nFinal branch count: {}", final_branches.len());
135
136    // Clean up
137    fs::remove_dir_all(&test_path).unwrap();
138    println!("\nCleaned up test repository");
139
140    Ok(())
141}
examples/repository_operations.rs (line 33)
15fn main() -> Result<()> {
16    println!("Rustic Git - Repository Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_repo_example");
19    let regular_repo_path = base_path.join("regular");
20    let bare_repo_path = base_path.join("bare");
21    let nonexistent_path = base_path.join("nonexistent");
22
23    // Clean up any previous runs
24    if base_path.exists() {
25        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
26    }
27    fs::create_dir_all(&base_path)?;
28
29    println!("=== Repository Initialization ===\n");
30
31    // 1. Initialize a regular repository
32    println!("Initializing regular repository...");
33    let regular_repo = Repository::init(&regular_repo_path, false)?;
34    println!(
35        "Regular repository created at: {}",
36        regular_repo_path.display()
37    );
38    println!("   Repository path: {:?}", regular_repo.repo_path());
39
40    // Verify it's a git repo by checking for .git directory
41    if regular_repo_path.join(".git").exists() {
42        println!("   .git directory found");
43    }
44    println!();
45
46    // 2. Initialize a bare repository
47    println!("Initializing bare repository...");
48    let bare_repo = Repository::init(&bare_repo_path, true)?;
49    println!("Bare repository created at: {}", bare_repo_path.display());
50    println!("   Repository path: {:?}", bare_repo.repo_path());
51
52    // Verify bare repo structure (has HEAD, objects, etc. directly)
53    if bare_repo_path.join("HEAD").exists() {
54        println!("   HEAD file found (bare repository structure)");
55    }
56    if bare_repo_path.join("objects").exists() {
57        println!("   objects directory found");
58    }
59    println!();
60
61    println!("=== Repository Opening ===\n");
62
63    // 3. Open the existing regular repository
64    println!("Opening existing regular repository...");
65    match Repository::open(&regular_repo_path) {
66        Ok(opened_repo) => {
67            println!("Successfully opened regular repository");
68            println!("   Repository path: {:?}", opened_repo.repo_path());
69
70            // Test that we can perform operations on the opened repo
71            let status = opened_repo.status()?;
72            println!("   Repository status: {} files", status.entries.len());
73        }
74        Err(e) => {
75            println!("Failed to open regular repository: {:?}", e);
76        }
77    }
78    println!();
79
80    // 4. Open the existing bare repository
81    println!("Opening existing bare repository...");
82    match Repository::open(&bare_repo_path) {
83        Ok(opened_bare) => {
84            println!("Successfully opened bare repository");
85            println!("   Repository path: {:?}", opened_bare.repo_path());
86
87            // Note: status operations might behave differently on bare repos
88            match opened_bare.status() {
89                Ok(status) => println!("   Bare repository status: {} files", status.entries.len()),
90                Err(e) => println!(
91                    "   Note: Status check on bare repo failed (expected): {:?}",
92                    e
93                ),
94            }
95        }
96        Err(e) => {
97            println!("Failed to open bare repository: {:?}", e);
98        }
99    }
100    println!();
101
102    println!("=== Error Handling ===\n");
103
104    // 5. Try to open a non-existent repository
105    println!("Attempting to open non-existent repository...");
106    match Repository::open(&nonexistent_path) {
107        Ok(_repo) => {
108            println!("Unexpectedly succeeded opening non-existent repo");
109        }
110        Err(GitError::CommandFailed(msg)) => {
111            println!("Expected error caught: CommandFailed");
112            println!("   Error message: {}", msg);
113        }
114        Err(GitError::IoError(msg)) => {
115            println!("Expected error caught: IoError");
116            println!("   Error message: {}", msg);
117        }
118    }
119    println!();
120
121    // 6. Try to open a regular file as a repository
122    let fake_repo_path = base_path.join("fake.txt");
123    fs::write(&fake_repo_path, "This is not a git repository")?;
124
125    println!("Attempting to open regular file as repository...");
126    match Repository::open(&fake_repo_path) {
127        Ok(_repo) => {
128            println!("Unexpectedly succeeded opening regular file as repo");
129        }
130        Err(GitError::CommandFailed(msg)) => {
131            println!("Expected error caught: CommandFailed");
132            println!("   Error message: {}", msg);
133        }
134        Err(GitError::IoError(msg)) => {
135            println!("Expected error caught: IoError");
136            println!("   Error message: {}", msg);
137        }
138    }
139    println!();
140
141    println!("=== Repository Information ===\n");
142
143    // 7. Compare regular vs bare repository information
144    println!("Comparing repository types:");
145
146    let regular_path = regular_repo.repo_path();
147    let bare_path = bare_repo.repo_path();
148
149    println!("   Regular repo path: {:?}", regular_path);
150    println!("   Bare repo path: {:?}", bare_path);
151
152    // Show directory contents
153    if let Ok(entries) = fs::read_dir(regular_path) {
154        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
155        files.sort_by_key(|e| e.file_name());
156
157        println!("   Regular repo contents:");
158        for entry in files {
159            if let Some(name) = entry.file_name().to_str() {
160                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
161                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
162                println!("     {} {}", marker, name);
163            }
164        }
165    }
166
167    if let Ok(entries) = fs::read_dir(bare_path) {
168        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
169        files.sort_by_key(|e| e.file_name());
170
171        println!("   Bare repo contents:");
172        for entry in files {
173            if let Some(name) = entry.file_name().to_str() {
174                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
175                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
176                println!("     {} {}", marker, name);
177            }
178        }
179    }
180    println!();
181
182    // Clean up
183    println!("Cleaning up example repositories...");
184    fs::remove_dir_all(&base_path)?;
185    println!("Repository operations example completed!");
186
187    Ok(())
188}
Source

pub fn repo_path(&self) -> &Path

Examples found in repository?
examples/repository_operations.rs (line 38)
15fn main() -> Result<()> {
16    println!("Rustic Git - Repository Operations Example\n");
17
18    let base_path = env::temp_dir().join("rustic_git_repo_example");
19    let regular_repo_path = base_path.join("regular");
20    let bare_repo_path = base_path.join("bare");
21    let nonexistent_path = base_path.join("nonexistent");
22
23    // Clean up any previous runs
24    if base_path.exists() {
25        fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
26    }
27    fs::create_dir_all(&base_path)?;
28
29    println!("=== Repository Initialization ===\n");
30
31    // 1. Initialize a regular repository
32    println!("Initializing regular repository...");
33    let regular_repo = Repository::init(&regular_repo_path, false)?;
34    println!(
35        "Regular repository created at: {}",
36        regular_repo_path.display()
37    );
38    println!("   Repository path: {:?}", regular_repo.repo_path());
39
40    // Verify it's a git repo by checking for .git directory
41    if regular_repo_path.join(".git").exists() {
42        println!("   .git directory found");
43    }
44    println!();
45
46    // 2. Initialize a bare repository
47    println!("Initializing bare repository...");
48    let bare_repo = Repository::init(&bare_repo_path, true)?;
49    println!("Bare repository created at: {}", bare_repo_path.display());
50    println!("   Repository path: {:?}", bare_repo.repo_path());
51
52    // Verify bare repo structure (has HEAD, objects, etc. directly)
53    if bare_repo_path.join("HEAD").exists() {
54        println!("   HEAD file found (bare repository structure)");
55    }
56    if bare_repo_path.join("objects").exists() {
57        println!("   objects directory found");
58    }
59    println!();
60
61    println!("=== Repository Opening ===\n");
62
63    // 3. Open the existing regular repository
64    println!("Opening existing regular repository...");
65    match Repository::open(&regular_repo_path) {
66        Ok(opened_repo) => {
67            println!("Successfully opened regular repository");
68            println!("   Repository path: {:?}", opened_repo.repo_path());
69
70            // Test that we can perform operations on the opened repo
71            let status = opened_repo.status()?;
72            println!("   Repository status: {} files", status.entries.len());
73        }
74        Err(e) => {
75            println!("Failed to open regular repository: {:?}", e);
76        }
77    }
78    println!();
79
80    // 4. Open the existing bare repository
81    println!("Opening existing bare repository...");
82    match Repository::open(&bare_repo_path) {
83        Ok(opened_bare) => {
84            println!("Successfully opened bare repository");
85            println!("   Repository path: {:?}", opened_bare.repo_path());
86
87            // Note: status operations might behave differently on bare repos
88            match opened_bare.status() {
89                Ok(status) => println!("   Bare repository status: {} files", status.entries.len()),
90                Err(e) => println!(
91                    "   Note: Status check on bare repo failed (expected): {:?}",
92                    e
93                ),
94            }
95        }
96        Err(e) => {
97            println!("Failed to open bare repository: {:?}", e);
98        }
99    }
100    println!();
101
102    println!("=== Error Handling ===\n");
103
104    // 5. Try to open a non-existent repository
105    println!("Attempting to open non-existent repository...");
106    match Repository::open(&nonexistent_path) {
107        Ok(_repo) => {
108            println!("Unexpectedly succeeded opening non-existent repo");
109        }
110        Err(GitError::CommandFailed(msg)) => {
111            println!("Expected error caught: CommandFailed");
112            println!("   Error message: {}", msg);
113        }
114        Err(GitError::IoError(msg)) => {
115            println!("Expected error caught: IoError");
116            println!("   Error message: {}", msg);
117        }
118    }
119    println!();
120
121    // 6. Try to open a regular file as a repository
122    let fake_repo_path = base_path.join("fake.txt");
123    fs::write(&fake_repo_path, "This is not a git repository")?;
124
125    println!("Attempting to open regular file as repository...");
126    match Repository::open(&fake_repo_path) {
127        Ok(_repo) => {
128            println!("Unexpectedly succeeded opening regular file as repo");
129        }
130        Err(GitError::CommandFailed(msg)) => {
131            println!("Expected error caught: CommandFailed");
132            println!("   Error message: {}", msg);
133        }
134        Err(GitError::IoError(msg)) => {
135            println!("Expected error caught: IoError");
136            println!("   Error message: {}", msg);
137        }
138    }
139    println!();
140
141    println!("=== Repository Information ===\n");
142
143    // 7. Compare regular vs bare repository information
144    println!("Comparing repository types:");
145
146    let regular_path = regular_repo.repo_path();
147    let bare_path = bare_repo.repo_path();
148
149    println!("   Regular repo path: {:?}", regular_path);
150    println!("   Bare repo path: {:?}", bare_path);
151
152    // Show directory contents
153    if let Ok(entries) = fs::read_dir(regular_path) {
154        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
155        files.sort_by_key(|e| e.file_name());
156
157        println!("   Regular repo contents:");
158        for entry in files {
159            if let Some(name) = entry.file_name().to_str() {
160                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
161                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
162                println!("     {} {}", marker, name);
163            }
164        }
165    }
166
167    if let Ok(entries) = fs::read_dir(bare_path) {
168        let mut files: Vec<_> = entries.filter_map(|e| e.ok()).collect();
169        files.sort_by_key(|e| e.file_name());
170
171        println!("   Bare repo contents:");
172        for entry in files {
173            if let Some(name) = entry.file_name().to_str() {
174                let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
175                let marker = if is_dir { "[DIR]" } else { "[FILE]" };
176                println!("     {} {}", marker, name);
177            }
178        }
179    }
180    println!();
181
182    // Clean up
183    println!("Cleaning up example repositories...");
184    fs::remove_dir_all(&base_path)?;
185    println!("Repository operations example completed!");
186
187    Ok(())
188}
Source

pub fn config(&self) -> RepoConfig<'_>

Get a configuration manager for this repository

Returns a RepoConfig instance that can be used to get and set git configuration values for this repository.

§Example
use rustic_git::Repository;
use std::env;

let test_path = env::temp_dir().join("test");
let repo = Repository::init(&test_path, false)?;
repo.config().set_user("John Doe", "john@example.com")?;

let (name, email) = repo.config().get_user()?;
assert_eq!(name, "John Doe");
assert_eq!(email, "john@example.com");
Examples found in repository?
examples/reset_operations.rs (line 30)
13fn main() -> Result<()> {
14    println!("=== Reset Operations Demo ===\n");
15
16    // Create a temporary directory for our example
17    let temp_dir = env::temp_dir().join("rustic_git_reset_demo");
18
19    // Clean up if exists
20    if temp_dir.exists() {
21        fs::remove_dir_all(&temp_dir)?;
22    }
23
24    println!("Working in temporary directory: {:?}\n", temp_dir);
25
26    // Initialize a new repository
27    let repo = Repository::init(&temp_dir, false)?;
28
29    // Configure user for commits
30    repo.config().set_user("Example User", "example@test.com")?;
31
32    demonstrate_reset_modes(&repo, &temp_dir)?;
33    demonstrate_file_resets(&repo, &temp_dir)?;
34    demonstrate_error_handling(&repo)?;
35
36    println!("\n=== Reset Operations Demo Complete ===");
37
38    // Clean up
39    fs::remove_dir_all(&temp_dir)?;
40    Ok(())
41}
More examples
Hide additional examples
examples/merge_operations.rs (line 31)
14fn main() -> Result<()> {
15    println!("=== Merge Operations Demo ===\n");
16
17    // Create a temporary directory for our example
18    let temp_dir = env::temp_dir().join("rustic_git_merge_demo");
19
20    // Clean up if exists
21    if temp_dir.exists() {
22        fs::remove_dir_all(&temp_dir)?;
23    }
24
25    println!("Working in temporary directory: {:?}\n", temp_dir);
26
27    // Initialize a new repository
28    let repo = Repository::init(&temp_dir, false)?;
29
30    // Configure user for commits
31    repo.config().set_user("Example User", "example@test.com")?;
32
33    demonstrate_fast_forward_merge(&repo, &temp_dir)?;
34    demonstrate_no_fast_forward_merge(&repo, &temp_dir)?;
35    demonstrate_merge_conflicts(&repo, &temp_dir)?;
36    demonstrate_merge_status_and_abort(&repo, &temp_dir)?;
37
38    println!("\n=== Merge Operations Demo Complete ===");
39
40    // Clean up
41    fs::remove_dir_all(&temp_dir)?;
42    Ok(())
43}
examples/config_operations.rs (line 35)
14fn main() -> Result<()> {
15    println!("Rustic Git - Repository Configuration Operations Example\n");
16
17    // Use a temporary directory for this example
18    let repo_path = env::temp_dir().join("rustic_git_config_example");
19
20    // Clean up any previous run
21    if repo_path.exists() {
22        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
23    }
24
25    println!("Initializing new repository at: {}", repo_path.display());
26
27    // Initialize a new repository
28    let repo = Repository::init(&repo_path, false)?;
29
30    // ==================== USER CONFIGURATION ====================
31
32    println!("\n[CONFIG] Configuring git user settings...");
33
34    // Set user configuration (convenience method)
35    repo.config()
36        .set_user("Alice Developer", "alice@example.com")?;
37    println!("Set user configuration");
38
39    // Verify user configuration
40    let (name, email) = repo.config().get_user()?;
41    println!("Current user: {} <{}>", name, email);
42
43    // ==================== GENERAL CONFIGURATION ====================
44
45    println!("\n[CONFIG] Setting repository configuration values...");
46
47    // Set various git configuration values
48    repo.config().set("core.autocrlf", "false")?;
49    repo.config().set("core.ignorecase", "true")?;
50    repo.config().set("pull.rebase", "true")?;
51    repo.config().set("push.default", "simple")?;
52    repo.config().set("branch.autosetupmerge", "always")?;
53
54    println!("Set core configuration values");
55
56    // Get and display configuration values
57    println!("\n[CONFIG] Current repository configuration:");
58
59    let configs = [
60        "core.autocrlf",
61        "core.ignorecase",
62        "pull.rebase",
63        "push.default",
64        "branch.autosetupmerge",
65    ];
66
67    for config_key in &configs {
68        match repo.config().get(config_key) {
69            Ok(value) => println!("  {} = {}", config_key, value),
70            Err(_) => println!("  {} = <not set>", config_key),
71        }
72    }
73
74    // ==================== CONFIGURATION WITH COMMITS ====================
75
76    println!("\n[COMMIT] Testing configuration with commit operations...");
77
78    // Create a test file
79    let test_file_path = repo_path.join("test.txt");
80    fs::write(
81        &test_file_path,
82        "Hello from rustic-git configuration example!",
83    )?;
84    println!("Created test file: test.txt");
85
86    // Stage the file
87    repo.add(&["test.txt"])?;
88    println!("Staged test.txt");
89
90    // Create a commit (this will use our configured user)
91    let commit_hash = repo.commit("Add test file with configuration example")?;
92    println!("Created commit: {}", commit_hash.short());
93
94    // ==================== CONFIGURATION MODIFICATION ====================
95
96    println!("\n[UPDATE] Modifying configuration values...");
97
98    // Change some configuration values
99    repo.config().set("core.autocrlf", "true")?;
100    repo.config()
101        .set("user.email", "alice.developer@newcompany.com")?;
102
103    println!("Updated configuration values");
104
105    // Display updated values
106    let autocrlf = repo.config().get("core.autocrlf")?;
107    let (updated_name, updated_email) = repo.config().get_user()?;
108
109    println!("Updated configuration:");
110    println!("  core.autocrlf = {}", autocrlf);
111    println!("  user: {} <{}>", updated_name, updated_email);
112
113    // ==================== CONFIGURATION REMOVAL ====================
114
115    println!("\n[REMOVE] Removing configuration values...");
116
117    // Remove a configuration value
118    repo.config().unset("branch.autosetupmerge")?;
119    println!("Removed branch.autosetupmerge");
120
121    // Try to get the removed value (should fail)
122    match repo.config().get("branch.autosetupmerge") {
123        Ok(value) => println!("Unexpected: branch.autosetupmerge = {}", value),
124        Err(_) => println!("Confirmed: branch.autosetupmerge is not set"),
125    }
126
127    // ==================== ADVANCED CONFIGURATION ====================
128
129    println!("\n[ADVANCED] Setting advanced configuration...");
130
131    // Set some advanced git configuration
132    repo.config().set("diff.tool", "vimdiff")?;
133    repo.config().set("merge.tool", "vimdiff")?;
134    repo.config().set("alias.st", "status")?;
135    repo.config().set("alias.co", "checkout")?;
136    repo.config().set("alias.br", "branch")?;
137    repo.config().set("alias.ci", "commit")?;
138
139    println!("Set advanced configuration (diff/merge tools and aliases)");
140
141    // Display all custom configuration
142    println!("\n[SUMMARY] Complete repository configuration summary:");
143
144    let all_configs = [
145        ("User", vec![("user.name", ""), ("user.email", "")]),
146        ("Core", vec![("core.autocrlf", ""), ("core.ignorecase", "")]),
147        ("Workflow", vec![("pull.rebase", ""), ("push.default", "")]),
148        ("Tools", vec![("diff.tool", ""), ("merge.tool", "")]),
149        (
150            "Aliases",
151            vec![
152                ("alias.st", ""),
153                ("alias.co", ""),
154                ("alias.br", ""),
155                ("alias.ci", ""),
156            ],
157        ),
158    ];
159
160    for (category, configs) in &all_configs {
161        println!("\n  {}:", category);
162        for (key, _) in configs {
163            match repo.config().get(key) {
164                Ok(value) => println!("    {} = {}", key, value),
165                Err(_) => println!("    {} = <not set>", key),
166            }
167        }
168    }
169
170    // ==================== PRACTICAL EXAMPLE ====================
171
172    println!("\n[TEAM] Practical example: Setting up repository for a team...");
173
174    // Configure repository for team development
175    repo.config().set("user.name", "Team Member")?;
176    repo.config().set("user.email", "team@company.com")?;
177    repo.config().set("core.autocrlf", "input")?;
178    repo.config().set("core.safecrlf", "true")?;
179    repo.config().set("pull.rebase", "true")?;
180    repo.config().set("push.default", "current")?;
181    repo.config().set("init.defaultBranch", "main")?;
182
183    println!("Configured repository for team development");
184
185    // Create another commit with the team configuration
186    fs::write(
187        repo_path.join("team.md"),
188        "# Team Development\n\nThis repository is configured for team development.",
189    )?;
190    repo.add(&["team.md"])?;
191    let team_commit = repo.commit("Add team development documentation")?;
192
193    println!("Created team commit: {}", team_commit.short());
194
195    // Final verification
196    let (final_name, final_email) = repo.config().get_user()?;
197    println!("\n[FINAL] Final repository configuration:");
198    println!("  User: {} <{}>", final_name, final_email);
199    println!("  Repository configured for team development workflow");
200
201    // ==================== CLEANUP ====================
202
203    println!("\n[CLEANUP] Cleaning up...");
204    fs::remove_dir_all(&repo_path).expect("Failed to clean up example");
205    println!("Example completed successfully!");
206
207    Ok(())
208}
examples/diff_operations.rs (line 19)
4fn main() -> rustic_git::Result<()> {
5    println!("Rustic Git - Diff Operations Example\n");
6
7    let repo_path = env::temp_dir().join("rustic_git_diff_example");
8    // Clean up any previous run
9    if repo_path.exists() {
10        fs::remove_dir_all(&repo_path).ok();
11    }
12    println!("Working in temporary directory: {}", repo_path.display());
13
14    // Initialize repository
15    let repo = Repository::init(&repo_path, false)?;
16    println!("Repository initialized successfully\n");
17
18    // Configure git user for commits
19    let config = repo.config();
20    config.set_user("Test User", "test@example.com")?;
21
22    println!("=== Creating Initial Files ===");
23
24    // Create initial files
25    let readme_path = repo_path.join("README.md");
26    let src_dir = repo_path.join("src");
27    fs::create_dir_all(&src_dir).unwrap();
28    let main_path = src_dir.join("main.rs");
29    let lib_path = src_dir.join("lib.rs");
30
31    fs::write(
32        &readme_path,
33        "# Test Project\n\nA sample project for testing diff operations.\n",
34    )
35    .unwrap();
36    fs::write(
37        &main_path,
38        "fn main() {\n    println!(\"Hello, world!\");\n}\n",
39    )
40    .unwrap();
41    fs::write(
42        &lib_path,
43        "pub fn add(a: i32, b: i32) -> i32 {\n    a + b\n}\n",
44    )
45    .unwrap();
46
47    println!("Created initial files: README.md, src/main.rs, src/lib.rs");
48
49    // Stage and commit initial files
50    repo.add_all()?;
51    let initial_commit = repo.commit("feat: initial commit with basic files")?;
52    println!("Initial commit: {}\n", initial_commit.short());
53
54    println!("=== Testing Different Diff Operations ===");
55
56    // Test 1: Diff with no changes (should be empty)
57    println!("1. Diff with no changes:");
58    let diff = repo.diff()?;
59    if diff.is_empty() {
60        println!("   ✓ No changes detected (as expected)");
61    } else {
62        println!("   ✗ Unexpected changes found");
63    }
64    println!();
65
66    // Test 2: Modify files and show unstaged changes
67    println!("2. Creating unstaged changes:");
68    fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
69    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");\n}\n").unwrap();
70
71    let diff = repo.diff()?;
72    println!("   Unstaged changes found:");
73    println!("   Files changed: {}", diff.len());
74    for file in diff.iter() {
75        println!("   - {} ({})", file.path.display(), file.status);
76    }
77    println!("   {}", diff.stats);
78    println!();
79
80    // Test 3: Stage some changes and show staged vs unstaged
81    println!("3. Staging README.md and checking staged diff:");
82    repo.add(&[&readme_path])?;
83
84    let staged_diff = repo.diff_staged()?;
85    println!("   Staged changes:");
86    for file in staged_diff.iter() {
87        println!("   - {} ({})", file.path.display(), file.status);
88    }
89    println!("   {}", staged_diff.stats);
90
91    let unstaged_diff = repo.diff()?;
92    println!("   Remaining unstaged changes:");
93    for file in unstaged_diff.iter() {
94        println!("   - {} ({})", file.path.display(), file.status);
95    }
96    println!("   {}", unstaged_diff.stats);
97    println!();
98
99    // Test 4: Diff with options
100    println!("4. Using diff options (name-only):");
101    let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
102    println!("   Modified files (name-only):");
103    for file in name_only_diff.iter() {
104        println!("   - {}", file.path.display());
105    }
106    println!();
107
108    // Test 5: Diff with file filtering
109    println!("5. Diff with path filtering (src/ only):");
110    let src_paths = vec![src_dir.clone()];
111    let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
112    println!("   Changes in src/ directory:");
113    for file in filtered_diff.iter() {
114        println!("   - {} ({})", file.path.display(), file.status);
115    }
116    println!();
117
118    // Stage remaining changes and commit
119    repo.add_all()?;
120    let second_commit = repo.commit("feat: add features section and improve main function")?;
121    println!("Second commit: {}", second_commit.short());
122
123    // Test 6: Diff between commits
124    println!("\n6. Diff between commits:");
125    let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
126    println!(
127        "   Changes from {} to {}:",
128        initial_commit.short(),
129        second_commit.short()
130    );
131    for file in commit_diff.iter() {
132        println!(
133            "   - {} ({}) +{} -{}",
134            file.path.display(),
135            file.status,
136            file.additions,
137            file.deletions
138        );
139    }
140    println!("   {}", commit_diff.stats);
141    println!();
142
143    // Test 7: Add a new file and show it in diff
144    println!("7. Adding new file and checking diff:");
145    let test_path = repo_path.join("test.txt");
146    fs::write(
147        &test_path,
148        "This is a new test file.\nWith multiple lines.\n",
149    )
150    .unwrap();
151
152    let new_file_diff = repo.diff()?;
153    println!("   New file detected:");
154    for file in new_file_diff.iter() {
155        println!("   - {} ({})", file.path.display(), file.status);
156    }
157    println!();
158
159    // Test 8: Delete a file and show in diff
160    println!("8. Deleting file and checking diff:");
161    fs::remove_file(&lib_path).unwrap();
162
163    let deleted_file_diff = repo.diff()?;
164    println!("   Changes after file deletion:");
165    for file in deleted_file_diff.iter() {
166        println!("   - {} ({})", file.path.display(), file.status);
167    }
168    println!();
169
170    // Test 9: Diff with ignore whitespace options
171    println!("9. Testing whitespace options:");
172
173    // Add some whitespace changes
174    fs::write(&main_path, "fn main() {\n    println!(\"Hello, world!\");\n    println!(\"Testing diff operations!\");    \n}\n").unwrap();
175
176    let normal_diff = repo.diff()?;
177    let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
178
179    println!("   Normal diff shows {} files changed", normal_diff.len());
180    println!(
181        "   Whitespace-ignoring diff shows {} files changed",
182        whitespace_diff.len()
183    );
184    println!();
185
186    // Test 10: Show diff with HEAD
187    println!("10. Diff with HEAD (all changes since last commit):");
188    let head_diff = repo.diff_head()?;
189    println!("    All changes since last commit:");
190    for file in head_diff.iter() {
191        println!("    - {} ({})", file.path.display(), file.status);
192    }
193    println!("    {}", head_diff.stats);
194    println!();
195
196    // Test 11: Different diff output formats
197    println!("11. Testing different output formats:");
198
199    let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
200    println!("    Stat format:");
201    println!("    {}", stat_diff);
202
203    let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
204    println!("    Numstat format - {} files changed", numstat_diff.len());
205    for file in numstat_diff.iter() {
206        println!(
207            "    {} +{} -{}",
208            file.path.display(),
209            file.additions,
210            file.deletions
211        );
212    }
213    println!();
214
215    // Test 12: Filtering by file status
216    println!("12. Filtering files by status:");
217    let all_changes = repo.diff_head()?;
218
219    let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
220    let modified_files: Vec<_> = all_changes
221        .files_with_status(DiffStatus::Modified)
222        .collect();
223    let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
224
225    println!("    Added files: {}", added_files.len());
226    for file in added_files {
227        println!("      - {}", file.path.display());
228    }
229
230    println!("    Modified files: {}", modified_files.len());
231    for file in modified_files {
232        println!("      - {}", file.path.display());
233    }
234
235    println!("    Deleted files: {}", deleted_files.len());
236    for file in deleted_files {
237        println!("      - {}", file.path.display());
238    }
239    println!();
240
241    println!("=== Diff Operations Demo Complete ===");
242    println!("All diff operations completed successfully!");
243    println!("Summary of tested features:");
244    println!("✓ Basic diff operations (working dir vs index)");
245    println!("✓ Staged diff operations (index vs HEAD)");
246    println!("✓ Diff between specific commits");
247    println!("✓ Diff with various options (name-only, stat, numstat)");
248    println!("✓ Path filtering");
249    println!("✓ Whitespace handling options");
250    println!("✓ File status filtering");
251    println!("✓ Comprehensive diff statistics");
252
253    println!("\nCleaning up temporary repository...");
254    fs::remove_dir_all(&repo_path).ok();
255
256    Ok(())
257}
examples/tag_operations.rs (line 31)
16fn main() -> Result<()> {
17    println!("Rustic Git - Tag Operations Example\n");
18
19    // Use a temporary directory for this example
20    let repo_path = env::temp_dir().join("rustic_git_tag_example");
21
22    // Clean up any previous run
23    if repo_path.exists() {
24        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25    }
26
27    println!("Initializing repository at: {}", repo_path.display());
28
29    // Initialize repository and configure user
30    let repo = Repository::init(&repo_path, false)?;
31    repo.config()
32        .set_user("Tag Demo User", "tags@example.com")?;
33
34    // Create some commits to tag
35    println!("\nCreating initial commits...");
36
37    // First commit
38    fs::write(
39        repo_path.join("README.md"),
40        "# Tag Demo Project\n\nDemonstrating Git tag operations.\n",
41    )?;
42    repo.add(&["README.md"])?;
43    let first_commit_hash = repo.commit("Initial commit: Add README")?;
44    println!("Created commit: {}", first_commit_hash.short());
45
46    // Second commit
47    fs::create_dir_all(repo_path.join("src"))?;
48    fs::write(
49        repo_path.join("src/main.rs"),
50        "fn main() {\n    println!(\"Hello, tags!\");\n}\n",
51    )?;
52    repo.add(&["src/main.rs"])?;
53    let second_commit_hash = repo.commit("Add main.rs with hello world")?;
54    println!("Created commit: {}", second_commit_hash.short());
55
56    // Third commit
57    fs::write(
58        repo_path.join("src/lib.rs"),
59        "//! Tag demo library\n\npub fn greet(name: &str) -> String {\n    format!(\"Hello, {}!\", name)\n}\n",
60    )?;
61    repo.add(&["src/lib.rs"])?;
62    let third_commit_hash = repo.commit("Add library with greet function")?;
63    println!("Created commit: {}", third_commit_hash.short());
64
65    // Demonstrate tag creation
66    println!("\n=== Creating Tags ===");
67
68    // Create lightweight tags
69    println!("\n1. Creating lightweight tags:");
70
71    let v0_1_0 = repo.create_tag("v0.1.0", Some(&first_commit_hash))?;
72    println!(
73        "Created lightweight tag: {} -> {} ({})",
74        v0_1_0.name,
75        v0_1_0.hash.short(),
76        v0_1_0.tag_type
77    );
78
79    let v0_2_0 = repo.create_tag("v0.2.0", Some(&second_commit_hash))?;
80    println!(
81        "Created lightweight tag: {} -> {} ({})",
82        v0_2_0.name,
83        v0_2_0.hash.short(),
84        v0_2_0.tag_type
85    );
86
87    // Create annotated tags
88    println!("\n2. Creating annotated tags:");
89
90    let options =
91        TagOptions::new().with_message("First stable release with basic functionality".to_string());
92    let v1_0_0 = repo.create_tag_with_options("v1.0.0", Some(&third_commit_hash), options)?;
93    println!(
94        "Created annotated tag: {} -> {} ({})",
95        v1_0_0.name,
96        v1_0_0.hash.short(),
97        v1_0_0.tag_type
98    );
99    if let Some(message) = &v1_0_0.message {
100        println!("  Message: {}", message);
101    }
102
103    // Tag current HEAD
104    let latest_options = TagOptions::new().with_message("Latest development version".to_string());
105    let latest_tag = repo.create_tag_with_options("latest", None, latest_options)?;
106    println!(
107        "Created annotated tag on HEAD: {} -> {} ({})",
108        latest_tag.name,
109        latest_tag.hash.short(),
110        latest_tag.tag_type
111    );
112
113    // Create some feature tags
114    println!("\n3. Creating feature and release candidate tags:");
115
116    let feature_options = TagOptions::new().with_message("Feature branch snapshot".to_string());
117    repo.create_tag_with_options("feature/demo", None, feature_options)?;
118
119    let rc_options = TagOptions::new().with_message("Release candidate for v1.1.0".to_string());
120    repo.create_tag_with_options("v1.1.0-rc1", None, rc_options)?;
121
122    // Create a couple more version tags
123    repo.create_tag("v0.3.0", None)?;
124    repo.create_tag("v0.9.0", None)?;
125
126    // Demonstrate tag listing and filtering
127    println!("\n=== Tag Listing and Filtering ===");
128
129    let tags = repo.tags()?;
130    println!("\nAll tags ({} total):", tags.len());
131    for tag in tags.iter() {
132        let type_marker = match tag.tag_type {
133            TagType::Lightweight => "L",
134            TagType::Annotated => "A",
135        };
136        println!("  [{}] {} -> {}", type_marker, tag.name, tag.hash.short());
137        if let Some(message) = &tag.message {
138            println!("      Message: {}", message.lines().next().unwrap_or(""));
139        }
140    }
141
142    // Filter by type
143    println!("\nLightweight tags ({} total):", tags.lightweight_count());
144    for tag in tags.lightweight() {
145        println!("  {} -> {}", tag.name, tag.hash.short());
146    }
147
148    println!("\nAnnotated tags ({} total):", tags.annotated_count());
149    for tag in tags.annotated() {
150        println!("  {} -> {}", tag.name, tag.hash.short());
151        if let Some(message) = &tag.message {
152            println!("    Message: {}", message.lines().next().unwrap_or(""));
153        }
154    }
155
156    // Search and filtering
157    println!("\n=== Tag Searching ===");
158
159    // Find specific tag
160    if let Some(tag) = tags.find("v1.0.0") {
161        println!("\nFound tag 'v1.0.0':");
162        println!("  Type: {}", tag.tag_type);
163        println!("  Hash: {}", tag.hash.short());
164        if let Some(message) = &tag.message {
165            println!("  Message: {}", message);
166        }
167    }
168
169    // Find version tags
170    let version_tags: Vec<_> = tags.find_containing("v").collect();
171    println!(
172        "\nVersion tags (containing 'v'): {} found",
173        version_tags.len()
174    );
175    for tag in &version_tags {
176        println!("  {}", tag.name);
177    }
178
179    // Find release candidates
180    let rc_tags: Vec<_> = tags.find_containing("rc").collect();
181    println!("\nRelease candidate tags: {} found", rc_tags.len());
182    for tag in &rc_tags {
183        println!("  {}", tag.name);
184    }
185
186    // Find tags for specific commit
187    let tags_for_third_commit: Vec<_> = tags.for_commit(&third_commit_hash).collect();
188    println!(
189        "\nTags pointing to commit {}: {} found",
190        third_commit_hash.short(),
191        tags_for_third_commit.len()
192    );
193    for tag in &tags_for_third_commit {
194        println!("  {}", tag.name);
195    }
196
197    // Demonstrate tag details
198    println!("\n=== Tag Details ===");
199
200    let detailed_tag = repo.show_tag("v1.0.0")?;
201    println!("\nDetailed information for 'v1.0.0':");
202    println!("  Name: {}", detailed_tag.name);
203    println!("  Type: {}", detailed_tag.tag_type);
204    println!("  Commit: {}", detailed_tag.hash);
205    println!("  Short hash: {}", detailed_tag.hash.short());
206
207    if let Some(message) = &detailed_tag.message {
208        println!("  Message: {}", message);
209    }
210
211    if let Some(tagger) = &detailed_tag.tagger {
212        println!("  Tagger: {}", tagger);
213        println!(
214            "  Tagged at: {}",
215            tagger.timestamp.format("%Y-%m-%d %H:%M:%S UTC")
216        );
217    }
218
219    if let Some(timestamp) = &detailed_tag.timestamp {
220        println!("  Timestamp: {}", timestamp.format("%Y-%m-%d %H:%M:%S UTC"));
221    }
222
223    // Demonstrate tag operations
224    println!("\n=== Tag Operations ===");
225
226    // Create and force overwrite a tag
227    println!("\n1. Testing tag overwrite:");
228
229    // This should fail (tag already exists)
230    match repo.create_tag("latest", None) {
231        Ok(_) => println!("  ERROR: Should have failed to create existing tag"),
232        Err(e) => println!("  Expected error creating existing tag: {}", e),
233    }
234
235    // Force overwrite
236    let force_options = TagOptions::new()
237        .with_force()
238        .with_message("Forcefully updated latest tag".to_string());
239
240    match repo.create_tag_with_options("latest", None, force_options) {
241        Ok(tag) => println!("  Successfully force-created tag: {}", tag.name),
242        Err(e) => println!("  Error force-creating tag: {}", e),
243    }
244
245    // Tag deletion
246    println!("\n2. Testing tag deletion:");
247
248    // Create a temporary tag to delete
249    repo.create_tag("temp-tag", None)?;
250    println!("  Created temporary tag: temp-tag");
251
252    // Verify it exists
253    let tags_before = repo.tags()?;
254    let temp_exists_before = tags_before.find("temp-tag").is_some();
255    println!("  Temp tag exists before deletion: {}", temp_exists_before);
256
257    // Delete it
258    repo.delete_tag("temp-tag")?;
259    println!("  Deleted temp-tag");
260
261    // Verify it's gone
262    let tags_after = repo.tags()?;
263    let temp_exists_after = tags_after.find("temp-tag").is_some();
264    println!("  Temp tag exists after deletion: {}", temp_exists_after);
265
266    // Summary
267    println!("\n=== Summary ===");
268    let final_tags = repo.tags()?;
269    println!("\nFinal repository state:");
270    println!("  Total tags: {}", final_tags.len());
271    println!("  Lightweight tags: {}", final_tags.lightweight_count());
272    println!("  Annotated tags: {}", final_tags.annotated_count());
273
274    println!("\nTag creation options demonstrated:");
275    println!("  ✓ Lightweight tags (simple references)");
276    println!("  ✓ Annotated tags (with messages and metadata)");
277    println!("  ✓ Tags on specific commits");
278    println!("  ✓ Tags on current HEAD");
279    println!("  ✓ Force tag creation/overwrite");
280
281    println!("\nTag listing and filtering demonstrated:");
282    println!("  ✓ List all tags");
283    println!("  ✓ Filter by tag type (lightweight/annotated)");
284    println!("  ✓ Search by name patterns");
285    println!("  ✓ Find tags by commit hash");
286    println!("  ✓ Show detailed tag information");
287
288    println!("\nTag management demonstrated:");
289    println!("  ✓ Tag creation with options");
290    println!("  ✓ Tag deletion");
291    println!("  ✓ Error handling for duplicate tags");
292
293    // Clean up
294    println!("\nCleaning up example repository...");
295    fs::remove_dir_all(&repo_path)?;
296    println!("Tag operations example completed successfully!");
297
298    Ok(())
299}
examples/stash_operations.rs (line 32)
17fn main() -> Result<()> {
18    println!("Rustic Git - Stash Operations Example\n");
19
20    // Use a temporary directory for this example
21    let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23    // Clean up any previous run
24    if repo_path.exists() {
25        fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26    }
27
28    println!("Initializing repository at: {}", repo_path.display());
29
30    // Initialize repository and configure user
31    let repo = Repository::init(&repo_path, false)?;
32    repo.config()
33        .set_user("Stash Demo User", "stash@example.com")?;
34
35    // Create initial commit to have a base
36    println!("\nCreating initial commit...");
37    fs::write(
38        repo_path.join("README.md"),
39        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40    )?;
41    repo.add(&["README.md"])?;
42    let initial_commit = repo.commit("Initial commit: Add README")?;
43    println!("Created initial commit: {}", initial_commit.short());
44
45    // Create some work to stash
46    println!("\n=== Creating Work to Stash ===");
47
48    // Create tracked file modifications
49    fs::write(
50        repo_path.join("README.md"),
51        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52    )?;
53
54    // Create new tracked files
55    fs::create_dir_all(repo_path.join("src"))?;
56    fs::write(
57        repo_path.join("src/main.rs"),
58        "fn main() {\n    println!(\"Hello, stash!\");\n}\n",
59    )?;
60
61    // Create untracked files
62    fs::write(
63        repo_path.join("untracked.txt"),
64        "This file is not tracked by git\n",
65    )?;
66    fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68    // Stage some changes
69    repo.add(&["src/main.rs"])?;
70
71    println!("Created various types of changes:");
72    println!("  - Modified tracked file (README.md)");
73    println!("  - Added new file and staged it (src/main.rs)");
74    println!("  - Created untracked files (untracked.txt, temp.log)");
75
76    // Check repository status before stashing
77    let status = repo.status()?;
78    println!("\nRepository status before stashing:");
79    println!("  Staged files: {}", status.staged_files().count());
80    println!("  Unstaged files: {}", status.unstaged_files().count());
81    println!("  Untracked files: {}", status.untracked_entries().count());
82
83    // Demonstrate stash creation
84    println!("\n=== Creating Stashes ===");
85
86    // 1. Simple stash save
87    println!("\n1. Creating simple stash:");
88    let simple_stash = repo.stash_save("WIP: working on main function")?;
89    println!("Created stash: {}", simple_stash);
90    println!("  Index: {}", simple_stash.index);
91    println!("  Branch: {}", simple_stash.branch);
92    println!("  Hash: {}", simple_stash.hash.short());
93
94    // Check status after stash
95    let status_after_stash = repo.status()?;
96    println!("\nStatus after simple stash:");
97    println!(
98        "  Staged files: {}",
99        status_after_stash.staged_files().count()
100    );
101    println!(
102        "  Unstaged files: {}",
103        status_after_stash.unstaged_files().count()
104    );
105    println!(
106        "  Untracked files: {}",
107        status_after_stash.untracked_entries().count()
108    );
109
110    // 2. Make more changes and create stash with untracked files
111    println!("\n2. Creating stash with untracked files:");
112
113    // Modify file again
114    fs::write(
115        repo_path.join("README.md"),
116        "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117    )?;
118
119    // Create more untracked files
120    fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122    let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123    let untracked_stash = repo.stash_push(
124        "WIP: config changes with untracked files",
125        untracked_options,
126    )?;
127    println!("Created stash with untracked files: {}", untracked_stash);
128
129    // 3. Create stash with specific paths
130    println!("\n3. Creating stash with specific paths:");
131
132    // Make changes to multiple files and add them to git
133    fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134    fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135    fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137    // Add all files so they're tracked
138    repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140    // Now modify them so there are changes to stash
141    fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142    fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143    fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145    let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146    let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147    println!("Created stash with specific paths: {}", path_stash);
148
149    // Demonstrate stash listing and filtering
150    println!("\n=== Stash Listing and Filtering ===");
151
152    let stashes = repo.stash_list()?;
153    println!("\nAll stashes ({} total):", stashes.len());
154    for stash in stashes.iter() {
155        println!(
156            "  [{}] {} -> {}",
157            stash.index,
158            stash.message,
159            stash.hash.short()
160        );
161        println!(
162            "      Branch: {} | Created: {}",
163            stash.branch,
164            stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165        );
166    }
167
168    // Test filtering
169    println!("\nFiltering examples:");
170
171    // Find stashes containing specific text
172    let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173    println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174    for stash in &wip_stashes {
175        println!("  - {}", stash.message);
176    }
177
178    let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179    println!(
180        "Stashes containing 'config': {} found",
181        config_stashes.len()
182    );
183
184    // Get latest stash
185    if let Some(latest) = stashes.latest() {
186        println!("Latest stash: {}", latest.message);
187    }
188
189    // Get specific stash by index
190    if let Some(second_stash) = stashes.get(1) {
191        println!("Second stash: {}", second_stash.message);
192    }
193
194    // Demonstrate stash content viewing
195    println!("\n=== Viewing Stash Contents ===");
196
197    println!("\nShowing contents of latest stash:");
198    let stash_contents = repo.stash_show(0)?;
199    println!("{}", stash_contents);
200
201    // Demonstrate stash application
202    println!("\n=== Applying and Popping Stashes ===");
203
204    println!("\n1. Testing stash apply (keeps stash in list):");
205    let stashes_before_apply = repo.stash_list()?;
206    println!("Stashes before apply: {}", stashes_before_apply.len());
207
208    // Apply the latest stash
209    repo.stash_apply(0, StashApplyOptions::new())?;
210    println!("Applied stash@{{0}}");
211
212    let stashes_after_apply = repo.stash_list()?;
213    println!("Stashes after apply: {}", stashes_after_apply.len());
214
215    // Check what was restored
216    let status_after_apply = repo.status()?;
217    println!("Status after apply:");
218    println!(
219        "  Staged files: {}",
220        status_after_apply.staged_files().count()
221    );
222    println!(
223        "  Unstaged files: {}",
224        status_after_apply.unstaged_files().count()
225    );
226    println!(
227        "  Untracked files: {}",
228        status_after_apply.untracked_entries().count()
229    );
230
231    println!("\n2. Testing stash pop (removes stash from list):");
232
233    // First, stash current changes again to have something to pop
234    repo.stash_save("Temporary stash for pop test")?;
235
236    let stashes_before_pop = repo.stash_list()?;
237    println!("Stashes before pop: {}", stashes_before_pop.len());
238
239    // Pop the latest stash
240    repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241    println!("Popped stash@{{0}}");
242
243    let stashes_after_pop = repo.stash_list()?;
244    println!("Stashes after pop: {}", stashes_after_pop.len());
245
246    // Demonstrate advanced apply options
247    println!("\n3. Testing apply with index restoration:");
248
249    // Create a stash with staged changes
250    fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251    repo.add(&["staged_file.txt"])?;
252
253    fs::write(
254        repo_path.join("unstaged_file.txt"),
255        "This will be unstaged\n",
256    )?;
257
258    repo.stash_save("Stash with staged and unstaged changes")?;
259
260    // Apply with index restoration
261    let apply_options = StashApplyOptions::new().with_index();
262    repo.stash_apply(0, apply_options)?;
263    println!("Applied stash with index restoration");
264
265    let final_status = repo.status()?;
266    println!("Final status after index restoration:");
267    println!("  Staged files: {}", final_status.staged_files().count());
268    println!(
269        "  Unstaged files: {}",
270        final_status.unstaged_files().count()
271    );
272
273    // Demonstrate stash management
274    println!("\n=== Stash Management ===");
275
276    // Create a few test stashes
277    for i in 1..=3 {
278        fs::write(
279            repo_path.join(format!("test{}.txt", i)),
280            format!("Test content {}\n", i),
281        )?;
282        repo.stash_save(&format!("Test stash {}", i))?;
283    }
284
285    let management_stashes = repo.stash_list()?;
286    println!(
287        "\nCreated {} test stashes for management demo",
288        management_stashes.len()
289    );
290
291    // Drop a specific stash
292    println!("\n1. Dropping middle stash:");
293    println!("Before drop: {} stashes", management_stashes.len());
294
295    repo.stash_drop(1)?; // Drop second stash (index 1)
296    println!("Dropped stash@{{1}}");
297
298    let after_drop = repo.stash_list()?;
299    println!("After drop: {} stashes", after_drop.len());
300
301    // Show remaining stashes
302    println!("Remaining stashes:");
303    for stash in after_drop.iter() {
304        println!("  [{}] {}", stash.index, stash.message);
305    }
306
307    // Clear all stashes
308    println!("\n2. Clearing all stashes:");
309    repo.stash_clear()?;
310    println!("Cleared all stashes");
311
312    let final_stashes = repo.stash_list()?;
313    println!("Stashes after clear: {}", final_stashes.len());
314
315    // Demonstrate error handling
316    println!("\n=== Error Handling ===");
317
318    println!("\n1. Testing operations on empty stash list:");
319
320    // Try to apply non-existent stash
321    match repo.stash_apply(0, StashApplyOptions::new()) {
322        Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323        Err(e) => println!("Expected error applying non-existent stash: {}", e),
324    }
325
326    // Try to show non-existent stash
327    match repo.stash_show(0) {
328        Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329        Err(e) => println!("Expected error showing non-existent stash: {}", e),
330    }
331
332    // Try to drop non-existent stash
333    match repo.stash_drop(0) {
334        Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335        Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336    }
337
338    // Summary
339    println!("\n=== Summary ===");
340    println!("\nStash operations demonstrated:");
341    println!("  ✓ Basic stash save and push with options");
342    println!("  ✓ Stash with untracked files and keep-index");
343    println!("  ✓ Stash specific paths only");
344    println!("  ✓ Comprehensive stash listing and filtering");
345    println!("  ✓ Stash content viewing");
346    println!("  ✓ Apply vs pop operations");
347    println!("  ✓ Index restoration during apply");
348    println!("  ✓ Stash dropping and clearing");
349    println!("  ✓ Error handling for edge cases");
350
351    println!("\nStash options demonstrated:");
352    println!("  ✓ with_untracked() - Include untracked files");
353    println!("  ✓ with_keep_index() - Keep staged changes");
354    println!("  ✓ with_paths() - Stash specific files only");
355    println!("  ✓ with_index() - Restore staged state on apply");
356    println!("  ✓ with_quiet() - Suppress output messages");
357
358    println!("\nStash filtering demonstrated:");
359    println!("  ✓ find_containing() - Search by message content");
360    println!("  ✓ latest() - Get most recent stash");
361    println!("  ✓ get() - Get stash by index");
362    println!("  ✓ for_branch() - Filter by branch name");
363
364    // Clean up
365    println!("\nCleaning up example repository...");
366    fs::remove_dir_all(&repo_path)?;
367    println!("Stash operations example completed successfully!");
368
369    Ok(())
370}

Trait Implementations§

Source§

impl Debug for Repository

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.