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?
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}
More examples
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}
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}
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}
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?
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?
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?
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?
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}
More examples
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}
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}
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}
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?
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?
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 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 status(&self) -> Result<GitStatus>
pub fn status(&self) -> Result<GitStatus>
Examples found in repository?
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}
More examples
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}
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}
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 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?
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}
More examples
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}
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}
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}
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?
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}
More examples
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 - 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}