or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdcomment-handling.mdglobal-licensing.mdindex.mdproject-management.mdreport-generation.mdreuse-info.mdvcs-integration.md

vcs-integration.mddocs/

0

# VCS Integration

1

2

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.

3

4

## Capabilities

5

6

### Abstract VCS Strategy

7

8

Base class for all version control system implementations.

9

10

```python { .api }

11

class VCSStrategy(ABC):

12

"""

13

Abstract base class for VCS strategies using the strategy pattern.

14

15

Provides the interface for integrating with different version

16

control systems, handling ignore patterns, and discovering

17

project boundaries.

18

"""

19

20

EXE: Optional[str] = None # Path to VCS executable

21

22

def __init__(self, root: StrPath):

23

"""

24

Initialize VCS strategy.

25

26

Args:

27

root: Root directory of the project

28

"""

29

self.root = Path(root)

30

31

@abstractmethod

32

def is_ignored(self, path: StrPath) -> bool:

33

"""

34

Check if path is ignored by VCS.

35

36

Args:

37

path: File or directory path to check (accepts str or Path-like)

38

39

Returns:

40

True if path should be ignored by REUSE processing

41

"""

42

43

@abstractmethod

44

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

45

"""

46

Check if path is a VCS submodule.

47

48

Args:

49

path: Directory path to check (accepts str or Path-like)

50

51

Returns:

52

True if path is a submodule/subrepo

53

"""

54

55

@classmethod

56

@abstractmethod

57

def in_repo(cls, directory: StrPath) -> bool:

58

"""

59

Check if directory is inside of the VCS repository.

60

61

Args:

62

directory: Directory path to check

63

64

Returns:

65

True if directory is within a VCS repository

66

67

Raises:

68

NotADirectoryError: if directory is not a directory.

69

"""

70

71

@classmethod

72

@abstractmethod

73

def find_root(cls, cwd: Optional[StrPath] = None) -> Optional[Path]:

74

"""

75

Try to find the root of the project from cwd.

76

77

Args:

78

cwd: Directory to start search from (defaults to current directory)

79

80

Returns:

81

Root directory path if found, None otherwise

82

83

Raises:

84

NotADirectoryError: if directory is not a directory.

85

"""

86

```

87

88

### Git VCS Strategy

89

90

Implementation for Git version control system.

91

92

```python { .api }

93

class VCSStrategyGit(VCSStrategy):

94

"""

95

Git VCS support.

96

97

Provides Git-specific functionality including .gitignore processing,

98

submodule detection, and Git repository root discovery.

99

"""

100

101

def is_ignored(self, path: Path) -> bool:

102

"""

103

Check if path is ignored by Git (.gitignore).

104

105

Args:

106

path: Path to check against Git ignore patterns

107

108

Returns:

109

True if Git would ignore this path

110

"""

111

112

def is_submodule(self, path: Path) -> bool:

113

"""

114

Check if path is a Git submodule.

115

116

Args:

117

path: Directory path to check

118

119

Returns:

120

True if path is a Git submodule

121

"""

122

123

@classmethod

124

def find_root(cls, path: Path) -> Optional[Path]:

125

"""

126

Find Git repository root by looking for .git directory.

127

128

Args:

129

path: Starting path for search

130

131

Returns:

132

Git repository root, or None if not in Git repo

133

"""

134

```

135

136

**Usage Examples:**

137

138

```python

139

from reuse.vcs import VCSStrategyGit

140

from pathlib import Path

141

142

# Create Git strategy for project

143

git_strategy = VCSStrategyGit(Path("/path/to/git/project"))

144

145

# Check if files are ignored

146

test_files = [

147

Path("src/main.py"),

148

Path("build/output.o"),

149

Path(".env"),

150

Path("node_modules/package/index.js")

151

]

152

153

for file_path in test_files:

154

if git_strategy.is_ignored(file_path):

155

print(f"Ignored: {file_path}")

156

else:

157

print(f"Tracked: {file_path}")

158

159

# Check for submodules

160

subdirs = [Path("lib/external"), Path("src/core")]

161

for subdir in subdirs:

162

if git_strategy.is_submodule(subdir):

163

print(f"Submodule: {subdir}")

164

165

# Find Git root

166

git_root = VCSStrategyGit.find_root(Path("/path/to/git/project/src"))

167

print(f"Git root: {git_root}")

168

```

