upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-12 12:25:10 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-12 12:25:10 +0000
commitc85ca81767d838797f6a1ab6651e9864c3f961c1 (patch)
tree2519376a5fecb7324346910707f1b378ead3f093 /src
parent116ab6757ef22779b913a5e1c5e289ba7f3daefb (diff)
fix: set up branch tracking when checking out proposals
When a nostr:// remote exists, run git fetch instead of internal fetch to populate remote tracking refs. Then checkout the remote branch with proper upstream tracking so git pull works correctly.
Diffstat (limited to 'src')
-rw-r--r--src/bin/ngit/sub_commands/apply.rs34
-rw-r--r--src/bin/ngit/sub_commands/checkout.rs102
-rw-r--r--src/bin/ngit/sub_commands/list.rs44
3 files changed, 165 insertions, 15 deletions
diff --git a/src/bin/ngit/sub_commands/apply.rs b/src/bin/ngit/sub_commands/apply.rs
index d32cd4f..fd9eae3 100644
--- a/src/bin/ngit/sub_commands/apply.rs
+++ b/src/bin/ngit/sub_commands/apply.rs
@@ -1,4 +1,7 @@
1use std::io::Write; 1use std::{
2 io::Write,
3 process::{Command, Stdio},
4};
2 5
3use anyhow::{Context, Result, bail}; 6use anyhow::{Context, Result, bail};
4use ngit::client::get_all_proposal_patch_pr_pr_update_events_from_cache; 7use ngit::client::get_all_proposal_patch_pr_pr_update_events_from_cache;
@@ -6,10 +9,25 @@ use ngit::git_events::get_pr_tip_event_or_most_recent_patch_with_ancestors;
6use nostr::nips::nip19::Nip19; 9use nostr::nips::nip19::Nip19;
7use nostr_sdk::{EventId, FromBech32}; 10use nostr_sdk::{EventId, FromBech32};
8 11
9use crate::client::{Client, Connect, get_repo_ref_from_cache}; 12use crate::client::{Client, Connect, fetching_with_report, get_repo_ref_from_cache};
10use crate::git::{Repo, RepoActions}; 13use crate::git::{Repo, RepoActions};
11use crate::repo_ref::get_repo_coordinates_when_remote_unknown; 14use crate::repo_ref::get_repo_coordinates_when_remote_unknown;
12 15
16fn run_git_fetch(remote_name: &str) -> Result<()> {
17 println!("fetching from {remote_name}...");
18 let exit_status = Command::new("git")
19 .args(["fetch", remote_name])
20 .stdout(Stdio::inherit())
21 .stderr(Stdio::inherit())
22 .status()
23 .context("failed to run git fetch")?;
24
25 if !exit_status.success() {
26 bail!("git fetch {remote_name} exited with error: {exit_status}");
27 }
28 Ok(())
29}
30
13pub async fn launch(id: &str, stdout: bool) -> Result<()> { 31pub async fn launch(id: &str, stdout: bool) -> Result<()> {
14 let event_id = parse_event_id(id)?; 32 let event_id = parse_event_id(id)?;
15 33
@@ -22,7 +40,17 @@ pub async fn launch(id: &str, stdout: bool) -> Result<()> {
22 40
23 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; 41 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?;
24 42
25 crate::client::fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; 43 let nostr_remote = git_repo
44 .get_first_nostr_remote_when_in_ngit_binary()
45 .await
46 .ok()
47 .flatten();
48
49 if let Some((remote_name, _)) = &nostr_remote {
50 run_git_fetch(remote_name)?;
51 } else {
52 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
53 }
26 54
27 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; 55 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?;
28 56
diff --git a/src/bin/ngit/sub_commands/checkout.rs b/src/bin/ngit/sub_commands/checkout.rs
index 0df1134..47d61ed 100644
--- a/src/bin/ngit/sub_commands/checkout.rs
+++ b/src/bin/ngit/sub_commands/checkout.rs
@@ -1,4 +1,7 @@
1use std::collections::HashSet; 1use std::{
2 collections::HashSet,
3 process::{Command, Stdio},
4};
2 5
3use anyhow::{Context, Result, bail}; 6use anyhow::{Context, Result, bail};
4use ngit::{ 7use ngit::{
@@ -33,7 +36,17 @@ pub async fn launch(id: &str) -> Result<()> {
33 36
34 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; 37 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?;
35 38
36 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; 39 let nostr_remote = git_repo
40 .get_first_nostr_remote_when_in_ngit_binary()
41 .await
42 .ok()
43 .flatten();
44
45 if let Some((remote_name, _)) = &nostr_remote {
46 run_git_fetch(remote_name)?;
47 } else {
48 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
49 }
37 50
38 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; 51 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?;
39 52
@@ -68,16 +81,33 @@ pub async fn launch(id: &str) -> Result<()> {
68 &repo_ref, 81 &repo_ref,
69 &cover_letter, 82 &cover_letter,
70 &most_recent_proposal_patch_chain_or_pr_or_pr_update, 83 &most_recent_proposal_patch_chain_or_pr_or_pr_update,
84 nostr_remote.as_ref().map(|(name, _)| name.as_str()),
71 ) 85 )
72 } else { 86 } else {
73 checkout_patch( 87 checkout_patch(
74 &git_repo, 88 &git_repo,
75 &cover_letter, 89 &cover_letter,
76 &most_recent_proposal_patch_chain_or_pr_or_pr_update, 90 &most_recent_proposal_patch_chain_or_pr_or_pr_update,
91 nostr_remote.as_ref().map(|(name, _)| name.as_str()),
77 ) 92 )
78 } 93 }
79} 94}
80 95
96fn run_git_fetch(remote_name: &str) -> Result<()> {
97 println!("fetching from {remote_name}...");
98 let exit_status = Command::new("git")
99 .args(["fetch", remote_name])
100 .stdout(Stdio::inherit())
101 .stderr(Stdio::inherit())
102 .status()
103 .context("failed to run git fetch")?;
104
105 if !exit_status.success() {
106 bail!("git fetch {remote_name} exited with error: {exit_status}");
107 }
108 Ok(())
109}
110
81fn parse_event_id(id: &str) -> Result<EventId> { 111fn parse_event_id(id: &str) -> Result<EventId> {
82 if let Ok(nip19) = Nip19::from_bech32(id) { 112 if let Ok(nip19) = Nip19::from_bech32(id) {
83 match nip19 { 113 match nip19 {
@@ -97,15 +127,15 @@ fn checkout_pr(
97 repo_ref: &RepoRef, 127 repo_ref: &RepoRef,
98 cover_letter: &crate::git_events::CoverLetter, 128 cover_letter: &crate::git_events::CoverLetter,
99 most_recent_proposal_patch_chain_or_pr_or_pr_update: &[nostr::Event], 129 most_recent_proposal_patch_chain_or_pr_or_pr_update: &[nostr::Event],
130 nostr_remote_name: Option<&str>,
100) -> Result<()> { 131) -> Result<()> {
101 let branch_name = cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?; 132 let branch_name = cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?;
102 let local_branch_tip = git_repo.get_tip_of_branch(&branch_name).ok();
103 let proposal_tip_event = most_recent_proposal_patch_chain_or_pr_or_pr_update 133 let proposal_tip_event = most_recent_proposal_patch_chain_or_pr_or_pr_update
104 .first() 134 .first()
105 .context("most_recent_proposal_patch_chain_or_pr_or_pr_update will always contain an event with c tag")?; 135 .context("most_recent_proposal_patch_chain_or_pr_or_pr_update will always contain an event with c tag")?;
106 let proposal_tip = tag_value(proposal_tip_event, "c")?; 136 let proposal_tip = tag_value(proposal_tip_event, "c")?;
107 137
108 if let Some(local_branch_tip) = local_branch_tip { 138 if let Ok(local_branch_tip) = git_repo.get_tip_of_branch(&branch_name) {
109 git_repo 139 git_repo
110 .checkout(&branch_name) 140 .checkout(&branch_name)
111 .context("cannot checkout existing proposal branch")?; 141 .context("cannot checkout existing proposal branch")?;
@@ -121,6 +151,15 @@ fn checkout_pr(
121 } 151 }
122 } 152 }
123 153
154 if let Some(remote_name) = nostr_remote_name {
155 let remote_branch = format!("{remote_name}/{branch_name}");
156 if git_repo.get_tip_of_branch(&remote_branch).is_ok() {
157 checkout_remote_branch_with_tracking(git_repo, remote_name, &branch_name)?;
158 println!("checked out proposal branch '{branch_name}' with tracking to {remote_name}");
159 return Ok(());
160 }
161 }
162
124 fetch_oid_for_from_servers_for_pr( 163 fetch_oid_for_from_servers_for_pr(
125 &proposal_tip, 164 &proposal_tip,
126 git_repo, 165 git_repo,
@@ -129,11 +168,7 @@ fn checkout_pr(
129 )?; 168 )?;
130 git_repo.create_branch_at_commit(&branch_name, &proposal_tip)?; 169 git_repo.create_branch_at_commit(&branch_name, &proposal_tip)?;
131 git_repo.checkout(&branch_name)?; 170 git_repo.checkout(&branch_name)?;
132 if local_branch_tip.is_some() { 171 println!("created and checked out proposal branch '{branch_name}'");
133 println!("checked out proposal branch and pulled updates '{branch_name}'");
134 } else {
135 println!("created and checked out proposal branch '{branch_name}'");
136 }
137 Ok(()) 172 Ok(())
138} 173}
139 174
@@ -141,6 +176,7 @@ fn checkout_patch(
141 git_repo: &Repo, 176 git_repo: &Repo,
142 cover_letter: &crate::git_events::CoverLetter, 177 cover_letter: &crate::git_events::CoverLetter,
143 most_recent_proposal_patch_chain_or_pr_or_pr_update: &[nostr::Event], 178 most_recent_proposal_patch_chain_or_pr_or_pr_update: &[nostr::Event],
179 nostr_remote_name: Option<&str>,
144) -> Result<()> { 180) -> Result<()> {
145 let no_support_for_patches_as_branch = most_recent_proposal_patch_chain_or_pr_or_pr_update 181 let no_support_for_patches_as_branch = most_recent_proposal_patch_chain_or_pr_or_pr_update
146 .iter() 182 .iter()
@@ -184,6 +220,14 @@ fn checkout_patch(
184 .any(|n| n.eq(&branch_name)); 220 .any(|n| n.eq(&branch_name));
185 221
186 if !branch_exists { 222 if !branch_exists {
223 if let Some(remote_name) = nostr_remote_name {
224 let remote_branch = format!("{remote_name}/{branch_name}");
225 if git_repo.get_tip_of_branch(&remote_branch).is_ok() {
226 checkout_remote_branch_with_tracking(git_repo, remote_name, &branch_name)?;
227 println!("checked out proposal branch '{branch_name}' with tracking to {remote_name}");
228 return Ok(());
229 }
230 }
187 let _ = git_repo 231 let _ = git_repo
188 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain_or_pr_or_pr_update.to_vec()) 232 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain_or_pr_or_pr_update.to_vec())
189 .context("failed to apply patch chain")?; 233 .context("failed to apply patch chain")?;
@@ -267,3 +311,43 @@ fn fetch_oid_for_from_servers_for_pr(
267 } 311 }
268 Ok(()) 312 Ok(())
269} 313}
314
315fn checkout_remote_branch_with_tracking(
316 git_repo: &Repo,
317 remote_name: &str,
318 branch_name: &str,
319) -> Result<()> {
320 let remote_branch_ref = format!("refs/remotes/{remote_name}/{branch_name}");
321 let remote_branch = git_repo
322 .git_repo
323 .find_reference(&remote_branch_ref)
324 .context(format!("failed to find remote branch {remote_branch_ref}"))?;
325 let commit = remote_branch
326 .peel_to_commit()
327 .context("failed to peel remote branch to commit")?;
328
329 let mut local_branch = git_repo
330 .git_repo
331 .branch(branch_name, &commit, false)
332 .context("failed to create local branch")?;
333
334 local_branch
335 .set_upstream(Some(&format!("{remote_name}/{branch_name}")))
336 .context("failed to set upstream tracking")?;
337
338 let local_branch_ref = local_branch.into_reference();
339 let local_branch_ref_name = local_branch_ref
340 .name()
341 .context("failed to get local branch ref name")?;
342
343 git_repo
344 .git_repo
345 .set_head(local_branch_ref_name)
346 .context("failed to set head to local branch")?;
347 git_repo
348 .git_repo
349 .checkout_head(None)
350 .context("failed to checkout head")?;
351
352 Ok(())
353}
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs
index 7873fae..6d78a1f 100644
--- a/src/bin/ngit/sub_commands/list.rs
+++ b/src/bin/ngit/sub_commands/list.rs
@@ -1,4 +1,7 @@
1use std::{collections::HashSet, io::Write, ops::Add}; 1use std::{
2 collections::HashSet, io::Write, ops::Add,
3 process::{Command, Stdio},
4};
2 5
3use anyhow::{Context, Result, bail}; 6use anyhow::{Context, Result, bail};
4use ngit::{ 7use ngit::{
@@ -31,6 +34,21 @@ use crate::{
31 repo_ref::get_repo_coordinates_when_remote_unknown, 34 repo_ref::get_repo_coordinates_when_remote_unknown,
32}; 35};
33 36
37fn run_git_fetch(remote_name: &str) -> Result<()> {
38 println!("fetching from {remote_name}...");
39 let exit_status = Command::new("git")
40 .args(["fetch", remote_name])
41 .stdout(Stdio::inherit())
42 .stderr(Stdio::inherit())
43 .status()
44 .context("failed to run git fetch")?;
45
46 if !exit_status.success() {
47 bail!("git fetch {remote_name} exited with error: {exit_status}");
48 }
49 Ok(())
50}
51
34#[allow(clippy::too_many_lines)] 52#[allow(clippy::too_many_lines)]
35pub async fn launch(status: String, json: bool, id: Option<String>) -> Result<()> { 53pub async fn launch(status: String, json: bool, id: Option<String>) -> Result<()> {
36 if std::env::var("NGIT_INTERACTIVE_MODE").is_ok() { 54 if std::env::var("NGIT_INTERACTIVE_MODE").is_ok() {
@@ -44,7 +62,17 @@ pub async fn launch(status: String, json: bool, id: Option<String>) -> Result<()
44 62
45 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; 63 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?;
46 64
47 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; 65 let nostr_remote = git_repo
66 .get_first_nostr_remote_when_in_ngit_binary()
67 .await
68 .ok()
69 .flatten();
70
71 if let Some((remote_name, _)) = &nostr_remote {
72 run_git_fetch(remote_name)?;
73 } else {
74 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
75 }
48 76
49 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; 77 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?;
50 78
@@ -285,7 +313,17 @@ async fn launch_interactive() -> Result<()> {
285 313
286 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; 314 let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?;
287 315
288 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; 316 let nostr_remote = git_repo
317 .get_first_nostr_remote_when_in_ngit_binary()
318 .await
319 .ok()
320 .flatten();
321
322 if let Some((remote_name, _)) = &nostr_remote {
323 run_git_fetch(remote_name)?;
324 } else {
325 fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
326 }
289 327
290 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; 328 let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?;
291 329