commit - 26344f80565be46e3d97014f055d027ce72b5def
commit + 325de7d3731d19a8290c08a540a09024b4b1c7fd
blob - 7a5662662225293dd263bf9a0b5de7084013b048
blob + 2c61999410586dc67cd4b9d4dfabf04c8f8bd440
--- dulwich/cli.py
+++ dulwich/cli.py
dest="filter_spec",
type=str,
help="git-rev-list-style object filter",
+ )
+ parser.add_option(
+ "--protocol", dest="protocol", type=int, help="Git protocol version to use"
)
options, args = parser.parse_args(args)
branch=options.branch,
refspec=options.refspec,
filter_spec=options.filter_spec,
+ protocol_version=options.protocol,
)
except GitProtocolError as e:
print(f"{e}")
parser.add_argument("from_location", type=str)
parser.add_argument("refspec", type=str, nargs="*")
parser.add_argument("--filter", type=str, nargs=1)
+ parser.add_argument("--protocol", type=int, nargs=1)
args = parser.parse_args(args)
porcelain.pull(
".",
args.from_location or None,
args.refspec or None,
filter_spec=args.filter,
+ protocol_version=args.protocol_version or None,
)
blob - 1d14db8be813ca5db392fb5a9d0017148f943716
blob + 80b203c147d755d8dc92268c9b30b1954670e04f
--- dulwich/client.py
+++ dulwich/client.py
extract_capability_names,
parse_capability,
pkt_line,
+ GIT_PROTOCOL_VERSIONS,
)
from .refs import PEELED_TAG_SUFFIX, _import_remote_refs, read_info_refs
from .repo import Repo
wants,
can_read,
depth,
- protocol_version=None,
+ protocol_version,
):
"""Handle the head of a 'git-upload-pack' request.
can_read: function that returns a boolean that indicates
whether there is extra graph data to read on proto
depth: Depth for request
- protocol_version: desired Git protocol version; defaults to v0
+ protocol_version: Neogiated Git protocol version.
"""
assert isinstance(wants, list) and isinstance(wants[0], bytes)
wantcmd = COMMAND_WANT + b" " + wants[0]
pack_data: Function to call with pack data
progress: Optional progress reporting function
rbufsize: Read buffer size
+ protocol_version: Neogiated Git protocol version.
"""
pkt = proto.read_pkt_line()
while pkt:
depth=None,
ref_prefix=[],
filter_spec=None,
+ protocol_version: Optional[int] = None,
) -> Repo:
"""Clone a repository."""
from .refs import _set_default_branch, _set_head, _set_origin_head
depth=depth,
ref_prefix=ref_prefix,
filter_spec=filter_spec,
+ protocol_version=protocol_version,
)
if origin is not None:
_import_remote_refs(
depth: Optional[int] = None,
ref_prefix: Optional[List[bytes]] = [],
filter_spec: Optional[bytes] = None,
+ protocol_version: Optional[int] = None,
) -> FetchPackResult:
"""Fetch into a target repository.
filter_spec: A git-rev-list-style object filter spec, as bytestring.
Only used if the server supports the Git protocol-v2 'filter'
feature, and ignored otherwise.
+ protocol_version: Desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
Returns:
Dictionary with all remote refs (not just those fetched)
depth=depth,
ref_prefix=ref_prefix,
filter_spec=filter_spec,
+ protocol_version=protocol_version,
)
except BaseException:
abort()
depth: Optional[int] = None,
ref_prefix=[],
filter_spec=None,
+ protocol_version: Optional[int] = None,
):
"""Retrieve a pack from a git smart server.
filter_spec: A git-rev-list-style object filter spec, as bytestring.
Only used if the server supports the Git protocol-v2 'filter'
feature, and ignored otherwise.
+ protocol_version: Desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
Returns:
FetchPackResult object
self._remote_path_encoding = path_encoding
super().__init__(**kwargs)
- async def _connect(self, cmd, path):
+ async def _connect(self, cmd, path, protocol_version=None):
"""Create a connection to the server.
This method is abstract - concrete implementations should
Args:
cmd: The git service name to which we should connect.
path: The path we should pass to the service. (as bytestirng)
+ protocol_version: Desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
"""
raise NotImplementedError
depth=None,
ref_prefix=[],
filter_spec=None,
+ protocol_version: Optional[int] = None,
):
"""Retrieve a pack from a git smart server.
filter_spec: A git-rev-list-style object filter spec, as bytestring.
Only used if the server supports the Git protocol-v2 'filter'
feature, and ignored otherwise.
+ protocol_version: Desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
Returns:
FetchPackResult object
"""
- proto, can_read, stderr = self._connect(b"upload-pack", path)
- self.protocol_version = negotiate_protocol_version(proto)
+ if (
+ protocol_version is not None
+ and protocol_version not in GIT_PROTOCOL_VERSIONS
+ ):
+ raise ValueError("unknown Git protocol version %d" % protocol_version)
+ proto, can_read, stderr = self._connect(b"upload-pack", path, protocol_version)
+ server_protocol_version = negotiate_protocol_version(proto)
+ if server_protocol_version not in GIT_PROTOCOL_VERSIONS:
+ raise ValueError(
+ "unknown Git protocol version %d used by server"
+ % server_protocol_version
+ )
+ if protocol_version and server_protocol_version > protocol_version:
+ raise ValueError(
+ "bad Git protocol version %d used by server" % server_protocol_version
+ )
+ self.protocol_version = server_protocol_version
with proto:
try:
if self.protocol_version == 2:
)
return FetchPackResult(refs, symrefs, agent, new_shallow, new_unshallow)
- def get_refs(self, path):
+ def get_refs(self, path, protocol_version=None):
"""Retrieve the current refs from a git smart server."""
# stock `git ls-remote` uses upload-pack
- proto, _, stderr = self._connect(b"upload-pack", path)
- self.protocol_version = negotiate_protocol_version(proto)
+ if (
+ protocol_version is not None
+ and protocol_version not in GIT_PROTOCOL_VERSIONS
+ ):
+ raise ValueError("unknown Git protocol version %d" % protocol_version)
+ proto, _, stderr = self._connect(b"upload-pack", path, protocol_version)
+ server_protocol_version = negotiate_protocol_version(proto)
+ if server_protocol_version not in GIT_PROTOCOL_VERSIONS:
+ raise ValueError(
+ "unknown Git protocol version %d used by server"
+ % server_protocol_version
+ )
+ if protocol_version and server_protocol_version > protocol_version:
+ raise ValueError(
+ "bad Git protocol version %d used by server" % server_protocol_version
+ )
+ self.protocol_version = server_protocol_version
if self.protocol_version == 2:
server_capabilities = read_server_capabilities(proto.read_pkt_seq())
proto.write_pkt_line(b"command=ls-refs\n")
netloc += ":%d" % self._port
return urlunsplit(("git", netloc, path, "", ""))
- def _connect(self, cmd, path):
+ def _connect(self, cmd, path, protocol_version=None):
if not isinstance(cmd, bytes):
raise TypeError(cmd)
if not isinstance(path, bytes):
if path.startswith(b"/~"):
path = path[1:]
if cmd == b"upload-pack":
- self.protocol_version = 2
+ if protocol_version is None:
+ self.protocol_version = 2
+ else:
+ self.protocol_version = protocol_version
+ else:
+ self.protocol_version = 0
+
+ if cmd == b"upload-pack" and self.protocol_version == 2:
# Git protocol version advertisement is hidden behind two NUL bytes
# for compatibility with older Git server implementations, which
# would crash if something other than a "host=" header was found
version_str = b"\0\0version=%d\0" % self.protocol_version
else:
version_str = b""
- self.protocol_version = 0
# TODO(jelmer): Alternative to ascii?
proto.send_cmd(
b"git-" + cmd, path, b"host=" + self._host.encode("ascii") + version_str
git_command = None
- def _connect(self, service, path):
+ def _connect(self, service, path, protocol_version=None):
if not isinstance(service, bytes):
raise TypeError(service)
if isinstance(path, bytes):
depth=None,
ref_prefix=[],
filter_spec=None,
+ **kwargs,
):
"""Fetch into a target repository.
depth=None,
ref_prefix: Optional[List[bytes]] = [],
filter_spec: Optional[bytes] = None,
+ protocol_version: Optional[int] = None,
) -> FetchPackResult:
"""Retrieve a pack from a local on-disk repository.
password: Optional ssh password for login or private key
key_filename: Optional path to private keyfile
ssh_command: Optional SSH command
+ protocol_version: Desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
"""
raise NotImplementedError(self.run_command)
assert isinstance(cmd, bytes)
return cmd
- def _connect(self, cmd, path):
+ def _connect(self, cmd, path, protocol_version=None):
if not isinstance(cmd, bytes):
raise TypeError(cmd)
if isinstance(path, bytes):
"""
raise NotImplementedError(self._http_request)
- def _discover_references(self, service, base_url):
+ def _discover_references(self, service, base_url, protocol_version=None):
+ if (
+ protocol_version is not None
+ and protocol_version not in GIT_PROTOCOL_VERSIONS
+ ):
+ raise ValueError("unknown Git protocol version %d" % protocol_version)
assert base_url[-1] == "/"
tail = "info/refs"
headers = {"Accept": "*/*"}
# we try: It responds with a Git-protocol-v1-style ref listing
# which lacks the "001f# service=git-receive-pack" marker.
if service == b"git-upload-pack":
- self.protocol_version = 2
- headers["Git-Protocol"] = "version=2"
+ if protocol_version is None:
+ self.protocol_version = 2
+ else:
+ self.protocol_version = protocol_version
+ if self.protocol_version == 2:
+ headers["Git-Protocol"] = "version=2"
else:
self.protocol_version = 0
url = urljoin(base_url, tail)
return server_capabilities, resp, read, proto
proto = Protocol(read, None)
- self.protocol_version = negotiate_protocol_version(proto)
+ server_protocol_version = negotiate_protocol_version(proto)
+ if server_protocol_version not in GIT_PROTOCOL_VERSIONS:
+ raise ValueError(
+ "unknown Git protocol version %d used by server"
+ % server_protocol_version
+ )
+ if protocol_version and server_protocol_version > protocol_version:
+ raise ValueError(
+ "bad Git protocol version %d used by server"
+ % server_protocol_version
+ )
+ self.protocol_version = server_protocol_version
if self.protocol_version == 2:
server_capabilities, resp, read, proto = begin_protocol_v2(proto)
else:
)
# Github sends "version 2" after sending the service name.
# Try to negotiate protocol version 2 again.
- self.protocol_version = negotiate_protocol_version(proto)
+ server_protocol_version = negotiate_protocol_version(proto)
+ if server_protocol_version not in GIT_PROTOCOL_VERSIONS:
+ raise ValueError(
+ "unknown Git protocol version %d used by server"
+ % server_protocol_version
+ )
+ if protocol_version and server_protocol_version > protocol_version:
+ raise ValueError(
+ "bad Git protocol version %d used by server"
+ % server_protocol_version
+ )
+ self.protocol_version = server_protocol_version
if self.protocol_version == 2:
server_capabilities, resp, read, proto = begin_protocol_v2(
proto
depth=None,
ref_prefix=[],
filter_spec=None,
+ protocol_version: Optional[int] = None,
):
"""Retrieve a pack from a git smart server.
filter_spec: A git-rev-list-style object filter spec, as bytestring.
Only used if the server supports the Git protocol-v2 'filter'
feature, and ignored otherwise.
+ protocol_version: Desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
Returns:
FetchPackResult object
"""
url = self._get_url(path)
refs, server_capabilities, url = self._discover_references(
- b"git-upload-pack", url
+ b"git-upload-pack", url, protocol_version
)
(
negotiated_capabilities,
blob - c803373375d6ae2badb37ecc784255a7c3992512
blob + 5c7ee29ec81ccaccd8ba412ddaa5891cbeb5334f
--- dulwich/porcelain.py
+++ dulwich/porcelain.py
refspecs=None,
refspec_encoding=DEFAULT_ENCODING,
filter_spec=None,
+ protocol_version: Optional[int] = None,
**kwargs,
):
"""Clone a local or remote git repository.
filter_spec: A git-rev-list-style object filter spec, as an ASCII string.
Only used if the server supports the Git protocol-v2 'filter'
feature, and ignored otherwise.
+ protocol_version: desired Git protocol version. By default the highest
+ mutually supported protocol version will be used.
Returns: The new repository
"""
if outstream is not None:
depth=depth,
ref_prefix=encoded_refs,
filter_spec=filter_spec,
+ protocol_version=protocol_version,
)
force=False,
refspec_encoding=DEFAULT_ENCODING,
filter_spec=None,
+ protocol_version=None,
**kwargs,
):
"""Pull from remote via dulwich.client.
filter_spec: A git-rev-list-style object filter spec, as an ASCII string.
Only used if the server supports the Git protocol-v2 'filter'
feature, and ignored otherwise.
+ protocol_version: desired Git protocol version. By default the highest
+ mutually supported protocol version will be used
"""
# Open the repo
with open_repo_closing(repo) as r:
determine_wants=determine_wants,
ref_prefix=refspecs,
filter_spec=filter_spec,
+ protocol_version=protocol_version,
)
for lh, rh, force_ref in selected_refs:
if not force_ref and rh in r.refs:
blob - 3d25e6798867b28a1a15ccd3e785ab72231fd3f8
blob + f1f0128fcdc51776d698834cc7574b78eb9a3d98
--- dulwich/protocol.py
+++ dulwich/protocol.py
from .errors import GitProtocolError, HangupException
TCP_GIT_PORT = 9418
+
+# Git protocol version 0 is the original Git protocol, which lacked a
+# version number until Git protocol version 1 was introduced by Brandon
+# Williams in 2017.
+#
+# Protocol version 1 is simply the original v0 protocol with the addition of
+# a single packet line, which precedes the ref advertisement, indicating the
+# protocol version being used. This was done in preparation for protocol v2.
+#
+# Git protocol version 2 was first introduced by Brandon Williams in 2018 and
+# adds many features. See the gitprotocol-v2(5) manual page for details.
+# As of 2024, Git only implements version 2 during 'git fetch' and still uses
+# version 0 during 'git push'.
+GIT_PROTOCOL_VERSIONS = [0, 1, 2]
ZERO_SHA = b"0" * 40
blob - d219bce0a253f7a28472a14c4acaf45864573718
blob + 4c577545c96c8286d0eceac5624dfecf0d6fdf85
--- tests/test_client.py
+++ tests/test_client.py
self.write = write
TraditionalGitClient.__init__(self)
- def _connect(self, service, path):
+ def _connect(self, service, path, protocol_version=None):
return Protocol(self.read, self.write), self.can_read, None