diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-23 08:15:24 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-23 08:15:24 +0000 |
| commit | 84d8f03cf2471d3530f4657055f272474880b6b5 (patch) | |
| tree | 36d1e57526fa9b201315552cada843a357334a55 /src/sub_commands | |
| parent | edb7bf7ee2ffbd718b927c5431d3c9fa5305ec06 (diff) | |
feat(push): add `--force` to issue revision
wrapping `send --in-reply-to` unless branch up-to-date
Diffstat (limited to 'src/sub_commands')
| -rw-r--r-- | src/sub_commands/push.rs | 67 | ||||
| -rw-r--r-- | src/sub_commands/send.rs | 14 |
2 files changed, 64 insertions, 17 deletions
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs index dd32b2c..75bff2d 100644 --- a/src/sub_commands/push.rs +++ b/src/sub_commands/push.rs | |||
| @@ -11,6 +11,7 @@ use crate::{ | |||
| 11 | login, | 11 | login, |
| 12 | repo_ref::{self, RepoRef}, | 12 | repo_ref::{self, RepoRef}, |
| 13 | sub_commands::{ | 13 | sub_commands::{ |
| 14 | self, | ||
| 14 | list::{ | 15 | list::{ |
| 15 | find_commits_for_proposal_root_events, find_proposal_events, get_commit_id_from_patch, | 16 | find_commits_for_proposal_root_events, find_proposal_events, get_commit_id_from_patch, |
| 16 | get_most_recent_patch_with_ancestors, tag_value, | 17 | get_most_recent_patch_with_ancestors, tag_value, |
| @@ -20,7 +21,18 @@ use crate::{ | |||
| 20 | Cli, | 21 | Cli, |
| 21 | }; | 22 | }; |
| 22 | 23 | ||
| 23 | pub async fn launch(cli_args: &Cli) -> Result<()> { | 24 | #[derive(Debug, clap::Args)] |
| 25 | pub struct SubCommandArgs { | ||
| 26 | #[arg(long, action)] | ||
| 27 | /// send proposal revision from checked out proposal branch | ||
| 28 | force: bool, | ||
| 29 | #[arg(long, action)] | ||
| 30 | /// dont prompt for cover letter when force pushing | ||
| 31 | no_cover_letter: bool, | ||
| 32 | } | ||
| 33 | |||
| 34 | #[allow(clippy::too_many_lines)] | ||
| 35 | pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { | ||
| 24 | let git_repo = Repo::discover().context("cannot find a git repository")?; | 36 | let git_repo = Repo::discover().context("cannot find a git repository")?; |
| 25 | 37 | ||
| 26 | let (main_or_master_branch_name, _) = git_repo | 38 | let (main_or_master_branch_name, _) = git_repo |
| @@ -59,26 +71,55 @@ pub async fn launch(cli_args: &Cli) -> Result<()> { | |||
| 59 | ) | 71 | ) |
| 60 | .await?; | 72 | .await?; |
| 61 | 73 | ||
| 62 | // TODO: fix these scenarios: | ||
| 63 | // - local proposal branch is 2 behind and 1 ahead. intructions: ... | ||
| 64 | // - proposal has been rebased. (against commit in main) instructions: ... | ||
| 65 | // - proposal has been rebased. (against commit not in repo) instructions: .. | ||
| 66 | |||
| 67 | let most_recent_proposal_patch_chain = get_most_recent_patch_with_ancestors(commit_events) | 74 | let most_recent_proposal_patch_chain = get_most_recent_patch_with_ancestors(commit_events) |
| 68 | .context("cannot get most recent patch for proposal")?; | 75 | .context("cannot get most recent patch for proposal")?; |
| 69 | 76 | ||
| 70 | let branch_tip = git_repo.get_tip_of_local_branch(&branch_name)?; | 77 | let branch_tip = git_repo.get_tip_of_local_branch(&branch_name)?; |
| 71 | 78 | ||
| 72 | let most_recent_patch_commit_id = str_to_sha1( | 79 | let most_recent_patch_commit_id = str_to_sha1( |
| 73 | &get_commit_id_from_patch(&most_recent_proposal_patch_chain[0]) | 80 | &get_commit_id_from_patch( |
| 74 | .context("latest patch event doesnt have a commit tag")?, | 81 | most_recent_proposal_patch_chain |
| 82 | .first() | ||
| 83 | .context("no patches found")?, | ||
| 84 | ) | ||
| 85 | .context("latest patch event doesnt have a commit tag")?, | ||
| 75 | ) | 86 | ) |
| 76 | .context("latest patch event commit tag isn't a valid SHA1 hash")?; | 87 | .context("latest patch event commit tag isn't a valid SHA1 hash")?; |
| 77 | 88 | ||
| 89 | let proposal_base_commit_id = str_to_sha1( | ||
| 90 | &tag_value( | ||
| 91 | most_recent_proposal_patch_chain | ||
| 92 | .last() | ||
| 93 | .context("no patches found")?, | ||
| 94 | "parent-commit", | ||
| 95 | ) | ||
| 96 | .context("patch is incorrectly formatted")?, | ||
| 97 | ) | ||
| 98 | .context("latest patch event parent-commit tag isn't a valid SHA1 hash")?; | ||
| 99 | |||
| 78 | if most_recent_patch_commit_id.eq(&branch_tip) { | 100 | if most_recent_patch_commit_id.eq(&branch_tip) { |
| 79 | bail!("proposal already up-to-date with local branch"); | 101 | bail!("proposal already up-to-date with local branch"); |
| 80 | } | 102 | } |
| 81 | 103 | ||
| 104 | if args.force { | ||
| 105 | println!("preparing to force push proposal revision..."); | ||
| 106 | sub_commands::send::launch( | ||
| 107 | cli_args, | ||
| 108 | &sub_commands::send::SubCommandArgs { | ||
| 109 | starting_commit: String::new(), | ||
| 110 | in_reply_to: Some(proposal_root_event.id.to_string()), | ||
| 111 | title: None, | ||
| 112 | description: None, | ||
| 113 | from_branch: None, | ||
| 114 | to_branch: None, | ||
| 115 | no_cover_letter: args.no_cover_letter, | ||
| 116 | }, | ||
| 117 | ) | ||
| 118 | .await?; | ||
| 119 | println!("force pushed proposal revision"); | ||
| 120 | return Ok(()); | ||
| 121 | } | ||
| 122 | |||
| 82 | if most_recent_proposal_patch_chain.iter().any(|e| { | 123 | if most_recent_proposal_patch_chain.iter().any(|e| { |
| 83 | let c = tag_value(e, "parent-commit").unwrap_or_default(); | 124 | let c = tag_value(e, "parent-commit").unwrap_or_default(); |
| 84 | c.eq(&branch_tip.to_string()) | 125 | c.eq(&branch_tip.to_string()) |
| @@ -86,9 +127,15 @@ pub async fn launch(cli_args: &Cli) -> Result<()> { | |||
| 86 | bail!("proposal is ahead of local branch"); | 127 | bail!("proposal is ahead of local branch"); |
| 87 | } | 128 | } |
| 88 | 129 | ||
| 89 | let (ahead, behind) = git_repo | 130 | let Ok((ahead, behind)) = git_repo |
| 90 | .get_commits_ahead_behind(&most_recent_patch_commit_id, &branch_tip) | 131 | .get_commits_ahead_behind(&most_recent_patch_commit_id, &branch_tip) |
| 91 | .context("the latest patch in proposal doesnt share an ancestor with your branch.")?; | 132 | .context("the latest patch in proposal doesnt share an ancestor with your branch.") |
| 133 | else { | ||
| 134 | if git_repo.ancestor_of(&proposal_base_commit_id, &branch_tip)? { | ||
| 135 | bail!("local unpublished proposal ammendments. consider force pushing."); | ||
| 136 | } | ||
| 137 | bail!("local unpublished proposal has been rebased. consider force pushing"); | ||
| 138 | }; | ||
| 92 | 139 | ||
| 93 | if !behind.is_empty() { | 140 | if !behind.is_empty() { |
| 94 | bail!( | 141 | bail!( |
diff --git a/src/sub_commands/send.rs b/src/sub_commands/send.rs index 1ccb1f4..ebe23b1 100644 --- a/src/sub_commands/send.rs +++ b/src/sub_commands/send.rs | |||
| @@ -27,26 +27,26 @@ pub struct SubCommandArgs { | |||
| 27 | #[arg(default_value = "")] | 27 | #[arg(default_value = "")] |
| 28 | /// starting commit (commits since in current branch) or commit range, like | 28 | /// starting commit (commits since in current branch) or commit range, like |
| 29 | /// in `git format-patch` | 29 | /// in `git format-patch` |
| 30 | starting_commit: String, | 30 | pub(crate) starting_commit: String, |
| 31 | #[clap(long)] | 31 | #[clap(long)] |
| 32 | /// nevent or event id of an existing proposal for which this is a new | 32 | /// nevent or event id of an existing proposal for which this is a new |
| 33 | /// version | 33 | /// version |
| 34 | in_reply_to: Option<String>, | 34 | pub(crate) in_reply_to: Option<String>, |
| 35 | /// optional cover letter title | 35 | /// optional cover letter title |
| 36 | #[clap(short, long)] | 36 | #[clap(short, long)] |
| 37 | title: Option<String>, | 37 | pub(crate) title: Option<String>, |
| 38 | #[clap(short, long)] | 38 | #[clap(short, long)] |
| 39 | /// optional cover letter description | 39 | /// optional cover letter description |
| 40 | description: Option<String>, | 40 | pub(crate) description: Option<String>, |
| 41 | #[clap(long)] | 41 | #[clap(long)] |
| 42 | /// branch to get changes from (defaults to head) | 42 | /// branch to get changes from (defaults to head) |
| 43 | from_branch: Option<String>, | 43 | pub(crate) from_branch: Option<String>, |
| 44 | #[clap(long)] | 44 | #[clap(long)] |
| 45 | /// destination branch (defaults to main or master) | 45 | /// destination branch (defaults to main or master) |
| 46 | to_branch: Option<String>, | 46 | pub(crate) to_branch: Option<String>, |
| 47 | /// don't ask about a cover letter | 47 | /// don't ask about a cover letter |
| 48 | #[arg(long, action)] | 48 | #[arg(long, action)] |
| 49 | no_cover_letter: bool, | 49 | pub(crate) no_cover_letter: bool, |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | #[allow(clippy::too_many_lines)] | 52 | #[allow(clippy::too_many_lines)] |