浏览代码

Update upstream source from tag 'upstream/0.19.10'

Update to upstream version '0.19.10'
with Debian dir 749ad66fdd0dce68757aa02590f43b7c422674ed
Jelmer Vernooij 6 年之前
父节点
当前提交
5c8f46ec5b

+ 1 - 0
.gitignore

@@ -21,3 +21,4 @@ dulwich.egg-info/
 .idea/
 .coverage
 htmlcov/
+docs/api/*.txt

+ 2 - 0
AUTHORS

@@ -144,5 +144,7 @@ Daniel M. Capella <polyzen@users.noreply.github.com>
 grun <grunseid@gmail.com>
 Sylvia van Os <sylvia@hackerchick.me>
 Boris Feld <lothiraldan@gmail.com>
+KS Chan <mrkschan@gmail.com>
+egor <egor@sourced.tech>
 
 If you contributed but are missing from this list, please send me an e-mail.

+ 2 - 1
CONTRIBUTING.md → CONTRIBUTING.rst

@@ -23,7 +23,8 @@ Running the tests
 To run the testsuite, you should be able to simply run "make check". This
 will run the tests using unittest.
 
- $ make check
+::
+   $ make check
 
 Tox configuration is also present as well as a Travis configuration file.
 

+ 3 - 3
MANIFEST.in

@@ -1,10 +1,10 @@
 include NEWS
 include AUTHORS
-include README.md
-include README.swift.md
+include README.rst
+include README.swift.rst
 include Makefile
 include COPYING
-include CONTRIBUTING.md
+include CONTRIBUTING.rst
 include TODO
 include setup.cfg
 include dulwich/stdint.h

+ 0 - 5
Makefile

@@ -3,7 +3,6 @@ PYFLAKES = pyflakes
 PEP8 = pep8
 FLAKE8 ?= flake8
 SETUP = $(PYTHON) setup.py
-PYDOCTOR ?= pydoctor
 TESTRUNNER ?= unittest
 RUNTEST = PYTHONHASHSEED=random PYTHONPATH=$(shell pwd)$(if $(PYTHONPATH),:$(PYTHONPATH),) $(PYTHON) -m $(TESTRUNNER) $(TEST_OPTIONS)
 COVERAGE = python3-coverage
@@ -12,15 +11,11 @@ DESTDIR=/
 
 all: build
 
-doc:: pydoctor
 doc:: sphinx
 
 sphinx::
 	$(MAKE) -C docs html
 
-pydoctor::
-	$(PYDOCTOR) --make-html -c dulwich.cfg
-
 build::
 	$(SETUP) build
 	$(SETUP) build_ext -i

+ 25 - 0
NEWS

@@ -1,3 +1,28 @@
+0.19.10	2018-01-15
+
+ IMPROVEMENTS
+
+ * Add `dulwich.porcelain.write_tree`.
+   (Jelmer Vernooij)
+
+ * Support reading ``MERGE_HEADS`` in ``Repo.do_commit``.
+   (Jelmer Vernooij)
+
+ * Import from ``collections.abc`` rather than ``collections`` where
+   applicable. Required for 3.8 compatibility.
+   (Jelmer Vernooij)
+
+ * Support plain strings as refspec arguments to
+   ``dulwich.porcelain.push``. (Jelmer Vernooij)
+
+ * Add support for creating signed tags.
+   (Jelmer Vernooij, #542)
+
+ BUG FIXES
+
+ *  Handle invalid ref that pretends to be a sub-folder under a valid ref.
+    (KS Chan)
+
 0.19.9	2018-11-17
 
  BUG FIXES

+ 20 - 15
PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.19.9
+Version: 0.19.10
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij
@@ -9,15 +9,20 @@ License: Apachev2 or later or GPLv2
 Project-URL: Bug Tracker, https://github.com/dulwich/dulwich/issues
 Project-URL: GitHub, https://github.com/dulwich/dulwich
 Project-URL: Repository, https://www.dulwich.io/code/
-Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=master)](https://travis-ci.org/dulwich/dulwich)
-        [![Windows Build status](https://ci.appveyor.com/api/projects/status/mob7g4vnrfvvoweb?svg=true)](https://ci.appveyor.com/project/jelmer/dulwich/branch/master)
+Description: .. image:: https://travis-ci.org/dulwich/dulwich.png?branch=master
+          :alt: Build Status
+          :target: https://travis-ci.org/dulwich/dulwich
+        
+        .. image:: https://ci.appveyor.com/api/projects/status/mob7g4vnrfvvoweb?svg=true
+          :alt: Windows Build Status
+          :target: https://ci.appveyor.com/project/jelmer/dulwich/branch/master
         
         This is the Dulwich project.
         
         It aims to provide an interface to git repos (both local and remote) that
         doesn't call out to git directly but instead uses pure Python.
         
-        **Main website**: [www.dulwich.io](https://www.dulwich.io/)
+        **Main website**: <https://www.dulwich.io/>
         
         **License**: Apache License, version 2 or GNU General Public License, version 2 or later.
         
@@ -40,7 +45,7 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
             $ pip install dulwich --global-option="--pure"
         
         Note that you can also specify --global-option in a
-        [requirements.txt](https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers)
+        `requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_
         file, e.g. like this::
         
             dulwich --global-option=--pure
@@ -51,7 +56,7 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
         Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain").
         
         For example, to use the lower level API to access the commit message of the
-        last commit:
+        last commit::
         
             >>> from dulwich.repo import Repo
             >>> r = Repo('.')
@@ -63,7 +68,7 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
             >>> c.message
             'Add note about encoding.\n'
         
-        And to print it using porcelain:
+        And to print it using porcelain::
         
             >>> from dulwich import porcelain
             >>> porcelain.log('.', max_entries=1)
@@ -78,26 +83,26 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
         ---------------------
         
         The dulwich documentation can be found in docs/ and
-        [on the web](https://www.dulwich.io/docs/).
+        `on the web <https://www.dulwich.io/docs/>`_.
         
         The API reference can be generated using pydoctor, by running "make pydoctor",
-        or [on the web](https://www.dulwich.io/apidocs).
+        or `on the web <https://www.dulwich.io/apidocs>`_.
         
         Help
         ----
         
-        There is a *#dulwich* IRC channel on the [Freenode](https://www.freenode.net/), and
-        [dulwich-announce](https://groups.google.com/forum/#!forum/dulwich-announce)
-        and [dulwich-discuss](https://groups.google.com/forum/#!forum/dulwich-discuss)
+        There is a *#dulwich* IRC channel on the `Freenode <https://www.freenode.net/>`_, and
+        `dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_
+        and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_
         mailing lists.
         
         Contributing
         ------------
         
-        For a full list of contributors, see the git logs or [AUTHORS](AUTHORS).
+        For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_.
         
-        If you'd like to contribute to Dulwich, see the [CONTRIBUTING](CONTRIBUTING.md)
-        file and [list of open issues](https://github.com/dulwich/dulwich/issues).
+        If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_
+        file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_.
         
         Supported versions of Python
         ----------------------------

+ 19 - 14
README.md → README.rst

@@ -1,12 +1,17 @@
-[![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=master)](https://travis-ci.org/dulwich/dulwich)
-[![Windows Build status](https://ci.appveyor.com/api/projects/status/mob7g4vnrfvvoweb?svg=true)](https://ci.appveyor.com/project/jelmer/dulwich/branch/master)
+.. image:: https://travis-ci.org/dulwich/dulwich.png?branch=master
+  :alt: Build Status
+  :target: https://travis-ci.org/dulwich/dulwich
+
+.. image:: https://ci.appveyor.com/api/projects/status/mob7g4vnrfvvoweb?svg=true
+  :alt: Windows Build Status
+  :target: https://ci.appveyor.com/project/jelmer/dulwich/branch/master
 
 This is the Dulwich project.
 
 It aims to provide an interface to git repos (both local and remote) that
 doesn't call out to git directly but instead uses pure Python.
 
-**Main website**: [www.dulwich.io](https://www.dulwich.io/)
+**Main website**: <https://www.dulwich.io/>
 
 **License**: Apache License, version 2 or GNU General Public License, version 2 or later.
 
@@ -29,7 +34,7 @@ or if you are installing from pip::
     $ pip install dulwich --global-option="--pure"
 
 Note that you can also specify --global-option in a
-[requirements.txt](https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers)
+`requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_
 file, e.g. like this::
 
     dulwich --global-option=--pure
@@ -40,7 +45,7 @@ Getting started
 Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain").
 
 For example, to use the lower level API to access the commit message of the
-last commit:
+last commit::
 
     >>> from dulwich.repo import Repo
     >>> r = Repo('.')
@@ -52,7 +57,7 @@ last commit:
     >>> c.message
     'Add note about encoding.\n'
 
-And to print it using porcelain:
+And to print it using porcelain::
 
     >>> from dulwich import porcelain
     >>> porcelain.log('.', max_entries=1)
@@ -67,26 +72,26 @@ Further documentation
 ---------------------
 
 The dulwich documentation can be found in docs/ and
-[on the web](https://www.dulwich.io/docs/).
+`on the web <https://www.dulwich.io/docs/>`_.
 
 The API reference can be generated using pydoctor, by running "make pydoctor",
-or [on the web](https://www.dulwich.io/apidocs).
+or `on the web <https://www.dulwich.io/apidocs>`_.
 
 Help
 ----
 
-There is a *#dulwich* IRC channel on the [Freenode](https://www.freenode.net/), and
-[dulwich-announce](https://groups.google.com/forum/#!forum/dulwich-announce)
-and [dulwich-discuss](https://groups.google.com/forum/#!forum/dulwich-discuss)
+There is a *#dulwich* IRC channel on the `Freenode <https://www.freenode.net/>`_, and
+`dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_
+and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_
 mailing lists.
 
 Contributing
 ------------
 
-For a full list of contributors, see the git logs or [AUTHORS](AUTHORS).
+For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_.
 
-If you'd like to contribute to Dulwich, see the [CONTRIBUTING](CONTRIBUTING.md)
-file and [list of open issues](https://github.com/dulwich/dulwich/issues).
+If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_
+file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_.
 
 Supported versions of Python
 ----------------------------

+ 8 - 8
README.swift.md → README.swift.rst

@@ -18,7 +18,7 @@ Configuration file
 
 We need to provide some configuration values in order to let Dulwich
 talk and authenticate against Swift. The following config file must
-be used as template:
+be used as template::
 
     [swift]
     # Authentication URL (Keystone or Swift)
@@ -54,7 +54,7 @@ How to start unittest
 ---------------------
 
 There is no need to have a Swift cluster running to run the unitests.
-Just run the following command in the Dulwich source directory:
+Just run the following command in the Dulwich source directory::
 
     $ PYTHONPATH=. python -m dulwich.contrib.test_swift
 
@@ -63,7 +63,7 @@ How to start functional tests
 
 We provide some basic tests to perform smoke tests against a real Swift
 cluster. To run those functional tests you need a properly configured
-configuration file. The tests can be run as follow:
+configuration file. The tests can be run as follow::
 
     $ DULWICH_SWIFT_CFG=/etc/swift-dul.conf PYTHONPATH=. python -m dulwich.contrib.test_swift_smoke
 
@@ -71,14 +71,14 @@ How to install
 --------------
 
 Install the Dulwich library via the setup.py. The dependencies will be
-automatically retrieved from pypi:
+automatically retrieved from pypi::
 
     $ python ./setup.py install
 
 How to run the server
 ---------------------
 
-Start the server using the following command:
+Start the server using the following command::
 
     $ python -m dulwich.contrib.swift daemon -c /etc/swift-dul.conf -l 127.0.0.1
 
@@ -92,7 +92,7 @@ How to use
 
 Once you have validated that the functional tests is working as expected and
 the server is running we can init a bare repository. Run this
-command with the name of the repository to create:
+command with the name of the repository to create::
 
     $ python -m dulwich.contrib.swift init -c /etc/swift-dul.conf edeploy
 
@@ -100,11 +100,11 @@ The repository name will be the container that will contain all the Git
 objects for the repository. Then standard c Git client can be used to
 perform operations against this repository.
 
-As an example we can clone the previously empty bare repository:
+As an example we can clone the previously empty bare repository::
 
     $ git clone git://localhost/edeploy
 
-Then push an existing project in it:
+Then push an existing project in it::
 
     $ git clone https://github.com/enovance/edeploy.git edeployclone
     $ cd edeployclone

+ 16 - 5
bin/dulwich

@@ -311,11 +311,13 @@ class cmd_rev_list(Command):
 class cmd_tag(Command):
 
     def run(self, args):
-        opts, args = getopt(args, '', [])
-        if len(args) < 2:
-            print('Usage: dulwich tag NAME')
-            sys.exit(1)
-        porcelain.tag('.', args[0])
+        parser = optparse.OptionParser()
+        parser.add_option("-a", "--annotated", help="Create an annotated tag.", action="store_true")
+        parser.add_option("-s", "--sign", help="Sign the annotated tag.", action="store_true")
+        options, args = parser.parse_args(args)
+        porcelain.tag_create(
+            '.', args[0], annotated=options.annotated,
+            sign=options.sign)
 
 
 class cmd_repack(Command):
@@ -388,6 +390,14 @@ class cmd_web_daemon(Command):
                              port=options.port)
 
 
+class cmd_write_tree(Command):
+
+    def run(self, args):
+        parser = optparse.OptionParser()
+        options, args = parser.parse_args(args)
+        sys.stdout.write('%s\n' % porcelain.write_tree('.'))
+
+
 class cmd_receive_pack(Command):
 
     def run(self, args):
@@ -689,6 +699,7 @@ commands = {
     "update-server-info": cmd_update_server_info,
     "upload-pack": cmd_upload_pack,
     "web-daemon": cmd_web_daemon,
+    "write-tree": cmd_write_tree,
     }
 
 if len(sys.argv) < 2:

+ 11 - 8
docs/Makefile

@@ -31,33 +31,36 @@ help:
 clean:
 	-rm -rf $(BUILDDIR)/*
 
-html:
+apidocs:
+	sphinx-apidoc -feM -s txt -o api ../dulwich
+
+html: apidocs
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 	@echo
 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
-dirhtml:
+dirhtml: apidocs
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 	@echo
 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
 
-pickle:
+pickle: apidocs
 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
 	@echo
 	@echo "Build finished; now you can process the pickle files."
 
-json:
+json: apidocs
 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
 	@echo
 	@echo "Build finished; now you can process the JSON files."
 
-htmlhelp:
+htmlhelp: apidocs
 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
 	@echo
 	@echo "Build finished; now you can run HTML Help Workshop with the" \
 	      ".hhp project file in $(BUILDDIR)/htmlhelp."
 
-qthelp:
+qthelp: apidocs
 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
 	@echo
 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
@@ -66,14 +69,14 @@ qthelp:
 	@echo "To view the help file:"
 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dulwich.qhc"
 
-latex:
+latex: apidocs
 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 	@echo
 	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
 	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
 	      "run these through (pdf)latex."
 
-changes:
+changes: apidocs
 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
 	@echo
 	@echo "The overview file is in $(BUILDDIR)/changes."

+ 14 - 0
docs/api/index.txt

@@ -0,0 +1,14 @@
+This is the API documentation for Dulwich.
+
+Module reference
+----------------
+
+.. toctree::
+    :maxdepth: 3
+
+    modules
+
+Indices:
+
+* :ref:`modindex`
+* :ref:`search`

+ 8 - 3
docs/conf.py

@@ -26,7 +26,12 @@ dulwich = __import__('dulwich')
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc']
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.ifconfig',
+    'sphinx.ext.intersphinx',
+    'sphinx_epytext',
+    ]
 try:
     import rst2pdf
     if rst2pdf.version >= '0.16':
@@ -51,7 +56,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'dulwich'
-copyright = u'2011, Jelmer Vernooij'
+copyright = u'2011-2018 Jelmer Vernooij'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -106,7 +111,7 @@ pygments_style = 'sphinx'
 # The theme to use for HTML and HTML Help pages.  Major themes that come with
 # Sphinx are currently 'default' and 'sphinxdoc'.
 # html_theme = 'default'
-html_theme = 'nature'
+html_theme = 'agogo'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the

+ 3 - 2
docs/index.txt

@@ -7,12 +7,11 @@ dulwich - Python implementation of Git
 Overview
 ========
 
-.. include:: ../README.md
+.. include:: ../README.rst
 
 Documentation
 =============
 
-
 .. toctree::
     :maxdepth: 2
 
@@ -21,6 +20,8 @@ Documentation
 
     tutorial/index
 
+    api/index
+
 
 Changelog
 =========

+ 20 - 15
dulwich.egg-info/PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.19.9
+Version: 0.19.10
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij
@@ -9,15 +9,20 @@ License: Apachev2 or later or GPLv2
 Project-URL: Bug Tracker, https://github.com/dulwich/dulwich/issues
 Project-URL: GitHub, https://github.com/dulwich/dulwich
 Project-URL: Repository, https://www.dulwich.io/code/
-Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=master)](https://travis-ci.org/dulwich/dulwich)
-        [![Windows Build status](https://ci.appveyor.com/api/projects/status/mob7g4vnrfvvoweb?svg=true)](https://ci.appveyor.com/project/jelmer/dulwich/branch/master)
+Description: .. image:: https://travis-ci.org/dulwich/dulwich.png?branch=master
+          :alt: Build Status
+          :target: https://travis-ci.org/dulwich/dulwich
+        
+        .. image:: https://ci.appveyor.com/api/projects/status/mob7g4vnrfvvoweb?svg=true
+          :alt: Windows Build Status
+          :target: https://ci.appveyor.com/project/jelmer/dulwich/branch/master
         
         This is the Dulwich project.
         
         It aims to provide an interface to git repos (both local and remote) that
         doesn't call out to git directly but instead uses pure Python.
         
-        **Main website**: [www.dulwich.io](https://www.dulwich.io/)
+        **Main website**: <https://www.dulwich.io/>
         
         **License**: Apache License, version 2 or GNU General Public License, version 2 or later.
         
@@ -40,7 +45,7 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
             $ pip install dulwich --global-option="--pure"
         
         Note that you can also specify --global-option in a
-        [requirements.txt](https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers)
+        `requirements.txt <https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers>`_
         file, e.g. like this::
         
             dulwich --global-option=--pure
@@ -51,7 +56,7 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
         Dulwich comes with both a lower-level API and higher-level plumbing ("porcelain").
         
         For example, to use the lower level API to access the commit message of the
-        last commit:
+        last commit::
         
             >>> from dulwich.repo import Repo
             >>> r = Repo('.')
@@ -63,7 +68,7 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
             >>> c.message
             'Add note about encoding.\n'
         
-        And to print it using porcelain:
+        And to print it using porcelain::
         
             >>> from dulwich import porcelain
             >>> porcelain.log('.', max_entries=1)
@@ -78,26 +83,26 @@ Description: [![Build Status](https://travis-ci.org/dulwich/dulwich.png?branch=m
         ---------------------
         
         The dulwich documentation can be found in docs/ and
-        [on the web](https://www.dulwich.io/docs/).
+        `on the web <https://www.dulwich.io/docs/>`_.
         
         The API reference can be generated using pydoctor, by running "make pydoctor",
-        or [on the web](https://www.dulwich.io/apidocs).
+        or `on the web <https://www.dulwich.io/apidocs>`_.
         
         Help
         ----
         
-        There is a *#dulwich* IRC channel on the [Freenode](https://www.freenode.net/), and
-        [dulwich-announce](https://groups.google.com/forum/#!forum/dulwich-announce)
-        and [dulwich-discuss](https://groups.google.com/forum/#!forum/dulwich-discuss)
+        There is a *#dulwich* IRC channel on the `Freenode <https://www.freenode.net/>`_, and
+        `dulwich-announce <https://groups.google.com/forum/#!forum/dulwich-announce>`_
+        and `dulwich-discuss <https://groups.google.com/forum/#!forum/dulwich-discuss>`_
         mailing lists.
         
         Contributing
         ------------
         
-        For a full list of contributors, see the git logs or [AUTHORS](AUTHORS).
+        For a full list of contributors, see the git logs or `AUTHORS <AUTHORS>`_.
         
-        If you'd like to contribute to Dulwich, see the [CONTRIBUTING](CONTRIBUTING.md)
-        file and [list of open issues](https://github.com/dulwich/dulwich/issues).
+        If you'd like to contribute to Dulwich, see the `CONTRIBUTING <CONTRIBUTING.rst>`_
+        file and `list of open issues <https://github.com/dulwich/dulwich/issues>`_.
         
         Supported versions of Python
         ----------------------------

+ 6 - 5
dulwich.egg-info/SOURCES.txt

@@ -4,13 +4,13 @@
 .testr.conf
 .travis.yml
 AUTHORS
-CONTRIBUTING.md
+CONTRIBUTING.rst
 COPYING
 MANIFEST.in
 Makefile
 NEWS
-README.md
-README.swift.md
+README.rst
+README.swift.rst
 TODO
 appveyor.yml
 build.cmd
@@ -31,6 +31,7 @@ docs/index.txt
 docs/make.bat
 docs/performance.txt
 docs/protocol.txt
+docs/api/index.txt
 docs/tutorial/.gitignore
 docs/tutorial/Makefile
 docs/tutorial/conclusion.txt
@@ -58,6 +59,7 @@ dulwich/greenthreads.py
 dulwich/hooks.py
 dulwich/ignore.py
 dulwich/index.py
+dulwich/line_ending.py
 dulwich/log_utils.py
 dulwich/lru_cache.py
 dulwich/mailmap.py
@@ -78,8 +80,6 @@ dulwich/walk.py
 dulwich/web.py
 dulwich.egg-info/PKG-INFO
 dulwich.egg-info/SOURCES.txt
-dulwich.egg-info/SOURCES.txt.~1~
-dulwich.egg-info/SOURCES.txt.~2~
 dulwich.egg-info/dependency_links.txt
 dulwich.egg-info/requires.txt
 dulwich.egg-info/top_level.txt
@@ -104,6 +104,7 @@ dulwich/tests/test_greenthreads.py
 dulwich/tests/test_hooks.py
 dulwich/tests/test_ignore.py
 dulwich/tests/test_index.py
+dulwich/tests/test_line_ending.py
 dulwich/tests/test_lru_cache.py
 dulwich/tests/test_mailmap.py
 dulwich/tests/test_missing_obj_finder.py

+ 0 - 240
dulwich.egg-info/SOURCES.txt.~1~

@@ -1,240 +0,0 @@
-.coveragerc
-.gitignore
-.mailmap
-.testr.conf
-.travis.yml
-AUTHORS
-CONTRIBUTING.md
-COPYING
-MANIFEST.in
-Makefile
-NEWS
-PKG-INFO
-README.md
-README.swift.md
-TODO
-appveyor.yml
-build.cmd
-dulwich.cfg
-requirements.txt
-setup.cfg
-setup.py
-tox.ini
-bin/dul-receive-pack
-bin/dul-upload-pack
-bin/dulwich
-debian/.gitignore
-debian/changelog
-debian/compat
-debian/control
-debian/copyright
-debian/gbp.conf
-debian/pypy-dulwich.examples
-debian/pypy-dulwich.lintian-overrides
-debian/python-dulwich.examples
-debian/python-dulwich.lintian-overrides
-debian/python3-dulwich.examples
-debian/python3-dulwich.lintian-overrides
-debian/rules
-debian/watch
-debian/patches/01_no_urllib3_pypy
-debian/patches/series
-debian/source/format
-debian/source/options
-debian/tests/control
-debian/tests/testsuite2
-debian/tests/testsuite3
-debian/upstream/metadata
-debian/upstream/signing-key.asc
-devscripts/PREAMBLE.c
-devscripts/PREAMBLE.py
-devscripts/replace-preamble.sh
-docs/Makefile
-docs/conf.py
-docs/index.txt
-docs/make.bat
-docs/performance.txt
-docs/protocol.txt
-docs/tutorial/.gitignore
-docs/tutorial/Makefile
-docs/tutorial/conclusion.txt
-docs/tutorial/encoding.txt
-docs/tutorial/file-format.txt
-docs/tutorial/index.txt
-docs/tutorial/introduction.txt
-docs/tutorial/object-store.txt
-docs/tutorial/porcelain.txt
-docs/tutorial/remote.txt
-docs/tutorial/repo.txt
-docs/tutorial/tag.txt
-dulwich/__init__.py
-dulwich/_diff_tree.c
-dulwich/_objects.c
-dulwich/_pack.c
-dulwich/archive.py
-dulwich/client.py
-dulwich/config.py
-dulwich/diff_tree.py
-dulwich/errors.py
-dulwich/fastexport.py
-dulwich/file.py
-dulwich/greenthreads.py
-dulwich/hooks.py
-dulwich/ignore.py
-dulwich/index.py
-dulwich/log_utils.py
-dulwich/lru_cache.py
-dulwich/mailmap.py
-dulwich/object_store.py
-dulwich/objects.py
-dulwich/objectspec.py
-dulwich/pack.py
-dulwich/patch.py
-dulwich/porcelain.py
-dulwich/protocol.py
-dulwich/reflog.py
-dulwich/refs.py
-dulwich/repo.py
-dulwich/server.py
-dulwich/stash.py
-dulwich/stdint.h
-dulwich/walk.py
-dulwich/web.py
-dulwich.egg-info/PKG-INFO
-dulwich.egg-info/SOURCES.txt
-dulwich.egg-info/dependency_links.txt
-dulwich.egg-info/requires.txt
-dulwich.egg-info/top_level.txt
-dulwich/contrib/README.md
-dulwich/contrib/__init__.py
-dulwich/contrib/paramiko_vendor.py
-dulwich/contrib/release_robot.py
-dulwich/contrib/swift.py
-dulwich/contrib/test_release_robot.py
-dulwich/contrib/test_swift.py
-dulwich/contrib/test_swift_smoke.py
-dulwich/tests/__init__.py
-dulwich/tests/test_archive.py
-dulwich/tests/test_blackbox.py
-dulwich/tests/test_client.py
-dulwich/tests/test_config.py
-dulwich/tests/test_diff_tree.py
-dulwich/tests/test_fastexport.py
-dulwich/tests/test_file.py
-dulwich/tests/test_grafts.py
-dulwich/tests/test_greenthreads.py
-dulwich/tests/test_hooks.py
-dulwich/tests/test_ignore.py
-dulwich/tests/test_index.py
-dulwich/tests/test_lru_cache.py
-dulwich/tests/test_mailmap.py
-dulwich/tests/test_missing_obj_finder.py
-dulwich/tests/test_object_store.py
-dulwich/tests/test_objects.py
-dulwich/tests/test_objectspec.py
-dulwich/tests/test_pack.py
-dulwich/tests/test_patch.py
-dulwich/tests/test_porcelain.py
-dulwich/tests/test_protocol.py
-dulwich/tests/test_reflog.py
-dulwich/tests/test_refs.py
-dulwich/tests/test_repository.py
-dulwich/tests/test_server.py
-dulwich/tests/test_stash.py
-dulwich/tests/test_utils.py
-dulwich/tests/test_walk.py
-dulwich/tests/test_web.py
-dulwich/tests/utils.py
-dulwich/tests/compat/__init__.py
-dulwich/tests/compat/server_utils.py
-dulwich/tests/compat/test_client.py
-dulwich/tests/compat/test_pack.py
-dulwich/tests/compat/test_repository.py
-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/latest_change.py
-examples/memoryrepo.py

+ 0 - 241
dulwich.egg-info/SOURCES.txt.~2~

@@ -1,241 +0,0 @@
-.coveragerc
-.gitignore
-.mailmap
-.testr.conf
-.travis.yml
-AUTHORS
-CONTRIBUTING.md
-COPYING
-MANIFEST.in
-Makefile
-NEWS
-PKG-INFO
-README.md
-README.swift.md
-TODO
-appveyor.yml
-build.cmd
-dulwich.cfg
-requirements.txt
-setup.cfg
-setup.py
-tox.ini
-bin/dul-receive-pack
-bin/dul-upload-pack
-bin/dulwich
-debian/.gitignore
-debian/changelog
-debian/compat
-debian/control
-debian/copyright
-debian/gbp.conf
-debian/pypy-dulwich.examples
-debian/pypy-dulwich.lintian-overrides
-debian/python-dulwich.examples
-debian/python-dulwich.lintian-overrides
-debian/python3-dulwich.examples
-debian/python3-dulwich.lintian-overrides
-debian/rules
-debian/watch
-debian/patches/01_no_urllib3_pypy
-debian/patches/series
-debian/source/format
-debian/source/options
-debian/tests/control
-debian/tests/testsuite2
-debian/tests/testsuite3
-debian/upstream/metadata
-debian/upstream/signing-key.asc
-devscripts/PREAMBLE.c
-devscripts/PREAMBLE.py
-devscripts/replace-preamble.sh
-docs/Makefile
-docs/conf.py
-docs/index.txt
-docs/make.bat
-docs/performance.txt
-docs/protocol.txt
-docs/tutorial/.gitignore
-docs/tutorial/Makefile
-docs/tutorial/conclusion.txt
-docs/tutorial/encoding.txt
-docs/tutorial/file-format.txt
-docs/tutorial/index.txt
-docs/tutorial/introduction.txt
-docs/tutorial/object-store.txt
-docs/tutorial/porcelain.txt
-docs/tutorial/remote.txt
-docs/tutorial/repo.txt
-docs/tutorial/tag.txt
-dulwich/__init__.py
-dulwich/_diff_tree.c
-dulwich/_objects.c
-dulwich/_pack.c
-dulwich/archive.py
-dulwich/client.py
-dulwich/config.py
-dulwich/diff_tree.py
-dulwich/errors.py
-dulwich/fastexport.py
-dulwich/file.py
-dulwich/greenthreads.py
-dulwich/hooks.py
-dulwich/ignore.py
-dulwich/index.py
-dulwich/log_utils.py
-dulwich/lru_cache.py
-dulwich/mailmap.py
-dulwich/object_store.py
-dulwich/objects.py
-dulwich/objectspec.py
-dulwich/pack.py
-dulwich/patch.py
-dulwich/porcelain.py
-dulwich/protocol.py
-dulwich/reflog.py
-dulwich/refs.py
-dulwich/repo.py
-dulwich/server.py
-dulwich/stash.py
-dulwich/stdint.h
-dulwich/walk.py
-dulwich/web.py
-dulwich.egg-info/PKG-INFO
-dulwich.egg-info/SOURCES.txt
-dulwich.egg-info/SOURCES.txt.~1~
-dulwich.egg-info/dependency_links.txt
-dulwich.egg-info/requires.txt
-dulwich.egg-info/top_level.txt
-dulwich/contrib/README.md
-dulwich/contrib/__init__.py
-dulwich/contrib/paramiko_vendor.py
-dulwich/contrib/release_robot.py
-dulwich/contrib/swift.py
-dulwich/contrib/test_release_robot.py
-dulwich/contrib/test_swift.py
-dulwich/contrib/test_swift_smoke.py
-dulwich/tests/__init__.py
-dulwich/tests/test_archive.py
-dulwich/tests/test_blackbox.py
-dulwich/tests/test_client.py
-dulwich/tests/test_config.py
-dulwich/tests/test_diff_tree.py
-dulwich/tests/test_fastexport.py
-dulwich/tests/test_file.py
-dulwich/tests/test_grafts.py
-dulwich/tests/test_greenthreads.py
-dulwich/tests/test_hooks.py
-dulwich/tests/test_ignore.py
-dulwich/tests/test_index.py
-dulwich/tests/test_lru_cache.py
-dulwich/tests/test_mailmap.py
-dulwich/tests/test_missing_obj_finder.py
-dulwich/tests/test_object_store.py
-dulwich/tests/test_objects.py
-dulwich/tests/test_objectspec.py
-dulwich/tests/test_pack.py
-dulwich/tests/test_patch.py
-dulwich/tests/test_porcelain.py
-dulwich/tests/test_protocol.py
-dulwich/tests/test_reflog.py
-dulwich/tests/test_refs.py
-dulwich/tests/test_repository.py
-dulwich/tests/test_server.py
-dulwich/tests/test_stash.py
-dulwich/tests/test_utils.py
-dulwich/tests/test_walk.py
-dulwich/tests/test_web.py
-dulwich/tests/utils.py
-dulwich/tests/compat/__init__.py
-dulwich/tests/compat/server_utils.py
-dulwich/tests/compat/test_client.py
-dulwich/tests/compat/test_pack.py
-dulwich/tests/compat/test_repository.py
-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/latest_change.py
-examples/memoryrepo.py

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

@@ -1,8 +1,8 @@
 certifi
-urllib3>=1.21
+urllib3>=1.23
 
 [fastimport]
 fastimport
 
 [https]
-urllib3[secure]>=1.21
+urllib3[secure]>=1.23

+ 1 - 1
dulwich/__init__.py

@@ -22,4 +22,4 @@
 
 """Python implementation of the Git file formats and protocols."""
 
-__version__ = (0, 19, 9)
+__version__ = (0, 19, 10)

+ 10 - 3
dulwich/config.py

@@ -31,11 +31,18 @@ import os
 import sys
 
 from collections import (
-    Iterable,
     OrderedDict,
-    MutableMapping,
     )
-
+try:
+    from collections.abc import (
+        Iterable,
+        MutableMapping,
+        )
+except ImportError:  # python < 3.7
+    from collections import (
+        Iterable,
+        MutableMapping,
+        )
 
 from dulwich.file import GitFile
 

+ 181 - 0
dulwich/line_ending.py

@@ -0,0 +1,181 @@
+# line_ending.py -- Line ending conversion functions
+# Copyright (C) 2018-2018 Boris Feld <boris.feld@comet.ml>
+#
+# 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.
+#
+""" All line-ending related functions, from conversions to config processing
+
+Line-ending normalization is a complex beast. Here is some notes and details
+about how it seems to work.
+
+The normalization is a two-fold process that happens at two moments:
+
+- When reading a file from the index and to the working directory. For example
+  when doing a `git clone` or `git checkout` call. We call this process the
+  read filter in this module.
+- When writing a file to the index from the working directory. For example
+  when doing a `git add` call. We call this process the write filter in this
+  module.
+
+One thing to know is that Git does line-ending normalization only on text
+files. How does Git know that a file is text? We can either mark a file as a
+text file, a binary file or ask Git to automatically decides. Git has an
+heuristic to detect if a file is a text file or a binary file. It seems based
+on the percentage of non-printable characters in files.
+
+The code for this heuristic is here:
+https://git.kernel.org/pub/scm/git/git.git/tree/convert.c#n46
+
+Dulwich have an implementation with a slightly different heuristic, the
+`is_binary` function in `dulwich.patch`.
+
+The binary detection heuristic implementation is close to the one in JGit:
+https://github.com/eclipse/jgit/blob/f6873ffe522bbc3536969a3a3546bf9a819b92bf/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java#L300
+
+There is multiple variables that impact the normalization.
+
+First, a repository can contains a `.gitattributes` file (or more than one...)
+that can further customize the operation on some file patterns, for example:
+
+    *.txt text
+
+Force all `.txt` files to be treated as text files and to have their lines
+endings normalized.
+
+    *.jpg -text
+
+Force all `.jpg` files to be treated as binary files and to not have their
+lines endings converted.
+
+    *.vcproj text eol=crlf
+
+Force all `.vcproj` files to be treated as text files and to have their lines
+endings converted into `CRLF` in working directory no matter the native EOL of
+the platform.
+
+    *.sh text eol=lf
+
+Force all `.sh` files to be treated as text files and to have their lines
+endings converted into `LF` in working directory no matter the native EOL of
+the platform.
+
+If the `eol` attribute is not defined, Git uses the `core.eol` configuration
+value described later.
+
+    * text=auto
+
+Force all files to be scanned by the text file heuristic detection and to have
+their line endings normalized in case they are detected as text files.
+
+Git also have a obsolete attribute named `crlf` that can be translated to the
+corresponding text attribute value.
+
+Then there are some configuration option (that can be defined at the
+repository or user level):
+
+- core.autocrlf
+- core.eol
+
+`core.autocrlf` is taken into account for all files that doesn't have a `text`
+attribute defined in `.gitattributes`; it takes three possible values:
+
+    - `true`: This forces all files on the working directory to have CRLF
+      line-endings in the working directory and convert line-endings to LF
+      when writing to the index. When autocrlf is set to true, eol value is
+      ignored.
+    - `input`: Quite similar to the `true` value but only force the write
+      filter, ie line-ending of new files added to the index will get their
+      line-endings converted to LF.
+    - `false` (default): No normalization is done.
+
+`core.eol` is the top-level configuration to define the line-ending to use
+when applying the read_filer. It takes three possible values:
+
+    - `lf`: When normalization is done, force line-endings to be `LF` in the
+      working directory.
+    - `crlf`: When normalization is done, force line-endings to be `CRLF` in
+      the working directory.
+    - `native` (default): When normalization is done, force line-endings to be
+      the platform's native line ending.
+
+One thing to remember is when line-ending normalization is done on a file, Git
+always normalize line-ending to `LF` when writing to the index.
+
+There are sources that seems to indicate that Git won't do line-ending
+normalization when a file contains mixed line-endings. I think this logic
+might be in text / binary detection heuristic but couldn't find it yet.
+
+Sources:
+- https://git-scm.com/docs/git-config#git-config-coreeol
+- https://git-scm.com/docs/git-config#git-config-coreautocrlf
+- https://git-scm.com/docs/gitattributes#_checking_out_and_checking_in
+- https://adaptivepatchwork.com/2012/03/01/mind-the-end-of-your-line/
+"""
+
+CRLF = b"\r\n"
+LF = b"\n"
+
+
+def convert_crlf_to_lf(text_hunk):
+    """Convert CRLF in text hunk into LF
+
+    :param text_hunk: A bytes string representing a text hunk
+    :return: The text hunk with the same type, with CRLF replaced into LF
+    """
+    return text_hunk.replace(CRLF, LF)
+
+
+def convert_lf_to_crlf(text_hunk):
+    """Convert LF in text hunk into CRLF
+
+    :param text_hunk: A bytes string representing a text hunk
+    :return: The text hunk with the same type, with LF replaced into CRLF
+    """
+    # TODO find a more efficient way of doing it
+    intermediary = text_hunk.replace(CRLF, LF)
+    return intermediary.replace(LF, CRLF)
+
+
+def get_checkout_filter_autocrlf(core_autocrlf):
+    """ Returns the correct checkout filter base on autocrlf value
+
+    :param core_autocrlf: The bytes configuration value of core.autocrlf.
+        Valid values are: b'true', b'false' or b'input'.
+    :return: Either None if no filter has to be applied or a function
+        accepting a single argument, a binary text hunk
+    """
+
+    if core_autocrlf == b"true":
+        return convert_lf_to_crlf
+
+    return None
+
+
+def get_checkin_filter_autocrlf(core_autocrlf):
+    """ Returns the correct checkin filter base on autocrlf value
+
+    :param core_autocrlf: The bytes configuration value of core.autocrlf.
+        Valid values are: b'true', b'false' or b'input'.
+    :return: Either None if no filter has to be applied or a function
+        accepting a single argument, a binary text hunk
+    """
+
+    if core_autocrlf == b"true" or core_autocrlf == b"input":
+        return convert_crlf_to_lf
+
+    # Checking filter should never be `convert_lf_to_crlf`
+    return None

+ 29 - 6
dulwich/objects.py

@@ -67,6 +67,8 @@ S_IFGITLINK = 0o160000
 
 MAX_TIME = 9223372036854775807  # (2**63) - 1 - signed long int max
 
+BEGIN_PGP_SIGNATURE = b"-----BEGIN PGP SIGNATURE-----"
+
 
 def S_ISGITLINK(m):
     """Check if a mode indicates a submodule.
@@ -520,27 +522,31 @@ class ShaFile(object):
         return "<%s %s>" % (self.__class__.__name__, self.id)
 
     def __ne__(self, other):
+        """Check whether this object does not match the other."""
         return not isinstance(other, ShaFile) or self.id != other.id
 
     def __eq__(self, other):
         """Return True if the SHAs of the two objects match.
-
-        It doesn't make sense to talk about an order on ShaFiles, so we don't
-        override the rich comparison methods (__le__, etc.).
         """
         return isinstance(other, ShaFile) and self.id == other.id
 
     def __lt__(self, other):
+        """Return whether SHA of this object is less than the other.
+        """
         if not isinstance(other, ShaFile):
             raise TypeError
         return self.id < other.id
 
     def __le__(self, other):
+        """Check whether SHA of this object is less than or equal to the other.
+        """
         if not isinstance(other, ShaFile):
             raise TypeError
         return self.id <= other.id
 
     def __cmp__(self, other):
+        """Compare the SHA of this object with that of the other object.
+        """
         if not isinstance(other, ShaFile):
             raise TypeError
         return cmp(self.id, other.id)  # noqa: F821
@@ -687,7 +693,7 @@ class Tag(ShaFile):
 
     __slots__ = ('_tag_timezone_neg_utc', '_name', '_object_sha',
                  '_object_class', '_tag_time', '_tag_timezone',
-                 '_tagger', '_message')
+                 '_tagger', '_message', '_signature')
 
     def __init__(self):
         super(Tag, self).__init__()
@@ -695,6 +701,7 @@ class Tag(ShaFile):
         self._tag_time = None
         self._tag_timezone = None
         self._tag_timezone_neg_utc = False
+        self._signature = None
 
     @classmethod
     def from_path(cls, filename):
@@ -753,6 +760,8 @@ class Tag(ShaFile):
         if self._message is not None:
             chunks.append(b'\n')  # To close headers
             chunks.append(self._message)
+        if self._signature is not None:
+            chunks.append(self._signature)
         return chunks
 
     def _deserialize(self, chunks):
@@ -777,7 +786,18 @@ class Tag(ShaFile):
                  (self._tag_timezone,
                   self._tag_timezone_neg_utc)) = parse_time_entry(value)
             elif field is None:
-                self._message = value
+                if value is None:
+                    self._message = None
+                    self._signature = None
+                else:
+                    try:
+                        sig_idx = value.index(BEGIN_PGP_SIGNATURE)
+                    except ValueError:
+                        self._message = value
+                        self._signature = None
+                    else:
+                        self._message = value[:sig_idx]
+                        self._signature = value[sig_idx:]
             else:
                 raise ObjectFormatException("Unknown field %s" % field)
 
@@ -806,7 +826,10 @@ class Tag(ShaFile):
             "tag_timezone",
             "The timezone that tag_time is in.")
     message = serializable_property(
-            "message", "The message attached to this tag")
+            "message", "the message attached to this tag")
+
+    signature = serializable_property(
+            "signature", "Optional detached GPG signature")
 
 
 class TreeEntry(namedtuple('TreeEntry', ['path', 'mode', 'sha'])):

+ 1 - 1
dulwich/objectspec.py

@@ -87,12 +87,12 @@ def parse_reftuple(lh_container, rh_container, refspec):
     :return: A tuple with left and right ref
     :raise KeyError: If one of the refs can not be found
     """
+    refspec = to_bytes(refspec)
     if refspec.startswith(b"+"):
         force = True
         refspec = refspec[1:]
     else:
         force = False
-    refspec = to_bytes(refspec)
     if b":" in refspec:
         (lh, rh) = refspec.split(b":")
     else:

+ 18 - 2
dulwich/porcelain.py

@@ -681,7 +681,8 @@ def tag(*args, **kwargs):
 
 def tag_create(
         repo, tag, author=None, message=None, annotated=False,
-        objectish="HEAD", tag_time=None, tag_timezone=None):
+        objectish="HEAD", tag_time=None, tag_timezone=None,
+        sign=False):
     """Creates a tag in git via dulwich calls:
 
     :param repo: Path to repository
@@ -692,6 +693,7 @@ def tag_create(
     :param objectish: object the tag should point at, defaults to HEAD
     :param tag_time: Optional time for annotated tag
     :param tag_timezone: Optional timezone for annotated tag
+    :param sign: GPG Sign the tag
     """
 
     with open_repo_closing(repo) as r:
@@ -702,7 +704,7 @@ def tag_create(
             tag_obj = Tag()
             if author is None:
                 # TODO(jelmer): Don't use repo private method.
-                author = r._get_user_identity()
+                author = r._get_user_identity(r.get_config_stack())
             tag_obj.tagger = author
             tag_obj.message = message
             tag_obj.name = tag
@@ -716,6 +718,10 @@ def tag_create(
             elif isinstance(tag_timezone, str):
                 tag_timezone = parse_timezone(tag_timezone)
             tag_obj.tag_timezone = tag_timezone
+            if sign:
+                import gpg
+                with gpg.Context(armor=True) as c:
+                    tag_obj.signature, result = c.sign(tag_obj.as_raw_string())
             r.object_store.add_object(tag_obj)
             tag_id = tag_obj.id
         else:
@@ -1411,3 +1417,13 @@ def get_object_by_path(repo, path, committish=None):
             r.object_store.__getitem__,
             base_tree, path)
         return r[sha]
+
+
+def write_tree(repo):
+    """Write a tree object from the index.
+
+    :param repo: Repository for which to write tree
+    :return: tree id for the tree that was written
+    """
+    with open_repo_closing(repo) as r:
+        return r.open_index().commit(r.object_store)

+ 11 - 1
dulwich/refs.py

@@ -617,7 +617,7 @@ class DiskRefsContainer(RefsContainer):
                     # Read only the first 40 bytes
                     return header + f.read(40 - len(SYMREF))
         except IOError as e:
-            if e.errno in (errno.ENOENT, errno.EISDIR):
+            if e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
                 return None
             raise
 
@@ -687,6 +687,16 @@ class DiskRefsContainer(RefsContainer):
         except (KeyError, IndexError):
             realname = name
         filename = self.refpath(realname)
+
+        # make sure none of the ancestor folders is in packed refs
+        probe_ref = os.path.dirname(realname)
+        packed_refs = self.get_packed_refs()
+        while probe_ref:
+            if packed_refs.get(probe_ref, None) is not None:
+                raise OSError(errno.ENOTDIR,
+                              'Not a directory: {}'.format(filename))
+            probe_ref = os.path.dirname(probe_ref)
+
         ensure_dir_exists(os.path.dirname(filename))
         with GitFile(filename, 'wb') as f:
             if old_ref is not None:

+ 28 - 2
dulwich/repo.py

@@ -252,6 +252,10 @@ 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."""
+        raise NotImplementedError(self._del_named_file)
+
     def open_index(self):
         """Open the index for this repository.
 
@@ -654,6 +658,13 @@ class BaseRepo(object):
         for sha in to_remove:
             del self._graftpoints[sha]
 
+    def _read_heads(self, name):
+        f = self.get_named_file(name)
+        if f is None:
+            return []
+        with f:
+            return [l.strip() for l in f.readlines() if l.strip()]
+
     def do_commit(self, message=None, committer=None,
                   author=None, commit_timestamp=None,
                   commit_timezone=None, author_timestamp=None,
@@ -696,8 +707,7 @@ class BaseRepo(object):
 
         config = self.get_config_stack()
         if merge_heads is None:
-            # FIXME: Read merge heads from .git/MERGE_HEADS
-            merge_heads = []
+            merge_heads = self._read_heads('MERGE_HEADS')
         if committer is None:
             committer = self._get_user_identity(config)
         check_user_identity(committer)
@@ -768,6 +778,8 @@ class BaseRepo(object):
                 # commit and all its objects as garbage.
                 raise CommitError("%s changed during commit" % (ref,))
 
+        self._del_named_file('MERGE_HEADS')
+
         try:
             self.hooks['post-commit'].execute()
         except HookError as e:  # silent failure
@@ -939,6 +951,14 @@ class Repo(BaseRepo):
         with GitFile(os.path.join(self.controldir(), path), 'wb') as f:
             f.write(contents)
 
+    def _del_named_file(self, path):
+        try:
+            os.unlink(os.path.join(self.controldir(), path))
+        except (IOError, OSError) as e:
+            if e.errno == errno.ENOENT:
+                return
+            raise
+
     def get_named_file(self, path, basedir=None):
         """Get a file from the control dir with a specific name.
 
@@ -1283,6 +1303,12 @@ class MemoryRepo(BaseRepo):
         """
         self._named_files[path] = contents
 
+    def _del_named_file(self, path):
+        try:
+            del self._named_files[path]
+        except KeyError:
+            pass
+
     def get_named_file(self, path):
         """Get a file from the control dir with a specific name.
 

+ 9 - 1
dulwich/server.py

@@ -331,6 +331,8 @@ class UploadPackHandler(PackHandler):
                 # all relevant tags.
                 # TODO: fix behavior when missing
                 return {}
+        # TODO(jelmer): Integrate this with the refs logic in
+        # Repo.fetch_objects
         tagged = {}
         for name, sha in refs.items():
             peeled_sha = repo.get_peeled(name)
@@ -551,6 +553,13 @@ class _ProtocolGraphWalker(object):
         values = set(heads.values())
         if self.advertise_refs or not self.http_req:
             for i, (ref, sha) in enumerate(sorted(heads.items())):
+                try:
+                    peeled_sha = self.get_peeled(ref)
+                except KeyError:
+                    # Skip refs that are inaccessible
+                    # TODO(jelmer): Integrate with Repo.fetch_objects refs
+                    # logic.
+                    continue
                 line = sha + b' ' + ref
                 if not i:
                     line += (b'\x00' +
@@ -558,7 +567,6 @@ class _ProtocolGraphWalker(object):
                                  self.handler.capabilities() +
                                  symref_capabilities(symrefs.items())))
                 self.proto.write_pkt_line(line + b'\n')
-                peeled_sha = self.get_peeled(ref)
                 if peeled_sha != sha:
                     self.proto.write_pkt_line(
                         peeled_sha + b' ' + ref + ANNOTATED_TAG_SUFFIX + b'\n')

+ 1 - 0
dulwich/tests/__init__.py

@@ -110,6 +110,7 @@ def self_test_suite():
         'hooks',
         'ignore',
         'index',
+        'line_ending',
         'lru_cache',
         'mailmap',
         'objects',

+ 94 - 0
dulwich/tests/test_line_ending.py

@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# test_line_ending.py -- Tests for the line ending functions
+# encoding: utf-8
+# Copyright (C) 2018-2019 Boris Feld <boris.feld@comet.ml>
+#
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as public by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
+"""Tests for the line ending conversion."""
+
+from dulwich.line_ending import (
+    convert_crlf_to_lf,
+    convert_lf_to_crlf,
+    get_checkin_filter_autocrlf,
+    get_checkout_filter_autocrlf,
+)
+from dulwich.tests import TestCase
+
+
+class LineEndingConversion(TestCase):
+    """Test the line ending conversion functions in various cases"""
+
+    def test_convert_crlf_to_lf_no_op(self):
+        self.assertEqual(convert_crlf_to_lf(b"foobar"), b"foobar")
+
+    def test_convert_crlf_to_lf(self):
+        self.assertEqual(
+            convert_crlf_to_lf(b"line1\r\nline2"), b"line1\nline2"
+        )
+
+    def test_convert_crlf_to_lf_mixed(self):
+        self.assertEqual(
+            convert_crlf_to_lf(b"line1\r\n\nline2"), b"line1\n\nline2"
+        )
+
+    def test_convert_lf_to_crlf_no_op(self):
+        self.assertEqual(convert_lf_to_crlf(b"foobar"), b"foobar")
+
+    def test_convert_lf_to_crlf(self):
+        self.assertEqual(
+            convert_lf_to_crlf(b"line1\nline2"), b"line1\r\nline2"
+        )
+
+    def test_convert_lf_to_crlf_mixed(self):
+        self.assertEqual(
+            convert_lf_to_crlf(b"line1\r\n\nline2"), b"line1\r\n\r\nline2"
+        )
+
+
+class GetLineEndingAutocrlfFilters(TestCase):
+
+    def test_get_checkin_filter_autocrlf_default(self):
+        checkin_filter = get_checkin_filter_autocrlf(b"false")
+
+        self.assertEqual(checkin_filter, None)
+
+    def test_get_checkin_filter_autocrlf_true(self):
+        checkin_filter = get_checkin_filter_autocrlf(b"true")
+
+        self.assertEqual(checkin_filter, convert_crlf_to_lf)
+
+    def test_get_checkin_filter_autocrlf_input(self):
+        checkin_filter = get_checkin_filter_autocrlf(b"input")
+
+        self.assertEqual(checkin_filter, convert_crlf_to_lf)
+
+    def test_get_checkout_filter_autocrlf_default(self):
+        checkout_filter = get_checkout_filter_autocrlf(b"false")
+
+        self.assertEqual(checkout_filter, None)
+
+    def test_get_checkout_filter_autocrlf_true(self):
+        checkout_filter = get_checkout_filter_autocrlf(b"true")
+
+        self.assertEqual(checkout_filter, convert_lf_to_crlf)
+
+    def test_get_checkout_filter_autocrlf_input(self):
+        checkout_filter = get_checkout_filter_autocrlf(b"input")
+
+        self.assertEqual(checkout_filter, None)

+ 3 - 0
dulwich/tests/test_objects.py

@@ -204,6 +204,9 @@ class BlobReadTests(TestCase):
         self.assertEqual(
                 t.message,
                 b'This is a signed tag\n'
+                )
+        self.assertEqual(
+                t.signature,
                 b'-----BEGIN PGP SIGNATURE-----\n'
                 b'Version: GnuPG v1.4.9 (GNU/Linux)\n'
                 b'\n'

+ 5 - 0
dulwich/tests/test_objectspec.py

@@ -174,6 +174,11 @@ class ParseReftupleTests(TestCase):
         self.assertEqual((b"refs/heads/foo", None, False),
                          parse_reftuple(r, r, b"refs/heads/foo:"))
 
+    def test_default_with_string(self):
+        r = {b"refs/heads/foo": "bla"}
+        self.assertEqual((b"refs/heads/foo", b"refs/heads/foo", False),
+                         parse_reftuple(r, r, "foo"))
+
 
 class ParseReftuplesTests(TestCase):
 

+ 12 - 0
dulwich/tests/test_porcelain.py

@@ -1562,3 +1562,15 @@ class GetObjectBypathTests(PorcelainTestCase):
         self.assertRaises(
             KeyError,
             porcelain.get_object_by_path, self.repo, 'foo')
+
+
+class WriteTreeTests(PorcelainTestCase):
+
+    def test_simple(self):
+        fullpath = os.path.join(self.repo.path, 'foo')
+        with open(fullpath, 'w') as f:
+            f.write("BAR")
+        porcelain.add(repo=self.repo.path, paths=[fullpath])
+        self.assertEqual(
+            b'd2092c8a9f311f0311083bf8d177f2ca0ab5b241',
+            porcelain.write_tree(self.repo))

+ 38 - 4
dulwich/tests/test_refs.py

@@ -335,10 +335,37 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
 
     def test_setitem(self):
         RefsContainerTests.test_setitem(self)
-        f = open(os.path.join(self._refs.path, b'refs', b'some', b'ref'), 'rb')
-        self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
-                         f.read()[:40])
-        f.close()
+        path = os.path.join(self._refs.path, b'refs', b'some', b'ref')
+        with open(path, 'rb') as f:
+            self.assertEqual(b'42d06bd4b77fed026b154d16493e5deab78f02ec',
+                             f.read()[:40])
+
+        self.assertRaises(
+            OSError, self._refs.__setitem__,
+            b'refs/some/ref/sub', b'42d06bd4b77fed026b154d16493e5deab78f02ec')
+
+    def test_setitem_packed(self):
+        with open(os.path.join(self._refs.path, b'packed-refs'), 'w') as f:
+            f.write('# pack-refs with: peeled fully-peeled sorted \n')
+            f.write(
+                '42d06bd4b77fed026b154d16493e5deab78f02ec refs/heads/packed\n')
+
+        # It's allowed to set a new ref on a packed ref, the new ref will be
+        # placed outside on refs/
+        self._refs[b'refs/heads/packed'] = (
+            b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8'
+        )
+        packed_ref_path = os.path.join(
+            self._refs.path, b'refs', b'heads', b'packed')
+        with open(packed_ref_path, 'rb') as f:
+            self.assertEqual(
+                b'3ec9c43c84ff242e3ef4a9fc5bc111fd780a76a8',
+                f.read()[:40])
+
+        self.assertRaises(
+            OSError, self._refs.__setitem__,
+            b'refs/heads/packed/sub',
+            b'42d06bd4b77fed026b154d16493e5deab78f02ec')
 
     def test_setitem_symbolic(self):
         ones = b'1' * 40
@@ -486,6 +513,13 @@ class DiskRefsContainerTests(RefsContainerTests, TestCase):
                          self._refs.read_ref(b'refs/heads/packed'))
         self.assertEqual(None, self._refs.read_ref(b'nonexistant'))
 
+    def test_read_loose_ref(self):
+        self._refs[b'refs/heads/foo'] = (
+            b'df6800012397fb85c56e7418dd4eb9405dee075c'
+        )
+
+        self.assertEqual(None, self._refs.read_ref(b'refs/heads/foo/bar'))
+
     def test_non_ascii(self):
         try:
             encoded_ref = u'refs/tags/schön'.encode(

+ 29 - 1
dulwich/tests/test_repository.py

@@ -700,7 +700,7 @@ class BuildRepoRootTests(TestCase):
 
     def test_update_shallow(self):
         self._repo.update_shallow(None, None)  # no op
-        self.assertEquals(set(), self._repo.get_shallow())
+        self.assertEqual(set(), self._repo.get_shallow())
         self._repo.update_shallow(
                 [b'a90fa2d900a17e99b433217e988c4eb4a2e9a097'],
                 None)
@@ -754,6 +754,34 @@ class BuildRepoRootTests(TestCase):
         self.assertTrue(stat.S_ISLNK(b_mode))
         self.assertEqual(b'a', r[b_id].data)
 
+    def test_commit_merge_heads_file(self):
+        tmp_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, tmp_dir)
+        r = Repo.init(tmp_dir)
+        with open(os.path.join(r.path, 'a'), 'w') as f:
+            f.write('initial text')
+        c1 = r.do_commit(
+            b'initial commit',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
+            commit_timestamp=12395, commit_timezone=0,
+            author_timestamp=12395, author_timezone=0)
+        with open(os.path.join(r.path, 'a'), 'w') as f:
+            f.write('merged text')
+        with open(os.path.join(r.path, '.git', 'MERGE_HEADS'), 'w') as f:
+            f.write('c27a2d21dd136312d7fa9e8baabb82561a1727d0\n')
+        r.stage(['a'])
+        commit_sha = r.do_commit(
+            b'deleted a',
+            committer=b'Test Committer <test@nodomain.com>',
+            author=b'Test Author <test@nodomain.com>',
+            commit_timestamp=12395, commit_timezone=0,
+            author_timestamp=12395, author_timezone=0)
+        self.assertEqual([
+            c1,
+            b'c27a2d21dd136312d7fa9e8baabb82561a1727d0'],
+            r[commit_sha].parents)
+
     def test_commit_deleted(self):
         r = self._repo
         os.remove(os.path.join(r.path, 'a'))

+ 1 - 1
requirements.txt

@@ -1 +1 @@
-urllib3[secure]==1.22
+urllib3[secure]>=1.23

+ 4 - 4
setup.py

@@ -15,7 +15,7 @@ import io
 import os
 import sys
 
-dulwich_version_string = '0.19.9'
+dulwich_version_string = '0.19.10'
 
 include_dirs = []
 # Windows MSVC support
@@ -77,14 +77,14 @@ setup_kwargs = {}
 if has_setuptools:
     setup_kwargs['extras_require'] = {
         'fastimport': ['fastimport'],
-        'https': ['urllib3[secure]>=1.21'],
+        'https': ['urllib3[secure]>=1.23'],
         }
-    setup_kwargs['install_requires'] = ['urllib3>=1.21', 'certifi']
+    setup_kwargs['install_requires'] = ['urllib3>=1.23', 'certifi']
     setup_kwargs['include_package_data'] = True
     setup_kwargs['test_suite'] = 'dulwich.tests.test_suite'
     setup_kwargs['tests_require'] = tests_require
 
-with io.open(os.path.join(os.path.dirname(__file__), "README.md"),
+with io.open(os.path.join(os.path.dirname(__file__), "README.rst"),
              encoding="utf-8") as f:
     description = f.read()