Skip to content

Commit c65df0d

Browse files
authored
🤖 fix: fetch when remote ref moves (#947)
## Summary - fetch when remote ref changes even if object already present locally - keep fetch skip when tracking ref matches remote - add regression test for remote ref move with existing object ## Testing - make static-check - bun test src/common/utils/git/gitStatus.fetch.test.ts _Generated with `mux`_.
1 parent 5bc5ae3 commit c65df0d

File tree

2 files changed

+69
-4
lines changed

2 files changed

+69
-4
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { mkdtemp, rm, writeFile } from "fs/promises";
2+
import * as path from "path";
3+
import * as os from "os";
4+
import { execSync } from "child_process";
5+
6+
import { GIT_FETCH_SCRIPT } from "./gitStatus";
7+
8+
describe("GIT_FETCH_SCRIPT", () => {
9+
test("fetches when remote ref moves to a commit already present locally", async () => {
10+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "mux-git-fetch-"));
11+
const originDir = path.join(tempDir, "origin.git");
12+
const workspaceDir = path.join(tempDir, "workspace");
13+
14+
const run = (cmd: string, cwd?: string) =>
15+
execSync(cmd, { cwd, stdio: "pipe" }).toString().trim();
16+
17+
try {
18+
// Initialize bare remote and clone it
19+
run(`git init --bare ${originDir}`);
20+
run(`git clone ${originDir} ${workspaceDir}`);
21+
22+
// Basic git identity configuration
23+
run('git config user.email "test@example.com"', workspaceDir);
24+
run('git config user.name "Test User"', workspaceDir);
25+
run("git config commit.gpgsign false", workspaceDir);
26+
27+
// Seed main with an initial commit
28+
await writeFile(path.join(workspaceDir, "README.md"), "init\n");
29+
run("git add README.md", workspaceDir);
30+
run('git commit -m "init"', workspaceDir);
31+
run("git branch -M main", workspaceDir);
32+
run("git push -u origin main", workspaceDir);
33+
34+
// Ensure remote HEAD points to main for deterministic primary branch detection
35+
run("git symbolic-ref HEAD refs/heads/main", originDir);
36+
37+
// Create a commit on a feature branch (object exists locally)
38+
run("git checkout -b feature", workspaceDir);
39+
await writeFile(path.join(workspaceDir, "feature.txt"), "feature\n");
40+
run("git add feature.txt", workspaceDir);
41+
run('git commit -m "feature"', workspaceDir);
42+
const featureSha = run("git rev-parse feature", workspaceDir);
43+
44+
// Push the feature branch so the remote has the object but main stays old
45+
run("git push origin feature", workspaceDir);
46+
47+
// Move remote main to the feature commit without updating local tracking ref
48+
run(`git update-ref refs/heads/main ${featureSha}`, originDir);
49+
50+
const localBefore = run("git rev-parse origin/main", workspaceDir);
51+
expect(localBefore).not.toBe(featureSha);
52+
53+
// Run the optimized fetch script (should update origin/main)
54+
run(GIT_FETCH_SCRIPT, workspaceDir);
55+
56+
const localAfter = run("git rev-parse origin/main", workspaceDir);
57+
expect(localAfter).toBe(featureSha);
58+
} finally {
59+
await rm(tempDir, { recursive: true, force: true });
60+
}
61+
}, 20000);
62+
});

src/common/utils/git/gitStatus.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,16 @@ if [ -z "$REMOTE_SHA" ]; then
117117
exit 0
118118
fi
119119
120-
# Check if SHA exists locally (no lock)
121-
if git cat-file -e "$REMOTE_SHA" 2>/dev/null; then
122-
echo "SKIP: Remote SHA already local"
120+
# Check current local remote-tracking ref (no lock)
121+
LOCAL_SHA=$(git rev-parse --verify "refs/remotes/origin/$PRIMARY_BRANCH" 2>/dev/null || echo "")
122+
123+
# If local tracking ref already matches remote, skip fetch
124+
if [ "$LOCAL_SHA" = "$REMOTE_SHA" ]; then
125+
echo "SKIP: Remote SHA already fetched"
123126
exit 0
124127
fi
125128
126-
# Remote has new commits we don't have - fetch them
129+
# Remote has new commits or ref moved - fetch updates
127130
git -c protocol.version=2 \\
128131
-c fetch.negotiationAlgorithm=skipping \\
129132
fetch origin \\

0 commit comments

Comments
 (0)