gitfluff 0.8.0__tar.gz → 0.9.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitfluff-0.8.0 → gitfluff-0.9.1}/PKG-INFO +10 -7
- {gitfluff-0.8.0 → gitfluff-0.9.1}/README.md +9 -6
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff/__init__.py +1 -1
- gitfluff-0.9.1/gitfluff/downloader.py +192 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff.egg-info/PKG-INFO +10 -7
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff.egg-info/SOURCES.txt +2 -1
- gitfluff-0.9.1/pyproject.toml +38 -0
- gitfluff-0.9.1/tests/test_downloader.py +78 -0
- gitfluff-0.8.0/gitfluff/downloader.py +0 -132
- gitfluff-0.8.0/pyproject.toml +0 -42
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff/__main__.py +0 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff/cli.py +0 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff.egg-info/dependency_links.txt +0 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff.egg-info/entry_points.txt +0 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff.egg-info/requires.txt +0 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/gitfluff.egg-info/top_level.txt +0 -0
- {gitfluff-0.8.0 → gitfluff-0.9.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: gitfluff
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Commit message linter and formatter
|
|
5
5
|
Author-email: Na'aman Hirschfeld <nhirschfeld@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -25,9 +25,9 @@ Requires-Dist: certifi>=2024.7.4
|
|
|
25
25
|
|
|
26
26
|
# gitfluff
|
|
27
27
|
|
|
28
|
-
Commit message linter with presets, custom formats, and cleanup automation. Fully compliant with [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Skips linting while Git is creating a merge commit.
|
|
28
|
+
Commit message linter with presets, custom formats, and cleanup automation. Fully compliant with [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Skips linting the repository’s `COMMIT_EDITMSG` while Git is creating a merge commit.
|
|
29
29
|
|
|
30
|
-
This Python package distributes prebuilt `gitfluff` binaries. On first use, the correct release binary for your platform is downloaded and cached under `~/.cache/gitfluff`.
|
|
30
|
+
This Python package distributes prebuilt `gitfluff` binaries. On first use, the correct release binary for your platform is downloaded and cached under `~/.cache/gitfluff`. The binary's SHA-256 checksum is verified against the release's published checksums manifest before extraction.
|
|
31
31
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
|
@@ -83,6 +83,8 @@ gitfluff hook install commit-msg
|
|
|
83
83
|
gitfluff hook install commit-msg --write
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
Hook installation respects `core.hooksPath` (including relative paths) and works correctly in linked git worktrees.
|
|
87
|
+
|
|
86
88
|
### pre-commit Framework
|
|
87
89
|
|
|
88
90
|
**Add to `.pre-commit-config.yaml`:**
|
|
@@ -94,7 +96,7 @@ default_install_hook_types:
|
|
|
94
96
|
|
|
95
97
|
repos:
|
|
96
98
|
- repo: https://github.com/Goldziher/gitfluff
|
|
97
|
-
rev: v0.
|
|
99
|
+
rev: v0.9.1
|
|
98
100
|
hooks:
|
|
99
101
|
- id: gitfluff-lint
|
|
100
102
|
stages: [commit-msg]
|
|
@@ -122,7 +124,7 @@ commit-msg:
|
|
|
122
124
|
**Install hooks:**
|
|
123
125
|
|
|
124
126
|
```bash
|
|
125
|
-
|
|
127
|
+
pnpm dlx lefthook install
|
|
126
128
|
```
|
|
127
129
|
|
|
128
130
|
## Optional configuration
|
|
@@ -135,15 +137,16 @@ write = true
|
|
|
135
137
|
|
|
136
138
|
[rules]
|
|
137
139
|
no_emojis = true
|
|
140
|
+
ai_cleanup = true # set to false to disable AI attribution cleanup
|
|
138
141
|
title_suffix = "\\(JIRA-[0-9]+\\)"
|
|
139
142
|
exit_nonzero_on_rewrite = true
|
|
140
143
|
```
|
|
141
144
|
|
|
142
|
-
Any setting can be left out; omit the file entirely to keep defaults.
|
|
145
|
+
Any setting can be left out; omit the file entirely to keep defaults. Unknown config keys now trigger an error rather than being silently ignored.
|
|
143
146
|
|
|
144
147
|
## Advanced usage
|
|
145
148
|
|
|
146
|
-
- Override rules per-invocation using CLI flags (e.g. `--preset`, `--msg-pattern`, `--cleanup-pattern`, `--exclude`, `--cleanup`, `--single-line`, `--no-emojis`, `--ascii-only`, `--title-prefix`, `--title-suffix`).
|
|
149
|
+
- Override rules per-invocation using CLI flags (e.g. `--preset`, `--msg-pattern`, `--cleanup-pattern`, `--exclude`, `--cleanup`, `--single-line`, `--no-emojis`, `--ascii-only`, `--title-prefix`, `--title-suffix`, `--no-ai-cleanup`).
|
|
147
150
|
- Set `GITFLUFF_BINARY` to point at a custom build when testing unpublished versions.
|
|
148
151
|
- Clear the cache (`rm ~/.cache/gitfluff/gitfluff*`) to force a fresh download.
|
|
149
152
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# gitfluff
|
|
2
2
|
|
|
3
|
-
Commit message linter with presets, custom formats, and cleanup automation. Fully compliant with [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Skips linting while Git is creating a merge commit.
|
|
3
|
+
Commit message linter with presets, custom formats, and cleanup automation. Fully compliant with [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Skips linting the repository’s `COMMIT_EDITMSG` while Git is creating a merge commit.
|
|
4
4
|
|
|
5
|
-
This Python package distributes prebuilt `gitfluff` binaries. On first use, the correct release binary for your platform is downloaded and cached under `~/.cache/gitfluff`.
|
|
5
|
+
This Python package distributes prebuilt `gitfluff` binaries. On first use, the correct release binary for your platform is downloaded and cached under `~/.cache/gitfluff`. The binary's SHA-256 checksum is verified against the release's published checksums manifest before extraction.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -58,6 +58,8 @@ gitfluff hook install commit-msg
|
|
|
58
58
|
gitfluff hook install commit-msg --write
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
Hook installation respects `core.hooksPath` (including relative paths) and works correctly in linked git worktrees.
|
|
62
|
+
|
|
61
63
|
### pre-commit Framework
|
|
62
64
|
|
|
63
65
|
**Add to `.pre-commit-config.yaml`:**
|
|
@@ -69,7 +71,7 @@ default_install_hook_types:
|
|
|
69
71
|
|
|
70
72
|
repos:
|
|
71
73
|
- repo: https://github.com/Goldziher/gitfluff
|
|
72
|
-
rev: v0.
|
|
74
|
+
rev: v0.9.1
|
|
73
75
|
hooks:
|
|
74
76
|
- id: gitfluff-lint
|
|
75
77
|
stages: [commit-msg]
|
|
@@ -97,7 +99,7 @@ commit-msg:
|
|
|
97
99
|
**Install hooks:**
|
|
98
100
|
|
|
99
101
|
```bash
|
|
100
|
-
|
|
102
|
+
pnpm dlx lefthook install
|
|
101
103
|
```
|
|
102
104
|
|
|
103
105
|
## Optional configuration
|
|
@@ -110,15 +112,16 @@ write = true
|
|
|
110
112
|
|
|
111
113
|
[rules]
|
|
112
114
|
no_emojis = true
|
|
115
|
+
ai_cleanup = true # set to false to disable AI attribution cleanup
|
|
113
116
|
title_suffix = "\\(JIRA-[0-9]+\\)"
|
|
114
117
|
exit_nonzero_on_rewrite = true
|
|
115
118
|
```
|
|
116
119
|
|
|
117
|
-
Any setting can be left out; omit the file entirely to keep defaults.
|
|
120
|
+
Any setting can be left out; omit the file entirely to keep defaults. Unknown config keys now trigger an error rather than being silently ignored.
|
|
118
121
|
|
|
119
122
|
## Advanced usage
|
|
120
123
|
|
|
121
|
-
- Override rules per-invocation using CLI flags (e.g. `--preset`, `--msg-pattern`, `--cleanup-pattern`, `--exclude`, `--cleanup`, `--single-line`, `--no-emojis`, `--ascii-only`, `--title-prefix`, `--title-suffix`).
|
|
124
|
+
- Override rules per-invocation using CLI flags (e.g. `--preset`, `--msg-pattern`, `--cleanup-pattern`, `--exclude`, `--cleanup`, `--single-line`, `--no-emojis`, `--ascii-only`, `--title-prefix`, `--title-suffix`, `--no-ai-cleanup`).
|
|
122
125
|
- Set `GITFLUFF_BINARY` to point at a custom build when testing unpublished versions.
|
|
123
126
|
- Clear the cache (`rm ~/.cache/gitfluff/gitfluff*`) to force a fresh download.
|
|
124
127
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.9.1"
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Fetch gitfluff release binaries for the Python wrapper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import ssl
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import tarfile
|
|
12
|
+
import tempfile
|
|
13
|
+
import zipfile
|
|
14
|
+
from pathlib import Path, PurePosixPath
|
|
15
|
+
from urllib.error import URLError
|
|
16
|
+
from urllib.request import Request, urlopen
|
|
17
|
+
|
|
18
|
+
import certifi
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _platform_triple() -> str:
|
|
22
|
+
system = platform.system().lower()
|
|
23
|
+
machine = platform.machine().lower()
|
|
24
|
+
|
|
25
|
+
if system == "windows":
|
|
26
|
+
if machine in {"amd64", "x86_64"}:
|
|
27
|
+
return "x86_64-pc-windows-gnu"
|
|
28
|
+
if machine in {"x86", "i386", "i686"}:
|
|
29
|
+
raise RuntimeError("32-bit Windows is not supported")
|
|
30
|
+
elif system == "linux":
|
|
31
|
+
if machine in {"amd64", "x86_64"}:
|
|
32
|
+
return "x86_64-unknown-linux-gnu"
|
|
33
|
+
if machine in {"aarch64", "arm64"}:
|
|
34
|
+
return "aarch64-unknown-linux-gnu"
|
|
35
|
+
elif system == "darwin":
|
|
36
|
+
if machine in {"amd64", "x86_64"}:
|
|
37
|
+
return "x86_64-apple-darwin"
|
|
38
|
+
if machine in {"aarch64", "arm64"}:
|
|
39
|
+
return "aarch64-apple-darwin"
|
|
40
|
+
|
|
41
|
+
raise RuntimeError(f"Unsupported platform: {system} {machine}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _python_version_to_tag(version: str) -> str:
|
|
45
|
+
if "rc" in version:
|
|
46
|
+
core, suffix = version.split("rc")
|
|
47
|
+
return f"{core}-rc.{suffix}"
|
|
48
|
+
return version
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _asset(version: str) -> tuple[str, str, str]:
|
|
52
|
+
"""Return the (url, ext, name) of the release archive for this platform.
|
|
53
|
+
|
|
54
|
+
`name` is returned rather than recomputed by the caller: it is the key the checksum manifest
|
|
55
|
+
is looked up by, and a name derived separately from the URL could drift out of step with it.
|
|
56
|
+
"""
|
|
57
|
+
tag = _python_version_to_tag(version)
|
|
58
|
+
triple = _platform_triple()
|
|
59
|
+
ext = "zip" if "windows" in triple else "tar.gz"
|
|
60
|
+
name = f"gitfluff-{triple}.{ext}"
|
|
61
|
+
url = f"https://github.com/Goldziher/gitfluff/releases/download/v{tag}/{name}"
|
|
62
|
+
return url, ext, name
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _download(url: str, destination: Path) -> None:
|
|
66
|
+
request = Request(url, headers={"User-Agent": "gitfluff-python-wrapper"})
|
|
67
|
+
context = ssl.create_default_context(cafile=certifi.where())
|
|
68
|
+
try:
|
|
69
|
+
with urlopen(request, timeout=30, context=context) as response:
|
|
70
|
+
if response.status != 200:
|
|
71
|
+
raise RuntimeError(f"HTTP {response.status}: {response.reason}")
|
|
72
|
+
destination.write_bytes(response.read())
|
|
73
|
+
except URLError as exc:
|
|
74
|
+
raise RuntimeError(f"Failed to download binary: {exc}") from exc
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _extract(archive: Path, ext: str, destination: Path) -> None:
|
|
78
|
+
if ext == "zip":
|
|
79
|
+
with zipfile.ZipFile(archive) as zf:
|
|
80
|
+
for member in zf.infolist():
|
|
81
|
+
if not member.is_dir() and PurePosixPath(member.filename).name == destination.name:
|
|
82
|
+
with zf.open(member) as src, destination.open("wb") as dst:
|
|
83
|
+
dst.write(src.read())
|
|
84
|
+
return
|
|
85
|
+
else:
|
|
86
|
+
with tarfile.open(archive, "r:gz") as tar:
|
|
87
|
+
for member in tar.getmembers():
|
|
88
|
+
if member.isfile() and PurePosixPath(member.name).name == destination.name:
|
|
89
|
+
with tar.extractfile(member) as src, destination.open("wb") as dst:
|
|
90
|
+
dst.write(src.read())
|
|
91
|
+
return
|
|
92
|
+
raise RuntimeError("Binary not found in downloaded archive")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _download_text(url: str) -> str:
|
|
96
|
+
request = Request(url, headers={"User-Agent": "gitfluff-python-wrapper"})
|
|
97
|
+
context = ssl.create_default_context(cafile=certifi.where())
|
|
98
|
+
try:
|
|
99
|
+
with urlopen(request, timeout=30, context=context) as response:
|
|
100
|
+
if response.status != 200:
|
|
101
|
+
raise RuntimeError(f"HTTP {response.status}: {response.reason}")
|
|
102
|
+
return response.read().decode("utf-8")
|
|
103
|
+
except URLError as exc:
|
|
104
|
+
raise RuntimeError(f"Failed to download: {exc}") from exc
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _sha256_file(path: Path) -> str:
|
|
108
|
+
sha256_hash = hashlib.sha256()
|
|
109
|
+
with path.open("rb") as f:
|
|
110
|
+
for byte_block in iter(lambda: f.read(4096), b""):
|
|
111
|
+
sha256_hash.update(byte_block)
|
|
112
|
+
return sha256_hash.hexdigest()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _verify_checksum(archive_path: Path, archive_name: str, version: str) -> None:
|
|
116
|
+
# The manifest is named after the release TAG, not the PyPI version string -- for a release
|
|
117
|
+
# candidate those differ (0.8.0rc1 vs 0.8.0-rc.1), and naming it after the version would
|
|
118
|
+
# request a file that does not exist.
|
|
119
|
+
tag = _python_version_to_tag(version)
|
|
120
|
+
checksum_url = f"https://github.com/Goldziher/gitfluff/releases/download/v{tag}/gitfluff_{tag}_checksums.txt"
|
|
121
|
+
|
|
122
|
+
print("Downloading checksums manifest...", file=sys.stderr)
|
|
123
|
+
checksum_text = _download_text(checksum_url)
|
|
124
|
+
|
|
125
|
+
# sha256sum format is "<hash> <name>". Match the name field exactly rather than with a
|
|
126
|
+
# substring test, so a manifest entry such as "<name>.sig" can never satisfy the lookup.
|
|
127
|
+
entry = next(
|
|
128
|
+
(
|
|
129
|
+
fields
|
|
130
|
+
for fields in (line.strip().split() for line in checksum_text.splitlines())
|
|
131
|
+
if len(fields) >= 2 and PurePosixPath(fields[-1]).name == archive_name
|
|
132
|
+
),
|
|
133
|
+
None,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if entry is None:
|
|
137
|
+
raise RuntimeError(f"Checksum not found for {archive_name} in manifest")
|
|
138
|
+
|
|
139
|
+
expected_hash = entry[0]
|
|
140
|
+
|
|
141
|
+
print("Verifying archive checksum...", file=sys.stderr)
|
|
142
|
+
actual_hash = _sha256_file(archive_path)
|
|
143
|
+
|
|
144
|
+
if actual_hash != expected_hash:
|
|
145
|
+
raise RuntimeError(f"Checksum mismatch for {archive_name}\nExpected: {expected_hash}\nActual: {actual_hash}")
|
|
146
|
+
|
|
147
|
+
print("Checksum verified successfully.", file=sys.stderr)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _cache_path(version: str) -> Path:
|
|
151
|
+
"""
|
|
152
|
+
Return a versioned cache path so upgrades download matching binaries.
|
|
153
|
+
"""
|
|
154
|
+
cache_dir = Path.home() / ".cache" / "gitfluff" / version
|
|
155
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
suffix = ".exe" if platform.system().lower() == "windows" else ""
|
|
157
|
+
return cache_dir / f"gitfluff{suffix}"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def ensure_binary() -> str:
|
|
161
|
+
from . import __version__
|
|
162
|
+
|
|
163
|
+
override = os.getenv("GITFLUFF_BINARY")
|
|
164
|
+
if override:
|
|
165
|
+
return override
|
|
166
|
+
|
|
167
|
+
binary_path = _cache_path(__version__)
|
|
168
|
+
if binary_path.exists() and os.access(binary_path, os.X_OK):
|
|
169
|
+
return str(binary_path)
|
|
170
|
+
|
|
171
|
+
url, ext, archive_name = _asset(__version__)
|
|
172
|
+
print(f"Downloading gitfluff binary v{__version__}...", file=sys.stderr)
|
|
173
|
+
|
|
174
|
+
with tempfile.TemporaryDirectory(dir=binary_path.parent) as tmpdir:
|
|
175
|
+
archive_path = Path(tmpdir) / f"gitfluff.{ext}"
|
|
176
|
+
staged_binary = Path(tmpdir) / binary_path.name
|
|
177
|
+
_download(url, archive_path)
|
|
178
|
+
_verify_checksum(archive_path, archive_name, __version__)
|
|
179
|
+
_extract(archive_path, ext, staged_binary)
|
|
180
|
+
|
|
181
|
+
if platform.system().lower() != "windows":
|
|
182
|
+
staged_binary.chmod(0o755)
|
|
183
|
+
staged_binary.replace(binary_path)
|
|
184
|
+
|
|
185
|
+
print("Binary downloaded successfully!", file=sys.stderr)
|
|
186
|
+
return str(binary_path)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def run_gitfluff(args: list[str]) -> int:
|
|
190
|
+
binary = ensure_binary()
|
|
191
|
+
status = subprocess.call([binary, *args])
|
|
192
|
+
return 128 - status if status < 0 else status
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: gitfluff
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Commit message linter and formatter
|
|
5
5
|
Author-email: Na'aman Hirschfeld <nhirschfeld@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -25,9 +25,9 @@ Requires-Dist: certifi>=2024.7.4
|
|
|
25
25
|
|
|
26
26
|
# gitfluff
|
|
27
27
|
|
|
28
|
-
Commit message linter with presets, custom formats, and cleanup automation. Fully compliant with [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Skips linting while Git is creating a merge commit.
|
|
28
|
+
Commit message linter with presets, custom formats, and cleanup automation. Fully compliant with [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Skips linting the repository’s `COMMIT_EDITMSG` while Git is creating a merge commit.
|
|
29
29
|
|
|
30
|
-
This Python package distributes prebuilt `gitfluff` binaries. On first use, the correct release binary for your platform is downloaded and cached under `~/.cache/gitfluff`.
|
|
30
|
+
This Python package distributes prebuilt `gitfluff` binaries. On first use, the correct release binary for your platform is downloaded and cached under `~/.cache/gitfluff`. The binary's SHA-256 checksum is verified against the release's published checksums manifest before extraction.
|
|
31
31
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
|
@@ -83,6 +83,8 @@ gitfluff hook install commit-msg
|
|
|
83
83
|
gitfluff hook install commit-msg --write
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
Hook installation respects `core.hooksPath` (including relative paths) and works correctly in linked git worktrees.
|
|
87
|
+
|
|
86
88
|
### pre-commit Framework
|
|
87
89
|
|
|
88
90
|
**Add to `.pre-commit-config.yaml`:**
|
|
@@ -94,7 +96,7 @@ default_install_hook_types:
|
|
|
94
96
|
|
|
95
97
|
repos:
|
|
96
98
|
- repo: https://github.com/Goldziher/gitfluff
|
|
97
|
-
rev: v0.
|
|
99
|
+
rev: v0.9.1
|
|
98
100
|
hooks:
|
|
99
101
|
- id: gitfluff-lint
|
|
100
102
|
stages: [commit-msg]
|
|
@@ -122,7 +124,7 @@ commit-msg:
|
|
|
122
124
|
**Install hooks:**
|
|
123
125
|
|
|
124
126
|
```bash
|
|
125
|
-
|
|
127
|
+
pnpm dlx lefthook install
|
|
126
128
|
```
|
|
127
129
|
|
|
128
130
|
## Optional configuration
|
|
@@ -135,15 +137,16 @@ write = true
|
|
|
135
137
|
|
|
136
138
|
[rules]
|
|
137
139
|
no_emojis = true
|
|
140
|
+
ai_cleanup = true # set to false to disable AI attribution cleanup
|
|
138
141
|
title_suffix = "\\(JIRA-[0-9]+\\)"
|
|
139
142
|
exit_nonzero_on_rewrite = true
|
|
140
143
|
```
|
|
141
144
|
|
|
142
|
-
Any setting can be left out; omit the file entirely to keep defaults.
|
|
145
|
+
Any setting can be left out; omit the file entirely to keep defaults. Unknown config keys now trigger an error rather than being silently ignored.
|
|
143
146
|
|
|
144
147
|
## Advanced usage
|
|
145
148
|
|
|
146
|
-
- Override rules per-invocation using CLI flags (e.g. `--preset`, `--msg-pattern`, `--cleanup-pattern`, `--exclude`, `--cleanup`, `--single-line`, `--no-emojis`, `--ascii-only`, `--title-prefix`, `--title-suffix`).
|
|
149
|
+
- Override rules per-invocation using CLI flags (e.g. `--preset`, `--msg-pattern`, `--cleanup-pattern`, `--exclude`, `--cleanup`, `--single-line`, `--no-emojis`, `--ascii-only`, `--title-prefix`, `--title-suffix`, `--no-ai-cleanup`).
|
|
147
150
|
- Set `GITFLUFF_BINARY` to point at a custom build when testing unpublished versions.
|
|
148
151
|
- Clear the cache (`rm ~/.cache/gitfluff/gitfluff*`) to force a fresh download.
|
|
149
152
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gitfluff"
|
|
7
|
+
version = "0.9.1"
|
|
8
|
+
description = "Commit message linter and formatter"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com" }]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.8",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Build Tools",
|
|
23
|
+
"Environment :: Console",
|
|
24
|
+
]
|
|
25
|
+
keywords = ["git", "commit", "lint", "conventional", "format"]
|
|
26
|
+
requires-python = ">=3.8"
|
|
27
|
+
dependencies = ["certifi>=2024.7.4"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/Goldziher/gitfluff"
|
|
31
|
+
Repository = "https://github.com/Goldziher/gitfluff.git"
|
|
32
|
+
Issues = "https://github.com/Goldziher/gitfluff/issues"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
gitfluff = "gitfluff.cli:main"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
packages = ["gitfluff"]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
5
|
+
import tarfile
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
import zipfile
|
|
9
|
+
from contextlib import ExitStack
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from unittest.mock import patch
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
14
|
+
|
|
15
|
+
from gitfluff import downloader
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DownloaderTests(unittest.TestCase):
|
|
19
|
+
def test_should_ignore_suffix_lookalikes_when_extracting_binary(self) -> None:
|
|
20
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
21
|
+
archive = Path(directory) / "release.tar.gz"
|
|
22
|
+
destination = Path(directory) / "gitfluff"
|
|
23
|
+
with tarfile.open(archive, "w:gz") as release:
|
|
24
|
+
for name, content in [("notgitfluff", b"wrong"), ("bin/gitfluff", b"correct")]:
|
|
25
|
+
member = tarfile.TarInfo(name)
|
|
26
|
+
member.size = len(content)
|
|
27
|
+
release.addfile(member, io.BytesIO(content))
|
|
28
|
+
downloader._extract(archive, "tar.gz", destination)
|
|
29
|
+
self.assertEqual(destination.read_bytes(), b"correct")
|
|
30
|
+
|
|
31
|
+
def test_should_extract_exact_binary_name_from_zip(self) -> None:
|
|
32
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
33
|
+
archive = Path(directory) / "release.zip"
|
|
34
|
+
destination = Path(directory) / "gitfluff.exe"
|
|
35
|
+
with zipfile.ZipFile(archive, "w") as release:
|
|
36
|
+
release.writestr("notgitfluff.exe", b"wrong")
|
|
37
|
+
release.writestr("bin/gitfluff.exe", b"correct")
|
|
38
|
+
downloader._extract(archive, "zip", destination)
|
|
39
|
+
self.assertEqual(destination.read_bytes(), b"correct")
|
|
40
|
+
|
|
41
|
+
def test_should_reject_directory_when_extracting_binary(self) -> None:
|
|
42
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
43
|
+
archive = Path(directory) / "release.tar.gz"
|
|
44
|
+
destination = Path(directory) / "gitfluff"
|
|
45
|
+
with tarfile.open(archive, "w:gz") as release:
|
|
46
|
+
member = tarfile.TarInfo("gitfluff")
|
|
47
|
+
member.type = tarfile.DIRTYPE
|
|
48
|
+
release.addfile(member)
|
|
49
|
+
with self.assertRaisesRegex(RuntimeError, "Binary not found"):
|
|
50
|
+
downloader._extract(archive, "tar.gz", destination)
|
|
51
|
+
|
|
52
|
+
def test_should_leave_no_cached_binary_when_extraction_fails(self) -> None:
|
|
53
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
54
|
+
destination = Path(directory) / "gitfluff"
|
|
55
|
+
|
|
56
|
+
def fail_extraction(_archive: Path, _extension: str, output: Path) -> None:
|
|
57
|
+
output.write_bytes(b"partial")
|
|
58
|
+
raise OSError("interrupted extraction")
|
|
59
|
+
|
|
60
|
+
with ExitStack() as stack:
|
|
61
|
+
stack.enter_context(patch.dict(os.environ, {"GITFLUFF_BINARY": ""}))
|
|
62
|
+
stack.enter_context(patch.object(downloader, "_cache_path", return_value=destination))
|
|
63
|
+
stack.enter_context(patch.object(downloader, "_download"))
|
|
64
|
+
stack.enter_context(patch.object(downloader, "_verify_checksum"))
|
|
65
|
+
stack.enter_context(patch.object(downloader, "_extract", side_effect=fail_extraction))
|
|
66
|
+
stack.enter_context(self.assertRaisesRegex(OSError, "interrupted extraction"))
|
|
67
|
+
downloader.ensure_binary()
|
|
68
|
+
self.assertEqual(destination.exists(), False)
|
|
69
|
+
|
|
70
|
+
@unittest.skipIf(os.name == "nt", "POSIX signals required")
|
|
71
|
+
def test_should_return_failure_status_when_child_is_terminated(self) -> None:
|
|
72
|
+
with patch.dict(os.environ, {"GITFLUFF_BINARY": sys.executable}):
|
|
73
|
+
status = downloader.run_gitfluff(["-c", "import os, signal; os.kill(os.getpid(), signal.SIGTERM)"])
|
|
74
|
+
self.assertEqual(status, 128 + signal.SIGTERM)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
unittest.main()
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""Fetch gitfluff release binaries for the Python wrapper."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import platform
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
import tempfile
|
|
10
|
-
import tarfile
|
|
11
|
-
import zipfile
|
|
12
|
-
import ssl
|
|
13
|
-
|
|
14
|
-
import certifi
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from urllib.error import URLError
|
|
17
|
-
from urllib.request import Request, urlopen
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _platform_triple() -> str:
|
|
21
|
-
system = platform.system().lower()
|
|
22
|
-
machine = platform.machine().lower()
|
|
23
|
-
|
|
24
|
-
if system == "windows":
|
|
25
|
-
if machine in {"amd64", "x86_64"}:
|
|
26
|
-
return "x86_64-pc-windows-gnu"
|
|
27
|
-
if machine in {"x86", "i386", "i686"}:
|
|
28
|
-
raise RuntimeError("32-bit Windows is not supported")
|
|
29
|
-
elif system == "linux":
|
|
30
|
-
if machine in {"amd64", "x86_64"}:
|
|
31
|
-
return "x86_64-unknown-linux-gnu"
|
|
32
|
-
if machine in {"aarch64", "arm64"}:
|
|
33
|
-
return "aarch64-unknown-linux-gnu"
|
|
34
|
-
elif system == "darwin":
|
|
35
|
-
if machine in {"amd64", "x86_64"}:
|
|
36
|
-
return "x86_64-apple-darwin"
|
|
37
|
-
if machine in {"aarch64", "arm64"}:
|
|
38
|
-
return "aarch64-apple-darwin"
|
|
39
|
-
|
|
40
|
-
raise RuntimeError(f"Unsupported platform: {system} {machine}")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _python_version_to_tag(version: str) -> str:
|
|
44
|
-
if "rc" in version:
|
|
45
|
-
core, suffix = version.split("rc")
|
|
46
|
-
return f"{core}-rc.{suffix}"
|
|
47
|
-
return version
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _asset(version: str) -> tuple[str, str]:
|
|
51
|
-
tag = _python_version_to_tag(version)
|
|
52
|
-
triple = _platform_triple()
|
|
53
|
-
ext = "zip" if "windows" in triple else "tar.gz"
|
|
54
|
-
url = (
|
|
55
|
-
f"https://github.com/Goldziher/gitfluff/releases/download/"
|
|
56
|
-
f"v{tag}/gitfluff-{triple}.{ext}"
|
|
57
|
-
)
|
|
58
|
-
return url, ext
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _download(url: str, destination: Path) -> None:
|
|
62
|
-
request = Request(url, headers={"User-Agent": "gitfluff-python-wrapper"})
|
|
63
|
-
context = ssl.create_default_context(cafile=certifi.where())
|
|
64
|
-
try:
|
|
65
|
-
with urlopen(request, timeout=30, context=context) as response:
|
|
66
|
-
if response.status != 200:
|
|
67
|
-
raise RuntimeError(f"HTTP {response.status}: {response.reason}")
|
|
68
|
-
destination.write_bytes(response.read())
|
|
69
|
-
except URLError as exc:
|
|
70
|
-
raise RuntimeError(f"Failed to download binary: {exc}") from exc
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _extract(archive: Path, ext: str, destination: Path) -> None:
|
|
74
|
-
if ext == "zip":
|
|
75
|
-
with zipfile.ZipFile(archive) as zf:
|
|
76
|
-
for name in zf.namelist():
|
|
77
|
-
if name.endswith("gitfluff") or name.endswith("gitfluff.exe"):
|
|
78
|
-
with zf.open(name) as src, destination.open("wb") as dst:
|
|
79
|
-
dst.write(src.read())
|
|
80
|
-
return
|
|
81
|
-
else:
|
|
82
|
-
with tarfile.open(archive, "r:gz") as tar:
|
|
83
|
-
for member in tar.getmembers():
|
|
84
|
-
if member.name.endswith("gitfluff") or member.name.endswith(
|
|
85
|
-
"gitfluff.exe"
|
|
86
|
-
):
|
|
87
|
-
with tar.extractfile(member) as src, destination.open("wb") as dst:
|
|
88
|
-
dst.write(src.read())
|
|
89
|
-
return
|
|
90
|
-
raise RuntimeError("Binary not found in downloaded archive")
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _cache_path(version: str) -> Path:
|
|
94
|
-
"""
|
|
95
|
-
Return a versioned cache path so upgrades download matching binaries.
|
|
96
|
-
"""
|
|
97
|
-
cache_dir = Path.home() / ".cache" / "gitfluff" / version
|
|
98
|
-
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
-
suffix = ".exe" if platform.system().lower() == "windows" else ""
|
|
100
|
-
return cache_dir / f"gitfluff{suffix}"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def ensure_binary() -> str:
|
|
104
|
-
from . import __version__
|
|
105
|
-
|
|
106
|
-
# Allow override via environment for power users/tests
|
|
107
|
-
override = os.getenv("GITFLUFF_BINARY")
|
|
108
|
-
if override:
|
|
109
|
-
return override
|
|
110
|
-
|
|
111
|
-
binary_path = _cache_path(__version__)
|
|
112
|
-
if binary_path.exists() and os.access(binary_path, os.X_OK):
|
|
113
|
-
return str(binary_path)
|
|
114
|
-
|
|
115
|
-
url, ext = _asset(__version__)
|
|
116
|
-
print(f"Downloading gitfluff binary v{__version__}...", file=sys.stderr)
|
|
117
|
-
|
|
118
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
119
|
-
archive_path = Path(tmpdir) / "gitfluff.tar.gz"
|
|
120
|
-
_download(url, archive_path)
|
|
121
|
-
_extract(archive_path, ext, binary_path)
|
|
122
|
-
|
|
123
|
-
if platform.system().lower() != "windows":
|
|
124
|
-
binary_path.chmod(0o755)
|
|
125
|
-
|
|
126
|
-
print("Binary downloaded successfully!", file=sys.stderr)
|
|
127
|
-
return str(binary_path)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def run_gitfluff(args: list[str]) -> int:
|
|
131
|
-
binary = ensure_binary()
|
|
132
|
-
return subprocess.call([binary, *args])
|
gitfluff-0.8.0/pyproject.toml
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "gitfluff"
|
|
7
|
-
version = "0.8.0"
|
|
8
|
-
description = "Commit message linter and formatter"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
license = {text = "MIT"}
|
|
11
|
-
authors = [
|
|
12
|
-
{name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com"}
|
|
13
|
-
]
|
|
14
|
-
classifiers = [
|
|
15
|
-
"Development Status :: 3 - Alpha",
|
|
16
|
-
"Intended Audience :: Developers",
|
|
17
|
-
"License :: OSI Approved :: MIT License",
|
|
18
|
-
"Programming Language :: Python :: 3",
|
|
19
|
-
"Programming Language :: Python :: 3.8",
|
|
20
|
-
"Programming Language :: Python :: 3.9",
|
|
21
|
-
"Programming Language :: Python :: 3.10",
|
|
22
|
-
"Programming Language :: Python :: 3.11",
|
|
23
|
-
"Programming Language :: Python :: 3.12",
|
|
24
|
-
"Topic :: Software Development :: Build Tools",
|
|
25
|
-
"Environment :: Console"
|
|
26
|
-
]
|
|
27
|
-
keywords = ["git", "commit", "lint", "conventional", "format"]
|
|
28
|
-
requires-python = ">=3.8"
|
|
29
|
-
dependencies = [
|
|
30
|
-
"certifi>=2024.7.4"
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
[project.urls]
|
|
34
|
-
Homepage = "https://github.com/Goldziher/gitfluff"
|
|
35
|
-
Repository = "https://github.com/Goldziher/gitfluff.git"
|
|
36
|
-
Issues = "https://github.com/Goldziher/gitfluff/issues"
|
|
37
|
-
|
|
38
|
-
[project.scripts]
|
|
39
|
-
gitfluff = "gitfluff.cli:main"
|
|
40
|
-
|
|
41
|
-
[tool.setuptools]
|
|
42
|
-
packages = ["gitfluff"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|