Skip to content

Commit 93895cf

Browse files
committed
feat(reset): implement comprehensive reset operations
Add complete reset functionality with soft, mixed, and hard modes including ResetMode enum, Repository methods, comprehensive tests, and example
1 parent 9038618 commit 93895cf

File tree

6 files changed

+585
-4
lines changed

6 files changed

+585
-4
lines changed

CLAUDE.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
- Author struct: name, email, timestamp with Display implementation
5757
- CommitMessage: subject and optional body parsing
5858
- CommitDetails: full commit info including file changes and diff stats
59-
- **Core types**: Hash (in src/types.rs), IndexStatus, WorktreeStatus, FileEntry (in src/commands/status.rs), Branch, BranchList, BranchType (in src/commands/branch.rs), Commit, CommitLog, Author, CommitMessage, CommitDetails, LogOptions (in src/commands/log.rs), RepoConfig (in src/commands/config.rs), Remote, RemoteList, FetchOptions, PushOptions (in src/commands/remote.rs), RestoreOptions, RemoveOptions, MoveOptions (in src/commands/files.rs), DiffOutput, FileDiff, DiffStatus, DiffOptions, DiffStats, DiffChunk, DiffLine, DiffLineType (in src/commands/diff.rs), Tag, TagList, TagType, TagOptions (in src/commands/tag.rs), Stash, StashList, StashOptions, StashApplyOptions (in src/commands/stash.rs)
59+
- **Core types**: Hash (in src/types.rs), IndexStatus, WorktreeStatus, FileEntry (in src/commands/status.rs), Branch, BranchList, BranchType (in src/commands/branch.rs), Commit, CommitLog, Author, CommitMessage, CommitDetails, LogOptions (in src/commands/log.rs), RepoConfig (in src/commands/config.rs), Remote, RemoteList, FetchOptions, PushOptions (in src/commands/remote.rs), RestoreOptions, RemoveOptions, MoveOptions (in src/commands/files.rs), DiffOutput, FileDiff, DiffStatus, DiffOptions, DiffStats, DiffChunk, DiffLine, DiffLineType (in src/commands/diff.rs), Tag, TagList, TagType, TagOptions (in src/commands/tag.rs), Stash, StashList, StashOptions, StashApplyOptions (in src/commands/stash.rs), ResetMode (in src/commands/reset.rs)
6060
- **Utility functions**: git(args, working_dir) -> Result<String>, git_raw(args, working_dir) -> Result<Output>
6161
- **Remote management**: Full remote operations with network support
6262
- Repository::add_remote(name, url) -> Result<()> - add remote repository
@@ -123,8 +123,16 @@
123123
- StashList: Box<[Stash]> with iterator methods (iter), search (find_containing, for_branch), access (latest, get), counting (len, is_empty)
124124
- StashOptions builder: untracked, keep_index, patch, staged_only, paths with builder pattern (with_untracked, with_keep_index, with_patch, with_staged_only, with_paths)
125125
- StashApplyOptions builder: restore_index, quiet with builder pattern (with_index, with_quiet)
126-
- **Command modules**: status.rs, add.rs, commit.rs, branch.rs, log.rs, config.rs, remote.rs, files.rs, diff.rs, tag.rs, stash.rs (in src/commands/)
127-
- **Testing**: 161+ tests covering all functionality with comprehensive edge cases
126+
- **Reset operations**: Complete reset functionality with type-safe API
127+
- Repository::reset_soft(commit) -> Result<()> - move HEAD, keep index and working tree
128+
- Repository::reset_mixed(commit) -> Result<()> - move HEAD, reset index, keep working tree (default)
129+
- Repository::reset_hard(commit) -> Result<()> - reset HEAD, index, and working tree to commit state
130+
- Repository::reset_with_mode(commit, mode) -> Result<()> - flexible reset with explicit ResetMode
131+
- Repository::reset_file(path) -> Result<()> - unstage specific file (already exists in files.rs)
132+
- ResetMode enum: Soft, Mixed, Hard with const as_str() methods
133+
- Complete error handling for invalid commits and references
134+
- **Command modules**: status.rs, add.rs, commit.rs, branch.rs, log.rs, config.rs, remote.rs, files.rs, diff.rs, tag.rs, stash.rs, reset.rs (in src/commands/)
135+
- **Testing**: 178+ tests covering all functionality with comprehensive edge cases
128136
- Run `cargo fmt && cargo build && cargo test && cargo clippy --all-targets --all-features -- -D warnings` after code changes
129137
- Make sure all examples are running
130138

