gitlogstats 1.3.0__tar.gz → 1.4.1__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 (48) hide show
  1. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/PKG-INFO +22 -14
  2. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/README.md +21 -13
  3. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/docs/SPEC.md +17 -5
  4. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/pyproject.toml +1 -1
  5. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/scripts/board/manifest.yaml +48 -15
  6. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/src/gitlogstats/__main__.py +14 -4
  7. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/src/gitlogstats/git_logs_parser.py +48 -3
  8. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/src/gitlogstats/github.py +131 -9
  9. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/src/gitlogstats/github_stats.py +26 -4
  10. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/src/gitlogstats/identity.py +20 -9
  11. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/pulls_page1.json +4 -0
  12. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/test_github.py +72 -0
  13. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/test_identity.py +22 -6
  14. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/test_integration.py +432 -16
  15. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/CLAUDE.md +0 -0
  16. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/agents/developer-agent.md +0 -0
  17. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/agents/spec-reviewer.md +0 -0
  18. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/hooks/format-edited.sh +0 -0
  19. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/hooks/guard-paths.sh +0 -0
  20. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/hooks/guard-paths.test.mjs +0 -0
  21. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/hooks/verify.sh +0 -0
  22. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/settings.json +0 -0
  23. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/skills/phase-handoff/SKILL.md +0 -0
  24. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.claude/skills/stale-check/SKILL.md +0 -0
  25. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.github/workflows/build.yaml +0 -0
  26. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.gitignore +0 -0
  27. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/.prettierrc.json +0 -0
  28. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/LICENSE +0 -0
  29. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/Pipfile +0 -0
  30. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/Pipfile.lock +0 -0
  31. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/package-lock.json +0 -0
  32. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/package.json +0 -0
  33. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/repos.txt +0 -0
  34. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/requirements.txt +0 -0
  35. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/scripts/board/config.mjs +0 -0
  36. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/scripts/board/derive.mjs +0 -0
  37. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/scripts/board/derive.test.mjs +0 -0
  38. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/scripts/board/sync.mjs +0 -0
  39. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/src/gitlogstats/__init__.py +0 -0
  40. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fake_gh.py +0 -0
  41. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/commit_linked.json +0 -0
  42. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/commit_unlinked.json +0 -0
  43. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/contributors.json +0 -0
  44. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/contributors_page2.json +0 -0
  45. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/pulls_page2.json +0 -0
  46. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/user_full.json +0 -0
  47. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/fixtures/github/user_private.json +0 -0
  48. {gitlogstats-1.3.0 → gitlogstats-1.4.1}/tests/test_git_logs_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gitlogstats
