Skip to content

Commit 12c52dd

Browse files
authored
Merge pull request #4 from eugener/develop
feat(config): add repository configuration management API
2 parents f8842ae + f55c05e commit 12c52dd

File tree

10 files changed

+615
-28
lines changed

10 files changed

+615
-28
lines changed

CLAUDE.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
- API methods: staged_files(), unstaged_files(), untracked_entries(), files_with_index_status(), files_with_worktree_status()
2828
- **Staging functionality**: Repository::add(paths), Repository::add_all(), Repository::add_update()
2929
- **Commit functionality**: Repository::commit(message), Repository::commit_with_author(message, author) - return Hash of created commit
30+
- **Configuration functionality**: Repository::config() -> RepoConfig - manage git configuration values
31+
- RepoConfig::set_user(name, email) -> Result<()> - convenience method for user.name and user.email
32+
- RepoConfig::get_user() -> Result<(String, String)> - get user configuration as tuple
33+
- RepoConfig::set(key, value) -> Result<()> - set any git configuration value
34+
- RepoConfig::get(key) -> Result<String> - get any git configuration value
35+
- RepoConfig::unset(key) -> Result<()> - remove git configuration value
3036
- **Branch functionality**: Complete branch operations with type-safe API
3137
- Repository::branches() -> Result<BranchList> - list all branches with comprehensive filtering
3238
- Repository::current_branch() -> Result<Option<Branch>> - get currently checked out branch
@@ -50,10 +56,10 @@
5056
- Author struct: name, email, timestamp with Display implementation
5157
- CommitMessage: subject and optional body parsing
5258
- CommitDetails: full commit info including file changes and diff stats
53-
- **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)
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)
5460
- **Utility functions**: git(args, working_dir) -> Result<String>, git_raw(args, working_dir) -> Result<Output>
55-
- **Command modules**: status.rs, add.rs, commit.rs, branch.rs, log.rs (in src/commands/)
56-
- **Testing**: 101+ tests covering all functionality with comprehensive edge cases
61+
- **Command modules**: status.rs, add.rs, commit.rs, branch.rs, log.rs, config.rs (in src/commands/)
62+
- **Testing**: 106+ tests covering all functionality with comprehensive edge cases
5763
- Run `cargo fmt && cargo build && cargo test && cargo clippy --all-targets --all-features -- -D warnings` after code changes
5864
- Make sure all examples are running
5965

README.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ Rustic Git provides a simple, ergonomic interface for common Git operations. It
1717
-**Branch management** (create, delete, checkout, list)
1818
-**Commit history & log operations** with multi-level API
1919
-**Advanced commit querying** with filtering and analysis
20+
-**Repository configuration management** with type-safe API
2021
- ✅ Type-safe error handling with custom GitError enum
2122
- ✅ Universal `Hash` type for Git objects
2223
-**Immutable collections** (Box<[T]>) for memory efficiency
2324
-**Const enum conversions** with zero runtime cost
24-
- ✅ Comprehensive test coverage (101+ tests)
25+
- ✅ Comprehensive test coverage (106+ tests)
2526

2627
## Installation
2728

