upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-11-04 12:02:52 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-11-04 12:02:52 +0000
commit43b5e9b38bf5dcfbac85637a2d3efc69ddfe77ac (patch)
tree28f0937414ec22921a73da08c38d93284bb50e41 /src/bin
parentd62a6a4355d47de9babd6fc86fae2b240ff3ab28 (diff)
refactor: remove ngit `pull` `push` `fetch`
simplify api to encougage use of the git plugin
Diffstat (limited to 'src/bin')
-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
6 files changed, 0 insertions, 471 deletions
diff --git a/src/bin/ngit/cli.rs b/src/bin/ngit/cli.rs
index d0f934e..13e18d1 100644
--- a/src/bin/ngit/cli.rs
+++ b/src/bin/ngit/cli.rs
@@ -27,18 +27,12 @@ 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),
32 /// signal you are this repo's maintainer accepting proposals via nostr 30 /// signal you are this repo's maintainer accepting proposals via nostr
33 Init(sub_commands::init::SubCommandArgs), 31 Init(sub_commands::init::SubCommandArgs),
34 /// issue commits as a proposal 32 /// issue commits as a proposal
35 Send(sub_commands::send::SubCommandArgs), 33 Send(sub_commands::send::SubCommandArgs),
36 /// list proposals; checkout, apply or download selected 34 /// list proposals; checkout, apply or download selected
37 List, 35 List,
38 /// send proposal revision
39 Push(sub_commands::push::SubCommandArgs),
40 /// fetch and apply new proposal commits / revisions linked to branch
41 Pull,
42 /// run with --nsec flag to change npub 36 /// run with --nsec flag to change npub
43 Login(sub_commands::login::SubCommandArgs), 37 Login(sub_commands::login::SubCommandArgs),
44} 38}
diff --git a/src/bin/ngit/main.rs b/src/bin/ngit/main.rs
index 45cbef5..38dc2c1 100644
--- a/src/bin/ngit/main.rs
+++ b/src/bin/ngit/main.rs
@@ -15,12 +15,9 @@ 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,
19 Commands::Login(args) => sub_commands::login::launch(&cli, args).await, 18 Commands::Login(args) => sub_commands::login::launch(&cli, args).await,
20 Commands::Init(args) => sub_commands::init::launch(&cli, args).await, 19 Commands::Init(args) => sub_commands::init::launch(&cli, args).await,
21 Commands::Send(args) => sub_commands::send::launch(&cli, args, false).await, 20 Commands::Send(args) => sub_commands::send::launch(&cli, args, false).await,
22 Commands::List => sub_commands::list::launch().await, 21 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,
25 } 22 }
26} 23}
diff --git a/src/bin/ngit/sub_commands/fetch.rs b/src/bin/ngit/sub_commands/fetch.rs
deleted file mode 100644
index c69f1c5..0000000
--- a/src/bin/ngit/sub_commands/fetch.rs
+++ /dev/null
@@ -1,37 +0,0 @@
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 29a60f9..a53197f 100644
--- a/src/bin/ngit/sub_commands/mod.rs
+++ b/src/bin/ngit/sub_commands/mod.rs
@@ -1,7 +1,4 @@
1pub mod fetch;
2pub mod init; 1pub mod init;
3pub mod list; 2pub mod list;
4pub mod login; 3pub mod login;
5pub mod pull;
6pub mod push;
7pub mod send; 4pub mod send;
diff --git a/src/bin/ngit/sub_commands/pull.rs b/src/bin/ngit/sub_commands/pull.rs
deleted file mode 100644
index d79b7b1..0000000
--- a/src/bin/ngit/sub_commands/pull.rs
+++ /dev/null
@@ -1,203 +0,0 @@
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
deleted file mode 100644
index a77f356..0000000
--- a/src/bin/ngit/sub_commands/push.rs
+++ /dev/null
@@ -1,219 +0,0 @@
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}