pretty-git 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e190585a91de27221e54bc2cbc58b5f6d26a1428c2e5cd5189ff0c18d362e6c
4
- data.tar.gz: f8d2bc8f5d6c6a4887a745abd611a038166bcb54e95267a15d4868ee9d530003
3
+ metadata.gz: 00b27dd7b14328c3aa2e5d3bf865dd778d512131d3f3b1c7d9fe1ca76939c5e6
4
+ data.tar.gz: 36c9f80ac26f1982bf2a4b2f1616bf5d309dec2c9dcda12603e2e2422500e3a4
5
5
  SHA512:
6
- metadata.gz: e11fd51d24ed1e63fd5c773f76a46510bbcedad23c02ace63b25d7d80c366479fd02fe2bfabaa58693389c222bafe4fd9e6fc0b157b7d598e96294a3f888fbef
7
- data.tar.gz: 38fc87c6467d72ae6a1cb63a13b06c417e81f714d5b074feae961f121e061cea0733494020e0b8f562f36ea00b2d537a0d6fd5ac36eea942465680376f8eb71e
6
+ metadata.gz: 8518117f9b018c89a31db647920d871fe68f1ba1a2ddb90e43f58ec08342c4d345393617144ba29705ef6a13f86c184123c72b99ee2de81ada45824df875593d
7
+ data.tar.gz: 59505cbed77cdf230c6b5c2960caa1bde851a4a17820d886b788b409a04f5b042cfe51e5a4e060471e90a303743369df335206f3fc51b97759c637086775abcf
data/CHANGELOG.md CHANGED
@@ -5,66 +5,144 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
6
6
 
7
7
  ## [Unreleased]
8
-
8
+
9
+ ## [0.1.6] - 2025-10-10
10
+
11
+ ### Security
12
+
13
+ - Git::Provider: added input validation for `--author` and `--branch` parameters to prevent command injection attacks
14
+ - Git::Provider: `validate_safe_string` method checks for shell metacharacters (`; & | \` $ ( ) < >`)
15
+ - Git::Provider: `validate_git_ref` method validates git reference format and rejects refs starting with `-`
16
+
17
+ ### Added
18
+
19
+ - Render::ConsoleRenderer: named constants for ANSI color codes (`Colors::AnsiCodes` module)
20
+ - Render::ReportSchema: centralized module for report headers (eliminates duplication across CSV/Markdown renderers)
21
+ - CLI: verbose mode now includes full backtrace for `StandardError` exceptions (helps with debugging)
22
+ - Analytics::Languages: `safe_file_size` and `safe_count_lines` now log warnings to stderr when `verbose=true`
23
+ - Tests: 20+ negative test cases for input validation (`spec/pretty_git/git/provider_validation_spec.rb`)
24
+ - Tests: CLI error handling coverage (`spec/pretty_git/cli_error_handling_spec.rb`)
25
+
26
+ ### Changed
27
+
28
+ - CLI: enhanced error handling with specific exit codes: `ArgumentError` → 1, file system errors (`ENOENT`, `EACCES`) → 2, other errors → 99
29
+ - CLI: improved error messages for `ArgumentError`, `Errno::ENOENT`, and `Errno::EACCES`
30
+ - Render::CsvRenderer: refactored to use `ReportSchema.headers_for` instead of local `HEADERS` constant
31
+ - Render::MarkdownRenderer: refactored to use `ReportSchema.headers_for` instead of local `HEADERS` constant
32
+
33
+ ### Fixed
34
+
35
+ - Security: command injection vulnerabilities via `--author` and `--branch` parameters accepting unvalidated shell input
36
+
37
+ ### Documentation
38
+
39
+ - Docs: `docs/testing.md` covering the golden workflow, snapshot update/validation; linked from `README.md`, `README.ru.md`, and `CONTRIBUTING.md`.
40
+ - Tests: determinism invariants for YAML/XML renderers (stable output regardless of input order).
41
+ - Scripts: `scripts/release.sh` automated release workflow with validation, testing, and git operations
42
+
43
+ ### Refactoring
44
+
45
+ - Code quality: eliminated ~40 lines of duplicated `HEADERS` definitions across renderers
46
+ - Readability: replaced magic numbers (`'1;35'`, `'1;36'`) with descriptive constant names (`BRIGHT_TITLE`, `BASIC_TITLE`)
47
+
48
+ ### Changed (from previous Unreleased)
49
+
50
+ - Completions: refreshed bash/zsh completions — added short flags `-f`/`-o`/`-l`, values for `--time-bucket` (`day|week|month`), and repo path completion as the 2nd positional argument.
51
+
52
+ ## [0.1.5] - 2025-08-17
53
+
54
+ ### Added
55
+
56
+ - CLI: warn to stderr when `--theme`/`--no-color` are used with non-console `--format` values.
57
+ - Docs: expanded Filters documentation (branches, authors, paths, time semantics, verbose diagnostics), schemas/examples pointers, performance and CI usage.
58
+ - Tests: unit tests for `PrettyGit::Utils::TimeUtils`.
59
+
60
+ ### Changed
61
+
62
+ - Internals: extracted time parsing/normalization to `PrettyGit::Utils::TimeUtils` and centralized verbose logging via `PrettyGit::Logger`. `Git::Provider` routes verbose messages through the centralized logger (stderr).
63
+ - Verbose mode: documentation clarified to note that diagnostics are printed to stderr for easier CI parsing.
64
+
65
+ ### Deprecated
66
+
67
+ - Filters: legacy `:until` keyword in `PrettyGit::Filters` initialization is accepted for backward compatibility and emits a deprecation warning; use `:until_at` instead.
68
+
69
+ ### Fixed
70
+
71
+ - Filters: allow initialization via a single Hash argument (legacy call sites) while preserving `Struct` keyword semantics.
9
72
 
