upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/sub_commands
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2023-12-01 00:00:00 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2023-12-01 00:00:00 +0000
commit06be0bc44011411b78217459f505ed12281b32c4 (patch)
tree36cab80e309d33f20fedcc97258700a379aa348e /src/sub_commands
parent492cc67887855cecb3fb501c4b61af50bf645b73 (diff)
feat(prs-list) list and pull selected as branch
- fetch prs and present as a selectable list - create and / or checkout branch for selected pr - apply latest patches as commits
Diffstat (limited to 'src/sub_commands')
-rw-r--r--src/sub_commands/prs/create.rs90
-rw-r--r--src/sub_commands/prs/list.rs225
-rw-r--r--src/sub_commands/prs/mod.rs3
3 files changed, 284 insertions, 34 deletions
diff --git a/src/sub_commands/prs/create.rs b/src/sub_commands/prs/create.rs
index ce30c12..5c4e578 100644
--- a/src/sub_commands/prs/create.rs
+++ b/src/sub_commands/prs/create.rs
@@ -301,9 +301,9 @@ mod tests_unique_and_duplicate {
301pub static PR_KIND: u64 = 318; 301pub static PR_KIND: u64 = 318;
302pub static PATCH_KIND: u64 = 317; 302pub static PATCH_KIND: u64 = 317;
303 303
304fn generate_pr_and_patch_events( 304pub fn generate_pr_and_patch_events(
305 title: &String, 305 title: &str,
306 description: &String, 306 description: &str,
307 to_branch: &str, 307 to_branch: &str,
308 git_repo: &Repo, 308 git_repo: &Repo,
309 commits: &Vec<Sha1Hash>, 309 commits: &Vec<Sha1Hash>,
@@ -314,6 +314,7 @@ fn generate_pr_and_patch_events(
314 .context("failed to get root commit of the repository")?; 314 .context("failed to get root commit of the repository")?;
315 315
316 let mut pr_tags = vec![ 316 let mut pr_tags = vec![
317 Tag::Identifier(root_commit.to_string()),
317 Tag::Reference(format!("r-{root_commit}")), 318 Tag::Reference(format!("r-{root_commit}")),
318 Tag::Name(title.to_string()), 319 Tag::Name(title.to_string()),
319 Tag::Description(description.to_string()), 320 Tag::Description(description.to_string()),
@@ -341,42 +342,63 @@ fn generate_pr_and_patch_events(
341 342
342 let mut events = vec![pr_event]; 343 let mut events = vec![pr_event];
343 for commit in commits { 344 for commit in commits {
344 let commit_parent = git_repo
345 .get_commit_parent(commit)
346 .context("failed to create patch event")?;
347 events.push( 345 events.push(
348 EventBuilder::new( 346 generate_patch_event(git_repo, &root_commit, commit, pr_event_id, keys)
349 nostr::event::Kind::Custom(PATCH_KIND), 347 .context("failed to generate patch event")?,
350 git_repo
351 .make_patch_from_commit(commit)
352 .context(format!("cannot make patch for commit {commit}"))?,
353 &[
354 Tag::Reference(format!("r-{root_commit}")),
355 Tag::Reference(commit.to_string()),
356 Tag::Reference(commit_parent.to_string()),
357 Tag::Event(
358 pr_event_id,
359 None, // TODO: add relay
360 Some(Marker::Root),
361 ),
362 Tag::Generic(
363 TagKind::Custom("commit".to_string()),
364 vec![commit.to_string()],
365 ),
366 Tag::Generic(
367 TagKind::Custom("parent-commit".to_string()),
368 vec![commit_parent.to_string()],
369 ),
370 // TODO: add Repo event tags
371 // TODO: people tag maintainers
372 // TODO: add relay tags
373 ],
374 )
375 .to_event(keys)?,
376 ); 348 );
377 } 349 }
378 Ok(events) 350 Ok(events)
379} 351}
352
353pub fn generate_patch_event(
354 git_repo: &Repo,
355 root_commit: &Sha1Hash,
356 commit: &Sha1Hash,
357 pr_event_id: nostr::EventId,
358 keys: &nostr::Keys,
359) -> Result<nostr::Event> {
360 let commit_parent = git_repo
361 .get_commit_parent(commit)
362 .context("failed to get parent commit")?;
363 EventBuilder::new(
364 nostr::event::Kind::Custom(PATCH_KIND),
365 git_repo
366 .make_patch_from_commit(commit)
367 .context(format!("cannot make patch for commit {commit}"))?,
368 &[
369 Tag::Reference(format!("r-{root_commit}")),
370 Tag::Reference(commit.to_string()),
371 Tag::Reference(commit_parent.to_string()),
372 Tag::Event(
373 pr_event_id,
374 None, // TODO: add relay
375 Some(Marker::Root),
376 ),
377 Tag::Generic(
378 TagKind::Custom("commit".to_string()),
379 vec![commit.to_string()],
380 ),
381 Tag::Generic(
382 TagKind::Custom("parent-commit".to_string()),
383 vec![commit_parent.to_string()],
384 ),
385 Tag::Description(git_repo.get_commit_message(commit)?.to_string()),
386 Tag::Generic(
387 TagKind::Custom("author".to_string()),
388 git_repo.get_commit_author(commit)?,
389 ),
390 Tag::Generic(
391 TagKind::Custom("committer".to_string()),
392 git_repo.get_commit_comitter(commit)?,
393 ),
394 // TODO: add Repo event tags
395 // TODO: people tag maintainers
396 // TODO: add relay tags
397 ],
398 )
399 .to_event(keys)
400 .context("failed to sign event")
401}
380// TODO 402// TODO
381// - find profile 403// - find profile
382// - file relays 404// - file relays
diff --git a/src/sub_commands/prs/list.rs b/src/sub_commands/prs/list.rs
new file mode 100644
index 0000000..13f29fd
--- /dev/null
+++ b/src/sub_commands/prs/list.rs
@@ -0,0 +1,225 @@
1use anyhow::{Context, Result};
2
3#[cfg(not(test))]
4use crate::client::Client;
5#[cfg(test)]
6use crate::client::MockConnect;
7use crate::{
8 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms},
9 client::Connect,
10 git::{Repo, RepoActions},
11 repo_ref,
12 sub_commands::prs::create::{PATCH_KIND, PR_KIND},
13 Cli,
14};
15
16#[derive(Debug, clap::Args)]
17pub struct SubCommandArgs {
18 /// TODO ignore merged, and closed
19 #[arg(long, action)]
20 open_only: bool,
21}
22
23pub async fn launch(
24 _cli_args: &Cli,
25 _pr_args: &super::SubCommandArgs,
26 _args: &SubCommandArgs,
27) -> Result<()> {
28 let git_repo = Repo::discover().context("cannot find a git repository")?;
29
30 let (main_or_master_branch_name, _) = git_repo
31 .get_main_or_master_branch()
32 .context("no main or master branch")?;
33
34 let root_commit = git_repo
35 .get_root_commit(main_or_master_branch_name)
36 .context("failed to get root commit of the repository")?;
37
38 // TODO: check for empty repo
39 // TODO: check for existing maintaiers file
40 // TODO: check for other claims
41
42 #[cfg(not(test))]
43 let client = Client::default();
44 #[cfg(test)]
45 let client = <MockConnect as std::default::Default>::default();
46
47 let repo_ref = repo_ref::fetch(
48 root_commit.to_string(),
49 &client,
50 client.get_more_fallback_relays().clone(),
51 )
52 .await?;
53
54 println!("finding PRs...");
55
56 let pr_events: Vec<nostr::Event> = client
57 .get_events(
58 repo_ref.relays.clone(),
59 vec![
60 nostr::Filter::default()
61 .kind(nostr::Kind::Custom(PR_KIND))
62 .reference(format!("r-{root_commit}")),
63 ],
64 )
65 .await?
66 .iter()
67 .filter(|e| {
68 e.kind.as_u64() == PR_KIND
69 && e.tags
70 .iter()
71 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("r-{root_commit}")))
72 })
73 .map(std::borrow::ToOwned::to_owned)
74 .collect();
75
76 // let pr_branch_names: Vec<String> = pr_events
77 // .iter()
78 // .map(|e| {
79 // format!(
80 // "{}-{}",
81 // &e.id.to_string()[..5],
82 // if let Some(t) = e.tags.iter().find(|t| t.as_vec()[0] ==
83 // "branch-name") { t.as_vec()[1].to_string()
84 // } else {
85 // "".to_string()
86 // } // git_repo.get_checked_out_branch_name(),
87 // )
88 // })
89 // .collect();
90
91 let selected_index = Interactor::default().choice(
92 PromptChoiceParms::default()
93 .with_prompt("All PRs")
94 .with_choices(
95 pr_events
96 .iter()
97 .map(|e| {
98 if let Ok(name) = tag_value(e, "name") {
99 name
100 } else {
101 e.id.to_string()
102 }
103 })
104 .collect(),
105 ),
106 )?;
107 // println!("prs:{:?}", &pr_events);
108
109 println!("finding commits...");
110
111 let commits_events: Vec<nostr::Event> = client
112 .get_events(
113 repo_ref.relays.clone(),
114 vec![
115 nostr::Filter::default()
116 .kind(nostr::Kind::Custom(PATCH_KIND))
117 .event(pr_events[selected_index].id)
118 .reference(format!("r-{root_commit}")),
119 ],
120 )
121 .await?
122 .iter()
123 .filter(|e| {
124 e.kind.as_u64() == PATCH_KIND
125 && e.tags.iter().any(|t| {
126 t.as_vec().len() > 2
127 && t.as_vec()[1].eq(&pr_events[selected_index].id.to_string())
128 })
129 && e.tags
130 .iter()
131 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("r-{root_commit}")))
132 })
133 .map(std::borrow::ToOwned::to_owned)
134 .collect();
135
136 // TODO: are there outstanding changes to prevent checking out a new branch?
137
138 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commits_events)
139 .context("cannot get most recent patch for PR")?;
140
141 let branch_name = tag_value(&pr_events[selected_index], "branch-name")?;
142
143 let applied = git_repo
144 .apply_patch_chain(&branch_name, most_recent_pr_patch_chain)
145 .context("cannot apply patch chain")?;
146
147 if applied.is_empty() {
148 println!("checked out PR branch. no new commits to pull");
149 } else {
150 println!(
151 "checked out PR branch. pulled {} new commits",
152 applied.len(),
153 );
154 }
155
156 // // TODO: look for mapping of existing branch
157
158 // // if latest_commit_id exists locally
159 // if local_branch_base == latest_commit_id {
160 // // TODO: check if its in the main / master branch (already merged)
161 // // TODO: check if it has any decendants and warn. maybe the user has
162 // // been working on a updates to be pushed? Suggest checking
163 // // out that branch.
164 // // we could search nostr for decendants of the commit as well?
165 // // perhaps this is overkill
166 // // TODO: check out the branch which it is the tip of. if the name of the
167 // // branch is different then ask the user if they would like to
168 // // use the existing branch or create one with the name of the PR.
169 // // TODO: if there are no decendants and its not the tip then
170 // // its an ophan commit so just make a branch from this commit.
171 // }
172 // // if commits ahead exist in a branch other than main or master
173 // // TODO: Identify probable existing branches - check remote tracker?
174 // // TODO: beind head
175 // else {
176 // // TODO: look for existing branch with same name
177 // // TODO: create remote tracker
178 // git_repo.create_branch_at_commit(&branch_name, &local_branch_base);
179 // git_repo.checkout(&branch_name)?;
180 // ahead.reverse();
181 // for event in ahead {
182 // git_repo.apply_patch(event, branch_name)?;
183 // }
184 // println!("applied!")
185 // }
186 // // TODO: check if commits in pr exist, if so look for branches with they are
187 // in // could we suggest pulling updates into that branch?
188 // //
189
190 // TODO: checkout PR branch
191 Ok(())
192}
193
194pub fn tag_value(event: &nostr::Event, tag_name: &str) -> Result<String> {
195 Ok(event
196 .tags
197 .iter()
198 .find(|t| t.as_vec()[0].eq(tag_name))
199 .context(format!("tag '{tag_name}'not present"))?
200 .as_vec()[1]
201 .clone())
202}
203
204pub fn get_most_recent_patch_with_ancestors(
205 mut patches: Vec<nostr::Event>,
206) -> Result<Vec<nostr::Event>> {
207 patches.sort_by_key(|e| e.created_at);
208
209 let mut res = vec![];
210
211 let latest_commit_id = tag_value(patches.first().context("no patches found")?, "commit")?;
212
213 let mut commit_id_to_search = latest_commit_id;
214
215 while let Some(event) = patches.iter().find(|e| {
216 tag_value(e, "commit")
217 .context("patch event doesnt contain commit tag")
218 .unwrap()
219 .eq(&commit_id_to_search)
220 }) {
221 res.push(event.clone());
222 commit_id_to_search = tag_value(event, "parent-commit")?;
223 }
224 Ok(res)
225}
diff --git a/src/sub_commands/prs/mod.rs b/src/sub_commands/prs/mod.rs
index 982e866..a41c495 100644
--- a/src/sub_commands/prs/mod.rs
+++ b/src/sub_commands/prs/mod.rs
@@ -3,6 +3,7 @@ use clap::Subcommand;
3 3
4use crate::Cli; 4use crate::Cli;
5pub mod create; 5pub mod create;
6pub mod list;
6 7
7#[derive(clap::Parser)] 8#[derive(clap::Parser)]
8pub struct SubCommandArgs { 9pub struct SubCommandArgs {
@@ -13,10 +14,12 @@ pub struct SubCommandArgs {
13#[derive(Debug, Subcommand)] 14#[derive(Debug, Subcommand)]
14pub enum Commands { 15pub enum Commands {
15 Create(create::SubCommandArgs), 16 Create(create::SubCommandArgs),
17 List(list::SubCommandArgs),
16} 18}
17 19
18pub async fn launch(cli_args: &Cli, pr_args: &SubCommandArgs) -> Result<()> { 20pub async fn launch(cli_args: &Cli, pr_args: &SubCommandArgs) -> Result<()> {
19 match &pr_args.prs_command { 21 match &pr_args.prs_command {
20 Commands::Create(args) => create::launch(cli_args, pr_args, args).await, 22 Commands::Create(args) => create::launch(cli_args, pr_args, args).await,
23 Commands::List(args) => list::launch(cli_args, pr_args, args).await,
21 } 24 }
22} 25}