git-pandas 2.2.1__tar.gz → 2.5.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.5.0/.github/workflows/docs.yml +37 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.github/workflows/pypi-publish.yml +3 -3
- git_pandas-2.5.0/.github/workflows/test-docs-build.yml +31 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.github/workflows/test-suite.yml +22 -10
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.gitignore +1 -1
- git_pandas-2.5.0/CHANGELOG.md +244 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/CONTRIBUTING.md +0 -2
- {git_pandas-2.2.1 → git_pandas-2.5.0}/Makefile +16 -8
- git_pandas-2.5.0/PKG-INFO +380 -0
- git_pandas-2.5.0/README.md +338 -0
- git_pandas-2.5.0/docs/AUDIT_REPORT.md +171 -0
- git_pandas-2.5.0/docs/requirements.txt +9 -0
- git_pandas-2.5.0/docs/source/CNAME +1 -0
- git_pandas-2.5.0/docs/source/_static/custom.css +2 -0
- git_pandas-2.5.0/docs/source/cache.rst +296 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/docs/source/conf.py +37 -23
- {git_pandas-2.2.1 → git_pandas-2.5.0}/docs/source/contributors.rst +22 -13
- git_pandas-2.5.0/docs/source/index.rst +130 -0
- git_pandas-2.5.0/docs/source/performance.rst +441 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/docs/source/project.rst +61 -50
- git_pandas-2.5.0/docs/source/remote_operations.rst +335 -0
- git_pandas-2.5.0/docs/source/repository.rst +160 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/docs/source/usecases.rst +46 -18
- git_pandas-2.5.0/examples/cache_management.py +373 -0
- git_pandas-2.5.0/examples/cache_timestamps.py +189 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/commit_history.py +1 -1
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/punchcard.py +8 -1
- git_pandas-2.5.0/examples/release_analytics.py +120 -0
- git_pandas-2.5.0/examples/remote_fetch_and_cache_warming.py +296 -0
- git_pandas-2.5.0/gitpandas/__init__.py +16 -0
- git_pandas-2.5.0/gitpandas/cache.py +862 -0
- git_pandas-2.5.0/gitpandas/logging.py +98 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/gitpandas/project.py +559 -129
- git_pandas-2.5.0/gitpandas/repository.py +3097 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/gitpandas/utilities/__init__.py +1 -1
- git_pandas-2.5.0/mcp_server/README.md +22 -0
- git_pandas-2.5.0/mcp_server/server.py +355 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/pyproject.toml +9 -8
- git_pandas-2.5.0/tests/conftest.py +96 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Project/test_advanced.py +31 -0
- git_pandas-2.5.0/tests/test_Project/test_blame_file_detail_empty.py +100 -0
- git_pandas-2.5.0/tests/test_Project/test_blame_multi_repo.py +106 -0
- git_pandas-2.5.0/tests/test_Project/test_bus_factor.py +62 -0
- git_pandas-2.5.0/tests/test_Project/test_commits_in_tags.py +71 -0
- git_pandas-2.5.0/tests/test_Project/test_default_branch_detection.py +57 -0
- git_pandas-2.5.0/tests/test_Project/test_edge_cases.py +603 -0
- git_pandas-2.5.0/tests/test_Project/test_error_handling.py +444 -0
- git_pandas-2.5.0/tests/test_Project/test_file_detail_author.py +33 -0
- git_pandas-2.5.0/tests/test_Project/test_github_profile.py +121 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Project/test_properties.py +11 -2
- git_pandas-2.5.0/tests/test_Project/test_punchcard.py +80 -0
- git_pandas-2.5.0/tests/test_Repository/test_advanced.py +469 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Repository/test_bus_factor.py +89 -5
- git_pandas-2.5.0/tests/test_Repository/test_commits_in_tags.py +176 -0
- git_pandas-2.5.0/tests/test_Repository/test_cumulative_blame_duplicate_dates.py +43 -0
- git_pandas-2.5.0/tests/test_Repository/test_edge_cases.py +549 -0
- git_pandas-2.5.0/tests/test_Repository/test_error_handling.py +391 -0
- git_pandas-2.5.0/tests/test_Repository/test_file_change_history_values.py +63 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Repository/test_file_detail.py +14 -10
- git_pandas-2.5.0/tests/test_Repository/test_file_operations.py +188 -0
- git_pandas-2.5.0/tests/test_Repository/test_file_owner_author.py +79 -0
- git_pandas-2.5.0/tests/test_Repository/test_hours_estimate.py +70 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Repository/test_properties.py +36 -35
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Repository/test_punchcard.py +22 -22
- git_pandas-2.5.0/tests/test_Repository/test_rev_to_rev.py +138 -0
- git_pandas-2.5.0/tests/test_bulk_fetch_warm.py +442 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_cache.py +202 -2
- git_pandas-2.5.0/tests/test_cache_immutability.py +185 -0
- git_pandas-2.5.0/tests/test_cache_integration.py +514 -0
- git_pandas-2.5.0/tests/test_cache_key_consistency.py +324 -0
- git_pandas-2.5.0/tests/test_cache_management.py +292 -0
- git_pandas-2.5.0/tests/test_cache_threading.py +790 -0
- git_pandas-2.5.0/tests/test_cache_warming.py +377 -0
- git_pandas-2.5.0/tests/test_cached_signatures.py +66 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_examples.py +2 -0
- git_pandas-2.5.0/tests/test_logging.py +410 -0
- git_pandas-2.5.0/tests/test_mcp_serialization.py +189 -0
- git_pandas-2.5.0/tests/test_remote_fetch.py +282 -0
- git_pandas-2.5.0/tests/test_repository_cache.py +181 -0
- git_pandas-2.5.0/tests/test_utilities/test_init.py +7 -0
- git_pandas-2.2.1/.github/workflows/docs.yml +0 -48
- git_pandas-2.2.1/.github/workflows/test-docs-build.yml +0 -29
- git_pandas-2.2.1/CHANGELOG.md +0 -99
- git_pandas-2.2.1/PKG-INFO +0 -152
- git_pandas-2.2.1/README.md +0 -111
- git_pandas-2.2.1/docs/requirements.txt +0 -4
- git_pandas-2.2.1/docs/source/cache.rst +0 -121
- git_pandas-2.2.1/docs/source/index.rst +0 -114
- git_pandas-2.2.1/docs/source/repository.rst +0 -119
- git_pandas-2.2.1/gitpandas/__init__.py +0 -6
- git_pandas-2.2.1/gitpandas/cache.py +0 -157
- git_pandas-2.2.1/gitpandas/repository.py +0 -1690
- git_pandas-2.2.1/tests/conftest.py +0 -12
- git_pandas-2.2.1/tests/test_Repository/test_advanced.py +0 -258
- git_pandas-2.2.1/tests/test_utilities/test_init.py +0 -7
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.cursor/rules/project_overview.mdc +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.cursor/rules/pytest_standards.mdc +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.cursor/rules/python_standards.mdc +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.cursor/rules/sphinx_docs_standards.mdc +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.cursor/rules/testing.mdc +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/LICENSE.md +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/docs/Makefile +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/docs/make.bat +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/__init__.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/attributes.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/bus_analysis.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/cloud_repo.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/cumulative_blame.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/definitions.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/file_change_rates.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/hours_estimate.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/img/cumulative_blame.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/img/cumulative_project_blame.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/img/githubblame.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/img/lifeline.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/img/stravalib_cumulative_blame.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/lifeline.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/parallel_blame.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/project_blame.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/examples/repo_file_detail.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/gitpandas/utilities/check_api.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/gitpandas/utilities/plotting.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/img/cumulative_blame.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/img/lifeline.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/img/punchcard.png +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/__init__.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Project/__init__.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_Repository/__init__.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_utilities/test_check_api.py +0 -0
- {git_pandas-2.2.1 → git_pandas-2.5.0}/tests/test_utilities/test_plotting.py +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: "Master Docs Publication"
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [ master ]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
docs:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- name: Clone
|
|
11
|
+
uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: '3.11'
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
run: |
|
|
20
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
21
|
+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
22
|
+
|
|
23
|
+
- name: Install Dependencies
|
|
24
|
+
run: |
|
|
25
|
+
uv pip install --system -e ".[dev]"
|
|
26
|
+
uv pip install --system -r docs/requirements.txt
|
|
27
|
+
|
|
28
|
+
- name: Build Docs
|
|
29
|
+
run: |
|
|
30
|
+
cd docs
|
|
31
|
+
sphinx-build -b html source build/html
|
|
32
|
+
|
|
33
|
+
- name: Deploy Docs
|
|
34
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
35
|
+
with:
|
|
36
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
37
|
+
publish_dir: "./docs/build/html/"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: "Pull Request Docs Check"
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
branches: [ master ]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
docs:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- name: Checkout
|
|
11
|
+
uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: '3.11'
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
run: |
|
|
20
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
21
|
+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
22
|
+
|
|
23
|
+
- name: Install Dependencies
|
|
24
|
+
run: |
|
|
25
|
+
uv pip install --system -e ".[dev]"
|
|
26
|
+
uv pip install --system -r docs/requirements.txt
|
|
27
|
+
|
|
28
|
+
- name: Build Docs
|
|
29
|
+
run: |
|
|
30
|
+
cd docs
|
|
31
|
+
sphinx-build -b html source build/html
|
|
@@ -9,27 +9,39 @@ on:
|
|
|
9
9
|
pull_request:
|
|
10
10
|
branches: [ master ]
|
|
11
11
|
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
12
16
|
jobs:
|
|
13
17
|
test:
|
|
14
18
|
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ['3.10', '3.11', '3.12']
|
|
15
22
|
|
|
16
23
|
steps:
|
|
17
24
|
- name: Checkout code
|
|
18
|
-
uses: actions/checkout@
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
with:
|
|
27
|
+
fetch-depth: 0
|
|
19
28
|
|
|
20
29
|
- name: Set up Python
|
|
21
|
-
uses: actions/setup-python@
|
|
30
|
+
uses: actions/setup-python@v5
|
|
22
31
|
with:
|
|
23
|
-
python-version:
|
|
24
|
-
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
25
34
|
- name: Install uv
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
uses: astral-sh/setup-uv@v5
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
|
|
39
|
+
- name: Check uv version
|
|
40
|
+
run: uv --version
|
|
29
41
|
|
|
30
42
|
- name: Install dependencies
|
|
31
43
|
run: |
|
|
32
|
-
uv pip install --system -e ".[dev
|
|
44
|
+
uv pip install --system -e ".[dev]"
|
|
33
45
|
|
|
34
46
|
- name: Configure Git
|
|
35
47
|
run: |
|
|
@@ -38,8 +50,8 @@ jobs:
|
|
|
38
50
|
|
|
39
51
|
- name: Run tests
|
|
40
52
|
run: |
|
|
41
|
-
|
|
53
|
+
MPLBACKEND=Agg uv run pytest -v -m "not slow"
|
|
42
54
|
|
|
43
55
|
- name: Run linting
|
|
44
56
|
run: |
|
|
45
|
-
|
|
57
|
+
uv run ruff check .
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
Unreleased
|
|
2
|
+
==========
|
|
3
|
+
|
|
4
|
+
v2.5.0
|
|
5
|
+
======
|
|
6
|
+
|
|
7
|
+
## New Features
|
|
8
|
+
|
|
9
|
+
### Remote Operations & Cache Warming
|
|
10
|
+
* **NEW**: `Repository.safe_fetch_remote()` - Safely fetch changes from remote repositories without modifying working directory
|
|
11
|
+
- Read-only operation with comprehensive error handling
|
|
12
|
+
- Support for dry-run preview and remote validation
|
|
13
|
+
- Configurable remote names and pruning options
|
|
14
|
+
* **NEW**: `Repository.warm_cache()` - Pre-populate repository cache for improved performance
|
|
15
|
+
- Configurable method selection with intelligent parameter handling
|
|
16
|
+
- Performance metrics and cache entry tracking
|
|
17
|
+
- Significant performance improvements (1.5-10x speedup demonstrated)
|
|
18
|
+
* **NEW**: `ProjectDirectory.bulk_fetch_and_warm()` - Efficiently process multiple repositories
|
|
19
|
+
- Parallel processing support when joblib is available
|
|
20
|
+
- Error isolation (failures in one repo don't affect others)
|
|
21
|
+
- Comprehensive summary statistics and progress tracking
|
|
22
|
+
|
|
23
|
+
### Enhanced Caching System
|
|
24
|
+
* **NEW**: `CacheEntry` class with metadata tracking (timestamps, age calculation)
|
|
25
|
+
* **ENHANCED**: Thread-safe cache operations with proper locking mechanisms
|
|
26
|
+
* **ENHANCED**: Cache key consistency improvements using `||` delimiter format
|
|
27
|
+
* **ENHANCED**: Cache timestamp and metadata access methods (`get_cache_info()`, `list_cached_keys()`)
|
|
28
|
+
|
|
29
|
+
### Documentation & Examples
|
|
30
|
+
* **NEW**: Comprehensive remote operations documentation (`docs/source/remote_operations.rst`)
|
|
31
|
+
* **NEW**: Cache warming and remote fetch example (`examples/remote_fetch_and_cache_warming.py`)
|
|
32
|
+
* **NEW**: Cache timestamp usage example (`examples/cache_timestamps.py`)
|
|
33
|
+
* **NEW**: Release analytics example (`examples/release_analytics.py`)
|
|
34
|
+
|
|
35
|
+
## Bug Fixes
|
|
36
|
+
|
|
37
|
+
### Commits In Tags History Walking
|
|
38
|
+
* **FIXED**: `Repository.commits_in_tags()` never walked history. It returned exactly one row per tag — the tagged commit — rather than attributing every commit to the release that shipped it, as documented. The backwards walk now runs, so each commit is attributed to the first tag that contains it, stopping at the previous tag, at the `start`/`end` bounds, or at a root commit. The walk uses an explicit worklist, so a release spanning more commits than the interpreter's recursion limit no longer raises `RecursionError`.
|
|
39
|
+
* **FIXED**: Tag lookup used positional `Series.__getitem__`, which emitted a pandas `FutureWarning` and would become a `KeyError` on a future pandas release.
|
|
40
|
+
|
|
41
|
+
**Note**: This is a user-visible output change. `commits_in_tags()` — and `ProjectDirectory.commits_in_tags()`, which aggregates it — now return one row per commit instead of one row per tag. Code that counted rows to count releases needs updating; count distinct values of the `tag` column instead.
|
|
42
|
+
|
|
43
|
+
### Project Default Branch Detection
|
|
44
|
+
* **FIXED**: `ProjectDirectory` and `GitHubProfile` now let each repository auto-detect `main` or `master` by default, so mixed-branch projects include every repository in branch-based history metrics. Passing an explicit `default_branch` continues to force that branch across all repositories.
|
|
45
|
+
|
|
46
|
+
**Note**: Repositories with neither a `main` nor `master` branch are now skipped with a warning during project initialization. Previously they were included but returned empty branch-based history.
|
|
47
|
+
|
|
48
|
+
### Cumulative Blame
|
|
49
|
+
* **FIXED**: `Repository.cumulative_blame()` and `parallel_cumulative_blame()` no longer duplicate rows when multiple commits share a timestamp.
|
|
50
|
+
|
|
51
|
+
### File Change History
|
|
52
|
+
* **FIXED**: `Repository.file_change_history()` now reports actual per-file insertions and deletions, including root commits, which restores non-zero churn metrics in `file_change_rates()`.
|
|
53
|
+
|
|
54
|
+
### Project Punchcard Aggregation
|
|
55
|
+
* **FIXED**: `ProjectDirectory.punchcard()` now returns a well-formed empty DataFrame when no repository yields commit data and avoids pandas aggregation `FutureWarning`s.
|
|
56
|
+
|
|
57
|
+
### File Ownership
|
|
58
|
+
* **FIXED**: `Repository.file_owner()` always read the commit's *committer* regardless of the `committer` flag, so `committer=False` returned the top committer under a column labelled `author`. It now selects the identity to match the flag, matching `Repository.blame()`. This also fixes `Repository.file_detail(committer=False)` and `ProjectDirectory.file_detail(committer=False)`, whose `file_owner` column reported the committer on rebased, cherry-picked, squash-merged, or web-UI-committed history.
|
|
59
|
+
|
|
60
|
+
### MCP Server Serialization
|
|
61
|
+
* **FIXED**: `serialize_pandas_object()` dropped every non-datetime DataFrame index during `orient="records"` conversion, so index-carried identity disappeared from tool results (e.g. a blame-shaped frame serialized as `[{"loc": 7}]` without the `committer`/`author` it belongs to). Named index levels, including `MultiIndex` levels such as `file` and `(tag_date, commit_date)`, are now materialized as columns.
|
|
62
|
+
* **FIXED**: Serialization mutated the DataFrame it was given — rewriting a `DatetimeIndex` into formatted strings and replacing datetime columns in place — which corrupted shared cached frames. Serialization now works on a copy and leaves its input untouched.
|
|
63
|
+
* **FIXED**: Serializing a frame that keeps a column and index level of the same name (e.g. `file_change_history()`'s `date`) raised `ValueError: cannot insert date, already exists`.
|
|
64
|
+
|
|
65
|
+
### GitHub Profile Discovery
|
|
66
|
+
* **FIXED**: `GitHubProfile` now follows GitHub API pagination links and requests up to 100 repositories per page. If any page fails, discovery returns an empty profile instead of silently analyzing partial results.
|
|
67
|
+
|
|
68
|
+
### Hours Estimation
|
|
69
|
+
* **FIXED**: `Repository.hours_estimate()` now includes the first-commit allowance, increasing estimates by `single_commit_hours` per contributor and giving single-commit contributors a non-zero estimate.
|
|
70
|
+
|
|
71
|
+
### Project Blame Aggregation
|
|
72
|
+
* **FIXED**: `ProjectDirectory.blame()` now preserves committer/author and file grouping keys when combining multiple repositories. Contributors are aggregated by name across repositories, `blame(by="file")` no longer raises `KeyError`, and project-level bus factors are calculated from contributors instead of row numbers.
|
|
73
|
+
|
|
74
|
+
### Project File Blame Aggregation
|
|
75
|
+
* **FIXED**: `ProjectDirectory.blame(by="file")` grouped on `(committer/author, file)` only. Because `file` is a repository-relative path, files sharing a path across repositories (`README.md`, `setup.py`, `__init__.py`, ...) were silently summed into a single row reporting a line count that matched neither repository, and the originating repository was discarded entirely. The repository is now part of the grouping, so the result is indexed by `(committer/author, file, repository)` — matching the identification that `file_detail()`, `file_change_rates()`, and `bus_factor(by="file")` already carry. `blame(by="repository")` is unchanged; aggregating a contributor across repositories there remains correct.
|
|
76
|
+
|
|
77
|
+
**Note**: This adds an index level to `blame(by="file")` output. Code that indexes that result by `(committer, file)` needs updating.
|
|
78
|
+
|
|
79
|
+
### Cumulative Blame Cache Mutation
|
|
80
|
+
* **FIXED**: `Repository.cumulative_blame()` reshaped the `revs()` frame it received in place — adding a column per committer, deleting `rev`, and replacing the index with the commit dates. Because a cache backend hands out the stored DataFrame by reference, a later `revs()` call returned that wrecked frame, and a *second* `cumulative_blame()` call raised `ValueError: Internal Error: self.revs() returned DataFrame without 'rev' column.` (`parallel_cumulative_blame()` swallowed the same failure and returned an empty DataFrame). It now works on a copy.
|
|
81
|
+
* **FIXED**: `ProjectDirectory.cumulative_blame()` renamed each member repository's cached `cumulative_blame()` columns in place while suffixing them with the repository name, so a later per-repository call returned `Alice__repo1` instead of `Alice`. The suffixing now happens on copies.
|
|
82
|
+
|
|
83
|
+
### Project Bus Factor Glob Filtering
|
|
84
|
+
* **FIXED**: `ProjectDirectory.bus_factor(by="repository")` passed the caller's `include_globs` into each repository's `ignore_globs` argument. The caller's `ignore_globs` was silently discarded and `include_globs` doubled as an exclusion list, so the bus factor was computed over the wrong set of files — typically excluding exactly the files the caller asked to include. (`by="file"` was already correct.)
|
|
85
|
+
|
|
86
|
+
### Project Empty-Result Guards
|
|
87
|
+
* **FIXED**: `ProjectDirectory.blame()` and `file_detail()` raised `AttributeError: 'NoneType' object has no attribute 'reset_index'` when the project contained no repositories, or when every repository raised `GitCommandError` (repositories with no commits, or a branch that doesn't exist). Both now return a well-formed empty DataFrame matching their normal output shape, as sibling methods such as `file_change_history()` already did.
|
|
88
|
+
* **FIXED**: `ProjectDirectory.commits_in_tags()` raised `ValueError: No objects to concatenate` when no repository yielded tagged commits. It now returns an empty DataFrame carrying the usual `commit_sha`, `tag`, and `repository` columns and the `(tag_date, commit_date)` index.
|
|
89
|
+
|
|
90
|
+
### Punchcard Cache Mutation
|
|
91
|
+
* **FIXED**: `Repository.punchcard()` wrote `day_of_week` and `hour_of_day` columns straight into the `commit_history()` frame it was handed. Because a cache backend returns the stored DataFrame *by reference*, every later `commit_history()` call then returned those two extra columns. The same in-place write affected `ProjectDirectory.commit_history()`, `file_detail()`, and `revs()`, which added a `repository` column to borrowed per-repository frames. All of these now copy before writing.
|
|
92
|
+
|
|
93
|
+
### Cache Warming
|
|
94
|
+
* **FIXED**: `Repository.warm_cache()` silently substituted `limit=100` for `commit_history` and `file_change_rates`, so it populated a cache key that no ordinary call would ever hit — the default `warm_cache()` gave those two methods no speedup whatsoever. The injected limit is gone, so warming populates the keys callers actually use.
|
|
95
|
+
* **FIXED**: `hours_estimate()`, `punchcard()`, `cumulative_blame()`, and `parallel_cumulative_blame()` resolved `branch=None` to the default branch *before* delegating to `commit_history()`/`revs()`. The inner call therefore keyed on `"master"` while a user's own `commit_history()` keyed on `None`, storing several copies of the same history and defeating cache reuse between them. The caller's `branch` value is now passed through unchanged; resolution happens only where a branch name is genuinely needed.
|
|
96
|
+
|
|
97
|
+
### Cached Method Introspection
|
|
98
|
+
* **FIXED**: `@multicache` returned a bare wrapper, so every decorated `Repository` method lost its name, docstring, and signature. `help()`, `inspect.signature()`, and any reflection over the API saw `(self, *args, **kwargs)`. The decorator now applies `functools.wraps`, which restores introspection and — because the MCP server generates its tool schemas by reflecting over `Repository` — restores the real parameter names and defaults in every MCP tool schema.
|
|
99
|
+
|
|
100
|
+
### Cache Correctness
|
|
101
|
+
* **FIXED**: `Repository.invalidate_cache()` now targets the method-first cache key layout, processes every key when combined with a pattern, and returns accurate removal counts for in-memory and Redis backends.
|
|
102
|
+
* **FIXED**: `@multicache` built cache keys from `kwargs` only, so any argument passed *positionally* was invisible to the key and collapsed to `None`. Keys are now resolved against the decorated method's signature (`inspect.signature().bind()` + `apply_defaults()`), so positional and keyword calls key identically. This fixes:
|
|
103
|
+
- `Repository(working_dir=<master-only repo>, cache_backend=...)` raising `ValueError: Could not detect default branch` — the internal `has_branch("main")` / `has_branch("master")` probes shared one key.
|
|
104
|
+
- `file_detail()` reporting the first file's owner for every file — the internal `file_owner(rev, file_path, ...)` calls shared one key.
|
|
105
|
+
* **FIXED**: `blame()`'s `key_list` misspelled `ignore_globs` as `ignore_blobs`, so `ignore_globs` never contributed to the cache key and all variants collided. `blame(ignore_globs=[...])` and `bus_factor(ignore_globs=[...])` now return the same results with and without a cache backend.
|
|
106
|
+
* **FIXED**: `skip_broken` was missing from the cache key of `file_change_history`, `file_change_rates`, `revs`, `cumulative_blame`, `parallel_cumulative_blame`, and `tags`, so `skip_broken=True` and `skip_broken=False` shared an entry.
|
|
107
|
+
* **FIXED**: Cache key parts are now joined with `||` instead of `_`, which could occur inside the values themselves (e.g. `get_file_content(path="docs", rev="release_2")` collided with `path="docs_release", rev="2"`).
|
|
108
|
+
* **NEW**: `@multicache` validates `key_list` against the decorated method's signature at decoration time and raises `ValueError` on a name that isn't a parameter, turning the typo class of bug into a loud failure.
|
|
109
|
+
|
|
110
|
+
**Note**: The cache key format has changed. Stale `EphemeralCache`/`DiskCache` entries simply miss and recompute, but `RedisDFCache` users sharing a cache across versions should flush it (or use a new key prefix) to avoid retaining entries under the old format.
|
|
111
|
+
|
|
112
|
+
## Testing & Quality
|
|
113
|
+
* **NEW**: 38 comprehensive tests for remote operations and cache warming
|
|
114
|
+
* **NEW**: Thread safety tests for cache operations
|
|
115
|
+
* **NEW**: Edge case and error handling test coverage
|
|
116
|
+
* **NEW**: Regression coverage for every fix above, including cached-vs-uncached parity, multi-repository aggregation against repositories that share file paths, and cache-immutability checks that fail if a method writes into a frame it was handed
|
|
117
|
+
* **NEW**: Exact rev-to-rev regression tests pinning churn, file, elapsed-time, author, and committer values, and covering `include_globs`/`ignore_globs` through `release_tag_summary()`
|
|
118
|
+
* **IMPROVED**: Overall test coverage and reliability
|
|
119
|
+
* **FIXED**: Various minor bugs and future warnings
|
|
120
|
+
|
|
121
|
+
## Backward Compatibility
|
|
122
|
+
* No API was removed, renamed, or given a new required argument; every existing call still runs.
|
|
123
|
+
* Three changes are user-visible in the *data* returned, and are called out in full above:
|
|
124
|
+
- `commits_in_tags()` now returns one row per commit rather than one row per tag.
|
|
125
|
+
- `blame(by="file")` on a `ProjectDirectory` gains a `repository` index level.
|
|
126
|
+
- The cache key format changed; `RedisDFCache` users sharing a cache across versions should flush it.
|
|
127
|
+
* Beyond those, results change only where they were previously wrong — the bug fixes above return corrected numbers for churn, hours, ownership, bus factor, and blame. Analyses pinned to specific values from an earlier release should expect them to move.
|
|
128
|
+
* Existing cache backends work seamlessly with new features.
|
|
129
|
+
|
|
130
|
+
v2.4.0
|
|
131
|
+
======
|
|
132
|
+
|
|
133
|
+
* Significant caching bugfixes and updates
|
|
134
|
+
* Added a DiskCache that persists across runs
|
|
135
|
+
* Added release analytics
|
|
136
|
+
|
|
137
|
+
v2.3.0
|
|
138
|
+
======
|
|
139
|
+
|
|
140
|
+
* Updated coverage file parsing to use coverage.py API instead of direct file parsing
|
|
141
|
+
* Added coverage>=5.0.0 as a core dependency
|
|
142
|
+
* Added a basic MCP server
|
|
143
|
+
* Added methods to `Repository` for getting files in repo, getting content of a file, and getting diffs of a revision
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
v2.2.1
|
|
147
|
+
======
|
|
148
|
+
|
|
149
|
+
* Docs CI bugfix
|
|
150
|
+
|
|
151
|
+
v2.2.0
|
|
152
|
+
======
|
|
153
|
+
|
|
154
|
+
* Support for default branch setting instead of assuming master, will infer if not passed
|
|
155
|
+
* Better handling of ignore repos in project directory setup
|
|
156
|
+
* Added a branch exists helper in repository
|
|
157
|
+
* Docs corrections
|
|
158
|
+
|
|
159
|
+
v2.1.0
|
|
160
|
+
======
|
|
161
|
+
|
|
162
|
+
* Imrpoved test suite
|
|
163
|
+
* Many bugfixes
|
|
164
|
+
* Updates for pandas v2
|
|
165
|
+
|
|
166
|
+
v2.0.0
|
|
167
|
+
======
|
|
168
|
+
|
|
169
|
+
* Fully transitioned to ignore_globs and include_globs style syntax
|
|
170
|
+
* Parallelized cumulative blame support with joblib threading backend
|
|
171
|
+
* Added threading parallelism to many project directory functions.
|
|
172
|
+
* Added a chaching module for optional redis or memory backed caching of certain resultsets
|
|
173
|
+
|
|
174
|
+
v1.2.0
|
|
175
|
+
======
|
|
176
|
+
|
|
177
|
+
* Added ignore_globs option alongside all methods with ignore_dir and extensions, will be the only method for filtering files in v2.0.0
|
|
178
|
+
|
|
179
|
+
v1.1.0
|
|
180
|
+
======
|
|
181
|
+
|
|
182
|
+
* _repo_name changed to repo_name in project directories (old method left with deprecation warning)
|
|
183
|
+
* repo_name property added to repositories
|
|
184
|
+
|
|
185
|
+
v1.0.3
|
|
186
|
+
======
|
|
187
|
+
|
|
188
|
+
* Support for estimating time spent developing on projects.
|
|
189
|
+
|
|
190
|
+
v1.0.2
|
|
191
|
+
======
|
|
192
|
+
|
|
193
|
+
* bugfix in ignore_dir option for root level directories
|
|
194
|
+
|
|
195
|
+
v1.0.1
|
|
196
|
+
======
|
|
197
|
+
|
|
198
|
+
* file details function
|
|
199
|
+
|
|
200
|
+
v1.0.0
|
|
201
|
+
======
|
|
202
|
+
|
|
203
|
+
* Stable API
|
|
204
|
+
* Punchcard dataframe added
|
|
205
|
+
* Plotting helpers added to library under utilities module
|
|
206
|
+
* Added github.com profile object
|
|
207
|
+
|
|
208
|
+
v0.0.6
|
|
209
|
+
======
|
|
210
|
+
|
|
211
|
+
* Added file owner utility
|
|
212
|
+
* Added lifelines example
|
|
213
|
+
* Added rev to file change history table
|
|
214
|
+
* Added file-wise blame using by='file' parameter
|
|
215
|
+
* Bus Factor returns a dataframe
|
|
216
|
+
* Now supporting python 2.7+ and 3.3+
|
|
217
|
+
|
|
218
|
+
v0.0.5
|
|
219
|
+
======
|
|
220
|
+
|
|
221
|
+
* Added file change rates table with risk metrics
|
|
222
|
+
* Added basic functionality with coverage files
|
|
223
|
+
* Added limited time window based dataset functionality
|
|
224
|
+
* Expanded docs
|
|
225
|
+
|
|
226
|
+
v0.0.4
|
|
227
|
+
======
|
|
228
|
+
|
|
229
|
+
* Added cumulative blame and revision history
|
|
230
|
+
|
|
231
|
+
v0.0.3
|
|
232
|
+
======
|
|
233
|
+
|
|
234
|
+
* Added approximate bus factor analysis
|
|
235
|
+
|
|
236
|
+
v0.0.2
|
|
237
|
+
======
|
|
238
|
+
|
|
239
|
+
* Added blame
|
|
240
|
+
|
|
241
|
+
v0.0.1
|
|
242
|
+
======
|
|
243
|
+
|
|
244
|
+
* Initial release, basic interface to commit history and descriptors
|
|
@@ -49,8 +49,6 @@ Easy Issues / Getting Started
|
|
|
49
49
|
There are a number of issues on the near term horizon that would be great to have help with.
|
|
50
50
|
|
|
51
51
|
1. Diff: it would be really nice to be able to call a function with 2 revs and return the diff as a dataframe. So columns for line number, filename, path, change, author, timestamp, etc.
|
|
52
|
-
2. Docs: We have a lot of examples, but they are largely not present in the documentation. Adding some plotting to the examples using pyplot and putting them into the docs would help out new users a lot.
|
|
53
|
-
3. Docs Deployment: currently we host the docs on Github pages, which has a kind of awkward deployment process. Anyone with experience automating this (or scripting even) would be very useful.
|
|
54
52
|
4. File-level tracking: it would be really cool to be able to get a dataframe of one file's history in great detail. It likely would be a subset of the existing file change history function.
|
|
55
53
|
5. Cross-Branch Analytics: finding differences between different branches of a single repository. Or aggregating the results of other functions across multiple branches.
|
|
56
54
|
6. Verbose Mode: add logging in more functions when verbose is set to True.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: setup test test-all lint format clean docs build run-example test-single
|
|
1
|
+
.PHONY: setup test test-all lint format clean docs build run-example test-single mcp gitnoc
|
|
2
2
|
|
|
3
3
|
# Use uv for all Python operations
|
|
4
4
|
PYTHON = python
|
|
@@ -20,10 +20,10 @@ setup-examples:
|
|
|
20
20
|
setup-all:
|
|
21
21
|
$(UV) pip install -e ".[all]"
|
|
22
22
|
|
|
23
|
-
test:
|
|
23
|
+
test: setup-all
|
|
24
24
|
MPLBACKEND=Agg $(UV) run pytest $(TESTS_DIR) --cov=$(PACKAGE_NAME) --cov-report=term-missing -m "not slow"
|
|
25
25
|
|
|
26
|
-
test-single:
|
|
26
|
+
test-single: setup-all
|
|
27
27
|
@if [ "$(test)" = "" ]; then \
|
|
28
28
|
echo "Error: Please specify a test using test=<path_to_test>"; \
|
|
29
29
|
echo "Example: make test-single test=tests/test_Repository/test_advanced.py::TestRepositoryAdvanced::test_parallel_cumulative_blame"; \
|
|
@@ -32,15 +32,14 @@ test-single:
|
|
|
32
32
|
MPLBACKEND=Agg $(UV) run pytest $(test) -v
|
|
33
33
|
|
|
34
34
|
test-all:
|
|
35
|
-
|
|
35
|
+
MPLBACKEmND=Agg $(UV) run pytest $(TESTS_DIR) --cov=$(PACKAGE_NAME) --cov-report=term-missing
|
|
36
36
|
|
|
37
37
|
lint:
|
|
38
|
-
$(UV) run ruff check .
|
|
38
|
+
$(UV) run ruff check --fix --unsafe-fixes .
|
|
39
39
|
|
|
40
40
|
format:
|
|
41
41
|
$(UV) run ruff format .
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
docs:
|
|
45
44
|
$(MAKE) -C $(DOCS_DIR) html
|
|
46
45
|
|
|
@@ -78,11 +77,18 @@ run-example:
|
|
|
78
77
|
fi
|
|
79
78
|
MPLBACKEND=Agg $(UV) run python $(EXAMPLES_DIR)/$(example).py
|
|
80
79
|
|
|
80
|
+
mcp:
|
|
81
|
+
$(UV) run python mcp_server/server.py
|
|
82
|
+
|
|
83
|
+
gitnoc:
|
|
84
|
+
$(UV) run python gitnoc/app.py
|
|
85
|
+
|
|
81
86
|
help:
|
|
82
87
|
@echo "Available commands:"
|
|
83
88
|
@echo " setup Install the package in development mode"
|
|
84
89
|
@echo " setup-examples Install the package with examples dependencies"
|
|
85
90
|
@echo " setup-all Install the package with all dependencies"
|
|
91
|
+
@echo " setup-gitnoc Install GitNOC dependencies"
|
|
86
92
|
@echo " test Run tests with pytest (excluding slow tests)"
|
|
87
93
|
@echo " test-single Run a single test (usage: make test-single test=<path_to_test>)"
|
|
88
94
|
@echo " test-all Run all tests including slow tests"
|
|
@@ -94,4 +100,6 @@ help:
|
|
|
94
100
|
@echo " build Build distribution packages"
|
|
95
101
|
@echo " publish Publish package to PyPI"
|
|
96
102
|
@echo " env-export Export dependencies to requirements.txt"
|
|
97
|
-
@echo " run-example Run a specific example (usage: make run-example example=<name>)"
|
|
103
|
+
@echo " run-example Run a specific example (usage: make run-example example=<name>)"
|
|
104
|
+
@echo " mcp Run the MCP server"
|
|
105
|
+
@echo " gitnoc Run the GitNOC Streamlit app"
|