3
- Version: 1.3.0
3
+ Version: 1.4.1
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, closed without merging and merged by themselves (see [GitHub pull requests](#github-pull-requests)).
712
+ With `--github`, it also reports each developer's GitHub **pull requests** opened, merged, closed without merging and merged by themselves, their **pushes** to the analyzed branch, their commits made on GitHub's **website**, and their **git and GitHub email addresses**. It matches each GitHub account to the developer's git names and combines a developer who committed under several names into one row (see [GitHub statistics](#github-statistics)).
713
713
 
714
714
  The results can be formatted as `csv`, `json`, or `markdown`.
715
715
 
@@ -753,8 +753,10 @@ options:
753
753
  -c, --clean Remove contributors without any contribuition
754
754
  --github Also report GitHub pull requests opened, merged,
755
755
  closed and self-merged per contributor, with their git
756
- and GitHub emails, and match GitHub users to git
757
- authors. Needs the GitHub CLI (gh), signed in
756
+ and GitHub emails, their pushes to the analyzed branch
757
+ and their commits made on the GitHub website, and
758
+ match GitHub users to git authors. Needs the GitHub
759
+ CLI (gh), signed in
758
760
  ```
759
761
 
760
762
  In case `gitlogstats` is not found as a command, even after installing, try `python -m gitlogstats` or `python3 -m gitlogstats` instead.
@@ -835,9 +837,9 @@ By default, every contributor in a repository's history is listed, with zeros fo
835
837
  gitlogstats -rf repos.txt -c
836
838
  ```
837
839
 
838
- ### GitHub pull requests
840
+ ### GitHub statistics
839
841
 
840
- Add `--github` to also count each contributor's pull requests. Seven columns are appended to every output format:
842
+ Add `--github` to also report each contributor's pull requests, pushes, website commits and email addresses. Nine columns are appended to every output format:
841
843
 
842
844
  | Column | Meaning |
843
845
  | --------------------- | ------------------------------------------------------------------------------------------------------- |
@@ -848,6 +850,8 @@ Add `--github` to also count each contributor's pull requests. Seven columns are
848
850
  | `prs_self_merged` | pull requests they authored and also merged themselves, merged in the date range (also in `prs_merged`) |
849
851
  | `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
852
  | `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 |
853
+ | `pushes_to_default` | times they pushed directly to the analyzed branch (the default branch, or the `-b` branch) in the date range; each push counts once however many commits it carries. Pull request merges, merge-queue merges and creating or deleting branches are not pushes |
854
+ | `web_commits` | their commits counted in `commits` that were made on GitHub's website (committer `GitHub <noreply@github.com>`), such as editing, creating, deleting or uploading a file in the browser; never more than `commits`. The commit GitHub makes when it squash- or rebase-merges a pull request is not counted |
851
855
 
852
856
  ```
853
857
  gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
@@ -860,7 +864,9 @@ gitlogstats: --github needs the GitHub CLI (gh), which is not installed. Install
860
864
  gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
861
865
  ```
862
866
 
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.
867
+ **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 or pushed 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.
868
+
869
+ **Limits of pushes and website commits.** Pushes come from GitHub's repository activity record, which starts in September 2023: earlier pushes are not counted, however old the date range. A file edited on the website directly on the analyzed branch is recorded by GitHub as a push too, so it counts in both `pushes_to_default` and `web_commits`. Reading it works for any public repository you can see; for a private one you need access to it, and if GitHub refuses, the repository's GitHub columns are empty with the reason on standard error, as above. A push is credited to the GitHub user who pushed it, so a person who only pushes (and has no commits or pull requests in the range) still gets a row of their own. Pushes made while a tag or commit (not a branch) is checked out with `-b` are not counted. Website commits are recognised by GitHub being the committer, so commits made through GitHub's API, for example in the `github.dev` editor, are counted as website commits too, and so is the first commit GitHub creates when a repository is created on the website. Website commits follow `commits`: the same date range, exclusions (`-x`) and branch, credited to the commit's author and added up when names are combined into one row.
864
870
 
865
871
  **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:
866
872
 
@@ -872,11 +878,11 @@ If a step finds several users it is skipped as ambiguous and the next is tried.
872
878
 
873
879
  **How the other options apply.**
874
880
 
875
- - `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed) against the same inclusive local-time range as commits.
876
- - `-b`: only pull requests whose base branch is that branch are counted; without `-b`, all pull requests count.
881
+ - `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed), and each push its push time, against the same inclusive local-time range as commits.
882
+ - `-b`: only pull requests whose base branch is that branch are counted (without `-b`, all pull requests count), and pushes and website commits are counted on that branch instead of the default branch.
877
883
  - `-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.
878
- - `-c`: removes only rows with no git activity and no pull requests.
879
- - `-x`: has no effect on pull requests.
884
+ - `-c`: removes only rows with no git activity, no pull requests and no pushes. Email addresses do not count as activity.
885
+ - `-x`: has no effect on pull requests or pushes; it does apply to `web_commits`, which only counts commits that `commits` counts.
880
886
 
881
887
  ### Formatting the results
882
888
 
@@ -918,7 +924,7 @@ Some merges are not counted:
918
924
  - Merges on other branches, unless that branch is analyzed with `-b`.
919
925
  - Local merges that were never pushed.
920
926
 
921
- A merge made on GitHub uses the person's GitHub profile name and email, which may differ from their local git settings. They can then appear under two names (see [Redundant usernames](#redundant-usernames)).
927
+ A merge made on GitHub uses the person's GitHub profile name and email, which may differ from their local git settings. They can then appear under two names; `--github` combines them into one row (see [Redundant usernames](#redundant-usernames)).
922
928
 
923
929
  Each commit is credited to its author's name, after applying the repository's `.mailmap` if it has one. Merges are credited the same way. Commit messages are never read, so statistics are the same whether messages are one line, many lines, or empty.
924
930
 
@@ -943,6 +949,8 @@ Most likely, the contributor has failed to update their version control settings
943
949
 
944
950
  ### Redundant usernames
945
951
 
946
- The output of the script sometimes lists the same individual contributor under more than one git username... This is most likely due to different username settings for various git and GitHub clients.
952
+ The output of the script sometimes lists the same individual contributor under more than one git username. This is most likely due to different username settings for various git and GitHub clients: for example, a merge made on GitHub's website uses the person's GitHub display name, while their own commits use their local `user.name`.
953
+
954
+ With `--github`, names that belong to the same GitHub account are combined into one row automatically, with their statistics added up (see [GitHub statistics](#github-statistics)). Check the `github_username` and `git_email` columns to see which names were combined. A name GitHub cannot link to any account, for example one whose email is a laptop's default address, keeps a row of its own; add the two names to a `.mailmap` file to combine them.
947
955
 
948
- If a single developer has multiple usernames that all show the same statistics, then only count those stats once. Otherwise, if a single developer has multiple usernames that show different statistics, then add them together to come up with the total for that developer.
956
+ Without `--github`, or for a repository not on GitHub, add up the rows of a developer who appears under several names yourself, or map the names together in a `.mailmap` file.
@@ -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, closed without merging and merged by themselves (see [GitHub pull requests](#github-pull-requests)).
15
+ With `--github`, it also reports each developer's GitHub **pull requests** opened, merged, closed without merging and merged by themselves, their **pushes** to the analyzed branch, their commits made on GitHub's **website**, and their **git and GitHub email addresses**. It matches each GitHub account to the developer's git names and combines a developer who committed under several names into one row (see [GitHub statistics](#github-statistics)).
16
16
 
17
17
  The results can be formatted as `csv`, `json`, or `markdown`.
18
18
 
@@ -56,8 +56,10 @@ options:
56
56
  -c, --clean Remove contributors without any contribuition
57
57
  --github Also report GitHub pull requests opened, merged,
58
58
  closed and self-merged per contributor, with their git
59
- and GitHub emails, and match GitHub users to git
60
- authors. Needs the GitHub CLI (gh), signed in
59
+ and GitHub emails, their pushes to the analyzed branch
60
+ and their commits made on the GitHub website, and
61
+ match GitHub users to git authors. Needs the GitHub
62
+ CLI (gh), signed in
61
63
  ```
62
64
 
63
65
  In case `gitlogstats` is not found as a command, even after installing, try `python -m gitlogstats` or `python3 -m gitlogstats` instead.
@@ -138,9 +140,9 @@ By default, every contributor in a repository's history is listed, with zeros fo
138
140
  gitlogstats -rf repos.txt -c
139
141
  ```
140
142
 
141
- ### GitHub pull requests
143
+ ### GitHub statistics
142
144
 
143
- Add `--github` to also count each contributor's pull requests. Seven columns are appended to every output format:
145
+ Add `--github` to also report each contributor's pull requests, pushes, website commits and email addresses. Nine columns are appended to every output format:
144
146
 
145
147
  | Column | Meaning |
146
148
  | --------------------- | ------------------------------------------------------------------------------------------------------- |
@@ -151,6 +153,8 @@ Add `--github` to also count each contributor's pull requests. Seven columns are
151
153
  | `prs_self_merged` | pull requests they authored and also merged themselves, merged in the date range (also in `prs_merged`) |
152
154
  | `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
155
  | `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 |
156
+ | `pushes_to_default` | times they pushed directly to the analyzed branch (the default branch, or the `-b` branch) in the date range; each push counts once however many commits it carries. Pull request merges, merge-queue merges and creating or deleting branches are not pushes |
157
+ | `web_commits` | their commits counted in `commits` that were made on GitHub's website (committer `GitHub <noreply@github.com>`), such as editing, creating, deleting or uploading a file in the browser; never more than `commits`. The commit GitHub makes when it squash- or rebase-merges a pull request is not counted |
154
158
 
155
159
  ```
156
160
  gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
@@ -163,7 +167,9 @@ gitlogstats: --github needs the GitHub CLI (gh), which is not installed. Install
163
167
  gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
164
168
  ```
165
169
 
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.
170
+ **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 or pushed 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.
171
+
172
+ **Limits of pushes and website commits.** Pushes come from GitHub's repository activity record, which starts in September 2023: earlier pushes are not counted, however old the date range. A file edited on the website directly on the analyzed branch is recorded by GitHub as a push too, so it counts in both `pushes_to_default` and `web_commits`. Reading it works for any public repository you can see; for a private one you need access to it, and if GitHub refuses, the repository's GitHub columns are empty with the reason on standard error, as above. A push is credited to the GitHub user who pushed it, so a person who only pushes (and has no commits or pull requests in the range) still gets a row of their own. Pushes made while a tag or commit (not a branch) is checked out with `-b` are not counted. Website commits are recognised by GitHub being the committer, so commits made through GitHub's API, for example in the `github.dev` editor, are counted as website commits too, and so is the first commit GitHub creates when a repository is created on the website. Website commits follow `commits`: the same date range, exclusions (`-x`) and branch, credited to the commit's author and added up when names are combined into one row.
167
173
 
168
174
  **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:
169
175
 
@@ -175,11 +181,11 @@ If a step finds several users it is skipped as ambiguous and the next is tried.
175
181
 
176
182
  **How the other options apply.**
177
183
 
178
- - `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed) against the same inclusive local-time range as commits.
179
- - `-b`: only pull requests whose base branch is that branch are counted; without `-b`, all pull requests count.
184
+ - `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed), and each push its push time, against the same inclusive local-time range as commits.
185
+ - `-b`: only pull requests whose base branch is that branch are counted (without `-b`, all pull requests count), and pushes and website commits are counted on that branch instead of the default branch.
180
186
  - `-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.
