or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-processing.mdconfiguration.mderror-processing.mdindex.mdmain-interface.mdplugin-development.mdpytest-integration.mdvcs-hooks.md

vcs-hooks.mddocs/

0

# Version Control Hooks

1

2

Pre-commit and post-commit hooks for Git and Mercurial integration. Pylama provides automated code quality checking as part of your version control workflow to catch issues before they enter the repository.

3

4

## Capabilities

5

6

### Git Integration

7

8

Git pre-commit hook implementation that checks staged files before allowing commits.

9

10

```python { .api }

11

def git_hook(error: bool = True):

12

"""

13

Git pre-commit hook implementation.

14

15

Args:

16

error: Whether to exit with error code if issues are found

17

18

This function:

19

- Gets list of staged files using 'git diff-index --cached --name-only HEAD'

20

- Filters for Python files (.py extension)

21

- Runs pylama checks on staged files only

22

- Displays any errors found

23

- Exits with error code to prevent commit if issues found and error=True

24

25

Usage in pre-commit hook:

26

#!/usr/bin/env python

27

import sys

28

from pylama.hook import git_hook

29

30

if __name__ == '__main__':

31

sys.exit(git_hook())

32

"""

33

```

34

35

### Mercurial Integration

36

37

Mercurial commit hook implementation for post-commit quality checking.

38

39

```python { .api }

40

def hg_hook(_, repo, node=None, **kwargs):

41

"""

42

Mercurial commit hook implementation.

43

44

Args:

45

_: Unused first argument (Mercurial convention)

46

repo: Mercurial repository object

47

node: Starting revision node for the commit range

48

**kwargs: Additional keyword arguments from Mercurial

49

50

This function:

51

- Iterates through committed revisions from node to tip

52

- Collects all modified files in the commit range

53

- Filters for existing Python files

54

- Runs pylama checks on modified files

55

- Displays errors and exits with error code if issues found

56

57

Configured in .hg/hgrc:

58

[hooks]

59

commit = python:pylama.hook.hg_hook

60

qrefresh = python:pylama.hook.hg_hook

61

"""

62

```

63

64

### Hook Installation

65

66

Automatic hook installation with VCS detection and setup.

67

68

```python { .api }

69

def install_hook(path: str):

70

"""

71

Auto-detect VCS and install appropriate hook.

72

73

Args:

74

path: Repository root path to install hooks in

75

76

Detection logic:

77

- Checks for .git/hooks directory -> installs Git hook

78

- Checks for .hg directory -> installs Mercurial hook

79

- Exits with error if no supported VCS found

80

81

This function provides a convenient way to set up hooks without

82

manual VCS-specific configuration.

83

"""

84

85

def install_git(path: str):

86

"""

87

Install Git pre-commit hook.

88

89

Args:

90

path: Path to .git/hooks directory

91

92

Creates executable pre-commit hook script that:

93

- Imports and calls git_hook() function

94

- Has proper shebang and permissions

95

- Prevents commits when code quality issues are found

96

"""

97

98

def install_hg(path: str):

99

"""

100

Install Mercurial commit hook.

101

102

Args:

103

path: Path to .hg directory

104

105

Modifies .hg/hgrc configuration file to:

106

- Add [hooks] section if not present

107

- Configure commit and qrefresh hooks

108

- Point to pylama.hook.hg_hook function

109

"""

110

```

111

112

### Shell Command Execution

113

114

Utility function for running shell commands from hooks.

115

116

```python { .api }

117

def run(command: str) -> Tuple[int, List[bytes], List[bytes]]:

118

"""

119

Run shell command and capture output.

120

121

Args:

122

command: Shell command string to execute

123

124

Returns:

125

tuple: (return_code, stdout_lines, stderr_lines)

126

- return_code: Exit code from command

127

- stdout_lines: List of stdout lines as bytes

128

- stderr_lines: List of stderr lines as bytes

129

130

Used internally by hooks to execute Git/Mercurial commands

131

for file discovery and status checking.

132

"""

133

```

134

135

## Installation and Setup

136

137

### Command Line Installation

138

139

```python

140

# Install hooks via command line

141

import subprocess

142

143

# For Git repositories

144

subprocess.run(['pylama', '--hook', '/path/to/git/repo'])

145

146

# For Mercurial repositories

147

subprocess.run(['pylama', '--hook', '/path/to/hg/repo'])

148

```

