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-26 16:35:59 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-26 16:35:59 +0000
commit01aeb2a3265bcafa162987c85dd281981770bba7 (patch)
tree76fe39f85d24a68586bdc282088b11474e3751d4
parent54f0542a27e4ccab459d87283e4668d865ddd2bb (diff)
fix: correct merge-base in PR events from git push of pr/ branch
When pushing a pr/ branch, the ahead slice is reversed by callers before being passed to generate_patches_or_pr_event_or_pr_updates, making it oldest-first. The tip/first_commit assignments were backwards (using first()/last() as if the slice were youngest-first), so the merge-base was computed as the parent of the PR tip rather than the parent of the oldest commit. Multi-commit PRs therefore showed only 1 commit when applied via ngit apply. Adds an integration test that pushes a two-commit large-file PR branch and asserts the merge-base tag equals the main branch tip.
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/bin/git_remote_nostr/push.rs4
-rw-r--r--tests/git_remote_nostr/push.rs99
3 files changed, 102 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31716ff..d4b8083 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14 14
15### Fixed 15### Fixed
16 16
17- `merge-base` tag in PR events generated by `git push` of a `pr/` branch was set to the parent of the PR tip instead of the actual base commit; multi-commit PRs showed only 1 commit when applied via `ngit apply`
17- `git-remote-nostr` list now advertises the newest state event whose OIDs are all confirmed present on a git server or locally, rather than unconditionally using the latest nostr state event; this prevents catastrophic fetch/clone failures when a state event was published before the corresponding git push completed 18- `git-remote-nostr` list now advertises the newest state event whose OIDs are all confirmed present on a git server or locally, rather than unconditionally using the latest nostr state event; this prevents catastrophic fetch/clone failures when a state event was published before the corresponding git push completed
18- Tag tracking refs written with wrong path (`refs/remotes/origin/refs/tags/v1.0.0` instead of `refs/remotes/origin/v1.0.0`) after a push via `git-remote-nostr`, causing `ngit sync` to fail with "src refspec does not match any existing object" when syncing tags 19- Tag tracking refs written with wrong path (`refs/remotes/origin/refs/tags/v1.0.0` instead of `refs/remotes/origin/v1.0.0`) after a push via `git-remote-nostr`, causing `ngit sync` to fail with "src refspec does not match any existing object" when syncing tags
19- Annotated tag tracking refs stored with the peeled commit OID instead of the tag object OID after a push via `git-remote-nostr`; this caused `ngit sync` to push the wrong object to grasp servers, which rejected it because the nostr state event referenced the tag object OID 20- Annotated tag tracking refs stored with the peeled commit OID instead of the tag object OID after a push via `git-remote-nostr`; this caused `ngit sync` to push the wrong object to grasp servers, which rejected it because the nostr state event referenced the tag object OID
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index bd1188b..870f22d 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -623,8 +623,8 @@ async fn generate_patches_or_pr_event_or_pr_updates(
623 let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead); 623 let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead);
624 624
625 if use_pr { 625 if use_pr {
626 let tip = ahead.first().context("no commits")?; // ahead is youngest first 626 let tip = ahead.last().context("no commits")?; // ahead is oldest first (callers reverse it)
627 let first_commit = ahead.last().context("no commits")?; 627 let first_commit = ahead.first().context("no commits")?;
628 let push_options_refs: Vec<&str> = 628 let push_options_refs: Vec<&str> =
629 git_server_push_options.iter().map(String::as_str).collect(); 629 git_server_push_options.iter().map(String::as_str).collect();
630 select_servers_push_refs_and_generate_pr_or_pr_update_event( 630 select_servers_push_refs_and_generate_pr_or_pr_update_event(
diff --git a/tests/git_remote_nostr/push.rs b/tests/git_remote_nostr/push.rs
index 6467dec..254208f 100644
--- a/tests/git_remote_nostr/push.rs
+++ b/tests/git_remote_nostr/push.rs
@@ -2122,6 +2122,105 @@ async fn force_push_to_existing_patch_series_with_title_description_options_crea
2122 Ok(()) 2122 Ok(())
2123} 2123}
2124 2124
2125#[tokio::test]
2126#[serial]
2127async fn push_new_pr_branch_with_multiple_commits_sets_merge_base_to_main_tip() -> Result<()> {
2128 let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?;
2129 let _source_path = source_git_repo.dir.to_str().unwrap().to_string();
2130
2131 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
2132 Relay::new(8051, None, None),
2133 Relay::new(8052, None, None),
2134 Relay::new(8053, None, None),
2135 Relay::new(8055, None, None),
2136 Relay::new(8056, None, None),
2137 Relay::new(8057, None, None),
2138 );
2139 r51.events = events.clone();
2140 r55.events = events.clone();
2141
2142 #[allow(clippy::mutable_key_type)]
2143 let before = r55.events.iter().cloned().collect::<HashSet<Event>>();
2144 let branch_name = "pr/multi-commit-pr";
2145
2146 let cli_tester_handle = std::thread::spawn(move || -> Result<String> {
2147 let mut git_repo = clone_git_repo_with_nostr_url()?;
2148 git_repo.delete_dir_on_drop = false;
2149
2150 // Record the main tip — this should become the merge-base in the PR event
2151 let main_tip = git_repo.get_tip_of_local_branch("main")?.to_string();
2152
2153 git_repo.create_branch(branch_name)?;
2154 git_repo.checkout(branch_name)?;
2155
2156 // Add two large commits so the push is forced into PR (not patch) mode
2157 let large_content = "x".repeat(70 * 1024);
2158 std::fs::write(git_repo.dir.join("large1.txt"), &large_content)?;
2159 git_repo.stage_and_commit("add large1")?;
2160
2161 std::fs::write(git_repo.dir.join("large2.txt"), &large_content)?;
2162 git_repo.stage_and_commit("add large2")?;
2163
2164 let mut p = CliTester::new_git_with_remote_helper_from_dir(
2165 &git_repo.dir,
2166 ["push", "-u", "origin", branch_name],
2167 );
2168 cli_expect_nostr_fetch(&mut p)?;
2169 p.expect("git servers: listing refs...\r\n")?;
2170 p.expect_eventually_and_print(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?;
2171 let output = p.expect_end_eventually()?;
2172
2173 for p in [51, 52, 53, 55, 56, 57] {
2174 relay::shutdown_relay(8000 + p)?;
2175 }
2176
2177 Ok(format!("{main_tip}\n{output}"))
2178 });
2179 let _ = join!(
2180 r51.listen_until_close(),
2181 r52.listen_until_close(),
2182 r53.listen_until_close(),
2183 r55.listen_until_close(),
2184 r56.listen_until_close(),
2185 r57.listen_until_close(),
2186 );
2187
2188 let result = cli_tester_handle.join().unwrap()?;
2189 let (main_tip, _output) = result.split_once('\n').unwrap();
2190
2191 let new_events = r55
2192 .events
2193 .iter()
2194 .cloned()
2195 .collect::<HashSet<Event>>()
2196 .difference(&before)
2197 .cloned()
2198 .collect::<Vec<Event>>();
2199 assert_eq!(new_events.len(), 1, "should create exactly 1 PR event");
2200
2201 let pr_event = new_events.first().unwrap();
2202 assert!(
2203 pr_event.kind.eq(&KIND_PULL_REQUEST),
2204 "event should be a PR event"
2205 );
2206
2207 let merge_base_tag = pr_event
2208 .tags
2209 .iter()
2210 .find(|t| t.as_slice()[0].eq("merge-base"));
2211 assert!(
2212 merge_base_tag.is_some(),
2213 "PR event should have a merge-base tag"
2214 );
2215 assert_eq!(
2216 merge_base_tag.unwrap().as_slice()[1],
2217 main_tip,
2218 "merge-base should be the main branch tip at the time of branching, not the parent of the PR tip"
2219 );
2220
2221 Ok(())
2222}
2223
2125mod push_from_another_maintainer { 2224mod push_from_another_maintainer {
2126 2225
2127 // TODO that has issued announcement 2226 // TODO that has issued announcement