diff --git a/README.md b/README.md index 83c0244e1..65f6b4f95 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Delta has many features and is very customizable; please see `delta -h` (short h - Line numbering - `n` and `N` keybindings to move between files in large diffs, and between diffs in `log -p` views (`--navigate`) - Improved merge conflict display -- Improved `git blame` display (syntax highlighting; `--hyperlinks` formats commits as links to hosting provider etc. Supported hosting providers are: GitHub, GitLab, SourceHut, Codeberg) +- Improved `git blame` display (syntax highlighting; `--hyperlinks` formats commits as links to hosting provider etc. Supported hosting providers are: GitHub, GitLab, SourceHut, Codeberg, Bitbucket) - Syntax-highlights grep output from `rg`, `git grep`, `grep`, etc - Support for Git's `--color-moved` feature. - Code can be copied directly from the diff (`-/+` markers are removed by default). diff --git a/src/git_config/remote.rs b/src/git_config/remote.rs index 6737a5031..5269728bf 100644 --- a/src/git_config/remote.rs +++ b/src/git_config/remote.rs @@ -12,6 +12,7 @@ pub enum GitRemoteRepo { GitLab { slug: String }, SourceHut { slug: String }, Codeberg { slug: String }, + Bitbucket { slug: String }, } impl GitRemoteRepo { @@ -29,6 +30,9 @@ impl GitRemoteRepo { Self::Codeberg { slug } => { format!("https://codeberg.org/{slug}/commit/{commit}") } + Self::Bitbucket { slug } => { + format!("https://bitbucket.org/{slug}/commits/{commit}") + } } } @@ -97,6 +101,20 @@ lazy_static! { " ) .unwrap(); + static ref BITBUCKET_REMOTE_URL: Regex = Regex::new( + r"(?x) + ^ + (?:https://|git@)? # Support both HTTPS and SSH URLs, SSH URLs optionally omitting the git@ + bitbucket\.org + [:/] # This separator differs between SSH and HTTPS URLs + ([^/]+) # Capture the user/org name + / + (.+?) # Capture the repo name (lazy to avoid consuming '.git' if present) + (?:\.git)? # Non-capturing group to consume '.git' if present + $ + " + ) + .unwrap(); } impl FromStr for GitRemoteRepo { @@ -135,8 +153,18 @@ impl FromStr for GitRemoteRepo { repo = caps.get(2).unwrap().as_str() ), }) + } else if let Some(caps) = BITBUCKET_REMOTE_URL.captures(s) { + Ok(Self::Bitbucket { + slug: format!( + "{user}/{repo}", + user = caps.get(1).unwrap().as_str(), + repo = caps.get(2).unwrap().as_str() + ), + }) } else { - Err(anyhow!("Not a GitHub, GitLab, SourceHut or Codeberg repo.")) + Err(anyhow!( + "Not a GitHub, GitLab, SourceHut, Codeberg or Bitbucket repo." + )) } } } @@ -288,4 +316,38 @@ mod tests { format!("https://codeberg.org/dnkl/foot/commit/{commit_hash}") ) } + + #[test] + fn test_parse_bitbucket_urls() { + let urls = &[ + "https://bitbucket.org/someuser/somerepo.git", + "https://bitbucket.org/someuser/somerepo", + "git@bitbucket.org:someuser/somerepo.git", + "git@bitbucket.org:someuser/somerepo", + "bitbucket.org:someuser/somerepo.git", + "bitbucket.org:someuser/somerepo", + ]; + for url in urls { + let parsed = GitRemoteRepo::from_str(url); + assert!(parsed.is_ok()); + assert_eq!( + parsed.unwrap(), + GitRemoteRepo::Bitbucket { + slug: "someuser/somerepo".to_string() + } + ); + } + } + + #[test] + fn test_format_bitbucket_commit_link() { + let repo = GitRemoteRepo::Bitbucket { + slug: "someuser/somerepo".to_string(), + }; + let commit_hash = "1c072856ebf12419378c5098ad543c497197c6da"; + assert_eq!( + repo.format_commit_url(commit_hash), + format!("https://bitbucket.org/someuser/somerepo/commits/{commit_hash}") + ) + } }