181
- - `-c`: removes only rows with no git activity and no pull requests.
182
- - `-x`: has no effect on pull requests.
187
+ - `-c`: removes only rows with no git activity, no pull requests and no pushes. Email addresses do not count as activity.
188
+ - `-x`: has no effect on pull requests or pushes; it does apply to `web_commits`, which only counts commits that `commits` counts.
183
189
 
184
190
  ### Formatting the results
185
191
 
@@ -221,7 +227,7 @@ Some merges are not counted:
221
227
  - Merges on other branches, unless that branch is analyzed with `-b`.
222
228
  - Local merges that were never pushed.
223
229
 
224
- A merge made on GitHub uses the person's GitHub profile name and email, which may differ from their local git settings. They can then appear under two names (see [Redundant usernames](#redundant-usernames)).
230
+ A merge made on GitHub uses the person's GitHub profile name and email, which may differ from their local git settings. They can then appear under two names; `--github` combines them into one row (see [Redundant usernames](#redundant-usernames)).
225
231
 
226
232
  Each commit is credited to its author's name, after applying the repository's `.mailmap` if it has one. Merges are credited the same way. Commit messages are never read, so statistics are the same whether messages are one line, many lines, or empty.
227
233
 
@@ -246,6 +252,8 @@ Most likely, the contributor has failed to update their version control settings
246
252
 
