pub struct Repository { /* private fields */ }
Implementations§
Source§impl Repository
impl Repository
Sourcepub fn add<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()>
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?
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
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}
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}
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}
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}
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}
Sourcepub fn add_all(&self) -> Result<()>
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?
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
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}
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}
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}
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}
Sourcepub fn add_update(&self) -> Result<()>
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?
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
impl Repository
Sourcepub fn branches(&self) -> Result<BranchList>
pub fn branches(&self) -> Result<BranchList>
List all branches in the repository
Examples found in repository?
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
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}
Sourcepub fn current_branch(&self) -> Result<Option<Branch>>
pub fn current_branch(&self) -> Result<Option<Branch>>
Get the current branch
Examples found in repository?
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}
Sourcepub fn create_branch(
&self,
name: &str,
start_point: Option<&str>,
) -> Result<Branch>
pub fn create_branch( &self, name: &str, start_point: Option<&str>, ) -> Result<Branch>
Create a new branch
Examples found in repository?
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}
Sourcepub fn delete_branch(&self, branch: &Branch, force: bool) -> Result<()>
pub fn delete_branch(&self, branch: &Branch, force: bool) -> Result<()>
Delete a branch
Examples found in repository?
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}
Sourcepub fn checkout(&self, branch: &Branch) -> Result<()>
pub fn checkout(&self, branch: &Branch) -> Result<()>
Switch to an existing branch
Examples found in repository?
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
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}
Sourcepub fn checkout_new(
&self,
name: &str,
start_point: Option<&str>,
) -> Result<Branch>
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?
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
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
impl Repository
Sourcepub fn commit(&self, message: &str) -> Result<Hash>
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?
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
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}
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}
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}
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}
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}
Create a commit with the given message and author.
§Arguments
message
- The commit messageauthor
- 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?
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
impl Repository
Sourcepub fn diff(&self) -> Result<DiffOutput>
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?
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}
Sourcepub fn diff_staged(&self) -> Result<DiffOutput>
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?
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}
Sourcepub fn diff_head(&self) -> Result<DiffOutput>
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?
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}
Sourcepub fn diff_commits(&self, from: &Hash, to: &Hash) -> Result<DiffOutput>
pub fn diff_commits(&self, from: &Hash, to: &Hash) -> Result<DiffOutput>
Get diff between two commits
§Arguments
from
- The starting commit hashto
- 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?
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}
Sourcepub fn diff_with_options(&self, options: &DiffOptions) -> Result<DiffOutput>
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?
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
impl Repository
Sourcepub fn checkout_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
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?
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}
Sourcepub fn restore<P: AsRef<Path>>(
&self,
paths: &[P],
options: RestoreOptions,
) -> Result<()>
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 restoreoptions
- 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?
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}
Sourcepub fn reset_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
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?
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
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}
Sourcepub fn rm<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()>
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?
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}
Sourcepub fn rm_with_options<P: AsRef<Path>>(
&self,
paths: &[P],
options: RemoveOptions,
) -> Result<()>
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 removeoptions
- 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?
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}
Sourcepub fn mv<P: AsRef<Path>, Q: AsRef<Path>>(
&self,
source: P,
destination: Q,
) -> Result<()>
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 pathdestination
- Destination path
§Example
use rustic_git::Repository;
// Rename a file
repo.mv("old_name.txt", "new_name.txt")?;
Examples found in repository?
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}
Sourcepub fn mv_with_options<P: AsRef<Path>, Q: AsRef<Path>>(
&self,
source: P,
destination: Q,
options: MoveOptions,
) -> Result<()>
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 pathdestination
- Destination pathoptions
- 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?
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}
Sourcepub fn ignore_add(&self, patterns: &[&str]) -> Result<()>
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?
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}
Sourcepub fn ignore_check<P: AsRef<Path>>(&self, path: P) -> Result<bool>
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 ignoredOk(false)
if the file is not ignoredErr(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?
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}
Sourcepub fn ignore_list(&self) -> Result<Vec<String>>
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?
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
impl Repository
Sourcepub fn log(&self) -> Result<CommitLog>
pub fn log(&self) -> Result<CommitLog>
Get commit history with default options
Examples found in repository?
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}
Sourcepub fn recent_commits(&self, count: usize) -> Result<CommitLog>
pub fn recent_commits(&self, count: usize) -> Result<CommitLog>
Get recent N commits
Examples found in repository?
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
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}
Sourcepub fn log_with_options(&self, options: &LogOptions) -> Result<CommitLog>
pub fn log_with_options(&self, options: &LogOptions) -> Result<CommitLog>
Get commit history with custom options
Examples found in repository?
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}
Sourcepub fn log_range(&self, from: &Hash, to: &Hash) -> Result<CommitLog>
pub fn log_range(&self, from: &Hash, to: &Hash) -> Result<CommitLog>
Get commits in a range between two commits
Examples found in repository?
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}
Sourcepub fn log_for_paths(&self, paths: &[impl AsRef<Path>]) -> Result<CommitLog>
pub fn log_for_paths(&self, paths: &[impl AsRef<Path>]) -> Result<CommitLog>
Get commits that affected specific paths
Examples found in repository?
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}
Sourcepub fn show_commit(&self, hash: &Hash) -> Result<CommitDetails>
pub fn show_commit(&self, hash: &Hash) -> Result<CommitDetails>
Get detailed information about a specific commit
Examples found in repository?
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
impl Repository
Sourcepub fn merge(&self, branch: &str) -> Result<MergeStatus>
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?
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}
Sourcepub fn merge_with_options(
&self,
branch: &str,
options: MergeOptions,
) -> Result<MergeStatus>
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 branchoptions
- 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?
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}
Sourcepub fn merge_in_progress(&self) -> Result<bool>
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?
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}
Sourcepub fn abort_merge(&self) -> Result<()>
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?
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
impl Repository
Sourcepub fn add_remote(&self, name: &str, url: &str) -> Result<()>
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?
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}
Sourcepub fn remove_remote(&self, name: &str) -> Result<()>
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?
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}
Sourcepub fn rename_remote(&self, old_name: &str, new_name: &str) -> Result<()>
pub fn rename_remote(&self, old_name: &str, new_name: &str) -> Result<()>
Rename a remote
§Arguments
old_name
- The current name of the remotenew_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?
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}
Sourcepub fn get_remote_url(&self, name: &str) -> Result<String>
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?
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}
Sourcepub fn list_remotes(&self) -> Result<RemoteList>
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?
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}
Sourcepub fn fetch(&self, remote: &str) -> Result<()>
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?
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}
Sourcepub fn fetch_with_options(
&self,
remote: &str,
options: FetchOptions,
) -> Result<()>
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 fromoptions
- 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?
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}
Sourcepub fn push(&self, remote: &str, branch: &str) -> Result<()>
pub fn push(&self, remote: &str, branch: &str) -> Result<()>
Push changes to a remote repository
§Arguments
remote
- The name of the remote to push tobranch
- The name of the branch to push
§Example
use rustic_git::Repository;
let repo = Repository::open(".")?;
repo.push("origin", "main")?;
Examples found in repository?
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}
Sourcepub fn push_with_options(
&self,
remote: &str,
branch: &str,
options: PushOptions,
) -> Result<()>
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 tobranch
- The name of the branch to pushoptions
- 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?
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}
Sourcepub fn clone<P: AsRef<Path>>(url: &str, path: P) -> Result<Repository>
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 clonepath
- 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?
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
impl Repository
Sourcepub fn reset_soft(&self, commit: &str) -> Result<()>
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?
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}
Sourcepub fn reset_mixed(&self, commit: &str) -> Result<()>
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?
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}
Sourcepub fn reset_hard(&self, commit: &str) -> Result<()>
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?
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}
Sourcepub fn reset_with_mode(&self, commit: &str, mode: ResetMode) -> Result<()>
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 tomode
- The reset mode (Soft, Mixed, or Hard)
§Returns
A Result
indicating success or a GitError
if the operation fails.
Examples found in repository?
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
impl Repository
Sourcepub fn stash_list(&self) -> Result<StashList>
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?
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}
Sourcepub fn stash_save(&self, message: &str) -> Result<Stash>
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?
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}
Sourcepub fn stash_push(&self, message: &str, options: StashOptions) -> Result<Stash>
pub fn stash_push(&self, message: &str, options: StashOptions) -> Result<Stash>
Create a stash with advanced options
§Arguments
message
- A descriptive message for the stashoptions
- 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?
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}
Sourcepub fn stash_apply(
&self,
index: usize,
options: StashApplyOptions,
) -> Result<()>
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?
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}
Sourcepub fn stash_pop(&self, index: usize, options: StashApplyOptions) -> Result<()>
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?
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}
Sourcepub fn stash_show(&self, index: usize) -> Result<String>
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?
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}
Sourcepub fn stash_drop(&self, index: usize) -> Result<()>
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?
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}
Sourcepub fn stash_clear(&self) -> Result<()>
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?
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
impl Repository
Sourcepub fn status(&self) -> Result<GitStatus>
pub fn status(&self) -> Result<GitStatus>
Examples found in repository?
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
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}
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}
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}
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(®ular_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(®ular_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}
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
impl Repository
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?
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}
Sourcepub fn create_tag(&self, name: &str, target: Option<&Hash>) -> Result<Tag>
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 createtarget
- 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?
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}
Sourcepub fn create_tag_with_options(
&self,
name: &str,
target: Option<&Hash>,
options: TagOptions,
) -> Result<Tag>
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 createtarget
- 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?
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}
Sourcepub fn delete_tag(&self, name: &str) -> Result<()>
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?
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}
Sourcepub fn show_tag(&self, name: &str) -> Result<Tag>
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?
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
impl Repository
Sourcepub fn ensure_git() -> Result<()>
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
.
Sourcepub fn open<P: AsRef<Path>>(path: P) -> Result<Self>
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?
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
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(®ular_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(®ular_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}
Sourcepub fn init<P: AsRef<Path>>(path: P, bare: bool) -> Result<Self>
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?
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
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}
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}
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}
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}
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(®ular_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(®ular_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}
Sourcepub fn repo_path(&self) -> &Path
pub fn repo_path(&self) -> &Path
Examples found in repository?
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(®ular_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(®ular_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}
Sourcepub fn config(&self) -> RepoConfig<'_>
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?
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
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}
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}
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}
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}
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}