fix(git): disable tag following during pushes

Programmatic `git push` operations should not follow annotated tags configured by a user's `push.followTags = true`. This setting can lead to push failures if the remote rejects tag creation (e.g., in PR-head forks), even if the branch update itself is valid.

Adding `--no-follow-tags` explicitly overrides this user setting, ensuring only the specified refspec is pushed and preventing rejections.
This commit is contained in:
can1357
2026-06-11 21:05:26 +02:00
parent 79ccb61d0c
commit edbdda6fbd
2 changed files with 30 additions and 2 deletions
+7 -2
View File
@@ -1126,9 +1126,14 @@ export async function commit(cwd: string, message: string, options: CommitOption
return runChecked(cwd, args, { signal: options.signal, stdin: message });
}
/** Push the current branch. */
/** Push the current branch (branch-scoped: never follows tags). */
export async function push(cwd: string, options: PushOptions = {}): Promise<void> {
const args = ["push"];
// `--no-follow-tags` overrides a user's `push.followTags = true`, which
// would otherwise ride every reachable annotated tag along with the
// branch — rejected refs ("permission denied") on remotes the user
// cannot tag (e.g. PR-head forks), failing the call after the branch
// itself already updated. Tool pushes push exactly the named refspec.
const args = ["push", "--no-follow-tags"];
if (options.forceWithLease) args.push("--force-with-lease");
if (options.remote) args.push(options.remote);
if (options.refspec) args.push(options.refspec);
@@ -87,4 +87,27 @@ describe("git subprocess config", () => {
"tracked.txt",
]);
});
it("scopes pushes to the named refspec, never following tags", async () => {
const spawnCalls: SpawnCall[] = [];
vi.spyOn(Bun, "spawn").mockImplementation(createSpawnMock(spawnCalls));
await git.push("/work/pi", { remote: "fork", refspec: "HEAD:refs/heads/feature" });
// `--no-follow-tags` must override a user's `push.followTags = true`:
// implicit tag pushes are rejected on remotes the user cannot tag
// (e.g. PR-head forks) and fail the call after the branch updated.
expect(spawnCalls).toHaveLength(1);
expect(spawnCalls[0]?.cmd).toEqual([
"git",
"-c",
"core.fsmonitor=false",
"-c",
"core.untrackedCache=false",
"push",
"--no-follow-tags",
"fork",
"HEAD:refs/heads/feature",
]);
});
});