pretty-git 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +413 -0
- data/bin/pretty-git +7 -0
- data/lib/pretty_git/analytics/activity.rb +82 -0
- data/lib/pretty_git/analytics/authors.rb +64 -0
- data/lib/pretty_git/analytics/files.rb +73 -0
- data/lib/pretty_git/analytics/heatmap.rb +51 -0
- data/lib/pretty_git/analytics/languages.rb +155 -0
- data/lib/pretty_git/analytics/summary.rb +94 -0
- data/lib/pretty_git/app.rb +83 -0
- data/lib/pretty_git/cli.rb +63 -0
- data/lib/pretty_git/cli_helpers.rb +130 -0
- data/lib/pretty_git/filters.rb +43 -0
- data/lib/pretty_git/git/provider.rb +134 -0
- data/lib/pretty_git/render/console_renderer.rb +280 -0
- data/lib/pretty_git/render/csv_renderer.rb +44 -0
- data/lib/pretty_git/render/json_renderer.rb +18 -0
- data/lib/pretty_git/render/languages_section.rb +47 -0
- data/lib/pretty_git/render/markdown_renderer.rb +68 -0
- data/lib/pretty_git/render/terminal_width.rb +38 -0
- data/lib/pretty_git/render/xml_renderer.rb +43 -0
- data/lib/pretty_git/render/yaml_renderer.rb +34 -0
- data/lib/pretty_git/types.rb +14 -0
- data/lib/pretty_git/version.rb +5 -0
- data/lib/pretty_git.rb +7 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9adec0f68c20ace4f919d32959eae6e9bfd79d412ca762f90a5b79320d018891
|
4
|
+
data.tar.gz: 7d7aad306dbd24fd20e54f89f39e3044cfa743c34a3259a06a085e14f03b7309
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 12914fd26d9c307551b19ca822c766927ec375fe6e77e85dc379e35e49080aec9ef0516648902d4b846e5d5d9145e471b88beeac2b88783b3f9b1417dca6401f
|
7
|
+
data.tar.gz: 612c40a60166e42be38f8318f9ab5f2e3e13f3e26cf9b97e580818741d124abc24b9fbd31c3c43c6faad4196f06a0d713684ff1c4d9d6189683f9ccc1e9c44bf
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Mikhail Matskevich
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
# Pretty Git
|
2
|
+
|
3
|
+
[](https://github.com/MikoMikocchi/pretty-git/actions/workflows/ci.yml)
|
4
|
+
[](LICENSE)
|
5
|
+

|
6
|
+
|
7
|
+
<p align="right">
|
8
|
+
<b>English</b> | <a href="./README.ru.md">Русский</a>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p align="center">
|
12
|
+
<img src="docs/images/PrettyGitIcon.png" alt="Pretty Git Logo" width="200">
|
13
|
+
<br>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
Generator of rich reports for a local Git repository: summary, activity, authors, files, heatmap, languages. Output to Console and formats: JSON, CSV, Markdown, YAML, XML.
|
17
|
+
|
18
|
+
— License: MIT.
|
19
|
+
|
20
|
+
## Table of Contents
|
21
|
+
- [Features](#features)
|
22
|
+
- [Requirements](#requirements)
|
23
|
+
- [Installation](#installation)
|
24
|
+
- [Quick Start](#quick-start)
|
25
|
+
- [CLI and Options](#cli-and-options)
|
26
|
+
- [Filters](#filters)
|
27
|
+
- [Output format](#output-format)
|
28
|
+
- [Write to file](#write-to-file)
|
29
|
+
- [Exit codes](#exit-codes)
|
30
|
+
- [Reports and Examples](#reports-and-examples)
|
31
|
+
- [summary — repository summary](#summary--repository-summary)
|
32
|
+
- [activity — activity (day/week/month)](#activity--activity-dayweekmonth)
|
33
|
+
- [authors — by authors](#authors--by-authors)
|
34
|
+
- [files — by files](#files--by-files)
|
35
|
+
- [heatmap — commit heatmap](#heatmap--commit-heatmap)
|
36
|
+
- [languages — languages](#languages--languages)
|
37
|
+
- [Exports](#exports)
|
38
|
+
- [Console](#console)
|
39
|
+
- [JSON](#json)
|
40
|
+
- [CSV (DR-001)](#csv-dr-001)
|
41
|
+
- [Markdown](#markdown)
|
42
|
+
- [YAML](#yaml)
|
43
|
+
- [XML](#xml)
|
44
|
+
- [Determinism and Sorting](#determinism-and-sorting)
|
45
|
+
- [Windows Notes](#windows-notes)
|
46
|
+
- [Diagnostics and Errors](#diagnostics-and-errors)
|
47
|
+
- [FAQ](#faq)
|
48
|
+
- [Development](#development)
|
49
|
+
- [License](#license)
|
50
|
+
|
51
|
+
## Features
|
52
|
+
* **Reports**: `summary`, `activity`, `authors`, `files`, `heatmap`, `languages`.
|
53
|
+
* **Filters**: branches, authors, paths, time period.
|
54
|
+
* **Exports**: `console`, `json`, `csv`, `md`, `yaml`, `xml`.
|
55
|
+
* **Output**: to stdout or file via `--out`.
|
56
|
+
|
57
|
+
## Requirements
|
58
|
+
* **Ruby**: >= 3.4 (recommended 3.4.x)
|
59
|
+
* **Git**: installed and available in `PATH`
|
60
|
+
|
61
|
+
## Installation
|
62
|
+
Choose one:
|
63
|
+
|
64
|
+
1) From source (recommended for development)
|
65
|
+
|
66
|
+
```bash
|
67
|
+
git clone <repo_url>
|
68
|
+
cd pretty-git
|
69
|
+
bin/setup
|
70
|
+
# run:
|
71
|
+
bundle exec bin/pretty-git --help
|
72
|
+
```
|
73
|
+
|
74
|
+
2) As a gem (after the first release)
|
75
|
+
|
76
|
+
```bash
|
77
|
+
gem install pretty-git
|
78
|
+
pretty-git --version
|
79
|
+
```
|
80
|
+
|
81
|
+
3) Via Bundler
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# Gemfile
|
85
|
+
gem 'pretty-git', '~> 0.1'
|
86
|
+
```
|
87
|
+
```bash
|
88
|
+
bundle install
|
89
|
+
bundle exec pretty-git --help
|
90
|
+
```
|
91
|
+
|
92
|
+
## Quick Start
|
93
|
+
```bash
|
94
|
+
# Repository summary to console
|
95
|
+
bundle exec bin/pretty-git summary .
|
96
|
+
|
97
|
+
# Authors in JSON written to file
|
98
|
+
bundle exec bin/pretty-git authors . --format json --out authors.json
|
99
|
+
|
100
|
+
# Weekly activity for period only for selected paths
|
101
|
+
bundle exec bin/pretty-git activity . --time-bucket week --since 2025-01-01 \
|
102
|
+
--paths app,lib --format csv --out activity.csv
|
103
|
+
```
|
104
|
+
|
105
|
+
## CLI and Options
|
106
|
+
General form:
|
107
|
+
|
108
|
+
```bash
|
109
|
+
pretty-git <report> <repo_path> [options]
|
110
|
+
```
|
111
|
+
|
112
|
+
Available reports: `summary`, `activity`, `authors`, `files`, `heatmap`, `languages`.
|
113
|
+
|
114
|
+
Key options:
|
115
|
+
* **--format, -f** `console|json|csv|md|yaml|xml` (default `console`)
|
116
|
+
* **--out, -o** Path to write output file
|
117
|
+
* **--limit, -l** Number of items shown; `all` or `0` — no limit
|
118
|
+
* **--time-bucket** `day|week|month` (for `activity`)
|
119
|
+
* **--since/--until** Date/time in ISO8601 or `YYYY-MM-DD` (DR-005)
|
120
|
+
* **--branch** Multi-option, can be specified multiple times
|
121
|
+
* **--author/--exclude-author** Filter by authors
|
122
|
+
* **--path/--exclude-path** Filter by paths (comma-separated or repeated option)
|
123
|
+
* **--no-color** Disable colors in console
|
124
|
+
* **--theme** `basic|bright|mono` — console theme (default `basic`; `mono` forces monochrome)
|
125
|
+
|
126
|
+
Examples with multiple values:
|
127
|
+
|
128
|
+
```bash
|
129
|
+
# Multiple branches
|
130
|
+
pretty-git summary . --branch main --branch develop
|
131
|
+
|
132
|
+
# Filter authors (include/exclude)
|
133
|
+
pretty-git authors . --author alice@example.com --exclude-author bot@company
|
134
|
+
|
135
|
+
# Filter paths
|
136
|
+
pretty-git files . --path app,lib --exclude-path vendor,node_modules
|
137
|
+
```
|
138
|
+
|
139
|
+
### Filters
|
140
|
+
Filters apply at commit fetch and later aggregation. Date format: ISO8601 or `YYYY-MM-DD`. If timezone is omitted — your local zone is assumed; output timestamps are normalized to UTC.
|
141
|
+
|
142
|
+
### Output format
|
143
|
+
Set via `--format`. For file formats it’s recommended to use `--out`.
|
144
|
+
|
145
|
+
### Write to file
|
146
|
+
```bash
|
147
|
+
pretty-git authors . --format csv --out authors.csv
|
148
|
+
```
|
149
|
+
|
150
|
+
### Exit codes
|
151
|
+
* `0` — success
|
152
|
+
* `1` — user error (unknown report/format, bad arguments)
|
153
|
+
* `2` — system error (git error etc.)
|
154
|
+
|
155
|
+
## Reports and Examples
|
156
|
+
|
157
|
+
### summary — repository summary
|
158
|
+
```bash
|
159
|
+
pretty-git summary . --format json
|
160
|
+
```
|
161
|
+
Contains totals (commits, authors, additions, deletions) and top authors/files.
|
162
|
+
|
163
|
+
### activity — activity (day/week/month)
|
164
|
+
```bash
|
165
|
+
pretty-git activity . --time-bucket week --format csv
|
166
|
+
```
|
167
|
+
CSV columns: `bucket,timestamp,commits,additions,deletions`.
|
168
|
+
JSON example:
|
169
|
+
```json
|
170
|
+
[
|
171
|
+
{"bucket":"week","timestamp":"2025-06-02T00:00:00Z","commits":120,"additions":3456,"deletions":2100},
|
172
|
+
{"bucket":"week","timestamp":"2025-06-09T00:00:00Z","commits":98,"additions":2890,"deletions":1760}
|
173
|
+
]
|
174
|
+
```
|
175
|
+
|
176
|
+
### authors — by authors
|
177
|
+
```bash
|
178
|
+
pretty-git authors . --format md --limit 10
|
179
|
+
```
|
180
|
+
CSV columns: `author,author_email,commits,additions,deletions,avg_commit_size`.
|
181
|
+
Markdown example:
|
182
|
+
```markdown
|
183
|
+
| author | author_email | commits | additions | deletions | avg_commit_size |
|
184
|
+
|---|---|---:|---:|---:|---:|
|
185
|
+
| Alice | a@example.com | 2 | 5 | 1 | 3.0 |
|
186
|
+
| Bob | b@example.com | 1 | 2 | 0 | 2.0 |
|
187
|
+
```
|
188
|
+
|
189
|
+
### files — by files
|
190
|
+
```bash
|
191
|
+
pretty-git files . --paths app,lib --format csv
|
192
|
+
```
|
193
|
+
CSV columns: `path,commits,additions,deletions,changes`.
|
194
|
+
XML example:
|
195
|
+
```xml
|
196
|
+
<files>
|
197
|
+
<item path="app/models/user.rb" commits="42" additions="2100" deletions="1400" changes="3500" />
|
198
|
+
<item path="app/services/auth.rb" commits="35" additions="1500" deletions="900" changes="2400" />
|
199
|
+
<generated_at>2025-01-31T00:00:00Z</generated_at>
|
200
|
+
<repo_path>/abs/path/to/repo</repo_path>
|
201
|
+
<report>files</report>
|
202
|
+
<period>
|
203
|
+
<since/>
|
204
|
+
<until/>
|
205
|
+
</period>
|
206
|
+
</files>
|
207
|
+
```
|
208
|
+
|
209
|
+
### heatmap — commit heatmap
|
210
|
+
```bash
|
211
|
+
pretty-git heatmap . --format json
|
212
|
+
```
|
213
|
+
JSON: an array of buckets for (day-of-week × hour) with commit counts.
|
214
|
+
CSV example:
|
215
|
+
```csv
|
216
|
+
dow,hour,commits
|
217
|
+
1,10,5
|
218
|
+
1,11,7
|
219
|
+
```
|
220
|
+
|
221
|
+
### languages — languages
|
222
|
+
```bash
|
223
|
+
pretty-git languages . --format md --limit 10
|
224
|
+
```
|
225
|
+
Determines language distribution in a repository by summing file bytes per language (similar to GitHub Linguist). Output includes language, total size (bytes) and percent share.
|
226
|
+
|
227
|
+
Console example:
|
228
|
+
```text
|
229
|
+
Languages for .
|
230
|
+
|
231
|
+
language bytes percent
|
232
|
+
-------- ---------- -------
|
233
|
+
Ruby 123456 60.0
|
234
|
+
JavaScript 78901 38.3
|
235
|
+
Markdown 1200 1.7
|
236
|
+
```
|
237
|
+
|
238
|
+

|
239
|
+
|
240
|
+
Notes:
|
241
|
+
- **Detection**: by file extensions and certain filenames (`Makefile`, `Dockerfile`).
|
242
|
+
- **Exclusions**: binary files and "vendor"-like directories are ignored. By default `vendor/`, `node_modules/`, `.git/`, build artifacts and caches are skipped. For Python projects additional directories are skipped: `.venv/`, `venv/`, `env/`, `__pycache__/`, `.mypy_cache/`, `.pytest_cache/`, `.tox/`, `.eggs/`, `.ruff_cache/`, `.ipynb_checkpoints/`.
|
243
|
+
- **JSON**: JSON is not counted as a language by default to avoid data files skewing statistics.
|
244
|
+
- **Path filters**: use `--path/--exclude-path` (glob patterns supported) to focus on relevant areas.
|
245
|
+
- **Limit**: `--limit N` restricts number of rows; `0`/`all` — no limit.
|
246
|
+
- **Console colors**: language names use approximate GitHub colors; `--no-color` disables, `--theme mono` makes output monochrome.
|
247
|
+
|
248
|
+
See also: [Ignored directories and files](#ignored-directories-and-files).
|
249
|
+
|
250
|
+
Export:
|
251
|
+
- CSV/MD: columns — `language,bytes,percent`.
|
252
|
+
- JSON/YAML/XML: full report structure including metadata (`report`, `generated_at`, `repo_path`).
|
253
|
+
|
254
|
+
## Exports
|
255
|
+
|
256
|
+
Below are exact serialization rules for each format to ensure compatibility with common tools (Excel, BI, CI, etc.).
|
257
|
+
|
258
|
+
### Console
|
259
|
+

|
260
|
+
_Example terminal output (theme: basic)._
|
261
|
+
* **Colors**: headers and table heads highlighted; totals: `commits` — yellow, `+additions` — green, `-deletions` — red. `--no-color` fully disables coloring.
|
262
|
+
* **Themes**: `--theme basic|bright|mono`. `bright` — more saturated headers, `mono` — monochrome (same as `--no-color`).
|
263
|
+
* **Highlight max**: numeric columns underline max values in bold for quick scanning.
|
264
|
+
* **Terminal width**: table output respects terminal width; first column is gracefully truncated with ellipsis `…` if needed.
|
265
|
+
* **Encoding**: UTF‑8, LF line endings.
|
266
|
+
* **Purpose**: human-readable terminal output.
|
267
|
+
* **Layout**: boxed tables, auto-truncation of long values.
|
268
|
+
|
269
|
+
### JSON
|
270
|
+
* **Keys**: `snake_case`.
|
271
|
+
* **Numbers**: integers/floats without localization (dot decimal separator).
|
272
|
+
* **Boolean**: `true/false`; **null**: `null`.
|
273
|
+
* **Date/time**: ISO8601 in UTC, e.g. `2025-01-31T00:00:00Z`.
|
274
|
+
* **Order**: fields arranged logically and consistently (e.g., `report`, `generated_at`, `repo_path`, then data).
|
275
|
+
* **Encoding/line endings**: UTF‑8, LF.
|
276
|
+
* **Suggested extension**: `.json`.
|
277
|
+
* **Example**:
|
278
|
+
```json
|
279
|
+
{"report":"summary","generated_at":"2025-01-31T00:00:00Z","totals":{"commits":123}}
|
280
|
+
```
|
281
|
+
|
282
|
+
### CSV (DR-001)
|
283
|
+
* **Structure**: flat table, first line is header.
|
284
|
+
* **Encoding**: UTF‑8 without BOM.
|
285
|
+
* **Delimiter**: comma `,`.
|
286
|
+
* **Escaping**: RFC 4180 — fields with commas/quotes/newlines are enclosed in double quotes, double quotes inside are doubled.
|
287
|
+
* **Empty values**: empty cell (not `null`).
|
288
|
+
* **Numbers**: no thousand separators, dot as decimal.
|
289
|
+
* **Date/time**: ISO8601 UTC.
|
290
|
+
* **Column order**: fixed per report and stable.
|
291
|
+
* **Line endings**: LF.
|
292
|
+
* **Suggested extension**: `.csv`.
|
293
|
+
* **Excel**: specify UTF‑8 on import.
|
294
|
+
* **Example**:
|
295
|
+
```csv
|
296
|
+
author,author_email,commits,additions,deletions,avg_commit_size
|
297
|
+
Alice,a@example.com,2,5,1,3.0
|
298
|
+
Bob,b@example.com,1,2,0,2.0
|
299
|
+
```
|
300
|
+
|
301
|
+
### Markdown
|
302
|
+
* **Tables**: GitHub Flavored Markdown.
|
303
|
+
* **Alignment**: numeric columns are right-aligned (`---:`).
|
304
|
+
* **Encoding/line endings**: UTF‑8, LF.
|
305
|
+
* **Suggested extension**: `.md`.
|
306
|
+
* **Empty datasets**: header-only table or a short `No data` message (depends on report).
|
307
|
+
* **Example**:
|
308
|
+
```markdown
|
309
|
+
| path | commits | additions | deletions |
|
310
|
+
|---|---:|---:|---:|
|
311
|
+
| app/models/user.rb | 42 | 2100 | 1400 |
|
312
|
+
```
|
313
|
+
|
314
|
+
### YAML
|
315
|
+
* **Structure**: full result hierarchy.
|
316
|
+
* **Keys**: serialized as strings.
|
317
|
+
* **Numbers/boolean/null**: standard YAML (`123`, `true/false`, `null`).
|
318
|
+
* **Date/time**: ISO8601 UTC as strings.
|
319
|
+
* **Encoding/line endings**: UTF‑8, LF.
|
320
|
+
* **Suggested extension**: `.yml` or `.yaml`.
|
321
|
+
* **Example**:
|
322
|
+
```yaml
|
323
|
+
report: authors
|
324
|
+
generated_at: "2025-01-31T00:00:00Z"
|
325
|
+
items:
|
326
|
+
- author: Alice
|
327
|
+
author_email: a@example.com
|
328
|
+
commits: 2
|
329
|
+
- author: Bob
|
330
|
+
author_email: b@example.com
|
331
|
+
commits: 1
|
332
|
+
```
|
333
|
+
|
334
|
+
### XML
|
335
|
+
* **Structure**: elements correspond to keys; arrays — repeated `<item>` or specialized tags.
|
336
|
+
* **Attributes**: for compact rows (e.g., files report) main fields may be attributes.
|
337
|
+
* **Text nodes**: used for scalar values when needed.
|
338
|
+
* **Escaping**: `& < > " ' ` per XML rules; CDATA may be used for arbitrary text.
|
339
|
+
* **Date/time**: ISO8601 UTC.
|
340
|
+
* **Encoding/line endings**: UTF‑8, LF; declaration `<?xml version="1.0" encoding="UTF-8"?>` may be added by the generator.
|
341
|
+
* **Suggested extension**: `.xml`.
|
342
|
+
* **Example**:
|
343
|
+
```xml
|
344
|
+
<authors>
|
345
|
+
<item author="Alice" author_email="a@example.com" commits="2" />
|
346
|
+
<item author="Bob" author_email="b@example.com" commits="1" />
|
347
|
+
<generated_at>2025-01-31T00:00:00Z</generated_at>
|
348
|
+
<repo_path>/abs/path</repo_path>
|
349
|
+
</authors>
|
350
|
+
```
|
351
|
+
|
352
|
+
## Ignored directories and files
|
353
|
+
|
354
|
+
To keep language statistics meaningful, certain directories and file types are skipped by default.
|
355
|
+
|
356
|
+
**Directories ignored** (any path segment matching one of these):
|
357
|
+
|
358
|
+
```
|
359
|
+
vendor, node_modules, .git, .bundle, dist, build, out, target, coverage,
|
360
|
+
.venv, venv, env, __pycache__, .mypy_cache, .pytest_cache, .tox, .eggs, .ruff_cache,
|
361
|
+
.ipynb_checkpoints
|
362
|
+
```
|
363
|
+
|
364
|
+
**Binary/data extensions ignored**:
|
365
|
+
|
366
|
+
```
|
367
|
+
.png, .jpg, .jpeg, .gif, .svg, .webp, .ico, .bmp,
|
368
|
+
.pdf, .zip, .tar, .gz, .tgz, .bz2, .7z, .rar,
|
369
|
+
.mp3, .ogg, .wav, .mp4, .mov, .avi, .mkv,
|
370
|
+
.woff, .woff2, .ttf, .otf, .eot,
|
371
|
+
.jar, .class, .dll, .so, .dylib,
|
372
|
+
.exe, .bin, .dat
|
373
|
+
```
|
374
|
+
|
375
|
+
These lists mirror the implementation in `lib/pretty_git/analytics/languages.rb` and may evolve.
|
376
|
+
|
377
|
+
## Determinism and Sorting
|
378
|
+
Output is deterministic given the same input. Sorting for files/authors: by changes (desc), then by commits (desc), then by path/name (asc). Limits are applied after sorting; `all` or `0` means no limit.
|
379
|
+
|
380
|
+
## Windows Notes
|
381
|
+
Primary targets — macOS/Linux. Windows is supported best‑effort:
|
382
|
+
* Running via Git Bash/WSL is OK
|
383
|
+
* Colors can be disabled by `--no-color`
|
384
|
+
* Carefully quote arguments when working with paths
|
385
|
+
|
386
|
+
## Diagnostics and Errors
|
387
|
+
Typical issues and solutions:
|
388
|
+
|
389
|
+
* **Unknown report/format** — check the first argument and `--format`.
|
390
|
+
* **Invalid date format** — use ISO8601 or `YYYY-MM-DD` (e.g., `2025-01-31` or `2025-01-31T12:00:00Z`).
|
391
|
+
* **Git not available** — ensure `git` is installed and in the `PATH`.
|
392
|
+
* **Empty result** — check your filters (`--since/--until`, `--branch`, `--path`); your selection might be too narrow.
|
393
|
+
* **CSV encoding issues** — files are saved as UTF‑8; when opening in Excel, pick UTF‑8.
|
394
|
+
|
395
|
+
## FAQ
|
396
|
+
* **Why Ruby 3.4+?** The project uses dependencies aligned with Ruby 3.4+ and targets the current ecosystem.
|
397
|
+
* **New formats?** Yes, add a renderer under `lib/pretty_git/render/` and wire it in the app.
|
398
|
+
* **Where does data come from?** From system `git` via CLI calls.
|
399
|
+
|
400
|
+
## Development
|
401
|
+
```bash
|
402
|
+
# Install deps
|
403
|
+
bin/setup
|
404
|
+
|
405
|
+
# Run tests and linter
|
406
|
+
bundle exec rspec
|
407
|
+
bundle exec rubocop
|
408
|
+
```
|
409
|
+
|
410
|
+
Style — RuboCop clean. Tests cover aggregators, renderers, CLI, and integration scenarios (determinism, format correctness).
|
411
|
+
|
412
|
+
## License
|
413
|
+
MIT © Contributors
|
data/bin/pretty-git
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module PrettyGit
|
6
|
+
module Analytics
|
7
|
+
# Activity analytics: buckets commits by day/week/month.
|
8
|
+
class Activity
|
9
|
+
class << self
|
10
|
+
def call(enum, filters)
|
11
|
+
bucket = (filters.time_bucket || 'week').to_s
|
12
|
+
groups = aggregate(enum, bucket)
|
13
|
+
items = groups_to_items(groups, bucket)
|
14
|
+
build_result(filters, bucket, items)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def aggregate(enum, bucket)
|
20
|
+
groups = Hash.new { |h, k| h[k] = { commits: 0, additions: 0, deletions: 0 } }
|
21
|
+
enum.each { |c| process_commit(groups, bucket, c) }
|
22
|
+
groups
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_commit(groups, bucket, commit)
|
26
|
+
ts = Time.parse(commit.authored_at.to_s).utc
|
27
|
+
key_time = bucket_start(ts, bucket)
|
28
|
+
g = groups[key_time]
|
29
|
+
g[:commits] += 1
|
30
|
+
g[:additions] += commit.additions.to_i
|
31
|
+
g[:deletions] += commit.deletions.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
def groups_to_items(groups, bucket)
|
35
|
+
groups.keys.sort.map do |t|
|
36
|
+
v = groups[t]
|
37
|
+
{
|
38
|
+
bucket: bucket,
|
39
|
+
timestamp: t.utc.iso8601,
|
40
|
+
commits: v[:commits],
|
41
|
+
additions: v[:additions],
|
42
|
+
deletions: v[:deletions]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_result(filters, bucket, items)
|
48
|
+
{
|
49
|
+
report: 'activity',
|
50
|
+
repo_path: File.expand_path(filters.repo_path),
|
51
|
+
period: { since: filters.since_iso8601, until: filters.until_iso8601 },
|
52
|
+
bucket: bucket,
|
53
|
+
items: items,
|
54
|
+
generated_at: Time.now.utc.iso8601
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def bucket_start(time, bucket)
|
59
|
+
return start_of_iso_week(time) if bucket == 'week'
|
60
|
+
return start_of_month(time) if bucket == 'month'
|
61
|
+
|
62
|
+
start_of_day(time)
|
63
|
+
end
|
64
|
+
|
65
|
+
def start_of_day(time)
|
66
|
+
Time.utc(time.year, time.month, time.day)
|
67
|
+
end
|
68
|
+
|
69
|
+
def start_of_iso_week(time)
|
70
|
+
# ISO week starts on Monday. Find Monday of the current week at 00:00 UTC.
|
71
|
+
wday = (time.wday + 6) % 7 # Monday=0 .. Sunday=6
|
72
|
+
base = time - (wday * 86_400)
|
73
|
+
Time.utc(base.year, base.month, base.day)
|
74
|
+
end
|
75
|
+
|
76
|
+
def start_of_month(time)
|
77
|
+
Time.utc(time.year, time.month, 1)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module PrettyGit
|
6
|
+
module Analytics
|
7
|
+
# Builds authors report: commits, additions, deletions, avg_commit_size
|
8
|
+
class Authors
|
9
|
+
class << self
|
10
|
+
# Computes aggregates from a commits enumerator
|
11
|
+
# Returns a Hash suitable for JSON/YAML serialization and renderers
|
12
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
13
|
+
def call(commits_enum, filters)
|
14
|
+
authors = Hash.new { |h, k| h[k] = base_author(k) }
|
15
|
+
|
16
|
+
commits_enum.each do |c|
|
17
|
+
key = [c.author_name, c.author_email]
|
18
|
+
a = authors[key]
|
19
|
+
a[:commits] += 1
|
20
|
+
a[:additions] += c.additions
|
21
|
+
a[:deletions] += c.deletions
|
22
|
+
end
|
23
|
+
|
24
|
+
rows = authors.values.map do |a|
|
25
|
+
size = a[:additions] + a[:deletions]
|
26
|
+
avg = a[:commits].positive? ? (size.to_f / a[:commits]).round(2) : 0.0
|
27
|
+
a.merge(avg_commit_size: avg)
|
28
|
+
end
|
29
|
+
|
30
|
+
rows.sort_by! { |a| [-a[:commits], -a[:additions], a[:author]] }
|
31
|
+
rows = rows.first(filters.limit) if filters.limit
|
32
|
+
|
33
|
+
{
|
34
|
+
report: 'authors',
|
35
|
+
repo_path: filters.repo_path,
|
36
|
+
period: { since: filters.since_iso8601, until: filters.until_iso8601 },
|
37
|
+
totals: {
|
38
|
+
authors: authors.length,
|
39
|
+
commits: rows.sum { |r| r[:commits] },
|
40
|
+
additions: rows.sum { |r| r[:additions] },
|
41
|
+
deletions: rows.sum { |r| r[:deletions] }
|
42
|
+
},
|
43
|
+
items: rows,
|
44
|
+
generated_at: Time.now.utc.iso8601
|
45
|
+
}
|
46
|
+
end
|
47
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def base_author(key)
|
52
|
+
name, email = key
|
53
|
+
{
|
54
|
+
author: name,
|
55
|
+
author_email: email,
|
56
|
+
commits: 0,
|
57
|
+
additions: 0,
|
58
|
+
deletions: 0
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrettyGit
|
4
|
+
module Analytics
|
5
|
+
# Files analytics: aggregates by file path across commits
|
6
|
+
class Files
|
7
|
+
class << self
|
8
|
+
def call(enum, filters)
|
9
|
+
per_file = aggregate_per_file(enum)
|
10
|
+
limit = normalize_limit(filters.limit)
|
11
|
+
items = build_items(per_file)
|
12
|
+
items = sort_and_limit(items, limit)
|
13
|
+
build_result(filters, items)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def aggregate_per_file(enum)
|
19
|
+
per_file = Hash.new { |h, k| h[k] = { commits: 0, additions: 0, deletions: 0 } }
|
20
|
+
enum.each do |c|
|
21
|
+
seen_paths = {}
|
22
|
+
c.files&.each { |f| process_file_entry(per_file, seen_paths, f) }
|
23
|
+
end
|
24
|
+
per_file
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_file_entry(per_file, seen_paths, file_stat)
|
28
|
+
path = file_stat.path
|
29
|
+
unless seen_paths[path]
|
30
|
+
per_file[path][:commits] += 1
|
31
|
+
seen_paths[path] = true
|
32
|
+
end
|
33
|
+
per_file[path][:additions] += file_stat.additions.to_i
|
34
|
+
per_file[path][:deletions] += file_stat.deletions.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_items(per_file)
|
38
|
+
per_file.map do |path, v|
|
39
|
+
{
|
40
|
+
path: path,
|
41
|
+
commits: v[:commits],
|
42
|
+
additions: v[:additions],
|
43
|
+
deletions: v[:deletions],
|
44
|
+
changes: v[:additions] + v[:deletions]
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_result(filters, items)
|
50
|
+
{
|
51
|
+
report: 'files',
|
52
|
+
repo_path: File.expand_path(filters.repo_path),
|
53
|
+
period: { since: filters.since_iso8601, until: filters.until_iso8601 },
|
54
|
+
items: items,
|
55
|
+
generated_at: Time.now.utc.iso8601
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def normalize_limit(raw)
|
60
|
+
return nil if raw.nil? || raw == 'all'
|
61
|
+
|
62
|
+
n = raw.to_i
|
63
|
+
n <= 0 ? nil : n
|
64
|
+
end
|
65
|
+
|
66
|
+
def sort_and_limit(arr, limit)
|
67
|
+
sorted = arr.sort_by { |h| [-h[:changes], -h[:commits], h[:path].to_s] }
|
68
|
+
limit ? sorted.first(limit) : sorted
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|