Sfoglia il codice sorgente

New upstream version 0.18.5

Jelmer Vernooij 7 anni fa
parent
commit
068bb54c5e

+ 23 - 0
NEWS

@@ -1,3 +1,26 @@
+0.18.5	2017-10-29
+
+ BUG FIXES
+
+  * Fix cwd for hooks. (Fabian Grünbichler)
+
+  * Fix setting of origin in config when non-standard origin is passed into
+    ``Repo.clone``. (Kenneth Lareau, #565)
+
+  * Prevent setting SSH arguments from SSH URLs when using SSH through a
+    subprocess. Note that Dulwich doesn't support cloning submodules.
+    (CVE 2017-1000117) (Jelmer Vernooij)
+
+ IMPROVEMENTS
+
+  * Silently ignored directories in ``Repo.stage``.
+    (Jelmer Vernooij, #564)
+
+ API CHANGES
+
+  * GitFile now raises ``FileLocked`` when encountering a lock
+    rather than OSError(EEXIST). (Jelmer Vernooij)
+
 0.18.4	2017-10-01
 
  BUG FIXES

+ 1 - 1
PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: dulwich
-Version: 0.18.4
+Version: 0.18.5
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: UNKNOWN

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

@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: dulwich
-Version: 0.18.4
+Version: 0.18.5
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: UNKNOWN

+ 1 - 1
dulwich/__init__.py

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

+ 9 - 0
dulwich/client.py

@@ -1080,6 +1080,13 @@ class SSHVendor(object):
         raise NotImplementedError(self.run_command)
 
 
+class StrangeHostname(Exception):
+    """Refusing to connect to strange SSH hostname."""
+
+    def __init__(self, hostname):
+        super(StrangeHostname, self).__init__(hostname)
+
+
 class SubprocessSSHVendor(SSHVendor):
     """SSH vendor that shells out to the local 'ssh' command."""
 
@@ -1090,6 +1097,8 @@ class SubprocessSSHVendor(SSHVendor):
             args.extend(['-p', str(port)])
         if username is not None:
             host = '%s@%s' % (username, host)
+        if host.startswith('-'):
+            raise StrangeHostname(hostname=host)
         args.append(host)
         proc = subprocess.Popen(args + [command], bufsize=0,
                                 stdin=subprocess.PIPE,

+ 19 - 3
dulwich/file.py

@@ -90,6 +90,15 @@ def GitFile(filename, mode='rb', bufsize=-1):
         return io.open(filename, mode, bufsize)
 
 
+class FileLocked(Exception):
+    """File is already locked."""
+
+    def __init__(self, filename, lockfilename):
+        self.filename = filename
+        self.lockfilename = lockfilename
+        super(FileLocked, self).__init__(filename, lockfilename)
+
+
 class _GitFile(object):
     """File that follows the git locking protocol for writes.
 
@@ -110,9 +119,15 @@ class _GitFile(object):
     def __init__(self, filename, mode, bufsize):
         self._filename = filename
         self._lockfilename = '%s.lock' % self._filename
-        fd = os.open(
-            self._lockfilename,
-            os.O_RDWR | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0))
+        try:
+            fd = os.open(
+                self._lockfilename,
+                os.O_RDWR | os.O_CREAT | os.O_EXCL |
+                getattr(os, "O_BINARY", 0))
+        except OSError as e:
+            if e.errno == errno.EEXIST:
+                raise FileLocked(filename, self._lockfilename)
+            raise
         self._file = os.fdopen(fd, mode, bufsize)
         self._closed = False
 
@@ -149,6 +164,7 @@ class _GitFile(object):
         """
         if self._closed:
             return
+        os.fsync(self._file.fileno())
         self._file.close()
         try:
             try:

+ 9 - 5
dulwich/hooks.py

@@ -52,7 +52,8 @@ class ShellHook(Hook):
     """
 
     def __init__(self, name, path, numparam,
-                 pre_exec_callback=None, post_exec_callback=None):
+                 pre_exec_callback=None, post_exec_callback=None,
+                 cwd=None):
         """Setup shell hook definition
 
         :param name: name of hook for error messages
@@ -66,6 +67,7 @@ class ShellHook(Hook):
             Defaults to None. Takes in a boolean for hook success and the
             modified argument list and returns the final hook return value
             if applicable
+        :param cwd: working directory to switch to when executing the hook
         """
         self.name = name
         self.filepath = path
@@ -74,6 +76,8 @@ class ShellHook(Hook):
         self.pre_exec_callback = pre_exec_callback
         self.post_exec_callback = post_exec_callback
 
+        self.cwd = cwd
+
         if sys.version_info[0] == 2 and sys.platform == 'win32':
             # Python 2 on windows does not support unicode file paths
             # http://bugs.python.org/issue1759845
@@ -91,7 +95,7 @@ class ShellHook(Hook):
             args = self.pre_exec_callback(*args)
 
         try:
-            ret = subprocess.call([self.filepath] + list(args))
+            ret = subprocess.call([self.filepath] + list(args), cwd=self.cwd)
             if ret != 0:
                 if (self.post_exec_callback is not None):
                     self.post_exec_callback(0, *args)
@@ -110,7 +114,7 @@ class PreCommitShellHook(ShellHook):
     def __init__(self, controldir):
         filepath = os.path.join(controldir, 'hooks', 'pre-commit')
 
-        ShellHook.__init__(self, 'pre-commit', filepath, 0)
+        ShellHook.__init__(self, 'pre-commit', filepath, 0, cwd=controldir)
 
 
 class PostCommitShellHook(ShellHook):
@@ -119,7 +123,7 @@ class PostCommitShellHook(ShellHook):
     def __init__(self, controldir):
         filepath = os.path.join(controldir, 'hooks', 'post-commit')
 
-        ShellHook.__init__(self, 'post-commit', filepath, 0)
+        ShellHook.__init__(self, 'post-commit', filepath, 0, cwd=controldir)
 
 
 class CommitMsgShellHook(ShellHook):
@@ -149,4 +153,4 @@ class CommitMsgShellHook(ShellHook):
             os.unlink(args[0])
 
         ShellHook.__init__(self, 'commit-msg', filepath, 1,
-                           prepare_msg, clean_msg)
+                           prepare_msg, clean_msg, controldir)

+ 1 - 1
dulwich/index.py

@@ -276,7 +276,7 @@ class Index(object):
         assert isinstance(name, bytes)
         assert len(x) == 10
         # Remove the old entry if any
-        self._byname[name] = x
+        self._byname[name] = IndexEntry(*x)
 
     def __delitem__(self, name):
         assert isinstance(name, bytes)

+ 4 - 3
dulwich/porcelain.py

@@ -276,6 +276,7 @@ def clone(source, target=None, bare=False, checkout=None,
     :param checkout: Whether or not to check-out HEAD after cloning
     :param errstream: Optional stream to write progress to
     :param outstream: Optional stream to write progress to (deprecated)
+    :param origin: Name of remote from the repository used to clone
     :return: The new repository
     """
     if outstream is not None:
@@ -323,10 +324,10 @@ def clone(source, target=None, bare=False, checkout=None,
         target_config = r.get_config()
         if not isinstance(source, bytes):
             source = source.encode(DEFAULT_ENCODING)
-        target_config.set((b'remote', b'origin'), b'url', source)
+        target_config.set((b'remote', origin), b'url', source)
         target_config.set(
-            (b'remote', b'origin'), b'fetch',
-            b'+refs/heads/*:refs/remotes/origin/*')
+            (b'remote', origin), b'fetch',
+            b'+refs/heads/*:refs/remotes/' + origin + b'/*')
         target_config.write_to_path()
         if checkout and b"HEAD" in r.refs:
             errstream.write(b'Checking out HEAD\n')

+ 9 - 3
dulwich/repo.py

@@ -869,9 +869,15 @@ class Repo(BaseRepo):
                 except KeyError:
                     pass  # already removed
             else:
-                blob = blob_from_path_and_stat(full_path, st)
-                self.object_store.add_object(blob)
-                index[tree_path] = index_entry_from_stat(st, blob.id, 0)
+                if not stat.S_ISDIR(st.st_mode):
+                    blob = blob_from_path_and_stat(full_path, st)
+                    self.object_store.add_object(blob)
+                    index[tree_path] = index_entry_from_stat(st, blob.id, 0)
+                else:
+                    try:
+                        del index[tree_path]
+                    except KeyError:
+                        pass
         index.write()
 
     def clone(self, target_path, mkdir=True, bare=False,

+ 10 - 0
dulwich/tests/test_client.py

@@ -50,6 +50,8 @@ from dulwich.client import (
     HttpGitClient,
     ReportStatusParser,
     SendPackError,
+    StrangeHostname,
+    SubprocessSSHVendor,
     UpdateRefsError,
     default_urllib2_opener,
     get_transport_and_path,
@@ -942,3 +944,11 @@ class DefaultUrllib2OpenerTest(TestCase):
         opener = default_urllib2_opener(config=config)
         self.assertIn(urllib2.ProxyHandler,
                       list(map(lambda x: x.__class__, opener.handlers)))
+
+
+class SubprocessSSHVendorTests(TestCase):
+
+    def test_run_command_dashes(self):
+        vendor = SubprocessSSHVendor()
+        self.assertRaises(StrangeHostname, vendor.run_command, '--weird-host',
+                          'git-clone-url')

+ 3 - 4
dulwich/tests/test_file.py

@@ -18,14 +18,13 @@
 # License, Version 2.0.
 #
 
-import errno
 import io
 import os
 import shutil
 import sys
 import tempfile
 
-from dulwich.file import GitFile, _fancy_rename
+from dulwich.file import FileLocked, GitFile, _fancy_rename
 from dulwich.tests import (
     SkipTest,
     TestCase,
@@ -158,8 +157,8 @@ class GitFileTests(TestCase):
         try:
             f2 = GitFile(foo, 'wb')
             self.fail()
-        except OSError as e:
-            self.assertEqual(errno.EEXIST, e.errno)
+        except FileLocked:
+            pass
         else:
             f2.close()
         f1.write(b' contents')

+ 40 - 14
dulwich/tests/test_hooks.py

@@ -43,6 +43,10 @@ class ShellHookTests(TestCase):
             self.skipTest('shell hook tests requires POSIX shell')
 
     def test_hook_pre_commit(self):
+        repo_dir = os.path.join(tempfile.mkdtemp())
+        os.mkdir(os.path.join(repo_dir, 'hooks'))
+        self.addCleanup(shutil.rmtree, repo_dir)
+
         pre_commit_fail = """#!/bin/sh
 exit 1
 """
@@ -50,10 +54,8 @@ exit 1
         pre_commit_success = """#!/bin/sh
 exit 0
 """
-
-        repo_dir = os.path.join(tempfile.mkdtemp())
-        os.mkdir(os.path.join(repo_dir, 'hooks'))
-        self.addCleanup(shutil.rmtree, repo_dir)
+        pre_commit_cwd = """#!/bin/sh
+if [ "$(pwd)" = '""" + repo_dir + "' ]; then exit 0; else exit 1; fi\n"
 
         pre_commit = os.path.join(repo_dir, 'hooks', 'pre-commit')
         hook = PreCommitShellHook(repo_dir)
@@ -64,6 +66,12 @@ exit 0
 
         self.assertRaises(errors.HookError, hook.execute)
 
+        with open(pre_commit, 'w') as f:
+            f.write(pre_commit_cwd)
+        os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+        hook.execute()
+
         with open(pre_commit, 'w') as f:
             f.write(pre_commit_success)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
@@ -72,6 +80,10 @@ exit 0
 
     def test_hook_commit_msg(self):
 
+        repo_dir = os.path.join(tempfile.mkdtemp())
+        os.mkdir(os.path.join(repo_dir, 'hooks'))
+        self.addCleanup(shutil.rmtree, repo_dir)
+
         commit_msg_fail = """#!/bin/sh
 exit 1
 """
@@ -80,9 +92,8 @@ exit 1
 exit 0
 """
 
-        repo_dir = os.path.join(tempfile.mkdtemp())
-        os.mkdir(os.path.join(repo_dir, 'hooks'))
-        self.addCleanup(shutil.rmtree, repo_dir)
+        commit_msg_cwd = """#!/bin/sh
+if [ "$(pwd)" = '""" + repo_dir + "' ]; then exit 0; else exit 1; fi\n"
 
         commit_msg = os.path.join(repo_dir, 'hooks', 'commit-msg')
         hook = CommitMsgShellHook(repo_dir)
@@ -93,6 +104,12 @@ exit 0
 
         self.assertRaises(errors.HookError, hook.execute, b'failed commit')
 
+        with open(commit_msg, 'w') as f:
+            f.write(commit_msg_cwd)
+        os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+        hook.execute(b'cwd test commit')
+
         with open(commit_msg, 'w') as f:
             f.write(commit_msg_success)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
@@ -104,28 +121,37 @@ exit 0
         (fd, path) = tempfile.mkstemp()
         os.close(fd)
 
-        post_commit_msg = """#!/bin/sh
+        repo_dir = os.path.join(tempfile.mkdtemp())
+        os.mkdir(os.path.join(repo_dir, 'hooks'))
+        self.addCleanup(shutil.rmtree, repo_dir)
+
+        post_commit_success = """#!/bin/sh
 rm """ + path + "\n"
 
-        post_commit_msg_fail = """#!/bin/sh
+        post_commit_fail = """#!/bin/sh
 exit 1
 """
 
-        repo_dir = os.path.join(tempfile.mkdtemp())
-        os.mkdir(os.path.join(repo_dir, 'hooks'))
-        self.addCleanup(shutil.rmtree, repo_dir)
+        post_commit_cwd = """#!/bin/sh
+if [ "$(pwd)" = '""" + repo_dir + "' ]; then exit 0; else exit 1; fi\n"
 
         post_commit = os.path.join(repo_dir, 'hooks', 'post-commit')
         hook = PostCommitShellHook(repo_dir)
 
         with open(post_commit, 'w') as f:
-            f.write(post_commit_msg_fail)
+            f.write(post_commit_fail)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
         self.assertRaises(errors.HookError, hook.execute)
 
         with open(post_commit, 'w') as f:
-            f.write(post_commit_msg)
+            f.write(post_commit_cwd)
+        os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+        hook.execute()
+
+        with open(post_commit, 'w') as f:
+            f.write(post_commit_success)
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
         hook.execute()

+ 6 - 0
dulwich/tests/test_repository.py

@@ -880,6 +880,12 @@ class BuildRepoRootTests(TestCase):
         r.stage(['a'])
         r.stage(['a'])  # double-stage a deleted path
 
+    def test_stage_directory(self):
+        r = self._repo
+        os.mkdir(os.path.join(r.path, 'c'))
+        r.stage(['c'])
+        self.assertEqual([b'a'], list(r.open_index()))
+
     @skipIf(sys.platform == 'win32' and sys.version_info[:2] >= (3, 6),
             'tries to implicitly decode as utf8')
     def test_commit_no_encode_decode(self):

+ 1 - 1
setup.py

@@ -11,7 +11,7 @@ from distutils.core import Distribution
 import os
 import sys
 
-dulwich_version_string = '0.18.4'
+dulwich_version_string = '0.18.5'
 
 include_dirs = []
 # Windows MSVC support