Skip to content

Commit

Permalink
feat(vcs): Add Jujutsu support
Browse files Browse the repository at this point in the history
Although it uses .gitignore, Jujutsu has support for repositories
without git ("non-colocated"), which we can't use "git ls-files" on

jj has its own command for listing out tracked files ("jj files"), and
we can use it to determine ignored files in a similar way to Pijul

Fixes: fsfe#711
  • Loading branch information
Minion3665 committed Jul 14, 2024
1 parent aa5db44 commit 5d65ebd
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 6 deletions.
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 <[email protected]>
#
# 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 <[email protected]>
- Сергій <[email protected]>
- Mersho <[email protected]>
- Skyler Grey <[email protected]>
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 <[email protected]>
# SPDX-FileCopyrightText: 2024 Rivos Inc.
# SPDX-FileCopyrightText: 2024 Skyler Grey <[email protected]>
# 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 <[email protected]>
# SPDX-FileCopyrightText: 2023 Markus Haug <[email protected]>
# SPDX-FileCopyrightText: 2024 Skyler Grey <[email protected]>
# 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 <[email protected]>
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]>
# SPDX-FileCopyrightText: 2023 Matthias Riße
# SPDX-FileCopyrightText: 2024 Skyler Grey <[email protected]>
#
# 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 <[email protected]>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]>
# SPDX-FileCopyrightText: 2022 Pietro Albini <[email protected]>
# SPDX-FileCopyrightText: 2024 Carmen Bianca BAKKER <[email protected]>
# SPDX-FileCopyrightText: 2024 Skyler Grey <[email protected]>
# 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 <[email protected]>
# SPDX-FileCopyrightText: 2023 Carmen Bianca BAKKER <[email protected]>
# SPDX-FileCopyrightText: 2024 Skyler Grey <[email protected]>
# 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 <[email protected]>
# SPDX-FileCopyrightText: 2024 Skyler Grey <[email protected]>
# 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

0 comments on commit 5d65ebd

Please sign in to comment.