From c85ca81767d838797f6a1ab6651e9864c3f961c1 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 12 Feb 2026 12:25:10 +0000 Subject: 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. --- src/bin/ngit/sub_commands/apply.rs | 34 +++++++++++- src/bin/ngit/sub_commands/checkout.rs | 102 +++++++++++++++++++++++++++++++--- src/bin/ngit/sub_commands/list.rs | 44 ++++++++++++++- 3 files changed, 165 insertions(+), 15 deletions(-) (limited to 'src/bin') 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 @@ -use std::io::Write; +use std::{ + io::Write, + process::{Command, Stdio}, +}; use anyhow::{Context, Result, bail}; 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; use nostr::nips::nip19::Nip19; use nostr_sdk::{EventId, FromBech32}; -use crate::client::{Client, Connect, get_repo_ref_from_cache}; +use crate::client::{Client, Connect, fetching_with_report, get_repo_ref_from_cache}; use crate::git::{Repo, RepoActions}; use crate::repo_ref::get_repo_coordinates_when_remote_unknown; +fn run_git_fetch(remote_name: &str) -> Result<()> { + println!("fetching from {remote_name}..."); + let exit_status = Command::new("git") + .args(["fetch", remote_name]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .context("failed to run git fetch")?; + + if !exit_status.success() { + bail!("git fetch {remote_name} exited with error: {exit_status}"); + } + Ok(()) +} + pub async fn launch(id: &str, stdout: bool) -> Result<()> { let event_id = parse_event_id(id)?; @@ -22,7 +40,17 @@ pub async fn launch(id: &str, stdout: bool) -> Result<()> { let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; - crate::client::fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + let nostr_remote = git_repo + .get_first_nostr_remote_when_in_ngit_binary() + .await + .ok() + .flatten(); + + if let Some((remote_name, _)) = &nostr_remote { + run_git_fetch(remote_name)?; + } else { + fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + } let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; 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 @@ -use std::collections::HashSet; +use std::{ + collections::HashSet, + process::{Command, Stdio}, +}; use anyhow::{Context, Result, bail}; use ngit::{ @@ -33,7 +36,17 @@ pub async fn launch(id: &str) -> Result<()> { let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; - fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + let nostr_remote = git_repo + .get_first_nostr_remote_when_in_ngit_binary() + .await + .ok() + .flatten(); + + if let Some((remote_name, _)) = &nostr_remote { + run_git_fetch(remote_name)?; + } else { + fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + } let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; @@ -68,16 +81,33 @@ pub async fn launch(id: &str) -> Result<()> { &repo_ref, &cover_letter, &most_recent_proposal_patch_chain_or_pr_or_pr_update, + nostr_remote.as_ref().map(|(name, _)| name.as_str()), ) } else { checkout_patch( &git_repo, &cover_letter, &most_recent_proposal_patch_chain_or_pr_or_pr_update, + nostr_remote.as_ref().map(|(name, _)| name.as_str()), ) } } +fn run_git_fetch(remote_name: &str) -> Result<()> { + println!("fetching from {remote_name}..."); + let exit_status = Command::new("git") + .args(["fetch", remote_name]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .context("failed to run git fetch")?; + + if !exit_status.success() { + bail!("git fetch {remote_name} exited with error: {exit_status}"); + } + Ok(()) +} + fn parse_event_id(id: &str) -> Result { if let Ok(nip19) = Nip19::from_bech32(id) { match nip19 { @@ -97,15 +127,15 @@ fn checkout_pr( repo_ref: &RepoRef, cover_letter: &crate::git_events::CoverLetter, most_recent_proposal_patch_chain_or_pr_or_pr_update: &[nostr::Event], + nostr_remote_name: Option<&str>, ) -> Result<()> { let branch_name = cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?; - let local_branch_tip = git_repo.get_tip_of_branch(&branch_name).ok(); let proposal_tip_event = most_recent_proposal_patch_chain_or_pr_or_pr_update .first() .context("most_recent_proposal_patch_chain_or_pr_or_pr_update will always contain an event with c tag")?; let proposal_tip = tag_value(proposal_tip_event, "c")?; - if let Some(local_branch_tip) = local_branch_tip { + if let Ok(local_branch_tip) = git_repo.get_tip_of_branch(&branch_name) { git_repo .checkout(&branch_name) .context("cannot checkout existing proposal branch")?; @@ -121,6 +151,15 @@ fn checkout_pr( } } + if let Some(remote_name) = nostr_remote_name { + let remote_branch = format!("{remote_name}/{branch_name}"); + if git_repo.get_tip_of_branch(&remote_branch).is_ok() { + checkout_remote_branch_with_tracking(git_repo, remote_name, &branch_name)?; + println!("checked out proposal branch '{branch_name}' with tracking to {remote_name}"); + return Ok(()); + } + } + fetch_oid_for_from_servers_for_pr( &proposal_tip, git_repo, @@ -129,11 +168,7 @@ fn checkout_pr( )?; git_repo.create_branch_at_commit(&branch_name, &proposal_tip)?; git_repo.checkout(&branch_name)?; - if local_branch_tip.is_some() { - println!("checked out proposal branch and pulled updates '{branch_name}'"); - } else { - println!("created and checked out proposal branch '{branch_name}'"); - } + println!("created and checked out proposal branch '{branch_name}'"); Ok(()) } @@ -141,6 +176,7 @@ fn checkout_patch( git_repo: &Repo, cover_letter: &crate::git_events::CoverLetter, most_recent_proposal_patch_chain_or_pr_or_pr_update: &[nostr::Event], + nostr_remote_name: Option<&str>, ) -> Result<()> { let no_support_for_patches_as_branch = most_recent_proposal_patch_chain_or_pr_or_pr_update .iter() @@ -184,6 +220,14 @@ fn checkout_patch( .any(|n| n.eq(&branch_name)); if !branch_exists { + if let Some(remote_name) = nostr_remote_name { + let remote_branch = format!("{remote_name}/{branch_name}"); + if git_repo.get_tip_of_branch(&remote_branch).is_ok() { + checkout_remote_branch_with_tracking(git_repo, remote_name, &branch_name)?; + println!("checked out proposal branch '{branch_name}' with tracking to {remote_name}"); + return Ok(()); + } + } let _ = git_repo .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain_or_pr_or_pr_update.to_vec()) .context("failed to apply patch chain")?; @@ -267,3 +311,43 @@ fn fetch_oid_for_from_servers_for_pr( } Ok(()) } + +fn checkout_remote_branch_with_tracking( + git_repo: &Repo, + remote_name: &str, + branch_name: &str, +) -> Result<()> { + let remote_branch_ref = format!("refs/remotes/{remote_name}/{branch_name}"); + let remote_branch = git_repo + .git_repo + .find_reference(&remote_branch_ref) + .context(format!("failed to find remote branch {remote_branch_ref}"))?; + let commit = remote_branch + .peel_to_commit() + .context("failed to peel remote branch to commit")?; + + let mut local_branch = git_repo + .git_repo + .branch(branch_name, &commit, false) + .context("failed to create local branch")?; + + local_branch + .set_upstream(Some(&format!("{remote_name}/{branch_name}"))) + .context("failed to set upstream tracking")?; + + let local_branch_ref = local_branch.into_reference(); + let local_branch_ref_name = local_branch_ref + .name() + .context("failed to get local branch ref name")?; + + git_repo + .git_repo + .set_head(local_branch_ref_name) + .context("failed to set head to local branch")?; + git_repo + .git_repo + .checkout_head(None) + .context("failed to checkout head")?; + + Ok(()) +} 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 @@ -use std::{collections::HashSet, io::Write, ops::Add}; +use std::{ + collections::HashSet, io::Write, ops::Add, + process::{Command, Stdio}, +}; use anyhow::{Context, Result, bail}; use ngit::{ @@ -31,6 +34,21 @@ use crate::{ repo_ref::get_repo_coordinates_when_remote_unknown, }; +fn run_git_fetch(remote_name: &str) -> Result<()> { + println!("fetching from {remote_name}..."); + let exit_status = Command::new("git") + .args(["fetch", remote_name]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .context("failed to run git fetch")?; + + if !exit_status.success() { + bail!("git fetch {remote_name} exited with error: {exit_status}"); + } + Ok(()) +} + #[allow(clippy::too_many_lines)] pub async fn launch(status: String, json: bool, id: Option) -> Result<()> { if std::env::var("NGIT_INTERACTIVE_MODE").is_ok() { @@ -44,7 +62,17 @@ pub async fn launch(status: String, json: bool, id: Option) -> Result<() let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; - fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + let nostr_remote = git_repo + .get_first_nostr_remote_when_in_ngit_binary() + .await + .ok() + .flatten(); + + if let Some((remote_name, _)) = &nostr_remote { + run_git_fetch(remote_name)?; + } else { + fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + } let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; @@ -285,7 +313,17 @@ async fn launch_interactive() -> Result<()> { let repo_coordinates = get_repo_coordinates_when_remote_unknown(&git_repo, &client).await?; - fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + let nostr_remote = git_repo + .get_first_nostr_remote_when_in_ngit_binary() + .await + .ok() + .flatten(); + + if let Some((remote_name, _)) = &nostr_remote { + run_git_fetch(remote_name)?; + } else { + fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; + } let repo_ref = get_repo_ref_from_cache(Some(git_repo_path), &repo_coordinates).await?; -- cgit v1.2.3