247
253
  ### Redundant usernames
248
254
 
249
- The output of the script sometimes lists the same individual contributor under more than one git username... This is most likely due to different username settings for various git and GitHub clients.
255
+ The output of the script sometimes lists the same individual contributor under more than one git username. This is most likely due to different username settings for various git and GitHub clients: for example, a merge made on GitHub's website uses the person's GitHub display name, while their own commits use their local `user.name`.
256
+
257
+ With `--github`, names that belong to the same GitHub account are combined into one row automatically, with their statistics added up (see [GitHub statistics](#github-statistics)). Check the `github_username` and `git_email` columns to see which names were combined. A name GitHub cannot link to any account, for example one whose email is a laptop's default address, keeps a row of its own; add the two names to a `.mailmap` file to combine them.
250
258
 
251
- If a single developer has multiple usernames that all show the same statistics, then only count those stats once. Otherwise, if a single developer has multiple usernames that show different statistics, then add them together to come up with the total for that developer.
259
+ Without `--github`, or for a repository not on GitHub, add up the rows of a developer who appears under several names yourself, or map the names together in a `.mailmap` file.
@@ -126,11 +126,23 @@ With `--github`, every row also reports the contributor's email addresses in two
126
126
 
127
127
  **Status:** Done
128
128
 
129
+ #### STAT-14 Pushes to the analyzed branch
130
+
131
+ With `--github`, for each contributor, the number of times they pushed directly to the analyzed branch (the default branch, or the `-b` branch) in the date range, reported in a `pushes_to_default` column appended after `github_email`. Pushes come from GitHub's repository activity record (`repos/{owner}/{repo}/activity`), which names the GitHub user who pushed: activity types `push` and `force_push` count; pull-request merges, merge-queue merges and branch creation or deletion do not. One push counts once however many commits it carries. Each push is filtered by its own timestamp, using the same inclusive local-time range as FILT-1, and credited to the row of the GitHub user who pushed (STAT-11); a GitHub user with pushes but no other row gets a GitHub-only row. GitHub's activity record does not go back before September 2023, so older pushes are not counted. Without `-b`, the branch is the default branch; if the clone is on no branch (a tag or commit was checked out), no pushes are counted. GitHub lets anyone who can see a public repository read its activity record; a private repository needs access to it.
132
+
133
+ **Status:** Done
134
+
135
+ #### STAT-15 Commits made on the GitHub website
136
+
137
+ With `--github`, for each contributor, how many of their commits counted in `commits` (STAT-1: same date range, exclusions and branch, merges excluded) were made on GitHub's website rather than in a local clone, reported in a `web_commits` column appended after `pushes_to_default`, so `web_commits` is never larger than `commits`. A commit counts when GitHub created it — its committer is `GitHub <noreply@github.com>` — and it is not the result of merging a pull request: a commit GitHub created when squash- or rebase-merging a pull request is not a website commit. Such a commit is the merge commit GitHub names for a merged pull request, or was committed within 2 seconds of one (the other commits of a rebase merge); GitHub commits the result a few seconds before it marks the pull request merged, so the pull request's merge time is used only when GitHub names no merge commit. A named merge commit committed more than a minute before or after the merge time is the pull request's own head commit, which reached the branch another way, so that pull request excludes nothing. A commit GitHub created when a repository was created on the website (a root commit) counts like any other. Such commits are typically file edits, creations, deletions and uploads made in the browser; commits made through GitHub's API (e.g. `github.dev`) look the same and are counted too. Website commits are credited to the commit's author, like `commits`, and summed when identities are combined (STAT-11). When GitHub statistics are unavailable for a repository (CLI-3), `pushes_to_default` and `web_commits` are empty (`null` in JSON).
138
+
139
+ **Status:** Done
140
+
129
141
  ### 3. Filtering
130
142
 
131
143
  #### FILT-1 Date range
132
144
 
133
- `-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).
145
+ `-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 and push count is filtered by its own timestamp in the same range (CLI-3).
134
146
 
135
147
  **Status:** Done
136
148
 
@@ -148,7 +160,7 @@ With `--github`, every row also reports the contributor's email addresses in two
148
160
 
149
161
  #### FILT-4 Remove inactive contributors
150
162
 
151
- 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).
163
+ 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, pull-request nor push activity are removed (CLI-3).
152
164
 
153
165
  **Status:** Done
154
166
 
@@ -156,7 +168,7 @@ By default, contributors with no activity in the date range are listed with zero
156
168
 
157
169
  #### OUT-1 CSV output
158
170
 
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).
171
+ `-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, pushes_to_default, web_commits` are appended, and are in the header even when there are no rows (CLI-3).
160
172
 
