diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2023-12-01 00:00:00 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2023-12-01 00:00:00 +0000 |
| commit | 06be0bc44011411b78217459f505ed12281b32c4 (patch) | |
| tree | 36cab80e309d33f20fedcc97258700a379aa348e /src/sub_commands | |
| parent | 492cc67887855cecb3fb501c4b61af50bf645b73 (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.rs | 90 | ||||
| -rw-r--r-- | src/sub_commands/prs/list.rs | 225 | ||||
| -rw-r--r-- | src/sub_commands/prs/mod.rs | 3 |
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 { | |||
| 301 | pub static PR_KIND: u64 = 318; | 301 | pub static PR_KIND: u64 = 318; |
| 302 | pub static PATCH_KIND: u64 = 317; | 302 | pub static PATCH_KIND: u64 = 317; |
| 303 | 303 | ||
| 304 | fn generate_pr_and_patch_events( | 304 | pub 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 | |||
| 353 | pub 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 @@ | |||
| 1 | use anyhow::{Context, Result}; | ||
| 2 | |||
| 3 | #[cfg(not(test))] | ||
| 4 | use crate::client::Client; | ||
| 5 | #[cfg(test)] | ||
| 6 | use crate::client::MockConnect; | ||
| 7 | use 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)] | ||
| 17 | pub struct SubCommandArgs { | ||
| 18 | /// TODO ignore merged, and closed | ||
| 19 | #[arg(long, action)] | ||
| 20 | open_only: bool, | ||
| 21 | } | ||
| 22 | |||
| 23 | pub 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 | |||
| 194 | pub 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 | |||
| 204 | pub 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 | ||
| 4 | use crate::Cli; | 4 | use crate::Cli; |
| 5 | pub mod create; | 5 | pub mod create; |
| 6 | pub mod list; | ||
| 6 | 7 | ||
| 7 | #[derive(clap::Parser)] | 8 | #[derive(clap::Parser)] |
| 8 | pub struct SubCommandArgs { | 9 | pub struct SubCommandArgs { |
| @@ -13,10 +14,12 @@ pub struct SubCommandArgs { | |||
| 13 | #[derive(Debug, Subcommand)] | 14 | #[derive(Debug, Subcommand)] |
| 14 | pub enum Commands { | 15 | pub enum Commands { |
| 15 | Create(create::SubCommandArgs), | 16 | Create(create::SubCommandArgs), |
| 17 | List(list::SubCommandArgs), | ||
| 16 | } | 18 | } |
| 17 | 19 | ||
| 18 | pub async fn launch(cli_args: &Cli, pr_args: &SubCommandArgs) -> Result<()> { | 20 | pub 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 | } |