Skip to content

Commit 1fe86a3

Browse files
committed
cygwin, gitpython-developers#525: FIX daemon launching
+ Rework git-daemon launching with `with` resource-management. + cmd: add `is_cygwin` optional override kwd on `Git.polish_url()`. - Cygwin TCs failing: - PY2: err: 13, fail: 3 - PY3: err: 12, fail: 3
1 parent 6b6661d commit 1fe86a3

File tree

2 files changed

+122
-112
lines changed

2 files changed

+122
-112
lines changed

git/cmd.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,19 @@ def is_cygwin(cls):
196196
return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE)
197197

198198
@classmethod
199-
def polish_url(cls, url):
200-
if cls.is_cygwin():
199+
def polish_url(cls, url, is_cygwin=None):
200+
if is_cygwin is None:
201+
is_cygwin = cls.is_cygwin()
202+
203+
if is_cygwin:
204+
url = cygpath(url)
205+
else:
201206
"""Remove any backslahes from urls to be written in config files.
202207
203208
Windows might create config-files containing paths with backslashed,
204209
but git stops liking them as it will escape the backslashes.
205210
Hence we undo the escaping just to be sure.
206211
"""
207-
url = cygpath(url)
208-
else:
209212
url = url.replace("\\\\", "\\").replace("\\", "/")
210213

211214
return url

git/test/lib/helper.py

+115-108
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
from __future__ import print_function
77

8+
import contextlib
89
from functools import wraps
910
import io
1011
import logging
@@ -16,7 +17,7 @@
1617
import unittest
1718

1819
from git.compat import string_types, is_win, PY3
19-
from git.util import rmtree
20+
from git.util import rmtree, cwd
2021

2122
import os.path as osp
2223

@@ -151,32 +152,67 @@ def repo_creator(self):
151152
return argument_passer
152153

153154

154-
def launch_git_daemon(base_path, ip, port):
155-
from git import Git
156-
if is_win:
157-
## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
158-
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
159-
# and then CANNOT DIE!
160-
# So, invoke it as a single command.
161-
## Cygwin-git has no daemon. But it can use MINGW's.
162-
#
163-
daemon_cmd = ['git-daemon',
164-
'--enable=receive-pack',
165-
'--listen=%s' % ip,
166-
'--port=%s' % port,
167-
'--base-path=%s' % base_path,
168-
base_path]
169-
gd = Git().execute(daemon_cmd, as_process=True)
170-
else:
171-
gd = Git().daemon(base_path,
172-
enable='receive-pack',
173-
listen=ip,
174-
port=port,
175-
base_path=base_path,
176-
as_process=True)
177-
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
178-
time.sleep(0.5)
179-
return gd
155+
@contextlib.contextmanager
156+
def git_daemon_launched(base_path, ip, port):
157+
from git import Git # Avoid circular deps.
158+
159+
gd = None
160+
try:
161+
if is_win:
162+
## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\,
163+
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
164+
# and then CANNOT DIE!
165+
# So, invoke it as a single command.
166+
## Cygwin-git has no daemon. But it can use MINGW's.
167+
#
168+
daemon_cmd = ['git-daemon',
169+
'--enable=receive-pack',
170+
'--listen=%s' % ip,
171+
'--port=%s' % port,
172+
'--base-path=%s' % base_path,
173+
base_path]
174+
gd = Git().execute(daemon_cmd, as_process=True)
175+
else:
176+
gd = Git().daemon(base_path,
177+
enable='receive-pack',
178+
listen=ip,
179+
port=port,
180+
base_path=base_path,
181+
as_process=True)
182+
# yes, I know ... fortunately, this is always going to work if sleep time is just large enough
183+
time.sleep(0.5 * (1 + is_win))
184+
185+
yield
186+
187+
except Exception as ex:
188+
msg = textwrap.dedent("""
189+
Launching git-daemon failed due to: %s
190+
Probably test will fail subsequently.
191+
192+
BUT you may start *git-daemon* manually with this command:"
193+
git daemon --enable=receive-pack --listen=%s --port=%s --base-path=%s %s
194+
You may also run the daemon on a different port by passing --port=<port>"
195+
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
196+
""")
197+
if is_win:
198+
msg += textwrap.dedent("""
199+
200+
On Windows,
201+
the `git-daemon.exe` must be in PATH.
202+
For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
203+
CYGWIN has no daemon, but if one exists, it gets along fine (but has also paths problems).""")
204+
log.warning(msg, ex, ip, port, base_path, base_path, exc_info=1)
205+
206+
yield
207+
208+
finally:
209+
if gd:
210+
try:
211+
log.debug("Killing git-daemon...")
212+
gd.proc.kill()
213+
except Exception as ex:
214+
## Either it has died (and we're here), or it won't die, again here...
215+
log.debug("Hidden error while Killing git-daemon: %s", ex, exc_info=1)
180216