161
173
  **Status:** Done
162
174
 
@@ -200,12 +212,12 @@ Invalid dates, and a start date after the end date, are rejected with a usage er
200
212
 
201
213
  #### CLI-3 GitHub pull-request statistics
202
214
 
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:
215
+ `--github` adds GitHub pull-request statistics (STAT-8 to STAT-12), contributor emails (STAT-13), pushes to the analyzed branch (STAT-14) and website commits (STAT-15) 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, pushes_to_default, web_commits` 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:
204
216
 
205
217
  - `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.`
206
218
  - `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.`
207
219
 
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.
220
+ 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, pull-request nor push activity; `-x` does not apply to pull requests or pushes (it limits which commits count as website commits, as it does for `commits`); `-b` also selects the branch whose pushes are counted. 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`, `pushes_to_default` and `web_commits`) 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.
209
221
 
210
222
  **Status:** Done
211
223
 
@@ -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.3.0"
8
+ version = "1.4.1"
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: --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…
22
+ full: --github adds GitHub pull-request statistics (STAT-8 to STAT-12),
23
+ contributor emails (STAT-13), pushes to the analyzed branch (STAT-14) and
24
+ website commits (STAT-15) for repositories hosted on github.com, appending
25
+ the columns github_username, prs_opened, prs_merged, prs_closed_unmerged,
26
+ prs_self_merged, git_email, github_email, pushes_to_default, web_commits to
27
+ every output format. Without --github, the output and behaviour are
28
+ unchanged, GitHub is never contacted and the GitHub CLI (gh) is not needed.
29
+ With --github, the GitHub API is called through gh api, so gh must be
30
+ installed and signed in. This is checked before any repository is cloned; if
31
+ it fails, nothing is printed to standard…
32
32
  section: §5 Command-Line Interface
