Skip to content

Commit 1a4aa9d

Browse files
committed
feat(branch): implement complete branch operations with type-safe API
Add comprehensive branch management functionality including: - Repository::branches() with BranchList for efficient filtering - Repository::current_branch() to get active branch - Repository::create_branch() and Repository::delete_branch() for lifecycle - Repository::checkout() and Repository::checkout_new() for switching - Branch, BranchType, and BranchList types with iterator methods - Complete test coverage with 11 new tests (90+ total) - branch_operations.rs example demonstrating full workflow - Updated documentation with API reference and examples
1 parent 450a0ca commit 1a4aa9d

File tree

7 files changed

+956
-21
lines changed

7 files changed

+956
-21
lines changed

CLAUDE.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,29 @@
1818
- **Command execution**: Use std::process::Command with proper error handling and stderr capture
1919

2020
## Implementation
21-
- Available methods: Repository::init(path, bare), Repository::open(path), Repository::status(), Repository::add(paths), Repository::add_all(), Repository::add_update(), Repository::commit(message), Repository::commit_with_author(message, author)
21+
- **Repository lifecycle**: Repository::init(path, bare), Repository::open(path)
2222
- **Status functionality**: Enhanced GitStatus API with separate staged/unstaged file tracking
2323
- GitStatus with entries as Box<[FileEntry]> for immutable, efficient storage
2424
- FileEntry contains PathBuf, IndexStatus, and WorktreeStatus for precise Git state representation
2525
- IndexStatus enum: Clean, Modified, Added, Deleted, Renamed, Copied (with const from_char/to_char methods)
2626
- WorktreeStatus enum: Clean, Modified, Deleted, Untracked, Ignored (with const from_char/to_char methods)
2727
- API methods: staged_files(), unstaged_files(), untracked_entries(), files_with_index_status(), files_with_worktree_status()
28-
- Add functionality: Stage specific files, all changes, or tracked file updates
29-
- Commit functionality: Create commits and return Hash of created commit
30-
- Hash type: Universal git object hash representation with short() and Display methods
31-
- Utility functions: git(args, working_dir) -> Result<String>, git_raw(args, working_dir) -> Result<Output>
32-
- Command modules: status.rs, add.rs, commit.rs (in src/commands/)
33-
- Core types: Hash (in src/types.rs), IndexStatus, WorktreeStatus, FileEntry (in src/commands/status.rs)
28+
- **Staging functionality**: Repository::add(paths), Repository::add_all(), Repository::add_update()
29+
- **Commit functionality**: Repository::commit(message), Repository::commit_with_author(message, author) - return Hash of created commit
30+
- **Branch functionality**: Complete branch operations with type-safe API
31+
- Repository::branches() -> Result<BranchList> - list all branches with comprehensive filtering
32+
- Repository::current_branch() -> Result<Option<Branch>> - get currently checked out branch
33+
- Repository::create_branch(name, start_point) -> Result<Branch> - create new branch
34+
- Repository::delete_branch(branch, force) -> Result<()> - delete branch with safety checks
35+
- Repository::checkout(branch) -> Result<()> - switch to existing branch
36+
- Repository::checkout_new(name, start_point) -> Result<Branch> - create and checkout branch
37+
- Branch struct: name, branch_type, is_current, commit_hash, upstream tracking
38+
- BranchType enum: Local, RemoteTracking
39+
- BranchList: Box<[Branch]> with iterator methods (iter, local, remote), search (find, find_by_short_name), counting (len, local_count, remote_count)
40+
- **Core types**: Hash (in src/types.rs), IndexStatus, WorktreeStatus, FileEntry (in src/commands/status.rs), Branch, BranchList, BranchType (in src/commands/branch.rs)
41+
- **Utility functions**: git(args, working_dir) -> Result<String>, git_raw(args, working_dir) -> Result<Output>
42+
- **Command modules**: status.rs, add.rs, commit.rs, branch.rs (in src/commands/)
43+
- **Testing**: 90+ tests covering all functionality with comprehensive edge cases
3444
- Run `cargo fmt && cargo build && cargo test && cargo clippy --all-targets --all-features -- -D warnings` after code changes
3545
- Make sure all examples are running
3646

