Commit Diff


commit - 6695633ce96f26d74642709e88ed7e5c3b5861e5
commit + 5e68456bd9c1bca441fd8cb99d2a24f0d6441b7d
blob - bde4be383f0965f03cce87d65ca9570aced4cdad
blob + 4273eb21cd51bec40e1b41bdf655033190bbf969
--- .flake8
+++ .flake8
@@ -1,5 +1,5 @@
 [flake8]
-extend-ignore = E203, E266, E501, W293, W291
+extend-ignore = E203, E266, E501, W293, W291, W503
 max-line-length = 88
 max-complexity = 18
 select = B,C,E,F,W,T4,B9
blob - 51594a39fc281388e601a07daaa5a7b36fbd5820
blob + 7342c577fe317795f76fe5f0045d4288dbbb048e
--- .github/workflows/pythonpackage.yml
+++ .github/workflows/pythonpackage.yml
@@ -4,16 +4,16 @@ on:
   push:
   pull_request:
   schedule:
-    - cron: '0 6 * * *'  # Daily 6AM UTC build
+    - cron: "0 6 * * *" # Daily 6AM UTC build
 
 jobs:
   build:
-
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", pypy3]
+        python-version:
+          ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-rc - 3.11", pypy3]
         exclude:
           # sqlite3 exit handling seems to get in the way
           - os: macos-latest
@@ -24,41 +24,38 @@ jobs:
       fail-fast: false
 
     steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install native dependencies (Ubuntu)
-      run: sudo apt-get update && sudo apt-get install -y libgpgme-dev libgpg-error-dev
-      if: "matrix.os == 'ubuntu-latest'"
-    - name: Install native dependencies (MacOS)
-      run: brew install swig gpgme
-      if: "matrix.os == 'macos-latest'"
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install -U pip coverage codecov flake8 fastimport paramiko
-    - name: Install gpg on supported platforms
-      run: pip install -U gpg
-      if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'"
-    - name: Install mypy
-      run: |
-        pip install -U mypy types-paramiko types-certifi types-requests
-      if: "matrix.python-version != 'pypy3'"
-    - name: Style checks
-      run: |
-        python -m flake8
-    - name: Typing checks
-      run: |
-        python -m mypy dulwich
-      if: "matrix.python-version != 'pypy3'"
-    - name: Build
-      run: |
-        python setup.py build_ext -i
-    - name: Coverage test suite run
-      run: |
-        python -m coverage run -p -m unittest dulwich.tests.test_suite
-    - name: Upload coverage details
-      run: |
-        codecov
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install native dependencies (Ubuntu)
+        run: sudo apt-get update && sudo apt-get install -y libgpgme-dev libgpg-error-dev
+        if: "matrix.os == 'ubuntu-latest'"
+      - name: Install native dependencies (MacOS)
+        run: brew install swig gpgme
+        if: "matrix.os == 'macos-latest'"
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -U pip coverage flake8 fastimport paramiko urllib3
+      - name: Install gpg on supported platforms
+        run: pip install -U gpg
+        if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'"
+      - name: Install mypy
+        run: |
+          pip install -U mypy types-paramiko types-requests
+        if: "matrix.python-version != 'pypy3'"
+      - name: Style checks
+        run: |
+          python -m flake8
+      - name: Typing checks
+        run: |
+          python -m mypy dulwich
+        if: "matrix.python-version != 'pypy3'"
+      - name: Build
+        run: |
+          python setup.py build_ext -i
+      - name: Coverage test suite run
+        run: |
+          python -m coverage run -p -m unittest dulwich.tests.test_suite
blob - bc3dba462a887afe6041cfd1bf98a57675cc749b
blob + d13985a3fe4bd97871756d041be847aac09b442a
--- .github/workflows/pythonwheels.yml
+++ .github/workflows/pythonwheels.yml
@@ -4,79 +4,83 @@ on:
   push:
   pull_request:
   schedule:
-    - cron: '0 6 * * *'  # Daily 6AM UTC build
+    - cron: "0 6 * * *" # Daily 6AM UTC build
 
 jobs:
   build:
-
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
         os: [macos-latest, windows-latest]
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-rc - 3.11"]
+        architecture: ["x64", "x86"]
         include:
           - os: ubuntu-latest
-            python-version: '3.x'
+            python-version: "3.x"
           # path encoding
+        exclude:
+          - os: macos-latest
+            architecture: "x86"
       fail-fast: true
 
     steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install native dependencies (Ubuntu)
