From 8a74c8198977b6d608da7bb83f132461f3c32335 Mon Sep 17 00:00:00 2001 From: Drew De Ponte Date: Sat, 6 Jul 2024 16:46:11 -0400 Subject: [PATCH] Add the push subcommand Add the push subcommand so that users will be able to easily push the local branch changes up to the configured remote tracking branch of a patch series by doing the following. gps push This is nice because you don't have to be checked out on the branch to push it up to the remote, and you don't have to know what the remote is or what the remote tracking branche's name is. This is generally useful and a convenience. But more specifically, it helps with the workflow where you create a patch series and request review for it, and then append one or more patches to that it, and then want to push those new patches up to the remote. [changelog] added: the push subcommand --- src/cli.rs | 9 ++++ src/commands/mod.rs | 1 + src/commands/push.rs | 13 ++++++ src/lib.rs | 1 + src/main.rs | 1 + src/ps/public/mod.rs | 1 + src/ps/public/push.rs | 98 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 124 insertions(+) create mode 100644 src/commands/push.rs create mode 100644 src/ps/public/push.rs diff --git a/src/cli.rs b/src/cli.rs index 3f8fe49..405f411 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -80,6 +80,11 @@ pub struct BackupStackCmdOpts { pub branch_name: String, } +#[derive(Debug, Args)] +pub struct PushCmdOpts { + pub branch_name: String, +} + #[derive(Debug, Subcommand)] pub enum Command { /// (b) - Create a branch for a patch or patch series @@ -237,6 +242,10 @@ either a Git alias or a shell alias for that command. #[command(name = "append", alias = "a")] Append(AppendCmdOpts), + /// Push the local patches of the named patch series up to it's remote tracking branch + #[command(name = "push")] + Push(PushCmdOpts), + /// (iso) - isolate a patch or series of patches for manual testing or evaluation. /// /// The `isolate` command isolates a patch or series of patches for manual testing or diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 27f51ba..43c7271 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -15,6 +15,7 @@ pub mod list; pub mod patch_index_range; pub mod patch_index_range_batch; pub mod pull; +pub mod push; pub mod rebase; pub mod request_review; pub mod sha; diff --git a/src/commands/push.rs b/src/commands/push.rs new file mode 100644 index 0000000..c014e55 --- /dev/null +++ b/src/commands/push.rs @@ -0,0 +1,13 @@ +use super::utils::print_error_chain; +use gps as ps; + +pub fn push(branch_name: String, color: bool) { + let res = ps::push(branch_name); + match res { + Ok(_) => {} + Err(e) => { + print_error_chain(color, e.into()); + std::process::exit(1); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index de7f3f8..2f0c608 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ pub use ps::public::isolate::{isolate, IsolateError}; pub use ps::public::latest_github_release::{newer_release_available, notify_of_newer_release}; pub use ps::public::list::list; pub use ps::public::pull::{pull, PullError}; +pub use ps::public::push::push; pub use ps::public::rebase::rebase; pub use ps::public::request_review::{request_review, RequestReviewError}; pub use ps::public::sha; diff --git a/src/main.rs b/src/main.rs index ce6866e..c075dc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ fn main() { cli::Command::Append(opts) => { commands::append::append(opts.patch_index_or_range, opts.branch_name, cli.color) } + cli::Command::Push(opts) => commands::push::push(opts.branch_name, cli.color), cli::Command::Isolate(opts) => { commands::isolate::isolate(opts.patch_index_or_range, cli.color) } diff --git a/src/ps/public/mod.rs b/src/ps/public/mod.rs index ac02b86..244c078 100644 --- a/src/ps/public/mod.rs +++ b/src/ps/public/mod.rs @@ -9,6 +9,7 @@ pub mod isolate; pub mod latest_github_release; pub mod list; pub mod pull; +pub mod push; pub mod rebase; pub mod request_review; pub mod sha; diff --git a/src/ps/public/push.rs b/src/ps/public/push.rs new file mode 100644 index 0000000..433631b --- /dev/null +++ b/src/ps/public/push.rs @@ -0,0 +1,98 @@ +use super::super::private::git; +use std::result::Result; + +#[derive(Debug)] +pub enum PushError { + OpenRepositoryFailed(Box), + FindBranchFailed(Box), + GetUpstreamBranchFailed(Box), + GetUpstreamBranchNameFailed(Box), + RemoteBranchNameNotUtf8, + ExtractRemoteFailed(String), + ExtractRelativeBranchNameFailed(String), + PushFailed(Box), + Unhandled(Box), +} + +impl std::fmt::Display for PushError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::OpenRepositoryFailed(e) => write!(f, "failed to open repository {}", e), + Self::FindBranchFailed(e) => { + write!(f, "failed to find branch with the provided name {}", e) + } + Self::GetUpstreamBranchFailed(e) => write!(f, "failed to get upstream branch, {}", e), + Self::GetUpstreamBranchNameFailed(e) => { + write!(f, "failed to get upstream branch name, {}", e) + } + Self::RemoteBranchNameNotUtf8 => write!(f, "remote branch name isn't valid utf-8"), + Self::ExtractRemoteFailed(from) => { + write!(f, "failed to extract remote name from {}", from) + } + Self::ExtractRelativeBranchNameFailed(from) => { + write!(f, "failed to extract relative branch name from {}", from) + } + Self::PushFailed(e) => write!(f, "failed when trying to push, {}", e), + Self::Unhandled(e) => write!(f, "{}", e), + } + } +} + +impl std::error::Error for PushError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::OpenRepositoryFailed(e) => Some(e.as_ref()), + Self::FindBranchFailed(e) => Some(e.as_ref()), + Self::GetUpstreamBranchFailed(e) => Some(e.as_ref()), + Self::GetUpstreamBranchNameFailed(e) => Some(e.as_ref()), + Self::RemoteBranchNameNotUtf8 => None, + Self::ExtractRemoteFailed(_) => None, + Self::ExtractRelativeBranchNameFailed(_) => None, + Self::PushFailed(e) => Some(e.as_ref()), + Self::Unhandled(e) => Some(e.as_ref()), + } + } +} + +pub fn push(branch_name: String) -> Result<(), PushError> { + let repo = git::create_cwd_repo().map_err(|e| PushError::OpenRepositoryFailed(e.into()))?; + + let branch = repo + .find_branch(&branch_name, git2::BranchType::Local) + .map_err(|e| PushError::FindBranchFailed(e.into()))?; + + let remote_branch = branch + .upstream() + .map_err(|e| PushError::GetUpstreamBranchFailed(e.into()))?; + + let remote_branch_name_str = remote_branch + .name() + .map_err(|e| PushError::GetUpstreamBranchNameFailed(e.into()))? + .ok_or(PushError::RemoteBranchNameNotUtf8)?; + + let mut remote_branch_name_parts = remote_branch_name_str.split('/'); + let remote_branch_remote = + remote_branch_name_parts + .next() + .ok_or(PushError::ExtractRemoteFailed( + remote_branch_name_str.to_owned(), + ))?; + let remote_branch_relative_name = + remote_branch_name_parts + .next() + .ok_or(PushError::ExtractRelativeBranchNameFailed( + remote_branch_name_str.to_owned(), + ))?; + + // e.g. origin/the-branch so it is / + + git::ext_push( + false, + remote_branch_remote, + &branch_name, + remote_branch_relative_name, + ) + .map_err(|e| PushError::PushFailed(e.into()))?; + + Ok(()) +}