gitlogstats 1.0.9__tar.gz → 1.2.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.0.9 → gitlogstats-1.2.0}/.claude/CLAUDE.md +2 -1
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/settings.json +3 -0
- gitlogstats-1.2.0/.github/workflows/build.yaml +65 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/PKG-INFO +61 -7
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/Pipfile +0 -2
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/Pipfile.lock +1 -59
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/README.md +60 -6
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/docs/SPEC.md +61 -9
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/pyproject.toml +1 -1
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/scripts/board/manifest.yaml +136 -11
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/src/gitlogstats/__main__.py +32 -4
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/src/gitlogstats/git_logs_parser.py +30 -3
- gitlogstats-1.2.0/src/gitlogstats/github.py +348 -0
- gitlogstats-1.2.0/src/gitlogstats/github_stats.py +152 -0
- gitlogstats-1.2.0/src/gitlogstats/identity.py +251 -0
- gitlogstats-1.2.0/tests/fake_gh.py +106 -0
- gitlogstats-1.2.0/tests/fixtures/github/commit_linked.json +5 -0
- gitlogstats-1.2.0/tests/fixtures/github/commit_unlinked.json +5 -0
- gitlogstats-1.2.0/tests/fixtures/github/contributors.json +4 -0
- gitlogstats-1.2.0/tests/fixtures/github/contributors_page2.json +3 -0
- gitlogstats-1.2.0/tests/fixtures/github/pulls_page1.json +29 -0
- gitlogstats-1.2.0/tests/fixtures/github/pulls_page2.json +53 -0
- gitlogstats-1.2.0/tests/fixtures/github/user_full.json +1 -0
- gitlogstats-1.2.0/tests/fixtures/github/user_private.json +1 -0
- gitlogstats-1.2.0/tests/test_github.py +726 -0
- gitlogstats-1.2.0/tests/test_identity.py +378 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/tests/test_integration.py +649 -0
- gitlogstats-1.0.9/.github/workflows/build.yaml +0 -46
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/agents/developer-agent.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/agents/spec-reviewer.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/hooks/format-edited.sh +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/hooks/guard-paths.sh +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/hooks/guard-paths.test.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/hooks/verify.sh +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/skills/phase-handoff/SKILL.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.claude/skills/stale-check/SKILL.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.gitignore +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/.prettierrc.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/LICENSE +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/package-lock.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/package.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/repos.txt +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/requirements.txt +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/scripts/board/config.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/scripts/board/derive.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/scripts/board/derive.test.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/scripts/board/sync.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.0}/src/gitlogstats/__init__.py +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.2.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.0
|
|
3
|
+
Version: 1.2.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,6 +709,8 @@ 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, closed without merging and merged by themselves (see [GitHub pull requests](#github-pull-requests)).
|
|
713
|
+
|
|
712
714
|
The results can be formatted as `csv`, `json`, or `markdown`.
|
|
713
715
|
|
|
714
716
|
## Install
|
|
@@ -722,26 +724,37 @@ e.g. `pip install gitlogstats`, `pip3 install gitlogstats`, depending on your en
|
|
|
722
724
|
The command `gitlogstats --help` shows the usage instructions:
|
|
723
725
|
|
|
724
726
|
```
|
|
725
|
-
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
727
|
+
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
728
|
+
[-e END] [-x EXCLUSIONS] [-f {csv,json,markdown}]
|
|
729
|
+
[-b BRANCH] [-v] [-c] [--github]
|
|
726
730
|
|
|
727
|
-
|
|
731
|
+
options:
|
|
728
732
|
-h, --help show this help message and exit
|
|
729
733
|
-r REPOSITORY, --repository REPOSITORY
|
|
730
734
|
the public URL of the repository whose logs to parse
|
|
731
735
|
-rf REPOFILE, --repofile REPOFILE
|
|
732
|
-
the path to simple text file with a list of repository
|
|
733
|
-
|
|
736
|
+
the path to simple text file with a list of repository
|
|
737
|
+
URLs to parse
|
|
738
|
+
-u USER, --user USER The git username (or email address) to report. Default
|
|
739
|
+
is all contributing users
|
|
734
740
|
-s START, --start START
|
|
735
741
|
Start date in mm/dd/yyyy format
|
|
736
742
|
-e END, --end END End date in mm/dd/yyyy format
|
|
737
743
|
-x EXCLUSIONS, --exclusions EXCLUSIONS
|
|
738
|
-
A comma-separated list of files, patterns or
|
|
744
|
+
A comma-separated list of files, patterns or
|
|
745
|
+
directories to exclude, e.g. --exclusions "foo.zip,
|
|
746
|
+
*.jpg, vendor". Replaces the default list; use "" to
|
|
747
|
+
exclude nothing
|
|
739
748
|
-f {csv,json,markdown}, --format {csv,json,markdown}
|
|
740
749
|
The format in which to output the results
|
|
741
750
|
-b BRANCH, --branch BRANCH
|
|
742
|
-
The branch to
|
|
751
|
+
The branch to parse, if not the default branch
|
|
743
752
|
-v, --verbose Whether to output debugging info
|
|
744
753
|
-c, --clean Remove contributors without any contribuition
|
|
754
|
+
--github Also report GitHub pull requests opened, merged,
|
|
755
|
+
closed and self-merged per contributor, and match
|
|
756
|
+
GitHub users to git authors. Needs the GitHub CLI
|
|
757
|
+
(gh), signed in
|
|
745
758
|
```
|
|
746
759
|
|
|
747
760
|
In case `gitlogstats` is not found as a command, even after installing, try `python -m gitlogstats` or `python3 -m gitlogstats` instead.
|
|
@@ -822,6 +835,47 @@ By default, every contributor in a repository's history is listed, with zeros fo
|
|
|
822
835
|
gitlogstats -rf repos.txt -c
|
|
823
836
|
```
|
|
824
837
|
|
|
838
|
+
### GitHub pull requests
|
|
839
|
+
|
|
840
|
+
Add `--github` to also count each contributor's pull requests. Five columns are appended to every output format:
|
|
841
|
+
|
|
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
|
+
|
|
850
|
+
```
|
|
851
|
+
gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
**Requirements.** Only `--github` needs the [GitHub CLI](https://cli.github.com) (`gh`); without the flag nothing changes and GitHub is never contacted. Install `gh`, then run `gh auth login`. The tool checks this before cloning anything. If it fails, nothing is printed to standard output, the exit status is 1 and one of these messages is printed to standard error:
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
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.
|
|
858
|
+
gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**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.
|
|
862
|
+
|
|
863
|
+
**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:
|
|
864
|
+
|
|
865
|
+
1. **Email.** The email is a GitHub `noreply` address containing the username, GitHub itself links one of the author's commits to an account, or the email is the public email of a GitHub profile.
|
|
866
|
+
2. **Username.** The author name, or the part of the email before the `@`, is the GitHub username (ignoring case, and treating spaces, hyphens, underscores and dots as alike). The part before the `@` is skipped when it names a role rather than a person (such as `noreply`, `admin` or `info`), or when several author names in the repository share it (`alex@gmail…` and `alex@school…`), since it could belong to either.
|
|
867
|
+
3. **Display name.** The author name is the GitHub profile name (ignoring case, accents, punctuation and word order, so `Grace Hopper` matches `Hopper, Grace`).
|
|
868
|
+
|
|
869
|
+
If a step finds several users it is skipped as ambiguous and the next is tried. All git names matched to one GitHub user become one row: the `username` is the name with the most commits and the git statistics are added up. Use `-v` to see which step matched each name, the ambiguous and unmatched names, and how many `gh` calls were made. A wrong match can be fixed with a `.mailmap` file.
|
|
870
|
+
|
|
871
|
+
**How the other options apply.**
|
|
872
|
+
|
|
873
|
+
- `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed) against the same inclusive local-time range as commits.
|
|
874
|
+
- `-b`: only pull requests whose base branch is that branch are counted; without `-b`, all pull requests count.
|
|
875
|
+
- `-u`: selects the combined row whose author names or emails equal the value exactly, or whose GitHub username equals it ignoring case. The whole combined row is reported, so `-u` with one email address also includes the person's commits under their other names and emails, and a value that matches nobody prints no row.
|
|
876
|
+
- `-c`: removes only rows with no git activity and no pull requests.
|
|
877
|
+
- `-x`: has no effect on pull requests.
|
|
878
|
+
|
|
825
879
|
### Formatting the results
|
|
826
880
|
|
|
827
881
|
The results can be formatted as `csv`, `json`, or a `markdown` table. The default is `csv`. Use the `-f` flag to control the output format.
|
|
@@ -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,6 +12,8 @@ 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, closed without merging and merged by themselves (see [GitHub pull requests](#github-pull-requests)).
|
|
16
|
+
|
|
15
17
|
The results can be formatted as `csv`, `json`, or `markdown`.
|
|
16
18
|
|
|
17
19
|
## Install
|
|
@@ -25,26 +27,37 @@ e.g. `pip install gitlogstats`, `pip3 install gitlogstats`, depending on your en
|
|
|
25
27
|
The command `gitlogstats --help` shows the usage instructions:
|
|
26
28
|
|
|
27
29
|
```
|
|
28
|
-
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
30
|
+
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
31
|
+
[-e END] [-x EXCLUSIONS] [-f {csv,json,markdown}]
|
|
32
|
+
[-b BRANCH] [-v] [-c] [--github]
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
options:
|
|
31
35
|
-h, --help show this help message and exit
|
|
32
36
|
-r REPOSITORY, --repository REPOSITORY
|
|
33
37
|
the public URL of the repository whose logs to parse
|
|
34
38
|
-rf REPOFILE, --repofile REPOFILE
|
|
35
|
-
the path to simple text file with a list of repository
|
|
36
|
-
|
|
39
|
+
the path to simple text file with a list of repository
|
|
40
|
+
URLs to parse
|
|
41
|
+
-u USER, --user USER The git username (or email address) to report. Default
|
|
42
|
+
is all contributing users
|
|
37
43
|
-s START, --start START
|
|
38
44
|
Start date in mm/dd/yyyy format
|
|
39
45
|
-e END, --end END End date in mm/dd/yyyy format
|
|
40
46
|
-x EXCLUSIONS, --exclusions EXCLUSIONS
|
|
41
|
-
A comma-separated list of files, patterns or
|
|
47
|
+
A comma-separated list of files, patterns or
|
|
48
|
+
directories to exclude, e.g. --exclusions "foo.zip,
|
|
49
|
+
*.jpg, vendor". Replaces the default list; use "" to
|
|
50
|
+
exclude nothing
|
|
42
51
|
-f {csv,json,markdown}, --format {csv,json,markdown}
|
|
43
52
|
The format in which to output the results
|
|
44
53
|
-b BRANCH, --branch BRANCH
|
|
45
|
-
The branch to
|
|
54
|
+
The branch to parse, if not the default branch
|
|
46
55
|
-v, --verbose Whether to output debugging info
|
|
47
56
|
-c, --clean Remove contributors without any contribuition
|
|
57
|
+
--github Also report GitHub pull requests opened, merged,
|
|
58
|
+
closed and self-merged per contributor, and match
|
|
59
|
+
GitHub users to git authors. Needs the GitHub CLI
|
|
60
|
+
(gh), signed in
|
|
48
61
|
```
|
|
49
62
|
|
|
50
63
|
In case `gitlogstats` is not found as a command, even after installing, try `python -m gitlogstats` or `python3 -m gitlogstats` instead.
|
|
@@ -125,6 +138,47 @@ By default, every contributor in a repository's history is listed, with zeros fo
|
|
|
125
138
|
gitlogstats -rf repos.txt -c
|
|
126
139
|
```
|
|
127
140
|
|
|
141
|
+
### GitHub pull requests
|
|
142
|
+
|
|
143
|
+
Add `--github` to also count each contributor's pull requests. Five columns are appended to every output format:
|
|
144
|
+
|
|
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
|
+
|
|
153
|
+
```
|
|
154
|
+
gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Requirements.** Only `--github` needs the [GitHub CLI](https://cli.github.com) (`gh`); without the flag nothing changes and GitHub is never contacted. Install `gh`, then run `gh auth login`. The tool checks this before cloning anything. If it fails, nothing is printed to standard output, the exit status is 1 and one of these messages is printed to standard error:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
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.
|
|
161
|
+
gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**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.
|
|
165
|
+
|
|
166
|
+
**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:
|
|
167
|
+
|
|
168
|
+
1. **Email.** The email is a GitHub `noreply` address containing the username, GitHub itself links one of the author's commits to an account, or the email is the public email of a GitHub profile.
|
|
169
|
+
2. **Username.** The author name, or the part of the email before the `@`, is the GitHub username (ignoring case, and treating spaces, hyphens, underscores and dots as alike). The part before the `@` is skipped when it names a role rather than a person (such as `noreply`, `admin` or `info`), or when several author names in the repository share it (`alex@gmail…` and `alex@school…`), since it could belong to either.
|
|
170
|
+
3. **Display name.** The author name is the GitHub profile name (ignoring case, accents, punctuation and word order, so `Grace Hopper` matches `Hopper, Grace`).
|
|
171
|
+
|
|
172
|
+
If a step finds several users it is skipped as ambiguous and the next is tried. All git names matched to one GitHub user become one row: the `username` is the name with the most commits and the git statistics are added up. Use `-v` to see which step matched each name, the ambiguous and unmatched names, and how many `gh` calls were made. A wrong match can be fixed with a `.mailmap` file.
|
|
173
|
+
|
|
174
|
+
**How the other options apply.**
|
|
175
|
+
|
|
176
|
+
- `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed) against the same inclusive local-time range as commits.
|
|
177
|
+
- `-b`: only pull requests whose base branch is that branch are counted; without `-b`, all pull requests count.
|
|
178
|
+
- `-u`: selects the combined row whose author names or emails equal the value exactly, or whose GitHub username equals it ignoring case. The whole combined row is reported, so `-u` with one email address also includes the person's commits under their other names and emails, and a value that matches nobody prints no row.
|
|
179
|
+
- `-c`: removes only rows with no git activity and no pull requests.
|
|
180
|
+
- `-x`: has no effect on pull requests.
|
|
181
|
+
|
|
128
182
|
### Formatting the results
|
|
129
183
|
|
|
130
184
|
The results can be formatted as `csv`, `json`, or a `markdown` table. The default is `csv`. Use the `-f` flag to control the output format.
|
|
@@ -35,7 +35,7 @@ Repositories are cloned into a `repos/` directory under the current working dire
|
|
|
35
35
|
|
|
36
36
|
#### REPO-4 Branch selection
|
|
37
37
|
|
|
38
|
-
`-b/--branch <name>` checks out the given branch before statistics are computed. Without `-b`, the repository's default branch is used — including when an earlier run left the cached clone on another branch.
|
|
38
|
+
`-b/--branch <name>` checks out the given branch before statistics are computed. Without `-b`, the repository's default branch is used — including when an earlier run left the cached clone on another branch. With `--github`, it also limits which pull requests are counted to those whose base branch is that branch (CLI-3).
|
|
39
39
|
|
|
40
40
|
**Status:** Done
|
|
41
41
|
|
|
@@ -83,17 +83,54 @@ For each contributor, the number of merge commits they authored directly on the
|
|
|
83
83
|
|
|
84
84
|
**Status:** Done
|
|
85
85
|
|
|
86
|
+
#### STAT-8 Pull requests opened
|
|
87
|
+
|
|
88
|
+
With `--github` (CLI-3), for each contributor, the number of pull requests they authored on GitHub whose creation time (`created_at`) falls in the date range, whatever their state now (open, merged or closed; drafts included). Reported in a `prs_opened` column.
|
|
89
|
+
|
|
90
|
+
**Status:** Done
|
|
91
|
+
|
|
92
|
+
#### STAT-9 Pull requests merged
|
|
93
|
+
|
|
94
|
+
With `--github`, for each contributor, the number of pull requests they authored that were merged (`merged_at` set) in the date range, whoever merged them. Reported in a `prs_merged` column. Unlike merges (STAT-7), this counts squash and rebase merges too, since GitHub records them as merged pull requests.
|
|
95
|
+
|
|
96
|
+
**Status:** Done
|
|
97
|
+
|
|
98
|
+
#### STAT-10 Pull requests closed without merging
|
|
99
|
+
|
|
100
|
+
With `--github`, for each contributor, the number of pull requests they authored that were closed without being merged (`state` closed and `merged_at` not set), with a closing time (`closed_at`) in the date range. Reported in a `prs_closed_unmerged` column.
|
|
101
|
+
|
|
102
|
+
**Status:** Done
|
|
103
|
+
|
|
104
|
+
#### STAT-11 Matching GitHub users to git authors
|
|
105
|
+
|
|
106
|
+
With `--github`, each pull-request author (a GitHub username) is matched to the git identities (author name and email) in the repository's history. Each git identity is tried against the GitHub users active in that repository (pull-request authors and the accounts GitHub links to its commits), tier by tier, stopping at the first tier that finds exactly one GitHub user:
|
|
107
|
+
|
|
108
|
+
1. **Email.** The email is a GitHub `noreply` address (`12345+login@users.noreply.github.com` or `login@users.noreply.github.com`); or GitHub links one of the identity's commits to an account (the commit's `author.login`, which covers private emails GitHub knows but does not publish); or the email equals a candidate's public profile email (case-insensitive).
|
|
109
|
+
2. **Username.** The author name, or the email's local part (before `@`), equals a candidate's GitHub username, ignoring case and treating spaces, hyphens, underscores and dots as the same (`Ada Lovelace` matches `Ada-Lovelace`). An email local part is not used when it names a role rather than a person (such as `noreply`, `admin` or `info`), or when it is shared by several author names in the repository (`alex@gmail…` and `alex@school…`), since it could belong to either.
|
|
110
|
+
3. **Display name.** The author name equals a candidate's GitHub display name, ignoring case, accents, repeated spaces and punctuation, and word order (`Grace Hopper` matches `Hopper, Grace`).
|
|
111
|
+
4. **No match.** A GitHub user matched by no git identity is reported in a row of its own, with an empty `username`, its pull-request statistics and zero git statistics.
|
|
112
|
+
|
|
113
|
+
If a tier finds several GitHub users for one identity, that tier is skipped as ambiguous and the next is tried; ambiguous and unmatched identities are listed with `-v`. All git identities matched to the same GitHub user are combined into one row: the `username` is the matched author name with the most commits (ties broken alphabetically), and the git statistics are summed. A git identity matched to no GitHub user keeps its own row with an empty `github_username` and zero pull-request statistics. Every row carries the matched GitHub username (never the display name) in a `github_username` column, so matches can be checked and corrected by hand, e.g. with a `.mailmap` (STAT-6).
|
|
114
|
+
|
|
115
|
+
**Status:** Done
|
|
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
|
+
|
|
86
123
|
### 3. Filtering
|
|
87
124
|
|
|
88
125
|
#### FILT-1 Date range
|
|
89
126
|
|
|
90
|
-
`-s/--start` and `-e/--end` (format `mm/dd/yyyy`) limit the analysis to commits whose commit date falls between the start of the start date and the end of the end date, inclusive, in local time. Defaults: one year ago until today. The output records the requested start and end dates on every row.
|
|
127
|
+
`-s/--start` and `-e/--end` (format `mm/dd/yyyy`) limit the analysis to commits whose commit date falls between the start of the start date and the end of the end date, inclusive, in local time. Defaults: one year ago until today. The output records the requested start and end dates on every row. With `--github`, each pull request count is filtered by its own timestamp in the same range (CLI-3).
|
|
91
128
|
|
|
92
129
|
**Status:** Done
|
|
93
130
|
|
|
94
131
|
#### FILT-2 Single contributor
|
|
95
132
|
|
|
96
|
-
`-u/--user <name>` reports only the contributor whose author name or email address matches exactly. Without `-u`, every contributor in the repository's history is reported.
|
|
133
|
+
`-u/--user <name>` reports only the contributor whose author name or email address matches exactly. Without `-u`, every contributor in the repository's history is reported. With `--github`, it selects the combined row, and a value may also be the GitHub username (ignoring case); see CLI-3.
|
|
97
134
|
|
|
98
135
|
**Status:** Done
|
|
99
136
|
|
|
@@ -105,7 +142,7 @@ For each contributor, the number of merge commits they authored directly on the
|
|
|
105
142
|
|
|
106
143
|
#### FILT-4 Remove inactive contributors
|
|
107
144
|
|
|
108
|
-
By default, contributors with no activity in the date range are listed with zeros. `-c/--clean` removes them.
|
|
145
|
+
By default, contributors with no activity in the date range are listed with zeros. `-c/--clean` removes them. With `--github`, only rows with neither git nor pull-request activity are removed (CLI-3).
|
|
109
146
|
|
|
110
147
|
**Status:** Done
|
|
111
148
|
|
|
@@ -113,7 +150,7 @@ By default, contributors with no activity in the date range are listed with zero
|
|
|
113
150
|
|
|
114
151
|
#### OUT-1 CSV output
|
|
115
152
|
|
|
116
|
-
`-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.
|
|
153
|
+
`-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` are appended, and are in the header even when there are no rows (CLI-3).
|
|
117
154
|
|
|
118
155
|
**Status:** Done
|
|
119
156
|
|
|
@@ -137,7 +174,7 @@ When several repositories are analyzed, the results are printed once, after all
|
|
|
137
174
|
|
|
138
175
|
#### OUT-5 Verbose diagnostics
|
|
139
176
|
|
|
140
|
-
`-v/--verbose` prints debugging information (directories, the git commands run, contributors found) to standard error, so standard output stays machine-readable.
|
|
177
|
+
`-v/--verbose` prints debugging information (directories, the git commands run, contributors found) to standard error, so standard output stays machine-readable. With `--github`, it also prints how each author name was matched to a GitHub user, the ambiguous and unmatched names, and the number of `gh` API calls made for each repository (CLI-3).
|
|
141
178
|
|
|
142
179
|
**Status:** Done
|
|
143
180
|
|
|
@@ -155,6 +192,17 @@ Invalid dates, and a start date after the end date, are rejected with a usage er
|
|
|
155
192
|
|
|
156
193
|
**Status:** Done
|
|
157
194
|
|
|
195
|
+
#### CLI-3 GitHub pull-request statistics
|
|
196
|
+
|
|
197
|
+
`--github` adds GitHub pull-request statistics (STAT-8 to STAT-12) for repositories hosted on github.com, appending the columns `github_username, prs_opened, prs_merged, prs_closed_unmerged, prs_self_merged` 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:
|
|
198
|
+
|
|
199
|
+
- `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.`
|
|
200
|
+
- `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.`
|
|
201
|
+
|
|
202
|
+
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 five GitHub columns are empty (`null` in JSON) rather than zero, and the reason is printed to standard error.
|
|
203
|
+
|
|
204
|
+
**Status:** Done
|
|
205
|
+
|
|
158
206
|
### 6. Packaging & Quality
|
|
159
207
|
|
|
160
208
|
#### PKG-1 Installable package
|
|
@@ -165,7 +213,7 @@ The tool installs with `pip install gitlogstats`, which provides a `gitlogstats`
|
|
|
165
213
|
|
|
166
214
|
#### PKG-2 Continuous integration and release
|
|
167
215
|
|
|
168
|
-
GitHub Actions builds the package on
|
|
216
|
+
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.
|
|
169
217
|
|
|
170
218
|
**Status:** Done
|
|
171
219
|
|
|
@@ -177,9 +225,9 @@ Unit tests cover parsing and formatting, and integration tests build real git re
|
|
|
177
225
|
|
|
178
226
|
#### PKG-4 Run tests in CI
|
|
179
227
|
|
|
180
|
-
The CI workflow runs the test suite on every push and
|
|
228
|
+
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.
|
|
181
229
|
|
|
182
|
-
**Status:**
|
|
230
|
+
**Status:** Done
|
|
183
231
|
|
|
184
232
|
### 7. Future Work
|
|
185
233
|
|
|
@@ -189,3 +237,7 @@ The CI workflow runs the test suite on every push and stops the release if any t
|
|
|
189
237
|
- **Parallel processing** — clone and analyze many repositories concurrently to speed up large batches.
|
|
190
238
|
- **Per-file breakdown** — optionally report each contributor's changes per file or directory.
|
|
191
239
|
- **Output to file** — write results to a file (e.g. `-o report.csv`) instead of only standard output.
|
|
240
|
+
- **GitHub without the gh CLI** — let `--github` call the GitHub API directly with a `GITHUB_TOKEN`, for users who cannot install `gh`.
|
|
241
|
+
- **GitHub Enterprise hosts** — extend `--github` to repositories on GitHub Enterprise Server, via `gh api --hostname` or a configurable API URL.
|
|
242
|
+
- **Pull-request reviews and mergers** — report who reviewed and who merged each pull request, in addition to who opened it.
|
|
243
|
+
- **GitHub-only mode** — report pull-request statistics without computing git statistics, for faster runs over large batches.
|
|
@@ -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.0
|
|
8
|
+
version = "1.2.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Tobody Tothing", email="nobody.nothing@nowhere.com" },
|
|
11
11
|
]
|