pub struct GitStatus {
pub entries: Box<[FileEntry]>,
}
Fields§
§entries: Box<[FileEntry]>
Implementations§
Source§impl GitStatus
impl GitStatus
Sourcepub fn is_clean(&self) -> bool
pub fn is_clean(&self) -> bool
Examples found in repository?
examples/staging_operations.rs (line 251)
250fn display_status_breakdown(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean");
253 return;
254 }
255
256 let mut index_counts = std::collections::HashMap::new();
257 let mut worktree_counts = std::collections::HashMap::new();
258
259 for entry in &status.entries {
260 if !matches!(entry.index_status, IndexStatus::Clean) {
261 *index_counts.entry(&entry.index_status).or_insert(0) += 1;
262 }
263 if !matches!(entry.worktree_status, WorktreeStatus::Clean) {
264 *worktree_counts.entry(&entry.worktree_status).or_insert(0) += 1;
265 }
266 }
267
268 println!(" Index status:");
269 for (index_status, count) in &index_counts {
270 let marker = match index_status {
271 IndexStatus::Modified => "[M]",
272 IndexStatus::Added => "[A]",
273 IndexStatus::Deleted => "[D]",
274 IndexStatus::Renamed => "[R]",
275 IndexStatus::Copied => "[C]",
276 IndexStatus::Clean => "[ ]",
277 };
278 println!(" {} {:?}: {} files", marker, index_status, count);
279 }
280
281 println!(" Worktree status:");
282 for (worktree_status, count) in &worktree_counts {
283 let marker = match worktree_status {
284 WorktreeStatus::Modified => "[M]",
285 WorktreeStatus::Deleted => "[D]",
286 WorktreeStatus::Untracked => "[?]",
287 WorktreeStatus::Ignored => "[I]",
288 WorktreeStatus::Clean => "[ ]",
289 };
290 println!(" {} {:?}: {} files", marker, worktree_status, count);
291 }
292}
More examples
examples/basic_usage.rs (line 61)
16fn main() -> Result<()> {
17 println!("Rustic Git - Basic Usage Example\n");
18
19 // Use a temporary directory for this example
20 let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22 // Clean up any previous run
23 if repo_path.exists() {
24 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25 }
26
27 println!("Initializing new repository at: {}", repo_path.display());
28
29 // Initialize a new repository
30 let repo = Repository::init(&repo_path, false)?;
31 println!("Repository initialized successfully\n");
32
33 // Create some example files
34 println!("Creating example files...");
35 fs::create_dir_all(repo_path.join("src"))?;
36
37 fs::write(
38 repo_path.join("README.md"),
39 "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40 )?;
41
42 fs::write(
43 repo_path.join("src/main.rs"),
44 r#"fn main() {
45 println!("Hello from rustic-git example!");
46}
47"#,
48 )?;
49
50 fs::write(
51 repo_path.join("src/lib.rs"),
52 "// Library code goes here\npub fn hello() -> &'static str {\n \"Hello, World!\"\n}\n",
53 )?;
54
55 println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57 // Check repository status
58 println!("Checking repository status...");
59 let status = repo.status()?;
60
61 if status.is_clean() {
62 println!(" Repository is clean (no changes)");
63 } else {
64 println!(" Repository has changes:");
65 println!(" Unstaged files: {}", status.unstaged_files().count());
66 println!(" Untracked files: {}", status.untracked_entries().count());
67
68 // Show untracked files
69 for entry in status.untracked_entries() {
70 println!(" - {}", entry.path.display());
71 }
72 }
73 println!();
74
75 // Stage specific files first
76 println!("Staging files...");
77
78 // Stage README.md first
79 repo.add(&["README.md"])?;
80 println!("Staged README.md");
81
82 // Stage all remaining files
83 repo.add_all()?;
84 println!("Staged all remaining files");
85
86 // Check status after staging
87 let status_after_staging = repo.status()?;
88 println!("\nStatus after staging:");
89 if status_after_staging.is_clean() {
90 println!(" Repository is clean (all changes staged)");
91 } else {
92 println!(
93 " Files staged for commit: {}",
94 status_after_staging.entries.len()
95 );
96 for entry in &status_after_staging.entries {
97 println!(
98 " Index {:?}, Worktree {:?}: {}",
99 entry.index_status,
100 entry.worktree_status,
101 entry.path.display()
102 );
103 }
104 }
105 println!();
106
107 // Create a commit
108 println!("Creating commit...");
109 let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111 println!("Commit created successfully!");
112 println!(" Full hash: {}", hash);
113 println!(" Short hash: {}", hash.short());
114 println!();
115
116 // Verify final status
117 println!("Final repository status:");
118 let final_status = repo.status()?;
119 if final_status.is_clean() {
120 println!(" Repository is clean - all changes committed!");
121 } else {
122 println!(" Repository still has uncommitted changes");
123 }
124 println!();
125
126 // Clean up
127 println!("Cleaning up example repository...");
128 fs::remove_dir_all(&repo_path)?;
129 println!("Example completed successfully!");
130
131 Ok(())
132}
examples/error_handling.rs (line 289)
207fn demonstrate_error_recovery_patterns(repo_path: &std::path::Path) -> Result<()> {
208 println!("Error Recovery Patterns:\n");
209
210 let repo = Repository::open(repo_path)?;
211
212 // Pattern 1: Retry with different approach
213 println!("1. Retry Pattern - Graceful degradation:");
214
215 // Try to add specific files, fall back to add_all on failure
216 let files_to_add = ["missing1.txt", "missing2.txt", "missing3.txt"];
217
218 println!(" Attempting to add specific files...");
219 match repo.add(&files_to_add) {
220 Ok(_) => println!(" Specific files added successfully"),
221 Err(e) => {
222 println!(" Specific files failed: {:?}", e);
223 println!(" Falling back to add_all()...");
224
225 match repo.add_all() {
226 Ok(_) => {
227 let status = repo.status()?;
228 println!(
229 " add_all() succeeded, {} files staged",
230 status.entries.len()
231 );
232 }
233 Err(fallback_error) => {
234 println!(" Fallback also failed: {:?}", fallback_error);
235 }
236 }
237 }
238 }
239
240 // Pattern 2: Partial success handling
241 println!("\n2. Partial Success Pattern:");
242
243 // Create some files with known issues
244 fs::write(repo_path.join("good1.txt"), "Good file 1")?;
245 fs::write(repo_path.join("good2.txt"), "Good file 2")?;
246 // Don't create bad1.txt - it will be missing
247
248 let mixed_files = ["good1.txt", "bad1.txt", "good2.txt"];
249
250 println!(" Attempting to add mixed valid/invalid files...");
251 match repo.add(&mixed_files) {
252 Ok(_) => println!(" All files added (unexpected success)"),
253 Err(GitError::CommandFailed(msg)) => {
254 println!(" Batch add failed: {}", msg);
255 println!(" Recovery: Adding files individually...");
256
257 let mut successful_adds = 0;
258 let mut failed_adds = 0;
259
260 for file in &mixed_files {
261 match repo.add(&[file]) {
262 Ok(_) => {
263 successful_adds += 1;
264 println!(" Added: {}", file);
265 }
266 Err(_) => {
267 failed_adds += 1;
268 println!(" Failed: {}", file);
269 }
270 }
271 }
272
273 println!(
274 " Results: {} succeeded, {} failed",
275 successful_adds, failed_adds
276 );
277 }
278 Err(GitError::IoError(msg)) => {
279 println!(" IoError during batch add: {}", msg);
280 }
281 }
282
283 // Pattern 3: Status checking before operations
284 println!("\n3. Preventive Pattern - Check before operation:");
285
286 println!(" Checking repository status before commit...");
287 let status = repo.status()?;
288
289 if status.is_clean() {
290 println!(" Repository is clean - no commit needed");
291 } else {
292 println!(" Repository has {} changes", status.entries.len());
293
294 // Show what would be committed
295 for entry in &status.entries {
296 println!(
297 " Index {:?}, Worktree {:?}: {}",
298 entry.index_status,
299 entry.worktree_status,
300 entry.path.display()
301 );
302 }
303
304 // Safe commit since we know there are changes
305 match repo.commit("Commit after status check") {
306 Ok(hash) => println!(" Safe commit succeeded: {}", hash.short()),
307 Err(e) => println!(" Even safe commit failed: {:?}", e),
308 }
309 }
310
311 println!();
312 Ok(())
313}
examples/status_checking.rs (line 85)
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}
248
249/// Display a summary of the repository status
250fn display_status_summary(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean (no changes)");
253 } else {
254 println!(" Repository has {} changes", status.entries.len());
255 println!(" Unstaged: {}", status.unstaged_files().count());
256 println!(" Untracked: {}", status.untracked_entries().count());
257 }
258}
examples/commit_workflows.rs (line 318)
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}
examples/file_lifecycle_operations.rs (line 420)
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 has_changes(&self) -> bool
pub fn has_changes(&self) -> bool
Examples found in repository?
examples/status_checking.rs (line 220)
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}
More examples
examples/staging_operations.rs (line 228)
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 staged_files(&self) -> impl Iterator<Item = &FileEntry> + '_
pub fn staged_files(&self) -> impl Iterator<Item = &FileEntry> + '_
Get all files that have changes in the index (staged)
Examples found in repository?
examples/reset_operations.rs (line 178)
175fn show_repo_state(repo: &Repository) -> Result<()> {
176 let status = repo.status()?;
177
178 let staged_count = status.staged_files().count();
179 let unstaged_count = status.unstaged_files().count();
180 let untracked_count = status.untracked_entries().count();
181
182 println!(" Repository state:");
183 println!(" - Staged files: {}", staged_count);
184 println!(" - Modified files: {}", unstaged_count);
185 println!(" - Untracked files: {}", untracked_count);
186
187 if staged_count > 0 {
188 println!(" - Staged:");
189 for file in status.staged_files().take(5) {
190 println!(" * {}", file.path.display());
191 }
192 }
193
194 if unstaged_count > 0 {
195 println!(" - Modified:");
196 for file in status.unstaged_files().take(5) {
197 println!(" * {}", file.path.display());
198 }
199 }
200
201 if untracked_count > 0 {
202 println!(" - Untracked:");
203 for file in status.untracked_entries().take(5) {
204 println!(" * {}", file.path.display());
205 }
206 }
207
208 Ok(())
209}
More examples
examples/merge_operations.rs (line 281)
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237 println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239 // Create a simple feature branch
240 println!("1. Creating simple feature branch...");
241 repo.checkout_new("feature/simple", None)?;
242
243 let simple_path = temp_dir.join("simple.txt");
244 fs::write(&simple_path, "Simple feature content")?;
245 repo.add(&["simple.txt"])?;
246 repo.commit("Add simple feature")?;
247
248 // Switch back to master
249 let branches = repo.branches()?;
250 let master_branch = branches.find("master").unwrap();
251 repo.checkout(master_branch)?;
252
253 // Test merge with different options
254 println!("\n2. Testing merge with custom options...");
255 let options = MergeOptions::new()
256 .with_fast_forward(FastForwardMode::Auto)
257 .with_message("Integrate simple feature".to_string());
258
259 let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261 match merge_status {
262 MergeStatus::FastForward(hash) => {
263 println!(" ✓ Fast-forward merge completed: {}", hash);
264 }
265 MergeStatus::Success(hash) => {
266 println!(" ✓ Merge commit created: {}", hash);
267 }
268 MergeStatus::UpToDate => {
269 println!(" ✓ Already up to date");
270 }
271 MergeStatus::Conflicts(_) => {
272 println!(" ⚠️ Unexpected conflicts");
273 }
274 }
275
276 // Show final repository state
277 println!("\n3. Final repository state:");
278 let status = repo.status()?;
279 println!(
280 " Working directory clean: {}",
281 status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282 );
283
284 let commits = repo.recent_commits(5)?;
285 println!(" Recent commits:");
286 for (i, commit) in commits.iter().enumerate() {
287 println!(
288 " {}: {} - {}",
289 i + 1,
290 commit.hash.short(),
291 commit.message.subject
292 );
293 }
294
295 Ok(())
296}
examples/status_checking.rs (line 235)
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}
examples/staging_operations.rs (line 229)
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}
248
249/// Display a breakdown of repository status
250fn display_status_breakdown(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean");
253 return;
254 }
255
256 let mut index_counts = std::collections::HashMap::new();
257 let mut worktree_counts = std::collections::HashMap::new();
258
259 for entry in &status.entries {
260 if !matches!(entry.index_status, IndexStatus::Clean) {
261 *index_counts.entry(&entry.index_status).or_insert(0) += 1;
262 }
263 if !matches!(entry.worktree_status, WorktreeStatus::Clean) {
264 *worktree_counts.entry(&entry.worktree_status).or_insert(0) += 1;
265 }
266 }
267
268 println!(" Index status:");
269 for (index_status, count) in &index_counts {
270 let marker = match index_status {
271 IndexStatus::Modified => "[M]",
272 IndexStatus::Added => "[A]",
273 IndexStatus::Deleted => "[D]",
274 IndexStatus::Renamed => "[R]",
275 IndexStatus::Copied => "[C]",
276 IndexStatus::Clean => "[ ]",
277 };
278 println!(" {} {:?}: {} files", marker, index_status, count);
279 }
280
281 println!(" Worktree status:");
282 for (worktree_status, count) in &worktree_counts {
283 let marker = match worktree_status {
284 WorktreeStatus::Modified => "[M]",
285 WorktreeStatus::Deleted => "[D]",
286 WorktreeStatus::Untracked => "[?]",
287 WorktreeStatus::Ignored => "[I]",
288 WorktreeStatus::Clean => "[ ]",
289 };
290 println!(" {} {:?}: {} files", marker, worktree_status, count);
291 }
292}
293
294/// Display changes between two status states
295fn display_status_changes(
296 before: &rustic_git::GitStatus,
297 after: &rustic_git::GitStatus,
298 description: &str,
299) {
300 println!("\n Status changes {}:", description);
301
302 let before_count = before.entries.len();
303 let after_count = after.entries.len();
304
305 if before_count == after_count {
306 println!(" Total files unchanged ({} files)", after_count);
307 } else {
308 println!(
309 " Total files: {} → {} ({:+})",
310 before_count,
311 after_count,
312 after_count as i32 - before_count as i32
313 );
314 }
315
316 // Show status summary
317 let before_staged = before.staged_files().count();
318 let after_staged = after.staged_files().count();
319 let before_untracked = before.untracked_entries().count();
320 let after_untracked = after.untracked_entries().count();
321
322 if before_staged != after_staged {
323 println!(
324 " Staged files: {} → {} ({:+})",
325 before_staged,
326 after_staged,
327 after_staged as i32 - before_staged as i32
328 );
329 }
330
331 if before_untracked != after_untracked {
332 println!(
333 " Untracked files: {} → {} ({:+})",
334 before_untracked,
335 after_untracked,
336 after_untracked as i32 - before_untracked as i32
337 );
338 }
339}
examples/stash_operations.rs (line 79)
17fn main() -> Result<()> {
18 println!("Rustic Git - Stash Operations Example\n");
19
20 // Use a temporary directory for this example
21 let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23 // Clean up any previous run
24 if repo_path.exists() {
25 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26 }
27
28 println!("Initializing repository at: {}", repo_path.display());
29
30 // Initialize repository and configure user
31 let repo = Repository::init(&repo_path, false)?;
32 repo.config()
33 .set_user("Stash Demo User", "stash@example.com")?;
34
35 // Create initial commit to have a base
36 println!("\nCreating initial commit...");
37 fs::write(
38 repo_path.join("README.md"),
39 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40 )?;
41 repo.add(&["README.md"])?;
42 let initial_commit = repo.commit("Initial commit: Add README")?;
43 println!("Created initial commit: {}", initial_commit.short());
44
45 // Create some work to stash
46 println!("\n=== Creating Work to Stash ===");
47
48 // Create tracked file modifications
49 fs::write(
50 repo_path.join("README.md"),
51 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52 )?;
53
54 // Create new tracked files
55 fs::create_dir_all(repo_path.join("src"))?;
56 fs::write(
57 repo_path.join("src/main.rs"),
58 "fn main() {\n println!(\"Hello, stash!\");\n}\n",
59 )?;
60
61 // Create untracked files
62 fs::write(
63 repo_path.join("untracked.txt"),
64 "This file is not tracked by git\n",
65 )?;
66 fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68 // Stage some changes
69 repo.add(&["src/main.rs"])?;
70
71 println!("Created various types of changes:");
72 println!(" - Modified tracked file (README.md)");
73 println!(" - Added new file and staged it (src/main.rs)");
74 println!(" - Created untracked files (untracked.txt, temp.log)");
75
76 // Check repository status before stashing
77 let status = repo.status()?;
78 println!("\nRepository status before stashing:");
79 println!(" Staged files: {}", status.staged_files().count());
80 println!(" Unstaged files: {}", status.unstaged_files().count());
81 println!(" Untracked files: {}", status.untracked_entries().count());
82
83 // Demonstrate stash creation
84 println!("\n=== Creating Stashes ===");
85
86 // 1. Simple stash save
87 println!("\n1. Creating simple stash:");
88 let simple_stash = repo.stash_save("WIP: working on main function")?;
89 println!("Created stash: {}", simple_stash);
90 println!(" Index: {}", simple_stash.index);
91 println!(" Branch: {}", simple_stash.branch);
92 println!(" Hash: {}", simple_stash.hash.short());
93
94 // Check status after stash
95 let status_after_stash = repo.status()?;
96 println!("\nStatus after simple stash:");
97 println!(
98 " Staged files: {}",
99 status_after_stash.staged_files().count()
100 );
101 println!(
102 " Unstaged files: {}",
103 status_after_stash.unstaged_files().count()
104 );
105 println!(
106 " Untracked files: {}",
107 status_after_stash.untracked_entries().count()
108 );
109
110 // 2. Make more changes and create stash with untracked files
111 println!("\n2. Creating stash with untracked files:");
112
113 // Modify file again
114 fs::write(
115 repo_path.join("README.md"),
116 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117 )?;
118
119 // Create more untracked files
120 fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122 let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123 let untracked_stash = repo.stash_push(
124 "WIP: config changes with untracked files",
125 untracked_options,
126 )?;
127 println!("Created stash with untracked files: {}", untracked_stash);
128
129 // 3. Create stash with specific paths
130 println!("\n3. Creating stash with specific paths:");
131
132 // Make changes to multiple files and add them to git
133 fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134 fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135 fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137 // Add all files so they're tracked
138 repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140 // Now modify them so there are changes to stash
141 fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142 fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143 fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145 let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146 let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147 println!("Created stash with specific paths: {}", path_stash);
148
149 // Demonstrate stash listing and filtering
150 println!("\n=== Stash Listing and Filtering ===");
151
152 let stashes = repo.stash_list()?;
153 println!("\nAll stashes ({} total):", stashes.len());
154 for stash in stashes.iter() {
155 println!(
156 " [{}] {} -> {}",
157 stash.index,
158 stash.message,
159 stash.hash.short()
160 );
161 println!(
162 " Branch: {} | Created: {}",
163 stash.branch,
164 stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165 );
166 }
167
168 // Test filtering
169 println!("\nFiltering examples:");
170
171 // Find stashes containing specific text
172 let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173 println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174 for stash in &wip_stashes {
175 println!(" - {}", stash.message);
176 }
177
178 let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179 println!(
180 "Stashes containing 'config': {} found",
181 config_stashes.len()
182 );
183
184 // Get latest stash
185 if let Some(latest) = stashes.latest() {
186 println!("Latest stash: {}", latest.message);
187 }
188
189 // Get specific stash by index
190 if let Some(second_stash) = stashes.get(1) {
191 println!("Second stash: {}", second_stash.message);
192 }
193
194 // Demonstrate stash content viewing
195 println!("\n=== Viewing Stash Contents ===");
196
197 println!("\nShowing contents of latest stash:");
198 let stash_contents = repo.stash_show(0)?;
199 println!("{}", stash_contents);
200
201 // Demonstrate stash application
202 println!("\n=== Applying and Popping Stashes ===");
203
204 println!("\n1. Testing stash apply (keeps stash in list):");
205 let stashes_before_apply = repo.stash_list()?;
206 println!("Stashes before apply: {}", stashes_before_apply.len());
207
208 // Apply the latest stash
209 repo.stash_apply(0, StashApplyOptions::new())?;
210 println!("Applied stash@{{0}}");
211
212 let stashes_after_apply = repo.stash_list()?;
213 println!("Stashes after apply: {}", stashes_after_apply.len());
214
215 // Check what was restored
216 let status_after_apply = repo.status()?;
217 println!("Status after apply:");
218 println!(
219 " Staged files: {}",
220 status_after_apply.staged_files().count()
221 );
222 println!(
223 " Unstaged files: {}",
224 status_after_apply.unstaged_files().count()
225 );
226 println!(
227 " Untracked files: {}",
228 status_after_apply.untracked_entries().count()
229 );
230
231 println!("\n2. Testing stash pop (removes stash from list):");
232
233 // First, stash current changes again to have something to pop
234 repo.stash_save("Temporary stash for pop test")?;
235
236 let stashes_before_pop = repo.stash_list()?;
237 println!("Stashes before pop: {}", stashes_before_pop.len());
238
239 // Pop the latest stash
240 repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241 println!("Popped stash@{{0}}");
242
243 let stashes_after_pop = repo.stash_list()?;
244 println!("Stashes after pop: {}", stashes_after_pop.len());
245
246 // Demonstrate advanced apply options
247 println!("\n3. Testing apply with index restoration:");
248
249 // Create a stash with staged changes
250 fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251 repo.add(&["staged_file.txt"])?;
252
253 fs::write(
254 repo_path.join("unstaged_file.txt"),
255 "This will be unstaged\n",
256 )?;
257
258 repo.stash_save("Stash with staged and unstaged changes")?;
259
260 // Apply with index restoration
261 let apply_options = StashApplyOptions::new().with_index();
262 repo.stash_apply(0, apply_options)?;
263 println!("Applied stash with index restoration");
264
265 let final_status = repo.status()?;
266 println!("Final status after index restoration:");
267 println!(" Staged files: {}", final_status.staged_files().count());
268 println!(
269 " Unstaged files: {}",
270 final_status.unstaged_files().count()
271 );
272
273 // Demonstrate stash management
274 println!("\n=== Stash Management ===");
275
276 // Create a few test stashes
277 for i in 1..=3 {
278 fs::write(
279 repo_path.join(format!("test{}.txt", i)),
280 format!("Test content {}\n", i),
281 )?;
282 repo.stash_save(&format!("Test stash {}", i))?;
283 }
284
285 let management_stashes = repo.stash_list()?;
286 println!(
287 "\nCreated {} test stashes for management demo",
288 management_stashes.len()
289 );
290
291 // Drop a specific stash
292 println!("\n1. Dropping middle stash:");
293 println!("Before drop: {} stashes", management_stashes.len());
294
295 repo.stash_drop(1)?; // Drop second stash (index 1)
296 println!("Dropped stash@{{1}}");
297
298 let after_drop = repo.stash_list()?;
299 println!("After drop: {} stashes", after_drop.len());
300
301 // Show remaining stashes
302 println!("Remaining stashes:");
303 for stash in after_drop.iter() {
304 println!(" [{}] {}", stash.index, stash.message);
305 }
306
307 // Clear all stashes
308 println!("\n2. Clearing all stashes:");
309 repo.stash_clear()?;
310 println!("Cleared all stashes");
311
312 let final_stashes = repo.stash_list()?;
313 println!("Stashes after clear: {}", final_stashes.len());
314
315 // Demonstrate error handling
316 println!("\n=== Error Handling ===");
317
318 println!("\n1. Testing operations on empty stash list:");
319
320 // Try to apply non-existent stash
321 match repo.stash_apply(0, StashApplyOptions::new()) {
322 Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323 Err(e) => println!("Expected error applying non-existent stash: {}", e),
324 }
325
326 // Try to show non-existent stash
327 match repo.stash_show(0) {
328 Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329 Err(e) => println!("Expected error showing non-existent stash: {}", e),
330 }
331
332 // Try to drop non-existent stash
333 match repo.stash_drop(0) {
334 Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335 Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336 }
337
338 // Summary
339 println!("\n=== Summary ===");
340 println!("\nStash operations demonstrated:");
341 println!(" ✓ Basic stash save and push with options");
342 println!(" ✓ Stash with untracked files and keep-index");
343 println!(" ✓ Stash specific paths only");
344 println!(" ✓ Comprehensive stash listing and filtering");
345 println!(" ✓ Stash content viewing");
346 println!(" ✓ Apply vs pop operations");
347 println!(" ✓ Index restoration during apply");
348 println!(" ✓ Stash dropping and clearing");
349 println!(" ✓ Error handling for edge cases");
350
351 println!("\nStash options demonstrated:");
352 println!(" ✓ with_untracked() - Include untracked files");
353 println!(" ✓ with_keep_index() - Keep staged changes");
354 println!(" ✓ with_paths() - Stash specific files only");
355 println!(" ✓ with_index() - Restore staged state on apply");
356 println!(" ✓ with_quiet() - Suppress output messages");
357
358 println!("\nStash filtering demonstrated:");
359 println!(" ✓ find_containing() - Search by message content");
360 println!(" ✓ latest() - Get most recent stash");
361 println!(" ✓ get() - Get stash by index");
362 println!(" ✓ for_branch() - Filter by branch name");
363
364 // Clean up
365 println!("\nCleaning up example repository...");
366 fs::remove_dir_all(&repo_path)?;
367 println!("Stash operations example completed successfully!");
368
369 Ok(())
370}
examples/file_lifecycle_operations.rs (line 152)
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 unstaged_files(&self) -> impl Iterator<Item = &FileEntry> + '_
pub fn unstaged_files(&self) -> impl Iterator<Item = &FileEntry> + '_
Get all files that have changes in the working tree (unstaged)
Examples found in repository?
examples/reset_operations.rs (line 179)
175fn show_repo_state(repo: &Repository) -> Result<()> {
176 let status = repo.status()?;
177
178 let staged_count = status.staged_files().count();
179 let unstaged_count = status.unstaged_files().count();
180 let untracked_count = status.untracked_entries().count();
181
182 println!(" Repository state:");
183 println!(" - Staged files: {}", staged_count);
184 println!(" - Modified files: {}", unstaged_count);
185 println!(" - Untracked files: {}", untracked_count);
186
187 if staged_count > 0 {
188 println!(" - Staged:");
189 for file in status.staged_files().take(5) {
190 println!(" * {}", file.path.display());
191 }
192 }
193
194 if unstaged_count > 0 {
195 println!(" - Modified:");
196 for file in status.unstaged_files().take(5) {
197 println!(" * {}", file.path.display());
198 }
199 }
200
201 if untracked_count > 0 {
202 println!(" - Untracked:");
203 for file in status.untracked_entries().take(5) {
204 println!(" * {}", file.path.display());
205 }
206 }
207
208 Ok(())
209}
More examples
examples/merge_operations.rs (line 281)
236fn demonstrate_merge_status_and_abort(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
237 println!("\n--- Demonstrating Merge Status and Options ---\n");
238
239 // Create a simple feature branch
240 println!("1. Creating simple feature branch...");
241 repo.checkout_new("feature/simple", None)?;
242
243 let simple_path = temp_dir.join("simple.txt");
244 fs::write(&simple_path, "Simple feature content")?;
245 repo.add(&["simple.txt"])?;
246 repo.commit("Add simple feature")?;
247
248 // Switch back to master
249 let branches = repo.branches()?;
250 let master_branch = branches.find("master").unwrap();
251 repo.checkout(master_branch)?;
252
253 // Test merge with different options
254 println!("\n2. Testing merge with custom options...");
255 let options = MergeOptions::new()
256 .with_fast_forward(FastForwardMode::Auto)
257 .with_message("Integrate simple feature".to_string());
258
259 let merge_status = repo.merge_with_options("feature/simple", options)?;
260
261 match merge_status {
262 MergeStatus::FastForward(hash) => {
263 println!(" ✓ Fast-forward merge completed: {}", hash);
264 }
265 MergeStatus::Success(hash) => {
266 println!(" ✓ Merge commit created: {}", hash);
267 }
268 MergeStatus::UpToDate => {
269 println!(" ✓ Already up to date");
270 }
271 MergeStatus::Conflicts(_) => {
272 println!(" ⚠️ Unexpected conflicts");
273 }
274 }
275
276 // Show final repository state
277 println!("\n3. Final repository state:");
278 let status = repo.status()?;
279 println!(
280 " Working directory clean: {}",
281 status.staged_files().count() == 0 && status.unstaged_files().count() == 0
282 );
283
284 let commits = repo.recent_commits(5)?;
285 println!(" Recent commits:");
286 for (i, commit) in commits.iter().enumerate() {
287 println!(
288 " {}: {} - {}",
289 i + 1,
290 commit.hash.short(),
291 commit.message.subject
292 );
293 }
294
295 Ok(())
296}
examples/basic_usage.rs (line 65)
16fn main() -> Result<()> {
17 println!("Rustic Git - Basic Usage Example\n");
18
19 // Use a temporary directory for this example
20 let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22 // Clean up any previous run
23 if repo_path.exists() {
24 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25 }
26
27 println!("Initializing new repository at: {}", repo_path.display());
28
29 // Initialize a new repository
30 let repo = Repository::init(&repo_path, false)?;
31 println!("Repository initialized successfully\n");
32
33 // Create some example files
34 println!("Creating example files...");
35 fs::create_dir_all(repo_path.join("src"))?;
36
37 fs::write(
38 repo_path.join("README.md"),
39 "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40 )?;
41
42 fs::write(
43 repo_path.join("src/main.rs"),
44 r#"fn main() {
45 println!("Hello from rustic-git example!");
46}
47"#,
48 )?;
49
50 fs::write(
51 repo_path.join("src/lib.rs"),
52 "// Library code goes here\npub fn hello() -> &'static str {\n \"Hello, World!\"\n}\n",
53 )?;
54
55 println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57 // Check repository status
58 println!("Checking repository status...");
59 let status = repo.status()?;
60
61 if status.is_clean() {
62 println!(" Repository is clean (no changes)");
63 } else {
64 println!(" Repository has changes:");
65 println!(" Unstaged files: {}", status.unstaged_files().count());
66 println!(" Untracked files: {}", status.untracked_entries().count());
67
68 // Show untracked files
69 for entry in status.untracked_entries() {
70 println!(" - {}", entry.path.display());
71 }
72 }
73 println!();
74
75 // Stage specific files first
76 println!("Staging files...");
77
78 // Stage README.md first
79 repo.add(&["README.md"])?;
80 println!("Staged README.md");
81
82 // Stage all remaining files
83 repo.add_all()?;
84 println!("Staged all remaining files");
85
86 // Check status after staging
87 let status_after_staging = repo.status()?;
88 println!("\nStatus after staging:");
89 if status_after_staging.is_clean() {
90 println!(" Repository is clean (all changes staged)");
91 } else {
92 println!(
93 " Files staged for commit: {}",
94 status_after_staging.entries.len()
95 );
96 for entry in &status_after_staging.entries {
97 println!(
98 " Index {:?}, Worktree {:?}: {}",
99 entry.index_status,
100 entry.worktree_status,
101 entry.path.display()
102 );
103 }
104 }
105 println!();
106
107 // Create a commit
108 println!("Creating commit...");
109 let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111 println!("Commit created successfully!");
112 println!(" Full hash: {}", hash);
113 println!(" Short hash: {}", hash.short());
114 println!();
115
116 // Verify final status
117 println!("Final repository status:");
118 let final_status = repo.status()?;
119 if final_status.is_clean() {
120 println!(" Repository is clean - all changes committed!");
121 } else {
122 println!(" Repository still has uncommitted changes");
123 }
124 println!();
125
126 // Clean up
127 println!("Cleaning up example repository...");
128 fs::remove_dir_all(&repo_path)?;
129 println!("Example completed successfully!");
130
131 Ok(())
132}
examples/status_checking.rs (line 133)
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}
248
249/// Display a summary of the repository status
250fn display_status_summary(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean (no changes)");
253 } else {
254 println!(" Repository has {} changes", status.entries.len());
255 println!(" Unstaged: {}", status.unstaged_files().count());
256 println!(" Untracked: {}", status.untracked_entries().count());
257 }
258}
examples/stash_operations.rs (line 80)
17fn main() -> Result<()> {
18 println!("Rustic Git - Stash Operations Example\n");
19
20 // Use a temporary directory for this example
21 let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23 // Clean up any previous run
24 if repo_path.exists() {
25 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26 }
27
28 println!("Initializing repository at: {}", repo_path.display());
29
30 // Initialize repository and configure user
31 let repo = Repository::init(&repo_path, false)?;
32 repo.config()
33 .set_user("Stash Demo User", "stash@example.com")?;
34
35 // Create initial commit to have a base
36 println!("\nCreating initial commit...");
37 fs::write(
38 repo_path.join("README.md"),
39 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40 )?;
41 repo.add(&["README.md"])?;
42 let initial_commit = repo.commit("Initial commit: Add README")?;
43 println!("Created initial commit: {}", initial_commit.short());
44
45 // Create some work to stash
46 println!("\n=== Creating Work to Stash ===");
47
48 // Create tracked file modifications
49 fs::write(
50 repo_path.join("README.md"),
51 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52 )?;
53
54 // Create new tracked files
55 fs::create_dir_all(repo_path.join("src"))?;
56 fs::write(
57 repo_path.join("src/main.rs"),
58 "fn main() {\n println!(\"Hello, stash!\");\n}\n",
59 )?;
60
61 // Create untracked files
62 fs::write(
63 repo_path.join("untracked.txt"),
64 "This file is not tracked by git\n",
65 )?;
66 fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68 // Stage some changes
69 repo.add(&["src/main.rs"])?;
70
71 println!("Created various types of changes:");
72 println!(" - Modified tracked file (README.md)");
73 println!(" - Added new file and staged it (src/main.rs)");
74 println!(" - Created untracked files (untracked.txt, temp.log)");
75
76 // Check repository status before stashing
77 let status = repo.status()?;
78 println!("\nRepository status before stashing:");
79 println!(" Staged files: {}", status.staged_files().count());
80 println!(" Unstaged files: {}", status.unstaged_files().count());
81 println!(" Untracked files: {}", status.untracked_entries().count());
82
83 // Demonstrate stash creation
84 println!("\n=== Creating Stashes ===");
85
86 // 1. Simple stash save
87 println!("\n1. Creating simple stash:");
88 let simple_stash = repo.stash_save("WIP: working on main function")?;
89 println!("Created stash: {}", simple_stash);
90 println!(" Index: {}", simple_stash.index);
91 println!(" Branch: {}", simple_stash.branch);
92 println!(" Hash: {}", simple_stash.hash.short());
93
94 // Check status after stash
95 let status_after_stash = repo.status()?;
96 println!("\nStatus after simple stash:");
97 println!(
98 " Staged files: {}",
99 status_after_stash.staged_files().count()
100 );
101 println!(
102 " Unstaged files: {}",
103 status_after_stash.unstaged_files().count()
104 );
105 println!(
106 " Untracked files: {}",
107 status_after_stash.untracked_entries().count()
108 );
109
110 // 2. Make more changes and create stash with untracked files
111 println!("\n2. Creating stash with untracked files:");
112
113 // Modify file again
114 fs::write(
115 repo_path.join("README.md"),
116 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117 )?;
118
119 // Create more untracked files
120 fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122 let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123 let untracked_stash = repo.stash_push(
124 "WIP: config changes with untracked files",
125 untracked_options,
126 )?;
127 println!("Created stash with untracked files: {}", untracked_stash);
128
129 // 3. Create stash with specific paths
130 println!("\n3. Creating stash with specific paths:");
131
132 // Make changes to multiple files and add them to git
133 fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134 fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135 fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137 // Add all files so they're tracked
138 repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140 // Now modify them so there are changes to stash
141 fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142 fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143 fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145 let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146 let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147 println!("Created stash with specific paths: {}", path_stash);
148
149 // Demonstrate stash listing and filtering
150 println!("\n=== Stash Listing and Filtering ===");
151
152 let stashes = repo.stash_list()?;
153 println!("\nAll stashes ({} total):", stashes.len());
154 for stash in stashes.iter() {
155 println!(
156 " [{}] {} -> {}",
157 stash.index,
158 stash.message,
159 stash.hash.short()
160 );
161 println!(
162 " Branch: {} | Created: {}",
163 stash.branch,
164 stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165 );
166 }
167
168 // Test filtering
169 println!("\nFiltering examples:");
170
171 // Find stashes containing specific text
172 let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173 println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174 for stash in &wip_stashes {
175 println!(" - {}", stash.message);
176 }
177
178 let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179 println!(
180 "Stashes containing 'config': {} found",
181 config_stashes.len()
182 );
183
184 // Get latest stash
185 if let Some(latest) = stashes.latest() {
186 println!("Latest stash: {}", latest.message);
187 }
188
189 // Get specific stash by index
190 if let Some(second_stash) = stashes.get(1) {
191 println!("Second stash: {}", second_stash.message);
192 }
193
194 // Demonstrate stash content viewing
195 println!("\n=== Viewing Stash Contents ===");
196
197 println!("\nShowing contents of latest stash:");
198 let stash_contents = repo.stash_show(0)?;
199 println!("{}", stash_contents);
200
201 // Demonstrate stash application
202 println!("\n=== Applying and Popping Stashes ===");
203
204 println!("\n1. Testing stash apply (keeps stash in list):");
205 let stashes_before_apply = repo.stash_list()?;
206 println!("Stashes before apply: {}", stashes_before_apply.len());
207
208 // Apply the latest stash
209 repo.stash_apply(0, StashApplyOptions::new())?;
210 println!("Applied stash@{{0}}");
211
212 let stashes_after_apply = repo.stash_list()?;
213 println!("Stashes after apply: {}", stashes_after_apply.len());
214
215 // Check what was restored
216 let status_after_apply = repo.status()?;
217 println!("Status after apply:");
218 println!(
219 " Staged files: {}",
220 status_after_apply.staged_files().count()
221 );
222 println!(
223 " Unstaged files: {}",
224 status_after_apply.unstaged_files().count()
225 );
226 println!(
227 " Untracked files: {}",
228 status_after_apply.untracked_entries().count()
229 );
230
231 println!("\n2. Testing stash pop (removes stash from list):");
232
233 // First, stash current changes again to have something to pop
234 repo.stash_save("Temporary stash for pop test")?;
235
236 let stashes_before_pop = repo.stash_list()?;
237 println!("Stashes before pop: {}", stashes_before_pop.len());
238
239 // Pop the latest stash
240 repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241 println!("Popped stash@{{0}}");
242
243 let stashes_after_pop = repo.stash_list()?;
244 println!("Stashes after pop: {}", stashes_after_pop.len());
245
246 // Demonstrate advanced apply options
247 println!("\n3. Testing apply with index restoration:");
248
249 // Create a stash with staged changes
250 fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251 repo.add(&["staged_file.txt"])?;
252
253 fs::write(
254 repo_path.join("unstaged_file.txt"),
255 "This will be unstaged\n",
256 )?;
257
258 repo.stash_save("Stash with staged and unstaged changes")?;
259
260 // Apply with index restoration
261 let apply_options = StashApplyOptions::new().with_index();
262 repo.stash_apply(0, apply_options)?;
263 println!("Applied stash with index restoration");
264
265 let final_status = repo.status()?;
266 println!("Final status after index restoration:");
267 println!(" Staged files: {}", final_status.staged_files().count());
268 println!(
269 " Unstaged files: {}",
270 final_status.unstaged_files().count()
271 );
272
273 // Demonstrate stash management
274 println!("\n=== Stash Management ===");
275
276 // Create a few test stashes
277 for i in 1..=3 {
278 fs::write(
279 repo_path.join(format!("test{}.txt", i)),
280 format!("Test content {}\n", i),
281 )?;
282 repo.stash_save(&format!("Test stash {}", i))?;
283 }
284
285 let management_stashes = repo.stash_list()?;
286 println!(
287 "\nCreated {} test stashes for management demo",
288 management_stashes.len()
289 );
290
291 // Drop a specific stash
292 println!("\n1. Dropping middle stash:");
293 println!("Before drop: {} stashes", management_stashes.len());
294
295 repo.stash_drop(1)?; // Drop second stash (index 1)
296 println!("Dropped stash@{{1}}");
297
298 let after_drop = repo.stash_list()?;
299 println!("After drop: {} stashes", after_drop.len());
300
301 // Show remaining stashes
302 println!("Remaining stashes:");
303 for stash in after_drop.iter() {
304 println!(" [{}] {}", stash.index, stash.message);
305 }
306
307 // Clear all stashes
308 println!("\n2. Clearing all stashes:");
309 repo.stash_clear()?;
310 println!("Cleared all stashes");
311
312 let final_stashes = repo.stash_list()?;
313 println!("Stashes after clear: {}", final_stashes.len());
314
315 // Demonstrate error handling
316 println!("\n=== Error Handling ===");
317
318 println!("\n1. Testing operations on empty stash list:");
319
320 // Try to apply non-existent stash
321 match repo.stash_apply(0, StashApplyOptions::new()) {
322 Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323 Err(e) => println!("Expected error applying non-existent stash: {}", e),
324 }
325
326 // Try to show non-existent stash
327 match repo.stash_show(0) {
328 Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329 Err(e) => println!("Expected error showing non-existent stash: {}", e),
330 }
331
332 // Try to drop non-existent stash
333 match repo.stash_drop(0) {
334 Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335 Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336 }
337
338 // Summary
339 println!("\n=== Summary ===");
340 println!("\nStash operations demonstrated:");
341 println!(" ✓ Basic stash save and push with options");
342 println!(" ✓ Stash with untracked files and keep-index");
343 println!(" ✓ Stash specific paths only");
344 println!(" ✓ Comprehensive stash listing and filtering");
345 println!(" ✓ Stash content viewing");
346 println!(" ✓ Apply vs pop operations");
347 println!(" ✓ Index restoration during apply");
348 println!(" ✓ Stash dropping and clearing");
349 println!(" ✓ Error handling for edge cases");
350
351 println!("\nStash options demonstrated:");
352 println!(" ✓ with_untracked() - Include untracked files");
353 println!(" ✓ with_keep_index() - Keep staged changes");
354 println!(" ✓ with_paths() - Stash specific files only");
355 println!(" ✓ with_index() - Restore staged state on apply");
356 println!(" ✓ with_quiet() - Suppress output messages");
357
358 println!("\nStash filtering demonstrated:");
359 println!(" ✓ find_containing() - Search by message content");
360 println!(" ✓ latest() - Get most recent stash");
361 println!(" ✓ get() - Get stash by index");
362 println!(" ✓ for_branch() - Filter by branch name");
363
364 // Clean up
365 println!("\nCleaning up example repository...");
366 fs::remove_dir_all(&repo_path)?;
367 println!("Stash operations example completed successfully!");
368
369 Ok(())
370}
examples/file_lifecycle_operations.rs (line 90)
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 untracked_entries(&self) -> impl Iterator<Item = &FileEntry> + '_
pub fn untracked_entries(&self) -> impl Iterator<Item = &FileEntry> + '_
Get all untracked files (new API)
Examples found in repository?
examples/reset_operations.rs (line 180)
175fn show_repo_state(repo: &Repository) -> Result<()> {
176 let status = repo.status()?;
177
178 let staged_count = status.staged_files().count();
179 let unstaged_count = status.unstaged_files().count();
180 let untracked_count = status.untracked_entries().count();
181
182 println!(" Repository state:");
183 println!(" - Staged files: {}", staged_count);
184 println!(" - Modified files: {}", unstaged_count);
185 println!(" - Untracked files: {}", untracked_count);
186
187 if staged_count > 0 {
188 println!(" - Staged:");
189 for file in status.staged_files().take(5) {
190 println!(" * {}", file.path.display());
191 }
192 }
193
194 if unstaged_count > 0 {
195 println!(" - Modified:");
196 for file in status.unstaged_files().take(5) {
197 println!(" * {}", file.path.display());
198 }
199 }
200
201 if untracked_count > 0 {
202 println!(" - Untracked:");
203 for file in status.untracked_entries().take(5) {
204 println!(" * {}", file.path.display());
205 }
206 }
207
208 Ok(())
209}
More examples
examples/basic_usage.rs (line 66)
16fn main() -> Result<()> {
17 println!("Rustic Git - Basic Usage Example\n");
18
19 // Use a temporary directory for this example
20 let repo_path = env::temp_dir().join("rustic_git_basic_example");
21
22 // Clean up any previous run
23 if repo_path.exists() {
24 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
25 }
26
27 println!("Initializing new repository at: {}", repo_path.display());
28
29 // Initialize a new repository
30 let repo = Repository::init(&repo_path, false)?;
31 println!("Repository initialized successfully\n");
32
33 // Create some example files
34 println!("Creating example files...");
35 fs::create_dir_all(repo_path.join("src"))?;
36
37 fs::write(
38 repo_path.join("README.md"),
39 "# My Awesome Project\n\nThis is a demo project for rustic-git!\n",
40 )?;
41
42 fs::write(
43 repo_path.join("src/main.rs"),
44 r#"fn main() {
45 println!("Hello from rustic-git example!");
46}
47"#,
48 )?;
49
50 fs::write(
51 repo_path.join("src/lib.rs"),
52 "// Library code goes here\npub fn hello() -> &'static str {\n \"Hello, World!\"\n}\n",
53 )?;
54
55 println!("Created 3 files: README.md, src/main.rs, src/lib.rs\n");
56
57 // Check repository status
58 println!("Checking repository status...");
59 let status = repo.status()?;
60
61 if status.is_clean() {
62 println!(" Repository is clean (no changes)");
63 } else {
64 println!(" Repository has changes:");
65 println!(" Unstaged files: {}", status.unstaged_files().count());
66 println!(" Untracked files: {}", status.untracked_entries().count());
67
68 // Show untracked files
69 for entry in status.untracked_entries() {
70 println!(" - {}", entry.path.display());
71 }
72 }
73 println!();
74
75 // Stage specific files first
76 println!("Staging files...");
77
78 // Stage README.md first
79 repo.add(&["README.md"])?;
80 println!("Staged README.md");
81
82 // Stage all remaining files
83 repo.add_all()?;
84 println!("Staged all remaining files");
85
86 // Check status after staging
87 let status_after_staging = repo.status()?;
88 println!("\nStatus after staging:");
89 if status_after_staging.is_clean() {
90 println!(" Repository is clean (all changes staged)");
91 } else {
92 println!(
93 " Files staged for commit: {}",
94 status_after_staging.entries.len()
95 );
96 for entry in &status_after_staging.entries {
97 println!(
98 " Index {:?}, Worktree {:?}: {}",
99 entry.index_status,
100 entry.worktree_status,
101 entry.path.display()
102 );
103 }
104 }
105 println!();
106
107 // Create a commit
108 println!("Creating commit...");
109 let hash = repo.commit("Initial commit: Add project structure and basic files")?;
110
111 println!("Commit created successfully!");
112 println!(" Full hash: {}", hash);
113 println!(" Short hash: {}", hash.short());
114 println!();
115
116 // Verify final status
117 println!("Final repository status:");
118 let final_status = repo.status()?;
119 if final_status.is_clean() {
120 println!(" Repository is clean - all changes committed!");
121 } else {
122 println!(" Repository still has uncommitted changes");
123 }
124 println!();
125
126 // Clean up
127 println!("Cleaning up example repository...");
128 fs::remove_dir_all(&repo_path)?;
129 println!("Example completed successfully!");
130
131 Ok(())
132}
examples/status_checking.rs (line 141)
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}
248
249/// Display a summary of the repository status
250fn display_status_summary(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean (no changes)");
253 } else {
254 println!(" Repository has {} changes", status.entries.len());
255 println!(" Unstaged: {}", status.unstaged_files().count());
256 println!(" Untracked: {}", status.untracked_entries().count());
257 }
258}
examples/staging_operations.rs (line 193)
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}
248
249/// Display a breakdown of repository status
250fn display_status_breakdown(status: &rustic_git::GitStatus) {
251 if status.is_clean() {
252 println!(" Repository is clean");
253 return;
254 }
255
256 let mut index_counts = std::collections::HashMap::new();
257 let mut worktree_counts = std::collections::HashMap::new();
258
259 for entry in &status.entries {
260 if !matches!(entry.index_status, IndexStatus::Clean) {
261 *index_counts.entry(&entry.index_status).or_insert(0) += 1;
262 }
263 if !matches!(entry.worktree_status, WorktreeStatus::Clean) {
264 *worktree_counts.entry(&entry.worktree_status).or_insert(0) += 1;
265 }
266 }
267
268 println!(" Index status:");
269 for (index_status, count) in &index_counts {
270 let marker = match index_status {
271 IndexStatus::Modified => "[M]",
272 IndexStatus::Added => "[A]",
273 IndexStatus::Deleted => "[D]",
274 IndexStatus::Renamed => "[R]",
275 IndexStatus::Copied => "[C]",
276 IndexStatus::Clean => "[ ]",
277 };
278 println!(" {} {:?}: {} files", marker, index_status, count);
279 }
280
281 println!(" Worktree status:");
282 for (worktree_status, count) in &worktree_counts {
283 let marker = match worktree_status {
284 WorktreeStatus::Modified => "[M]",
285 WorktreeStatus::Deleted => "[D]",
286 WorktreeStatus::Untracked => "[?]",
287 WorktreeStatus::Ignored => "[I]",
288 WorktreeStatus::Clean => "[ ]",
289 };
290 println!(" {} {:?}: {} files", marker, worktree_status, count);
291 }
292}
293
294/// Display changes between two status states
295fn display_status_changes(
296 before: &rustic_git::GitStatus,
297 after: &rustic_git::GitStatus,
298 description: &str,
299) {
300 println!("\n Status changes {}:", description);
301
302 let before_count = before.entries.len();
303 let after_count = after.entries.len();
304
305 if before_count == after_count {
306 println!(" Total files unchanged ({} files)", after_count);
307 } else {
308 println!(
309 " Total files: {} → {} ({:+})",
310 before_count,
311 after_count,
312 after_count as i32 - before_count as i32
313 );
314 }
315
316 // Show status summary
317 let before_staged = before.staged_files().count();
318 let after_staged = after.staged_files().count();
319 let before_untracked = before.untracked_entries().count();
320 let after_untracked = after.untracked_entries().count();
321
322 if before_staged != after_staged {
323 println!(
324 " Staged files: {} → {} ({:+})",
325 before_staged,
326 after_staged,
327 after_staged as i32 - before_staged as i32
328 );
329 }
330
331 if before_untracked != after_untracked {
332 println!(
333 " Untracked files: {} → {} ({:+})",
334 before_untracked,
335 after_untracked,
336 after_untracked as i32 - before_untracked as i32
337 );
338 }
339}
examples/stash_operations.rs (line 81)
17fn main() -> Result<()> {
18 println!("Rustic Git - Stash Operations Example\n");
19
20 // Use a temporary directory for this example
21 let repo_path = env::temp_dir().join("rustic_git_stash_example");
22
23 // Clean up any previous run
24 if repo_path.exists() {
25 fs::remove_dir_all(&repo_path).expect("Failed to clean up previous example");
26 }
27
28 println!("Initializing repository at: {}", repo_path.display());
29
30 // Initialize repository and configure user
31 let repo = Repository::init(&repo_path, false)?;
32 repo.config()
33 .set_user("Stash Demo User", "stash@example.com")?;
34
35 // Create initial commit to have a base
36 println!("\nCreating initial commit...");
37 fs::write(
38 repo_path.join("README.md"),
39 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n",
40 )?;
41 repo.add(&["README.md"])?;
42 let initial_commit = repo.commit("Initial commit: Add README")?;
43 println!("Created initial commit: {}", initial_commit.short());
44
45 // Create some work to stash
46 println!("\n=== Creating Work to Stash ===");
47
48 // Create tracked file modifications
49 fs::write(
50 repo_path.join("README.md"),
51 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nAdded some new content!\n",
52 )?;
53
54 // Create new tracked files
55 fs::create_dir_all(repo_path.join("src"))?;
56 fs::write(
57 repo_path.join("src/main.rs"),
58 "fn main() {\n println!(\"Hello, stash!\");\n}\n",
59 )?;
60
61 // Create untracked files
62 fs::write(
63 repo_path.join("untracked.txt"),
64 "This file is not tracked by git\n",
65 )?;
66 fs::write(repo_path.join("temp.log"), "Temporary log file\n")?;
67
68 // Stage some changes
69 repo.add(&["src/main.rs"])?;
70
71 println!("Created various types of changes:");
72 println!(" - Modified tracked file (README.md)");
73 println!(" - Added new file and staged it (src/main.rs)");
74 println!(" - Created untracked files (untracked.txt, temp.log)");
75
76 // Check repository status before stashing
77 let status = repo.status()?;
78 println!("\nRepository status before stashing:");
79 println!(" Staged files: {}", status.staged_files().count());
80 println!(" Unstaged files: {}", status.unstaged_files().count());
81 println!(" Untracked files: {}", status.untracked_entries().count());
82
83 // Demonstrate stash creation
84 println!("\n=== Creating Stashes ===");
85
86 // 1. Simple stash save
87 println!("\n1. Creating simple stash:");
88 let simple_stash = repo.stash_save("WIP: working on main function")?;
89 println!("Created stash: {}", simple_stash);
90 println!(" Index: {}", simple_stash.index);
91 println!(" Branch: {}", simple_stash.branch);
92 println!(" Hash: {}", simple_stash.hash.short());
93
94 // Check status after stash
95 let status_after_stash = repo.status()?;
96 println!("\nStatus after simple stash:");
97 println!(
98 " Staged files: {}",
99 status_after_stash.staged_files().count()
100 );
101 println!(
102 " Unstaged files: {}",
103 status_after_stash.unstaged_files().count()
104 );
105 println!(
106 " Untracked files: {}",
107 status_after_stash.untracked_entries().count()
108 );
109
110 // 2. Make more changes and create stash with untracked files
111 println!("\n2. Creating stash with untracked files:");
112
113 // Modify file again
114 fs::write(
115 repo_path.join("README.md"),
116 "# Stash Demo Project\n\nDemonstrating Git stash operations.\n\nSecond round of changes!\n",
117 )?;
118
119 // Create more untracked files
120 fs::write(repo_path.join("config.json"), "{\"debug\": true}\n")?;
121
122 let untracked_options = StashOptions::new().with_untracked().with_keep_index();
123 let untracked_stash = repo.stash_push(
124 "WIP: config changes with untracked files",
125 untracked_options,
126 )?;
127 println!("Created stash with untracked files: {}", untracked_stash);
128
129 // 3. Create stash with specific paths
130 println!("\n3. Creating stash with specific paths:");
131
132 // Make changes to multiple files and add them to git
133 fs::write(repo_path.join("file1.txt"), "Content for file 1\n")?;
134 fs::write(repo_path.join("file2.txt"), "Content for file 2\n")?;
135 fs::write(repo_path.join("file3.txt"), "Content for file 3\n")?;
136
137 // Add all files so they're tracked
138 repo.add(&["file1.txt", "file2.txt", "file3.txt"])?;
139
140 // Now modify them so there are changes to stash
141 fs::write(repo_path.join("file1.txt"), "Modified content for file 1\n")?;
142 fs::write(repo_path.join("file2.txt"), "Modified content for file 2\n")?;
143 fs::write(repo_path.join("file3.txt"), "Modified content for file 3\n")?;
144
145 let path_options = StashOptions::new().with_paths(vec!["file1.txt".into(), "file2.txt".into()]);
146 let path_stash = repo.stash_push("WIP: specific files only", path_options)?;
147 println!("Created stash with specific paths: {}", path_stash);
148
149 // Demonstrate stash listing and filtering
150 println!("\n=== Stash Listing and Filtering ===");
151
152 let stashes = repo.stash_list()?;
153 println!("\nAll stashes ({} total):", stashes.len());
154 for stash in stashes.iter() {
155 println!(
156 " [{}] {} -> {}",
157 stash.index,
158 stash.message,
159 stash.hash.short()
160 );
161 println!(
162 " Branch: {} | Created: {}",
163 stash.branch,
164 stash.timestamp.format("%Y-%m-%d %H:%M:%S")
165 );
166 }
167
168 // Test filtering
169 println!("\nFiltering examples:");
170
171 // Find stashes containing specific text
172 let wip_stashes: Vec<_> = stashes.find_containing("WIP").collect();
173 println!("Stashes containing 'WIP': {} found", wip_stashes.len());
174 for stash in &wip_stashes {
175 println!(" - {}", stash.message);
176 }
177
178 let config_stashes: Vec<_> = stashes.find_containing("config").collect();
179 println!(
180 "Stashes containing 'config': {} found",
181 config_stashes.len()
182 );
183
184 // Get latest stash
185 if let Some(latest) = stashes.latest() {
186 println!("Latest stash: {}", latest.message);
187 }
188
189 // Get specific stash by index
190 if let Some(second_stash) = stashes.get(1) {
191 println!("Second stash: {}", second_stash.message);
192 }
193
194 // Demonstrate stash content viewing
195 println!("\n=== Viewing Stash Contents ===");
196
197 println!("\nShowing contents of latest stash:");
198 let stash_contents = repo.stash_show(0)?;
199 println!("{}", stash_contents);
200
201 // Demonstrate stash application
202 println!("\n=== Applying and Popping Stashes ===");
203
204 println!("\n1. Testing stash apply (keeps stash in list):");
205 let stashes_before_apply = repo.stash_list()?;
206 println!("Stashes before apply: {}", stashes_before_apply.len());
207
208 // Apply the latest stash
209 repo.stash_apply(0, StashApplyOptions::new())?;
210 println!("Applied stash@{{0}}");
211
212 let stashes_after_apply = repo.stash_list()?;
213 println!("Stashes after apply: {}", stashes_after_apply.len());
214
215 // Check what was restored
216 let status_after_apply = repo.status()?;
217 println!("Status after apply:");
218 println!(
219 " Staged files: {}",
220 status_after_apply.staged_files().count()
221 );
222 println!(
223 " Unstaged files: {}",
224 status_after_apply.unstaged_files().count()
225 );
226 println!(
227 " Untracked files: {}",
228 status_after_apply.untracked_entries().count()
229 );
230
231 println!("\n2. Testing stash pop (removes stash from list):");
232
233 // First, stash current changes again to have something to pop
234 repo.stash_save("Temporary stash for pop test")?;
235
236 let stashes_before_pop = repo.stash_list()?;
237 println!("Stashes before pop: {}", stashes_before_pop.len());
238
239 // Pop the latest stash
240 repo.stash_pop(0, StashApplyOptions::new().with_quiet())?;
241 println!("Popped stash@{{0}}");
242
243 let stashes_after_pop = repo.stash_list()?;
244 println!("Stashes after pop: {}", stashes_after_pop.len());
245
246 // Demonstrate advanced apply options
247 println!("\n3. Testing apply with index restoration:");
248
249 // Create a stash with staged changes
250 fs::write(repo_path.join("staged_file.txt"), "This will be staged\n")?;
251 repo.add(&["staged_file.txt"])?;
252
253 fs::write(
254 repo_path.join("unstaged_file.txt"),
255 "This will be unstaged\n",
256 )?;
257
258 repo.stash_save("Stash with staged and unstaged changes")?;
259
260 // Apply with index restoration
261 let apply_options = StashApplyOptions::new().with_index();
262 repo.stash_apply(0, apply_options)?;
263 println!("Applied stash with index restoration");
264
265 let final_status = repo.status()?;
266 println!("Final status after index restoration:");
267 println!(" Staged files: {}", final_status.staged_files().count());
268 println!(
269 " Unstaged files: {}",
270 final_status.unstaged_files().count()
271 );
272
273 // Demonstrate stash management
274 println!("\n=== Stash Management ===");
275
276 // Create a few test stashes
277 for i in 1..=3 {
278 fs::write(
279 repo_path.join(format!("test{}.txt", i)),
280 format!("Test content {}\n", i),
281 )?;
282 repo.stash_save(&format!("Test stash {}", i))?;
283 }
284
285 let management_stashes = repo.stash_list()?;
286 println!(
287 "\nCreated {} test stashes for management demo",
288 management_stashes.len()
289 );
290
291 // Drop a specific stash
292 println!("\n1. Dropping middle stash:");
293 println!("Before drop: {} stashes", management_stashes.len());
294
295 repo.stash_drop(1)?; // Drop second stash (index 1)
296 println!("Dropped stash@{{1}}");
297
298 let after_drop = repo.stash_list()?;
299 println!("After drop: {} stashes", after_drop.len());
300
301 // Show remaining stashes
302 println!("Remaining stashes:");
303 for stash in after_drop.iter() {
304 println!(" [{}] {}", stash.index, stash.message);
305 }
306
307 // Clear all stashes
308 println!("\n2. Clearing all stashes:");
309 repo.stash_clear()?;
310 println!("Cleared all stashes");
311
312 let final_stashes = repo.stash_list()?;
313 println!("Stashes after clear: {}", final_stashes.len());
314
315 // Demonstrate error handling
316 println!("\n=== Error Handling ===");
317
318 println!("\n1. Testing operations on empty stash list:");
319
320 // Try to apply non-existent stash
321 match repo.stash_apply(0, StashApplyOptions::new()) {
322 Ok(_) => println!("ERROR: Should have failed to apply non-existent stash"),
323 Err(e) => println!("Expected error applying non-existent stash: {}", e),
324 }
325
326 // Try to show non-existent stash
327 match repo.stash_show(0) {
328 Ok(_) => println!("ERROR: Should have failed to show non-existent stash"),
329 Err(e) => println!("Expected error showing non-existent stash: {}", e),
330 }
331
332 // Try to drop non-existent stash
333 match repo.stash_drop(0) {
334 Ok(_) => println!("ERROR: Should have failed to drop non-existent stash"),
335 Err(e) => println!("Expected error dropping non-existent stash: {}", e),
336 }
337
338 // Summary
339 println!("\n=== Summary ===");
340 println!("\nStash operations demonstrated:");
341 println!(" ✓ Basic stash save and push with options");
342 println!(" ✓ Stash with untracked files and keep-index");
343 println!(" ✓ Stash specific paths only");
344 println!(" ✓ Comprehensive stash listing and filtering");
345 println!(" ✓ Stash content viewing");
346 println!(" ✓ Apply vs pop operations");
347 println!(" ✓ Index restoration during apply");
348 println!(" ✓ Stash dropping and clearing");
349 println!(" ✓ Error handling for edge cases");
350
351 println!("\nStash options demonstrated:");
352 println!(" ✓ with_untracked() - Include untracked files");
353 println!(" ✓ with_keep_index() - Keep staged changes");
354 println!(" ✓ with_paths() - Stash specific files only");
355 println!(" ✓ with_index() - Restore staged state on apply");
356 println!(" ✓ with_quiet() - Suppress output messages");
357
358 println!("\nStash filtering demonstrated:");
359 println!(" ✓ find_containing() - Search by message content");
360 println!(" ✓ latest() - Get most recent stash");
361 println!(" ✓ get() - Get stash by index");
362 println!(" ✓ for_branch() - Filter by branch name");
363
364 // Clean up
365 println!("\nCleaning up example repository...");
366 fs::remove_dir_all(&repo_path)?;
367 println!("Stash operations example completed successfully!");
368
369 Ok(())
370}
examples/file_lifecycle_operations.rs (line 428)
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 ignored_files(&self) -> impl Iterator<Item = &FileEntry> + '_
pub fn ignored_files(&self) -> impl Iterator<Item = &FileEntry> + '_
Get all ignored files
Sourcepub fn files_with_index_status(
&self,
status: IndexStatus,
) -> impl Iterator<Item = &FileEntry> + '_
pub fn files_with_index_status( &self, status: IndexStatus, ) -> impl Iterator<Item = &FileEntry> + '_
Get files with specific index status
Examples found in repository?
examples/status_checking.rs (line 151)
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 files_with_worktree_status(
&self,
status: WorktreeStatus,
) -> impl Iterator<Item = &FileEntry> + '_
pub fn files_with_worktree_status( &self, status: WorktreeStatus, ) -> impl Iterator<Item = &FileEntry> + '_
Get files with specific worktree status
Trait Implementations§
impl StructuralPartialEq for GitStatus
Auto Trait Implementations§
impl Freeze for GitStatus
impl RefUnwindSafe for GitStatus
impl Send for GitStatus
impl Sync for GitStatus
impl Unpin for GitStatus
impl UnwindSafe for GitStatus
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more