Add support for --force-if-includes to force push more safely (#187932)

* Add support for `--force-if-includes` to force push

* Change force push failed error message

* Separate force push (no with lease) failed error message

* Switch to `"markdownDescription"`

* Add Git version requirement for config description

* Improve error message when safer force push is rejected

* Eliminate the option's effect if Git is too old

* Minor improvements to community contribution

---------

Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com>
This commit is contained in:
Tatsunori Uchino
2023-10-24 00:47:46 +09:00
committed by GitHub
parent f4f0a1dd95
commit 2683aa01ac
5 changed files with 28 additions and 4 deletions

View File

@@ -16,7 +16,8 @@ export interface InputBox {
export const enum ForcePushMode {
Force,
ForceWithLease
ForceWithLease,
ForceWithLeaseIfIncludes,
}
export const enum RefType {
@@ -366,6 +367,8 @@ export const enum GitErrorCodes {
StashConflict = 'StashConflict',
UnmergedChanges = 'UnmergedChanges',
PushRejected = 'PushRejected',
ForcePushWithLeaseRejected = 'ForcePushWithLeaseRejected',
ForcePushWithLeaseIfIncludesRejected = 'ForcePushWithLeaseIfIncludesRejected',
RemoteConnectionError = 'RemoteConnectionError',
DirtyWorkTree = 'DirtyWorkTree',
CantOpenResource = 'CantOpenResource',

View File

@@ -2793,7 +2793,9 @@ export class CommandCenter {
return;
}
forcePushMode = config.get<boolean>('useForcePushWithLease') === true ? ForcePushMode.ForceWithLease : ForcePushMode.Force;
const useForcePushWithLease = config.get<boolean>('useForcePushWithLease') === true;
const useForcePushIfIncludes = config.get<boolean>('useForcePushIfIncludes') === true;
forcePushMode = useForcePushWithLease ? useForcePushIfIncludes ? ForcePushMode.ForceWithLeaseIfIncludes : ForcePushMode.ForceWithLease : ForcePushMode.Force;
if (config.get<boolean>('confirmForcePush')) {
const message = l10n.t('You are about to force push your changes, this can be destructive and could inadvertently overwrite changes made by others.\n\nAre you sure to continue?');
@@ -3682,6 +3684,10 @@ export class CommandCenter {
case GitErrorCodes.PushRejected:
message = l10n.t('Can\'t push refs to remote. Try running "Pull" first to integrate your changes.');
break;
case GitErrorCodes.ForcePushWithLeaseRejected:
case GitErrorCodes.ForcePushWithLeaseIfIncludesRejected:
message = l10n.t('Can\'t force push refs to remote. The tip of the remote-tracking branch has been updated since the last checkout. Try running "Pull" first to pull the latest changes from the remote branch first.');
break;
case GitErrorCodes.Conflict:
message = l10n.t('There are merge conflicts. Resolve them before committing.');
type = 'warning';

View File

@@ -1910,8 +1910,11 @@ export class Repository {
async push(remote?: string, name?: string, setUpstream: boolean = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise<void> {
const args = ['push'];
if (forcePushMode === ForcePushMode.ForceWithLease) {
if (forcePushMode === ForcePushMode.ForceWithLease || forcePushMode === ForcePushMode.ForceWithLeaseIfIncludes) {
args.push('--force-with-lease');
if (forcePushMode === ForcePushMode.ForceWithLeaseIfIncludes && this._git.compareGitVersionTo('2.30') !== -1) {
args.push('--force-if-includes');
}
} else if (forcePushMode === ForcePushMode.Force) {
args.push('--force');
}
@@ -1940,7 +1943,13 @@ export class Repository {
await this.exec(args, { env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } });
} catch (err) {
if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.PushRejected;
if (forcePushMode === ForcePushMode.ForceWithLease && /! \[rejected\].*\(stale info\)/m.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.ForcePushWithLeaseRejected;
} else if (forcePushMode === ForcePushMode.ForceWithLeaseIfIncludes && /! \[rejected\].*\(remote ref updated since checkout\)/m.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.ForcePushWithLeaseIfIncludesRejected;
} else {
err.gitErrorCode = GitErrorCodes.PushRejected;
}
} else if (/Permission.*denied/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.PermissionDenied;
} else if (/Could not read from remote repository/.test(err.stderr || '')) {