diff --git a/app/git.js b/app/git.js index acaa58e..38c5582 100644 --- a/app/git.js +++ b/app/git.js @@ -62,11 +62,22 @@ function withAskpass(workspace, fn) { const askpassScript = path.join(workspace, '.git-askpass.sh'); fs.writeFileSync(askpassScript, '#!/bin/sh\necho "$GIT_TOKEN"\n', { mode: 0o700 }); const credEnv = { ...process.env, GIT_ASKPASS: askpassScript, GIT_USERNAME: 'x-token', GIT_TOKEN: GITEA_TOKEN }; + const cleanup = () => { try { fs.unlinkSync(askpassScript); } catch {} }; + let result; try { - return fn(credEnv); - } finally { - try { fs.unlinkSync(askpassScript); } catch {} + result = fn(credEnv); + } catch (e) { + cleanup(); + throw e; } + // Defer cleanup until an async callback settles, otherwise the askpass script + // is deleted at the first `await` and later network ops (e.g. git push) fail + // with "cannot exec .git-askpass.sh". Sync callbacks clean up immediately. + if (result && typeof result.then === 'function') { + return result.finally(cleanup); + } + cleanup(); + return result; } function readGitOutput(run, args, cwd, env) { diff --git a/app/git.test.js b/app/git.test.js index 0ebb723..5d92566 100644 --- a/app/git.test.js +++ b/app/git.test.js @@ -93,6 +93,18 @@ describe('commitAndPush', () => { } }); + it('keeps the askpass script present while the network push runs', async () => { + let askpassExistsAtPush = null; + const spawn = makeSpawn({ + push: (_args, opts) => { + askpassExistsAtPush = !!(opts?.env?.GIT_ASKPASS && fs.existsSync(opts.env.GIT_ASKPASS)); + return { status: 0, stdout: '', stderr: '', error: null }; + }, + }); + await commitAndPush(workspace, path.join(workspace, 'repo'), spawn, sourceRoot); + assert.equal(askpassExistsAtPush, true, 'askpass script must still exist when git push runs'); + }); + it('cleans up askpass script after successful run', async () => { await commitAndPush(workspace, path.join(workspace, 'repo'), makeSpawn(), sourceRoot); const leftover = fs.readdirSync(workspace).filter(f => f.endsWith('.git-askpass.sh'));