GitStatus

Struct GitStatus 

Source
pub struct GitStatus {
    pub entries: Box<[FileEntry]>,
}

Fields§

§entries: Box<[FileEntry]>

Implementations§

Source§

impl GitStatus

Source

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
Hide additional 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}
Source

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
Hide additional 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}
Source

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
Hide additional 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}
Source

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
Hide additional 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}
Source

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
Hide additional 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}
Source

pub fn ignored_files(&self) -> impl Iterator<Item = &FileEntry> + '_

Get all ignored files

Source

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}
Source

pub fn files_with_worktree_status( &self, status: WorktreeStatus, ) -> impl Iterator<Item = &FileEntry> + '_

Get files with specific worktree status

Source

pub fn entries(&self) -> &[FileEntry]

Get all file entries

Trait Implementations§

Source§

impl Clone for GitStatus

Source§

fn clone(&self) -> GitStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for GitStatus

Source§

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

Formats the value using the given formatter. Read more
Source§

impl PartialEq for GitStatus

Source§

fn eq(&self, other: &GitStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl StructuralPartialEq for GitStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.