Explorar o código

Import upstream version 0.20.15, md5 fe617f3ad2bcd30f156bcbded7ff5ec3

Jelmer Vernooij %!s(int64=4) %!d(string=hai) anos
pai
achega
19354d687b

+ 13 - 5
.github/workflows/pythonpublish.yml

@@ -20,6 +20,8 @@ jobs:
         exclude:
           - os: windows-latest
             python-version: 3.5
+          - os: macos-latest
+            python-version: 3.5
       fail-fast: false
 
     steps:
@@ -38,23 +40,29 @@ jobs:
     - name: Build
       run: |
         python setup.py sdist bdist_wheel
-        mkdir wheelhouse
-        mv dist/*.whl wheelhouse
       if: "matrix.os != 'ubuntu-latest'"
     - name: Build and publish (Linux)
-      uses: RalfG/python-wheels-manylinux-build@v0.2.2
+      uses: RalfG/python-wheels-manylinux-build@v0.3.1
+      with:
+        python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39'
+      env:
+        # Temporary fix for LD_LIBRARY_PATH issue. See
+        # https://github.com/RalfG/python-wheels-manylinux-build/issues/26
+        LD_LIBRARY_PATH: /usr/local/lib:${{ env.LD_LIBRARY_PATH }}
       if: "matrix.os == 'ubuntu-latest'"
     - name: Publish (Linux)
       env:
         TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
         TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
       run: |
-        twine upload wheelhouse/*manylinux*
+        # Only include *manylinux* wheels; the other wheels files are built but
+        # rejected by pip.
+        twine upload dist/*manylinux*.whl
       if: "matrix.os == 'ubuntu-latest'"
     - name: Publish
       env:
         TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
         TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
       run: |
-        twine upload wheelhouse/*
+        twine upload dist/*.whl
       if: "matrix.os != 'ubuntu-latest'"

+ 1 - 0
MANIFEST.in

@@ -8,6 +8,7 @@ include CONTRIBUTING.rst
 include TODO
 include setup.cfg
 include dulwich/stdint.h
+include dulwich/py.typed
 recursive-include docs conf.py *.txt Makefile make.bat
 recursive-include examples *.py
 graft dulwich/tests/data

+ 31 - 0
NEWS

@@ -1,3 +1,34 @@
+0.20.15	2020-12-23
+
+ * Add some functions for parsing and writing bundles.
+   (Jelmer Vernooij)
+
+ * Add ``no_verify`` flag to ``porcelain.commit`` and ``Repo.do_commit``.
+   (Peter Rowlands)
+
+ * Remove dependency on external mock module.
+   (Matěj Cepl, #820)
+
+0.20.14	2020-11-26
+
+ * Fix some stash functions on Python 3. (Peter Rowlands)
+
+ * Fix handling of relative paths in alternates files on Python 3.
+   (Georges Racinet)
+
+0.20.13	2020-11-22
+
+ * Add py.typed to allow type checking. (David Caro)
+
+ * Add tests demonstrating a bug in the walker code.
+   (Doug Hellman)
+
+0.20.11	2020-10-30
+
+ * Fix wheels build on Linux. (Ruslan Kuprieiev)
+
+ * Enable wheels build for Python 3.9 on Linux. (Jelmer Vernooij)
+
 0.20.8	2020-10-29
 
  * Build wheels on Mac OS X / Windows for Python 3.9.

+ 1 - 1
PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.20.8
+Version: 0.20.15
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij

+ 1 - 1
dulwich.egg-info/PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.20.8
+Version: 0.20.15
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij

+ 3 - 0
dulwich.egg-info/SOURCES.txt

@@ -52,6 +52,7 @@ dulwich/_diff_tree.c
 dulwich/_objects.c
 dulwich/_pack.c
 dulwich/archive.py
+dulwich/bundle.py
 dulwich/cli.py
 dulwich/client.py
 dulwich/config.py
@@ -76,6 +77,7 @@ dulwich/pack.py
 dulwich/patch.py
 dulwich/porcelain.py
 dulwich/protocol.py
+dulwich/py.typed
 dulwich/reflog.py
 dulwich/refs.py
 dulwich/repo.py
@@ -102,6 +104,7 @@ dulwich/contrib/test_swift_smoke.py
 dulwich/tests/__init__.py
 dulwich/tests/test_archive.py
 dulwich/tests/test_blackbox.py
+dulwich/tests/test_bundle.py
 dulwich/tests/test_client.py
 dulwich/tests/test_config.py
 dulwich/tests/test_diff_tree.py

+ 1 - 1
dulwich.egg-info/requires.txt

@@ -1,5 +1,5 @@
-urllib3>=1.24.1
 certifi
+urllib3>=1.24.1
 
 [fastimport]
 fastimport

+ 1 - 1
dulwich/__init__.py

@@ -22,4 +22,4 @@
 
 """Python implementation of the Git file formats and protocols."""
 
-__version__ = (0, 20, 8)
+__version__ = (0, 20, 15)

+ 123 - 0
dulwich/bundle.py

@@ -0,0 +1,123 @@
+# bundle.py -- Bundle format support
+# Copyright (C) 2020 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# 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
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
+"""Bundle format support.
+"""
+
+from typing import Dict, List, Tuple, Optional, Union, Sequence
+from .pack import PackData, write_pack_data
+
+
+class Bundle(object):
+
+    version = None  # type: Optional[int]
+
+    capabilities = {}  # type: Dict[str, str]
+    prerequisites = []  # type: List[Tuple[bytes, str]]
+    references = {}  # type: Dict[str, bytes]
+    pack_data = []  # type: Union[PackData, Sequence[bytes]]
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return False
+        if self.version != other.version:
+            return False
+        if self.capabilities != other.capabilities:
+            return False
+        if self.prerequisites != other.prerequisites:
+            return False
+        if self.references != other.references:
+            return False
+        if self.pack_data != other.pack_data:
+            return False
+        return True
+
+
+def _read_bundle(f, version):
+    capabilities = {}
+    prerequisites = []
+    references = {}
+    line = f.readline()
+    if version >= 3:
+        while line.startswith(b'@'):
+            line = line[1:].rstrip(b'\n')
+            try:
+                key, value = line.split(b'=', 1)
+            except ValueError:
+                key = line
+                value = None
+            else:
+                value = value.decode('utf-8')
+            capabilities[key.decode('utf-8')] = value
+            line = f.readline()
+    while line.startswith(b'-'):
+        (obj_id, comment) = line[1:].rstrip(b'\n').split(b' ', 1)
+        prerequisites.append((obj_id, comment.decode('utf-8')))
+        line = f.readline()
+    while line != b'\n':
+        (obj_id, ref) = line.rstrip(b'\n').split(b' ', 1)
+        references[ref] = obj_id
+        line = f.readline()
+    pack_data = PackData.from_file(f)
+    ret = Bundle()
+    ret.references = references
+    ret.capabilities = capabilities
+    ret.prerequisites = prerequisites
+    ret.pack_data = pack_data
+    ret.version = version
+    return ret
+
+
+def read_bundle(f):
+    """Read a bundle file."""
+    firstline = f.readline()
+    if firstline == b'# v2 git bundle\n':
+        return _read_bundle(f, 2)
+    if firstline == b'# v3 git bundle\n':
+        return _read_bundle(f, 3)
+    raise AssertionError(
+        'unsupported bundle format header: %r' % firstline)
+
+
+def write_bundle(f, bundle):
+    version = bundle.version
+    if version is None:
+        if bundle.capabilities:
+            version = 3
+        else:
+            version = 2
+    if version == 2:
+        f.write(b'# v2 git bundle\n')
+    elif version == 3:
+        f.write(b'# v3 git bundle\n')
+    else:
+        raise AssertionError('unknown version %d' % version)
+    if version == 3:
+        for key, value in bundle.capabilities.items():
+            f.write(b'@' + key.encode('utf-8'))
+            if value is not None:
+                f.write(b'=' + value.encode('utf-8'))
+            f.write(b'\n')
+    for (obj_id, comment) in bundle.prerequisites:
+        f.write(b'-%s %s\n' % (obj_id, comment.encode('utf-8')))
+    for ref, obj_id in bundle.references.items():
+        f.write(b'%s %s\n' % (obj_id, ref))
+    f.write(b'\n')
+    write_pack_data(f, len(bundle.pack_data), iter(bundle.pack_data))

+ 3 - 0
dulwich/cli.py

@@ -710,6 +710,9 @@ commands = {
 
 
 def main(argv=None):
+    if argv is None:
+        argv = sys.argv
+
     if len(argv) < 1:
         print("Usage: dulwich <%s> [OPTIONS...]" % ("|".join(commands.keys())))
         return 1

+ 5 - 4
dulwich/client.py

@@ -123,9 +123,10 @@ class InvalidWants(Exception):
 class HTTPUnauthorized(Exception):
     """Raised when authentication fails."""
 
-    def __init__(self, www_authenticate):
+    def __init__(self, www_authenticate, url):
         Exception.__init__(self, "No valid credentials provided")
         self.www_authenticate = www_authenticate
+        self.url = url
 
 
 def _fileno_can_read(fileno):
@@ -1637,9 +1638,9 @@ class HttpGitClient(GitClient):
 
         if resp.status == 404:
             raise NotGitRepository()
-        elif resp.status == 401:
-            raise HTTPUnauthorized(resp.getheader('WWW-Authenticate'))
-        elif resp.status != 200:
+        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))
 

+ 1 - 1
dulwich/contrib/test_swift.py

@@ -58,7 +58,7 @@ except ImportError:
     missing_libs.append("geventhttpclient")
 
 try:
-    from mock import patch
+    from unittest.mock import patch
 except ImportError:
     missing_libs.append("mock")
 

+ 3 - 2
dulwich/object_store.py

@@ -577,12 +577,13 @@ class DiskObjectStore(PackBasedObjectStore):
         with f:
             for line in f.readlines():
                 line = line.rstrip(b"\n")
-                if line[0] == b"#":
+                if line.startswith(b"#"):
                     continue
                 if os.path.isabs(line):
                     yield os.fsdecode(line)
                 else:
-                    yield os.fsdecode(os.path.join(self.path, line))
+                    yield os.fsdecode(os.path.join(os.fsencode(self.path),
+                                                   line))
 
     def add_alternate_path(self, path):
         """Add an alternate path to this object store.

+ 13 - 1
dulwich/pack.py

@@ -1056,7 +1056,7 @@ class PackData(object):
         return self._filename
 
     @classmethod
-    def from_file(cls, file, size):
+    def from_file(cls, file, size=None):
         return cls(str(file), file=file, size=size)
 
     @classmethod
@@ -1072,6 +1072,18 @@ class PackData(object):
     def __exit__(self, exc_type, exc_val, exc_tb):
         self.close()
 
+    def __eq__(self, other):
+        if isinstance(other, PackData):
+            return self.get_stored_checksum() == other.get_stored_checksum()
+        if isinstance(other, list):
+            if len(self) != len(other):
+                return False
+            for o1, o2 in zip(self.iterobjects(), other):
+                if o1 != o2:
+                    return False
+            return True
+        return False
+
     def _get_size(self):
         if self._size is not None:
             return self._size

+ 4 - 2
dulwich/porcelain.py

@@ -323,7 +323,8 @@ def symbolic_ref(repo, ref_name, force=False):
         repo_obj.refs.set_symbolic_ref(b'HEAD', ref_path)
 
 
-def commit(repo=".", message=None, author=None, committer=None, encoding=None):
+def commit(repo=".", message=None, author=None, committer=None, encoding=None,
+           no_verify=False):
     """Create a new commit.
 
     Args:
@@ -331,6 +332,7 @@ def commit(repo=".", message=None, author=None, committer=None, encoding=None):
       message: Optional commit message
       author: Optional author name and email
       committer: Optional committer name and email
+      no_verify: Skip pre-commit and commit-msg hooks
     Returns: SHA1 of the new commit
     """
     # FIXME: Support --all argument
@@ -344,7 +346,7 @@ def commit(repo=".", message=None, author=None, committer=None, encoding=None):
     with open_repo_closing(repo) as r:
         return r.do_commit(
                 message=message, author=author, committer=committer,
-                encoding=encoding)
+                encoding=encoding, no_verify=no_verify)
 
 
 def commit_tree(repo, tree, message=None, author=None, committer=None):

+ 0 - 0
dulwich/py.typed


+ 9 - 4
dulwich/repo.py

@@ -822,7 +822,7 @@ class BaseRepo(object):
                   author=None, commit_timestamp=None,
                   commit_timezone=None, author_timestamp=None,
                   author_timezone=None, tree=None, encoding=None,
-                  ref=b'HEAD', merge_heads=None):
+                  ref=b'HEAD', merge_heads=None, no_verify=False):
         """Create a new commit.
 
         If not specified, `committer` and `author` default to
@@ -844,6 +844,7 @@ class BaseRepo(object):
           encoding: Encoding
           ref: Optional ref to commit to (defaults to current branch)
           merge_heads: Merge heads (defaults to .git/MERGE_HEADS)
+          no_verify: Skip pre-commit and commit-msg hooks
 
         Returns:
           New commit SHA1
@@ -859,7 +860,8 @@ class BaseRepo(object):
             c.tree = tree
 
         try:
-            self.hooks['pre-commit'].execute()
+            if not no_verify:
+                self.hooks['pre-commit'].execute()
         except HookError as e:
             raise CommitError(e)
         except KeyError:  # no hook defined, silent fallthrough
@@ -903,9 +905,12 @@ class BaseRepo(object):
             raise ValueError("No commit message specified")
 
         try:
-            c.message = self.hooks['commit-msg'].execute(message)
-            if c.message is None:
+            if no_verify:
                 c.message = message
+            else:
+                c.message = self.hooks['commit-msg'].execute(message)
+                if c.message is None:
+                    c.message = message
         except HookError as e:
             raise CommitError(e)
         except KeyError:  # no hook defined, message not modified

+ 4 - 4
dulwich/stash.py

@@ -47,7 +47,7 @@ class Stash(object):
 
     def stashes(self):
         reflog_path = os.path.join(
-            self._repo.commondir(), 'logs', self._ref)
+            self._repo.commondir(), 'logs', os.fsdecode(self._ref))
         try:
             with GitFile(reflog_path, 'rb') as f:
                 return reversed(list(read_reflog(f)))
@@ -93,7 +93,7 @@ class Stash(object):
         stash_tree_id = commit_tree(
                 self._repo.object_store,
                 iter_fresh_objects(
-                    index, self._repo.path,
+                    index, os.fsencode(self._repo.path),
                     object_store=self._repo.object_store))
 
         if message is None:
@@ -111,7 +111,7 @@ class Stash(object):
         return cid
 
     def __getitem__(self, index):
-        return self._stashes()[index]
+        return list(self.stashes())[index]
 
     def __len__(self):
-        return len(self._stashes())
+        return len(list(self.stashes()))

+ 1 - 0
dulwich/tests/__init__.py

@@ -102,6 +102,7 @@ def self_test_suite():
     names = [
         'archive',
         'blackbox',
+        'bundle',
         'client',
         'config',
         'diff_tree',

+ 1 - 1
dulwich/tests/test_archive.py

@@ -41,7 +41,7 @@ from dulwich.tests.utils import (
     )
 
 try:
-    from mock import patch
+    from unittest.mock import patch
 except ImportError:
     patch = None   # type: ignore
 

+ 52 - 0
dulwich/tests/test_bundle.py

@@ -0,0 +1,52 @@
+# test_bundle.py -- tests for bundle
+# Copyright (C) 2020 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# 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
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
+"""Tests for bundle support."""
+
+import os
+import tempfile
+
+from dulwich.tests import (
+    TestCase,
+    )
+
+from dulwich.bundle import (
+    Bundle,
+    read_bundle,
+    write_bundle,
+    )
+
+
+class BundleTests(TestCase):
+
+    def test_roundtrip_bundle(self):
+        origbundle = Bundle()
+        origbundle.version = 3
+        origbundle.capabilities = {'foo': None}
+        origbundle.references = {b'refs/heads/master': b'ab' * 20}
+        origbundle.prerequisites = [(b'cc' * 20, 'comment')]
+        with tempfile.TemporaryDirectory() as td:
+            with open(os.path.join(td, 'foo'), 'wb') as f:
+                write_bundle(f, origbundle)
+
+            with open(os.path.join(td, 'foo'), 'rb') as f:
+                newbundle = read_bundle(f)
+
+                self.assertEqual(origbundle, newbundle)

+ 18 - 0
dulwich/tests/test_object_store.py

@@ -362,6 +362,24 @@ class DiskObjectStoreTests(PackBasedObjectStoreTests, TestCase):
         self.assertIn(b2.id, store)
         self.assertEqual(b2, store[b2.id])
 
+    def test_read_alternate_paths(self):
+        store = DiskObjectStore(self.store_dir)
+
+        abs_path = os.path.abspath(os.path.normpath('/abspath'))
+        # ensures in particular existence of the alternates file
+        store.add_alternate_path(abs_path)
+        self.assertEqual(set(store._read_alternate_paths()), {abs_path})
+
+        store.add_alternate_path("relative-path")
+        self.assertIn(os.path.join(store.path, "relative-path"),
+                      set(store._read_alternate_paths()))
+
+        # arguably, add_alternate_path() could strip comments.
+        # Meanwhile it's more convenient to use it than to import INFODIR
+        store.add_alternate_path("# comment")
+        for alt_path in store._read_alternate_paths():
+            self.assertNotIn("#", alt_path)
+
     def test_corrupted_object_raise_exception(self):
         """Corrupted sha1 disk file should raise specific exception"""
         self.store.add_object(testobject)

+ 48 - 0
dulwich/tests/test_porcelain.py

@@ -24,12 +24,14 @@ from io import BytesIO, StringIO
 import os
 import re
 import shutil
+import stat
 import tarfile
 import tempfile
 import time
 
 from dulwich import porcelain
 from dulwich.diff_tree import tree_changes
+from dulwich.errors import CommitError
 from dulwich.objects import (
     Blob,
     Tag,
@@ -125,6 +127,52 @@ class CommitTests(PorcelainTestCase):
         self.assertTrue(isinstance(sha, bytes))
         self.assertEqual(len(sha), 40)
 
+    def test_no_verify(self):
+        if os.name != 'posix':
+            self.skipTest('shell hook tests requires POSIX shell')
+        self.assertTrue(os.path.exists('/bin/sh'))
+
+        hooks_dir = os.path.join(self.repo.controldir(), "hooks")
+        os.makedirs(hooks_dir, exist_ok=True)
+        self.addCleanup(shutil.rmtree, hooks_dir)
+
+        c1, c2, c3 = build_commit_graph(
+                self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
+
+        hook_fail = "#!/bin/sh\nexit 1"
+
+        # hooks are executed in pre-commit, commit-msg order
+        # test commit-msg failure first, then pre-commit failure, then
+        # no_verify to skip both hooks
+        commit_msg = os.path.join(hooks_dir, "commit-msg")
+        with open(commit_msg, 'w') as f:
+            f.write(hook_fail)
+        os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+        with self.assertRaises(CommitError):
+            porcelain.commit(
+                    self.repo.path, message="Some message",
+                    author="Joe <joe@example.com>",
+                    committer="Bob <bob@example.com>")
+
+        pre_commit = os.path.join(hooks_dir, "pre-commit")
+        with open(pre_commit, 'w') as f:
+            f.write(hook_fail)
+        os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+        with self.assertRaises(CommitError):
+            porcelain.commit(
+                    self.repo.path, message="Some message",
+                    author="Joe <joe@example.com>",
+                    committer="Bob <bob@example.com>")
+
+        sha = porcelain.commit(
+                self.repo.path, message="Some message",
+                author="Joe <joe@example.com>",
+                committer="Bob <bob@example.com>", no_verify=True)
+        self.assertTrue(isinstance(sha, bytes))
+        self.assertEqual(len(sha), 40)
+
 
 class CleanTests(PorcelainTestCase):
 

+ 29 - 0
dulwich/tests/test_walk.py

@@ -23,6 +23,7 @@
 from itertools import (
     permutations,
     )
+from unittest import expectedFailure
 
 from dulwich.diff_tree import (
     CHANGE_MODIFY,
@@ -151,6 +152,34 @@ class WalkerTest(TestCase):
         self.assertWalkYields([c4, c3], [c4.id], exclude=[c2.id])
         self.assertWalkYields([c4, c2], [c4.id], exclude=[c3.id])
 
+    def test_merge_of_new_branch_from_old_base(self):
+        # The commit on the branch was made at a time after any of the
+        # commits on master, but the branch was from an older commit.
+        # See also test_merge_of_old_branch
+        self.maxDiff = None
+        c1, c2, c3, c4, c5 = self.make_commits(
+            [[1], [2, 1], [3, 2], [4, 1], [5, 3, 4]],
+            times=[1, 2, 3, 4, 5],
+        )
+        self.assertWalkYields([c5, c4, c3, c2, c1], [c5.id])
+        self.assertWalkYields([c3, c2, c1], [c3.id])
+        self.assertWalkYields([c2, c1], [c2.id])
+
+    @expectedFailure
+    def test_merge_of_old_branch(self):
+        # The commit on the branch was made at a time before any of
+        # the commits on master, but it was merged into master after
+        # those commits.
+        # See also test_merge_of_new_branch_from_old_base
+        self.maxDiff = None
+        c1, c2, c3, c4, c5 = self.make_commits(
+            [[1], [2, 1], [3, 2], [4, 1], [5, 3, 4]],
+            times=[1, 3, 4, 2, 5],
+        )
+        self.assertWalkYields([c5, c4, c3, c2, c1], [c5.id])
+        self.assertWalkYields([c3, c2, c1], [c3.id])
+        self.assertWalkYields([c2, c1], [c2.id])
+
     def test_reverse(self):
         c1, c2, c3 = self.make_linear_commits(3)
         self.assertWalkYields([c1, c2, c3], [c3.id], reverse=True)

+ 24 - 8
examples/clone.py

@@ -1,18 +1,34 @@
-#!/usr/bin/python
-# This trivial script demonstrates how to clone a remote repository.
-#
-# Example usage:
-#  python examples/clone.py git://github.com/jelmer/dulwich dulwich-clone
+"""Clone.
+
+This trivial script demonstrates how to clone or lock a remote repository.
+
+Example usage:
+  1. python examples/clone.py git://github.com/jelmer/dulwich
+  2. python examples/clone.py git://github.com/jelmer/dulwich.git dulwich
+"""
+
 
 import sys
+
+from os.path import basename
+
 from getopt import getopt
+
 from dulwich import porcelain
 
-opts, args = getopt(sys.argv, "", [])
-opts = dict(opts)
+
+_, args = getopt(sys.argv, "", [])
+
 
 if len(args) < 2:
     print("usage: %s host:path path" % (args[0], ))
     sys.exit(1)
 
-porcelain.clone(args[1], args[2])
+elif len(args) < 3:
+    target_path = basename(args[1].split(":")[-1])
+    if target_path[-4:] == ".git":
+        target_path = target_path[:-4]
+else:
+    target_path = args[2]
+
+porcelain.clone(args[1], target_path)

+ 3 - 3
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.8'
+dulwich_version_string = '0.20.15'
 
 
 class DulwichDistribution(Distribution):
@@ -62,7 +62,7 @@ tests_require = ['fastimport']
 
 if '__pypy__' not in sys.modules and not sys.platform == 'win32':
     tests_require.extend([
-        'gevent', 'geventhttpclient', 'mock', 'setuptools>=17.1'])
+        'gevent', 'geventhttpclient', 'setuptools>=17.1'])
 
 
 ext_modules = [
@@ -113,7 +113,7 @@ setup(name='dulwich',
       keywords="git vcs",
       packages=['dulwich', 'dulwich.tests', 'dulwich.tests.compat',
                 'dulwich.contrib'],
-      package_data={'': ['../docs/tutorial/*.txt']},
+      package_data={'': ['../docs/tutorial/*.txt', 'py.typed']},
       scripts=scripts,
       ext_modules=ext_modules,
       distclass=DulwichDistribution,