Skip to content

Repository service push() silently swallows non-fast-forward (tipBehind) rejections #5364

Description

@ianhattendorf

Summary

RepositoryService.ops.push() (the OperationsGitSubProvider.push implementation in the @gitlens/git-cli package) resolves successfully when git push is rejected with a non-fast-forward error whose output contains "tip of your current branch is behind its remote counterpart". The caller cannot tell a real push from a rejected one, and the remote is left unchanged.

This was found while debugging a downstream (Kepler) issue: pushing a branch that needs a force-push reported success, but nothing reached the remote.

Root cause

pushCore invokes this.git.run({ cwd: repoPath, ...runOptions }, ...params) without errors: 'throw', so a rejection falls through to the default handler. defaultExceptionHandler matches the message against GitWarnings.tipBehind (/tip of your current branch is behind/i), treats it as non-fatal, logs a warning, and returns without throwing. pushCore's catch block — which is written to build a PushError via getGitCommandError('push', ...) — therefore never runs, and run resolves with a success-shaped result ({ stdout: '', stderr: undefined, exitCode: 0 }).

The contradiction confirms this is an oversight, not intended:

  • errorToReasonMap['push'] already contains [GitWarnings.tipBehind, 'tipBehind']
  • PushErrorReason includes 'tipBehind', and PushError has a dedicated message for it
  • pushCore's catch already maps and builds the PushError

…but that mapping is unreachable for tipBehind because the default handler swallows the rejection first.

The bug is broader than tipBehind: because defaultExceptionHandler checks all GitWarnings regexes before pushCore's catch can map anything, a push that fails with a message matching GitWarnings.remoteConnectionError (Could not read from remote repository) or GitWarnings.noRemoteRepositorySpecified is also silently reported as success. Other push failures (e.g. remoteAhead from "remote contains work", stale info with --force-with-lease) are GitErrors (not warnings), so they already throw correctly.

The reported case is exactly the common force-push scenario: you rewrote your own history (amend/rebase) and nobody else touched the remote, so a normal git push is rejected as non-fast-forward — and GitLens (e.g. gitRepositoryService.ts which surfaces PushError to the user) never sees the failure.

Expected behavior

A rejected push should reject with a PushError (reason 'tipBehind' for the non-fast-forward case) so callers can surface the failure (and offer force-push where appropriate).

Fix

Have pushCore pass errors: 'throw' so its catch always runs and builds the correct PushError — matching what commit, merge, rebase, and cherryPick already do. (Moving the tipBehind regex out of GitWarnings would also work but is riskier, since the default handler uses it for every command.)

Affected versions

Present in the published @gitlens/git-cli 0.2.0 and 0.3.0 (where run was renamed to exec but execCore keeps the same logic and pushCore still omits errors: 'throw').

Metadata

Metadata

Assignees

Labels

bugneeds-verificationRequest for verificationpending-releaseResolved but not yet released to the stable edition

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions