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