@@ -12,6 +12,7 @@ const optCheck = require('./opt-check')
12
12
const osenv = require ( 'osenv' )
13
13
const path = require ( 'path' )
14
14
const pinflight = require ( 'promise-inflight' )
15
+ const promiseRetry = require ( 'promise-retry' )
15
16
const uniqueFilename = require ( 'unique-filename' )
16
17
const which = BB . promisify ( require ( 'which' ) )
17
18
const semver = require ( 'semver' )
@@ -26,6 +27,23 @@ const GOOD_ENV_VARS = new Set([
26
27
'GIT_SSL_NO_VERIFY'
27
28
] )
28
29
30
+ const GIT_TRANSIENT_ERRORS = [
31
+ 'remote error: Internal Server Error' ,
32
+ 'The remote end hung up unexpectedly' ,
33
+ 'Connection timed out' ,
34
+ 'Operation timed out' ,
35
+ 'Failed to connect to .* Timed out' ,
36
+ 'Connection reset by peer' ,
37
+ 'SSL_ERROR_SYSCALL' ,
38
+ 'The requested URL returned error: 503'
39
+ ] . join ( '|' )
40
+
41
+ const GIT_TRANSIENT_ERROR_RE = new RegExp ( GIT_TRANSIENT_ERRORS )
42
+
43
+ function shouldRetry ( error ) {
44
+ return GIT_TRANSIENT_ERROR_RE . test ( error )
45
+ }
46
+
29
47
const GIT_ = 'GIT_'
30
48
let GITENV
31
49
function gitEnv ( ) {
@@ -114,57 +132,51 @@ function revs (repo, opts) {
114
132
return pinflight ( `ls-remote:${ repo } ` , ( ) => {
115
133
return spawnGit ( [ 'ls-remote' , '-h' , '-t' , repo ] , {
116
134
env : gitEnv ( )
117
- } , opts ) . then ( child => {
118
- let stdout = ''
119
- let stderr = ''
120
- child . stdout . on ( 'data' , d => { stdout += d } )
121
- child . stderr . on ( 'data' , d => { stderr += d } )
122
- return finished ( child ) . catch ( err => {
123
- err . message = `Error while executing:\n${ GITPATH } ls-remote -h -t ${ repo } \n\n${ stderr } \n${ err . message } `
124
- throw err
125
- } ) . then ( ( ) => {
126
- return stdout . split ( '\n' ) . reduce ( ( revs , line ) => {
127
- const split = line . split ( / \s + / , 2 )
128
- if ( split . length < 2 ) { return revs }
129
- const sha = split [ 0 ] . trim ( )
130
- const ref = split [ 1 ] . trim ( ) . match ( / (?: r e f s \/ [ ^ / ] + \/ ) ? ( .* ) / ) [ 1 ]
131
- if ( ! ref ) { return revs } // ???
132
- if ( ref . endsWith ( CARET_BRACES ) ) { return revs } // refs/tags/x^{} crap
133
- const type = refType ( line )
134
- const doc = { sha, ref, type}
135
-
136
- revs . refs [ ref ] = doc
137
- // We can check out shallow clones on specific SHAs if we have a ref
138
- if ( revs . shas [ sha ] ) {
139
- revs . shas [ sha ] . push ( ref )
140
- } else {
141
- revs . shas [ sha ] = [ ref ]
142
- }
135
+ } , opts ) . then ( ( stdout ) => {
136
+ return stdout . split ( '\n' ) . reduce ( ( revs , line ) => {
137
+ const split = line . split ( / \s + / , 2 )
138
+ if ( split . length < 2 ) { return revs }
139
+ const sha = split [ 0 ] . trim ( )
140
+ const ref = split [ 1 ] . trim ( ) . match ( / (?: r e f s \/ [ ^ / ] + \/ ) ? ( .* ) / ) [ 1 ]
141
+ if ( ! ref ) { return revs } // ???
142
+ if ( ref . endsWith ( CARET_BRACES ) ) { return revs } // refs/tags/x^{} crap
143
+ const type = refType ( line )
144
+ const doc = { sha, ref, type}
143
145
144
- if ( type === 'tag' ) {
145
- const match = ref . match ( / v ? ( \d + \. \d + \. \d + (?: [ - + ] .+ ) ? ) $ / )
146
- if ( match && semver . valid ( match [ 1 ] , true ) ) {
147
- revs . versions [ semver . clean ( match [ 1 ] , true ) ] = doc
148
- }
149
- }
146
+ revs . refs [ ref ] = doc
147
+ // We can check out shallow clones on specific SHAs if we have a ref
148
+ if ( revs . shas [ sha ] ) {
149
+ revs . shas [ sha ] . push ( ref )
150
+ } else {
151
+ revs . shas [ sha ] = [ ref ]
152
+ }
150
153
151
- return revs
152
- } , { versions : { } , 'dist-tags' : { } , refs : { } , shas : { } } )
153
- } ) . then ( revs => {
154
- if ( revs . refs . HEAD ) {
155
- const HEAD = revs . refs . HEAD
156
- Object . keys ( revs . versions ) . forEach ( v => {
157
- if ( v . sha === HEAD . sha ) {
158
- revs [ 'dist-tags' ] . HEAD = v
159
- if ( ! revs . refs . latest ) {
160
- revs [ 'dist-tags' ] . latest = revs . refs . HEAD
161
- }
162
- }
163
- } )
154
+ if ( type === 'tag' ) {
155
+ const match = ref . match ( / v ? ( \d + \. \d + \. \d + (?: [ - + ] .+ ) ? ) $ / )
156
+ if ( match && semver . valid ( match [ 1 ] , true ) ) {
157
+ revs . versions [ semver . clean ( match [ 1 ] , true ) ] = doc
158
+ }
164
159
}
165
- REVS . set ( repo , revs )
160
+
166
161
return revs
167
- } )
162
+ } , { versions : { } , 'dist-tags' : { } , refs : { } , shas : { } } )
163
+ } , err => {
164
+ err . message = `Error while executing:\n${ GITPATH } ls-remote -h -t ${ repo } \n\n${ err . stderr } \n${ err . message } `
165
+ throw err
166
+ } ) . then ( revs => {
167
+ if ( revs . refs . HEAD ) {
168
+ const HEAD = revs . refs . HEAD
169
+ Object . keys ( revs . versions ) . forEach ( v => {
170
+ if ( v . sha === HEAD . sha ) {
171
+ revs [ 'dist-tags' ] . HEAD = v
172
+ if ( ! revs . refs . latest ) {
173
+ revs [ 'dist-tags' ] . latest = revs . refs . HEAD
174
+ }
175
+ }
176
+ } )
177
+ }
178
+ REVS . set ( repo , revs )
179
+ return revs
168
180
} )
169
181
} )
170
182
}
@@ -173,15 +185,47 @@ module.exports._exec = execGit
173
185
function execGit ( gitArgs , gitOpts , opts ) {
174
186
opts = optCheck ( opts )
175
187
return checkGit ( ) . then ( gitPath => {
176
- return execFileAsync ( gitPath , gitArgs , mkOpts ( gitOpts , opts ) )
188
+ return promiseRetry ( ( retry , number ) => {
189
+ if ( number !== 1 ) {
190
+ opts . log . silly ( 'pacote' , 'Retrying git command: ' + gitArgs . join ( ' ' ) + ' attempt # ' + number )
191
+ }
192
+ return execFileAsync ( gitPath , gitArgs , mkOpts ( gitOpts , opts ) ) . catch ( ( err ) => {
193
+ if ( shouldRetry ( err ) ) {
194
+ retry ( err )
195
+ } else {
196
+ throw err
197
+ }
198
+ } )
199
+ } , opts . retry )
177
200
} )
178
201
}
179
202
180
203
module . exports . _spawn = spawnGit
181
204
function spawnGit ( gitArgs , gitOpts , opts ) {
182
205
opts = optCheck ( opts )
183
206
return checkGit ( ) . then ( gitPath => {
184
- return cp . spawn ( gitPath , gitArgs , mkOpts ( gitOpts , opts ) )
207
+ return promiseRetry ( ( retry , number ) => {
208
+ if ( number !== 1 ) {
209
+ opts . log . silly ( 'pacote' , 'Retrying git command: ' + gitArgs . join ( ' ' ) + ' attempt # ' + number )
210
+ }
211
+ const child = cp . spawn ( gitPath , gitArgs , mkOpts ( gitOpts , opts ) )
212
+
213
+ let stdout = ''
214
+ let stderr = ''
215
+ child . stdout . on ( 'data' , d => { stdout += d } )
216
+ child . stderr . on ( 'data' , d => { stderr += d } )
217
+
218
+ return finished ( child ) . catch ( err => {
219
+ if ( shouldRetry ( stderr ) ) {
220
+ retry ( err )
221
+ } else {
222
+ err . stderr = stderr
223
+ throw err
224
+ }
225
+ } ) . then ( ( ) => {
226
+ return stdout
227
+ } )
228
+ } , opts . retry )
185
229
} )
186
230
}
187
231
0 commit comments