33
33
  family: CLI
34
34
  phase: 1
@@ -39,8 +39,8 @@
39
39
  whose commit date falls between the start of the start date and the end of
40
40
  the end date, inclusive, in local time. Defaults: one year ago until today.
41
41
  The output records the requested start and end dates on every row. With
42
- --github, each pull request count is filtered by its own timestamp in the
43
- same range (CLI-3)."
42
+ --github, each pull request and push count is filtered by its own timestamp
43
+ in the same range (CLI-3)."
44
44
  section: §3 Filtering
45
45
  family: FILT
46
46
  phase: 1
@@ -75,7 +75,7 @@
75
75
  title: Remove inactive contributors
76
76
  full: By default, contributors with no activity in the date range are listed
77
77
  with zeros. -c/--clean removes them. With --github, only rows with neither
78
- git nor pull-request activity are removed (CLI-3).
78
+ git, pull-request nor push activity are removed (CLI-3).
79
79
  section: §3 Filtering
80
80
  family: FILT
81
81
  phase: 1
@@ -89,8 +89,9 @@
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, prs_self_merged, git_email, github_email are appended,
93
- and are in the header even when there are no rows (CLI-3).
92
+ prs_closed_unmerged, prs_self_merged, git_email, github_email,
93
+ pushes_to_default, web_commits are appended, and are in the header even when
94
+ there are no rows (CLI-3).
94
95
  section: §4 Output
