git-pandas 2.6.0__tar.gz → 2.7.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.
Files changed (129) hide show
  1. git_pandas-2.7.0/.obvious/codebase-map.md +18 -0
  2. git_pandas-2.7.0/.obvious/config.yml +6 -0
  3. git_pandas-2.7.0/.obvious/obvious.md +97 -0
  4. git_pandas-2.7.0/.obvious/skills/local-dev/SKILL.md +76 -0
  5. {git_pandas-2.6.0 → git_pandas-2.7.0}/CHANGELOG.md +15 -0
  6. {git_pandas-2.6.0 → git_pandas-2.7.0}/PKG-INFO +8 -18
  7. {git_pandas-2.6.0 → git_pandas-2.7.0}/README.md +7 -17
  8. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/project.py +19 -1
  9. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/repository.py +70 -5
  10. {git_pandas-2.6.0 → git_pandas-2.7.0}/pyproject.toml +1 -1
  11. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_advanced.py +1 -0
  12. git_pandas-2.7.0/tests/test_Project/test_date_source.py +75 -0
  13. git_pandas-2.7.0/tests/test_Repository/test_date_source.py +130 -0
  14. git_pandas-2.7.0/tests/test_by_validation.py +79 -0
  15. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_management.py +1 -1
  16. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cached_signatures.py +2 -0
  17. {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/project_overview.mdc +0 -0
  18. {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/pytest_standards.mdc +0 -0
  19. {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/python_standards.mdc +0 -0
  20. {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/sphinx_docs_standards.mdc +0 -0
  21. {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/testing.mdc +0 -0
  22. {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/ISSUE_TEMPLATE.md +0 -0
  23. {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/docs.yml +0 -0
  25. {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/pypi-publish.yml +0 -0
  26. {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/test-docs-build.yml +0 -0
  27. {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/test-suite.yml +0 -0
  28. {git_pandas-2.6.0 → git_pandas-2.7.0}/.gitignore +0 -0
  29. {git_pandas-2.6.0 → git_pandas-2.7.0}/CONTRIBUTING.md +0 -0
  30. {git_pandas-2.6.0 → git_pandas-2.7.0}/LICENSE.md +0 -0
  31. {git_pandas-2.6.0 → git_pandas-2.7.0}/Makefile +0 -0
  32. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/AUDIT_REPORT.md +0 -0
  33. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/Makefile +0 -0
  34. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/make.bat +0 -0
  35. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/requirements.txt +0 -0
  36. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/CNAME +0 -0
  37. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/_static/custom.css +0 -0
  38. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/cache.rst +0 -0
  39. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/conf.py +0 -0
  40. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/contributors.rst +0 -0
  41. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/index.rst +0 -0
  42. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/performance.rst +0 -0
  43. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/project.rst +0 -0
  44. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/remote_operations.rst +0 -0
  45. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/repository.rst +0 -0
  46. {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/usecases.rst +0 -0
  47. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/__init__.py +0 -0
  48. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/attributes.py +0 -0
  49. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/bus_analysis.py +0 -0
  50. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cache_management.py +0 -0
  51. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cache_timestamps.py +0 -0
  52. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cloud_repo.py +0 -0
  53. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/commit_history.py +0 -0
  54. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cumulative_blame.py +0 -0
  55. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/definitions.py +0 -0
  56. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/file_change_rates.py +0 -0
  57. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/hours_estimate.py +0 -0
  58. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/cumulative_blame.png +0 -0
  59. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/cumulative_project_blame.png +0 -0
  60. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/githubblame.png +0 -0
  61. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/lifeline.png +0 -0
  62. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/stravalib_cumulative_blame.png +0 -0
  63. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/lifeline.py +0 -0
  64. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/parallel_blame.py +0 -0
  65. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/project_blame.py +0 -0
  66. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/punchcard.py +0 -0
  67. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/release_analytics.py +0 -0
  68. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/remote_fetch_and_cache_warming.py +0 -0
  69. {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/repo_file_detail.py +0 -0
  70. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/__init__.py +0 -0
  71. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/cache.py +0 -0
  72. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/logging.py +0 -0
  73. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/utilities/__init__.py +0 -0
  74. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/utilities/check_api.py +0 -0
  75. {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/utilities/plotting.py +0 -0
  76. {git_pandas-2.6.0 → git_pandas-2.7.0}/img/cumulative_blame.png +0 -0
  77. {git_pandas-2.6.0 → git_pandas-2.7.0}/img/lifeline.png +0 -0
  78. {git_pandas-2.6.0 → git_pandas-2.7.0}/img/punchcard.png +0 -0
  79. {git_pandas-2.6.0 → git_pandas-2.7.0}/mcp_server/README.md +0 -0
  80. {git_pandas-2.6.0 → git_pandas-2.7.0}/mcp_server/server.py +0 -0
  81. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/__init__.py +0 -0
  82. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/conftest.py +0 -0
  83. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/__init__.py +0 -0
  84. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_blame_file_detail_empty.py +0 -0
  85. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_blame_multi_repo.py +0 -0
  86. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_bus_factor.py +0 -0
  87. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_commits_in_tags.py +0 -0
  88. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_cumulative_blame_output_shape.py +0 -0
  89. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_default_branch_detection.py +0 -0
  90. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_edge_cases.py +0 -0
  91. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_error_handling.py +0 -0
  92. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_file_detail_author.py +0 -0
  93. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_github_profile.py +0 -0
  94. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_project_limit_allocation.py +0 -0
  95. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_properties.py +0 -0
  96. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_punchcard.py +0 -0
  97. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_revs_num_datapoints.py +0 -0
  98. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/__init__.py +0 -0
  99. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_advanced.py +0 -0
  100. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_bus_factor.py +0 -0
  101. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_commits_in_tags.py +0 -0
  102. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_cumulative_blame_duplicate_dates.py +0 -0
  103. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_cumulative_blame_output_shape.py +0 -0
  104. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_edge_cases.py +0 -0
  105. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_error_handling.py +0 -0
  106. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_change_history_values.py +0 -0
  107. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_detail.py +0 -0
  108. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_operations.py +0 -0
  109. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_owner_author.py +0 -0
  110. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_hours_estimate.py +0 -0
  111. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_properties.py +0 -0
  112. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_punchcard.py +0 -0
  113. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_rev_to_rev.py +0 -0
  114. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_revs_num_datapoints.py +0 -0
  115. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_bulk_fetch_warm.py +0 -0
  116. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache.py +0 -0
  117. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_immutability.py +0 -0
  118. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_integration.py +0 -0
  119. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_key_consistency.py +0 -0
  120. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_threading.py +0 -0
  121. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_warming.py +0 -0
  122. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_examples.py +0 -0
  123. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_logging.py +0 -0
  124. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_mcp_serialization.py +0 -0
  125. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_remote_fetch.py +0 -0
  126. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_repository_cache.py +0 -0
  127. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_utilities/test_check_api.py +0 -0
  128. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_utilities/test_init.py +0 -0
  129. {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_utilities/test_plotting.py +0 -0
@@ -0,0 +1,18 @@
1
+ # git-pandas — Codebase Map
2
+
3
+ Folder-level overview (depth 2). `wdm0006/git-pandas`, default branch `master`, library v2.5.0.
4
+
5
+ | Path | What it is |
6
+ |---|---|
7
+ | `gitpandas/` | The library package. `repository.py` (~3.1k lines) — `Repository` class: single-repo metrics (commit history, blame, cumulative blame, bus factor, hours estimate, rev-to-rev, cache warming, remote fetch). `project.py` (~1.6k lines) — `ProjectDirectory` (multi-repo discovery + aggregation) and `GitHubProfile`. `cache.py` (~860 lines) — `EphemeralCache`, `DiskCache`, `RedisDFCache` + the `_string_handle_to_df` memoization decorator every metric goes through. `logging.py` — verbose-mode logger. |
8
+ | `gitpandas/utilities/` | `plotting.py` — chart helpers (punchcard, lifeline, cumulative blame); `check_api.py` — API-parity checker (CONTRIBUTING asks for Repository/ProjectDirectory parity). |
9
+ | `tests/` | pytest suite. `test_Repository/`, `test_Project/`, `test_utilities/` mirror the package; root-level files cover cache behavior (immutability, threading, warming, key consistency, management), remote fetch, MCP serialization (mocks the `mcp` import — runs without mcp installed), logging, examples smoke (`test_examples.py`, marked `slow`). `conftest.py` builds throwaway git repos in tmp dirs. |
10
+ | `examples/` | One runnable script per analysis (commit_history, bus_analysis, cumulative_blame, punchcard, …). Run from repo root: `make run-example example=<name>`. `definitions.py` locates the git-pandas checkout for self-analysis. |
11
+ | `mcp_server/` | FastMCP **stdio** server exposing gitpandas metrics as MCP tools. Own README. Needs `mcp[cli]<2` + uvicorn (PEP 723 block, not in extras). |
12
+ | `docs/` | Sphinx docs with their own `Makefile` and `requirements.txt`. |
13
+ | `.github/workflows/` | `test-suite.yml` — CI matrix py 3.10–3.12 × pandas 2/3, runs `pytest -m "not slow"` + `ruff check`; `test-docs-build.yml` — PR docs check; `docs.yml` — publishes docs from master; `pypi-publish.yml` — sdist to PyPI on release. |
14
+ | `.cursor/rules/` | Agent/editor guidance: python, pytest, sphinx-docs, testing standards + a project overview. |
15
+ | `img/`, `examples/img/` | Images used by README and examples. |
16
+
17
+ **Key invariants** (from CONTRIBUTING.md): maintain feature/API parity between `Repository` and
18
+ `ProjectDirectory`; sphinx-format docstrings; memory-heavy functions take a `limit` option.
@@ -0,0 +1,6 @@
1
+ # Repo policy for obvious workers.
2
+ # Auto-detected from GitHub repo settings (wdm0006/git-pandas) on 2026-09-06.
3
+ defaultBranch: master
4
+ # All three merge methods are enabled on this repo; squash keeps setup PRs to one commit.
5
+ mergeMethod: squash
6
+ requireHumanMerge: true
@@ -0,0 +1,97 @@
1
+ # git-pandas — Agent Guide
2
+
3
+ **Repo:** `wdm0006/git-pandas` · **Default branch:** `master` · **Language:** Python (library, v2.5.0)
4
+
5
+ git-pandas turns Git repository data into pandas DataFrames — a pure Python library with an
6
+ optional MCP stdio server. **No web app, no database, no listening ports, no long-running
7
+ services.** "Local dev is healthy" means: venv installed, test suite green, lint clean,
8
+ examples run.
9
+
10
+ ## Stack
11
+
12
+ - **Runtime:** Python ≥ 3.10 (CI matrix: 3.10 / 3.11 / 3.12 × pandas 2.x / 3.x)
13
+ - **Package manager:** uv (hatchling build backend; `uv.lock` is gitignored — resolution is fresh from `pyproject.toml`)
14
+ - **Core deps:** gitpython, numpy, pandas (≥2.0), requests
15
+ - **Tooling:** pytest (+ pytest-cov, pytest-mock), ruff (lint + format), sphinx, coverage
16
+ - **Parallelism:** joblib — ⚠️ only in the `dev` extra, **not** in `all`
17
+ - **Optional:** redis (RedisDFCache — its tests are fully mocked, no server needed), matplotlib / lifelines (examples)
18
+ - **MCP server:** `mcp[cli]<2` + uvicorn — in no extras group; declared only in the PEP 723 block of `mcp_server/server.py`
19
+
20
+ ## Environment (baked into the sandbox snapshot)
21
+
22
+ - `.venv/` at repo root: uv venv on CPython 3.12.14 (uv-managed), git-pandas 2.5.0 installed
23
+ editable with extras `dev` + `all`, plus `mcp[cli]<2` (1.29.1) and uvicorn
24
+ - uv 0.12.10 at `~/.local/bin` (add to PATH: `export PATH="$HOME/.local/bin:$PATH"`)
25
+ - git identity configured globally — required, tests create repos and make commits
26
+ - Activate: `source .venv/bin/activate` (or prefix commands with `uv run`)
27
+
28
+ ## Commands
29
+
30
+ | Task | Command |
31
+ |---|---|
32
+ | Fresh install | `uv venv --python 3.12 && uv pip install -e ".[dev,all]"` |
33
+ | Tests (CI parity) | `MPLBACKEND=Agg python -m pytest -m "not slow"` |
34
+ | Tests with coverage (Makefile) | `make test` (reinstalls `.[all]` first — see gotcha #1) |
35
+ | Single test | `make test-single test=tests/test_Repository/test_properties.py` |
36
+ | Lint (read-only, CI gate) | `python -m ruff check .` |
37
+ | Lint (⚠️ mutates files) | `make lint` — `--fix --unsafe-fixes`; run only when asked |
38
+ | Format | `make format` |
39
+ | Run an example | `make run-example example=commit_history` |
40
+ | MCP server (stdio) | `make mcp` → `python mcp_server/server.py` |
41
+ | Docs build | `make docs` (CI also installs `docs/requirements.txt` first) |
42
+
43
+ ## Gotchas
44
+
45
+ 1. **joblib is only in the `dev` extra.** `uv pip install -e ".[all]"` alone → 14 test failures
46
+ in `parallel_cumulative_blame` / bulk-fetch tests ("Joblib not installed"). CI installs
47
+ `.[dev]`; the Makefile's `make test` path installs `.[all]`. Install `.[dev,all]` to cover both.
48
+ 2. **`make lint` auto-fixes** and can rewrite source files. Use `python -m ruff check .` for a
49
+ read-only gate (that's what CI runs).
50
+ 3. **MCP server needs mcp v1 API** (`from mcp.server.fastmcp import FastMCP`). mcp 2.x renamed
51
+ FastMCP → MCPServer and breaks the import. Pin `mcp[cli]<2`.
52
+ 4. MCP server scans `~/Documents` by default (`Config.SCAN_ROOT_DIR`, hardcoded — not env-configurable).
53
+ 5. Redis tests are mocked (`@patch` on `gitpandas.cache`) — no Redis server is needed; the
54
+ `redis` pytest marker is informational.
55
+ 6. Tests marked `remote` fetch from github.com — network required. `slow` (example smoke tests)
56
+ is deselected by the default `-m "not slow"`.
57
+ 7. `make test-all` has a typo (`MPLBACKEmND=Agg`) — harmless, but the env var isn't set.
58
+ 8. The Makefile `gitnoc` target points at a `gitnoc/` directory that does not exist in this repo.
59
+ 9. `.gitignore` ignores `uv.lock` and `scratch`; `.venv`/`.pytest_cache`/`.ruff_cache` are kept
60
+ out of git via `.git/info/exclude` in the sandbox, not via `.gitignore`.
61
+
62
+ ## Environment variables
63
+
64
+ None required for tests, lint, or examples. `GitHubProfile` (in `gitpandas/project.py`) queries
65
+ the public GitHub REST API over the network via `requests` — no token is read by the library.
66
+
67
+ ## Codebase map
68
+
69
+ See [`codebase-map.md`](codebase-map.md).
70
+
71
+ ## Local verification
72
+
73
+ The onboarding run's evidence is in the **Local Verification Summary** below and in
74
+ [`skills/local-dev/SKILL.md`](skills/local-dev/SKILL.md).
75
+
76
+ ## Sandbox snapshot
77
+
78
+ - **Template/snapshot ID:** `b04vxdj9qvlfbrzgb205:default`
79
+ - **Captured:** 2026-09-06T20:25:59.276Z (UTC), from live session `izj5y29hgyt91gu7jjefe`
80
+ - **Contains:** everything in "Environment" above — uv 0.12.10, uv-managed CPython 3.12.14,
81
+ repo at `/home/user/work/git-pandas` with the populated `.venv`, global git identity
82
+
83
+ ## Local Verification Summary
84
+
85
+ **Run:** autobuild onboarding, 2026-09-06 (UTC), against `master` @ `e75870c` ("Preserve project revision limit remainder (#87)")
86
+ **Stack:** CPython 3.12.14 · pandas 3.0.5 · numpy 2.5.3 · git 2.47.3 · uv 0.12.10
87
+
88
+ | Check | Result |
89
+ |---|---|
90
+ | `MPLBACKEND=Agg python -m pytest -m "not slow"` | ✅ **572 passed**, 1 deselected (slow), 2 warnings, 0 failed — exit 0, ~160 s |
91
+ | `python -m ruff check .` | ✅ All checks passed |
92
+ | `make run-example example=commit_history` | ✅ exit 0 — committer stats, file-change and per-extension frames printed |
93
+ | Library API (`Repository`) | ✅ `commit_history` (5 rows), `blame(by="repository")` (5 committers), `bus_factor(by="file")` (120 rows), `bus_factor(by="repository")` (bus factor 2), `hours_estimate` (6 rows) |
94
+ | Library API (`ProjectDirectory`) | ✅ `commit_history` (3 rows, `repository` column populated) |
95
+ | MCP server | ✅ stdio JSON-RPC `initialize` + `tools/list` handshake OK (serverInfo: GitPandas MCP Server; tools incl. `list_available_repos`, `repo_branches`) |
96
+
97
+ Dev stack healthy: **yes**. No services to keep alive — the venv *is* the dev stack.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: local-dev
3
+ description: Stand up and verify a working local dev environment for git-pandas (uv venv, pytest, ruff, examples, MCP server) — recorded from the 2026-09-06 onboarding run
4
+ ---
5
+
6
+ # Local dev onboarding — git-pandas
7
+
8
+ Durable record of the autobuild onboarding run (2026-09-06 UTC) against `master` @ `e75870c`,
9
+ in sandbox `cmp_aPYkTByo`. The snapshot for that sandbox already contains a working
10
+ environment — this skill also documents how to rebuild it from a bare checkout.
11
+
12
+ ## What "healthy" means here
13
+
14
+ git-pandas is a pure Python library. There is no app to serve, no database, no ports.
15
+ A healthy dev stack = venv with the right extras + green test suite + clean lint + a
16
+ runnable example. Evidence below.
17
+
18
+ ## Fresh setup (from a bare checkout)
19
+
20
+ ```bash
21
+ # 1. uv (package manager the repo standardizes on — Makefile + CI both use it)
22
+ curl -LsSf https://astral.sh/uv/install.sh | sh
23
+ export PATH="$HOME/.local/bin:$PATH"
24
+
25
+ # 2. git identity — tests create repos and make commits
26
+ git config --global user.email "dev@example.com"
27
+ git config --global user.name "Dev"
28
+
29
+ # 3. venv on 3.12 (matches the CI matrix; uv downloads its managed CPython)
30
+ uv venv --python 3.12
31
+
32
+ # 4. deps — dev AND all. dev is not optional: joblib + pytest-mock live only there
33
+ uv pip install -e ".[dev,all]"
34
+
35
+ # 5. optional, only if you need the MCP server entry point (make mcp)
36
+ uv pip install "mcp[cli]<2" uvicorn
37
+ ```
38
+
39
+ ## Verification checklist (all must pass)
40
+
41
+ 1. `source .venv/bin/activate`
42
+ 2. `MPLBACKEND=Agg python -m pytest -m "not slow"` → expect **572 passed, 1 deselected
43
+ (slow), 0 failed** (~160 s; tests marked `remote` fetch from github.com — network required)
44
+ 3. `python -m ruff check .` → "All checks passed!"
45
+ 4. `MPLBACKEND=Agg python examples/commit_history.py` → exit 0, prints committer stats and
46
+ file-change frames analyzing the git-pandas repo itself
47
+ 5. Optional MCP smoke: pipe an `initialize` + `tools/list` JSON-RPC pair into
48
+ `python mcp_server/server.py` and check for a `serverInfo` response
49
+
50
+ ## Known traps (cost real time during onboarding)
51
+
52
+ - **joblib only in `dev` extra.** Installing `.[all]` (what `make test` does via `setup-all`)
53
+ yields 14 failures in `parallel_cumulative_blame` + bulk-fetch tests, all logging
54
+ "Joblib not installed". Fix: install `.[dev,all]`. CI avoids this by installing `.[dev]`.
55
+ - **mcp 2.x breaks the server.** `mcp_server/server.py` imports the v1 API
56
+ (`mcp.server.fastmcp.FastMCP`); mcp 2.x renamed it to `MCPServer`. Pin `mcp[cli]<2`
57
+ (onboarding used 1.29.1). The pytest suite never hits this — `test_mcp_serialization.py`
58
+ stubs the import.
59
+ - **`make lint` mutates** (`ruff check --fix --unsafe-fixes`). The CI gate is the read-only
60
+ `python -m ruff check .` — use that unless asked to auto-fix.
61
+ - API signatures to not mis-guess (verified): `blame(rev, committer, by, ignore_globs,
62
+ include_globs)` — no `exts` kwarg; `bus_factor(by, ignore_globs, include_globs)` — no
63
+ `limit` kwarg; `ProjectDirectory(working_dir)` — no `max_depth`, and it has no
64
+ `repositories()` attribute.
65
+
66
+ ## Evidence from the onboarding run
67
+
68
+ - CPython 3.12.14 · pandas 3.0.5 · numpy 2.5.3 · git 2.47.3 · uv 0.12.10 · gitpandas 2.5.0 (editable)
69
+ - pytest `-m "not slow"`: **572 passed, 1 deselected, 2 warnings, 0 failed**, exit 0 (~160 s)
70
+ - ruff check: all checks passed
71
+ - `examples/commit_history.py`: exit 0 — repository + project-directory analyses printed
72
+ - Direct API: `commit_history`, `blame(by="repository")`, `bus_factor(by="file")` (120 rows),
73
+ `bus_factor(by="repository")` (2), `hours_estimate` (6 rows), `ProjectDirectory.commit_history`
74
+ - MCP: stdio JSON-RPC initialize + tools/list handshake OK with mcp 1.29.1
75
+ - Snapshot baked after all checks passed: template `b04vxdj9qvlfbrzgb205:default`
76
+ @ 2026-09-06T20:25:59.276Z
@@ -1,6 +1,21 @@
1
1
  Unreleased
2
2
  ==========
3
3
 
4
+ v2.7.0
5
+ ======
6
+
7
+ ## New Features
8
+
9
+ ### Author-Date Commit Timing
10
+
11
+ * **NEW**: `Repository.commit_history()`, `Repository.hours_estimate()` and the matching `ProjectDirectory` methods accept `date_source="committer"` (default) or `"author"`. With `"author"` the `date` index, the `days` cutoff and the session grouping in `hours_estimate` use the author timestamp, so rebased, cherry-picked or squash-merged history (where every rewritten commit shares one committer timestamp) no longer collapses into a single session. Any other value raises `ValueError`. Default output is unchanged. `date_source` is part of the cache key, so `RedisDFCache` entries written by earlier versions are simply not matched; flush them if you share a cache across versions.
12
+
13
+ ## Bug Fixes
14
+
15
+ ### Blame and Bus Factor Validation
16
+
17
+ * **FIXED**: Repository and project blame/bus-factor methods reject invalid `by` values with a `ValueError` naming the allowed values before accessing Git or caches.
18
+
4
19
  v2.6.0
5
20
  ======
6
21
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: git-pandas
3
- Version: 2.6.0
3
+ Version: 2.7.0
4
4
  Summary: A utility for interacting with data from git repositories as Pandas dataframes
5
5
  Project-URL: Documentation, https://gitpandas.mcginniscommawill.com
6
6
  Project-URL: Source, https://github.com/wdm0006/git-pandas
@@ -55,23 +55,13 @@ Git-Pandas is a powerful Python library that transforms Git repository data into
55
55
 
56
56
  ![Cumulative Blame](https://raw.githubusercontent.com/wdm0006/git-pandas/master/examples/img/githubblame.png)
57
57
 
58
- ## What's New in v2.6.0
59
-
60
- - **📦 Smaller Default Install**: `redis` and `coverage` are no longer installed by
61
- `pip install git-pandas`. Use `pip install "git-pandas[redis]"` for `RedisDFCache`
62
- and `pip install "git-pandas[coverage]"` for `Repository.coverage()`.
63
- - **🐼 pandas 3 Support**: The `pandas<3` cap is gone; the suite runs green against
64
- pandas 2 and pandas 3.
65
- - **📈 Usable Cumulative Blame**: `cumulative_blame()` and `parallel_cumulative_blame()`
66
- now return only contributor columns on an ascending `DatetimeIndex`, so `df.sum(axis=1)`,
67
- date slicing, and `.diff()` work as expected.
68
- - **🎯 Exact `num_datapoints`**: `revs(num_datapoints=N)` returns exactly `N` evenly
69
- spaced revisions instead of over-sampling.
70
- - **🧮 Honest Project Limits**: `ProjectDirectory.commit_history()`, `file_change_history()`,
71
- `hours_estimate()` and `revs()` now split a project-wide `limit` across repositories without
72
- dropping the remainder, so small limits no longer return empty or short frames.
73
- - **🔢 Finite Punchcards**: `punchcard(normalize=...)` no longer emits `NaN` for an
74
- all-zero metric.
58
+ ## What's New in v2.7.0
59
+
60
+ - **🕰️ Author-Date Timing**: `commit_history()` and `hours_estimate()` (on `Repository` and
61
+ `ProjectDirectory`) accept `date_source="author"` so rebased, cherry-picked or squash-merged
62
+ history is timed by when work was authored. The default (`"committer"`) is unchanged.
63
+ - **🛡️ Clear `by=` Errors**: `blame()` and `bus_factor()` now raise a `ValueError` naming the
64
+ allowed values instead of `UnboundLocalError` or returning `None`.
75
65
 
76
66
  See the [changelog](CHANGELOG.md) for the full list, including the migration notes for
77
67
  the `cumulative_blame` and `revs` output changes.
@@ -7,23 +7,13 @@ Git-Pandas is a powerful Python library that transforms Git repository data into
7
7
 
8
8
  ![Cumulative Blame](https://raw.githubusercontent.com/wdm0006/git-pandas/master/examples/img/githubblame.png)
9
9
 
10
- ## What's New in v2.6.0
11
-
12
- - **📦 Smaller Default Install**: `redis` and `coverage` are no longer installed by
13
- `pip install git-pandas`. Use `pip install "git-pandas[redis]"` for `RedisDFCache`
14
- and `pip install "git-pandas[coverage]"` for `Repository.coverage()`.
15
- - **🐼 pandas 3 Support**: The `pandas<3` cap is gone; the suite runs green against
16
- pandas 2 and pandas 3.
17
- - **📈 Usable Cumulative Blame**: `cumulative_blame()` and `parallel_cumulative_blame()`
18
- now return only contributor columns on an ascending `DatetimeIndex`, so `df.sum(axis=1)`,
19
- date slicing, and `.diff()` work as expected.
20
- - **🎯 Exact `num_datapoints`**: `revs(num_datapoints=N)` returns exactly `N` evenly
21
- spaced revisions instead of over-sampling.
22
- - **🧮 Honest Project Limits**: `ProjectDirectory.commit_history()`, `file_change_history()`,
23
- `hours_estimate()` and `revs()` now split a project-wide `limit` across repositories without
24
- dropping the remainder, so small limits no longer return empty or short frames.
25
- - **🔢 Finite Punchcards**: `punchcard(normalize=...)` no longer emits `NaN` for an
26
- all-zero metric.
10
+ ## What's New in v2.7.0
11
+
12
+ - **🕰️ Author-Date Timing**: `commit_history()` and `hours_estimate()` (on `Repository` and
13
+ `ProjectDirectory`) accept `date_source="author"` so rebased, cherry-picked or squash-merged
14
+ history is timed by when work was authored. The default (`"committer"`) is unchanged.
15
+ - **🛡️ Clear `by=` Errors**: `blame()` and `bus_factor()` now raise a `ValueError` naming the
16
+ allowed values instead of `UnboundLocalError` or returning `None`.
27
17
 
28
18
  See the [changelog](CHANGELOG.md) for the full list, including the migration notes for
29
19
  the `cumulative_blame` and `revs` output changes.
@@ -17,7 +17,7 @@ import requests
17
17
  from git import GitCommandError
18
18
 
19
19
  from gitpandas.logging import logger # Import the logger
20
- from gitpandas.repository import Repository
20
+ from gitpandas.repository import Repository, _validate_date_source
21
21
 
22
22
  try:
23
23
  from joblib import Parallel, delayed
@@ -357,6 +357,7 @@ class ProjectDirectory:
357
357
  by=None,
358
358
  ignore_globs=None,
359
359
  include_globs=None,
360
+ date_source="committer",
360
361
  ):
361
362
  """
362
363
  Returns a DataFrame containing the estimated hours spent by each committer/author.
@@ -372,10 +373,12 @@ class ProjectDirectory:
372
373
  by (Optional[str]): How to group results. One of None, 'committer', 'author'
373
374
  ignore_globs (Optional[List[str]]): List of glob patterns for files to ignore
374
375
  include_globs (Optional[List[str]]): List of glob patterns for files to include
376
+ date_source (str): Timestamp used to order and group commits: "committer" (default) or "author"
375
377
 
376
378
  Returns:
377
379
  DataFrame: DataFrame with hours estimates
378
380
  """
381
+ _validate_date_source(date_source)
379
382
  logger.info(f"Estimating hours for branch '{branch or self.default_branch}'.")
380
383
  if branch is None:
381
384
  branch = self.default_branch
@@ -397,6 +400,7 @@ class ProjectDirectory:
397
400
  committer=committer,
398
401
  ignore_globs=ignore_globs,
399
402
  include_globs=include_globs,
403
+ date_source=date_source,
400
404
  )
401
405
  if not ch.empty:
402
406
  ch = ch.copy() # Avoid SettingWithCopyWarning
@@ -424,6 +428,7 @@ class ProjectDirectory:
424
428
  days=None,
425
429
  ignore_globs=None,
426
430
  include_globs=None,
431
+ date_source="committer",
427
432
  ):
428
433
  """
429
434
  Returns a DataFrame containing the commit history for all repositories.
@@ -434,10 +439,12 @@ class ProjectDirectory:
434
439
  days (Optional[int]): If provided, only return commits from last N days
435
440
  ignore_globs (Optional[List[str]]): List of glob patterns for files to ignore
436
441
  include_globs (Optional[List[str]]): List of glob patterns for files to include
442
+ date_source (str): Timestamp used for the date index and ``days`` cutoff: "committer" (default) or "author"
437
443
 
438
444
  Returns:
439
445
  DataFrame: DataFrame with commit history
440
446
  """
447
+ _validate_date_source(date_source)
441
448
  logger.info(f"Generating commit history for branch '{branch or self.default_branch}'.")
442
449
  if branch is None:
443
450
  branch = self.default_branch
@@ -455,6 +462,7 @@ class ProjectDirectory:
455
462
  days=days,
456
463
  ignore_globs=ignore_globs,
457
464
  include_globs=include_globs,
465
+ date_source=date_source,
458
466
  )
459
467
  if not ch.empty:
460
468
  ch = ch.copy() # Avoid SettingWithCopyWarning
@@ -620,11 +628,16 @@ class ProjectDirectory:
620
628
  The repository is part of the grouping because file paths are
621
629
  repository-relative and would otherwise collide across repositories.
622
630
 
631
+ Raises:
632
+ ValueError: If by is not one of the supported grouping values.
633
+
623
634
  Note:
624
635
  Results are sorted by lines of code in descending order.
625
636
  If both ignore_globs and include_globs are provided, files must match an include pattern
626
637
  and not match any ignore patterns to be included.
627
638
  """
639
+ if by not in ("repository", "file"):
640
+ raise ValueError(f"by must be one of ('repository', 'file'); got {by!r}")
628
641
  logger.info(f"Calculating blame grouped by {'committer' if committer else 'author'} and '{by}'.")
629
642
  df = None
630
643
 
@@ -1086,10 +1099,15 @@ class ProjectDirectory:
1086
1099
  - bus factor (int): Bus factor for that file
1087
1100
  - repository (str): Repository name
1088
1101
 
1102
+ Raises:
1103
+ ValueError: If by is not one of the supported grouping values.
1104
+
1089
1105
  Note:
1090
1106
  A low bus factor (e.g. 1-2) indicates high risk as knowledge is concentrated among
1091
1107
  few contributors. A higher bus factor indicates knowledge is better distributed.
1092
1108
  """
1109
+ if by not in ("projectd", "repository", "file"):
1110
+ raise ValueError(f"by must be one of ('projectd', 'repository', 'file'); got {by!r}")
1093
1111
  logger.info(f"Calculating bus factor grouped by '{by}'.")
1094
1112
  if by == "file":
1095
1113
  # Calculate file-wise bus factor across all repositories
@@ -9,12 +9,15 @@
9
9
  """
10
10
 
11
11
  import fnmatch
12
+ import functools
13
+ import inspect
12
14
  import json
13
15
  import logging
14
16
  import os
15
17
  import shutil
16
18
  import tempfile
17
19
  import time
20
+ from functools import wraps
18
21
 
19
22
  import git # Import the full git module
20
23
  import numpy as np
@@ -34,6 +37,44 @@ except ImportError:
34
37
 
35
38
  __author__ = "willmcginnis"
36
39
 
40
+ DATE_SOURCES = ("committer", "author")
41
+
42
+
43
+ def _validate_date_source(date_source):
44
+ if date_source not in DATE_SOURCES:
45
+ raise ValueError(f"date_source must be one of {DATE_SOURCES}, got {date_source!r}")
46
+ return date_source
47
+
48
+
49
+ def _validates_date_source(func):
50
+ """Reject an invalid ``date_source`` before the cache layer reads or stores anything."""
51
+ sig = inspect.signature(func)
52
+
53
+ @functools.wraps(func)
54
+ def wrapper(*args, **kwargs):
55
+ bound = sig.bind(*args, **{k: v for k, v in kwargs.items() if k != "force_refresh"})
56
+ _validate_date_source(bound.arguments.get("date_source", "committer"))
57
+ return func(*args, **kwargs)
58
+
59
+ return wrapper
60
+
61
+
62
+ def _validates_by(*allowed):
63
+ def decorate(func):
64
+ signature = inspect.signature(func)
65
+
66
+ @wraps(func)
67
+ def validate(self, *args, **kwargs):
68
+ arguments = signature.bind(self, *args, **{k: v for k, v in kwargs.items() if k != "force_refresh"})
69
+ arguments.apply_defaults()
70
+ if arguments.arguments["by"] not in allowed:
71
+ raise ValueError(f"by must be one of {allowed!r}; got {arguments.arguments['by']!r}")
72
+ return func(self, *args, **kwargs)
73
+
74
+ return validate
75
+
76
+ return decorate
77
+
37
78
 
38
79
  def _parallel_cumulative_blame_func(self_, x, committer, ignore_globs, include_globs):
39
80
  blm = self_.blame(
@@ -283,6 +324,7 @@ class Repository:
283
324
  logger.error(f"Unexpected error analyzing coverage data: {e}", exc_info=True)
284
325
  return DataFrame(columns=["filename", "lines_covered", "total_lines", "coverage"])
285
326
 
327
+ @_validates_date_source
286
328
  @multicache(
287
329
  key_prefix="hours_estimate",
288
330
  key_list=[
@@ -294,6 +336,7 @@ class Repository:
294
336
  "committer",
295
337
  "ignore_globs",
296
338
  "include_globs",
339
+ "date_source",
297
340
  ],
298
341
  )
299
342
  def hours_estimate(
@@ -306,6 +349,7 @@ class Repository:
306
349
  committer=True,
307
350
  ignore_globs=None,
308
351
  include_globs=None,
352
+ date_source="committer",
309
353
  ):
310
354
  """
311
355
  inspired by: https://github.com/kimmobrunfeldt/git-hours/blob/8aaeee237cb9d9028e7a2592a25ad8468b1f45e4/index.js#L114-L143
@@ -322,6 +366,9 @@ class Repository:
322
366
  :param committer: (optional, default=True) whether to use committer vs. author
323
367
  :param ignore_globs: (optional, default=None) a list of globs to ignore, default none excludes nothing
324
368
  :param include_globs: (optinal, default=None) a list of globs to include, default of None includes everything.
369
+ :param date_source: (optional, default="committer") which timestamp orders and groups commits, "committer" or
370
+ "author". Use "author" for rebased, cherry-picked or squash-merged history, where every rewritten commit
371
+ shares one committer timestamp.
325
372
  :return: DataFrame
326
373
  """
327
374
  resolved_branch = self.default_branch if branch is None else branch
@@ -338,6 +385,7 @@ class Repository:
338
385
  days=days,
339
386
  ignore_globs=ignore_globs,
340
387
  include_globs=include_globs,
388
+ date_source=date_source,
341
389
  )
342
390
 
343
391
  # split by committer|author
@@ -370,7 +418,11 @@ class Repository:
370
418
  logger.info(f"Finished hours estimation for branch '{branch}'. Found data for {len(df)} contributors.")
371
419
  return df
372
420
 
373
- @multicache(key_prefix="commit_history", key_list=["branch", "limit", "days", "ignore_globs", "include_globs"])
421
+ @_validates_date_source
422
+ @multicache(
423
+ key_prefix="commit_history",
424
+ key_list=["branch", "limit", "days", "ignore_globs", "include_globs", "date_source"],
425
+ )
374
426
  def commit_history(
375
427
  self,
376
428
  branch=None,
@@ -378,6 +430,7 @@ class Repository:
378
430
  days=None,
379
431
  ignore_globs=None,
380
432
  include_globs=None,
433
+ date_source="committer",
381
434
  ):
382
435
  """
383
436
  Returns a DataFrame containing the commit history for a branch.
@@ -391,6 +444,8 @@ class Repository:
391
444
  days (Optional[int]): If provided, only return commits from the last N days
392
445
  ignore_globs (Optional[List[str]]): List of glob patterns for files to ignore
393
446
  include_globs (Optional[List[str]]): List of glob patterns for files to include
447
+ date_source (str): Timestamp used for the ``date`` index and the ``days`` cutoff: "committer"
448
+ (default) or "author". Raises ValueError for anything else.
394
449
 
395
450
  Returns:
396
451
  DataFrame: A DataFrame with columns:
@@ -412,6 +467,8 @@ class Repository:
412
467
  if branch is None:
413
468
  branch = self.default_branch
414
469
 
470
+ date_attr = "authored_date" if date_source == "author" else "committed_date"
471
+
415
472
  logger.info(f"Fetching commit history for branch '{branch}'. Limit: {limit}, Days: {days}")
416
473
 
417
474
  # setup the data-set of commits
@@ -422,7 +479,7 @@ class Repository:
422
479
  [
423
480
  x.author.name,
424
481
  x.committer.name,
425
- x.committed_date,
482
+ getattr(x, date_attr),
426
483
  x.message,
427
484
  x.hexsha,
428
485
  self.__check_extension(
@@ -443,7 +500,7 @@ class Repository:
443
500
  x = next(commits)
444
501
  except StopIteration:
445
502
  break
446
- c_date = x.committed_date
503
+ c_date = getattr(x, date_attr)
447
504
  if c_date > dlim:
448
505
  commit_count += 1
449
506
  if logger.isEnabledFor(logging.DEBUG) and commit_count % 1000 == 0:
@@ -452,7 +509,7 @@ class Repository:
452
509
  [
453
510
  x.author.name,
454
511
  x.committer.name,
455
- x.committed_date,
512
+ getattr(x, date_attr),
456
513
  x.message,
457
514
  x.hexsha,
458
515
  self.__check_extension(
@@ -468,7 +525,7 @@ class Repository:
468
525
  [
469
526
  x.author.name,
470
527
  x.committer.name,
471
- x.committed_date,
528
+ getattr(x, date_attr),
472
529
  x.message,
473
530
  x.hexsha,
474
531
  self.__check_extension(
@@ -1044,6 +1101,7 @@ class Repository:
1044
1101
  logger.debug(f"Finished checking extensions. Filtered files count: {len(out)}")
1045
1102
  return out
1046
1103
 
1104
+ @_validates_by("repository", "file")
1047
1105
  @multicache(key_prefix="blame", key_list=["rev", "committer", "by", "ignore_globs", "include_globs"])
1048
1106
  def blame(
1049
1107
  self,
@@ -1079,6 +1137,9 @@ class Repository:
1079
1137
  - file (str): File path
1080
1138
  - loc (int): Lines of code attributed to that person in that file
1081
1139
 
1140
+ Raises:
1141
+ ValueError: If by is not one of the supported grouping values.
1142
+
1082
1143
  Note:
1083
1144
  Results are sorted by lines of code in descending order.
1084
1145
  If both ignore_globs and include_globs are provided, files must match an include pattern
@@ -2263,6 +2324,7 @@ class Repository:
2263
2324
  """
2264
2325
  return str(self.git_dir)
2265
2326
 
2327
+ @_validates_by("repository", "file")
2266
2328
  @multicache(key_prefix="bus_factor", key_list=["by", "ignore_globs", "include_globs"])
2267
2329
  def bus_factor(self, by="repository", ignore_globs=None, include_globs=None):
2268
2330
  """Calculates the "bus factor" for the repository.
@@ -2288,6 +2350,9 @@ class Repository:
2288
2350
  - bus factor (int): Bus factor for that file
2289
2351
  - repository (str): Repository name
2290
2352
 
2353
+ Raises:
2354
+ ValueError: If by is not one of the supported grouping values.
2355
+
2291
2356
  Note:
2292
2357
  A low bus factor (e.g. 1-2) indicates high risk as knowledge is concentrated among
2293
2358
  few contributors. A higher bus factor indicates knowledge is better distributed.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "git-pandas"
7
- version = "2.6.0"
7
+ version = "2.7.0"
8
8
  description = "A utility for interacting with data from git repositories as Pandas dataframes"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -124,6 +124,7 @@ class TestProjectDirectoryAdvanced:
124
124
  committer=True,
125
125
  ignore_globs=None,
126
126
  include_globs=None,
127
+ date_source="committer",
127
128
  )
128
129
 
129
130
  def test_bus_factor(self, repo_directories):