@@ -11,7 +11,7 @@ import TelemetryReporter from '@vscode/extension-telemetry';
1111import { Branch , Change , ForcePushMode , GitErrorCodes , LogOptions , Ref , RefType , Remote , Status , CommitOptions , BranchQuery , FetchOptions } from './api/git' ;
1212import { AutoFetcher } from './autofetch' ;
1313import { debounce , memoize , throttle } from './decorators' ;
14- import { Commit , GitError , Repository as BaseRepository , Stash , Submodule , LogFileOptions } from './git' ;
14+ import { Commit , GitError , Repository as BaseRepository , Stash , Submodule , LogFileOptions , PullOptions } from './git' ;
1515import { StatusBarCommands } from './statusbar' ;
1616import { toGitUri } from './uri' ;
1717import { anyEvent , combinedDisposable , debounceEvent , dispose , EmptyDisposable , eventToPromise , filterEvent , find , IDisposable , isDescendant , onceEvent , pathEquals , relativePath } from './util' ;
@@ -1658,12 +1658,28 @@ export class Repository implements Disposable {
16581658 }
16591659
16601660 if ( await this . checkIfMaybeRebased ( this . HEAD ?. name ) ) {
1661- await this . repository . pull ( rebase , remote , branch , { unshallow, tags } ) ;
1661+ this . _pullAndHandleTagConflict ( rebase , remote , branch , { unshallow, tags } ) ;
16621662 }
16631663 } ) ;
16641664 } ) ;
16651665 }
16661666
1667+ private async _pullAndHandleTagConflict ( rebase ?: boolean , remote ?: string , branch ?: string , options : PullOptions = { } ) : Promise < void > {
1668+ try {
1669+ await this . repository . pull ( rebase , remote , branch , options ) ;
1670+ }
1671+ catch ( err ) {
1672+ if ( err . gitErrorCode !== GitErrorCodes . TagConflict ) {
1673+ throw err ;
1674+ }
1675+
1676+ // Handle tag(s) conflict
1677+ if ( await this . handleTagConflict ( remote , err . stderr ) ) {
1678+ await this . repository . pull ( rebase , remote , branch , options ) ;
1679+ }
1680+ }
1681+ }
1682+
16671683 @throttle
16681684 async push ( head : Branch , forcePushMode ?: ForcePushMode ) : Promise < void > {
16691685 let remote : string | undefined ;
@@ -1724,7 +1740,7 @@ export class Repository implements Disposable {
17241740 }
17251741
17261742 if ( await this . checkIfMaybeRebased ( this . HEAD ?. name ) ) {
1727- await this . repository . pull ( rebase , remoteName , pullBranch , { tags, cancellationToken } ) ;
1743+ this . _pullAndHandleTagConflict ( rebase , remoteName , pullBranch , { tags, cancellationToken } ) ;
17281744 }
17291745 } ;
17301746
@@ -2476,6 +2492,38 @@ export class Repository implements Disposable {
24762492 return config . get < boolean > ( 'optimisticUpdate' ) === true ;
24772493 }
24782494
2495+ private async handleTagConflict ( remote : string | undefined , raw : string ) : Promise < boolean > {
2496+ // Ensure there is a remote
2497+ remote = remote ?? this . HEAD ?. upstream ?. remote ;
2498+ if ( ! remote ) {
2499+ throw new Error ( 'Unable to resolve tag conflict due to missing remote.' ) ;
2500+ }
2501+
2502+ // Extract tag names from message
2503+ const tags : string [ ] = [ ] ;
2504+ for ( const match of raw . matchAll ( / ^ ! \[ r e j e c t e d \] \s + ( [ ^ \s ] + ) \s + - > \s + ( [ ^ \s ] + ) \s + \( w o u l d c l o b b e r e x i s t i n g t a g \) $ / gm) ) {
2505+ if ( match . length === 3 ) {
2506+ tags . push ( match [ 1 ] ) ;
2507+ }
2508+ }
2509+ if ( tags . length === 0 ) {
2510+ throw new Error ( `Unable to extract tag names from error message: ${ raw } ` ) ;
2511+ }
2512+
2513+ // Notification
2514+ const replaceLocalTags = l10n . t ( 'Replace Local Tag(s)' ) ;
2515+ const message = l10n . t ( 'Unable to pull from remote repository due to conflicting tag(s): {0}. Would you like to resolve the conflict by replacing the local tag(s)?' , tags . join ( ', ' ) ) ;
2516+ const choice = await window . showErrorMessage ( message , { modal : true } , replaceLocalTags ) ;
2517+
2518+ if ( choice !== replaceLocalTags ) {
2519+ return false ;
2520+ }
2521+
2522+ // Force fetch tags
2523+ await this . repository . fetchTags ( { remote, tags, force : true } ) ;
2524+ return true ;
2525+ }
2526+
24792527 public isBranchProtected ( name : string = this . HEAD ?. name ?? '' ) : boolean {
24802528 return this . isBranchProtectedMatcher ? this . isBranchProtectedMatcher ( name ) : false ;
24812529 }
0 commit comments