@@ -29,11 +29,12 @@ Rustic Git provides a simple, ergonomic interface for common Git operations. It
2929- ✅ ** Remote management** with full CRUD operations and network support
3030- ✅ ** Network operations** (fetch, push, clone) with advanced options
3131- ✅ ** File lifecycle operations** (restore, reset, remove, move, .gitignore management)
32+ - ✅ ** Diff operations** with multi-level API and comprehensive options
3233- ✅ Type-safe error handling with custom GitError enum
3334- ✅ Universal ` Hash ` type for Git objects
3435- ✅ ** Immutable collections** (Box<[ T] >) for memory efficiency
3536- ✅ ** Const enum conversions** with zero runtime cost
36- - ✅ Comprehensive test coverage (128 + tests)
37+ - ✅ Comprehensive test coverage (144 + tests)
3738
3839## Installation
3940
@@ -53,7 +54,7 @@ cargo add rustic-git
5354## Quick Start
5455
5556``` rust
56- use rustic_git :: {Repository , Result , IndexStatus , WorktreeStatus , LogOptions , FetchOptions , PushOptions , RestoreOptions , RemoveOptions , MoveOptions };
57+ use rustic_git :: {Repository , Result , IndexStatus , WorktreeStatus , LogOptions , FetchOptions , PushOptions , RestoreOptions , RemoveOptions , MoveOptions , DiffOptions , DiffOutput , DiffStatus };
5758
5859fn main () -> Result <()> {
5960 // Initialize a new repository
@@ -164,6 +165,41 @@ fn main() -> Result<()> {
164165 let is_ignored = repo . ignore_check (" temp_file.tmp" )? ;
165166 let patterns = repo . ignore_list ()? ;
166167
168+ // Diff operations
169+ // Check for unstaged changes
170+ let diff = repo . diff ()? ;
171+ if ! diff . is_empty () {
172+ println! (" Unstaged changes found:" );
173+ for file in diff . iter () {
174+ println! (" {} {}" , file . status, file . path. display ());
175+ }
176+ }
177+
178+ // Check for staged changes
179+ let staged_diff = repo . diff_staged ()? ;
180+ println! (" Files staged for commit: {}" , staged_diff . len ());
181+
182+ // Compare between commits
183+ let recent_commits = repo . recent_commits (2 )? ;
184+ if recent_commits . len () >= 2 {
185+ let commit_diff = repo . diff_commits (
186+ & recent_commits . iter (). nth (1 ). unwrap (). hash,
187+ & recent_commits . iter (). nth (0 ). unwrap (). hash,
188+ )? ;
189+ println! (" Changes in last commit: {}" , commit_diff . stats);
190+ }
191+
192+ // Diff with options
193+ let diff_opts = DiffOptions :: new ()
194+ . ignore_whitespace ()
195+ . context_lines (5 );
196+ let detailed_diff = repo . diff_with_options (& diff_opts )? ;
197+
198+ // Filter by status
199+ let added_files : Vec <_ > = detailed_diff . files_with_status (DiffStatus :: Added ). collect ();
200+ let modified_files : Vec <_ > = detailed_diff . files_with_status (DiffStatus :: Modified ). collect ();
201+ println! (" Added: {} files, Modified: {} files" , added_files . len (), modified_files . len ());
202+
167203 Ok (())
168204}
169205```
@@ -1127,6 +1163,218 @@ fn main() -> rustic_git::Result<()> {
11271163}
11281164```
11291165
1166+ ### Diff Operations
1167+
1168+ The diff operations provide a comprehensive API for comparing different states in your Git repository. All diff operations return a ` DiffOutput ` containing file changes and statistics.
1169+
1170+ #### ` Repository::diff() -> Result<DiffOutput> `
1171+
1172+ Get differences between working directory and index (unstaged changes).
1173+
1174+ ``` rust
1175+ let diff = repo . diff ()? ;
1176+
1177+ if diff . is_empty () {
1178+ println! (" No unstaged changes" );
1179+ } else {
1180+ println! (" Unstaged changes in {} files:" , diff . len ());
1181+ for file in diff . iter () {
1182+ println! (" {} {} (+{} -{} lines)" ,
1183+ file . status,
1184+ file . path. display (),
1185+ file . additions,
1186+ file . deletions);
1187+ }
1188+ println! (" {}" , diff . stats);
1189+ }
1190+ ```
1191+
1192+ #### ` Repository::diff_staged() -> Result<DiffOutput> `
1193+
1194+ Get differences between index and HEAD (staged changes).
1195+
1196+ ``` rust
1197+ let staged_diff = repo . diff_staged ()? ;
1198+ println! (" Files staged for commit: {}" , staged_diff . len ());
1199+
1200+ // Filter by change type
1201+ let added_files : Vec <_ > = staged_diff . files_with_status (DiffStatus :: Added ). collect ();
1202+ let modified_files : Vec <_ > = staged_diff . files_with_status (DiffStatus :: Modified ). collect ();
1203+ let deleted_files : Vec <_ > = staged_diff . files_with_status (DiffStatus :: Deleted ). collect ();
1204+
1205+ println! (" Staged changes: {} added, {} modified, {} deleted" ,
1206+ added_files . len (), modified_files . len (), deleted_files . len ());
1207+ ```
1208+
1209+ #### ` Repository::diff_head() -> Result<DiffOutput> `
1210+
1211+ Get all differences between working directory and HEAD (both staged and unstaged).
1212+
1213+ ``` rust
1214+ let head_diff = repo . diff_head ()? ;
1215+ println! (" All changes since last commit:" );
1216+ for file in head_diff . iter () {
1217+ println! (" {} {}" , file . status, file . path. display ());
1218+ }
1219+ ```
1220+
1221+ #### ` Repository::diff_commits(from, to) -> Result<DiffOutput> `
1222+
1223+ Compare two specific commits.
1224+
1225+ ``` rust
1226+ let commits = repo . recent_commits (2 )? ;
1227+ if commits . len () >= 2 {
1228+ let diff = repo . diff_commits (& commits [1 ]. hash, & commits [0 ]. hash)? ;
1229+ println! (" Changes in last commit:" );
1230+ println! (" {}" , diff . stats);
1231+
1232+ // Show renames and copies
1233+ for file in diff . iter () {
1234+ match file . status {
1235+ DiffStatus :: Renamed => {
1236+ if let Some (old_path ) = & file . old_path {
1237+ println! (" Renamed: {} -> {}" , old_path . display (), file . path. display ());
1238+ }
1239+ },
1240+ DiffStatus :: Copied => {
1241+ if let Some (old_path ) = & file . old_path {
1242+ println! (" Copied: {} -> {}" , old_path . display (), file . path. display ());
1243+ }
1244+ },
1245+ _ => println! (" {} {}" , file . status, file . path. display ()),
1246+ }
1247+ }
1248+ }
1249+ ```
1250+
1251+ #### ` Repository::diff_with_options(options) -> Result<DiffOutput> `
1252+
1253+ Advanced diff operations with custom options.
1254+
1255+ ``` rust
1256+ // Diff with custom options
1257+ let options = DiffOptions :: new ()
1258+ . ignore_whitespace () // Ignore whitespace changes
1259+ . ignore_whitespace_change () // Ignore whitespace amount changes
1260+ . ignore_blank_lines () // Ignore blank line changes
1261+ . context_lines (10 ) // Show 10 lines of context
1262+ . paths (vec! [PathBuf :: from (" src/" )]); // Only diff src/ directory
1263+
1264+ let diff = repo . diff_with_options (& options )? ;
1265+
1266+ // Different output formats
1267+ let name_only = repo . diff_with_options (& DiffOptions :: new (). name_only ())? ;
1268+ println! (" Changed files:" );
1269+ for file in name_only . iter () {
1270+ println! (" {}" , file . path. display ());
1271+ }
1272+
1273+ let stat_diff = repo . diff_with_options (& DiffOptions :: new (). stat_only ())? ;
1274+ println! (" Diff statistics:\ n {}" , stat_diff );
1275+
1276+ let numstat_diff = repo . diff_with_options (& DiffOptions :: new (). numstat ())? ;
1277+ for file in numstat_diff . iter () {
1278+ println! (" {}\ t +{}\ t -{}" , file . path. display (), file . additions, file . deletions);
1279+ }
1280+ ```
1281+
1282+ #### Diff Types and Data Structures
1283+
1284+ ``` rust
1285+ // Main diff output containing files and statistics
1286+ pub struct DiffOutput {
1287+ pub files : Box <[FileDiff ]>, // Immutable collection of file changes
1288+ pub stats : DiffStats , // Aggregate statistics
1289+ }
1290+
1291+ // Individual file changes
1292+ pub struct FileDiff {
1293+ pub path : PathBuf , // Current file path
1294+ pub old_path : Option <PathBuf >, // Original path (for renames/copies)
1295+ pub status : DiffStatus , // Type of change
1296+ pub chunks : Box <[DiffChunk ]>, // Diff chunks (for full diff parsing)
1297+ pub additions : usize , // Lines added
1298+ pub deletions : usize , // Lines deleted
1299+ }
1300+
1301+ // Change status for files
1302+ pub enum DiffStatus {
1303+ Added , // New file
1304+ Modified , // Changed file
1305+ Deleted , // Removed file
1306+ Renamed , // File renamed
1307+ Copied , // File copied
1308+ }
1309+
1310+ // Aggregate statistics
1311+ pub struct DiffStats {
1312+ pub files_changed : usize ,
1313+ pub insertions : usize ,
1314+ pub deletions : usize ,
1315+ }
1316+ ```
1317+
1318+ #### Diff Options Builder
1319+
1320+ ``` rust
1321+ // Build custom diff options
1322+ let options = DiffOptions :: new ()
1323+ . context_lines (5 ) // Lines of context around changes
1324+ . ignore_whitespace () // --ignore-all-space
1325+ . ignore_whitespace_change () // --ignore-space-change
1326+ . ignore_blank_lines () // --ignore-blank-lines
1327+ . name_only () // Show only file names
1328+ . stat_only () // Show only statistics
1329+ . numstat () // Show numerical statistics
1330+ . cached () // Compare index with HEAD
1331+ . no_index () // Compare files outside git
1332+ . paths (vec! [PathBuf :: from (" src/" )]); // Limit to specific paths
1333+
1334+ let diff = repo . diff_with_options (& options )? ;
1335+ ```
1336+
1337+ #### Working with Diff Results
1338+
1339+ ``` rust
1340+ let diff = repo . diff ()? ;
1341+
1342+ // Check if any changes exist
1343+ if diff . is_empty () {
1344+ println! (" No changes" );
1345+ return Ok (());
1346+ }
1347+
1348+ // Iterate over all changed files
1349+ for file in diff . iter () {
1350+ println! (" {} {}" , file . status, file . path. display ());
1351+
1352+ // Check if file is binary
1353+ if file . is_binary () {
1354+ println! (" (binary file)" );
1355+ continue ;
1356+ }
1357+
1358+ // Show change statistics
1359+ println! (" +{} -{} lines" , file . additions, file . deletions);
1360+ }
1361+
1362+ // Filter by specific change types
1363+ let new_files : Vec <_ > = diff . files_with_status (DiffStatus :: Added ). collect ();
1364+ let modified_files : Vec <_ > = diff . files_with_status (DiffStatus :: Modified ). collect ();
1365+ let deleted_files : Vec <_ > = diff . files_with_status (DiffStatus :: Deleted ). collect ();
1366+
1367+ println! (" Summary: {} new, {} modified, {} deleted" ,
1368+ new_files . len (), modified_files . len (), deleted_files . len ());
1369+
1370+ // Access aggregate statistics
1371+ println! (" Total: {}" , diff . stats);
1372+ println! (" Files: {}, +{} insertions, -{} deletions" ,
1373+ diff . stats. files_changed,
1374+ diff . stats. insertions,
1375+ diff . stats. deletions);
1376+ ```
1377+
11301378## Examples
11311379
11321380The ` examples/ ` directory contains comprehensive demonstrations of library functionality:
@@ -1164,6 +1412,9 @@ cargo run --example remote_operations
11641412# File lifecycle operations (restore, remove, move, .gitignore)
11651413cargo run --example file_lifecycle_operations
11661414
1415+ # Diff operations with multi-level API and comprehensive options
1416+ cargo run --example diff_operations
1417+
11671418# Error handling patterns and recovery strategies
11681419cargo run --example error_handling
11691420```
@@ -1179,6 +1430,7 @@ cargo run --example error_handling
11791430- ** ` config_operations.rs ` ** - Repository configuration management demonstration: user setup, configuration values, and repository-scoped settings
11801431- ** ` commit_history.rs ` ** - Comprehensive commit history & log operations showing all querying APIs, filtering, analysis, and advanced LogOptions usage
11811432- ** ` remote_operations.rs ` ** - Complete remote management demonstration: add, remove, rename remotes, fetch/push operations with options, and network operations
1433+ - ** ` diff_operations.rs ` ** - Comprehensive diff operations showcase: unstaged/staged diffs, commit comparisons, advanced options, filtering, and output formats
11821434- ** ` file_lifecycle_operations.rs ` ** - Comprehensive file management demonstration: restore, reset, remove, move operations, .gitignore management, and advanced file lifecycle workflows
11831435- ** ` error_handling.rs ` ** - Comprehensive error handling patterns showing GitError variants, recovery strategies, and best practices
11841436
0 commit comments