@@ -42,6 +52,7 @@ The `examples/` directory contains comprehensive demonstrations of library funct
4252
- **status_checking.rs**: Enhanced GitStatus API usage - staged/unstaged file queries, IndexStatus/WorktreeStatus filtering, comprehensive status analysis
4353
- **staging_operations.rs**: Staging operations - add(), add_all(), add_update() with before/after comparisons
4454
- **commit_workflows.rs**: Commit operations and Hash type - commit(), commit_with_author(), Hash methods
55+
- **branch_operations.rs**: Complete branch management - create/delete/checkout branches, BranchList filtering, branch type handling, search operations
4556
- **error_handling.rs**: Comprehensive error handling patterns - GitError variants, recovery strategies
4657

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

README.md

Lines changed: 216 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ Rustic Git provides a simple, ergonomic interface for common Git operations. It
1111
- ✅ Repository initialization and opening
1212
-**Enhanced file status checking** with separate staged/unstaged tracking
1313
-**Precise Git state representation** using IndexStatus and WorktreeStatus enums
14-
- ✅ File staging (add files, add all, add updates)
14+
- ✅ File staging (add files, add all, add updates)
1515
- ✅ Commit creation with hash return
16+
-**Complete branch operations** with type-safe Branch API
17+
-**Branch management** (create, delete, checkout, list)
1618
- ✅ Type-safe error handling with custom GitError enum
1719
- ✅ Universal `Hash` type for Git objects
18-
-**Immutable collections** (Box<[FileEntry]>) for memory efficiency
20+
-**Immutable collections** (Box<[T]>) for memory efficiency
1921
-**Const enum conversions** with zero runtime cost
20-
- ✅ Comprehensive test coverage (80+ tests)
22+
- ✅ Comprehensive test coverage (90+ tests)
2123

2224
## Installation
2325