181217

182218
def with_rw_and_rw_remote_repo(working_tree_ref):
@@ -193,10 +229,10 @@ def with_rw_and_rw_remote_repo(working_tree_ref):
193229
directories in it.
194230
195231
The following scetch demonstrates this::
196-
rorepo ---<bare clone>---> rw_remote_repo ---<clone>---> rw_repo
232+
rorepo ---<bare clone>---> rw_daemon_repo ---<clone>---> rw_repo
197233
198234
The test case needs to support the following signature::
199-
def case(self, rw_repo, rw_remote_repo)
235+
def case(self, rw_repo, rw_daemon_repo)
200236
201237
This setup allows you to test push and pull scenarios and hooks nicely.
202238
@@ -211,94 +247,65 @@ def argument_passer(func):
211247

212248
@wraps(func)
213249
def remote_repo_creator(self):
214-
remote_repo_dir = tempfile.mktemp("remote_repo_%s" % func.__name__)
215-
repo_dir = tempfile.mktemp("remote_clone_non_bare_repo")
250+
rw_daemon_repo_dir = tempfile.mktemp(prefix="daemon_repo-%s-" % func.__name__)
251+
rw_repo_dir = tempfile.mktemp("daemon_cloned_repo-%s-" % func.__name__)
216252

217-
rw_remote_repo = self.rorepo.clone(remote_repo_dir, shared=True, bare=True)
253+
rw_daemon_repo = self.rorepo.clone(rw_daemon_repo_dir, shared=True, bare=True)
218254
# recursive alternates info ?
219-
rw_repo = rw_remote_repo.clone(repo_dir, shared=True, bare=False, n=True)
220-
rw_repo.head.commit = working_tree_ref
221-
rw_repo.head.reference.checkout()
222-
223-
# prepare for git-daemon
224-
rw_remote_repo.daemon_export = True
225-
226-
# this thing is just annoying !
227-
with rw_remote_repo.config_writer() as crw:
228-
section = "daemon"
229-
try:
230-
crw.add_section(section)
231-
except Exception:
232-
pass
233-
crw.set(section, "receivepack", True)
234-
235-
# Initialize the remote - first do it as local remote and pull, then
236-
# we change the url to point to the daemon.
237-
d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir)
238-
d_remote.fetch()
239-
240-
base_path, rel_repo_dir = osp.split(remote_repo_dir)
241-
242-
remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir))
243-
with d_remote.config_writer as cw:
244-
cw.set('url', remote_repo_url)
245-
255+
rw_repo = rw_daemon_repo.clone(rw_repo_dir, shared=True, bare=False, n=True)
246256
try:
247-
gd = launch_git_daemon(Git.polish_url(base_path), '127.0.0.1', GIT_DAEMON_PORT)
248-
except Exception as ex:
249-
if is_win:
250-
msg = textwrap.dedent("""
251-
The `git-daemon.exe` must be in PATH.
252-
For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear.
253-
CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems)
254-
Anyhow, alternatively try starting `git-daemon` manually:""")
255-
else:
256-
msg = "Please try starting `git-daemon` manually:"
257-
msg += textwrap.dedent("""
258-
git daemon --enable=receive-pack --base-path=%s %s
259-
You can also run the daemon on a different port by passing --port=<port>"
260-
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
261-
""" % (base_path, base_path))
262-
raise AssertionError(ex, msg)
263-
# END make assertion
264-
else:
265-
# Try listing remotes, to diagnose whether the daemon is up.
266-
rw_repo.git.ls_remote(d_remote)
267-
268-
# adjust working dir
269-
prev_cwd = os.getcwd()
270-
os.chdir(rw_repo.working_dir)
257+
rw_repo.head.commit = working_tree_ref
258+
rw_repo.head.reference.checkout()
271259

