5
5
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6
6
from __future__ import print_function
7
7
8
+ import contextlib
8
9
from functools import wraps
9
10
import io
10
11
import logging
11
12
import os
12
- import tempfile
13
+ import tempfile # @UnusedImport
13
14
import textwrap
14
15
import time
15
16
from unittest import TestCase
16
17
17
18
from git .compat import string_types , is_win
18
- from git .util import rmtree
19
-
19
+ from git .util import rmtree , cwd
20
20
import os .path as osp
21
21
22
22
23
+ try :
24
+ from unittest import mock
25
+ from contextlib import ExitStack
26
+ from tempfile import TemporaryDirectory
27
+ except ImportError : # PY2
28
+ import mock
29
+ from contextlib2 import ExitStack # @UnusedImport
30
+ from backports .tempfile import TemporaryDirectory # @UnusedImport
31
+
32
+
23
33
ospd = osp .dirname
24
34
25
35
GIT_REPO = os .environ .get ("GIT_PYTHON_TEST_GIT_REPO_BASE" , ospd (ospd (ospd (ospd (__file__ )))))
26
36
GIT_DAEMON_PORT = os .environ .get ("GIT_PYTHON_TEST_GIT_DAEMON_PORT" , "19418" )
27
37
28
38
__all__ = (
29
39
'fixture_path' , 'fixture' , 'StringProcessAdapter' ,
30
- 'with_rw_directory' , 'with_rw_repo' , 'with_rw_and_rw_remote_repo ' , 'TestBase' , 'TestCase' ,
40
+ 'with_rw_directory' , 'with_rw_repo' , 'rw_and_rw_remote_repos ' , 'TestBase' , 'TestCase' ,
31
41
'GIT_REPO' , 'GIT_DAEMON_PORT'
32
42
)
33
43
@@ -162,39 +172,91 @@ def repo_creator(self):
162
172
return argument_passer
163
173
164
174
175
+ @contextlib .contextmanager
165
176
def launch_git_daemon (base_path , ip , port ):
166
- from git import Git
167
- if is_win :
168
- ## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
169
- # but if invoked as 'git daemon', it detaches from parent `git` cmd,
170
- # and then CANNOT DIE!
171
- # So, invoke it as a single command.
172
- ## Cygwin-git has no daemon. But it can use MINGW's.
173
- #
174
- daemon_cmd = ['git-daemon' ,
175
- '--enable=receive-pack' ,
176
- '--listen=%s' % ip ,
177
- '--port=%s' % port ,
178
- '--base-path=%s' % base_path ,
179
- base_path ]
180
- gd = Git ().execute (daemon_cmd , as_process = True )
181
- else :
182
- gd = Git ().daemon (base_path ,
183
- enable = 'receive-pack' ,
184
- listen = ip ,
185
- port = port ,
186
- base_path = base_path ,
187
- as_process = True )
188
- # yes, I know ... fortunately, this is always going to work if sleep time is just large enough
189
- time .sleep (0.5 )
190
- return gd
191
-
192
-
193
- def with_rw_and_rw_remote_repo (working_tree_ref ):
177
+ from git import Git # Avoid circular deps.
178
+
179
+ gd_launched = False
180
+ try :
181
+ if is_win :
182
+ ## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
183
+ # but if invoked as 'git daemon', it detaches from parent `git` cmd,
184
+ # and then CANNOT DIE!
185
+ # So, invoke it as a single command.
186
+ ## Cygwin-git has no daemon. But it can use MINGW's.
187
+ #
188
+ daemon_cmd = ['git-daemon' ,
189
+ '--enable=receive-pack' ,
190
+ '--listen=%s' % ip ,
191
+ '--port=%s' % port ,
192
+ '--base-path=%s' % base_path ,
193
+ base_path ]
194
+ gd = Git ().execute (daemon_cmd , as_process = True )
195
+ else :
196
+ gd = Git ().daemon (base_path ,
197
+ enable = 'receive-pack' ,
198
+ listen = ip ,
199
+ port = port ,
200
+ base_path = base_path ,
201
+ as_process = True )
202
+ gd_launched = True
203
+ # yes, I know ... fortunately, this is always going to work if sleep time is just large enough
204
+ time .sleep (0.5 * (1 + is_win ))
205
+
206
+ yield gd
207
+
208
+ except Exception as ex :
209
+ msg = textwrap .dedent ("""
210
+ Launching git-daemon failed due to: %s
211
+ Probably test will fail subsequently.
212
+
213
+ BUT you may start *git-daemon* manually with this command:"
214
+ git daemon --enable=receive-pack --listen=%s --port=%s --base-path=%s %s
215
+ You may also run the daemon on a different port by passing --port=<port>"
216
+ and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
217
+ """ )
218
+ if is_win :
219
+ msg += textwrap .dedent ("""
220
+
221
+ On Windows,
222
+ the `git-daemon.exe` must be in PATH.
223
+ For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
224
+ CYGWIN has no daemon, but if one exists, it gets along fine (but has also paths problems).""" )
225
+ log .warning (msg , ex , ip , port , base_path , base_path , exc_info = 1 )
226
+
227
+ yield mock .MagicMock () # @UndefinedVariable
228
+
229
+ finally :
230
+ if gd_launched :
231
+ try :
232
+ log .debug ("Killing git-daemon..." )
233
+ gd .proc .kill ()
234
+ except Exception as ex :
235
+ ## Either it has died (and we're here), or it won't die, again here...
236
+ log .debug ("Hidden error while Killing git-daemon: %s" , ex , exc_info = 1 )
237
+
238
+
239
+ @contextlib .contextmanager
240
+ def tmp_clone (repo , clone_prefix , ** clone_kwargs ):
241
+ def cleanup_clone (repo ):
242
+ repo .git .clear_cache ()
243
+ import gc
244
+ gc .collect ()
245
+
246
+ with ExitStack () as stack :
247
+ clone_dir = stack .enter_context (TemporaryDirectory (prefix = clone_prefix ))
248
+ clone = repo .clone (clone_dir , ** clone_kwargs )
249
+ stack .callback (cleanup_clone , clone )
250
+
251
+ yield clone
252
+
253
+
254
+ @contextlib .contextmanager
255
+ def rw_and_rw_remote_repos (repo , working_tree_ref ):
194
256
"""
195
- Same as with_rw_repo, but also provides a writable remote repository from which the
196
- rw_repo has been forked as well as a handle for a git-daemon that may be started to
197
- run the remote_repo.
257
+ A context-manager creating the same temporary-repo as `with_rw_repo` and in addition
258
+ a writable remote non-bare repository from which the rw_repo has been forked as well as a handle
259
+ for a git-daemon that may be started to run the remote_repo.
198
260
The remote repository was cloned as bare repository from the rorepo, wheras
199
261
the rw repo has a working tree and was cloned from the remote repository.
200
262
@@ -203,11 +265,13 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
203
265
and should be an inetd service that serves tempdir.gettempdir() and all
204
266
directories in it.
205
267
206
- The following scetch demonstrates this::
207
- rorepo ---<bare clone>---> rw_remote_repo ---<clone>---> rw_repo
268
+ The following sketch demonstrates this::
269
+ rorepo ---<bare clone>---> remote_repo ---<clone>---> rw_repo
270
+
271
+ It is used like that::
208
272
209
- The test case needs to support the following signature: :
210
- def case(self, rw_repo, rw_remote_repo)
273
+ with rw_and_rw_remote_repos(origin_repo) as (rw_repo, remote_repo) :
274
+ ...
211
275
212
276
This setup allows you to test push and pull scenarios and hooks nicely.
213
277
@@ -218,105 +282,51 @@ def case(self, rw_repo, rw_remote_repo)
218
282
219
283
assert isinstance (working_tree_ref , string_types ), "Decorator requires ref name for working tree checkout"
220
284
221
- def argument_passer (func ):
222
-
223
- @wraps (func )
224
- def remote_repo_creator (self ):
225
- remote_repo_dir = _mktemp ("remote_repo_%s" % func .__name__ )
226
- repo_dir = _mktemp ("remote_clone_non_bare_repo" )
227
-
228
- rw_remote_repo = self .rorepo .clone (remote_repo_dir , shared = True , bare = True )
229
- # recursive alternates info ?
230
- rw_repo = rw_remote_repo .clone (repo_dir , shared = True , bare = False , n = True )
231
- rw_repo .head .commit = working_tree_ref
232
- rw_repo .head .reference .checkout ()
233
-
234
- # prepare for git-daemon
235
- rw_remote_repo .daemon_export = True
236
-
237
- # this thing is just annoying !
238
- with rw_remote_repo .config_writer () as crw :
239
- section = "daemon"
240
- try :
241
- crw .add_section (section )
242
- except Exception :
243
- pass
244
- crw .set (section , "receivepack" , True )
245
-
246
- # Initialize the remote - first do it as local remote and pull, then
247
- # we change the url to point to the daemon.
248
- d_remote = Remote .create (rw_repo , "daemon_origin" , remote_repo_dir )
249
- d_remote .fetch ()
250
-
251
- base_path , rel_repo_dir = osp .split (remote_repo_dir )
252
-
253
- remote_repo_url = Git .polish_url ("git://localhost:%s/%s" % (GIT_DAEMON_PORT , rel_repo_dir ))
254
- with d_remote .config_writer as cw :
255
- cw .set ('url' , remote_repo_url )
256
-
285
+ with ExitStack () as stack :
286
+ rw_remote_repo = stack .enter_context (tmp_clone (repo ,
287
+ clone_prefix = "remote_bare_repo_%s" ,
288
+ shared = True ,
289
+ bare = True ))
290
+ rw_repo = stack .enter_context (tmp_clone (rw_remote_repo ,
291
+ clone_prefix = "remote_clone_non_bare_repo_" ,
292
+ shared = True ,
293
+ bare = False ,
294
+ n = True ))
295
+ remote_repo_dir = rw_remote_repo .working_dir
296
+
297
+ # recursive alternates info ?
298
+ rw_repo .head .commit = working_tree_ref
299
+ rw_repo .head .reference .checkout ()
300
+
301
+ # Allow git-daemon in bare-repo (https://git-scm.com/book/en/v2/Git-on-the-Server-Git-Daemon).
302
+ rw_remote_repo .daemon_export = True
303
+
304
+ section = "daemon"
305
+ with rw_remote_repo .config_writer () as crw :
257
306
try :
258
- gd = launch_git_daemon (Git .polish_url (base_path ), '127.0.0.1' , GIT_DAEMON_PORT )
259
- except Exception as ex :
260
- if is_win :
261
- msg = textwrap .dedent ("""
262
- The `git-daemon.exe` must be in PATH.
263
- For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
264
- CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
265
- Anyhow, alternatively try starting `git-daemon` manually:""" )
266
- else :
267
- msg = "Please try starting `git-daemon` manually:"
268
- msg += textwrap .dedent ("""
269
- git daemon --enable=receive-pack --base-path=%s %s
270
- You can also run the daemon on a different port by passing --port=<port>"
271
- and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
272
- """ % (base_path , base_path ))
273
- raise AssertionError (ex , msg )
274
- # END make assertion
275
- else :
276
- # Try listing remotes, to diagnose whether the daemon is up.
277
- rw_repo .git .ls_remote (d_remote )
278
-
279
- # adjust working dir
280
- prev_cwd = os .getcwd ()
281
- os .chdir (rw_repo .working_dir )
282
-
283
- try :
284
- return func (self , rw_repo , rw_remote_repo )
285
- except :
286
- log .info ("Keeping repos after failure: repo_dir = %s, remote_repo_dir = %s" ,
287
- repo_dir , remote_repo_dir )
288
- repo_dir = remote_repo_dir = None
289
- raise
290
- finally :
291
- os .chdir (prev_cwd )
307
+ crw .add_section (section ) # TODO: Add section if not exists.
308
+ except Exception :
309
+ pass
310
+ crw .set (section , "receivepack" , True )
292
311
293
- finally :
294
- try :
295
- log .debug ("Killing git-daemon..." )
296
- gd .proc .kill ()
297
- except :
298
- ## Either it has died (and we're here), or it won't die, again here...
299
- pass
312
+ # Initialize the non-bare repo - first do it as local remote and pull, then
313
+ # we change the URL to point to the "relative" against "daemon's `--base-path`.
314
+ #
315
+ d_remote = Remote .create (rw_repo , "daemon_origin" , remote_repo_dir )
316
+ d_remote .fetch ()
317
+ base_path , rel_repo_dir = osp .split (remote_repo_dir )
318
+ remote_repo_url = Git .polish_url ("git://localhost:%s/%s" % (GIT_DAEMON_PORT , rel_repo_dir ))
319
+ with d_remote .config_writer as cw :
320
+ cw .set ('url' , remote_repo_url )
300
321
301
- rw_repo .git .clear_cache ()
302
- rw_remote_repo .git .clear_cache ()
303
- rw_repo = rw_remote_repo = None
304
- import gc
305
- gc .collect ()
306
- if repo_dir :
307
- rmtree (repo_dir )
308
- if remote_repo_dir :
309
- rmtree (remote_repo_dir )
322
+ stack .enter_context (launch_git_daemon (Git .polish_url (base_path ), '127.0.0.1' , GIT_DAEMON_PORT ))
310
323
311
- if gd is not None :
312
- gd .proc .wait ()
313
- # END cleanup
314
- # END bare repo creator
315
- return remote_repo_creator
316
- # END remote repo creator
317
- # END argument parser
324
+ # Try listing remotes, to diagnose whether the daemon is up.
325
+ rw_repo .git .ls_remote (d_remote )
318
326
319
- return argument_passer
327
+ # adjust working dir
328
+ stack .enter_context (cwd (rw_repo .working_dir ))
329
+ yield rw_repo , rw_remote_repo
320
330
321
331
#} END decorators
322
332
0 commit comments