10
73
  ## [0.1.4] - 2025-08-17
74
+
11
75
  ### Added
76
+
12
77
  - Integration tests for new reports exports: CSV/Markdown/YAML/XML for `hotspots`, `churn`, `ownership`.
13
78
  - Schema validations: `rake validate:json`, `rake validate:xml` to ensure format compatibility.
14
79
  - CI: expanded matrix to include macOS; smoke test for installed binary (`--help`, `--version`).
15
80
 
16
81
  ### Changed
82
+
17
83
  - Renderers (`MarkdownRenderer`, `YamlRenderer`, `XmlRenderer`): deterministic sorting for all new reports according to `docs/determinism.md`.
18
84
  - XML: per-report root elements in XML exports to match XSDs (`hotspotsReport`, `churnReport`, `ownershipReport`, `languagesReport`, etc.).
19
85
  - Documentation: `README.md` and `README.ru.md` updated with sections and examples for new reports and all export formats.
20
86
  - CLI: keep `--time-bucket` permissive; default `time_bucket=nil`.
21
87
 
22
88
  ### Fixed
89
+
23
90
  - Time parsing: interpret date-only inputs (`YYYY-MM-DD`) as UTC midnight and normalize to UTC ISO8601.
24
91
  - CLI UX: error when `--metric` is used outside `languages` report.
25
92
  - Tests/specs: updated XML specs to per-report roots; added timezone edge cases; fixed Open3 `popen3` stubs (`chdir:`) and integration requires.
26
93
 
27
-
28
94
  ## [0.1.3] - 2025-08-14
95
+
29
96
  ### Added
97
+
30
98
  - New analytics reports: `hotspots`, `churn`, `ownership` with sorting, scoring, and limits.
31
99
  - Exporters: CSV and Markdown support for new reports with dynamic headers via mapping constants.
32
100
  - Docs: Detailed sections for new reports in `README.md` and `README.ru.md` with usage and examples (CSV/JSON/YAML/XML).
33
101
 
34
102
  ### Changed
103
+
35
104
  - Console: dispatching and rendering wired for new reports; consistent theming and width handling.
36
105
  - CLI/App: unified analytics dispatch for all reports.
37
- - Docs: public READMEs cleaned up from internal DR-* mentions; anchors and headings aligned (CSV).
106
+ - Docs: public READMEs cleaned up from internal DR-\* mentions; anchors and headings aligned (CSV).
38
107
 
39
108
  ### Fixed
40
- - Minor documentation inaccuracies and anchor mismatches.
41
109
 
110
+ - Minor documentation inaccuracies and anchor mismatches.
42
111
 
43
112
  ## [0.1.2] - 2025-08-13
113
+
44
114
  ### Added
115
+
45
116
  - Languages report: support multiple metrics — `bytes`, `files`, `loc`; dynamic columns in Console/CSV/Markdown; color and percent fields in output.
46
117
  - CLI: `--metric` option for the `languages` report with value validation.
47
118
 
48
119
  ### Changed
120
+
49
121
  - Languages: JSON language reinstated in the mapping and color scheme; sorting and percent calculations are based on the selected metric; percentages rounded to two decimals.
50
122
  - Renderers: updated `csv`, `markdown`, and console renderers to work with dynamic metrics.
51
123
  - Internal specs updated: `docs/output_formats.md`, `docs/cli_spec.md`, `docs/languages_map.md`.
52
124
 
53
125
  ### Fixed
