Update the toast shown after running `git: push` so as to always include a button to create a pull request for the current branch. We used to only do this on the initial push, as that is when the output of running `git push` would include an URL that could be used to create the Pull Request. However, since we now have a `git: create pull request`, we can always build this URL, meaning we no longer need to rely on the command's output in order to display the "Create Pull Request" button. Something worth noting is that the `git_ui::remote_output::format_output` function used to also match on Bitbucket's terminal output, so these changes include the implementation of `GitHostingProvider::build_create_pull_request_url` for the `Bitbucket` provider. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved the toast shown after `git: push` to always display the "Create Pull Request" button
262 lines
8.9 KiB
Rust
262 lines
8.9 KiB
Rust
use anyhow::Context as _;
|
|
|
|
use git::repository::{Remote, RemoteCommandOutput};
|
|
use ui::SharedString;
|
|
use util::ResultExt as _;
|
|
|
|
#[derive(Clone)]
|
|
pub enum RemoteAction {
|
|
Fetch(Option<Remote>),
|
|
Pull(Remote),
|
|
Push(SharedString, Remote),
|
|
}
|
|
|
|
impl RemoteAction {
|
|
pub fn name(&self) -> &'static str {
|
|
match self {
|
|
RemoteAction::Fetch(_) => "fetch",
|
|
RemoteAction::Pull(_) => "pull",
|
|
RemoteAction::Push(_, _) => "push",
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum SuccessStyle {
|
|
Toast,
|
|
ToastWithLog { output: RemoteCommandOutput },
|
|
}
|
|
|
|
pub struct SuccessMessage {
|
|
pub message: String,
|
|
pub style: SuccessStyle,
|
|
}
|
|
|
|
pub fn format_output(action: &RemoteAction, output: RemoteCommandOutput) -> SuccessMessage {
|
|
match action {
|
|
RemoteAction::Fetch(remote) => {
|
|
if output.stderr.is_empty() {
|
|
SuccessMessage {
|
|
message: "Fetch: Already up to date".into(),
|
|
style: SuccessStyle::Toast,
|
|
}
|
|
} else {
|
|
let message = match remote {
|
|
Some(remote) => format!("Synchronized with {}", remote.name),
|
|
None => "Synchronized with remotes".into(),
|
|
};
|
|
SuccessMessage {
|
|
message,
|
|
style: SuccessStyle::ToastWithLog { output },
|
|
}
|
|
}
|
|
}
|
|
RemoteAction::Pull(remote_ref) => {
|
|
let get_changes = |output: &RemoteCommandOutput| -> anyhow::Result<u32> {
|
|
let last_line = output
|
|
.stdout
|
|
.lines()
|
|
.last()
|
|
.context("Failed to get last line of output")?
|
|
.trim();
|
|
|
|
let files_changed = last_line
|
|
.split_whitespace()
|
|
.next()
|
|
.context("Failed to get first word of last line")?
|
|
.parse()?;
|
|
|
|
Ok(files_changed)
|
|
};
|
|
if output.stdout.ends_with("Already up to date.\n") {
|
|
SuccessMessage {
|
|
message: "Pull: Already up to date".into(),
|
|
style: SuccessStyle::Toast,
|
|
}
|
|
} else if output.stdout.starts_with("Updating") {
|
|
let files_changed = get_changes(&output).log_err();
|
|
let message = if let Some(files_changed) = files_changed {
|
|
format!(
|
|
"Received {} file change{} from {}",
|
|
files_changed,
|
|
if files_changed == 1 { "" } else { "s" },
|
|
remote_ref.name
|
|
)
|
|
} else {
|
|
format!("Fast forwarded from {}", remote_ref.name)
|
|
};
|
|
SuccessMessage {
|
|
message,
|
|
style: SuccessStyle::ToastWithLog { output },
|
|
}
|
|
} else if output.stdout.starts_with("Merge") {
|
|
let files_changed = get_changes(&output).log_err();
|
|
let message = if let Some(files_changed) = files_changed {
|
|
format!(
|
|
"Merged {} file change{} from {}",
|
|
files_changed,
|
|
if files_changed == 1 { "" } else { "s" },
|
|
remote_ref.name
|
|
)
|
|
} else {
|
|
format!("Merged from {}", remote_ref.name)
|
|
};
|
|
SuccessMessage {
|
|
message,
|
|
style: SuccessStyle::ToastWithLog { output },
|
|
}
|
|
} else if output.stdout.contains("Successfully rebased") {
|
|
SuccessMessage {
|
|
message: format!("Successfully rebased from {}", remote_ref.name),
|
|
style: SuccessStyle::ToastWithLog { output },
|
|
}
|
|
} else {
|
|
SuccessMessage {
|
|
message: format!("Successfully pulled from {}", remote_ref.name),
|
|
style: SuccessStyle::ToastWithLog { output },
|
|
}
|
|
}
|
|
}
|
|
RemoteAction::Push(branch_name, remote_ref) => {
|
|
if output.stderr.ends_with("Everything up-to-date\n") {
|
|
SuccessMessage {
|
|
message: "Push: Everything is up-to-date".to_string(),
|
|
style: SuccessStyle::Toast,
|
|
}
|
|
} else {
|
|
SuccessMessage {
|
|
message: format!("Pushed {} to {}", branch_name, remote_ref.name),
|
|
style: SuccessStyle::ToastWithLog { output },
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use indoc::indoc;
|
|
|
|
#[test]
|
|
fn test_push_new_branch_pull_request() {
|
|
let action = RemoteAction::Push(
|
|
SharedString::new_static("test_branch"),
|
|
Remote {
|
|
name: SharedString::new_static("test_remote"),
|
|
},
|
|
);
|
|
|
|
let output = RemoteCommandOutput {
|
|
stdout: String::new(),
|
|
stderr: indoc! { "
|
|
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
|
|
remote:
|
|
remote: Create a pull request for 'test' on GitHub by visiting:
|
|
remote: https://example.com/test/test/pull/new/test
|
|
remote:
|
|
To example.com:test/test.git
|
|
* [new branch] test -> test
|
|
"}
|
|
.to_string(),
|
|
};
|
|
|
|
let msg = format_output(&action, output);
|
|
|
|
assert!(matches!(msg.style, SuccessStyle::ToastWithLog { .. }));
|
|
assert_eq!(msg.message, "Pushed test_branch to test_remote");
|
|
}
|
|
|
|
#[test]
|
|
fn test_push_new_branch_merge_request() {
|
|
let action = RemoteAction::Push(
|
|
SharedString::new_static("test_branch"),
|
|
Remote {
|
|
name: SharedString::new_static("test_remote"),
|
|
},
|
|
);
|
|
|
|
let output = RemoteCommandOutput {
|
|
stdout: String::new(),
|
|
stderr: indoc! {"
|
|
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
|
|
remote:
|
|
remote: To create a merge request for test, visit:
|
|
remote: https://example.com/test/test/-/merge_requests/new?merge_request%5Bsource_branch%5D=test
|
|
remote:
|
|
To example.com:test/test.git
|
|
* [new branch] test -> test
|
|
"}
|
|
.to_string()
|
|
};
|
|
|
|
let msg = format_output(&action, output);
|
|
|
|
assert!(matches!(msg.style, SuccessStyle::ToastWithLog { .. }));
|
|
assert_eq!(msg.message, "Pushed test_branch to test_remote");
|
|
}
|
|
|
|
#[test]
|
|
fn test_push_branch_existing_merge_request() {
|
|
let action = RemoteAction::Push(
|
|
SharedString::new_static("test_branch"),
|
|
Remote {
|
|
name: SharedString::new_static("test_remote"),
|
|
},
|
|
);
|
|
|
|
let output = RemoteCommandOutput {
|
|
stdout: String::new(),
|
|
// Simulate an extraneous link that should not be found in top 3 lines
|
|
stderr: indoc! {"
|
|
** WARNING: connection is not using a post-quantum key exchange algorithm.
|
|
** This session may be vulnerable to \"store now, decrypt later\" attacks.
|
|
** The server may need to be upgraded. See https://openssh.com/pq.html
|
|
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
|
|
remote:
|
|
remote: View merge request for test:
|
|
remote: https://example.com/test/test/-/merge_requests/99999
|
|
remote:
|
|
To example.com:test/test.git
|
|
+ 80bd3c83be...e03d499d2e test -> test
|
|
"}
|
|
.to_string(),
|
|
};
|
|
|
|
let msg = format_output(&action, output);
|
|
|
|
assert!(matches!(msg.style, SuccessStyle::ToastWithLog { .. }));
|
|
assert_eq!(msg.message, "Pushed test_branch to test_remote");
|
|
}
|
|
|
|
#[test]
|
|
fn test_push_new_branch_no_link() {
|
|
let action = RemoteAction::Push(
|
|
SharedString::new_static("test_branch"),
|
|
Remote {
|
|
name: SharedString::new_static("test_remote"),
|
|
},
|
|
);
|
|
|
|
let output = RemoteCommandOutput {
|
|
stdout: String::new(),
|
|
stderr: indoc! { "
|
|
To http://example.com/test/test.git
|
|
* [new branch] test -> test
|
|
",
|
|
}
|
|
.to_string(),
|
|
};
|
|
|
|
let msg = format_output(&action, output);
|
|
|
|
if let SuccessStyle::ToastWithLog { output } = &msg.style {
|
|
assert_eq!(
|
|
output.stderr,
|
|
"To http://example.com/test/test.git\n * [new branch] test -> test\n"
|
|
);
|
|
} else {
|
|
panic!("Expected ToastWithLog variant");
|
|
}
|
|
}
|
|
}
|