13
13
# License for the specific language governing permissions and limitations
14
14
# under the License.
15
15
16
+ from contextlib import contextmanager
17
+ import logging
18
+ import os
19
+ import shutil
20
+
16
21
import git
17
22
import gitdb
18
- import os
19
- import logging
20
23
21
24
import zuul .model
22
25
@@ -38,14 +41,25 @@ def reset_repo_to_head(repo):
38
41
raise
39
42
40
43
44
+ @contextmanager
45
+ def timeout_handler (path ):
46
+ try :
47
+ yield
48
+ except git .exc .GitCommandError as e :
49
+ if e .status == - 9 :
50
+ # Timeout. The repo could be in a bad state, so delete it.
51
+ shutil .rmtree (path )
52
+ raise
53
+
54
+
41
55
class ZuulReference (git .Reference ):
42
56
_common_path_default = "refs/zuul"
43
57
_points_to_commits_only = True
44
58
45
59
46
60
class Repo (object ):
47
61
def __init__ (self , remote , local , email , username , speed_limit , speed_time ,
48
- sshkey = None , cache_path = None , logger = None ):
62
+ sshkey = None , cache_path = None , logger = None , git_timeout = 300 ):
49
63
if logger is None :
50
64
self .log = logging .getLogger ("zuul.Repo" )
51
65
else :
@@ -54,6 +68,7 @@ def __init__(self, remote, local, email, username, speed_limit, speed_time,
54
68
'GIT_HTTP_LOW_SPEED_LIMIT' : speed_limit ,
55
69
'GIT_HTTP_LOW_SPEED_TIME' : speed_time ,
56
70
}
71
+ self .git_timeout = git_timeout
57
72
if sshkey :
58
73
self .env ['GIT_SSH_COMMAND' ] = 'ssh -i %s' % (sshkey ,)
59
74
@@ -65,7 +80,7 @@ def __init__(self, remote, local, email, username, speed_limit, speed_time,
65
80
self ._initialized = False
66
81
try :
67
82
self ._ensure_cloned ()
68
- except :
83
+ except Exception :
69
84
self .log .exception ("Unable to initialize repo for %s" % remote )
70
85
71
86
def _ensure_cloned (self ):
@@ -78,12 +93,10 @@ def _ensure_cloned(self):
78
93
self .log .debug ("Cloning from %s to %s" % (self .remote_url ,
79
94
self .local_path ))
80
95
if self .cache_path :
81
- git .Repo .clone_from (self .cache_path , self .local_path ,
82
- env = self .env )
96
+ self ._git_clone (self .cache_path )
83
97
rewrite_url = True
84
98
else :
85
- git .Repo .clone_from (self .remote_url , self .local_path ,
86
- env = self .env )
99
+ self ._git_clone (self .remote_url )
87
100
repo = git .Repo (self .local_path )
88
101
repo .git .update_environment (** self .env )
89
102
# Create local branches corresponding to all the remote branches
@@ -107,6 +120,18 @@ def _ensure_cloned(self):
107
120
def isInitialized (self ):
108
121
return self ._initialized
109
122
123
+ def _git_clone (self , url ):
124
+ mygit = git .cmd .Git (os .getcwd ())
125
+ mygit .update_environment (** self .env )
126
+ with timeout_handler (self .local_path ):
127
+ mygit .clone (git .cmd .Git .polish_url (url ), self .local_path ,
128
+ kill_after_timeout = self .git_timeout )
129
+
130
+ def _git_fetch (self , repo , remote , ref = None , ** kwargs ):
131
+ with timeout_handler (self .local_path ):
132
+ repo .git .fetch (remote , ref , kill_after_timeout = self .git_timeout ,
133
+ ** kwargs )
134
+
110
135
def createRepoObject (self ):
111
136
self ._ensure_cloned ()
112
137
repo = git .Repo (self .local_path )
@@ -228,19 +253,18 @@ def merge(self, ref, strategy=None):
228
253
229
254
def fetch (self , ref ):
230
255
repo = self .createRepoObject ()
231
- # The git.remote.fetch method may read in git progress info and
232
- # interpret it improperly causing an AssertionError. Because the
233
- # data was fetched properly subsequent fetches don't seem to fail.
234
- # So try again if an AssertionError is caught.
235
- origin = repo .remotes .origin
236
- try :
237
- origin .fetch (ref )
238
- except AssertionError :
239
- origin .fetch (ref )
256
+ # NOTE: The following is currently not applicable, but if we
257
+ # switch back to fetch methods from GitPython, we need to
258
+ # consider it:
259
+ # The git.remote.fetch method may read in git progress info and
260
+ # interpret it improperly causing an AssertionError. Because the
261
+ # data was fetched properly subsequent fetches don't seem to fail.
262
+ # So try again if an AssertionError is caught.
263
+ self ._git_fetch (repo , 'origin' , ref )
240
264
241
265
def fetchFrom (self , repository , ref ):
242
266
repo = self .createRepoObject ()
243
- repo . git . fetch ( repository , ref )
267
+ self . _git_fetch ( repo , repository , ref )
244
268
245
269
def createZuulRef (self , ref , commit = 'HEAD' ):
246
270
repo = self .createRepoObject ()
@@ -257,15 +281,14 @@ def push(self, local, remote):
257
281
def update (self ):
258
282
repo = self .createRepoObject ()
259
283
self .log .debug ("Updating repository %s" % self .local_path )
260
- origin = repo .remotes .origin
261
284
if repo .git .version_info [:2 ] < (1 , 9 ):
262
285
# Before 1.9, 'git fetch --tags' did not include the
263
286
# behavior covered by 'git --fetch', so we run both
264
287
# commands in that case. Starting with 1.9, 'git fetch
265
288
# --tags' is all that is necessary. See
266
289
# https://github.com/git/git/blob/master/Documentation/RelNotes/1.9.0.txt#L18-L20
267
- origin . fetch ( )
268
- origin . fetch ( tags = True )
290
+ self . _git_fetch ( repo , 'origin' )
291
+ self . _git_fetch ( repo , 'origin' , tags = True )
269
292
270
293
def getFiles (self , files , dirs = [], branch = None , commit = None ):
271
294
ret = {}
0 commit comments