126
+
54
127
  - Git provider: correct commit counting — emit a new commit when a header is read and remove the record separator from the subject (`lib/pretty_git/git/provider.rb`).
55
128
  - RuboCop: targeted suppressions for complex methods/classes and style fixes in `cli_helpers.rb`.
56
129
 
57
130
  ## [0.1.1] - 2025-08-13
131
+
58
132
  ### Changed
133
+
59
134
  - Release automation: added GitHub Actions workflow to publish gem on tags and open PR to Homebrew tap (`.github/workflows/release.yml`).
60
135
  - Documentation: README badges and installation instructions for Homebrew and RubyGems in `README.md` and `README.ru.md`.
61
136
  - Gemspec: bounded runtime dependencies for `csv` and `rexml` to satisfy RubyGems recommendations.
62
137
 
63
138
  ### Fixed
139
+
64
140
  - Homebrew formula installation stability: formula installs gem into `libexec/vendor` and wraps `pretty-git` binary to avoid file collisions on reinstall.
65
141
 
66
142
  ## [0.1.0] - 2025-08-13
143
+
67
144
  ### Added
145
+
68
146
  - Languages report: bytes per language, percentages, sorting, limit.
69
147
  - Console: colorized languages section; terminal width handling via `TerminalWidth`.
70
148
  - Export: languages in Markdown/CSV/JSON/YAML/XML.
@@ -75,10 +153,12 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
75
153
  - Docs: Added screenshot `PrettyGitConsoleLanguages.png`.
76
154
 
77
155
  ### Changed
156
+
78
157
  - Analytics: exclude JSON from language mapping by default to avoid data skew.
79
158
  - Analytics: ignore Python env/cache directories by default.
80
159
  - Refactor: `ConsoleRenderer` split (LanguagesSection, TerminalWidth); reduced complexity.
81
160
  - App: extracted `analytics_for` from `App#run`.
82
161
 
83
162
  ### Fixed
163
+
84
164
  - RuboCop violations in new specs and minor guard clause spacing.
data/README.md CHANGED
@@ -58,6 +58,13 @@ Generator of rich reports for a local Git repository: summary, activity, authors
58
58
  * **Exports**: `console`, `json`, `csv`, `md`, `yaml`, `xml`.
59
59
  * **Output**: to stdout or file via `--out`.
60
60
 
61
+ ## ❓ Why Pretty Git
62
+ * **One tool, many views**: activity, authorship, hotspots, languages, ownership — consistent UX and outputs.
63
+ * **Deterministic results**: stable sorting and formatting make it reliable for CI and diffs.
64
+ * **Format-first**: JSON/CSV/Markdown/YAML/XML out of the box with strict and documented rules.
65
+ * **Fast enough for daily use**: streams `git log` and aggregates in-memory; tips below for large repos.
66
+ * **Safe defaults**: sensible path and binary ignores for the `languages` report; colorized console output with themes.
67
+
61
68
  ## ⚙️ Requirements
62
69
  * **Ruby**: >= 3.4 (recommended 3.4.x)
63
70
  * **Git**: installed and available in `PATH`
@@ -142,12 +149,15 @@ Key options:
142
149
  * **--no-color** Disable colors in console
143
150
  * **--theme** `basic|bright|mono` — console theme (default `basic`; `mono` forces monochrome)
144
151
  * **--metric** `bytes|files|loc` — metric for `languages` report (default `bytes`)
152
+ * **--verbose** Print debug information (effective git command, filters)
145
153
 
146
154
  Examples with multiple values:
147
155
 
148
156
  ```bash
149
- # Multiple branches
157
+ # Multiple branches (treated as explicit revisions)
150
158
  pretty-git summary . --branch main --branch develop
159
+ ## This is equivalent to:
160
+ ## git log main develop -- ...
151
161
 
152
162
  # Filter authors (include/exclude)
153
163
  pretty-git authors . --author alice@example.com --exclude-author bot@company
@@ -157,7 +167,30 @@ pretty-git files . --path app,lib --exclude-path vendor,node_modules
157
167
  ```
158
168
 
159
169
  ### Filters
