git-pandas 2.3.0__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.
Files changed (131) hide show
  1. {git_pandas-2.3.0 → git_pandas-2.5.0}/.github/workflows/docs.yml +13 -12
  2. {git_pandas-2.3.0 → git_pandas-2.5.0}/.github/workflows/pypi-publish.yml +3 -3
  3. git_pandas-2.5.0/.github/workflows/test-docs-build.yml +31 -0
  4. {git_pandas-2.3.0 → git_pandas-2.5.0}/.github/workflows/test-suite.yml +22 -10
  5. {git_pandas-2.3.0 → git_pandas-2.5.0}/.gitignore +1 -1
  6. git_pandas-2.5.0/CHANGELOG.md +244 -0
  7. {git_pandas-2.3.0 → git_pandas-2.5.0}/CONTRIBUTING.md +0 -2
  8. {git_pandas-2.3.0 → git_pandas-2.5.0}/Makefile +16 -8
  9. git_pandas-2.5.0/PKG-INFO +380 -0
  10. git_pandas-2.5.0/README.md +338 -0
  11. git_pandas-2.5.0/docs/AUDIT_REPORT.md +171 -0
  12. git_pandas-2.5.0/docs/requirements.txt +9 -0
  13. git_pandas-2.5.0/docs/source/_static/custom.css +2 -0
  14. git_pandas-2.5.0/docs/source/cache.rst +296 -0
  15. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/source/conf.py +26 -22
  16. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/source/contributors.rst +11 -2
  17. git_pandas-2.5.0/docs/source/index.rst +130 -0
  18. git_pandas-2.5.0/docs/source/performance.rst +441 -0
  19. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/source/project.rst +55 -31
  20. git_pandas-2.5.0/docs/source/remote_operations.rst +335 -0
  21. git_pandas-2.5.0/docs/source/repository.rst +160 -0
  22. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/source/usecases.rst +31 -3
  23. git_pandas-2.5.0/examples/cache_management.py +373 -0
  24. git_pandas-2.5.0/examples/cache_timestamps.py +189 -0
  25. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/commit_history.py +1 -1
  26. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/punchcard.py +8 -1
  27. git_pandas-2.5.0/examples/release_analytics.py +120 -0
  28. git_pandas-2.5.0/examples/remote_fetch_and_cache_warming.py +296 -0
  29. git_pandas-2.5.0/gitpandas/__init__.py +16 -0
  30. git_pandas-2.5.0/gitpandas/cache.py +862 -0
  31. {git_pandas-2.3.0 → git_pandas-2.5.0}/gitpandas/project.py +462 -101
  32. git_pandas-2.5.0/gitpandas/repository.py +3097 -0
  33. {git_pandas-2.3.0 → git_pandas-2.5.0}/gitpandas/utilities/__init__.py +1 -1
  34. {git_pandas-2.3.0 → git_pandas-2.5.0}/mcp_server/server.py +22 -11
  35. {git_pandas-2.3.0 → git_pandas-2.5.0}/pyproject.toml +8 -8
  36. git_pandas-2.5.0/tests/conftest.py +96 -0
  37. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Project/test_advanced.py +31 -0
  38. git_pandas-2.5.0/tests/test_Project/test_blame_file_detail_empty.py +100 -0
  39. git_pandas-2.5.0/tests/test_Project/test_blame_multi_repo.py +106 -0
  40. git_pandas-2.5.0/tests/test_Project/test_bus_factor.py +62 -0
  41. git_pandas-2.5.0/tests/test_Project/test_commits_in_tags.py +71 -0
  42. git_pandas-2.5.0/tests/test_Project/test_default_branch_detection.py +57 -0
  43. git_pandas-2.5.0/tests/test_Project/test_edge_cases.py +603 -0
  44. git_pandas-2.5.0/tests/test_Project/test_error_handling.py +444 -0
  45. git_pandas-2.5.0/tests/test_Project/test_file_detail_author.py +33 -0
  46. git_pandas-2.5.0/tests/test_Project/test_github_profile.py +121 -0
  47. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Project/test_properties.py +11 -2
  48. git_pandas-2.5.0/tests/test_Project/test_punchcard.py +80 -0
  49. git_pandas-2.5.0/tests/test_Repository/test_advanced.py +469 -0
  50. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Repository/test_bus_factor.py +89 -5
  51. git_pandas-2.5.0/tests/test_Repository/test_commits_in_tags.py +176 -0
  52. git_pandas-2.5.0/tests/test_Repository/test_cumulative_blame_duplicate_dates.py +43 -0
  53. git_pandas-2.5.0/tests/test_Repository/test_edge_cases.py +549 -0
  54. git_pandas-2.5.0/tests/test_Repository/test_error_handling.py +391 -0
  55. git_pandas-2.5.0/tests/test_Repository/test_file_change_history_values.py +63 -0
  56. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Repository/test_file_detail.py +14 -10
  57. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Repository/test_file_operations.py +32 -5
  58. git_pandas-2.5.0/tests/test_Repository/test_file_owner_author.py +79 -0
  59. git_pandas-2.5.0/tests/test_Repository/test_hours_estimate.py +70 -0
  60. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Repository/test_properties.py +36 -35
  61. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Repository/test_punchcard.py +22 -22
  62. git_pandas-2.5.0/tests/test_Repository/test_rev_to_rev.py +138 -0
  63. git_pandas-2.5.0/tests/test_bulk_fetch_warm.py +442 -0
  64. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_cache.py +202 -2
  65. git_pandas-2.5.0/tests/test_cache_immutability.py +185 -0
  66. git_pandas-2.5.0/tests/test_cache_integration.py +514 -0
  67. git_pandas-2.5.0/tests/test_cache_key_consistency.py +324 -0
  68. git_pandas-2.5.0/tests/test_cache_management.py +292 -0
  69. git_pandas-2.5.0/tests/test_cache_threading.py +790 -0
  70. git_pandas-2.5.0/tests/test_cache_warming.py +377 -0
  71. git_pandas-2.5.0/tests/test_cached_signatures.py +66 -0
  72. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_examples.py +2 -0
  73. git_pandas-2.5.0/tests/test_logging.py +410 -0
  74. git_pandas-2.5.0/tests/test_mcp_serialization.py +189 -0
  75. git_pandas-2.5.0/tests/test_remote_fetch.py +282 -0
  76. git_pandas-2.5.0/tests/test_repository_cache.py +181 -0
  77. git_pandas-2.5.0/tests/test_utilities/test_init.py +7 -0
  78. git_pandas-2.3.0/.github/workflows/test-docs-build.yml +0 -29
  79. git_pandas-2.3.0/CHANGELOG.md +0 -108
  80. git_pandas-2.3.0/PKG-INFO +0 -153
  81. git_pandas-2.3.0/README.md +0 -111
  82. git_pandas-2.3.0/docs/requirements.txt +0 -9
  83. git_pandas-2.3.0/docs/source/cache.rst +0 -116
  84. git_pandas-2.3.0/docs/source/index.rst +0 -91
  85. git_pandas-2.3.0/docs/source/repository.rst +0 -98
  86. git_pandas-2.3.0/gitpandas/__init__.py +0 -7
  87. git_pandas-2.3.0/gitpandas/cache.py +0 -157
  88. git_pandas-2.3.0/gitpandas/repository.py +0 -2008
  89. git_pandas-2.3.0/tests/conftest.py +0 -12
  90. git_pandas-2.3.0/tests/test_Repository/test_advanced.py +0 -258
  91. git_pandas-2.3.0/tests/test_utilities/test_init.py +0 -7
  92. {git_pandas-2.3.0 → git_pandas-2.5.0}/.cursor/rules/project_overview.mdc +0 -0
  93. {git_pandas-2.3.0 → git_pandas-2.5.0}/.cursor/rules/pytest_standards.mdc +0 -0
  94. {git_pandas-2.3.0 → git_pandas-2.5.0}/.cursor/rules/python_standards.mdc +0 -0
  95. {git_pandas-2.3.0 → git_pandas-2.5.0}/.cursor/rules/sphinx_docs_standards.mdc +0 -0
  96. {git_pandas-2.3.0 → git_pandas-2.5.0}/.cursor/rules/testing.mdc +0 -0
  97. {git_pandas-2.3.0 → git_pandas-2.5.0}/.github/ISSUE_TEMPLATE.md +0 -0
  98. {git_pandas-2.3.0 → git_pandas-2.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  99. {git_pandas-2.3.0 → git_pandas-2.5.0}/LICENSE.md +0 -0
  100. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/Makefile +0 -0
  101. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/make.bat +0 -0
  102. {git_pandas-2.3.0 → git_pandas-2.5.0}/docs/source/CNAME +0 -0
  103. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/__init__.py +0 -0
  104. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/attributes.py +0 -0
  105. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/bus_analysis.py +0 -0
  106. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/cloud_repo.py +0 -0
  107. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/cumulative_blame.py +0 -0
  108. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/definitions.py +0 -0
  109. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/file_change_rates.py +0 -0
  110. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/hours_estimate.py +0 -0
  111. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/img/cumulative_blame.png +0 -0
  112. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/img/cumulative_project_blame.png +0 -0
  113. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/img/githubblame.png +0 -0
  114. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/img/lifeline.png +0 -0
  115. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/img/stravalib_cumulative_blame.png +0 -0
  116. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/lifeline.py +0 -0
  117. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/parallel_blame.py +0 -0
  118. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/project_blame.py +0 -0
  119. {git_pandas-2.3.0 → git_pandas-2.5.0}/examples/repo_file_detail.py +0 -0
  120. {git_pandas-2.3.0 → git_pandas-2.5.0}/gitpandas/logging.py +0 -0
  121. {git_pandas-2.3.0 → git_pandas-2.5.0}/gitpandas/utilities/check_api.py +0 -0
  122. {git_pandas-2.3.0 → git_pandas-2.5.0}/gitpandas/utilities/plotting.py +0 -0
  123. {git_pandas-2.3.0 → git_pandas-2.5.0}/img/cumulative_blame.png +0 -0
  124. {git_pandas-2.3.0 → git_pandas-2.5.0}/img/lifeline.png +0 -0
  125. {git_pandas-2.3.0 → git_pandas-2.5.0}/img/punchcard.png +0 -0
  126. {git_pandas-2.3.0 → git_pandas-2.5.0}/mcp_server/README.md +0 -0
  127. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/__init__.py +0 -0
  128. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Project/__init__.py +0 -0
  129. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_Repository/__init__.py +0 -0
  130. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_utilities/test_check_api.py +0 -0
  131. {git_pandas-2.3.0 → git_pandas-2.5.0}/tests/test_utilities/test_plotting.py +0 -0
@@ -8,29 +8,30 @@ jobs:
8
8
  runs-on: ubuntu-latest
9
9
  steps:
10
10
  - name: Clone
11
- uses: actions/checkout@v1
12
-
11
+ uses: actions/checkout@v4
12
+
13
13
  - name: Set up Python
14
- uses: actions/setup-python@v4
14
+ uses: actions/setup-python@v5
15
15
  with:
16
- python-version: '3.10'
17
-
16
+ python-version: '3.11'
17
+
18
18
  - name: Install uv
19
19
  run: |
20
20
  curl -LsSf https://astral.sh/uv/install.sh | sh
21
21
  echo "$HOME/.cargo/bin" >> $GITHUB_PATH
22
-
22
+
23
23
  - name: Install Dependencies
24
24
  run: |
25
25
  uv pip install --system -e ".[dev]"
26
-
26
+ uv pip install --system -r docs/requirements.txt
27
+
27
28
  - name: Build Docs
28
- uses: ammaraskar/sphinx-action@master
29
- with:
30
- docs-folder: "./docs/"
31
-
29
+ run: |
30
+ cd docs
31
+ sphinx-build -b html source build/html
32
+
32
33
  - name: Deploy Docs
33
- uses: peaceiris/actions-gh-pages@v3
34
+ uses: peaceiris/actions-gh-pages@v4
34
35
  with:
35
36
  github_token: ${{ secrets.GITHUB_TOKEN }}
36
37
  publish_dir: "./docs/build/html/"
@@ -11,10 +11,10 @@ jobs:
11
11
  id-token: write
12
12
  steps:
13
13
  - name: Clone
14
- uses: actions/checkout@v3
15
-
14
+ uses: actions/checkout@v5
15
+
16
16
  - name: Set up Python
17
- uses: actions/setup-python@v4
17
+ uses: actions/setup-python@v5
18
18
  with:
19
19
  python-version: '3.11'
20
20
 
@@ -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@v3
25
+ uses: actions/checkout@v4
26
+ with:
27
+ fetch-depth: 0
19
28
 
20
29
  - name: Set up Python
21
- uses: actions/setup-python@v4
30
+ uses: actions/setup-python@v5
22
31
  with:
23
- python-version: '3.10'
24
-
32
+ python-version: ${{ matrix.python-version }}
33
+
25
34
  - name: Install uv
26
- run: |
27
- curl -LsSf https://astral.sh/uv/install.sh | sh
28
- echo "$HOME/.cargo/bin" >> $GITHUB_PATH
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,datasets]"
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
- make test
53
+ MPLBACKEND=Agg uv run pytest -v -m "not slow"
42
54
 
43
55
  - name: Run linting
44
56
  run: |
45
- make lint
57
+ uv run ruff check .
@@ -18,7 +18,7 @@ __pycache__/
18
18
  .DS_Store
19
19
  # C extensions
20
20
  *.so
21
-
21
+ gitnoc_desktop/.venv/
22
22
  # Distribution / packaging
23
23
  .Python
24
24
  env/
@@ -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
- MPLBACKEND=Agg $(UV) run pytest $(TESTS_DIR) --cov=$(PACKAGE_NAME) --cov-report=term-missing
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
- $(UV) run ruff check --fix --unsafe-fixes .
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"