diff --git a/scripts/ubicloud/ubi-runner.sh b/scripts/ubicloud/ubi-runner.sh index 4f0fba5f5..19fb9435e 100755 --- a/scripts/ubicloud/ubi-runner.sh +++ b/scripts/ubicloud/ubi-runner.sh @@ -627,15 +627,16 @@ cmd_pack() { done < <(git -C "$gitdir" config --local --null --get-regexp '^(remote|branch)\.' 2>/dev/null || true) # The checkout's index rides along so the VM's status matches, except a # split index, whose shared half lives only in the original git dir. The - # blobs staged against HEAD are behind no ref, so they travel as a pack of + # index's blobs that HEAD's tree lacks (staged, intent-to-add, the stages + # of an unmerged entry) are behind no ref, so they travel as a pack of # their own; without them the VM's write-tree (gstack-wtree) cannot read a # staged file. if [ "$(git -C "$src" config --bool core.splitIndex 2>/dev/null)" != true ]; then cp "$(cd "$src" && cd "$(dirname "$(git rev-parse --git-path index)")" && pwd)/index" "$tmp/.git/index" # Read the copy, not the live index, so the pack matches it; a gitlink's # sha (mode 160000) is a submodule commit this object store lacks. - GIT_INDEX_FILE="$tmp/.git/index" git -C "$src" diff --cached --raw --no-abbrev HEAD \ - | awk '$2 != "160000" && $4 !~ /^0+$/ {print $4}' \ + GIT_INDEX_FILE="$tmp/.git/index" git -C "$src" ls-files -s | awk '$1 != "160000" {print $2}' | sort -u \ + | comm -23 - <(git -C "$src" ls-tree -r HEAD | awk '{print $3}' | sort -u) \ | git -C "$src" pack-objects -q --stdout \ | git -C "$tmp/.git" unpack-objects -q \ || die "pack: could not snapshot the staged blobs of $src" diff --git a/test/ubicloud-runner.test.ts b/test/ubicloud-runner.test.ts index 6122d0bc9..8adacf278 100644 --- a/test/ubicloud-runner.test.ts +++ b/test/ubicloud-runner.test.ts @@ -315,7 +315,7 @@ test('pack ships a linked worktree with a self-contained .git on the checked-out const d = mkdtempSync(join(tmpdir(), 'ubi-pack-live-')); try { const sh = (cmd: string, cwd: string) => { const r = Bun.spawnSync(['bash', '-c', cmd], { cwd, timeout: 60_000, env: { ...process.env, GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@t', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@t' } }); expect(r.exitCode, r.stderr.toString()).toBe(0); return r.stdout.toString(); }; - sh('git init -q --bare origin.git && git clone -q origin.git src 2>/dev/null && cd src && echo a > a && git add a && git commit -qm one && git push -q origin HEAD:main && echo b > b && git add b && git commit -qm two && git push -q origin HEAD:main && git checkout -q --detach && echo c > c && echo s > staged && git add staged', d); + sh('git init -q --bare origin.git && git clone -q origin.git src 2>/dev/null && cd src && echo a > a && git add a && git commit -qm one && git push -q origin HEAD:main && echo b > b && git add b && git commit -qm two && git push -q origin HEAD:main && git checkout -q --detach && echo c > c && echo s > staged && git add staged && echo i > inta && git add -N inta', d); const src = join(d, 'src'); const pack = `bash ${JSON.stringify(join(DIR, 'ubi-runner.sh'))} pack .`; // Twenty seconds of index.lock, worktree and ref churn in the checkout while pack runs three times. @@ -328,8 +328,8 @@ test('pack ships a linked worktree with a self-contained .git on the checked-out expect(lstatSync(join(vm, '.git')).isDirectory()).toBe(true); const head = sh('git rev-parse HEAD', src).trim(); expect(sh('git rev-parse HEAD && git rev-parse --abbrev-ref HEAD && git rev-parse origin/main && git config --get remote.origin.url && git status --short', vm).split('\n').filter(Boolean)) - .toEqual([head, `detached-${head.slice(0, 12)}`, head, join(d, 'origin.git'), 'A staged', '?? c']); - // The staged blob is behind no ref; the snapshot carries it, so the VM can read and write-tree it. + .toEqual([head, `detached-${head.slice(0, 12)}`, head, join(d, 'origin.git'), ' A inta', 'A staged', '?? c']); + // The staged blob and the intent-to-add entry's empty blob are behind no ref; the snapshot carries them, so the VM can read and write-tree them. expect(sh('git fsck --no-progress 2>&1; git diff --cached --name-only', vm).trim()).toBe('staged'); const wtree = (cwd: string) => sh(`bash ${JSON.stringify(join(ROOT, 'bin/gstack-wtree'))}`, cwd).trim(); expect(wtree(vm)).toBe(wtree(src));