Skip to content

Commit 14e7c61

Browse files
committed
refactor: group git operations
1 parent 6985788 commit 14e7c61

File tree

4 files changed

+183
-78
lines changed

4 files changed

+183
-78
lines changed

src/git/clone.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! Git clone and repository removal operations
2+
//!
3+
//! This module handles the core repository lifecycle operations:
4+
//! cloning repositories from remote URLs and removing local repository
5+
//! directories when they're no longer needed.
6+
//!
7+
//! ## Functions
8+
//!
9+
//! - [`clone_repository`]: Clone a repository from its remote URL
10+
//! - [`remove_repository`]: Remove a cloned repository directory
11+
//!
12+
//! Both functions work with the [`Repository`] configuration type and
13+
//! provide detailed logging throughout the operation.
14+
15+
use crate::config::Repository;
16+
use anyhow::{Context, Result};
17+
use std::path::Path;
18+
use std::process::Command;
19+
20+
use super::common::Logger;
21+
22+
/// Clone a repository from its URL to the target directory
23+
pub fn clone_repository(repo: &Repository) -> Result<()> {
24+
let logger = Logger;
25+
let target_dir = repo.get_target_dir();
26+
27+
// Check if directory already exists
28+
if Path::new(&target_dir).exists() {
29+
logger.warn(repo, "Repository directory already exists, skipping");
30+
return Ok(());
31+
}
32+
33+
let mut args = vec!["clone"];
34+
35+
// Add branch flag if a branch is specified
36+
if let Some(branch) = &repo.branch {
37+
args.extend_from_slice(&["-b", branch]);
38+
logger.info(
39+
repo,
40+
&format!("Cloning branch '{}' from {}", branch, repo.url),
41+
);
42+
} else {
43+
logger.info(repo, &format!("Cloning default branch from {}", repo.url));
44+
}
45+
46+
// Add repository URL and target directory
47+
args.push(&repo.url);
48+
args.push(&target_dir);
49+
50+
let output = Command::new("git")
51+
.args(&args)
52+
.output()
53+
.context("Failed to execute git clone command")?;
54+
55+
if !output.status.success() {
56+
let stderr = String::from_utf8_lossy(&output.stderr);
57+
anyhow::bail!("Failed to clone repository: {}", stderr);
58+
}
59+
60+
logger.success(repo, "Successfully cloned");
61+
Ok(())
62+
}
63+
64+
/// Remove a cloned repository directory
65+
pub fn remove_repository(repo: &Repository) -> Result<()> {
66+
let target_dir = repo.get_target_dir();
67+
68+
if Path::new(&target_dir).exists() {
69+
std::fs::remove_dir_all(&target_dir).context("Failed to remove repository directory")?;
70+
Ok(())
71+
} else {
72+
anyhow::bail!("Repository directory does not exist: {}", target_dir);
73+
}
74+
}

src/git/common.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! Common git utilities and shared helpers
2+
//!
3+
//! This module contains utilities that are shared across different git workflows,
4+
//! such as logging and error handling helpers.
5+
6+
use crate::config::Repository;
7+
use colored::*;
8+
9+
/// Logger for git operations with consistent formatting
10+
///
11+
/// Provides standardized logging methods for git operations, ensuring
12+
/// consistent output formatting across all git workflows. Each log
13+
/// message is prefixed with the repository name in cyan/bold for
14+
/// easy identification.
15+
///
16+
/// ## Example
17+
///
18+
/// ```rust,no_run
19+
/// use repos::git::Logger;
20+
/// use repos::config::Repository;
21+
///
22+
/// let logger = Logger::default();
23+
/// let repo = Repository::new("my-repo".to_string(), "https://github.com/user/repo.git".to_string());
24+
/// logger.info(&repo, "Starting operation");
25+
/// logger.success(&repo, "Operation completed");
26+
/// ```
27+
#[derive(Default)]
28+
pub struct Logger;
29+
30+
impl Logger {
31+
pub fn info(&self, repo: &Repository, msg: &str) {
32+
println!("{} | {}", repo.name.cyan().bold(), msg);
33+
}
34+
35+
pub fn success(&self, repo: &Repository, msg: &str) {
36+
println!("{} | {}", repo.name.cyan().bold(), msg.green());
37+
}
38+
39+
pub fn warn(&self, repo: &Repository, msg: &str) {
40+
println!("{} | {}", repo.name.cyan().bold(), msg.yellow());
41+
}
42+
43+
#[allow(dead_code)]
44+
pub fn error(&self, repo: &Repository, msg: &str) {
45+
eprintln!("{} | {}", repo.name.cyan().bold(), msg.red());
46+
}
47+
}