160
- 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.
170
+ Filters apply at commit fetch and later aggregation. Belowexact semantics and tips.
171
+
172
+ #### Branches / revisions
173
+ * `--branch BRANCH` may be provided multiple times.
174
+ * Multiple branches are treated as explicit revisions to `git log` (no implicit merge-base range). Example: `--branch main --branch develop` → `git log main develop -- ...`.
175
+ * If no branches are specified, the repository’s current `HEAD` is used.
176
+
177
+ #### Authors
178
+ * `--author` and `--exclude-author` accept name or email substrings (case-insensitive match by `git log`).
179
+ * Multiple values may be provided by repeating the option.
180
+
181
+ #### Paths
182
+ * `--path` and `--exclude-path` accept comma-separated values or repeated options.
183
+ * Globs are supported by git pathspec. Excludes are translated to `:(exclude)pattern` and applied consistently.
184
+ * When only excludes are present, `.` is included to ensure the pathspec is valid (mirrors tests in `spec/pretty_git/git/provider_spec.rb`).
185
+
186
+ #### Time period
187
+ * `--since` / `--until`: ISO8601 (e.g. `2025-01-31T12:00:00Z`) or `YYYY-MM-DD`.
188
+ * Date-only values are interpreted as UTC midnight to avoid timezone drift in different environments.
189
+ * Time values are normalized to UTC in outputs.
190
+
191
+ #### Verbose diagnostics
192
+ * `--verbose` prints the effective `git log` command and active filters to stderr.
193
+ * Useful for debugging filters, CI logs, or when reproducing results locally.
161
194
 
162
195
  ### Output format
163
196
  Set via `--format`. For file formats it’s recommended to use `--out`.
@@ -397,6 +430,13 @@ _Example terminal output (theme: basic)._
397
430
  {"report":"summary","generated_at":"2025-01-31T00:00:00Z","totals":{"commits":123}}