169

170

### Mercurial VCS Strategy

171

172

Implementation for Mercurial version control system.

173

174

```python { .api }

175

class VCSStrategyHg(VCSStrategy):

176

"""

177

Mercurial VCS support.

178

179

Provides Mercurial-specific functionality including .hgignore processing,

180

subrepo detection, and Mercurial repository root discovery.

181

"""

182

183

def is_ignored(self, path: Path) -> bool:

184

"""

185

Check if path is ignored by Mercurial (.hgignore).

186

187

Args:

188

path: Path to check against Mercurial ignore patterns

189

190

Returns:

191

True if Mercurial would ignore this path

192

"""

193

194

def is_submodule(self, path: Path) -> bool:

195

"""

196

Check if path is a Mercurial subrepo.

197

198

Args:

199

path: Directory path to check

200

201

Returns:

202

True if path is a Mercurial subrepo

203

"""

204

205

@classmethod

206

def find_root(cls, path: Path) -> Optional[Path]:

207

"""

208

Find Mercurial repository root by looking for .hg directory.

209

210

Args:

211

path: Starting path for search

212

213

Returns:

214

Mercurial repository root, or None if not in Hg repo

215

"""

216

```

217

218

### Jujutsu VCS Strategy

219

220

Implementation for Jujutsu version control system.

221

222

```python { .api }

223

class VCSStrategyJujutsu(VCSStrategy):

224

"""

225

Jujutsu VCS support.

226

227

Provides Jujutsu-specific functionality including ignore pattern processing

228

and repository root discovery for the modern Jujutsu VCS.

229

"""

230

231

def is_ignored(self, path: Path) -> bool:

232

"""

233

Check if path is ignored by Jujutsu.

234

235

Args:

236

path: Path to check against Jujutsu ignore patterns

237

238

Returns:

239

True if Jujutsu would ignore this path

240

"""

241

242

def is_submodule(self, path: Path) -> bool:

243

"""

244

Check if path is a Jujutsu workspace/submodule.

245

246

Args:

247

path: Directory path to check

248

249

Returns:

250

True if path is a Jujutsu submodule

251

"""

252

253

@classmethod

254

def find_root(cls, path: Path) -> Optional[Path]:

255

"""

256

Find Jujutsu repository root.

257

258

Args:

259

path: Starting path for search

260

261

Returns:

262

Jujutsu repository root, or None if not in Jujutsu repo

263

"""

264

```

265

266

### Pijul VCS Strategy

267

268

Implementation for Pijul version control system.

269

270

```python { .api }

271

class VCSStrategyPijul(VCSStrategy):

272

"""

273

Pijul VCS support.

274

275

Provides Pijul-specific functionality including ignore pattern processing

276

and repository root discovery for the Pijul patch-based VCS.

277

"""

278

279

def is_ignored(self, path: Path) -> bool:

280

"""

281

Check if path is ignored by Pijul.

282

283

Args:

284

path: Path to check against Pijul ignore patterns

285

286

Returns:

287

True if Pijul would ignore this path

288

"""

289

290

def is_submodule(self, path: Path) -> bool:

291

"""

292

Check if path is a Pijul subrepository.

293

294

Args:

295

path: Directory path to check

296

297

Returns:

298

True if path is a Pijul subrepository

299

"""

300

301

@classmethod

302

def find_root(cls, path: Path) -> Optional[Path]:

303

"""

304

Find Pijul repository root.

305

306

Args:

307

path: Starting path for search

308

309

Returns:

310

Pijul repository root, or None if not in Pijul repo

311

"""

312

```

313

314

### No VCS Strategy

315

316

Implementation for projects without version control.

317

318

