New upstream release.
Jelmer Vernooij
2 years ago
0 | 0 | name: Python package |
1 | 1 | |
2 | on: [push, pull_request] | |
2 | on: | |
3 | push: | |
4 | pull_request: | |
5 | schedule: | |
6 | - cron: '0 6 * * *' # Daily 6AM UTC build | |
3 | 7 | |
4 | 8 | jobs: |
5 | 9 | build: |
8 | 12 | strategy: |
9 | 13 | matrix: |
10 | 14 | os: [ubuntu-latest, macos-latest, windows-latest] |
11 | python-version: [3.5, 3.6, 3.7, 3.8, 3.9, pypy3] | |
15 | python-version: [3.5, 3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy3] | |
12 | 16 | exclude: |
13 | 17 | # sqlite3 exit handling seems to get in the way |
14 | 18 | - os: macos-latest |
45 | 49 | if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'" |
46 | 50 | - name: Install mypy |
47 | 51 | run: | |
48 | pip install -U mypy | |
52 | pip install -U mypy types-paramiko types-certifi | |
49 | 53 | if: "matrix.python-version != 'pypy3'" |
50 | 54 | - name: Style checks |
51 | 55 | run: | |
0 | 0.20.25 2021-08-23 | |
1 | ||
2 | * Fix ``dulwich`` script when installed via setup.py. | |
3 | (Dan Villiom Podlaski Christiansen) | |
4 | ||
5 | * Make default file mask consistent | |
6 | with Git. (Dan Villiom Podlaski Christiansen, #884) | |
7 | ||
8 | 0.20.24 2021-07-18 | |
9 | ||
10 | * config: disregard UTF-8 BOM when reading file. | |
11 | (Dan Villiom Podlaski Christiansen) | |
12 | ||
13 | * Skip lines with spaces only in .gitignore. (Andrey Torsunov, #878) | |
14 | ||
15 | * Add a separate HTTPProxyUnauthorized exception for 407 errors. | |
16 | (Jelmer Vernooij, #822) | |
17 | ||
18 | * Split out a AbstractHTTPGitClient class. | |
19 | (Jelmer Vernooij) | |
20 | ||
0 | 21 | 0.20.23 2021-05-24 |
1 | 22 | |
2 | 23 | * Fix installation of GPG during package publishing. |
281 | 302 | BUG FIXES |
282 | 303 | |
283 | 304 | * Avoid ``PermissionError``, since it is Python3-specific. |
284 | (Jelmer Vernooij) | |
305 | (Jelmer Vernooij) | |
285 | 306 | |
286 | 307 | * Fix regression that added a dependency on C git for the |
287 | 308 | test suite. (Jelmer Vernooij, #720) |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: dulwich |
2 | Version: 0.20.23 | |
2 | Version: 0.20.25 | |
3 | 3 | Summary: Python Git Library |
4 | 4 | Home-page: https://www.dulwich.io/ |
5 | 5 | Author: Jelmer Vernooij |
8 | 8 | Project-URL: Bug Tracker, https://github.com/dulwich/dulwich/issues |
9 | 9 | Project-URL: Repository, https://www.dulwich.io/code/ |
10 | 10 | Project-URL: GitHub, https://github.com/dulwich/dulwich |
11 | Description: Dulwich | |
12 | ======= | |
13 | ||
14 | This is the Dulwich project. | |
15 | ||
16 | It aims to provide an interface to git repos (both local and remote) that | |
17 | doesn't call out to git directly but instead uses pure Python. | |
18 | ||
19 | **Main website**: <https://www.dulwich.io/> | |
20 | ||
21 | **License**: Apache License, version 2 or GNU General Public License, version 2 or later. | |
22 | ||
23 | The project is named after the part of London that Mr. and Mrs. Git live in | |
24 | in the particular Monty Python sketch. | |
25 | ||
26 | Installation | |
27 | ------------ | |
28 | ||
29 | By default, Dulwich' setup.py will attempt to build and install the optional C | |
30 | extensions. The reason for this is that they significantly improve the performance | |
31 | since some low-level operations that are executed often are much slower in CPython. | |
32 | ||
33 | If you don't want to install the C bindings, specify the --pure argument to setup.py:: | |
34 | ||
35 | $ python setup.py --pure install | |
36 | ||
37 | or if you are installing from pip:: | |
38 | ||
39 | $ pip install dulwich --global-option="--pure" | |
40 | ||
41 | Note that you can also specify --global-option in a | |
42 | `requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_ | |
43 | file, e.g. like this:: | |
44 | ||
45 | dulwich --global-option=--pure | |
46 | ||
47 | Getting started | |
48 | --------------- | |
49 | ||
50 | Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). | |
51 | ||
52 | For example, to use the lower level API to access the commit message of the | |
53 | last commit:: | |
54 | ||
55 | >>> from dulwich.repo import Repo | |
56 | >>> r = Repo('.') | |
57 | >>> r.head() | |
58 | '57fbe010446356833a6ad1600059d80b1e731e15' | |
59 | >>> c = r[r.head()] | |
60 | >>> c | |
61 | <Commit 015fc1267258458901a94d228e39f0a378370466> | |
62 | >>> c.message | |
63 | 'Add note about encoding.\n' | |
64 | ||
65 | And to print it using porcelain:: | |
66 | ||
67 | >>> from dulwich import porcelain | |
68 | >>> porcelain.log('.', max_entries=1) | |
69 | -------------------------------------------------- | |
70 | commit: 57fbe010446356833a6ad1600059d80b1e731e15 | |
71 | Author: Jelmer Vernooij <jelmer@jelmer.uk> | |
72 | Date: Sat Apr 29 2017 23:57:34 +0000 | |
73 | ||
74 | Add note about encoding. | |
75 | ||
76 | Further documentation | |
77 | --------------------- | |
78 | ||
79 | The dulwich documentation can be found in docs/ and built by running ``make | |
80 | doc``. It can also be found `on the web <https://www.dulwich.io/docs/>`_. | |
81 | ||
82 | Help | |
83 | ---- | |
84 | ||
85 | There is a *#dulwich* IRC channel on the `OFTC <https://www.oftc.net/>`_, and | |
86 | `dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_ | |
87 | and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_ | |
88 | mailing lists. | |
89 | ||
90 | Contributing | |
91 | ------------ | |
92 | ||
93 | For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_. | |
94 | ||
95 | If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_ | |
96 | file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_. | |
97 | ||
98 | Supported versions of Python | |
99 | ---------------------------- | |
100 | ||
101 | At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and | |
102 | Pypy. | |
103 | ||
104 | The latest release series to support Python 2.x was the 0.19 series. See | |
105 | the 0.19 branch in the Dulwich git repository. | |
106 | ||
107 | 11 | Keywords: git vcs |
108 | 12 | Platform: UNKNOWN |
109 | 13 | Classifier: Development Status :: 4 - Beta |
123 | 27 | Provides-Extra: https |
124 | 28 | Provides-Extra: pgp |
125 | 29 | Provides-Extra: watch |
30 | License-File: COPYING | |
31 | License-File: AUTHORS | |
32 | ||
33 | Dulwich | |
34 | ======= | |
35 | ||
36 | This is the Dulwich project. | |
37 | ||
38 | It aims to provide an interface to git repos (both local and remote) that | |
39 | doesn't call out to git directly but instead uses pure Python. | |
40 | ||
41 | **Main website**: <https://www.dulwich.io/> | |
42 | ||
43 | **License**: Apache License, version 2 or GNU General Public License, version 2 or later. | |
44 | ||
45 | The project is named after the part of London that Mr. and Mrs. Git live in | |
46 | in the particular Monty Python sketch. | |
47 | ||
48 | Installation | |
49 | ------------ | |
50 | ||
51 | By default, Dulwich' setup.py will attempt to build and install the optional C | |
52 | extensions. The reason for this is that they significantly improve the performance | |
53 | since some low-level operations that are executed often are much slower in CPython. | |
54 | ||
55 | If you don't want to install the C bindings, specify the --pure argument to setup.py:: | |
56 | ||
57 | $ python setup.py --pure install | |
58 | ||
59 | or if you are installing from pip:: | |
60 | ||
61 | $ pip install dulwich --global-option="--pure" | |
62 | ||
63 | Note that you can also specify --global-option in a | |
64 | `requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_ | |
65 | file, e.g. like this:: | |
66 | ||
67 | dulwich --global-option=--pure | |
68 | ||
69 | Getting started | |
70 | --------------- | |
71 | ||
72 | Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). | |
73 | ||
74 | For example, to use the lower level API to access the commit message of the | |
75 | last commit:: | |
76 | ||
77 | >>> from dulwich.repo import Repo | |
78 | >>> r = Repo('.') | |
79 | >>> r.head() | |
80 | '57fbe010446356833a6ad1600059d80b1e731e15' | |
81 | >>> c = r[r.head()] | |
82 | >>> c | |
83 | <Commit 015fc1267258458901a94d228e39f0a378370466> | |
84 | >>> c.message | |
85 | 'Add note about encoding.\n' | |
86 | ||
87 | And to print it using porcelain:: | |
88 | ||
89 | >>> from dulwich import porcelain | |
90 | >>> porcelain.log('.', max_entries=1) | |
91 | -------------------------------------------------- | |
92 | commit: 57fbe010446356833a6ad1600059d80b1e731e15 | |
93 | Author: Jelmer Vernooij <jelmer@jelmer.uk> | |
94 | Date: Sat Apr 29 2017 23:57:34 +0000 | |
95 | ||
96 | Add note about encoding. | |
97 | ||
98 | Further documentation | |
99 | --------------------- | |
100 | ||
101 | The dulwich documentation can be found in docs/ and built by running ``make | |
102 | doc``. It can also be found `on the web <https://www.dulwich.io/docs/>`_. | |
103 | ||
104 | Help | |
105 | ---- | |
106 | ||
107 | There is a *#dulwich* IRC channel on the `OFTC <https://www.oftc.net/>`_, and | |
108 | `dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_ | |
109 | and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_ | |
110 | mailing lists. | |
111 | ||
112 | Contributing | |
113 | ------------ | |
114 | ||
115 | For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_. | |
116 | ||
117 | If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_ | |
118 | file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_. | |
119 | ||
120 | Supported versions of Python | |
121 | ---------------------------- | |
122 | ||
123 | At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and | |
124 | Pypy. | |
125 | ||
126 | The latest release series to support Python 2.x was the 0.19 series. See | |
127 | the 0.19 branch in the Dulwich git repository. | |
128 | ||
129 |
0 | @echo off | |
1 | :: To build extensions for 64 bit Python 3, we need to configure environment | |
2 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: | |
3 | :: MS Windows SDK for Windows 7 and .NET Framework 4 | |
4 | :: | |
5 | :: More details at: | |
6 | :: https://github.com/cython/cython/wiki/CythonExtensionsOnWindows | |
7 | ||
8 | IF "%DISTUTILS_USE_SDK%"=="1" ( | |
9 | ECHO Configuring environment to build with MSVC on a 64bit architecture | |
10 | ECHO Using Windows SDK 7.1 | |
11 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 | |
12 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release | |
13 | SET MSSdk=1 | |
14 | REM Need the following to allow tox to see the SDK compiler | |
15 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB | |
16 | ) ELSE ( | |
17 | ECHO Using default MSVC build environment | |
18 | ) | |
19 | ||
20 | CALL %* |
0 | dulwich (0.20.25-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Jelmer Vernooij <jelmer@debian.org> Wed, 25 Aug 2021 17:47:30 -0000 | |
5 | ||
0 | 6 | dulwich (0.20.23-1) unstable; urgency=medium |
1 | 7 | |
2 | 8 | * Update watch file format version to 4. |
21 | 21 | |
22 | 22 | """Python implementation of the Git file formats and protocols.""" |
23 | 23 | |
24 | __version__ = (0, 20, 23) | |
24 | __version__ = (0, 20, 25) |
730 | 730 | |
731 | 731 | def main(argv=None): |
732 | 732 | if argv is None: |
733 | argv = sys.argv | |
733 | argv = sys.argv[1:] | |
734 | 734 | |
735 | 735 | if len(argv) < 1: |
736 | 736 | print("Usage: dulwich <%s> [OPTIONS...]" % ("|".join(commands.keys()))) |
746 | 746 | return cmd_kls().run(argv[1:]) |
747 | 747 | |
748 | 748 | |
749 | if __name__ == "__main__": | |
749 | def _main(): | |
750 | 750 | if "DULWICH_PDB" in os.environ and getattr(signal, "SIGQUIT", None): |
751 | 751 | signal.signal(signal.SIGQUIT, signal_quit) # type: ignore |
752 | 752 | signal.signal(signal.SIGINT, signal_int) |
753 | 753 | |
754 | sys.exit(main(sys.argv[1:])) | |
754 | sys.exit(main()) | |
755 | ||
756 | ||
757 | if __name__ == "__main__": | |
758 | _main() |
132 | 132 | self.url = url |
133 | 133 | |
134 | 134 | |
135 | class HTTPProxyUnauthorized(Exception): | |
136 | """Raised when proxy authentication fails.""" | |
137 | ||
138 | def __init__(self, proxy_authenticate, url): | |
139 | Exception.__init__(self, "No valid proxy credentials provided") | |
140 | self.proxy_authenticate = proxy_authenticate | |
141 | self.url = url | |
142 | ||
143 | ||
135 | 144 | def _fileno_can_read(fileno): |
136 | 145 | """Check if a file descriptor is readable.""" |
137 | 146 | return len(select.select([fileno], [], [], 0)[0]) > 0 |
548 | 557 | Args: |
549 | 558 | path: Remote path to fetch from |
550 | 559 | determine_wants: Function determine what refs |
551 | to fetch. Receives dictionary of name->sha, should return | |
552 | list of shas to fetch. | |
560 | to fetch. Receives dictionary of name->sha, should return | |
561 | list of shas to fetch. | |
553 | 562 | graph_walker: Object with next() and ack(). |
554 | 563 | pack_data: Callback called for each bit of data in the pack |
555 | 564 | progress: Callback for progress reports (strings) |
900 | 909 | Args: |
901 | 910 | path: Repository path (as bytestring) |
902 | 911 | update_refs: Function to determine changes to remote refs. |
903 | Receive dict with existing remote refs, returns dict with | |
904 | changed refs (name -> sha, where sha=ZERO_SHA for deletions) | |
912 | Receive dict with existing remote refs, returns dict with | |
913 | changed refs (name -> sha, where sha=ZERO_SHA for deletions) | |
905 | 914 | generate_pack_data: Function that can return a tuple with |
906 | number of objects and pack data to upload. | |
915 | number of objects and pack data to upload. | |
907 | 916 | progress: Optional callback called with progress updates |
908 | 917 | |
909 | 918 | Returns: |
994 | 1003 | Args: |
995 | 1004 | path: Remote path to fetch from |
996 | 1005 | determine_wants: Function determine what refs |
997 | to fetch. Receives dictionary of name->sha, should return | |
998 | list of shas to fetch. | |
1006 | to fetch. Receives dictionary of name->sha, should return | |
1007 | list of shas to fetch. | |
999 | 1008 | graph_walker: Object with next() and ack(). |
1000 | 1009 | pack_data: Callback called for each bit of data in the pack |
1001 | 1010 | progress: Callback for progress reports (strings) |
1291 | 1300 | Args: |
1292 | 1301 | path: Repository path (as bytestring) |
1293 | 1302 | update_refs: Function to determine changes to remote refs. |
1294 | Receive dict with existing remote refs, returns dict with | |
1295 | changed refs (name -> sha, where sha=ZERO_SHA for deletions) | |
1296 | with number of items and pack data to upload. | |
1303 | Receive dict with existing remote refs, returns dict with | |
1304 | changed refs (name -> sha, where sha=ZERO_SHA for deletions) | |
1305 | with number of items and pack data to upload. | |
1297 | 1306 | progress: Optional progress function |
1298 | 1307 | |
1299 | 1308 | Returns: |
1352 | 1361 | path: Path to fetch from (as bytestring) |
1353 | 1362 | target: Target repository to fetch into |
1354 | 1363 | determine_wants: Optional function determine what refs |
1355 | to fetch. Receives dictionary of name->sha, should return | |
1356 | list of shas to fetch. Defaults to all shas. | |
1364 | to fetch. Receives dictionary of name->sha, should return | |
1365 | list of shas to fetch. Defaults to all shas. | |
1357 | 1366 | progress: Optional progress function |
1358 | 1367 | depth: Shallow fetch depth |
1359 | 1368 | |
1384 | 1393 | Args: |
1385 | 1394 | path: Remote path to fetch from |
1386 | 1395 | determine_wants: Function determine what refs |
1387 | to fetch. Receives dictionary of name->sha, should return | |
1388 | list of shas to fetch. | |
1396 | to fetch. Receives dictionary of name->sha, should return | |
1397 | list of shas to fetch. | |
1389 | 1398 | graph_walker: Object with next() and ack(). |
1390 | 1399 | pack_data: Callback called for each bit of data in the pack |
1391 | 1400 | progress: Callback for progress reports (strings) |
1758 | 1767 | if proxy_server is not None: |
1759 | 1768 | if proxy_manager_cls is None: |
1760 | 1769 | proxy_manager_cls = urllib3.ProxyManager |
1761 | # `urllib3` requires a `str` object in both Python 2 and 3, while | |
1762 | # `ConfigDict` coerces entries to `bytes` on Python 3. Compensate. | |
1763 | 1770 | if not isinstance(proxy_server, str): |
1764 | 1771 | proxy_server = proxy_server.decode() |
1765 | 1772 | manager = proxy_manager_cls(proxy_server, headers=headers, **kwargs) |
1771 | 1778 | return manager |
1772 | 1779 | |
1773 | 1780 | |
1774 | class HttpGitClient(GitClient): | |
1775 | def __init__( | |
1776 | self, | |
1777 | base_url, | |
1778 | dumb=None, | |
1779 | pool_manager=None, | |
1780 | config=None, | |
1781 | username=None, | |
1782 | password=None, | |
1783 | **kwargs | |
1784 | ): | |
1781 | class AbstractHttpGitClient(GitClient): | |
1782 | """Abstract base class for HTTP Git Clients. | |
1783 | ||
1784 | This is agonistic of the actual HTTP implementation. | |
1785 | ||
1786 | Subclasses should provide an implementation of the | |
1787 | _http_request method. | |
1788 | """ | |
1789 | ||
1790 | def __init__(self, base_url, dumb=False, **kwargs): | |
1785 | 1791 | self._base_url = base_url.rstrip("/") + "/" |
1786 | self._username = username | |
1787 | self._password = password | |
1788 | 1792 | self.dumb = dumb |
1789 | ||
1790 | if pool_manager is None: | |
1791 | self.pool_manager = default_urllib3_manager(config) | |
1792 | else: | |
1793 | self.pool_manager = pool_manager | |
1794 | ||
1795 | if username is not None: | |
1796 | # No escaping needed: ":" is not allowed in username: | |
1797 | # https://tools.ietf.org/html/rfc2617#section-2 | |
1798 | credentials = "%s:%s" % (username, password) | |
1799 | import urllib3.util | |
1800 | ||
1801 | basic_auth = urllib3.util.make_headers(basic_auth=credentials) | |
1802 | self.pool_manager.headers.update(basic_auth) | |
1803 | ||
1804 | 1793 | GitClient.__init__(self, **kwargs) |
1805 | ||
1806 | def get_url(self, path): | |
1807 | return self._get_url(path).rstrip("/") | |
1808 | ||
1809 | @classmethod | |
1810 | def from_parsedurl(cls, parsedurl, **kwargs): | |
1811 | password = parsedurl.password | |
1812 | if password is not None: | |
1813 | kwargs["password"] = urlunquote(password) | |
1814 | username = parsedurl.username | |
1815 | if username is not None: | |
1816 | kwargs["username"] = urlunquote(username) | |
1817 | netloc = parsedurl.hostname | |
1818 | if parsedurl.port: | |
1819 | netloc = "%s:%s" % (netloc, parsedurl.port) | |
1820 | if parsedurl.username: | |
1821 | netloc = "%s@%s" % (parsedurl.username, netloc) | |
1822 | parsedurl = parsedurl._replace(netloc=netloc) | |
1823 | return cls(urlunparse(parsedurl), **kwargs) | |
1824 | ||
1825 | def __repr__(self): | |
1826 | return "%s(%r, dumb=%r)" % ( | |
1827 | type(self).__name__, | |
1828 | self._base_url, | |
1829 | self.dumb, | |
1830 | ) | |
1831 | ||
1832 | def _get_url(self, path): | |
1833 | if not isinstance(path, str): | |
1834 | # urllib3.util.url._encode_invalid_chars() converts the path back | |
1835 | # to bytes using the utf-8 codec. | |
1836 | path = path.decode("utf-8") | |
1837 | return urljoin(self._base_url, path).rstrip("/") + "/" | |
1838 | 1794 | |
1839 | 1795 | def _http_request(self, url, headers=None, data=None, allow_compression=False): |
1840 | 1796 | """Perform HTTP request. |
1852 | 1808 | method for the response data. |
1853 | 1809 | |
1854 | 1810 | """ |
1855 | req_headers = self.pool_manager.headers.copy() | |
1856 | if headers is not None: | |
1857 | req_headers.update(headers) | |
1858 | req_headers["Pragma"] = "no-cache" | |
1859 | if allow_compression: | |
1860 | req_headers["Accept-Encoding"] = "gzip" | |
1861 | else: | |
1862 | req_headers["Accept-Encoding"] = "identity" | |
1863 | ||
1864 | if data is None: | |
1865 | resp = self.pool_manager.request("GET", url, headers=req_headers) | |
1866 | else: | |
1867 | resp = self.pool_manager.request( | |
1868 | "POST", url, headers=req_headers, body=data | |
1869 | ) | |
1870 | ||
1871 | if resp.status == 404: | |
1872 | raise NotGitRepository() | |
1873 | if resp.status == 401: | |
1874 | raise HTTPUnauthorized(resp.getheader("WWW-Authenticate"), url) | |
1875 | if resp.status != 200: | |
1876 | raise GitProtocolError( | |
1877 | "unexpected http resp %d for %s" % (resp.status, url) | |
1878 | ) | |
1879 | ||
1880 | # TODO: Optimization available by adding `preload_content=False` to the | |
1881 | # request and just passing the `read` method on instead of going via | |
1882 | # `BytesIO`, if we can guarantee that the entire response is consumed | |
1883 | # before issuing the next to still allow for connection reuse from the | |
1884 | # pool. | |
1885 | read = BytesIO(resp.data).read | |
1886 | ||
1887 | resp.content_type = resp.getheader("Content-Type") | |
1888 | # Check if geturl() is available (urllib3 version >= 1.23) | |
1889 | try: | |
1890 | resp_url = resp.geturl() | |
1891 | except AttributeError: | |
1892 | # get_redirect_location() is available for urllib3 >= 1.1 | |
1893 | resp.redirect_location = resp.get_redirect_location() | |
1894 | else: | |
1895 | resp.redirect_location = resp_url if resp_url != url else "" | |
1896 | return resp, read | |
1811 | ||
1812 | raise NotImplementedError(self._http_request) | |
1897 | 1813 | |
1898 | 1814 | def _discover_references(self, service, base_url): |
1899 | 1815 | assert base_url[-1] == "/" |
1933 | 1849 | resp.close() |
1934 | 1850 | |
1935 | 1851 | def _smart_request(self, service, url, data): |
1852 | """Send a 'smart' HTTP request. | |
1853 | ||
1854 | This is a simple wrapper around _http_request that sets | |
1855 | a couple of extra headers. | |
1856 | """ | |
1936 | 1857 | assert url[-1] == "/" |
1937 | 1858 | url = urljoin(url, service) |
1938 | 1859 | result_content_type = "application/x-%s-result" % service |
1954 | 1875 | Args: |
1955 | 1876 | path: Repository path (as bytestring) |
1956 | 1877 | update_refs: Function to determine changes to remote refs. |
1957 | Receives dict with existing remote refs, returns dict with | |
1958 | changed refs (name -> sha, where sha=ZERO_SHA for deletions) | |
1878 | Receives dict with existing remote refs, returns dict with | |
1879 | changed refs (name -> sha, where sha=ZERO_SHA for deletions) | |
1959 | 1880 | generate_pack_data: Function that can return a tuple |
1960 | with number of elements and pack data to upload. | |
1881 | with number of elements and pack data to upload. | |
1961 | 1882 | progress: Optional progress function |
1962 | 1883 | |
1963 | 1884 | Returns: |
2088 | 2009 | refs, _, _ = self._discover_references(b"git-upload-pack", url) |
2089 | 2010 | return refs |
2090 | 2011 | |
2012 | def get_url(self, path): | |
2013 | return self._get_url(path).rstrip("/") | |
2014 | ||
2015 | def _get_url(self, path): | |
2016 | return urljoin(self._base_url, path).rstrip("/") + "/" | |
2017 | ||
2018 | @classmethod | |
2019 | def from_parsedurl(cls, parsedurl, **kwargs): | |
2020 | password = parsedurl.password | |
2021 | if password is not None: | |
2022 | kwargs["password"] = urlunquote(password) | |
2023 | username = parsedurl.username | |
2024 | if username is not None: | |
2025 | kwargs["username"] = urlunquote(username) | |
2026 | netloc = parsedurl.hostname | |
2027 | if parsedurl.port: | |
2028 | netloc = "%s:%s" % (netloc, parsedurl.port) | |
2029 | if parsedurl.username: | |
2030 | netloc = "%s@%s" % (parsedurl.username, netloc) | |
2031 | parsedurl = parsedurl._replace(netloc=netloc) | |
2032 | return cls(urlunparse(parsedurl), **kwargs) | |
2033 | ||
2034 | def __repr__(self): | |
2035 | return "%s(%r, dumb=%r)" % ( | |
2036 | type(self).__name__, | |
2037 | self._base_url, | |
2038 | self.dumb, | |
2039 | ) | |
2040 | ||
2041 | ||
2042 | class Urllib3HttpGitClient(AbstractHttpGitClient): | |
2043 | def __init__( | |
2044 | self, | |
2045 | base_url, | |
2046 | dumb=None, | |
2047 | pool_manager=None, | |
2048 | config=None, | |
2049 | username=None, | |
2050 | password=None, | |
2051 | **kwargs | |
2052 | ): | |
2053 | self._username = username | |
2054 | self._password = password | |
2055 | ||
2056 | if pool_manager is None: | |
2057 | self.pool_manager = default_urllib3_manager(config) | |
2058 | else: | |
2059 | self.pool_manager = pool_manager | |
2060 | ||
2061 | if username is not None: | |
2062 | # No escaping needed: ":" is not allowed in username: | |
2063 | # https://tools.ietf.org/html/rfc2617#section-2 | |
2064 | credentials = "%s:%s" % (username, password) | |
2065 | import urllib3.util | |
2066 | ||
2067 | basic_auth = urllib3.util.make_headers(basic_auth=credentials) | |
2068 | self.pool_manager.headers.update(basic_auth) | |
2069 | ||
2070 | super(Urllib3HttpGitClient, self).__init__( | |
2071 | base_url=base_url, dumb=dumb, **kwargs) | |
2072 | ||
2073 | def _get_url(self, path): | |
2074 | if not isinstance(path, str): | |
2075 | # urllib3.util.url._encode_invalid_chars() converts the path back | |
2076 | # to bytes using the utf-8 codec. | |
2077 | path = path.decode("utf-8") | |
2078 | return urljoin(self._base_url, path).rstrip("/") + "/" | |
2079 | ||
2080 | def _http_request(self, url, headers=None, data=None, allow_compression=False): | |
2081 | req_headers = self.pool_manager.headers.copy() | |
2082 | if headers is not None: | |
2083 | req_headers.update(headers) | |
2084 | req_headers["Pragma"] = "no-cache" | |
2085 | if allow_compression: | |
2086 | req_headers["Accept-Encoding"] = "gzip" | |
2087 | else: | |
2088 | req_headers["Accept-Encoding"] = "identity" | |
2089 | ||
2090 | if data is None: | |
2091 | resp = self.pool_manager.request("GET", url, headers=req_headers) | |
2092 | else: | |
2093 | resp = self.pool_manager.request( | |
2094 | "POST", url, headers=req_headers, body=data | |
2095 | ) | |
2096 | ||
2097 | if resp.status == 404: | |
2098 | raise NotGitRepository() | |
2099 | if resp.status == 401: | |
2100 | raise HTTPUnauthorized(resp.getheader("WWW-Authenticate"), url) | |
2101 | if resp.status == 407: | |
2102 | raise HTTPProxyUnauthorized(resp.getheader("Proxy-Authenticate"), url) | |
2103 | if resp.status != 200: | |
2104 | raise GitProtocolError( | |
2105 | "unexpected http resp %d for %s" % (resp.status, url) | |
2106 | ) | |
2107 | ||
2108 | # TODO: Optimization available by adding `preload_content=False` to the | |
2109 | # request and just passing the `read` method on instead of going via | |
2110 | # `BytesIO`, if we can guarantee that the entire response is consumed | |
2111 | # before issuing the next to still allow for connection reuse from the | |
2112 | # pool. | |
2113 | read = BytesIO(resp.data).read | |
2114 | ||
2115 | resp.content_type = resp.getheader("Content-Type") | |
2116 | # Check if geturl() is available (urllib3 version >= 1.23) | |
2117 | try: | |
2118 | resp_url = resp.geturl() | |
2119 | except AttributeError: | |
2120 | # get_redirect_location() is available for urllib3 >= 1.1 | |
2121 | resp.redirect_location = resp.get_redirect_location() | |
2122 | else: | |
2123 | resp.redirect_location = resp_url if resp_url != url else "" | |
2124 | return resp, read | |
2125 | ||
2126 | ||
2127 | HttpGitClient = Urllib3HttpGitClient | |
2128 | ||
2091 | 2129 | |
2092 | 2130 | def get_transport_and_path_from_url(url, config=None, **kwargs): |
2093 | 2131 | """Obtain a git client from a URL. |
40 | 40 | MutableMapping, |
41 | 41 | ) |
42 | 42 | except ImportError: # python < 3.7 |
43 | from collections import ( | |
43 | from collections import ( # type: ignore | |
44 | 44 | Iterable, |
45 | 45 | MutableMapping, |
46 | 46 | ) |
386 | 386 | super(ConfigFile, self).__init__(values=values, encoding=encoding) |
387 | 387 | self.path = None |
388 | 388 | |
389 | @classmethod | |
390 | def from_file(cls, f: BinaryIO) -> "ConfigFile": | |
389 | @classmethod # noqa: C901 | |
390 | def from_file(cls, f: BinaryIO) -> "ConfigFile": # noqa: C901 | |
391 | 391 | """Read configuration from a file-like object.""" |
392 | 392 | ret = cls() |
393 | 393 | section = None # type: Optional[Tuple[bytes, ...]] |
394 | 394 | setting = None |
395 | 395 | continuation = None |
396 | 396 | for lineno, line in enumerate(f.readlines()): |
397 | if lineno == 0 and line.startswith(b'\xef\xbb\xbf'): | |
398 | line = line[3:] | |
397 | 399 | line = line.lstrip() |
398 | 400 | if setting is None: |
399 | 401 | # Parse section header ("[bla]") |
65 | 65 | os.remove(tmpfile) |
66 | 66 | |
67 | 67 | |
68 | def GitFile(filename, mode="rb", bufsize=-1): | |
68 | def GitFile(filename, mode="rb", bufsize=-1, mask=0o644): | |
69 | 69 | """Create a file object that obeys the git file locking protocol. |
70 | 70 | |
71 | 71 | Returns: a builtin file object or a _GitFile object |
76 | 76 | are not. To read and write from the same file, you can take advantage of |
77 | 77 | the fact that opening a file for write does not actually open the file you |
78 | 78 | request. |
79 | ||
80 | The default file mask makes any created files user-writable and | |
81 | world-readable. | |
82 | ||
79 | 83 | """ |
80 | 84 | if "a" in mode: |
81 | 85 | raise IOError("append mode not supported for Git files") |
84 | 88 | if "b" not in mode: |
85 | 89 | raise IOError("text mode not supported for Git files") |
86 | 90 | if "w" in mode: |
87 | return _GitFile(filename, mode, bufsize) | |
91 | return _GitFile(filename, mode, bufsize, mask) | |
88 | 92 | else: |
89 | 93 | return io.open(filename, mode, bufsize) |
90 | 94 | |
135 | 139 | "writelines", |
136 | 140 | ) |
137 | 141 | |
138 | def __init__(self, filename, mode, bufsize): | |
142 | def __init__(self, filename, mode, bufsize, mask): | |
139 | 143 | self._filename = filename |
140 | 144 | if isinstance(self._filename, bytes): |
141 | 145 | self._lockfilename = self._filename + b".lock" |
145 | 149 | fd = os.open( |
146 | 150 | self._lockfilename, |
147 | 151 | os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0), |
152 | mask, | |
148 | 153 | ) |
149 | 154 | except FileExistsError: |
150 | 155 | raise FileLocked(filename, self._lockfilename) |
122 | 122 | line = line.rstrip(b"\r\n") |
123 | 123 | |
124 | 124 | # Ignore blank lines, they're used for readability. |
125 | if not line: | |
125 | if not line.strip(): | |
126 | 126 | continue |
127 | 127 | |
128 | 128 | if line.startswith(b"#"): |
69 | 69 | INFODIR = "info" |
70 | 70 | PACKDIR = "pack" |
71 | 71 | |
72 | # use permissions consistent with Git; just readable by everyone | |
73 | # TODO: should packs also be non-writable on Windows? if so, that | |
74 | # would requite some rather significant adjustments to the test suite | |
75 | PACK_MODE = 0o444 if sys.platform != "win32" else 0o644 | |
76 | ||
72 | 77 | |
73 | 78 | class BaseObjectStore(object): |
74 | 79 | """Object store interface.""" |
804 | 809 | os.rename(path, target_pack) |
805 | 810 | |
806 | 811 | # Write the index. |
807 | index_file = GitFile(pack_base_name + ".idx", "wb") | |
812 | index_file = GitFile(pack_base_name + ".idx", "wb", mask=PACK_MODE) | |
808 | 813 | try: |
809 | 814 | write_pack_index_v2(index_file, entries, pack_sha) |
810 | 815 | index_file.close() |
836 | 841 | |
837 | 842 | fd, path = tempfile.mkstemp(dir=self.path, prefix="tmp_pack_") |
838 | 843 | with os.fdopen(fd, "w+b") as f: |
844 | os.chmod(path, PACK_MODE) | |
839 | 845 | indexer = PackIndexer(f, resolve_ext_ref=self.get_raw) |
840 | 846 | copier = PackStreamCopier(read_all, read_some, f, delta_iter=indexer) |
841 | 847 | copier.verify() |
855 | 861 | basename = self._get_pack_basepath(entries) |
856 | 862 | index_name = basename + ".idx" |
857 | 863 | if not os.path.exists(index_name): |
858 | with GitFile(index_name, "wb") as f: | |
864 | with GitFile(index_name, "wb", mask=PACK_MODE) as f: | |
859 | 865 | write_pack_index_v2(f, entries, p.get_stored_checksum()) |
860 | 866 | for pack in self.packs: |
861 | 867 | if pack._basename == basename: |
884 | 890 | |
885 | 891 | fd, path = tempfile.mkstemp(dir=self.pack_dir, suffix=".pack") |
886 | 892 | f = os.fdopen(fd, "wb") |
893 | os.chmod(path, PACK_MODE) | |
887 | 894 | |
888 | 895 | def commit(): |
889 | 896 | f.flush() |
915 | 922 | pass |
916 | 923 | if os.path.exists(path): |
917 | 924 | return # Already there, no need to write again |
918 | with GitFile(path, "wb") as f: | |
925 | with GitFile(path, "wb", mask=PACK_MODE) as f: | |
919 | 926 | f.write( |
920 | 927 | obj.as_legacy_object(compression_level=self.loose_compression_level) |
921 | 928 | ) |
1807 | 1807 | stash.push() |
1808 | 1808 | |
1809 | 1809 | |
1810 | def stash_pop(repo): | |
1811 | """Pop a new stash from the stack.""" | |
1810 | def stash_pop(repo, index): | |
1811 | """Pop a stash from the stack.""" | |
1812 | 1812 | with open_repo_closing(repo) as r: |
1813 | 1813 | from dulwich.stash import Stash |
1814 | 1814 | |
1815 | 1815 | stash = Stash.from_repo(r) |
1816 | stash.pop() | |
1816 | stash.pop(index) | |
1817 | ||
1818 | ||
1819 | def stash_drop(repo, index): | |
1820 | """Drop a stash from the stack.""" | |
1821 | with open_repo_closing(repo) as r: | |
1822 | from dulwich.stash import Stash | |
1823 | ||
1824 | stash = Stash.from_repo(r) | |
1825 | stash.drop(index) | |
1817 | 1826 | |
1818 | 1827 | |
1819 | 1828 | def ls_files(repo): |
1261 | 1261 | |
1262 | 1262 | root_path_bytes = os.fsencode(self.path) |
1263 | 1263 | |
1264 | if not isinstance(fs_paths, list): | |
1264 | if isinstance(fs_paths, str): | |
1265 | 1265 | fs_paths = [fs_paths] |
1266 | fs_paths = list(fs_paths) | |
1267 | ||
1266 | 1268 | from dulwich.index import ( |
1267 | 1269 | blob_from_path_and_stat, |
1268 | 1270 | index_entry_from_stat, |
107 | 107 | self.assertEqual(b"bar", cf.get((b"core",), b"foo")) |
108 | 108 | self.assertEqual(b"bar", cf.get((b"core", b"foo"), b"foo")) |
109 | 109 | |
110 | def test_from_file_utf8_bom(self): | |
111 | text = "[core]\nfoo = b\u00e4r\n".encode("utf-8-sig") | |
112 | cf = self.from_file(text) | |
113 | self.assertEqual(b"b\xc3\xa4r", cf.get((b"core",), b"foo")) | |
114 | ||
110 | 115 | def test_from_file_section_case_insensitive_lower(self): |
111 | 116 | cf = self.from_file(b"[cOre]\nfOo = bar\n") |
112 | 117 | self.assertEqual(b"bar", cf.get((b"core",), b"foo")) |
104 | 104 | f = BytesIO( |
105 | 105 | b""" |
106 | 106 | # a comment |
107 | ||
107 | \x20\x20 | |
108 | 108 | # and an empty line: |
109 | 109 | |
110 | 110 | \\#not a comment |
26 | 26 | import os |
27 | 27 | import shutil |
28 | 28 | import stat |
29 | import sys | |
29 | 30 | import tempfile |
30 | 31 | |
31 | 32 | from dulwich.index import ( |
437 | 438 | for alt_path in store._read_alternate_paths(): |
438 | 439 | self.assertNotIn("#", alt_path) |
439 | 440 | |
441 | def test_file_modes(self): | |
442 | self.store.add_object(testobject) | |
443 | path = self.store._get_shafile_path(testobject.id) | |
444 | mode = os.stat(path).st_mode | |
445 | ||
446 | packmode = "0o100444" if sys.platform != "win32" else "0o100666" | |
447 | self.assertEqual(oct(mode), packmode) | |
448 | ||
440 | 449 | def test_corrupted_object_raise_exception(self): |
441 | 450 | """Corrupted sha1 disk file should raise specific exception""" |
442 | 451 | self.store.add_object(testobject) |
447 | 456 | self.assertIsNotNone(self.store._get_loose_object(testobject.id)) |
448 | 457 | |
449 | 458 | path = self.store._get_shafile_path(testobject.id) |
459 | old_mode = os.stat(path).st_mode | |
460 | os.chmod(path, 0o600) | |
450 | 461 | with open(path, "wb") as f: # corrupt the file |
451 | 462 | f.write(b"") |
463 | os.chmod(path, old_mode) | |
452 | 464 | |
453 | 465 | expected_error_msg = "Corrupted empty file detected" |
454 | 466 | try: |
25 | 25 | from hashlib import sha1 |
26 | 26 | import os |
27 | 27 | import shutil |
28 | import sys | |
28 | 29 | import tempfile |
29 | 30 | import zlib |
30 | 31 | |
82 | 83 | a_sha = b"6f670c0fb53f9463760b7295fbb814e965fb20c8" |
83 | 84 | tree_sha = b"b2a2766a2879c209ab1176e7e778b81ae422eeaa" |
84 | 85 | commit_sha = b"f18faa16531ac570a3fdc8c7ca16682548dafd12" |
86 | indexmode = "0o100644" if sys.platform != "win32" else "0o100666" | |
85 | 87 | |
86 | 88 | |
87 | 89 | class PackTests(TestCase): |
337 | 339 | p.create_index_v1(filename) |
338 | 340 | idx1 = load_pack_index(filename) |
339 | 341 | idx2 = self.get_pack_index(pack1_sha) |
342 | self.assertEqual(oct(os.stat(filename).st_mode), indexmode) | |
340 | 343 | self.assertEqual(idx1, idx2) |
341 | 344 | |
342 | 345 | def test_create_index_v2(self): |
345 | 348 | p.create_index_v2(filename) |
346 | 349 | idx1 = load_pack_index(filename) |
347 | 350 | idx2 = self.get_pack_index(pack1_sha) |
351 | self.assertEqual(oct(os.stat(filename).st_mode), indexmode) | |
348 | 352 | self.assertEqual(idx1, idx2) |
349 | 353 | |
350 | 354 | def test_compute_file_sha(self): |
110 | 110 | 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 | |
161 | =YPjc | |
113 | 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 | |
160 | brMb2upTHa+kjktjdO96/BgSnKYqmNmPB/qB | |
161 | =ivA/ | |
162 | 162 | -----END PGP PRIVATE KEY BLOCK----- |
163 | 163 | """ |
164 | 164 | |
196 | 196 | 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 | |
247 | =kOKh | |
199 | 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 | |
247 | =9zU5 | |
248 | 248 | -----END PGP PRIVATE KEY BLOCK----- |
249 | 249 | """ |
250 | 250 |
20 | 20 | |
21 | 21 | """Tests for the repository.""" |
22 | 22 | |
23 | import glob | |
23 | 24 | import locale |
24 | 25 | import os |
26 | import shutil | |
25 | 27 | import stat |
26 | import shutil | |
27 | 28 | import sys |
28 | 29 | import tempfile |
29 | 30 | import warnings |
78 | 79 | with repo.get_named_file("config") as f: |
79 | 80 | config_text = f.read() |
80 | 81 | self.assertTrue(barestr in config_text, "%r" % config_text) |
82 | ||
83 | if isinstance(repo, Repo): | |
84 | expected_mode = '0o100644' if expect_filemode else '0o100666' | |
85 | expected = { | |
86 | 'HEAD': expected_mode, | |
87 | 'config': expected_mode, | |
88 | 'description': expected_mode, | |
89 | } | |
90 | actual = { | |
91 | f[len(repo._controldir) + 1:]: oct(os.stat(f).st_mode) | |
92 | for f in glob.glob(os.path.join(repo._controldir, '*')) | |
93 | if os.path.isfile(f) | |
94 | } | |
95 | ||
96 | self.assertEqual(expected, actual) | |
81 | 97 | |
82 | 98 | def test_create_memory(self): |
83 | 99 | repo = MemoryRepo.init_bare([], {}) |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: dulwich |
2 | Version: 0.20.23 | |
2 | Version: 0.20.25 | |
3 | 3 | Summary: Python Git Library |
4 | 4 | Home-page: https://www.dulwich.io/ |
5 | 5 | Author: Jelmer Vernooij |
8 | 8 | Project-URL: Bug Tracker, https://github.com/dulwich/dulwich/issues |
9 | 9 | Project-URL: Repository, https://www.dulwich.io/code/ |
10 | 10 | Project-URL: GitHub, https://github.com/dulwich/dulwich |
11 | Description: Dulwich | |
12 | ======= | |
13 | ||
14 | This is the Dulwich project. | |
15 | ||
16 | It aims to provide an interface to git repos (both local and remote) that | |
17 | doesn't call out to git directly but instead uses pure Python. | |
18 | ||
19 | **Main website**: <https://www.dulwich.io/> | |
20 | ||
21 | **License**: Apache License, version 2 or GNU General Public License, version 2 or later. | |
22 | ||
23 | The project is named after the part of London that Mr. and Mrs. Git live in | |
24 | in the particular Monty Python sketch. | |
25 | ||
26 | Installation | |
27 | ------------ | |
28 | ||
29 | By default, Dulwich' setup.py will attempt to build and install the optional C | |
30 | extensions. The reason for this is that they significantly improve the performance | |
31 | since some low-level operations that are executed often are much slower in CPython. | |
32 | ||
33 | If you don't want to install the C bindings, specify the --pure argument to setup.py:: | |
34 | ||
35 | $ python setup.py --pure install | |
36 | ||
37 | or if you are installing from pip:: | |
38 | ||
39 | $ pip install dulwich --global-option="--pure" | |
40 | ||
41 | Note that you can also specify --global-option in a | |
42 | `requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_ | |
43 | file, e.g. like this:: | |
44 | ||
45 | dulwich --global-option=--pure | |
46 | ||
47 | Getting started | |
48 | --------------- | |
49 | ||
50 | Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). | |
51 | ||
52 | For example, to use the lower level API to access the commit message of the | |
53 | last commit:: | |
54 | ||
55 | >>> from dulwich.repo import Repo | |
56 | >>> r = Repo('.') | |
57 | >>> r.head() | |
58 | '57fbe010446356833a6ad1600059d80b1e731e15' | |
59 | >>> c = r[r.head()] | |
60 | >>> c | |
61 | <Commit 015fc1267258458901a94d228e39f0a378370466> | |
62 | >>> c.message | |
63 | 'Add note about encoding.\n' | |
64 | ||
65 | And to print it using porcelain:: | |
66 | ||
67 | >>> from dulwich import porcelain | |
68 | >>> porcelain.log('.', max_entries=1) | |
69 | -------------------------------------------------- | |
70 | commit: 57fbe010446356833a6ad1600059d80b1e731e15 | |
71 | Author: Jelmer Vernooij <jelmer@jelmer.uk> | |
72 | Date: Sat Apr 29 2017 23:57:34 +0000 | |
73 | ||
74 | Add note about encoding. | |
75 | ||
76 | Further documentation | |
77 | --------------------- | |
78 | ||
79 | The dulwich documentation can be found in docs/ and built by running ``make | |
80 | doc``. It can also be found `on the web <https://www.dulwich.io/docs/>`_. | |
81 | ||
82 | Help | |
83 | ---- | |
84 | ||
85 | There is a *#dulwich* IRC channel on the `OFTC <https://www.oftc.net/>`_, and | |
86 | `dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_ | |
87 | and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_ | |
88 | mailing lists. | |
89 | ||
90 | Contributing | |
91 | ------------ | |
92 | ||
93 | For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_. | |
94 | ||
95 | If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_ | |
96 | file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_. | |
97 | ||
98 | Supported versions of Python | |
99 | ---------------------------- | |
100 | ||
101 | At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and | |
102 | Pypy. | |
103 | ||
104 | The latest release series to support Python 2.x was the 0.19 series. See | |
105 | the 0.19 branch in the Dulwich git repository. | |
106 | ||
107 | 11 | Keywords: git vcs |
108 | 12 | Platform: UNKNOWN |
109 | 13 | Classifier: Development Status :: 4 - Beta |
123 | 27 | Provides-Extra: https |
124 | 28 | Provides-Extra: pgp |
125 | 29 | Provides-Extra: watch |
30 | License-File: COPYING | |
31 | License-File: AUTHORS | |
32 | ||
33 | Dulwich | |
34 | ======= | |
35 | ||
36 | This is the Dulwich project. | |
37 | ||
38 | It aims to provide an interface to git repos (both local and remote) that | |
39 | doesn't call out to git directly but instead uses pure Python. | |
40 | ||
41 | **Main website**: <https://www.dulwich.io/> | |
42 | ||
43 | **License**: Apache License, version 2 or GNU General Public License, version 2 or later. | |
44 | ||
45 | The project is named after the part of London that Mr. and Mrs. Git live in | |
46 | in the particular Monty Python sketch. | |
47 | ||
48 | Installation | |
49 | ------------ | |
50 | ||
51 | By default, Dulwich' setup.py will attempt to build and install the optional C | |
52 | extensions. The reason for this is that they significantly improve the performance | |
53 | since some low-level operations that are executed often are much slower in CPython. | |
54 | ||
55 | If you don't want to install the C bindings, specify the --pure argument to setup.py:: | |
56 | ||
57 | $ python setup.py --pure install | |
58 | ||
59 | or if you are installing from pip:: | |
60 | ||
61 | $ pip install dulwich --global-option="--pure" | |
62 | ||
63 | Note that you can also specify --global-option in a | |
64 | `requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_ | |
65 | file, e.g. like this:: | |
66 | ||
67 | dulwich --global-option=--pure | |
68 | ||
69 | Getting started | |
70 | --------------- | |
71 | ||
72 | Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain"). | |
73 | ||
74 | For example, to use the lower level API to access the commit message of the | |
75 | last commit:: | |
76 | ||
77 | >>> from dulwich.repo import Repo | |
78 | >>> r = Repo('.') | |
79 | >>> r.head() | |
80 | '57fbe010446356833a6ad1600059d80b1e731e15' | |
81 | >>> c = r[r.head()] | |
82 | >>> c | |
83 | <Commit 015fc1267258458901a94d228e39f0a378370466> | |
84 | >>> c.message | |
85 | 'Add note about encoding.\n' | |
86 | ||
87 | And to print it using porcelain:: | |
88 | ||
89 | >>> from dulwich import porcelain | |
90 | >>> porcelain.log('.', max_entries=1) | |
91 | -------------------------------------------------- | |
92 | commit: 57fbe010446356833a6ad1600059d80b1e731e15 | |
93 | Author: Jelmer Vernooij <jelmer@jelmer.uk> | |
94 | Date: Sat Apr 29 2017 23:57:34 +0000 | |
95 | ||
96 | Add note about encoding. | |
97 | ||
98 | Further documentation | |
99 | --------------------- | |
100 | ||
101 | The dulwich documentation can be found in docs/ and built by running ``make | |
102 | doc``. It can also be found `on the web <https://www.dulwich.io/docs/>`_. | |
103 | ||
104 | Help | |
105 | ---- | |
106 | ||
107 | There is a *#dulwich* IRC channel on the `OFTC <https://www.oftc.net/>`_, and | |
108 | `dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_ | |
109 | and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_ | |
110 | mailing lists. | |
111 | ||
112 | Contributing | |
113 | ------------ | |
114 | ||
115 | For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_. | |
116 | ||
117 | If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_ | |
118 | file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_. | |
119 | ||
120 | Supported versions of Python | |
121 | ---------------------------- | |
122 | ||
123 | At the moment, Dulwich supports (and is tested on) CPython 3.5 and later and | |
124 | Pypy. | |
125 | ||
126 | The latest release series to support Python 2.x was the 0.19 series. See | |
127 | the 0.19 branch in the Dulwich git repository. | |
128 | ||
129 |
14 | 14 | README.swift.rst |
15 | 15 | SECURITY.md |
16 | 16 | TODO |
17 | build.cmd | |
18 | 17 | dulwich.cfg |
19 | 18 | releaser.conf |
20 | 19 | requirements.txt |
50 | 49 | docs/tutorial/repo.txt |
51 | 50 | docs/tutorial/tag.txt |
52 | 51 | dulwich/__init__.py |
52 | dulwich/__main__.py | |
53 | 53 | dulwich/_diff_tree.c |
54 | 54 | dulwich/_objects.c |
55 | 55 | dulwich/_pack.c |
92 | 92 | dulwich.egg-info/SOURCES.txt |
93 | 93 | dulwich.egg-info/dependency_links.txt |
94 | 94 | dulwich.egg-info/entry_points.txt |
95 | dulwich.egg-info/not-zip-safe | |
95 | 96 | dulwich.egg-info/requires.txt |
96 | 97 | dulwich.egg-info/top_level.txt |
97 | 98 | dulwich/cloud/__init__.py |
0 | # See https://github.com/jelmer/releaser | |
0 | 1 | news_file: "NEWS" |
1 | 2 | timeout_days: 5 |
2 | 3 | tag_name: "dulwich-$VERSION" |
22 | 22 | 'For 2.7 support, please install a version prior to 0.20') |
23 | 23 | |
24 | 24 | |
25 | dulwich_version_string = '0.20.23' | |
25 | dulwich_version_string = '0.20.25' | |
26 | 26 | |
27 | 27 | |
28 | 28 | class DulwichDistribution(Distribution): |
115 | 115 | package_data={'': ['../docs/tutorial/*.txt', 'py.typed']}, |
116 | 116 | scripts=scripts, |
117 | 117 | ext_modules=ext_modules, |
118 | zip_safe=False, | |
118 | 119 | distclass=DulwichDistribution, |
119 | 120 | classifiers=[ |
120 | 121 | 'Development Status :: 4 - Beta', |