149

150

### Programmatic Installation

151

152

```python

153

from pylama.hook import install_hook, install_git, install_hg

154

155

# Auto-detect and install

156

install_hook('/path/to/repository')

157

158

# Install specific VCS hooks

159

install_git('/path/to/repo/.git/hooks')

160

install_hg('/path/to/repo/.hg')

161

```

162

163

### Manual Git Hook Setup

164

165

```bash

166

#!/usr/bin/env python

167

# .git/hooks/pre-commit

168

169

import sys

170

from pylama.hook import git_hook

171

172

if __name__ == '__main__':

173

sys.exit(git_hook())

174

```

175

176

```bash

177

# Make hook executable

178

chmod +x .git/hooks/pre-commit

179

```

180

181

### Manual Mercurial Hook Setup

182

183

```ini

184

# .hg/hgrc

185

[hooks]

186

commit = python:pylama.hook.hg_hook

187

qrefresh = python:pylama.hook.hg_hook

188

```

189

190

## Usage Examples

191

192

### Basic Hook Usage

193

194

```python

195

from pylama.hook import git_hook

196

197

# Run git hook manually (for testing)

198

try:

199

git_hook(error=False) # Don't exit on errors

200

print("All staged files pass quality checks")

201

except SystemExit as e:

202

if e.code != 0:

203

print("Quality issues found in staged files")

204

```

205

206

### Custom Hook Configuration

207

208

```python

209

import os

210

from pylama.hook import git_hook

211

from pylama.config import parse_options

212

213

def custom_git_hook():

214

"""Custom git hook with specific configuration."""

215

216

# Set custom pylama configuration

217

os.environ['PYLAMA_CONFIG'] = 'hooks/pylama.ini'

218

219

# Run hook with custom settings

220

git_hook()

221

222

# Custom configuration file (hooks/pylama.ini)

223

hook_config = """

224

[pylama]

225

linters = pycodestyle,pyflakes

226

ignore = E501,W503

227

format = parsable

228

"""

229

```

230

231

### Conditional Hook Execution

232

233

```python

234

import os

235

from pylama.hook import git_hook

236

237

def conditional_git_hook():

238

"""Run hook only in certain conditions."""

239

240

# Skip hook in CI environment

241

if os.getenv('CI') == 'true':

242

print("Skipping pylama hook in CI environment")

243

return 0

244

245

# Skip hook for merge commits

246

if os.path.exists('.git/MERGE_HEAD'):

247

print("Skipping pylama hook for merge commit")

248

return 0

249

250

# Run normal hook

251

return git_hook()

252

```

253

254

### Hook with Custom File Filtering

255

256

```python

257

import subprocess

258

from pylama.hook import run

259

from pylama.main import check_paths

260

from pylama.config import parse_options

261

262

def selective_git_hook():

263

"""Git hook that only checks certain file patterns."""

264

265

# Get staged files

266

_, files_modified, _ = run("git diff-index --cached --name-only HEAD")

267

268

# Filter for specific patterns

269

python_files = []

270

for file_bytes in files_modified:

271

filename = file_bytes.decode('utf-8')

272

if filename.endswith('.py') and not filename.startswith('tests/'):

273

python_files.append(filename)

274

275

if not python_files:

276

return 0

277

278

# Check filtered files

279

options = parse_options(['--linters=pycodestyle,pyflakes'])

280

errors = check_paths(python_files, options)

281

282

if errors:

283

for error in errors:

284

print(f"{error.filename}:{error.lnum} - {error.message}")

285

return 1

286

287

return 0

288

```

289

290

### Hook Integration with CI/CD

291

292

```python

293

import json

294

from pylama.hook import git_hook

295

from pylama.main import check_paths

296

from pylama.config import parse_options

297

298

def ci_hook():

299

"""Hook that generates CI-friendly output."""

300

301

try:

302

# Configure for JSON output

303

options = parse_options(['--format=json'])

304

305

# Get modified files (in CI, check all files)

306

errors = check_paths(['.'], options)

307

308

if errors:

309

# Output for CI system

310

error_data = [error.to_dict() for error in errors]

311

with open('pylama-results.json', 'w') as f:

312

json.dump(error_data, f, indent=2)

313

314

print(f"Found {len(errors)} code quality issues")

315

return 1

316

317

print("All files pass code quality checks")

318

return 0

319

320

except Exception as e:

321

print(f"Hook execution failed: {e}")

322

return 1

323

```

