|
@@ -43,7 +43,6 @@ from io import BytesIO, BufferedReader
|
|
|
import logging
|
|
|
import os
|
|
|
import select
|
|
|
-import shlex
|
|
|
import socket
|
|
|
import subprocess
|
|
|
import sys
|
|
@@ -57,7 +56,6 @@ from urllib.parse import (
|
|
|
urlunsplit,
|
|
|
urlunparse,
|
|
|
)
|
|
|
-from urllib.request import url2pathname
|
|
|
|
|
|
|
|
|
import dulwich
|
|
@@ -118,6 +116,10 @@ from dulwich.refs import (
|
|
|
from dulwich.repo import Repo
|
|
|
|
|
|
|
|
|
+# url2pathname is lazily imported
|
|
|
+url2pathname = None
|
|
|
+
|
|
|
+
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
@@ -503,6 +505,129 @@ def _read_side_band64k_data(pkt_seq, channel_callbacks):
|
|
|
cb(pkt)
|
|
|
|
|
|
|
|
|
+def _handle_upload_pack_head(
|
|
|
+ proto, capabilities, graph_walker, wants, can_read, depth
|
|
|
+):
|
|
|
+ """Handle the head of a 'git-upload-pack' request.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ proto: Protocol object to read from
|
|
|
+ capabilities: List of negotiated capabilities
|
|
|
+ graph_walker: GraphWalker instance to call .ack() on
|
|
|
+ wants: List of commits to fetch
|
|
|
+ can_read: function that returns a boolean that indicates
|
|
|
+ whether there is extra graph data to read on proto
|
|
|
+ depth: Depth for request
|
|
|
+
|
|
|
+ Returns:
|
|
|
+
|
|
|
+ """
|
|
|
+ assert isinstance(wants, list) and isinstance(wants[0], bytes)
|
|
|
+ proto.write_pkt_line(
|
|
|
+ COMMAND_WANT
|
|
|
+ + b" "
|
|
|
+ + wants[0]
|
|
|
+ + b" "
|
|
|
+ + b" ".join(sorted(capabilities))
|
|
|
+ + b"\n"
|
|
|
+ )
|
|
|
+ for want in wants[1:]:
|
|
|
+ proto.write_pkt_line(COMMAND_WANT + b" " + want + b"\n")
|
|
|
+ if depth not in (0, None) or getattr(graph_walker, "shallow", None):
|
|
|
+ if CAPABILITY_SHALLOW not in capabilities:
|
|
|
+ raise GitProtocolError(
|
|
|
+ "server does not support shallow capability required for " "depth"
|
|
|
+ )
|
|
|
+ for sha in graph_walker.shallow:
|
|
|
+ proto.write_pkt_line(COMMAND_SHALLOW + b" " + sha + b"\n")
|
|
|
+ if depth is not None:
|
|
|
+ proto.write_pkt_line(
|
|
|
+ COMMAND_DEEPEN + b" " + str(depth).encode("ascii") + b"\n"
|
|
|
+ )
|
|
|
+ proto.write_pkt_line(None)
|
|
|
+ if can_read is not None:
|
|
|
+ (new_shallow, new_unshallow) = _read_shallow_updates(proto.read_pkt_seq())
|
|
|
+ else:
|
|
|
+ new_shallow = new_unshallow = None
|
|
|
+ else:
|
|
|
+ new_shallow = new_unshallow = set()
|
|
|
+ proto.write_pkt_line(None)
|
|
|
+ have = next(graph_walker)
|
|
|
+ while have:
|
|
|
+ proto.write_pkt_line(COMMAND_HAVE + b" " + have + b"\n")
|
|
|
+ if can_read is not None and can_read():
|
|
|
+ pkt = proto.read_pkt_line()
|
|
|
+ parts = pkt.rstrip(b"\n").split(b" ")
|
|
|
+ if parts[0] == b"ACK":
|
|
|
+ graph_walker.ack(parts[1])
|
|
|
+ if parts[2] in (b"continue", b"common"):
|
|
|
+ pass
|
|
|
+ elif parts[2] == b"ready":
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ raise AssertionError(
|
|
|
+ "%s not in ('continue', 'ready', 'common)" % parts[2]
|
|
|
+ )
|
|
|
+ have = next(graph_walker)
|
|
|
+ proto.write_pkt_line(COMMAND_DONE + b"\n")
|
|
|
+ return (new_shallow, new_unshallow)
|
|
|
+
|
|
|
+
|
|
|
+def _handle_upload_pack_tail(
|
|
|
+ proto,
|
|
|
+ capabilities,
|
|
|
+ graph_walker,
|
|
|
+ pack_data,
|
|
|
+ progress=None,
|
|
|
+ rbufsize=_RBUFSIZE,
|
|
|
+):
|
|
|
+ """Handle the tail of a 'git-upload-pack' request.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ proto: Protocol object to read from
|
|
|
+ capabilities: List of negotiated capabilities
|
|
|
+ graph_walker: GraphWalker instance to call .ack() on
|
|
|
+ pack_data: Function to call with pack data
|
|
|
+ progress: Optional progress reporting function
|
|
|
+ rbufsize: Read buffer size
|
|
|
+
|
|
|
+ Returns:
|
|
|
+
|
|
|
+ """
|
|
|
+ pkt = proto.read_pkt_line()
|
|
|
+ while pkt:
|
|
|
+ parts = pkt.rstrip(b"\n").split(b" ")
|
|
|
+ if parts[0] == b"ACK":
|
|
|
+ graph_walker.ack(parts[1])
|
|
|
+ if len(parts) < 3 or parts[2] not in (
|
|
|
+ b"ready",
|
|
|
+ b"continue",
|
|
|
+ b"common",
|
|
|
+ ):
|
|
|
+ break
|
|
|
+ pkt = proto.read_pkt_line()
|
|
|
+ if CAPABILITY_SIDE_BAND_64K in capabilities:
|
|
|
+ if progress is None:
|
|
|
+ # Just ignore progress data
|
|
|
+
|
|
|
+ def progress(x):
|
|
|
+ pass
|
|
|
+
|
|
|
+ _read_side_band64k_data(
|
|
|
+ proto.read_pkt_seq(),
|
|
|
+ {
|
|
|
+ SIDE_BAND_CHANNEL_DATA: pack_data,
|
|
|
+ SIDE_BAND_CHANNEL_PROGRESS: progress,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ while True:
|
|
|
+ data = proto.read(rbufsize)
|
|
|
+ if data == b"":
|
|
|
+ break
|
|
|
+ pack_data(data)
|
|
|
+
|
|
|
+
|
|
|
# TODO(durin42): this doesn't correctly degrade if the server doesn't
|
|
|
# support some capabilities. This should work properly with servers
|
|
|
# that don't support multi_ack.
|
|
@@ -825,128 +950,6 @@ class GitClient(object):
|
|
|
negotiated_capabilities = self._fetch_capabilities & server_capabilities
|
|
|
return (negotiated_capabilities, symrefs, agent)
|
|
|
|
|
|
- def _handle_upload_pack_head(
|
|
|
- self, proto, capabilities, graph_walker, wants, can_read, depth
|
|
|
- ):
|
|
|
- """Handle the head of a 'git-upload-pack' request.
|
|
|
-
|
|
|
- Args:
|
|
|
- proto: Protocol object to read from
|
|
|
- capabilities: List of negotiated capabilities
|
|
|
- graph_walker: GraphWalker instance to call .ack() on
|
|
|
- wants: List of commits to fetch
|
|
|
- can_read: function that returns a boolean that indicates
|
|
|
- whether there is extra graph data to read on proto
|
|
|
- depth: Depth for request
|
|
|
-
|
|
|
- Returns:
|
|
|
-
|
|
|
- """
|
|
|
- assert isinstance(wants, list) and isinstance(wants[0], bytes)
|
|
|
- proto.write_pkt_line(
|
|
|
- COMMAND_WANT
|
|
|
- + b" "
|
|
|
- + wants[0]
|
|
|
- + b" "
|
|
|
- + b" ".join(sorted(capabilities))
|
|
|
- + b"\n"
|
|
|
- )
|
|
|
- for want in wants[1:]:
|
|
|
- proto.write_pkt_line(COMMAND_WANT + b" " + want + b"\n")
|
|
|
- if depth not in (0, None) or getattr(graph_walker, "shallow", None):
|
|
|
- if CAPABILITY_SHALLOW not in capabilities:
|
|
|
- raise GitProtocolError(
|
|
|
- "server does not support shallow capability required for " "depth"
|
|
|
- )
|
|
|
- for sha in graph_walker.shallow:
|
|
|
- proto.write_pkt_line(COMMAND_SHALLOW + b" " + sha + b"\n")
|
|
|
- if depth is not None:
|
|
|
- proto.write_pkt_line(
|
|
|
- COMMAND_DEEPEN + b" " + str(depth).encode("ascii") + b"\n"
|
|
|
- )
|
|
|
- proto.write_pkt_line(None)
|
|
|
- if can_read is not None:
|
|
|
- (new_shallow, new_unshallow) = _read_shallow_updates(proto.read_pkt_seq())
|
|
|
- else:
|
|
|
- new_shallow = new_unshallow = None
|
|
|
- else:
|
|
|
- new_shallow = new_unshallow = set()
|
|
|
- proto.write_pkt_line(None)
|
|
|
- have = next(graph_walker)
|
|
|
- while have:
|
|
|
- proto.write_pkt_line(COMMAND_HAVE + b" " + have + b"\n")
|
|
|
- if can_read is not None and can_read():
|
|
|
- pkt = proto.read_pkt_line()
|
|
|
- parts = pkt.rstrip(b"\n").split(b" ")
|
|
|
- if parts[0] == b"ACK":
|
|
|
- graph_walker.ack(parts[1])
|
|
|
- if parts[2] in (b"continue", b"common"):
|
|
|
- pass
|
|
|
- elif parts[2] == b"ready":
|
|
|
- break
|
|
|
- else:
|
|
|
- raise AssertionError(
|
|
|
- "%s not in ('continue', 'ready', 'common)" % parts[2]
|
|
|
- )
|
|
|
- have = next(graph_walker)
|
|
|
- proto.write_pkt_line(COMMAND_DONE + b"\n")
|
|
|
- return (new_shallow, new_unshallow)
|
|
|
-
|
|
|
- def _handle_upload_pack_tail(
|
|
|
- self,
|
|
|
- proto,
|
|
|
- capabilities,
|
|
|
- graph_walker,
|
|
|
- pack_data,
|
|
|
- progress=None,
|
|
|
- rbufsize=_RBUFSIZE,
|
|
|
- ):
|
|
|
- """Handle the tail of a 'git-upload-pack' request.
|
|
|
-
|
|
|
- Args:
|
|
|
- proto: Protocol object to read from
|
|
|
- capabilities: List of negotiated capabilities
|
|
|
- graph_walker: GraphWalker instance to call .ack() on
|
|
|
- pack_data: Function to call with pack data
|
|
|
- progress: Optional progress reporting function
|
|
|
- rbufsize: Read buffer size
|
|
|
-
|
|
|
- Returns:
|
|
|
-
|
|
|
- """
|
|
|
- pkt = proto.read_pkt_line()
|
|
|
- while pkt:
|
|
|
- parts = pkt.rstrip(b"\n").split(b" ")
|
|
|
- if parts[0] == b"ACK":
|
|
|
- graph_walker.ack(parts[1])
|
|
|
- if len(parts) < 3 or parts[2] not in (
|
|
|
- b"ready",
|
|
|
- b"continue",
|
|
|
- b"common",
|
|
|
- ):
|
|
|
- break
|
|
|
- pkt = proto.read_pkt_line()
|
|
|
- if CAPABILITY_SIDE_BAND_64K in capabilities:
|
|
|
- if progress is None:
|
|
|
- # Just ignore progress data
|
|
|
-
|
|
|
- def progress(x):
|
|
|
- pass
|
|
|
-
|
|
|
- _read_side_band64k_data(
|
|
|
- proto.read_pkt_seq(),
|
|
|
- {
|
|
|
- SIDE_BAND_CHANNEL_DATA: pack_data,
|
|
|
- SIDE_BAND_CHANNEL_PROGRESS: progress,
|
|
|
- },
|
|
|
- )
|
|
|
- else:
|
|
|
- while True:
|
|
|
- data = proto.read(rbufsize)
|
|
|
- if data == b"":
|
|
|
- break
|
|
|
- pack_data(data)
|
|
|
-
|
|
|
|
|
|
def check_wants(wants, refs):
|
|
|
"""Check that a set of wants is valid.
|
|
@@ -1141,7 +1144,7 @@ class TraditionalGitClient(GitClient):
|
|
|
if not wants:
|
|
|
proto.write_pkt_line(None)
|
|
|
return FetchPackResult(refs, symrefs, agent)
|
|
|
- (new_shallow, new_unshallow) = self._handle_upload_pack_head(
|
|
|
+ (new_shallow, new_unshallow) = _handle_upload_pack_head(
|
|
|
proto,
|
|
|
negotiated_capabilities,
|
|
|
graph_walker,
|
|
@@ -1149,7 +1152,7 @@ class TraditionalGitClient(GitClient):
|
|
|
can_read,
|
|
|
depth=depth,
|
|
|
)
|
|
|
- self._handle_upload_pack_tail(
|
|
|
+ _handle_upload_pack_tail(
|
|
|
proto,
|
|
|
negotiated_capabilities,
|
|
|
graph_walker,
|
|
@@ -1612,6 +1615,7 @@ class SubprocessSSHVendor(SSHVendor):
|
|
|
)
|
|
|
|
|
|
if ssh_command:
|
|
|
+ import shlex
|
|
|
args = shlex.split(ssh_command) + ["-x"]
|
|
|
else:
|
|
|
args = ["ssh", "-x"]
|
|
@@ -1653,6 +1657,7 @@ class PLinkSSHVendor(SSHVendor):
|
|
|
):
|
|
|
|
|
|
if ssh_command:
|
|
|
+ import shlex
|
|
|
args = shlex.split(ssh_command) + ["-ssh"]
|
|
|
elif sys.platform == "win32":
|
|
|
args = ["plink.exe", "-ssh"]
|
|
@@ -1905,14 +1910,13 @@ class AbstractHttpGitClient(GitClient):
|
|
|
self.dumb = dumb
|
|
|
GitClient.__init__(self, **kwargs)
|
|
|
|
|
|
- def _http_request(self, url, headers=None, data=None, allow_compression=False):
|
|
|
+ def _http_request(self, url, headers=None, data=None):
|
|
|
"""Perform HTTP request.
|
|
|
|
|
|
Args:
|
|
|
url: Request URL.
|
|
|
headers: Optional custom headers to override defaults.
|
|
|
data: Request data.
|
|
|
- allow_compression: Allow GZipped communication.
|
|
|
|
|
|
Returns:
|
|
|
Tuple (`response`, `read`), where response is an `urllib3`
|
|
@@ -1931,7 +1935,7 @@ class AbstractHttpGitClient(GitClient):
|
|
|
if self.dumb is not True:
|
|
|
tail += "?service=%s" % service.decode("ascii")
|
|
|
url = urljoin(base_url, tail)
|
|
|
- resp, read = self._http_request(url, headers, allow_compression=True)
|
|
|
+ resp, read = self._http_request(url, headers)
|
|
|
|
|
|
if resp.redirect_location:
|
|
|
# Something changed (redirect!), so let's update the base URL
|
|
@@ -2092,7 +2096,7 @@ class AbstractHttpGitClient(GitClient):
|
|
|
raise NotImplementedError(self.fetch_pack)
|
|
|
req_data = BytesIO()
|
|
|
req_proto = Protocol(None, req_data.write)
|
|
|
- (new_shallow, new_unshallow) = self._handle_upload_pack_head(
|
|
|
+ (new_shallow, new_unshallow) = _handle_upload_pack_head(
|
|
|
req_proto,
|
|
|
negotiated_capabilities,
|
|
|
graph_walker,
|
|
@@ -2106,8 +2110,9 @@ class AbstractHttpGitClient(GitClient):
|
|
|
try:
|
|
|
resp_proto = Protocol(read, None)
|
|
|
if new_shallow is None and new_unshallow is None:
|
|
|
- (new_shallow, new_unshallow) = _read_shallow_updates(resp_proto.read_pkt_seq())
|
|
|
- self._handle_upload_pack_tail(
|
|
|
+ (new_shallow, new_unshallow) = _read_shallow_updates(
|
|
|
+ resp_proto.read_pkt_seq())
|
|
|
+ _handle_upload_pack_tail(
|
|
|
resp_proto,
|
|
|
negotiated_capabilities,
|
|
|
graph_walker,
|
|
@@ -2194,15 +2199,11 @@ class Urllib3HttpGitClient(AbstractHttpGitClient):
|
|
|
path = path.decode("utf-8")
|
|
|
return urljoin(self._base_url, path).rstrip("/") + "/"
|
|
|
|
|
|
- def _http_request(self, url, headers=None, data=None, allow_compression=False):
|
|
|
+ def _http_request(self, url, headers=None, data=None):
|
|
|
req_headers = self.pool_manager.headers.copy()
|
|
|
if headers is not None:
|
|
|
req_headers.update(headers)
|
|
|
req_headers["Pragma"] = "no-cache"
|
|
|
- if allow_compression:
|
|
|
- req_headers["Accept-Encoding"] = "gzip"
|
|
|
- else:
|
|
|
- req_headers["Accept-Encoding"] = "identity"
|
|
|
|
|
|
if data is None:
|
|
|
resp = self.pool_manager.request(
|
|
@@ -2232,17 +2233,14 @@ class Urllib3HttpGitClient(AbstractHttpGitClient):
|
|
|
resp.redirect_location = resp.get_redirect_location()
|
|
|
else:
|
|
|
resp.redirect_location = resp_url if resp_url != url else ""
|
|
|
- # TODO(jelmer): Remove BytesIO() call that caches entire response in
|
|
|
- # memory. See https://github.com/jelmer/dulwich/issues/966
|
|
|
- return resp, BytesIO(resp.data).read
|
|
|
+ return resp, resp.read
|
|
|
|
|
|
|
|
|
HttpGitClient = Urllib3HttpGitClient
|
|
|
|
|
|
|
|
|
def _win32_url_to_path(parsed) -> str:
|
|
|
- """
|
|
|
- Convert a file: URL to a path.
|
|
|
+ """Convert a file: URL to a path.
|
|
|
|
|
|
https://datatracker.ietf.org/doc/html/rfc8089
|
|
|
"""
|
|
@@ -2264,7 +2262,10 @@ def _win32_url_to_path(parsed) -> str:
|
|
|
else:
|
|
|
raise NotImplementedError("Non-local file URLs are not supported")
|
|
|
|
|
|
- return url2pathname(netloc + path)
|
|
|
+ global url2pathname
|
|
|
+ if url2pathname is None:
|
|
|
+ from urllib.request import url2pathname # type: ignore
|
|
|
+ return url2pathname(netloc + path) # type: ignore
|
|
|
|
|
|
|
|
|
def get_transport_and_path_from_url(url, config=None, **kwargs):
|