-      run: sudo apt-get update && sudo apt-get install -y libgpgme-dev libgpg-error-dev
-      if: "matrix.os == 'ubuntu-latest'"
-    - name: Install native dependencies (MacOS)
-      run: brew install swig gpgme
-      if: "matrix.os == 'macos-latest'"
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install setuptools wheel fastimport paramiko urllib3
-    - name: Install gpg on supported platforms
-      run: pip install -U gpg
-      if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'"
-    - name: Run test suite
-      run: |
-        python -m unittest dulwich.tests.test_suite
-    - name: Build
-      run: |
-        python setup.py sdist bdist_wheel
-      if: "matrix.os != 'ubuntu-latest'"
-    - uses: docker/setup-qemu-action@v1
-      name: Set up QEMU
-      if: "matrix.os == 'ubuntu-latest'"
-    - name: Build (Linux aarch64)
-      uses: RalfG/python-wheels-manylinux-build@v0.3.3-manylinux2014_aarch64
-      with:
-        python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310'
-      if: "matrix.os == 'ubuntu-latest'"
-    - name: Build (Linux)
-      uses: RalfG/python-wheels-manylinux-build@v0.3.1
-      with:
-        python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310'
-      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: Upload wheels (Linux)
-      uses: actions/upload-artifact@v2
-      # Only include *manylinux* wheels; the other wheels files are built but
-      # rejected by pip.
-      if: "matrix.os == 'ubuntu-latest'"
-      with:
-        name: dist
-        path: dist/*manylinux*.whl
-    - name: Upload wheels (non-Linux)
-      uses: actions/upload-artifact@v2
-      with:
-        name: dist
-        path: dist/*.whl
-      if: "matrix.os != 'ubuntu-latest'"
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+          architecture: ${{ matrix.architecture }}
+      - name: Install native dependencies (Ubuntu)
+        run: sudo apt-get update && sudo apt-get install -y libgpgme-dev libgpg-error-dev
+        if: "matrix.os == 'ubuntu-latest'"
+      - name: Install native dependencies (MacOS)
+        run: brew install swig gpgme
+        if: "matrix.os == 'macos-latest'"
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install setuptools wheel fastimport paramiko urllib3
+      - name: Install gpg on supported platforms
+        run: pip install -U gpg
+        if: "matrix.os != 'windows-latest' && matrix.python-version != 'pypy3'"
+      - name: Run test suite
+        run: |
+          python -m unittest dulwich.tests.test_suite
+      - name: Build
+        run: |
+          python setup.py sdist bdist_wheel
+        if: "matrix.os != 'ubuntu-latest'"
+      - uses: docker/setup-qemu-action@v1
+        name: Set up QEMU
+        if: "matrix.os == 'ubuntu-latest'"
+      - name: Build (Linux aarch64)
+        uses: RalfG/python-wheels-manylinux-build@v0.5.0-manylinux2014_aarch64
+        with:
+          python-versions: "cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311"
+        if: "matrix.os == 'ubuntu-latest'"
+      - name: Build (Linux)
+        uses: RalfG/python-wheels-manylinux-build@v0.5.0
+        with:
+          python-versions: "cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311"
+        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: Upload wheels (Linux)
+        uses: actions/upload-artifact@v2
+        # Only include *manylinux* wheels; the other wheels files are built but
+        # rejected by pip.
+        if: "matrix.os == 'ubuntu-latest'"
+        with:
+          name: dist
+          path: dist/*manylinux*.whl
+      - name: Upload wheels (non-Linux)
+        uses: actions/upload-artifact@v2
+        with:
+          name: dist
+          path: dist/*.whl
+        if: "matrix.os != 'ubuntu-latest'"
 
   publish:
     runs-on: ubuntu-latest
@@ -84,18 +88,18 @@ jobs:
     needs: build
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/dulwich-')
     steps:
-    - name: Set up Python
-      uses: actions/setup-python@v2
-      with:
-        python-version: "3.x"
-    - name: Install twine
-      run: |
-        python -m pip install --upgrade pip
-        pip install twine
-    - name: Download wheels
-      uses: actions/download-artifact@v2
-    - name: Publish wheels
-      env:
-        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
-        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
-      run: twine upload dist/*.whl
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: "3.x"
+      - name: Install twine
+        run: |
+          python -m pip install --upgrade pip
+          pip install twine
+      - name: Download wheels
+        uses: actions/download-artifact@v2
+      - name: Publish wheels
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        run: twine upload dist/*.whl
blob - dd4bb87f3abccf1d8f03aede67f5fed7c74e1c5c
blob + 8a64b18818ffddc5361f7470e595d0c94e8a5bdf
--- CODE_OF_CONDUCT.md
+++ CODE_OF_CONDUCT.md
@@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and ori
 Examples of behavior that contributes to creating a positive environment
 include:
 
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
 
 Examples of unacceptable behavior by participants include:
 
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
+- The use of sexualized language or imagery and unwelcome sexual attention or
+  advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+  professional setting
 
 ## Our Responsibilities
 
@@ -67,10 +67,11 @@ members of the project's leadership.
 
 ## Attribution
 
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.4, available at
+<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
 
 [homepage]: https://www.contributor-covenant.org
 
 For answers to common questions about this code of conduct, see
-https://www.contributor-covenant.org/faq
+<https://www.contributor-covenant.org/faq>
blob - 63d3dfa8fc48ef4dfbc5f4499d7d302a6d99bd24
blob + 66b91b804fd655b96ef62cf1836895bbe1e7cc41
--- Makefile
+++ Makefile
@@ -74,4 +74,4 @@ coverage-html: coverage
 .PHONY: apidocs
 
 apidocs:
-	pydoctor --docformat=google dulwich --project-url=https://www.dulwich.io/
+	pydoctor --intersphinx http://urllib3.readthedocs.org/en/latest/objects.inv --intersphinx http://docs.python.org/3/objects.inv --docformat=google dulwich --project-url=https://www.dulwich.io/
blob - 956ea765820765587c72d393dd007aa4fb95592a
blob + e9a4915216bd7eeacb6d4cb12ba218d4fccd1077
--- NEWS
+++ NEWS
@@ -1,3 +1,64 @@
+0.20.46	2022-09-06
+
+ * Apply insteadOf to rsync-style location strings
+   (previously it was just applied to URLs).
+   (Jelmer Vernooij, python-poetry/poetry#6329)
+
+ * Drop use of certifi, instead relying on urllib3's default
+   code to find system CAs. (Jelmer Vernooij, #1025)
+
+ * Implement timezone parsing in porcelain.
+   (springheeledjack0, #1026)
+
+ * Drop support for running without setuptools.
+   (Jelmer Vernooij)
+
+ * Ensure configuration is loaded when
+   running "dulwich clone".
+   (Jelmer Vernooij)
+
+ * Build 32 bit wheels for Windows.
+   (Benjamin Parzella)
+
+ * tests: Ignore errors when deleting GNUPG 
+   home directory. Fixes spurious errors racing
+   gnupg-agent. Thanks, Matěj Cepl. Fixes #1000
+
+ * config: Support closing brackets in quotes in section
+   names. (Jelmer Vernooij, #10124)
+
+ * Various and formatting fixes. (Kian-Meng Ang)
+
+ * Document basic authentication in dulwich.porcelain.clone.
+   (TuringTux)
+
+ * Flush before calling fsync, ensuring buffers
+   are filled. (wernha)
+
+ * Support GPG commit signing. (springheeledjack0)
+
+ * Add python 3.11 support. (Saugat Pachhai)
+
+ * Allow missing GPG during tests. (Jakub Kulík)
+
+ * status: return posix-style untracked paths instead of nt-style paths on
+   win32 (Daniele Trifirò)
+
+ * Honour PATH environment when running C Git for testing.
+   (Stefan Sperling)
+
+ * Split out exception for symbolic reference loops.
+   (Jelmer Vernooij)
+
+ * Move various long-deprecated methods.
+   (Jelmer Vernooij)
+
+
+0.20.45	2022-07-15
+
+ * Add basic ``dulwich.porcelain.submodule_list`` and ``dulwich.porcelain.submodule_add``
+  (Jelmer Vernooij)
+
 0.20.44	2022-06-30
 
  * Fix reading of chunks in server. (Jelmer Vernooij, #977)
@@ -980,7 +1041,7 @@
     probing the filesystem for trustable permissions.
     (Koen Martens)
 
-  * Fix ``porcelain.reset`` to respect the comittish argument.
+  * Fix ``porcelain.reset`` to respect the committish argument.
     (Koen Martens)
 
   * Fix dulwich.porcelain.ls_remote() on Python 3.
@@ -2017,7 +2078,7 @@ FEATURES
 
   * Provide strnlen() on mingw32 which doesn't have it. (Hans Kolek)
 
-  * Set bare=true in the configuratin for bare repositories. (Dirk Neumann)
+  * Set bare=true in the configuration for bare repositories. (Dirk Neumann)
 
  FEATURES
 
@@ -2101,7 +2162,7 @@ FEATURES
 
  FEATURES
 
-  * Move named file initilization to BaseRepo. (Dave Borowitz)
+  * Move named file initialization to BaseRepo. (Dave Borowitz)
 
   * Add logging utilities and git/HTTP server logging. (Dave Borowitz)
 
@@ -2182,7 +2243,7 @@ note: This list is most likely incomplete for 0.6.0.
   * Fix RefsContainer.add_if_new to support dangling symrefs.
     (Dave Borowitz)
 
-  * Non-existant index files in non-bare repositories are now treated as 
+  * Non-existent index files in non-bare repositories are now treated as 
     empty. (Dave Borowitz)
 
   * Always update ShaFile.id when the contents of the object get changed. 
blob - 3de0b5b9f6f458c21bdbe15bb6a586b4bbcc5588
blob + 41cceb42e189fa38d09214f9115560438252f579
--- PKG-INFO
+++ PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.20.44
+Version: 0.20.46
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Operating System :: POSIX
blob - 7fefafc226806b050a4e21e0be999dd10781f958
blob + 4976b90a36c6fc3693937aa1aad00137c503c98b
--- README.swift.rst
+++ README.swift.rst
@@ -46,7 +46,7 @@ be used as template::
 
 
 Note that for now we use the same tenant to perform the requests
-against Swift. Therefor there is only one Swift account used
+against Swift. Therefore there is only one Swift account used
 for storing repositories. Each repository will be contained in
 a Swift container.
 
@@ -118,7 +118,7 @@ The other Git commands can be used the way you do usua
 a regular repository.
 
 Note the daemon subcommands starts a Git server listening for the
-Git protocol. Therefor there is no authentication or encryption
+Git protocol. Therefore there is no authentication or encryption
 at all between the cGIT client and the GIT server (Dulwich).
 
 Note on the .info file for pack object
blob - f1f27068980f3909300a902ddb04c7da4d6a5908
blob + f5887a0d60cf9cb0fc5ce71c90417b6e9c29fa8e
--- SECURITY.md
+++ SECURITY.md
@@ -9,4 +9,5 @@
 
 ## Reporting a Vulnerability
 
-Please report security issues by e-mail to jelmer@jelmer.uk, ideally PGP encrypted to the key at https://jelmer.uk/D729A457.asc
+Please report security issues by e-mail to jelmer@jelmer.uk, ideally PGP
+encrypted to the key at <https://jelmer.uk/D729A457.asc>
blob - e9f070e7868757cbf3bc121a67c780ff9a47c5c1
blob + 49cd8d18d723d61d507793ee9f47c0a85ec3b0f3
--- docs/conf.py
+++ docs/conf.py
@@ -31,7 +31,7 @@ extensions = [
     'sphinx.ext.ifconfig',
     'sphinx.ext.intersphinx',
     'sphinx.ext.napoleon',
-    ]
+]
 
 autoclass_content = "both"
 
@@ -186,8 +186,8 @@ htmlhelp_basename = 'dulwichdoc'
 # (source start file, target name, title, author, documentclass
 # [howto/manual]).
 latex_documents = [
-  ('index', 'dulwich.tex', u'dulwich Documentation',
-   u'Jelmer Vernooij', 'manual'),
+    ('index', 'dulwich.tex', u'dulwich Documentation',
+     'Jelmer Vernooij', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -206,3 +206,9 @@ latex_documents = [
 
 # If false, no module index is generated.
 # latex_use_modindex = True
+
+# Add mappings
+intersphinx_mapping = {
+    'urllib3': ('http://urllib3.readthedocs.org/en/latest', None),
+    'python': ('http://docs.python.org/3', None),
+}
blob - 0dd0d7e7a9dd117df456cdc48188f39e9425047e
blob + 9f1ea60edd3fb5e5450615dff45b2e77e3a65416
--- docs/tutorial/encoding.txt
+++ docs/tutorial/encoding.txt
@@ -12,7 +12,7 @@ and commit messages.
 .. _C git: https://github.com/git/git/blob/master/Documentation/i18n.txt
 
 The library should be able to read *all* existing git repositories,
-irregardless of what encoding they use. This is the main reason why Dulwich
+regardless of what encoding they use. This is the main reason why Dulwich
 does not convert paths to unicode strings.
 
 A further consideration is that converting back and forth to unicode
blob - b85e29e5ad15aba4b3a8baf0de52769c5b564c84
blob + 84da8ebe22d04adc72f703508210b49b72bc7ce6
--- docs/tutorial/porcelain.txt
+++ docs/tutorial/porcelain.txt
@@ -24,7 +24,14 @@ Clone a repository
 ------------------
 
   >>> porcelain.clone("git://github.com/jelmer/dulwich", "dulwich-clone")
+  
+Basic authentication works using the ``username`` and ``password`` parameters:
 
+  >>> porcelain.clone(
+      "https://example.com/a-private-repo.git",
+      "a-private-repo-clone",
+      username="user", password="password")
+
 Commit changes
 --------------
 
blob - 4544902ed0a6ad3a3d904cd6c64d39e9cb72860f
blob + 2577ade721f7b412ff248e3d56e938072b35bfdd
--- dulwich/__init__.py
+++ dulwich/__init__.py
@@ -22,4 +22,4 @@
 
 """Python implementation of the Git file formats and protocols."""
 
-__version__ = (0, 20, 44)
+__version__ = (0, 20, 46)
blob - 5f4baf95e0c3830b82bec31d3ad82dbe0dbfd537
blob + 57677d3241bb5f2bb8fe56a7504438610bacccdf
--- dulwich/archive.py
+++ dulwich/archive.py
@@ -90,7 +90,7 @@ def tar_stream(store, tree, mtime, prefix=b"", format=
         if format == "gz":
             # Manually correct the gzip header file modification time so that
             # archives created from the same Git tree are always identical.
-            # The gzip header file modification time is not currenctly
+            # The gzip header file modification time is not currently
             # accessible from the tarfile API, see:
             # https://bugs.python.org/issue31526
             buf.seek(0)
blob - 235f80b996fbd0254f44349709de633d1769f7a9
blob + 38b805f0c7eaa07e074c0fa7683442377eeab1a2
--- dulwich/cli.py
+++ dulwich/cli.py
@@ -321,6 +321,14 @@ class cmd_rev_list(Command):
         porcelain.rev_list(".", args)
 
 
+class cmd_submodule(Command):
+    def run(self, args):
+        parser = optparse.OptionParser()
+        options, args = parser.parse_args(args)
+        for path, sha in porcelain.submodule_list("."):
+            sys.stdout.write(' %s %s\n' % (sha, path))
+
+
 class cmd_tag(Command):
     def run(self, args):
         parser = optparse.OptionParser()
@@ -499,7 +507,7 @@ class cmd_ls_tree(Command):
             "-r",
             "--recursive",
             action="store_true",
-            help="Recusively list tree contents.",
+            help="Recursively list tree contents.",
         )
         parser.add_option("--name-only", action="store_true", help="Only display name.")
         options, args = parser.parse_args(args)
@@ -553,10 +561,15 @@ class cmd_push(Command):
 
     def run(self, argv):
         parser = argparse.ArgumentParser()
+        parser.add_argument('-f', '--force', action='store_true', help='Force')
         parser.add_argument('to_location', type=str)
         parser.add_argument('refspec', type=str, nargs='*')
         args = parser.parse_args(argv)
-        porcelain.push('.', args.to_location, args.refspec or None)
+        try:
+            porcelain.push('.', args.to_location, args.refspec or None, force=args.force)
+        except porcelain.DivergedBranches:
+            sys.stderr.write('Diverged branches; specify --force to override')
+            return 1
 
 
 class cmd_remote_add(Command):
@@ -721,6 +734,7 @@ commands = {
     "stash": cmd_stash,
     "status": cmd_status,
     "symbolic-ref": cmd_symbolic_ref,
+    "submodule": cmd_submodule,
     "tag": cmd_tag,
     "update-server-info": cmd_update_server_info,
     "upload-pack": cmd_upload_pack,
blob - 19f540e69893a800afb90122765f0e02d003faf1
blob + cf2e2c516350dafdb60847ebb3adcb2c7614f22c
--- dulwich/client.py
+++ dulwich/client.py
@@ -46,7 +46,18 @@ import select
 import socket
 import subprocess
 import sys
-from typing import Any, Callable, Dict, List, Optional, Set, Tuple, IO, Iterable
+from typing import (
+    Any,
+    Callable,
+    Dict,
+    List,
+    Optional,
+    Set,
+    Tuple,
+    IO,
+    Union,
+    TYPE_CHECKING,
+)
 
 from urllib.parse import (
     quote as urlquote,
@@ -57,9 +68,12 @@ from urllib.parse import (
     urlunparse,
 )
 
+if TYPE_CHECKING:
+    import urllib3
 
+
 import dulwich
-from dulwich.config import get_xdg_config_home_path, Config
+from dulwich.config import get_xdg_config_home_path, Config, apply_instead_of
 from dulwich.errors import (
     GitProtocolError,
     NotGitRepository,
@@ -437,7 +451,6 @@ class _v1ReceivePackHeader(object):
         """Handle the head of a 'git-receive-pack' request.
 
         Args:
-          proto: Protocol object to read from
           capabilities: List of negotiated capabilities
           old_refs: Old refs, as received from the server
           new_refs: Refs to change
@@ -949,6 +962,21 @@ class GitClient(object):
 
         negotiated_capabilities = self._fetch_capabilities & server_capabilities
         return (negotiated_capabilities, symrefs, agent)
+
+    def archive(
+        self,
+        path,
+        committish,
+        write_data,
+        progress=None,
+        write_error=None,
+        format=None,
+        subdirs=None,
+        prefix=None,
+    ):
+        """Retrieve an archive of the specified tree.
+        """
+        raise NotImplementedError(self.archive)
 
 
 def check_wants(wants, refs):
@@ -1532,32 +1560,7 @@ default_local_git_client_cls = LocalGitClient
 
 class SSHVendor(object):
     """A client side SSH implementation."""
-
-    def connect_ssh(
-        self,
-        host,
-        command,
-        username=None,
-        port=None,
-        password=None,
-        key_filename=None,
-    ):
-        # This function was deprecated in 0.9.1
-        import warnings
 
-        warnings.warn(
-            "SSHVendor.connect_ssh has been renamed to SSHVendor.run_command",
-            DeprecationWarning,
-        )
-        return self.run_command(
-            host,
-            command,
-            username=username,
-            port=port,
-            password=password,
-            key_filename=key_filename,
-        )
-
     def run_command(
         self,
         host,
@@ -1616,7 +1619,8 @@ class SubprocessSSHVendor(SSHVendor):
 
         if ssh_command:
             import shlex
-            args = shlex.split(ssh_command) + ["-x"]
+            args = shlex.split(
+                ssh_command, posix=(sys.platform != 'win32')) + ["-x"]
         else:
             args = ["ssh", "-x"]
 
@@ -1658,7 +1662,8 @@ class PLinkSSHVendor(SSHVendor):
 
         if ssh_command:
             import shlex
-            args = shlex.split(ssh_command) + ["-ssh"]
+            args = shlex.split(
+                ssh_command, posix=(sys.platform != 'win32')) + ["-ssh"]
         elif sys.platform == "win32":
             args = ["plink.exe", "-ssh"]
         else:
@@ -1781,7 +1786,7 @@ class SSHGitClient(TraditionalGitClient):
             kwargs["password"] = self.password
         if self.key_filename is not None:
             kwargs["key_filename"] = self.key_filename
-        # GIT_SSH_COMMAND takes precendence over GIT_SSH
+        # GIT_SSH_COMMAND takes precedence over GIT_SSH
         if self.ssh_command is not None:
             kwargs["ssh_command"] = self.ssh_command
         con = self.ssh_vendor.run_command(
@@ -1807,8 +1812,8 @@ def default_user_agent_string():
 
 def default_urllib3_manager(   # noqa: C901
     config, pool_manager_cls=None, proxy_manager_cls=None, **override_kwargs
-):
-    """Return `urllib3` connection pool manager.
+) -> Union["urllib3.ProxyManager", "urllib3.PoolManager"]:
+    """Return urllib3 connection pool manager.
 
     Honour detected proxy configurations.
 
@@ -1817,9 +1822,9 @@ def default_urllib3_manager(   # noqa: C901
       override_kwargs: Additional arguments for `urllib3.ProxyManager`
 
     Returns:
-      `pool_manager_cls` (defaults to `urllib3.ProxyManager`) instance for
-      proxy configurations, `proxy_manager_cls` (defaults to
-      `urllib3.PoolManager`) instance otherwise.
+      Either pool_manager_cls (defaults to `urllib3.ProxyManager`) instance for
+      proxy configurations, proxy_manager_cls
+      (defaults to `urllib3.PoolManager`) instance otherwise
 
     """
     proxy_server = user_agent = None
@@ -1858,7 +1863,9 @@ def default_urllib3_manager(   # noqa: C901
 
     headers = {"User-agent": user_agent}
 
-    kwargs = {}
+    kwargs = {
+        "ca_certs" : ca_certs,
+    }
     if ssl_verify is True:
         kwargs["cert_reqs"] = "CERT_REQUIRED"
     elif ssl_verify is False:
@@ -1867,18 +1874,7 @@ def default_urllib3_manager(   # noqa: C901
         # Default to SSL verification
         kwargs["cert_reqs"] = "CERT_REQUIRED"
 
-    if ca_certs is not None:
-        kwargs["ca_certs"] = ca_certs
     kwargs.update(override_kwargs)
-
-    # Try really hard to find a SSL certificate path
-    if "ca_certs" not in kwargs and kwargs.get("cert_reqs") != "CERT_NONE":
-        try:
-            import certifi
-        except ImportError:
-            pass
-        else:
-            kwargs["ca_certs"] = certifi.where()
 
     import urllib3
 
@@ -1919,9 +1915,9 @@ class AbstractHttpGitClient(GitClient):
           data: Request data.
 
         Returns:
-          Tuple (`response`, `read`), where response is an `urllib3`
-          response object with additional `content_type` and
-          `redirect_location` properties, and `read` is a consumable read
+          Tuple (response, read), where response is an urllib3
+          response object with additional content_type and
+          redirect_location properties, and read is a consumable read
           method for the response data.
 
         """
@@ -2266,42 +2262,8 @@ def _win32_url_to_path(parsed) -> str:
     if url2pathname is None:
         from urllib.request import url2pathname  # type: ignore
     return url2pathname(netloc + path)  # type: ignore
-
-
-def iter_instead_of(config: Config, push: bool = False) -> Iterable[Tuple[str, str]]:
-    """Iterate over insteadOf / pushInsteadOf values.
-    """
-    for section in config.sections():
-        if section[0] != b'url':
-            continue
-        replacement = section[1]
-        try:
-            needles = list(config.get_multivar(section, "insteadOf"))
-        except KeyError:
-            needles = []
-        if push:
-            try:
-                needles += list(config.get_multivar(section, "pushInsteadOf"))
-            except KeyError:
-                pass
-        for needle in needles:
-            yield needle.decode('utf-8'), replacement.decode('utf-8')
 
 
-def apply_instead_of(config: Config, orig_url: str, push: bool = False) -> str:
-    """Apply insteadOf / pushInsteadOf to a URL.
-    """
-    longest_needle = ""
-    updated_url = orig_url
-    for needle, replacement in iter_instead_of(config, push):
-        if not orig_url.startswith(needle):
-            continue
-        if len(longest_needle) < len(needle):
-            longest_needle = needle
-            updated_url = replacement + orig_url[len(needle):]
-    return updated_url
-
-
 def get_transport_and_path_from_url(
         url: str, config: Optional[Config] = None,
         operation: Optional[str] = None, **kwargs) -> Tuple[GitClient, str]:
@@ -2321,6 +2283,12 @@ def get_transport_and_path_from_url(
     """
     if config is not None:
         url = apply_instead_of(config, url, push=(operation == "push"))
+
+    return _get_transport_and_path_from_url(
+        url, config=config, operation=operation, **kwargs)
+
+
+def _get_transport_and_path_from_url(url, config, operation, **kwargs):
     parsed = urlparse(url)
     if parsed.scheme == "git":
         return (TCPGitClient.from_parsedurl(parsed, **kwargs), parsed.path)
@@ -2363,6 +2331,7 @@ def parse_rsync_url(location: str) -> Tuple[Optional[s
 
 def get_transport_and_path(
     location: str,
+    config: Optional[Config] = None,
     operation: Optional[str] = None,
     **kwargs: Any
 ) -> Tuple[GitClient, str]:
@@ -2380,9 +2349,13 @@ def get_transport_and_path(
       Tuple with client instance and relative path.
 
     """
+    if config is not None:
+        location = apply_instead_of(config, location, push=(operation == "push"))
+
     # First, try to parse it as a URL
     try:
-        return get_transport_and_path_from_url(location, operation=operation, **kwargs)
+        return _get_transport_and_path_from_url(
+            location, config=config, operation=operation, **kwargs)
     except ValueError:
         pass
 
blob - e52d468e3bc66f6fa295218061a2656e207ac800
blob + 7f09ae57a41e4ec5ad9871016e300ad1652086b9
--- dulwich/config.py
+++ dulwich/config.py
@@ -28,7 +28,6 @@ TODO:
 
 import os
 import sys
-import warnings
 from typing import (
     BinaryIO,
     Iterable,
@@ -45,7 +44,7 @@ from typing import (
 from dulwich.file import GitFile
 
 
-SENTINAL = object()
+SENTINEL = object()
 
 
 def lower_key(key):
@@ -112,13 +111,13 @@ class CaseInsensitiveOrderedMultiDict(MutableMapping):
     def __getitem__(self, item):
         return self._keyed[lower_key(item)]
 
-    def get(self, key, default=SENTINAL):
+    def get(self, key, default=SENTINEL):
         try:
             return self[key]
         except KeyError:
             pass
 
-        if default is SENTINAL:
+        if default is SENTINEL:
             return type(self)()
 
         return default
@@ -129,7 +128,7 @@ class CaseInsensitiveOrderedMultiDict(MutableMapping):
             if lower_key(actual) == key:
                 yield value
 
-    def setdefault(self, key, default=SENTINAL):
+    def setdefault(self, key, default=SENTINEL):
         try:
             return self[key]
         except KeyError:
@@ -231,29 +230,6 @@ class Config(object):
         """
         raise NotImplementedError(self.items)
 
-    def iteritems(self, section: SectionLike) -> Iterator[Tuple[Name, Value]]:
-        """Iterate over the configuration pairs for a specific section.
-
-        Args:
-          section: Tuple with section name and optional subsection namee
-        Returns:
-          Iterator over (name, value) pairs
-        """
-        warnings.warn(
-            "Use %s.items instead." % type(self).__name__,
-            DeprecationWarning,
-            stacklevel=3,
-        )
-        return self.items(section)
-
-    def itersections(self) -> Iterator[Section]:
-        warnings.warn(
-            "Use %s.items instead." % type(self).__name__,
-            DeprecationWarning,
-            stacklevel=3,
-        )
-        return self.sections()
-
     def sections(self) -> Iterator[Section]:
         """Iterate over the sections.
 
@@ -505,6 +481,46 @@ def _strip_comments(line: bytes) -> bytes:
         elif not string_open and character in comment_bytes:
             return line[:i]
     return line
+
+
+def _parse_section_header_line(line: bytes) -> Tuple[Section, bytes]:
+    # Parse section header ("[bla]")
+    line = _strip_comments(line).rstrip()
+    in_quotes = False
+    escaped = False
+    for i, c in enumerate(line):
+        if escaped:
+            escaped = False
+            continue
+        if c == ord(b'"'):
+            in_quotes = not in_quotes
+        if c == ord(b'\\'):
+            escaped = True
+        if c == ord(b']') and not in_quotes:
+            last = i
+            break
+    else:
+        raise ValueError("expected trailing ]")
+    pts = line[1:last].split(b" ", 1)
+    line = line[last + 1:]
+    section: Section
+    if len(pts) == 2:
+        if pts[1][:1] != b'"' or pts[1][-1:] != b'"':
+            raise ValueError("Invalid subsection %r" % pts[1])
+        else:
+            pts[1] = pts[1][1:-1]
+        if not _check_section_name(pts[0]):
+            raise ValueError("invalid section name %r" % pts[0])
+        section = (pts[0], pts[1])
+    else:
+        if not _check_section_name(pts[0]):
+            raise ValueError("invalid section name %r" % pts[0])
+        pts = pts[0].split(b".", 1)
+        if len(pts) == 2:
+            section = (pts[0], pts[1])
+        else:
+            section = (pts[0],)
+    return section, line
 
 
 class ConfigFile(ConfigDict):
@@ -532,31 +548,8 @@ class ConfigFile(ConfigDict):
                 line = line[3:]
             line = line.lstrip()
             if setting is None:
-                # Parse section header ("[bla]")
                 if len(line) > 0 and line[:1] == b"[":
-                    line = _strip_comments(line).rstrip()
-                    try:
-                        last = line.index(b"]")
-                    except ValueError:
-                        raise ValueError("expected trailing ]")
-                    pts = line[1:last].split(b" ", 1)
-                    line = line[last + 1 :]
-                    if len(pts) == 2:
-                        if pts[1][:1] != b'"' or pts[1][-1:] != b'"':
-                            raise ValueError("Invalid subsection %r" % pts[1])
-                        else:
-                            pts[1] = pts[1][1:-1]
-                        if not _check_section_name(pts[0]):
-                            raise ValueError("invalid section name %r" % pts[0])
-                        section = (pts[0], pts[1])
-                    else:
-                        if not _check_section_name(pts[0]):
-                            raise ValueError("invalid section name %r" % pts[0])
-                        pts = pts[0].split(b".", 1)
-                        if len(pts) == 2:
-                            section = (pts[0], pts[1])
-                        else:
-                            section = (pts[0],)
+                    section, line = _parse_section_header_line(line)
                     ret._values.setdefault(section)
                 if _strip_comments(line).strip() == b"":
                     continue
@@ -772,3 +765,38 @@ def parse_submodules(config: ConfigFile) -> Iterator[T
             sm_path = config.get(section, b"path")
             sm_url = config.get(section, b"url")
             yield (sm_path, sm_url, section_name)
+
+
+def iter_instead_of(config: Config, push: bool = False) -> Iterable[Tuple[str, str]]:
+    """Iterate over insteadOf / pushInsteadOf values.
+    """
+    for section in config.sections():
+        if section[0] != b'url':
+            continue
+        replacement = section[1]
+        try:
+            needles = list(config.get_multivar(section, "insteadOf"))
+        except KeyError:
+            needles = []
+        if push:
+            try:
+                needles += list(config.get_multivar(section, "pushInsteadOf"))
+            except KeyError:
+                pass
+        for needle in needles:
+            assert isinstance(needle, bytes)
+            yield needle.decode('utf-8'), replacement.decode('utf-8')
+
+
+def apply_instead_of(config: Config, orig_url: str, push: bool = False) -> str:
+    """Apply insteadOf / pushInsteadOf to a URL.
+    """
+    longest_needle = ""
+    updated_url = orig_url
+    for needle, replacement in iter_instead_of(config, push):
+        if not orig_url.startswith(needle):
+            continue
+        if len(longest_needle) < len(needle):
+            longest_needle = needle
+            updated_url = replacement + orig_url[len(needle):]
+    return updated_url
blob - 83b1638922dbd682089d554cbf247988cdb75c92
blob + 7a552ed4aedc218db9ab27c6ce475a4d5047e97a
--- dulwich/contrib/diffstat.py
+++ dulwich/contrib/diffstat.py
@@ -184,7 +184,7 @@ def diffstat(lines, max_width=80):
 
 def main():
     argv = sys.argv
-    # allow diffstat.py to also be used from the comand line
+    # allow diffstat.py to also be used from the command line
     if len(sys.argv) > 1:
         diffpath = argv[1]
         data = b""
@@ -197,7 +197,7 @@ def main():
 
     # if no path argument to a diff file is passed in, run
     # a self test. The test case includes tricky things like
-    # a diff of diff, binary files, renames with futher changes
+    # a diff of diff, binary files, renames with further changes
     # added files and removed files.
     # All extracted from Sigil-Ebook/Sigil's github repo with
     # full permission to use under this license.
blob - 33dc60fcb9d411f4a5f5eeb3ebde3eb3ecfbe9be
blob + 5b2734e7ec053960d6c3ceb6f7280d916d7de4c5
--- dulwich/contrib/release_robot.py
+++ dulwich/contrib/release_robot.py
@@ -29,7 +29,7 @@ Copy the following into the package ``__init__.py`` mo
     __version__ = get_current_version()
 
 This example assumes the tags have a leading "v" like "v0.3", and that the
-``.git`` folder is in a project folder that containts the package folder.
+``.git`` folder is in a project folder that contains the package folder.
 
 EG::
 
blob - 3d76acbe73c57e9c0bbbf1bba0e533410807ccaa
blob + 80ced06cbc13f3dffe04498deecf5ab3b841babe
--- dulwich/contrib/swift.py
+++ dulwich/contrib/swift.py
@@ -504,9 +504,9 @@ class SwiftPackReader(object):
     """A SwiftPackReader that mimic read and sync method
 
     The reader allows to read a specified amount of bytes from
-    a given offset of a Swift object. A read offset is kept internaly.
+    a given offset of a Swift object. A read offset is kept internally.
     The reader will read from Swift a specified amount of data to complete
-    its internal buffer. chunk_length specifiy the amount of data
+    its internal buffer. chunk_length specify the amount of data
     to read from Swift.
     """
 
blob - 33496543afbf40e7153e151911ee656ebfb30b8f
blob + 712b4f6cdc8b7335a9843183ca47f47084ef94d8
--- dulwich/contrib/test_paramiko_vendor.py
+++ dulwich/contrib/test_paramiko_vendor.py
@@ -131,7 +131,7 @@ class ParamikoSSHVendorTests(TestCase):
     def setUp(self):
         import paramiko.transport
 
-        # reenable server functionality for tests
+        # re-enable server functionality for tests
         if hasattr(paramiko.transport, "SERVER_DISABLED_BY_GENTOO"):
             paramiko.transport.SERVER_DISABLED_BY_GENTOO = False
 
blob - 5cb540a9a23eadb6270b916c36b38c62a848db42
blob + 461f75f80d1fda5589fcb49bcce975b91e6c4ae3
--- dulwich/contrib/test_swift_smoke.py
+++ dulwich/contrib/test_swift_smoke.py
@@ -123,7 +123,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         swift.SwiftRepo.init_bare(self.scon, self.conf)
         tcp_client = client.TCPGitClient(self.server_address, port=self.port)
         remote_refs = tcp_client.fetch(self.fakerepo, local_repo)
-        # The remote repo is empty (no refs retreived)
+        # The remote repo is empty (no refs retrieved)
         self.assertEqual(remote_refs, None)
 
     def test_push_commit(self):
blob - acdec8c74d0e42c3480a4ad10ba5bf517107beca
blob + b7720277bff5eee6a77ff68886721c8bce34c548
--- dulwich/file.py
+++ dulwich/file.py
@@ -189,6 +189,7 @@ class _GitFile(object):
         """
         if self._closed:
             return
+        self._file.flush()
         os.fsync(self._file.fileno())
         self._file.close()
         try:
blob - b9d24d359b26561781c0cfd1fff1f98ad2fc2c14
blob + a948cc91862d9784e4f957c71c6a6687fe7219dc
--- dulwich/graph.py
+++ dulwich/graph.py
@@ -74,7 +74,7 @@ def _find_lcas(lookup_parents, c1, c2s):
                     cstates[pcmt] = flags
                 wlst.append(pcmt)
 
-    # walk final candidates removing any superceded by _DNC by later lower LCAs
+    # walk final candidates removing any superseded by _DNC by later lower LCAs
     results = []
     for cmt in cands:
         if not (cstates[cmt] & _DNC):
blob - e920ab831bf1663734695d8fc9e477c0ee57e676
blob + a72cde13eacc974f797a5b996998b2f02c0d8adc
--- dulwich/hooks.py
+++ dulwich/hooks.py
@@ -100,7 +100,9 @@ class ShellHook(Hook):
             args = self.pre_exec_callback(*args)
 
         try:
-            ret = subprocess.call([self.filepath] + list(args), cwd=self.cwd)
+            ret = subprocess.call(
+                [os.path.relpath(self.filepath, self.cwd)] + list(args),
+                cwd=self.cwd)
             if ret != 0:
                 if self.post_exec_callback is not None:
                     self.post_exec_callback(0, *args)
blob - 309d5cf95a999b0257d02ad7c3b4bd4b0848c291
blob + 3066f42d616f148f12fda51fb08f26c90a410ede
--- dulwich/index.py
+++ dulwich/index.py
@@ -886,7 +886,7 @@ def index_entry_from_path(path, object_store=None):
 
     This returns an index value for files, symlinks
     and tree references. for directories and
-    non-existant files it returns None
+    non-existent files it returns None
 
     Args:
       path: Path to create an index entry for
@@ -929,7 +929,7 @@ def iter_fresh_entries(
 
 
 def iter_fresh_objects(paths, root_path, include_deleted=False, object_store=None):
-    """Iterate over versions of objecs on disk referenced by index.
+    """Iterate over versions of objects on disk referenced by index.
 
     Args:
       root_path: Root path to access from
blob - 561b6f96d282da770f2ba9a6a95a372b951bfef6
blob + 358338a4708f648c0d5444f233ac5879b1232bec
--- dulwich/object_store.py
+++ dulwich/object_store.py
@@ -1151,12 +1151,6 @@ class ObjectStoreIterator(ObjectIterator):
     def __len__(self):
         """Return the number of objects."""
         return len(list(self.itershas()))
-
-    def empty(self):
-        import warnings
-
-        warnings.warn("Use bool() instead.", DeprecationWarning)
-        return self._empty()
 
     def _empty(self):
         it = self.itershas()
blob - 90f7de22615ee11b67adfd203f46044e4367e7a7
blob + 07b5208743ca11cb05a40252983a001f552e86cd
--- dulwich/objects.py
+++ dulwich/objects.py
@@ -34,7 +34,6 @@ from typing import (
     Union,
     Type,
 )
-import warnings
 import zlib
 from hashlib import sha1
 
@@ -1091,13 +1090,6 @@ class Tree(ShaFile):
           name: The name of the entry, as a string.
           hexsha: The hex SHA of the entry as a string.
         """
-        if isinstance(name, int) and isinstance(mode, bytes):
-            (name, mode) = (mode, name)
-            warnings.warn(
-                "Please use Tree.add(name, mode, hexsha)",
-                category=DeprecationWarning,
-                stacklevel=2,
-            )
         self._entries[name] = mode, hexsha
         self._needs_serialization = True
 
@@ -1427,7 +1419,63 @@ class Commit(ShaFile):
             last = field
 
         # TODO: optionally check for duplicate parents
+
+    def sign(self, keyid: Optional[str] = None):
+        import gpg
+        with gpg.Context(armor=True) as c:
+            if keyid is not None:
+                key = c.get_key(keyid)
+                with gpg.Context(armor=True, signers=[key]) as ctx:
+                    self.gpgsig, unused_result = ctx.sign(
+                        self.as_raw_string(),
+                        mode=gpg.constants.sig.mode.DETACH,
+                    )
+            else:
+                self.gpgsig, unused_result = c.sign(
+                    self.as_raw_string(), mode=gpg.constants.sig.mode.DETACH
+                )
+
+    def verify(self, keyids: Optional[Iterable[str]] = None):
+        """Verify GPG signature for this commit (if it is signed).
+
+        Args:
+          keyids: Optional iterable of trusted keyids for this commit.
+            If this commit is not signed by any key in keyids verification will
+            fail. If not specified, this function only verifies that the commit
+            has a valid signature.
+
+        Raises:
+          gpg.errors.BadSignatures: if GPG signature verification fails
+          gpg.errors.MissingSignatures: if commit was not signed by a key
+            specified in keyids
+        """
+        if self._gpgsig is None:
+            return
 
+        import gpg
+
+        with gpg.Context() as ctx:
+            self_without_gpgsig = self.copy()
+            self_without_gpgsig._gpgsig = None
+            self_without_gpgsig.gpgsig = None
+            data, result = ctx.verify(
+                self_without_gpgsig.as_raw_string(),
+                signature=self._gpgsig,
+            )
+            if keyids:
+                keys = [
+                    ctx.get_key(key)
+                    for key in keyids
+                ]
+                for key in keys:
+                    for subkey in keys:
+                        for sig in result.signatures:
+                            if subkey.can_sign and subkey.fpr == sig.fpr:
+                                return
+                raise gpg.errors.MissingSignatures(
+                    result, keys, results=(data, result)
+                )
+
     def _serialize(self):
         chunks = []
         tree_bytes = self._tree.id if isinstance(self._tree, Tree) else self._tree
blob - 82ab5bef310451e75665c1da2ce0ce6ff2b9b21e
blob + 8c92a878cb540f6095fc6bef3e09c06ec6614d3b
--- dulwich/patch.py
+++ dulwich/patch.py
@@ -202,7 +202,7 @@ def write_object_diff(f, store, old_file, new_file, di
       diff_binary: Whether to diff files even if they
         are considered binary files by is_binary().
 
-    Note: the tuple elements should be None for nonexistant files
+    Note: the tuple elements should be None for nonexistent files
     """
     (old_path, old_mode, old_id) = old_file
     (new_path, new_mode, new_id) = new_file
blob - d34dac7e8a6f3a6113e2b30e97f9f4f27dbe1a1a
blob + 7136bdbcf995cac7d0671d400dac3162cf53a18a
--- dulwich/porcelain.py
+++ dulwich/porcelain.py
@@ -43,6 +43,7 @@ Currently implemented:
  * remote{_add}
  * receive-pack
  * reset
+ * submodule_list
  * rev-list
  * tag{_create,_delete,_list}
  * upload-pack
@@ -86,6 +87,7 @@ from dulwich.client import (
     get_transport_and_path,
 )
 from dulwich.config import (
+    ConfigFile,
     StackedConfig,
 )
 from dulwich.diff_tree import (
@@ -189,6 +191,78 @@ class RemoteExists(Error):
     """Raised when the remote already exists."""
 
 
+class TimezoneFormatError(Error):
+    """Raised when the timezone cannot be determined from a given string."""
+
+
+def parse_timezone_format(tz_str):
+    """Parse given string and attempt to return a timezone offset.
+    Different formats are considered in the following order:
+        - Git internal format: <unix timestamp> <timezone offset>
+        - RFC 2822: e.g. Mon, 20 Nov 1995 19:12:08 -0500
+        - ISO 8601: e.g. 1995-11-20T19:12:08-0500
+    Args:
+      tz_str: datetime string
+    Returns: Timezone offset as integer
+    Raises:
+      TimezoneFormatError: if timezone information cannot be extracted
+   """
+    import re
+
+    # Git internal format
+    internal_format_pattern = re.compile("^[0-9]+ [+-][0-9]{,4}$")
+    if re.match(internal_format_pattern, tz_str):
+        try:
+            tz_internal = parse_timezone(tz_str.split(" ")[1].encode(DEFAULT_ENCODING))
+            return tz_internal[0]
+        except ValueError:
+            pass
+
+    # RFC 2822
+    import email.utils
+    rfc_2822 = email.utils.parsedate_tz(tz_str)
+    if rfc_2822:
+        return rfc_2822[9]
+
+    # ISO 8601
+
+    # Supported offsets:
+    # sHHMM, sHH:MM, sHH
+    iso_8601_pattern = re.compile("[0-9] ?([+-])([0-9]{2})(?::(?=[0-9]{2}))?([0-9]{2})?$")
+    match = re.search(iso_8601_pattern, tz_str)
+    total_secs = 0
+    if match:
+        sign, hours, minutes = match.groups()
+        total_secs += int(hours) * 3600
+        if minutes:
+            total_secs += int(minutes) * 60
+        total_secs = -total_secs if sign == "-" else total_secs
+        return total_secs
+
+    # YYYY.MM.DD, MM/DD/YYYY, DD.MM.YYYY contain no timezone information
+
+    raise TimezoneFormatError(tz_str)
+
+
+def get_user_timezones():
+    """Retrieve local timezone as described in
+    https://raw.githubusercontent.com/git/git/v2.3.0/Documentation/date-formats.txt
+    Returns: A tuple containing author timezone, committer timezone
+    """
+    local_timezone = time.localtime().tm_gmtoff
+
+    if os.environ.get("GIT_AUTHOR_DATE"):
+        author_timezone = parse_timezone_format(os.environ["GIT_AUTHOR_DATE"])
+    else:
+        author_timezone = local_timezone
+    if os.environ.get("GIT_COMMITTER_DATE"):
+        commit_timezone = parse_timezone_format(os.environ["GIT_COMMITTER_DATE"])
+    else:
+        commit_timezone = local_timezone
+
+    return author_timezone, commit_timezone
+
+
 def open_repo(path_or_repo):
     """Open an argument that can be a repository or a path for a repository."""
     if isinstance(path_or_repo, BaseRepo):
@@ -327,9 +401,12 @@ def commit(
     repo=".",
     message=None,
     author=None,
+    author_timezone=None,
     committer=None,
+    commit_timezone=None,
     encoding=None,
     no_verify=False,
+    signoff=False,
 ):
     """Create a new commit.
 
@@ -337,25 +414,37 @@ def commit(
       repo: Path to repository
       message: Optional commit message
       author: Optional author name and email
+      author_timezone: Author timestamp timezone
       committer: Optional committer name and email
+      commit_timezone: Commit timestamp timezone
       no_verify: Skip pre-commit and commit-msg hooks
+      signoff: GPG Sign the commit (bool, defaults to False,
+        pass True to use default GPG key,
+        pass a str containing Key ID to use a specific GPG key)
     Returns: SHA1 of the new commit
     """
     # FIXME: Support --all argument
-    # FIXME: Support --signoff argument
     if getattr(message, "encode", None):
         message = message.encode(encoding or DEFAULT_ENCODING)
     if getattr(author, "encode", None):
         author = author.encode(encoding or DEFAULT_ENCODING)
     if getattr(committer, "encode", None):
         committer = committer.encode(encoding or DEFAULT_ENCODING)
+    local_timezone = get_user_timezones()
+    if author_timezone is None:
+        author_timezone = local_timezone[0]
+    if commit_timezone is None:
+        commit_timezone = local_timezone[1]
     with open_repo_closing(repo) as r:
         return r.do_commit(
             message=message,
             author=author,
+            author_timezone=author_timezone,
             committer=committer,
+            commit_timezone=commit_timezone,
             encoding=encoding,
             no_verify=no_verify,
+            sign=signoff if isinstance(signoff, (str, bool)) else None,
         )
 
 
@@ -401,6 +490,7 @@ def clone(
     origin="origin",
     depth=None,
     branch=None,
+    config=None,
     **kwargs
 ):
     """Clone a local or remote git repository.
@@ -416,6 +506,7 @@ def clone(
       depth: Depth to fetch at
       branch: Optional branch or tag to be used as HEAD in the new repository
         instead of the cloned repository's HEAD.
+      config: Configuration to use
     Returns: The new repository
     """
     if outstream is not None:
@@ -427,6 +518,9 @@ def clone(
             stacklevel=3,
         )
         # TODO(jelmer): Capture logging output and stream to errstream
+
+    if config is None:
+        config = StackedConfig.default()
 
     if checkout is None:
         checkout = not bare
@@ -438,7 +532,8 @@ def clone(
 
     mkdir = not os.path.exists(target)
 
-    (client, path) = get_transport_and_path(source, **kwargs)
+    (client, path) = get_transport_and_path(
+        source, config=config, **kwargs)
 
     return client.clone(
         path,
@@ -858,15 +953,51 @@ def rev_list(repo, commits, outstream=sys.stdout):
             outstream.write(entry.commit.id + b"\n")
 
 
-def tag(*args, **kwargs):
-    import warnings
+def _canonical_part(url: str) -> str:
+    name = url.rsplit('/', 1)[-1]
+    if name.endswith('.git'):
+        name = name[:-4]
+    return name
 
-    warnings.warn(
-        "tag has been deprecated in favour of tag_create.", DeprecationWarning
-    )
-    return tag_create(*args, **kwargs)
+
+def submodule_add(repo, url, path=None, name=None):
+    """Add a new submodule.
+
+    Args:
+      repo: Path to repository
+      url: URL of repository to add as submodule
+      path: Path where submodule should live
+    """
+    with open_repo_closing(repo) as r:
+        if path is None:
+            path = os.path.relpath(_canonical_part(url), r.path)
+        if name is None:
+            name = path
+
+        # TODO(jelmer): Move this logic to dulwich.submodule
+        gitmodules_path = os.path.join(r.path, ".gitmodules")
+        try:
+            config = ConfigFile.from_path(gitmodules_path)
+        except FileNotFoundError:
+            config = ConfigFile()
+            config.path = gitmodules_path
+        config.set(("submodule", name), "url", url)
+        config.set(("submodule", name), "path", path)
+        config.write_to_path()
 
 
+def submodule_list(repo):
+    """List submodules.
+
+    Args:
+      repo: Path to repository
+    """
+    from .submodule import iter_cached_submodules
+    with open_repo_closing(repo) as r:
+        for path, sha in iter_cached_submodules(r.object_store, r[r.head()].tree):
+            yield path.decode(DEFAULT_ENCODING), sha.decode(DEFAULT_ENCODING)
+
+
 def tag_create(
     repo,
     tag,
@@ -911,8 +1042,7 @@ def tag_create(
                 tag_time = int(time.time())
             tag_obj.tag_time = tag_time
             if tag_timezone is None:
-                # TODO(jelmer) Use current user timezone rather than UTC
-                tag_timezone = 0
+                tag_timezone = get_user_timezones()[1]
             elif isinstance(tag_timezone, str):
                 tag_timezone = parse_timezone(tag_timezone)
             tag_obj.tag_timezone = tag_timezone
@@ -925,16 +1055,6 @@ def tag_create(
             tag_id = object.id
 
         r.refs[_make_tag_ref(tag)] = tag_id
-
-
-def list_tags(*args, **kwargs):
-    import warnings
-
-    warnings.warn(
-        "list_tags has been deprecated in favour of tag_list.",
-        DeprecationWarning,
-    )
-    return tag_list(*args, **kwargs)
 
 
 def tag_list(repo, outstream=sys.stdout):
@@ -1164,10 +1284,10 @@ def status(repo=".", ignored=False, untracked_files="a
       untracked_files: How to handle untracked files, defaults to "all":
           "no": do not return untracked files
           "all": include all files in untracked directories
-        Using `untracked_files="no"` can be faster than "all" when the worktreee
+        Using untracked_files="no" can be faster than "all" when the worktreee
           contains many untracked files/directories.
 
-    Note: `untracked_files="normal" (`git`'s default) is not implemented.
+    Note: untracked_files="normal" (git's default) is not implemented.
 
     Returns: GitStatus tuple,
         staged -  dict with lists of staged paths (diff index/HEAD)
@@ -1190,7 +1310,12 @@ def status(repo=".", ignored=False, untracked_files="a
             exclude_ignored=not ignored,
             untracked_files=untracked_files,
         )
-        untracked_changes = list(untracked_paths)
+        if sys.platform == "win32":
+            untracked_changes = [
+                path.replace(os.path.sep, "/") for path in untracked_paths
+            ]
+        else:
+            untracked_changes = list(untracked_paths)
 
         return GitStatus(tracked_changes, unstaged_changes, untracked_changes)
 
@@ -1456,7 +1581,7 @@ def branch_create(repo, name, objectish=None, force=Fa
             objectish = "HEAD"
         object = parse_object(r, objectish)
         refname = _make_branch_ref(name)
-        ref_message = b"branch: Created from " + objectish.encode("utf-8")
+        ref_message = b"branch: Created from " + objectish.encode(DEFAULT_ENCODING)
         if force:
             r.refs.set_if_equals(refname, None, object.id, message=ref_message)
         else:
@@ -1541,7 +1666,7 @@ def fetch(
     with open_repo_closing(repo) as r:
         (remote_name, remote_location) = get_remote_repo(r, remote_location)
         if message is None:
-            message = b"fetch: from " + remote_location.encode("utf-8")
+            message = b"fetch: from " + remote_location.encode(DEFAULT_ENCODING)
         client, path = get_transport_and_path(
             remote_location, config=r.get_config_stack(), **kwargs
         )
blob - 553e9cd0bddfa016637e9ced37dd844a159ec1e0
blob + 4deb48b2af49dc431e3f93e60968ddbebde3c6f5
--- dulwich/protocol.py
+++ dulwich/protocol.py
@@ -238,6 +238,8 @@ class Protocol(object):
             if self.report_activity:
                 self.report_activity(size, "read")
             pkt_contents = read(size - 4)
+        except ConnectionResetError:
+            raise HangupException()
         except socket.error as e:
             raise GitProtocolError(e)
         else:
blob - 1a1fc547e1d0a4f8c074ca2bbc52ff21d66589a7
blob + c10826614b153b5db94651475fa54a823545aa55
--- dulwich/refs.py
+++ dulwich/refs.py
@@ -47,6 +47,14 @@ LOCAL_BRANCH_PREFIX = b"refs/heads/"
 LOCAL_TAG_PREFIX = b"refs/tags/"
 BAD_REF_CHARS = set(b"\177 ~^:?*[")
 ANNOTATED_TAG_SUFFIX = b"^{}"
+
+
+class SymrefLoop(Exception):
+    """There is a loop between one or more symrefs."""
+
+    def __init__(self, ref, depth):
+        self.ref = ref
+        self.depth = depth
 
 
 def parse_symref_value(contents):
@@ -231,7 +239,7 @@ class RefsContainer(object):
         for key in keys:
             try:
                 ret[key] = self[(base + b"/" + key).strip(b"/")]
-            except KeyError:
+            except (SymrefLoop, KeyError):
                 continue  # Unable to resolve
 
         return ret
@@ -294,20 +302,8 @@ class RefsContainer(object):
                 break
             depth += 1
             if depth > 5:
-                raise KeyError(name)
+                raise SymrefLoop(name, depth)
         return refnames, contents
-
-    def _follow(self, name):
-        import warnings
-
-        warnings.warn(
-            "RefsContainer._follow is deprecated. Use RefsContainer.follow " "instead.",
-            DeprecationWarning,
-        )
-        refnames, contents = self.follow(name)
-        if not refnames:
-            return (None, contents)
-        return (refnames[-1], contents)
 
     def __contains__(self, refname):
         if self.read_ref(refname):
@@ -511,12 +507,12 @@ class DictRefsContainer(RefsContainer):
 
     def add_if_new(
         self,
-        name,
-        ref,
+        name: bytes,
+        ref: bytes,
         committer=None,
         timestamp=None,
         timezone=None,
-        message=None,
+        message: Optional[bytes] = None,
     ):
         if name in self._refs:
             return False
@@ -883,12 +879,12 @@ class DiskRefsContainer(RefsContainer):
 
     def add_if_new(
         self,
-        name,
-        ref,
+        name: bytes,
+        ref: bytes,
         committer=None,
         timestamp=None,
         timezone=None,
-        message=None,
+        message: Optional[bytes] = None,
     ):
         """Add a new reference only if it does not already exist.
 
@@ -1137,7 +1133,11 @@ def _set_origin_head(refs, origin, origin_head):
             refs.set_symbolic_ref(origin_ref, target_ref)
 
 
-def _set_default_branch(refs, origin, origin_head, branch, ref_message):
+def _set_default_branch(
+        refs: RefsContainer, origin: bytes, origin_head: bytes, branch: bytes,
+        ref_message: Optional[bytes]) -> bytes:
+    """Set the default branch.
+    """
     origin_base = b"refs/remotes/" + origin + b"/"
     if branch:
         origin_ref = origin_base + branch
@@ -1151,7 +1151,7 @@ def _set_default_branch(refs, origin, origin_head, bra
             head_ref = LOCAL_TAG_PREFIX + branch
         else:
             raise ValueError(
-                "%s is not a valid branch or tag" % os.fsencode(branch)
+                "%r is not a valid branch or tag" % os.fsencode(branch)
             )
     elif origin_head:
         head_ref = origin_head
@@ -1165,6 +1165,8 @@ def _set_default_branch(refs, origin, origin_head, bra
             )
         except KeyError:
             pass
+    else:
+        raise ValueError('neither origin_head nor branch are provided')
     return head_ref
 
 
blob - 80c2ad575723a1452ebcc6bd4b34a2fe267fc7e1
blob + f3cc5947a9924148753d6e4554f2ae7736582e9c
--- dulwich/repo.py
+++ dulwich/repo.py
@@ -303,7 +303,7 @@ def _set_filesystem_hidden(path):
         if not SetFileAttributesW(path, FILE_ATTRIBUTE_HIDDEN):
             pass  # Could raise or log `ctypes.WinError()` here
 
-    # Could implement other platform specific filesytem hiding here
+    # Could implement other platform specific filesystem hiding here
 
 
 class ParentsProvider(object):
@@ -399,7 +399,7 @@ class BaseRepo(object):
         raise NotImplementedError(self._put_named_file)
 
     def _del_named_file(self, path):
-        """Delete a file in the contrl directory with the given name."""
+        """Delete a file in the control directory with the given name."""
         raise NotImplementedError(self._del_named_file)
 
     def open_index(self):
@@ -876,6 +876,7 @@ class BaseRepo(object):
         ref=b"HEAD",
         merge_heads=None,
         no_verify=False,
+        sign=False,
     ):
         """Create a new commit.
 
@@ -899,6 +900,9 @@ class BaseRepo(object):
           ref: Optional ref to commit to (defaults to current branch)
           merge_heads: Merge heads (defaults to .git/MERGE_HEAD)
           no_verify: Skip pre-commit and commit-msg hooks
+          sign: GPG Sign the commit (bool, defaults to False,
+            pass True to use default GPG key,
+            pass a str containing Key ID to use a specific GPG key)
 
         Returns:
           New commit SHA1
@@ -969,15 +973,21 @@ class BaseRepo(object):
             raise CommitError(e)
         except KeyError:  # no hook defined, message not modified
             c.message = message
+
+        keyid = sign if isinstance(sign, str) else None
 
         if ref is None:
             # Create a dangling commit
             c.parents = merge_heads
+            if sign:
+                c.sign(keyid)
             self.object_store.add_object(c)
         else:
             try:
                 old_head = self.refs[ref]
                 c.parents = [old_head] + merge_heads
+                if sign:
+                    c.sign(keyid)
                 self.object_store.add_object(c)
                 ok = self.refs.set_if_equals(
                     ref,
@@ -990,6 +1000,8 @@ class BaseRepo(object):
                 )
             except KeyError:
                 c.parents = merge_heads
+                if sign:
+                    c.sign(keyid)
                 self.object_store.add_object(c)
                 ok = self.refs.add_if_new(
                     ref,
@@ -1041,7 +1053,7 @@ class UnsupportedVersion(Exception):
 class Repo(BaseRepo):
     """A git repository backed by local disk.
 
-    To open an existing repository, call the contructor with
+    To open an existing repository, call the constructor with
     the path of the repository.
 
     To create a new repository, use the Repo.init class method.
@@ -1052,11 +1064,14 @@ class Repo(BaseRepo):
 
     Attributes:
 
-      path (str): Path to the working copy (if it exists) or repository control
-        directory (if the repository is bare)
-      bare (bool): Whether this is a bare repository
+      path: Path to the working copy (if it exists) or repository control
+        directory (if the repository is bare)
+      bare: Whether this is a bare repository
     """
 
+    path: str
+    bare: bool
+
     def __init__(
         self,
         root: str,
@@ -1065,11 +1080,11 @@ class Repo(BaseRepo):
     ) -> None:
         hidden_path = os.path.join(root, CONTROLDIR)
         if bare is None:
-            if (os.path.isfile(hidden_path) or
-                    os.path.isdir(os.path.join(hidden_path, OBJECTDIR))):
+            if (os.path.isfile(hidden_path)
+                    or os.path.isdir(os.path.join(hidden_path, OBJECTDIR))):
                 bare = False
-            elif (os.path.isdir(os.path.join(root, OBJECTDIR)) and
-                    os.path.isdir(os.path.join(root, REFSDIR))):
+            elif (os.path.isdir(os.path.join(root, OBJECTDIR))
+                    and os.path.isdir(os.path.join(root, REFSDIR))):
                 bare = True
             else:
                 raise NotGitRepository(
@@ -1356,7 +1371,7 @@ class Repo(BaseRepo):
         from dulwich.index import (
             IndexEntry,
             _fs_to_tree_path,
-            )
+        )
 
         index = self.open_index()
         try:
@@ -1375,7 +1390,7 @@ class Repo(BaseRepo):
                 tree_entry = self.object_store[tree_id].lookup_path(
                     self.object_store.__getitem__, tree_path)
             except KeyError:
-                # if tree_entry didnt exist, this file was being added, so
+                # if tree_entry didn't exist, this file was being added, so
                 # remove index entry
                 try:
                     del index[tree_path]
blob - 1c5b03526d737831f8872f51a0ceb8c74793d356
blob + ab5f3ec7f9931bad0bd426620502fce33dca91d3
--- dulwich/server.py
+++ dulwich/server.py
@@ -1068,7 +1068,7 @@ class ReceivePackHandler(PackHandler):
         client_refs = []
         ref = self.proto.read_pkt_line()
 
-        # if ref is none then client doesnt want to send us anything..
+        # if ref is none then client doesn't want to send us anything..
         if ref is None:
             return
 
blob - 1309ecf8cfb5dd6c274538d8b5c94b1fb54b04b2
blob + c1e093590e4f93b4ec4b8c329f5c256f9c6db414
--- dulwich/tests/__init__.py
+++ dulwich/tests/__init__.py
@@ -50,7 +50,7 @@ class TestCase(_TestCase):
     def setUp(self):
         super(TestCase, self).setUp()
         self._old_home = os.environ.get("HOME")
-        os.environ["HOME"] = "/nonexistant"
+        os.environ["HOME"] = "/nonexistent"
         os.environ["GIT_CONFIG_NOSYSTEM"] = "1"
 
     def tearDown(self):
blob - 2f9cad482119221abd0c24c55b4ca10bc7b7726b
blob + e9d5d2d169b195a3546aa33e229474629a402f21
--- dulwich/tests/compat/test_client.py
+++ dulwich/tests/compat/test_client.py
@@ -1,4 +1,4 @@
-# test_client.py -- Compatibilty tests for git client.
+# test_client.py -- Compatibility tests for git client.
 # Copyright (C) 2010 Google, Inc.
 #
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
@@ -18,7 +18,7 @@
 # License, Version 2.0.
 #
 
-"""Compatibilty tests between the Dulwich client and the cgit server."""
+"""Compatibility tests between the Dulwich client and the cgit server."""
 
 import copy
 from io import BytesIO
@@ -429,7 +429,12 @@ class DulwichTCPClientTest(CompatTestCase, DulwichClie
         def test_fetch_pack_no_side_band_64k(self):
             DulwichClientTestBase.test_fetch_pack_no_side_band_64k(self)
 
+    def test_send_remove_branch(self):
+        # This test fails intermittently on my machine, probably due to some sort
+        # of race condition. Probably also related to #1015
+        self.skipTest('skip flaky test; see #1015')
 
+
 class TestSSHVendor(object):
     @staticmethod
     def run_command(
blob - b05cad7d0302332a192dddeb0a64512a04d9ede1
blob + e0aed812aed92c29043ca7cb0b9fc0093d3b296e
--- dulwich/tests/compat/test_porcelain.py
+++ dulwich/tests/compat/test_porcelain.py
@@ -94,7 +94,7 @@ class TagCreateSignTestCase(PorcelainGpgTestCase, Comp
                 'GNUPGHOME': os.environ['GNUPGHOME'],
                 'GIT_COMMITTER_NAME': 'Joe Example',
                 'GIT_COMMITTER_EMAIL': 'joe@example.com',
-                },
+            },
         )
         tag = self.repo[b"refs/tags/verifyme"]
         self.assertNotEqual(tag.signature, None)
blob - bec350c7c68f4a937259f741e537d7be21dd6541
blob + 71a48fd691bb7dfd344632f49253cc2f1caee162
--- dulwich/tests/compat/utils.py
+++ dulwich/tests/compat/utils.py
@@ -43,7 +43,9 @@ from dulwich.tests import (
 _DEFAULT_GIT = "git"
 _VERSION_LEN = 4
 _REPOS_DATA_DIR = os.path.abspath(
-    os.path.join(os.path.dirname(__file__), os.pardir, "data", "repos")
+    os.path.join(
+        os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
+        "testdata", "repos")
 )
 
 
@@ -82,7 +84,7 @@ def require_git_version(required_version, git_path=_DE
 
     Args:
       required_version: A tuple of ints of the form (major, minor, point,
-        sub-point); ommitted components default to 0.
+        sub-point); omitted components default to 0.
       git_path: Path to the git executable; defaults to the version in
         the system path.
     Raises:
@@ -137,6 +139,7 @@ def run_git(
 
     env = popen_kwargs.pop("env", {})
     env["LC_ALL"] = env["LANG"] = "C"
+    env["PATH"] = os.getenv("PATH")
 
     args = [git_path] + args
     popen_kwargs["stdin"] = subprocess.PIPE
blob - 1942d238fd2e742022b145383e67863d02862c08 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/blobs/11/11111111111111111111111111111111111111 and /dev/null differ
blob - 8f8ed37f1e6b8f0af781c26daa8f31ae9bd2167d (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/blobs/6f/670c0fb53f9463760b7295fbb814e965fb20c8 and /dev/null differ
blob - 7bef12912d59b8fab01801f66978456947e6ce59 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/blobs/95/4a536f7819d40e6f637f849ee187dd10066349 and /dev/null differ
blob - 8c901c5b89f920a740af8b23b771ef4019cdb665 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/blobs/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 and /dev/null differ
blob - b4c3a1d99a4d24753afd83c3e179bd428b24764c (mode 755)
blob + /dev/null
--- dulwich/tests/data/commits/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
+++ /dev/null
@@ -1,2 +0,0 @@
-xK
-@])z53׮==-&e=i:"Z=H)r芔>4wYԯMx|q=s)&6Dh6{Ym/LXg?
\ No newline at end of file
blob - 69c6dff1ab6b88df371b5f539df78e705d1f05a5 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/commits/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc and /dev/null differ
blob - 9e1d72632e9aa355b6f3efed9aa13f3a1259c426 (mode 644)
blob + /dev/null
--- dulwich/tests/data/commits/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
+++ /dev/null
@@ -1,4 +0,0 @@
-x
-0E]+f/N"]g	*5"O.υɗia	UBERr[P\ʋ

-Tz靖-zN0Q

-)ZOEv,pIop['lǺ<|fֶk)PGX{&K0?yMQ
\ No newline at end of file
blob - 96f9998c0a1883d2b96b5088650eec063a5d3e97 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/indexes/index and /dev/null differ
blob - ca0454de928844663ddb20a0369f1d9af0391c72 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.idx and /dev/null differ
blob - e2754b2777a048fd7e83b22d77a89462c2394d2f (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.pack and /dev/null differ
blob - 26cc331fb8748e2cf588db343a1c2f8242d4b692 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-*.export eol=lf
blob - cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/a.git/HEAD
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/master
blob - f13a21bc5735e6b8bcf4423ce6645fcf32a1fafd (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/a.git/objects/28/237f4dc30d0d462658d6b937b08a0f0b6ef55a
+++ /dev/null
@@ -1,2 +0,0 @@
-x5A
-0a9\@i""L1T"uPMA7o~2(0H\uB\]MNc+H!0&5Zi-)~	ߓ~ÏsP~Gl֮`јkN0
\ No newline at end of file
blob - dfc9847b6f39818fb3a16abd03a52cca8b0488df (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/2a/72d929692c41d8554c07f6301757ba18a65d91 and /dev/null differ
blob - 00d4a694a367c8bef7c4fae99f1f18e6edfa1e53 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/4e/f30bbfe26431a69c3820d3a683df54d688f2ec and /dev/null differ
blob - 522a3def4bbdd7178cd5f5758fd7f51831e394ea (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/4f/2e6529203aa6d44b5af6e3292c837ceda003f9 and /dev/null differ
blob - 8a8432a9e223891b4250ec4ac8631b55b346017d (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/7d/9a07d797595ef11344549b8d08198e48c15364 and /dev/null differ
blob - a044c5926c95969c522e5fabc37161ca65232d57 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/a2/96d0bb611188cabb256919f36bc30117cca005 and /dev/null differ
blob - 7d172f39d4e00239ca900fc7f1b94ee2f145db36 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/a9/0fa2d900a17e99b433217e988c4eb4a2e9a097 and /dev/null differ
blob - e8b676a287932c6cce97f02dbf8ae805babc6f72 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/a.git/objects/b0/931cadc54336e78a1d980420e3268903b57a50
+++ /dev/null
@@ -1,3 +0,0 @@
-x-[
-0**I75T[oRWo
-w*`e/i7sjpیhjkL[c7L><2ݏ 1Jrtqصh̰ɾ֥2v
\ No newline at end of file
blob - 81b997b636b0ddc243de5d5c11eb9e4a93450bf2 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/a.git/objects/ff/d47d45845a8f6576491e1edb97e3fe6a850e7f and /dev/null differ
blob - daf2fc46e6e666c83d5da3aeb78a10abdcfd37b1 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/a.git/packed-refs
+++ /dev/null
@@ -1,3 +0,0 @@
-# pack-refs with: peeled 
-b0931cadc54336e78a1d980420e3268903b57a50 refs/tags/mytag-packed
-^2a72d929692c41d8554c07f6301757ba18a65d91
blob - e28347ea6dc57992bb974ef79daa09d8c760e075 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/a.git/refs/heads/master
+++ /dev/null
@@ -1 +0,0 @@
-a90fa2d900a17e99b433217e988c4eb4a2e9a097
blob - cb5c1106d769d9358863fd29b0d869de55b9ce83 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/a.git/refs/tags/mytag
+++ /dev/null
@@ -1 +0,0 @@
-28237f4dc30d0d462658d6b937b08a0f0b6ef55a
blob - cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/empty.git/HEAD
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/master
blob - 90e16477bddfa48ec34154f9d7211aac4e32a511 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/empty.git/config
+++ /dev/null
@@ -1,7 +0,0 @@
-[core]
-	repositoryformatversion = 0
-	filemode = false
-	bare = true
-	symlinks = false
-	ignorecase = true
-	hideDotFiles = dotGitOnly
blob - c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/empty.git/objects/info/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
\ No newline at end of file
blob - c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/empty.git/objects/pack/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
\ No newline at end of file
blob - c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/empty.git/refs/heads/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
\ No newline at end of file
blob - c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/empty.git/refs/tags/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
\ No newline at end of file
blob - 48bdc140b6ec1a433f0d765bbb5affb6fd0200a3 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/issue88_expect_ack_nak_client.export
+++ /dev/null
@@ -1,260 +0,0 @@
-reset refs/heads/master
-commit refs/heads/master
-mark :1
-author User <user@localhost> 1427183369 +1300
-committer User <user@localhost> 1427183369 +1300
-data 6
-empty
-
-blob
-mark :2
-data 35
-We will reproduce a problem here.
-
-commit refs/heads/master
-mark :3
-author User <user@localhost> 1427183376 +1300
-committer User <user@localhost> 1427183376 +1300
-data 11
-demo file.
-from :1
-M 100644 :2 demo.txt
-
-blob
-mark :4
-data 62
-We will reproduce a problem here.
-
-This will take some time.
-
-commit refs/heads/master
-mark :5
-author User <user@localhost> 1427185135 +1300
-committer User <user@localhost> 1427185135 +1300
-data 13
-added a line
-from :3
-M 100644 :4 demo.txt
-
-blob
-mark :6
-data 57
-We will reproduce a problem here.
-
-We will change these.
-
-commit refs/heads/master
-mark :7
-author User <user@localhost> 1427185245 +1300
-committer User <user@localhost> 1427185245 +1300
-data 14
-replace a line
-from :5
-M 100644 :6 demo.txt
-
-blob
-mark :8
-data 52
-We will change these.
-
-Then issues will be proven.
-
-commit refs/heads/master
-mark :9
-author User <user@localhost> 1427185343 +1300
-committer User <user@localhost> 1427185343 +1300
-data 13
-Yes we will.
-from :7
-M 100644 :8 demo.txt
-
-blob
-mark :10
-data 69
-We will change these. 
-
-Then issues will be proven once and for all.
-
-commit refs/heads/master
-mark :11
-author User <user@localhost> 1427185440 +1300
-committer User <user@localhost> 1427185440 +1300
-data 6
-sure.
-from :9
-M 100644 :10 demo.txt
-
-blob
-mark :12
-data 0
-
-commit refs/heads/master
-mark :13
-author User <user@localhost> 1427185512 +1300
-committer User <user@localhost> 1427185516 +1300
-data 26
-not an actual readme, yet
-from :11
-M 100644 :12 readme.txt
-
-blob
-mark :14
-data 61
-This will for sure we will prove a problem exist somewhere.
-
-blob
-mark :15
-data 49
-okay fine add something here this is only a test
-
-commit refs/heads/master
-mark :16
-author User <user@localhost> 1427185569 +1300
-committer User <user@localhost> 1427185569 +1300
-data 12
-more things
-from :13
-M 100644 :14 demo.txt
-M 100644 :15 readme.txt
-
-blob
-mark :17
-data 100
-This will for sure we will prove a problem exist somewhere. 
-
-Just that we need a few more commits.
-
-commit refs/heads/master
-mark :18
-author User <user@localhost> 1427185659 +1300
-committer User <user@localhost> 1427185659 +1300
-data 13
-one more try
-from :16
-M 100644 :17 demo.txt
-
-blob
-mark :19
-data 54
-It might have something to do with number of commits?
-
-commit refs/heads/master
-mark :20
-author User <user@localhost> 1427185905 +1300
-committer User <user@localhost> 1427185905 +1300
-data 18
-is this number 9?
-from :18
-M 100644 :19 commitcount
-
-blob
-mark :21
-data 123
-This will for sure we will prove a problem exist somewhere. 
-
-Just that we need a few more commits.
-
-Hey look we need more
-
-commit refs/heads/master
-mark :22
-author User <user@localhost> 1427185922 +1300
-committer User <user@localhost> 1427185922 +1300
-data 5
-cool
-from :20
-M 100644 :21 demo.txt
-
-blob
-mark :23
-data 50
-Okay fine add something here this is only a test.
-
-commit refs/heads/master
-mark :24
-author User <user@localhost> 1427185936 +1300
-committer User <user@localhost> 1427185936 +1300
-data 7
-readme
-from :22
-M 100644 :23 readme.txt
-
-blob
-mark :25
-data 74
-Okay come on this is getting boring.
-
-Yes I went and edit all the things.
-
-commit refs/heads/master
-mark :26
-author User <user@localhost> 1427185954 +1300
-committer User <user@localhost> 1427185954 +1300
-data 14
-remove a line
-from :24
-M 100644 :25 demo.txt
-
-blob
-mark :27
-data 186
-Okay come on this is getting boring. 
-
-Yes I went and edit all the things. 
-
-Of course, making test data can be somewhat tedious, especially a
-minimum set that can be easily reproduced.
-
-commit refs/heads/master
-mark :28
-author User <user@localhost> 1427185996 +1300
-committer User <user@localhost> 1427185996 +1300
-data 25
-Getting serious mode on.
-from :26
-M 100644 :27 demo.txt
-
-blob
-mark :29
-data 48
-This is taking a bit longer than I remembered.
-
-commit refs/heads/master
-mark :30
-author User <user@localhost> 1427186065 +1300
-committer User <user@localhost> 1427186065 +1300
-data 40
-At least we will have things minimized.
-from :28
-M 100644 :29 demo.txt
-
-blob
-mark :31
-data 11
-there yet?
-
-commit refs/heads/master
-mark :32
-author User <user@localhost> 1427186080 +1300
-committer User <user@localhost> 1427186080 +1300
-data 7
-are we
-from :30
-M 100644 :31 demo.txt
-
-blob
-mark :33
-data 237
-This should be the head commit for the client repo for testing out
-the failure case reported in issue 88.  Just do a git pull from the
-repo that includes the following commit that is hosted with dulwich.
-The issue should be reproduced.
-
-commit refs/heads/master
-mark :34
-author User <user@localhost> 1427186109 +1300
-committer User <user@localhost> 1427186109 +1300
-data 6
-okay?
-from :32
-M 100644 :33 readme.txt
blob - 6897693c08c6d44ed57dd8af26d9aeb45379679b (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/issue88_expect_ack_nak_other.export
+++ /dev/null
@@ -1,293 +0,0 @@
-blob
-mark :1
-data 33
-We will sneak in a blob like so.
-
-reset refs/heads/master
-commit refs/heads/master
-mark :2
-author User <user@localhost> 1427183369 +1300
-committer User <user@localhost> 1427183369 +1300
-data 7
-sneaky
-M 100644 :1 problem.questionmark
-
-blob
-mark :3
-data 35
-We will introduce a problem here.
-
-
-commit refs/heads/master
-mark :4
-author User <user@localhost> 1427183376 +1300
-committer User <user@localhost> 1427183376 +1300
-data 11
-demo file.
-from :2
-M 100644 :3 demo.rst
-
-blob
-mark :5
-data 62
-We will introduce a problem here.
-
-This will take some time.
-
-
-commit refs/heads/master
-mark :6
-author User <user@localhost> 1427185135 +1300
-committer User <user@localhost> 1427185135 +1300
-data 13
-added a line
-from :4
-M 100644 :5 demo.rst
-
-blob
-mark :7
-data 57
-We will introduce a problem here.
-
-We will change these.
-
-commit refs/heads/master
-mark :8
-author User <user@localhost> 1427185245 +1300
-committer User <user@localhost> 1427185245 +1300
-data 14
-replace a linefrom :6
-M 100644 :7 demo.rst
-
-blob
-mark :9
-data 52
-We will change these.
-
-Then issues will be proven.
-
-
-commit refs/heads/master
-mark :10
-author User <user@localhost> 1427185343 +1300
-committer User <user@localhost> 1427185343 +1300
-data 13
-Yes we will.
-from :8
-M 100644 :9 demo.rst
-
-blob
-mark :11
-data 72
-We will change these. 
-
-Then issues will be construed once and for all.
-
-commit refs/heads/master
-mark :12
-author User <user@localhost> 1427185440 +1300
-committer User <user@localhost> 1427185440 +1300
-data 6
-sure.
-from :10
-M 100644 :11 demo.rst
-
-blob
-mark :13
-data 0
-
-commit refs/heads/master
-mark :14
-author User <user@localhost> 1427185512 +1300
-committer User <user@localhost> 1427185516 +1300
-data 26
-not an actual readme, yet
-from :12
-M 100644 :13 emdaer.txt
-
-blob
-mark :15
-data 58
-This will for sure we will prove issues exist somewhere.
-
-
-blob
-mark :16
-data 49
-okay fine add something here this is only a test
-
-commit refs/heads/master
-mark :17
-author User <user@localhost> 1427185569 +1300
-committer User <user@localhost> 1427185569 +1300
-data 12
-more things
-from :14
-M 100644 :15 demo.rst
-M 100644 :16 emdaer.txt
-
-blob
-mark :18
-data 97
-This will for sure prove issue exist somewhere.
-
-Just that we need a few more commits as usual.
-
-
-commit refs/heads/master
-mark :19
-author User <user@localhost> 1427185659 +1300
-committer User <user@localhost> 1427185659 +1300
-data 13
-one more try
-from :17
-M 100644 :18 demo.rst
-
-blob
-mark :20
-data 54
-It might have something to do with number of commits?
-
-commit refs/heads/master
-mark :21
-author User <user@localhost> 1427185905 +1300
-committer User <user@localhost> 1427185905 +1300
-data 18
-is this number 9?
-from :19
-M 100644 :20 count
-
-blob
-mark :22
-data 119
-This will for sure we will prove issues exist somewhere.
-
-Just that we need a few more commits.
-
-Hey look we need more
-
-commit refs/heads/master
-mark :23
-author User <user@localhost> 1427185922 +1300
-committer User <user@localhost> 1427185922 +1300
-data 5
-cool
-from :21
-M 100644 :22 demo.rst
-
-blob
-mark :24
-data 50
-Okay fine add something here this is only a test.
-
-commit refs/heads/master
-mark :25
-author User <user@localhost> 1427185936 +1300
-committer User <user@localhost> 1427185936 +1300
-data 7
-readme
-from :23
-M 100644 :24 emdaer.txt
-
-blob
-mark :26
-data 74
-Okay come on this is getting boring.
-
-Yes I went and edit all the things.
-
-commit refs/heads/master
-mark :27
-author User <user@localhost> 1427185954 +1300
-committer User <user@localhost> 1427185954 +1300
-data 14
-remove a line
-from :25
-M 100644 :26 demo.rst
-
-blob
-mark :28
-data 186
-Okay come on this is getting boring. 
-
-Yes I went and edit all the things. 
-
-Of course, making test data can be somewhat tedious, especially a
-minimum set that can be easily reproduced.
-
-commit refs/heads/master
-mark :29
-author User <user@localhost> 1427185996 +1300
-committer User <user@localhost> 1427185996 +1300
-data 25
-Getting serious mode on.
-from :27
-M 100644 :28 demo.rst
-
-blob
-mark :30
-data 48
-This is taking a bit longer than I remembered.
-
-
-commit refs/heads/master
-mark :31
-author User <user@localhost> 1427186065 +1300
-committer User <user@localhost> 1427186065 +1300
-data 40
-At least we will have things minimized.
-from :29
-M 100644 :30 demo.rst
-
-blob
-mark :32
-data 11
-there yet?
-
-commit refs/heads/master
-mark :33
-author User <user@localhost> 1427186080 +1300
-committer User <user@localhost> 1427186080 +1300
-data 7
-are we
-from :31
-M 100644 :32 demo.rst
-
-blob
-mark :34
-data 237
-This should be the head commit for the client repo for testing out
-the failure case reported in issue 88.  Just do a git pull from the
-repo that includes the following commit that is hosted with dulwich.
-The issue should be reproduced.
-
-
-commit refs/heads/master
-mark :35
-author User <user@localhost> 1427186109 +1300
-committer User <user@localhost> 1427186109 +1300
-data 6
-okay?
-from :33
-M 100644 :34 emdaer.txt
-
-blob
-mark :36
-data 394
-This should be the commit that will trigger the bug noted in issue 88
-(https://github.com/jelmer/dulwich/issues/88).  To reproduce, run git
-fast-import using this fast-export and host this using dulwich, and
-then make a copy of this, strip out this blob and the following commit
-block, import to another git repo and then git clone from the previous.
-
-Naturally, this is part of the test case.
-
-commit refs/heads/master
-mark :37
-author User <user@localhost> 1427244891 +1300
-committer User <user@localhost> 1427248186 +1300
-data 49
-Added instructions on how to use this to readme.
-from :35
-M 100644 :36 emdaer.txt
-
blob - 0124fb32fe9d31db068b58fbaa68dbcf880fd3cb (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/issue88_expect_ack_nak_server.export
+++ /dev/null
@@ -1,281 +0,0 @@
-reset refs/heads/master
-commit refs/heads/master
-mark :1
-author User <user@localhost> 1427183369 +1300
-committer User <user@localhost> 1427183369 +1300
-data 6
-empty
-
-blob
-mark :2
-data 35
-We will reproduce a problem here.
-
-commit refs/heads/master
-mark :3
-author User <user@localhost> 1427183376 +1300
-committer User <user@localhost> 1427183376 +1300
-data 11
-demo file.
-from :1
-M 100644 :2 demo.txt
-
-blob
-mark :4
-data 62
-We will reproduce a problem here.
-
-This will take some time.
-
-commit refs/heads/master
-mark :5
-author User <user@localhost> 1427185135 +1300
-committer User <user@localhost> 1427185135 +1300
-data 13
-added a line
-from :3
-M 100644 :4 demo.txt
-
-blob
-mark :6
-data 57
-We will reproduce a problem here.
-
-We will change these.
-
-commit refs/heads/master
-mark :7
-author User <user@localhost> 1427185245 +1300
-committer User <user@localhost> 1427185245 +1300
-data 14
-replace a line
-from :5
-M 100644 :6 demo.txt
-
-blob
-mark :8
-data 52
-We will change these.
-
-Then issues will be proven.
-
-commit refs/heads/master
-mark :9
-author User <user@localhost> 1427185343 +1300
-committer User <user@localhost> 1427185343 +1300
-data 13
-Yes we will.
-from :7
-M 100644 :8 demo.txt
-
-blob
-mark :10
-data 69
-We will change these. 
-
-Then issues will be proven once and for all.
-
-commit refs/heads/master
-mark :11
-author User <user@localhost> 1427185440 +1300
-committer User <user@localhost> 1427185440 +1300
-data 6
-sure.
-from :9
-M 100644 :10 demo.txt
-
-blob
-mark :12
-data 0
-
-commit refs/heads/master
-mark :13
-author User <user@localhost> 1427185512 +1300
-committer User <user@localhost> 1427185516 +1300
-data 26
-not an actual readme, yet
-from :11
-M 100644 :12 readme.txt
-
-blob
-mark :14
-data 61
-This will for sure we will prove a problem exist somewhere.
-
-blob
-mark :15
-data 49
-okay fine add something here this is only a test
-
-commit refs/heads/master
-mark :16
-author User <user@localhost> 1427185569 +1300
-committer User <user@localhost> 1427185569 +1300
-data 12
-more things
-from :13
-M 100644 :14 demo.txt
-M 100644 :15 readme.txt
-
-blob
-mark :17
-data 100
-This will for sure we will prove a problem exist somewhere. 
-
-Just that we need a few more commits.
-
-commit refs/heads/master
-mark :18
-author User <user@localhost> 1427185659 +1300
-committer User <user@localhost> 1427185659 +1300
-data 13
-one more try
-from :16
-M 100644 :17 demo.txt
-
-blob
-mark :19
-data 54
-It might have something to do with number of commits?
-
-commit refs/heads/master
-mark :20
-author User <user@localhost> 1427185905 +1300
-committer User <user@localhost> 1427185905 +1300
-data 18
-is this number 9?
-from :18
-M 100644 :19 commitcount
-
-blob
-mark :21
-data 123
-This will for sure we will prove a problem exist somewhere. 
-
-Just that we need a few more commits.
-
-Hey look we need more
-
-commit refs/heads/master
-mark :22
-author User <user@localhost> 1427185922 +1300
-committer User <user@localhost> 1427185922 +1300
-data 5
-cool
-from :20
-M 100644 :21 demo.txt
-
-blob
-mark :23
-data 50
-Okay fine add something here this is only a test.
-
-commit refs/heads/master
-mark :24
-author User <user@localhost> 1427185936 +1300
-committer User <user@localhost> 1427185936 +1300
-data 7
-readme
-from :22
-M 100644 :23 readme.txt
-
-blob
-mark :25
-data 74
-Okay come on this is getting boring.
-
-Yes I went and edit all the things.
-
-commit refs/heads/master
-mark :26
-author User <user@localhost> 1427185954 +1300
-committer User <user@localhost> 1427185954 +1300
-data 14
-remove a line
-from :24
-M 100644 :25 demo.txt
-
-blob
-mark :27
-data 186
-Okay come on this is getting boring. 
-
-Yes I went and edit all the things. 
-
-Of course, making test data can be somewhat tedious, especially a
-minimum set that can be easily reproduced.
-
-commit refs/heads/master
-mark :28
-author User <user@localhost> 1427185996 +1300
-committer User <user@localhost> 1427185996 +1300
-data 25
-Getting serious mode on.
-from :26
-M 100644 :27 demo.txt
-
-blob
-mark :29
-data 48
-This is taking a bit longer than I remembered.
-
-commit refs/heads/master
-mark :30
-author User <user@localhost> 1427186065 +1300
-committer User <user@localhost> 1427186065 +1300
-data 40
-At least we will have things minimized.
-from :28
-M 100644 :29 demo.txt
-
-blob
-mark :31
-data 11
-there yet?
-
-commit refs/heads/master
-mark :32
-author User <user@localhost> 1427186080 +1300
-committer User <user@localhost> 1427186080 +1300
-data 7
-are we
-from :30
-M 100644 :31 demo.txt
-
-blob
-mark :33
-data 237
-This should be the head commit for the client repo for testing out
-the failure case reported in issue 88.  Just do a git pull from the
-repo that includes the following commit that is hosted with dulwich.
-The issue should be reproduced.
-
-commit refs/heads/master
-mark :34
-author User <user@localhost> 1427186109 +1300
-committer User <user@localhost> 1427186109 +1300
-data 6
-okay?
-from :32
-M 100644 :33 readme.txt
-
-blob
-mark :35
-data 394
-This should be the commit that will trigger the bug noted in issue 88
-(https://github.com/jelmer/dulwich/issues/88).  To reproduce, run git
-fast-import using this fast-export and host this using dulwich, and
-then make a copy of this, strip out this blob and the following commit
-block, import to another git repo and then git clone from the previous.
-
-Naturally, this is part of the test case.
-
-commit refs/heads/master
-mark :36
-author User <user@localhost> 1427244891 +1300
-committer User <user@localhost> 1427248186 +1300
-data 49
-Added instructions on how to use this to readme.
-from :34
-M 100644 :35 readme.txt
-
blob - cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/ooo_merge.git/HEAD
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/master
blob - cbe43c2ebd526cac5d66a4d0f614abf4d77c3e59 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b and /dev/null differ
blob - 3f3699b36e39f0046a19227248c45215b0f5e452 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/38/74e9c60a6d149c44c928140f250d81e6381520 and /dev/null differ
blob - 95c3c19bbeaeda10c619530ac6ea902ae1c0cdd4 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8 and /dev/null differ
blob - 2569779c10cc06f9e3639d73183c80b6fbd8f243 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6 and /dev/null differ
blob - e88303b28d023fc86c939c7480d259df081ea402 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/ooo_merge.git/objects/76/01d7f6231db6a57f7bbb79ee52e4d462fd44d1
+++ /dev/null
@@ -1,3 +0,0 @@
-xAj1E)L%[PJօ46CftxӲzL

-`MH*[dL:^l8++Pb+46nhb&ei?:檵SH@mD
-r_-ᗮxMY_~{aU*Z{<Fx0<w_LY
\ No newline at end of file
blob - 610b7dfa3a19490625df89ba689ce6efe928d9eb (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870 and /dev/null differ
blob - 7bef12912d59b8fab01801f66978456947e6ce59 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349 and /dev/null differ
blob - 165943d626144b8c1aa9859bfac35e4cb7afda09 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/b2/a2766a2879c209ab1176e7e778b81ae422eeaa and /dev/null differ
blob - 4438cdd7cad0280b0d0e50bc44d975d20f441b0e (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/f5/07291b64138b875c28e03469025b1ea20bc614 and /dev/null differ
blob - d5a5677b135f4c653d3853bdb9b5c5b4d8dc95f8 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/ooo_merge.git/objects/f9/e39b120c68182a4ba35349f832d0e4e61f485c
+++ /dev/null
@@ -1,3 +0,0 @@
-x
-0ay$Yb6)=7qB)>2

-CW%>rT&I@$P5iX莥7y w:^o_|Q[IZSsVIEy?
\ No newline at end of file
blob - 5bda024ac879ef55da75751f82e4c7e9e417ad69 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/ooo_merge.git/objects/fb/5b0425c7ce46959bec94d54b9a157645e114f5 and /dev/null differ
blob - 34c0a7949167c0d22872f6e5a5707a4cbe1621c4 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/ooo_merge.git/refs/heads/master
+++ /dev/null
@@ -1 +0,0 @@
-7601d7f6231db6a57f7bbb79ee52e4d462fd44d1
blob - cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/HEAD
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/master
blob - 6160481e0d0f5c125699a9e97daba274708564ec (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/refs.git/objects/3b/9e5457140e738c2dcd39bf6d7acf88379b90d1 and /dev/null differ
blob - 478d0379acec45352e22d368d2b6711b9285ef36 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/objects/3e/c9c43c84ff242e3ef4a9fc5bc111fd780a76a8
+++ /dev/null
@@ -1,3 +0,0 @@
-x-Q
-0D)-mV^i66.~{#Cm]rwyu=u5^[o<H<*y?ƴ,()a߈2<)$8xR.4YktPaԵ
-q?W)'ǧ6
\ No newline at end of file
blob - 1be455a300b50e3b834ff22744acb008d64ca20a (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/refs.git/objects/42/d06bd4b77fed026b154d16493e5deab78f02ec and /dev/null differ
blob - 7e69c0efaf3324e3bde674e3bc90b4370466e315 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/refs.git/objects/a1/8114c31713746a33a2e70d9914d1ef3e781425 and /dev/null differ
blob - 777d49daa06e828ec64f48b9f3c14396d3fae3a5 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/objects/cd/a609072918d7b70057b6bef9f4c2537843fcfe
+++ /dev/null
@@ -1,6 +0,0 @@
-x-Q
-0D)m7iV^i6bIEOo
-~c`Av.;Zyku<*^Zox\T4
-<	4.Lam
-Fj#e/s=SRYBcQk

-eZ-\r?)Y9
\ No newline at end of file
blob - e40b2074233e9aef431a50bf761970cb7ccc50d9 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/refs.git/objects/df/6800012397fb85c56e7418dd4eb9405dee075c and /dev/null differ
blob - bf2f9e36e89d2085d2a1f03e477d1344b1be8a2f (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/packed-refs
+++ /dev/null
@@ -1,4 +0,0 @@
-# pack-refs with: peeled 
-df6800012397fb85c56e7418dd4eb9405dee075c refs/tags/refs-0.1
-^42d06bd4b77fed026b154d16493e5deab78f02ec
-42d06bd4b77fed026b154d16493e5deab78f02ec refs/heads/packed
blob - 2623a489ebb544326db1702067b25f08c63e517e (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/refs/heads/40-char-ref-aaaaaaaaaaaaaaaaaa
+++ /dev/null
@@ -1 +0,0 @@
-42d06bd4b77fed026b154d16493e5deab78f02ec
blob - ed2f1caab8d539a193a6167080d82e8ac540d2b4 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/refs/heads/loop
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/loop
blob - 2623a489ebb544326db1702067b25f08c63e517e (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/refs/heads/master
+++ /dev/null
@@ -1 +0,0 @@
-42d06bd4b77fed026b154d16493e5deab78f02ec
blob - 7ac75665748fe576c76fa53d29733f1f62ba2bd3 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/refs.git/refs/tags/refs-0.2
+++ /dev/null
@@ -1 +0,0 @@
-3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8
blob - 25d48cae10efef0a108d2e255e836223bced827e (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/server_new.export
+++ /dev/null
@@ -1,99 +0,0 @@
-blob
-mark :1
-data 13
-foo contents
-
-reset refs/heads/master
-commit refs/heads/master
-mark :2
-author Dave Borowitz <dborowitz@google.com> 1265755064 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755064 -0800
-data 16
-initial checkin
-M 100644 :1 foo
-
-blob
-mark :3
-data 13
-baz contents
-
-blob
-mark :4
-data 21
-updated foo contents
-
-commit refs/heads/master
-mark :5
-author Dave Borowitz <dborowitz@google.com> 1265755140 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755140 -0800
-data 15
-master checkin
-from :2
-M 100644 :3 baz
-M 100644 :4 foo
-
-blob
-mark :6
-data 24
-updated foo contents v2
-
-commit refs/heads/master
-mark :7
-author Dave Borowitz <dborowitz@google.com> 1265755287 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755287 -0800
-data 17
-master checkin 2
-from :5
-M 100644 :6 foo
-
-blob
-mark :8
-data 24
-updated foo contents v3
-
-commit refs/heads/master
-mark :9
-author Dave Borowitz <dborowitz@google.com> 1265755295 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755295 -0800
-data 17
-master checkin 3
-from :7
-M 100644 :8 foo
-
-blob
-mark :10
-data 22
-branched bar contents
-
-blob
-mark :11
-data 22
-branched foo contents
-
-commit refs/heads/branch
-mark :12
-author Dave Borowitz <dborowitz@google.com> 1265755111 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755111 -0800
-data 15
-branch checkin
-from :2
-M 100644 :10 bar
-M 100644 :11 foo
-
-blob
-mark :13
-data 25
-branched bar contents v2
-
-commit refs/heads/branch
-mark :14
-author Dave Borowitz <dborowitz@google.com> 1265755319 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755319 -0800
-data 17
-branch checkin 2
-from :12
-M 100644 :13 bar
-
-reset refs/heads/master
-from :9
-
blob - b02a3391baf0453d7341623ba62f814edb2769c7 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/server_old.export
+++ /dev/null
@@ -1,57 +0,0 @@
-blob
-mark :1
-data 13
-foo contents
-
-reset refs/heads/master
-commit refs/heads/master
-mark :2
-author Dave Borowitz <dborowitz@google.com> 1265755064 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755064 -0800
-data 16
-initial checkin
-M 100644 :1 foo
-
-blob
-mark :3
-data 22
-branched bar contents
-
-blob
-mark :4
-data 22
-branched foo contents
-
-commit refs/heads/branch
-mark :5
-author Dave Borowitz <dborowitz@google.com> 1265755111 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755111 -0800
-data 15
-branch checkin
-from :2
-M 100644 :3 bar
-M 100644 :4 foo
-
-blob
-mark :6
-data 13
-baz contents
-
-blob
-mark :7
-data 21
-updated foo contents
-
-commit refs/heads/master
-mark :8
-author Dave Borowitz <dborowitz@google.com> 1265755140 -0800
-committer Dave Borowitz <dborowitz@google.com> 1265755140 -0800
-data 15
-master checkin
-from :2
-M 100644 :6 baz
-M 100644 :7 foo
-
-reset refs/heads/master
-from :8
-
blob - cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/simple_merge.git/HEAD
+++ /dev/null
@@ -1 +0,0 @@
-ref: refs/heads/master
blob - b4c3a1d99a4d24753afd83c3e179bd428b24764c (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/simple_merge.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
+++ /dev/null
@@ -1,2 +0,0 @@
-xK
-@])z53׮==-&e=i:"Z=H)r芔>4wYԯMx|q=s)&6Dh6{Ym/LXg?
\ No newline at end of file
blob - a2247b203f986dcb9c22184b1aae17353960c5b4 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853 and /dev/null differ
blob - 3dec0bf77a94a1664e95bd056f41ddb563072c89 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b and /dev/null differ
blob - a73c46c7b6800a9df41452174604dcf600efd325 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/simple_merge.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6
+++ /dev/null
@@ -1,4 +0,0 @@
-xK
-1D]L~=pݝŒ2Fo-U:.UUAI!KU.!kj

-FN

-*R{QofwSQ[!)G;]g8Шw863Mˇ/_m1 tSK0]i*'pCO
\ No newline at end of file
blob - 69c6dff1ab6b88df371b5f539df78e705d1f05a5 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc and /dev/null differ
blob - 9e1d72632e9aa355b6f3efed9aa13f3a1259c426 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/simple_merge.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
+++ /dev/null
@@ -1,4 +0,0 @@
-x
-0E]+f/N"]g	*5"O.υɗia	UBERr[P\ʋ

-Tz靖-zN0Q

-)ZOEv,pIop['lǺ<|fֶk)PGX{&K0?yMQ
\ No newline at end of file
blob - 8f8ed37f1e6b8f0af781c26daa8f31ae9bd2167d (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8 and /dev/null differ
blob - 2569779c10cc06f9e3639d73183c80b6fbd8f243 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6 and /dev/null differ
blob - 610b7dfa3a19490625df89ba689ce6efe928d9eb (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870 and /dev/null differ
blob - 7bef12912d59b8fab01801f66978456947e6ce59 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349 and /dev/null differ
blob - 67706b5810550b826b747525ac1924499c87a6d2 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/simple_merge.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd
+++ /dev/null
@@ -1,2 +0,0 @@
-xM
-0]$cL";/:3bmo|6=~SgU@c̍bUA)ƙ{ulR+ld&z7ږm{IiKt.pmQx?|ݨ!־c仴?s9r4/mO+
\ No newline at end of file
blob - d45835e8201d096288a79b38053dbe259d5475b5 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7 and /dev/null differ
blob - dce887e80f980d37bb634a0e7c99c5e062292fbd (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86 and /dev/null differ
blob - 8c901c5b89f920a740af8b23b771ef4019cdb665 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/repos/simple_merge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 and /dev/null differ
blob - c6b8285fbdb5a417f490579d79252fc6d2d03c97 (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/simple_merge.git/refs/heads/master
+++ /dev/null
@@ -1 +0,0 @@
-5dac377bdded4c9aeb8dff595f0faeebcc8498cc
blob - c493b47db9e7c5cf34da754182f3b099e44a64fc (mode 644)
blob + /dev/null
--- dulwich/tests/data/repos/submodule/dotgit
+++ /dev/null
@@ -1 +0,0 @@
-gitdir: ./a.git
blob - 8c85e3a618f862f3df42941e44592d22f75f7695 (mode 644)
blob + /dev/null
--- dulwich/tests/data/tags/71/033db03a03c6a36721efcf1968dd8f8e0cf023
+++ /dev/null
@@ -1,5 +0,0 @@
-xmMO@=x#݅QnIMEQ
-mʿG'sL2OSz,$1vqnaJb+0u3mMr
-adɢs,=RB bY(֝cQ	Yjn!p	7#	ݜ5!X[GپM}n}]8m9pzd%
-!#f|X`fBKD%'sKCӝ5\<a5EDpDd-=n

-oKk=ʽn~6iM
\ No newline at end of file
blob - 2569779c10cc06f9e3639d73183c80b6fbd8f243 (mode 644)
blob + /dev/null
Binary files dulwich/tests/data/trees/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6 and /dev/null differ
blob - d8c00b557f47b03af641d3e2624c4142616b8c12
blob + 767fe94a49c684c5c481bf515a8b19c4892dfe1c
--- dulwich/tests/test_client.py
+++ dulwich/tests/test_client.py
@@ -52,7 +52,6 @@ from dulwich.client import (
     PLinkSSHVendor,
     HangupException,
     GitProtocolError,
-    apply_instead_of,
     check_wants,
     default_urllib3_manager,
     get_credentials_from_store,
@@ -1616,31 +1615,3 @@ And this line is just random noise, too.
                 ]
             ),
         )
-
-
-class ApplyInsteadOfTests(TestCase):
-    def test_none(self):
-        config = ConfigDict()
-        self.assertEqual(
-            'https://example.com/', apply_instead_of(config, 'https://example.com/'))
-
-    def test_apply(self):
-        config = ConfigDict()
-        config.set(
-            ('url', 'https://samba.org/'), 'insteadOf', 'https://example.com/')
-        self.assertEqual(
-            'https://samba.org/',
-            apply_instead_of(config, 'https://example.com/'))
-
-    def test_apply_multiple(self):
-        config = ConfigDict()
-        config.set(
-            ('url', 'https://samba.org/'), 'insteadOf', 'https://blah.com/')
-        config.set(
-            ('url', 'https://samba.org/'), 'insteadOf', 'https://example.com/')
-        self.assertEqual(
-            [b'https://blah.com/', b'https://example.com/'],
-            list(config.get_multivar(('url', 'https://samba.org/'), 'insteadOf')))
-        self.assertEqual(
-            'https://samba.org/',
-            apply_instead_of(config, 'https://example.com/'))
blob - 39f58c87262e96c0e28783e3f312a4fb7c29a5b9
blob + eea02e2c2e0e67ddfcd48c3e07731957da5f22fa
--- dulwich/tests/test_config.py
+++ dulwich/tests/test_config.py
@@ -36,6 +36,7 @@ from dulwich.config import (
     _escape_value,
     _parse_string,
     parse_submodules,
+    apply_instead_of,
 )
 from dulwich.tests import (
     TestCase,
@@ -103,6 +104,10 @@ class ConfigFileTests(TestCase):
         cf = self.from_file(b'[branch "foo#bar"] # a comment\nbar= foo\n')
         self.assertEqual(ConfigFile({(b"branch", b"foo#bar"): {b"bar": b"foo"}}), cf)
 
+    def test_closing_bracket_within_section_string(self):
+        cf = self.from_file(b'[branch "foo]bar"] # a comment\nbar= foo\n')
+        self.assertEqual(ConfigFile({(b"branch", b"foo]bar"): {b"bar": b"foo"}}), cf)
+
     def test_from_file_section(self):
         cf = self.from_file(b"[core]\nfoo = bar\n")
         self.assertEqual(b"bar", cf.get((b"core",), b"foo"))
@@ -300,7 +305,7 @@ class StackedConfigTests(TestCase):
     def test_default_backends(self):
         StackedConfig.default_backends()
 
-    @skipIf(sys.platform != "win32", "Windows specfic config location.")
+    @skipIf(sys.platform != "win32", "Windows specific config location.")
     def test_windows_config_from_path(self):
         from dulwich.config import get_win_system_paths
 
@@ -316,7 +321,7 @@ class StackedConfigTests(TestCase):
             paths,
         )
 
-    @skipIf(sys.platform != "win32", "Windows specfic config location.")
+    @skipIf(sys.platform != "win32", "Windows specific config location.")
     def test_windows_config_from_reg(self):
         import winreg
 
@@ -428,3 +433,31 @@ class SubmodulesTests(TestCase):
             ],
             got,
         )
+
+
+class ApplyInsteadOfTests(TestCase):
+    def test_none(self):
+        config = ConfigDict()
+        self.assertEqual(
+            'https://example.com/', apply_instead_of(config, 'https://example.com/'))
+
+    def test_apply(self):
+        config = ConfigDict()
+        config.set(
+            ('url', 'https://samba.org/'), 'insteadOf', 'https://example.com/')
+        self.assertEqual(
+            'https://samba.org/',
+            apply_instead_of(config, 'https://example.com/'))
+
+    def test_apply_multiple(self):
+        config = ConfigDict()
+        config.set(
+            ('url', 'https://samba.org/'), 'insteadOf', 'https://blah.com/')
+        config.set(
+            ('url', 'https://samba.org/'), 'insteadOf', 'https://example.com/')
+        self.assertEqual(
+            [b'https://blah.com/', b'https://example.com/'],
+            list(config.get_multivar(('url', 'https://samba.org/'), 'insteadOf')))
+        self.assertEqual(
+            'https://samba.org/',
+            apply_instead_of(config, 'https://example.com/'))
blob - c5868953bb5193a9aafb12a6d72a65c2f85865a5
blob + 2606aea97affdc0c3080d0cf82b96489da5f485b
--- dulwich/tests/test_greenthreads.py
+++ dulwich/tests/test_greenthreads.py
@@ -130,6 +130,6 @@ class TestGreenThreadsMissingObjectFinder(TestCase):
         finder = GreenThreadsMissingObjectFinder(
             self.store, wants[0 : int(self.cmt_amount / 2)], wants
         )
-        # sha_done will contains commit id and sha of blob refered in tree
+        # sha_done will contains commit id and sha of blob referred in tree
         self.assertEqual(len(finder.sha_done), (self.cmt_amount / 2) * 2)
         self.assertEqual(len(finder.objects_to_send), self.cmt_amount / 2)
blob - aa6731815124912c8a1ef32acd6911c633d9f21b
blob + 7d3a784c21ef83ef16dea99da7ab608c060ca8a7
--- dulwich/tests/test_index.py
+++ dulwich/tests/test_index.py
@@ -86,7 +86,7 @@ def can_symlink():
 
 class IndexTestCase(TestCase):
 
-    datadir = os.path.join(os.path.dirname(__file__), "data/indexes")
+    datadir = os.path.join(os.path.dirname(__file__), "../../testdata/indexes")
 
     def get_simple_index(self, name):
         return Index(os.path.join(self.datadir, name))
blob - 7c8256d34bc4d081583f9b5e430ec7a0c110513f
blob + d4a8fa52be5698b2f3e2d6928251cbcff44c45e5
--- dulwich/tests/test_objects.py
+++ dulwich/tests/test_objects.py
@@ -30,7 +30,6 @@ from itertools import (
 )
 import os
 import stat
-import warnings
 from contextlib import contextmanager
 
 from dulwich.errors import (
@@ -87,7 +86,7 @@ class BlobReadTests(TestCase):
     """Test decompression of blobs"""
 
     def get_sha_file(self, cls, base, sha):
-        dir = os.path.join(os.path.dirname(__file__), "data", base)
+        dir = os.path.join(os.path.dirname(__file__), "..", "..", "testdata", base)
         return cls.from_path(hex_to_filename(dir, sha))
 
     def get_blob(self, sha):
@@ -837,17 +836,6 @@ class TreeTests(ShaFileCheckTests):
         myhexsha = b"d80c186a03f423a81b39df39dc87fd269736ca86"
         x = Tree()
         x.add(b"myname", 0o100755, myhexsha)
-        self.assertEqual(x[b"myname"], (0o100755, myhexsha))
-        self.assertEqual(b"100755 myname\0" + hex_to_sha(myhexsha), x.as_raw_string())
-
-    def test_add_old_order(self):
-        myhexsha = b"d80c186a03f423a81b39df39dc87fd269736ca86"
-        x = Tree()
-        warnings.simplefilter("ignore", DeprecationWarning)
-        try:
-            x.add(0o100755, b"myname", myhexsha)
-        finally:
-            warnings.resetwarnings()
         self.assertEqual(x[b"myname"], (0o100755, myhexsha))
         self.assertEqual(b"100755 myname\0" + hex_to_sha(myhexsha), x.as_raw_string())
 
@@ -878,7 +866,7 @@ class TreeTests(ShaFileCheckTests):
         self.assertEqual(_SORTED_TREE_ITEMS, x.items())
 
     def _do_test_parse_tree(self, parse_tree):
-        dir = os.path.join(os.path.dirname(__file__), "data", "trees")
+        dir = os.path.join(os.path.dirname(__file__), "..", "..", "testdata", "trees")
         o = Tree.from_path(hex_to_filename(dir, tree_sha))
         self.assertEqual(
             [(b"a", 0o100644, a_sha), (b"b", 0o100644, b_sha)],
blob - d8708c906ad2ff1a57620d5fdf265b91bc023077
blob + a5bb9cbf013dd735a91e3776810dd09da5c423e1
--- dulwich/tests/test_pack.py
+++ dulwich/tests/test_pack.py
@@ -95,7 +95,7 @@ class PackTests(TestCase):
         self.tempdir = tempfile.mkdtemp()
         self.addCleanup(shutil.rmtree, self.tempdir)
 
-    datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data/packs"))
+    datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../testdata/packs"))
 
     def get_pack_index(self, sha):
         """Returns a PackIndex from the datadir with the given sha"""
blob - 6ed9cac2c3b9e595b3eba070fd0ccef979c56148
blob + c19cf0ed1f69cc6e61b11467e2c5bfd9ca157737
--- dulwich/tests/test_porcelain.py
+++ dulwich/tests/test_porcelain.py
@@ -66,7 +66,12 @@ from dulwich.web import (
     make_wsgi_chain,
 )
 
+try:
+    import gpg
+except ImportError:
+    gpg = None
 
+
 def flat_walk_dir(dir_to_walk):
     for dirpath, _, filenames in os.walk(dir_to_walk):
         rel_dirpath = os.path.relpath(dirpath, dir_to_walk)
@@ -94,6 +99,7 @@ class PorcelainTestCase(TestCase):
         self.assertLess(time.time() - ts, 50)
 
 
+@skipIf(gpg is None, "gpg is not available")
 class PorcelainGpgTestCase(PorcelainTestCase):
     DEFAULT_KEY = """
 -----BEGIN PGP PRIVATE KEY BLOCK-----
@@ -271,7 +277,10 @@ ya6JVZCRbMXfdCy8lVPgtNQ6VlHaj8Wvnn2FLbWWO2n2r3s=
         super(PorcelainGpgTestCase, self).setUp()
         self.gpg_dir = os.path.join(self.test_dir, "gpg")
         os.mkdir(self.gpg_dir, mode=0o700)
-        self.addCleanup(shutil.rmtree, self.gpg_dir)
+        # Ignore errors when deleting GNUPGHOME, because of race conditions
+        # (e.g. the gpg-agent socket having been deleted). See
+        # https://github.com/jelmer/dulwich/issues/1000
+        self.addCleanup(shutil.rmtree, self.gpg_dir, ignore_errors=True)
         self._old_gnupghome = os.environ.get("GNUPGHOME")
         os.environ["GNUPGHOME"] = self.gpg_dir
         if self._old_gnupghome is None:
@@ -408,11 +417,200 @@ class CommitTests(PorcelainTestCase):
             author="Joe <joe@example.com>",
             committer="Bob <bob@example.com>",
             no_verify=True,
+        )
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+
+    def test_timezone(self):
+        c1, c2, c3 = build_commit_graph(
+            self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
+        )
+        self.repo.refs[b"refs/heads/foo"] = c3.id
+        sha = porcelain.commit(
+            self.repo.path,
+            message="Some message",
+            author="Joe <joe@example.com>",
+            author_timezone=18000,
+            committer="Bob <bob@example.com>",
+            commit_timezone=18000,
+        )
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+
+        commit = self.repo.get_object(sha)
+        self.assertEqual(commit._author_timezone, 18000)
+        self.assertEqual(commit._commit_timezone, 18000)
+
+        os.environ["GIT_AUTHOR_DATE"] = os.environ["GIT_COMMITTER_DATE"] = "1995-11-20T19:12:08-0501"
+
+        sha = porcelain.commit(
+            self.repo.path,
+            message="Some message",
+            author="Joe <joe@example.com>",
+            committer="Bob <bob@example.com>",
+        )
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+
+        commit = self.repo.get_object(sha)
+        self.assertEqual(commit._author_timezone, -18060)
+        self.assertEqual(commit._commit_timezone, -18060)
+
+        del os.environ["GIT_AUTHOR_DATE"]
+        del os.environ["GIT_COMMITTER_DATE"]
+        local_timezone = time.localtime().tm_gmtoff
+
+        sha = porcelain.commit(
+            self.repo.path,
+            message="Some message",
+            author="Joe <joe@example.com>",
+            committer="Bob <bob@example.com>",
+        )
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+
+        commit = self.repo.get_object(sha)
+        self.assertEqual(commit._author_timezone, local_timezone)
+        self.assertEqual(commit._commit_timezone, local_timezone)
+
+
+@skipIf(platform.python_implementation() == "PyPy" or sys.platform == "win32", "gpgme not easily available or supported on Windows and PyPy")
+class CommitSignTests(PorcelainGpgTestCase):
+
+    def test_default_key(self):
+        c1, c2, c3 = build_commit_graph(
+            self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
+        )
+        self.repo.refs[b"HEAD"] = c3.id
+        cfg = self.repo.get_config()
+        cfg.set(("user",), "signingKey", PorcelainGpgTestCase.DEFAULT_KEY_ID)
+        self.import_default_key()
+
+        sha = porcelain.commit(
+            self.repo.path,
+            message="Some message",
+            author="Joe <joe@example.com>",
+            committer="Bob <bob@example.com>",
+            signoff=True,
+        )
+        self.assertIsInstance(sha, bytes)
+        self.assertEqual(len(sha), 40)
+
+        commit = self.repo.get_object(sha)
+        # GPG Signatures aren't deterministic, so we can't do a static assertion.
+        commit.verify()
+        commit.verify(keyids=[PorcelainGpgTestCase.DEFAULT_KEY_ID])
+
+        self.import_non_default_key()
+        self.assertRaises(
+            gpg.errors.MissingSignatures,
+            commit.verify,
+            keyids=[PorcelainGpgTestCase.NON_DEFAULT_KEY_ID],
+        )
+
+        commit.committer = b"Alice <alice@example.com>"
+        self.assertRaises(
+            gpg.errors.BadSignatures,
+            commit.verify,
+        )
+
+    def test_non_default_key(self):
+        c1, c2, c3 = build_commit_graph(
+            self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
+        )
+        self.repo.refs[b"HEAD"] = c3.id
+        cfg = self.repo.get_config()
+        cfg.set(("user",), "signingKey", PorcelainGpgTestCase.DEFAULT_KEY_ID)
+        self.import_non_default_key()
+
+        sha = porcelain.commit(
+            self.repo.path,
+            message="Some message",
+            author="Joe <joe@example.com>",
+            committer="Bob <bob@example.com>",
+            signoff=PorcelainGpgTestCase.NON_DEFAULT_KEY_ID,
         )
         self.assertIsInstance(sha, bytes)
         self.assertEqual(len(sha), 40)
 
+        commit = self.repo.get_object(sha)
+        # GPG Signatures aren't deterministic, so we can't do a static assertion.
+        commit.verify()
 
+
+class TimezoneTests(PorcelainTestCase):
+
+    def put_envs(self, value):
+        os.environ["GIT_AUTHOR_DATE"] = os.environ["GIT_COMMITTER_DATE"] = value
+
+    def fallback(self, value):
+        self.put_envs(value)
+        self.assertRaises(porcelain.TimezoneFormatError, porcelain.get_user_timezones)
+
+    def test_internal_format(self):
+        self.put_envs("0 +0500")
+        self.assertTupleEqual((18000, 18000), porcelain.get_user_timezones())
+
+    def test_rfc_2822(self):
+        self.put_envs("Mon, 20 Nov 1995 19:12:08 -0500")
+        self.assertTupleEqual((-18000, -18000), porcelain.get_user_timezones())
+
+        self.put_envs("Mon, 20 Nov 1995 19:12:08")
+        self.assertTupleEqual((0, 0), porcelain.get_user_timezones())
+
+    def test_iso8601(self):
+        self.put_envs("1995-11-20T19:12:08-0501")
+        self.assertTupleEqual((-18060, -18060), porcelain.get_user_timezones())
+
+        self.put_envs("1995-11-20T19:12:08+0501")
+        self.assertTupleEqual((18060, 18060), porcelain.get_user_timezones())
+
+        self.put_envs("1995-11-20T19:12:08-05:01")
+        self.assertTupleEqual((-18060, -18060), porcelain.get_user_timezones())
+
+        self.put_envs("1995-11-20 19:12:08-05")
+        self.assertTupleEqual((-18000, -18000), porcelain.get_user_timezones())
+
+        # https://github.com/git/git/blob/96b2d4fa927c5055adc5b1d08f10a5d7352e2989/t/t6300-for-each-ref.sh#L128
+        self.put_envs("2006-07-03 17:18:44 +0200")
+        self.assertTupleEqual((7200, 7200), porcelain.get_user_timezones())
+
+    def test_missing_or_malformed(self):
+        # TODO: add more here
+        self.fallback("0 + 0500")
+        self.fallback("a +0500")
+
+        self.fallback("1995-11-20T19:12:08")
+        self.fallback("1995-11-20T19:12:08-05:")
+
+        self.fallback("1995.11.20")
+        self.fallback("11/20/1995")
+        self.fallback("20.11.1995")
+
+    def test_different_envs(self):
+        os.environ["GIT_AUTHOR_DATE"] = "0 +0500"
+        os.environ["GIT_COMMITTER_DATE"] = "0 +0501"
+        self.assertTupleEqual((18000, 18060), porcelain.get_user_timezones())
+
+    def test_no_envs(self):
+        local_timezone = time.localtime().tm_gmtoff
+
+        self.put_envs("0 +0500")
+        self.assertTupleEqual((18000, 18000), porcelain.get_user_timezones())
+
+        del os.environ["GIT_COMMITTER_DATE"]
+        self.assertTupleEqual((18000, local_timezone), porcelain.get_user_timezones())
+
+        self.put_envs("0 +0500")
+        del os.environ["GIT_AUTHOR_DATE"]
+        self.assertTupleEqual((local_timezone, 18000), porcelain.get_user_timezones())
+
+        self.put_envs("0 +0500")
+        del os.environ["GIT_AUTHOR_DATE"]
+        del os.environ["GIT_COMMITTER_DATE"]
+        self.assertTupleEqual((local_timezone, local_timezone), porcelain.get_user_timezones())
+
+
 class CleanTests(PorcelainTestCase):
     def put_files(self, tracked, ignored, untracked, empty_dirs):
         """Put the described files in the wd"""
@@ -647,7 +845,7 @@ class CloneTests(PorcelainTestCase):
         with tempfile.TemporaryDirectory() as parent:
             target_path = os.path.join(parent, "target")
             self.assertRaises(
-                Exception, porcelain.clone, "/nonexistant/repo", target_path
+                Exception, porcelain.clone, "/nonexistent/repo", target_path
             )
             self.assertFalse(os.path.exists(target_path))
 
@@ -1128,8 +1326,6 @@ class RevListTests(PorcelainTestCase):
 class TagCreateSignTests(PorcelainGpgTestCase):
 
     def test_default_key(self):
-        import gpg
-
         c1, c2, c3 = build_commit_graph(
             self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
         )
@@ -1406,6 +1602,28 @@ class ResetFileTests(PorcelainTestCase):
         porcelain.reset_file(self.repo, os.path.join('new_dir', 'foo'), target=sha)
         with open(full_path, 'r') as f:
             self.assertEqual('hello', f.read())
+
+
+class SubmoduleTests(PorcelainTestCase):
+
+    def test_empty(self):
+        porcelain.commit(
+            repo=self.repo.path,
+            message=b"init",
+            author=b"author <email>",
+            committer=b"committer <email>",
+        )
+
+        self.assertEqual([], list(porcelain.submodule_list(self.repo)))
+
+    def test_add(self):
+        porcelain.submodule_add(self.repo, "../bar.git", "bar")
+        with open('%s/.gitmodules' % self.repo.path, 'r') as f:
+            self.assertEqual("""\
+[submodule "bar"]
+\turl = ../bar.git
+\tpath = bar
+""", f.read())
 
 
 class PushTests(PorcelainTestCase):
@@ -1897,6 +2115,16 @@ class StatusTests(PorcelainTestCase):
         with self.assertRaises(ValueError):
             porcelain.status(self.repo.path, untracked_files="antani")
 
+    def test_status_untracked_path(self):
+        untracked_dir = os.path.join(self.repo_path, "untracked_dir")
+        os.mkdir(untracked_dir)
+        untracked_file = os.path.join(untracked_dir, "untracked_file")
+        with open(untracked_file, "w") as fh:
+            fh.write("untracked")
+
+        _, _, untracked = porcelain.status(self.repo.path, untracked_files="all")
+        self.assertEqual(untracked, ["untracked_dir/untracked_file"])
+
     def test_status_crlf_mismatch(self):
         # First make a commit as if the file has been added on a Linux system
         # or with core.autocrlf=True
blob - 47e38a2eb776e39b8b38ad685df9bd3adcc4cc3c
blob + c381f42b0df713b8fdc8f87fce1f8808467913eb
--- dulwich/tests/test_refs.py
+++ dulwich/tests/test_refs.py
@@ -34,6 +34,7 @@ from dulwich.objects import ZERO_SHA
 from dulwich.refs import (
     DictRefsContainer,
     InfoRefsContainer,
+    SymrefLoop,
     check_ref_format,
     _split_ref_line,
     parse_symref_value,
@@ -246,9 +247,9 @@ class RefsContainerTests(object):
         self.assertEqual(nines, self._refs[b"refs/heads/master"])
 
         self.assertTrue(
-            self._refs.set_if_equals(b"refs/heads/nonexistant", ZERO_SHA, nines)
+            self._refs.set_if_equals(b"refs/heads/nonexistent", ZERO_SHA, nines)
         )
-        self.assertEqual(nines, self._refs[b"refs/heads/nonexistant"])
+        self.assertEqual(nines, self._refs[b"refs/heads/nonexistent"])
 
     def test_add_if_new(self):
         nines = b"9" * 40
@@ -518,7 +519,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestC
             ),
             self._refs.follow(b"refs/heads/master"),
         )
-        self.assertRaises(KeyError, self._refs.follow, b"refs/heads/loop")
+        self.assertRaises(SymrefLoop, self._refs.follow, b"refs/heads/loop")
 
     def test_delitem(self):
         RefsContainerTests.test_delitem(self)
@@ -622,7 +623,7 @@ class DiskRefsContainerTests(RefsContainerTests, TestC
             b"42d06bd4b77fed026b154d16493e5deab78f02ec",
             self._refs.read_ref(b"refs/heads/packed"),
         )
-        self.assertEqual(None, self._refs.read_ref(b"nonexistant"))
+        self.assertEqual(None, self._refs.read_ref(b"nonexistent"))
 
     def test_read_loose_ref(self):
         self._refs[b"refs/heads/foo"] = b"df6800012397fb85c56e7418dd4eb9405dee075c"
blob - f22f5e41630931d817e697658b60699119fd3b6f
blob + 1d07c0d1cc7ea9dbb3bb7476150aa2919ff70703
--- dulwich/tests/test_repository.py
+++ dulwich/tests/test_repository.py
@@ -409,7 +409,7 @@ class RepositoryRootTests(TestCase):
     def test_clone_no_head(self):
         temp_dir = self.mkdtemp()
         self.addCleanup(shutil.rmtree, temp_dir)
-        repo_dir = os.path.join(os.path.dirname(__file__), "data", "repos")
+        repo_dir = os.path.join(os.path.dirname(__file__), "..", "..", "testdata", "repos")
         dest_dir = os.path.join(temp_dir, "a.git")
         shutil.copytree(os.path.join(repo_dir, "a.git"), dest_dir, symlinks=True)
         r = Repo(dest_dir)
blob - 09f92cb3838a676b8a81a17b53c8fda9d746cf6d
blob + f6b9d592a693c0057ae0abb952093c99406ddc5b
--- dulwich/tests/utils.py
+++ dulwich/tests/utils.py
@@ -76,7 +76,7 @@ def open_repo(name, temp_dir=None):
     """
     if temp_dir is None:
         temp_dir = tempfile.mkdtemp()
-    repo_dir = os.path.join(os.path.dirname(__file__), "data", "repos", name)
+    repo_dir = os.path.join(os.path.dirname(__file__), "..", "..", "testdata", "repos", name)
     temp_repo_dir = os.path.join(temp_dir, name)
     shutil.copytree(repo_dir, temp_repo_dir, symlinks=True)
     return Repo(temp_repo_dir)
blob - /dev/null
blob + dc98d8792450299ff187445bbf197eda758a6580 (mode 644)
--- /dev/null
+++ dulwich/submodule.py
@@ -0,0 +1,40 @@
+# config.py - Reading and writing Git config files
+# Copyright (C) 2011-2013 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.
+#
+
+"""Working with Git submodules.
+"""
+
+from typing import Iterator, Tuple
+from .objects import S_ISGITLINK
+
+
+def iter_cached_submodules(store, root_tree_id: bytes) -> Iterator[Tuple[str, bytes]]:
+    """iterate over cached submodules.
+
+    Args:
+      store: Object store to iterate
+      root_tree_id: SHA of root tree
+
+    Returns:
+      Iterator over over (path, sha) tuples
+    """
+    for entry in store.iter_tree_contents(root_tree_id):
+        if S_ISGITLINK(entry.mode):
+            yield entry.path, entry.sha
blob - 3de0b5b9f6f458c21bdbe15bb6a586b4bbcc5588
blob + 41cceb42e189fa38d09214f9115560438252f579
--- dulwich.egg-info/PKG-INFO
+++ dulwich.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.20.44
+Version: 0.20.46
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Operating System :: POSIX
blob - 0ced704f94567aab055500a6215305a5a139b053
blob + cfa030d35106e11f5f4ba7c7bb238183eb0d959a
--- dulwich.egg-info/SOURCES.txt
+++ dulwich.egg-info/SOURCES.txt
@@ -89,6 +89,7 @@ dulwich/repo.py
 dulwich/server.py
 dulwich/stash.py
 dulwich/stdint.h
+dulwich/submodule.py
 dulwich/walk.py
 dulwich/web.py
 dulwich.egg-info/PKG-INFO
@@ -168,89 +169,89 @@ dulwich/tests/compat/test_server.py
 dulwich/tests/compat/test_utils.py
 dulwich/tests/compat/test_web.py
 dulwich/tests/compat/utils.py
-dulwich/tests/data/blobs/11/11111111111111111111111111111111111111
-dulwich/tests/data/blobs/6f/670c0fb53f9463760b7295fbb814e965fb20c8
-dulwich/tests/data/blobs/95/4a536f7819d40e6f637f849ee187dd10066349
-dulwich/tests/data/blobs/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
-dulwich/tests/data/commits/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
-dulwich/tests/data/commits/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
-dulwich/tests/data/commits/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
-dulwich/tests/data/indexes/index
-dulwich/tests/data/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.idx
-dulwich/tests/data/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.pack
-dulwich/tests/data/repos/.gitattributes
-dulwich/tests/data/repos/issue88_expect_ack_nak_client.export
-dulwich/tests/data/repos/issue88_expect_ack_nak_other.export
-dulwich/tests/data/repos/issue88_expect_ack_nak_server.export
-dulwich/tests/data/repos/server_new.export
-dulwich/tests/data/repos/server_old.export
-dulwich/tests/data/repos/a.git/HEAD
-dulwich/tests/data/repos/a.git/packed-refs
-dulwich/tests/data/repos/a.git/objects/28/237f4dc30d0d462658d6b937b08a0f0b6ef55a
-dulwich/tests/data/repos/a.git/objects/2a/72d929692c41d8554c07f6301757ba18a65d91
-dulwich/tests/data/repos/a.git/objects/4e/f30bbfe26431a69c3820d3a683df54d688f2ec
-dulwich/tests/data/repos/a.git/objects/4f/2e6529203aa6d44b5af6e3292c837ceda003f9
-dulwich/tests/data/repos/a.git/objects/7d/9a07d797595ef11344549b8d08198e48c15364
-dulwich/tests/data/repos/a.git/objects/a2/96d0bb611188cabb256919f36bc30117cca005
-dulwich/tests/data/repos/a.git/objects/a9/0fa2d900a17e99b433217e988c4eb4a2e9a097
-dulwich/tests/data/repos/a.git/objects/b0/931cadc54336e78a1d980420e3268903b57a50
-dulwich/tests/data/repos/a.git/objects/ff/d47d45845a8f6576491e1edb97e3fe6a850e7f
-dulwich/tests/data/repos/a.git/refs/heads/master
-dulwich/tests/data/repos/a.git/refs/tags/mytag
-dulwich/tests/data/repos/empty.git/HEAD
-dulwich/tests/data/repos/empty.git/config
-dulwich/tests/data/repos/empty.git/objects/info/.gitignore
-dulwich/tests/data/repos/empty.git/objects/pack/.gitignore
-dulwich/tests/data/repos/empty.git/refs/heads/.gitignore
-dulwich/tests/data/repos/empty.git/refs/tags/.gitignore
-dulwich/tests/data/repos/ooo_merge.git/HEAD
-dulwich/tests/data/repos/ooo_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
-dulwich/tests/data/repos/ooo_merge.git/objects/38/74e9c60a6d149c44c928140f250d81e6381520
-dulwich/tests/data/repos/ooo_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
-dulwich/tests/data/repos/ooo_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
-dulwich/tests/data/repos/ooo_merge.git/objects/76/01d7f6231db6a57f7bbb79ee52e4d462fd44d1
-dulwich/tests/data/repos/ooo_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
-dulwich/tests/data/repos/ooo_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
-dulwich/tests/data/repos/ooo_merge.git/objects/b2/a2766a2879c209ab1176e7e778b81ae422eeaa
-dulwich/tests/data/repos/ooo_merge.git/objects/f5/07291b64138b875c28e03469025b1ea20bc614
-dulwich/tests/data/repos/ooo_merge.git/objects/f9/e39b120c68182a4ba35349f832d0e4e61f485c
-dulwich/tests/data/repos/ooo_merge.git/objects/fb/5b0425c7ce46959bec94d54b9a157645e114f5
-dulwich/tests/data/repos/ooo_merge.git/refs/heads/master
-dulwich/tests/data/repos/refs.git/HEAD
-dulwich/tests/data/repos/refs.git/packed-refs
-dulwich/tests/data/repos/refs.git/objects/3b/9e5457140e738c2dcd39bf6d7acf88379b90d1
-dulwich/tests/data/repos/refs.git/objects/3e/c9c43c84ff242e3ef4a9fc5bc111fd780a76a8
-dulwich/tests/data/repos/refs.git/objects/42/d06bd4b77fed026b154d16493e5deab78f02ec
-dulwich/tests/data/repos/refs.git/objects/a1/8114c31713746a33a2e70d9914d1ef3e781425
-dulwich/tests/data/repos/refs.git/objects/cd/a609072918d7b70057b6bef9f4c2537843fcfe
-dulwich/tests/data/repos/refs.git/objects/df/6800012397fb85c56e7418dd4eb9405dee075c
-dulwich/tests/data/repos/refs.git/refs/heads/40-char-ref-aaaaaaaaaaaaaaaaaa
-dulwich/tests/data/repos/refs.git/refs/heads/loop
-dulwich/tests/data/repos/refs.git/refs/heads/master
-dulwich/tests/data/repos/refs.git/refs/tags/refs-0.2
-dulwich/tests/data/repos/simple_merge.git/HEAD
-dulwich/tests/data/repos/simple_merge.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
-dulwich/tests/data/repos/simple_merge.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853
-dulwich/tests/data/repos/simple_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
-dulwich/tests/data/repos/simple_merge.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6
-dulwich/tests/data/repos/simple_merge.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
-dulwich/tests/data/repos/simple_merge.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
-dulwich/tests/data/repos/simple_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
-dulwich/tests/data/repos/simple_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
-dulwich/tests/data/repos/simple_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
-dulwich/tests/data/repos/simple_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
-dulwich/tests/data/repos/simple_merge.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd
-dulwich/tests/data/repos/simple_merge.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7
-dulwich/tests/data/repos/simple_merge.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86
-dulwich/tests/data/repos/simple_merge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
-dulwich/tests/data/repos/simple_merge.git/refs/heads/master
-dulwich/tests/data/repos/submodule/dotgit
-dulwich/tests/data/tags/71/033db03a03c6a36721efcf1968dd8f8e0cf023
-dulwich/tests/data/trees/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
 examples/clone.py
 examples/config.py
 examples/diff.py
 examples/gcs.py
 examples/latest_change.py
 examples/memoryrepo.py
-examples/rename-branch.py
\ No newline at end of file
+examples/rename-branch.py
+testdata/blobs/11/11111111111111111111111111111111111111
+testdata/blobs/6f/670c0fb53f9463760b7295fbb814e965fb20c8
+testdata/blobs/95/4a536f7819d40e6f637f849ee187dd10066349
+testdata/blobs/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
+testdata/commits/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
+testdata/commits/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
+testdata/commits/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
+testdata/indexes/index
+testdata/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.idx
+testdata/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.pack
+testdata/repos/.gitattributes
+testdata/repos/issue88_expect_ack_nak_client.export
+testdata/repos/issue88_expect_ack_nak_other.export
+testdata/repos/issue88_expect_ack_nak_server.export
+testdata/repos/server_new.export
+testdata/repos/server_old.export
+testdata/repos/a.git/HEAD
+testdata/repos/a.git/packed-refs
+testdata/repos/a.git/objects/28/237f4dc30d0d462658d6b937b08a0f0b6ef55a
+testdata/repos/a.git/objects/2a/72d929692c41d8554c07f6301757ba18a65d91
+testdata/repos/a.git/objects/4e/f30bbfe26431a69c3820d3a683df54d688f2ec
+testdata/repos/a.git/objects/4f/2e6529203aa6d44b5af6e3292c837ceda003f9
+testdata/repos/a.git/objects/7d/9a07d797595ef11344549b8d08198e48c15364
+testdata/repos/a.git/objects/a2/96d0bb611188cabb256919f36bc30117cca005
+testdata/repos/a.git/objects/a9/0fa2d900a17e99b433217e988c4eb4a2e9a097
+testdata/repos/a.git/objects/b0/931cadc54336e78a1d980420e3268903b57a50
+testdata/repos/a.git/objects/ff/d47d45845a8f6576491e1edb97e3fe6a850e7f
+testdata/repos/a.git/refs/heads/master
+testdata/repos/a.git/refs/tags/mytag
+testdata/repos/empty.git/HEAD
+testdata/repos/empty.git/config
+testdata/repos/empty.git/objects/info/.gitignore
+testdata/repos/empty.git/objects/pack/.gitignore
+testdata/repos/empty.git/refs/heads/.gitignore
+testdata/repos/empty.git/refs/tags/.gitignore
+testdata/repos/ooo_merge.git/HEAD
+testdata/repos/ooo_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
+testdata/repos/ooo_merge.git/objects/38/74e9c60a6d149c44c928140f250d81e6381520
+testdata/repos/ooo_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
+testdata/repos/ooo_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
+testdata/repos/ooo_merge.git/objects/76/01d7f6231db6a57f7bbb79ee52e4d462fd44d1
+testdata/repos/ooo_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
+testdata/repos/ooo_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
+testdata/repos/ooo_merge.git/objects/b2/a2766a2879c209ab1176e7e778b81ae422eeaa
+testdata/repos/ooo_merge.git/objects/f5/07291b64138b875c28e03469025b1ea20bc614
+testdata/repos/ooo_merge.git/objects/f9/e39b120c68182a4ba35349f832d0e4e61f485c
+testdata/repos/ooo_merge.git/objects/fb/5b0425c7ce46959bec94d54b9a157645e114f5
+testdata/repos/ooo_merge.git/refs/heads/master
+testdata/repos/refs.git/HEAD
+testdata/repos/refs.git/packed-refs
+testdata/repos/refs.git/objects/3b/9e5457140e738c2dcd39bf6d7acf88379b90d1
+testdata/repos/refs.git/objects/3e/c9c43c84ff242e3ef4a9fc5bc111fd780a76a8
+testdata/repos/refs.git/objects/42/d06bd4b77fed026b154d16493e5deab78f02ec
+testdata/repos/refs.git/objects/a1/8114c31713746a33a2e70d9914d1ef3e781425
+testdata/repos/refs.git/objects/cd/a609072918d7b70057b6bef9f4c2537843fcfe
+testdata/repos/refs.git/objects/df/6800012397fb85c56e7418dd4eb9405dee075c
+testdata/repos/refs.git/refs/heads/40-char-ref-aaaaaaaaaaaaaaaaaa
+testdata/repos/refs.git/refs/heads/loop
+testdata/repos/refs.git/refs/heads/master
+testdata/repos/refs.git/refs/tags/refs-0.2
+testdata/repos/simple_merge.git/HEAD
+testdata/repos/simple_merge.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
+testdata/repos/simple_merge.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853
+testdata/repos/simple_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b
+testdata/repos/simple_merge.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6
+testdata/repos/simple_merge.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc
+testdata/repos/simple_merge.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
+testdata/repos/simple_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8
+testdata/repos/simple_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
+testdata/repos/simple_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870
+testdata/repos/simple_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349
+testdata/repos/simple_merge.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd
+testdata/repos/simple_merge.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7
+testdata/repos/simple_merge.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86
+testdata/repos/simple_merge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
+testdata/repos/simple_merge.git/refs/heads/master
+testdata/repos/submodule/dotgit
+testdata/tags/71/033db03a03c6a36721efcf1968dd8f8e0cf023
+testdata/trees/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6
\ No newline at end of file
blob - 480a2c67cf189b71abcf69eaa147f9f4f0259abf
blob + dc8ea2de09c23fbff85f876f58f1090dd0fe993c
--- dulwich.egg-info/requires.txt
+++ dulwich.egg-info/requires.txt
@@ -1,11 +1,10 @@
-certifi
-urllib3>=1.24.1
+urllib3>=1.25
 
 [fastimport]
 fastimport
 
 [https]
-urllib3[secure]>=1.24.1
+urllib3>=1.24.1
 
 [paramiko]
 paramiko
blob - cff1307d6ca8b1c57376e9b7516adddb3701ab04
blob + d9de11354f71ee2763db9b3690c6a41e2b7a0172
--- requirements.txt
+++ requirements.txt
@@ -1 +1 @@
-urllib3[secure]>=1.23
+urllib3>=1.23
blob - 3c557ab7db8b44fb11b1ad1d677fee8ec3467d63
blob + 9484ff8bf2849b5df232bb50a2398d6e158b4bdd
--- setup.py
+++ setup.py
@@ -1,20 +1,12 @@
 #!/usr/bin/python3
 # encoding: utf-8
 # Setup file for dulwich
-# Copyright (C) 2008-2016 Jelmer Vernooij <jelmer@jelmer.uk>
+# Copyright (C) 2008-2022 Jelmer Vernooij <jelmer@jelmer.uk>
 
-try:
-    from setuptools import setup, Extension
-except ImportError:
-    from distutils.core import setup, Extension
-    has_setuptools = False
-else:
-    has_setuptools = True
-from distutils.core import Distribution
+from setuptools import setup, Extension, Distribution
 import io
 import os
 import sys
-from typing import Dict, Any
 
 
 if sys.version_info < (3, 6):
@@ -23,7 +15,7 @@ if sys.version_info < (3, 6):
         'For 2.7 support, please install a version prior to 0.20')
 
 
-dulwich_version_string = '0.20.44'
+dulwich_version_string = '0.20.46'
 
 
 class DulwichDistribution(Distribution):
@@ -53,8 +45,8 @@ if sys.platform == 'darwin' and os.path.exists('/usr/b
     for line in out.splitlines():
         line = line.decode("utf8")
         # Also parse only first digit, because 3.2.1 can't be parsed nicely
-        if (line.startswith('Xcode') and
-                int(line.split()[1].split('.')[0]) >= 4):
+        if (line.startswith('Xcode')
+                and int(line.split()[1].split('.')[0]) >= 4):
             os.environ['ARCHFLAGS'] = ''
 
 tests_require = ['fastimport']
@@ -71,26 +63,7 @@ ext_modules = [
     Extension('dulwich._diff_tree', ['dulwich/_diff_tree.c']),
 ]
 
-setup_kwargs = {}  # type: Dict[str, Any]
 scripts = ['bin/dul-receive-pack', 'bin/dul-upload-pack']
-if has_setuptools:
-    setup_kwargs['extras_require'] = {
-        'fastimport': ['fastimport'],
-        'https': ['urllib3[secure]>=1.24.1'],
-        'pgp': ['gpg'],
-        'paramiko': ['paramiko'],
-        }
-    setup_kwargs['install_requires'] = ['urllib3>=1.24.1', 'certifi']
-    setup_kwargs['include_package_data'] = True
-    setup_kwargs['test_suite'] = 'dulwich.tests.test_suite'
-    setup_kwargs['tests_require'] = tests_require
-    setup_kwargs['entry_points'] = {
-        "console_scripts": [
-            "dulwich=dulwich.cli:main",
-        ]}
-    setup_kwargs['python_requires'] = '>=3.6'
-else:
-    scripts.append('bin/dulwich')
 
 
 with io.open(os.path.join(os.path.dirname(__file__), "README.rst"),
@@ -111,13 +84,21 @@ setup(name='dulwich',
           "GitHub": "https://github.com/dulwich/dulwich",
       },
       keywords="git vcs",
-      packages=['dulwich', 'dulwich.tests', 'dulwich.tests.compat',
-                'dulwich.contrib'],
+      packages=['dulwich', 'dulwich.cloud', 'dulwich.tests',
+                'dulwich.tests.compat', 'dulwich.contrib'],
       package_data={'': ['../docs/tutorial/*.txt', 'py.typed']},
       scripts=scripts,
       ext_modules=ext_modules,
       zip_safe=False,
-      distclass=DulwichDistribution,
+      distclass=DulwichDistribution,  # type: ignore
+      install_requires=['urllib3>=1.25'],
+      include_package_data=True,
+      test_suite='dulwich.tests.test_suite',
+      tests_require=tests_require,
+      entry_points={
+          "console_scripts": ["dulwich=dulwich.cli:main"]
+      },
+      python_requires='>=3.6',
       classifiers=[
           'Development Status :: 4 - Beta',
           'License :: OSI Approved :: Apache Software License',
@@ -126,11 +107,16 @@ setup(name='dulwich',
           'Programming Language :: Python :: 3.8',
           'Programming Language :: Python :: 3.9',
           'Programming Language :: Python :: 3.10',
+          'Programming Language :: Python :: 3.11',
           'Programming Language :: Python :: Implementation :: CPython',
           'Programming Language :: Python :: Implementation :: PyPy',
           'Operating System :: POSIX',
           'Operating System :: Microsoft :: Windows',
           'Topic :: Software Development :: Version Control',
       ],
-      **setup_kwargs
-      )
+      extras_require={
+          'fastimport': ['fastimport'],
+          'https': ['urllib3>=1.24.1'],
+          'pgp': ['gpg'],
+          'paramiko': ['paramiko'],
+      })
blob - db78d5a81eb707ec8a87ab85c170e6637e71acae
blob + e4e0e18e675d4c4c09c4a3baf39baa5e42b85a7f
--- status.yaml
+++ status.yaml
@@ -1,30 +1,30 @@
 ---
 configuration:
- - key: core.compression
-   status: supported
- - key: core.looseCompression
-   status: supported
- - key: core.packCompression
-   status: supported
- - key: core.filemode
-   status: supported
- - key: http.proxy
-   status: supported
- - key: http.useragent
-   status: supported
- - key: http.sslVerify
-   status: supported
- - key: http.sslCAInfo
-   status: supported
- - key: i18n.commitEncoding
-   status: supported
- - key: core.excludsFile
-   status: supported
- - key: user.name
-   status: supported
- - key: user.email
-   status: supported
- - key: core.protectNTFS
-   status: supported
- - key: core.ignorecase
-   status: supported
+  - key: core.compression
+    status: supported
+  - key: core.looseCompression
+    status: supported
+  - key: core.packCompression
+    status: supported
+  - key: core.filemode
+    status: supported
+  - key: http.proxy
+    status: supported
+  - key: http.useragent
+    status: supported
+  - key: http.sslVerify
+    status: supported
+  - key: http.sslCAInfo
+    status: supported
+  - key: i18n.commitEncoding
+    status: supported
+  - key: core.excludsFile
+    status: supported
+  - key: user.name
+    status: supported
+  - key: user.email
+    status: supported
+  - key: core.protectNTFS
+    status: supported
+  - key: core.ignorecase
+    status: supported
blob - /dev/null
blob + 1942d238fd2e742022b145383e67863d02862c08 (mode 644)
Binary files /dev/null and testdata/blobs/11/11111111111111111111111111111111111111 differ
blob - /dev/null
blob + 8f8ed37f1e6b8f0af781c26daa8f31ae9bd2167d (mode 644)
Binary files /dev/null and testdata/blobs/6f/670c0fb53f9463760b7295fbb814e965fb20c8 differ
blob - /dev/null
blob + 7bef12912d59b8fab01801f66978456947e6ce59 (mode 644)
Binary files /dev/null and testdata/blobs/95/4a536f7819d40e6f637f849ee187dd10066349 differ
blob - /dev/null
blob + 8c901c5b89f920a740af8b23b771ef4019cdb665 (mode 644)
Binary files /dev/null and testdata/blobs/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ
blob - /dev/null
blob + b4c3a1d99a4d24753afd83c3e179bd428b24764c (mode 755)
--- /dev/null
+++ testdata/commits/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
@@ -0,0 +1,2 @@
+xK
+@])z53׮==-&e=i:"Z=H)r芔>4wYԯMx|q=s)&6Dh6{Ym/LXg?
\ No newline at end of file
blob - /dev/null
blob + 69c6dff1ab6b88df371b5f539df78e705d1f05a5 (mode 644)
Binary files /dev/null and testdata/commits/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc differ
blob - /dev/null
blob + 9e1d72632e9aa355b6f3efed9aa13f3a1259c426 (mode 644)
--- /dev/null
+++ testdata/commits/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
@@ -0,0 +1,4 @@
+x
+0E]+f/N"]g	*5"O.υɗia	UBERr[P\ʋ

+Tz靖-zN0Q

+)ZOEv,pIop['lǺ<|fֶk)PGX{&K0?yMQ
\ No newline at end of file
blob - /dev/null
blob + 96f9998c0a1883d2b96b5088650eec063a5d3e97 (mode 644)
Binary files /dev/null and testdata/indexes/index differ
blob - /dev/null
blob + ca0454de928844663ddb20a0369f1d9af0391c72 (mode 644)
Binary files /dev/null and testdata/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.idx differ
blob - /dev/null
blob + e2754b2777a048fd7e83b22d77a89462c2394d2f (mode 644)
Binary files /dev/null and testdata/packs/pack-bc63ddad95e7321ee734ea11a7a62d314e0d7481.pack differ
blob - /dev/null
blob + 26cc331fb8748e2cf588db343a1c2f8242d4b692 (mode 644)
--- /dev/null
+++ testdata/repos/.gitattributes
@@ -0,0 +1 @@
+*.export eol=lf
blob - /dev/null
blob + cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
--- /dev/null
+++ testdata/repos/a.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
blob - /dev/null
blob + f13a21bc5735e6b8bcf4423ce6645fcf32a1fafd (mode 644)
--- /dev/null
+++ testdata/repos/a.git/objects/28/237f4dc30d0d462658d6b937b08a0f0b6ef55a
@@ -0,0 +1,2 @@
+x5A
+0a9\@i""L1T"uPMA7o~2(0H\uB\]MNc+H!0&5Zi-)~	ߓ~ÏsP~Gl֮`јkN0
\ No newline at end of file
blob - /dev/null
blob + dfc9847b6f39818fb3a16abd03a52cca8b0488df (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/2a/72d929692c41d8554c07f6301757ba18a65d91 differ
blob - /dev/null
blob + 00d4a694a367c8bef7c4fae99f1f18e6edfa1e53 (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/4e/f30bbfe26431a69c3820d3a683df54d688f2ec differ
blob - /dev/null
blob + 522a3def4bbdd7178cd5f5758fd7f51831e394ea (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/4f/2e6529203aa6d44b5af6e3292c837ceda003f9 differ
blob - /dev/null
blob + 8a8432a9e223891b4250ec4ac8631b55b346017d (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/7d/9a07d797595ef11344549b8d08198e48c15364 differ
blob - /dev/null
blob + a044c5926c95969c522e5fabc37161ca65232d57 (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/a2/96d0bb611188cabb256919f36bc30117cca005 differ
blob - /dev/null
blob + 7d172f39d4e00239ca900fc7f1b94ee2f145db36 (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/a9/0fa2d900a17e99b433217e988c4eb4a2e9a097 differ
blob - /dev/null
blob + e8b676a287932c6cce97f02dbf8ae805babc6f72 (mode 644)
--- /dev/null
+++ testdata/repos/a.git/objects/b0/931cadc54336e78a1d980420e3268903b57a50
@@ -0,0 +1,3 @@
+x-[
+0**I75T[oRWo
+w*`e/i7sjpیhjkL[c7L><2ݏ 1Jrtqصh̰ɾ֥2v
\ No newline at end of file
blob - /dev/null
blob + 81b997b636b0ddc243de5d5c11eb9e4a93450bf2 (mode 644)
Binary files /dev/null and testdata/repos/a.git/objects/ff/d47d45845a8f6576491e1edb97e3fe6a850e7f differ
blob - /dev/null
blob + daf2fc46e6e666c83d5da3aeb78a10abdcfd37b1 (mode 644)
--- /dev/null
+++ testdata/repos/a.git/packed-refs
@@ -0,0 +1,3 @@
+# pack-refs with: peeled 
+b0931cadc54336e78a1d980420e3268903b57a50 refs/tags/mytag-packed
+^2a72d929692c41d8554c07f6301757ba18a65d91
blob - /dev/null
blob + e28347ea6dc57992bb974ef79daa09d8c760e075 (mode 644)
--- /dev/null
+++ testdata/repos/a.git/refs/heads/master
@@ -0,0 +1 @@
+a90fa2d900a17e99b433217e988c4eb4a2e9a097
blob - /dev/null
blob + cb5c1106d769d9358863fd29b0d869de55b9ce83 (mode 644)
--- /dev/null
+++ testdata/repos/a.git/refs/tags/mytag
@@ -0,0 +1 @@
+28237f4dc30d0d462658d6b937b08a0f0b6ef55a
blob - /dev/null
blob + cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
--- /dev/null
+++ testdata/repos/empty.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
blob - /dev/null
blob + 90e16477bddfa48ec34154f9d7211aac4e32a511 (mode 644)
--- /dev/null
+++ testdata/repos/empty.git/config
@@ -0,0 +1,7 @@
+[core]
+	repositoryformatversion = 0
+	filemode = false
+	bare = true
+	symlinks = false
+	ignorecase = true
+	hideDotFiles = dotGitOnly
blob - /dev/null
blob + c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
--- /dev/null
+++ testdata/repos/empty.git/objects/info/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
blob - /dev/null
blob + c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
--- /dev/null
+++ testdata/repos/empty.git/objects/pack/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
blob - /dev/null
blob + c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
--- /dev/null
+++ testdata/repos/empty.git/refs/heads/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
blob - /dev/null
blob + c96a04f008ee21e260b28f7701595ed59e2839e3 (mode 644)
--- /dev/null
+++ testdata/repos/empty.git/refs/tags/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
blob - /dev/null
blob + 48bdc140b6ec1a433f0d765bbb5affb6fd0200a3 (mode 644)
--- /dev/null
+++ testdata/repos/issue88_expect_ack_nak_client.export
@@ -0,0 +1,260 @@
+reset refs/heads/master
+commit refs/heads/master
+mark :1
+author User <user@localhost> 1427183369 +1300
+committer User <user@localhost> 1427183369 +1300
+data 6
+empty
+
+blob
+mark :2
+data 35
+We will reproduce a problem here.
+
+commit refs/heads/master
+mark :3
+author User <user@localhost> 1427183376 +1300
+committer User <user@localhost> 1427183376 +1300
+data 11
+demo file.
+from :1
+M 100644 :2 demo.txt
+
+blob
+mark :4
+data 62
+We will reproduce a problem here.
+
+This will take some time.
+
+commit refs/heads/master
+mark :5
+author User <user@localhost> 1427185135 +1300
+committer User <user@localhost> 1427185135 +1300
+data 13
+added a line
+from :3
+M 100644 :4 demo.txt
+
+blob
+mark :6
+data 57
+We will reproduce a problem here.
+
+We will change these.
+
+commit refs/heads/master
+mark :7
+author User <user@localhost> 1427185245 +1300
+committer User <user@localhost> 1427185245 +1300
+data 14
+replace a line
+from :5
+M 100644 :6 demo.txt
+
+blob
+mark :8
+data 52
+We will change these.
+
+Then issues will be proven.
+
+commit refs/heads/master
+mark :9
+author User <user@localhost> 1427185343 +1300
+committer User <user@localhost> 1427185343 +1300
+data 13
+Yes we will.
+from :7
+M 100644 :8 demo.txt
+
+blob
+mark :10
+data 69
+We will change these. 
+
+Then issues will be proven once and for all.
+
+commit refs/heads/master
+mark :11
+author User <user@localhost> 1427185440 +1300
+committer User <user@localhost> 1427185440 +1300
+data 6
+sure.
+from :9
+M 100644 :10 demo.txt
+
+blob
+mark :12
+data 0
+
+commit refs/heads/master
+mark :13
+author User <user@localhost> 1427185512 +1300
+committer User <user@localhost> 1427185516 +1300
+data 26
+not an actual readme, yet
+from :11
+M 100644 :12 readme.txt
+
+blob
+mark :14
+data 61
+This will for sure we will prove a problem exist somewhere.
+
+blob
+mark :15
+data 49
+okay fine add something here this is only a test
+
+commit refs/heads/master
+mark :16
+author User <user@localhost> 1427185569 +1300
+committer User <user@localhost> 1427185569 +1300
+data 12
+more things
+from :13
+M 100644 :14 demo.txt
+M 100644 :15 readme.txt
+
+blob
+mark :17
+data 100
+This will for sure we will prove a problem exist somewhere. 
+
+Just that we need a few more commits.
+
+commit refs/heads/master
+mark :18
+author User <user@localhost> 1427185659 +1300
+committer User <user@localhost> 1427185659 +1300
+data 13
+one more try
+from :16
+M 100644 :17 demo.txt
+
+blob
+mark :19
+data 54
+It might have something to do with number of commits?
+
+commit refs/heads/master
+mark :20
+author User <user@localhost> 1427185905 +1300
+committer User <user@localhost> 1427185905 +1300
+data 18
+is this number 9?
+from :18
+M 100644 :19 commitcount
+
+blob
+mark :21
+data 123
+This will for sure we will prove a problem exist somewhere. 
+
+Just that we need a few more commits.
+
+Hey look we need more
+
+commit refs/heads/master
+mark :22
+author User <user@localhost> 1427185922 +1300
+committer User <user@localhost> 1427185922 +1300
+data 5
+cool
+from :20
+M 100644 :21 demo.txt
+
+blob
+mark :23
+data 50
+Okay fine add something here this is only a test.
+
+commit refs/heads/master
+mark :24
+author User <user@localhost> 1427185936 +1300
+committer User <user@localhost> 1427185936 +1300
+data 7
+readme
+from :22
+M 100644 :23 readme.txt
+
+blob
+mark :25
+data 74
+Okay come on this is getting boring.
+
+Yes I went and edit all the things.
+
+commit refs/heads/master
+mark :26
+author User <user@localhost> 1427185954 +1300
+committer User <user@localhost> 1427185954 +1300
+data 14
+remove a line
+from :24
+M 100644 :25 demo.txt
+
+blob
+mark :27
+data 186
+Okay come on this is getting boring. 
+
+Yes I went and edit all the things. 
+
+Of course, making test data can be somewhat tedious, especially a
+minimum set that can be easily reproduced.
+
+commit refs/heads/master
+mark :28
+author User <user@localhost> 1427185996 +1300
+committer User <user@localhost> 1427185996 +1300
+data 25
+Getting serious mode on.
+from :26
+M 100644 :27 demo.txt
+
+blob
+mark :29
+data 48
+This is taking a bit longer than I remembered.
+
+commit refs/heads/master
+mark :30
+author User <user@localhost> 1427186065 +1300
+committer User <user@localhost> 1427186065 +1300
+data 40
+At least we will have things minimized.
+from :28
+M 100644 :29 demo.txt
+
+blob
+mark :31
+data 11
+there yet?
+
+commit refs/heads/master
+mark :32
+author User <user@localhost> 1427186080 +1300
+committer User <user@localhost> 1427186080 +1300
+data 7
+are we
+from :30
+M 100644 :31 demo.txt
+
+blob
+mark :33
+data 237
+This should be the head commit for the client repo for testing out
+the failure case reported in issue 88.  Just do a git pull from the
+repo that includes the following commit that is hosted with dulwich.
+The issue should be reproduced.
+
+commit refs/heads/master
+mark :34
+author User <user@localhost> 1427186109 +1300
+committer User <user@localhost> 1427186109 +1300
+data 6
+okay?
+from :32
+M 100644 :33 readme.txt
blob - /dev/null
blob + 6897693c08c6d44ed57dd8af26d9aeb45379679b (mode 644)
--- /dev/null
+++ testdata/repos/issue88_expect_ack_nak_other.export
@@ -0,0 +1,293 @@
+blob
+mark :1
+data 33
+We will sneak in a blob like so.
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author User <user@localhost> 1427183369 +1300
+committer User <user@localhost> 1427183369 +1300
+data 7
+sneaky
+M 100644 :1 problem.questionmark
+
+blob
+mark :3
+data 35
+We will introduce a problem here.
+
+
+commit refs/heads/master
+mark :4
+author User <user@localhost> 1427183376 +1300
+committer User <user@localhost> 1427183376 +1300
+data 11
+demo file.
+from :2
+M 100644 :3 demo.rst
+
+blob
+mark :5
+data 62
+We will introduce a problem here.
+
+This will take some time.
+
+
+commit refs/heads/master
+mark :6
+author User <user@localhost> 1427185135 +1300
+committer User <user@localhost> 1427185135 +1300
+data 13
+added a line
+from :4
+M 100644 :5 demo.rst
+
+blob
+mark :7
+data 57
+We will introduce a problem here.
+
+We will change these.
+
+commit refs/heads/master
+mark :8
+author User <user@localhost> 1427185245 +1300
+committer User <user@localhost> 1427185245 +1300
+data 14
+replace a linefrom :6
+M 100644 :7 demo.rst
+
+blob
+mark :9
+data 52
+We will change these.
+
+Then issues will be proven.
+
+
+commit refs/heads/master
+mark :10
+author User <user@localhost> 1427185343 +1300
+committer User <user@localhost> 1427185343 +1300
+data 13
+Yes we will.
+from :8
+M 100644 :9 demo.rst
+
+blob
+mark :11
+data 72
+We will change these. 
+
+Then issues will be construed once and for all.
+
+commit refs/heads/master
+mark :12
+author User <user@localhost> 1427185440 +1300
+committer User <user@localhost> 1427185440 +1300
+data 6
+sure.
+from :10
+M 100644 :11 demo.rst
+
+blob
+mark :13
+data 0
+
+commit refs/heads/master
+mark :14
+author User <user@localhost> 1427185512 +1300
+committer User <user@localhost> 1427185516 +1300
+data 26
+not an actual readme, yet
+from :12
+M 100644 :13 emdaer.txt
+
+blob
+mark :15
+data 58
+This will for sure we will prove issues exist somewhere.
+
+
+blob
+mark :16
+data 49
+okay fine add something here this is only a test
+
+commit refs/heads/master
+mark :17
+author User <user@localhost> 1427185569 +1300
+committer User <user@localhost> 1427185569 +1300
+data 12
+more things
+from :14
+M 100644 :15 demo.rst
+M 100644 :16 emdaer.txt
+
+blob
+mark :18
+data 97
+This will for sure prove issue exist somewhere.
+
+Just that we need a few more commits as usual.
+
+
+commit refs/heads/master
+mark :19
+author User <user@localhost> 1427185659 +1300
+committer User <user@localhost> 1427185659 +1300
+data 13
+one more try
+from :17
+M 100644 :18 demo.rst
+
+blob
+mark :20
+data 54
+It might have something to do with number of commits?
+
+commit refs/heads/master
+mark :21
+author User <user@localhost> 1427185905 +1300
+committer User <user@localhost> 1427185905 +1300
+data 18
+is this number 9?
+from :19
+M 100644 :20 count
+
+blob
+mark :22
+data 119
+This will for sure we will prove issues exist somewhere.
+
+Just that we need a few more commits.
+
+Hey look we need more
+
+commit refs/heads/master
+mark :23
+author User <user@localhost> 1427185922 +1300
+committer User <user@localhost> 1427185922 +1300
+data 5
+cool
+from :21
+M 100644 :22 demo.rst
+
+blob
+mark :24
+data 50
+Okay fine add something here this is only a test.
+
+commit refs/heads/master
+mark :25
+author User <user@localhost> 1427185936 +1300
+committer User <user@localhost> 1427185936 +1300
+data 7
+readme
+from :23
+M 100644 :24 emdaer.txt
+
+blob
+mark :26
+data 74
+Okay come on this is getting boring.
+
+Yes I went and edit all the things.
+
+commit refs/heads/master
+mark :27
+author User <user@localhost> 1427185954 +1300
+committer User <user@localhost> 1427185954 +1300
+data 14
+remove a line
+from :25
+M 100644 :26 demo.rst
+
+blob
+mark :28
+data 186
+Okay come on this is getting boring. 
+
+Yes I went and edit all the things. 
+
+Of course, making test data can be somewhat tedious, especially a
+minimum set that can be easily reproduced.
+
+commit refs/heads/master
+mark :29
+author User <user@localhost> 1427185996 +1300
+committer User <user@localhost> 1427185996 +1300
+data 25
+Getting serious mode on.
+from :27
+M 100644 :28 demo.rst
+
+blob
+mark :30
+data 48
+This is taking a bit longer than I remembered.
+
+
+commit refs/heads/master
+mark :31
+author User <user@localhost> 1427186065 +1300
+committer User <user@localhost> 1427186065 +1300
+data 40
+At least we will have things minimized.
+from :29
+M 100644 :30 demo.rst
+
+blob
+mark :32
+data 11
+there yet?
+
+commit refs/heads/master
+mark :33
+author User <user@localhost> 1427186080 +1300
+committer User <user@localhost> 1427186080 +1300
+data 7
+are we
+from :31
+M 100644 :32 demo.rst
+
+blob
+mark :34
+data 237
+This should be the head commit for the client repo for testing out
+the failure case reported in issue 88.  Just do a git pull from the
+repo that includes the following commit that is hosted with dulwich.
+The issue should be reproduced.
+
+
+commit refs/heads/master
+mark :35
+author User <user@localhost> 1427186109 +1300
+committer User <user@localhost> 1427186109 +1300
+data 6
+okay?
+from :33
+M 100644 :34 emdaer.txt
+
+blob
+mark :36
+data 394
+This should be the commit that will trigger the bug noted in issue 88
+(https://github.com/jelmer/dulwich/issues/88).  To reproduce, run git
+fast-import using this fast-export and host this using dulwich, and
+then make a copy of this, strip out this blob and the following commit
+block, import to another git repo and then git clone from the previous.
+
+Naturally, this is part of the test case.
+
+commit refs/heads/master
+mark :37
+author User <user@localhost> 1427244891 +1300
+committer User <user@localhost> 1427248186 +1300
+data 49
+Added instructions on how to use this to readme.
+from :35
+M 100644 :36 emdaer.txt
+
blob - /dev/null
blob + 0124fb32fe9d31db068b58fbaa68dbcf880fd3cb (mode 644)
--- /dev/null
+++ testdata/repos/issue88_expect_ack_nak_server.export
@@ -0,0 +1,281 @@
+reset refs/heads/master
+commit refs/heads/master
+mark :1
+author User <user@localhost> 1427183369 +1300
+committer User <user@localhost> 1427183369 +1300
+data 6
+empty
+
+blob
+mark :2
+data 35
+We will reproduce a problem here.
+
+commit refs/heads/master
+mark :3
+author User <user@localhost> 1427183376 +1300
+committer User <user@localhost> 1427183376 +1300
+data 11
+demo file.
+from :1
+M 100644 :2 demo.txt
+
+blob
+mark :4
+data 62
+We will reproduce a problem here.
+
+This will take some time.
+
+commit refs/heads/master
+mark :5
+author User <user@localhost> 1427185135 +1300
+committer User <user@localhost> 1427185135 +1300
+data 13
+added a line
+from :3
+M 100644 :4 demo.txt
+
+blob
+mark :6
+data 57
+We will reproduce a problem here.
+
+We will change these.
+
+commit refs/heads/master
+mark :7
+author User <user@localhost> 1427185245 +1300
+committer User <user@localhost> 1427185245 +1300
+data 14
+replace a line
+from :5
+M 100644 :6 demo.txt
+
+blob
+mark :8
+data 52
+We will change these.
+
+Then issues will be proven.
+
+commit refs/heads/master
+mark :9
+author User <user@localhost> 1427185343 +1300
+committer User <user@localhost> 1427185343 +1300
+data 13
+Yes we will.
+from :7
+M 100644 :8 demo.txt
+
+blob
+mark :10
+data 69
+We will change these. 
+
+Then issues will be proven once and for all.
+
+commit refs/heads/master
+mark :11
+author User <user@localhost> 1427185440 +1300
+committer User <user@localhost> 1427185440 +1300
+data 6
+sure.
+from :9
+M 100644 :10 demo.txt
+
+blob
+mark :12
+data 0
+
+commit refs/heads/master
+mark :13
+author User <user@localhost> 1427185512 +1300
+committer User <user@localhost> 1427185516 +1300
+data 26
+not an actual readme, yet
+from :11
+M 100644 :12 readme.txt
+
+blob
+mark :14
+data 61
+This will for sure we will prove a problem exist somewhere.
+
+blob
+mark :15
+data 49
+okay fine add something here this is only a test
+
+commit refs/heads/master
+mark :16
+author User <user@localhost> 1427185569 +1300
+committer User <user@localhost> 1427185569 +1300
+data 12
+more things
+from :13
+M 100644 :14 demo.txt
+M 100644 :15 readme.txt
+
+blob
+mark :17
+data 100
+This will for sure we will prove a problem exist somewhere. 
+
+Just that we need a few more commits.
+
+commit refs/heads/master
+mark :18
+author User <user@localhost> 1427185659 +1300
+committer User <user@localhost> 1427185659 +1300
+data 13
+one more try
+from :16
+M 100644 :17 demo.txt
+
+blob
+mark :19
+data 54
+It might have something to do with number of commits?
+
+commit refs/heads/master
+mark :20
+author User <user@localhost> 1427185905 +1300
+committer User <user@localhost> 1427185905 +1300
+data 18
+is this number 9?
+from :18
+M 100644 :19 commitcount
+
+blob
+mark :21
+data 123
+This will for sure we will prove a problem exist somewhere. 
+
+Just that we need a few more commits.
+
+Hey look we need more
+
+commit refs/heads/master
+mark :22
+author User <user@localhost> 1427185922 +1300
+committer User <user@localhost> 1427185922 +1300
+data 5
+cool
+from :20
+M 100644 :21 demo.txt
+
+blob
+mark :23
+data 50
+Okay fine add something here this is only a test.
+
+commit refs/heads/master
+mark :24
+author User <user@localhost> 1427185936 +1300
+committer User <user@localhost> 1427185936 +1300
+data 7
+readme
+from :22
+M 100644 :23 readme.txt
+
+blob
+mark :25
+data 74
+Okay come on this is getting boring.
+
+Yes I went and edit all the things.
+
+commit refs/heads/master
+mark :26
+author User <user@localhost> 1427185954 +1300
+committer User <user@localhost> 1427185954 +1300
+data 14
+remove a line
+from :24
+M 100644 :25 demo.txt
+
+blob
+mark :27
+data 186
+Okay come on this is getting boring. 
+
+Yes I went and edit all the things. 
+
+Of course, making test data can be somewhat tedious, especially a
+minimum set that can be easily reproduced.
+
+commit refs/heads/master
+mark :28
+author User <user@localhost> 1427185996 +1300
+committer User <user@localhost> 1427185996 +1300
+data 25
+Getting serious mode on.
+from :26
+M 100644 :27 demo.txt
+
+blob
+mark :29
+data 48
+This is taking a bit longer than I remembered.
+
+commit refs/heads/master
+mark :30
+author User <user@localhost> 1427186065 +1300
+committer User <user@localhost> 1427186065 +1300
+data 40
+At least we will have things minimized.
+from :28
+M 100644 :29 demo.txt
+
+blob
+mark :31
+data 11
+there yet?
+
+commit refs/heads/master
+mark :32
+author User <user@localhost> 1427186080 +1300
+committer User <user@localhost> 1427186080 +1300
+data 7
+are we
+from :30
+M 100644 :31 demo.txt
+
+blob
+mark :33
+data 237
+This should be the head commit for the client repo for testing out
+the failure case reported in issue 88.  Just do a git pull from the
+repo that includes the following commit that is hosted with dulwich.
+The issue should be reproduced.
+
+commit refs/heads/master
+mark :34
+author User <user@localhost> 1427186109 +1300
+committer User <user@localhost> 1427186109 +1300
+data 6
+okay?
+from :32
+M 100644 :33 readme.txt
+
+blob
+mark :35
+data 394
+This should be the commit that will trigger the bug noted in issue 88
+(https://github.com/jelmer/dulwich/issues/88).  To reproduce, run git
+fast-import using this fast-export and host this using dulwich, and
+then make a copy of this, strip out this blob and the following commit
+block, import to another git repo and then git clone from the previous.
+
+Naturally, this is part of the test case.
+
+commit refs/heads/master
+mark :36
+author User <user@localhost> 1427244891 +1300
+committer User <user@localhost> 1427248186 +1300
+data 49
+Added instructions on how to use this to readme.
+from :34
+M 100644 :35 readme.txt
+
blob - /dev/null
blob + cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
--- /dev/null
+++ testdata/repos/ooo_merge.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
blob - /dev/null
blob + cbe43c2ebd526cac5d66a4d0f614abf4d77c3e59 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b differ
blob - /dev/null
blob + 3f3699b36e39f0046a19227248c45215b0f5e452 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/38/74e9c60a6d149c44c928140f250d81e6381520 differ
blob - /dev/null
blob + 95c3c19bbeaeda10c619530ac6ea902ae1c0cdd4 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8 differ
blob - /dev/null
blob + 2569779c10cc06f9e3639d73183c80b6fbd8f243 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6 differ
blob - /dev/null
blob + e88303b28d023fc86c939c7480d259df081ea402 (mode 644)
--- /dev/null
+++ testdata/repos/ooo_merge.git/objects/76/01d7f6231db6a57f7bbb79ee52e4d462fd44d1
@@ -0,0 +1,3 @@
+xAj1E)L%[PJօ46CftxӲzL

+`MH*[dL:^l8++Pb+46nhb&ei?:檵SH@mD
+r_-ᗮxMY_~{aU*Z{<Fx0<w_LY
\ No newline at end of file
blob - /dev/null
blob + 610b7dfa3a19490625df89ba689ce6efe928d9eb (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870 differ
blob - /dev/null
blob + 7bef12912d59b8fab01801f66978456947e6ce59 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349 differ
blob - /dev/null
blob + 165943d626144b8c1aa9859bfac35e4cb7afda09 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/b2/a2766a2879c209ab1176e7e778b81ae422eeaa differ
blob - /dev/null
blob + 4438cdd7cad0280b0d0e50bc44d975d20f441b0e (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/f5/07291b64138b875c28e03469025b1ea20bc614 differ
blob - /dev/null
blob + d5a5677b135f4c653d3853bdb9b5c5b4d8dc95f8 (mode 644)
--- /dev/null
+++ testdata/repos/ooo_merge.git/objects/f9/e39b120c68182a4ba35349f832d0e4e61f485c
@@ -0,0 +1,3 @@
+x
+0ay$Yb6)=7qB)>2

+CW%>rT&I@$P5iX莥7y w:^o_|Q[IZSsVIEy?
\ No newline at end of file
blob - /dev/null
blob + 5bda024ac879ef55da75751f82e4c7e9e417ad69 (mode 644)
Binary files /dev/null and testdata/repos/ooo_merge.git/objects/fb/5b0425c7ce46959bec94d54b9a157645e114f5 differ
blob - /dev/null
blob + 34c0a7949167c0d22872f6e5a5707a4cbe1621c4 (mode 644)
--- /dev/null
+++ testdata/repos/ooo_merge.git/refs/heads/master
@@ -0,0 +1 @@
+7601d7f6231db6a57f7bbb79ee52e4d462fd44d1
blob - /dev/null
blob + cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
blob - /dev/null
blob + 6160481e0d0f5c125699a9e97daba274708564ec (mode 644)
Binary files /dev/null and testdata/repos/refs.git/objects/3b/9e5457140e738c2dcd39bf6d7acf88379b90d1 differ
blob - /dev/null
blob + 478d0379acec45352e22d368d2b6711b9285ef36 (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/objects/3e/c9c43c84ff242e3ef4a9fc5bc111fd780a76a8
@@ -0,0 +1,3 @@
+x-Q
+0D)-mV^i66.~{#Cm]rwyu=u5^[o<H<*y?ƴ,()a߈2<)$8xR.4YktPaԵ
+q?W)'ǧ6
\ No newline at end of file
blob - /dev/null
blob + 1be455a300b50e3b834ff22744acb008d64ca20a (mode 644)
Binary files /dev/null and testdata/repos/refs.git/objects/42/d06bd4b77fed026b154d16493e5deab78f02ec differ
blob - /dev/null
blob + 7e69c0efaf3324e3bde674e3bc90b4370466e315 (mode 644)
Binary files /dev/null and testdata/repos/refs.git/objects/a1/8114c31713746a33a2e70d9914d1ef3e781425 differ
blob - /dev/null
blob + 777d49daa06e828ec64f48b9f3c14396d3fae3a5 (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/objects/cd/a609072918d7b70057b6bef9f4c2537843fcfe
@@ -0,0 +1,6 @@
+x-Q
+0D)m7iV^i6bIEOo
+~c`Av.;Zyku<*^Zox\T4
+<	4.Lam
+Fj#e/s=SRYBcQk

+eZ-\r?)Y9
\ No newline at end of file
blob - /dev/null
blob + e40b2074233e9aef431a50bf761970cb7ccc50d9 (mode 644)
Binary files /dev/null and testdata/repos/refs.git/objects/df/6800012397fb85c56e7418dd4eb9405dee075c differ
blob - /dev/null
blob + bf2f9e36e89d2085d2a1f03e477d1344b1be8a2f (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/packed-refs
@@ -0,0 +1,4 @@
+# pack-refs with: peeled 
+df6800012397fb85c56e7418dd4eb9405dee075c refs/tags/refs-0.1
+^42d06bd4b77fed026b154d16493e5deab78f02ec
+42d06bd4b77fed026b154d16493e5deab78f02ec refs/heads/packed
blob - /dev/null
blob + 2623a489ebb544326db1702067b25f08c63e517e (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/refs/heads/40-char-ref-aaaaaaaaaaaaaaaaaa
@@ -0,0 +1 @@
+42d06bd4b77fed026b154d16493e5deab78f02ec
blob - /dev/null
blob + ed2f1caab8d539a193a6167080d82e8ac540d2b4 (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/refs/heads/loop
@@ -0,0 +1 @@
+ref: refs/heads/loop
blob - /dev/null
blob + 2623a489ebb544326db1702067b25f08c63e517e (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/refs/heads/master
@@ -0,0 +1 @@
+42d06bd4b77fed026b154d16493e5deab78f02ec
blob - /dev/null
blob + 7ac75665748fe576c76fa53d29733f1f62ba2bd3 (mode 644)
--- /dev/null
+++ testdata/repos/refs.git/refs/tags/refs-0.2
@@ -0,0 +1 @@
+3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8
blob - /dev/null
blob + 25d48cae10efef0a108d2e255e836223bced827e (mode 644)
--- /dev/null
+++ testdata/repos/server_new.export
@@ -0,0 +1,99 @@
+blob
+mark :1
+data 13
+foo contents
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author Dave Borowitz <dborowitz@google.com> 1265755064 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755064 -0800
+data 16
+initial checkin
+M 100644 :1 foo
+
+blob
+mark :3
+data 13
+baz contents
+
+blob
+mark :4
+data 21
+updated foo contents
+
+commit refs/heads/master
+mark :5
+author Dave Borowitz <dborowitz@google.com> 1265755140 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755140 -0800
+data 15
+master checkin
+from :2
+M 100644 :3 baz
+M 100644 :4 foo
+
+blob
+mark :6
+data 24
+updated foo contents v2
+
+commit refs/heads/master
+mark :7
+author Dave Borowitz <dborowitz@google.com> 1265755287 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755287 -0800
+data 17
+master checkin 2
+from :5
+M 100644 :6 foo
+
+blob
+mark :8
+data 24
+updated foo contents v3
+
+commit refs/heads/master
+mark :9
+author Dave Borowitz <dborowitz@google.com> 1265755295 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755295 -0800
+data 17
+master checkin 3
+from :7
+M 100644 :8 foo
+
+blob
+mark :10
+data 22
+branched bar contents
+
+blob
+mark :11
+data 22
+branched foo contents
+
+commit refs/heads/branch
+mark :12
+author Dave Borowitz <dborowitz@google.com> 1265755111 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755111 -0800
+data 15
+branch checkin
+from :2
+M 100644 :10 bar
+M 100644 :11 foo
+
+blob
+mark :13
+data 25
+branched bar contents v2
+
+commit refs/heads/branch
+mark :14
+author Dave Borowitz <dborowitz@google.com> 1265755319 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755319 -0800
+data 17
+branch checkin 2
+from :12
+M 100644 :13 bar
+
+reset refs/heads/master
+from :9
+
blob - /dev/null
blob + b02a3391baf0453d7341623ba62f814edb2769c7 (mode 644)
--- /dev/null
+++ testdata/repos/server_old.export
@@ -0,0 +1,57 @@
+blob
+mark :1
+data 13
+foo contents
+
+reset refs/heads/master
+commit refs/heads/master
+mark :2
+author Dave Borowitz <dborowitz@google.com> 1265755064 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755064 -0800
+data 16
+initial checkin
+M 100644 :1 foo
+
+blob
+mark :3
+data 22
+branched bar contents
+
+blob
+mark :4
+data 22
+branched foo contents
+
+commit refs/heads/branch
+mark :5
+author Dave Borowitz <dborowitz@google.com> 1265755111 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755111 -0800
+data 15
+branch checkin
+from :2
+M 100644 :3 bar
+M 100644 :4 foo
+
+blob
+mark :6
+data 13
+baz contents
+
+blob
+mark :7
+data 21
+updated foo contents
+
+commit refs/heads/master
+mark :8
+author Dave Borowitz <dborowitz@google.com> 1265755140 -0800
+committer Dave Borowitz <dborowitz@google.com> 1265755140 -0800
+data 15
+master checkin
+from :2
+M 100644 :6 baz
+M 100644 :7 foo
+
+reset refs/heads/master
+from :8
+
blob - /dev/null
blob + cb089cd89a7d7686d284d8761201649346b5aa1c (mode 644)
--- /dev/null
+++ testdata/repos/simple_merge.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
blob - /dev/null
blob + b4c3a1d99a4d24753afd83c3e179bd428b24764c (mode 644)
--- /dev/null
+++ testdata/repos/simple_merge.git/objects/0d/89f20333fbb1d2f3a94da77f4981373d8f4310
@@ -0,0 +1,2 @@
+xK
+@])z53׮==-&e=i:"Z=H)r芔>4wYԯMx|q=s)&6Dh6{Ym/LXg?
\ No newline at end of file
blob - /dev/null
blob + a2247b203f986dcb9c22184b1aae17353960c5b4 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/1b/6318f651a534b38f9c7aedeebbd56c1e896853 differ
blob - /dev/null
blob + 3dec0bf77a94a1664e95bd056f41ddb563072c89 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/29/69be3e8ee1c0222396a5611407e4769f14e54b differ
blob - /dev/null
blob + a73c46c7b6800a9df41452174604dcf600efd325 (mode 644)
--- /dev/null
+++ testdata/repos/simple_merge.git/objects/4c/ffe90e0a41ad3f5190079d7c8f036bde29cbe6
@@ -0,0 +1,4 @@
+xK
+1D]L~=pݝŒ2Fo-U:.UUAI!KU.!kj

+FN

+*R{QofwSQ[!)G;]g8Шw863Mˇ/_m1 tSK0]i*'pCO
\ No newline at end of file
blob - /dev/null
blob + 69c6dff1ab6b88df371b5f539df78e705d1f05a5 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/5d/ac377bdded4c9aeb8dff595f0faeebcc8498cc differ
blob - /dev/null
blob + 9e1d72632e9aa355b6f3efed9aa13f3a1259c426 (mode 644)
--- /dev/null
+++ testdata/repos/simple_merge.git/objects/60/dacdc733de308bb77bb76ce0fb0f9b44c9769e
@@ -0,0 +1,4 @@
+x
+0E]+f/N"]g	*5"O.υɗia	UBERr[P\ʋ

+Tz靖-zN0Q

+)ZOEv,pIop['lǺ<|fֶk)PGX{&K0?yMQ
\ No newline at end of file
blob - /dev/null
blob + 8f8ed37f1e6b8f0af781c26daa8f31ae9bd2167d (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/6f/670c0fb53f9463760b7295fbb814e965fb20c8 differ
blob - /dev/null
blob + 2569779c10cc06f9e3639d73183c80b6fbd8f243 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6 differ
blob - /dev/null
blob + 610b7dfa3a19490625df89ba689ce6efe928d9eb (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/90/182552c4a85a45ec2a835cadc3451bebdfe870 differ
blob - /dev/null
blob + 7bef12912d59b8fab01801f66978456947e6ce59 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/95/4a536f7819d40e6f637f849ee187dd10066349 differ
blob - /dev/null
blob + 67706b5810550b826b747525ac1924499c87a6d2 (mode 644)
--- /dev/null
+++ testdata/repos/simple_merge.git/objects/ab/64bbdcc51b170d21588e5c5d391ee5c0c96dfd
@@ -0,0 +1,2 @@
+xM
+0]$cL";/:3bmo|6=~SgU@c̍bUA)ƙ{ulR+ld&z7ږm{IiKt.pmQx?|ݨ!־c仴?s9r4/mO+
\ No newline at end of file
blob - /dev/null
blob + d45835e8201d096288a79b38053dbe259d5475b5 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/d4/bdad6549dfedf25d3b89d21f506aff575b28a7 differ
blob - /dev/null
blob + dce887e80f980d37bb634a0e7c99c5e062292fbd (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/d8/0c186a03f423a81b39df39dc87fd269736ca86 differ
blob - /dev/null
blob + 8c901c5b89f920a740af8b23b771ef4019cdb665 (mode 644)
Binary files /dev/null and testdata/repos/simple_merge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ
blob - /dev/null
blob + c6b8285fbdb5a417f490579d79252fc6d2d03c97 (mode 644)
--- /dev/null
+++ testdata/repos/simple_merge.git/refs/heads/master
@@ -0,0 +1 @@
+5dac377bdded4c9aeb8dff595f0faeebcc8498cc
blob - /dev/null
blob + c493b47db9e7c5cf34da754182f3b099e44a64fc (mode 644)
--- /dev/null
+++ testdata/repos/submodule/dotgit
@@ -0,0 +1 @@
+gitdir: ./a.git
blob - /dev/null
blob + 8c85e3a618f862f3df42941e44592d22f75f7695 (mode 644)
--- /dev/null
+++ testdata/tags/71/033db03a03c6a36721efcf1968dd8f8e0cf023
@@ -0,0 +1,5 @@
+xmMO@=x#݅QnIMEQ
+mʿG'sL2OSz,$1vqnaJb+0u3mMr
+adɢs,=RB bY(֝cQ	Yjn!p	7#	ݜ5!X[GپM}n}]8m9pzd%
+!#f|X`fBKD%'sKCӝ5\<a5EDpDd-=n

+oKk=ʽn~6iM
\ No newline at end of file
blob - /dev/null
blob + 2569779c10cc06f9e3639d73183c80b6fbd8f243 (mode 644)
Binary files /dev/null and testdata/trees/70/c190eb48fa8bbb50ddc692a17b44cb781af7f6 differ