398
431
  ```
399
432
 
433
+ ## 🧾 Schemas and Examples
434
+ Machine-readable examples and schemas live under `docs/export_schemas/` and `docs/examples/`.
435
+
436
+ * **Schemas**: see `docs/export_schemas/README.md` for JSON and XML schema notes.
437
+ * **Examples**: example payloads for JSON/XML for each report under `docs/examples/`.
438
+ * Intended use: validation in CI, contract documentation, and integration tests.
439
+
400
440
  ### CSV
401
441
  * **Structure**: flat table, first line is header.
402
442
  * **Encoding**: UTF‑8 without BOM.
@@ -495,16 +535,46 @@ These lists mirror the implementation in `lib/pretty_git/analytics/languages.rb`
495
535
  ## 🔁 Determinism and Sorting
496
536
  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.
497
537
 
538
+ ## ⚡ Performance Tips
539
+ * Prefer narrowing by `--path`/`--exclude-path` and `--since/--until` on large repositories.
540
+ * Use multiple `--branch` only when you explicitly want to include several heads; otherwise rely on current `HEAD`.
541
+ * For CI, cache the repository and fetch shallow history if full history is unnecessary for your report.
542
+
543
+ ## 🤖 CI Usage
544
+ Examples for common pipelines:
545
+
546
+ ```yaml
547
+ # GitHub Actions (excerpt)
548
+ jobs:
549
+ reports:
550
+ runs-on: ubuntu-latest
551
+ steps:
552
+ - uses: actions/checkout@v4
553
+ - uses: ruby/setup-ruby@v1
554
+ with:
555
+ ruby-version: '3.4'
556
+ - run: gem install pretty-git
557
+ - run: pretty-git authors . --format json --out authors.json
558
+ - uses: actions/upload-artifact@v4
559
+ with:
560
+ name: authors-report
561
+ path: authors.json
562
+ ```
563
+
498
564
  ## 🪟 Windows Notes
499
- Primary targets — macOS/Linux. Windows is supported best‑effort:
500
- * Running via Git Bash/WSL is OK
501
- * Colors can be disabled by `--no-color`
502
- * Carefully quote arguments when working with paths
565
+ Primary targets — macOS/Linux. Windows is supported best‑effort. See detailed notes in [docs/windows.md](docs/windows.md).
566
+
567
+ Highlights:
568
+ * Running via Git Bash/WSL is recommended.
569
+ * CRLF output from git is handled by the parser; exports use UTF‑8 with LF.
570
+ * Path filters are normalized to Unicode NFC when available; otherwise pass‑through.
571
+ * Colors can be disabled by `--no-color` or `--theme mono`.
503
572
 
504
573
  ## 🩺 Diagnostics and Errors
505
574
  Typical issues and solutions:
506
575
 
507
576
  * **Unknown report/format** — check the first argument and `--format`.
577
+ * **Debugging** — add `--verbose` to see the effective `git log` command and applied filters.
508
578
  * **Invalid date format** — use ISO8601 or `YYYY-MM-DD` (e.g., `2025-01-31` or `2025-01-31T12:00:00Z`).
509
579
  * **Git not available** — ensure `git` is installed and in the `PATH`.
510
580
  * **Empty result** — check your filters (`--since/--until`, `--branch`, `--path`); your selection might be too narrow.
@@ -527,5 +597,7 @@ bundle exec rubocop
527
597
 
528
598
  Style — RuboCop clean. Tests cover aggregators, renderers, CLI, and integration scenarios (determinism, format correctness).
529
599
 
600
+ For detailed testing strategy, determinism rules, and golden tests workflow (how to run/update snapshots), see `docs/testing.md`.
601
+
530
602
  ## 📄 License
531
603
  MIT © Contributors
data/README.ru.md CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  ## Содержание
22
22
  - [Возможности](#возможности)
23
+ - [Почему Pretty Git](#почему-pretty-git)
23
24
  - [Требования](#требования)
24
25
  - [Установка](#установка)
25
26
  - [Быстрый старт](#быстрый-старт)
@@ -41,11 +42,14 @@
41
42
  - [Экспорт в форматы](#экспорт-в-форматы)
42
43
  - [Console](#console)
43
44
  - [JSON](#json)
45
+ - [Схемы и примеры](#схемы-и-примеры)
44
46
  - [CSV](#csv)
45
47
  - [Markdown](#markdown)
46
48
  - [YAML](#yaml)
47
49
  - [XML](#xml)
48
50
  - [Детерминизм и сортировка](#детерминизм-и-сортировка)
51
+ - [Советы по производительности](#советы-по-производительности)
52
+ - [Использование в CI](#использование-в-ci)
49
53
  - [Советы по Windows](#советы-по-windows)
50
54
  - [Диагностика и ошибки](#диагностика-и-ошибки)
51
55
  - [FAQ](#faq)
@@ -58,6 +62,13 @@
58
62
  * __Экспорт__: `console`, `json`, `csv`, `md`, `yaml`, `xml`.
59
63
  * __Вывод__: в stdout или файл через `--out`.
60
64
 
65
+ ## ❓ Почему Pretty Git
66
+ * __Один инструмент — много представлений__: активность, авторство, риск, языки, владение — единый UX и форматы.
67
+ * __Детерминированные результаты__: стабильные сортировки и форматирование — удобно для CI и диффов.
68
+ * __Сразу форматы__: JSON/CSV/Markdown/YAML/XML из коробки с чёткими правилами.
69
+ * __Достаточно быстро__: потоковый `git log` и ин‑мемори агрегации; советы для больших репозиториев ниже.
70
+ * __Безопасные дефолты__: разумные игноры путей и бинарников для отчёта `languages`; цветной консольный вывод с темами.
71
+
61
72
  ## ⚙️ Требования
62
73
  * __Ruby__: >= 3.4 (рекомендуется 3.4.x)
63
74
  * __Git__: установлен и доступен в `PATH`
@@ -142,12 +153,15 @@ pretty-git <report> <repo_path> [options]
142
153
  * __--no-color__ Отключить цвета в консоли
143
154
  * __--theme__ `basic|bright|mono` — тема оформления консольного вывода (по умолчанию `basic`; `mono` принудительно отключает цвета)
144
155
  * __--metric__ `bytes|files|loc` — метрика для отчёта `languages` (по умолчанию `bytes`)
156
+ * **--verbose** Печатать отладочную информацию (эффективная команда git, применённые фильтры)
145
157
 
146
158
  Примеры значений с несколькими параметрами:
147
159
 
148
160
  ```bash
149
- # Несколько веток
161
+ # Несколько веток (трактуются как явные ревизии)
150
162
  pretty-git summary . --branch main --branch develop
163
+ ## Эквивалентно:
164
+ ## git log main develop -- ...
151
165
 
152
166
  # Фильтрация по авторам (включая/исключая)
153
167
  pretty-git authors . --author alice@example.com --exclude-author bot@company
