upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/sub_commands/list.rs262
-rw-r--r--tests/list.rs115
2 files changed, 159 insertions, 218 deletions
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs
index d3f583f..2ae4cfb 100644
--- a/src/sub_commands/list.rs
+++ b/src/sub_commands/list.rs
@@ -1,17 +1,19 @@
1use std::{io::Write, ops::Add}; 1use std::{collections::HashSet, io::Write, ops::Add, path::Path};
2 2
3use anyhow::{bail, Context, Result}; 3use anyhow::{bail, Context, Result};
4use nostr::nips::nip01::Coordinate;
5use nostr_sdk::PublicKey;
4 6
5use super::send::event_is_patch_set_root; 7use super::send::event_is_patch_set_root;
6#[cfg(not(test))]
7use crate::client::Client;
8#[cfg(test)] 8#[cfg(test)]
9use crate::client::MockConnect; 9use crate::client::MockConnect;
10#[cfg(not(test))]
11use crate::client::{Client, Connect};
10use crate::{ 12use crate::{
11 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, 13 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms},
12 client::Connect, 14 client::{fetching_with_report, get_events_from_cache, get_repo_ref_from_cache},
13 git::{str_to_sha1, Repo, RepoActions}, 15 git::{str_to_sha1, Repo, RepoActions},
14 repo_ref::{self, RepoRef, REPO_REF_KIND}, 16 repo_ref::{get_repo_coordinates, RepoRef},
15 sub_commands::send::{ 17 sub_commands::send::{
16 commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, 18 commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root,
17 event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND, 19 event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND,
@@ -21,10 +23,7 @@ use crate::{
21#[allow(clippy::too_many_lines)] 23#[allow(clippy::too_many_lines)]
22pub async fn launch() -> Result<()> { 24pub async fn launch() -> Result<()> {
23 let git_repo = Repo::discover().context("cannot find a git repository")?; 25 let git_repo = Repo::discover().context("cannot find a git repository")?;
24 26 let git_repo_path = git_repo.get_path()?;
25 let root_commit = git_repo
26 .get_root_commit()
27 .context("failed to get root commit of the repository")?;
28 27
29 // TODO: check for empty repo 28 // TODO: check for empty repo
30 // TODO: check for existing maintaiers file 29 // TODO: check for existing maintaiers file
@@ -35,36 +34,47 @@ pub async fn launch() -> Result<()> {
35 #[cfg(test)] 34 #[cfg(test)]
36 let client = <MockConnect as std::default::Default>::default(); 35 let client = <MockConnect as std::default::Default>::default();
37 36
38 let repo_ref = repo_ref::fetch( 37 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?;
39 &git_repo,
40 root_commit.to_string(),
41 &client,
42 client.get_fallback_relays().clone(),
43 true,
44 )
45 .await?;
46 38
47 println!("finding proposals..."); 39 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
48 40
49 let proposal_events_and_revisions_and_statuses: Vec<nostr::Event> = 41 let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?;
50 find_proposal_and_status_events(&client, &repo_ref, &root_commit.to_string()).await?;
51 42
52 if proposal_events_and_revisions_and_statuses.is_empty() { 43 let proposals_and_revisions: Vec<nostr::Event> =
44 get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates()).await?;
45 if proposals_and_revisions.is_empty() {
53 println!("no proposals found... create one? try `ngit send`"); 46 println!("no proposals found... create one? try `ngit send`");
54 return Ok(()); 47 return Ok(());
55 } 48 }
56 let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions_and_statuses 49
57 .iter() 50 let statuses: Vec<nostr::Event> = {
58 .filter(|e| !event_is_revision_root(e) && e.kind().as_u16().eq(&PATCH_KIND)) 51 let mut statuses = get_events_from_cache(
59 .collect::<Vec<&nostr::Event>>(); 52 git_repo_path,
53 vec![
54 nostr::Filter::default()
55 .kinds(status_kinds().clone())
56 .events(proposals_and_revisions.iter().map(nostr::Event::id)),
57 ],
58 )
59 .await?;
60 statuses.sort_by_key(|e| e.created_at);
61 statuses.reverse();
62 statuses
63 };
60 64
61 let mut open_proposals: Vec<&nostr::Event> = vec![]; 65 let mut open_proposals: Vec<&nostr::Event> = vec![];
62 let mut draft_proposals: Vec<&nostr::Event> = vec![]; 66 let mut draft_proposals: Vec<&nostr::Event> = vec![];
63 let mut closed_proposals: Vec<&nostr::Event> = vec![]; 67 let mut closed_proposals: Vec<&nostr::Event> = vec![];
64 let mut applied_proposals: Vec<&nostr::Event> = vec![]; 68 let mut applied_proposals: Vec<&nostr::Event> = vec![];
65 69
66 for proposal in &proposal_events { 70 let proposals: Vec<nostr::Event> = proposals_and_revisions
67 let status = if let Some(e) = proposal_events_and_revisions_and_statuses 71 .iter()
72 .filter(|e| !event_is_revision_root(e))
73 .cloned()
74 .collect();
75
76 for proposal in &proposals {
77 let status = if let Some(e) = statuses
68 .iter() 78 .iter()
69 .filter(|e| { 79 .filter(|e| {
70 status_kinds().contains(&e.kind()) 80 status_kinds().contains(&e.kind())
@@ -89,11 +99,6 @@ pub async fn launch() -> Result<()> {
89 } 99 }
90 } 100 }
91 101
92 if proposal_events_and_revisions_and_statuses.is_empty() {
93 println!("no open proposals found... create one? try `ngit send`");
94 return Ok(());
95 }
96
97 let mut selected_status = STATUS_KIND_OPEN; 102 let mut selected_status = STATUS_KIND_OPEN;
98 103
99 loop { 104 loop {
@@ -106,10 +111,10 @@ pub async fn launch() -> Result<()> {
106 } else if selected_status == STATUS_KIND_APPLIED { 111 } else if selected_status == STATUS_KIND_APPLIED {
107 &applied_proposals 112 &applied_proposals
108 } else { 113 } else {
109 &proposal_events 114 &open_proposals
110 }; 115 };
111 116
112 let prompt = if proposal_events.len().eq(&open_proposals.len()) { 117 let prompt = if proposals.len().eq(&open_proposals.len()) {
113 "all proposals" 118 "all proposals"
114 } else if selected_status == STATUS_KIND_OPEN { 119 } else if selected_status == STATUS_KIND_OPEN {
115 if open_proposals.is_empty() { 120 if open_proposals.is_empty() {
@@ -176,31 +181,12 @@ pub async fn launch() -> Result<()> {
176 let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) 181 let cover_letter = event_to_cover_letter(proposals_for_status[selected_index])
177 .context("cannot extract proposal details from proposal root event")?; 182 .context("cannot extract proposal details from proposal root event")?;
178 183
179 println!("finding commits..."); 184 let commits_events: Vec<nostr::Event> = get_all_proposal_patch_events_from_cache(
180 185 git_repo_path,
181 let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events(
182 &client,
183 &[
184 vec![proposals_for_status[selected_index]],
185 proposal_events_and_revisions_and_statuses
186 .iter()
187 .filter(|e| {
188 e.kind.as_u16().eq(&PATCH_KIND)
189 && e.tags.iter().any(|t| {
190 t.as_vec().len().gt(&1)
191 && t.as_vec()[1]
192 .eq(&proposals_for_status[selected_index].id.to_string())
193 })
194 })
195 .collect::<Vec<&nostr::Event>>(),
196 ]
197 .concat(),
198 &repo_ref, 186 &repo_ref,
187 &proposals_for_status[selected_index].id(),
199 ) 188 )
200 .await?; 189 .await?;
201 // for commit in &commits_events {
202 // println!("cevent: {:?}", commit.as_json());
203 // }
204 190
205 let Ok(most_recent_proposal_patch_chain) = 191 let Ok(most_recent_proposal_patch_chain) =
206 get_most_recent_patch_with_ancestors(commits_events.clone()) 192 get_most_recent_patch_with_ancestors(commits_events.clone())
@@ -829,109 +815,93 @@ pub fn status_kinds() -> Vec<nostr::Kind> {
829 ] 815 ]
830} 816}
831 817
832pub async fn find_proposal_and_status_events( 818pub async fn get_proposals_and_revisions_from_cache(
833 #[cfg(test)] client: &crate::client::MockConnect, 819 git_repo_path: &Path,
834 #[cfg(not(test))] client: &Client, 820 repo_coordinates: HashSet<Coordinate>,
835 repo_ref: &RepoRef,
836 root_commit: &str,
837) -> Result<Vec<nostr::Event>> { 821) -> Result<Vec<nostr::Event>> {
838 let repo_tags_filter = nostr::Filter::default().custom_tag( 822 let mut proposals = get_events_from_cache(
839 nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), 823 git_repo_path,
840 repo_ref 824 vec![
841 .maintainers 825 nostr::Filter::default()
842 .iter() 826 .kind(nostr::Kind::Custom(PATCH_KIND))
843 .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), 827 .custom_tag(
844 ); 828 nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A),
845 let mut proposals = client 829 repo_coordinates
846 .get_events( 830 .iter()
847 repo_ref.relays.clone(), 831 .map(std::string::ToString::to_string)
848 vec![ 832 .collect::<Vec<String>>(),
849 repo_tags_filter 833 ),
850 .clone() 834 ],
851 .kind(nostr::Kind::Custom(PATCH_KIND)) 835 )
852 .custom_tag( 836 .await?
853 nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), 837 .iter()
854 vec!["root"], 838 .filter(|e| event_is_patch_set_root(e))
855 ), 839 .cloned()
856 // also pick up proposals from the same repo but no target at our maintainers repo 840 .collect::<Vec<nostr::Event>>();
857 // events
858 nostr::Filter::default()
859 .reference(root_commit)
860 .kind(nostr::Kind::Custom(PATCH_KIND))
861 .custom_tag(
862 nostr::SingleLetterTag::lowercase(nostr::Alphabet::T),
863 vec!["root"],
864 ),
865 repo_tags_filter.clone().kinds(status_kinds().clone()),
866 nostr::Filter::default()
867 .reference(root_commit)
868 .kinds(status_kinds().clone()),
869 ],
870 )
871 .await
872 .context("cannot get proposal events")?
873 .iter()
874 .filter(|e| {
875 (event_is_patch_set_root(e) || status_kinds().contains(&e.kind()))
876 && (e
877 .tags
878 .iter()
879 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(root_commit))
880 || e.tags.iter().any(|t| {
881 t.as_vec().len() > 1
882 && repo_ref
883 .maintainers
884 .iter()
885 .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier))
886 .any(|d| t.as_vec()[1].eq(&d))
887 }))
888 })
889 .map(std::borrow::ToOwned::to_owned)
890 .collect::<Vec<nostr::Event>>();
891 proposals.sort_by_key(|e| e.created_at); 841 proposals.sort_by_key(|e| e.created_at);
892 proposals.reverse(); 842 proposals.reverse();
893 Ok(proposals) 843 Ok(proposals)
894} 844}
895 845
896pub async fn find_commits_for_proposal_root_events( 846pub async fn get_all_proposal_patch_events_from_cache(
897 #[cfg(test)] client: &crate::client::MockConnect, 847 git_repo_path: &Path,
898 #[cfg(not(test))] client: &Client,
899 proposal_root_events: &Vec<&nostr::Event>,
900 repo_ref: &RepoRef, 848 repo_ref: &RepoRef,
849 proposal_id: &nostr::EventId,
901) -> Result<Vec<nostr::Event>> { 850) -> Result<Vec<nostr::Event>> {
902 let mut patch_events: Vec<nostr::Event> = client 851 let mut commit_events = get_events_from_cache(
903 .get_events( 852 git_repo_path,
904 repo_ref.relays.clone(), 853 vec![
854 nostr::Filter::default()
855 .kind(nostr::Kind::Custom(PATCH_KIND))
856 .event(*proposal_id),
857 nostr::Filter::default()
858 .kind(nostr::Kind::Custom(PATCH_KIND))
859 .id(*proposal_id),
860 ],
861 )
862 .await?;
863
864 let permissioned_users: HashSet<PublicKey> = [
865 repo_ref.maintainers.clone(),
866 vec![
867 commit_events
868 .iter()
869 .find(|e| e.id().eq(proposal_id))
870 .context("proposal not in cache")?
871 .author(),
872 ],
873 ]
874 .concat()
875 .iter()
876 .copied()
877 .collect();
878 commit_events.retain(|e| permissioned_users.contains(e.author_ref()));
879
880 let revision_roots: HashSet<nostr::EventId> = commit_events
881 .iter()
882 .filter(|e| event_is_revision_root(e))
883 .map(nostr::Event::id)
884 .collect();
885
886 if !revision_roots.is_empty() {
887 for event in get_events_from_cache(
888 git_repo_path,
905 vec![ 889 vec![
906 nostr::Filter::default() 890 nostr::Filter::default()
907 .kind(nostr::Kind::Custom(PATCH_KIND)) 891 .kind(nostr::Kind::Custom(PATCH_KIND))
908 .events( 892 .events(revision_roots)
909 proposal_root_events 893 .authors(permissioned_users.clone()),
910 .iter()
911 .map(|e| e.id)
912 .collect::<Vec<nostr::EventId>>(),
913 ),
914 ], 894 ],
915 ) 895 )
916 .await 896 .await?
917 .context("cannot fetch patch events")? 897 {
918 .iter() 898 commit_events.push(event);
919 .filter(|e| {
920 e.kind.as_u16() == PATCH_KIND
921 && e.tags.iter().any(|t| {
922 t.as_vec().len() > 2
923 && proposal_root_events
924 .iter()
925 .any(|e| t.as_vec()[1].eq(&e.id.to_string()))
926 })
927 })
928 .map(std::borrow::ToOwned::to_owned)
929 .collect();
930
931 for e in proposal_root_events {
932 if !event_is_cover_letter(e) && !patch_events.iter().any(|e2| e2.id.eq(&e.id)) {
933 patch_events.push(e.to_owned().clone());
934 } 899 }
935 } 900 }
936 Ok(patch_events) 901
902 Ok(commit_events
903 .iter()
904 .filter(|e| !event_is_cover_letter(e) && permissioned_users.contains(e.author_ref()))
905 .cloned()
906 .collect())
937} 907}
diff --git a/tests/list.rs b/tests/list.rs
index cd071a9..0005053 100644
--- a/tests/list.rs
+++ b/tests/list.rs
@@ -132,17 +132,10 @@ fn cli_tester_create_proposal(
132mod cannot_find_repo_event { 132mod cannot_find_repo_event {
133 use super::*; 133 use super::*;
134 mod cli_prompts { 134 mod cli_prompts {
135 use nostr::{ 135 use nostr::{nips::nip01::Coordinate, ToBech32};
136 nips::{nip01::Coordinate, nip19::Nip19Event},
137 ToBech32,
138 };
139 136
140 use super::*; 137 use super::*;
141 async fn run_async_repo_event_ref_needed( 138 async fn run_async_repo_event_ref_needed(invalid_input: bool, naddr: bool) -> Result<()> {
142 invalid_input: bool,
143 nevent: bool,
144 naddr: bool,
145 ) -> Result<()> {
146 let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( 139 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
147 Relay::new(8051, None, None), 140 Relay::new(8051, None, None),
148 Relay::new(8052, None, None), 141 Relay::new(8052, None, None),
@@ -161,35 +154,19 @@ mod cannot_find_repo_event {
161 r56.events.push(repo_event.clone()); 154 r56.events.push(repo_event.clone());
162 155
163 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 156 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
164 let test_repo = GitTestRepo::default(); 157 let test_repo = GitTestRepo::without_repo_in_git_config();
165 test_repo.populate()?; 158 test_repo.populate()?;
166 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 159 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
167 160
168 p.expect("cannot find repo event\r\n")?;
169
170 if invalid_input { 161 if invalid_input {
171 let mut input = p.expect_input("repository naddr or nevent")?; 162 let mut input = p.expect_input("repository naddr")?;
172 input.succeeds_with("dfgvfvfzadvd")?; 163 input.succeeds_with("dfgvfvfzadvd")?;
173 p.expect("not a valid nevent or naddr\r\n")?; 164 p.expect("not a valid naddr\r\n")?;
174 let _ = p.expect_input("repository naddr or nevent")?; 165 let _ = p.expect_input("repository naddr")?;
175 p.exit()?; 166 p.exit()?;
176 } 167 }
177 if nevent {
178 let mut input = p.expect_input("repository naddr or nevent")?;
179 input.succeeds_with(
180 &Nip19Event {
181 event_id: repo_event.id,
182 author: Some(TEST_KEY_1_KEYS.public_key()),
183 relays: vec!["ws://localhost:8056".to_string()],
184 kind: None,
185 }
186 .to_bech32()?,
187 )?;
188 p.expect("finding proposals...\r\n")?;
189 p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?;
190 }
191 if naddr { 168 if naddr {
192 let mut input = p.expect_input("repository naddr or nevent")?; 169 let mut input = p.expect_input("repository naddr")?;
193 input.succeeds_with( 170 input.succeeds_with(
194 &Coordinate { 171 &Coordinate {
195 kind: nostr::Kind::Custom(REPOSITORY_KIND), 172 kind: nostr::Kind::Custom(REPOSITORY_KIND),
@@ -199,9 +176,9 @@ mod cannot_find_repo_event {
199 } 176 }
200 .to_bech32()?, 177 .to_bech32()?,
201 )?; 178 )?;
202 p.expect("finding proposals...\r\n")?; 179 p.expect("fetching updates...\r\n")?;
180 p.expect_eventually("\r\n")?; // some updates listed here
203 p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?; 181 p.expect_end_with("no proposals found... create one? try `ngit send`\r\n")?;
204 p.expect_end_eventually()?;
205 } 182 }
206 183
207 for p in [51, 52, 53, 55, 56] { 184 for p in [51, 52, 53, 55, 56] {
@@ -225,19 +202,13 @@ mod cannot_find_repo_event {
225 #[tokio::test] 202 #[tokio::test]
226 #[serial] 203 #[serial]
227 async fn warns_not_valid_input_and_asks_again() -> Result<()> { 204 async fn warns_not_valid_input_and_asks_again() -> Result<()> {
228 run_async_repo_event_ref_needed(true, false, false).await 205 run_async_repo_event_ref_needed(true, false).await
229 }
230
231 #[tokio::test]
232 #[serial]
233 async fn finds_based_on_nevent_on_embeded_relay() -> Result<()> {
234 run_async_repo_event_ref_needed(false, true, false).await
235 } 206 }
236 207
237 #[tokio::test] 208 #[tokio::test]
238 #[serial] 209 #[serial]
239 async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> { 210 async fn finds_based_on_naddr_on_embeded_relay() -> Result<()> {
240 run_async_repo_event_ref_needed(false, false, true).await 211 run_async_repo_event_ref_needed(false, true).await
241 } 212 }
242 } 213 }
243} 214}
@@ -281,7 +252,8 @@ mod when_main_branch_is_uptodate {
281 test_repo.populate()?; 252 test_repo.populate()?;
282 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 253 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
283 254
284 p.expect("finding proposals...\r\n")?; 255 p.expect("fetching updates...\r\n")?;
256 p.expect_eventually("\r\n")?; // some updates listed here
285 let mut c = p.expect_choice( 257 let mut c = p.expect_choice(
286 "all proposals", 258 "all proposals",
287 vec![ 259 vec![
@@ -292,7 +264,6 @@ mod when_main_branch_is_uptodate {
292 )?; 264 )?;
293 c.succeeds_with(2, true, None)?; 265 c.succeeds_with(2, true, None)?;
294 266
295 p.expect("finding commits...\r\n")?;
296 let mut c = p.expect_choice( 267 let mut c = p.expect_choice(
297 "", 268 "",
298 vec![ 269 vec![
@@ -356,7 +327,8 @@ mod when_main_branch_is_uptodate {
356 test_repo.populate()?; 327 test_repo.populate()?;
357 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 328 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
358 329
359 p.expect("finding proposals...\r\n")?; 330 p.expect("fetching updates...\r\n")?;
331 p.expect_eventually("\r\n")?; // some updates listed here
360 let mut c = p.expect_choice( 332 let mut c = p.expect_choice(
361 "all proposals", 333 "all proposals",
362 vec![ 334 vec![
@@ -366,7 +338,6 @@ mod when_main_branch_is_uptodate {
366 ], 338 ],
367 )?; 339 )?;
368 c.succeeds_with(2, true, None)?; 340 c.succeeds_with(2, true, None)?;
369 p.expect("finding commits...\r\n")?;
370 let mut c = p.expect_choice( 341 let mut c = p.expect_choice(
371 "", 342 "",
372 vec![ 343 vec![
@@ -465,7 +436,8 @@ mod when_main_branch_is_uptodate {
465 test_repo.populate()?; 436 test_repo.populate()?;
466 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 437 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
467 438
468 p.expect("finding proposals...\r\n")?; 439 p.expect("fetching updates...\r\n")?;
440 p.expect_eventually("\r\n")?; // some updates listed here
469 let mut c = p.expect_choice( 441 let mut c = p.expect_choice(
470 "all proposals", 442 "all proposals",
471 vec![ 443 vec![
@@ -476,7 +448,6 @@ mod when_main_branch_is_uptodate {
476 )?; 448 )?;
477 c.succeeds_with(0, true, None)?; 449 c.succeeds_with(0, true, None)?;
478 450
479 p.expect("finding commits...\r\n")?;
480 let mut c = p.expect_choice( 451 let mut c = p.expect_choice(
481 "", 452 "",
482 vec![ 453 vec![
@@ -542,7 +513,8 @@ mod when_main_branch_is_uptodate {
542 test_repo.populate()?; 513 test_repo.populate()?;
543 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 514 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
544 515
545 p.expect("finding proposals...\r\n")?; 516 p.expect("fetching updates...\r\n")?;
517 p.expect_eventually("\r\n")?; // some updates listed here
546 let mut c = p.expect_choice( 518 let mut c = p.expect_choice(
547 "all proposals", 519 "all proposals",
548 vec![ 520 vec![
@@ -552,7 +524,6 @@ mod when_main_branch_is_uptodate {
552 ], 524 ],
553 )?; 525 )?;
554 c.succeeds_with(0, true, None)?; 526 c.succeeds_with(0, true, None)?;
555 p.expect("finding commits...\r\n")?;
556 let mut c = p.expect_choice( 527 let mut c = p.expect_choice(
557 "", 528 "",
558 vec![ 529 vec![
@@ -657,7 +628,8 @@ mod when_main_branch_is_uptodate {
657 test_repo.populate()?; 628 test_repo.populate()?;
658 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 629 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
659 630
660 p.expect("finding proposals...\r\n")?; 631 p.expect("fetching updates...\r\n")?;
632 p.expect_eventually("\r\n")?; // some updates listed here
661 let mut c = p.expect_choice( 633 let mut c = p.expect_choice(
662 "all proposals", 634 "all proposals",
663 vec![ 635 vec![
@@ -668,7 +640,6 @@ mod when_main_branch_is_uptodate {
668 ], 640 ],
669 )?; 641 )?;
670 c.succeeds_with(0, true, None)?; 642 c.succeeds_with(0, true, None)?;
671 p.expect("finding commits...\r\n")?;
672 let mut c = p.expect_choice( 643 let mut c = p.expect_choice(
673 "", 644 "",
674 vec![ 645 vec![
@@ -741,7 +712,8 @@ mod when_main_branch_is_uptodate {
741 test_repo.populate()?; 712 test_repo.populate()?;
742 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 713 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
743 714
744 p.expect("finding proposals...\r\n")?; 715 p.expect("fetching updates...\r\n")?;
716 p.expect_eventually("\r\n")?; // some updates listed here
745 let mut c = p.expect_choice( 717 let mut c = p.expect_choice(
746 "all proposals", 718 "all proposals",
747 vec![ 719 vec![
@@ -752,7 +724,6 @@ mod when_main_branch_is_uptodate {
752 ], 724 ],
753 )?; 725 )?;
754 c.succeeds_with(0, true, None)?; 726 c.succeeds_with(0, true, None)?;
755 p.expect("finding commits...\r\n")?;
756 let mut c = p.expect_choice( 727 let mut c = p.expect_choice(
757 "", 728 "",
758 vec![ 729 vec![
@@ -866,7 +837,8 @@ mod when_main_branch_is_uptodate {
866 false, 837 false,
867 )?; 838 )?;
868 test_repo.checkout("main")?; 839 test_repo.checkout("main")?;
869 p.expect("finding proposals...\r\n")?; 840 p.expect("fetching updates...\r\n")?;
841 p.expect_eventually("\r\n")?; // some updates listed here
870 let mut c = p.expect_choice( 842 let mut c = p.expect_choice(
871 "all proposals", 843 "all proposals",
872 vec![ 844 vec![
@@ -876,7 +848,6 @@ mod when_main_branch_is_uptodate {
876 ], 848 ],
877 )?; 849 )?;
878 c.succeeds_with(2, true, None)?; 850 c.succeeds_with(2, true, None)?;
879 p.expect("finding commits...\r\n")?;
880 let mut c = p.expect_choice( 851 let mut c = p.expect_choice(
881 "", 852 "",
882 vec![ 853 vec![
@@ -948,7 +919,8 @@ mod when_main_branch_is_uptodate {
948 )?; 919 )?;
949 test_repo.checkout("main")?; 920 test_repo.checkout("main")?;
950 921
951 p.expect("finding proposals...\r\n")?; 922 p.expect("fetching updates...\r\n")?;
923 p.expect_eventually("\r\n")?; // some updates listed here
952 let mut c = p.expect_choice( 924 let mut c = p.expect_choice(
953 "all proposals", 925 "all proposals",
954 vec![ 926 vec![
@@ -958,7 +930,6 @@ mod when_main_branch_is_uptodate {
958 ], 930 ],
959 )?; 931 )?;
960 c.succeeds_with(2, true, None)?; 932 c.succeeds_with(2, true, None)?;
961 p.expect("finding commits...\r\n")?;
962 let mut c = p.expect_choice( 933 let mut c = p.expect_choice(
963 "", 934 "",
964 vec![ 935 vec![
@@ -1043,7 +1014,8 @@ mod when_main_branch_is_uptodate {
1043 )?; 1014 )?;
1044 test_repo.checkout("main")?; 1015 test_repo.checkout("main")?;
1045 1016
1046 p.expect("finding proposals...\r\n")?; 1017 p.expect("fetching updates...\r\n")?;
1018 p.expect_eventually("\r\n")?; // some updates listed here
1047 let mut c = p.expect_choice( 1019 let mut c = p.expect_choice(
1048 "all proposals", 1020 "all proposals",
1049 vec![ 1021 vec![
@@ -1053,7 +1025,6 @@ mod when_main_branch_is_uptodate {
1053 ], 1025 ],
1054 )?; 1026 )?;
1055 c.succeeds_with(2, true, None)?; 1027 c.succeeds_with(2, true, None)?;
1056 p.expect("finding commits...\r\n")?;
1057 let mut c = p.expect_choice( 1028 let mut c = p.expect_choice(
1058 "", 1029 "",
1059 vec![ 1030 vec![
@@ -1124,7 +1095,8 @@ mod when_main_branch_is_uptodate {
1124 )?; 1095 )?;
1125 test_repo.checkout("main")?; 1096 test_repo.checkout("main")?;
1126 1097
1127 p.expect("finding proposals...\r\n")?; 1098 p.expect("fetching updates...\r\n")?;
1099 p.expect_eventually("\r\n")?; // some updates listed here
1128 let mut c = p.expect_choice( 1100 let mut c = p.expect_choice(
1129 "all proposals", 1101 "all proposals",
1130 vec![ 1102 vec![
@@ -1134,7 +1106,6 @@ mod when_main_branch_is_uptodate {
1134 ], 1106 ],
1135 )?; 1107 )?;
1136 c.succeeds_with(2, true, None)?; 1108 c.succeeds_with(2, true, None)?;
1137 p.expect("finding commits...\r\n")?;
1138 let mut c = p.expect_choice( 1109 let mut c = p.expect_choice(
1139 "", 1110 "",
1140 vec![ 1111 vec![
@@ -1236,7 +1207,8 @@ mod when_main_branch_is_uptodate {
1236 )?; 1207 )?;
1237 1208
1238 test_repo.checkout("main")?; 1209 test_repo.checkout("main")?;
1239 p.expect("finding proposals...\r\n")?; 1210 p.expect("fetching updates...\r\n")?;
1211 p.expect_eventually("\r\n")?; // some updates listed here
1240 let mut c = p.expect_choice( 1212 let mut c = p.expect_choice(
1241 "all proposals", 1213 "all proposals",
1242 vec![ 1214 vec![
@@ -1246,7 +1218,6 @@ mod when_main_branch_is_uptodate {
1246 ], 1218 ],
1247 )?; 1219 )?;
1248 c.succeeds_with(2, true, None)?; 1220 c.succeeds_with(2, true, None)?;
1249 p.expect("finding commits...\r\n")?;
1250 p.expect_eventually("--force`\r\n")?; 1221 p.expect_eventually("--force`\r\n")?;
1251 1222
1252 let mut c = p.expect_choice( 1223 let mut c = p.expect_choice(
@@ -1329,7 +1300,8 @@ mod when_main_branch_is_uptodate {
1329 )?; 1300 )?;
1330 1301
1331 test_repo.checkout("main")?; 1302 test_repo.checkout("main")?;
1332 p.expect("finding proposals...\r\n")?; 1303 p.expect("fetching updates...\r\n")?;
1304 p.expect_eventually("\r\n")?; // some updates listed here
1333 let mut c = p.expect_choice( 1305 let mut c = p.expect_choice(
1334 "all proposals", 1306 "all proposals",
1335 vec![ 1307 vec![
@@ -1339,7 +1311,6 @@ mod when_main_branch_is_uptodate {
1339 ], 1311 ],
1340 )?; 1312 )?;
1341 c.succeeds_with(2, true, None)?; 1313 c.succeeds_with(2, true, None)?;
1342 p.expect("finding commits...\r\n")?;
1343 p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?; 1314 p.expect("you have an amended/rebase version the proposal that is unpublished\r\n")?;
1344 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")?; 1315 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")?;
1345 p.expect("to view the latest proposal but retain your changes:\r\n")?; 1316 p.expect("to view the latest proposal but retain your changes:\r\n")?;
@@ -1435,7 +1406,8 @@ mod when_main_branch_is_uptodate {
1435 test_repo.stage_and_commit("appended commit")?; 1406 test_repo.stage_and_commit("appended commit")?;
1436 1407
1437 test_repo.checkout("main")?; 1408 test_repo.checkout("main")?;
1438 p.expect("finding proposals...\r\n")?; 1409 p.expect("fetching updates...\r\n")?;
1410 p.expect_eventually("\r\n")?; // some updates listed here
1439 let mut c = p.expect_choice( 1411 let mut c = p.expect_choice(
1440 "all proposals", 1412 "all proposals",
1441 vec![ 1413 vec![
@@ -1445,7 +1417,6 @@ mod when_main_branch_is_uptodate {
1445 ], 1417 ],
1446 )?; 1418 )?;
1447 c.succeeds_with(2, true, None)?; 1419 c.succeeds_with(2, true, None)?;
1448 p.expect("finding commits...\r\n")?;
1449 p.expect( 1420 p.expect(
1450 "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", 1421 "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n",
1451 )?; 1422 )?;
@@ -1521,7 +1492,8 @@ mod when_main_branch_is_uptodate {
1521 test_repo.stage_and_commit("appended commit")?; 1492 test_repo.stage_and_commit("appended commit")?;
1522 1493
1523 test_repo.checkout("main")?; 1494 test_repo.checkout("main")?;
1524 p.expect("finding proposals...\r\n")?; 1495 p.expect("fetching updates...\r\n")?;
1496 p.expect_eventually("\r\n")?; // some updates listed here
1525 let mut c = p.expect_choice( 1497 let mut c = p.expect_choice(
1526 "all proposals", 1498 "all proposals",
1527 vec![ 1499 vec![
@@ -1531,7 +1503,6 @@ mod when_main_branch_is_uptodate {
1531 ], 1503 ],
1532 )?; 1504 )?;
1533 c.succeeds_with(2, true, None)?; 1505 c.succeeds_with(2, true, None)?;
1534 p.expect("finding commits...\r\n")?;
1535 p.expect( 1506 p.expect(
1536 "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n", 1507 "local proposal branch exists with 1 unpublished commits on top of the most up-to-date version of the proposal (3 ahead 0 behind 'main')\r\n",
1537 )?; 1508 )?;
@@ -1680,7 +1651,8 @@ mod when_main_branch_is_uptodate {
1680 test_repo.stage_and_commit("commit for rebasing on top of")?; 1651 test_repo.stage_and_commit("commit for rebasing on top of")?;
1681 1652
1682 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1653 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1683 p.expect("finding proposals...\r\n")?; 1654 p.expect("fetching updates...\r\n")?;
1655 p.expect_eventually("\r\n")?; // some updates listed here
1684 let mut c = p.expect_choice( 1656 let mut c = p.expect_choice(
1685 "all proposals", 1657 "all proposals",
1686 vec![ 1658 vec![
@@ -1690,7 +1662,6 @@ mod when_main_branch_is_uptodate {
1690 ], 1662 ],
1691 )?; 1663 )?;
1692 c.succeeds_with(2, true, None)?; 1664 c.succeeds_with(2, true, None)?;
1693 p.expect("finding commits...\r\n")?;
1694 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; 1665 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?;
1695 let mut c = p.expect_choice( 1666 let mut c = p.expect_choice(
1696 "", 1667 "",
@@ -1814,7 +1785,8 @@ mod when_main_branch_is_uptodate {
1814 test_repo.stage_and_commit("commit for rebasing on top of")?; 1785 test_repo.stage_and_commit("commit for rebasing on top of")?;
1815 1786
1816 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); 1787 let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]);
1817 p.expect("finding proposals...\r\n")?; 1788 p.expect("fetching updates...\r\n")?;
1789 p.expect_eventually("\r\n")?; // some updates listed here
1818 let mut c = p.expect_choice( 1790 let mut c = p.expect_choice(
1819 "all proposals", 1791 "all proposals",
1820 vec![ 1792 vec![
@@ -1824,7 +1796,6 @@ mod when_main_branch_is_uptodate {
1824 ], 1796 ],
1825 )?; 1797 )?;
1826 c.succeeds_with(2, true, None)?; 1798 c.succeeds_with(2, true, None)?;
1827 p.expect("finding commits...\r\n")?;
1828 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; 1799 p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?;
1829 let mut c = p.expect_choice( 1800 let mut c = p.expect_choice(
1830 "", 1801 "",