95
96
  family: OUT
96
97
  phase: 1
@@ -364,6 +365,38 @@
364
365
  family: STAT
365
366
  phase: 1
366
367
  status: Done
368
+ - id: STAT-14
369
+ title: Pushes to the analyzed branch
370
+ full: "With --github, for each contributor, the number of times they pushed
371
+ directly to the analyzed branch (the default branch, or the -b branch) in
372
+ the date range, reported in a pushes_to_default column appended after
373
+ github_email. Pushes come from GitHub's repository activity record
374
+ (repos/{owner}/{repo}/activity), which names the GitHub user who pushed:
375
+ activity types push and force_push count; pull-request merges, merge-queue
376
+ merges and branch creation or deletion do not. One push counts once however
377
+ many commits it carries. Each push is filtered by its own timestamp, using
378
+ the same inclusive local-time range as FILT-1, and credited to the row of
379
+ the GitHub user who pushed (STAT-11); a GitHub…"
380
+ section: §2 Contribution Statistics
381
+ family: STAT
382
+ phase: 1
383
+ status: Done
384
+ - id: STAT-15
385
+ title: Commits made on the GitHub website
386
+ full: "With --github, for each contributor, how many of their commits counted in
387
+ commits (STAT-1: same date range, exclusions and branch, merges excluded)
388
+ were made on GitHub's website rather than in a local clone, reported in a
389
+ web_commits column appended after pushes_to_default, so web_commits is never
390
+ larger than commits. A commit counts when GitHub created it — its committer
391
+ is GitHub <noreply@github.com> — and it is not the result of merging a pull
392
+ request: a commit GitHub created when squash- or rebase-merging a pull
393
+ request is not a website commit. Such a commit is the merge commit GitHub
394
+ names for a merged pull request, or was committed within 2 seconds of one
395
+ (the other commits of a…"
396
+ section: §2 Contribution Statistics
397
+ family: STAT
398
+ phase: 1
399
+ status: Done
367
400
  - id: FUTURE-author-date-filtering
368
401
  title: Author-date filtering
369
402
  full: Author-date filtering — offer filtering by author date, so rebased or
@@ -6,7 +6,13 @@ import re
6
6
  import sys
7
7
  from . import GitLogsParser
8
8
  from .git_logs_parser import FIELDNAMES
9
- from .github import EMAIL_FIELDNAMES, GITHUB_FIELDNAMES, GitHubUnavailable, check_gh
9
+ from .github import (
10
+ ACTIVITY_FIELDNAMES,
11
+ EMAIL_FIELDNAMES,
12
+ GITHUB_FIELDNAMES,
13
+ GitHubUnavailable,
14
+ check_gh,
15
+ )
10
16
  from .github_stats import github_rows
11
17
 
12
18
 
@@ -142,8 +148,10 @@ def main():
142
148
  parser.add_argument(
143
149
  "--github",
144
150
  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",
151
+ "self-merged per contributor, with their git and GitHub emails, their "
152
+ "pushes to the analyzed branch and their commits made on the GitHub "
153
+ "website, and match GitHub users to git authors. "
154
+ "Needs the GitHub CLI (gh), signed in",
147
155
  default=False,
148
156
  action="store_true",
149
157
  )
@@ -234,7 +242,9 @@ def main():
234
242
  if last_parser is not None:
235
243
  # an empty CSV table still gets the GitHub columns with --github
236
244
  fieldnames = (
237
- FIELDNAMES + GITHUB_FIELDNAMES + EMAIL_FIELDNAMES if args.github else None
245
+ FIELDNAMES + GITHUB_FIELDNAMES + EMAIL_FIELDNAMES + ACTIVITY_FIELDNAMES
246
+ if args.github
247
+ else None
238
248
  )
239
249
  output = last_parser.format_results(all_results, args.format, fieldnames)
240
250
  # end with exactly one newline: CSV and Markdown output already has one
@@ -21,6 +21,11 @@ FILES_RE = re.compile(r"(\d+) files? changed")
21
21
  INSERTIONS_RE = re.compile(r"(\d+) insertions?\(\+\)")
22
22
  DELETIONS_RE = re.compile(r"(\d+) deletions?\(-\)")
23
23
 
24
+ # STAT-15: the header of each commit when listing who committed it and when
25
+ COMMITTER_LOG_FORMAT = (
26
+ f"--format={RECORD_SEP}%H{FIELD_SEP}%aN{FIELD_SEP}%aE{FIELD_SEP}%cE{FIELD_SEP}%ct"
27
+ )
28
+
24
29
  # the fields reported for each contributor, in output order
25
30
  FIELDNAMES = [
26
31
  "username",
@@ -128,11 +133,13 @@ class GitLogsParser:
128
133
  f"--until={until:%Y-%m-%d %H:%M:%S}",
129
134
  ]
130
135
 
131
- def git_log_command(self):
136
+ def git_log_command(self, log_format=LOG_FORMAT, shortstat=True):
132
137
  """
133
138
  Build the git log command that lists every non-merge commit in the date
134
139
  range with its author and a one-line summary of its changes. Merge
135
140
  commits come from merge_log_command instead.
141
+ @param log_format: the --format argument
142
+ @param shortstat: whether to add the summary of changes
136
143
  @returns: the command as a list of arguments, ready for subprocess
137
144
  """
138
145
  # --full-history stops git from hiding commits while simplifying the
@@ -142,8 +149,8 @@ class GitLogsParser:
142
149
  "log",
143
150
  "--full-history",
144
151
  "--no-merges",
145
- "--shortstat",
146
- LOG_FORMAT,
152
+ *(["--shortstat"] if shortstat else []),
153
+ log_format,
147
154
  *self.date_range_args(),
148
155
  "--",
149
156
  ".",
@@ -185,6 +192,44 @@ class GitLogsParser:
185
192
  *self.date_range_args(),
186
193
  ]
