diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-12 12:25:10 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-12 12:25:10 +0000 |
| commit | c85ca81767d838797f6a1ab6651e9864c3f961c1 (patch) | |
| tree | 2519376a5fecb7324346910707f1b378ead3f093 | |
| parent | 116ab6757ef22779b913a5e1c5e289ba7f3daefb (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.rs | 34 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/checkout.rs | 102 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 44 |
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 @@ | |||
| 1 | use std::io::Write; | 1 | use std::{ |
| 2 | io::Write, | ||
| 3 | process::{Command, Stdio}, | ||
| 4 | }; | ||
| 2 | 5 | ||
| 3 | use anyhow::{Context, Result, bail}; | 6 | use anyhow::{Context, Result, bail}; |
| 4 | use ngit::client::get_all_proposal_patch_pr_pr_update_events_from_cache; | 7 | use 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; | |||
| 6 | use nostr::nips::nip19::Nip19; | 9 | use nostr::nips::nip19::Nip19; |
| 7 | use nostr_sdk::{EventId, FromBech32}; | 10 | use nostr_sdk::{EventId, FromBech32}; |
| 8 | 11 | ||
| 9 | use crate::client::{Client, Connect, get_repo_ref_from_cache}; | 12 | use crate::client::{Client, Connect, fetching_with_report, get_repo_ref_from_cache}; |
| 10 | use crate::git::{Repo, RepoActions}; | 13 | use crate::git::{Repo, RepoActions}; |
| 11 | use crate::repo_ref::get_repo_coordinates_when_remote_unknown; | 14 | use crate::repo_ref::get_repo_coordinates_when_remote_unknown; |
| 12 | 15 | ||
| 16 | fn 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 | |||
| 13 | pub async fn launch(id: &str, stdout: bool) -> Result<()> { | 31 | pub 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 @@ | |||
| 1 | use std::collections::HashSet; | 1 | use std::{ |
| 2 | collections::HashSet, | ||
| 3 | process::{Command, Stdio}, | ||
| 4 | }; | ||
| 2 | 5 | ||
| 3 | use anyhow::{Context, Result, bail}; | 6 | use anyhow::{Context, Result, bail}; |
| 4 | use ngit::{ | 7 | use 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 | ||
| 96 | fn 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 | |||
| 81 | fn parse_event_id(id: &str) -> Result<EventId> { | 111 | fn 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 | |||
| 315 | fn 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 @@ | |||
| 1 | use std::{collections::HashSet, io::Write, ops::Add}; | 1 | use std::{ |
| 2 | collections::HashSet, io::Write, ops::Add, | ||
| 3 | process::{Command, Stdio}, | ||
| 4 | }; | ||
| 2 | 5 | ||
| 3 | use anyhow::{Context, Result, bail}; | 6 | use anyhow::{Context, Result, bail}; |
| 4 | use ngit::{ | 7 | use 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 | ||
| 37 | fn 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)] |
| 35 | pub async fn launch(status: String, json: bool, id: Option<String>) -> Result<()> { | 53 | pub 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 | ||