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.
- git_pandas-2.7.0/.obvious/codebase-map.md +18 -0
- git_pandas-2.7.0/.obvious/config.yml +6 -0
- git_pandas-2.7.0/.obvious/obvious.md +97 -0
- git_pandas-2.7.0/.obvious/skills/local-dev/SKILL.md +76 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/CHANGELOG.md +15 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/PKG-INFO +8 -18
- {git_pandas-2.6.0 → git_pandas-2.7.0}/README.md +7 -17
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/project.py +19 -1
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/repository.py +70 -5
- {git_pandas-2.6.0 → git_pandas-2.7.0}/pyproject.toml +1 -1
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_advanced.py +1 -0
- git_pandas-2.7.0/tests/test_Project/test_date_source.py +75 -0
- git_pandas-2.7.0/tests/test_Repository/test_date_source.py +130 -0
- git_pandas-2.7.0/tests/test_by_validation.py +79 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_management.py +1 -1
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cached_signatures.py +2 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/project_overview.mdc +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/pytest_standards.mdc +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/python_standards.mdc +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/sphinx_docs_standards.mdc +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.cursor/rules/testing.mdc +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/docs.yml +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/pypi-publish.yml +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/test-docs-build.yml +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.github/workflows/test-suite.yml +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/.gitignore +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/CONTRIBUTING.md +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/LICENSE.md +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/Makefile +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/AUDIT_REPORT.md +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/Makefile +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/make.bat +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/requirements.txt +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/CNAME +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/_static/custom.css +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/cache.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/conf.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/contributors.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/index.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/performance.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/project.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/remote_operations.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/repository.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/docs/source/usecases.rst +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/__init__.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/attributes.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/bus_analysis.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cache_management.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cache_timestamps.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cloud_repo.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/commit_history.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/cumulative_blame.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/definitions.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/file_change_rates.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/hours_estimate.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/cumulative_blame.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/cumulative_project_blame.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/githubblame.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/lifeline.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/img/stravalib_cumulative_blame.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/lifeline.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/parallel_blame.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/project_blame.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/punchcard.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/release_analytics.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/remote_fetch_and_cache_warming.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/examples/repo_file_detail.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/__init__.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/cache.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/logging.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/utilities/__init__.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/utilities/check_api.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/gitpandas/utilities/plotting.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/img/cumulative_blame.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/img/lifeline.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/img/punchcard.png +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/mcp_server/README.md +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/mcp_server/server.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/__init__.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/conftest.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/__init__.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_blame_file_detail_empty.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_blame_multi_repo.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_bus_factor.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_commits_in_tags.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_cumulative_blame_output_shape.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_default_branch_detection.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_edge_cases.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_error_handling.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_file_detail_author.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_github_profile.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_project_limit_allocation.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_properties.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_punchcard.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Project/test_revs_num_datapoints.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/__init__.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_advanced.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_bus_factor.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_commits_in_tags.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_cumulative_blame_duplicate_dates.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_cumulative_blame_output_shape.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_edge_cases.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_error_handling.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_change_history_values.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_detail.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_operations.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_file_owner_author.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_hours_estimate.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_properties.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_punchcard.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_rev_to_rev.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_Repository/test_revs_num_datapoints.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_bulk_fetch_warm.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_immutability.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_integration.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_key_consistency.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_threading.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_cache_warming.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_examples.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_logging.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_mcp_serialization.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_remote_fetch.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_repository_cache.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_utilities/test_check_api.py +0 -0
- {git_pandas-2.6.0 → git_pandas-2.7.0}/tests/test_utilities/test_init.py +0 -0
- {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.
|
|
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
|

|
|
57
57
|
|
|
58
|
-
## What's New in v2.
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
|
|
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
|

|
|
9
9
|
|
|
10
|
-
## What's New in v2.
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
`
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|