diff --git a/src/merge.rs b/src/merge.rs index 265dc32..690a15a 100644 --- a/src/merge.rs +++ b/src/merge.rs @@ -341,6 +341,9 @@ fn reconcile_namespace_one( let new_oid = repo.commit(None, &sig, &sig, &msg, &repo.find_tree(tree_oid)?, &parents)?; repo.reference(&namespace::refname(name), new_oid, true, &msg)?; + if !renumbers.is_empty() { + adjust_renumbered_internal_links(repo, &local_ns, &renumbers)?; + } Ok(Some(NamespaceReconciliation { namespace: name.to_string(), renumbers, @@ -349,6 +352,32 @@ fn reconcile_namespace_one( } } +fn adjust_renumbered_internal_links( + repo: &Repository, + local_ns: &Namespace, + renumbers: &[(u32, u32)], +) -> Result<()> { + let mut seen = HashSet::new(); + for stable in local_ns.mapping.values() { + if !seen.insert(stable.clone()) { + continue; + } + let Some(mut task) = object::read(repo, stable)? else { + continue; + }; + let original = task.content.clone(); + for (old, new) in renumbers { + task.content = task + .content + .replace(&format!("[[tsk-{old}]]"), &format!("[[tsk-{new}]]")); + } + if task.content != original { + object::update(repo, stable, &task, "rewrite-renumbered-links")?; + } + } + Ok(()) +} + /// One queue's reconciliation outcome at pull time. Currently we only /// surface that a non-trivial merge happened; counts/diffs could be /// added later if useful. diff --git a/tests/multi_user.rs b/tests/multi_user.rs index 49899c1..21d4d18 100644 --- a/tests/multi_user.rs +++ b/tests/multi_user.rs @@ -506,3 +506,34 @@ fn namespace_collision_renumbers_local_on_pull() { "tsk-2 must point at bob's task after renumber: {show2}" ); } + +#[test] +fn namespace_collision_rewrites_local_internal_links() { + let (_dir, alice, bob) = setup_two_clones(); + + // Bob's second task links to his local tsk-1 before the pull. + tsk_ok(&bob, &["push", "bob's task"]); + tsk_ok(&bob, &["push", "blocked by bob\n\nrelated to [[tsk-1]]"]); + + // Alice independently claims tsk-1 and pushes it to origin. + tsk_ok(&alice, &["push", "alice's task"]); + tsk_ok(&alice, &["git-push"]); + + // Bob's original tsk-1 is renumbered past his local-only tsk-2, so the + // link in tsk-2 must follow it to tsk-3. + let pull_out = tsk_ok(&bob, &["git-pull"]); + assert!( + pull_out.contains("tsk-1 → tsk-3") || pull_out.contains("tsk-1 \u{2192} tsk-3"), + "expected renumber message in pull output, got: {pull_out}" + ); + + let linked = tsk_ok(&bob, &["show", "-T", "tsk-2", "-R"]); + assert!( + linked.contains("[[tsk-3]]"), + "local link must track bob's renumbered task: {linked}" + ); + assert!( + !linked.contains("[[tsk-1]]"), + "local link must not keep pointing at alice's task: {linked}" + ); +}