@@ -157,7 +171,30 @@ pretty-git files . --path app,lib --exclude-path vendor,node_modules
157
171
  ```
158
172
 
159
173
  ### Фильтры
160
- Фильтры применяются на этапе выборки коммитов и последующей агрегации. Формат дат: ISO8601 или `YYYY-MM-DD`. Если часовой пояс не указан используется локальная зона пользователя; на выводе время нормализуется к UTC.
174
+ Фильтры применяются на этапе выборки коммитов и последующей агрегации. Нижеточные правила и советы.
175
+
176
+ #### Ветки / ревизии
177
+ * `--branch BRANCH` можно указывать несколько раз.
178
+ * Несколько веток трактуются как явные ревизии для `git log` (без неявного диапазона от merge-base). Пример: `--branch main --branch develop` → `git log main develop -- ...`.
179
+ * Если ветки не указаны — используется текущий `HEAD` репозитория.
180
+
181
+ #### Авторы
182
+ * `--author` и `--exclude-author` принимают подстроки имени или email (регистронезависимо по логике `git log`).
183
+ * Можно передавать несколько значений повтором опции.
184
+
185
+ #### Пути
186
+ * `--path` и `--exclude-path` принимают значения через запятую или повтором опции.
187
+ * Поддерживаются glob‑маски git pathspec. Исключения переводятся в `:(exclude)pattern` и применяются последовательно.
188
+ * Если заданы только исключения — автоматически добавляется `.` для корректного pathspec (как в тестах `spec/pretty_git/git/provider_spec.rb`).
189
+
190
+ #### Период времени
191
+ * `--since` / `--until`: ISO8601 (например, `2025-01-31T12:00:00Z`) или `YYYY-MM-DD`.
192
+ * Даты без времени интерпретируются как полночь UTC, чтобы исключить сдвиги между средами.
193
+ * На выводе время нормализуется к UTC.
194
+
195
+ #### Подробная диагностика (verbose)
196
+ * `--verbose` печатает эффективную команду `git log` и активные фильтры в stderr.
197
+ * Полезно для отладки фильтров, логов CI и воспроизведения результатов локально.
161
198
 
162
199
  ### Формат вывода
163
200
  Задаётся через `--format`. Для файловых форматов рекомендуется использовать `--out`.
@@ -423,6 +460,13 @@ _Пример вывода в терминале (тема: basic)._
423
460
  {"report":"summary","generated_at":"2025-01-31T00:00:00Z","totals":{"commits":123}}
424
461
  ```
425
462
 
463
+ ## 🧾 Схемы и примеры
464
+ Машиночитаемые примеры и схемы лежат в `docs/export_schemas/` и `docs/examples/`.
465
+
466
+ * __Схемы__: смотрите `docs/export_schemas/README.md` с заметками по JSON и XML схемам.
467
+ * __Примеры__: примеры нагрузок для JSON/XML по каждому отчёту — в `docs/examples/`.
468
+ * Назначение: валидация в CI, документация контрактов и интеграционные тесты.
469
+
426
470
  ### CSV
427
471
  * __Структура__: плоская таблица, первая строка — заголовок.
428
472
  * __Кодировка__: UTF‑8, без BOM.
@@ -496,6 +540,32 @@ _Пример вывода в терминале (тема: basic)._
496
540
  ## 🔁 Детерминизм и сортировка
497
541
  Вывод детерминирован при одинаковых входных данных. Сортировка для файлов/авторов: по количеству изменений (desc), затем по числу коммитов (desc), затем по пути/имени (asc). Лимиты применяются поверх отсортированного списка; значение `all` или `0` означает отсутствие ограничения.
498
542
 
543
+ ## ⚡ Советы по производительности
544
+ * На больших репозиториях сужайте выборку `--path/--exclude-path` и `--since/--until`.
545
+ * Несколько `--branch` используйте, только если нужно включить несколько голов; иначе полагайтесь на текущий `HEAD`.
546
+ * В CI кешируйте репозиторий и используйте shallow‑fetch, если полная история не нужна для выбранного отчёта.
547
+
548
+ ## 🤖 Использование в CI
549
+ Пример для GitHub Actions:
550
+
551
+ ```yaml
552
+ # GitHub Actions (фрагмент)
553
+ jobs:
554
+ reports:
555
+ runs-on: ubuntu-latest
556
+ steps:
557
+ - uses: actions/checkout@v4
558
+ - uses: ruby/setup-ruby@v1
559
+ with:
560
+ ruby-version: '3.4'
561
+ - run: gem install pretty-git
562
+ - run: pretty-git authors . --format json --out authors.json
563
+ - uses: actions/upload-artifact@v4
564
+ with:
565
+ name: authors-report
566
+ path: authors.json
567
+ ```
568
+
499
569
  ## 🪟 Советы по Windows
500
570
  Целевая платформа — macOS/Linux. Windows поддерживается в режиме best‑effort:
501
571
  * Запуск через Git Bash/WSL допустим
@@ -505,7 +575,8 @@ _Пример вывода в терминале (тема: basic)._
505
575
  ## 🩺 Диагностика и ошибки
506
576
  Типичные ошибки и решения:
507
577
 
508
- * __Неизвестный отчёт/формат__ — проверьте значение первого аргумента и `--format`.
578
+ * **Неизвестный отчёт/формат** — проверьте первый аргумент и `--format`.
579
+ * **Отладка** — добавьте `--verbose`, чтобы увидеть фактическую команду `git log` и применённые фильтры.
509
580
  * __Неверный формат даты__ — используйте ISO8601 или `YYYY-MM-DD` (например, `2025-01-31` или `2025-01-31T12:00:00Z`).
