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 /src/bin/ngit | |
| 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.
Diffstat (limited to 'src/bin/ngit')
| -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 | ||