fix: Handle push to checked-out branch in local non-bare repos

git refuses to push to a branch that is currently checked out in a
non-bare repository. When the clone's origin is the user's local repo,
this blocks merge_and_push entirely.

On "branch is currently checked out" error, temporarily set
receive.denyCurrentBranch=updateInstead on the remote and retry.
This uses git's built-in mechanism to update the working tree safely
(rejects if dirty).
This commit is contained in:
Lukas May
2026-03-06 12:37:21 +01:00
parent 940b0f8ed2
commit a86a373d42

View File

@@ -6,7 +6,7 @@
* on project clones without requiring a worktree.
*/
import { join } from 'node:path';
import { join, resolve } from 'node:path';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { simpleGit } from 'simple-git';
@@ -164,7 +164,26 @@ export class SimpleGitBranchManager implements BranchManager {
async pushBranch(repoPath: string, branch: string, remote = 'origin'): Promise<void> {
const git = simpleGit(repoPath);
try {
await git.push(remote, branch);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (!msg.includes('branch is currently checked out')) throw err;
// Local non-bare repo with the branch checked out — temporarily allow it.
// receive.denyCurrentBranch=updateInstead updates the remote's working tree
// and index to match, or rejects if the working tree is dirty.
const remoteUrl = (await git.remote(['get-url', remote]))?.trim();
if (!remoteUrl) throw err;
const remotePath = resolve(repoPath, remoteUrl);
const remoteGit = simpleGit(remotePath);
await remoteGit.addConfig('receive.denyCurrentBranch', 'updateInstead');
try {
await git.push(remote, branch);
} finally {
await remoteGit.raw(['config', '--unset', 'receive.denyCurrentBranch']);
}
}
log.info({ repoPath, branch, remote }, 'branch pushed to remote');
}