Skip to content

Commit db587ad

Browse files
committed
Add proxy option to clone_repository
Fixes: #1328
1 parent c4b9c2e commit db587ad

File tree

5 files changed

+88
-43
lines changed

5 files changed

+88
-43
lines changed

docs/repository.rst

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Functions
2929
>>> repo_path = '/path/to/create/repository'
3030
>>> repo = clone_repository(repo_url, repo_path) # Clones a non-bare repository
3131
>>> repo = clone_repository(repo_url, repo_path, bare=True) # Clones a bare repository
32+
>>> repo = clone_repository(repo_url, repo_path, proxy=True) # Enable automatic proxy detection
3233

3334

3435
.. autofunction:: pygit2.discover_repository

pygit2/__init__.py

+25-12
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@
4040
from .blame import Blame, BlameHunk
4141
from .blob import BlobIO
4242
from .callbacks import Payload, RemoteCallbacks, CheckoutCallbacks, StashApplyCallbacks
43-
from .callbacks import git_clone_options, git_fetch_options, get_credentials
43+
from .callbacks import (
44+
git_clone_options,
45+
git_fetch_options,
46+
git_proxy_options,
47+
get_credentials,
48+
)
4449
from .config import Config
4550
from .credentials import *
4651
from .errors import check_error, Passthrough
@@ -146,14 +151,15 @@ def init_repository(
146151

147152

148153
def clone_repository(
149-
url,
150-
path,
151-
bare=False,
152-
repository=None,
153-
remote=None,
154-
checkout_branch=None,
155-
callbacks=None,
156-
depth=0,
154+
url: str,
155+
path: str,
156+
bare: bool = False,
157+
repository: typing.Callable | None = None,
158+
remote: typing.Callable | None = None,
159+
checkout_branch: str | None = None,
160+
callbacks: RemoteCallbacks | None = None,
161+
depth: int = 0,
162+
proxy: None | bool | str = None,
157163
):
158164
"""
159165
Clones a new Git repository from *url* in the given *path*.
@@ -194,6 +200,12 @@ def clone_repository(
194200
If greater than 0, creates a shallow clone with a history truncated to
195201
the specified number of commits.
196202
The default is 0 (full commit history).
203+
proxy : None or True or str
204+
Proxy configuration. Can be one of:
205+
206+
* `None` (the default) to disable proxy usage
207+
* `True` to enable automatic proxy detection
208+
* an url to a proxy (`http://proxy.example.org:3128/`)
197209
"""
198210

199211
if callbacks is None:
@@ -214,9 +226,10 @@ def clone_repository(
214226
opts.checkout_branch = checkout_branch_ref
215227

216228
with git_fetch_options(payload, opts=opts.fetch_opts):
217-
crepo = ffi.new('git_repository **')
218-
err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)
219-
payload.check_error(err)
229+
with git_proxy_options(payload, opts.fetch_opts.proxy_opts, proxy):
230+
crepo = ffi.new('git_repository **')
231+
err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)
232+
payload.check_error(err)
220233

221234
# Ok
222235
return Repository._from_c(crepo[0], owned=True)

pygit2/callbacks.py

+23
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,29 @@ def git_fetch_options(payload, opts=None):
370370
yield payload
371371

372372

373+
@contextmanager
374+
def git_proxy_options(
375+
payload: object,
376+
opts: object | None = None,
377+
proxy: None | bool | str = None,
378+
):
379+
if opts is None:
380+
opts = ffi.new('git_proxy_options *')
381+
C.git_proxy_options_init(opts, C.GIT_PROXY_OPTIONS_VERSION)
382+
if proxy is None:
383+
opts.type = C.GIT_PROXY_NONE
384+
elif proxy is True:
385+
opts.type = C.GIT_PROXY_AUTO
386+
elif type(proxy) is str:
387+
opts.type = C.GIT_PROXY_SPECIFIED
388+
# Keep url in memory, otherwise memory is freed and bad things happen
389+
payload.__proxy_url = ffi.new('char[]', to_bytes(proxy))
390+
opts.url = payload.__proxy_url
391+
else:
392+
raise TypeError('Proxy must be None, True, or a string')
393+
yield opts
394+
395+
373396
@contextmanager
374397
def git_push_options(payload, opts=None):
375398
if payload is None:

pygit2/remotes.py

+27-31
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@
2828

