Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[run]
branch = True
source = scmrepo

[report]
exclude_lines =
if TYPE_CHECKING:
3 changes: 2 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ name: Tests
on: push
jobs:
tests:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, windows-latest, macos-latest]
python-version: ['3.7', '3.8', '3.9', '3.10']
name: Python ${{ matrix.python-version }}
steps:
Expand Down
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ profile = "black"
known_first_party = ["scmrepo"]
line_length = 79

[tool.pytest.ini_options]
markers = [
"skip_git_backend: skip tests for given backend",
]

[tool.mypy]
# Error output
show_column_numbers = true
Expand All @@ -37,7 +42,7 @@ check_untyped_defs = false
warn_no_return = true
warn_redundant_casts = true
warn_unreachable = true
files = ["scmrepo"]
files = ["scmrepo", "tests"]

[[tool.mypy.overrides]]
module = [
Expand All @@ -59,7 +64,7 @@ extension-pkg-whitelist = ["pygit2"]
disable = [
"format", "refactoring", "design", "no-self-use", "invalid-name", "duplicate-code",
"missing-function-docstring", "missing-module-docstring", "missing-class-docstring",
"raise-missing-from", "import-outside-toplevel", "cyclic-import"
"raise-missing-from", "import-outside-toplevel", "cyclic-import", "fixme",
]
enable = ["c-extension-no-member", "no-else-return"]

Expand Down
25 changes: 24 additions & 1 deletion scmrepo/git/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from collections.abc import Mapping
from contextlib import contextmanager
from functools import partialmethod
from typing import Dict, Iterable, Optional, Tuple, Type
from typing import Dict, Iterable, Optional, Tuple, Type, Union

from funcy import cached_property, first
from pathspec.patterns import GitWildMatchPattern
Expand Down Expand Up @@ -260,6 +260,29 @@ def get_fs(self, rev: str):

return GitFileSystem(scm=self, rev=rev)

@classmethod
def init(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented init and added tests for all backends.

cls, path: str, bare: bool = False, _backend: str = None
) -> "Git":
for name, backend in GitBackends.DEFAULT.items():
if _backend and name != _backend:
continue
try:
backend.init(path, bare=bare)
# TODO: reuse created object instead of initializing a new one.
return cls(path)
except NotImplementedError:
pass
raise NoGitBackendError("init")

def add_commit(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have scm_gen, so I added this add_commit to simplify tests. It's questionable, but scmrepo should also aim to provide higher-level APIs than underlying backends.

self,
paths: Union[str, Iterable[str]],
message: str,
) -> None:
self.add(paths)
self.commit(msg=message)

is_ignored = partialmethod(_backend_func, "is_ignored")
add = partialmethod(_backend_func, "add")
commit = partialmethod(_backend_func, "commit")
Expand Down
5 changes: 5 additions & 0 deletions scmrepo/git/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ def clone(
):
pass

@staticmethod
@abstractmethod
def init(path: str, bare: bool = False) -> None:
pass

@property
@abstractmethod
def dir(self) -> str:
Expand Down
6 changes: 6 additions & 0 deletions scmrepo/git/backend/dulwich/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ def clone(
):
raise NotImplementedError

@staticmethod
def init(path: str, bare: bool = False) -> None:
from dulwich.porcelain import init

init(path, bare=bare)

@property
def dir(self) -> str:
return self.repo.commondir()
Expand Down
15 changes: 15 additions & 0 deletions scmrepo/git/backend/gitpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ def clone(
)
) from exc

@staticmethod
def init(path: str, bare: bool = False) -> None:
from funcy import retry
from git import Repo
from git.exc import GitCommandNotFound

# NOTE: handles EAGAIN error on BSD systems (osx in our case).
# Otherwise when running tests you might get this exception:
#
# GitCommandNotFound: Cmd('git') not found due to:
# OSError('[Errno 35] Resource temporarily unavailable')
method = retry(5, GitCommandNotFound)(Repo.init)
git = method(path, bare=bare)
git.close()

@staticmethod
def is_sha(rev):
import git
Expand Down
6 changes: 6 additions & 0 deletions scmrepo/git/backend/pygit2.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ def clone(
):
raise NotImplementedError

@staticmethod
def init(path: str, bare: bool = False) -> None:
from pygit2 import init_repository

init_repository(path, bare=bare)

@property
def dir(self) -> str:
raise NotImplementedError
Expand Down
8 changes: 8 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ install_requires=
dev =
pytest==6.2.5
pytest-sugar==0.9.4
pytest-test-utils==0.0.5
pytest-cov==3.0.0
pytest-mock==3.6.1
pylint==2.11.1
mypy==0.910
types-certifi==2021.10.8.0
types-paramiko==2.8.1

[options.packages.find]
exclude =
tests
tests.*

[flake8]
ignore=
E203, # Whitespace before ':'
Expand Down
Empty file added tests/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
import sys

import pygit2
import pytest
from pytest_test_utils import TempDirFactory, TmpDir

from scmrepo.git import Git


@pytest.fixture(autouse=True)
def isolate(tmp_dir_factory: TempDirFactory, monkeypatch: pytest.MonkeyPatch):
path = tmp_dir_factory.mktemp("mock")
home_dir = path / "home"
home_dir.mkdir()

if sys.platform == "win32":
home_drive, home_path = os.path.splitdrive(home_dir)
monkeypatch.setenv("USERPROFILE", str(home_dir))
monkeypatch.setenv("HOMEDRIVE", home_drive)
monkeypatch.setenv("HOMEPATH", home_path)
else:
monkeypatch.setenv("HOME", str(home_dir))

monkeypatch.setenv("GIT_CONFIG_NOSYSTEM", "1")
contents = b"""
[user]
name=DVC Tester
email=dvctester@example.com
[init]
defaultBranch=master
"""
(home_dir / ".gitconfig").write_bytes(contents)
pygit2.settings.search_path[pygit2.GIT_CONFIG_LEVEL_GLOBAL] = str(home_dir)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like pygit2 does not use the monkeypatched environment variable, so we have to set manually.



@pytest.fixture
def scm(tmp_dir: TmpDir):
git_ = Git.init(tmp_dir)
sig = git_.pygit2.default_signature

assert sig.email == "dvctester@example.com"
assert sig.name == "DVC Tester"

yield git_
git_.close()
28 changes: 28 additions & 0 deletions tests/test_dulwich.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pytest
from pytest_mock import MockerFixture


@pytest.mark.parametrize(
"algorithm", [b"ssh-rsa", b"rsa-sha2-256", b"rsa-sha2-512"]
)
def test_dulwich_github_compat(mocker: MockerFixture, algorithm: bytes):
from asyncssh.misc import ProtocolError

from scmrepo.git.backend.dulwich.asyncssh_vendor import (
_process_public_key_ok_gh,
)

key_data = b"foo"
auth = mocker.Mock(
_keypair=mocker.Mock(algorithm=algorithm, public_data=key_data),
)
packet = mocker.Mock()

with pytest.raises(ProtocolError):
strings = iter((b"ed21556", key_data))
packet.get_string = lambda: next(strings)
_process_public_key_ok_gh(auth, None, None, packet)

strings = iter((b"ssh-rsa", key_data))
packet.get_string = lambda: next(strings)
_process_public_key_ok_gh(auth, None, None, packet)
Loading