510
581
  * __Git недоступен__ — убедитесь, что `git` установлен и доступен в `PATH`.
511
582
  * __Пустой результат__ — проверьте фильтры (`--since/--until`, `--branch`, `--path`), возможно, выборка слишком узкая.
@@ -528,5 +599,7 @@ bundle exec rubocop
528
599
 
529
600
  Стиль — RuboCop без ошибок. Тесты покрывают агрегаторы, рендереры, CLI и интеграционные сценарии (детерминизм, корректность форматов).
530
601
 
602
+ Подробнее о стратегии тестирования, правилах детерминизма и процессе работы с golden‑тестами (как запускать/обновлять снапшоты) см. `docs/testing.md`.
603
+
531
604
  ## 📄 Лицензия
532
605
  MIT © Contributors
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
+ require 'json'
5
+ require 'find'
4
6
 
5
7
  module PrettyGit
6
8
  module Analytics
@@ -79,30 +81,47 @@ module PrettyGit
79
81
  # Default metric: bytes (similar to GitHub Linguist approach).
80
82
  # rubocop:disable Metrics/ClassLength
81
83
  class Languages
84
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
82
85
  def self.call(_enum, filters)
83
86
  repo = filters.repo_path
84
- items = calculate(repo, include_globs: filters.paths, exclude_globs: filters.exclude_paths)
87
+ prof = ENV['PG_PROF'] == '1'
88
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) if prof
85
89
  metric = (filters.metric || 'bytes').to_s
90
+ items = calculate(repo, include_globs: filters.paths, exclude_globs: filters.exclude_paths, metric: metric)
86
91
  totals = compute_totals(items)
87
92
  items = add_percents(items, totals, metric)
88
93
  items = add_colors(items)
89
94
  items = sort_and_limit(items, filters.limit, metric)
90
95
 
91
- build_result(repo, items, totals, metric)
96
+ res = build_result(repo, items, totals, metric)
97
+ if prof
98
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
+ elapsed = (t1 - t0)
100
+ files = totals[:files]
101
+ warn format(
102
+ '[pg_prof] languages: time=%<sec>.3fs files=%<files>d metric=%<metric>s',
103
+ { sec: elapsed, files: files, metric: metric }
104
+ )
105
+ summary = { component: 'languages', time_sec: elapsed, files: files, metric: metric }
106
+ warn("[pg_prof_json] #{summary.to_json}")
107
+ end
108
+ res
92
109
  end
110
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
93
111
 
94
112
  # rubocop:disable Metrics/AbcSize
95
- def self.calculate(repo_path, include_globs:, exclude_globs:)
113
+ def self.calculate(repo_path, include_globs:, exclude_globs:, metric: 'bytes')
96
114
  by_lang = Hash.new { |h, k| h[k] = { bytes: 0, files: 0, loc: 0 } }
115
+ verbose = ENV['PG_VERBOSE'] == '1'
97
116
  Dir.chdir(repo_path) do
98
- each_source_file(include_globs, exclude_globs) do |abs_path|
99
- basename = File.basename(abs_path)
100
- ext = File.extname(abs_path).downcase
117
+ each_source_file(include_globs, exclude_globs) do |path|
118
+ basename = File.basename(path)
119
+ ext = File.extname(path).downcase
101
120
  lang = FILENAME_TO_LANG[basename] || EXT_TO_LANG[ext]
102
121
  next unless lang
103
122
 
104
- size = safe_file_size(abs_path)
105
- lines = safe_count_lines(abs_path)
123
+ size = safe_file_size(path, verbose: verbose)
124
+ lines = metric == 'loc' ? safe_count_lines(path, verbose: verbose) : 0
106
125
  agg = by_lang[lang]
107
126
  agg[:bytes] += size
108
127
  agg[:files] += 1
@@ -113,26 +132,47 @@ module PrettyGit
113
132
  end
114
133
  # rubocop:enable Metrics/AbcSize
115
134
 
116
- def self.each_source_file(include_globs, exclude_globs)
117
- # Build list of files under repo respecting includes/excludes
118
- all = Dir.glob('**/*', File::FNM_DOTMATCH).select { |p| File.file?(p) }
119
- files = all.reject { |p| vendor_path?(p) || binary_ext?(p) }
135
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
136
+ def self.each_source_file(include_globs, exclude_globs, &block)
137
+ # Traverse tree with early prune for vendor/binary paths, then apply include/exclude
138
+ files = []
139
+ Find.find('.') do |path|
140
+ rel = path.sub(%r{^\./}, '')
141
+ # Prune vendor dirs early
142
+ if File.directory?(path)
143
+ dir = File.basename(path)
144
+ if VENDOR_DIRS.include?(dir)
145
+ Find.prune
146
+ next
147
+ end
148
+ next
149
+ end
150
+ next unless File.file?(path)
151
+ next if rel.empty?
152
+ next if vendor_path?(rel) || binary_ext?(rel)
153
+
154
+ files << rel
155
+ end
156
+
120
157
  files = filter_includes(files, include_globs)
