Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vcs): Add Jujutsu support #1051

Merged
merged 1 commit into from
Jul 23, 2024
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
37 changes: 37 additions & 0 deletions .github/workflows/jujutsu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2023 Free Software Foundation Europe e.V.
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
#
# SPDX-License-Identifier: GPL-3.0-or-later

name: Test with Jujutsu

# These tests are run exclusively on the main branch to reduce CPU time wasted
# on every single PR that very likely does not affect Jujutsu functionality.
on:
push:
branches:
- main
paths:
- "src/reuse/**.py"
- "tests/**.py"
jobs:
test-jujutsu:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install dependencies
run: |
pip install poetry~=1.3.0
poetry install --no-interaction --only main,test
- name: Set up Jujutsu
run: |
cargo install cargo-binstall
cargo binstall --strategies crate-meta-data jj-cli --no-confirm
export PATH=~/.cargo/bin:$PATH
- name: Run tests with pytest
run: |
poetry run pytest --cov=reuse
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ Contributors
- rajivsunar07 <56905029+rajivsunar07@users.noreply.github.com>
- Сергій <sergiy.goncharuk.1@gmail.com>
- Mersho <code.rezaei@gmail.com>
- Skyler Grey <sky@a.starrysky.fyi>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ For full functionality, the following pieces of software are recommended:
- Git
- Mercurial 4.3+
- Pijul
- Jujutsu

### Installation via pip