2929
# Import from pygit2
3030
from ._pygit2 import Oid
31-
from .callbacks import git_fetch_options, git_push_options, git_remote_callbacks
31+
from .callbacks import (
32+
git_fetch_options,
33+
git_push_options,
34+
git_proxy_options,
35+
git_remote_callbacks,
36+
)
3237
from .enums import FetchPrune
3338
from .errors import check_error
3439
from .ffi import ffi, C
@@ -107,14 +112,16 @@ def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH, proxy=None):
107112
* `True` to enable automatic proxy detection
108113
* an url to a proxy (`http://proxy.example.org:3128/`)
109114
"""
110-
proxy_opts = ffi.new('git_proxy_options *')
111-
C.git_proxy_options_init(proxy_opts, C.GIT_PROXY_OPTIONS_VERSION)
112-
self.__set_proxy(proxy_opts, proxy)
113-
with git_remote_callbacks(callbacks) as payload:
114-
err = C.git_remote_connect(
115-
self._remote, direction, payload.remote_callbacks, proxy_opts, ffi.NULL
116-
)
117-
payload.check_error(err)
115+
with git_proxy_options(self, proxy=proxy) as proxy_opts:
116+
with git_remote_callbacks(callbacks) as payload:
117+
err = C.git_remote_connect(
118+
self._remote,
119+
direction,
120+
payload.remote_callbacks,
121+
proxy_opts,
122+
ffi.NULL,
123+
)
124+
payload.check_error(err)
118125

119126
def fetch(
120127
self,
@@ -154,10 +161,12 @@ def fetch(
154161
opts = payload.fetch_options
155162
opts.prune = prune
156163
opts.depth = depth
157-
self.__set_proxy(opts.proxy_opts, proxy)
158-
with StrArray(refspecs) as arr:
159-
err = C.git_remote_fetch(self._remote, arr.ptr, opts, to_bytes(message))
160-
payload.check_error(err)
164+
with git_proxy_options(self, payload.fetch_options.proxy_opts, proxy):
165+
with StrArray(refspecs) as arr:
166+
err = C.git_remote_fetch(
167+
self._remote, arr.ptr, opts, to_bytes(message)
168+
)
169+
payload.check_error(err)
161170

162171
return TransferProgress(C.git_remote_stats(self._remote))
163172

@@ -276,24 +285,11 @@ def push(self, specs, callbacks=None, proxy=None, push_options=None, threads=1):
276285
with git_push_options(callbacks) as payload:
277286
opts = payload.push_options
278287
opts.pb_parallelism = threads
279-
self.__set_proxy(opts.proxy_opts, proxy)
280-
with StrArray(specs) as refspecs, StrArray(push_options) as pushopts:
281-
pushopts.assign_to(opts.remote_push_options)
282-
err = C.git_remote_push(self._remote, refspecs.ptr, opts)
283-
payload.check_error(err)
284-
285-
def __set_proxy(self, proxy_opts, proxy):
286-
if proxy is None:
287-
proxy_opts.type = C.GIT_PROXY_NONE
288-
elif proxy is True:
289-
proxy_opts.type = C.GIT_PROXY_AUTO
290-
elif type(proxy) is str:
291-
proxy_opts.type = C.GIT_PROXY_SPECIFIED
292-
# Keep url in memory, otherwise memory is freed and bad things happen
293-
self.__url = ffi.new('char[]', to_bytes(proxy))
294-
proxy_opts.url = self.__url
295-
else:
296-
raise TypeError('Proxy must be None, True, or a string')
288+
with git_proxy_options(self, payload.push_options.proxy_opts, proxy):
289+
with StrArray(specs) as refspecs, StrArray(push_options) as pushopts:
290+
pushopts.assign_to(opts.remote_push_options)
291+
err = C.git_remote_push(self._remote, refspecs.ptr, opts)
292+
payload.check_error(err)
297293

298294

299295
class RemoteCollection:

test/test_repository.py

+12
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,18 @@ def test_clone_with_checkout_branch(barerepo, tmp_path):
736736
assert repo.lookup_reference('HEAD').target == 'refs/heads/test'
737737

738738

739+
@utils.requires_proxy
740+
@utils.requires_network
741+
def test_clone_with_proxy(tmp_path):
742+
url = 'https://github.com/libgit2/TestGitRepository'
743+
repo = clone_repository(
744+
url,
745+
tmp_path / 'testrepo-orig.git',
746+
proxy=True,
747+
)
748+
assert not repo.is_empty
749+
750+
739751
# FIXME The tests below are commented because they are broken:
740752
#
741753
# - test_clone_push_url: Passes, but does nothing useful.

0 commit comments

Comments
 (0)