272-
try:
273-
return func(self, rw_repo, rw_remote_repo)
274-
except:
275-
log.info("Keeping repos after failure: repo_dir = %s, remote_repo_dir = %s",
276-
repo_dir, remote_repo_dir)
277-
repo_dir = remote_repo_dir = None
278-
raise
279-
finally:
280-
os.chdir(prev_cwd)
260+
# prepare for git-daemon
261+
rw_daemon_repo.daemon_export = True
262+
263+
# this thing is just annoying !
264+
with rw_daemon_repo.config_writer() as crw:
265+
section = "daemon"
266+
try:
267+
crw.add_section(section)
268+
except Exception:
269+
pass
270+
crw.set(section, "receivepack", True)
271+
272+
# Initialize the remote - first do it as local remote and pull, then
273+
# we change the url to point to the daemon.
274+
d_remote = Remote.create(rw_repo, "daemon_origin", rw_daemon_repo_dir)
275+
d_remote.fetch()
276+
277+
base_daemon_path, rel_repo_dir = osp.split(rw_daemon_repo_dir)
278+
279+
remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir))
280+
with d_remote.config_writer as cw:
281+
cw.set('url', remote_repo_url)
282+
283+
with git_daemon_launched(Git.polish_url(base_daemon_path, is_cygwin=False), # No daemon in Cygwin.
284+
'127.0.0.1',
285+
GIT_DAEMON_PORT):
286+
# Try listing remotes, to diagnose whether the daemon is up.
287+
rw_repo.git.ls_remote(d_remote)
288+
289+
with cwd(rw_repo.working_dir):
290+
try:
291+
return func(self, rw_repo, rw_daemon_repo)
292+
except:
293+
log.info("Keeping repos after failure: \n rw_repo_dir: %s \n rw_daemon_repo_dir: %s",
294+
rw_repo_dir, rw_daemon_repo_dir)
295+
rw_repo_dir = rw_daemon_repo_dir = None
296+
raise
281297

282298
finally:
283-
try:
284-
log.debug("Killing git-daemon...")
285-
gd.proc.kill()
286-
except:
287-
## Either it has died (and we're here), or it won't die, again here...
288-
pass
289-
290299
rw_repo.git.clear_cache()
291-
rw_remote_repo.git.clear_cache()
292-
rw_repo = rw_remote_repo = None
300+
rw_daemon_repo.git.clear_cache()
301+
del rw_repo
302+
del rw_daemon_repo
293303
import gc
294304
gc.collect()
295-
if repo_dir:
296-
rmtree(repo_dir)
297-
if remote_repo_dir:
298-
rmtree(remote_repo_dir)
299-
300-
if gd is not None:
301-
gd.proc.wait()
305+
if rw_repo_dir:
306+
rmtree(rw_repo_dir)
307+
if rw_daemon_repo_dir:
308+
rmtree(rw_daemon_repo_dir)
302309
# END cleanup
303310
# END bare repo creator
304311
return remote_repo_creator

0 commit comments

Comments
 (0)