@@ -75,6 +76,9 @@ fn main() -> Result<()> {
7576
// Or stage all changes
7677
repo.add_all()?;
7778

79+
// Configure git user for commits
80+
repo.config().set_user("Your Name", "[email protected]")?;
81+
7882
// Create a commit
7983
let hash = repo.commit("Add new features")?;
8084
println!("Created commit: {}", hash.short());
@@ -259,6 +263,42 @@ Add all tracked files that have been modified (equivalent to `git add -u`).
259263
repo.add_update()?;
260264
```
261265

266+
### Configuration Operations
267+
268+
#### `Repository::config() -> RepoConfig`
269+
270+
Get a configuration manager for the repository to set and get git configuration values.
271+
272+
```rust
273+
// Configure git user (convenience method)
274+
repo.config().set_user("Your Name", "[email protected]")?;
275+
276+
// Get user configuration
277+
let (name, email) = repo.config().get_user()?;
278+
println!("User: {} <{}>", name, email);
279+
280+
// Set any git configuration value
281+
repo.config().set("core.autocrlf", "false")?;
282+
repo.config().set("pull.rebase", "true")?;
283+
284+
// Get any git configuration value
285+
let autocrlf = repo.config().get("core.autocrlf")?;
286+
println!("autocrlf setting: {}", autocrlf);
287+
288+
// Remove a configuration value
289+
repo.config().unset("user.signingkey")?;
290+
```
291+
292+
#### Configuration Methods
293+
294+
- **`set_user(name, email)`** - Convenience method to set both user.name and user.email
295+
- **`get_user()`** - Get user configuration as a tuple (name, email)
296+
- **`set(key, value)`** - Set any git configuration value
297+
- **`get(key)`** - Get any git configuration value as String
298+
- **`unset(key)`** - Remove a git configuration value
299+
300+
All configuration operations are scoped to the specific repository.
301+
262302
### Commit Operations
263303

264304
#### `Repository::commit(message) -> Result<Hash>`
@@ -675,6 +715,13 @@ fn main() -> rustic_git::Result<()> {
675715
// Create a new repository
676716
let repo = Repository::init("./my-project", false)?;
677717

718+
// Configure git user for commits
719+
repo.config().set_user("Your Name", "[email protected]")?;
720+
721+
// Set some additional repository settings
722+
repo.config().set("core.autocrlf", "false")?;
723+
repo.config().set("pull.rebase", "true")?;
724+
678725
// Create some files
679726
fs::write("./my-project/README.md", "# My Project")?;
680727
fs::create_dir_all("./my-project/src")?;
@@ -748,6 +795,14 @@ fn main() -> rustic_git::Result<()> {
748795
assert!(status.is_clean());
749796
println!("Repository is clean!");
750797

798+
// Display final configuration
799+
let (user_name, user_email) = repo.config().get_user()?;
800+
println!("Repository configured for: {} <{}>", user_name, user_email);
801+
802+
let autocrlf = repo.config().get("core.autocrlf")?;
803+
let rebase_setting = repo.config().get("pull.rebase")?;
804+
println!("Settings: autocrlf={}, pull.rebase={}", autocrlf, rebase_setting);
805+
751806
Ok(())
752807
}
753808
```
@@ -777,6 +832,9 @@ cargo run --example commit_workflows
777832
# Branch operations (create, delete, checkout, list)
778833
cargo run --example branch_operations
779834

835+
# Repository configuration management
836+
cargo run --example config_operations
837+
780838
# Commit history and log operations with advanced querying
781839
cargo run --example commit_history
782840

@@ -792,6 +850,7 @@ cargo run --example error_handling
792850
- **`staging_operations.rs`** - Shows all staging methods (add, add_all, add_update) with before/after status comparisons
793851
- **`commit_workflows.rs`** - Demonstrates commit operations and Hash type methods, including custom authors and hash management
794852
- **`branch_operations.rs`** - Complete branch management demonstration: create, checkout, delete branches, and BranchList filtering
853+
- **`config_operations.rs`** - Repository configuration management demonstration: user setup, configuration values, and repository-scoped settings
795854
- **`commit_history.rs`** - Comprehensive commit history & log operations showing all querying APIs, filtering, analysis, and advanced LogOptions usage
796855
- **`error_handling.rs`** - Comprehensive error handling patterns showing GitError variants, recovery strategies, and best practices
797856

@@ -852,6 +911,7 @@ cargo run --example status_checking
852911
cargo run --example staging_operations
853912
cargo run --example commit_workflows
854913
cargo run --example branch_operations
914+
cargo run --example config_operations
855915
cargo run --example commit_history
856916
cargo run --example error_handling
857917
```

examples/config_operations.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
//! Repository Configuration Operations Example
2+
//!
3+
//! This example demonstrates the repository configuration management features:
4+
//! - Setting and getting user configuration
5+
//! - Managing arbitrary git configuration values
6+
//! - Repository-scoped configuration operations
7+
//! - Configuration integration with commit operations
8+
//!
9+
//! Run with: cargo run --example config_operations
10+
11+
use rustic_git::{Repository, Result};
12+
use std::fs;
13+
use std::path::Path;
14+
15+
fn main() -> Result<()> {
16+
println!("Rustic Git - Repository Configuration Operations Example\n");
17+
18+
// Use a temporary directory for this example
19+
let repo_path = "/tmp/rustic_git_config_example";
20+
21+
// Clean up any previous run
22+
if Path::new(repo_path).exists() {
23+
fs::remove_dir_all(repo_path).expect("Failed to clean up previous example");
24+
}
25+
26+
println!("Initializing new repository at: {}", repo_path);
27+
28+
// Initialize a new repository
29+
let repo = Repository::init(repo_path, false)?;
30+
31+
// ==================== USER CONFIGURATION ====================
32+
33+
println!("\n[CONFIG] Configuring git user settings...");
34+
35+
// Set user configuration (convenience method)
36+
repo.config()
37+
.set_user("Alice Developer", "[email protected]")?;
38+
println!("Set user configuration");
39+
40+
// Verify user configuration
41+
let (name, email) = repo.config().get_user()?;
42+
println!("Current user: {} <{}>", name, email);
43+
44+
// ==================== GENERAL CONFIGURATION ====================
45+
46+
println!("\n[CONFIG] Setting repository configuration values...");
47+
48+
// Set various git configuration values
49+
repo.config().set("core.autocrlf", "false")?;
50+
repo.config().set("core.ignorecase", "true")?;
51+
repo.config().set("pull.rebase", "true")?;
52+
repo.config().set("push.default", "simple")?;
53+
repo.config().set("branch.autosetupmerge", "always")?;
54+
55+
println!("Set core configuration values");
56+
57+
// Get and display configuration values
58+
println!("\n[CONFIG] Current repository configuration:");
59+
60+
let configs = [
61+
"core.autocrlf",
62+
"core.ignorecase",
63+
"pull.rebase",
64+
"push.default",
65+
"branch.autosetupmerge",
66+
];
67+
68+
for config_key in &configs {
69+
match repo.config().get(config_key) {
70+
Ok(value) => println!(" {} = {}", config_key, value),
71+
Err(_) => println!(" {} = <not set>", config_key),
72+
}
73+
}
74+
75+
// ==================== CONFIGURATION WITH COMMITS ====================
76+
77+
println!("\n[COMMIT] Testing configuration with commit operations...");
78+
79+
// Create a test file
80+
let test_file_path = format!("{}/test.txt", repo_path);
81+
fs::write(
82+
&test_file_path,
83+
"Hello from rustic-git configuration example!",
84+
)?;
85+
println!("Created test file: test.txt");
86+
87+
// Stage the file
88+
repo.add(&["test.txt"])?;
89+
println!("Staged test.txt");
90+
91+
// Create a commit (this will use our configured user)
92+
let commit_hash = repo.commit("Add test file with configuration example")?;
93+
println!("Created commit: {}", commit_hash.short());
94+
95+
// ==================== CONFIGURATION MODIFICATION ====================
96+
97+
println!("\n[UPDATE] Modifying configuration values...");
98+
99+
// Change some configuration values
100+
repo.config().set("core.autocrlf", "true")?;
101+
repo.config()
102+
.set("user.email", "[email protected]")?;
103+
104+
println!("Updated configuration values");
105+
106+
// Display updated values
107+
let autocrlf = repo.config().get("core.autocrlf")?;
108+
let (updated_name, updated_email) = repo.config().get_user()?;
109+
110+
println!("Updated configuration:");
111+
println!(" core.autocrlf = {}", autocrlf);
112+
println!(" user: {} <{}>", updated_name, updated_email);
113+
114+
// ==================== CONFIGURATION REMOVAL ====================
115+
116+
println!("\n[REMOVE] Removing configuration values...");
117+
118+
// Remove a configuration value
119+
repo.config().unset("branch.autosetupmerge")?;
120+
println!("Removed branch.autosetupmerge");
121+
122+
// Try to get the removed value (should fail)
123+
match repo.config().get("branch.autosetupmerge") {
124+
Ok(value) => println!("Unexpected: branch.autosetupmerge = {}", value),
125+
Err(_) => println!("Confirmed: branch.autosetupmerge is not set"),
126+
}
127+
128+
// ==================== ADVANCED CONFIGURATION ====================
129+
130+
println!("\n[ADVANCED] Setting advanced configuration...");
131+
132+
// Set some advanced git configuration
133+
repo.config().set("diff.tool", "vimdiff")?;
134+
repo.config().set("merge.tool", "vimdiff")?;
135+
repo.config().set("alias.st", "status")?;
136+
repo.config().set("alias.co", "checkout")?;
137+
repo.config().set("alias.br", "branch")?;
138+
repo.config().set("alias.ci", "commit")?;
139+
140+
println!("Set advanced configuration (diff/merge tools and aliases)");
141+
142+
// Display all custom configuration
143+
println!("\n[SUMMARY] Complete repository configuration summary:");
144+
145+
let all_configs = [
146+
("User", vec![("user.name", ""), ("user.email", "")]),
147+
("Core", vec![("core.autocrlf", ""), ("core.ignorecase", "")]),
148+
("Workflow", vec![("pull.rebase", ""), ("push.default", "")]),
149+
("Tools", vec![("diff.tool", ""), ("merge.tool", "")]),
150+
(
151+
"Aliases",
152+
vec![
153+
("alias.st", ""),
154+
("alias.co", ""),
155+
("alias.br", ""),
156+
("alias.ci", ""),
157+
],
158+
),
159+
];
160+
161+
for (category, configs) in &all_configs {
162+
println!("\n {}:", category);
163+
for (key, _) in configs {
164+
match repo.config().get(key) {
165+
Ok(value) => println!(" {} = {}", key, value),
166+
Err(_) => println!(" {} = <not set>", key),
167+
}
168+
}
169+
}
170+
171+
// ==================== PRACTICAL EXAMPLE ====================
172+
173+
println!("\n[TEAM] Practical example: Setting up repository for a team...");
174+
175+
// Configure repository for team development
176+
repo.config().set("user.name", "Team Member")?;
177+
repo.config().set("user.email", "[email protected]")?;
178+
repo.config().set("core.autocrlf", "input")?;
179+
repo.config().set("core.safecrlf", "true")?;
180+
repo.config().set("pull.rebase", "true")?;
181+
repo.config().set("push.default", "current")?;
182+
repo.config().set("init.defaultBranch", "main")?;
183+
184+
println!("Configured repository for team development");
185+
186+
// Create another commit with the team configuration
187+
fs::write(
188+
format!("{}/team.md", repo_path),
189+
"# Team Development\n\nThis repository is configured for team development.",
190+
)?;
191+
repo.add(&["team.md"])?;
192+
let team_commit = repo.commit("Add team development documentation")?;
193+
194+
println!("Created team commit: {}", team_commit.short());
195+
196+
// Final verification
197+
let (final_name, final_email) = repo.config().get_user()?;
198+
println!("\n[FINAL] Final repository configuration:");
199+
println!(" User: {} <{}>", final_name, final_email);
200+
println!(" Repository configured for team development workflow");
201+
202+
// ==================== CLEANUP ====================
203+
204+
println!("\n[CLEANUP] Cleaning up...");
205+
fs::remove_dir_all(repo_path).expect("Failed to clean up example");
206+
println!("Example completed successfully!");
207+
208+
Ok(())
209+
}

src/commands/branch.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,11 @@ mod tests {
549549
// Create a repository with an initial commit
550550
let repo = Repository::init(test_path, false).unwrap();
551551

552+
// Configure git user for this repository to enable commits
553+
repo.config()
554+
.set_user("Test User", "[email protected]")
555+
.unwrap();
556+
552557
// Create a test file and commit to have a valid HEAD
553558
std::fs::write(format!("{}/test.txt", test_path), "test content").unwrap();
554559
repo.add(&["test.txt"]).unwrap();

0 commit comments

Comments
 (0)