121
158
  files = filter_excludes(files, exclude_globs)
122
- files.each { |rel| yield File.expand_path(rel) }
159
+ files.each { |rel| block.call(rel) }
123
160
  end
161
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
124
162
 
125
- def self.safe_file_size(path)
163
+ def self.safe_file_size(path, verbose: false)
126
164
  File.size(path)
127
- rescue StandardError
165
+ rescue StandardError => e
166
+ warn("Warning: failed to read size of #{path}: #{e.message}") if verbose
128
167
  0
129
168
  end
130
169
 
131
- def self.safe_count_lines(path)
170
+ def self.safe_count_lines(path, verbose: false)
132
171
  count = 0
133
172
  File.foreach(path) { |_l| count += 1 }
134
173
  count
135
- rescue StandardError
174
+ rescue StandardError => e
175
+ warn("Warning: failed to count lines in #{path}: #{e.message}") if verbose
136
176
  0
137
177
  end
138
178
 
@@ -141,7 +181,8 @@ module PrettyGit
141
181
  return files if globs.empty?
142
182
 
143
183
  allowed = globs.flat_map { |g| Dir.glob(g) }
144
- files.select { |f| allowed.include?(f) }
184
+ allowed_map = allowed.each_with_object({}) { |f, h| h[f] = true }
185
+ files.select { |f| allowed_map[f] }
145
186
  end
146
187
 
147
188
  def self.filter_excludes(files, globs)
@@ -149,7 +190,8 @@ module PrettyGit
149
190
  return files if globs.empty?
150
191
 
151
192
  blocked = globs.flat_map { |g| Dir.glob(g) }
152
- files.reject { |f| blocked.include?(f) }
193
+ blocked_map = blocked.each_with_object({}) { |f, h| h[f] = true }
194
+ files.reject { |f| blocked_map[f] }
153
195
  end
154
196
 
155
197
  def self.vendor_path?(path)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
3
4
  require_relative 'git/provider'
4
5
  require_relative 'analytics/summary'
5
6
  require_relative 'analytics/activity'
@@ -37,7 +38,9 @@ module PrettyGit
37
38
  private
38
39
 
39
40
  def ensure_repo!(path)
40
- return if File.directory?(File.join(path, '.git'))
41
+ # Use git to reliably detect work-trees/worktrees/bare repos
42
+ stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--is-inside-work-tree', chdir: path)
43
+ return if status.success? && stdout.to_s.strip == 'true'
41
44
 
42
45
  raise ArgumentError, "Not a git repository: #{path}"
43
46
  end
@@ -47,21 +50,25 @@ module PrettyGit
47
50
  end
48
51
 
49
52
  def renderer_for(filters, io)
50
- case filters.format
51
- when 'console'
52
- use_color = !filters.no_color && filters.theme != 'mono'
53
- Render::ConsoleRenderer.new(io: io, color: use_color, theme: filters.theme)
54
- when 'csv'
55
- Render::CsvRenderer.new(io: io)
56
- when 'md'
57
- Render::MarkdownRenderer.new(io: io)
58
- when 'yaml'
59
- Render::YamlRenderer.new(io: io)
60
- when 'xml'
61
- Render::XmlRenderer.new(io: io)
62
- else
63
- Render::JsonRenderer.new(io: io)
53
+ if filters.format == 'console'
54
+ return Render::ConsoleRenderer.new(
55
+ io: io,
56
+ color: !filters.no_color && filters.theme != 'mono',
57
+ theme: filters.theme
58
+ )
64
59
  end
60
+
61
+ dispatch = {
62
+ 'csv' => Render::CsvRenderer,
63
+ 'md' => Render::MarkdownRenderer,
64
+ 'yaml' => Render::YamlRenderer,
65
+ 'xml' => Render::XmlRenderer,
66
+ 'json' => Render::JsonRenderer
67
+ }
68
+ klass = dispatch[filters.format]
69
+ raise ArgumentError, "Unknown format: #{filters.format}" unless klass
70
+
71
+ klass.new(io: io)
65
72
  end
66
73
 
67
74
  def analytics_for(report, enum, filters)