upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/sub_commands/push.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sub_commands/push.rs')
-rw-r--r--src/sub_commands/push.rs182
1 files changed, 182 insertions, 0 deletions
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs
new file mode 100644
index 0000000..968aa0a
--- /dev/null
+++ b/src/sub_commands/push.rs
@@ -0,0 +1,182 @@
1use anyhow::{bail, Context, Result};
2use nostr::prelude::sha1::Hash as Sha1Hash;
3
4#[cfg(not(test))]
5use crate::client::Client;
6#[cfg(test)]
7use crate::client::MockConnect;
8use crate::{
9 client::Connect,
10 git::{str_to_sha1, Repo, RepoActions},
11 login,
12 repo_ref::{self, RepoRef},
13 sub_commands::prs::{
14 create::{generate_patch_event, send_events, PATCH_KIND, PR_KIND},
15 list::{get_most_recent_patch_with_ancestors, tag_value},
16 },
17 Cli,
18};
19
20pub async fn launch(cli_args: &Cli) -> Result<()> {
21 let git_repo = Repo::discover().context("cannot find a git repository")?;
22
23 let (main_or_master_branch_name, _) = git_repo
24 .get_main_or_master_branch()
25 .context("no main or master branch")?;
26
27 let root_commit = git_repo
28 .get_root_commit(main_or_master_branch_name)
29 .context("failed to get root commit of the repository")?;
30
31 let branch_name = git_repo
32 .get_checked_out_branch_name()
33 .context("cannot get checked out branch name")?;
34
35 if branch_name == main_or_master_branch_name {
36 bail!("checkout a branch associated with a PR first")
37 }
38 #[cfg(not(test))]
39 let mut client = Client::default();
40 #[cfg(test)]
41 let mut client = <MockConnect as std::default::Default>::default();
42
43 let repo_ref = repo_ref::fetch(
44 root_commit.to_string(),
45 &client,
46 client.get_more_fallback_relays().clone(),
47 )
48 .await?;
49
50 let (pr_event, commit_events) =
51 fetch_pr_and_most_recent_patch_chain(&client, &repo_ref, &root_commit, &branch_name)
52 .await?;
53
54 // TODO: fix these scenarios:
55 // - local PR branch is 2 behind and 1 ahead. intructions: ...
56 // - PR has been rebased. (against commit in main) instructions: ...
57 // - PR has been rebased. (against commit not in repo) instructions: ..
58
59 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commit_events)
60 .context("cannot get most recent patch for PR")?;
61
62 let branch_tip = git_repo.get_tip_of_local_branch(&branch_name)?;
63
64 let most_recent_patch_commit_id = str_to_sha1(
65 &tag_value(&most_recent_pr_patch_chain[0], "commit")
66 .context("latest patch event doesnt have a commit tag")?,
67 )
68 .context("latest patch event commit tag isn't a valid SHA1 hash")?;
69
70 if most_recent_patch_commit_id.eq(&branch_tip) {
71 bail!("nostr pr already up-to-date with local branch");
72 }
73
74 if most_recent_pr_patch_chain.iter().any(|e| {
75 let c = tag_value(e, "parent-commit").unwrap_or_default();
76 c.eq(&branch_tip.to_string())
77 }) {
78 bail!("nostr pr is ahead of local branch");
79 }
80
81 let (ahead, behind) = git_repo
82 .get_commits_ahead_behind(&most_recent_patch_commit_id, &branch_tip)
83 .context("the latest patch in pr doesnt share an ancestor with your branch.")?;
84
85 if !behind.is_empty() {
86 bail!(
87 "your local pr branch is {} behind patches on nostr. consider rebasing or force pushing",
88 behind.len()
89 )
90 }
91
92 println!(
93 "{} commits ahead. preparing to create creating patch events.",
94 ahead.len()
95 );
96
97 let (keys, user_ref) = login::launch(&cli_args.nsec, &cli_args.password, Some(&client)).await?;
98
99 client.set_keys(&keys).await;
100
101 let mut patch_events: Vec<nostr::Event> = vec![];
102 for commit in &ahead {
103 patch_events.push(
104 generate_patch_event(&git_repo, &root_commit, commit, pr_event.id, &keys)
105 .context("cannot make patch event from commit")?,
106 );
107 }
108 println!("pushing {} commits", ahead.len());
109
110 send_events(
111 &client,
112 patch_events,
113 user_ref.relays.write(),
114 repo_ref.relays.clone(),
115 !cli_args.disable_cli_spinners,
116 )
117 .await?;
118
119 println!("pushed {} commits", ahead.len());
120
121 Ok(())
122}
123
124async fn fetch_pr_and_most_recent_patch_chain(
125 #[cfg(test)] client: &crate::client::MockConnect,
126 #[cfg(not(test))] client: &Client,
127 repo_ref: &RepoRef,
128 root_commit: &Sha1Hash,
129 branch_name: &String,
130) -> Result<(nostr::Event, Vec<nostr::Event>)> {
131 println!("finding PR event...");
132
133 let pr_event: nostr::Event = client
134 .get_events(
135 repo_ref.relays.clone(),
136 vec![
137 nostr::Filter::default()
138 .kind(nostr::Kind::Custom(PR_KIND))
139 .reference(format!("r-{root_commit}")),
140 ],
141 )
142 .await?
143 .iter()
144 .find(|e| {
145 e.kind.as_u64() == PR_KIND
146 && e.tags
147 .iter()
148 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("r-{root_commit}")))
149 && tag_value(e, "branch-name")
150 .unwrap_or_default()
151 .eq(branch_name)
152 })
153 .context("cannot find a PR event associated with the checked out branch name")?
154 .to_owned();
155
156 println!("found PR event. finding commits...");
157
158 let commits_events: Vec<nostr::Event> = client
159 .get_events(
160 repo_ref.relays.clone(),
161 vec![
162 nostr::Filter::default()
163 .kind(nostr::Kind::Custom(PATCH_KIND))
164 .event(pr_event.id)
165 .reference(format!("r-{root_commit}")),
166 ],
167 )
168 .await?
169 .iter()
170 .filter(|e| {
171 e.kind.as_u64() == PATCH_KIND
172 && e.tags
173 .iter()
174 .any(|t| t.as_vec().len() > 2 && t.as_vec()[1].eq(&pr_event.id.to_string()))
175 && e.tags
176 .iter()
177 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("r-{root_commit}")))
178 })
179 .map(std::borrow::ToOwned::to_owned)
180 .collect();
181 Ok((pr_event, commits_events))
182}