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>2024-11-11 09:06:19 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-11-11 09:06:19 +0000
commitf08ee98ab7e19d4e42ffa85aa619f012441fbe47 (patch)
treee4d2a15ebeb8a7549ce7e233f4690d71ac95c398
parent4331b73fbda4831f09a783732d6710012a4dcf20 (diff)
Revert "refactor: remove ngit `pull` `push` `fetch`"
This reverts commit 43b5e9b38bf5dcfbac85637a2d3efc69ddfe77ac.
-rw-r--r--src/bin/ngit/cli.rs6
-rw-r--r--src/bin/ngit/main.rs3
-rw-r--r--src/bin/ngit/sub_commands/fetch.rs37
-rw-r--r--src/bin/ngit/sub_commands/mod.rs3
-rw-r--r--src/bin/ngit/sub_commands/pull.rs203
-rw-r--r--src/bin/ngit/sub_commands/push.rs219
-rw-r--r--tests/ngit_pull.rs615
-rw-r--r--tests/ngit_push.rs531
8 files changed, 1617 insertions, 0 deletions
diff --git a/src/bin/ngit/cli.rs b/src/bin/ngit/cli.rs
index 13e18d1..d0f934e 100644
--- a/src/bin/ngit/cli.rs
+++ b/src/bin/ngit/cli.rs
@@ -27,12 +27,18 @@ pub struct Cli {
27 27
28#[derive(Subcommand)] 28#[derive(Subcommand)]
29pub enum Commands { 29pub enum Commands {
30 /// update cache with latest updates from nostr
31 Fetch(sub_commands::fetch::SubCommandArgs),
30 /// signal you are this repo's maintainer accepting proposals via nostr 32 /// signal you are this repo's maintainer accepting proposals via nostr
31 Init(sub_commands::init::SubCommandArgs), 33 Init(sub_commands::init::SubCommandArgs),
32 /// issue commits as a proposal 34 /// issue commits as a proposal
33 Send(sub_commands::send::SubCommandArgs), 35 Send(sub_commands::send::SubCommandArgs),
34 /// list proposals; checkout, apply or download selected 36 /// list proposals; checkout, apply or download selected
35 List, 37 List,
38 /// send proposal revision
39 Push(sub_commands::push::SubCommandArgs),
40 /// fetch and apply new proposal commits / revisions linked to branch
41 Pull,
36 /// run with --nsec flag to change npub 42 /// run with --nsec flag to change npub
37 Login(sub_commands::login::SubCommandArgs), 43 Login(sub_commands::login::SubCommandArgs),
38} 44}
diff --git a/src/bin/ngit/main.rs b/src/bin/ngit/main.rs
index 38dc2c1..45cbef5 100644
--- a/src/bin/ngit/main.rs
+++ b/src/bin/ngit/main.rs
@@ -15,9 +15,12 @@ mod sub_commands;
15async fn main() -> Result<()> { 15async fn main() -> Result<()> {
16 let cli = Cli::parse(); 16 let cli = Cli::parse();
17 match &cli.command { 17 match &cli.command {
18 Commands::Fetch(args) => sub_commands::fetch::launch(&cli, args).await,
18 Commands::Login(args) => sub_commands::login::launch(&cli, args).await, 19 Commands::Login(args) => sub_commands::login::launch(&cli, args).await,
19 Commands::Init(args) => sub_commands::init::launch(&cli, args).await, 20 Commands::Init(args) => sub_commands::init::launch(&cli, args).await,
20 Commands::Send(args) => sub_commands::send::launch(&cli, args, false).await, 21 Commands::Send(args) => sub_commands::send::launch(&cli, args, false).await,
21 Commands::List => sub_commands::list::launch().await, 22 Commands::List => sub_commands::list::launch().await,
23 Commands::Pull => sub_commands::pull::launch().await,
24 Commands::Push(args) => sub_commands::push::launch(&cli, args).await,
22 } 25 }
23} 26}
diff --git a/src/bin/ngit/sub_commands/fetch.rs b/src/bin/ngit/sub_commands/fetch.rs
new file mode 100644
index 0000000..c69f1c5
--- /dev/null
+++ b/src/bin/ngit/sub_commands/fetch.rs
@@ -0,0 +1,37 @@
1use std::collections::HashSet;
2
3use anyhow::{Context, Result};
4use clap;
5use nostr::nips::nip01::Coordinate;
6
7use crate::{
8 cli::Cli,
9 client::{fetching_with_report, Client, Connect},
10 git::{Repo, RepoActions},
11 repo_ref::get_repo_coordinates,
12};
13
14#[derive(clap::Args)]
15pub struct SubCommandArgs {
16 /// address pointer to repo announcement
17 #[arg(long, action)]
18 repo: Vec<String>,
19}
20
21pub async fn launch(args: &Cli, command_args: &SubCommandArgs) -> Result<()> {
22 let _ = args;
23 let git_repo = Repo::discover().context("cannot find a git repository")?;
24 let client = Client::default();
25 let repo_coordinates = if command_args.repo.is_empty() {
26 get_repo_coordinates(&git_repo, &client).await?
27 } else {
28 let mut repo_coordinates = HashSet::new();
29 for repo in &command_args.repo {
30 repo_coordinates.insert(Coordinate::parse(repo.clone())?);
31 }
32 repo_coordinates
33 };
34 fetching_with_report(git_repo.get_path()?, &client, &repo_coordinates).await?;
35 client.disconnect().await?;
36 Ok(())
37}
diff --git a/src/bin/ngit/sub_commands/mod.rs b/src/bin/ngit/sub_commands/mod.rs
index a53197f..29a60f9 100644
--- a/src/bin/ngit/sub_commands/mod.rs
+++ b/src/bin/ngit/sub_commands/mod.rs
@@ -1,4 +1,7 @@
1pub mod fetch;
1pub mod init; 2pub mod init;
2pub mod list; 3pub mod list;
3pub mod login; 4pub mod login;
5pub mod pull;
6pub mod push;
4pub mod send; 7pub mod send;
diff --git a/src/bin/ngit/sub_commands/pull.rs b/src/bin/ngit/sub_commands/pull.rs
new file mode 100644
index 0000000..d79b7b1
--- /dev/null
+++ b/src/bin/ngit/sub_commands/pull.rs
@@ -0,0 +1,203 @@
1use anyhow::{bail, Context, Result};
2use ngit::git_events::is_event_proposal_root_for_branch;
3use nostr_sdk::PublicKey;
4
5use crate::{
6 client::{
7 fetching_with_report, get_all_proposal_patch_events_from_cache,
8 get_proposals_and_revisions_from_cache, get_repo_ref_from_cache, Client, Connect,
9 },
10 git::{str_to_sha1, Repo, RepoActions},
11 git_events::{get_commit_id_from_patch, get_most_recent_patch_with_ancestors, tag_value},
12 repo_ref::get_repo_coordinates,
13};
14
15#[allow(clippy::too_many_lines)]
16pub async fn launch() -> Result<()> {
17 let git_repo = Repo::discover().context("cannot find a git repository")?;
18 let git_repo_path = git_repo.get_path()?;
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 branch_name = git_repo
25 .get_checked_out_branch_name()
26 .context("cannot get checked out branch name")?;
27
28 if branch_name == main_or_master_branch_name {
29 bail!("checkout a branch associated with a proposal first")
30 }
31 let client = Client::default();
32
33 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?;
34 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
35
36 let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?;
37
38 let logged_in_public_key =
39 if let Ok(Some(npub)) = git_repo.get_git_config_item("nostr.npub", None) {
40 PublicKey::parse(npub).ok()
41 } else {
42 None
43 };
44
45 let proposal_root_event =
46 get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates())
47 .await?
48 .iter()
49 .find(|e| {
50 is_event_proposal_root_for_branch(e, &branch_name, &logged_in_public_key)
51 .unwrap_or(false)
52 })
53 .context("cannot find proposal that matches the current branch name")?
54 .clone();
55
56 let commit_events =
57 get_all_proposal_patch_events_from_cache(git_repo_path, &repo_ref, &proposal_root_event.id)
58 .await?;
59
60 let most_recent_proposal_patch_chain =
61 get_most_recent_patch_with_ancestors(commit_events.clone())
62 .context("cannot get most recent patch for proposal")?;
63
64 let local_branch_tip = git_repo.get_tip_of_branch(&branch_name)?;
65
66 let (main_branch_name, master_tip) = git_repo.get_main_or_master_branch()?;
67
68 let (local_ahead_of_main, local_beind_main) =
69 git_repo.get_commits_ahead_behind(&master_tip, &local_branch_tip)?;
70
71 let proposal_base_commit = str_to_sha1(&tag_value(
72 most_recent_proposal_patch_chain
73 .last()
74 .context("there should be at least one patch as we have already checked for this")?,
75 "parent-commit",
76 )?)
77 .context("cannot get valid parent commit id from patch")?;
78
79 let (_, proposal_behind_main) =
80 git_repo.get_commits_ahead_behind(&master_tip, &proposal_base_commit)?;
81
82 let proposal_tip =
83 str_to_sha1(
84 &get_commit_id_from_patch(most_recent_proposal_patch_chain.first().context(
85 "there should be at least one patch as we have already checked for this",
86 )?)
87 .context("cannot get valid commit_id from patch")?,
88 )
89 .context("cannot get valid commit_id from patch")?;
90
91 // if uptodate
92 if proposal_tip.eq(&local_branch_tip) {
93 println!("branch already up-to-date");
94 }
95 // if new appendments
96 else if most_recent_proposal_patch_chain.iter().any(|patch| {
97 get_commit_id_from_patch(patch)
98 .unwrap_or_default()
99 .eq(&local_branch_tip.to_string())
100 }) {
101 check_clean(&git_repo)?;
102 let applied = git_repo
103 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain)
104 .context("cannot apply patch chain")?;
105 println!("applied {} new commits", applied.len(),);
106 }
107 // if parent commit doesnt exist
108 else if !git_repo.does_commit_exist(&proposal_base_commit.to_string())? {
109 println!(
110 "a new version of the proposal has a prant commit that doesnt exist in your local repository."
111 );
112 println!("your '{main_branch_name}' branch may not be up-to-date.");
113 println!("manually run `git pull` on '{main_branch_name}' and try again");
114 }
115 // if new revision and no local changes (tip of local in proposal history)
116 else if commit_events.iter().any(|patch| {
117 get_commit_id_from_patch(patch)
118 .unwrap_or_default()
119 .eq(&local_branch_tip.to_string())
120 }) {
121 check_clean(&git_repo)?;
122
123 git_repo.create_branch_at_commit(&branch_name, &proposal_base_commit.to_string())?;
124 let applied = git_repo
125 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain)
126 .context("cannot apply patch chain")?;
127
128 println!(
129 "pulled new version of proposal ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')",
130 applied.len(),
131 proposal_behind_main.len(),
132 local_ahead_of_main.len(),
133 local_beind_main.len(),
134 );
135 }
136 // if tip of proposal in branch in history (local appendments made to up-to-date
137 // proposal)
138 else if git_repo.ancestor_of(&local_branch_tip, &proposal_tip)? {
139 let (local_ahead_of_proposal, _) = git_repo
140 .get_commits_ahead_behind(&proposal_tip, &local_branch_tip)
141 .context("cannot get commits ahead behind for propsal_top and local_branch_tip")?;
142 println!(
143 "local proposal branch exists with {} unpublished commits on top of the most up-to-date version of the proposal",
144 local_ahead_of_proposal.len()
145 );
146 } else {
147 println!("you have an amended/rebase version the proposal that is unpublished");
148 // user probably has a unpublished amended or rebase version of the latest
149 // proposal version
150 // if tip of proposal commits exist (were once part of branch but have been
151 // amended and git clean up job hasn't removed them)
152 if git_repo.does_commit_exist(&proposal_tip.to_string())? {
153 println!(
154 "you have previously applied the latest version of the proposal ({} ahead {} behind '{main_branch_name}') but your local proposal branch has amended or rebased it ({} ahead {} behind '{main_branch_name}')",
155 most_recent_proposal_patch_chain.len(),
156 proposal_behind_main.len(),
157 local_ahead_of_main.len(),
158 local_beind_main.len(),
159 );
160 }
161 // user probably has a unpublished amended or rebase version of an older
162 // proposal version
163 else {
164 println!(
165 "your local proposal branch ({} ahead {} behind '{main_branch_name}') has conflicting changes with the latest published proposal ({} ahead {} behind '{main_branch_name}')",
166 local_ahead_of_main.len(),
167 local_beind_main.len(),
168 most_recent_proposal_patch_chain.len(),
169 proposal_behind_main.len(),
170 );
171
172 println!(
173 "its likely that you have rebased / amended an old proposal version because git has no record of the latest proposal commit."
174 );
175 println!(
176 "it is possible that you have been working off the latest version and git has delete this commit as part of a clean up"
177 );
178 }
179 println!("to view the latest proposal but retain your changes:");
180 println!(" 1) create a new branch off the tip commit of this one to store your changes");
181 println!(" 2) run `ngit list` and checkout the latest published version of this proposal");
182
183 println!("if you are confident in your changes consider running `ngit push --force`");
184
185 // TODO: this copy could be refined further based on this:
186 // - amended commits in the proposal
187 // - if local_base eq proposal base
188 // - amended an older version of proposal
189 // - if local_base is behind proposal_base
190 // - rebased the proposal
191 // - if local_base is ahead of proposal_base
192 }
193 Ok(())
194}
195
196fn check_clean(git_repo: &Repo) -> Result<()> {
197 if git_repo.has_outstanding_changes()? {
198 bail!(
199 "cannot pull proposal branch when repository is not clean. discard or stash (un)staged changes and try again."
200 );
201 }
202 Ok(())
203}
diff --git a/src/bin/ngit/sub_commands/push.rs b/src/bin/ngit/sub_commands/push.rs
new file mode 100644
index 0000000..a77f356
--- /dev/null
+++ b/src/bin/ngit/sub_commands/push.rs
@@ -0,0 +1,219 @@
1use anyhow::{bail, Context, Result};
2use ngit::{
3 client::send_events,
4 git_events::{is_event_proposal_root_for_branch, tag_value},
5};
6use nostr_sdk::PublicKey;
7
8use crate::{
9 cli::Cli,
10 client::{
11 fetching_with_report, get_all_proposal_patch_events_from_cache,
12 get_proposals_and_revisions_from_cache, get_repo_ref_from_cache, Client, Connect,
13 },
14 git::{identify_ahead_behind, str_to_sha1, Repo, RepoActions},
15 git_events::{
16 generate_patch_event, get_commit_id_from_patch, get_most_recent_patch_with_ancestors,
17 },
18 login,
19 repo_ref::get_repo_coordinates,
20 sub_commands,
21};
22
23#[derive(Debug, clap::Args)]
24pub struct SubCommandArgs {
25 #[arg(long, action)]
26 /// send proposal revision from checked out proposal branch
27 force: bool,
28}
29
30#[allow(clippy::too_many_lines)]
31pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
32 let git_repo = Repo::discover().context("cannot find a git repository")?;
33 let git_repo_path = git_repo.get_path()?;
34
35 let (main_or_master_branch_name, _) = git_repo
36 .get_main_or_master_branch()
37 .context("no main or master branch")?;
38
39 let root_commit = git_repo
40 .get_root_commit()
41 .context("failed to get root commit of the repository")?;
42
43 let branch_name = git_repo
44 .get_checked_out_branch_name()
45 .context("cannot get checked out branch name")?;
46
47 if branch_name == main_or_master_branch_name {
48 bail!("checkout a branch associated with a proposal first")
49 }
50 let mut client = Client::default();
51
52 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?;
53
54 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
55
56 let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?;
57
58 let logged_in_public_key =
59 if let Ok(Some(npub)) = git_repo.get_git_config_item("nostr.npub", None) {
60 PublicKey::parse(npub).ok()
61 } else {
62 None
63 };
64
65 let proposal_root_event =
66 get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates())
67 .await?
68 .iter()
69 .find(|e| {
70 is_event_proposal_root_for_branch(e, &branch_name, &logged_in_public_key)
71 .unwrap_or(false)
72 })
73 .context("cannot find proposal that matches the current branch name")?
74 .clone();
75
76 let commit_events =
77 get_all_proposal_patch_events_from_cache(git_repo_path, &repo_ref, &proposal_root_event.id)
78 .await?;
79
80 let most_recent_proposal_patch_chain = get_most_recent_patch_with_ancestors(commit_events)
81 .context("cannot get most recent patch for proposal")?;
82
83 let branch_tip = git_repo.get_tip_of_branch(&branch_name)?;
84
85 let most_recent_patch_commit_id = str_to_sha1(
86 &get_commit_id_from_patch(
87 most_recent_proposal_patch_chain
88 .first()
89 .context("no patches found")?,
90 )
91 .context("latest patch event doesnt have a commit tag")?,
92 )
93 .context("latest patch event commit tag isn't a valid SHA1 hash")?;
94
95 let proposal_base_commit_id = str_to_sha1(
96 &tag_value(
97 most_recent_proposal_patch_chain
98 .last()
99 .context("no patches found")?,
100 "parent-commit",
101 )
102 .context("patch is incorrectly formatted")?,
103 )
104 .context("latest patch event parent-commit tag isn't a valid SHA1 hash")?;
105
106 if most_recent_patch_commit_id.eq(&branch_tip) {
107 bail!("proposal already up-to-date with local branch");
108 }
109
110 if args.force {
111 println!("preparing to force push proposal revision...");
112 sub_commands::send::launch(
113 cli_args,
114 &sub_commands::send::SubCommandArgs {
115 // if not ahead of master prompt, otherwise assume proposal revision is all commits
116 // ahead
117 since_or_range: if let Ok((_, _, ahead, _)) =
118 identify_ahead_behind(&git_repo, &None, &None)
119 {
120 if ahead.is_empty() {
121 String::new()
122 } else {
123 format!("HEAD~{}", ahead.len())
124 }
125 } else {
126 String::new()
127 },
128 in_reply_to: vec![proposal_root_event.id.to_string()],
129 title: None,
130 description: None,
131 no_cover_letter: true,
132 },
133 true,
134 )
135 .await?;
136 println!("force pushed proposal revision");
137 return Ok(());
138 }
139
140 if most_recent_proposal_patch_chain.iter().any(|e| {
141 let c = tag_value(e, "parent-commit").unwrap_or_default();
142 c.eq(&branch_tip.to_string())
143 }) {
144 bail!("proposal is ahead of local branch");
145 }
146
147 let Ok((ahead, behind)) = git_repo
148 .get_commits_ahead_behind(&most_recent_patch_commit_id, &branch_tip)
149 .context("the latest patch in proposal doesnt share an ancestor with your branch.")
150 else {
151 if git_repo.ancestor_of(&proposal_base_commit_id, &branch_tip)? {
152 bail!("local unpublished proposal ammendments. consider force pushing.");
153 }
154 bail!("local unpublished proposal has been rebased. consider force pushing");
155 };
156
157 if !behind.is_empty() {
158 bail!(
159 "your local proposal branch is {} behind patches on nostr. consider rebasing or force pushing",
160 behind.len()
161 )
162 }
163
164 println!(
165 "{} commits ahead. preparing to create creating patch events.",
166 ahead.len()
167 );
168
169 let (signer, user_ref) = login::launch(
170 &git_repo,
171 &cli_args.bunker_uri,
172 &cli_args.bunker_app_key,
173 &cli_args.nsec,
174 &cli_args.password,
175 Some(&client),
176 false,
177 false,
178 )
179 .await?;
180
181 let mut patch_events: Vec<nostr::Event> = vec![];
182 for commit in &ahead {
183 patch_events.push(
184 generate_patch_event(
185 &git_repo,
186 &root_commit,
187 commit,
188 Some(proposal_root_event.id),
189 &signer,
190 &repo_ref,
191 patch_events.last().map(|e| e.id),
192 None,
193 None,
194 &None,
195 &[],
196 )
197 .await
198 .context("cannot make patch event from commit")?,
199 );
200 }
201 println!("pushing {} commits", ahead.len());
202
203 client.set_signer(signer).await;
204
205 send_events(
206 &client,
207 git_repo_path,
208 patch_events,
209 user_ref.relays.write(),
210 repo_ref.relays.clone(),
211 !cli_args.disable_cli_spinners,
212 false,
213 )
214 .await?;
215
216 println!("pushed {} commits", ahead.len());
217
218 Ok(())
219}
diff --git a/tests/ngit_pull.rs b/tests/ngit_pull.rs
new file mode 100644
index 0000000..6637859
--- /dev/null
+++ b/tests/ngit_pull.rs
@@ -0,0 +1,615 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6mod when_main_is_checked_out {
7 use super::*;
8
9 mod cli_prompts {
10 use super::*;
11
12 #[tokio::test]
13 #[serial]
14 async fn cli_show_error() -> Result<()> {
15 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
16 Relay::new(8051, None, None),
17 Relay::new(8052, None, None),
18 Relay::new(8053, None, None),
19 Relay::new(8055, None, None),
20 Relay::new(8056, None, None),
21 );
22
23 r51.events.push(generate_test_key_1_relay_list_event());
24 r51.events.push(generate_test_key_1_metadata_event("fred"));
25 r51.events.push(generate_repo_ref_event());
26
27 r55.events.push(generate_repo_ref_event());
28 r55.events.push(generate_test_key_1_metadata_event("fred"));
29 r55.events.push(generate_test_key_1_relay_list_event());
30
31 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
32 cli_tester_create_proposals()?;
33
34 let test_repo = create_repo_with_proposal_branch_pulled_and_checkedout(1)?;
35
36 test_repo.checkout("main")?;
37
38 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
39 p.expect("Error: checkout a branch associated with a proposal first\r\n")?;
40 p.expect_end()?;
41
42 for p in [51, 52, 53, 55, 56] {
43 relay::shutdown_relay(8000 + p)?;
44 }
45 Ok(())
46 });
47
48 // launch relay
49 let _ = join!(
50 r51.listen_until_close(),
51 r52.listen_until_close(),
52 r53.listen_until_close(),
53 r55.listen_until_close(),
54 r56.listen_until_close(),
55 );
56 cli_tester_handle.join().unwrap()?;
57 Ok(())
58 }
59 }
60}
61
62mod when_branch_doesnt_exist {
63 use super::*;
64
65 mod cli_prompts {
66 use super::*;
67
68 #[tokio::test]
69 #[serial]
70 async fn cli_show_error() -> Result<()> {
71 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
72 Relay::new(8051, None, None),
73 Relay::new(8052, None, None),
74 Relay::new(8053, None, None),
75 Relay::new(8055, None, None),
76 Relay::new(8056, None, None),
77 );
78
79 r51.events.push(generate_test_key_1_relay_list_event());
80 r51.events.push(generate_test_key_1_metadata_event("fred"));
81 r51.events.push(generate_repo_ref_event());
82
83 r55.events.push(generate_repo_ref_event());
84 r55.events.push(generate_test_key_1_metadata_event("fred"));
85 r55.events.push(generate_test_key_1_relay_list_event());
86
87 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
88 cli_tester_create_proposals()?;
89
90 let test_repo = GitTestRepo::default();
91 test_repo.populate()?;
92
93 test_repo.create_branch("random-name")?;
94 test_repo.checkout("random-name")?;
95
96 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
97 p.expect("fetching updates...\r\n")?;
98 p.expect_eventually("\r\n")?; // some updates listed here
99 p.expect("Error: cannot find proposal that matches the current branch name\r\n")?;
100
101 p.expect_end()?;
102
103 for p in [51, 52, 53, 55, 56] {
104 relay::shutdown_relay(8000 + p)?;
105 }
106 Ok(())
107 });
108
109 // launch relay
110 let _ = join!(
111 r51.listen_until_close(),
112 r52.listen_until_close(),
113 r53.listen_until_close(),
114 r55.listen_until_close(),
115 r56.listen_until_close(),
116 );
117 cli_tester_handle.join().unwrap()?;
118 Ok(())
119 }
120 }
121}
122
123mod when_branch_is_checked_out {
124 use super::*;
125
126 mod when_branch_is_up_to_date {
127 use super::*;
128
129 mod cli_prompts {
130 use super::*;
131 #[tokio::test]
132 #[serial]
133 async fn cli_show_up_to_date() -> Result<()> {
134 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
135 Relay::new(8051, None, None),
136 Relay::new(8052, None, None),
137 Relay::new(8053, None, None),
138 Relay::new(8055, None, None),
139 Relay::new(8056, None, None),
140 );
141
142 r51.events.push(generate_test_key_1_relay_list_event());
143 r51.events.push(generate_test_key_1_metadata_event("fred"));
144 r51.events.push(generate_repo_ref_event());
145
146 r55.events.push(generate_repo_ref_event());
147 r55.events.push(generate_test_key_1_metadata_event("fred"));
148 r55.events.push(generate_test_key_1_relay_list_event());
149
150 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
151 let (_, test_repo) =
152 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
153
154 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
155 p.expect("fetching updates...\r\n")?;
156 p.expect_eventually("\r\n")?; // some updates listed here
157 p.expect("branch already up-to-date\r\n")?;
158 p.expect_end()?;
159
160 for p in [51, 52, 53, 55, 56] {
161 relay::shutdown_relay(8000 + p)?;
162 }
163 Ok(())
164 });
165
166 // launch relay
167 let _ = join!(
168 r51.listen_until_close(),
169 r52.listen_until_close(),
170 r53.listen_until_close(),
171 r55.listen_until_close(),
172 r56.listen_until_close(),
173 );
174 cli_tester_handle.join().unwrap()?;
175 Ok(())
176 }
177 }
178 }
179
180 mod when_branch_is_behind {
181 use super::*;
182
183 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
184 // fallback (51,52) user write (53, 55) repo (55, 56)
185 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
186 Relay::new(8051, None, None),
187 Relay::new(8052, None, None),
188 Relay::new(8053, None, None),
189 Relay::new(8055, None, None),
190 Relay::new(8056, None, None),
191 );
192
193 r51.events.push(generate_test_key_1_relay_list_event());
194 r51.events.push(generate_test_key_1_metadata_event("fred"));
195 r51.events.push(generate_repo_ref_event());
196
197 r55.events.push(generate_repo_ref_event());
198 r55.events.push(generate_test_key_1_metadata_event("fred"));
199 r55.events.push(generate_test_key_1_relay_list_event());
200
201 let cli_tester_handle =
202 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
203 let (originating_repo, test_repo) =
204 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
205
206 let branch_name =
207 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
208 &test_repo,
209 )?;
210 test_repo.checkout(&branch_name)?;
211
212 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
213 p.expect_end_eventually()?;
214
215 for p in [51, 52, 53, 55, 56] {
216 relay::shutdown_relay(8000 + p)?;
217 }
218 Ok((originating_repo, test_repo))
219 });
220
221 // launch relay
222 let _ = join!(
223 r51.listen_until_close(),
224 r52.listen_until_close(),
225 r53.listen_until_close(),
226 r55.listen_until_close(),
227 r56.listen_until_close(),
228 );
229 let res = cli_tester_handle.join().unwrap()?;
230
231 Ok(res)
232 }
233
234 mod cli_prompts {
235 use super::*;
236
237 #[tokio::test]
238 #[serial]
239 async fn cli_applied_1_commit() -> Result<()> {
240 // fallback (51,52) user write (53, 55) repo (55, 56)
241 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
242 Relay::new(8051, None, None),
243 Relay::new(8052, None, None),
244 Relay::new(8053, None, None),
245 Relay::new(8055, None, None),
246 Relay::new(8056, None, None),
247 );
248
249 r51.events.push(generate_test_key_1_relay_list_event());
250 r51.events.push(generate_test_key_1_metadata_event("fred"));
251 r51.events.push(generate_repo_ref_event());
252
253 r55.events.push(generate_repo_ref_event());
254 r55.events.push(generate_test_key_1_metadata_event("fred"));
255 r55.events.push(generate_test_key_1_relay_list_event());
256
257 let cli_tester_handle =
258 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
259 let (originating_repo, test_repo) =
260 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
261
262 let branch_name =
263 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
264 &test_repo,
265 )?;
266 test_repo.checkout(&branch_name)?;
267
268 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
269 p.expect("fetching updates...\r\n")?;
270 p.expect_eventually("\r\n")?; // some updates listed here
271 p.expect_end_with("applied 1 new commits\r\n")?;
272
273 for p in [51, 52, 53, 55, 56] {
274 relay::shutdown_relay(8000 + p)?;
275 }
276 Ok((originating_repo, test_repo))
277 });
278
279 // launch relay
280 let _ = join!(
281 r51.listen_until_close(),
282 r52.listen_until_close(),
283 r53.listen_until_close(),
284 r55.listen_until_close(),
285 r56.listen_until_close(),
286 );
287 cli_tester_handle.join().unwrap()?;
288
289 Ok(())
290 }
291 }
292
293 #[tokio::test]
294 #[serial]
295 async fn proposal_branch_tip_is_most_recent_patch() -> Result<()> {
296 let (originating_repo, test_repo) = prep_and_run().await?;
297 assert_eq!(
298 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
299 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
300 &test_repo,
301 FEATURE_BRANCH_NAME_1
302 )?)?,
303 );
304 Ok(())
305 }
306 }
307
308 mod when_latest_proposal_amended_locally {
309 use super::*;
310
311 mod cli_prompts {
312 use super::*;
313
314 #[tokio::test]
315 #[serial]
316 async fn cli_output_correct() -> Result<()> {
317 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
318 Relay::new(8051, None, None),
319 Relay::new(8052, None, None),
320 Relay::new(8053, None, None),
321 Relay::new(8055, None, None),
322 Relay::new(8056, None, None),
323 );
324
325 r51.events.push(generate_test_key_1_relay_list_event());
326 r51.events.push(generate_test_key_1_metadata_event("fred"));
327 r51.events.push(generate_repo_ref_event());
328
329 r55.events.push(generate_repo_ref_event());
330 r55.events.push(generate_test_key_1_metadata_event("fred"));
331 r55.events.push(generate_test_key_1_relay_list_event());
332
333 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
334 let (_, test_repo) =
335 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
336
337 amend_last_commit(&test_repo, "add ammended-commit.md")?;
338
339 // run test
340 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
341 p.expect("fetching updates...\r\n")?;
342 p.expect_eventually("\r\n")?; // some updates listed here
343 p.expect(
344 "you have an amended/rebase version the proposal that is unpublished\r\n",
345 )?;
346 p.expect("you have previously applied the latest version of the proposal (2 ahead 0 behind 'main') but your local proposal branch has amended or rebased it (2 ahead 0 behind 'main')\r\n")?;
347 p.expect("to view the latest proposal but retain your changes:\r\n")?;
348 p.expect(" 1) create a new branch off the tip commit of this one to store your changes\r\n")?;
349 p.expect(" 2) run `ngit list` and checkout the latest published version of this proposal\r\n")?;
350 p.expect("if you are confident in your changes consider running `ngit push --force`\r\n")?;
351 p.expect_end()?;
352
353 for p in [51, 52, 53, 55, 56] {
354 relay::shutdown_relay(8000 + p)?;
355 }
356 Ok(())
357 });
358
359 // launch relay
360 let _ = join!(
361 r51.listen_until_close(),
362 r52.listen_until_close(),
363 r53.listen_until_close(),
364 r55.listen_until_close(),
365 r56.listen_until_close(),
366 );
367 cli_tester_handle.join().unwrap()?;
368 println!("{:?}", r55.events);
369 Ok(())
370 }
371 }
372 }
373
374 mod when_local_commits_on_uptodate_proposal {
375 use super::*;
376 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
377 // fallback (51,52) user write (53, 55) repo (55, 56)
378 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
379 Relay::new(8051, None, None),
380 Relay::new(8052, None, None),
381 Relay::new(8053, None, None),
382 Relay::new(8055, None, None),
383 Relay::new(8056, None, None),
384 );
385
386 r51.events.push(generate_test_key_1_relay_list_event());
387 r51.events.push(generate_test_key_1_metadata_event("fred"));
388 r51.events.push(generate_repo_ref_event());
389
390 r55.events.push(generate_repo_ref_event());
391 r55.events.push(generate_test_key_1_metadata_event("fred"));
392 r55.events.push(generate_test_key_1_relay_list_event());
393
394 let cli_tester_handle = std::thread::spawn(
395 move || -> Result<(GitTestRepo, GitTestRepo)> {
396 let (originating_repo, test_repo) =
397 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
398
399 // add another commit (so we have a local branch 1 ahead)
400 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
401 test_repo.stage_and_commit("add ammended-commit.md")?;
402
403 // run test
404 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
405 p.expect("fetching updates...\r\n")?;
406 p.expect_eventually("\r\n")?; // some updates listed here
407 p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?;
408 p.expect_end()?;
409
410 for p in [51, 52, 53, 55, 56] {
411 relay::shutdown_relay(8000 + p)?;
412 }
413 Ok((originating_repo, test_repo))
414 },
415 );
416
417 // launch relay
418 let _ = join!(
419 r51.listen_until_close(),
420 r52.listen_until_close(),
421 r53.listen_until_close(),
422 r55.listen_until_close(),
423 r56.listen_until_close(),
424 );
425 let res = cli_tester_handle.join().unwrap()?;
426
427 Ok(res)
428 }
429
430 mod cli_prompts {
431 use super::*;
432
433 #[tokio::test]
434 #[serial]
435 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
436 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
437 Relay::new(8051, None, None),
438 Relay::new(8052, None, None),
439 Relay::new(8053, None, None),
440 Relay::new(8055, None, None),
441 Relay::new(8056, None, None),
442 );
443
444 r51.events.push(generate_test_key_1_relay_list_event());
445 r51.events.push(generate_test_key_1_metadata_event("fred"));
446 r51.events.push(generate_repo_ref_event());
447
448 r55.events.push(generate_repo_ref_event());
449 r55.events.push(generate_test_key_1_metadata_event("fred"));
450 r55.events.push(generate_test_key_1_relay_list_event());
451
452 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
453 let (_, test_repo) =
454 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
455
456 // add another commit (so we have a local branch 1 ahead)
457 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
458 test_repo.stage_and_commit("add ammended-commit.md")?;
459
460 // run test
461 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
462 p.expect("fetching updates...\r\n")?;
463 p.expect_eventually("\r\n")?; // some updates listed here
464 p.expect("local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal\r\n")?;
465 p.expect_end()?;
466
467 for p in [51, 52, 53, 55, 56] {
468 relay::shutdown_relay(8000 + p)?;
469 }
470 Ok(())
471 });
472
473 // launch relay
474 let _ = join!(
475 r51.listen_until_close(),
476 r52.listen_until_close(),
477 r53.listen_until_close(),
478 r55.listen_until_close(),
479 r56.listen_until_close(),
480 );
481 cli_tester_handle.join().unwrap()?;
482 println!("{:?}", r55.events);
483 Ok(())
484 }
485 }
486
487 #[tokio::test]
488 #[serial]
489 async fn didnt_overwrite_local_appendments() -> Result<()> {
490 let (originating_repo, test_repo) = prep_and_run().await?;
491 assert_ne!(
492 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
493 &test_repo,
494 FEATURE_BRANCH_NAME_1
495 )?)?,
496 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
497 );
498 Ok(())
499 }
500 }
501 mod when_latest_event_rebases_branch {
502 use tokio::task::JoinHandle;
503
504 use super::*;
505
506 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
507 // fallback (51,52) user write (53, 55) repo (55, 56)
508 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
509 Relay::new(8051, None, None),
510 Relay::new(8052, None, None),
511 Relay::new(8053, None, None),
512 Relay::new(8055, None, None),
513 Relay::new(8056, None, None),
514 );
515
516 r51.events.push(generate_test_key_1_relay_list_event());
517 r51.events.push(generate_test_key_1_metadata_event("fred"));
518 r51.events.push(generate_repo_ref_event());
519
520 r55.events.push(generate_repo_ref_event());
521 r55.events.push(generate_test_key_1_metadata_event("fred"));
522 r55.events.push(generate_test_key_1_relay_list_event());
523
524 let cli_tester_handle: JoinHandle<Result<(GitTestRepo, GitTestRepo)>> =
525 tokio::task::spawn_blocking(move || {
526 let (originating_repo, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
527
528 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
529 p.expect_end_eventually_and_print()?;
530
531 for p in [51, 52, 53, 55, 56] {
532 relay::shutdown_relay(8000 + p)?;
533 }
534 Ok((originating_repo, test_repo))
535 });
536
537 // launch relay
538 let _ = join!(
539 r51.listen_until_close(),
540 r52.listen_until_close(),
541 r53.listen_until_close(),
542 r55.listen_until_close(),
543 r56.listen_until_close(),
544 );
545 let res = cli_tester_handle.await??;
546
547 Ok(res)
548 }
549
550 mod cli_prompts {
551 use super::*;
552
553 #[tokio::test]
554 #[serial]
555 async fn prompts_to_choose_from_proposal_titles() -> Result<()> {
556 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
557 Relay::new(8051, None, None),
558 Relay::new(8052, None, None),
559 Relay::new(8053, None, None),
560 Relay::new(8055, None, None),
561 Relay::new(8056, None, None),
562 );
563
564 r51.events.push(generate_test_key_1_relay_list_event());
565 r51.events.push(generate_test_key_1_metadata_event("fred"));
566 r51.events.push(generate_repo_ref_event());
567
568 r55.events.push(generate_repo_ref_event());
569 r55.events.push(generate_test_key_1_metadata_event("fred"));
570 r55.events.push(generate_test_key_1_relay_list_event());
571
572 let cli_tester_handle: JoinHandle<Result<()>> = tokio::task::spawn_blocking(
573 move || {
574 let (_, test_repo) = create_proposals_with_first_rebased_and_repo_with_latest_main_and_unrebased_proposal()?;
575
576 let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]);
577 p.expect("fetching updates...\r\n")?;
578 p.expect_eventually("\r\n")?; // some updates listed here
579 p.expect_end_with("pulled new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?;
580 for p in [51, 52, 53, 55, 56] {
581 relay::shutdown_relay(8000 + p)?;
582 }
583 Ok(())
584 },
585 );
586
587 // launch relay
588 let _ = join!(
589 r51.listen_until_close(),
590 r52.listen_until_close(),
591 r53.listen_until_close(),
592 r55.listen_until_close(),
593 r56.listen_until_close(),
594 );
595 cli_tester_handle.await??;
596 println!("{:?}", r55.events);
597 Ok(())
598 }
599 }
600
601 #[tokio::test]
602 #[serial]
603 async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> {
604 let (originating_repo, test_repo) = prep_and_run().await?;
605 assert_eq!(
606 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?,
607 test_repo.get_tip_of_local_branch(&get_proposal_branch_name(
608 &test_repo,
609 FEATURE_BRANCH_NAME_1
610 )?)?,
611 );
612 Ok(())
613 }
614 }
615}
diff --git a/tests/ngit_push.rs b/tests/ngit_push.rs
new file mode 100644
index 0000000..3d89f6b
--- /dev/null
+++ b/tests/ngit_push.rs
@@ -0,0 +1,531 @@
1use anyhow::Result;
2use futures::join;
3use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *};
5
6mod when_main_is_checked_out {
7 use super::*;
8
9 #[test]
10 fn cli_returns_error() -> Result<()> {
11 let test_repo = GitTestRepo::default();
12 test_repo.populate()?;
13 create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?;
14 test_repo.checkout("main")?;
15 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
16 p.expect("Error: checkout a branch associated with a proposal first\r\n")?;
17 p.expect_end()?;
18 Ok(())
19 }
20}
21
22mod when_proposal_isnt_associated_with_branch_name {
23 use super::*;
24
25 mod cli_prompts {
26
27 use super::*;
28
29 #[tokio::test]
30 #[serial]
31 async fn cli_show_error() -> Result<()> {
32 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
33 Relay::new(8051, None, None),
34 Relay::new(8052, None, None),
35 Relay::new(8053, None, None),
36 Relay::new(8055, None, None),
37 Relay::new(8056, None, None),
38 );
39
40 r51.events.push(generate_test_key_1_relay_list_event());
41 r51.events.push(generate_test_key_1_metadata_event("fred"));
42 r51.events.push(generate_repo_ref_event());
43
44 r55.events.push(generate_repo_ref_event());
45 r55.events.push(generate_test_key_1_metadata_event("fred"));
46 r55.events.push(generate_test_key_1_relay_list_event());
47
48 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
49 cli_tester_create_proposals()?;
50
51 let test_repo = GitTestRepo::default();
52 test_repo.populate()?;
53
54 test_repo.create_branch("random-name")?;
55 test_repo.checkout("random-name")?;
56
57 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
58 p.expect("fetching updates...\r\n")?;
59 p.expect_eventually("\r\n")?; // some updates listed here
60 p.expect_end_with(
61 "Error: cannot find proposal that matches the current branch name\r\n",
62 )?;
63 for p in [51, 52, 53, 55, 56] {
64 relay::shutdown_relay(8000 + p)?;
65 }
66 Ok(())
67 });
68
69 // launch relay
70 let _ = join!(
71 r51.listen_until_close(),
72 r52.listen_until_close(),
73 r53.listen_until_close(),
74 r55.listen_until_close(),
75 r56.listen_until_close(),
76 );
77 cli_tester_handle.join().unwrap()?;
78 Ok(())
79 }
80 }
81}
82
83mod when_branch_is_checked_out {
84 use super::*;
85
86 mod when_branch_is_up_to_date {
87 use super::*;
88
89 mod cli_prompts {
90 use super::*;
91 #[tokio::test]
92 #[serial]
93 async fn cli_show_up_to_date() -> Result<()> {
94 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
95 Relay::new(8051, None, None),
96 Relay::new(8052, None, None),
97 Relay::new(8053, None, None),
98 Relay::new(8055, None, None),
99 Relay::new(8056, None, None),
100 );
101
102 r51.events.push(generate_test_key_1_relay_list_event());
103 r51.events.push(generate_test_key_1_metadata_event("fred"));
104 r51.events.push(generate_repo_ref_event());
105
106 r55.events.push(generate_repo_ref_event());
107 r55.events.push(generate_test_key_1_metadata_event("fred"));
108 r55.events.push(generate_test_key_1_relay_list_event());
109
110 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
111 let (_, test_repo) =
112 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
113
114 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
115 p.expect("fetching updates...\r\n")?;
116 p.expect_eventually("\r\n")?; // some updates listed here
117 p.expect_end_with("Error: proposal already up-to-date with local branch\r\n")?;
118
119 for p in [51, 52, 53, 55, 56] {
120 relay::shutdown_relay(8000 + p)?;
121 }
122 Ok(())
123 });
124
125 // launch relay
126 let _ = join!(
127 r51.listen_until_close(),
128 r52.listen_until_close(),
129 r53.listen_until_close(),
130 r55.listen_until_close(),
131 r56.listen_until_close(),
132 );
133 cli_tester_handle.join().unwrap()?;
134 Ok(())
135 }
136 }
137 }
138
139 mod when_branch_is_behind {
140 use super::*;
141
142 mod cli_prompts {
143 use super::*;
144
145 #[tokio::test]
146 #[serial]
147 async fn cli_show_proposal_ahead_error() -> Result<()> {
148 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
149 Relay::new(8051, None, None),
150 Relay::new(8052, None, None),
151 Relay::new(8053, None, None),
152 Relay::new(8055, None, None),
153 Relay::new(8056, None, None),
154 );
155
156 r51.events.push(generate_test_key_1_relay_list_event());
157 r51.events.push(generate_test_key_1_metadata_event("fred"));
158 r51.events.push(generate_repo_ref_event());
159
160 r55.events.push(generate_repo_ref_event());
161 r55.events.push(generate_test_key_1_metadata_event("fred"));
162 r55.events.push(generate_test_key_1_relay_list_event());
163
164 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
165 let (_, test_repo) =
166 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
167
168 let branch_name =
169 remove_latest_commit_so_proposal_branch_is_behind_and_checkout_main(
170 &test_repo,
171 )?;
172
173 test_repo.checkout(&branch_name)?;
174 // run test
175 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
176 p.expect("fetching updates...\r\n")?;
177 p.expect_eventually("\r\n")?; // some updates listed here
178 p.expect("Error: proposal is ahead of local branch\r\n")?;
179 p.expect_end()?;
180
181 for p in [51, 52, 53, 55, 56] {
182 relay::shutdown_relay(8000 + p)?;
183 }
184 Ok(())
185 });
186
187 // launch relay
188 let _ = join!(
189 r51.listen_until_close(),
190 r52.listen_until_close(),
191 r53.listen_until_close(),
192 r55.listen_until_close(),
193 r56.listen_until_close(),
194 );
195 cli_tester_handle.join().unwrap()?;
196 Ok(())
197 }
198 }
199 }
200
201 mod when_branch_is_ahead {
202 use super::*;
203
204 mod cli_prompts {
205 use test_utils::relay::expect_send_with_progress;
206
207 use super::*;
208
209 #[tokio::test]
210 #[serial]
211 async fn cli_applied_1_commit() -> Result<()> {
212 // fallback (51,52) user write (53, 55) repo (55, 56)
213 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
214 Relay::new(8051, None, None),
215 Relay::new(8052, None, None),
216 Relay::new(8053, None, None),
217 Relay::new(8055, None, None),
218 Relay::new(8056, None, None),
219 );
220
221 r51.events.push(generate_test_key_1_relay_list_event());
222 r51.events.push(generate_test_key_1_metadata_event("fred"));
223 r51.events.push(generate_repo_ref_event());
224
225 r55.events.push(generate_repo_ref_event());
226 r55.events.push(generate_test_key_1_metadata_event("fred"));
227 r55.events.push(generate_test_key_1_relay_list_event());
228
229 let cli_tester_handle =
230 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
231 let (originating_repo, test_repo) =
232 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
233
234 // add another commit (so we have an ammened local branch)
235 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
236 test_repo.stage_and_commit("add ammended-commit.md")?;
237
238 // run test
239 let mut p = CliTester::new_from_dir(
240 &test_repo.dir,
241 [
242 "--nsec",
243 TEST_KEY_1_NSEC,
244 "--password",
245 TEST_PASSWORD,
246 "--disable-cli-spinners",
247 "push",
248 ],
249 );
250 p.expect("fetching updates...\r\n")?;
251 p.expect_eventually("\r\n")?; // some updates listed here
252 p.expect(
253 "1 commits ahead. preparing to create creating patch events.\r\n",
254 )?;
255 p.expect("logged in as fred\r\n")?;
256 p.expect("pushing 1 commits\r\n")?;
257
258 expect_send_with_progress(
259 &mut p,
260 vec![
261 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
262 (" [my-relay] ws://localhost:8053", true, ""),
263 (" [repo-relay] ws://localhost:8056", true, ""),
264 (" [default] ws://localhost:8051", true, ""),
265 (" [default] ws://localhost:8052", true, ""),
266 ],
267 1,
268 )?;
269 p.expect_eventually("pushed 1 commits\r\n")?;
270 p.expect_end()?;
271
272 for p in [51, 52, 53, 55, 56] {
273 relay::shutdown_relay(8000 + p)?;
274 }
275 Ok((originating_repo, test_repo))
276 });
277
278 // launch relay
279 let _ = join!(
280 r51.listen_until_close(),
281 r52.listen_until_close(),
282 r53.listen_until_close(),
283 r55.listen_until_close(),
284 r56.listen_until_close(),
285 );
286 cli_tester_handle.join().unwrap()?;
287
288 Ok(())
289 }
290 }
291
292 async fn prep_and_run() -> Result<(GitTestRepo, Vec<nostr::Event>)> {
293 // fallback (51,52) user write (53, 55) repo (55, 56)
294 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
295 Relay::new(8051, None, None),
296 Relay::new(8052, None, None),
297 Relay::new(8053, None, None),
298 Relay::new(8055, None, None),
299 Relay::new(8056, None, None),
300 );
301
302 r51.events.push(generate_test_key_1_relay_list_event());
303 r51.events.push(generate_test_key_1_metadata_event("fred"));
304 r51.events.push(generate_repo_ref_event());
305
306 r55.events.push(generate_repo_ref_event());
307 r55.events.push(generate_test_key_1_metadata_event("fred"));
308 r55.events.push(generate_test_key_1_relay_list_event());
309
310 let cli_tester_handle = std::thread::spawn(move || -> Result<GitTestRepo> {
311 let (_, test_repo) =
312 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
313
314 // add another commit (so we have an ammened local branch)
315 std::fs::write(test_repo.dir.join("ammended-commit.md"), "some content")?;
316 test_repo.stage_and_commit("add ammended-commit.md")?;
317
318 // run test
319
320 let mut p = CliTester::new_from_dir(
321 &test_repo.dir,
322 [
323 "--nsec",
324 TEST_KEY_1_NSEC,
325 "--password",
326 TEST_PASSWORD,
327 "--disable-cli-spinners",
328 "push",
329 ],
330 );
331 p.expect_end_eventually()?;
332
333 for p in [51, 52, 53, 55, 56] {
334 relay::shutdown_relay(8000 + p)?;
335 }
336 Ok(test_repo)
337 });
338
339 // launch relay
340 let _ = join!(
341 r51.listen_until_close(),
342 r52.listen_until_close(),
343 r53.listen_until_close(),
344 r55.listen_until_close(),
345 r56.listen_until_close(),
346 );
347 let res = cli_tester_handle.join().unwrap()?;
348
349 Ok((res, r55.events.clone()))
350 }
351 #[tokio::test]
352 #[serial]
353 async fn commits_issued_as_patch_event() -> Result<()> {
354 let (test_repo, r55_events) = prep_and_run().await?;
355
356 let commit_id = test_repo
357 .get_tip_of_local_branch(&test_repo.get_checked_out_branch_name()?)?
358 .to_string();
359 assert!(r55_events.iter().any(|e| {
360 e.tags
361 .iter()
362 .any(|t| t.as_slice()[0].eq("commit") && t.as_slice()[1].eq(&commit_id))
363 }));
364 Ok(())
365 }
366 }
367
368 mod when_branch_has_been_rebased {
369 use super::*;
370
371 mod cli_prompts {
372 use super::*;
373
374 #[tokio::test]
375 #[serial]
376 async fn cli_shows_unpublished_rebase_error() -> Result<()> {
377 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
378 Relay::new(8051, None, None),
379 Relay::new(8052, None, None),
380 Relay::new(8053, None, None),
381 Relay::new(8055, None, None),
382 Relay::new(8056, None, None),
383 );
384
385 r51.events.push(generate_test_key_1_relay_list_event());
386 r51.events.push(generate_test_key_1_metadata_event("fred"));
387 r51.events.push(generate_repo_ref_event());
388
389 r55.events.push(generate_repo_ref_event());
390 r55.events.push(generate_test_key_1_metadata_event("fred"));
391 r55.events.push(generate_test_key_1_relay_list_event());
392
393 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
394 let (_, tmp_repo) =
395 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
396 let branch_name = tmp_repo.get_checked_out_branch_name()?;
397
398 let test_repo = GitTestRepo::default();
399 test_repo.populate()?;
400
401 // simulate rebase
402 std::fs::write(test_repo.dir.join("amazing.md"), "some content")?;
403 test_repo.stage_and_commit("commit for rebasing on top of")?;
404 create_and_populate_branch(&test_repo, &branch_name, "a", true)?;
405
406 let mut p = CliTester::new_from_dir(&test_repo.dir, ["push"]);
407 // p.expect_end_eventually_and_print()?;
408
409 p.expect("fetching updates...\r\n")?;
410 p.expect_eventually("\r\n")?; // some updates listed here
411 p.expect(
412 "Error: local unpublished proposal has been rebased. consider force pushing\r\n",
413 )?;
414 p.expect_end()?;
415
416 for p in [51, 52, 53, 55, 56] {
417 relay::shutdown_relay(8000 + p)?;
418 }
419 Ok(())
420 });
421
422 // launch relay
423 let _ = join!(
424 r51.listen_until_close(),
425 r52.listen_until_close(),
426 r53.listen_until_close(),
427 r55.listen_until_close(),
428 r56.listen_until_close(),
429 );
430 cli_tester_handle.join().unwrap()?;
431 Ok(())
432 }
433 }
434 mod with_force_flag {
435 use super::*;
436
437 mod cli_prompts {
438 use super::*;
439
440 #[tokio::test]
441 #[serial]
442 async fn cli_shows_revision_sent() -> Result<()> {
443 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
444 Relay::new(8051, None, None),
445 Relay::new(8052, None, None),
446 Relay::new(8053, None, None),
447 Relay::new(8055, None, None),
448 Relay::new(8056, None, None),
449 );
450
451 r51.events.push(generate_test_key_1_relay_list_event());
452 r51.events.push(generate_test_key_1_metadata_event("fred"));
453 r51.events.push(generate_repo_ref_event());
454
455 r55.events.push(generate_repo_ref_event());
456 r55.events.push(generate_test_key_1_metadata_event("fred"));
457 r55.events.push(generate_test_key_1_relay_list_event());
458
459 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
460 let (_, tmp_repo) =
461 create_proposals_and_repo_with_proposal_pulled_and_checkedout(1)?;
462 let branch_name = tmp_repo.get_checked_out_branch_name()?;
463
464 let test_repo = GitTestRepo::default();
465 test_repo.populate()?;
466
467 // simulate rebase
468 std::fs::write(test_repo.dir.join("amazing.md"), "some content")?;
469 test_repo.stage_and_commit("commit for rebasing on top of")?;
470 create_and_populate_branch(&test_repo, &branch_name, "a", false)?;
471 let mut p = CliTester::new_from_dir(
472 &test_repo.dir,
473 [
474 "--nsec",
475 TEST_KEY_1_NSEC,
476 "--password",
477 TEST_PASSWORD,
478 "--disable-cli-spinners",
479 "push",
480 "--force",
481 ],
482 );
483 p.expect("fetching updates...\r\n")?;
484 p.expect_eventually("\r\n")?; // some updates listed here
485 p.expect("preparing to force push proposal revision...\r\n")?;
486 // standard output from `ngit send`
487 p.expect("creating proposal revision for: ")?;
488 // proposal id will be printed in this gap
489 p.expect_eventually("\r\n")?;
490 p.expect("creating proposal from 2 commits:\r\n")?;
491 p.expect("355bdf1 add a4.md\r\n")?;
492 p.expect("dbd1115 add a3.md\r\n")?;
493 p.expect("logged in as fred\r\n")?;
494 p.expect("posting 2 patches without a covering letter...\r\n")?;
495
496 relay::expect_send_with_progress(
497 &mut p,
498 vec![
499 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
500 (" [my-relay] ws://localhost:8053", true, ""),
501 (" [repo-relay] ws://localhost:8056", true, ""),
502 (" [default] ws://localhost:8051", true, ""),
503 (" [default] ws://localhost:8052", true, ""),
504 ],
505 2,
506 )?;
507 // end standard `ngit send output`
508 p.expect_after_whitespace("force pushed proposal revision\r\n")?;
509 p.expect_end()?;
510
511 for p in [51, 52, 53, 55, 56] {
512 relay::shutdown_relay(8000 + p)?;
513 }
514 Ok(())
515 });
516
517 // launch relay
518 let _ = join!(
519 r51.listen_until_close(),
520 r52.listen_until_close(),
521 r53.listen_until_close(),
522 r55.listen_until_close(),
523 r56.listen_until_close(),
524 );
525 cli_tester_handle.join().unwrap()?;
526 Ok(())
527 }
528 }
529 }
530 }
531}