@@ -144,6 +152,7 @@ The `examples/` directory contains comprehensive demonstrations of library funct
144152
- **diff_operations.rs**: Comprehensive diff operations showcase - unstaged/staged diffs, commit comparisons, advanced options (whitespace handling, path filtering), output formats (name-only, stat, numstat), and change analysis
145153
- **tag_operations.rs**: Complete tag management - create/delete/list tags, lightweight vs annotated tags, TagOptions builder, tag filtering and search, comprehensive tag workflows
146154
- **stash_operations.rs**: Complete stash management - save/apply/pop/list stashes, advanced options (untracked files, keep index, specific paths), stash filtering and search, comprehensive stash workflows
155+
- **reset_operations.rs**: Complete reset management - soft/mixed/hard resets, commit targeting, file-specific resets, error handling for invalid commits, comprehensive reset workflows
147156
- **error_handling.rs**: Comprehensive error handling patterns - GitError variants, recovery strategies
148157

149158
Run examples with: `cargo run --example <example_name>`

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Rustic Git provides a simple, ergonomic interface for common Git operations. It
3434
-**Lightweight and annotated tags** with type-safe API
3535
-**Stash operations** with comprehensive stash management and filtering
3636
-**Advanced stash options** (untracked files, keep index, specific paths)
37+
-**Reset operations** with comprehensive soft/mixed/hard reset support
38+
-**Repository history management** with type-safe ResetMode API
3739
- ✅ Type-safe error handling with custom GitError enum
3840
- ✅ Universal `Hash` type for Git objects
3941
-**Immutable collections** (Box<[T]>) for memory efficiency

