Skip to main content

vex_git/
lib.rs

1use std::path::Path;
2
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum GitError {
7    #[error("not a git repository: {0}")]
8    NotARepo(String),
9    #[error("git command failed: {0}")]
10    CommandFailed(String),
11    #[error("worktree error: {0}")]
12    WorktreeError(String),
13    #[error("IO error: {0}")]
14    Io(#[from] std::io::Error),
15}
16
17#[derive(Debug, Clone)]
18pub struct CommitInfo {
19    pub hash: String,
20    pub short_hash: String,
21    pub subject: String,
22    pub author: String,
23    pub date: String,
24}
25
26/// Parse "owner/repo" from a GitHub remote URL.
27/// Handles SSH (`git@github.com:owner/repo.git`) and HTTPS (`https://github.com/owner/repo.git`).
28pub fn parse_github_owner_repo(remote: &str) -> Option<(String, String)> {
29    let stripped = remote.trim().trim_end_matches(".git");
30    // SSH: git@github.com:owner/repo
31    if let Some(rest) = stripped.strip_prefix("git@github.com:") {
32        let parts: Vec<&str> = rest.splitn(2, '/').collect();
33        if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
34            return Some((parts[0].to_string(), parts[1].to_string()));
35        }
36    }
37    // HTTPS: https://github.com/owner/repo
38    if stripped.contains("github.com/") {
39        let after = stripped.split("github.com/").last()?;
40        let parts: Vec<&str> = after.splitn(2, '/').collect();
41        if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
42            return Some((parts[0].to_string(), parts[1].to_string()));
43        }
44    }
45    None
46}
47
48pub trait GitPort {
49    fn is_git_repo(&self, path: &Path) -> Result<bool, GitError>;
50    fn get_github_remote(&self, path: &Path) -> Result<Option<String>, GitError>;
51    fn create_worktree(
52        &self,
53        repo_path: &Path,
54        worktree_path: &Path,
55        branch: &str,
56        create_branch: bool,
57    ) -> Result<(), GitError>;
58    fn remove_worktree(&self, repo_path: &Path, worktree_path: &Path) -> Result<(), GitError>;
59    fn current_branch(&self, path: &Path) -> Result<String, GitError>;
60    fn get_pr_branch(&self, pr_url: &str) -> Result<String, GitError>;
61    fn staged_diff(&self, path: &Path) -> Result<String, GitError>;
62    fn unstaged_diff(&self, path: &Path) -> Result<String, GitError>;
63    fn log_commits(&self, path: &Path, base: &str) -> Result<Vec<CommitInfo>, GitError>;
64    fn rename_branch(&self, repo_path: &Path, old: &str, new: &str) -> Result<(), GitError>;
65    fn move_worktree(
66        &self,
67        repo_path: &Path,
68        old_path: &Path,
69        new_path: &Path,
70    ) -> Result<(), GitError>;
71}