```python { .api }

319

class VCSStrategyNone(VCSStrategy):

320

"""

321

No VCS support.

322

323

Used for projects that don't use version control or when

324

VCS detection fails. Provides minimal functionality with

325

no ignore pattern processing.

326

"""

327

328

def is_ignored(self, path: Path) -> bool:

329

"""

330

Always returns False (no ignore patterns).

331

332

Args:

333

path: Path to check (ignored)

334

335

Returns:

336

False (nothing is ignored without VCS)

337

"""

338

return False

339

340

def is_submodule(self, path: Path) -> bool:

341

"""

342

Always returns False (no submodules without VCS).

343

344

Args:

345

path: Directory path to check (ignored)

346

347

Returns:

348

False (no submodules without VCS)

349

"""

350

return False

351

352

@classmethod

353

def find_root(cls, path: Path) -> Optional[Path]:

354

"""

355

Returns None (no VCS root to find).

356

357

Args:

358

path: Starting path (ignored)

359

360

Returns:

361

None (no VCS root exists)

362

"""

363

return None

364

```

365

366

### VCS Discovery Functions

367

368

Utility functions for discovering and working with VCS systems.

369

370

```python { .api }

371

def all_vcs_strategies() -> Generator[Type[VCSStrategy], None, None]:

372

"""

373

Get all available VCS strategies.

374

375

Yields:

376

VCS strategy classes in order of preference

377

378

Note:

379

Yields concrete VCS strategy classes (Git, Mercurial, etc.)

380

but not the abstract base class or VCSStrategyNone.

381

"""

382

383

def find_root(cwd: Optional[StrPath] = None) -> Optional[Path]:

384

"""

385

Find VCS root directory starting from current working directory.

386

387

Args:

388

cwd: Starting directory (default: current working directory)

389

390

Returns:

391

VCS root directory, or None if no VCS found

392

393

Note:

394

Tries all available VCS strategies in order until one

395

successfully finds a repository root.

396

"""

397

```

398

399

**Usage Examples:**

400

401

```python

402

from reuse.vcs import all_vcs_strategies, find_root

403

from pathlib import Path

404

405

# List all available VCS strategies

406

print("Available VCS strategies:")

407

for strategy_class in all_vcs_strategies():

408

print(f" - {strategy_class.__name__}")

409

410

# Find VCS root automatically

411

project_root = find_root()

412

if project_root:

413

print(f"Found VCS root: {project_root}")

414

else:

415

print("No VCS repository found")

416

417

# Find VCS root from specific directory

418

specific_root = find_root("/path/to/project/subdir")

419

if specific_root:

420

print(f"VCS root: {specific_root}")

421

422

# Try each VCS strategy manually

423

test_path = Path("/path/to/project")

424

for strategy_class in all_vcs_strategies():

425

root = strategy_class.find_root(test_path)

426

if root:

427

print(f"Found {strategy_class.__name__} repo at: {root}")

428

break

429

```

430

431

## VCS Integration Examples

432

433

### Automatic VCS Strategy Selection

434

435

```python

436

from reuse.vcs import all_vcs_strategies, VCSStrategyNone

437

from pathlib import Path

438

439

def detect_vcs_strategy(project_path: Path) -> VCSStrategy:

440

"""Automatically detect and create appropriate VCS strategy."""

441

442

# Try each VCS strategy

443

for strategy_class in all_vcs_strategies():

444

root = strategy_class.find_root(project_path)

445

if root:

446

print(f"Detected {strategy_class.__name__} at {root}")

447

return strategy_class(root)

448

449

# Fallback to no VCS

450

print("No VCS detected, using VCSStrategyNone")

451

return VCSStrategyNone(project_path)

452

453

# Usage

454

project = Path("/path/to/project")

455

vcs_strategy = detect_vcs_strategy(project)

456

print(f"Using strategy: {type(vcs_strategy).__name__}")

457

```

458

459

### VCS-Aware File Processing

460

461

