commit e13cc98a4f8ca8afc857730719164e4608c28d3f from: Jelmer Vernooij date: Wed Aug 25 16:47:22 2021 UTC Import upstream version 0.20.25 commit - 0c434308d1ee1b1b9abd345f147baa4ddb578585 commit + e13cc98a4f8ca8afc857730719164e4608c28d3f blob - 2762e93d176e655d090d0063bd9b2fa9c837f649 blob + c68de5a472fda8cf8f731d472dae6ec625d41c94 --- .github/workflows/pythonpackage.yml +++ .github/workflows/pythonpackage.yml @@ -1,6 +1,10 @@ name: Python package -on: [push, pull_request] +on: + push: + pull_request: + schedule: + - cron: '0 6 * * *' # Daily 6AM UTC build jobs: build: @@ -9,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.5, 3.6, 3.7, 3.8, 3.9, pypy3] + python-version: [3.5, 3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy3] exclude: # sqlite3 exit handling seems to get in the way - os: macos-latest @@ -46,7 +50,7 @@ jobs: if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'" - name: Install mypy run: | - pip install -U mypy + pip install -U mypy types-paramiko types-certifi if: "matrix.python-version != 'pypy3'" - name: Style checks run: | blob - 7eaeae907ad187a5b602325d2be7510490c74357 blob + 953bbaf1c3d45903f5987567909163d9d9385983 --- NEWS +++ NEWS @@ -1,3 +1,24 @@ +0.20.25 2021-08-23 + + * Fix ``dulwich`` script when installed via setup.py. + (Dan Villiom Podlaski Christiansen) + + * Make default file mask consistent + with Git. (Dan Villiom Podlaski Christiansen, #884) + +0.20.24 2021-07-18 + + * config: disregard UTF-8 BOM when reading file. + (Dan Villiom Podlaski Christiansen) + + * Skip lines with spaces only in .gitignore. (Andrey Torsunov, #878) + + * Add a separate HTTPProxyUnauthorized exception for 407 errors. + (Jelmer Vernooij, #822) + + * Split out a AbstractHTTPGitClient class. + (Jelmer Vernooij) + 0.20.23 2021-05-24 * Fix installation of GPG during package publishing. @@ -282,7 +303,7 @@ BUG FIXES * Avoid ``PermissionError``, since it is Python3-specific. - (Jelmer Vernooij) + (Jelmer Vernooij) * Fix regression that added a dependency on C git for the test suite. (Jelmer Vernooij, #720) blob - e54aeeed9312dfef00c8d803064cdd5db15255df blob + f42454ceef107f4fb2a83f9dfedde4aad6197efb --- PKG-INFO +++ PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dulwich -Version: 0.20.23 +Version: 0.20.25 Summary: Python Git Library Home-page: https://www.dulwich.io/ Author: Jelmer Vernooij @@ -9,102 +9,6 @@ License: Apachev2 or later or GPLv2 Project-URL: Bug Tracker, https://github.com/dulwich/dulwich/issues Project-URL: Repository, https://www.dulwich.io/code/ Project-URL: GitHub, https://github.com/dulwich/dulwich -Description: Dulwich - ======= - - This is the Dulwich project. - - It aims to provide an interface to git repos (both local and remote) that - doesn't call out to git directly but instead uses pure Python. - - **Main website**: - - **License**: Apache License, version 2 or GNU General Public License, version 2 or later. - - The project is named after the part of London that Mr. and Mrs. Git live in - in the particular Monty Python sketch. - - Installation - ------------ - - By default, Dulwich' setup.py will attempt to build and install the optional C - extensions. The reason for this is that they significantly improve the performance - since some low-level operations that are executed often are much slower in CPython. - - If you don't want to install the C bindings, specify the --pure argument to setup.py:: - - $ python setup.py --pure install - - or if you are installing from pip:: - - $ pip install dulwich --global-option="--pure" - - Note that you can also specify --global-option in a - `requirements.txt `_ - file, e.g. like this:: - - dulwich --global-option=--pure - - Getting started - --------------- - - Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). - - For example, to use the lower level API to access the commit message of the - last commit:: - - >>> from dulwich.repo import Repo - >>> r = Repo('.') - >>> r.head() - '57fbe010446356833a6ad1600059d80b1e731e15' - >>> c = r[r.head()] - >>> c - - >>> c.message - 'Add note about encoding.\n' - - And to print it using porcelain:: - - >>> from dulwich import porcelain - >>> porcelain.log('.', max_entries=1) - -------------------------------------------------- - commit: 57fbe010446356833a6ad1600059d80b1e731e15 - Author: Jelmer Vernooij - Date: Sat Apr 29 2017 23:57:34 +0000 - - Add note about encoding. - - Further documentation - --------------------- - - The dulwich documentation can be found in docs/ and built by running ``make - doc``. It can also be found `on the web `_. - - Help - ---- - - There is a *#dulwich* IRC channel on the `OFTC `_, and - `dulwich-announce `_ - and `dulwich-discuss `_ - mailing lists. - - Contributing - ------------ - - For a full list of contributors, see the git logs or `AUTHORS `_. - - If you'd like to contribute to Dulwich, see the `CONTRIBUTING `_ - file and `list of open issues `_. - - Supported versions of Python - ---------------------------- - - At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and - Pypy. - - The latest release series to support Python 2.x was the 0.19 series. See - the 0.19 branch in the Dulwich git repository. - Keywords: git vcs Platform: UNKNOWN Classifier: Development Status :: 4 - Beta @@ -124,3 +28,103 @@ Provides-Extra: fastimport Provides-Extra: https Provides-Extra: pgp Provides-Extra: watch +License-File: COPYING +License-File: AUTHORS + +Dulwich +======= + +This is the Dulwich project. + +It aims to provide an interface to git repos (both local and remote) that +doesn't call out to git directly but instead uses pure Python. + +**Main website**: + +**License**: Apache License, version 2 or GNU General Public License, version 2 or later. + +The project is named after the part of London that Mr. and Mrs. Git live in +in the particular Monty Python sketch. + +Installation +------------ + +By default, Dulwich' setup.py will attempt to build and install the optional C +extensions. The reason for this is that they significantly improve the performance +since some low-level operations that are executed often are much slower in CPython. + +If you don't want to install the C bindings, specify the --pure argument to setup.py:: + + $ python setup.py --pure install + +or if you are installing from pip:: + + $ pip install dulwich --global-option="--pure" + +Note that you can also specify --global-option in a +`requirements.txt `_ +file, e.g. like this:: + + dulwich --global-option=--pure + +Getting started +--------------- + +Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). + +For example, to use the lower level API to access the commit message of the +last commit:: + + >>> from dulwich.repo import Repo + >>> r = Repo('.') + >>> r.head() + '57fbe010446356833a6ad1600059d80b1e731e15' + >>> c = r[r.head()] + >>> c + + >>> c.message + 'Add note about encoding.\n' + +And to print it using porcelain:: + + >>> from dulwich import porcelain + >>> porcelain.log('.', max_entries=1) + -------------------------------------------------- + commit: 57fbe010446356833a6ad1600059d80b1e731e15 + Author: Jelmer Vernooij + Date: Sat Apr 29 2017 23:57:34 +0000 + + Add note about encoding. + +Further documentation +--------------------- + +The dulwich documentation can be found in docs/ and built by running ``make +doc``. It can also be found `on the web `_. + +Help +---- + +There is a *#dulwich* IRC channel on the `OFTC `_, and +`dulwich-announce `_ +and `dulwich-discuss `_ +mailing lists. + +Contributing +------------ + +For a full list of contributors, see the git logs or `AUTHORS `_. + +If you'd like to contribute to Dulwich, see the `CONTRIBUTING `_ +file and `list of open issues `_. + +Supported versions of Python +---------------------------- + +At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and +Pypy. + +The latest release series to support Python 2.x was the 0.19 series. See +the 0.19 branch in the Dulwich git repository. + + blob - 23df2b69b8598fb4a22f6c2fa5a9ca1d699e0bf6 (mode 644) blob + /dev/null --- build.cmd +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -:: To build extensions for 64 bit Python 3, we need to configure environment -:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 4 -:: -:: More details at: -:: https://github.com/cython/cython/wiki/CythonExtensionsOnWindows - -IF "%DISTUTILS_USE_SDK%"=="1" ( - ECHO Configuring environment to build with MSVC on a 64bit architecture - ECHO Using Windows SDK 7.1 - "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 - CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release - SET MSSdk=1 - REM Need the following to allow tox to see the SDK compiler - SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB -) ELSE ( - ECHO Using default MSVC build environment -) - -CALL %* blob - 46661a032954a650806d5726c2539e2e0ee12cba blob + 17705982feb817acf7ec992a221782feac8d18e6 --- dulwich/__init__.py +++ dulwich/__init__.py @@ -22,4 +22,4 @@ """Python implementation of the Git file formats and protocols.""" -__version__ = (0, 20, 23) +__version__ = (0, 20, 25) blob - /dev/null blob + 3453a7e6d4cae4b300f04411042416a7eb7f3963 (mode 644) --- /dev/null +++ dulwich/__main__.py @@ -0,0 +1,4 @@ +from . import cli + +if __name__ == "__main__": + cli._main() blob - bcdd094b0d0e89eb1bfa26849b831f4fa64a8d1f blob + d0cedf7479da398329c5c53b34651510a932c13e --- dulwich/cli.py +++ dulwich/cli.py @@ -731,7 +731,7 @@ commands = { def main(argv=None): if argv is None: - argv = sys.argv + argv = sys.argv[1:] if len(argv) < 1: print("Usage: dulwich <%s> [OPTIONS...]" % ("|".join(commands.keys()))) @@ -747,9 +747,13 @@ def main(argv=None): return cmd_kls().run(argv[1:]) -if __name__ == "__main__": +def _main(): if "DULWICH_PDB" in os.environ and getattr(signal, "SIGQUIT", None): signal.signal(signal.SIGQUIT, signal_quit) # type: ignore signal.signal(signal.SIGINT, signal_int) - sys.exit(main(sys.argv[1:])) + sys.exit(main()) + + +if __name__ == "__main__": + _main() blob - 8cc1f40c9d55657633f6e2f98cd540f28b8ce803 blob + 2527853c78580ce737d656858002f15513a3bc57 --- dulwich/client.py +++ dulwich/client.py @@ -130,6 +130,15 @@ class HTTPUnauthorized(Exception): def __init__(self, www_authenticate, url): Exception.__init__(self, "No valid credentials provided") self.www_authenticate = www_authenticate + self.url = url + + +class HTTPProxyUnauthorized(Exception): + """Raised when proxy authentication fails.""" + + def __init__(self, proxy_authenticate, url): + Exception.__init__(self, "No valid proxy credentials provided") + self.proxy_authenticate = proxy_authenticate self.url = url @@ -549,8 +558,8 @@ class GitClient(object): Args: path: Remote path to fetch from determine_wants: Function determine what refs - to fetch. Receives dictionary of name->sha, should return - list of shas to fetch. + to fetch. Receives dictionary of name->sha, should return + list of shas to fetch. graph_walker: Object with next() and ack(). pack_data: Callback called for each bit of data in the pack progress: Callback for progress reports (strings) @@ -901,10 +910,10 @@ class TraditionalGitClient(GitClient): Args: path: Repository path (as bytestring) update_refs: Function to determine changes to remote refs. - Receive dict with existing remote refs, returns dict with - changed refs (name -> sha, where sha=ZERO_SHA for deletions) + Receive dict with existing remote refs, returns dict with + changed refs (name -> sha, where sha=ZERO_SHA for deletions) generate_pack_data: Function that can return a tuple with - number of objects and pack data to upload. + number of objects and pack data to upload. progress: Optional callback called with progress updates Returns: @@ -995,8 +1004,8 @@ class TraditionalGitClient(GitClient): Args: path: Remote path to fetch from determine_wants: Function determine what refs - to fetch. Receives dictionary of name->sha, should return - list of shas to fetch. + to fetch. Receives dictionary of name->sha, should return + list of shas to fetch. graph_walker: Object with next() and ack(). pack_data: Callback called for each bit of data in the pack progress: Callback for progress reports (strings) @@ -1292,9 +1301,9 @@ class LocalGitClient(GitClient): Args: path: Repository path (as bytestring) update_refs: Function to determine changes to remote refs. - Receive dict with existing remote refs, returns dict with - changed refs (name -> sha, where sha=ZERO_SHA for deletions) - with number of items and pack data to upload. + Receive dict with existing remote refs, returns dict with + changed refs (name -> sha, where sha=ZERO_SHA for deletions) + with number of items and pack data to upload. progress: Optional progress function Returns: @@ -1353,8 +1362,8 @@ class LocalGitClient(GitClient): path: Path to fetch from (as bytestring) target: Target repository to fetch into determine_wants: Optional function determine what refs - to fetch. Receives dictionary of name->sha, should return - list of shas to fetch. Defaults to all shas. + to fetch. Receives dictionary of name->sha, should return + list of shas to fetch. Defaults to all shas. progress: Optional progress function depth: Shallow fetch depth @@ -1385,8 +1394,8 @@ class LocalGitClient(GitClient): Args: path: Remote path to fetch from determine_wants: Function determine what refs - to fetch. Receives dictionary of name->sha, should return - list of shas to fetch. + to fetch. Receives dictionary of name->sha, should return + list of shas to fetch. graph_walker: Object with next() and ack(). pack_data: Callback called for each bit of data in the pack progress: Callback for progress reports (strings) @@ -1759,8 +1768,6 @@ def default_urllib3_manager( # noqa: C901 if proxy_server is not None: if proxy_manager_cls is None: proxy_manager_cls = urllib3.ProxyManager - # `urllib3` requires a `str` object in both Python 2 and 3, while - # `ConfigDict` coerces entries to `bytes` on Python 3. Compensate. if not isinstance(proxy_server, str): proxy_server = proxy_server.decode() manager = proxy_manager_cls(proxy_server, headers=headers, **kwargs) @@ -1772,71 +1779,20 @@ def default_urllib3_manager( # noqa: C901 return manager -class HttpGitClient(GitClient): - def __init__( - self, - base_url, - dumb=None, - pool_manager=None, - config=None, - username=None, - password=None, - **kwargs - ): - self._base_url = base_url.rstrip("/") + "/" - self._username = username - self._password = password - self.dumb = dumb +class AbstractHttpGitClient(GitClient): + """Abstract base class for HTTP Git Clients. - if pool_manager is None: - self.pool_manager = default_urllib3_manager(config) - else: - self.pool_manager = pool_manager + This is agonistic of the actual HTTP implementation. - if username is not None: - # No escaping needed: ":" is not allowed in username: - # https://tools.ietf.org/html/rfc2617#section-2 - credentials = "%s:%s" % (username, password) - import urllib3.util - - basic_auth = urllib3.util.make_headers(basic_auth=credentials) - self.pool_manager.headers.update(basic_auth) + Subclasses should provide an implementation of the + _http_request method. + """ + def __init__(self, base_url, dumb=False, **kwargs): + self._base_url = base_url.rstrip("/") + "/" + self.dumb = dumb GitClient.__init__(self, **kwargs) - def get_url(self, path): - return self._get_url(path).rstrip("/") - - @classmethod - def from_parsedurl(cls, parsedurl, **kwargs): - password = parsedurl.password - if password is not None: - kwargs["password"] = urlunquote(password) - username = parsedurl.username - if username is not None: - kwargs["username"] = urlunquote(username) - netloc = parsedurl.hostname - if parsedurl.port: - netloc = "%s:%s" % (netloc, parsedurl.port) - if parsedurl.username: - netloc = "%s@%s" % (parsedurl.username, netloc) - parsedurl = parsedurl._replace(netloc=netloc) - return cls(urlunparse(parsedurl), **kwargs) - - def __repr__(self): - return "%s(%r, dumb=%r)" % ( - type(self).__name__, - self._base_url, - self.dumb, - ) - - def _get_url(self, path): - if not isinstance(path, str): - # urllib3.util.url._encode_invalid_chars() converts the path back - # to bytes using the utf-8 codec. - path = path.decode("utf-8") - return urljoin(self._base_url, path).rstrip("/") + "/" - def _http_request(self, url, headers=None, data=None, allow_compression=False): """Perform HTTP request. @@ -1853,48 +1809,8 @@ class HttpGitClient(GitClient): method for the response data. """ - req_headers = self.pool_manager.headers.copy() - if headers is not None: - req_headers.update(headers) - req_headers["Pragma"] = "no-cache" - if allow_compression: - req_headers["Accept-Encoding"] = "gzip" - else: - req_headers["Accept-Encoding"] = "identity" - if data is None: - resp = self.pool_manager.request("GET", url, headers=req_headers) - else: - resp = self.pool_manager.request( - "POST", url, headers=req_headers, body=data - ) - - if resp.status == 404: - raise NotGitRepository() - if resp.status == 401: - raise HTTPUnauthorized(resp.getheader("WWW-Authenticate"), url) - if resp.status != 200: - raise GitProtocolError( - "unexpected http resp %d for %s" % (resp.status, url) - ) - - # TODO: Optimization available by adding `preload_content=False` to the - # request and just passing the `read` method on instead of going via - # `BytesIO`, if we can guarantee that the entire response is consumed - # before issuing the next to still allow for connection reuse from the - # pool. - read = BytesIO(resp.data).read - - resp.content_type = resp.getheader("Content-Type") - # Check if geturl() is available (urllib3 version >= 1.23) - try: - resp_url = resp.geturl() - except AttributeError: - # get_redirect_location() is available for urllib3 >= 1.1 - resp.redirect_location = resp.get_redirect_location() - else: - resp.redirect_location = resp_url if resp_url != url else "" - return resp, read + raise NotImplementedError(self._http_request) def _discover_references(self, service, base_url): assert base_url[-1] == "/" @@ -1934,6 +1850,11 @@ class HttpGitClient(GitClient): resp.close() def _smart_request(self, service, url, data): + """Send a 'smart' HTTP request. + + This is a simple wrapper around _http_request that sets + a couple of extra headers. + """ assert url[-1] == "/" url = urljoin(url, service) result_content_type = "application/x-%s-result" % service @@ -1955,10 +1876,10 @@ class HttpGitClient(GitClient): Args: path: Repository path (as bytestring) update_refs: Function to determine changes to remote refs. - Receives dict with existing remote refs, returns dict with - changed refs (name -> sha, where sha=ZERO_SHA for deletions) + Receives dict with existing remote refs, returns dict with + changed refs (name -> sha, where sha=ZERO_SHA for deletions) generate_pack_data: Function that can return a tuple - with number of elements and pack data to upload. + with number of elements and pack data to upload. progress: Optional progress function Returns: @@ -2089,7 +2010,124 @@ class HttpGitClient(GitClient): refs, _, _ = self._discover_references(b"git-upload-pack", url) return refs + def get_url(self, path): + return self._get_url(path).rstrip("/") + def _get_url(self, path): + return urljoin(self._base_url, path).rstrip("/") + "/" + + @classmethod + def from_parsedurl(cls, parsedurl, **kwargs): + password = parsedurl.password + if password is not None: + kwargs["password"] = urlunquote(password) + username = parsedurl.username + if username is not None: + kwargs["username"] = urlunquote(username) + netloc = parsedurl.hostname + if parsedurl.port: + netloc = "%s:%s" % (netloc, parsedurl.port) + if parsedurl.username: + netloc = "%s@%s" % (parsedurl.username, netloc) + parsedurl = parsedurl._replace(netloc=netloc) + return cls(urlunparse(parsedurl), **kwargs) + + def __repr__(self): + return "%s(%r, dumb=%r)" % ( + type(self).__name__, + self._base_url, + self.dumb, + ) + + +class Urllib3HttpGitClient(AbstractHttpGitClient): + def __init__( + self, + base_url, + dumb=None, + pool_manager=None, + config=None, + username=None, + password=None, + **kwargs + ): + self._username = username + self._password = password + + if pool_manager is None: + self.pool_manager = default_urllib3_manager(config) + else: + self.pool_manager = pool_manager + + if username is not None: + # No escaping needed: ":" is not allowed in username: + # https://tools.ietf.org/html/rfc2617#section-2 + credentials = "%s:%s" % (username, password) + import urllib3.util + + basic_auth = urllib3.util.make_headers(basic_auth=credentials) + self.pool_manager.headers.update(basic_auth) + + super(Urllib3HttpGitClient, self).__init__( + base_url=base_url, dumb=dumb, **kwargs) + + def _get_url(self, path): + if not isinstance(path, str): + # urllib3.util.url._encode_invalid_chars() converts the path back + # to bytes using the utf-8 codec. + path = path.decode("utf-8") + return urljoin(self._base_url, path).rstrip("/") + "/" + + def _http_request(self, url, headers=None, data=None, allow_compression=False): + req_headers = self.pool_manager.headers.copy() + if headers is not None: + req_headers.update(headers) + req_headers["Pragma"] = "no-cache" + if allow_compression: + req_headers["Accept-Encoding"] = "gzip" + else: + req_headers["Accept-Encoding"] = "identity" + + if data is None: + resp = self.pool_manager.request("GET", url, headers=req_headers) + else: + resp = self.pool_manager.request( + "POST", url, headers=req_headers, body=data + ) + + if resp.status == 404: + raise NotGitRepository() + if resp.status == 401: + raise HTTPUnauthorized(resp.getheader("WWW-Authenticate"), url) + if resp.status == 407: + raise HTTPProxyUnauthorized(resp.getheader("Proxy-Authenticate"), url) + if resp.status != 200: + raise GitProtocolError( + "unexpected http resp %d for %s" % (resp.status, url) + ) + + # TODO: Optimization available by adding `preload_content=False` to the + # request and just passing the `read` method on instead of going via + # `BytesIO`, if we can guarantee that the entire response is consumed + # before issuing the next to still allow for connection reuse from the + # pool. + read = BytesIO(resp.data).read + + resp.content_type = resp.getheader("Content-Type") + # Check if geturl() is available (urllib3 version >= 1.23) + try: + resp_url = resp.geturl() + except AttributeError: + # get_redirect_location() is available for urllib3 >= 1.1 + resp.redirect_location = resp.get_redirect_location() + else: + resp.redirect_location = resp_url if resp_url != url else "" + return resp, read + + +HttpGitClient = Urllib3HttpGitClient + + def get_transport_and_path_from_url(url, config=None, **kwargs): """Obtain a git client from a URL. blob - 7868faece3ef5c7566164681f7f2cfa2d7818291 blob + 65f7de9e6ed5c8aacbec0b0c84dff92f24c76224 --- dulwich/config.py +++ dulwich/config.py @@ -41,7 +41,7 @@ try: MutableMapping, ) except ImportError: # python < 3.7 - from collections import ( + from collections import ( # type: ignore Iterable, MutableMapping, ) @@ -387,14 +387,16 @@ class ConfigFile(ConfigDict): super(ConfigFile, self).__init__(values=values, encoding=encoding) self.path = None - @classmethod - def from_file(cls, f: BinaryIO) -> "ConfigFile": + @classmethod # noqa: C901 + def from_file(cls, f: BinaryIO) -> "ConfigFile": # noqa: C901 """Read configuration from a file-like object.""" ret = cls() section = None # type: Optional[Tuple[bytes, ...]] setting = None continuation = None for lineno, line in enumerate(f.readlines()): + if lineno == 0 and line.startswith(b'\xef\xbb\xbf'): + line = line[3:] line = line.lstrip() if setting is None: # Parse section header ("[bla]") blob - 6abdc27dce50a22d372feeb383b89b909485cc7a blob + acdec8c74d0e42c3480a4ad10ba5bf517107beca --- dulwich/file.py +++ dulwich/file.py @@ -66,7 +66,7 @@ def _fancy_rename(oldname, newname): os.remove(tmpfile) -def GitFile(filename, mode="rb", bufsize=-1): +def GitFile(filename, mode="rb", bufsize=-1, mask=0o644): """Create a file object that obeys the git file locking protocol. Returns: a builtin file object or a _GitFile object @@ -77,6 +77,10 @@ def GitFile(filename, mode="rb", bufsize=-1): are not. To read and write from the same file, you can take advantage of the fact that opening a file for write does not actually open the file you request. + + The default file mask makes any created files user-writable and + world-readable. + """ if "a" in mode: raise IOError("append mode not supported for Git files") @@ -85,7 +89,7 @@ def GitFile(filename, mode="rb", bufsize=-1): if "b" not in mode: raise IOError("text mode not supported for Git files") if "w" in mode: - return _GitFile(filename, mode, bufsize) + return _GitFile(filename, mode, bufsize, mask) else: return io.open(filename, mode, bufsize) @@ -136,7 +140,7 @@ class _GitFile(object): "writelines", ) - def __init__(self, filename, mode, bufsize): + def __init__(self, filename, mode, bufsize, mask): self._filename = filename if isinstance(self._filename, bytes): self._lockfilename = self._filename + b".lock" @@ -146,6 +150,7 @@ class _GitFile(object): fd = os.open( self._lockfilename, os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0), + mask, ) except FileExistsError: raise FileLocked(filename, self._lockfilename) blob - cc457a22c467b77297ccf68cd3a37a57fc94e2c2 blob + b75560f35c84987f5fe9a4ab835faf773f428756 --- dulwich/ignore.py +++ dulwich/ignore.py @@ -123,7 +123,7 @@ def read_ignore_patterns(f: BinaryIO) -> Iterable[byte line = line.rstrip(b"\r\n") # Ignore blank lines, they're used for readability. - if not line: + if not line.strip(): continue if line.startswith(b"#"): blob - 99c816388356f2a3d2bc51a88eac1178be84de1e blob + 551f9c1f100fe4abcfe4907bf58ca38e468bce2d --- dulwich/object_store.py +++ dulwich/object_store.py @@ -70,7 +70,12 @@ from dulwich.refs import ANNOTATED_TAG_SUFFIX INFODIR = "info" PACKDIR = "pack" +# use permissions consistent with Git; just readable by everyone +# TODO: should packs also be non-writable on Windows? if so, that +# would requite some rather significant adjustments to the test suite +PACK_MODE = 0o444 if sys.platform != "win32" else 0o644 + class BaseObjectStore(object): """Object store interface.""" @@ -805,7 +810,7 @@ class DiskObjectStore(PackBasedObjectStore): os.rename(path, target_pack) # Write the index. - index_file = GitFile(pack_base_name + ".idx", "wb") + index_file = GitFile(pack_base_name + ".idx", "wb", mask=PACK_MODE) try: write_pack_index_v2(index_file, entries, pack_sha) index_file.close() @@ -837,6 +842,7 @@ class DiskObjectStore(PackBasedObjectStore): fd, path = tempfile.mkstemp(dir=self.path, prefix="tmp_pack_") with os.fdopen(fd, "w+b") as f: + os.chmod(path, PACK_MODE) indexer = PackIndexer(f, resolve_ext_ref=self.get_raw) copier = PackStreamCopier(read_all, read_some, f, delta_iter=indexer) copier.verify() @@ -856,7 +862,7 @@ class DiskObjectStore(PackBasedObjectStore): basename = self._get_pack_basepath(entries) index_name = basename + ".idx" if not os.path.exists(index_name): - with GitFile(index_name, "wb") as f: + with GitFile(index_name, "wb", mask=PACK_MODE) as f: write_pack_index_v2(f, entries, p.get_stored_checksum()) for pack in self.packs: if pack._basename == basename: @@ -885,6 +891,7 @@ class DiskObjectStore(PackBasedObjectStore): fd, path = tempfile.mkstemp(dir=self.pack_dir, suffix=".pack") f = os.fdopen(fd, "wb") + os.chmod(path, PACK_MODE) def commit(): f.flush() @@ -916,7 +923,7 @@ class DiskObjectStore(PackBasedObjectStore): pass if os.path.exists(path): return # Already there, no need to write again - with GitFile(path, "wb") as f: + with GitFile(path, "wb", mask=PACK_MODE) as f: f.write( obj.as_legacy_object(compression_level=self.loose_compression_level) ) blob - ea1722ed440b2f655df90a7287cbdfa073d34cbb blob + e3cf42621f37b8972edb77a7ab332d747c21ae62 --- dulwich/porcelain.py +++ dulwich/porcelain.py @@ -1808,15 +1808,24 @@ def stash_push(repo): stash.push() -def stash_pop(repo): - """Pop a new stash from the stack.""" +def stash_pop(repo, index): + """Pop a stash from the stack.""" with open_repo_closing(repo) as r: from dulwich.stash import Stash stash = Stash.from_repo(r) - stash.pop() + stash.pop(index) +def stash_drop(repo, index): + """Drop a stash from the stack.""" + with open_repo_closing(repo) as r: + from dulwich.stash import Stash + + stash = Stash.from_repo(r) + stash.drop(index) + + def ls_files(repo): """List all files in an index.""" with open_repo_closing(repo) as r: blob - 6cbdbf8f0833d8b11ca159fa3460e298f4cb27c3 blob + 0d34658f1ee8cf20e73ecdb9bd36bad5a269f8ba --- dulwich/repo.py +++ dulwich/repo.py @@ -1262,8 +1262,10 @@ class Repo(BaseRepo): root_path_bytes = os.fsencode(self.path) - if not isinstance(fs_paths, list): + if isinstance(fs_paths, str): fs_paths = [fs_paths] + fs_paths = list(fs_paths) + from dulwich.index import ( blob_from_path_and_stat, index_entry_from_stat, blob - 72f9e5c2d9aabb93cd8065664e910b485e9ccea2 blob + 487c2aa87a71268507d4f499917e408a8868ef4e --- 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_utf8_bom(self): + text = "[core]\nfoo = b\u00e4r\n".encode("utf-8-sig") + cf = self.from_file(text) + self.assertEqual(b"b\xc3\xa4r", cf.get((b"core",), b"foo")) + def test_from_file_section_case_insensitive_lower(self): cf = self.from_file(b"[cOre]\nfOo = bar\n") self.assertEqual(b"bar", cf.get((b"core",), b"foo")) blob - 1e947beba3a3f8a5a19c8f9aee9538832dbe232c blob + 6cb5c6611243da3c836351588f13e1cec027657a --- dulwich/tests/test_ignore.py +++ dulwich/tests/test_ignore.py @@ -105,7 +105,7 @@ class ReadIgnorePatterns(TestCase): f = BytesIO( b""" # a comment - +\x20\x20 # and an empty line: \\#not a comment blob - 2e416cff8dfc51bbc9809e0e69387b8508eb2a15 blob + 68789aacb3abed59695a69a647b10b55b13e909d --- dulwich/tests/test_object_store.py +++ dulwich/tests/test_object_store.py @@ -27,6 +27,7 @@ from unittest import skipUnless import os import shutil import stat +import sys import tempfile from dulwich.index import ( @@ -437,6 +438,14 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, store.add_alternate_path("# comment") for alt_path in store._read_alternate_paths(): self.assertNotIn("#", alt_path) + + def test_file_modes(self): + self.store.add_object(testobject) + path = self.store._get_shafile_path(testobject.id) + mode = os.stat(path).st_mode + + packmode = "0o100444" if sys.platform != "win32" else "0o100666" + self.assertEqual(oct(mode), packmode) def test_corrupted_object_raise_exception(self): """Corrupted sha1 disk file should raise specific exception""" @@ -448,8 +457,11 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, self.assertIsNotNone(self.store._get_loose_object(testobject.id)) path = self.store._get_shafile_path(testobject.id) + old_mode = os.stat(path).st_mode + os.chmod(path, 0o600) with open(path, "wb") as f: # corrupt the file f.write(b"") + os.chmod(path, old_mode) expected_error_msg = "Corrupted empty file detected" try: blob - ded79b24a6dc65b53153a374c50447a9fd2dc17e blob + 278259c3b4eb3afb8a8d9170cdf94b9786105bff --- dulwich/tests/test_pack.py +++ dulwich/tests/test_pack.py @@ -26,6 +26,7 @@ from io import BytesIO from hashlib import sha1 import os import shutil +import sys import tempfile import zlib @@ -83,6 +84,7 @@ pack1_sha = b"bc63ddad95e7321ee734ea11a7a62d314e0d7481 a_sha = b"6f670c0fb53f9463760b7295fbb814e965fb20c8" tree_sha = b"b2a2766a2879c209ab1176e7e778b81ae422eeaa" commit_sha = b"f18faa16531ac570a3fdc8c7ca16682548dafd12" +indexmode = "0o100644" if sys.platform != "win32" else "0o100666" class PackTests(TestCase): @@ -338,6 +340,7 @@ class TestPackData(PackTests): p.create_index_v1(filename) idx1 = load_pack_index(filename) idx2 = self.get_pack_index(pack1_sha) + self.assertEqual(oct(os.stat(filename).st_mode), indexmode) self.assertEqual(idx1, idx2) def test_create_index_v2(self): @@ -346,6 +349,7 @@ class TestPackData(PackTests): p.create_index_v2(filename) idx1 = load_pack_index(filename) idx2 = self.get_pack_index(pack1_sha) + self.assertEqual(oct(os.stat(filename).st_mode), indexmode) self.assertEqual(idx1, idx2) def test_compute_file_sha(self): blob - 0a0e1882e5bf32cd0f4640be692a5c03e272a5c8 blob + 7717701f4009b24b8b2fe7246c08490fa59c9b2c --- dulwich/tests/test_porcelain.py +++ dulwich/tests/test_porcelain.py @@ -111,55 +111,55 @@ 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 -=YPjc +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 +brMb2upTHa+kjktjdO96/BgSnKYqmNmPB/qB +=ivA/ -----END PGP PRIVATE KEY BLOCK----- """ @@ -197,55 +197,55 @@ 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 -=kOKh +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 +=9zU5 -----END PGP PRIVATE KEY BLOCK----- """ blob - 9495d2f111061be0689b18bbb8012ac26c3e6bbf blob + 4b75e0431b816f9e47d5ac5f0fb206354d533542 --- dulwich/tests/test_repository.py +++ dulwich/tests/test_repository.py @@ -21,10 +21,11 @@ """Tests for the repository.""" +import glob import locale import os -import stat import shutil +import stat import sys import tempfile import warnings @@ -79,7 +80,22 @@ class CreateRepositoryTests(TestCase): with repo.get_named_file("config") as f: config_text = f.read() self.assertTrue(barestr in config_text, "%r" % config_text) + + if isinstance(repo, Repo): + expected_mode = '0o100644' if expect_filemode else '0o100666' + expected = { + 'HEAD': expected_mode, + 'config': expected_mode, + 'description': expected_mode, + } + actual = { + f[len(repo._controldir) + 1:]: oct(os.stat(f).st_mode) + for f in glob.glob(os.path.join(repo._controldir, '*')) + if os.path.isfile(f) + } + self.assertEqual(expected, actual) + def test_create_memory(self): repo = MemoryRepo.init_bare([], {}) self._check_repo_contents(repo, True) blob - e54aeeed9312dfef00c8d803064cdd5db15255df blob + f42454ceef107f4fb2a83f9dfedde4aad6197efb --- dulwich.egg-info/PKG-INFO +++ dulwich.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dulwich -Version: 0.20.23 +Version: 0.20.25 Summary: Python Git Library Home-page: https://www.dulwich.io/ Author: Jelmer Vernooij @@ -9,102 +9,6 @@ License: Apachev2 or later or GPLv2 Project-URL: Bug Tracker, https://github.com/dulwich/dulwich/issues Project-URL: Repository, https://www.dulwich.io/code/ Project-URL: GitHub, https://github.com/dulwich/dulwich -Description: Dulwich - ======= - - This is the Dulwich project. - - It aims to provide an interface to git repos (both local and remote) that - doesn't call out to git directly but instead uses pure Python. - - **Main website**: - - **License**: Apache License, version 2 or GNU General Public License, version 2 or later. - - The project is named after the part of London that Mr. and Mrs. Git live in - in the particular Monty Python sketch. - - Installation - ------------ - - By default, Dulwich' setup.py will attempt to build and install the optional C - extensions. The reason for this is that they significantly improve the performance - since some low-level operations that are executed often are much slower in CPython. - - If you don't want to install the C bindings, specify the --pure argument to setup.py:: - - $ python setup.py --pure install - - or if you are installing from pip:: - - $ pip install dulwich --global-option="--pure" - - Note that you can also specify --global-option in a - `requirements.txt `_ - file, e.g. like this:: - - dulwich --global-option=--pure - - Getting started - --------------- - - Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). - - For example, to use the lower level API to access the commit message of the - last commit:: - - >>> from dulwich.repo import Repo - >>> r = Repo('.') - >>> r.head() - '57fbe010446356833a6ad1600059d80b1e731e15' - >>> c = r[r.head()] - >>> c - - >>> c.message - 'Add note about encoding.\n' - - And to print it using porcelain:: - - >>> from dulwich import porcelain - >>> porcelain.log('.', max_entries=1) - -------------------------------------------------- - commit: 57fbe010446356833a6ad1600059d80b1e731e15 - Author: Jelmer Vernooij - Date: Sat Apr 29 2017 23:57:34 +0000 - - Add note about encoding. - - Further documentation - --------------------- - - The dulwich documentation can be found in docs/ and built by running ``make - doc``. It can also be found `on the web `_. - - Help - ---- - - There is a *#dulwich* IRC channel on the `OFTC `_, and - `dulwich-announce `_ - and `dulwich-discuss `_ - mailing lists. - - Contributing - ------------ - - For a full list of contributors, see the git logs or `AUTHORS `_. - - If you'd like to contribute to Dulwich, see the `CONTRIBUTING `_ - file and `list of open issues `_. - - Supported versions of Python - ---------------------------- - - At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and - Pypy. - - The latest release series to support Python 2.x was the 0.19 series. See - the 0.19 branch in the Dulwich git repository. - Keywords: git vcs Platform: UNKNOWN Classifier: Development Status :: 4 - Beta @@ -124,3 +28,103 @@ Provides-Extra: fastimport Provides-Extra: https Provides-Extra: pgp Provides-Extra: watch +License-File: COPYING +License-File: AUTHORS + +Dulwich +======= + +This is the Dulwich project. + +It aims to provide an interface to git repos (both local and remote) that +doesn't call out to git directly but instead uses pure Python. + +**Main website**: + +**License**: Apache License, version 2 or GNU General Public License, version 2 or later. + +The project is named after the part of London that Mr. and Mrs. Git live in +in the particular Monty Python sketch. + +Installation +------------ + +By default, Dulwich' setup.py will attempt to build and install the optional C +extensions. The reason for this is that they significantly improve the performance +since some low-level operations that are executed often are much slower in CPython. + +If you don't want to install the C bindings, specify the --pure argument to setup.py:: + + $ python setup.py --pure install + +or if you are installing from pip:: + + $ pip install dulwich --global-option="--pure" + +Note that you can also specify --global-option in a +`requirements.txt `_ +file, e.g. like this:: + + dulwich --global-option=--pure + +Getting started +--------------- + +Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). + +For example, to use the lower level API to access the commit message of the +last commit:: + + >>> from dulwich.repo import Repo + >>> r = Repo('.') + >>> r.head() + '57fbe010446356833a6ad1600059d80b1e731e15' + >>> c = r[r.head()] + >>> c + + >>> c.message + 'Add note about encoding.\n' + +And to print it using porcelain:: + + >>> from dulwich import porcelain + >>> porcelain.log('.', max_entries=1) + -------------------------------------------------- + commit: 57fbe010446356833a6ad1600059d80b1e731e15 + Author: Jelmer Vernooij + Date: Sat Apr 29 2017 23:57:34 +0000 + + Add note about encoding. + +Further documentation +--------------------- + +The dulwich documentation can be found in docs/ and built by running ``make +doc``. It can also be found `on the web `_. + +Help +---- + +There is a *#dulwich* IRC channel on the `OFTC `_, and +`dulwich-announce `_ +and `dulwich-discuss `_ +mailing lists. + +Contributing +------------ + +For a full list of contributors, see the git logs or `AUTHORS `_. + +If you'd like to contribute to Dulwich, see the `CONTRIBUTING `_ +file and `list of open issues `_. + +Supported versions of Python +---------------------------- + +At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and +Pypy. + +The latest release series to support Python 2.x was the 0.19 series. See +the 0.19 branch in the Dulwich git repository. + + blob - 7e9c7a83aa250024ec3157354277444b57b1af47 blob + 75bc62eb2b568f43d43518ae5b2c38c61eaa47be --- dulwich.egg-info/SOURCES.txt +++ dulwich.egg-info/SOURCES.txt @@ -15,7 +15,6 @@ README.rst README.swift.rst SECURITY.md TODO -build.cmd dulwich.cfg releaser.conf requirements.txt @@ -51,6 +50,7 @@ docs/tutorial/remote.txt docs/tutorial/repo.txt docs/tutorial/tag.txt dulwich/__init__.py +dulwich/__main__.py dulwich/_diff_tree.c dulwich/_objects.c dulwich/_pack.c @@ -93,6 +93,7 @@ dulwich.egg-info/PKG-INFO dulwich.egg-info/SOURCES.txt dulwich.egg-info/dependency_links.txt dulwich.egg-info/entry_points.txt +dulwich.egg-info/not-zip-safe dulwich.egg-info/requires.txt dulwich.egg-info/top_level.txt dulwich/cloud/__init__.py blob - /dev/null blob + 8b137891791fe96927ad78e64b0aad7bded08bdc (mode 644) --- /dev/null +++ dulwich.egg-info/not-zip-safe @@ -0,0 +1 @@ + blob - cf6b7a3527d1b3a5537644f176fbbab6df669daf blob + bcad72df255a46d39eb9f694dbc8467fb8880de4 --- releaser.conf +++ releaser.conf @@ -1,3 +1,4 @@ +# See https://github.com/jelmer/releaser news_file: "NEWS" timeout_days: 5 tag_name: "dulwich-$VERSION" blob - c9da33d268834db81a4b27b70faab81bce4e93ef blob + 8168ef69730e5fec94de224eeac1c970f189fe58 --- setup.py +++ setup.py @@ -23,7 +23,7 @@ if sys.version_info < (3, 5): 'For 2.7 support, please install a version prior to 0.20') -dulwich_version_string = '0.20.23' +dulwich_version_string = '0.20.25' class DulwichDistribution(Distribution): @@ -116,6 +116,7 @@ setup(name='dulwich', package_data={'': ['../docs/tutorial/*.txt', 'py.typed']}, scripts=scripts, ext_modules=ext_modules, + zip_safe=False, distclass=DulwichDistribution, classifiers=[ 'Development Status :: 4 - Beta',