@@ -47,7 +49,7 @@ fn main() -> Result<()> {
4749
let staged_count = status.staged_files().count();
4850
let unstaged_count = status.unstaged_files().count();
4951
let untracked_count = status.untracked_entries().count();
50-
52+
5153
println!("Repository status:");
5254
println!(" Staged: {} files", staged_count);
5355
println!(" Unstaged: {} files", unstaged_count);
@@ -69,6 +71,14 @@ fn main() -> Result<()> {
6971
let hash = repo.commit("Add new features")?;
7072
println!("Created commit: {}", hash.short());
7173

74+
// Branch operations
75+
let branches = repo.branches()?;
76+
println!("Current branch: {:?}", repo.current_branch()?.map(|b| b.name));
77+
78+
// Create and switch to new branch
79+
let feature_branch = repo.checkout_new("feature/new-api", None)?;
80+
println!("Created and switched to: {}", feature_branch.name);
81+
7282
Ok(())
7383
}
7484
```
@@ -128,8 +138,8 @@ let modified_in_worktree: Vec<_> = status
128138

129139
// Work with all file entries directly
130140
for entry in status.entries() {
131-
println!("[{}][{}] {}",
132-
entry.index_status.to_char(),
141+
println!("[{}][{}] {}",
142+
entry.index_status.to_char(),
133143
entry.worktree_status.to_char(),
134144
entry.path.display()
135145
);
@@ -247,6 +257,165 @@ let hash = repo.commit_with_author(
247257
)?;
248258
```
249259

260+
### Branch Operations
261+
262+
#### `Repository::branches() -> Result<BranchList>`
263+
264+
List all branches in the repository.
265+
266+
```rust
267+
let branches = repo.branches()?;
268+
269+
// Check total count
270+
println!("Total branches: {}", branches.len());
271+
println!("Local branches: {}", branches.local_count());
272+
println!("Remote branches: {}", branches.remote_count());
273+
274+
// Iterate over all branches
275+
for branch in branches.iter() {
276+
let marker = if branch.is_current { "*" } else { " " };
277+
println!(" {}{} ({})", marker, branch.name, branch.commit_hash.short());
278+
}
279+
280+
// Filter by type
281+
let local_branches: Vec<_> = branches.local().collect();
282+
let remote_branches: Vec<_> = branches.remote().collect();
283+
```
284+
285+
#### `Repository::current_branch() -> Result<Option<Branch>>`
286+
287+
Get the currently checked out branch.
288+
289+
```rust
290+
if let Some(current) = repo.current_branch()? {
291+
println!("On branch: {}", current.name);
292+
println!("Last commit: {}", current.commit_hash.short());
293+
if let Some(upstream) = &current.upstream {
294+
println!("Tracking: {}", upstream);
295+
}
296+
}
297+
```
298+
299+
#### `Repository::create_branch(name, start_point) -> Result<Branch>`
300+
301+
Create a new branch.
302+
303+
```rust
304+
// Create branch from current HEAD
305+
let branch = repo.create_branch("feature/new-api", None)?;
306+
307+
// Create branch from specific commit/branch
308+
let branch = repo.create_branch("hotfix/bug-123", Some("main"))?;
309+
let branch = repo.create_branch("release/v1.0", Some("develop"))?;
310+
```
311+
312+
#### `Repository::checkout(branch) -> Result<()>`
313+
314+
Switch to an existing branch.
315+
316+
```rust
317+
let branches = repo.branches()?;
318+
if let Some(branch) = branches.find("develop") {
319+
repo.checkout(&branch)?;
320+
println!("Switched to: {}", branch.name);
321+
}
322+
```
323+
324+
#### `Repository::checkout_new(name, start_point) -> Result<Branch>`
325+
326+
Create a new branch and switch to it immediately.
327+
328+
```rust
329+
// Create and checkout new branch from current HEAD
330+
let branch = repo.checkout_new("feature/auth", None)?;
331+
332+
// Create and checkout from specific starting point
333+
let branch = repo.checkout_new("feature/api", Some("develop"))?;
334+
println!("Created and switched to: {}", branch.name);
335+
```
336+
337+
#### `Repository::delete_branch(branch, force) -> Result<()>`
338+
339+
Delete a branch.
340+
341+
```rust
342+
let branches = repo.branches()?;
343+
if let Some(branch) = branches.find("old-feature") {
344+
// Safe delete (fails if unmerged)
345+
repo.delete_branch(&branch, false)?;
346+
347+
// Force delete
348+
// repo.delete_branch(&branch, true)?;
349+
}
350+
```
351+
352+
#### Branch Types
353+
354+
The branch API uses structured types for type safety:
355+
356+
```rust
357+
// Branch represents a single branch
358+
pub struct Branch {
359+
pub name: String,
360+
pub branch_type: BranchType,
361+
pub is_current: bool,
362+
pub commit_hash: Hash,
363+
pub upstream: Option<String>,
364+
}
365+
366+
// Branch type enumeration
367+
pub enum BranchType {
368+
Local, // Local branch
369+
RemoteTracking, // Remote-tracking branch
370+
}
371+
372+
// BranchList contains all branches with efficient methods
373+
pub struct BranchList {
374+
// Methods:
375+
// - iter() -> iterator over all branches
376+
// - local() -> iterator over local branches
377+
// - remote() -> iterator over remote branches
378+
// - current() -> get current branch
379+
// - find(name) -> find branch by exact name
380+
// - find_by_short_name(name) -> find by short name
381+
// - len(), is_empty() -> collection info
382+
}
383+
```
384+
385+
#### Branch Search and Filtering
386+
387+
```rust
388+
let branches = repo.branches()?;
389+
390+
// Find specific branches
391+
if let Some(main) = branches.find("main") {
392+
println!("Found main branch: {}", main.commit_hash.short());
393+
}
394+
395+
// Find by short name (useful for remote branches)
396+
if let Some(feature) = branches.find_by_short_name("feature") {
397+
println!("Found feature branch: {}", feature.name);
398+
}
399+
400+
// Filter by type
401+
println!("Local branches:");
402+
for branch in branches.local() {
403+
println!(" - {}", branch.name);
404+
}
405+
406+
if branches.remote_count() > 0 {
407+
println!("Remote branches:");
408+
for branch in branches.remote() {
409+
println!(" - {}", branch.name);
410+
}
411+
}
412+
413+
// Get current branch
414+
if let Some(current) = branches.current() {
415+
println!("Currently on: {}", current.name);
416+
}
417+
```
418+
250419
### Hash Type
251420

252421
The `Hash` type represents Git object hashes (commits, trees, blobs, etc.).
@@ -297,10 +466,10 @@ fn main() -> rustic_git::Result<()> {
297466
let status = repo.status()?;
298467
let untracked_count = status.untracked_entries().count();
299468
println!("Found {} untracked files", untracked_count);
300-
469+
301470
// Display detailed status
302471
for entry in status.entries() {
303-
println!("[{}][{}] {}",
472+
println!("[{}][{}] {}",
304473
entry.index_status.to_char(),
305474
entry.worktree_status.to_char(),
306475
entry.path.display()
@@ -314,7 +483,7 @@ fn main() -> rustic_git::Result<()> {
314483
let status = repo.status()?;
315484
let staged_files: Vec<_> = status.staged_files().collect();
316485
println!("Staged {} files", staged_files.len());
317-
486+
318487
// Show specifically added files
319488
let added_files: Vec<_> = status
320489
.files_with_index_status(IndexStatus::Added)
@@ -325,10 +494,41 @@ fn main() -> rustic_git::Result<()> {
325494
let hash = repo.commit("Initial commit with project structure")?;
326495
println!("Created commit: {}", hash.short());
327496

497+
// Branch operations workflow
498+
let branches = repo.branches()?;
499+
println!("Current branch: {:?}", repo.current_branch()?.map(|b| b.name));
500+
501+
// Create a feature branch
502+
let feature_branch = repo.checkout_new("feature/user-auth", None)?;
503+
println!("Created and switched to: {}", feature_branch.name);
504+
505+
// Make changes on the feature branch
506+
fs::write("./my-project/src/auth.rs", "pub fn authenticate() { /* TODO */ }")?;
507+
repo.add(&["src/auth.rs"])?;
508+
let feature_commit = repo.commit("Add authentication module")?;
509+
println!("Feature commit: {}", feature_commit.short());
510+
511+
// Switch back to main and create another branch
512+
if let Some(main_branch) = branches.find("main") {
513+
repo.checkout(&main_branch)?;
514+
println!("Switched back to main");
515+
}
516+
517+
let doc_branch = repo.create_branch("docs/api", None)?;
518+
println!("Created documentation branch: {}", doc_branch.name);
519+
520+
// List all branches
521+
let final_branches = repo.branches()?;
522+
println!("\nFinal branch summary:");
523+
for branch in final_branches.iter() {
524+
let marker = if branch.is_current { "*" } else { " " };
525+
println!(" {}{} ({})", marker, branch.name, branch.commit_hash.short());
526+
}
527+
328528
// Verify clean state
329529
let status = repo.status()?;
330530
assert!(status.is_clean());
331-
println!("Repository is now clean!");
531+
println!("Repository is clean!");
332532

333533
Ok(())
334534
}
@@ -356,6 +556,9 @@ cargo run --example staging_operations
356556
# Commit workflows and Hash type usage
357557
cargo run --example commit_workflows
358558

559+
# Branch operations (create, delete, checkout, list)
560+
cargo run --example branch_operations
561+
359562
# Error handling patterns and recovery strategies
360563
cargo run --example error_handling
361564
```
@@ -367,6 +570,7 @@ cargo run --example error_handling
367570
- **`status_checking.rs`** - Comprehensive demonstration of GitStatus and FileStatus usage with all query methods and filtering capabilities
368571
- **`staging_operations.rs`** - Shows all staging methods (add, add_all, add_update) with before/after status comparisons
369572
- **`commit_workflows.rs`** - Demonstrates commit operations and Hash type methods, including custom authors and hash management
573+
- **`branch_operations.rs`** - Complete branch management demonstration: create, checkout, delete branches, and BranchList filtering
370574
- **`error_handling.rs`** - Comprehensive error handling patterns showing GitError variants, recovery strategies, and best practices
371575

372576
All examples use temporary directories in `/tmp/` and include automatic cleanup for safe execution.
@@ -425,6 +629,7 @@ cargo run --example repository_operations
425629
cargo run --example status_checking
426630
cargo run --example staging_operations
427631
cargo run --example commit_workflows
632+
cargo run --example branch_operations
428633
cargo run --example error_handling
429634
```
430635

@@ -442,12 +647,11 @@ cargo run --example error_handling
442647
Future planned features:
443648
- [ ] Commit history and log operations
444649
- [ ] Diff operations
445-
- [ ] Branch operations
446650
- [ ] Remote operations (clone, push, pull)
447651
- [ ] Merge and rebase operations
448652
- [ ] Tag operations
449653
- [ ] Stash operations
450654

451655
## Version
452656

453-
Current version: 0.1.0 - Basic git workflow (init, status, add, commit)
657+
Current version: 0.1.0 - Basic git workflow with branch operations (init, status, add, commit, branch)

0 commit comments

Comments
 (0)