src/git/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! Git operations using system git commands for maximum compatibility
2+
//!
3+
//! This module is organized into sub-modules for different git workflows:
4+
//!
5+
//! ## Sub-modules
6+
//!
7+
//! - [`clone`]: Repository cloning and removal operations
8+
//! - `clone_repository()` - Clone a repository from URL
9+
//! - `remove_repository()` - Remove a cloned repository directory
10+
//!
11+
//! - [`pull_request`]: Git operations specific to pull request workflows
12+
//! - `has_changes()` - Check for uncommitted changes
13+
//! - `create_and_checkout_branch()` - Create and switch to new branch
14+
//! - `add_all_changes()` - Stage all changes
15+
//! - `commit_changes()` - Commit staged changes
16+
//! - `push_branch()` - Push branch to remote
17+
//! - `get_default_branch()` - Get repository's default branch
18+
//!
19+
//! - [`common`]: Shared utilities and helpers
20+
//! - `Logger` - Consistent logging for git operations
21+
//!
22+
//! ## Benefits of this organization
23+
//!
24+
//! - **Scalability**: Easy to add new git features without making single files unwieldy
25+
//! - **Readability**: Developers can quickly find code for specific git workflows
26+
//! - **Maintainability**: Clear separation of concerns between different git operations
27+
//! - **Backward compatibility**: All functions are re-exported at the module level
28+
29+
pub mod clone;
30+
pub mod common;
31+
pub mod pull_request;
32+
33+
// Re-export all public functions to maintain backward compatibility
34+
pub use clone::{clone_repository, remove_repository};
35+
pub use common::Logger;
36+
pub use pull_request::{
37+
add_all_changes, commit_changes, create_and_checkout_branch, get_default_branch, has_changes,
38+
push_branch,
39+
};

src/git.rs renamed to src/git/pull_request.rs

Lines changed: 23 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,25 @@
1-
//! Git operations using system git commands for maximum compatibility
1+
//! Git operations for pull request workflows
2+
//!
3+
//! This module contains git operations that are commonly used in pull request
4+
//! workflows, including checking for changes, creating branches, staging and
5+
//! committing changes, and pushing branches to remotes.
6+
//!
7+
//! ## Typical PR Workflow
8+
//!
9+
//! 1. [`has_changes`] - Check if repository has uncommitted changes
10+
//! 2. [`create_and_checkout_branch`] - Create and switch to a new branch
11+
//! 3. [`add_all_changes`] - Stage all changes for commit
12+
//! 4. [`commit_changes`] - Commit the staged changes with a message
13+
//! 5. [`push_branch`] - Push the branch to the remote repository
14+
//!
15+
//! ## Additional Utilities
16+
//!
17+
//! - [`get_default_branch`] - Determine the repository's default branch
218
3-
use crate::config::Repository;
419
use anyhow::{Context, Result};
5-
use colored::*;
6-
use std::path::Path;
720
use std::process::Command;
821

