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>2026-02-12 12:25:10 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-12 12:25:10 +0000
commitc85ca81767d838797f6a1ab6651e9864c3f961c1 (patch)
tree2519376a5fecb7324346910707f1b378ead3f093
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.
-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