Commit Diff


commit - 467a9f9e6cf87869b5f547253ba5148b1288b4b0
commit + 662b2117d16044d0c348a48e316229a2da046adf
blob - eaa127a1296a7d588ce5a43c71ebfc40e8520ca5
blob + d1a876fd7fb6bb7a57e253919eadae85fcdd0af4
--- .gitignore
+++ .gitignore
@@ -25,3 +25,4 @@ docs/api/*.txt
 .mypy_cache/
 .eggs
 dulwich.dist-info
+.stestr
blob - /dev/null
blob + 64964357e1f50efe6cb570df8d609b9a692a93d2 (mode 644)
--- /dev/null
+++ .stestr.conf
@@ -0,0 +1,2 @@
+[DEFAULT]
+test_path=dulwich/tests
blob - 29a4749f6eb7e1cfed1b60a5d90cd021e9fd10d1
blob + 41df77cb18f9fe41ee34b2ea91f62eb998ccbe15
--- NEWS
+++ NEWS
@@ -1,3 +1,11 @@
+0.20.27	2022-01-04
+
+ * Allow adding files to repository in pre-commit hook.
+   (Jelmer Vernooij, #916)
+
+ * Raise SubmoduleEncountered in ``tree_lookup_path``.
+   (Jelmer Vernooij)
+
 0.20.26	2021-10-29
 
  * Support os.PathLike arguments to Repo.stage().
blob - 4433a294d4ae9d8cc99984c45c134ee665cfa451
blob + 3f3f472b7348f4aa816b6ebe9616187914f8c6b9
--- PKG-INFO
+++ PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.20.26
+Version: 0.20.27
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij
blob - 888b48f0605589b966ee4e903a226ba8c5b54014
blob + cb4d90567e8c0bd5de88570241991fe0bf84be73
--- dulwich/__init__.py
+++ dulwich/__init__.py
@@ -22,4 +22,4 @@
 
 """Python implementation of the Git file formats and protocols."""
 
-__version__ = (0, 20, 26)
+__version__ = (0, 20, 27)
blob - 85de5f098d2cb3b9b1a346c45fd812372f2096d7
blob + 001abe42b13471c889d2eb464026b8492bfcb3fb
--- dulwich/hooks.py
+++ dulwich/hooks.py
@@ -117,10 +117,10 @@ class ShellHook(Hook):
 class PreCommitShellHook(ShellHook):
     """pre-commit shell hook"""
 
-    def __init__(self, controldir):
+    def __init__(self, cwd, controldir):
         filepath = os.path.join(controldir, "hooks", "pre-commit")
 
-        ShellHook.__init__(self, "pre-commit", filepath, 0, cwd=controldir)
+        ShellHook.__init__(self, "pre-commit", filepath, 0, cwd=cwd)
 
 
 class PostCommitShellHook(ShellHook):
@@ -173,7 +173,7 @@ class PostReceiveShellHook(ShellHook):
     def __init__(self, controldir):
         self.controldir = controldir
         filepath = os.path.join(controldir, "hooks", "post-receive")
-        ShellHook.__init__(self, "post-receive", filepath, 0)
+        ShellHook.__init__(self, "post-receive", path=filepath, numparam=0)
 
     def execute(self, client_refs):
         # do nothing if the script doesn't exist
blob - 551f9c1f100fe4abcfe4907bf58ca38e468bce2d
blob + f6b0db483bbba0874007a906e9fd32a305d95bf4
--- dulwich/object_store.py
+++ dulwich/object_store.py
@@ -331,8 +331,8 @@ class BaseObjectStore(object):
     def _collect_ancestors(
         self,
         heads,
-        common=set(),
-        shallow=set(),
+        common=frozenset(),
+        shallow=frozenset(),
         get_parents=lambda commit: commit.parents,
     ):
         """Collect all ancestors of heads up to (excluding) those in common.
blob - 8bdb40176b5daff30a4b46c8cee6435c288f1229
blob + 74ade5c1eff423b5dc083664681cd0f3cabc9023
--- dulwich/objects.py
+++ dulwich/objects.py
@@ -100,7 +100,7 @@ def _decompress(string):
 def sha_to_hex(sha):
     """Takes a string and returns the hex of the sha within"""
     hexsha = binascii.hexlify(sha)
-    assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % hexsha
+    assert len(hexsha) == 40, "Incorrect length of sha1 string: %s" % hexsha
     return hexsha
 
 
@@ -1030,6 +1030,14 @@ def pretty_format_tree_entry(name, mode, hexsha, encod
         hexsha.decode("ascii"),
         name.decode(encoding, "replace"),
     )
+
+
+class SubmoduleEncountered(Exception):
+    """A submodule was encountered while resolving a path."""
+
+    def __init__(self, path, sha):
+        self.path = path
+        self.sha = sha
 
 
 class Tree(ShaFile):
@@ -1182,9 +1190,11 @@ class Tree(ShaFile):
         parts = path.split(b"/")
         sha = self.id
         mode = None
-        for p in parts:
+        for i, p in enumerate(parts):
             if not p:
                 continue
+            if mode is not None and S_ISGITLINK(mode):
+                raise SubmoduleEncountered(b'/'.join(parts[:i]), sha)
             obj = lookup_obj(sha)
             if not isinstance(obj, Tree):
                 raise NotTreeError(sha)
blob - 235e71774a5d60d12d4f964ccb185a5fb16d768e
blob + 29b0e6694b1e27e3cf4ba3de92cdd5f95f3ed00b
--- dulwich/repo.py
+++ dulwich/repo.py
@@ -895,6 +895,14 @@ class BaseRepo(object):
           New commit SHA1
         """
 
+        try:
+            if not no_verify:
+                self.hooks["pre-commit"].execute()
+        except HookError as e:
+            raise CommitError(e)
+        except KeyError:  # no hook defined, silent fallthrough
+            pass
+
         c = Commit()
         if tree is None:
             index = self.open_index()
@@ -904,14 +912,6 @@ class BaseRepo(object):
                 raise ValueError("tree must be a 40-byte hex sha string")
             c.tree = tree
 
-        try:
-            if not no_verify:
-                self.hooks["pre-commit"].execute()
-        except HookError as e:
-            raise CommitError(e)
-        except KeyError:  # no hook defined, silent fallthrough
-            pass
-
         config = self.get_config_stack()
         if merge_heads is None:
             merge_heads = self._read_heads("MERGE_HEAD")
@@ -1057,7 +1057,6 @@ class Repo(BaseRepo):
             if os.path.isfile(hidden_path):
                 with open(hidden_path, "r") as f:
                     path = read_gitfile(f)
-                self.bare = False
                 self._controldir = os.path.join(root, path)
             else:
                 self._controldir = hidden_path
@@ -1101,7 +1100,7 @@ class Repo(BaseRepo):
             with graft_file:
                 self._graftpoints.update(parse_graftpoints(graft_file))
 
-        self.hooks["pre-commit"] = PreCommitShellHook(self.controldir())
+        self.hooks["pre-commit"] = PreCommitShellHook(self.path, self.controldir())
         self.hooks["commit-msg"] = CommitMsgShellHook(self.controldir())
         self.hooks["post-commit"] = PostCommitShellHook(self.controldir())
         self.hooks["post-receive"] = PostReceiveShellHook(self.controldir())
blob - b3fb0e290776b37986485e859d7af0054f76f444
blob + 6e416f1d5e43f52d9eeb75674927246b12f99bbf
--- dulwich/tests/test_hooks.py
+++ dulwich/tests/test_hooks.py
@@ -71,7 +71,7 @@ exit 0
         )
 
         pre_commit = os.path.join(repo_dir, "hooks", "pre-commit")
-        hook = PreCommitShellHook(repo_dir)
+        hook = PreCommitShellHook(repo_dir, repo_dir)
 
         with open(pre_commit, "w") as f:
             f.write(pre_commit_fail)
blob - 68789aacb3abed59695a69a647b10b55b13e909d
blob + 2f0329f2840b84326570efa1fb06f8d84a87407e
--- dulwich/tests/test_object_store.py
+++ dulwich/tests/test_object_store.py
@@ -42,6 +42,8 @@ from dulwich.objects import (
     Tree,
     TreeEntry,
     EmptyFileException,
+    SubmoduleEncountered,
+    S_IFGITLINK,
 )
 from dulwich.object_store import (
     DiskObjectStore,
@@ -582,6 +584,7 @@ class TreeLookupPathTests(TestCase):
             (b"ad/bd/c", blob_c.id, 0o100755),
             (b"ad/c", blob_c.id, 0o100644),
             (b"c", blob_c.id, 0o100644),
+            (b"d", blob_c.id, S_IFGITLINK),
         ]
         self.tree_id = commit_tree(self.store, blobs)
 
@@ -600,6 +603,12 @@ class TreeLookupPathTests(TestCase):
         o_id = tree_lookup_path(self.get_object, self.tree_id, b"ad/bd/")[1]
         self.assertTrue(isinstance(self.store[o_id], Tree))
 
+    def test_lookup_submodule(self):
+        tree_lookup_path(self.get_object, self.tree_id, b"d")[1]
+        self.assertRaises(
+            SubmoduleEncountered, tree_lookup_path, self.get_object,
+            self.tree_id, b"d/a")
+
     def test_lookup_nonexistent(self):
         self.assertRaises(
             KeyError, tree_lookup_path, self.get_object, self.tree_id, b"j"
blob - 05034888609904094a31020f069f235f8c6c016a
blob + f8975cad544709687eb17bcaf16907b184744b56
--- dulwich/tests/test_repository.py
+++ dulwich/tests/test_repository.py
@@ -568,9 +568,9 @@ exit 0
         self.assertRaises(
             errors.CommitError,
             r.do_commit,
-            "failed commit",
-            committer="Test Committer <test@nodomain.com>",
-            author="Test Author <test@nodomain.com>",
+            b"failed commit",
+            committer=b"Test Committer <test@nodomain.com>",
+            author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
             commit_timezone=0,
             author_timestamp=12345,
@@ -633,6 +633,49 @@ exit 0
 
         commit_sha = r.do_commit(
             b"empty 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,
+        )
+        self.assertEqual([], r[commit_sha].parents)
+
+    def test_shell_hook_pre_commit_add_files(self):
+        if os.name != "posix":
+            self.skipTest("shell hook tests requires POSIX shell")
+
+        pre_commit_contents = """#!%(executable)s
+import sys
+sys.path.extend(':'.join(%(path)s))
+from dulwich.repo import Repo
+
+with open('foo', 'w') as f:
+    f.write('newfile')
+
+r = Repo('.')
+r.stage(['foo'])
+""" % {'executable': sys.executable, 'path': repr(sys.path)}
+
+        repo_dir = os.path.join(self.mkdtemp())
+        self.addCleanup(shutil.rmtree, repo_dir)
+        r = Repo.init(repo_dir)
+        self.addCleanup(r.close)
+
+        with open(os.path.join(repo_dir, 'blah'), 'w') as f:
+            f.write('blah')
+
+        r.stage(['blah'])
+
+        pre_commit = os.path.join(r.controldir(), "hooks", "pre-commit")
+
+        with open(pre_commit, "w") as f:
+            f.write(pre_commit_contents)
+        os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+        commit_sha = r.do_commit(
+            b"new commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -642,6 +685,9 @@ exit 0
         )
         self.assertEqual([], r[commit_sha].parents)
 
+        tree = r[r[commit_sha].tree]
+        self.assertEqual(set([b'blah', b'foo']), set(tree))
+
     def test_shell_hook_post_commit(self):
         if os.name != "posix":
             self.skipTest("shell hook tests requires POSIX shell")
blob - 4433a294d4ae9d8cc99984c45c134ee665cfa451
blob + 3f3f472b7348f4aa816b6ebe9616187914f8c6b9
--- dulwich.egg-info/PKG-INFO
+++ dulwich.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dulwich
-Version: 0.20.26
+Version: 0.20.27
 Summary: Python Git Library
 Home-page: https://www.dulwich.io/
 Author: Jelmer Vernooij
blob - 75bc62eb2b568f43d43518ae5b2c38c61eaa47be
blob + a9f8ea6e10a235c8784b00f27a4bd695767ba248
--- dulwich.egg-info/SOURCES.txt
+++ dulwich.egg-info/SOURCES.txt
@@ -3,6 +3,7 @@
 .flake8
 .gitignore
 .mailmap
+.stestr.conf
 .testr.conf
 AUTHORS
 CODE_OF_CONDUCT.md
@@ -96,6 +97,16 @@ dulwich.egg-info/entry_points.txt
 dulwich.egg-info/not-zip-safe
 dulwich.egg-info/requires.txt
 dulwich.egg-info/top_level.txt
+dulwich/../docs/tutorial/conclusion.txt
+dulwich/../docs/tutorial/encoding.txt
+dulwich/../docs/tutorial/file-format.txt
+dulwich/../docs/tutorial/index.txt
+dulwich/../docs/tutorial/introduction.txt
+dulwich/../docs/tutorial/object-store.txt
+dulwich/../docs/tutorial/porcelain.txt
+dulwich/../docs/tutorial/remote.txt
+dulwich/../docs/tutorial/repo.txt
+dulwich/../docs/tutorial/tag.txt
 dulwich/cloud/__init__.py
 dulwich/cloud/gcs.py
 dulwich/contrib/README.md
blob - b1b6e5e3283945b58ca84f7e567f5baf3212060b
blob + 8ca2a2fb253b8e6c74e80d5499feec21834c7deb
--- setup.py
+++ setup.py
@@ -23,7 +23,7 @@ if sys.version_info < (3, 6):
         'For 2.7 support, please install a version prior to 0.20')
 
 
-dulwich_version_string = '0.20.26'
+dulwich_version_string = '0.20.27'
 
 
 class DulwichDistribution(Distribution):