9-
#[derive(Default)]
10-
pub struct Logger;
11-
12-
impl Logger {
13-
pub fn info(&self, repo: &Repository, msg: &str) {
14-
println!("{} | {}", repo.name.cyan().bold(), msg);
15-
}
16-
17-
pub fn success(&self, repo: &Repository, msg: &str) {
18-
println!("{} | {}", repo.name.cyan().bold(), msg.green());
19-
}
20-
21-
pub fn warn(&self, repo: &Repository, msg: &str) {
22-
println!("{} | {}", repo.name.cyan().bold(), msg.yellow());
23-
}
24-
25-
#[allow(dead_code)]
26-
pub fn error(&self, repo: &Repository, msg: &str) {
27-
eprintln!("{} | {}", repo.name.cyan().bold(), msg.red());
28-
}
29-
}
30-
31-
pub fn clone_repository(repo: &Repository) -> Result<()> {
32-
let logger = Logger;
33-
let target_dir = repo.get_target_dir();
34-
35-
// Check if directory already exists
36-
if Path::new(&target_dir).exists() {
37-
logger.warn(repo, "Repository directory already exists, skipping");
38-
return Ok(());
39-
}
40-
41-
let mut args = vec!["clone"];
42-
43-
// Add branch flag if a branch is specified
44-
if let Some(branch) = &repo.branch {
45-
args.extend_from_slice(&["-b", branch]);
46-
logger.info(
47-
repo,
48-
&format!("Cloning branch '{}' from {}", branch, repo.url),
49-
);
50-
} else {
51-
logger.info(repo, &format!("Cloning default branch from {}", repo.url));
52-
}
53-
54-
// Add repository URL and target directory
55-
args.push(&repo.url);
56-
args.push(&target_dir);
57-
58-
let output = Command::new("git")
59-
.args(&args)
60-
.output()
61-
.context("Failed to execute git clone command")?;
62-
63-
if !output.status.success() {
64-
let stderr = String::from_utf8_lossy(&output.stderr);
65-
anyhow::bail!("Failed to clone repository: {}", stderr);
66-
}
67-
68-
logger.success(repo, "Successfully cloned");
69-
Ok(())
70-
}
71-
72-
pub fn remove_repository(repo: &Repository) -> Result<()> {
73-
let target_dir = repo.get_target_dir();
74-
75-
if Path::new(&target_dir).exists() {
76-
std::fs::remove_dir_all(&target_dir).context("Failed to remove repository directory")?;
77-
Ok(())
78-
} else {
79-
anyhow::bail!("Repository directory does not exist: {}", target_dir);
80-
}
81-
}
82-
22+
/// Check if a repository has uncommitted changes
8323
pub fn has_changes(repo_path: &str) -> Result<bool> {
8424
// Check if there are any uncommitted changes using git status
8525
let output = Command::new("git")
@@ -100,6 +40,7 @@ pub fn has_changes(repo_path: &str) -> Result<bool> {
10040
Ok(!output.stdout.is_empty())
10141
}
10242

43+
/// Create and checkout a new branch
10344
pub fn create_and_checkout_branch(repo_path: &str, branch_name: &str) -> Result<()> {
10445
// Create and checkout a new branch using git checkout -b
10546
let output = Command::new("git")
@@ -121,6 +62,7 @@ pub fn create_and_checkout_branch(repo_path: &str, branch_name: &str) -> Result<
12162
Ok(())
12263
}
12364

65+
/// Add all changes to the staging area
12466
pub fn add_all_changes(repo_path: &str) -> Result<()> {
12567
// Add all changes using git add .
12668
let output = Command::new("git")
@@ -140,6 +82,7 @@ pub fn add_all_changes(repo_path: &str) -> Result<()> {
14082
Ok(())
14183
}
14284

85+
/// Commit staged changes with a message
14386
pub fn commit_changes(repo_path: &str, message: &str) -> Result<()> {
14487
// Commit changes using git commit
14588
let output = Command::new("git")
@@ -160,6 +103,7 @@ pub fn commit_changes(repo_path: &str, message: &str) -> Result<()> {
160103
Ok(())
161104
}
162105

106+
/// Push a branch to remote and set upstream
163107
pub fn push_branch(repo_path: &str, branch_name: &str) -> Result<()> {
164108
// Push branch using git push
165109
let output = Command::new("git")
@@ -181,6 +125,7 @@ pub fn push_branch(repo_path: &str, branch_name: &str) -> Result<()> {
181125
Ok(())
182126
}
183127

128+
/// Get the default branch of a repository
184129
pub fn get_default_branch(repo_path: &str) -> Result<String> {
185130
// Try to get the default branch using git symbolic-ref
186131
let output = Command::new("git")

0 commit comments

Comments
 (0)