From 8577fbc5f4ca49450abdf85ffb03824d7fa46b8e Mon Sep 17 00:00:00 2001 From: garrytan Date: Mon, 2 Nov 2026 05:28:49 +0000 Subject: [PATCH] fix(ubicloud): pack takes an ignored subdirectory whole; correct the cmd_run tree-check comment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- scripts/ubicloud/ubi-runner.sh | 17 ++++++++++------- test/ubicloud-runner.test.ts | 5 ++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/ubicloud/ubi-runner.sh b/scripts/ubicloud/ubi-runner.sh index 5943a729c..f09922bb4 100755 --- a/scripts/ubicloud/ubi-runner.sh +++ b/scripts/ubicloud/ubi-runner.sh @@ -1116,11 +1116,14 @@ cmd_down() { # subdirectory of a work tree ships the same file set without a .git: git's # local transport fetches only from a repository's root, and the whole # repository's .git would show the VM every file outside the subdirectory as -# deleted (GST-935). +# deleted (GST-935). A directory git ignores has no such file set and is +# taken whole, like a non-git directory. cmd_pack() { local src tmp gitdir branch head entry src=$(cd "${1:-.}" && pwd) - if git -C "$src" rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -n "$(git -C "$src" rev-parse --show-prefix)" ]; then + if ! git -C "$src" rev-parse --is-inside-work-tree >/dev/null 2>&1 || git -C "$src" check-ignore -q .; then + COPYFILE_DISABLE=1 tar -C "$src" -czf - . + elif [ -n "$(git -C "$src" rev-parse --show-prefix)" ]; then ( cd "$src" export COPYFILE_DISABLE=1 @@ -1131,7 +1134,7 @@ cmd_pack() { | tar --null -T - -cf - \ | gzip -c ) - elif git -C "$src" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + else head=$(git -C "$src" rev-parse --verify -q HEAD) || die "pack: $src has no commits; the VM needs a HEAD to fingerprint" branch=$(git -C "$src" symbolic-ref -q --short HEAD || true) tmp=$(mktemp -d) @@ -1182,8 +1185,6 @@ cmd_pack() { && gzip -c "$tmp/files.tar" ) rm -rf "$tmp"; trap - EXIT - else - COPYFILE_DISABLE=1 tar -C "$src" -czf - . fi } @@ -1408,8 +1409,10 @@ cmd_run() { for n in ${passes[@]+"${passes[@]}"}; do printf 'export %s=%q\n' "$n" "${!n}"; done; } \ | remote "$name" 'umask 077 && cat > ~/.ubirun-env' cmd_sync "$name" "$src" "$dest" - # A subdirectory of a checkout uploads the whole .git with only its own - # files, so its tree is compared VM to VM (tiers) but not to this side. + # A subdirectory of a checkout ships without a .git, and its local tree + # (a scratch repository, blind to the parent's ignore rules) can count + # files pack leaves out, so it is compared VM to VM (tiers) but not to + # this side. if [ -z "$(git -C "$src" rev-parse --show-prefix 2>/dev/null)" ]; then vmt=$(vm_tree "$name" "$qdest") || vmt=unavailable if [ "$vmt" != "$TREE" ]; then diff --git a/test/ubicloud-runner.test.ts b/test/ubicloud-runner.test.ts index 98fb5027a..2a77155b6 100644 --- a/test/ubicloud-runner.test.ts +++ b/test/ubicloud-runner.test.ts @@ -385,7 +385,7 @@ test('pack ships a linked worktree with a self-contained .git on the checked-out const d = mkdtempSync(join(tmpdir(), 'ubi-pack-subdir-')); try { const sh = (cmd: string, cwd: string) => { const r = Bun.spawnSync(['bash', '-c', cmd], { cwd, timeout: 30_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, `${cmd}\n${r.stderr.toString()}`).toBe(0); return r.stdout.toString(); }; - sh('git init -q repo && cd repo && echo top > top && mkdir -p sub/nested && echo a > sub/a && echo b > sub/nested/b && echo g > sub/gone && echo "*.log" > sub/.gitignore && git add -A && git commit -qm one && git worktree add -q ../wt', d); + sh('git init -q repo && cd repo && echo top > top && echo ign/ > .gitignore && mkdir -p sub/nested ign && echo i > ign/i && echo a > sub/a && echo b > sub/nested/b && echo g > sub/gone && echo "*.log" > sub/.gitignore && git add -A && git commit -qm one && git worktree add -q ../wt', d); const tree = (dir: string) => sh('find . -type f | sort', dir).split('\n').filter(Boolean); for (const checkout of ['repo', 'wt']) { const sub = join(d, checkout, 'sub'); @@ -395,6 +395,9 @@ test('pack ships a linked worktree with a self-contained .git on the checked-out expect(tree(vm)).toEqual(['./.gitignore', './a', './nested/b', './new']); expect(readFileSync(join(vm, 'a'), 'utf8')).toBe('edited\n'); } + // A directory git ignores lists no files, so it is taken whole instead of shipping an empty archive. + sh(`mkdir -p repo/ign/deep && echo d > repo/ign/deep/d && mkdir vm-ign && bash ${JSON.stringify(join(DIR, 'ubi-runner.sh'))} pack repo/ign | tar -xzf - -C vm-ign`, d); + expect(tree(join(d, 'vm-ign'))).toEqual(['./deep/d', './i']); } finally { rmSync(d, { recursive: true, force: true }); } }, 60_000);