Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proxy option to clone_repository #1354

Merged
merged 2 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/repository.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Functions
>>> repo_path = '/path/to/create/repository'
>>> repo = clone_repository(repo_url, repo_path) # Clones a non-bare repository
>>> repo = clone_repository(repo_url, repo_path, bare=True) # Clones a bare repository
>>> repo = clone_repository(repo_url, repo_path, proxy=True) # Enable automatic proxy detection


.. autofunction:: pygit2.discover_repository
Expand Down
38 changes: 26 additions & 12 deletions pygit2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,20 @@

# Low level API
from ._pygit2 import *
from ._pygit2 import _cache_enums

# High level API
from . import enums
from ._build import __version__
from .blame import Blame, BlameHunk
from .blob import BlobIO
from .callbacks import Payload, RemoteCallbacks, CheckoutCallbacks, StashApplyCallbacks
from .callbacks import git_clone_options, git_fetch_options, get_credentials
from .callbacks import (
git_clone_options,
git_fetch_options,
git_proxy_options,
get_credentials,
)
from .config import Config
from .credentials import *
from .errors import check_error, Passthrough
Expand Down Expand Up @@ -145,14 +151,15 @@ def init_repository(


def clone_repository(
url,
path,
bare=False,
repository=None,
remote=None,
checkout_branch=None,
callbacks=None,
depth=0,
url: str,
path: str,
bare: bool = False,
repository: typing.Callable | None = None,
remote: typing.Callable | None = None,
checkout_branch: str | None = None,
callbacks: RemoteCallbacks | None = None,
depth: int = 0,
proxy: None | bool | str = None,
):
"""
Clones a new Git repository from *url* in the given *path*.
Expand Down Expand Up @@ -193,6 +200,12 @@ def clone_repository(
If greater than 0, creates a shallow clone with a history truncated to
the specified number of commits.
The default is 0 (full commit history).
proxy : None or True or str
Proxy configuration. Can be one of:

* `None` (the default) to disable proxy usage
* `True` to enable automatic proxy detection
* an url to a proxy (`http://proxy.example.org:3128/`)
"""

if callbacks is None:
Expand All @@ -213,9 +226,10 @@ def clone_repository(
opts.checkout_branch = checkout_branch_ref

with git_fetch_options(payload, opts=opts.fetch_opts):
crepo = ffi.new('git_repository **')
err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)
payload.check_error(err)
with git_proxy_options(payload, opts.fetch_opts.proxy_opts, proxy):
crepo = ffi.new('git_repository **')
err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)
payload.check_error(err)

# Ok
return Repository._from_c(crepo[0], owned=True)
Expand Down
23 changes: 23 additions & 0 deletions pygit2/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,29 @@ def git_fetch_options(payload, opts=None):
yield payload


@contextmanager
def git_proxy_options(
payload: object,
opts: object | None = None,
proxy: None | bool | str = None,
):
if opts is None:
opts = ffi.new('git_proxy_options *')
C.git_proxy_options_init(opts, C.GIT_PROXY_OPTIONS_VERSION)
if proxy is None:
opts.type = C.GIT_PROXY_NONE
elif proxy is True:
opts.type = C.GIT_PROXY_AUTO
elif type(proxy) is str:
opts.type = C.GIT_PROXY_SPECIFIED
# Keep url in memory, otherwise memory is freed and bad things happen
payload.__proxy_url = ffi.new('char[]', to_bytes(proxy))
opts.url = payload.__proxy_url
else:
raise TypeError('Proxy must be None, True, or a string')
yield opts


@contextmanager
def git_push_options(payload, opts=None):
if payload is None:
Expand Down
58 changes: 27 additions & 31 deletions pygit2/remotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@

