commit 467a9f9e6cf87869b5f547253ba5148b1288b4b0 from: Jelmer Vernooij date: Fri Oct 29 00:42:42 2021 UTC Import upstream version 0.20.26 commit - e13cc98a4f8ca8afc857730719164e4608c28d3f commit + 467a9f9e6cf87869b5f547253ba5148b1288b4b0 blob - c68de5a472fda8cf8f731d472dae6ec625d41c94 blob + 54939dc83a90ef3b2982fae4ffb4984afafb2ec3 --- .github/workflows/pythonpackage.yml +++ .github/workflows/pythonpackage.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.5, 3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev, pypy3] exclude: # sqlite3 exit handling seems to get in the way - os: macos-latest @@ -21,12 +21,6 @@ jobs: # doesn't support passing in bytestrings to os.scandir - os: windows-latest python-version: pypy3 - # path encoding - - os: windows-latest - python-version: 3.5 - # path encoding - - os: macos-latest - python-version: 3.5 fail-fast: false steps: blob - 20268fb77742a5f07eb15fc5fc2e5787cdb057fc blob + faa4e95a3189e74e3468469d798ea3af5ff936ad --- .github/workflows/pythonpublish.yml +++ .github/workflows/pythonpublish.yml @@ -12,16 +12,11 @@ jobs: strategy: matrix: os: [macos-latest, windows-latest] - python-version: ['3.5', '3.6', '3.7', '3.8', '3.9'] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] include: - os: ubuntu-latest python-version: '3.x' # path encoding - exclude: - - os: windows-latest - python-version: 3.5 - - os: macos-latest - python-version: 3.5 fail-fast: false steps: @@ -56,12 +51,12 @@ jobs: - name: Build and publish (Linux aarch64) uses: RalfG/python-wheels-manylinux-build@v0.3.3-manylinux2014_aarch64 with: - python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39' + python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310' if: "matrix.os == 'ubuntu-latest'" - name: Build and publish (Linux) uses: RalfG/python-wheels-manylinux-build@v0.3.1 with: - python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39' + python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310' env: # Temporary fix for LD_LIBRARY_PATH issue. See # https://github.com/RalfG/python-wheels-manylinux-build/issues/26 blob - 953bbaf1c3d45903f5987567909163d9d9385983 blob + 29a4749f6eb7e1cfed1b60a5d90cd021e9fd10d1 --- NEWS +++ NEWS @@ -1,3 +1,15 @@ +0.20.26 2021-10-29 + + * Support os.PathLike arguments to Repo.stage(). + (Jan Wiśniewski, #907) + + * Drop support for Python 3.5. (Jelmer Vernooij) + + * Add ``dulwich.porcelain._reset_file``. + (Ded_Secer) + + * Add ``Repo.unstage``. (Ded_Secer) + 0.20.25 2021-08-23 * Fix ``dulwich`` script when installed via setup.py. blob - f42454ceef107f4fb2a83f9dfedde4aad6197efb blob + 4433a294d4ae9d8cc99984c45c134ee665cfa451 --- PKG-INFO +++ PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dulwich -Version: 0.20.25 +Version: 0.20.26 Summary: Python Git Library Home-page: https://www.dulwich.io/ Author: Jelmer Vernooij @@ -13,17 +13,17 @@ Keywords: git vcs Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: Apache Software License -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Topic :: Software Development :: Version Control -Requires-Python: >=3.5 +Requires-Python: >=3.6 Provides-Extra: fastimport Provides-Extra: https Provides-Extra: pgp @@ -121,10 +121,7 @@ file and `list of open issues None: """Stage a set of paths. Args: @@ -1262,7 +1262,7 @@ class Repo(BaseRepo): root_path_bytes = os.fsencode(self.path) - if isinstance(fs_paths, str): + if isinstance(fs_paths, (str, bytes, os.PathLike)): fs_paths = [fs_paths] fs_paths = list(fs_paths) @@ -1305,6 +1305,65 @@ class Repo(BaseRepo): index[tree_path] = index_entry_from_stat(st, blob.id, 0) index.write() + def unstage(self, fs_paths: List[str]): + """unstage specific file in the index + Args: + fs_paths: a list of files to unstage, + relative to the repository path + """ + from dulwich.index import ( + IndexEntry, + _fs_to_tree_path, + ) + + index = self.open_index() + try: + tree_id = self[b'HEAD'].tree + except KeyError: + # no head mean no commit in the repo + for fs_path in fs_paths: + tree_path = _fs_to_tree_path(fs_path) + del index[tree_path] + index.write() + return + + for fs_path in fs_paths: + tree_path = _fs_to_tree_path(fs_path) + try: + tree_entry = self.object_store[tree_id].lookup_path( + self.object_store.__getitem__, tree_path) + except KeyError: + # if tree_entry didnt exist, this file was being added, so + # remove index entry + try: + del index[tree_path] + continue + except KeyError: + raise KeyError("file '%s' not in index" % (tree_path.decode())) + + st = None + try: + st = os.lstat(os.path.join(self.path, fs_path)) + except FileNotFoundError: + pass + + index_entry = IndexEntry( + ctime=(self[b'HEAD'].commit_time, 0), + mtime=(self[b'HEAD'].commit_time, 0), + dev=st.st_dev if st else 0, + ino=st.st_ino if st else 0, + mode=tree_entry[0], + uid=st.st_uid if st else 0, + gid=st.st_gid if st else 0, + size=len(self[tree_entry[1]].data), + sha=tree_entry[1], + flags=0, + extended_flags=0 + ) + + index[tree_path] = index_entry + index.write() + def clone( self, target_path, blob - 84f8fa6cd02939a09946a49ba1244e4e45829c64 blob + db5ed86a9b515476060843c85841e0be0751cc40 --- dulwich/tests/test_client.py +++ dulwich/tests/test_client.py @@ -705,6 +705,7 @@ class TestSSHVendor(object): port=None, password=None, key_filename=None, + ssh_command=None, ): self.host = host self.command = command @@ -712,6 +713,7 @@ class TestSSHVendor(object): self.port = port self.password = password self.key_filename = key_filename + self.ssh_command = ssh_command class Subprocess: pass @@ -785,7 +787,22 @@ class SSHGitClientTests(TestCase): client._connect(b"relative-command", b"/~/path/to/repo") self.assertEqual("git-relative-command '~/path/to/repo'", server.command) + def test_ssh_command_precedence(self): + os.environ["GIT_SSH"] = "/path/to/ssh" + test_client = SSHGitClient("git.samba.org") + self.assertEqual(test_client.ssh_command, "/path/to/ssh") + os.environ["GIT_SSH_COMMAND"] = "/path/to/ssh -o Option=Value" + test_client = SSHGitClient("git.samba.org") + self.assertEqual(test_client.ssh_command, "/path/to/ssh -o Option=Value") + + test_client = SSHGitClient("git.samba.org", ssh_command="ssh -o Option1=Value1") + self.assertEqual(test_client.ssh_command, "ssh -o Option1=Value1") + + del os.environ["GIT_SSH"] + del os.environ["GIT_SSH_COMMAND"] + + class ReportStatusParserTests(TestCase): def test_invalid_pack(self): parser = ReportStatusParser() @@ -1230,7 +1247,27 @@ class SubprocessSSHVendorTests(TestCase): self.assertListEqual(expected, args[0]) + def test_run_with_ssh_command(self): + expected = [ + "/path/to/ssh", + "-o", + "Option=Value", + "-x", + "host", + "git-clone-url", + ] + vendor = SubprocessSSHVendor() + command = vendor.run_command( + "host", + "git-clone-url", + ssh_command="/path/to/ssh -o Option=Value", + ) + + args = command.proc.args + self.assertListEqual(expected, args[0]) + + class PLinkSSHVendorTests(TestCase): def setUp(self): # Monkey Patch client subprocess popen @@ -1353,7 +1390,25 @@ class PLinkSSHVendorTests(TestCase): self.assertListEqual(expected, args[0]) + def test_run_with_ssh_command(self): + expected = [ + "/path/to/plink", + "-x", + "host", + "git-clone-url", + ] + vendor = SubprocessSSHVendor() + command = vendor.run_command( + "host", + "git-clone-url", + ssh_command="/path/to/plink", + ) + + args = command.proc.args + self.assertListEqual(expected, args[0]) + + class RsyncUrlTests(TestCase): def test_simple(self): self.assertEqual(parse_rsync_url("foo:bar/path"), (None, "foo", "bar/path")) blob - 67c6049b82c7c5c86714ec80983fb177a3d824b4 blob + 22340eb6cf86efc545ce1edbce9b3784d5f065e7 --- dulwich/tests/test_objectspec.py +++ dulwich/tests/test_objectspec.py @@ -258,3 +258,9 @@ class ParseTreeTests(TestCase): c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]]) self.assertEqual(r[c1.tree], parse_tree(r, c1.id)) self.assertEqual(r[c1.tree], parse_tree(r, c1.tree)) + + def test_from_ref(self): + r = MemoryRepo() + c1, c2, c3 = build_commit_graph(r.object_store, [[1], [2, 1], [3, 1, 2]]) + r.refs[b'refs/heads/foo'] = c1.id + self.assertEqual(r[c1.tree], parse_tree(r, b'foo')) blob - 278259c3b4eb3afb8a8d9170cdf94b9786105bff blob + 407893e9725ea126bc6c10657861381eb47b1c52 --- dulwich/tests/test_pack.py +++ dulwich/tests/test_pack.py @@ -1032,7 +1032,8 @@ class DeltaChainIteratorTests(TestCase): (OFS_DELTA, (0, b"blob2")), ], ) - self.assertEntriesMatch([0, 1, 2], entries, self.make_pack_iter(f)) + # Delta resolution changed to DFS + self.assertEntriesMatch([0, 2, 1], entries, self.make_pack_iter(f)) def test_ofs_deltas_chain(self): f = BytesIO() @@ -1056,7 +1057,8 @@ class DeltaChainIteratorTests(TestCase): (REF_DELTA, (1, b"blob2")), ], ) - self.assertEntriesMatch([1, 0, 2], entries, self.make_pack_iter(f)) + # Delta resolution changed to DFS + self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f)) def test_ref_deltas_chain(self): f = BytesIO() @@ -1082,7 +1084,9 @@ class DeltaChainIteratorTests(TestCase): (OFS_DELTA, (1, b"blob2")), ], ) - self.assertEntriesMatch([1, 2, 0], entries, self.make_pack_iter(f)) + + # Delta resolution changed to DFS + self.assertEntriesMatch([1, 0, 2], entries, self.make_pack_iter(f)) def test_mixed_chain(self): f = BytesIO() @@ -1094,9 +1098,9 @@ class DeltaChainIteratorTests(TestCase): (OFS_DELTA, (0, b"blob1")), (OFS_DELTA, (1, b"blob3")), (OFS_DELTA, (0, b"bob")), - ], - ) - self.assertEntriesMatch([0, 2, 4, 1, 3], entries, self.make_pack_iter(f)) + ]) + # Delta resolution changed to DFS + self.assertEntriesMatch([0, 4, 2, 1, 3], entries, self.make_pack_iter(f)) def test_long_chain(self): n = 100 @@ -1114,7 +1118,9 @@ class DeltaChainIteratorTests(TestCase): objects_spec.append((OFS_DELTA, (0, b"blob" + str(i).encode("ascii")))) f = BytesIO() entries = build_pack(f, objects_spec) - self.assertEntriesMatch(range(n + 1), entries, self.make_pack_iter(f)) + # Delta resolution changed to DFS + indices = [0] + list(range(100, 0, -1)) + self.assertEntriesMatch(indices, entries, self.make_pack_iter(f)) def test_ext_ref(self): (blob,) = self.store_blobs([b"blob"]) blob - 7717701f4009b24b8b2fe7246c08490fa59c9b2c blob + 0334f192e879227d61a3fe1641117cf9a6e824e1 --- dulwich/tests/test_porcelain.py +++ dulwich/tests/test_porcelain.py @@ -35,7 +35,9 @@ from unittest import skipIf from dulwich import porcelain from dulwich.diff_tree import tree_changes -from dulwich.errors import CommitError +from dulwich.errors import ( + CommitError, +) from dulwich.objects import ( Blob, Tag, @@ -1317,6 +1319,73 @@ class ResetTests(PorcelainTestCase): self.assertEqual([], changes) +class ResetFileTests(PorcelainTestCase): + + def test_reset_modify_file_to_commit(self): + file = 'foo' + full_path = os.path.join(self.repo.path, file) + + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self.repo, paths=[full_path]) + sha = porcelain.commit( + self.repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + with open(full_path, 'a') as f: + f.write('something new') + porcelain.reset_file(self.repo, file, target=sha) + + with open(full_path, 'r') as f: + self.assertEqual('hello', f.read()) + + def test_reset_remove_file_to_commit(self): + file = 'foo' + full_path = os.path.join(self.repo.path, file) + + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self.repo, paths=[full_path]) + sha = porcelain.commit( + self.repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + os.remove(full_path) + porcelain.reset_file(self.repo, file, target=sha) + + with open(full_path, 'r') as f: + self.assertEqual('hello', f.read()) + + def test_resetfile_with_dir(self): + os.mkdir(os.path.join(self.repo.path, 'new_dir')) + full_path = os.path.join(self.repo.path, 'new_dir', 'foo') + + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self.repo, paths=[full_path]) + sha = porcelain.commit( + self.repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + with open(full_path, 'a') as f: + f.write('something new') + porcelain.commit( + self.repo, + message=b"unitest 2", + committer=b"Jane ", + author=b"John ", + ) + porcelain.reset_file(self.repo, os.path.join('new_dir', 'foo'), target=sha) + with open(full_path, 'r') as f: + self.assertEqual('hello', f.read()) + + class PushTests(PorcelainTestCase): def test_simple(self): """ blob - 4b75e0431b816f9e47d5ac5f0fb206354d533542 blob + 05034888609904094a31020f069f235f8c6c016a --- dulwich/tests/test_repository.py +++ dulwich/tests/test_repository.py @@ -31,6 +31,7 @@ import tempfile import warnings from dulwich import errors +from dulwich import porcelain from dulwich.object_store import ( tree_lookup_path, ) @@ -1225,7 +1226,88 @@ class BuildRepoRootTests(TestCase): os.mkdir(os.path.join(r.path, "c")) r.stage(["c"]) self.assertEqual([b"a"], list(r.open_index())) + + def test_unstage_midify_file_with_dir(self): + os.mkdir(os.path.join(self._repo.path, 'new_dir')) + full_path = os.path.join(self._repo.path, 'new_dir', 'foo') + + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self._repo, paths=[full_path]) + porcelain.commit( + self._repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + with open(full_path, 'a') as f: + f.write('something new') + self._repo.unstage(['new_dir/foo']) + status = list(porcelain.status(self._repo)) + self.assertEqual([{'add': [], 'delete': [], 'modify': []}, [b'new_dir/foo'], []], status) + + def test_unstage_while_no_commit(self): + file = 'foo' + full_path = os.path.join(self._repo.path, file) + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self._repo, paths=[full_path]) + self._repo.unstage([file]) + status = list(porcelain.status(self._repo)) + self.assertEqual([{'add': [], 'delete': [], 'modify': []}, [], ['foo']], status) + + def test_unstage_add_file(self): + file = 'foo' + full_path = os.path.join(self._repo.path, file) + porcelain.commit( + self._repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self._repo, paths=[full_path]) + self._repo.unstage([file]) + status = list(porcelain.status(self._repo)) + self.assertEqual([{'add': [], 'delete': [], 'modify': []}, [], ['foo']], status) + def test_unstage_modify_file(self): + file = 'foo' + full_path = os.path.join(self._repo.path, file) + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self._repo, paths=[full_path]) + porcelain.commit( + self._repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + with open(full_path, 'a') as f: + f.write('broken') + porcelain.add(self._repo, paths=[full_path]) + self._repo.unstage([file]) + status = list(porcelain.status(self._repo)) + self.assertEqual([{'add': [], 'delete': [], 'modify': []}, [b'foo'], []], status) + + def test_unstage_remove_file(self): + file = 'foo' + full_path = os.path.join(self._repo.path, file) + with open(full_path, 'w') as f: + f.write('hello') + porcelain.add(self._repo, paths=[full_path]) + porcelain.commit( + self._repo, + message=b"unitest", + committer=b"Jane ", + author=b"John ", + ) + os.remove(full_path) + self._repo.unstage([file]) + status = list(porcelain.status(self._repo)) + self.assertEqual([{'add': [], 'delete': [], 'modify': []}, [b'foo'], []], status) + @skipIf( sys.platform in ("win32", "darwin"), "tries to implicitly decode as utf8", blob - f42454ceef107f4fb2a83f9dfedde4aad6197efb blob + 4433a294d4ae9d8cc99984c45c134ee665cfa451 --- dulwich.egg-info/PKG-INFO +++ dulwich.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dulwich -Version: 0.20.25 +Version: 0.20.26 Summary: Python Git Library Home-page: https://www.dulwich.io/ Author: Jelmer Vernooij @@ -13,17 +13,17 @@ Keywords: git vcs Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: Apache Software License -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Topic :: Software Development :: Version Control -Requires-Python: >=3.5 +Requires-Python: >=3.6 Provides-Extra: fastimport Provides-Extra: https Provides-Extra: pgp @@ -121,10 +121,7 @@ file and `list of open issues