A tool for compliance with the REUSE recommendations for software licensing and copyright management.
—
REUSE provides pluggable version control system support through a strategy pattern. The system supports Git, Mercurial, Jujutsu, and Pijul, with automatic VCS detection and root directory discovery.
Base class for all version control system implementations.
class VCSStrategy(ABC):
"""
Abstract base class for VCS strategies using the strategy pattern.
Provides the interface for integrating with different version
control systems, handling ignore patterns, and discovering
project boundaries.
"""
EXE: Optional[str] = None # Path to VCS executable
def __init__(self, root: StrPath):
"""
Initialize VCS strategy.
Args:
root: Root directory of the project
"""
self.root = Path(root)
@abstractmethod
def is_ignored(self, path: StrPath) -> bool:
"""
Check if path is ignored by VCS.
Args:
path: File or directory path to check (accepts str or Path-like)
Returns:
True if path should be ignored by REUSE processing
"""
@abstractmethod
def is_submodule(self, path: StrPath) -> bool:
"""
Check if path is a VCS submodule.
Args:
path: Directory path to check (accepts str or Path-like)
Returns:
True if path is a submodule/subrepo
"""
@classmethod
@abstractmethod
def in_repo(cls, directory: StrPath) -> bool:
"""
Check if directory is inside of the VCS repository.
Args:
directory: Directory path to check
Returns:
True if directory is within a VCS repository
Raises:
NotADirectoryError: if directory is not a directory.
"""
@classmethod
@abstractmethod
def find_root(cls, cwd: Optional[StrPath] = None) -> Optional[Path]:
"""
Try to find the root of the project from cwd.
Args:
cwd: Directory to start search from (defaults to current directory)
Returns:
Root directory path if found, None otherwise
Raises:
NotADirectoryError: if directory is not a directory.
"""Implementation for Git version control system.
class VCSStrategyGit(VCSStrategy):
"""
Git VCS support.
Provides Git-specific functionality including .gitignore processing,
submodule detection, and Git repository root discovery.
"""
def is_ignored(self, path: Path) -> bool:
"""
Check if path is ignored by Git (.gitignore).
Args:
path: Path to check against Git ignore patterns
Returns:
True if Git would ignore this path
"""
def is_submodule(self, path: Path) -> bool:
"""
Check if path is a Git submodule.
Args:
path: Directory path to check
Returns:
True if path is a Git submodule
"""
@classmethod
def find_root(cls, path: Path) -> Optional[Path]:
"""
Find Git repository root by looking for .git directory.
Args:
path: Starting path for search
Returns:
Git repository root, or None if not in Git repo
"""Usage Examples:
from reuse.vcs import VCSStrategyGit
from pathlib import Path
# Create Git strategy for project
git_strategy = VCSStrategyGit(Path("/path/to/git/project"))
# Check if files are ignored
test_files = [
Path("src/main.py"),
Path("build/output.o"),
Path(".env"),
Path("node_modules/package/index.js")
]
for file_path in test_files:
if git_strategy.is_ignored(file_path):
print(f"Ignored: {file_path}")
else:
print(f"Tracked: {file_path}")
# Check for submodules
subdirs = [Path("lib/external"), Path("src/core")]
for subdir in subdirs:
if git_strategy.is_submodule(subdir):
print(f"Submodule: {subdir}")
# Find Git root
git_root = VCSStrategyGit.find_root(Path("/path/to/git/project/src"))
print(f"Git root: {git_root}")Implementation for Mercurial version control system.
class VCSStrategyHg(VCSStrategy):
"""
Mercurial VCS support.
Provides Mercurial-specific functionality including .hgignore processing,
subrepo detection, and Mercurial repository root discovery.
"""
def is_ignored(self, path: Path) -> bool:
"""
Check if path is ignored by Mercurial (.hgignore).
Args:
path: Path to check against Mercurial ignore patterns
Returns:
True if Mercurial would ignore this path
"""
def is_submodule(self, path: Path) -> bool:
"""
Check if path is a Mercurial subrepo.
Args:
path: Directory path to check
Returns:
True if path is a Mercurial subrepo
"""
@classmethod
def find_root(cls, path: Path) -> Optional[Path]:
"""
Find Mercurial repository root by looking for .hg directory.
Args:
path: Starting path for search
Returns:
Mercurial repository root, or None if not in Hg repo
"""Implementation for Jujutsu version control system.
class VCSStrategyJujutsu(VCSStrategy):
"""
Jujutsu VCS support.
Provides Jujutsu-specific functionality including ignore pattern processing
and repository root discovery for the modern Jujutsu VCS.
"""
def is_ignored(self, path: Path) -> bool:
"""
Check if path is ignored by Jujutsu.
Args:
path: Path to check against Jujutsu ignore patterns
Returns:
True if Jujutsu would ignore this path
"""
def is_submodule(self, path: Path) -> bool:
"""
Check if path is a Jujutsu workspace/submodule.
Args:
path: Directory path to check
Returns:
True if path is a Jujutsu submodule
"""
@classmethod
def find_root(cls, path: Path) -> Optional[Path]:
"""
Find Jujutsu repository root.
Args:
path: Starting path for search
Returns:
Jujutsu repository root, or None if not in Jujutsu repo
"""Implementation for Pijul version control system.
class VCSStrategyPijul(VCSStrategy):
"""
Pijul VCS support.
Provides Pijul-specific functionality including ignore pattern processing
and repository root discovery for the Pijul patch-based VCS.
"""
def is_ignored(self, path: Path) -> bool:
"""
Check if path is ignored by Pijul.
Args:
path: Path to check against Pijul ignore patterns
Returns:
True if Pijul would ignore this path
"""
def is_submodule(self, path: Path) -> bool:
"""
Check if path is a Pijul subrepository.
Args:
path: Directory path to check
Returns:
True if path is a Pijul subrepository
"""
@classmethod
def find_root(cls, path: Path) -> Optional[Path]:
"""
Find Pijul repository root.
Args:
path: Starting path for search
Returns:
Pijul repository root, or None if not in Pijul repo
"""Implementation for projects without version control.
class VCSStrategyNone(VCSStrategy):
"""
No VCS support.
Used for projects that don't use version control or when
VCS detection fails. Provides minimal functionality with
no ignore pattern processing.
"""
def is_ignored(self, path: Path) -> bool:
"""
Always returns False (no ignore patterns).
Args:
path: Path to check (ignored)
Returns:
False (nothing is ignored without VCS)
"""
return False
def is_submodule(self, path: Path) -> bool:
"""
Always returns False (no submodules without VCS).
Args:
path: Directory path to check (ignored)
Returns:
False (no submodules without VCS)
"""
return False
@classmethod
def find_root(cls, path: Path) -> Optional[Path]:
"""
Returns None (no VCS root to find).
Args:
path: Starting path (ignored)
Returns:
None (no VCS root exists)
"""
return NoneUtility functions for discovering and working with VCS systems.
def all_vcs_strategies() -> Generator[Type[VCSStrategy], None, None]:
"""
Get all available VCS strategies.
Yields:
VCS strategy classes in order of preference
Note:
Yields concrete VCS strategy classes (Git, Mercurial, etc.)
but not the abstract base class or VCSStrategyNone.
"""
def find_root(cwd: Optional[StrPath] = None) -> Optional[Path]:
"""
Find VCS root directory starting from current working directory.
Args:
cwd: Starting directory (default: current working directory)
Returns:
VCS root directory, or None if no VCS found
Note:
Tries all available VCS strategies in order until one
successfully finds a repository root.
"""Usage Examples:
from reuse.vcs import all_vcs_strategies, find_root
from pathlib import Path
# List all available VCS strategies
print("Available VCS strategies:")
for strategy_class in all_vcs_strategies():
print(f" - {strategy_class.__name__}")
# Find VCS root automatically
project_root = find_root()
if project_root:
print(f"Found VCS root: {project_root}")
else:
print("No VCS repository found")
# Find VCS root from specific directory
specific_root = find_root("/path/to/project/subdir")
if specific_root:
print(f"VCS root: {specific_root}")
# Try each VCS strategy manually
test_path = Path("/path/to/project")
for strategy_class in all_vcs_strategies():
root = strategy_class.find_root(test_path)
if root:
print(f"Found {strategy_class.__name__} repo at: {root}")
breakfrom reuse.vcs import all_vcs_strategies, VCSStrategyNone
from pathlib import Path
def detect_vcs_strategy(project_path: Path) -> VCSStrategy:
"""Automatically detect and create appropriate VCS strategy."""
# Try each VCS strategy
for strategy_class in all_vcs_strategies():
root = strategy_class.find_root(project_path)
if root:
print(f"Detected {strategy_class.__name__} at {root}")
return strategy_class(root)
# Fallback to no VCS
print("No VCS detected, using VCSStrategyNone")
return VCSStrategyNone(project_path)
# Usage
project = Path("/path/to/project")
vcs_strategy = detect_vcs_strategy(project)
print(f"Using strategy: {type(vcs_strategy).__name__}")from reuse.vcs import find_root, all_vcs_strategies
from pathlib import Path
from typing import Iterator
def get_vcs_tracked_files(project_path: Path) -> Iterator[Path]:
"""Get all files that should be processed by REUSE (not ignored by VCS)."""
# Detect VCS strategy
vcs_strategy = None
for strategy_class in all_vcs_strategies():
root = strategy_class.find_root(project_path)
if root:
vcs_strategy = strategy_class(root)
break
if not vcs_strategy:
# No VCS, process all files
for file_path in project_path.rglob("*"):
if file_path.is_file():
yield file_path
return
# Process files not ignored by VCS
for file_path in project_path.rglob("*"):
if file_path.is_file() and not vcs_strategy.is_ignored(file_path):
yield file_path
# Usage
project_files = list(get_vcs_tracked_files(Path("/path/to/project")))
print(f"Found {len(project_files)} VCS-tracked files")from reuse.vcs import VCSStrategyGit
from pathlib import Path
def analyze_submodules(project_path: Path) -> dict:
"""Analyze Git submodules in a project."""
git_root = VCSStrategyGit.find_root(project_path)
if not git_root:
return {"error": "Not a Git repository"}
git_strategy = VCSStrategyGit(git_root)
analysis = {
"root": str(git_root),
"submodules": [],
"submodule_files": 0,
"main_repo_files": 0
}
# Check all directories
for dir_path in git_root.rglob("*"):
if dir_path.is_dir() and git_strategy.is_submodule(dir_path):
submodule_info = {
"path": str(dir_path.relative_to(git_root)),
"absolute_path": str(dir_path),
"file_count": sum(1 for f in dir_path.rglob("*") if f.is_file())
}
analysis["submodules"].append(submodule_info)
analysis["submodule_files"] += submodule_info["file_count"]
# Count main repository files
for file_path in git_root.rglob("*"):
if file_path.is_file():
# Check if file is in a submodule
in_submodule = any(
git_strategy.is_submodule(parent)
for parent in file_path.parents
)
if not in_submodule:
analysis["main_repo_files"] += 1
return analysis
# Usage
submodule_analysis = analyze_submodules(Path("/path/to/git/project"))
print(f"Found {len(submodule_analysis.get('submodules', []))} submodules")
for submod in submodule_analysis.get("submodules", []):
print(f" {submod['path']}: {submod['file_count']} files")from reuse.vcs import all_vcs_strategies
from pathlib import Path
from typing import Dict, List
def detect_all_vcs_repos(search_path: Path) -> Dict[str, List[Path]]:
"""Detect all VCS repositories under a search path."""
repos = {}
# Initialize results for each VCS type
for strategy_class in all_vcs_strategies():
vcs_name = strategy_class.__name__.replace("VCSStrategy", "").lower()
repos[vcs_name] = []
# Search for repositories
for dir_path in search_path.rglob("*"):
if dir_path.is_dir():
for strategy_class in all_vcs_strategies():
root = strategy_class.find_root(dir_path)
if root and root not in repos[strategy_class.__name__.replace("VCSStrategy", "").lower()]:
vcs_name = strategy_class.__name__.replace("VCSStrategy", "").lower()
repos[vcs_name].append(root)
return repos
# Usage
all_repos = detect_all_vcs_repos(Path("/workspace"))
for vcs_type, repo_list in all_repos.items():
if repo_list:
print(f"{vcs_type.capitalize()} repositories:")
for repo in repo_list:
print(f" - {repo}")# Type alias used in VCS functions
StrPath = Union[str, PathLike[str]] # Something that looks like a pathThis type alias is used throughout the VCS integration system to accept both string paths and Path objects.
Install with Tessl CLI
npx tessl i tessl/pypi-reuse