examples/reset_operations.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use rustic_git::{Repository, ResetMode, Result};
2+
use std::{env, fs};
3+
4+
/// Comprehensive demonstration of reset operations in rustic-git
5+
///
6+
/// This example showcases:
7+
/// - Different reset modes (soft, mixed, hard)
8+
/// - Reset to specific commits
9+
/// - File-specific resets
10+
/// - Error handling for invalid commits
11+
///
12+
/// Reset operations are essential for managing git history and staging area.
13+
fn main() -> Result<()> {
14+
println!("=== Reset Operations Demo ===\n");
15+
16+
// Create a temporary directory for our example
17+
let temp_dir = env::temp_dir().join("rustic_git_reset_demo");
18+
19+
// Clean up if exists
20+
if temp_dir.exists() {
21+
fs::remove_dir_all(&temp_dir)?;
22+
}
23+
24+
println!("Working in temporary directory: {:?}\n", temp_dir);
25+
26+
// Initialize a new repository
27+
let repo = Repository::init(&temp_dir, false)?;
28+
29+
// Configure user for commits
30+
repo.config().set_user("Example User", "[email protected]")?;
31+
32+
demonstrate_reset_modes(&repo, &temp_dir)?;
33+
demonstrate_file_resets(&repo, &temp_dir)?;
34+
demonstrate_error_handling(&repo)?;
35+
36+
println!("\n=== Reset Operations Demo Complete ===");
37+
38+
// Clean up
39+
fs::remove_dir_all(&temp_dir)?;
40+
Ok(())
41+
}
42+
43+
fn demonstrate_reset_modes(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
44+
println!("--- Demonstrating Reset Modes ---\n");
45+
46+
// Create initial commits
47+
println!("1. Creating initial commits...");
48+
49+
// First commit
50+
let file1_path = temp_dir.join("file1.txt");
51+
fs::write(&file1_path, "Initial content")?;
52+
repo.add(&["file1.txt"])?;
53+
let first_commit = repo.commit("Initial commit")?;
54+
println!(" Created first commit: {}", first_commit);
55+
56+
// Second commit
57+
let file2_path = temp_dir.join("file2.txt");
58+
fs::write(&file2_path, "Second file content")?;
59+
repo.add(&["file2.txt"])?;
60+
let second_commit = repo.commit("Add file2.txt")?;
61+
println!(" Created second commit: {}", second_commit);
62+
63+
// Third commit
64+
fs::write(&file1_path, "Modified content")?;
65+
repo.add(&["file1.txt"])?;
66+
let third_commit = repo.commit("Modify file1.txt")?;
67+
println!(" Created third commit: {}", third_commit);
68+
69+
// Show current status
70+
println!("\n2. Current repository state:");
71+
show_repo_state(repo)?;
72+
73+
// Demonstrate soft reset
74+
println!("\n3. Performing soft reset to second commit...");
75+
repo.reset_soft(&second_commit.to_string())?;
76+
77+
println!(" After soft reset:");
78+
show_repo_state(repo)?;
79+
println!(" Note: Changes are still staged, working directory unchanged");
80+
81+
// Reset back to third commit for next demonstration
82+
repo.reset_hard(&third_commit.to_string())?;
83+
84+
// Demonstrate mixed reset (default)
85+
println!("\n4. Performing mixed reset to second commit...");
86+
repo.reset_mixed(&second_commit.to_string())?;
87+
88+
println!(" After mixed reset:");
89+
show_repo_state(repo)?;
90+
println!(" Note: Changes are unstaged but preserved in working directory");
91+
92+
// Reset back to third commit for next demonstration
93+
repo.reset_hard(&third_commit.to_string())?;
94+
95+
// Demonstrate hard reset
96+
println!("\n5. Performing hard reset to first commit...");
97+
repo.reset_hard(&first_commit.to_string())?;
98+
99+
println!(" After hard reset:");
100+
show_repo_state(repo)?;
101+
println!(" Note: All changes discarded, working directory matches commit");
102+
103+
// Demonstrate reset_with_mode for flexibility
104+
println!("\n6. Using reset_with_mode for explicit control...");
105+
106+
// Recreate second commit for demo
107+
fs::write(&file2_path, "Recreated second file")?;
108+
repo.add(&["file2.txt"])?;
109+
let _new_commit = repo.commit("Recreate file2.txt")?;
110+
111+
repo.reset_with_mode(&first_commit.to_string(), ResetMode::Mixed)?;
112+
println!(" Used ResetMode::Mixed explicitly");
113+
show_repo_state(repo)?;
114+
115+
Ok(())
116+
}
117+
118+
fn demonstrate_file_resets(repo: &Repository, temp_dir: &std::path::Path) -> Result<()> {
119+
println!("\n--- Demonstrating File-Specific Resets ---\n");
120+
121+
// Create some files and stage them
122+
println!("1. Creating and staging multiple files...");
123+
124+
let file_a = temp_dir.join("fileA.txt");
125+
let file_b = temp_dir.join("fileB.txt");
126+
127+
fs::write(&file_a, "Content A")?;
128+
fs::write(&file_b, "Content B")?;
129+
130+
repo.add(&["fileA.txt", "fileB.txt"])?;
131+
println!(" Staged fileA.txt and fileB.txt");
132+
133+
show_repo_state(repo)?;
134+
135+
// Reset a single file (using existing reset_file from files.rs)
136+
println!("\n2. Resetting single file (fileA.txt)...");
137+
repo.reset_file("fileA.txt")?;
138+
139+
println!(" After resetting fileA.txt:");
140+
show_repo_state(repo)?;
141+
println!(" Note: fileA.txt is unstaged, fileB.txt remains staged");
142+
143+
// Demonstrate HEAD reset (unstage all changes)
144+
println!("\n3. Performing mixed reset to HEAD (unstage all)...");
145+
repo.reset_mixed("HEAD")?;
146+
147+
println!(" After reset HEAD:");
148+
show_repo_state(repo)?;
149+
println!(" Note: All staged changes are now unstaged");
150+
151+
Ok(())
152+
}
153+
154+
fn demonstrate_error_handling(repo: &Repository) -> Result<()> {
155+
println!("\n--- Demonstrating Error Handling ---\n");
156+
157+
// Try to reset to invalid commit
158+
println!("1. Attempting reset to invalid commit hash...");
159+
match repo.reset_mixed("invalid_commit_hash") {
160+
Ok(_) => println!(" Unexpected success!"),
161+
Err(e) => println!(" Expected error: {}", e),
162+
}
163+
164+
// Try to reset to non-existent reference
165+
println!("\n2. Attempting reset to non-existent reference...");
166+
match repo.reset_soft("nonexistent-branch") {
167+
Ok(_) => println!(" Unexpected success!"),
168+
Err(e) => println!(" Expected error: {}", e),
169+
}
170+
171+
println!("\n Error handling works correctly!");
172+
Ok(())
173+
}
174+
175+
fn 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+
}

src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod diff;
66
pub mod files;
77
pub mod log;
88
pub mod remote;
9+
pub mod reset;
910
pub mod stash;
1011
pub mod status;
1112
pub mod tag;
@@ -18,6 +19,7 @@ pub use diff::{
1819
pub use files::{MoveOptions, RemoveOptions, RestoreOptions};
1920
pub use log::{Author, Commit, CommitDetails, CommitLog, CommitMessage, LogOptions};
2021
pub use remote::{FetchOptions, PushOptions, Remote, RemoteList};
22+
pub use reset::ResetMode;
2123
pub use stash::{Stash, StashApplyOptions, StashList, StashOptions};
2224
pub use status::{FileEntry, GitStatus, IndexStatus, WorktreeStatus};
2325
pub use tag::{Tag, TagList, TagOptions, TagType};

0 commit comments

Comments
 (0)