```python

462

from reuse.vcs import find_root, all_vcs_strategies

463

from pathlib import Path

464

from typing import Iterator

465

466

def get_vcs_tracked_files(project_path: Path) -> Iterator[Path]:

467

"""Get all files that should be processed by REUSE (not ignored by VCS)."""

468

469

# Detect VCS strategy

470

vcs_strategy = None

471

for strategy_class in all_vcs_strategies():

472

root = strategy_class.find_root(project_path)

473

if root:

474

vcs_strategy = strategy_class(root)

475

break

476

477

if not vcs_strategy:

478

# No VCS, process all files

479

for file_path in project_path.rglob("*"):

480

if file_path.is_file():

481

yield file_path

482

return

483

484

# Process files not ignored by VCS

485

for file_path in project_path.rglob("*"):

486

if file_path.is_file() and not vcs_strategy.is_ignored(file_path):

487

yield file_path

488

489

# Usage

490

project_files = list(get_vcs_tracked_files(Path("/path/to/project")))

491

print(f"Found {len(project_files)} VCS-tracked files")

492

```

493

494

### Submodule Handling

495

496

```python

497

from reuse.vcs import VCSStrategyGit

498

from pathlib import Path

499

500

def analyze_submodules(project_path: Path) -> dict:

501

"""Analyze Git submodules in a project."""

502

503

git_root = VCSStrategyGit.find_root(project_path)

504

if not git_root:

505

return {"error": "Not a Git repository"}

506

507

git_strategy = VCSStrategyGit(git_root)

508

509

analysis = {

510

"root": str(git_root),

511

"submodules": [],

512

"submodule_files": 0,

513

"main_repo_files": 0

514

}

515

516

# Check all directories

517

for dir_path in git_root.rglob("*"):

518

if dir_path.is_dir() and git_strategy.is_submodule(dir_path):

519

submodule_info = {

520

"path": str(dir_path.relative_to(git_root)),

521

"absolute_path": str(dir_path),

522

"file_count": sum(1 for f in dir_path.rglob("*") if f.is_file())

523

}

524

analysis["submodules"].append(submodule_info)

525

analysis["submodule_files"] += submodule_info["file_count"]

526

527

# Count main repository files

528

for file_path in git_root.rglob("*"):

529

if file_path.is_file():

530

# Check if file is in a submodule

531

in_submodule = any(

532

git_strategy.is_submodule(parent)

533

for parent in file_path.parents

534

)

535

if not in_submodule:

536

analysis["main_repo_files"] += 1

537

538

return analysis

539

540

# Usage

541

submodule_analysis = analyze_submodules(Path("/path/to/git/project"))

542

print(f"Found {len(submodule_analysis.get('submodules', []))} submodules")

543

for submod in submodule_analysis.get("submodules", []):

544

print(f" {submod['path']}: {submod['file_count']} files")

545

```

546

547

### Multi-VCS Project Detection

548

549

```python

550

from reuse.vcs import all_vcs_strategies

551

from pathlib import Path

552

from typing import Dict, List

553

554

def detect_all_vcs_repos(search_path: Path) -> Dict[str, List[Path]]:

555

"""Detect all VCS repositories under a search path."""

556

557

repos = {}

558

559

# Initialize results for each VCS type

560

for strategy_class in all_vcs_strategies():

561

vcs_name = strategy_class.__name__.replace("VCSStrategy", "").lower()

562

repos[vcs_name] = []

563

564

# Search for repositories

565

for dir_path in search_path.rglob("*"):

566

if dir_path.is_dir():

567

for strategy_class in all_vcs_strategies():

568

root = strategy_class.find_root(dir_path)

569

if root and root not in repos[strategy_class.__name__.replace("VCSStrategy", "").lower()]:

570

vcs_name = strategy_class.__name__.replace("VCSStrategy", "").lower()

571

repos[vcs_name].append(root)

572

573

return repos

574

575

# Usage

576

all_repos = detect_all_vcs_repos(Path("/workspace"))

577

for vcs_type, repo_list in all_repos.items():

578

if repo_list:

579

print(f"{vcs_type.capitalize()} repositories:")

580

for repo in repo_list:

581

print(f" - {repo}")

582

```

583

584

## Type Definitions

585

586

```python { .api }

587

# Type alias used in VCS functions

588

StrPath = Union[str, PathLike[str]] # Something that looks like a path

589

```

590

591

This type alias is used throughout the VCS integration system to accept both string paths and Path objects.