gitmole 0.3.0__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.
- gitmole-0.3.0/LICENSE +21 -0
- gitmole-0.3.0/PKG-INFO +469 -0
- gitmole-0.3.0/README.md +449 -0
- gitmole-0.3.0/gitmole/__init__.py +3 -0
- gitmole-0.3.0/gitmole/__main__.py +6 -0
- gitmole-0.3.0/gitmole/backtest.py +82 -0
- gitmole-0.3.0/gitmole/banner.py +107 -0
- gitmole-0.3.0/gitmole/blame.py +135 -0
- gitmole-0.3.0/gitmole/cli.py +439 -0
- gitmole-0.3.0/gitmole/filetypes.py +78 -0
- gitmole-0.3.0/gitmole/findings.py +405 -0
- gitmole-0.3.0/gitmole/functions.py +108 -0
- gitmole-0.3.0/gitmole/hotspots.py +18 -0
- gitmole-0.3.0/gitmole/identity.py +66 -0
- gitmole-0.3.0/gitmole/knowledge.py +55 -0
- gitmole-0.3.0/gitmole/leaks.py +124 -0
- gitmole-0.3.0/gitmole/load.py +253 -0
- gitmole-0.3.0/gitmole/loss.py +44 -0
- gitmole-0.3.0/gitmole/maat.py +299 -0
- gitmole-0.3.0/gitmole/render.py +736 -0
- gitmole-0.3.0/gitmole/run.py +407 -0
- gitmole-0.3.0/gitmole/textfmt.py +91 -0
- gitmole-0.3.0/gitmole/trend.py +154 -0
- gitmole-0.3.0/gitmole/watch.py +172 -0
- gitmole-0.3.0/gitmole.egg-info/PKG-INFO +469 -0
- gitmole-0.3.0/gitmole.egg-info/SOURCES.txt +51 -0
- gitmole-0.3.0/gitmole.egg-info/dependency_links.txt +1 -0
- gitmole-0.3.0/gitmole.egg-info/entry_points.txt +2 -0
- gitmole-0.3.0/gitmole.egg-info/requires.txt +5 -0
- gitmole-0.3.0/gitmole.egg-info/top_level.txt +1 -0
- gitmole-0.3.0/pyproject.toml +34 -0
- gitmole-0.3.0/setup.cfg +4 -0
- gitmole-0.3.0/tests/test_backtest.py +122 -0
- gitmole-0.3.0/tests/test_banner.py +126 -0
- gitmole-0.3.0/tests/test_blame.py +108 -0
- gitmole-0.3.0/tests/test_cli.py +658 -0
- gitmole-0.3.0/tests/test_filetypes.py +96 -0
- gitmole-0.3.0/tests/test_findings.py +580 -0
- gitmole-0.3.0/tests/test_functions.py +122 -0
- gitmole-0.3.0/tests/test_golden.py +100 -0
- gitmole-0.3.0/tests/test_hotspots.py +22 -0
- gitmole-0.3.0/tests/test_identity.py +75 -0
- gitmole-0.3.0/tests/test_knowledge.py +64 -0
- gitmole-0.3.0/tests/test_leaks.py +153 -0
- gitmole-0.3.0/tests/test_load.py +338 -0
- gitmole-0.3.0/tests/test_loss.py +70 -0
- gitmole-0.3.0/tests/test_maat.py +352 -0
- gitmole-0.3.0/tests/test_packaging.py +27 -0
- gitmole-0.3.0/tests/test_render.py +913 -0
- gitmole-0.3.0/tests/test_run.py +585 -0
- gitmole-0.3.0/tests/test_textfmt.py +79 -0
- gitmole-0.3.0/tests/test_trend.py +127 -0
- gitmole-0.3.0/tests/test_watch.py +166 -0
gitmole-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 antvinni
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gitmole-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitmole
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Offline git repository analysis with a terminal report: hotspots, coupling, ownership, code age, secrets, repo health.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/antvinni/gitmole
|
|
7
|
+
Keywords: git,analysis,hotspots,code-age,repository,metrics
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: rich>=13
|
|
16
|
+
Requires-Dist: lizard>=1.24
|
|
17
|
+
Provides-Extra: plots
|
|
18
|
+
Requires-Dist: git-of-theseus; extra == "plots"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
<img src="https://raw.githubusercontent.com/antvinni/gitmole/main/docs/banner.svg" width="912" alt="gitmole">
|
|
22
|
+
|
|
23
|
+
# gitmole
|
|
24
|
+
|
|
25
|
+
> A toolkit for digging into any cloned git repository: who works on it,
|
|
26
|
+
> where the risk is, how old the code is, whether the repo itself is healthy,
|
|
27
|
+
> and whether anything sensitive was ever committed.
|
|
28
|
+
>
|
|
29
|
+
> Any stack. Free. Offline. No token. No AI. Light.
|
|
30
|
+
|
|
31
|
+
## Principles
|
|
32
|
+
|
|
33
|
+
- **Free.** MIT licence, no paid tier, no account, nothing to sign up for.
|
|
34
|
+
The tools it runs are open source too.
|
|
35
|
+
- **Any stack.** It reads what every repository has: the git log, git blame
|
|
36
|
+
and the files themselves. Python, Vue, Terraform or a Makefile get the same
|
|
37
|
+
treatment; there is no language it has to support first.
|
|
38
|
+
- **Offline.** Everything runs against a clone on your machine. Nothing is
|
|
39
|
+
uploaded, nothing is fetched, nothing phones home. Pull the network cable
|
|
40
|
+
and every number comes out the same.
|
|
41
|
+
- **No token.** A local clone needs no GitHub token, no API access, no
|
|
42
|
+
credentials of any kind. The optional `owner/repo` shortcut clones with the
|
|
43
|
+
`gh` login you already have; that is the one network call, and you ask for
|
|
44
|
+
it.
|
|
45
|
+
- **No AI.** Every finding is a plain rule over counts you can recompute by
|
|
46
|
+
hand: revisions, lines, dates, names. No model, no prompt, no guessing. The
|
|
47
|
+
same clone gives the same report every time, and the report says what each
|
|
48
|
+
number is.
|
|
49
|
+
- **Light.** A run on a 4,400-commit repository takes under thirty seconds.
|
|
50
|
+
The package is a few thousand lines of Python plus two libraries, and
|
|
51
|
+
nothing new gets in unless it changes what you do next.
|
|
52
|
+
|
|
53
|
+
## The tool set
|
|
54
|
+
|
|
55
|
+
One tool per question; together they cover what a single command can tell
|
|
56
|
+
you about a clone.
|
|
57
|
+
|
|
58
|
+
| Question | Tool | Install |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| What is this repo, at a glance; who commits, when, how much churn | gitmole itself, from the git log | built in |
|
|
61
|
+
| How big is the codebase, per language | [scc](https://github.com/boyter/scc) | brew |
|
|
62
|
+
| Is the repo itself healthy (huge blobs, deep trees) | [git-sizer](https://github.com/github/git-sizer) | brew |
|
|
63
|
+
| Where is the risk: hotspots, coupling, ownership | gitmole's own change analysis over `git log --numstat` | built in |
|
|
64
|
+
| How old is the surviving code, per year and author | gitmole's own blame pass (one `git blame` per file at HEAD) | built in |
|
|
65
|
+
| Code-age and survival plots over time | [git-of-theseus](https://github.com/erikbern/git-of-theseus) | pip, opt-in with `--plots` |
|
|
66
|
+
| Per-function complexity, length, parameters; duplicated blocks with `--duplicates` | [lizard](https://github.com/terryyin/lizard) | pip, installed with gitmole; tracked code files only |
|
|
67
|
+
| Have secrets ever been committed | [gitleaks](https://github.com/gitleaks/gitleaks) | brew |
|
|
68
|
+
|
|
69
|
+
Why these and not others: [docs/tools.md](https://github.com/antvinni/gitmole/blob/main/docs/tools.md).
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
gitmole needs git, Python 3.9 or newer, and three tools on your PATH:
|
|
74
|
+
[scc](https://github.com/boyter/scc) for size,
|
|
75
|
+
[git-sizer](https://github.com/github/git-sizer) for repository health and
|
|
76
|
+
[gitleaks](https://github.com/gitleaks/gitleaks) for secrets. gitmole itself
|
|
77
|
+
is a Python package; install it with [pipx](https://pipx.pypa.io) so it gets
|
|
78
|
+
its own environment and a `gitmole` command.
|
|
79
|
+
|
|
80
|
+
### macOS
|
|
81
|
+
|
|
82
|
+
Homebrew installs gitmole and the three tools in one go. The tap lives in
|
|
83
|
+
this repository, so the first command names it by URL; the second marks it
|
|
84
|
+
trusted, which Homebrew 7 requires before it will install from a third-party
|
|
85
|
+
tap; after that the short name works everywhere, `brew upgrade` included.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
brew tap antvinni/gitmole https://github.com/antvinni/gitmole
|
|
89
|
+
brew trust antvinni/gitmole
|
|
90
|
+
brew install gitmole
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Without Homebrew, install the three tools yourself and use pipx:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pipx ensurepath # once; then open a new shell
|
|
97
|
+
pipx install gitmole
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Linux
|
|
101
|
+
|
|
102
|
+
With Homebrew on Linux the same three commands work unchanged; all three tools
|
|
103
|
+
are bottled there. Without Homebrew, take the tools from your package manager
|
|
104
|
+
where it has them and from the projects' release pages otherwise; each ships
|
|
105
|
+
a static binary, so dropping it into `~/.local/bin` is enough.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Debian and Ubuntu: git-sizer and pipx are packaged
|
|
109
|
+
sudo apt install git git-sizer pipx
|
|
110
|
+
pipx ensurepath # once; then open a new shell
|
|
111
|
+
|
|
112
|
+
# scc and gitleaks: one static binary each, from their release pages
|
|
113
|
+
# https://github.com/boyter/scc/releases (the Linux x86_64 or arm64 archive)
|
|
114
|
+
# https://github.com/gitleaks/gitleaks/releases (the linux x64 or arm64 archive)
|
|
115
|
+
# unpack and move the binary into ~/.local/bin, then:
|
|
116
|
+
chmod +x ~/.local/bin/scc ~/.local/bin/gitleaks
|
|
117
|
+
|
|
118
|
+
pipx install gitmole
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
On a distribution without a `pipx` package, `python3 -m pip install --user
|
|
122
|
+
pipx` installs it. Some distributions package scc or gitleaks as well; if
|
|
123
|
+
yours does, prefer that to a downloaded binary.
|
|
124
|
+
|
|
125
|
+
### Check
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
scc --version && git-sizer --version && gitleaks version && gitmole --version
|
|
129
|
+
gitmole . # a report of the clone you are in
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`gitmole` reports any tool it cannot find on the first run.
|
|
133
|
+
|
|
134
|
+
### Other ways to install
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pipx install 'gitmole[plots]' # adds git-of-theseus for --plots
|
|
138
|
+
pipx install git+https://github.com/antvinni/gitmole # main, unreleased
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`python -m gitmole` works too. From a checkout, `pip install -e .` in a
|
|
142
|
+
virtual environment gives an editable install. Use pip 22 or newer: the pip
|
|
143
|
+
that ships with macOS's system Python is older and silently builds an empty
|
|
144
|
+
package called UNKNOWN from modern project files. pipx brings its own current
|
|
145
|
+
pip, and `python3 -m pip install -U pip` fixes a plain venv.
|
|
146
|
+
|
|
147
|
+
## Run
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
gitmole . # the clone you are in
|
|
151
|
+
gitmole /path/to/clone # any local clone
|
|
152
|
+
gitmole owner/repo # clones with gh into a temp dir first
|
|
153
|
+
gitmole https://github.com/o/r # same, from a URL
|
|
154
|
+
gitmole 'owner/*' # every non-archived repo of a user or org
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The last form is portfolio mode: each repository is cloned and analysed in
|
|
158
|
+
turn into `analysis-<owner>/<repo>/`, then one table summarises them all with
|
|
159
|
+
commits, people, the top author's share of surviving code, secrets found,
|
|
160
|
+
size, and the worst finding per repo. `--markdown` and `--json` write a
|
|
161
|
+
portfolio document with every repo's findings; `--fail-on` looks across all
|
|
162
|
+
of them.
|
|
163
|
+
|
|
164
|
+
Options: `--full` for every column and every row (the default report keeps the
|
|
165
|
+
columns you read, caps each table, and elides long paths in the middle),
|
|
166
|
+
`--out DIR` to choose the output directory, `--no-run DIR` to re-render the
|
|
167
|
+
report from an earlier run, `--since 2y` (or `18m`, `90d`, a date) to bound
|
|
168
|
+
the history by author date so people, activity, timeline, hotspots and
|
|
169
|
+
coupling describe the current team rather than the founders (file ages and
|
|
170
|
+
code age always cover the whole history; identity aliases are still merged
|
|
171
|
+
over all of it; an empty window is an error), `--plots` to also draw the
|
|
172
|
+
git-of-theseus code-age and survival charts, `--file-types py,sql` to choose
|
|
173
|
+
which files count as code (or `all`; `--list-file-types` shows what is in the
|
|
174
|
+
tree and what the default includes), `--duplicates` to also look for
|
|
175
|
+
duplicated blocks, `--workers N` to change how many tools run at once,
|
|
176
|
+
`--timeout S` to cap any single tool (default 15 minutes), `--gone MONTHS` to
|
|
177
|
+
change how long without a commit counts as gone (default 12, measured before
|
|
178
|
+
the last commit), `--risk BASE` to score the files changed since BASE (the
|
|
179
|
+
merge base with HEAD) with the watch list's score, in one extra section with
|
|
180
|
+
a total; it works with `--no-run` and the JSON carries the number for CI.
|
|
181
|
+
Ctrl-C kills every running step, including their child processes, and exits
|
|
182
|
+
with code 130.
|
|
183
|
+
|
|
184
|
+
All tools run concurrently, so a run takes about as long as the slowest tool.
|
|
185
|
+
Tool stderr goes to `run.log` in the output directory, not the terminal.
|
|
186
|
+
|
|
187
|
+
### Exports and CI
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
gitmole . --markdown report.md # the same report as a Markdown document
|
|
191
|
+
gitmole . --json report.json # every table, the watch list and the findings, machine-readable
|
|
192
|
+
gitmole . --markdown - | pbcopy # - means stdout; banner and progress go to stderr
|
|
193
|
+
gitmole . --fail-on warning # exit 3 if any finding is a warning or worse
|
|
194
|
+
gitmole . --risk main --risk-threshold 5 # exit 3 if the changed files are too risky
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`--fail-on` accepts `critical`, `warning`, or `info`. A CI job that runs
|
|
198
|
+
`gitmole . --fail-on critical --markdown - >> "$GITHUB_STEP_SUMMARY"` blocks
|
|
199
|
+
on secrets in source files and still posts the report. Secrets found only in
|
|
200
|
+
test files are a warning, so gate on `warning` to block on those too. Both exports also work with
|
|
201
|
+
`--no-run` against an earlier output directory. `--risk-threshold` needs `--risk`: it exits 3 when
|
|
202
|
+
the files changed since main add up to more than 5 on the watch-list scale; the total prints in the
|
|
203
|
+
Change risk caption.
|
|
204
|
+
|
|
205
|
+
### Big repositories
|
|
206
|
+
|
|
207
|
+
Blame and lizard's duplicate finder are the two costs that scale with repo
|
|
208
|
+
size. gitmole keeps them in check:
|
|
209
|
+
|
|
210
|
+
- the code-age table comes from one `git blame` per tracked code file at
|
|
211
|
+
HEAD, run on all but two CPU cores at low priority so the machine stays
|
|
212
|
+
usable. That is all the table needs;
|
|
213
|
+
- blame cost depends on file size and history depth, not file count, so
|
|
214
|
+
gitmole times a sample of 25 blames first and projects the whole pass. If
|
|
215
|
+
the projection exceeds `--time-budget` (default 60 seconds) the pass is
|
|
216
|
+
skipped with a message, and the report shows net lines added per year from
|
|
217
|
+
the change log instead, labelled as an approximation;
|
|
218
|
+
- the plots need history, so `--plots` runs git-of-theseus with monthly
|
|
219
|
+
sampling (tracked files × samples blames) on top, skipped above
|
|
220
|
+
`--budget` (default 50,000 blames);
|
|
221
|
+
- `--deep` forces both regardless of the budgets;
|
|
222
|
+
- the duplicate finder is off by default. It keeps a hash node per token,
|
|
223
|
+
so on a repo of a few thousand files it runs for minutes at one or two
|
|
224
|
+
gigabytes per worker, which is why `--duplicates` also caps that step at
|
|
225
|
+
two workers. Function metrics without it take a second or two;
|
|
226
|
+
- `--ignore-data` excludes data-like files (csv, json, lock files, minified
|
|
227
|
+
and vendored assets) from blame and from the function metrics, and
|
|
228
|
+
`--ignore GLOB` adds your own patterns, repeatable. Both shrink the blame
|
|
229
|
+
count a lot on repos full of exports and fixtures.
|
|
230
|
+
|
|
231
|
+
Two steps read history rather than the working tree, and both are bounded.
|
|
232
|
+
The trend behind the hotspots' `trend` column runs scc over the ten top
|
|
233
|
+
hotspots at up to twelve sampled commits, one run per sample, not one per
|
|
234
|
+
file. The backtest behind the watch list's caption is a second change
|
|
235
|
+
analysis over the same log with the window closed six months before the
|
|
236
|
+
last commit, plus one checkout of the tree as it was then, exported under
|
|
237
|
+
the output directory and removed again when the step ends.
|
|
238
|
+
|
|
239
|
+
A tool that exceeds `--timeout` is killed along with its child processes,
|
|
240
|
+
marked in the report, and the rest of the report still renders.
|
|
241
|
+
|
|
242
|
+
## Example
|
|
243
|
+
|
|
244
|
+
Running `gitmole .` inside this repository:
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
╭─ gitmole ────────────────────────────────────────────────────────────────────────────────────────╮
|
|
248
|
+
│ 115 commits · 2026-09-15 → 2026-09-16 · 1 identity · branch main │
|
|
249
|
+
│ 6,944 lines in 44 files · Python, Ruby │
|
|
250
|
+
│ most commits on Wed at 20:00 · 3% of commits are fixes · 100% of surviving code from 2026 │
|
|
251
|
+
│ 3 warnings, 1 note │
|
|
252
|
+
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
253
|
+
╭─ Findings (4) ───────────────────────────────────────────────────────────────────────────────────╮
|
|
254
|
+
│ ▲ Bus factor of one │
|
|
255
|
+
│ vinni wrote 100% of the code that survives today │
|
|
256
|
+
│ ↳ Pair someone with vinni on gitmole/ and build/ first; they are 100% and 100% theirs. │
|
|
257
|
+
│ ▲ Hotspots getting more complex │
|
|
258
|
+
│ 4 of the 10 top source hotspots grew by 25% or more in a year: gitmole/render.py (+150%), │
|
|
259
|
+
│ gitmole/cli.py (+32%), gitmole/findings.py (+266%), gitmole/run.py (+26%) │
|
|
260
|
+
│ ↳ Split gitmole/render.py before the next change; its complexity grew 150% in a year. │
|
|
261
|
+
│ ▲ Knowledge islands │
|
|
262
|
+
│ 2 area(s) with at least 200 lines were written almost entirely by one person: gitmole/ (vinni │
|
|
263
|
+
│ 100%); build/ (vinni 100%). That is 97% of all lines added │
|
|
264
|
+
│ ↳ Pair someone with vinni on gitmole/ first; it is the largest at 5,458 lines. │
|
|
265
|
+
│ ● Bug magnets │
|
|
266
|
+
│ 1 file(s) were fixed 3+ times in the last six months: gitmole/run.py (3 recent, 3 total) │
|
|
267
|
+
│ ↳ Review gitmole/run.py before the next release; expect the next bug there. │
|
|
268
|
+
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
269
|
+
|
|
270
|
+
◎ Watch list
|
|
271
|
+
file why
|
|
272
|
+
──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
273
|
+
gitmole/render.py changed 38 times · fixed twice in six months · only vinni has touched it ·
|
|
274
|
+
hotspots_section() complexity 23 · changes with gitmole/cli.py (63%) and 3
|
|
275
|
+
others
|
|
276
|
+
gitmole/cli.py changed 38 times · fixed twice in six months · only vinni has touched it ·
|
|
277
|
+
main() complexity 17 · changes with gitmole/render.py (63%) and 1 other
|
|
278
|
+
gitmole/run.py changed 25 times · fixed 3 times in six months · only vinni has touched it ·
|
|
279
|
+
collect_meta() complexity 23 · changes with gitmole/load.py (67%) and 2
|
|
280
|
+
others
|
|
281
|
+
gitmole/findings.py changed 25 times · fixed twice in six months · only vinni has touched it ·
|
|
282
|
+
complexity_growth() complexity 18 · changes with gitmole/load.py (62%) and 1
|
|
283
|
+
other
|
|
284
|
+
gitmole/load.py changed 17 times · fixed twice in six months · only vinni has touched it ·
|
|
285
|
+
parse_git_sizer() complexity 15 · changes with gitmole/run.py (67%) and 2
|
|
286
|
+
others
|
|
287
|
+
ranked by churn × recent fixes × complexity × single ownership
|
|
288
|
+
too little history to backtest
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The full report is in [docs/example.md](https://github.com/antvinni/gitmole/blob/main/docs/example.md).
|
|
292
|
+
|
|
293
|
+
### The terminal report
|
|
294
|
+
|
|
295
|
+
1. **Header**: commits, date span, identities, branch, size, top languages,
|
|
296
|
+
one line for the busiest day and hour, the share of fix commits, the
|
|
297
|
+
share that are reverts when there are any, and the year most surviving
|
|
298
|
+
code was written (or why the blame pass did not run), and a one-line
|
|
299
|
+
tally of the findings.
|
|
300
|
+
2. **Findings**: anything the heuristics flagged, worst first. Findings of
|
|
301
|
+
the same kind are grouped into one entry with a list, and every finding
|
|
302
|
+
ends with a next step that names the file, area or person to start with,
|
|
303
|
+
on its own line under the facts. Currently:
|
|
304
|
+
secrets in history (see below), an unconfigured git identity
|
|
305
|
+
(example.com and the like), one author owning most surviving code,
|
|
306
|
+
git-sizer concerns, one file dominating the churn, bug magnets (source
|
|
307
|
+
files fixed three or more times in the last six months; a warning at
|
|
308
|
+
five), reverts (5% of commits or five of them; a warning at 10%; names
|
|
309
|
+
the file most often backed out), brain methods (functions with
|
|
310
|
+
complexity 15+ and 100+ lines; a warning when one sits in a hotspot),
|
|
311
|
+
hotspots getting more complex (three or more of the top ten hotspots
|
|
312
|
+
grew by a quarter in a year; a warning when the top one did),
|
|
313
|
+
tightly coupled file pairs (a file and its test are expected to change
|
|
314
|
+
together, so those pairs are left out), duplicated blocks of 30+ lines
|
|
315
|
+
(with `--duplicates`), a large share of stale files (files still in the
|
|
316
|
+
tree; deleted paths do not count), knowledge islands: areas of at least
|
|
317
|
+
200 lines written almost entirely by one person (a warning when such
|
|
318
|
+
areas hold most of the code), and knowledge loss (people with no commits
|
|
319
|
+
in the twelve months before the last commit who wrote 10% or more of the
|
|
320
|
+
surviving code; a warning at 30%). An unconfigured identity is only
|
|
321
|
+
flagged when it made at least 1% of the commits.
|
|
322
|
+
|
|
323
|
+
Secrets are grouped by value, so one key copied into ten files is one
|
|
324
|
+
entry with its places counted. A value found in any source file is
|
|
325
|
+
critical. A value found only in test files, such as fixtures and saved
|
|
326
|
+
web pages, is a warning. Version strings and tokens shortened with "..."
|
|
327
|
+
cannot be live secrets, so they are left out and counted on the footer
|
|
328
|
+
line. Nothing is skipped by prefix. To silence a false positive for
|
|
329
|
+
good, copy its fingerprint from `secrets.json` into a `.gitleaksignore`
|
|
330
|
+
at the repository root; gitleaks reads it on the next run.
|
|
331
|
+
|
|
332
|
+
A commit counts as a fix when its subject starts with `fix:`, `hotfix:` or
|
|
333
|
+
`bugfix:` in the conventional style, or mentions fix, bug, hotfix,
|
|
334
|
+
regression or crash. Test files are left out of every finding that names a
|
|
335
|
+
file, area or function: they change with every fix, and owning the tests is
|
|
336
|
+
not the knowledge risk. The default tables leave them out too; `--full`
|
|
337
|
+
shows them.
|
|
338
|
+
3. **Watch list**: the five files where the next bug is most likely, with
|
|
339
|
+
the reasons in words. Every source file still in the tree that changed
|
|
340
|
+
more than once is scored churn × (1 + recent fixes) × (1 + complexity),
|
|
341
|
+
times 1.5 when one person wrote 90% or more of it, each factor scaled to
|
|
342
|
+
the worst file in the repo. Churn is the base because a file nobody
|
|
343
|
+
changes is not where the next bug lands; complexity is scc's per-file
|
|
344
|
+
total, one scale for every file, while the most complex function lizard
|
|
345
|
+
found is named in the reasons. The reasons name the fix count, the sole
|
|
346
|
+
owner, the function and the files it always changes with. Test files are
|
|
347
|
+
left out. Under `--since`, churn and ownership are windowed and the list
|
|
348
|
+
says so. `--full` and the exports show fifteen. With `--risk BASE`, a
|
|
349
|
+
Change risk section follows: every file changed since BASE with its watch
|
|
350
|
+
score as a bar and the reasons, or why it has none (new file, changed
|
|
351
|
+
once, test file, not scored).
|
|
352
|
+
|
|
353
|
+
Under the watch list, one line says how the list would have done:
|
|
354
|
+
gitmole reruns the change analysis as of six months before the last
|
|
355
|
+
commit, with scc on the tree at that time, ranks the watch list from
|
|
356
|
+
that, and counts how many of the files fixed since were on it, next to
|
|
357
|
+
what a random list of the same size, drawn from the files that had
|
|
358
|
+
changed more than once, would score. Repositories with under a year
|
|
359
|
+
of history say `too little history to backtest`.
|
|
360
|
+
4. **Tables**: people (identities merged by name and email similarity on
|
|
361
|
+
top of `.mailmap`, and the caption says whose; bots such as renovate,
|
|
362
|
+
dependabot and GitHub Actions are counted apart in the caption and kept
|
|
363
|
+
out of the timeline), a knowledge map (lines added per area of the tree
|
|
364
|
+
and who wrote them), a timeline of commits per author over the last
|
|
365
|
+
twelve months, hotspots ranked by revisions times lines of code with the
|
|
366
|
+
number of fix commits alongside, change coupling, the most complex
|
|
367
|
+
functions, repo health. Hotspots carry a `trend` column, sampled for the
|
|
368
|
+
top ten hotspots: the change in complexity over the last year from scc on
|
|
369
|
+
the file at sampled commits (`--full` shows the whole series as a
|
|
370
|
+
sparkline). The knowledge map marks owners who have stopped committing
|
|
371
|
+
with `(gone)`, and under `--full` shows the share of each area's lines
|
|
372
|
+
that they wrote. With `--full`: size by language, activity by weekday
|
|
373
|
+
with the busiest hour and the share of commits that are fixes, and
|
|
374
|
+
surviving code by year.
|
|
375
|
+
|
|
376
|
+
Size, hotspots, coupling, ownership, code age and the watch list analyse
|
|
377
|
+
source files: a built-in list of code extensions plus names like Makefile
|
|
378
|
+
and Dockerfile (`--file-types all` counts everything). In the default
|
|
379
|
+
report, the hotspots and complex functions tables hide test files, and
|
|
380
|
+
the change coupling table hides pairs with a test file; the captions
|
|
381
|
+
show how many are hidden, and `--full` shows them. Activity and the
|
|
382
|
+
timeline cover the whole history.
|
|
383
|
+
5. **Footer**: where the files and plots are.
|
|
384
|
+
|
|
385
|
+
The files each run writes, and how to read them: [docs/output.md](https://github.com/antvinni/gitmole/blob/main/docs/output.md).
|
|
386
|
+
|
|
387
|
+
## Development
|
|
388
|
+
|
|
389
|
+
Developer setup: Homebrew for the three tools,
|
|
390
|
+
`brew install scc git-sizer gitleaks`. Then either a virtual environment
|
|
391
|
+
with `pip install -e .`, or the checkout style:
|
|
392
|
+
`python3 -m pip install --user rich lizard` and
|
|
393
|
+
`ln -sfn "$PWD/bin/gitmole" "$(brew --prefix)/bin/gitmole"`, which makes
|
|
394
|
+
the checkout what runs.
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
python3 -m unittest discover -s tests -t .
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`tests/test_golden.py` builds a small synthetic repository, runs the whole
|
|
401
|
+
pipeline with the real tools, and compares the plain-text report against
|
|
402
|
+
`tests/golden/report.txt`. It skips itself when the tools are not installed.
|
|
403
|
+
When a change to the report is intended, regenerate the stored file and
|
|
404
|
+
review the diff:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
UPDATE_GOLDEN=1 python3 -m unittest tests.test_golden
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`GITMOLE_NOW=YYYY-MM-DD` fixes the reference date for file ages, which is
|
|
411
|
+
what keeps that report stable. gitmole validates it, announces it at the
|
|
412
|
+
start of a run, and records it in `meta.json`, so a forgotten export cannot
|
|
413
|
+
silently skew a real report.
|
|
414
|
+
|
|
415
|
+
### Releases
|
|
416
|
+
|
|
417
|
+
Versions are git tags. To release: bump `__version__` in `gitmole/__init__.py`,
|
|
418
|
+
merge, then tag that commit `vX.Y.Z` and push the tag. CI runs the tests, checks
|
|
419
|
+
that the tag matches `__version__`, builds the sdist and wheel, and creates the
|
|
420
|
+
GitHub release with notes generated from the merged pull requests and the
|
|
421
|
+
artefacts attached. The release job then bumps `Formula/gitmole.rb` on main
|
|
422
|
+
to the new release, so `brew upgrade gitmole` follows within minutes; the tag
|
|
423
|
+
also publishes to PyPI. `pipx install gitmole==X.Y.Z` installs a release with
|
|
424
|
+
pipx. Releases are listed at
|
|
425
|
+
https://github.com/antvinni/gitmole/releases.
|
|
426
|
+
|
|
427
|
+
`bin/render-banner` regenerates `docs/banner.svg` from the banner code.
|
|
428
|
+
The code lives in `gitmole/`: `run.py` plans and executes the tools,
|
|
429
|
+
`maat.py` is the standalone change analysis (revisions, coupling, authors,
|
|
430
|
+
age, ownership over the numstat log; the file names still say maat because
|
|
431
|
+
the layout matches what code-maat produced), `blame.py` is the standalone
|
|
432
|
+
code-age pass (its output mimics git-of-theseus so one loader serves both),
|
|
433
|
+
`identity.py` merges author aliases, `load.py` parses the outputs, `findings.py` holds the heuristics,
|
|
434
|
+
and `render.py` draws the report. `bin/gitmole` is a thin launcher.
|
|
435
|
+
|
|
436
|
+
## Safety notes
|
|
437
|
+
|
|
438
|
+
- Everything here is offline except the optional clone step, which uses
|
|
439
|
+
your existing gh auth. None of the tools send data anywhere.
|
|
440
|
+
- Remote targets are cloned into a fresh temp directory. Local clones are
|
|
441
|
+
only read, but the log export and the gitleaks scan touch all branches.
|
|
442
|
+
- Secret values never reach the output directory. gitleaks writes its report
|
|
443
|
+
to gitmole in memory, and gitmole stores a short keyed hash of each value
|
|
444
|
+
in place of the value, the matched text and the commit message. The key is
|
|
445
|
+
random, made for that one report and never saved, so a stored hash cannot
|
|
446
|
+
be checked against a list of common passwords. It only tells you which
|
|
447
|
+
hits in one report share a value.
|
|
448
|
+
- Install from the official repos or Homebrew with pinned versions, not from
|
|
449
|
+
forks.
|
|
450
|
+
|
|
451
|
+
## License
|
|
452
|
+
|
|
453
|
+
gitmole is released under the [MIT License](https://github.com/antvinni/gitmole/blob/main/LICENSE).
|
|
454
|
+
|
|
455
|
+
It does not bundle any of the tools it wraps; gitmole runs them as
|
|
456
|
+
separate processes. Their licences:
|
|
457
|
+
|
|
458
|
+
| Tool | Licence |
|
|
459
|
+
|---|---|
|
|
460
|
+
| scc | MIT |
|
|
461
|
+
| git-sizer | MIT |
|
|
462
|
+
| gitleaks | MIT |
|
|
463
|
+
| rich | MIT |
|
|
464
|
+
| lizard | MIT |
|
|
465
|
+
| git-of-theseus | Apache-2.0 |
|
|
466
|
+
|
|
467
|
+
The change analysis (hotspots, coupling, ownership, age) is gitmole's own
|
|
468
|
+
code, written after the ideas in Adam Tornhill's code-maat but sharing no
|
|
469
|
+
code with it.
|