Expand Down
1 change: 1 addition & 0 deletions changelog.d/added/jujutsu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add Jujutsu VCS support. (#TODO)
2 changes: 2 additions & 0 deletions src/reuse/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# SPDX-FileCopyrightText: 2023 DB Systel GmbH
# SPDX-FileCopyrightText: 2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
# SPDX-FileCopyrightText: 2024 Rivos Inc.
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
Expand Down Expand Up @@ -61,6 +62,7 @@

GIT_EXE = shutil.which("git")
HG_EXE = shutil.which("hg")
JUJUTSU_EXE = shutil.which("jj")
PIJUL_EXE = shutil.which("pijul")

REUSE_IGNORE_START = "REUSE-IgnoreStart"
Expand Down
77 changes: 75 additions & 2 deletions src/reuse/vcs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2020 John Mulligan <jmulligan@redhat.com>
# SPDX-FileCopyrightText: 2023 Markus Haug <korrat@proton.me>
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -16,7 +17,14 @@
from pathlib import Path
from typing import TYPE_CHECKING, Generator, Optional, Set, Type

from ._util import GIT_EXE, HG_EXE, PIJUL_EXE, StrPath, execute_command
from ._util import (
GIT_EXE,
HG_EXE,
JUJUTSU_EXE,
PIJUL_EXE,
StrPath,
execute_command,
)

if TYPE_CHECKING:
from .project import Project
Expand Down Expand Up @@ -243,6 +251,71 @@ def find_root(cls, cwd: Optional[StrPath] = None) -> Optional[Path]:
return None


class VCSStrategyJujutsu(VCSStrategy):
"""Strategy that is used for Jujutsu."""

EXE = JUJUTSU_EXE

def __init__(self, project: Project):
super().__init__(project)
if not self.EXE:
raise FileNotFoundError("Could not find binary for Jujutsu")
self._all_tracked_files = self._find_all_tracked_files()

def _find_all_tracked_files(self) -> Set[Path]:
"""
Return a set of all files tracked in the current jj revision
"""
command = [str(self.EXE), "files"]
result = execute_command(command, _LOGGER, cwd=self.project.root)
all_files = result.stdout.decode("utf-8").split("\n")
return {Path(file_) for file_ in all_files if file_}

def is_ignored(self, path: StrPath) -> bool:
path = self.project.relative_from_root(path)

for tracked in self._all_tracked_files:
if tracked.parts[: len(path.parts)] == path.parts:
# We can't check only if the path is in our tracked files as we
# must support directories as well as files
#
# We'll consider a directory "tracked" if there are any tracked
# files inside it
return False

return True

def is_submodule(self, path: StrPath) -> bool:
return False

@classmethod
def in_repo(cls, directory: StrPath) -> bool:
if not Path(directory).is_dir():
raise NotADirectoryError()

command = [str(cls.EXE), "root"]
result = execute_command(command, _LOGGER, cwd=directory)

return not result.returncode

@classmethod
def find_root(cls, cwd: Optional[StrPath] = None) -> Optional[Path]:
if cwd is None:
cwd = Path.cwd()

if not Path(cwd).is_dir():
raise NotADirectoryError()

command = [str(cls.EXE), "root"]
result = execute_command(command, _LOGGER, cwd=cwd)

if not result.returncode:
path = result.stdout.decode("utf-8")[:-1]
return Path(os.path.relpath(path, cwd))

return None


class VCSStrategyPijul(VCSStrategy):
"""Strategy that is used for Pijul."""

Expand Down
30 changes: 29 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: 2022 Carmen Bianca Bakker <carmenbianca@fsfe.org>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
# SPDX-FileCopyrightText: 2023 Matthias Riße
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -37,7 +38,13 @@
except ImportError:
sys.path.append(os.path.join(Path(__file__).parent.parent, "src"))
finally:
from reuse._util import GIT_EXE, HG_EXE, PIJUL_EXE, setup_logging
from reuse._util import (
GIT_EXE,
HG_EXE,
JUJUTSU_EXE,
PIJUL_EXE,
setup_logging,
)
from reuse.global_licensing import ReuseDep5

CWD = Path.cwd()
Expand Down Expand Up @@ -108,6 +115,14 @@ def hg_exe() -> str:
return str(HG_EXE)


@pytest.fixture()
def jujutsu_exe() -> str:
"""Run the test with Jujutsu."""
if not JUJUTSU_EXE:
pytest.skip("cannot run this test without jujutsu")
return str(JUJUTSU_EXE)


@pytest.fixture()
def pijul_exe() -> str:
"""Run the test with Pijul."""
Expand Down Expand Up @@ -275,6 +290,19 @@ def hg_repository(fake_repository: Path, hg_exe: str) -> Path:
return fake_repository


@pytest.fixture()
def jujutsu_repository(fake_repository: Path, jujutsu_exe: str) -> Path:
"""Create a jujutsu repository with ignored files."""
os.chdir(fake_repository)
_repo_contents(fake_repository)

subprocess.run(
[jujutsu_exe, "git", "init", str(fake_repository)], check=True
)

return fake_repository


@pytest.fixture()
def pijul_repository(fake_repository: Path, pijul_exe: str) -> Path:
"""Create a pijul repository with ignored files."""
Expand Down
16 changes: 14 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# SPDX-FileCopyrightText: 2019 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: 2019 Stefan Bakker <s.bakker777@gmail.com>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
# SPDX-FileCopyrightText: 2022 Pietro Albini <pietro.albini@ferrous-systems.com>
# SPDX-FileCopyrightText: 2024 Carmen Bianca BAKKER <carmenbianca@fsfe.org>
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand All @@ -29,7 +30,7 @@

from reuse import download
from reuse._main import main
from reuse._util import GIT_EXE, HG_EXE, PIJUL_EXE, cleandoc_nl
from reuse._util import GIT_EXE, HG_EXE, JUJUTSU_EXE, PIJUL_EXE, cleandoc_nl
from reuse.report import LINT_VERSION

# REUSE-IgnoreStart
Expand Down Expand Up @@ -57,6 +58,17 @@ def optional_hg_exe(
yield exe


@pytest.fixture(params=[True, False])
def optional_jujutsu_exe(
request, monkeypatch
) -> Generator[Optional[str], None, None]:
"""Run the test with or without Jujutsu."""
exe = JUJUTSU_EXE if request.param else ""
monkeypatch.setattr("reuse.vcs.JUJUTSU_EXE", exe)
monkeypatch.setattr("reuse._util.JUJUTSU_EXE", exe)
yield exe


@pytest.fixture(params=[True, False])
def optional_pijul_exe(
request, monkeypatch
Expand Down
35 changes: 35 additions & 0 deletions tests/test_project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
# SPDX-FileCopyrightText: 2023 Carmen Bianca BAKKER <carmenbianca@fsfe.org>
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
Expand Down Expand Up @@ -240,6 +241,40 @@ def test_all_files_hg_ignored_contains_newline(hg_repository):
assert Path("hello\nworld.pyc").absolute() not in project.all_files()


def test_all_files_jujutsu_ignored(jujutsu_repository):
"""Given a jujutsu repository where some files are ignored, do not yield
those files.
"""
project = Project.from_directory(jujutsu_repository)
assert Path("build/hello.py").absolute() not in project.all_files()


def test_all_files_jujutsu_ignored_different_cwd(jujutsu_repository):
"""Given a jujutsu repository where some files are ignored, do not yield
those files.

Be in a different CWD during the above.
"""
os.chdir(jujutsu_repository / "LICENSES")
project = Project.from_directory(jujutsu_repository)
assert Path("build/hello.py").absolute() not in project.all_files()


def test_all_files_jujutsu_ignored_contains_space(jujutsu_repository):
"""File names that contain spaces are also ignored."""
(jujutsu_repository / "I contain spaces.pyc").touch()
project = Project.from_directory(jujutsu_repository)
assert Path("I contain spaces.pyc").absolute() not in project.all_files()


@posix
def test_all_files_jujutsu_ignored_contains_newline(jujutsu_repository):
"""File names that contain newlines are also ignored."""
(jujutsu_repository / "hello\nworld.pyc").touch()
project = Project.from_directory(jujutsu_repository)
assert Path("hello\nworld.pyc").absolute() not in project.all_files()


def test_all_files_pijul_ignored(pijul_repository):
"""Given a pijul repository where some files are ignored, do not yield
those files.
Expand Down
13 changes: 12 additions & 1 deletion tests/test_vcs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
# SPDX-FileCopyrightText: 2024 Skyler Grey <sky@a.starrysky.fyi>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -33,6 +34,16 @@ def test_find_root_in_hg_repo(hg_repository):
assert Path(result).absolute().resolve() == hg_repository


def test_find_root_in_jujutsu_repo(jujutsu_repository):
"""When using reuse from a child directory in a Jujutsu repo, always find
the root directory.
"""
os.chdir("src")
result = vcs.find_root()

assert Path(result).absolute().resolve() == jujutsu_repository


def test_find_root_in_pijul_repo(pijul_repository):
"""When using reuse from a child directory in a Pijul repo, always find
the root directory.
Expand Down
Loading