gitlogstats 1.1.0__tar.gz → 1.3.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.
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/CLAUDE.md +2 -1
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/settings.json +3 -0
- gitlogstats-1.3.0/.github/workflows/build.yaml +65 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/PKG-INFO +16 -12
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/Pipfile +0 -2
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/Pipfile.lock +1 -59
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/README.md +15 -11
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/docs/SPEC.md +18 -6
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/pyproject.toml +1 -1
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/scripts/board/manifest.yaml +52 -17
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/src/gitlogstats/__main__.py +7 -5
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/src/gitlogstats/github.py +99 -19
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/src/gitlogstats/github_stats.py +30 -3
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/src/gitlogstats/identity.py +43 -10
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fake_gh.py +16 -2
- gitlogstats-1.3.0/tests/fixtures/github/pulls_page1.json +29 -0
- gitlogstats-1.3.0/tests/fixtures/github/pulls_page2.json +53 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/test_github.py +213 -25
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/test_identity.py +76 -13
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/test_integration.py +313 -40
- gitlogstats-1.1.0/.github/workflows/build.yaml +0 -46
- gitlogstats-1.1.0/tests/fixtures/github/pulls_page1.json +0 -20
- gitlogstats-1.1.0/tests/fixtures/github/pulls_page2.json +0 -29
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/agents/developer-agent.md +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/agents/spec-reviewer.md +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/hooks/format-edited.sh +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/hooks/guard-paths.sh +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/hooks/guard-paths.test.mjs +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/hooks/verify.sh +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/skills/phase-handoff/SKILL.md +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.claude/skills/stale-check/SKILL.md +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.gitignore +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/.prettierrc.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/LICENSE +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/package-lock.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/package.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/repos.txt +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/requirements.txt +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/scripts/board/config.mjs +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/scripts/board/derive.mjs +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/scripts/board/derive.test.mjs +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/scripts/board/sync.mjs +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/src/gitlogstats/__init__.py +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/src/gitlogstats/git_logs_parser.py +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fixtures/github/commit_linked.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fixtures/github/commit_unlinked.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fixtures/github/contributors.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fixtures/github/contributors_page2.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fixtures/github/user_full.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/fixtures/github/user_private.json +0 -0
- {gitlogstats-1.1.0 → gitlogstats-1.3.0}/tests/test_git_logs_parser.py +0 -0
|
@@ -70,7 +70,8 @@ Changes are tracked on the GitHub project board (https://github.com/users/bloomb
|
|
|
70
70
|
- **Do not commit code changes directly to the default branch (`master`).** Create a feature branch named `feat/<REQ-ID>-<slug>` (e.g. `feat/STAT-6-mailmap`; use a short descriptive slug when no requirement id applies). **Exception:** documentation (Markdown and anything under `docs/`) and project tooling under `scripts/` and `.claude/` may be committed directly to the default branch without a PR.
|
|
71
71
|
- **Open a pull request** whose description includes `Closes #N` — `N` is the board issue's number — so the issue closes on merge. Commit and push only when asked, and run the checks above first.
|
|
72
72
|
- **Do not hand-create board issues.** The board is generated from the SPEC: `npm run board:derive` regenerates `scripts/board/manifest.yaml`, then `npm run board:sync` creates or updates the issues (`-- --dry-run` previews; `-- --reconcile` forces SPEC statuses back onto the board). A requirement marked `Done` in the SPEC is always closed and moved to Done; other status moves are left to the board unless `--reconcile` is passed.
|
|
73
|
-
-
|
|
73
|
+
- **Claude runs both board commands itself** (they are pre-approved in `.claude/settings.json`): `board:derive` whenever `docs/SPEC.md` changes, committing the manifest with the change; `board:sync` **before** opening a PR when the SPEC adds requirements (so `Closes #N` has an issue to point at), and again **after** the PR merges. Sync reads the working tree, so before the merge run it from a tree where the new requirements are still `Todo`/`In Progress` — syncing a branch that already marks them `Done` closes their issues before the code lands.
|
|
74
|
+
- Merging a pull request into `master` publishes a release to PyPI once the tests pass on the merged code (see `.github/workflows/build.yaml`); pushes to other branches, and direct pushes to `master`, publish nothing. Bump `version` in `pyproject.toml` in the PR when a change to the package should be released; a merge without a bump skips the upload.
|
|
74
75
|
|
|
75
76
|
If asked to use a different workflow (e.g. commit code straight to the default branch), **remind the user that it diverges from this flow and confirm before proceeding** — then honor the confirmed request.
|
|
76
77
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: CI / CD
|
|
2
|
+
# CI runs on every pull request into master and on every push to master.
|
|
3
|
+
# Delivery to PyPI happens only when a pull request is merged into master,
|
|
4
|
+
# and only if the tests pass on the merged code (PKG-2, PKG-4).
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [master]
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [master]
|
|
10
|
+
types: [opened, synchronize, reopened, closed]
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
# a pull request closed without merging needs no checks
|
|
16
|
+
if: github.event.action != 'closed' || github.event.pull_request.merged
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 10
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ['3.9', '3.11']
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
# after a merge, test the merge commit that landed on master
|
|
26
|
+
ref: ${{ github.event.pull_request.merged && github.event.pull_request.merge_commit_sha || github.sha }}
|
|
27
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: ${{ matrix.python-version }}
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: |
|
|
33
|
+
python -m pip install --upgrade pip
|
|
34
|
+
pip install --user pipenv
|
|
35
|
+
pipenv --python python install --dev --skip-lock
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: |
|
|
38
|
+
pipenv run python -m pytest -q
|
|
39
|
+
- name: Build package
|
|
40
|
+
run: |
|
|
41
|
+
pipenv run python -m build .
|
|
42
|
+
deliver:
|
|
43
|
+
# only a merged pull request is released, and only after build passed
|
|
44
|
+
if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged
|
|
45
|
+
needs: [build]
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
timeout-minutes: 5
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
with:
|
|
51
|
+
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
|
52
|
+
- name: Set up Python
|
|
53
|
+
uses: actions/setup-python@v5
|
|
54
|
+
with:
|
|
55
|
+
python-version: '3.11'
|
|
56
|
+
- name: Build package
|
|
57
|
+
run: |
|
|
58
|
+
python -m pip install --upgrade pip build
|
|
59
|
+
python -m build .
|
|
60
|
+
- name: Publish to PyPI
|
|
61
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
62
|
+
with:
|
|
63
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
64
|
+
# a merge without a version bump (e.g. tooling only) skips the upload
|
|
65
|
+
skip-existing: true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gitlogstats
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Track developers' contributions to one or more git repositories within a particular time range.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bloombar/gitlogstats
|
|
6
6
|
Project-URL: Repository, https://github.com/bloombar/gitlogstats.git
|
|
@@ -709,7 +709,7 @@ This script calculates the following, for each developer in each repository:
|
|
|
709
709
|
- number of **files changed**
|
|
710
710
|
- number of **merges** into the analyzed branch
|
|
711
711
|
|
|
712
|
-
With `--github`, it also reports each developer's GitHub **pull requests** opened, merged
|
|
712
|
+
With `--github`, it also reports each developer's GitHub **pull requests** opened, merged, closed without merging and merged by themselves (see [GitHub pull requests](#github-pull-requests)).
|
|
713
713
|
|
|
714
714
|
The results can be formatted as `csv`, `json`, or `markdown`.
|
|
715
715
|
|
|
@@ -751,8 +751,9 @@ options:
|
|
|
751
751
|
The branch to parse, if not the default branch
|
|
752
752
|
-v, --verbose Whether to output debugging info
|
|
753
753
|
-c, --clean Remove contributors without any contribuition
|
|
754
|
-
--github Also report GitHub pull requests opened, merged
|
|
755
|
-
closed per contributor,
|
|
754
|
+
--github Also report GitHub pull requests opened, merged,
|
|
755
|
+
closed and self-merged per contributor, with their git
|
|
756
|
+
and GitHub emails, and match GitHub users to git
|
|
756
757
|
authors. Needs the GitHub CLI (gh), signed in
|
|
757
758
|
```
|
|
758
759
|
|
|
@@ -836,14 +837,17 @@ gitlogstats -rf repos.txt -c
|
|
|
836
837
|
|
|
837
838
|
### GitHub pull requests
|
|
838
839
|
|
|
839
|
-
Add `--github` to also count each contributor's pull requests.
|
|
840
|
+
Add `--github` to also count each contributor's pull requests. Seven columns are appended to every output format:
|
|
840
841
|
|
|
841
|
-
| Column | Meaning
|
|
842
|
-
| --------------------- |
|
|
843
|
-
| `github_username` | the GitHub account matched to the contributor (never the display name), so you can check the matching
|
|
844
|
-
| `prs_opened` | pull requests they opened in the date range, whatever their state now (drafts included)
|
|
845
|
-
| `prs_merged` | pull requests they authored that were merged in the date range, by anyone and in any way (squash too)
|
|
846
|
-
| `prs_closed_unmerged` | pull requests they authored that were closed without merging in the date range
|
|
842
|
+
| Column | Meaning |
|
|
843
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
844
|
+
| `github_username` | the GitHub account matched to the contributor (never the display name), so you can check the matching |
|
|
845
|
+
| `prs_opened` | pull requests they opened in the date range, whatever their state now (drafts included) |
|
|
846
|
+
| `prs_merged` | pull requests they authored that were merged in the date range, by anyone and in any way (squash too) |
|
|
847
|
+
| `prs_closed_unmerged` | pull requests they authored that were closed without merging in the date range |
|
|
848
|
+
| `prs_self_merged` | pull requests they authored and also merged themselves, merged in the date range (also in `prs_merged`) |
|
|
849
|
+
| `git_email` | every distinct author email of the git names combined into the row, sorted alphabetically (ignoring case) and separated by `; `; empty for a GitHub-only row |
|
|
850
|
+
| `github_email` | the public email on the matched GitHub profile; empty when the profile has none (GitHub does not reveal private emails) or no account matched |
|
|
847
851
|
|
|
848
852
|
```
|
|
849
853
|
gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
|
|
@@ -856,7 +860,7 @@ gitlogstats: --github needs the GitHub CLI (gh), which is not installed. Install
|
|
|
856
860
|
gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
|
|
857
861
|
```
|
|
858
862
|
|
|
859
|
-
**Empty versus zero.** A zero means GitHub was asked and the person has none. An empty value (`null` in JSON) means GitHub could not be asked for that repository: it is not on github.com, or the request failed (no network, rate limit, a private repository you cannot see). The git statistics are still reported, the reason is printed to standard error, and the other repositories carry on. A contributor with no GitHub account matched has an empty `github_username` and zero pull requests. A GitHub user who opened pull requests but has no commits is listed in a row of their own with an empty `username` and zero git statistics.
|
|
863
|
+
**Empty versus zero.** A zero means GitHub was asked and the person has none. An empty value (`null` in JSON) means GitHub could not be asked for that repository: it is not on github.com, or the request failed (no network, rate limit, a private repository you cannot see). The git statistics are still reported, the reason is printed to standard error, and the other repositories carry on. A contributor with no GitHub account matched has an empty `github_username` and zero pull requests. A GitHub user who opened pull requests but has no commits is listed in a row of their own with an empty `username` and zero git statistics. When GitHub could not be asked, `git_email` is still filled in and `github_email` is empty. The emails come from the whole history of the branch, not only from commits in the date range.
|
|
860
864
|
|
|
861
865
|
**Matching GitHub users to git authors.** Each git author name is compared with the GitHub users active in the repository, trying these in order and stopping at the first that finds exactly one user:
|
|
862
866
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
3
|
"hash": {
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "d1fee7a304fa34eaf74ad0dd93b88859d18b03c32476298928a490a03ec616e8"
|
|
5
5
|
},
|
|
6
6
|
"pipfile-spec": 6,
|
|
7
7
|
"requires": {
|
|
@@ -16,67 +16,9 @@
|
|
|
16
16
|
]
|
|
17
17
|
},
|
|
18
18
|
"default": {
|
|
19
|
-
"build": {
|
|
20
|
-
"hashes": [
|
|
21
|
-
"sha256:51cc11666391ab6f092070437ac747002ff46f3e4113a3622177ee6b488bfc53",
|
|
22
|
-
"sha256:ecd351a4be9d35a9eaaba244a7687143c9c7d4aea6ac964e7e7ddab20cbcf4e7"
|
|
23
|
-
],
|
|
24
|
-
"index": "pypi",
|
|
25
|
-
"markers": "python_version >= '3.10'",
|
|
26
|
-
"version": "==1.6.1"
|
|
27
|
-
},
|
|
28
19
|
"gitlogstats": {
|
|
29
20
|
"editable": true,
|
|
30
21
|
"path": "."
|
|
31
|
-
},
|
|
32
|
-
"iniconfig": {
|
|
33
|
-
"hashes": [
|
|
34
|
-
"sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960",
|
|
35
|
-
"sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7"
|
|
36
|
-
],
|
|
37
|
-
"markers": "python_version >= '3.10'",
|
|
38
|
-
"version": "==2.3.1"
|
|
39
|
-
},
|
|
40
|
-
"packaging": {
|
|
41
|
-
"hashes": [
|
|
42
|
-
"sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79",
|
|
43
|
-
"sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c"
|
|
44
|
-
],
|
|
45
|
-
"markers": "python_version >= '3.9'",
|
|
46
|
-
"version": "==26.3"
|
|
47
|
-
},
|
|
48
|
-
"pluggy": {
|
|
49
|
-
"hashes": [
|
|
50
|
-
"sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec",
|
|
51
|
-
"sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8"
|
|
52
|
-
],
|
|
53
|
-
"markers": "python_version >= '3.10'",
|
|
54
|
-
"version": "==1.7.0"
|
|
55
|
-
},
|
|
56
|
-
"pygments": {
|
|
57
|
-
"hashes": [
|
|
58
|
-
"sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9",
|
|
59
|
-
"sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"
|
|
60
|
-
],
|
|
61
|
-
"markers": "python_version >= '3.9'",
|
|
62
|
-
"version": "==2.21.0"
|
|
63
|
-
},
|
|
64
|
-
"pyproject-hooks": {
|
|
65
|
-
"hashes": [
|
|
66
|
-
"sha256:5fc53fdac9f7bd63fbcdc868fb5f90b4784d78a53a3d3388cd738b807441a20b",
|
|
67
|
-
"sha256:defda19b854fa0d3bd4f76ea4ddcba8abd7dcfcdd585a6690ade050744fc5f43"
|
|
68
|
-
],
|
|
69
|
-
"markers": "python_version >= '3.8'",
|
|
70
|
-
"version": "==1.3.3"
|
|
71
|
-
},
|
|
72
|
-
"pytest": {
|
|
73
|
-
"hashes": [
|
|
74
|
-
"sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313",
|
|
75
|
-
"sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"
|
|
76
|
-
],
|
|
77
|
-
"index": "pypi",
|
|
78
|
-
"markers": "python_version >= '3.10'",
|
|
79
|
-
"version": "==9.1.1"
|
|
80
22
|
}
|
|
81
23
|
},
|
|
82
24
|
"develop": {
|
|
@@ -12,7 +12,7 @@ This script calculates the following, for each developer in each repository:
|
|
|
12
12
|
- number of **files changed**
|
|
13
13
|
- number of **merges** into the analyzed branch
|
|
14
14
|
|
|
15
|
-
With `--github`, it also reports each developer's GitHub **pull requests** opened, merged
|
|
15
|
+
With `--github`, it also reports each developer's GitHub **pull requests** opened, merged, closed without merging and merged by themselves (see [GitHub pull requests](#github-pull-requests)).
|
|
16
16
|
|
|
17
17
|
The results can be formatted as `csv`, `json`, or `markdown`.
|
|
18
18
|
|
|
@@ -54,8 +54,9 @@ options:
|
|
|
54
54
|
The branch to parse, if not the default branch
|
|
55
55
|
-v, --verbose Whether to output debugging info
|
|
56
56
|
-c, --clean Remove contributors without any contribuition
|
|
57
|
-
--github Also report GitHub pull requests opened, merged
|
|
58
|
-
closed per contributor,
|
|
57
|
+
--github Also report GitHub pull requests opened, merged,
|
|
58
|
+
closed and self-merged per contributor, with their git
|
|
59
|
+
and GitHub emails, and match GitHub users to git
|
|
59
60
|
authors. Needs the GitHub CLI (gh), signed in
|
|
60
61
|
```
|
|
61
62
|
|
|
@@ -139,14 +140,17 @@ gitlogstats -rf repos.txt -c
|
|
|
139
140
|
|
|
140
141
|
### GitHub pull requests
|
|
141
142
|
|
|
142
|
-
Add `--github` to also count each contributor's pull requests.
|
|
143
|
+
Add `--github` to also count each contributor's pull requests. Seven columns are appended to every output format:
|
|
143
144
|
|
|
144
|
-
| Column | Meaning
|
|
145
|
-
| --------------------- |
|
|
146
|
-
| `github_username` | the GitHub account matched to the contributor (never the display name), so you can check the matching
|
|
147
|
-
| `prs_opened` | pull requests they opened in the date range, whatever their state now (drafts included)
|
|
148
|
-
| `prs_merged` | pull requests they authored that were merged in the date range, by anyone and in any way (squash too)
|
|
149
|
-
| `prs_closed_unmerged` | pull requests they authored that were closed without merging in the date range
|
|
145
|
+
| Column | Meaning |
|
|
146
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `github_username` | the GitHub account matched to the contributor (never the display name), so you can check the matching |
|
|
148
|
+
| `prs_opened` | pull requests they opened in the date range, whatever their state now (drafts included) |
|
|
149
|
+
| `prs_merged` | pull requests they authored that were merged in the date range, by anyone and in any way (squash too) |
|
|
150
|
+
| `prs_closed_unmerged` | pull requests they authored that were closed without merging in the date range |
|
|
151
|
+
| `prs_self_merged` | pull requests they authored and also merged themselves, merged in the date range (also in `prs_merged`) |
|
|
152
|
+
| `git_email` | every distinct author email of the git names combined into the row, sorted alphabetically (ignoring case) and separated by `; `; empty for a GitHub-only row |
|
|
153
|
+
| `github_email` | the public email on the matched GitHub profile; empty when the profile has none (GitHub does not reveal private emails) or no account matched |
|
|
150
154
|
|
|
151
155
|
```
|
|
152
156
|
gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
|
|
@@ -159,7 +163,7 @@ gitlogstats: --github needs the GitHub CLI (gh), which is not installed. Install
|
|
|
159
163
|
gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
|
|
160
164
|
```
|
|
161
165
|
|
|
162
|
-
**Empty versus zero.** A zero means GitHub was asked and the person has none. An empty value (`null` in JSON) means GitHub could not be asked for that repository: it is not on github.com, or the request failed (no network, rate limit, a private repository you cannot see). The git statistics are still reported, the reason is printed to standard error, and the other repositories carry on. A contributor with no GitHub account matched has an empty `github_username` and zero pull requests. A GitHub user who opened pull requests but has no commits is listed in a row of their own with an empty `username` and zero git statistics.
|
|
166
|
+
**Empty versus zero.** A zero means GitHub was asked and the person has none. An empty value (`null` in JSON) means GitHub could not be asked for that repository: it is not on github.com, or the request failed (no network, rate limit, a private repository you cannot see). The git statistics are still reported, the reason is printed to standard error, and the other repositories carry on. A contributor with no GitHub account matched has an empty `github_username` and zero pull requests. A GitHub user who opened pull requests but has no commits is listed in a row of their own with an empty `username` and zero git statistics. When GitHub could not be asked, `git_email` is still filled in and `github_email` is empty. The emails come from the whole history of the branch, not only from commits in the date range.
|
|
163
167
|
|
|
164
168
|
**Matching GitHub users to git authors.** Each git author name is compared with the GitHub users active in the repository, trying these in order and stopping at the first that finds exactly one user:
|
|
165
169
|
|
|
@@ -114,6 +114,18 @@ If a tier finds several GitHub users for one identity, that tier is skipped as a
|
|
|
114
114
|
|
|
115
115
|
**Status:** Done
|
|
116
116
|
|
|
117
|
+
#### STAT-12 Self-merged pull requests
|
|
118
|
+
|
|
119
|
+
With `--github`, for each contributor, the number of pull requests they authored and also merged themselves (the GitHub user who merged the pull request is its author, ignoring case), with a merge time (`merged_at`) in the date range. Reported in a `prs_self_merged` column, appended after `prs_closed_unmerged`. Every self-merged pull request is also counted in `prs_merged` (STAT-9), so `prs_self_merged` is never larger than `prs_merged`. A merged pull request whose merger GitHub does not report (e.g. a deleted account) is not counted as self-merged. `-b` and `-s/-e` apply as for `prs_merged`.
|
|
120
|
+
|
|
121
|
+
**Status:** Done
|
|
122
|
+
|
|
123
|
+
#### STAT-13 Contributor email addresses
|
|
124
|
+
|
|
125
|
+
With `--github`, every row also reports the contributor's email addresses in two columns appended after `prs_self_merged`: `git_email` lists every distinct author email of the git identities combined into the row (STAT-11), sorted alphabetically (ignoring case) and separated by `; `, and is empty for a GitHub-only row; `github_email` is the public email on the matched GitHub user's profile, and is empty when the profile has none (GitHub does not reveal private emails) or the row has no GitHub user. Emails come from the whole history of the analyzed branch, like the contributor list, not only from commits in the date range. When GitHub statistics are unavailable for a repository (CLI-3), `git_email` is still filled in and `github_email` is empty (`null` in JSON). Without `--github`, neither column appears.
|
|
126
|
+
|
|
127
|
+
**Status:** Done
|
|
128
|
+
|
|
117
129
|
### 3. Filtering
|
|
118
130
|
|
|
119
131
|
#### FILT-1 Date range
|
|
@@ -144,7 +156,7 @@ By default, contributors with no activity in the date range are listed with zero
|
|
|
144
156
|
|
|
145
157
|
#### OUT-1 CSV output
|
|
146
158
|
|
|
147
|
-
`-f csv` (the default) prints a header row followed by one row per contributor per repository, with fields `username, repository, start_date, end_date, commits, insertions, deletions, files, merges`. New fields are added at the end so existing column positions do not change. Values containing commas, quotes or line breaks are quoted. The header is printed even when there are no rows, and the output ends with a single newline. With `--github`, the columns `github_username, prs_opened, prs_merged, prs_closed_unmerged` are appended, and are in the header even when there are no rows (CLI-3).
|
|
159
|
+
`-f csv` (the default) prints a header row followed by one row per contributor per repository, with fields `username, repository, start_date, end_date, commits, insertions, deletions, files, merges`. New fields are added at the end so existing column positions do not change. Values containing commas, quotes or line breaks are quoted. The header is printed even when there are no rows, and the output ends with a single newline. With `--github`, the columns `github_username, prs_opened, prs_merged, prs_closed_unmerged, prs_self_merged, git_email, github_email` are appended, and are in the header even when there are no rows (CLI-3).
|
|
148
160
|
|
|
149
161
|
**Status:** Done
|
|
150
162
|
|
|
@@ -188,12 +200,12 @@ Invalid dates, and a start date after the end date, are rejected with a usage er
|
|
|
188
200
|
|
|
189
201
|
#### CLI-3 GitHub pull-request statistics
|
|
190
202
|
|
|
191
|
-
`--github` adds GitHub pull-request statistics (STAT-8 to STAT-
|
|
203
|
+
`--github` adds GitHub pull-request statistics (STAT-8 to STAT-12) and contributor emails (STAT-13) for repositories hosted on github.com, appending the columns `github_username, prs_opened, prs_merged, prs_closed_unmerged, prs_self_merged, git_email, github_email` to every output format. Without `--github`, the output and behaviour are unchanged, GitHub is never contacted and the GitHub CLI (`gh`) is not needed. With `--github`, the GitHub API is called through `gh api`, so `gh` must be installed and signed in. This is checked before any repository is cloned; if it fails, nothing is printed to standard output, the tool exits with status 1, and one plain message on standard error says why and what to do — no stack trace:
|
|
192
204
|
|
|
193
205
|
- `gh` not found: `gitlogstats: --github needs the GitHub CLI (gh), which is not installed. Install it from https://cli.github.com and run 'gh auth login', or run without --github.`
|
|
194
206
|
- `gh` not signed in (`gh auth status` fails): `gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.`
|
|
195
207
|
|
|
196
|
-
The other options apply to pull requests as follows: `-s/-e` filter each count by its own timestamp, using the same inclusive local-time range as FILT-1; `-u` selects the combined row whose author names, emails or GitHub username match exactly (the username ignoring case); `-b` counts only pull requests whose base branch is that branch, and without `-b` every pull request counts; `-c` removes only rows with neither git nor pull-request activity; `-x` does not apply. For a repository not on github.com, or when GitHub cannot be reached for one repository (no network, rate limit, private repository without access), the git statistics are still reported, the
|
|
208
|
+
The other options apply to pull requests as follows: `-s/-e` filter each count by its own timestamp, using the same inclusive local-time range as FILT-1; `-u` selects the combined row whose author names, emails or GitHub username match exactly (the username ignoring case); `-b` counts only pull requests whose base branch is that branch, and without `-b` every pull request counts; `-c` removes only rows with neither git nor pull-request activity; `-x` does not apply. For a repository not on github.com, or when GitHub cannot be reached for one repository (no network, rate limit, private repository without access), the git statistics are still reported, the GitHub columns (including `github_email`) are empty (`null` in JSON) rather than zero while `git_email` is still filled in (STAT-13), and the reason is printed to standard error.
|
|
197
209
|
|
|
198
210
|
**Status:** Done
|
|
199
211
|
|
|
@@ -207,7 +219,7 @@ The tool installs with `pip install gitlogstats`, which provides a `gitlogstats`
|
|
|
207
219
|
|
|
208
220
|
#### PKG-2 Continuous integration and release
|
|
209
221
|
|
|
210
|
-
GitHub Actions builds the package on
|
|
222
|
+
GitHub Actions builds the package on Python 3.9 and 3.11 for every pull request into `master` and every push to `master`. It publishes to PyPI only when a pull request is merged into `master` and the build of the merged code passes; pushes to other branches publish nothing. A merge whose version is already on PyPI (no version bump) skips the upload instead of failing.
|
|
211
223
|
|
|
212
224
|
**Status:** Done
|
|
213
225
|
|
|
@@ -219,9 +231,9 @@ Unit tests cover parsing and formatting, and integration tests build real git re
|
|
|
219
231
|
|
|
220
232
|
#### PKG-4 Run tests in CI
|
|
221
233
|
|
|
222
|
-
The CI workflow runs the test suite on every push and
|
|
234
|
+
The CI workflow runs the Python test suite (`pytest`) on every pull request into `master` and every push to `master`, and on the merged code before a release; a failing test stops the release.
|
|
223
235
|
|
|
224
|
-
**Status:**
|
|
236
|
+
**Status:** Done
|
|
225
237
|
|
|
226
238
|
### 7. Future Work
|
|
227
239
|
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitlogstats"
|
|
7
7
|
description = "Track developers' contributions to one or more git repositories within a particular time range."
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.3.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Tobody Tothing", email="nobody.nothing@nowhere.com" },
|
|
11
11
|
]
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
status: Done
|
|
20
20
|
- id: CLI-3
|
|
21
21
|
title: GitHub pull-request statistics
|
|
22
|
-
full:
|
|
23
|
-
repositories hosted on github.com,
|
|
24
|
-
prs_opened, prs_merged,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
full: --github adds GitHub pull-request statistics (STAT-8 to STAT-12) and
|
|
23
|
+
contributor emails (STAT-13) for repositories hosted on github.com,
|
|
24
|
+
appending the columns github_username, prs_opened, prs_merged,
|
|
25
|
+
prs_closed_unmerged, prs_self_merged, git_email, github_email to every
|
|
26
|
+
output format. Without --github, the output and behaviour are unchanged,
|
|
27
|
+
GitHub is never contacted and the GitHub CLI (gh) is not needed. With
|
|
28
|
+
--github, the GitHub API is called through gh api, so gh must be installed
|
|
29
|
+
and signed in. This is checked before any repository is cloned; if it fails,
|
|
30
|
+
nothing is printed to standard output, the tool exits with status 1, and one
|
|
31
|
+
plain message on standard error says why and what to do…
|
|
32
32
|
section: §5 Command-Line Interface
|
|
33
33
|
family: CLI
|
|
34
34
|
phase: 1
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
containing commas, quotes or line breaks are quoted. The header is printed
|
|
90
90
|
even when there are no rows, and the output ends with a single newline. With
|
|
91
91
|
--github, the columns github_username, prs_opened, prs_merged,
|
|
92
|
-
prs_closed_unmerged
|
|
93
|
-
no rows (CLI-3).
|
|
92
|
+
prs_closed_unmerged, prs_self_merged, git_email, github_email are appended,
|
|
93
|
+
and are in the header even when there are no rows (CLI-3).
|
|
94
94
|
section: §4 Output
|
|
95
95
|
family: OUT
|
|
96
96
|
phase: 1
|
|
@@ -141,8 +141,11 @@
|
|
|
141
141
|
status: Done
|
|
142
142
|
- id: PKG-2
|
|
143
143
|
title: Continuous integration and release
|
|
144
|
-
full: GitHub Actions builds the package on
|
|
145
|
-
and
|
|
144
|
+
full: GitHub Actions builds the package on Python 3.9 and 3.11 for every pull
|
|
145
|
+
request into master and every push to master. It publishes to PyPI only when
|
|
146
|
+
a pull request is merged into master and the build of the merged code
|
|
147
|
+
passes; pushes to other branches publish nothing. A merge whose version is
|
|
148
|
+
already on PyPI (no version bump) skips the upload instead of failing.
|
|
146
149
|
section: §6 Packaging & Quality
|
|
147
150
|
family: PKG
|
|
148
151
|
phase: 1
|
|
@@ -159,12 +162,13 @@
|
|
|
159
162
|
status: Done
|
|
160
163
|
- id: PKG-4
|
|
161
164
|
title: Run tests in CI
|
|
162
|
-
full: The CI workflow runs the test suite on every
|
|
163
|
-
|
|
165
|
+
full: The CI workflow runs the Python test suite (pytest) on every pull request
|
|
166
|
+
into master and every push to master, and on the merged code before a
|
|
167
|
+
release; a failing test stops the release.
|
|
164
168
|
section: §6 Packaging & Quality
|
|
165
169
|
family: PKG
|
|
166
170
|
phase: 1
|
|
167
|
-
status:
|
|
171
|
+
status: Done
|
|
168
172
|
- id: REPO-1
|
|
169
173
|
title: Single repository
|
|
170
174
|
full: -r/--repository <url> analyzes one repository. Any URL or path that git
|
|
@@ -329,6 +333,37 @@
|
|
|
329
333
|
family: STAT
|
|
330
334
|
phase: 1
|
|
331
335
|
status: Done
|
|
336
|
+
- id: STAT-12
|
|
337
|
+
title: Self-merged pull requests
|
|
338
|
+
full: With --github, for each contributor, the number of pull requests they
|
|
339
|
+
authored and also merged themselves (the GitHub user who merged the pull
|
|
340
|
+
request is its author, ignoring case), with a merge time (merged_at) in the
|
|
341
|
+
date range. Reported in a prs_self_merged column, appended after
|
|
342
|
+
prs_closed_unmerged. Every self-merged pull request is also counted in
|
|
343
|
+
prs_merged (STAT-9), so prs_self_merged is never larger than prs_merged. A
|
|
344
|
+
merged pull request whose merger GitHub does not report (e.g. a deleted
|
|
345
|
+
account) is not counted as self-merged. -b and -s/-e apply as for
|
|
346
|
+
prs_merged.
|
|
347
|
+
section: §2 Contribution Statistics
|
|
348
|
+
family: STAT
|
|
349
|
+
phase: 1
|
|
350
|
+
status: Done
|
|
351
|
+
- id: STAT-13
|
|
352
|
+
title: Contributor email addresses
|
|
353
|
+
full: "With --github, every row also reports the contributor's email addresses
|
|
354
|
+
in two columns appended after prs_self_merged: git_email lists every
|
|
355
|
+
distinct author email of the git identities combined into the row (STAT-11),
|
|
356
|
+
sorted alphabetically (ignoring case) and separated by ; , and is empty for
|
|
357
|
+
a GitHub-only row; github_email is the public email on the matched GitHub
|
|
358
|
+
user's profile, and is empty when the profile has none (GitHub does not
|
|
359
|
+
reveal private emails) or the row has no GitHub user. Emails come from the
|
|
360
|
+
whole history of the analyzed branch, like the contributor list, not only
|
|
361
|
+
from commits in the date range. When GitHub statistics are unavailable for a
|
|
362
|
+
repository (CLI-3), git_email is…"
|
|
363
|
+
section: §2 Contribution Statistics
|
|
364
|
+
family: STAT
|
|
365
|
+
phase: 1
|
|
366
|
+
status: Done
|
|
332
367
|
- id: FUTURE-author-date-filtering
|
|
333
368
|
title: Author-date filtering
|
|
334
369
|
full: Author-date filtering — offer filtering by author date, so rebased or
|
|
@@ -6,7 +6,7 @@ import re
|
|
|
6
6
|
import sys
|
|
7
7
|
from . import GitLogsParser
|
|
8
8
|
from .git_logs_parser import FIELDNAMES
|
|
9
|
-
from .github import GITHUB_FIELDNAMES, GitHubUnavailable, check_gh
|
|
9
|
+
from .github import EMAIL_FIELDNAMES, GITHUB_FIELDNAMES, GitHubUnavailable, check_gh
|
|
10
10
|
from .github_stats import github_rows
|
|
11
11
|
|
|
12
12
|
|
|
@@ -141,9 +141,9 @@ def main():
|
|
|
141
141
|
)
|
|
142
142
|
parser.add_argument(
|
|
143
143
|
"--github",
|
|
144
|
-
help="Also report GitHub pull requests opened, merged
|
|
145
|
-
"contributor,
|
|
146
|
-
"(gh), signed in",
|
|
144
|
+
help="Also report GitHub pull requests opened, merged, closed and "
|
|
145
|
+
"self-merged per contributor, with their git and GitHub emails, and "
|
|
146
|
+
"match GitHub users to git authors. Needs the GitHub CLI (gh), signed in",
|
|
147
147
|
default=False,
|
|
148
148
|
action="store_true",
|
|
149
149
|
)
|
|
@@ -233,7 +233,9 @@ def main():
|
|
|
233
233
|
# emit a single table (or JSON array) covering all repositories
|
|
234
234
|
if last_parser is not None:
|
|
235
235
|
# an empty CSV table still gets the GitHub columns with --github
|
|
236
|
-
fieldnames =
|
|
236
|
+
fieldnames = (
|
|
237
|
+
FIELDNAMES + GITHUB_FIELDNAMES + EMAIL_FIELDNAMES if args.github else None
|
|
238
|
+
)
|
|
237
239
|
output = last_parser.format_results(all_results, args.format, fieldnames)
|
|
238
240
|
# end with exactly one newline: CSV and Markdown output already has one
|
|
239
241
|
print(output, end="" if output.endswith("\n") else "\n")
|