upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2023-12-07 09:57:43 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2023-12-07 09:57:43 +0000
commit82bf53ac3c18e15b75852a48b2e5b432c75a5c7f (patch)
tree8f7ffb8d3ad11462fe87196ae61ad32446becedc
parentf811835ca768d6cbcef24f2873c43b51e63578ce (diff)
feat(pull) pull commits for checked out pr branch
- find pr event which matches branch name - fetch and apply latest commits
-rw-r--r--src/main.rs3
-rw-r--r--src/sub_commands/mod.rs1
-rw-r--r--src/sub_commands/pull.rs114
-rw-r--r--tests/pull.rs422
4 files changed, 540 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
index 805edd7..8c6f0d0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -38,6 +38,8 @@ enum Commands {
38 Claim(sub_commands::claim::SubCommandArgs), 38 Claim(sub_commands::claim::SubCommandArgs),
39 /// create and issue prs 39 /// create and issue prs
40 Prs(sub_commands::prs::SubCommandArgs), 40 Prs(sub_commands::prs::SubCommandArgs),
41 /// pull latest commits in pr linked to checked out branch
42 Pull,
41} 43}
42 44
43#[tokio::main] 45#[tokio::main]
@@ -51,5 +53,6 @@ async fn main() -> Result<()> {
51 futures::executor::block_on(sub_commands::claim::launch(&cli, args)) 53 futures::executor::block_on(sub_commands::claim::launch(&cli, args))
52 } 54 }
53 Commands::Prs(args) => futures::executor::block_on(sub_commands::prs::launch(&cli, args)), 55 Commands::Prs(args) => futures::executor::block_on(sub_commands::prs::launch(&cli, args)),
56 Commands::Pull => futures::executor::block_on(sub_commands::pull::launch()),
54 } 57 }
55} 58}
diff --git a/src/sub_commands/mod.rs b/src/sub_commands/mod.rs
index 6e99ca5..12a7f0f 100644
--- a/src/sub_commands/mod.rs
+++ b/src/sub_commands/mod.rs
@@ -1,3 +1,4 @@
1pub mod claim; 1pub mod claim;
2pub mod login; 2pub mod login;
3pub mod prs; 3pub mod prs;
4pub mod pull;
diff --git a/src/sub_commands/pull.rs b/src/sub_commands/pull.rs
new file mode 100644
index 0000000..a6513e8
--- /dev/null
+++ b/src/sub_commands/pull.rs
@@ -0,0 +1,114 @@
1use anyhow::{bail, Context, Result};
2
3#[cfg(not(test))]
4use crate::client::Client;
5#[cfg(test)]
6use crate::client::MockConnect;
7use crate::{
8 client::Connect,
9 git::{Repo, RepoActions},
10 repo_ref,
11 sub_commands::prs::{
12 create::{PATCH_KIND, PR_KIND},
13 list::{get_most_recent_patch_with_ancestors, tag_value},
14 },
15};
16
17pub async fn launch() -> Result<()> {
18 let git_repo = Repo::discover().context("cannot find a git repository")?;
19
20 let (main_or_master_branch_name, _) = git_repo
21 .get_main_or_master_branch()
22 .context("no main or master branch")?;
23
24 let root_commit = git_repo
25 .get_root_commit(main_or_master_branch_name)
26 .context("failed to get root commit of the repository")?;
27
28 let branch_name = git_repo
29 .get_checked_out_branch_name()
30 .context("cannot get checked out branch name")?;
31
32 if branch_name == main_or_master_branch_name {
33 bail!("checkout a branch associated with a PR first")
34 }
35 #[cfg(not(test))]
36 let client = Client::default();
37 #[cfg(test)]
38 let client = <MockConnect as std::default::Default>::default();
39
40 let repo_ref = repo_ref::fetch(
41 root_commit.to_string(),
42 &client,
43 client.get_more_fallback_relays().clone(),
44 )
45 .await?;
46
47 println!("finding PR event...");
48
49 let pr_event: nostr::Event = client
50 .get_events(
51 repo_ref.relays.clone(),
52 vec![
53 nostr::Filter::default()
54 .kind(nostr::Kind::Custom(PR_KIND))
55 .reference(format!("r-{root_commit}")),
56 ],
57 )
58 .await?
59 .iter()
60 .find(|e| {
61 e.kind.as_u64() == PR_KIND
62 && e.tags
63 .iter()
64 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("r-{root_commit}")))
65 && tag_value(e, "branch-name")
66 .unwrap_or_default()
67 .eq(&branch_name)
68 })
69 .context("cannot find a PR event associated with the checked out branch name")?
70 .to_owned();
71
72 println!("found PR event. finding commits...");
73
74 let commits_events: Vec<nostr::Event> = client
75 .get_events(
76 repo_ref.relays.clone(),
77 vec![
78 nostr::Filter::default()
79 .kind(nostr::Kind::Custom(PATCH_KIND))
80 .event(pr_event.id)
81 .reference(format!("r-{root_commit}")),
82 ],
83 )
84 .await?
85 .iter()
86 .filter(|e| {
87 e.kind.as_u64() == PATCH_KIND
88 && e.tags
89 .iter()
90 .any(|t| t.as_vec().len() > 2 && t.as_vec()[1].eq(&pr_event.id.to_string()))
91 && e.tags
92 .iter()
93 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("r-{root_commit}")))
94 })
95 .map(std::borrow::ToOwned::to_owned)
96 .collect();
97
98 // TODO: are there outstanding changes to prevent checking out a new branch?
99
100 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commits_events)
101 .context("cannot get most recent patch for PR")?;
102
103 let applied = git_repo
104 .apply_patch_chain(&branch_name, most_recent_pr_patch_chain)
105 .context("cannot apply patch chain")?;
106
107 if applied.is_empty() {
108 println!("branch already up-to-date");
109 } else {
110 println!("applied {} new commits", applied.len(),);
111 }
112
113 Ok(())
114}
diff --git a/tests/pull.rs b/tests/pull.rs
new file mode 100644
index 0000000..9b88cb5
--- /dev/null
+++ b/tests/pull.rs
@@ -0,0 +1,422 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6static FEATURE_BRANCH_NAME_1: &str = "feature-example-t";
7static FEATURE_BRANCH_NAME_2: &str = "feature-example-f";
8static FEATURE_BRANCH_NAME_3: &str = "feature-example-c";
9
10static PR_TITLE_1: &str = "pr a";
11static PR_TITLE_2: &str = "pr b";
12static PR_TITLE_3: &str = "pr c";
13
14fn cli_tester_create_prs() -> Result<GitTestRepo> {
15 let git_repo = GitTestRepo::default();
16 git_repo.populate()?;
17 cli_tester_create_pr(
18 &git_repo,
19 FEATURE_BRANCH_NAME_1,
20 "a",
21 PR_TITLE_1,
22 "pr a description",
23 )?;
24 cli_tester_create_pr(
25 &git_repo,
26 FEATURE_BRANCH_NAME_2,
27 "b",
28 PR_TITLE_2,
29 "pr b description",
30 )?;
31 cli_tester_create_pr(
32 &git_repo,
33 FEATURE_BRANCH_NAME_3,
34 "c",
35 PR_TITLE_3,
36 "pr c description",
37 )?;
38 Ok(git_repo)
39}
40
41fn create_and_populate_branch(
42 test_repo: &GitTestRepo,
43 branch_name: &str,
44 prefix: &str,
45 only_one_commit: bool,
46) -> Result<()> {
47 test_repo.checkout("main")?;
48 test_repo.create_branch(branch_name)?;
49 test_repo.checkout(branch_name)?;
50 std::fs::write(
51 test_repo.dir.join(format!("{}3.md", prefix)),
52 "some content",
53 )?;
54 test_repo.stage_and_commit(format!("add {}3.md", prefix).as_str())?;
55 if !only_one_commit {
56 std::fs::write(
57 test_repo.dir.join(format!("{}4.md", prefix)),
58 "some content",
59 )?;
60 test_repo.stage_and_commit(format!("add {}4.md", prefix).as_str())?;
61 }
62 Ok(())
63}
64
65fn cli_tester_create_pr(
66 test_repo: &GitTestRepo,
67 branch_name: &str,
68 prefix: &str,
69 title: &str,
70 description: &str,
71) -> Result<()> {
72 create_and_populate_branch(test_repo, branch_name, prefix, false)?;
73
74 let mut p = CliTester::new_from_dir(
75 &test_repo.dir,
76 [
77 "--nsec",
78 TEST_KEY_1_NSEC,
79 "--password",
80 TEST_PASSWORD,
81 "--disable-cli-spinners",
82 "prs",
83 "create",
84 "--title",
85 format!("\"{title}\"").as_str(),
86 "--description",
87 format!("\"{description}\"").as_str(),
88 ],
89 );
90 p.expect_end_eventually()?;
91 Ok(())
92}
93
94mod when_main_is_checked_out {
95 use super::*;
96
97 mod cli_prompts {
98 use super::*;
99 async fn run_async_cli_show_error() -> Result<()> {
100 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
101 Relay::new(8051, None, None),
102 Relay::new(8052, None, None),
103 Relay::new(8053, None, None),
104 Relay::new(8055, None, None),
105 Relay::new(8056, None, None),
106 );
107
108 r51.events.push(generate_test_key_1_relay_list_event());
109 r51.events.push(generate_test_key_1_metadata_event("fred"));
110 r51.events.push(generate_repo_ref_event());
111
112 r55.events.push(generate_repo_ref_event());
113 r55.events.push(generate_test_key_1_metadata_event("fred"));
114 r55.events.push(generate_test_key_1_relay_list_event());
115
116 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
117 cli_tester_create_prs()?;
118
119 let test_repo = GitTestRepo::default();
120 test_repo.populate()?;
121
122 create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?;
123 test_repo.checkout("main")?;
124
125 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
126 p.expect("Error: checkout a branch associated with a PR first\r\n")?;
127 p.expect_end()?;
128
129 for p in [51, 52, 53, 55, 56] {
130 relay::shutdown_relay(8000 + p)?;
131 }
132 Ok(())
133 });
134
135 // launch relay
136 let _ = join!(
137 r51.listen_until_close(),
138 r52.listen_until_close(),
139 r53.listen_until_close(),
140 r55.listen_until_close(),
141 r56.listen_until_close(),
142 );
143 cli_tester_handle.join().unwrap()?;
144 Ok(())
145 }
146
147 #[test]
148 #[serial]
149 fn cli_show_error() -> Result<()> {
150 futures::executor::block_on(run_async_cli_show_error())
151 }
152 }
153}
154
155mod when_branch_doesnt_exist {
156 use super::*;
157
158 mod cli_prompts {
159 use super::*;
160 async fn run_async_cli_show_error() -> Result<()> {
161 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
162 Relay::new(8051, None, None),
163 Relay::new(8052, None, None),
164 Relay::new(8053, None, None),
165 Relay::new(8055, None, None),
166 Relay::new(8056, None, None),
167 );
168
169 r51.events.push(generate_test_key_1_relay_list_event());
170 r51.events.push(generate_test_key_1_metadata_event("fred"));
171 r51.events.push(generate_repo_ref_event());
172
173 r55.events.push(generate_repo_ref_event());
174 r55.events.push(generate_test_key_1_metadata_event("fred"));
175 r55.events.push(generate_test_key_1_relay_list_event());
176
177 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
178 cli_tester_create_prs()?;
179
180 let test_repo = GitTestRepo::default();
181 test_repo.populate()?;
182
183 test_repo.create_branch("random-name")?;
184 test_repo.checkout("random-name")?;
185
186 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
187 p.expect("finding PR event...\r\n")?;
188 p.expect(
189 "Error: cannot find a PR event associated with the checked out branch name\r\n",
190 )?;
191
192 p.expect_end()?;
193
194 for p in [51, 52, 53, 55, 56] {
195 relay::shutdown_relay(8000 + p)?;
196 }
197 Ok(())
198 });
199
200 // launch relay
201 let _ = join!(
202 r51.listen_until_close(),
203 r52.listen_until_close(),
204 r53.listen_until_close(),
205 r55.listen_until_close(),
206 r56.listen_until_close(),
207 );
208 cli_tester_handle.join().unwrap()?;
209 Ok(())
210 }
211
212 #[test]
213 #[serial]
214 fn cli_show_error() -> Result<()> {
215 futures::executor::block_on(run_async_cli_show_error())
216 }
217 }
218}
219
220mod when_branch_is_checked_out {
221 use super::*;
222
223 mod when_branch_is_up_to_date {
224 use super::*;
225
226 mod cli_prompts {
227 use super::*;
228 async fn run_async_cli_show_up_to_date() -> Result<()> {
229 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
230 Relay::new(8051, None, None),
231 Relay::new(8052, None, None),
232 Relay::new(8053, None, None),
233 Relay::new(8055, None, None),
234 Relay::new(8056, None, None),
235 );
236
237 r51.events.push(generate_test_key_1_relay_list_event());
238 r51.events.push(generate_test_key_1_metadata_event("fred"));
239 r51.events.push(generate_repo_ref_event());
240
241 r55.events.push(generate_repo_ref_event());
242 r55.events.push(generate_test_key_1_metadata_event("fred"));
243 r55.events.push(generate_test_key_1_relay_list_event());
244
245 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
246 cli_tester_create_prs()?;
247
248 let test_repo = GitTestRepo::default();
249 test_repo.populate()?;
250
251 create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?;
252
253 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
254 p.expect("finding PR event...\r\n")?;
255 p.expect("found PR event. finding commits...\r\n")?;
256 p.expect("branch already up-to-date\r\n")?;
257 p.expect_end()?;
258
259 for p in [51, 52, 53, 55, 56] {
260 relay::shutdown_relay(8000 + p)?;
261 }
262 Ok(())
263 });
264
265 // launch relay
266 let _ = join!(
267 r51.listen_until_close(),
268 r52.listen_until_close(),
269 r53.listen_until_close(),
270 r55.listen_until_close(),
271 r56.listen_until_close(),
272 );
273 cli_tester_handle.join().unwrap()?;
274 Ok(())
275 }
276
277 #[test]
278 #[serial]
279 fn cli_show_up_to_date() -> Result<()> {
280 futures::executor::block_on(run_async_cli_show_up_to_date())
281 }
282 }
283 }
284
285 mod when_branch_is_behind {
286 use super::*;
287
288 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
289 // fallback (51,52) user write (53, 55) repo (55, 56)
290 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
291 Relay::new(8051, None, None),
292 Relay::new(8052, None, None),
293 Relay::new(8053, None, None),
294 Relay::new(8055, None, None),
295 Relay::new(8056, None, None),
296 );
297
298 r51.events.push(generate_test_key_1_relay_list_event());
299 r51.events.push(generate_test_key_1_metadata_event("fred"));
300 r51.events.push(generate_repo_ref_event());
301
302 r55.events.push(generate_repo_ref_event());
303 r55.events.push(generate_test_key_1_metadata_event("fred"));
304 r55.events.push(generate_test_key_1_relay_list_event());
305
306 let cli_tester_handle =
307 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
308 let originating_repo = cli_tester_create_prs()?;
309
310 let test_repo = GitTestRepo::default();
311 test_repo.populate()?;
312
313 create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", true)?;
314
315 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
316 p.expect_end_eventually()?;
317
318 for p in [51, 52, 53, 55, 56] {
319 relay::shutdown_relay(8000 + p)?;
320 }
321 Ok((originating_repo, test_repo))
322 });
323
324 // launch relay
325 let _ = join!(
326 r51.listen_until_close(),
327 r52.listen_until_close(),
328 r53.listen_until_close(),
329 r55.listen_until_close(),
330 r56.listen_until_close(),
331 );
332 let res = cli_tester_handle.join().unwrap()?;
333
334 Ok(res)
335 }
336
337 mod cli_prompts {
338 use super::*;
339
340 async fn run_async_cli_applied_1_commit() -> Result<()> {
341 // fallback (51,52) user write (53, 55) repo (55, 56)
342 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
343 Relay::new(8051, None, None),
344 Relay::new(8052, None, None),
345 Relay::new(8053, None, None),
346 Relay::new(8055, None, None),
347 Relay::new(8056, None, None),
348 );
349
350 r51.events.push(generate_test_key_1_relay_list_event());
351 r51.events.push(generate_test_key_1_metadata_event("fred"));
352 r51.events.push(generate_repo_ref_event());
353
354 r55.events.push(generate_repo_ref_event());
355 r55.events.push(generate_test_key_1_metadata_event("fred"));
356 r55.events.push(generate_test_key_1_relay_list_event());
357
358 let cli_tester_handle =
359 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
360 let originating_repo = cli_tester_create_prs()?;
361
362 let test_repo = GitTestRepo::default();
363 test_repo.populate()?;
364
365 create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", true)?;
366
367 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
368 p.expect("finding PR event...\r\n")?;
369 p.expect("found PR event. finding commits...\r\n")?;
370 p.expect("applied 1 new commits\r\n")?;
371 p.expect_end()?;
372
373 for p in [51, 52, 53, 55, 56] {
374 relay::shutdown_relay(8000 + p)?;
375 }
376 Ok((originating_repo, test_repo))
377 });
378
379 // launch relay
380 let _ = join!(
381 r51.listen_until_close(),
382 r52.listen_until_close(),
383 r53.listen_until_close(),
384 r55.listen_until_close(),
385 r56.listen_until_close(),
386 );
387 cli_tester_handle.join().unwrap()?;
388
389 Ok(())
390 }
391
392 #[test]
393 #[serial]
394 fn cli_applied_1_commit() -> Result<()> {
395 futures::executor::block_on(run_async_cli_applied_1_commit())
396 }
397 }
398
399 #[test]
400 #[serial]
401 fn pr_branch_tip_is_most_recent_patch() -> Result<()> {
402 let (originating_repo, test_repo) = futures::executor::block_on(prep_and_run())?;
403 assert_eq!(
404 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
405 test_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
406 );
407 Ok(())
408 }
409 }
410
411 mod when_branch_is_ahead {
412 // use super::*;
413 // TODO latest commit in pr builds off an older commit in pr
414 // instead of previous.
415 // TODO current git user created commit on branch
416 }
417
418 mod when_latest_event_rebases_branch {
419 // use super::*;
420 // TODO
421 }
422}