@@ -25,7 +25,8 @@ import {
2525 Status ,
2626 SvnDepth ,
2727 SvnUriAction ,
28- ISvnPathChange
28+ ISvnPathChange ,
29+ IStoredAuth
2930} from "./common/types" ;
3031import { debounce , globalSequentialize , memoize , throttle } from "./decorators" ;
3132import { exists } from "./fs" ;
@@ -50,6 +51,7 @@ import {
5051} from "./util" ;
5152import { match , matchAll } from "./util/globMatch" ;
5253import { RepositoryFilesWatcher } from "./watchers/repositoryFilesWatcher" ;
54+ import { keytar } from "./vscodeModules" ;
5355
5456function shouldShowProgress ( operation : Operation ) : boolean {
5557 switch ( operation ) {
@@ -79,6 +81,7 @@ export class Repository implements IRemoteRepository {
7981 public needCleanUp : boolean = false ;
8082 private remoteChangedUpdateInterval ?: NodeJS . Timer ;
8183 private deletedUris : Uri [ ] = [ ] ;
84+ private canSaveAuth : boolean = false ;
8285
8386 private lastPromptAuth ?: Thenable < IAuth | undefined > ;
8487
@@ -919,6 +922,39 @@ export class Repository implements IRemoteRepository {
919922 return new PathNormalizer ( this . repository . info ) ;
920923 }
921924
925+ protected getCredentialServiceName ( ) {
926+ let key = "vscode.svn-scm" ;
927+
928+ const info = this . repository . info ;
929+
930+ if ( info . repository && info . repository . root ) {
931+ key += ":" + info . repository . root ;
932+ } else if ( info . url ) {
933+ key += ":" + info . url ;
934+ }
935+
936+ return key ;
937+ }
938+
939+ public async loadStoredAuths ( ) : Promise < Array < IStoredAuth > > {
940+ // Prevent multiple prompts for auth
941+ if ( this . lastPromptAuth ) {
942+ await this . lastPromptAuth ;
943+ }
944+ return keytar . findCredentials ( this . getCredentialServiceName ( ) ) ;
945+ }
946+
947+ public async saveAuth ( ) : Promise < void > {
948+ if ( this . canSaveAuth && this . username && this . password ) {
949+ await keytar . setPassword (
950+ this . getCredentialServiceName ( ) ,
951+ this . username ,
952+ this . password
953+ ) ;
954+ this . canSaveAuth = false ;
955+ }
956+ }
957+
922958 public async promptAuth ( ) : Promise < IAuth | undefined > {
923959 // Prevent multiple prompts for auth
924960 if ( this . lastPromptAuth ) {
@@ -931,6 +967,7 @@ export class Repository implements IRemoteRepository {
931967 if ( result ) {
932968 this . username = result . username ;
933969 this . password = result . password ;
970+ this . canSaveAuth = true ;
934971 }
935972
936973 this . lastPromptAuth = undefined ;
@@ -1002,11 +1039,14 @@ export class Repository implements IRemoteRepository {
10021039 runOperation : ( ) => Promise < T > = ( ) => Promise . resolve < any > ( null )
10031040 ) : Promise < T > {
10041041 let attempt = 0 ;
1042+ let accounts : IStoredAuth [ ] = [ ] ;
10051043
10061044 while ( true ) {
10071045 try {
10081046 attempt ++ ;
1009- return await runOperation ( ) ;
1047+ const result = await runOperation ( ) ;
1048+ this . saveAuth ( ) ;
1049+ return result ;
10101050 } catch ( err ) {
10111051 if (
10121052 err . svnErrorCode === svnErrorCodes . RepositoryIsLocked &&
@@ -1016,7 +1056,22 @@ export class Repository implements IRemoteRepository {
10161056 await timeout ( Math . pow ( attempt , 2 ) * 50 ) ;
10171057 } else if (
10181058 err . svnErrorCode === svnErrorCodes . AuthorizationFailed &&
1019- attempt <= 3
1059+ attempt <= 1 + accounts . length
1060+ ) {
1061+ // First attempt load all stored auths
1062+ if ( attempt === 1 ) {
1063+ accounts = await this . loadStoredAuths ( ) ;
1064+ }
1065+
1066+ // each attempt, try a different account
1067+ const index = accounts . length - 1 ;
1068+ if ( typeof accounts [ index ] !== "undefined" ) {
1069+ this . username = accounts [ index ] . account ;
1070+ this . password = accounts [ index ] . password ;
1071+ }
1072+ } else if (
1073+ err . svnErrorCode === svnErrorCodes . AuthorizationFailed &&
1074+ attempt <= 3 + accounts . length
10201075 ) {
10211076 const result = await this . promptAuth ( ) ;
10221077 if ( ! result ) {
0 commit comments