( process: SandboxProcess, defaultRoot: string, )
| 41 | '!f() { echo "username=${GIT_ASKPASS_USER}"; echo "password=${GIT_ASKPASS_TOKEN}"; }; f' |
| 42 | |
| 43 | export function createExecBackedGit( |
| 44 | process: SandboxProcess, |
| 45 | defaultRoot: string, |
| 46 | ): SandboxGit { |
| 47 | const at = (dir?: string): string => { |
| 48 | const d = dir ?? defaultRoot |
| 49 | assertNoLeadingDash(d, 'dir') |
| 50 | return q(d) |
| 51 | } |
| 52 | |
| 53 | return { |
| 54 | clone: async ({ url, dir, ref, auth, depth }) => { |
| 55 | assertNoLeadingDash(url, 'url') |
| 56 | const target = dir ?? defaultRoot |
| 57 | assertNoLeadingDash(target, 'dir') |
| 58 | if (ref !== undefined) assertNoLeadingDash(ref, 'ref') |
| 59 | const refArg = ref ? `--branch ${q(ref)} ` : '' |
| 60 | const resolvedDepth = depth ?? 1 |
| 61 | // `depth` is interpolated unquoted into the command, so validate it the |
| 62 | // same way other positionals are guarded — a non-positive-integer (e.g. an |
| 63 | // untyped caller passing a string) must never reach the shell. |
| 64 | if ( |
| 65 | resolvedDepth !== 'full' && |
| 66 | (!Number.isInteger(resolvedDepth) || resolvedDepth <= 0) |
| 67 | ) { |
| 68 | throw new Error('git-exec: depth must be a positive integer or "full".') |
| 69 | } |
| 70 | const depthArg = |
| 71 | resolvedDepth === 'full' |
| 72 | ? '' |
| 73 | : `--depth ${resolvedDepth} --single-branch ` |
| 74 | |
| 75 | if (auth?.token) { |
| 76 | await process.exec( |
| 77 | `git -c credential.helper=${q(CREDENTIAL_HELPER)} clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`, |
| 78 | { |
| 79 | // Token lives only in the child env, never in argv. |
| 80 | env: { |
| 81 | GIT_ASKPASS_USER: auth.username ?? 'x-access-token', |
| 82 | GIT_ASKPASS_TOKEN: auth.token, |
| 83 | GIT_TERMINAL_PROMPT: '0', |
| 84 | }, |
| 85 | }, |
| 86 | ) |
| 87 | return |
| 88 | } |
| 89 | |
| 90 | await process.exec( |
| 91 | `git clone ${refArg}${depthArg}-- ${q(url)} ${q(target)}`, |
| 92 | ) |
| 93 | }, |
| 94 | status: async (dir) => |
| 95 | (await process.exec(`git -C ${at(dir)} status --porcelain`)).stdout, |
| 96 | add: async (paths, dir) => { |
| 97 | paths.forEach((p, i) => assertNoLeadingDash(p, `path[${i}]`)) |
| 98 | await process.exec(`git -C ${at(dir)} add -- ${paths.map(q).join(' ')}`) |
| 99 | }, |
| 100 | commit: async (message, dir) => { |
no test coverage detected