Remote defaults lead with their own rev, like upstreams already do

Round seven: a remote-advertised default candidate tried the local
branch first, so a stale local main outranked the fresher origin/main
the symref points at and refilled changedFiles with the divergence.
The candidate now leads with the advertised remote rev, mirroring the
upstream candidate's reasoning. Failing-first test: local main forced
two commits behind the remote default, feature delta stays clean.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-26 18:56:33 -07:00
co-authored by Claude Code
parent a470fc777a
commit e2c1c43ee7
2 changed files with 30 additions and 1 deletions
+4 -1
View File
@@ -169,7 +169,10 @@ function gitSignals(cwd) {
// main; an existing develop therefore outranks the remote default. This
// is #302's own repro shape, and repos without develop are unaffected.
addCandidate('develop', revsFor('develop'));
for (const head of remoteHeads) addCandidate(head.name, revsFor(head.name));
// A remote's advertised default prefers its own remote-tracking rev over
// a possibly stale local checkout of the same name, for the same reason
// the upstream candidate leads with its rev.
for (const head of remoteHeads) addCandidate(head.name, [...new Set([head.rev, ...revsFor(head.name)])]);
for (const name of ['main', 'master']) addCandidate(name, revsFor(name));
for (const c of candidates) {
const rev = c.revs.find((r) => run(['rev-parse', '--verify', '--quiet', r]) !== null);
+26
View File
@@ -551,6 +551,32 @@ describe('gatherSignals', () => {
assert.deepEqual(s.git.changedFiles, ['src/Hero.tsx']);
});
it('a remote-advertised default outranks a stale local checkout (#302)', async () => {
const { execFileSync } = await import('node:child_process');
const git = (...args) => execFileSync('git', args, { cwd: scratch, stdio: 'ignore' });
git('init', '-q', '-b', 'main');
git('config', 'user.email', 't@example.com');
git('config', 'user.name', 'Test');
write('src/base.css', 'a{}\n');
git('add', '.');
git('commit', '-qm', 'A');
write('src/extra.css', 'b{}\n');
git('add', '.');
git('commit', '-qm', 'A2');
git('update-ref', 'refs/remotes/origin/main', 'HEAD');
git('symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/main');
git('checkout', '-q', '-b', 'feature/s');
write('src/Hero.tsx', 'export const Hero = () => null;\n');
git('add', '.');
git('commit', '-qm', 'feature work');
// The local main checkout is stale (still at A); the remote default is
// at A2. Diffing against the stale local would drag src/extra.css in.
git('branch', '-f', 'main', 'HEAD~2');
const s = await gatherSignals(scratch);
assert.equal(s.git.base, 'main');
assert.deepEqual(s.git.changedFiles, ['src/Hero.tsx']);
});
it('never diffs one integration branch against another (#302)', async () => {
const { execFileSync } = await import('node:child_process');
const git = (...args) => execFileSync('git', args, { cwd: scratch, stdio: 'ignore' });