diff --git a/Cargo.lock b/Cargo.lock index c662bfe5d8d2f1..8037638a74a8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7329,7 +7329,6 @@ dependencies = [ "itertools 0.14.0", "language", "language_model", - "linkify", "log", "markdown", "menu", diff --git a/crates/git_hosting_providers/src/providers/bitbucket.rs b/crates/git_hosting_providers/src/providers/bitbucket.rs index df216d1801bbb4..0e1fc8fb9e0f1f 100644 --- a/crates/git_hosting_providers/src/providers/bitbucket.rs +++ b/crates/git_hosting_providers/src/providers/bitbucket.rs @@ -15,6 +15,7 @@ use git::{ BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote, PullRequest, RemoteUrl, }; +use urlencoding::encode; use crate::get_host_from_git_remote_url; @@ -255,6 +256,33 @@ impl GitHostingProvider for Bitbucket { permalink } + fn build_create_pull_request_url( + &self, + remote: &ParsedGitRemote, + source_branch: &str, + ) -> Option { + let ParsedGitRemote { owner, repo } = remote; + + if self.is_self_hosted() { + let mut url = self + .base_url() + .join(&format!("projects/{owner}/repos/{repo}/compare/commits")) + .ok()?; + let source_ref = format!("refs/heads/{source_branch}"); + let encoded_ref = encode(&source_ref); + url.set_query(Some(&format!("sourceBranch={encoded_ref}"))); + Some(url) + } else { + let mut url = self + .base_url() + .join(&format!("{owner}/{repo}/pull-requests/new")) + .ok()?; + let encoded_branch = encode(source_branch); + url.set_query(Some(&format!("source={encoded_branch}"))); + Some(url) + } + } + fn extract_pull_request(&self, remote: &ParsedGitRemote, message: &str) -> Option { // Check first line of commit message for PR references let first_line = message.lines().next()?; @@ -528,6 +556,42 @@ mod tests { assert_eq!(permalink.to_string(), expected_url.to_string()) } + #[test] + fn test_build_bitbucket_create_pr_url() { + let remote = ParsedGitRemote { + owner: "zed-industries".into(), + repo: "zed".into(), + }; + + let url = Bitbucket::public_instance() + .build_create_pull_request_url(&remote, "feature/my-branch") + .expect("url should be constructed"); + + assert_eq!( + url.as_str(), + "https://bitbucket.org/zed-industries/zed/pull-requests/new?source=feature%2Fmy-branch" + ); + } + + #[test] + fn test_build_bitbucket_self_hosted_create_pr_url() { + let remote = ParsedGitRemote { + owner: "zed-industries".into(), + repo: "zed".into(), + }; + + let url = + Bitbucket::from_remote_url("https://bitbucket.company.com/zed-industries/zed.git") + .unwrap() + .build_create_pull_request_url(&remote, "feature/my-branch") + .expect("url should be constructed"); + + assert_eq!( + url.as_str(), + "https://bitbucket.company.com/projects/zed-industries/repos/zed/compare/commits?sourceBranch=refs%2Fheads%2Ffeature%2Fmy-branch" + ); + } + #[test] fn test_bitbucket_pull_requests() { use indoc::indoc; diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index 5a9350f8aec7ae..02c0eaf66cdcf0 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -36,7 +36,6 @@ gpui.workspace = true itertools.workspace = true language.workspace = true language_model.workspace = true -linkify.workspace = true log.workspace = true markdown.workspace = true menu.workspace = true diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 13e2d0970f1639..aed5f494f0db02 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3877,6 +3877,8 @@ impl GitPanel { return; }; + let is_push = matches!(action, RemoteAction::Push(_, _)); + workspace.update(cx, |workspace, cx| { let SuccessMessage { message, style } = remote_output::format_output(&action, info); let workspace_weak = cx.weak_entity(); @@ -3884,19 +3886,21 @@ impl GitPanel { let status_toast = StatusToast::new(message, cx, move |this, _cx| { use remote_output::SuccessStyle::*; - match style { - Toast => this.icon( - Icon::new(IconName::GitBranch) - .size(IconSize::Small) - .color(Color::Muted), - ), - ToastWithLog { output } => this - .icon( - Icon::new(IconName::GitBranch) - .size(IconSize::Small) - .color(Color::Muted), - ) - .action("View Log", move |window, cx| { + let this = this.icon( + Icon::new(IconName::GitBranch) + .size(IconSize::Small) + .color(Color::Muted), + ); + match (style, is_push) { + (Toast | ToastWithLog { .. }, true) => { + this.action("Create Pull Request", move |window, cx| { + window + .dispatch_action(Box::new(zed_actions::git::CreatePullRequest), cx); + }) + } + (Toast, false) => this, + (ToastWithLog { output }, false) => { + this.action("View Log", move |window, cx| { let output = output.clone(); let output = format!("stdout:\n{}\nstderr:\n{}", output.stdout, output.stderr); @@ -3905,14 +3909,8 @@ impl GitPanel { open_output(operation, workspace, &output, window, cx) }) .ok(); - }), - PushPrLink { text, link } => this - .icon( - Icon::new(IconName::GitBranch) - .size(IconSize::Small) - .color(Color::Muted), - ) - .action(text, move |_, cx| cx.open_url(&link)), + }) + } } .dismiss_button(true) }); diff --git a/crates/git_ui/src/remote_output.rs b/crates/git_ui/src/remote_output.rs index a5259631e34740..157ce8316775d9 100644 --- a/crates/git_ui/src/remote_output.rs +++ b/crates/git_ui/src/remote_output.rs @@ -1,7 +1,6 @@ use anyhow::Context as _; use git::repository::{Remote, RemoteCommandOutput}; -use linkify::{LinkFinder, LinkKind}; use ui::SharedString; use util::ResultExt as _; @@ -25,7 +24,6 @@ impl RemoteAction { pub enum SuccessStyle { Toast, ToastWithLog { output: RemoteCommandOutput }, - PushPrLink { text: String, link: String }, } pub struct SuccessMessage { @@ -119,47 +117,16 @@ pub fn format_output(action: &RemoteAction, output: RemoteCommandOutput) -> Succ } } RemoteAction::Push(branch_name, remote_ref) => { - let message = if output.stderr.ends_with("Everything up-to-date\n") { - "Push: Everything is up-to-date".to_string() - } else { - format!("Pushed {} to {}", branch_name, remote_ref.name) - }; - - let style = if output.stderr.ends_with("Everything up-to-date\n") { - Some(SuccessStyle::Toast) - } else if output.stderr.contains("\nremote: ") { - let pr_hints = [ - ("Create a pull request", "Create Pull Request"), // GitHub - ("Create pull request", "Create Pull Request"), // Bitbucket - ("create a merge request", "Create Merge Request"), // GitLab - ("View merge request", "View Merge Request"), // GitLab - ]; - pr_hints - .iter() - .find(|(indicator, _)| output.stderr.contains(indicator)) - .and_then(|(_, mapped)| { - let finder = LinkFinder::new(); - - output - .stderr - .lines() - .filter(|line| line.trim_start().starts_with("remote:")) - .find_map(|line| { - finder - .links(line) - .find(|link| *link.kind() == LinkKind::Url) - .map(|link| SuccessStyle::PushPrLink { - text: mapped.to_string(), - link: link.as_str().to_string(), - }) - }) - }) + if output.stderr.ends_with("Everything up-to-date\n") { + SuccessMessage { + message: "Push: Everything is up-to-date".to_string(), + style: SuccessStyle::Toast, + } } else { - None - }; - SuccessMessage { - message, - style: style.unwrap_or(SuccessStyle::ToastWithLog { output }), + SuccessMessage { + message: format!("Pushed {} to {}", branch_name, remote_ref.name), + style: SuccessStyle::ToastWithLog { output }, + } } } } @@ -195,12 +162,8 @@ mod tests { let msg = format_output(&action, output); - if let SuccessStyle::PushPrLink { text: hint, link } = &msg.style { - assert_eq!(hint, "Create Pull Request"); - assert_eq!(link, "https://example.com/test/test/pull/new/test"); - } else { - panic!("Expected PushPrLink variant"); - } + assert!(matches!(msg.style, SuccessStyle::ToastWithLog { .. })); + assert_eq!(msg.message, "Pushed test_branch to test_remote"); } #[test] @@ -228,15 +191,8 @@ mod tests { let msg = format_output(&action, output); - if let SuccessStyle::PushPrLink { text, link } = &msg.style { - assert_eq!(text, "Create Merge Request"); - assert_eq!( - link, - "https://example.com/test/test/-/merge_requests/new?merge_request%5Bsource_branch%5D=test" - ); - } else { - panic!("Expected PushPrLink variant"); - } + assert!(matches!(msg.style, SuccessStyle::ToastWithLog { .. })); + assert_eq!(msg.message, "Pushed test_branch to test_remote"); } #[test] @@ -268,12 +224,8 @@ mod tests { let msg = format_output(&action, output); - if let SuccessStyle::PushPrLink { text, link } = &msg.style { - assert_eq!(text, "View Merge Request"); - assert_eq!(link, "https://example.com/test/test/-/merge_requests/99999"); - } else { - panic!("Expected PushPrLink variant"); - } + assert!(matches!(msg.style, SuccessStyle::ToastWithLog { .. })); + assert_eq!(msg.message, "Pushed test_branch to test_remote"); } #[test]