187
194
 
195
+ def current_branch(self):
196
+ """
197
+ Name the branch checked out in the repository.
198
+ @returns: the branch name, or None if HEAD is detached
199
+ """
200
+ result = subprocess.run(
201
+ ["git", "symbolic-ref", "--short", "-q", "HEAD"], capture_output=True
202
+ )
203
+ if result.returncode != 0:
204
+ return None
205
+ return result.stdout.decode("utf-8", errors="replace").strip()
206
+
207
+ def committed_commits(self):
208
+ """
209
+ STAT-15: list the commits counted in `commits` (STAT-1: the same date
210
+ range, exclusions and branch, merges left out) with their committers.
211
+ @returns: a list of dicts with hash, name (author), email (author),
212
+ committer_email and committed (committer time, epoch seconds)
213
+ """
214
+ cmd = self.git_log_command(COMMITTER_LOG_FORMAT, shortstat=False)
215
+ self.verboseprint(f"Running command: {' '.join(cmd)}")
216
+ result = subprocess.run(cmd, capture_output=True, check=True)
217
+ commits = []
218
+ for record in result.stdout.decode("utf-8", errors="replace").split(RECORD_SEP)[
219
+ 1:
220
+ ]:
221
+ fields = record.strip("\n").split(FIELD_SEP)
222
+ commits.append(
223
+ {
224
+ "hash": fields[0],
225
+ "name": fields[1],
226
+ "email": fields[2],
227
+ "committer_email": fields[3],
228
+ "committed": int(fields[4]),
229
+ }
230
+ )
231
+ return commits
232
+
188
233
  def run_git_log(self, cmd):
189
234
  """
190
235
  Run a git log command and parse its output into per-commit records.