pretty-git 0.1.1 → 0.1.3
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 +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +125 -40
- data/README.ru.md +511 -0
- data/lib/pretty_git/analytics/churn.rb +75 -0
- data/lib/pretty_git/analytics/hotspots.rb +78 -0
- data/lib/pretty_git/analytics/languages.rb +80 -23
- data/lib/pretty_git/analytics/ownership.rb +90 -0
- data/lib/pretty_git/app.rb +18 -16
- data/lib/pretty_git/cli.rb +1 -1
- data/lib/pretty_git/cli_helpers.rb +20 -4
- data/lib/pretty_git/filters.rb +1 -0
- data/lib/pretty_git/git/provider.rb +8 -10
- data/lib/pretty_git/render/console_renderer.rb +53 -16
- data/lib/pretty_git/render/csv_renderer.rb +19 -14
- data/lib/pretty_git/render/languages_section.rb +7 -4
- data/lib/pretty_git/render/markdown_renderer.rb +35 -16
- data/lib/pretty_git/version.rb +1 -1
- metadata +6 -1
data/README.ru.md
ADDED
@@ -0,0 +1,511 @@
|
|
1
|
+
# Pretty Git
|
2
|
+
|
3
|
+
[](https://github.com/MikoMikocchi/pretty-git/actions/workflows/ci.yml)
|
4
|
+
[](https://rubygems.org/gems/pretty-git)
|
5
|
+
[](LICENSE)
|
6
|
+

|
7
|
+
|
8
|
+
<p align="right">
|
9
|
+
<a href="./README.md">English</a> | <b>Русский</b>
|
10
|
+
</p>
|
11
|
+
|
12
|
+
<p align="center">
|
13
|
+
<img src="docs/images/PrettyGitIcon.png" alt="Pretty Git Logo" width="200">
|
14
|
+
<br>
|
15
|
+
</p>
|
16
|
+
|
17
|
+
Генератор отчётов по локальному Git-репозиторию: сводка, активность, авторы, файлы, теплокарта, языки, hotspots, churn, ownership. Вывод в консоль и форматы: JSON, CSV, Markdown, YAML, XML.
|
18
|
+
|
19
|
+
— Лицензия: MIT.
|
20
|
+
|
21
|
+
## Содержание
|
22
|
+
- [Возможности](#возможности)
|
23
|
+
- [Требования](#требования)
|
24
|
+
- [Установка](#установка)
|
25
|
+
- [Быстрый старт](#быстрый-старт)
|
26
|
+
- [CLI и параметры](#cli-и-параметры)
|
27
|
+
- [Фильтры](#фильтры)
|
28
|
+
- [Формат вывода](#формат-вывода)
|
29
|
+
- [Вывод в файл](#вывод-в-файл)
|
30
|
+
- [Коды возврата](#коды-возврата)
|
31
|
+
- [Отчёты и примеры](#отчёты-и-примеры)
|
32
|
+
- [summary — сводка](#summary--сводка)
|
33
|
+
- [activity — активность (day/week/month)](#activity--активность-dayweekmonth)
|
34
|
+
- [authors — по авторам](#authors--по-авторам)
|
35
|
+
- [files — по файлам](#files--по-файлам)
|
36
|
+
- [heatmap — тепловая карта](#heatmap--тепловая-карта)
|
37
|
+
- [languages — языки](#languages--языки)
|
38
|
+
- [hotspots — рискованные файлы](#hotspots--рискованные-файлы)
|
39
|
+
- [churn — churn по файлам](#churn--churn-по-файлам)
|
40
|
+
- [ownership — владение кодом](#ownership--владение-кодом)
|
41
|
+
- [Экспорт в форматы](#экспорт-в-форматы)
|
42
|
+
- [Console](#console)
|
43
|
+
- [JSON](#json)
|
44
|
+
- [CSV](#csv)
|
45
|
+
- [Markdown](#markdown)
|
46
|
+
- [YAML](#yaml)
|
47
|
+
- [XML](#xml)
|
48
|
+
- [Детерминизм и сортировка](#детерминизм-и-сортировка)
|
49
|
+
- [Советы по Windows](#советы-по-windows)
|
50
|
+
- [Диагностика и ошибки](#диагностика-и-ошибки)
|
51
|
+
- [FAQ](#faq)
|
52
|
+
- [Разработка](#разработка)
|
53
|
+
- [Лицензия](#лицензия)
|
54
|
+
|
55
|
+
## ✨ Возможности
|
56
|
+
* __Отчёты__: `summary`, `activity`, `authors`, `files`, `heatmap`, `languages`, `hotspots`, `churn`, `ownership`.
|
57
|
+
* __Фильтры__: ветки, авторы, пути, период времени.
|
58
|
+
* __Экспорт__: `console`, `json`, `csv`, `md`, `yaml`, `xml`.
|
59
|
+
* __Вывод__: в stdout или файл через `--out`.
|
60
|
+
|
61
|
+
## ⚙️ Требования
|
62
|
+
* __Ruby__: >= 3.4 (рекомендуется 3.4.x)
|
63
|
+
* __Git__: установлен и доступен в `PATH`
|
64
|
+
|
65
|
+
## 📦 Установка
|
66
|
+
|
67
|
+
### 🍺 Homebrew (рекомендуется)
|
68
|
+
```bash
|
69
|
+
brew tap MikoMikocchi/tap
|
70
|
+
brew install pretty-git
|
71
|
+
```
|
72
|
+
|
73
|
+
### ♦️ RubyGems
|
74
|
+
```bash
|
75
|
+
gem install pretty-git
|
76
|
+
```
|
77
|
+
Выберите один из вариантов:
|
78
|
+
|
79
|
+
1) 🛠️ Локально из исходников (рекомендуется для разработки)
|
80
|
+
|
81
|
+
```bash
|
82
|
+
git clone <repo_url>
|
83
|
+
cd pretty-git
|
84
|
+
bin/setup
|
85
|
+
# запуск:
|
86
|
+
bundle exec bin/pretty-git --help
|
87
|
+
```
|
88
|
+
|
89
|
+
2) ♦️ Как gem (после первого релиза)
|
90
|
+
|
91
|
+
```bash
|
92
|
+
gem install pretty-git
|
93
|
+
pretty-git --version
|
94
|
+
```
|
95
|
+
|
96
|
+
3) 📦 Использование через Bundler
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# Gemfile
|
100
|
+
gem 'pretty-git', '~> 0.1'
|
101
|
+
```
|
102
|
+
```bash
|
103
|
+
bundle install
|
104
|
+
bundle exec pretty-git --help
|
105
|
+
```
|
106
|
+
|
107
|
+
## 🚀 Быстрый старт
|
108
|
+
```bash
|
109
|
+
# Сводка репозитория в консоль
|
110
|
+
bundle exec bin/pretty-git summary .
|
111
|
+
|
112
|
+
# Авторы в JSON и запись в файл
|
113
|
+
bundle exec bin/pretty-git authors . --format json --out authors.json
|
114
|
+
|
115
|
+
# Активность по неделям за период и только по указанным путям
|
116
|
+
bundle exec bin/pretty-git activity . --time-bucket week --since 2025-01-01 \
|
117
|
+
--paths app,lib --format csv --out activity.csv
|
118
|
+
```
|
119
|
+
|
120
|
+
## 🧰 CLI и параметры
|
121
|
+
Общий вид:
|
122
|
+
|
123
|
+
```bash
|
124
|
+
pretty-git <report> <repo_path> [options]
|
125
|
+
```
|
126
|
+
|
127
|
+
Доступные отчёты: `summary`, `activity`, `authors`, `files`, `heatmap`, `languages`, `hotspots`, `churn`, `ownership`.
|
128
|
+
|
129
|
+
Ключевые опции:
|
130
|
+
* __--format, -f__ `console|json|csv|md|yaml|xml` (по умолчанию `console`)
|
131
|
+
* __--out, -o__ Путь для записи в файл
|
132
|
+
* __--limit, -l__ Число элементов в топах/выводе; `all` или `0` — без ограничения
|
133
|
+
* __--time-bucket__ `day|week|month` (для `activity`)
|
134
|
+
* __--since/--until__ Дата/время в ISO8601 или `YYYY-MM-DD`
|
135
|
+
* __--branch__ Мульти-опция, можно указывать несколько веток
|
136
|
+
* __--author/--exclude-author__ Фильтрация по авторам
|
137
|
+
* __--path/--exclude-path__ Фильтрация по путям (через запятую или повтор опции)
|
138
|
+
* __--no-color__ Отключить цвета в консоли
|
139
|
+
* __--theme__ `basic|bright|mono` — тема оформления консольного вывода (по умолчанию `basic`; `mono` принудительно отключает цвета)
|
140
|
+
* __--metric__ `bytes|files|loc` — метрика для отчёта `languages` (по умолчанию `bytes`)
|
141
|
+
|
142
|
+
Примеры значений с несколькими параметрами:
|
143
|
+
|
144
|
+
```bash
|
145
|
+
# Несколько веток
|
146
|
+
pretty-git summary . --branch main --branch develop
|
147
|
+
|
148
|
+
# Фильтрация по авторам (включая/исключая)
|
149
|
+
pretty-git authors . --author alice@example.com --exclude-author bot@company
|
150
|
+
|
151
|
+
# Фильтрация по путям
|
152
|
+
pretty-git files . --path app,lib --exclude-path vendor,node_modules
|
153
|
+
```
|
154
|
+
|
155
|
+
### Фильтры
|
156
|
+
Фильтры применяются на этапе выборки коммитов и последующей агрегации. Формат дат: ISO8601 или `YYYY-MM-DD`. Если часовой пояс не указан — используется локальная зона пользователя; на выводе время нормализуется к UTC.
|
157
|
+
|
158
|
+
### Формат вывода
|
159
|
+
Задаётся через `--format`. Для файловых форматов рекомендуется использовать `--out`.
|
160
|
+
|
161
|
+
### Вывод в файл
|
162
|
+
```bash
|
163
|
+
pretty-git authors . --format csv --out authors.csv
|
164
|
+
```
|
165
|
+
|
166
|
+
### Коды возврата
|
167
|
+
* `0` — успех
|
168
|
+
* `1` — ошибка пользователя (неизвестный отчёт/формат, неверные аргументы)
|
169
|
+
* `2` — системная ошибка (ошибка Git и пр.)
|
170
|
+
|
171
|
+
## 📊 Отчёты и примеры
|
172
|
+
|
173
|
+
### 🧭 summary — сводка
|
174
|
+
```bash
|
175
|
+
pretty-git summary . --format json
|
176
|
+
```
|
177
|
+
Содержит totals (commits, authors, additions, deletions) и топы по авторам/файлам.
|
178
|
+
|
179
|
+
### 📆 activity — активность (day/week/month)
|
180
|
+
```bash
|
181
|
+
pretty-git activity . --time-bucket week --format csv
|
182
|
+
```
|
183
|
+
CSV-колонки: `bucket,timestamp,commits,additions,deletions`.
|
184
|
+
Пример JSON:
|
185
|
+
```json
|
186
|
+
[
|
187
|
+
{"bucket":"week","timestamp":"2025-06-02T00:00:00Z","commits":120,"additions":3456,"deletions":2100},
|
188
|
+
{"bucket":"week","timestamp":"2025-06-09T00:00:00Z","commits":98,"additions":2890,"deletions":1760}
|
189
|
+
]
|
190
|
+
```
|
191
|
+
|
192
|
+
### 👤 authors — по авторам
|
193
|
+
```bash
|
194
|
+
pretty-git authors . --format md --limit 10
|
195
|
+
```
|
196
|
+
CSV-колонки: `author,author_email,commits,additions,deletions,avg_commit_size`.
|
197
|
+
Пример Markdown:
|
198
|
+
```markdown
|
199
|
+
| author | author_email | commits | additions | deletions | avg_commit_size |
|
200
|
+
|---|---|---:|---:|---:|---:|
|
201
|
+
| Alice | a@example.com | 2 | 5 | 1 | 3.0 |
|
202
|
+
| Bob | b@example.com | 1 | 2 | 0 | 2.0 |
|
203
|
+
```
|
204
|
+
|
205
|
+
### 📁 files — по файлам
|
206
|
+
```bash
|
207
|
+
pretty-git files . --paths app,lib --format csv
|
208
|
+
```
|
209
|
+
CSV-колонки: `path,commits,additions,deletions,changes`.
|
210
|
+
Пример XML:
|
211
|
+
```xml
|
212
|
+
<files>
|
213
|
+
<item path="app/models/user.rb" commits="42" additions="2100" deletions="1400" changes="3500" />
|
214
|
+
<item path="app/services/auth.rb" commits="35" additions="1500" deletions="900" changes="2400" />
|
215
|
+
<generated_at>2025-01-31T00:00:00Z</generated_at>
|
216
|
+
<repo_path>/abs/path/to/repo</repo_path>
|
217
|
+
<report>files</report>
|
218
|
+
<period>
|
219
|
+
<since/>
|
220
|
+
<until/>
|
221
|
+
</period>
|
222
|
+
</files>
|
223
|
+
```
|
224
|
+
|
225
|
+
### 🔥 heatmap — тепловая карта
|
226
|
+
```bash
|
227
|
+
pretty-git heatmap . --format json
|
228
|
+
```
|
229
|
+
JSON: массив бакетов по (день недели × час) с числом коммитов.
|
230
|
+
Пример CSV:
|
231
|
+
```csv
|
232
|
+
dow,hour,commits
|
233
|
+
1,10,5
|
234
|
+
1,11,7
|
235
|
+
```
|
236
|
+
|
237
|
+
### 🈺 languages — языки
|
238
|
+
```bash
|
239
|
+
pretty-git languages . --format md --limit 10
|
240
|
+
```
|
241
|
+
Определяет распределение языков в репозитории. Поддерживаются несколько метрик: `bytes`, `files`, `loc` (по умолчанию: `bytes`). Вывод включает язык, выбранную метрику и долю в процентах; в консоли отображаются цвета языков.
|
242
|
+
|
243
|
+
Пример консольного вывода:
|
244
|
+
```text
|
245
|
+
Languages for .
|
246
|
+
|
247
|
+
language bytes percent
|
248
|
+
-------- ---------- -------
|
249
|
+
Ruby 123456 60.0
|
250
|
+
JavaScript 78901 38.3
|
251
|
+
Markdown 1200 1.7
|
252
|
+
```
|
253
|
+
|
254
|
+

|
255
|
+
|
256
|
+
Замечания:
|
257
|
+
- __Определение языка__: по расширениям файлов и некоторым именам файлов (`Makefile`, `Dockerfile`).
|
258
|
+
- __Исключения__: бинарные файлы и "vendor"‑директории игнорируются. По умолчанию пропускаются `vendor/`, `node_modules/`, `.git/`, артефакты сборки и кэши. Для Python дополнительно исключаются `.venv/`, `venv/`, `env/`, `__pycache__/`, `.mypy_cache/`, `.pytest_cache/`, `.tox/`, `.eggs/`, `.ruff_cache/`, `.ipynb_checkpoints/`.
|
259
|
+
- __JSON__: JSON включён как язык. Если крупные файлы данных искажают картину, сузьте область `--path/--exclude-path`.
|
260
|
+
- __Фильтры путей__: используйте `--path/--exclude-path` (поддерживаются glob‑маски), чтобы сосредоточиться на соответствующих директориях.
|
261
|
+
- __Лимит__: `--limit N` ограничивает число строк в итоговой таблице; `0`/`all` — без ограничения.
|
262
|
+
- __Цвета в консоли__: имена языков подсвечиваются приблизительными цветами GitHub; `--no-color` отключает, `--theme mono` делает монохром.
|
263
|
+
|
264
|
+
Экспорт:
|
265
|
+
- CSV/MD: колонки динамические — `language,<metric>,percent`. В Markdown дополнительно присутствует колонка `color`.
|
266
|
+
- JSON/YAML/XML: полная структура отчёта, включая `color` на язык и метаданные (`report`, `generated_at`, `repo_path`).
|
267
|
+
|
268
|
+
### ⚠️ hotspots — рискованные файлы
|
269
|
+
```bash
|
270
|
+
pretty-git hotspots . --format csv --limit 20
|
271
|
+
```
|
272
|
+
Подсвечивает потенциально более рискованные файлы по сочетанию частоты и масштаба изменений.
|
273
|
+
|
274
|
+
CSV‑колонки: `path,score,commits,additions,deletions,changes`.
|
275
|
+
|
276
|
+
Пример:
|
277
|
+
```csv
|
278
|
+
path,score,commits,additions,deletions,changes
|
279
|
+
lib/a.rb,9.5,12,300,220,520
|
280
|
+
app/b.rb,7.1,8,140,60,200
|
281
|
+
```
|
282
|
+
|
283
|
+
Пример JSON:
|
284
|
+
```json
|
285
|
+
{
|
286
|
+
"report": "hotspots",
|
287
|
+
"generated_at": "2025-01-31T00:00:00Z",
|
288
|
+
"repo_path": ".",
|
289
|
+
"items": [
|
290
|
+
{"path": "lib/a.rb", "score": 9.5, "commits": 12, "additions": 300, "deletions": 220, "changes": 520}
|
291
|
+
]
|
292
|
+
}
|
293
|
+
```
|
294
|
+
|
295
|
+
### 🔄 churn — churn по файлам
|
296
|
+
```bash
|
297
|
+
pretty-git churn . --format md --limit 20
|
298
|
+
```
|
299
|
+
Оценивает churn (объём часто меняющегося кода) по файлам.
|
300
|
+
|
301
|
+
CSV‑колонки: `path,churn,commits,additions,deletions`.
|
302
|
+
|
303
|
+
Пример Markdown:
|
304
|
+
```markdown
|
305
|
+
| path | churn | commits | additions | deletions |
|
306
|
+
|---|---:|---:|---:|---:|
|
307
|
+
| lib/a.rb | 520 | 12 | 300 | 220 |
|
308
|
+
```
|
309
|
+
|
310
|
+
Пример YAML:
|
311
|
+
```yaml
|
312
|
+
report: churn
|
313
|
+
generated_at: '2025-01-31T00:00:00Z'
|
314
|
+
repo_path: .
|
315
|
+
items:
|
316
|
+
- path: lib/a.rb
|
317
|
+
churn: 520
|
318
|
+
commits: 12
|
319
|
+
additions: 300
|
320
|
+
deletions: 220
|
321
|
+
```
|
322
|
+
|
323
|
+
### 🏷️ ownership — владение кодом
|
324
|
+
```bash
|
325
|
+
pretty-git ownership . --format csv --limit 50
|
326
|
+
```
|
327
|
+
Показывает концентрацию владения файлами по основному владельцу.
|
328
|
+
|
329
|
+
CSV‑колонки: `path,owner,owner_share,authors`.
|
330
|
+
|
331
|
+
Примечания:
|
332
|
+
- `owner`: идентификатор автора (имя/email) с наибольшей долей правок.
|
333
|
+
- `owner_share`: доля правок владельца в процентах (0..100).
|
334
|
+
- `authors`: всего уникальных авторов, редактировавших файл.
|
335
|
+
|
336
|
+
Пример XML:
|
337
|
+
```xml
|
338
|
+
<ownership>
|
339
|
+
<report>ownership</report>
|
340
|
+
<generated_at>2025-01-31T00:00:00Z</generated_at>
|
341
|
+
<repo_path>.</repo_path>
|
342
|
+
<items>
|
343
|
+
<item path="lib/a.rb" owner="Alice <a@example.com>" owner_share="82.5" authors="2"/>
|
344
|
+
</items>
|
345
|
+
|
346
|
+
</ownership>
|
347
|
+
```
|
348
|
+
|
349
|
+
## 🚫 Игнорируемые директории и файлы
|
350
|
+
|
351
|
+
Чтобы статистика по языкам оставалась релевантной, некоторые директории и типы файлов пропускаются по умолчанию.
|
352
|
+
|
353
|
+
**Игнорируемые директории** (если сегмент пути совпадает):
|
354
|
+
|
355
|
+
```
|
356
|
+
vendor, node_modules, .git, .bundle, dist, build, out, target, coverage,
|
357
|
+
.venv, venv, env, __pycache__, .mypy_cache, .pytest_cache, .tox, .eggs, .ruff_cache,
|
358
|
+
.ipynb_checkpoints
|
359
|
+
```
|
360
|
+
|
361
|
+
**Игнорируемые бинарные/данные расширения**:
|
362
|
+
|
363
|
+
```
|
364
|
+
.png, .jpg, .jpeg, .gif, .svg, .webp, .ico, .bmp,
|
365
|
+
.pdf, .zip, .tar, .gz, .tgz, .bz2, .7z, .rar,
|
366
|
+
.mp3, .ogg, .wav, .mp4, .mov, .avi, .mkv,
|
367
|
+
.woff, .woff2, .ttf, .otf, .eot,
|
368
|
+
.jar, .class, .dll, .so, .dylib,
|
369
|
+
.exe, .bin, .dat
|
370
|
+
```
|
371
|
+
|
372
|
+
Списки соответствуют реализации в `lib/pretty_git/analytics/languages.rb` и могут изменяться.
|
373
|
+
|
374
|
+
## 📤 Экспорт в форматы
|
375
|
+
|
376
|
+
Ниже — точные правила сериализации для каждого формата, чтобы обеспечивалась совместимость с популярными инструментами (Excel, BI, CI и т.п.).
|
377
|
+
|
378
|
+
### 🖥️ Console
|
379
|
+

|
380
|
+
_Пример вывода в терминале (тема: basic)._
|
381
|
+
* __Цвета__: заголовки и шапки таблиц подсвечены; суммы: `commits` — жёлтым, `+additions` — зелёным, `-deletions` — красным. `--no-color` полностью отключает раскраску.
|
382
|
+
* __Темы__: `--theme basic|bright|mono`. `bright` — более насыщенные заголовки, `mono` — монохром (аналогично `--no-color`).
|
383
|
+
* __Выделение максимума__: на числовых колонках максимальные значения подчёркиваются жирным для быстрого сканирования.
|
384
|
+
* __Ширина терминала__: при выводе таблиц автоматически учитывается ширина терминала. Если ширины не хватает, первый столбец аккуратно обрезается с многоточием `…`.
|
385
|
+
* __Кодировка__: UTF‑8, переводы строк LF.
|
386
|
+
* __Назначение__: человекочитаемый вывод в терминал.
|
387
|
+
* __Оформление__: таблицы с границами, авто-обрезка длинных значений.
|
388
|
+
* __Цвета__: включены, если есть TTY; отключаются `--no-color`.
|
389
|
+
* __Пустые наборы__: печатается `No data`.
|
390
|
+
* __Кодировка/переводы строк__: UTF‑8, LF (`\n`).
|
391
|
+
|
392
|
+
### 🧾 JSON
|
393
|
+
* __Ключи__: `snake_case`.
|
394
|
+
* __Числа__: целые/вещественные без локализации (точка как разделитель).
|
395
|
+
* __Булевы__: `true/false`; __null__: `null`.
|
396
|
+
* __Даты/время__: ISO8601 в UTC, например `2025-01-31T00:00:00Z`.
|
397
|
+
* __Порядок__: поля в объектах стабильно упорядочены логически (например, `report`, `generated_at`, `repo_path`, затем данные).
|
398
|
+
* __Кодировка/переводы строк__: UTF‑8, LF.
|
399
|
+
* __Рекомендуемое расширение__: `.json`.
|
400
|
+
* __Пример__:
|
401
|
+
```json
|
402
|
+
{"report":"summary","generated_at":"2025-01-31T00:00:00Z","totals":{"commits":123}}
|
403
|
+
```
|
404
|
+
|
405
|
+
### CSV
|
406
|
+
* __Структура__: плоская таблица, первая строка — заголовок.
|
407
|
+
* __Кодировка__: UTF‑8, без BOM.
|
408
|
+
* __Разделитель__: запятая `,`.
|
409
|
+
* __Экранирование__: по RFC 4180 — поля с запятыми/кавычками/переводами строк заключаются в двойные кавычки, двойные кавычки внутри поля удваиваются.
|
410
|
+
* __Пустые значения__: пустая ячейка (не `null`).
|
411
|
+
* __Числа__: без тысячных разделителей, десятичная точка.
|
412
|
+
* __Даты/время__: ISO8601 UTC.
|
413
|
+
* __Порядок колонок__: фиксирован на отчёт и стабилен.
|
414
|
+
* __Переводы строк__: LF.
|
415
|
+
* __Рекомендуемое расширение__: `.csv`.
|
416
|
+
* __Открытие в Excel__: указывайте кодировку UTF‑8 при импорте.
|
417
|
+
* __Пример__:
|
418
|
+
```csv
|
419
|
+
author,author_email,commits,additions,deletions,avg_commit_size
|
420
|
+
Alice,a@example.com,2,5,1,3.0
|
421
|
+
Bob,b@example.com,1,2,0,2.0
|
422
|
+
```
|
423
|
+
|
424
|
+
### 📝 Markdown
|
425
|
+
* __Таблицы__: стандартный синтаксис GitHub Flavored Markdown.
|
426
|
+
* __Выравнивание__: числовые колонки выравниваются по правому краю (`---:`).
|
427
|
+
* __Кодировка/переводы строк__: UTF‑8, LF.
|
428
|
+
* __Рекомендуемое расширение__: `.md`.
|
429
|
+
* __Пустые наборы__: таблица с заголовком и без строк либо краткое сообщение `No data` (в зависимости от отчёта).
|
430
|
+
* __Пример__:
|
431
|
+
```markdown
|
432
|
+
| path | commits | additions | deletions |
|
433
|
+
|---|---:|---:|---:|
|
434
|
+
| app/models/user.rb | 42 | 2100 | 1400 |
|
435
|
+
```
|
436
|
+
|
437
|
+
### 📄 YAML
|
438
|
+
* __Структура__: полная иерархия результата.
|
439
|
+
* __Ключи__: сериализуются строками.
|
440
|
+
* __Числа/булевы/null__: стандарт YAML (`123`, `true/false`, `null`).
|
441
|
+
* __Даты/время__: ISO8601 UTC как строки.
|
442
|
+
* __Кодировка/переводы строк__: UTF‑8, LF.
|
443
|
+
* __Рекомендуемое расширение__: `.yml` или `.yaml`.
|
444
|
+
* __Пример__:
|
445
|
+
```yaml
|
446
|
+
report: authors
|
447
|
+
generated_at: "2025-01-31T00:00:00Z"
|
448
|
+
items:
|
449
|
+
- author: Alice
|
450
|
+
author_email: a@example.com
|
451
|
+
commits: 2
|
452
|
+
- author: Bob
|
453
|
+
author_email: b@example.com
|
454
|
+
commits: 1
|
455
|
+
```
|
456
|
+
|
457
|
+
### 🗂️ XML
|
458
|
+
* __Структура__: элементы соответствуют ключам; массивы — повторяющиеся `<item>` или специализированные теги.
|
459
|
+
* __Атрибуты__: для компактных записей (например, строк файлового отчёта) основные поля могут быть атрибутами элемента.
|
460
|
+
* __Текстовые узлы__: применяются для скалярных значений при необходимости.
|
461
|
+
* __Экранирование__: `& < > " ' ` по правилам XML, при необходимости CDATA для произвольного текста.
|
462
|
+
* __Даты/время__: ISO8601 UTC.
|
463
|
+
* __Кодировка/переводы строк__: UTF‑8, LF; декларация `<?xml version="1.0" encoding="UTF-8"?>` может добавляться генератором.
|
464
|
+
* __Рекомендуемое расширение__: `.xml`.
|
465
|
+
* __Пример__:
|
466
|
+
```xml
|
467
|
+
<authors>
|
468
|
+
<item author="Alice" author_email="a@example.com" commits="2" />
|
469
|
+
<item author="Bob" author_email="b@example.com" commits="1" />
|
470
|
+
<generated_at>2025-01-31T00:00:00Z</generated_at>
|
471
|
+
<repo_path>/abs/path</repo_path>
|
472
|
+
</authors>
|
473
|
+
```
|
474
|
+
|
475
|
+
## 🔁 Детерминизм и сортировка
|
476
|
+
Вывод детерминирован при одинаковых входных данных. Сортировка для файлов/авторов: по количеству изменений (desc), затем по числу коммитов (desc), затем по пути/имени (asc). Лимиты применяются поверх отсортированного списка; значение `all` или `0` означает отсутствие ограничения.
|
477
|
+
|
478
|
+
## 🪟 Советы по Windows
|
479
|
+
Целевая платформа — macOS/Linux. Windows поддерживается в режиме best‑effort:
|
480
|
+
* Запуск через Git Bash/WSL допустим
|
481
|
+
* Цвета можно отключить `--no-color`
|
482
|
+
* Аккуратное квотирование аргументов при работе с путями
|
483
|
+
|
484
|
+
## 🩺 Диагностика и ошибки
|
485
|
+
Типичные ошибки и решения:
|
486
|
+
|
487
|
+
* __Неизвестный отчёт/формат__ — проверьте значение первого аргумента и `--format`.
|
488
|
+
* __Неверный формат даты__ — используйте ISO8601 или `YYYY-MM-DD` (например, `2025-01-31` или `2025-01-31T12:00:00Z`).
|
489
|
+
* __Git недоступен__ — убедитесь, что `git` установлен и доступен в `PATH`.
|
490
|
+
* __Пустой результат__ — проверьте фильтры (`--since/--until`, `--branch`, `--path`), возможно, выборка слишком узкая.
|
491
|
+
* __Проблемы с кодировкой CSV__ — файлы сохраняются в UTF‑8, при открытии в Excel выбирайте UTF‑8.
|
492
|
+
|
493
|
+
## ❓ FAQ
|
494
|
+
* __Почему Ruby 3.4+?__ Проект использует зависимости, согласованные с версией 3.4+, и ориентируется на актуальную экосистему.
|
495
|
+
* __Можно ли добавить новые форматы?__ Да, добавьте рендерер в `lib/pretty_git/render/` и зарегистрируйте его в приложении.
|
496
|
+
* __Откуда берутся данные?__ Из системного `git` через вызовы CLI.
|
497
|
+
|
498
|
+
## 🛠️ Разработка
|
499
|
+
```bash
|
500
|
+
# Установка зависимостей
|
501
|
+
bin/setup
|
502
|
+
|
503
|
+
# Запуск тестов и линтера
|
504
|
+
bundle exec rspec
|
505
|
+
bundle exec rubocop
|
506
|
+
```
|
507
|
+
|
508
|
+
Стиль — RuboCop без ошибок. Тесты покрывают агрегаторы, рендереры, CLI и интеграционные сценарии (детерминизм, корректность форматов).
|
509
|
+
|
510
|
+
## 📄 Лицензия
|
511
|
+
MIT © Contributors
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module PrettyGit
|
6
|
+
module Analytics
|
7
|
+
# Churn: per-file volatility over the selected period.
|
8
|
+
# churn = additions + deletions (total changed lines)
|
9
|
+
class Churn
|
10
|
+
class << self
|
11
|
+
def call(enum, filters)
|
12
|
+
per_file = aggregate(enum)
|
13
|
+
items = build_items(per_file)
|
14
|
+
items = sort_and_limit(items, filters.limit)
|
15
|
+
build_result(filters, items)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def aggregate(enum)
|
21
|
+
acc = Hash.new { |h, k| h[k] = { commits: 0, additions: 0, deletions: 0 } }
|
22
|
+
enum.each do |commit|
|
23
|
+
seen = {}
|
24
|
+
commit.files&.each { |f| process_file_entry(acc, seen, f) }
|
25
|
+
end
|
26
|
+
acc
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_file_entry(acc, seen, file_stat)
|
30
|
+
path = file_stat.path
|
31
|
+
unless seen[path]
|
32
|
+
acc[path][:commits] += 1
|
33
|
+
seen[path] = true
|
34
|
+
end
|
35
|
+
acc[path][:additions] += file_stat.additions.to_i
|
36
|
+
acc[path][:deletions] += file_stat.deletions.to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_items(per_file)
|
40
|
+
per_file.map do |path, v|
|
41
|
+
churn = v[:additions] + v[:deletions]
|
42
|
+
{
|
43
|
+
path: path,
|
44
|
+
churn: churn,
|
45
|
+
commits: v[:commits]
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def sort_and_limit(items, raw_limit)
|
51
|
+
limit = normalize_limit(raw_limit)
|
52
|
+
sorted = items.sort_by { |h| [-h[:churn], -h[:commits], h[:path].to_s] }
|
53
|
+
limit ? sorted.first(limit) : sorted
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_result(filters, items)
|
57
|
+
{
|
58
|
+
report: 'churn',
|
59
|
+
repo_path: File.expand_path(filters.repo_path),
|
60
|
+
period: { since: filters.since_iso8601, until: filters.until_iso8601 },
|
61
|
+
items: items,
|
62
|
+
generated_at: Time.now.utc.iso8601
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def normalize_limit(raw)
|
67
|
+
return nil if raw.nil? || raw == 'all'
|
68
|
+
|
69
|
+
n = raw.to_i
|
70
|
+
n <= 0 ? nil : n
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module PrettyGit
|
6
|
+
module Analytics
|
7
|
+
# Hotspots: files with the highest change activity over the selected period.
|
8
|
+
# score = commits * (additions + deletions)
|
9
|
+
class Hotspots
|
10
|
+
class << self
|
11
|
+
def call(enum, filters)
|
12
|
+
per_file = aggregate(enum)
|
13
|
+
items = build_items(per_file)
|
14
|
+
items = sort_and_limit(items, filters.limit)
|
15
|
+
build_result(filters, items)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def aggregate(enum)
|
21
|
+
acc = Hash.new { |h, k| h[k] = { commits: 0, additions: 0, deletions: 0 } }
|
22
|
+
enum.each do |commit|
|
23
|
+
seen = {}
|
24
|
+
commit.files&.each { |f| process_file_entry(acc, seen, f) }
|
25
|
+
end
|
26
|
+
acc
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_file_entry(acc, seen, file_stat)
|
30
|
+
path = file_stat.path
|
31
|
+
unless seen[path]
|
32
|
+
acc[path][:commits] += 1
|
33
|
+
seen[path] = true
|
34
|
+
end
|
35
|
+
acc[path][:additions] += file_stat.additions.to_i
|
36
|
+
acc[path][:deletions] += file_stat.deletions.to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_items(per_file)
|
40
|
+
per_file.map do |path, v|
|
41
|
+
changes = v[:additions] + v[:deletions]
|
42
|
+
score = v[:commits] * changes
|
43
|
+
{
|
44
|
+
path: path,
|
45
|
+
score: score,
|
46
|
+
commits: v[:commits],
|
47
|
+
additions: v[:additions],
|
48
|
+
deletions: v[:deletions]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def sort_and_limit(items, raw_limit)
|
54
|
+
limit = normalize_limit(raw_limit)
|
55
|
+
sorted = items.sort_by { |h| [-h[:score], -h[:commits], -h[:additions] - h[:deletions], h[:path].to_s] }
|
56
|
+
limit ? sorted.first(limit) : sorted
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_result(filters, items)
|
60
|
+
{
|
61
|
+
report: 'hotspots',
|
62
|
+
repo_path: File.expand_path(filters.repo_path),
|
63
|
+
period: { since: filters.since_iso8601, until: filters.until_iso8601 },
|
64
|
+
items: items,
|
65
|
+
generated_at: Time.now.utc.iso8601
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_limit(raw)
|
70
|
+
return nil if raw.nil? || raw == 'all'
|
71
|
+
|
72
|
+
n = raw.to_i
|
73
|
+
n <= 0 ? nil : n
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|