commit b7b12f804e747621a3a8fa6d949998b289ca0857 from: Jelmer Vernooij date: Sun Mar 20 13:02:36 2022 UTC New upstream release. commit - a5c8b09f2e040ef4ddfc9b7539a30f8125934fda commit + b7b12f804e747621a3a8fa6d949998b289ca0857 blob - 54939dc83a90ef3b2982fae4ffb4984afafb2ec3 blob + 659665c21e87afa5cbeafbbb74e328b0d8f16608 --- .github/workflows/pythonpackage.yml +++ .github/workflows/pythonpackage.yml @@ -44,7 +44,7 @@ jobs: if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'" - name: Install mypy run: | - pip install -U mypy types-paramiko types-certifi + pip install -U mypy types-paramiko types-certifi types-requests if: "matrix.python-version != 'pypy3'" - name: Style checks run: | blob - b6841613dca06b36f8bdac79df0d508aa1648935 blob + f81a3e36726e35ffaecbc9ed7af1a139a801eb5c --- MANIFEST.in +++ MANIFEST.in @@ -14,6 +14,4 @@ recursive-include examples *.py graft dulwich/tests/data include tox.ini include dulwich.cfg -include appveyor.yml include .testr.conf -include .travis.yml blob - b676540d23334b7030edf05c1ca6f6d9cc310cb9 blob + e6809e5671c9611848dbedc5b12f75108647fd8a --- Makefile +++ Makefile @@ -67,3 +67,8 @@ coverage: coverage-html: coverage $(COVERAGE) html + +.PHONY: apidocs + +apidocs: + pydoctor --docformat=google dulwich --project-url=https://www.dulwich.io/ blob - b63494263735a2f6758a23bbafb6950c98683faa blob + f89546cd103dce449639447d94e4e8c6650d2b53 --- NEWS +++ NEWS @@ -1,3 +1,28 @@ +0.20.35 2022-03-20 + + * Document the ``path`` attribute for ``Repo``. + (Jelmer Vernooij, #854) + +0.20.34 2022-03-14 + + * Add support for multivars in configuration. + (Jelmer Vernooij, #718) + +0.20.33 2022-03-05 + + * Fix handling of escaped characters in ignore patterns. + (Jelmer Vernooij, #930) + + * Add ``dulwich.contrib.requests_vendor``. (epopcon) + + * Ensure git config is available in a linked working tree. + (Jesse Cureton, #926) + +0.20.32 2022-01-24 + + * Properly close result repository during test. + (Jelmer Vernooij, #928) + 0.20.31 2022-01-21 * Add GitClient.clone(). (Jelmer Vernooij, #920) blob - 10989e70ddc37e59a0b546b680dcdbc375f5e454 blob + 898d25899cc09937c040d2f8c9a1e9fa914bee87 --- PKG-INFO +++ PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dulwich -Version: 0.20.31 +Version: 0.20.35 Summary: Python Git Library Home-page: https://www.dulwich.io/ Author: Jelmer Vernooij blob - 16dc77096737a4595f741dff25e65abb0eef4f3e blob + 96ad71faa79dddd5cce265c59ee2aaa9482567bd --- debian/changelog +++ debian/changelog @@ -1,8 +1,9 @@ -dulwich (0.20.31-1.2) UNRELEASED; urgency=medium +dulwich (0.20.35-1) UNRELEASED; urgency=medium * Re-export upstream signing key without extra signatures. + * New upstream release. - -- Jelmer Vernooij Sun, 20 Mar 2022 12:59:42 -0000 + -- Jelmer Vernooij Sun, 20 Mar 2022 13:01:46 -0000 dulwich (0.20.31-1.1) unstable; urgency=medium blob - e313fa9d074b29fbbc1daf5fc705e0caf99fc051 blob + 0280087fb5605eb2c6788bcbc757e3d6e6ca0616 --- dulwich/__init__.py +++ dulwich/__init__.py @@ -22,4 +22,4 @@ """Python implementation of the Git file formats and protocols.""" -__version__ = (0, 20, 31) +__version__ = (0, 20, 35) blob - d0cedf7479da398329c5c53b34651510a932c13e blob + 235f80b996fbd0254f44349709de633d1769f7a9 --- dulwich/cli.py +++ dulwich/cli.py @@ -40,8 +40,8 @@ from dulwich import porcelain from dulwich.client import get_transport_and_path from dulwich.errors import ApplyDeltaError from dulwich.index import Index +from dulwich.objectspec import parse_commit from dulwich.pack import Pack, sha_to_hex -from dulwich.patch import write_tree_diff from dulwich.repo import Repo @@ -172,15 +172,15 @@ class cmd_diff(Command): def run(self, args): opts, args = getopt(args, "", []) - if args == []: - print("Usage: dulwich diff COMMITID") - sys.exit(1) - r = Repo(".") - commit_id = args[0] - commit = r[commit_id] + if args == []: + commit_id = b'HEAD' + else: + commit_id = args[0] + commit = parse_commit(r, commit_id) parent_commit = r[commit.parents[0]] - write_tree_diff(sys.stdout, r.object_store, parent_commit.tree, commit.tree) + porcelain.diff_tree( + r, parent_commit.tree, commit.tree, outstream=sys.stdout.buffer) class cmd_dump_pack(Command): blob - 0dc009171566b286623cc4f8411cfd1331872c21 blob + 5f684ff047585e234af4dda7a315530501a81b6d --- dulwich/client.py +++ dulwich/client.py @@ -497,7 +497,7 @@ class GitClient(object): raise NotImplementedError(self.send_pack) def clone(self, path, target_path, mkdir: bool = True, bare=False, origin="origin", - checkout=None, branch=None, depth=None): + checkout=None, branch=None, progress=None, depth=None): """Clone a repository.""" from .refs import _set_origin_head, _set_default_branch, _set_head from .repo import Repo @@ -532,7 +532,7 @@ class GitClient(object): target_config.write_to_path() ref_message = b"clone: from " + encoded_path - result = self.fetch(path, target, depth=depth) + result = self.fetch(path, target, progress=progress, depth=depth) _import_remote_refs( target.refs, origin, result.refs, message=ref_message) @@ -1782,8 +1782,8 @@ def default_urllib3_manager( # noqa: C901 Honour detected proxy configurations. Args: - config: dulwich.config.ConfigDict` instance with Git configuration. - kwargs: Additional arguments for urllib3.ProxyManager + config: `dulwich.config.ConfigDict` instance with Git configuration. + override_kwargs: Additional arguments for `urllib3.ProxyManager` Returns: `pool_manager_cls` (defaults to `urllib3.ProxyManager`) instance for @@ -2154,6 +2154,8 @@ class Urllib3HttpGitClient(AbstractHttpGitClient): basic_auth = urllib3.util.make_headers(basic_auth=credentials) self.pool_manager.headers.update(basic_auth) + self.config = config + super(Urllib3HttpGitClient, self).__init__( base_url=base_url, dumb=dumb, **kwargs) blob - 65f7de9e6ed5c8aacbec0b0c84dff92f24c76224 blob + 7a91806fad30162070ece5f4ab3913cc08eec8dc --- dulwich/config.py +++ dulwich/config.py @@ -28,13 +28,10 @@ TODO: import os import sys +import warnings from typing import BinaryIO, Tuple, Optional -from collections import ( - OrderedDict, -) - try: from collections.abc import ( Iterable, @@ -62,7 +59,12 @@ def lower_key(key): return key -class CaseInsensitiveDict(OrderedDict): +class CaseInsensitiveOrderedMultiDict(MutableMapping): + + def __init__(self): + self._real = [] + self._keyed = {} + @classmethod def make(cls, dict_in=None): @@ -82,16 +84,35 @@ class CaseInsensitiveDict(OrderedDict): return out - def __setitem__(self, key, value, **kwargs): - key = lower_key(key) + def __len__(self): + return len(self._keyed) - super(CaseInsensitiveDict, self).__setitem__(key, value, **kwargs) + def keys(self): + return self._keyed.keys() - def __getitem__(self, item): - key = lower_key(item) + def items(self): + return iter(self._real) - return super(CaseInsensitiveDict, self).__getitem__(key) + def __iter__(self): + return self._keyed.__iter__() + def values(self): + return self._keyed.values() + + def __setitem__(self, key, value): + self._real.append((key, value)) + self._keyed[lower_key(key)] = value + + def __delitem__(self, key): + key = lower_key(key) + del self._keyed[key] + for i, (actual, unused_value) in reversed(list(enumerate(self._real))): + if lower_key(actual) == key: + del self._real[i] + + def __getitem__(self, item): + return self._keyed[lower_key(item)] + def get(self, key, default=SENTINAL): try: return self[key] @@ -103,6 +124,12 @@ class CaseInsensitiveDict(OrderedDict): return default + def get_all(self, key): + key = lower_key(key) + for actual, value in self._real: + if lower_key(actual) == key: + yield value + def setdefault(self, key, default=SENTINAL): try: return self[key] @@ -120,13 +147,26 @@ class Config(object): Args: section: Tuple with section name and optional subsection namee - subsection: Subsection name + name: Variable name Returns: Contents of the setting Raises: KeyError: if the value is not set """ raise NotImplementedError(self.get) + + def get_multivar(self, section, name): + """Retrieve the contents of a multivar configuration setting. + + Args: + section: Tuple with section name and optional subsection namee + name: Variable name + Returns: + Contents of the setting as iterable + Raises: + KeyError: if the value is not set + """ + raise NotImplementedError(self.get_multivar) def get_boolean(self, section, name, default=None): """Retrieve a configuration setting as boolean. @@ -157,10 +197,20 @@ class Config(object): section: Tuple with section name and optional subsection namee name: Name of the configuration value, including section and optional subsection - value: value of the setting + value: value of the setting """ raise NotImplementedError(self.set) + def items(self, section): + """Iterate over the configuration pairs for a specific section. + + Args: + section: Tuple with section name and optional subsection namee + Returns: + Iterator over (name, value) pairs + """ + raise NotImplementedError(self.items) + def iteritems(self, section): """Iterate over the configuration pairs for a specific section. @@ -169,14 +219,27 @@ class Config(object): Returns: Iterator over (name, value) pairs """ - raise NotImplementedError(self.iteritems) + warnings.warn( + "Use %s.items instead." % type(self).__name__, + DeprecationWarning, + stacklevel=3, + ) + return self.items(section) def itersections(self): + warnings.warn( + "Use %s.items instead." % type(self).__name__, + DeprecationWarning, + stacklevel=3, + ) + return self.sections() + + def sections(self): """Iterate over the sections. Returns: Iterator over section tuples """ - raise NotImplementedError(self.itersections) + raise NotImplementedError(self.sections) def has_section(self, name): """Check if a specified section exists. @@ -186,7 +249,7 @@ class Config(object): Returns: boolean indicating whether the section exists """ - return name in self.itersections() + return name in self.sections() class ConfigDict(Config, MutableMapping): @@ -197,7 +260,7 @@ class ConfigDict(Config, MutableMapping): if encoding is None: encoding = sys.getdefaultencoding() self.encoding = encoding - self._values = CaseInsensitiveDict.make(values) + self._values = CaseInsensitiveOrderedMultiDict.make(values) def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self._values) @@ -246,6 +309,17 @@ class ConfigDict(Config, MutableMapping): return section, name + def get_multivar(self, section, name): + section, name = self._check_section_and_name(section, name) + + if len(section) > 1: + try: + return self._values[section][name] + except KeyError: + pass + + return self._values[(section[0],)].get_all(name) + def get(self, section, name): section, name = self._check_section_and_name(section, name) @@ -265,10 +339,10 @@ class ConfigDict(Config, MutableMapping): self._values.setdefault(section)[name] = value - def iteritems(self, section): + def items(self, section): return self._values.get(section).items() - def itersections(self): + def sections(self): return self._values.keys() blob - 5159ee4c27a4cdb6a52d676d1e851ef207216548 blob + 83b1638922dbd682089d554cbf247988cdb75c92 --- dulwich/contrib/diffstat.py +++ dulwich/contrib/diffstat.py @@ -56,15 +56,11 @@ _GIT_UNCHANGED_START = b" " def _parse_patch(lines): - """An internal routine to parse a git style diff or patch to generate - diff stats + """Parse a git style diff or patch to generate diff stats. + Args: - lines: list of byte strings "lines" from the diff to be parsed - Returns: A tuple (names, nametypes, counts) of three lists: - names = list of repo relative file paths - nametypes - list of booolean values indicating if file - is binary (True means binary file) - counts = list of tuples of (added, deleted) counts for that file + lines: list of byte string lines from the diff to be parsed + Returns: A tuple (names, is_binary, counts) of three lists """ names = [] nametypes = [] @@ -323,14 +319,14 @@ index 3b41fd80..64914c78 100644 2. open Sigil.app to the normal nearly blank template epub it generates when opened 3. use Plugins->Manage Plugins menu and make sure the "Use Bundled Python" checkbox is checked 4. use the "Add Plugin" button to navigate to and add testplugin.zip and then hit "Okay" to exit the Manage Plugins Dialog -""" # noqa: E501 W293 +""" testoutput = b""" docs/qt512.7_remove_bad_workaround.patch | 15 ++++++++++++ docs/testplugin_v017.zip | Bin ci_scripts/macgddeploy.py => ci_scripts/gddeploy.py | 0 docs/qt512.6_backport_009abcd_fix.patch | 26 --------------------- docs/Building_Sigil_On_MacOSX.txt | 2 +- - 5 files changed, 16 insertions(+), 27 deletions(-)""" # noqa: W291 + 5 files changed, 16 insertions(+), 27 deletions(-)""" # return 0 on success otherwise return -1 result = diffstat(selftest.split(b"\n")) blob - /dev/null blob + c7f0183a24082ae1b6e78f35444aa1341ce2e187 (mode 644) --- /dev/null +++ dulwich/contrib/requests_vendor.py @@ -0,0 +1,145 @@ +# requests_vendor.py -- requests implementation of the AbstractHttpGitClient interface +# Copyright (C) 2022 Eden Shalit +# +# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU +# General Public License as public by the Free Software Foundation; version 2.0 +# or (at your option) any later version. You can redistribute it and/or +# modify it under the terms of either of these two licenses. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# You should have received a copy of the licenses; if not, see +# for a copy of the GNU General Public License +# and for a copy of the Apache +# License, Version 2.0. + + +"""Requests HTTP client support for Dulwich. + +To use this implementation as the HTTP implementation in Dulwich, override +the dulwich.client.HttpGitClient attribute: + + >>> from dulwich import client as _mod_client + >>> from dulwich.contrib.requests_vendor import RequestsHttpGitClient + >>> _mod_client.HttpGitClient = RequestsHttpGitClient + +This implementation is experimental and does not have any tests. +""" +from io import BytesIO + +from requests import Session + +from dulwich.client import AbstractHttpGitClient, HTTPUnauthorized, HTTPProxyUnauthorized, default_user_agent_string +from dulwich.errors import NotGitRepository, GitProtocolError + + +class RequestsHttpGitClient(AbstractHttpGitClient): + def __init__( + self, + base_url, + dumb=None, + config=None, + username=None, + password=None, + **kwargs + ): + self._username = username + self._password = password + + self.session = get_session(config) + + if username is not None: + self.session.auth = (username, password) + + super(RequestsHttpGitClient, self).__init__( + base_url=base_url, dumb=dumb, **kwargs) + + def _http_request(self, url, headers=None, data=None, allow_compression=False): + req_headers = self.session.headers.copy() + if headers is not None: + req_headers.update(headers) + + if allow_compression: + req_headers["Accept-Encoding"] = "gzip" + else: + req_headers["Accept-Encoding"] = "identity" + + if data: + resp = self.session.post(url, headers=req_headers, data=data) + else: + resp = self.session.get(url, headers=req_headers) + + if resp.status_code == 404: + raise NotGitRepository() + if resp.status_code == 401: + raise HTTPUnauthorized(resp.headers.get("WWW-Authenticate"), url) + if resp.status_code == 407: + raise HTTPProxyUnauthorized(resp.headers.get("Proxy-Authenticate"), url) + if resp.status_code != 200: + raise GitProtocolError( + "unexpected http resp %d for %s" % (resp.status_code, url) + ) + + # Add required fields as stated in AbstractHttpGitClient._http_request + resp.content_type = resp.headers.get("Content-Type") + resp.redirect_location = "" + if resp.history: + resp.redirect_location = resp.url + + read = BytesIO(resp.content).read + + return resp, read + + +def get_session(config): + session = Session() + session.headers.update({"Pragma": "no-cache"}) + + proxy_server = user_agent = ca_certs = ssl_verify = None + + if config is not None: + try: + proxy_server = config.get(b"http", b"proxy") + if isinstance(proxy_server, bytes): + proxy_server = proxy_server.decode() + except KeyError: + pass + + try: + user_agent = config.get(b"http", b"useragent") + if isinstance(user_agent, bytes): + user_agent = user_agent.decode() + except KeyError: + pass + + try: + ssl_verify = config.get_boolean(b"http", b"sslVerify") + except KeyError: + ssl_verify = True + + try: + ca_certs = config.get(b"http", b"sslCAInfo") + if isinstance(ca_certs, bytes): + ca_certs = ca_certs.decode() + except KeyError: + ca_certs = None + + if user_agent is None: + user_agent = default_user_agent_string() + session.headers.update({"User-agent": user_agent}) + + if ca_certs: + session.verify = ca_certs + elif ssl_verify is False: + session.verify = ssl_verify + + if proxy_server: + session.proxies.update({ + "http": proxy_server, + "https": proxy_server + }) + return session blob - 6e53c52d95fbeb7f6b70c4af39343ffdb57ca07a blob + e6cabff0f5ef2e55310a09609f2d189b30feb5a6 --- dulwich/diff_tree.py +++ dulwich/diff_tree.py @@ -130,7 +130,7 @@ def walk_trees(store, tree1_id, tree2_id, prune_identi store: An ObjectStore for looking up objects. tree1_id: The SHA of the first Tree object to iterate, or None. tree2_id: The SHA of the second Tree object to iterate, or None. - param prune_identical: If True, identical subtrees will not be walked. + prune_identical: If True, identical subtrees will not be walked. Returns: Iterator over Pairs of TreeEntry objects for each pair of entries in the trees and their subtrees recursively. If an entry exists in one @@ -345,8 +345,8 @@ def _common_bytes(blocks1, blocks2): """Count the number of common bytes in two block count dicts. Args: - block1: The first dict of block hashcode -> total bytes. - block2: The second dict of block hashcode -> total bytes. + blocks1: The first dict of block hashcode -> total bytes. + blocks2: The second dict of block hashcode -> total bytes. Returns: The number of bytes in common between blocks1 and blocks2. This is only approximate due to possible hash collisions. blob - f34e0dd3f9f2e899e304c9ba41016aec243fb594 blob + 5b596b9aaeef89cf1c7c160d47e87b186f50e3c3 --- dulwich/fastexport.py +++ dulwich/fastexport.py @@ -30,14 +30,14 @@ from dulwich.objects import ( Tag, ZERO_SHA, ) -from fastimport import ( # noqa: E402 +from fastimport import ( commands, errors as fastimport_errors, parser, processor, ) -import stat # noqa: E402 +import stat def split_email(text): blob - 001abe42b13471c889d2eb464026b8492bfcb3fb blob + e920ab831bf1663734695d8fc9e477c0ee57e676 --- dulwich/hooks.py +++ dulwich/hooks.py @@ -134,11 +134,6 @@ class PostCommitShellHook(ShellHook): class CommitMsgShellHook(ShellHook): """commit-msg shell hook - - Args: - args[0]: commit message - Returns: - new commit message or None """ def __init__(self, controldir): blob - b75560f35c84987f5fe9a4ab835faf773f428756 blob + 55a821f46c14bf7e8392d1d7d4ca85dd77cf3551 --- dulwich/ignore.py +++ dulwich/ignore.py @@ -52,6 +52,9 @@ def _translate_segment(segment: bytes) -> bytes: res += b"[^/]*" elif c == b"?": res += b"[^/]" + elif c == b"\\": + res += re.escape(segment[i : i + 1]) + i += 1 elif c == b"[": j = i if j < n and segment[j : j + 1] == b"!": blob - 2c5a1735a5a870714373032bc92e38bb63c090fd blob + 309d5cf95a999b0257d02ad7c3b4bd4b0848c291 --- dulwich/index.py +++ dulwich/index.py @@ -579,7 +579,7 @@ def build_file_from_blob( """Build a file or symlink on disk based on a Git object. Args: - obj: The git object + blob: The git object mode: File mode target_path: Path to write to honor_filemode: An optional flag to honor core.filemode setting in @@ -718,7 +718,7 @@ def blob_from_path_and_mode(fs_path, mode, tree_encodi Args: fs_path: Full file system path to file - st: A stat object + mode: File mode Returns: A `Blob` object """ assert isinstance(fs_path, bytes) @@ -916,7 +916,7 @@ def iter_fresh_entries( Args: paths: Paths to iterate over root_path: Root path to access from - store: Optional store to save new blobs in + object_store: Optional store to save new blobs in Returns: Iterator over path, index_entry """ for path in paths: @@ -958,3 +958,29 @@ def refresh_index(index, root_path): """ for path, entry in iter_fresh_entries(index, root_path): index[path] = path + + +class locked_index(object): + """Lock the index while making modifications. + + Works as a context manager. + """ + def __init__(self, path): + self._path = path + + def __enter__(self): + self._file = GitFile(self._path, "wb") + self._index = Index(self._path) + return self._index + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is not None: + self._file.abort() + return + try: + f = SHA1Writer(self._file) + write_index_dict(f, self._index._byname) + except BaseException: + self._file.abort() + else: + f.close() blob - 92341c4fa7cab4cab008bd0d6887e07df8f10fc4 blob + 1f931b63f20787ec9a40a6d28cbce860623d2a84 --- dulwich/line_ending.py +++ dulwich/line_ending.py @@ -17,7 +17,7 @@ # and for a copy of the Apache # License, Version 2.0. # -""" All line-ending related functions, from conversions to config processing +"""All line-ending related functions, from conversions to config processing Line-ending normalization is a complex beast. Here is some notes and details about how it seems to work. @@ -25,10 +25,10 @@ about how it seems to work. The normalization is a two-fold process that happens at two moments: - When reading a file from the index and to the working directory. For example - when doing a `git clone` or `git checkout` call. We call this process the + when doing a ``git clone`` or ``git checkout`` call. We call this process the read filter in this module. - When writing a file to the index from the working directory. For example - when doing a `git add` call. We call this process the write filter in this + when doing a ``git add`` call. We call this process the write filter in this module. Note that when checking status (getting unstaged changes), whether or not @@ -51,47 +51,47 @@ The code for this heuristic is here: https://git.kernel.org/pub/scm/git/git.git/tree/convert.c#n46 Dulwich have an implementation with a slightly different heuristic, the -`is_binary` function in `dulwich.patch`. +`dulwich.patch.is_binary` function. The binary detection heuristic implementation is close to the one in JGit: https://github.com/eclipse/jgit/blob/f6873ffe522bbc3536969a3a3546bf9a819b92bf/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java#L300 There is multiple variables that impact the normalization. -First, a repository can contains a `.gitattributes` file (or more than one...) +First, a repository can contains a ``.gitattributes`` file (or more than one...) that can further customize the operation on some file patterns, for example: - *.txt text + \\*.txt text -Force all `.txt` files to be treated as text files and to have their lines +Force all ``.txt`` files to be treated as text files and to have their lines endings normalized. - *.jpg -text + \\*.jpg -text -Force all `.jpg` files to be treated as binary files and to not have their +Force all ``.jpg`` files to be treated as binary files and to not have their lines endings converted. - *.vcproj text eol=crlf + \\*.vcproj text eol=crlf -Force all `.vcproj` files to be treated as text files and to have their lines -endings converted into `CRLF` in working directory no matter the native EOL of +Force all ``.vcproj`` files to be treated as text files and to have their lines +endings converted into ``CRLF`` in working directory no matter the native EOL of the platform. - *.sh text eol=lf + \\*.sh text eol=lf -Force all `.sh` files to be treated as text files and to have their lines -endings converted into `LF` in working directory no matter the native EOL of +Force all ``.sh`` files to be treated as text files and to have their lines +endings converted into ``LF`` in working directory no matter the native EOL of the platform. -If the `eol` attribute is not defined, Git uses the `core.eol` configuration +If the ``eol`` attribute is not defined, Git uses the ``core.eol`` configuration value described later. - * text=auto + \\* text=auto Force all files to be scanned by the text file heuristic detection and to have their line endings normalized in case they are detected as text files. -Git also have a obsolete attribute named `crlf` that can be translated to the +Git also have a obsolete attribute named ``crlf`` that can be translated to the corresponding text attribute value. Then there are some configuration option (that can be defined at the @@ -100,30 +100,30 @@ repository or user level): - core.autocrlf - core.eol -`core.autocrlf` is taken into account for all files that doesn't have a `text` -attribute defined in `.gitattributes`; it takes three possible values: +``core.autocrlf`` is taken into account for all files that doesn't have a ``text`` +attribute defined in ``.gitattributes``; it takes three possible values: - - `true`: This forces all files on the working directory to have CRLF + - ``true``: This forces all files on the working directory to have CRLF line-endings in the working directory and convert line-endings to LF when writing to the index. When autocrlf is set to true, eol value is ignored. - - `input`: Quite similar to the `true` value but only force the write + - ``input``: Quite similar to the ``true`` value but only force the write filter, ie line-ending of new files added to the index will get their line-endings converted to LF. - - `false` (default): No normalization is done. + - ``false`` (default): No normalization is done. -`core.eol` is the top-level configuration to define the line-ending to use +``core.eol`` is the top-level configuration to define the line-ending to use when applying the read_filer. It takes three possible values: - - `lf`: When normalization is done, force line-endings to be `LF` in the + - ``lf``: When normalization is done, force line-endings to be ``LF`` in the working directory. - - `crlf`: When normalization is done, force line-endings to be `CRLF` in + - ``crlf``: When normalization is done, force line-endings to be ``CRLF`` in the working directory. - - `native` (default): When normalization is done, force line-endings to be + - ``native`` (default): When normalization is done, force line-endings to be the platform's native line ending. One thing to remember is when line-ending normalization is done on a file, Git -always normalize line-ending to `LF` when writing to the index. +always normalize line-ending to ``LF`` when writing to the index. There are sources that seems to indicate that Git won't do line-ending normalization when a file contains mixed line-endings. I think this logic blob - f6b0db483bbba0874007a906e9fd32a305d95bf4 blob + 5918fa5c48bdc68b61a7969eef2a079f6308c54b --- dulwich/object_store.py +++ dulwich/object_store.py @@ -158,7 +158,7 @@ class BaseObjectStore(object): """Add pack data to this object store. Args: - num_items: Number of items to add + count: Number of items to add pack_data: Iterator over pack data tuples """ if count == 0: @@ -1350,8 +1350,9 @@ class MissingObjectFinder(object): class ObjectStoreGraphWalker(object): """Graph walker that finds what commits are missing from an object store. - :ivar heads: Revisions without descendants in the local repo - :ivar get_parents: Function to retrieve parents in the local repo + Attributes: + heads: Revisions without descendants in the local repo + get_parents: Function to retrieve parents in the local repo """ def __init__(self, local_heads, get_parents, shallow=None): blob - 74ade5c1eff423b5dc083664681cd0f3cabc9023 blob + 5c1eb3a6880bb91cdb18a512cfde52b02b1e1990 --- dulwich/objects.py +++ dulwich/objects.py @@ -222,7 +222,7 @@ def check_time(time_seconds): This will raise an exception if the time is not valid. Args: - time_info: author/committer/tagger info + time_seconds: time in seconds """ # Prevent overflow error blob - 5dbbaffbeda8e8cccfb1edefcde3273c583e9a9f blob + 044e65809841079ebc98cb8ebe5ec15e50005259 --- dulwich/objectspec.py +++ dulwich/objectspec.py @@ -94,7 +94,7 @@ def parse_reftuple(lh_container, rh_container, refspec Args: lh_container: A RefsContainer object - hh_container: A RefsContainer object + rh_container: A RefsContainer object refspec: A string Returns: A tuple with left and right ref Raises: @@ -132,7 +132,7 @@ def parse_reftuples( Args: lh_container: A RefsContainer object - hh_container: A RefsContainer object + rh_container: A RefsContainer object refspecs: A list of refspecs or a string force: Force overwriting for all reftuples Returns: A list of refs @@ -211,7 +211,7 @@ def parse_commit(repo, committish): Args: repo: A` Repo` object - commitish: A string referring to a single commit. + committish: A string referring to a single commit. Returns: A Commit object Raises: KeyError: When the reference commits can not be found blob - 74c1a1fa59090fec5a02a427fa823fe1b1fc983c blob + e7d969eb9b52e7983d2f3a780038242848a3083b --- dulwich/pack.py +++ dulwich/pack.py @@ -66,15 +66,15 @@ else: if sys.platform == "Plan9": has_mmap = False -from dulwich.errors import ( # noqa: E402 +from dulwich.errors import ( ApplyDeltaError, ChecksumMismatch, ) -from dulwich.file import GitFile # noqa: E402 -from dulwich.lru_cache import ( # noqa: E402 +from dulwich.file import GitFile +from dulwich.lru_cache import ( LRUSizeCache, ) -from dulwich.objects import ( # noqa: E402 +from dulwich.objects import ( ShaFile, hex_to_sha, sha_to_hex, @@ -274,7 +274,7 @@ def load_pack_index(path): """Load an index file by path. Args: - filename: Path to the index file + path: Path to the index file Returns: A PackIndex loaded from the given path """ with GitFile(path, "rb") as f: @@ -1599,9 +1599,8 @@ def write_pack( Args: filename: Path to the new pack file (without .pack extension) - objects: Iterable of (object, path) tuples to write. - Should provide __len__ - window_size: Delta window size + objects: (object, path) tuple iterable to write. Should provide __len__ + delta_window_size: Delta window size deltify: Whether to deltify pack objects compression_level: the zlib compression level Returns: Tuple with checksum of pack file and index file @@ -1688,10 +1687,10 @@ def write_pack_objects( Args: f: File to write to - objects: Iterable of (object, path) tuples to write. - Should provide __len__ - window_size: Sliding window size for searching for deltas; - Set to None for default window size. + objects: Iterable of (object, path) tuples to write. Should provide + __len__ + delta_window_size: Sliding window size for searching for deltas; + Set to None for default window size. deltify: Whether to deltify objects compression_level: the zlib compression level to use Returns: Dict mapping id -> (offset, crc32 checksum), pack checksum blob - 95309066ea3b3918b21a7bdac2b7a26d1ff0f0dc blob + 699864caa0e476d41beda09a4b630442422bd036 --- dulwich/porcelain.py +++ dulwich/porcelain.py @@ -448,6 +448,7 @@ def clone( origin=origin, checkout=checkout, branch=branch, + progress=errstream.write, depth=depth, ) @@ -508,7 +509,7 @@ def _is_subdir(subdir, parentdir): def clean(repo=".", target_dir=None): """Remove any untracked files from the target directory recursively - Equivalent to running `git clean -fd` in target_dir. + Equivalent to running ``git clean -fd`` in target_dir. Args: repo: Repository where the files may be tracked @@ -831,7 +832,7 @@ def show( show_object(r, o, decode, outstream) -def diff_tree(repo, old_tree, new_tree, outstream=sys.stdout): +def diff_tree(repo, old_tree, new_tree, outstream=default_bytes_out_stream): """Compares the content and mode of blobs found via two tree objects. Args: @@ -1107,7 +1108,7 @@ def pull( Args: repo: Path to repository remote_location: Location of the remote - refspec: refspecs to fetch + refspecs: refspecs to fetch outstream: A stream file to write to output errstream: A stream file to write to errors """ @@ -1159,7 +1160,7 @@ def status(repo=".", ignored=False): Args: repo: Path to repository or repository object - ignored: Whether to include ignored files in `untracked` + ignored: Whether to include ignored files in untracked Returns: GitStatus tuple, staged - dict with lists of staged paths (diff index/HEAD) unstaged - list of unstaged paths (diff index/working-tree) @@ -1591,7 +1592,7 @@ def ls_tree( Args: repo: Path to the repository - tree_ish: Tree id to list + treeish: Tree id to list outstream: Output stream (defaults to stdout) recursive: Whether to recursively list files name_only: Only print item name @@ -1662,7 +1663,7 @@ def update_head(repo, target, detached=False, new_bran Args: repo: Path to the repository - detach: Create a detached head + detached: Create a detached head target: Branch or committish to switch to new_branch: New branch to create """ @@ -1780,11 +1781,18 @@ def ls_files(repo): return sorted(r.open_index()) +def find_unique_abbrev(object_store, object_id): + """For now, just return 7 characters.""" + # TODO(jelmer): Add some logic here to return a number of characters that + # scales relative with the size of the repository + return object_id.decode("ascii")[:7] + + def describe(repo): """Describe the repository version. Args: - projdir: git repository root + repo: git repository Returns: a string description of the current git revision Examples: "gabcdefh", "v0.1" or "v0.1-5-gabcdefh". @@ -1817,7 +1825,7 @@ def describe(repo): # If there are no tags, return the current commit if len(sorted_tags) == 0: - return "g{}".format(r[r.head()].id.decode("ascii")[:7]) + return "g{}".format(find_unique_abbrev(r.object_store, r[r.head()].id)) # We're now 0 commits from the top commit_count = 0 blob - e82f08d5b3d2cb245370501143754fb2a0e6afca blob + 317475c5be36785854bcea0689a3ce26b40d8523 --- dulwich/refs.py +++ dulwich/refs.py @@ -350,13 +350,13 @@ class RefsContainer(object): """ raise NotImplementedError(self.set_if_equals) - def add_if_new(self, name, ref): + def add_if_new(self, name, ref, committer=None, timestamp=None, + timezone=None, message=None): """Add a new reference only if it does not already exist. Args: name: Ref name ref: Ref value - message: Message for reflog """ raise NotImplementedError(self.add_if_new) blob - 93bfe021f0127a20a677900d924f342440be9602 blob + 7c3c3a782b9b206c29f6311985c4f63e279b9ca9 --- dulwich/repo.py +++ dulwich/repo.py @@ -147,8 +147,8 @@ def _get_default_identity() -> Tuple[str, str]: fullname = None else: try: - gecos = pwd.getpwnam(username).pw_gecos - except KeyError: + gecos = pwd.getpwnam(username).pw_gecos # type: ignore + except (KeyError, AttributeError): fullname = None else: if gecos: @@ -327,9 +327,10 @@ class ParentsProvider(object): class BaseRepo(object): """Base class for a git repository. - :ivar object_store: Dictionary-like object for accessing + Attributes: + object_store: Dictionary-like object for accessing the objects - :ivar refs: Dictionary-like object with the refs in this + refs: Dictionary-like object with the refs in this repository """ @@ -878,7 +879,7 @@ class BaseRepo(object): ): """Create a new commit. - If not specified, `committer` and `author` default to + If not specified, committer and author default to get_user_identity(..., 'COMMITTER') and get_user_identity(..., 'AUTHOR') respectively. @@ -1044,6 +1045,16 @@ class Repo(BaseRepo): the path of the repository. To create a new repository, use the Repo.init class method. + + Note that a repository object may hold on to resources such + as file handles for performance reasons; call .close() to free + up those resources. + + Attributes: + + path (str): Path to the working copy (if it exists) or repository control + directory (if the repository is bare) + bare (bool): Whether this is a bare repository """ def __init__(self, root, object_store=None, bare=None): @@ -1170,8 +1181,8 @@ class Repo(BaseRepo): For a main working tree, it is identical to controldir(). For a linked working tree, it is the control directory of the - main working tree.""" - + main working tree. + """ return self._commondir def _determine_file_mode(self): @@ -1389,6 +1400,7 @@ class Repo(BaseRepo): origin=b"origin", checkout=None, branch=None, + progress=None, depth=None, ): """Clone this repository. @@ -1402,6 +1414,7 @@ class Repo(BaseRepo): cloned from this repository branch: Optional branch or tag to be used as HEAD in the new repository instead of this repository's HEAD. + progress: Optional progress function depth: Depth at which to fetch Returns: Created repository as `Repo` """ @@ -1512,7 +1525,7 @@ class Repo(BaseRepo): """ from dulwich.config import ConfigFile - path = os.path.join(self._controldir, "config") + path = os.path.join(self._commondir, "config") try: return ConfigFile.from_path(path) except FileNotFoundError: blob - 7e8da3c90f09becf1309a403d9408927507ed519 blob + 1c5b03526d737831f8872f51a0ceb8c74793d356 --- dulwich/server.py +++ dulwich/server.py @@ -70,7 +70,7 @@ from dulwich.objects import ( from dulwich.pack import ( write_pack_objects, ) -from dulwich.protocol import ( # noqa: F401 +from dulwich.protocol import ( BufferedPktLineWriter, capability_agent, CAPABILITIES_REF, blob - 2b1e37d46a2a0e1407a73a7d28ad76ec45a04710 blob + 1fd839da6664491e553d2413929d81faeadb24d2 --- dulwich/tests/__init__.py +++ dulwich/tests/__init__.py @@ -145,12 +145,12 @@ def self_test_suite(): def tutorial_test_suite(): - import dulwich.client # noqa: F401 - import dulwich.config # noqa: F401 - import dulwich.index # noqa: F401 - import dulwich.reflog # noqa: F401 - import dulwich.repo # noqa: F401 - import dulwich.server # noqa: F401 + import dulwich.client + import dulwich.config + import dulwich.index + import dulwich.reflog + import dulwich.repo + import dulwich.server import dulwich.patch # noqa: F401 tutorial = [ blob - 965f5c54ccb76ec7544ceecc392b5eb29f270341 blob + 97d835311f22e66de92b4f572277fdfab58fdacc --- dulwich/tests/compat/test_client.py +++ dulwich/tests/compat/test_client.py @@ -337,7 +337,7 @@ class DulwichClientTestBase(object): c = self._client() self.assertEqual(dest.refs[b"refs/heads/abranch"], dummy_commit) c.send_pack(self._build_path("/dest"), lambda _: sendrefs, gen_pack) - self.assertFalse(b"refs/heads/abranch" in dest.refs) + self.assertNotIn(b"refs/heads/abranch", dest.refs) def test_send_new_branch_empty_pack(self): with repo.Repo(os.path.join(self.gitroot, "dest")) as dest: blob - e7fa282cf654333f5cd0582e532af865acbd80b2 blob + 8d2459d14b4fbb4cb69b8da4625ee33b1f6854e8 --- dulwich/tests/compat/test_repository.py +++ dulwich/tests/compat/test_repository.py @@ -192,7 +192,33 @@ class WorkingTreeTestCase(ObjectStoreTestCase): self.assertEqual(worktrees[0][1], "(bare)") self.assertTrue(os.path.samefile(worktrees[0][0], self._mainworktree_repo.path)) + def test_git_worktree_config(self): + """Test that git worktree config parsing matches the git CLI's behavior.""" + # Set some config value in the main repo using the git CLI + require_git_version((2, 7, 0)) + test_name = "Jelmer" + test_email = "jelmer@apache.org" + run_git_or_fail(["config", "user.name", test_name], cwd=self._repo.path) + run_git_or_fail(["config", "user.email", test_email], cwd=self._repo.path) + worktree_cfg = self._worktree_repo.get_config() + main_cfg = self._repo.get_config() + + # Assert that both the worktree repo and main repo have the same view of the config, + # and that the config matches what we set with the git cli + self.assertEqual(worktree_cfg, main_cfg) + for c in [worktree_cfg, main_cfg]: + self.assertEqual(test_name.encode(), c.get((b"user",), b"name")) + self.assertEqual(test_email.encode(), c.get((b"user",), b"email")) + + # Read the config values in the worktree with the git cli and assert they match + # the dulwich-parsed configs + output_name = run_git_or_fail(["config", "user.name"], cwd=self._mainworktree_repo.path).decode().rstrip("\n") + output_email = run_git_or_fail(["config", "user.email"], cwd=self._mainworktree_repo.path).decode().rstrip("\n") + self.assertEqual(test_name, output_name) + self.assertEqual(test_email, output_email) + + class InitNewWorkingDirectoryTestCase(WorkingTreeTestCase): """Test compatibility of Repo.init_new_working_directory.""" blob - 7f0f859409e4b65d8bc44f8d8efe4afff56095c2 blob + 5efa414cdaece60573672bc53a01dc9525ed4cb4 --- dulwich/tests/compat/test_server.py +++ dulwich/tests/compat/test_server.py @@ -59,7 +59,7 @@ class GitServerTestCase(ServerTests, CompatTestCase): def _check_server(self, dul_server): receive_pack_handler_cls = dul_server.handlers[b"git-receive-pack"] caps = receive_pack_handler_cls.capabilities() - self.assertFalse(b"side-band-64k" in caps) + self.assertNotIn(b"side-band-64k", caps) def _start_server(self, repo): backend = DictBackend({b"/": repo}) @@ -94,4 +94,4 @@ class GitServerSideBand64kTestCase(GitServerTestCase): def _check_server(self, server): receive_pack_handler_cls = server.handlers[b"git-receive-pack"] caps = receive_pack_handler_cls.capabilities() - self.assertTrue(b"side-band-64k" in caps) + self.assertIn(b"side-band-64k", caps) blob - 1717b942ad30f42fe2a9338a4e5a37c8979ffe42 blob + 55acf585a0255c3b05b8be4231d4125db3889cdb --- dulwich/tests/test_client.py +++ dulwich/tests/test_client.py @@ -427,21 +427,21 @@ class GitClientTests(TestCase): class TestGetTransportAndPath(TestCase): def test_tcp(self): c, path = get_transport_and_path("git://foo.com/bar/baz") - self.assertTrue(isinstance(c, TCPGitClient)) + self.assertIsInstance(c, TCPGitClient) self.assertEqual("foo.com", c._host) self.assertEqual(TCP_GIT_PORT, c._port) self.assertEqual("/bar/baz", path) def test_tcp_port(self): c, path = get_transport_and_path("git://foo.com:1234/bar/baz") - self.assertTrue(isinstance(c, TCPGitClient)) + self.assertIsInstance(c, TCPGitClient) self.assertEqual("foo.com", c._host) self.assertEqual(1234, c._port) self.assertEqual("/bar/baz", path) def test_git_ssh_explicit(self): c, path = get_transport_and_path("git+ssh://foo.com/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -449,7 +449,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_explicit(self): c, path = get_transport_and_path("ssh://foo.com/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -457,20 +457,20 @@ class TestGetTransportAndPath(TestCase): def test_ssh_port_explicit(self): c, path = get_transport_and_path("git+ssh://foo.com:1234/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(1234, c.port) self.assertEqual("/bar/baz", path) def test_username_and_port_explicit_unknown_scheme(self): c, path = get_transport_and_path("unknown://git@server:7999/dply/stuff.git") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("unknown", c.host) self.assertEqual("//git@server:7999/dply/stuff.git", path) def test_username_and_port_explicit(self): c, path = get_transport_and_path("ssh://git@server:7999/dply/stuff.git") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("git", c.username) self.assertEqual("server", c.host) self.assertEqual(7999, c.port) @@ -478,7 +478,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_abspath_doubleslash(self): c, path = get_transport_and_path("git+ssh://foo.com//bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -486,14 +486,14 @@ class TestGetTransportAndPath(TestCase): def test_ssh_port(self): c, path = get_transport_and_path("git+ssh://foo.com:1234/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(1234, c.port) self.assertEqual("/bar/baz", path) def test_ssh_implicit(self): c, path = get_transport_and_path("foo:/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -501,7 +501,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_host(self): c, path = get_transport_and_path("foo.com:/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -509,7 +509,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_user_host(self): c, path = get_transport_and_path("user@foo.com:/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual("user", c.username) @@ -517,7 +517,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_relpath(self): c, path = get_transport_and_path("foo:bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -525,7 +525,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_host_relpath(self): c, path = get_transport_and_path("foo.com:bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -533,7 +533,7 @@ class TestGetTransportAndPath(TestCase): def test_ssh_user_host_relpath(self): c, path = get_transport_and_path("user@foo.com:bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual("user", c.username) @@ -541,25 +541,25 @@ class TestGetTransportAndPath(TestCase): def test_local(self): c, path = get_transport_and_path("foo.bar/baz") - self.assertTrue(isinstance(c, LocalGitClient)) + self.assertIsInstance(c, LocalGitClient) self.assertEqual("foo.bar/baz", path) @skipIf(sys.platform != "win32", "Behaviour only happens on windows.") def test_local_abs_windows_path(self): c, path = get_transport_and_path("C:\\foo.bar\\baz") - self.assertTrue(isinstance(c, LocalGitClient)) + self.assertIsInstance(c, LocalGitClient) self.assertEqual("C:\\foo.bar\\baz", path) def test_error(self): # Need to use a known urlparse.uses_netloc URL scheme to get the # expected parsing of the URL on Python versions less than 2.6.5 c, path = get_transport_and_path("prospero://bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) def test_http(self): url = "https://github.com/jelmer/dulwich" c, path = get_transport_and_path(url) - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) def test_http_auth(self): @@ -567,7 +567,7 @@ class TestGetTransportAndPath(TestCase): c, path = get_transport_and_path(url) - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) self.assertEqual("user", c._username) self.assertEqual("passwd", c._password) @@ -577,7 +577,7 @@ class TestGetTransportAndPath(TestCase): c, path = get_transport_and_path(url, username="user2", password="blah") - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) self.assertEqual("user2", c._username) self.assertEqual("blah", c._password) @@ -587,7 +587,7 @@ class TestGetTransportAndPath(TestCase): c, path = get_transport_and_path(url, username="user2", password="blah") - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) self.assertEqual("user", c._username) self.assertEqual("passwd", c._password) @@ -597,7 +597,7 @@ class TestGetTransportAndPath(TestCase): c, path = get_transport_and_path(url) - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) self.assertIs(None, c._username) self.assertIs(None, c._password) @@ -606,21 +606,21 @@ class TestGetTransportAndPath(TestCase): class TestGetTransportAndPathFromUrl(TestCase): def test_tcp(self): c, path = get_transport_and_path_from_url("git://foo.com/bar/baz") - self.assertTrue(isinstance(c, TCPGitClient)) + self.assertIsInstance(c, TCPGitClient) self.assertEqual("foo.com", c._host) self.assertEqual(TCP_GIT_PORT, c._port) self.assertEqual("/bar/baz", path) def test_tcp_port(self): c, path = get_transport_and_path_from_url("git://foo.com:1234/bar/baz") - self.assertTrue(isinstance(c, TCPGitClient)) + self.assertIsInstance(c, TCPGitClient) self.assertEqual("foo.com", c._host) self.assertEqual(1234, c._port) self.assertEqual("/bar/baz", path) def test_ssh_explicit(self): c, path = get_transport_and_path_from_url("git+ssh://foo.com/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -628,14 +628,14 @@ class TestGetTransportAndPathFromUrl(TestCase): def test_ssh_port_explicit(self): c, path = get_transport_and_path_from_url("git+ssh://foo.com:1234/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(1234, c.port) self.assertEqual("/bar/baz", path) def test_ssh_homepath(self): c, path = get_transport_and_path_from_url("git+ssh://foo.com/~/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(None, c.port) self.assertEqual(None, c.username) @@ -643,7 +643,7 @@ class TestGetTransportAndPathFromUrl(TestCase): def test_ssh_port_homepath(self): c, path = get_transport_and_path_from_url("git+ssh://foo.com:1234/~/bar/baz") - self.assertTrue(isinstance(c, SSHGitClient)) + self.assertIsInstance(c, SSHGitClient) self.assertEqual("foo.com", c.host) self.assertEqual(1234, c.port) self.assertEqual("/~/bar/baz", path) @@ -671,7 +671,7 @@ class TestGetTransportAndPathFromUrl(TestCase): def test_http(self): url = "https://github.com/jelmer/dulwich" c, path = get_transport_and_path_from_url(url) - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("https://github.com", c.get_url(b"/")) self.assertEqual("/jelmer/dulwich", path) @@ -679,12 +679,12 @@ class TestGetTransportAndPathFromUrl(TestCase): url = "https://github.com:9090/jelmer/dulwich" c, path = get_transport_and_path_from_url(url) self.assertEqual("https://github.com:9090", c.get_url(b"/")) - self.assertTrue(isinstance(c, HttpGitClient)) + self.assertIsInstance(c, HttpGitClient) self.assertEqual("/jelmer/dulwich", path) def test_file(self): c, path = get_transport_and_path_from_url("file:///home/jelmer/foo") - self.assertTrue(isinstance(c, LocalGitClient)) + self.assertIsInstance(c, LocalGitClient) self.assertEqual("/home/jelmer/foo", path) @@ -848,6 +848,7 @@ class LocalGitClientTests(TestCase): target = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, target) result_repo = c.clone(s.path, target, mkdir=False) + self.addCleanup(result_repo.close) expected = dict(s.get_refs()) expected[b'refs/remotes/origin/HEAD'] = expected[b'HEAD'] expected[b'refs/remotes/origin/master'] = expected[b'refs/heads/master'] blob - 487c2aa87a71268507d4f499917e408a8868ef4e blob + 39f58c87262e96c0e28783e3f312a4fb7c29a5b9 --- dulwich/tests/test_config.py +++ dulwich/tests/test_config.py @@ -108,6 +108,11 @@ class ConfigFileTests(TestCase): self.assertEqual(b"bar", cf.get((b"core",), b"foo")) self.assertEqual(b"bar", cf.get((b"core", b"foo"), b"foo")) + def test_from_file_multiple(self): + cf = self.from_file(b"[core]\nfoo = bar\nfoo = blah\n") + self.assertEqual([b"bar", b"blah"], list(cf.get_multivar((b"core",), b"foo"))) + self.assertEqual([], list(cf.get_multivar((b"core", ), b"blah"))) + def test_from_file_utf8_bom(self): text = "[core]\nfoo = b\u00e4r\n".encode("utf-8-sig") cf = self.from_file(text) @@ -156,6 +161,12 @@ class ConfigFileTests(TestCase): cf = self.from_file(b"[branch.foo]\nfoo = bar\n") self.assertEqual(b"bar", cf.get((b"branch", b"foo"), b"foo")) + def test_write_preserve_multivar(self): + cf = self.from_file(b"[core]\nfoo = bar\nfoo = blah\n") + f = BytesIO() + cf.write_to_file(f) + self.assertEqual(b"[core]\n\tfoo = bar\n\tfoo = blah\n", f.getvalue()) + def test_write_to_file_empty(self): c = ConfigFile() f = BytesIO() @@ -257,24 +268,24 @@ class ConfigDictTests(TestCase): cd[b"a"] = b"b" self.assertEqual(cd[b"a"], b"b") - def test_iteritems(self): + def test_items(self): cd = ConfigDict() cd.set((b"core",), b"foo", b"bla") cd.set((b"core2",), b"foo", b"bloe") - self.assertEqual([(b"foo", b"bla")], list(cd.iteritems((b"core",)))) + self.assertEqual([(b"foo", b"bla")], list(cd.items((b"core",)))) - def test_iteritems_nonexistant(self): + def test_items_nonexistant(self): cd = ConfigDict() cd.set((b"core2",), b"foo", b"bloe") - self.assertEqual([], list(cd.iteritems((b"core",)))) + self.assertEqual([], list(cd.items((b"core",)))) - def test_itersections(self): + def test_sections(self): cd = ConfigDict() cd.set((b"core2",), b"foo", b"bloe") - self.assertEqual([(b"core2",)], list(cd.itersections())) + self.assertEqual([(b"core2",)], list(cd.sections())) class StackedConfigTests(TestCase): blob - 09d55c26914e851357eed4b366f004897a3d15ec blob + cd2196e30bfe4a74ff5d71bb881b90e838d863b4 --- dulwich/tests/test_fastexport.py +++ dulwich/tests/test_fastexport.py @@ -197,8 +197,8 @@ M 100644 :1 a ) ) self.assertEqual(2, len(markers)) - self.assertTrue(isinstance(self.repo[markers[b"1"]], Blob)) - self.assertTrue(isinstance(self.repo[markers[b"2"]], Commit)) + self.assertIsInstance(self.repo[markers[b"1"]], Blob) + self.assertIsInstance(self.repo[markers[b"2"]], Commit) def test_file_add(self): from fastimport import commands blob - ec0f4d73817458e7e9e697ec4a90f2e36ba4b06f blob + cc6d68a5788bad477de2ea137f64e5234083ddfb --- dulwich/tests/test_file.py +++ dulwich/tests/test_file.py @@ -112,7 +112,7 @@ class GitFileTests(TestCase): def test_readonly(self): f = GitFile(self.path("foo"), "rb") - self.assertTrue(isinstance(f, io.IOBase)) + self.assertIsInstance(f, io.IOBase) self.assertEqual(b"foo contents", f.read()) self.assertEqual(b"", f.read()) f.seek(4) blob - 6cb5c6611243da3c836351588f13e1cec027657a blob + b3a6ab135e31ae9c4413e62cb04f0b72db074786 --- dulwich/tests/test_ignore.py +++ dulwich/tests/test_ignore.py @@ -82,6 +82,9 @@ TRANSLATE_TESTS = [ (b"**/bla.c", b"(?ms)(.*/)?bla\\.c/?\\Z"), (b"foo/**/bar", b"(?ms)foo(/.*)?\\/bar/?\\Z"), (b"foo/bar/*", b"(?ms)foo\\/bar\\/[^/]+/?\\Z"), + (b"/foo\\[bar\\]", b"(?ms)foo\\[bar\\]/?\\Z"), + (b"/foo[bar]", b"(?ms)foo[bar]/?\\Z"), + (b"/foo[0-9]", b"(?ms)foo[0-9]/?\\Z"), ] @@ -113,7 +116,7 @@ class ReadIgnorePatterns(TestCase): with trailing whitespace with escaped trailing whitespace\\ """ - ) # noqa: W291 + ) self.assertEqual( list(read_ignore_patterns(f)), [ @@ -183,7 +186,13 @@ class IgnoreFilterTests(TestCase): self.assertFalse(filter.is_ignored(b"foo/bar/")) self.assertFalse(filter.is_ignored(b"foo/bar/bloe")) + def test_regex_special(self): + # See https://github.com/dulwich/dulwich/issues/930#issuecomment-1026166429 + filter = IgnoreFilter([b"/foo\\[bar\\]", b"/foo"]) + self.assertTrue(filter.is_ignored("foo")) + self.assertTrue(filter.is_ignored("foo[bar]")) + class IgnoreFilterStackTests(TestCase): def test_stack_first(self): filter1 = IgnoreFilter([b"[a].c", b"[b].c", b"![d].c"]) @@ -239,7 +248,7 @@ class IgnoreFilterManagerTests(TestCase): with open(os.path.join(repo.path, 'foo', 'bar'), 'wb') as f: f.write(b'IGNORED') - + m = IgnoreFilterManager.from_repo(repo) self.assertTrue(m.is_ignored('foo/bar')) blob - 7696e3eec71c6c00d994a75f59e2a337d54822e8 blob + 74fb848291178e8cd036b00e96506fdf46c6daf6 --- dulwich/tests/test_lru_cache.py +++ dulwich/tests/test_lru_cache.py @@ -43,18 +43,18 @@ class TestLRUCache(TestCase): def test_missing(self): cache = lru_cache.LRUCache(max_cache=10) - self.assertFalse("foo" in cache) + self.assertNotIn("foo", cache) self.assertRaises(KeyError, cache.__getitem__, "foo") cache["foo"] = "bar" self.assertEqual("bar", cache["foo"]) - self.assertTrue("foo" in cache) - self.assertFalse("bar" in cache) + self.assertIn("foo", cache) + self.assertNotIn("bar", cache) def test_map_None(self): # Make sure that we can properly map None as a key. cache = lru_cache.LRUCache(max_cache=10) - self.assertFalse(None in cache) + self.assertNotIn(None, cache) cache[None] = 1 self.assertEqual(1, cache[None]) cache[None] = 2 @@ -80,8 +80,8 @@ class TestLRUCache(TestCase): # With a max cache of 1, adding 'baz' should pop out 'foo' cache["baz"] = "biz" - self.assertFalse("foo" in cache) - self.assertTrue("baz" in cache) + self.assertNotIn("foo", cache) + self.assertIn("baz", cache) self.assertEqual("biz", cache["baz"]) @@ -97,7 +97,7 @@ class TestLRUCache(TestCase): # This must kick out 'foo' because it was the last accessed cache["nub"] = "in" - self.assertFalse("foo" in cache) + self.assertNotIn("foo", cache) def test_cleanup(self): """Test that we can use a cleanup function.""" @@ -236,7 +236,7 @@ class TestLRUCache(TestCase): self.assertEqual(20, cache.get(2)) self.assertEqual(None, cache.get(3)) obj = object() - self.assertTrue(obj is cache.get(3, obj)) + self.assertIs(obj, cache.get(3, obj)) self.assertEqual([2, 1], [n.key for n in cache._walk_lru()]) self.assertEqual(10, cache.get(1)) self.assertEqual([1, 2], [n.key for n in cache._walk_lru()]) blob - 742ce9acc0aae01695b445c9c9108da5ec61e25d blob + 817f4d15a1a24e84f6b267875d821213814bbe29 --- dulwich/tests/test_missing_obj_finder.py +++ dulwich/tests/test_missing_obj_finder.py @@ -43,9 +43,10 @@ class MissingObjectFinderTest(TestCase): def assertMissingMatch(self, haves, wants, expected): for sha, path in self.store.find_missing_objects(haves, wants, set()): - self.assertTrue( - sha in expected, - "(%s,%s) erroneously reported as missing" % (sha, path), + self.assertIn( + sha, + expected, + "(%s,%s) erroneously reported as missing" % (sha, path) ) expected.remove(sha) blob - 2f0329f2840b84326570efa1fb06f8d84a87407e blob + 08850a68060621ab80be4cb2e9557d57d431df69 --- dulwich/tests/test_object_store.py +++ dulwich/tests/test_object_store.py @@ -138,7 +138,7 @@ class ObjectStoreTests(object): self.assertRaises(KeyError, lambda: self.store[b"a" * 40]) def test_contains_nonexistant(self): - self.assertFalse((b"a" * 40) in self.store) + self.assertNotIn(b"a" * 40, self.store) def test_add_objects_empty(self): self.store.add_objects([]) @@ -165,7 +165,7 @@ class ObjectStoreTests(object): def test_add_object(self): self.store.add_object(testobject) self.assertEqual(set([testobject.id]), set(self.store)) - self.assertTrue(testobject.id in self.store) + self.assertIn(testobject.id, self.store) r = self.store[testobject.id] self.assertEqual(r, testobject) @@ -173,7 +173,7 @@ class ObjectStoreTests(object): data = [(testobject, "mypath")] self.store.add_objects(data) self.assertEqual(set([testobject.id]), set(self.store)) - self.assertTrue(testobject.id in self.store) + self.assertIn(testobject.id, self.store) r = self.store[testobject.id] self.assertEqual(r, testobject) @@ -593,15 +593,15 @@ class TreeLookupPathTests(TestCase): def test_lookup_blob(self): o_id = tree_lookup_path(self.get_object, self.tree_id, b"a")[1] - self.assertTrue(isinstance(self.store[o_id], Blob)) + self.assertIsInstance(self.store[o_id], Blob) def test_lookup_tree(self): o_id = tree_lookup_path(self.get_object, self.tree_id, b"ad")[1] - self.assertTrue(isinstance(self.store[o_id], Tree)) + self.assertIsInstance(self.store[o_id], Tree) o_id = tree_lookup_path(self.get_object, self.tree_id, b"ad/bd")[1] - self.assertTrue(isinstance(self.store[o_id], Tree)) + self.assertIsInstance(self.store[o_id], Tree) o_id = tree_lookup_path(self.get_object, self.tree_id, b"ad/bd/")[1] - self.assertTrue(isinstance(self.store[o_id], Tree)) + self.assertIsInstance(self.store[o_id], Tree) def test_lookup_submodule(self): tree_lookup_path(self.get_object, self.tree_id, b"d")[1] blob - d2c37455b8486f7a7a3d3f5f2c1ce8449e2e83e5 blob + 7c8256d34bc4d081583f9b5e430ec7a0c110513f --- dulwich/tests/test_objects.py +++ dulwich/tests/test_objects.py @@ -267,7 +267,7 @@ class BlobReadTests(TestCase): def test_stub_sha(self): sha = b"5" * 40 c = make_commit(id=sha, message=b"foo") - self.assertTrue(isinstance(c, Commit)) + self.assertIsInstance(c, Commit) self.assertEqual(sha, c.id) self.assertNotEqual(sha, c.sha()) @@ -333,7 +333,7 @@ class CommitSerializationTests(TestCase): def test_encoding(self): c = self.make_commit(encoding=b"iso8859-1") - self.assertTrue(b"encoding iso8859-1\n" in c.as_raw_string()) + self.assertIn(b"encoding iso8859-1\n", c.as_raw_string()) def test_short_timestamp(self): c = self.make_commit(commit_time=30) @@ -373,11 +373,11 @@ class CommitSerializationTests(TestCase): def test_timezone(self): c = self.make_commit(commit_timezone=(5 * 60)) - self.assertTrue(b" +0005\n" in c.as_raw_string()) + self.assertIn(b" +0005\n", c.as_raw_string()) def test_neg_timezone(self): c = self.make_commit(commit_timezone=(-1 * 3600)) - self.assertTrue(b" -0100\n" in c.as_raw_string()) + self.assertIn(b" -0100\n", c.as_raw_string()) def test_deserialize(self): c = self.make_commit() @@ -434,7 +434,7 @@ gpgsig -----BEGIN PGP SIGNATURE----- Merge ../b """, commit.as_raw_string(), - ) # noqa: W291,W293 + ) def test_serialize_mergetag(self): tag = make_object( @@ -472,7 +472,7 @@ mergetag object a38d6181ff27824c79fc7df825164a212eff6a Merge ../b """, commit.as_raw_string(), - ) # noqa: W291,W293 + ) def test_serialize_mergetags(self): tag = make_object( @@ -523,7 +523,7 @@ mergetag object a38d6181ff27824c79fc7df825164a212eff6a Merge ../b """, commit.as_raw_string(), - ) # noqa: W291,W293 + ) def test_deserialize_mergetag(self): tag = make_object( @@ -756,7 +756,7 @@ gpgsig -----BEGIN PGP SIGNATURE----- foo """ - ) # noqa: W291,W293 + ) self.assertEqual(b"foo\n", c.message) self.assertEqual([], c.extra) self.assertEqual( @@ -801,7 +801,7 @@ gpgsig -----BEGIN PGP SIGNATURE----- 3.3.0 version bump and docs """ - ) # noqa: W291,W293 + ) self.assertEqual([], c.extra) self.assertEqual( b"""\ @@ -904,7 +904,7 @@ class TreeTests(ShaFileCheckTests): actual = do_sort(_TREE_ITEMS) self.assertEqual(_SORTED_TREE_ITEMS, actual) - self.assertTrue(isinstance(actual[0], TreeEntry)) + self.assertIsInstance(actual[0], TreeEntry) # C/Python implementations may differ in specific error types, but # should all error on invalid inputs. @@ -1295,9 +1295,9 @@ class ShaFileCopyTests(TestCase): oclass = object_class(orig.type_num) copy = orig.copy() - self.assertTrue(isinstance(copy, oclass)) + self.assertIsInstance(copy, oclass) self.assertEqual(copy, orig) - self.assertTrue(copy is not orig) + self.assertIsNot(copy, orig) def test_commit_copy(self): attrs = { blob - 407893e9725ea126bc6c10657861381eb47b1c52 blob + d8708c906ad2ff1a57620d5fdf265b91bc023077 --- dulwich/tests/test_pack.py +++ dulwich/tests/test_pack.py @@ -390,7 +390,7 @@ class TestPack(PackTests): def test_contains(self): with self.get_pack(pack1_sha) as p: - self.assertTrue(tree_sha in p) + self.assertIn(tree_sha, p) def test_get(self): with self.get_pack(pack1_sha) as p: @@ -527,9 +527,9 @@ class TestPack(PackTests): objs = {o.id: o for o in p.iterobjects()} self.assertEqual(3, len(objs)) self.assertEqual(sorted(objs), sorted(p.index)) - self.assertTrue(isinstance(objs[a_sha], Blob)) - self.assertTrue(isinstance(objs[tree_sha], Tree)) - self.assertTrue(isinstance(objs[commit_sha], Commit)) + self.assertIsInstance(objs[a_sha], Blob) + self.assertIsInstance(objs[tree_sha], Tree) + self.assertIsInstance(objs[commit_sha], Commit) class TestThinPack(PackTests): @@ -703,7 +703,7 @@ class BaseTestPackIndexWriting(object): if self._has_crc32_checksum: self.assertEqual(my_crc, actual_crc) else: - self.assertTrue(actual_crc is None) + self.assertIsNone(actual_crc) def test_single(self): entry_sha = hex_to_sha("6f670c0fb53f9463760b7295fbb814e965fb20c8") @@ -721,7 +721,7 @@ class BaseTestPackIndexWriting(object): if self._has_crc32_checksum: self.assertEqual(my_crc, actual_crc) else: - self.assertTrue(actual_crc is None) + self.assertIsNone(actual_crc) class BaseTestFilePackIndexWriting(BaseTestPackIndexWriting): @@ -992,9 +992,10 @@ class DeltaChainIteratorTests(TestCase): def get_raw_no_repeat(self, bin_sha): """Wrapper around store.get_raw that doesn't allow repeat lookups.""" hex_sha = sha_to_hex(bin_sha) - self.assertFalse( - hex_sha in self.fetched, - "Attempted to re-fetch object %s" % hex_sha, + self.assertNotIn( + hex_sha, + self.fetched, + "Attempted to re-fetch object %s" % hex_sha ) self.fetched.add(hex_sha) return self.store.get_raw(hex_sha) blob - a553a34a6b6ce0271aa6edb2b7e3aff313a2db72 blob + 38aac88f64c2b9be1578d7f151269b1520b00bf1 --- dulwich/tests/test_patch.py +++ dulwich/tests/test_patch.py @@ -93,7 +93,7 @@ Subject: [PATCH 1/2] Remove executable bit from prey.i -- 1.7.0.4 -""" # noqa: W291 +""" c, diff, version = git_am_patch_split(StringIO(text.decode("utf-8")), "utf-8") self.assertEqual(b"Jelmer Vernooij ", c.committer) self.assertEqual(b"Jelmer Vernooij ", c.author) @@ -125,7 +125,7 @@ Subject: [PATCH 1/2] Remove executable bit from prey.i -- 1.7.0.4 -""" # noqa: W291 +""" c, diff, version = git_am_patch_split(BytesIO(text)) self.assertEqual(b"Jelmer Vernooij ", c.committer) self.assertEqual(b"Jelmer Vernooij ", c.author) @@ -160,7 +160,7 @@ Subject: [Dulwich-users] [PATCH] Added unit tests for -- 1.7.0.4 -""" # noqa: W291 +""" c, diff, version = git_am_patch_split(BytesIO(text), "utf-8") self.assertEqual( b"""\ @@ -192,7 +192,7 @@ From: Jelmer Vernooij -- 1.7.0.4 -""" # noqa: W291 +""" c, diff, version = git_am_patch_split(BytesIO(text), "utf-8") self.assertEqual(b"Jelmer Vernooij ", c.author) self.assertEqual( @@ -242,7 +242,7 @@ diff --git a/dulwich/tests/test_patch.py b/dulwich/tes class DiffTests(TestCase): -""" # noqa: W291,W293 +""" text = ( """\ From dulwich-users-bounces+jelmer=samba.org@lists.launchpad.net \ @@ -265,7 +265,7 @@ More help : https://help.launchpad.net/ListHelp """ % expected_diff - ) # noqa: W291 + ) c, diff, version = git_am_patch_split(BytesIO(text)) self.assertEqual(expected_diff, diff) self.assertEqual(None, version) blob - 5618066e8105401fe014c7b5e104a5d1e64f9d30 blob + a1d37c0f96ce73b10dd47fda9e180499afedb4ee --- dulwich/tests/test_porcelain.py +++ dulwich/tests/test_porcelain.py @@ -327,7 +327,7 @@ class CommitTests(PorcelainTestCase): author=b"Joe ", committer=b"Bob ", ) - self.assertTrue(isinstance(sha, bytes)) + self.assertIsInstance(sha, bytes) self.assertEqual(len(sha), 40) def test_unicode(self): @@ -341,7 +341,7 @@ class CommitTests(PorcelainTestCase): author="Joe ", committer="Bob ", ) - self.assertTrue(isinstance(sha, bytes)) + self.assertIsInstance(sha, bytes) self.assertEqual(len(sha), 40) def test_no_verify(self): @@ -395,7 +395,7 @@ class CommitTests(PorcelainTestCase): committer="Bob ", no_verify=True, ) - self.assertTrue(isinstance(sha, bytes)) + self.assertIsInstance(sha, bytes) self.assertEqual(len(sha), 40) @@ -519,8 +519,8 @@ class CloneTests(PorcelainTestCase): target_repo = Repo(target_path) self.assertEqual(0, len(target_repo.open_index())) self.assertEqual(c3.id, target_repo.refs[b"refs/tags/foo"]) - self.assertTrue(b"f1" not in os.listdir(target_path)) - self.assertTrue(b"f2" not in os.listdir(target_path)) + self.assertNotIn(b"f1", os.listdir(target_path)) + self.assertNotIn(b"f2", os.listdir(target_path)) c = r.get_config() encoded_path = self.repo.path if not isinstance(encoded_path, bytes): @@ -551,8 +551,8 @@ class CloneTests(PorcelainTestCase): self.assertEqual(r.path, target_path) with Repo(target_path) as r: self.assertEqual(r.head(), c3.id) - self.assertTrue("f1" in os.listdir(target_path)) - self.assertTrue("f2" in os.listdir(target_path)) + self.assertIn("f1", os.listdir(target_path)) + self.assertIn("f2", os.listdir(target_path)) def test_bare_local_with_checkout(self): f1_1 = make_object(Blob, data=b"f1") @@ -575,8 +575,8 @@ class CloneTests(PorcelainTestCase): with Repo(target_path) as r: r.head() self.assertRaises(NoIndexPresent, r.open_index) - self.assertFalse(b"f1" in os.listdir(target_path)) - self.assertFalse(b"f2" in os.listdir(target_path)) + self.assertNotIn(b"f1", os.listdir(target_path)) + self.assertNotIn(b"f2", os.listdir(target_path)) def test_no_checkout_with_bare(self): f1_1 = make_object(Blob, data=b"f1") @@ -1094,7 +1094,7 @@ class CommitTreeTests(PorcelainTestCase): author=b"Joe ", committer=b"Jane ", ) - self.assertTrue(isinstance(sha, bytes)) + self.assertIsInstance(sha, bytes) self.assertEqual(len(sha), 40) @@ -1135,7 +1135,7 @@ class TagCreateSignTests(PorcelainGpgTestCase): tags = self.repo.refs.as_dict(b"refs/tags") self.assertEqual(list(tags.keys()), [b"tryme"]) tag = self.repo[b"refs/tags/tryme"] - self.assertTrue(isinstance(tag, Tag)) + self.assertIsInstance(tag, Tag) self.assertEqual(b"foo ", tag.tagger) self.assertEqual(b"bar\n", tag.message) self.assertLess(time.time() - tag.tag_time, 5) @@ -1178,7 +1178,7 @@ class TagCreateSignTests(PorcelainGpgTestCase): tags = self.repo.refs.as_dict(b"refs/tags") self.assertEqual(list(tags.keys()), [b"tryme"]) tag = self.repo[b"refs/tags/tryme"] - self.assertTrue(isinstance(tag, Tag)) + self.assertIsInstance(tag, Tag) self.assertEqual(b"foo ", tag.tagger) self.assertEqual(b"bar\n", tag.message) self.assertLess(time.time() - tag.tag_time, 5) @@ -1205,7 +1205,7 @@ class TagCreateTests(PorcelainTestCase): tags = self.repo.refs.as_dict(b"refs/tags") self.assertEqual(list(tags.keys()), [b"tryme"]) tag = self.repo[b"refs/tags/tryme"] - self.assertTrue(isinstance(tag, Tag)) + self.assertIsInstance(tag, Tag) self.assertEqual(b"foo ", tag.tagger) self.assertEqual(b"bar\n", tag.message) self.assertLess(time.time() - tag.tag_time, 5) @@ -1255,9 +1255,9 @@ class TagDeleteTests(PorcelainTestCase): [c1] = build_commit_graph(self.repo.object_store, [[1]]) self.repo[b"HEAD"] = c1.id porcelain.tag_create(self.repo, b"foo") - self.assertTrue(b"foo" in porcelain.tag_list(self.repo)) + self.assertIn(b"foo", porcelain.tag_list(self.repo)) porcelain.tag_delete(self.repo, b"foo") - self.assertFalse(b"foo" in porcelain.tag_list(self.repo)) + self.assertNotIn(b"foo", porcelain.tag_list(self.repo)) class ResetTests(PorcelainTestCase): @@ -2208,7 +2208,7 @@ class ReceivePackTests(PorcelainTestCase): outlines = outf.getvalue().splitlines() self.assertEqual( [ - b"0091319b56ce3aee2d489f759736a79cc552c9bb86d9 HEAD\x00 report-status " # noqa: E501 + b"0091319b56ce3aee2d489f759736a79cc552c9bb86d9 HEAD\x00 report-status " b"delete-refs quiet ofs-delta side-band-64k " b"no-done symref=HEAD:refs/heads/master", b"003f319b56ce3aee2d489f759736a79cc552c9bb86d9 refs/heads/master", @@ -2254,17 +2254,17 @@ class BranchDeleteTests(PorcelainTestCase): [c1] = build_commit_graph(self.repo.object_store, [[1]]) self.repo[b"HEAD"] = c1.id porcelain.branch_create(self.repo, b"foo") - self.assertTrue(b"foo" in porcelain.branch_list(self.repo)) + self.assertIn(b"foo", porcelain.branch_list(self.repo)) porcelain.branch_delete(self.repo, b"foo") - self.assertFalse(b"foo" in porcelain.branch_list(self.repo)) + self.assertNotIn(b"foo", porcelain.branch_list(self.repo)) def test_simple_unicode(self): [c1] = build_commit_graph(self.repo.object_store, [[1]]) self.repo[b"HEAD"] = c1.id porcelain.branch_create(self.repo, "foo") - self.assertTrue(b"foo" in porcelain.branch_list(self.repo)) + self.assertIn(b"foo", porcelain.branch_list(self.repo)) porcelain.branch_delete(self.repo, "foo") - self.assertFalse(b"foo" in porcelain.branch_list(self.repo)) + self.assertNotIn(b"foo", porcelain.branch_list(self.repo)) class FetchTests(PorcelainTestCase): @@ -2301,7 +2301,7 @@ class FetchTests(PorcelainTestCase): committer=b"test2 ", ) - self.assertFalse(self.repo[b"HEAD"].id in target_repo) + self.assertNotIn(self.repo[b"HEAD"].id, target_repo) target_repo.close() # Fetch changes into the cloned repo @@ -2312,7 +2312,7 @@ class FetchTests(PorcelainTestCase): # Check the target repo for pushed changes with Repo(target_path) as r: - self.assertTrue(self.repo[b"HEAD"].id in r) + self.assertIn(self.repo[b"HEAD"].id, r) def test_with_remote_name(self): remote_name = "origin" @@ -2351,7 +2351,7 @@ class FetchTests(PorcelainTestCase): committer=b"test2 ", ) - self.assertFalse(self.repo[b"HEAD"].id in target_repo) + self.assertNotIn(self.repo[b"HEAD"].id, target_repo) target_config = target_repo.get_config() target_config.set( @@ -2370,7 +2370,7 @@ class FetchTests(PorcelainTestCase): # Check the target repo for pushed changes, as well as updates # for the refs with Repo(target_path) as r: - self.assertTrue(self.repo[b"HEAD"].id in r) + self.assertIn(self.repo[b"HEAD"].id, r) self.assertNotEqual(self.repo.get_refs(), target_refs) def assert_correct_remote_refs( @@ -2813,3 +2813,15 @@ class WriteTreeTests(PorcelainTestCase): class ActiveBranchTests(PorcelainTestCase): def test_simple(self): self.assertEqual(b"master", porcelain.active_branch(self.repo)) + + +class FindUniqueAbbrevTests(PorcelainTestCase): + + def test_simple(self): + c1, c2, c3 = build_commit_graph( + self.repo.object_store, [[1], [2, 1], [3, 1, 2]] + ) + self.repo.refs[b"HEAD"] = c3.id + self.assertEqual( + c1.id.decode('ascii')[:7], + porcelain.find_unique_abbrev(self.repo.object_store, c1.id)) blob - e5aefc0090760a9a2c519e9a0e25c8e8221c931e blob + 6ad74c6c247e4be21331a85367dfc8747343fea2 --- dulwich/tests/test_refs.py +++ dulwich/tests/test_refs.py @@ -274,7 +274,7 @@ class RefsContainerTests(object): def test_set_symbolic_ref_overwrite(self): nines = b"9" * 40 - self.assertFalse(b"refs/heads/symbolic" in self._refs) + self.assertNotIn(b"refs/heads/symbolic", self._refs) self._refs[b"refs/heads/symbolic"] = nines self.assertEqual(nines, self._refs.read_loose_ref(b"refs/heads/symbolic")) self._refs.set_symbolic_ref(b"refs/heads/symbolic", b"refs/heads/master") @@ -298,8 +298,8 @@ class RefsContainerTests(object): ) def test_contains(self): - self.assertTrue(b"refs/heads/master" in self._refs) - self.assertFalse(b"refs/heads/bar" in self._refs) + self.assertIn(b"refs/heads/master", self._refs) + self.assertNotIn(b"refs/heads/bar", self._refs) def test_delitem(self): self.assertEqual( @@ -321,7 +321,7 @@ class RefsContainerTests(object): ) ) self.assertTrue(self._refs.remove_if_equals(b"refs/tags/refs-0.2", ZERO_SHA)) - self.assertFalse(b"refs/tags/refs-0.2" in self._refs) + self.assertNotIn(b"refs/tags/refs-0.2", self._refs) def test_import_refs_name(self): self._refs[ @@ -526,7 +526,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestC nines = b"9" * 40 self.assertEqual(b"ref: refs/heads/master", refs.read_ref(b"HEAD")) - self.assertFalse(b"refs/heads/master" in refs) + self.assertNotIn(b"refs/heads/master", refs) self.assertTrue(refs.add_if_new(b"HEAD", nines)) self.assertEqual(b"ref: refs/heads/master", refs.read_ref(b"HEAD")) self.assertEqual(nines, refs[b"HEAD"]) @@ -556,7 +556,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestC RefsContainerTests.test_delitem(self) ref_file = os.path.join(self._refs.path, b"refs", b"heads", b"master") self.assertFalse(os.path.exists(ref_file)) - self.assertFalse(b"refs/heads/master" in self._refs.get_packed_refs()) + self.assertNotIn(b"refs/heads/master", self._refs.get_packed_refs()) def test_delitem_symbolic(self): self.assertEqual(b"ref: refs/heads/master", self._refs.read_loose_ref(b"HEAD")) @@ -745,8 +745,8 @@ class InfoRefsContainerTests(TestCase): def test_contains(self): refs = InfoRefsContainer(BytesIO(_TEST_REFS_SERIALIZED)) - self.assertTrue(b"refs/heads/master" in refs) - self.assertFalse(b"refs/heads/bar" in refs) + self.assertIn(b"refs/heads/master", refs) + self.assertNotIn(b"refs/heads/bar", refs) def test_get_peeled(self): refs = InfoRefsContainer(BytesIO(_TEST_REFS_SERIALIZED)) blob - ee328013b16ac8a38bcb848a4eb1455d8453b1dd blob + f22f5e41630931d817e697658b60699119fd3b6f --- dulwich/tests/test_repository.py +++ dulwich/tests/test_repository.py @@ -75,12 +75,12 @@ class CreateRepositoryTests(TestCase): barestr = b"bare = " + str(expect_bare).lower().encode("ascii") with repo.get_named_file("config") as f: config_text = f.read() - self.assertTrue(barestr in config_text, "%r" % config_text) + self.assertIn(barestr, config_text, "%r" % config_text) expect_filemode = sys.platform != "win32" barestr = b"filemode = " + str(expect_filemode).lower().encode("ascii") with repo.get_named_file("config") as f: config_text = f.read() - self.assertTrue(barestr in config_text, "%r" % config_text) + self.assertIn(barestr, config_text, "%r" % config_text) if isinstance(repo, Repo): expected_mode = '0o100644' if expect_filemode else '0o100666' @@ -224,12 +224,12 @@ class RepositoryRootTests(TestCase): def test_contains_object(self): r = self.open_repo("a.git") - self.assertTrue(r.head() in r) - self.assertFalse(b"z" * 40 in r) + self.assertIn(r.head(), r) + self.assertNotIn(b"z" * 40, r) def test_contains_ref(self): r = self.open_repo("a.git") - self.assertTrue(b"HEAD" in r) + self.assertIn(b"HEAD", r) def test_get_no_description(self): r = self.open_repo("a.git") @@ -249,7 +249,7 @@ class RepositoryRootTests(TestCase): def test_contains_missing(self): r = self.open_repo("a.git") - self.assertFalse(b"bar" in r) + self.assertNotIn(b"bar", r) def test_get_peeled(self): # unpacked ref @@ -1210,7 +1210,7 @@ class BuildRepoRootTests(TestCase): self.assertEqual(self._root_commit, r[b"HEAD"].id) self.assertEqual(commit_sha, r[b"refs/heads/new_branch"].id) self.assertEqual([], r[commit_sha].parents) - self.assertTrue(b"refs/heads/new_branch" in r) + self.assertIn(b"refs/heads/new_branch", r) new_branch_head = commit_sha blob - 42ff04d12bd9ad06f1f28062778b8181dfa08ae8 blob + eec21133f84fd1917ec883d252aff5e234a403ba --- dulwich/tests/test_server.py +++ dulwich/tests/test_server.py @@ -148,7 +148,7 @@ class HandlerTestCase(TestCase): # ignore innocuous but unknown capabilities self.assertRaises(GitProtocolError, set_caps, [b"cap2", b"ignoreme"]) - self.assertFalse(b"ignoreme" in self._handler.capabilities()) + self.assertNotIn(b"ignoreme", self._handler.capabilities()) self._handler.innocuous_capabilities = lambda: (b"ignoreme",) self.assertSucceeds(set_caps, [b"cap2", b"ignoreme"]) blob - a7dca5d6a42a0276d032014c9f4a2e50be3eb0b3 blob + 847ef5e2e8699091a40fe2d4219d1ea931be0294 --- dulwich/tests/test_utils.py +++ dulwich/tests/test_utils.py @@ -43,20 +43,20 @@ class BuildCommitGraphTest(TestCase): def test_linear(self): c1, c2 = build_commit_graph(self.store, [[1], [2, 1]]) for obj_id in [c1.id, c2.id, c1.tree, c2.tree]: - self.assertTrue(obj_id in self.store) + self.assertIn(obj_id, self.store) self.assertEqual([], c1.parents) self.assertEqual([c1.id], c2.parents) self.assertEqual(c1.tree, c2.tree) self.assertEqual([], self.store[c1.tree].items()) - self.assertTrue(c2.commit_time > c1.commit_time) + self.assertGreater(c2.commit_time, c1.commit_time) def test_merge(self): c1, c2, c3, c4 = build_commit_graph( self.store, [[1], [2, 1], [3, 1], [4, 2, 3]] ) self.assertEqual([c2.id, c3.id], c4.parents) - self.assertTrue(c4.commit_time > c2.commit_time) - self.assertTrue(c4.commit_time > c3.commit_time) + self.assertGreater(c4.commit_time, c2.commit_time) + self.assertGreater(c4.commit_time, c3.commit_time) def test_missing_parent(self): self.assertRaises( blob - 6541c7c1e33d435c70209c774415c55352f5715e blob + 34fcc545649a62719b7dabd344e9376f4a2a4b46 --- dulwich/tests/test_web.py +++ dulwich/tests/test_web.py @@ -131,7 +131,7 @@ class WebTestCase(TestCase): return None def assertContentTypeEquals(self, expected): - self.assertTrue(("Content-Type", expected) in self._headers) + self.assertIn(("Content-Type", expected), self._headers) def _test_backend(objects, refs=None, named_files=None): @@ -355,7 +355,7 @@ class SmartHandlersTestCase(WebTestCase): mat = re.search(".*", "/git-evil-handler") content = list(handle_service_request(self._req, "backend", mat)) self.assertEqual(HTTP_FORBIDDEN, self._status) - self.assertFalse(b"git-evil-handler" in b"".join(content)) + self.assertNotIn(b"git-evil-handler", b"".join(content)) self.assertFalse(self._req.cached) def _run_handle_service_request(self, content_length=None): @@ -396,7 +396,7 @@ class SmartHandlersTestCase(WebTestCase): mat = re.search(".*", "/git-evil-pack") content = list(get_info_refs(self._req, Backend(), mat)) - self.assertFalse(b"git-evil-handler" in b"".join(content)) + self.assertNotIn(b"git-evil-handler", b"".join(content)) self.assertEqual(HTTP_FORBIDDEN, self._status) self.assertFalse(self._req.cached) blob - 328b391352b8f9bda26597c78fde602a20065bf0 blob + 27ac8577ad6eb73a7fb204437a432ad9c37402e9 --- dulwich/web.py +++ dulwich/web.py @@ -286,7 +286,8 @@ def handle_service_request(req, backend, mat): class HTTPGitRequest(object): """Class encapsulating the state of a single git HTTP request. - :ivar environ: the WSGI environment for the request. + Attributes: + environ: the WSGI environment for the request. """ def __init__(self, environ, start_response, dumb: bool = False, handlers=None): @@ -358,7 +359,8 @@ class HTTPGitRequest(object): class HTTPGitApplication(object): """Class encapsulating the state of a git WSGI application. - :ivar backend: the Backend object backing this application + Attributes: + backend: the Backend object backing this application """ services = { blob - 10989e70ddc37e59a0b546b680dcdbc375f5e454 blob + 898d25899cc09937c040d2f8c9a1e9fa914bee87 --- dulwich.egg-info/PKG-INFO +++ dulwich.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dulwich -Version: 0.20.31 +Version: 0.20.35 Summary: Python Git Library Home-page: https://www.dulwich.io/ Author: Jelmer Vernooij blob - cb1509bcd238b2846f70be01ca0e2cd58bfb5f67 blob + df6f41c058c8948945b84cd38845c17eb4f85c28 --- dulwich.egg-info/SOURCES.txt +++ dulwich.egg-info/SOURCES.txt @@ -115,6 +115,7 @@ dulwich/contrib/__init__.py dulwich/contrib/diffstat.py dulwich/contrib/paramiko_vendor.py dulwich/contrib/release_robot.py +dulwich/contrib/requests_vendor.py dulwich/contrib/swift.py dulwich/contrib/test_paramiko_vendor.py dulwich/contrib/test_release_robot.py blob - 4d8177fc36e4b011df20e44ae387cdc9b50ec33e blob + a838c1e971cd9c6f8916e4fe6ad026845900aaa5 --- releaser.conf +++ releaser.conf @@ -3,6 +3,7 @@ news_file: "NEWS" timeout_days: 5 tag_name: "dulwich-$VERSION" verify_command: "flake8 && make check" +github_url: "https://github.com/dulwich/dulwich" update_version { path: "setup.py" match: "^dulwich_version_string = '(.*)'$" blob - 5d4ab14a88f6589b45ef7675a38bb8c7dc836708 blob + c1eed47566a5bedae1bc21014b88862d3edb217f --- setup.py +++ setup.py @@ -23,7 +23,7 @@ if sys.version_info < (3, 6): 'For 2.7 support, please install a version prior to 0.20') -dulwich_version_string = '0.20.31' +dulwich_version_string = '0.20.35' class DulwichDistribution(Distribution):