Skip to content

Commit dcaf72d

Browse files
committed
Add support for Sapling as a VCS as well.
I introduced support to ignore the `.sl` directory already, but that did not include support for the VCS interface. As a VCS, Sapling uses effectively the same interface as Mercurial, although notably it does not support Mercurial's `.hgignore` file, nor does it invent a new one: it uses `.gitignore`. I opted for copying the Mercurial implementation though, it looks more adequate.
1 parent 4d18332 commit dcaf72d

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

src/reuse/_util.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
GIT_EXE = shutil.which("git")
6363
HG_EXE = shutil.which("hg")
6464
PIJUL_EXE = shutil.which("pijul")
65+
SL_EXE = shutil.which("sl")
6566

6667
REUSE_IGNORE_START = "REUSE-IgnoreStart"
6768
REUSE_IGNORE_END = "REUSE-IgnoreEnd"

src/reuse/vcs.py

+69-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from pathlib import Path
1717
from typing import TYPE_CHECKING, Generator, Optional, Set, Type
1818

19-
from ._util import GIT_EXE, HG_EXE, PIJUL_EXE, StrPath, execute_command
19+
from ._util import GIT_EXE, HG_EXE, PIJUL_EXE, SL_EXE, StrPath, execute_command
2020

2121
if TYPE_CHECKING:
2222
from .project import Project
@@ -249,6 +249,74 @@ def find_root(cls, cwd: Optional[StrPath] = None) -> Optional[Path]:
249249
return None
250250

251251

252+
class VSStrategySapling(VCSStrategy):
253+
EXE = SL_EXE
254+
255+
def __init__(self, project: Project):
256+
super().__init__(project)
257+
if not self.EXE:
258+
raise FileNotFoundError("Could not find binary for Sapling")
259+
self._all_ignored_files = self._find_all_ignored_files()
260+
261+
def _find_all_ignored_files(self) -> Set[Path]:
262+
"""Return a set of all files ignored by sapling. If a whole directory
263+
is ignored, don't return all files inside of it.
264+
"""
265+
command = [
266+
str(self.EXE),
267+
"status",
268+
"--ignored",
269+
# terse is marked 'experimental' in the hg help but is documented
270+
# in the man page. It collapses the output of a dir containing only
271+
# ignored files to the ignored name like the git command does.
272+
# TODO: Re-enable this flag in the future.
273+
# "--terse=i",
274+
"--no-status",
275+
"--print0",
276+
]
277+
result = execute_command(command, _LOGGER, cwd=self.project.root)
278+
all_files = result.stdout.decode("utf-8").split("\0")
279+
return {Path(file_) for file_ in all_files}
280+
281+
def is_ignored(self, path: StrPath) -> bool:
282+
path = self.project.relative_from_root(path)
283+
return path in self._all_ignored_files
284+
285+
def is_submodule(self, path: StrPath) -> bool:
286+
# TODO: Implement me.
287+
return False
288+
289+
@classmethod
290+
def in_repo(cls, directory: StrPath) -> bool:
291+
if directory is None:
292+
directory = Path.cwd()
293+
294+
if not Path(directory).is_dir():
295+
raise NotADirectoryError()
296+
297+
command = [str(cls.EXE), "root"]
298+
result = execute_command(command, _LOGGER, cwd=directory)
299+
300+
return not result.returncode
301+
302+
@classmethod
303+
def find_root(cls, cwd: Optional[StrPath] = None) -> Optional[Path]:
304+
if cwd is None:
305+
cwd = Path.cwd()
306+
307+
if not Path(cwd).is_dir():
308+
raise NotADirectoryError()
309+
310+
command = [str(cls.EXE), "root"]
311+
result = execute_command(command, _LOGGER, cwd=cwd)
312+
313+
if not result.returncode:
314+
path = result.stdout.decode("utf-8")[:-1]
315+
return Path(os.path.relpath(path, cwd))
316+
317+
return None
318+
319+
252320
class VCSStrategyPijul(VCSStrategy):
253321
"""Strategy that is used for Pijul."""
254322

0 commit comments

Comments
 (0)