# Import from pygit2
from ._pygit2 import Oid
from .callbacks import git_fetch_options, git_push_options, git_remote_callbacks
from .callbacks import (
git_fetch_options,
git_push_options,
git_proxy_options,
git_remote_callbacks,
)
from .enums import FetchPrune
from .errors import check_error
from .ffi import ffi, C
Expand Down Expand Up @@ -107,14 +112,16 @@ def connect(self, callbacks=None, direction=C.GIT_DIRECTION_FETCH, proxy=None):
* `True` to enable automatic proxy detection
* an url to a proxy (`http://proxy.example.org:3128/`)
"""
proxy_opts = ffi.new('git_proxy_options *')
C.git_proxy_options_init(proxy_opts, C.GIT_PROXY_OPTIONS_VERSION)
self.__set_proxy(proxy_opts, proxy)
with git_remote_callbacks(callbacks) as payload:
err = C.git_remote_connect(
self._remote, direction, payload.remote_callbacks, proxy_opts, ffi.NULL
)
payload.check_error(err)
with git_proxy_options(self, proxy=proxy) as proxy_opts:
with git_remote_callbacks(callbacks) as payload:
err = C.git_remote_connect(
self._remote,
direction,
payload.remote_callbacks,
proxy_opts,
ffi.NULL,
)
payload.check_error(err)

def fetch(
self,
Expand Down Expand Up @@ -154,10 +161,12 @@ def fetch(
opts = payload.fetch_options
opts.prune = prune
opts.depth = depth
self.__set_proxy(opts.proxy_opts, proxy)
with StrArray(refspecs) as arr:
err = C.git_remote_fetch(self._remote, arr.ptr, opts, to_bytes(message))
payload.check_error(err)
with git_proxy_options(self, payload.fetch_options.proxy_opts, proxy):
with StrArray(refspecs) as arr:
err = C.git_remote_fetch(
self._remote, arr.ptr, opts, to_bytes(message)
)
payload.check_error(err)

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

Expand Down Expand Up @@ -276,24 +285,11 @@ def push(self, specs, callbacks=None, proxy=None, push_options=None, threads=1):
with git_push_options(callbacks) as payload:
opts = payload.push_options
opts.pb_parallelism = threads
self.__set_proxy(opts.proxy_opts, proxy)
with StrArray(specs) as refspecs, StrArray(push_options) as pushopts:
pushopts.assign_to(opts.remote_push_options)
err = C.git_remote_push(self._remote, refspecs.ptr, opts)
payload.check_error(err)

def __set_proxy(self, proxy_opts, proxy):
if proxy is None:
proxy_opts.type = C.GIT_PROXY_NONE
elif proxy is True:
proxy_opts.type = C.GIT_PROXY_AUTO
elif type(proxy) is str:
proxy_opts.type = C.GIT_PROXY_SPECIFIED
# Keep url in memory, otherwise memory is freed and bad things happen
self.__url = ffi.new('char[]', to_bytes(proxy))
proxy_opts.url = self.__url
else:
raise TypeError('Proxy must be None, True, or a string')
with git_proxy_options(self, payload.push_options.proxy_opts, proxy):
with StrArray(specs) as refspecs, StrArray(push_options) as pushopts:
pushopts.assign_to(opts.remote_push_options)
err = C.git_remote_push(self._remote, refspecs.ptr, opts)
payload.check_error(err)


class RemoteCollection:
Expand Down
12 changes: 12 additions & 0 deletions test/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,18 @@ def test_clone_with_checkout_branch(barerepo, tmp_path):
assert repo.lookup_reference('HEAD').target == 'refs/heads/test'


@utils.requires_proxy
@utils.requires_network
def test_clone_with_proxy(tmp_path):
url = 'https://github.com/libgit2/TestGitRepository'
repo = clone_repository(
url,
tmp_path / 'testrepo-orig.git',
proxy=True,
)
assert not repo.is_empty


# FIXME The tests below are commented because they are broken:
#
# - test_clone_push_url: Passes, but does nothing useful.
Expand Down
Loading