324

325

### Pre-push Hook

326

327

```python

328

import sys

329

from pylama.main import check_paths

330

from pylama.config import parse_options

331

332

def pre_push_hook():

333

"""Check all files before pushing to remote."""

334

335

print("Running code quality checks before push...")

336

337

# Check entire codebase

338

options = parse_options([

339

'--linters=pycodestyle,pyflakes,mccabe',

340

'--ignore=E501',

341

'.'

342

])

343

344

errors = check_paths(['.'], options)

345

346

if errors:

347

print(f"\nFound {len(errors)} code quality issues:")

348

for error in errors[:10]: # Show first 10 errors

349

print(f" {error.filename}:{error.lnum} - {error.message}")

350

351

if len(errors) > 10:

352

print(f" ... and {len(errors) - 10} more issues")

353

354

print("\nPush aborted. Fix issues before pushing.")

355

return 1

356

357

print("All files pass code quality checks. Push allowed.")

358

return 0

359

360

if __name__ == '__main__':

361

sys.exit(pre_push_hook())

362

```

363

364

### Hook Bypass Mechanism

365

366

```python

367

import os

368

import sys

369

from pylama.hook import git_hook

370

371

def bypassable_git_hook():

372

"""Git hook that can be bypassed with environment variable."""

373

374

# Check for bypass flag

375

if os.getenv('PYLAMA_SKIP_HOOK') == '1':

376

print("Pylama hook bypassed via PYLAMA_SKIP_HOOK")

377

return 0

378

379

# Check for --no-verify in git commit command

380

if '--no-verify' in ' '.join(sys.argv):

381

print("Pylama hook bypassed via --no-verify")

382

return 0

383

384

# Run normal hook

385

return git_hook()

386

387

# Usage: PYLAMA_SKIP_HOOK=1 git commit -m "emergency fix"

388

# Usage: git commit --no-verify -m "bypass hook"

389

```

390

391

### Hook Performance Optimization

392

393

```python

394

import time

395

from pylama.hook import run, git_hook

396

from pylama.main import check_paths

397

from pylama.config import parse_options

398

399

def fast_git_hook():

400

"""Optimized git hook for large repositories."""

401

402

start_time = time.time()

403

404

# Get only staged Python files

405

_, files_modified, _ = run("git diff-index --cached --name-only HEAD")

406

python_files = [

407

f.decode('utf-8') for f in files_modified

408

if f.decode('utf-8').endswith('.py')

409

]

410

411

if not python_files:

412

return 0

413

414

# Use fast linters only

415

options = parse_options([

416

'--linters=pycodestyle,pyflakes', # Skip slow linters

417

'--async', # Use parallel processing

418

'--ignore=E501,W503' # Ignore non-critical issues

419

])

420

421

errors = check_paths(python_files, options)

422

423

elapsed = time.time() - start_time

424

print(f"Hook completed in {elapsed:.2f}s")

425

426

if errors:

427

print(f"Found {len(errors)} issues in staged files")

428

for error in errors:

429

print(f" {error.filename}:{error.lnum} - {error.message}")

430

return 1

431

432

return 0

433

```

434

435

## Hook Configuration

436

437

### Repository-specific Settings

438

439

```ini

440

# .pylama.ini (in repository root)

441

[pylama]

442

# Hook-specific configuration

443

linters = pycodestyle,pyflakes

444

ignore = E501,W503,E203

445

skip = migrations/*,venv/*

446

format = parsable

447

448

# Different settings for hooks vs manual runs

449

[pylama:hook]

450

linters = pycodestyle,pyflakes # Faster linters for hooks

451

max_line_length = 100

452

```

453

454

### Global Hook Settings

455

456

```ini

457

# ~/.pylama.ini (global configuration)

458

[pylama]

459

# Global defaults for all projects

460

ignore = E501,W503

461

format = parsable

462

463

[pylama:hook]

464

# Specific settings when running from hooks

465

async = 1

466

concurrent = 1

467

```

468

469

### Environment Variables

470

471

```bash

472

# Hook-specific environment variables

473

export PYLAMA_CONFIG="/path/to/hook-config.ini"

474

export PYLAMA_SKIP_HOOK="0"

475

export PYLAMA_HOOK_TIMEOUT="30"

476

```