gitlogstats 1.5.2__tar.gz → 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/PKG-INFO +29 -2
  2. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/README.md +28 -1
  3. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/docs/SPEC.md +18 -0
  4. gitlogstats-1.7.0/docs/examples/project-1-stats.csv +126 -0
  5. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/pyproject.toml +1 -1
  6. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/scripts/board/manifest.yaml +32 -0
  7. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/__main__.py +38 -12
  8. gitlogstats-1.7.0/src/gitlogstats/aggregate.py +195 -0
  9. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/git_logs_parser.py +73 -17
  10. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/github.py +45 -0
  11. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/github_stats.py +143 -30
  12. gitlogstats-1.7.0/tests/test_aggregate.py +455 -0
  13. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/test_github.py +38 -0
  14. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/test_integration.py +445 -0
  15. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/CLAUDE.md +0 -0
  16. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/agents/developer-agent.md +0 -0
  17. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/agents/spec-reviewer.md +0 -0
  18. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/hooks/format-edited.sh +0 -0
  19. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/hooks/guard-paths.sh +0 -0
  20. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/hooks/guard-paths.test.mjs +0 -0
  21. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/hooks/verify.sh +0 -0
  22. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/settings.json +0 -0
  23. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/skills/phase-handoff/SKILL.md +0 -0
  24. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.claude/skills/stale-check/SKILL.md +0 -0
  25. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.github/workflows/build.yaml +0 -0
  26. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.gitignore +0 -0
  27. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/.prettierrc.json +0 -0
  28. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/LICENSE +0 -0
  29. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/Pipfile +0 -0
  30. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/Pipfile.lock +0 -0
  31. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/docs/examples/README.md +0 -0
  32. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/docs/examples/assignment-stats.sh +0 -0
  33. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/package-lock.json +0 -0
  34. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/package.json +0 -0
  35. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/repos.txt +0 -0
  36. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/requirements.txt +0 -0
  37. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/scripts/board/config.mjs +0 -0
  38. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/scripts/board/derive.mjs +0 -0
  39. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/scripts/board/derive.test.mjs +0 -0
  40. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/scripts/board/sync.mjs +0 -0
  41. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/__init__.py +0 -0
  42. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/clones.py +0 -0
  43. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/src/gitlogstats/identity.py +0 -0
  44. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fake_gh.py +0 -0
  45. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/commit_linked.json +0 -0
  46. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/commit_unlinked.json +0 -0
  47. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/contributors.json +0 -0
  48. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/contributors_page2.json +0 -0
  49. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/pulls_page1.json +0 -0
  50. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/pulls_page2.json +0 -0
  51. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/user_full.json +0 -0
  52. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/fixtures/github/user_private.json +0 -0
  53. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/test_clobber.py +0 -0
  54. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/test_git_logs_parser.py +0 -0
  55. {gitlogstats-1.5.2 → gitlogstats-1.7.0}/tests/test_identity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gitlogstats
3
- Version: 1.5.2
3
+ Version: 1.7.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
@@ -726,7 +726,8 @@ The command `gitlogstats --help` shows the usage instructions:
726
726
  ```
727
727
  usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
728
728
  [-e END] [-x EXCLUSIONS] [-f {csv,json,markdown}]
729
- [-b BRANCH] [-v] [-c] [--github] [--clobber]
729
+ [-b BRANCH] [-v] [-c] [-a {developer,repository,day}]
730
+ [--github] [--clobber]
730
731
 
731
732
  options:
732
733
  -h, --help show this help message and exit
@@ -751,6 +752,11 @@ options:
751
752
  The branch to parse, if not the default branch
752
753
  -v, --verbose Whether to output debugging info
753
754
  -c, --clean Remove contributors without any contribuition
755
+ -a {developer,repository,day}, --aggregate {developer,repository,day}
756
+ Combine the results into one row per developer, summed
757
+ over all repositories, one row per repository, summed
758
+ over its contributors, or one row per day with
759
+ activity, summed over everyone
754
760
  --github Also report GitHub pull requests opened, merged,
755
761
  closed and self-merged per contributor, with their git
756
762
  and GitHub emails, their pushes to the analyzed branch
@@ -840,6 +846,27 @@ By default, every contributor in a repository's history is listed, with zeros fo
840
846
  gitlogstats -rf repos.txt -c
841
847
  ```
842
848
 
849
+ ### Totals per developer or per repository
850
+
851
+ By default there is one row per contributor per repository. Add `-a developer` to get one row per developer with their statistics summed over every repository, or `-a repository` to get one row per repository summed over its contributors.
852
+
853
+ ```
854
+ gitlogstats -rf repos.txt -a developer
855
+ gitlogstats -rf repos.txt -a repository
856
+ ```
857
+
858
+ Developer rows have no `repository` column; instead a `repositories` column counts the repositories the developer was active in during the date range. Repository rows have no username or email columns; a `contributors` column counts the contributors who were active in the repository during the date range. `-u` and `-c` are applied first, so `-u alice -a repository` shows Alice's share of each repository. With `--github`, a developer's rows are combined by GitHub username, so different git names linked to one GitHub account become one row, and their email addresses are listed together. A developer matched to a GitHub account in one repository but not in another (for example a repository not on GitHub) keeps a separate row for each.
859
+
860
+ ### Activity per day
861
+
862
+ Add `-a day` to get one row per day on which anything happened, summed over every contributor and repository, in date order:
863
+
864
+ ```
865
+ gitlogstats -rf repos.txt -a day
866
+ ```
867
+
868
+ The columns are `date`, the usual counts, `repositories` (how many repositories had activity that day) and `contributors` (how many developers were active that day; someone active in several repositories counts once, but, as with `-a developer`, someone matched to a GitHub account in one repository and not in another counts twice). Every event is counted on its own day in local time: commits and merges on their commit date and, with `--github`, pull requests on the day they were opened, merged or closed and pushes on the day they were pushed. The days therefore add up to the totals printed without `-a`. Days without activity are left out. Combine with `-u` to see one contributor's days.
869
+
843
870
  ### Cloned repositories
844
871
 
845
872
  Each repository is cloned into a `repos/` directory under the directory you run the command from, and later runs update (`git pull`) the existing clone instead of cloning again, which makes them much faster. Add `--clobber` to delete the clones the run used, once the results have been printed; `repos/` itself is removed too if nothing else is left in it. Clones from other runs are kept, and nothing is deleted if the run fails before printing, so you can inspect it. With `-v`, each deleted path is listed on standard error.
@@ -29,7 +29,8 @@ The command `gitlogstats --help` shows the usage instructions:
29
29
  ```
30
30
  usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
31
31
  [-e END] [-x EXCLUSIONS] [-f {csv,json,markdown}]
32
- [-b BRANCH] [-v] [-c] [--github] [--clobber]
32
+ [-b BRANCH] [-v] [-c] [-a {developer,repository,day}]
33
+ [--github] [--clobber]
33
34
 
34
35
  options:
35
36
  -h, --help show this help message and exit
@@ -54,6 +55,11 @@ options:
54
55
  The branch to parse, if not the default branch
55
56
  -v, --verbose Whether to output debugging info
56
57
  -c, --clean Remove contributors without any contribuition
58
+ -a {developer,repository,day}, --aggregate {developer,repository,day}
59
+ Combine the results into one row per developer, summed
60
+ over all repositories, one row per repository, summed
61
+ over its contributors, or one row per day with
62
+ activity, summed over everyone
57
63
  --github Also report GitHub pull requests opened, merged,
58
64
  closed and self-merged per contributor, with their git
59
65
  and GitHub emails, their pushes to the analyzed branch
@@ -143,6 +149,27 @@ By default, every contributor in a repository's history is listed, with zeros fo
143
149
  gitlogstats -rf repos.txt -c
144
150
  ```
145
151
 
152
+ ### Totals per developer or per repository
153
+
154
+ By default there is one row per contributor per repository. Add `-a developer` to get one row per developer with their statistics summed over every repository, or `-a repository` to get one row per repository summed over its contributors.
155
+
156
+ ```
157
+ gitlogstats -rf repos.txt -a developer
158
+ gitlogstats -rf repos.txt -a repository
159
+ ```
160
+
161
+ Developer rows have no `repository` column; instead a `repositories` column counts the repositories the developer was active in during the date range. Repository rows have no username or email columns; a `contributors` column counts the contributors who were active in the repository during the date range. `-u` and `-c` are applied first, so `-u alice -a repository` shows Alice's share of each repository. With `--github`, a developer's rows are combined by GitHub username, so different git names linked to one GitHub account become one row, and their email addresses are listed together. A developer matched to a GitHub account in one repository but not in another (for example a repository not on GitHub) keeps a separate row for each.
162
+
163
+ ### Activity per day
164
+
165
+ Add `-a day` to get one row per day on which anything happened, summed over every contributor and repository, in date order:
166
+
167
+ ```
168
+ gitlogstats -rf repos.txt -a day
169
+ ```
170
+
171
+ The columns are `date`, the usual counts, `repositories` (how many repositories had activity that day) and `contributors` (how many developers were active that day; someone active in several repositories counts once, but, as with `-a developer`, someone matched to a GitHub account in one repository and not in another counts twice). Every event is counted on its own day in local time: commits and merges on their commit date and, with `--github`, pull requests on the day they were opened, merged or closed and pushes on the day they were pushed. The days therefore add up to the totals printed without `-a`. Days without activity are left out. Combine with `-u` to see one contributor's days.
172
+
146
173
  ### Cloned repositories
147
174
 
148
175
  Each repository is cloned into a `repos/` directory under the directory you run the command from, and later runs update (`git pull`) the existing clone instead of cloning again, which makes them much faster. Add `--clobber` to delete the clones the run used, once the results have been printed; `repos/` itself is removed too if nothing else is left in it. Clones from other runs are kept, and nothing is deleted if the run fails before printing, so you can inspect it. With `-v`, each deleted path is listed on standard error.
@@ -202,6 +202,24 @@ When several repositories are analyzed, the results are printed once, after all
202
202
 
203
203
  **Status:** Done
204
204
 
205
+ #### OUT-6 Aggregated results
206
+
207
+ `-a/--aggregate developer` prints one row per developer, summed over every repository analyzed, instead of one row per contributor per repository. Its columns are `username, start_date, end_date, commits, insertions, deletions, files, merges, repositories`, where `repositories` is the number of repositories in which the developer has any activity in the date range (a non-zero count, git or GitHub, as for `-c`); there is no `repository` column. 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` follow. Rows are combined when they share a GitHub username (ignoring case), or, for rows without one, the same git `username` exactly; a developer matched to a GitHub user in one repository but not in another is reported as two rows. `username` is the git username of the developer's row with the most commits (ties alphabetical, as in STAT-11), `github_username` the first non-empty one among the developer's rows, and `git_email` and `github_email` list every distinct email of the combined rows, sorted alphabetically (ignoring case) and separated by `; `.
208
+
209
+ `-a/--aggregate repository` prints one row per repository, summed over its contributors. Its columns are `repository, start_date, end_date, commits, insertions, deletions, files, merges, contributors`, where `contributors` is the number of its contributors with any activity in the date range; with `--github`, `prs_opened, prs_merged, prs_closed_unmerged, prs_self_merged, pushes_to_default, web_commits` follow. There are no `username`, `github_username`, `git_email` or `github_email` columns.
210
+
211
+ In both modes every count is the sum of the combined rows, so each total equals the sum of the rows printed without `--aggregate`. A GitHub count is empty (`null` in JSON) only when it is empty in every combined row (CLI-3). Aggregation is applied after `-u` and `-c`, so it combines only the rows those options keep. Rows are sorted by `username` (or `github_username`, for a developer with no git username) or by `repository`, ignoring case. The aggregated columns are printed in every output format, and in the CSV header even when there are no rows. Without `-a`, the output is unchanged.
212
+
213
+ **Status:** Done
214
+
215
+ #### OUT-7 Results by day
216
+
217
+ `-a/--aggregate day` prints one row per day on which anything was counted, summed over every contributor and repository analyzed, in date order. Its columns are `date, commits, insertions, deletions, files, merges, repositories, contributors`, where `date` is the day in `mm/dd/yyyy` format, `repositories` is the number of repositories with any activity that day and `contributors` the number of developers with any activity that day (a developer is identified as in OUT-6, so one person active in several repositories counts once, while one matched to a GitHub user in one repository but not in another counts twice). With `--github`, the columns `prs_opened, prs_merged, prs_closed_unmerged, prs_self_merged, pushes_to_default, web_commits` follow. There are no `username`, `repository`, `start_date`, `end_date`, `github_username`, `git_email` or `github_email` columns.
218
+
219
+ Each event is counted on the day it happened, in local time, the same way FILT-1 limits the date range: a commit, merge or website commit on its commit date; a pull request as opened on the day it was created, as merged (and self-merged) on the day it was merged, and as closed without merging on the day it was closed; a push on the day it was pushed. So the day totals add up to the totals printed without `-a`. Days without any activity are left out. `-u` limits the counts to that contributor (as in CLI-3 with `--github`); `-c` changes nothing, since inactive contributors add nothing to a day. A GitHub count is empty (`null` in JSON) only when no repository with activity that day could be asked about (CLI-3). The columns are printed in the CSV header even when there are no rows.
220
+
221
+ **Status:** Done
222
+
205
223
  ### 5. Command-Line Interface
206
224
 
207
225
  #### CLI-1 Help and usage
@@ -0,0 +1,126 @@
1
+ username,repository,start_date,end_date,commits,insertions,deletions,files,merges,github_username,prs_opened,prs_merged,prs_closed_unmerged,prs_self_merged,git_email,github_email,pushes_to_default,web_commits
2
+ Jade Leong,specification-phase-exercise-sakjp,09/01/2026,10/16/2026,4,124,45,4,0,Jade-Leong,1,1,0,0,jyl9618@nyu.edu,,2,0
3
+ Khidir Ahmed,specification-phase-exercise-sakjp,09/01/2026,10/16/2026,4,9,6,4,0,khidirahmed,2,2,0,1,kka6822@nyu.edu,kka6822@nyu.edu,2,1
4
+ Pope,specification-phase-exercise-sakjp,09/01/2026,10/16/2026,6,31,5,6,3,pope-cruz,4,4,0,3,168572503+pope-cruz@users.noreply.github.com; popecruz822@gmail.com,,1,0
5
+ sethderusha,specification-phase-exercise-sakjp,09/01/2026,10/16/2026,10,55,34,10,7,sethderusha,2,2,0,2,165086066+sethderusha@users.noreply.github.com; srd6665@nyu.edu,,8,0
6
+ alissawu,specification-phase-exercise-sakjp,09/01/2026,10/16/2026,1,1,0,1,0,alissawu,1,1,0,0,alissawu77@gmail.com,alissawu77@gmail.com,0,0
7
+ bloombar,specification-phase-exercise-sakjp,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
8
+ Dialloni,specification-phase-exercise-leodiajulce,09/01/2026,10/16/2026,5,130,6,6,3,Dialloni,0,0,0,0,119769795+Dialloni@users.noreply.github.com; dialoabo@gmail.com,,0,0
9
+ Jrleitersdorf,specification-phase-exercise-leodiajulce,09/01/2026,10/16/2026,4,437,13,5,2,Jrleitersdorf,0,0,0,0,jrleitersdorf@gmail.com,,0,0
10
+ Leonid2004,specification-phase-exercise-leodiajulce,09/01/2026,10/16/2026,3,189,6,3,3,Leonid2004,0,0,0,0,72578139+Leonid2004@users.noreply.github.com; leophilo7@gmail.com,,0,0
11
+ bloombar,specification-phase-exercise-leodiajulce,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
12
+ carinutza,specification-phase-exercise-leodiajulce,09/01/2026,10/16/2026,13,146,33,18,4,carinutza,0,0,0,0,ilie.carina@gmail.com,,1,0
13
+ YusefMoustafa,specification-phase-exercise-waterfall,09/01/2026,10/16/2026,6,119,7,6,0,YusefMoustafa,0,0,0,0,158067706+YusefMoustafa@users.noreply.github.com,,6,6
14
+ bloombar,specification-phase-exercise-waterfall,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
15
+ krishivseth,specification-phase-exercise-waterfall,09/01/2026,10/16/2026,7,555,23,10,1,krishivseth,2,2,0,2,93362198+krishivseth@users.noreply.github.com,,1,7
16
+ monica9482,specification-phase-exercise-waterfall,09/01/2026,10/16/2026,15,77,97,15,1,monica9482,0,0,0,0,ml9482@nyu.edu,,16,0
17
+ sing1179,specification-phase-exercise-waterfall,09/01/2026,10/16/2026,5,91,19,5,0,sing1179,0,0,0,0,143670921+sing1179@users.noreply.github.com,,5,5
18
+ Sarah Toussaint,specification-phase-exercise-tigers,09/01/2026,10/16/2026,3,50,2,3,0,sarahtoussaint,1,0,1,0,sat10002@nyu.edu,,3,3
19
+ bloombar,specification-phase-exercise-tigers,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
20
+ izz04,specification-phase-exercise-tigers,09/01/2026,10/16/2026,14,175,70,14,4,izz04,6,6,0,2,73780798+izz04@users.noreply.github.com,,9,14
21
+ jk8308-jpg,specification-phase-exercise-tigers,09/01/2026,10/16/2026,2,48,1,2,3,jk8308-jpg,2,2,0,0,jk8308@nyu.edu,,0,0
22
+ nonyembaezue,specification-phase-exercise-tigers,09/01/2026,10/16/2026,7,32,6,7,1,nonyembaezue,0,0,0,0,cam9137@nyu.edu,,7,7
23
+ bloombar,specification-exercise-project-1,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
24
+ sallyhegab,specification-exercise-project-1,09/01/2026,10/16/2026,5,109,14,5,1,sallyhegab,0,0,0,0,158313024+sallyhegab@users.noreply.github.com; sh6568@nyu.edu,,5,0
25
+ sar9249,specification-exercise-project-1,09/01/2026,10/16/2026,1,1,1,1,0,sar9249,1,1,0,0,sar9249@nyu.edu,,0,1
26
+ Ryan Lin,specification-phase-exercise-codekitchen,09/01/2026,10/16/2026,2,3,1,2,0,ryanwlin,0,0,0,0,ryanandlin@gmail.com,,2,2
27
+ berrizy,specification-phase-exercise-codekitchen,09/01/2026,10/16/2026,2,2,3,2,0,berrizy,0,0,0,0,nahasselma@gmail.com,,2,0
28
+ bloombar,specification-phase-exercise-codekitchen,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
29
+ rishachada,specification-phase-exercise-codekitchen,09/01/2026,10/16/2026,6,114,37,6,0,rishachada,0,0,0,0,rc5294@nyu.edu,,6,2
30
+ schauhans,specification-phase-exercise-codekitchen,09/01/2026,10/16/2026,2,24,3,2,0,schauhans,0,0,0,0,139730145+schauhans@users.noreply.github.com; sc10486@nyu.edu,,2,1
31
+ yufeiihuang,specification-phase-exercise-codekitchen,09/01/2026,10/16/2026,9,65,13,9,0,yufeiihuang,0,0,0,0,yufeixyhuang@gmail.com,,9,9
32
+ AmitavNarayan,specification-phase-exercise-no-ai-team,09/01/2026,10/16/2026,26,489,443,26,7,AmitavNarayan,6,6,0,6,amitavnarayan2@gmail.com,,21,8
33
+ Trisolis,specification-phase-exercise-no-ai-team,09/01/2026,10/16/2026,8,221,109,8,3,Trisolis,3,3,0,2,papijackwaiter@gmail.com,papijackwaiter@gmail.com,5,0
34
+ bloombar,specification-phase-exercise-no-ai-team,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
35
+ rfang1224,specification-phase-exercise-no-ai-team,09/01/2026,10/16/2026,3,9,5,3,0,rfang1224,1,1,0,0,rf2789@nyu.edu,,2,2
36
+ Abel200595,specification-phase-exercise-smart_monkeys-,09/01/2026,10/16/2026,1,507,1,4,0,Abel200595,0,0,0,0,ym3057@nyu.edu,,0,0
37
+ Jialiang Cao,specification-phase-exercise-smart_monkeys-,09/01/2026,10/16/2026,4,9,19,4,0,jialiangcao,0,0,0,0,jcao2080@gmail.com,,3,0
38
+ RayyanAhmed21,specification-phase-exercise-smart_monkeys-,09/01/2026,10/16/2026,7,252,111,7,5,RayyanAhmed21,5,5,0,5,ra4073@nyu.edu,,0,3
39
+ bloombar,specification-phase-exercise-smart_monkeys-,09/01/2026,10/16/2026,1,15,0,1,1,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
40
+ rz3007,specification-phase-exercise-smart_monkeys-,09/01/2026,10/16/2026,2,54,4,2,0,rz3007,0,0,0,0,rz3007@nyu.edu,,0,2
41
+ Asritha Pannem,specification-phase-exercise-controller,09/01/2026,10/16/2026,1,42,0,1,0,,0,0,0,0,asp9404@nyu.edu,,0,0
42
+ NISUN05,specification-phase-exercise-controller,09/01/2026,10/16/2026,6,106,7,6,0,NISUN05,0,0,0,0,na3755@nyu.edu,,6,0
43
+ Nishika Gorla,specification-phase-exercise-controller,09/01/2026,10/16/2026,3,32,4,3,0,nishikagorla,2,2,0,0,nishika.gorla@gmail.com,nishika.gorla@gmail.com,3,0
44
+ Pranay Engineer,specification-phase-exercise-controller,09/01/2026,10/16/2026,6,70,11,6,5,PranayEng,3,3,0,3,88552049+PranayEng@users.noreply.github.com; Pme7634@nyu.edu,,1,1
45
+ ai2652-png,specification-phase-exercise-controller,09/01/2026,10/16/2026,8,317,213,8,5,ai2652-png,5,5,0,5,ai2652@nyu.edu,,6,8
46
+ bloombar,specification-phase-exercise-controller,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
47
+ ,specification-phase-exercise-controller,09/01/2026,10/16/2026,0,0,0,0,0,scanfasri,0,0,0,0,,,1,0
48
+ ShuoWu6529,specification-phase-exercise-forked,09/01/2026,10/16/2026,7,24,6,7,11,ShuoWu6529,6,6,0,4,sw6529@nyu.edu,sw6529@nyu.edu,1,0
49
+ bloombar,specification-phase-exercise-forked,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
50
+ ew2725,specification-phase-exercise-forked,09/01/2026,10/16/2026,6,24,14,6,6,ew2725,5,5,0,1,ew2725@nyu.edu,,0,0
51
+ jack-k-jiang,specification-phase-exercise-forked,09/01/2026,10/16/2026,8,37,14,8,6,jack-k-jiang,8,8,0,6,82908596+jack-k-jiang@users.noreply.github.com; jackj12358@gmail.com,,0,0
52
+ ronaldszeto,specification-phase-exercise-forked,09/01/2026,10/16/2026,7,24,3,7,1,ronaldszeto,5,5,0,1,rs9316@nyu.edu,,1,2
53
+ xuruikun,specification-phase-exercise-forked,09/01/2026,10/16/2026,23,93,61,23,3,xuruikun,7,7,0,7,88195416+xuruikun@users.noreply.github.com,,8,23
54
+ MarcoHGulino,specification-phase-exercise-team2,09/01/2026,10/16/2026,5,81,36,5,0,MarcoHGulino,0,0,0,0,mg6378@nyu.edu,,3,5
55
+ Sai Shettar,specification-phase-exercise-team2,09/01/2026,10/16/2026,12,46,14,12,0,saishettar,0,0,0,0,sai.r.shettar@gmail.com,saishettar@nyu.edu,4,12
56
+ SiennaSSM,specification-phase-exercise-team2,09/01/2026,10/16/2026,14,122,48,14,0,SiennaSSM,0,0,0,0,sm12131@nyu.edu,,4,14
57
+ bloombar,specification-phase-exercise-team2,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
58
+ elbow74,specification-phase-exercise-team2,09/01/2026,10/16/2026,5,86,48,5,0,elbow74,0,0,0,0,92692888+elbow74@users.noreply.github.com,,5,5
59
+ Abyssjac,specification-phase-exercise-sth,09/01/2026,10/16/2026,7,229,183,8,1,Abyssjac,0,0,0,0,131870078+Abyssjac@users.noreply.github.com; zuzhou.abyss@gmail.com,,7,0
60
+ Jessie Zhang,specification-phase-exercise-sth,09/01/2026,10/16/2026,7,24,10,7,0,jessiezhang218,1,1,0,1,jessiezhang0218@gmail.com; jz6102@nyu.edu,jessiezhang0218@gmail.com,1,7
61
+ Shiqi Wang,specification-phase-exercise-sth,09/01/2026,10/16/2026,5,132,66,5,4,ShiqiWang1115,4,4,0,4,sw6423@nyu.edu,,0,0
62
+ Sinu112,specification-phase-exercise-sth,09/01/2026,10/16/2026,2,74,2,2,0,Sinu112,2,2,0,2,xs2471@nyu.edu,,0,2
63
+ bloombar,specification-phase-exercise-sth,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
64
+ TracyWang0904,specification-phase-exercise-byteme,09/01/2026,10/16/2026,8,226,43,21,0,TracyWang0904,0,0,0,0,yw8252@nyu.edu,,8,0
65
+ bloombar,specification-phase-exercise-byteme,09/01/2026,10/16/2026,0,0,0,0,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
66
+ AliG643,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,4,90,9,4,4,AliG643,1,1,0,0,sg7920@nyu.edu,sg7920@nyu.edu,0,0
67
+ S1rius-Yang,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,1,3,1,1,0,S1rius-Yang,0,0,0,0,hy2743@nyu.edu,,0,1
68
+ Sirius,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,3,6,4,3,0,,0,0,0,0,s1rius@SiriusdeMacBook-Pro.local,,0,0
69
+ Yuyang-Ding1102,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,4,425,63,4,7,Yuyang-Ding1102,2,1,1,1,yd2734@nyu.edu,,0,4
70
+ Yvonne,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,2,80,27,2,0,Lyvoj,0,0,0,0,261538801+Lyvoj@users.noreply.github.com,,0,0
71
+ Zihan Chen,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,6,148,88,6,0,Vent1Zzz,0,0,0,0,203582969+Vent1Zzz@users.noreply.github.com,,1,0
72
+ bloombar,specification-phase-exercise-purple-sasquatch,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
73
+ Dizay-53,specification-exercise-hibiscus,09/01/2026,10/16/2026,3,46,3,3,0,Dizay-53,2,2,0,1,Ya2522@nyu.edu,,0,3
74
+ LefeiKe,specification-exercise-hibiscus,09/01/2026,10/16/2026,4,54,6,4,3,LefeiKe,4,4,0,1,87459566+LefeiKe@users.noreply.github.com; lk3104@nyu.edu,,0,0
75
+ ange285,specification-exercise-hibiscus,09/01/2026,10/16/2026,1,2,1,1,1,ange285,1,1,0,0,enjelia126@gmail.com,,0,1
76
+ bloombar,specification-exercise-hibiscus,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
77
+ sofia-matari,specification-exercise-hibiscus,09/01/2026,10/16/2026,2,46,11,2,6,sofia-matari,2,2,0,0,sbm9838@nyu.edu,,0,0
78
+ yasminek27,specification-exercise-hibiscus,09/01/2026,10/16/2026,9,118,28,9,5,yasminek27,9,9,0,0,yk2154@nyu.edu,,0,0
79
+ aw4580,specification-phase-exercise-search,09/01/2026,10/16/2026,6,57,30,6,0,aw4580,0,0,0,0,134334340+aw4580@users.noreply.github.com; aw4580@nyu.edu,,7,5
80
+ benzaslavsky,specification-phase-exercise-search,09/01/2026,10/16/2026,1,17,2,1,2,benzaslavsky,1,1,0,1,62486098+benzaslavsky@users.noreply.github.com; benzy.venue@gmail.com,,0,0
81
+ bloombar,specification-phase-exercise-search,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
82
+ iindes,specification-phase-exercise-search,09/01/2026,10/16/2026,1,1,1,1,1,iindes,1,1,0,1,59502115+iindes@users.noreply.github.com,,0,1
83
+ lkykathy,specification-phase-exercise-search,09/01/2026,10/16/2026,7,338,61,7,6,lkykathy,7,7,0,6,67247076+lkykathy@users.noreply.github.com; linkeying0425@gmail.com,,0,0
84
+ saramhersch,specification-phase-exercise-search,09/01/2026,10/16/2026,1,4,0,1,0,saramhersch,0,0,0,0,smh9864@nyu.edu,,1,1
85
+ SparkFan-ui,specification-phase-exercise-sttke,09/01/2026,10/16/2026,4,271,28,5,0,SparkFan-ui,0,0,0,0,jf4832@nyu.edu,,6,4
86
+ bloombar,specification-phase-exercise-sttke,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
87
+ kevinhan923,specification-phase-exercise-sttke,09/01/2026,10/16/2026,3,116,185,3,0,kevinhan923,0,0,0,0,69257524+kevinhan923@users.noreply.github.com,,4,0
88
+ 23matthewc,specification-phase-exercise-nocturnal,09/01/2026,10/16/2026,0,0,0,0,2,23matthewc,0,0,0,0,76405126+23matthewc@users.noreply.github.com,,0,0
89
+ ajoykumarrishi,specification-phase-exercise-nocturnal,09/01/2026,10/16/2026,14,310,24,14,0,ajoykumarrishi,1,1,0,0,151567609+ajoykumarrishi@users.noreply.github.com; rishi.ajoykumar9@gmail.com,,0,14
90
+ bloombar,specification-phase-exercise-nocturnal,09/01/2026,10/16/2026,0,0,0,0,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
91
+ christinalin06,specification-phase-exercise-nocturnal,09/01/2026,10/16/2026,7,119,31,7,1,christinalin06,2,2,0,0,Christinalin006@gmail.com,,3,1
92
+ DyingRavioli,specification-phase-exercise-goodTeam,09/01/2026,10/16/2026,5,147,87,5,3,DyingRavioli,5,5,0,5,109256424+DyingRavioli@users.noreply.github.com,,1,5
93
+ RayyanAhmed21,specification-phase-exercise-goodTeam,09/01/2026,10/16/2026,4,121,17,4,0,RayyanAhmed21,0,0,0,0,ra4073@nyu.edu,,0,0
94
+ UIYrj,specification-phase-exercise-goodTeam,09/01/2026,10/16/2026,3,38,4,3,2,UIYrj,2,2,0,2,158982316+UIYrj@users.noreply.github.com,,0,3
95
+ bloombar,specification-phase-exercise-goodTeam,09/01/2026,10/16/2026,1,15,0,1,1,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
96
+ rz3007,specification-phase-exercise-goodTeam,09/01/2026,10/16/2026,2,54,4,2,0,rz3007,0,0,0,0,rz3007@nyu.edu,,0,2
97
+ sanay-d-nyu,specification-phase-exercise-goodTeam,09/01/2026,10/16/2026,6,77,9,6,1,sanay-d-nyu,1,1,0,1,sd5113@nyu.edu,sd5113@nyu.edu,1,0
98
+ DaniyalMl,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,1,1,0,1,0,DaniyalMlk,0,0,0,0,103329154+DaniyalMlk@users.noreply.github.com,,1,1
99
+ Jayyu2005,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,1,6,1,1,0,Jayyu2005,0,0,0,0,jy4886@nyu.edu,,1,1
100
+ LefeiKe,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,2,2,2,2,0,LefeiKe,0,0,0,0,lk3104@nyu.edu,,2,0
101
+ bloombar,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,1,1,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
102
+ catw1thtea,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,3,5,3,3,0,catw1thtea,0,0,0,0,ds7746@nyu.edu,danialsagymbayev@gmail.com,3,3
103
+ denisekos,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,1,129,5,1,0,denisekos,0,0,0,0,denise.koswara28@gmail.com,,1,1
104
+ manzim7,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,3,9,3,3,5,manzim7,0,0,0,0,mm13507@nyu.edu,,3,0
105
+ ,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,0,0,0,0,0,mosada2515,1,0,1,0,,,0,0
106
+ ,specification-phase-exercise-ddjz,09/01/2026,10/16/2026,0,0,0,0,0,Yded21,1,0,0,0,,,0,0
107
+ LefeiKe,specification-phase-exercise-team1,09/01/2026,10/16/2026,2,2,2,2,0,LefeiKe,0,0,0,0,lk3104@nyu.edu,,0,0
108
+ Yded21,specification-phase-exercise-team1,09/01/2026,10/16/2026,1,187,14,1,0,Yded21,1,1,0,0,eddyzzy@outlook.com,,0,1
109
+ ac11238,specification-phase-exercise-team1,09/01/2026,10/16/2026,9,29,12,9,0,ac11238,0,0,0,0,ac11348@nyu.edu,,12,9
110
+ bloombar,specification-phase-exercise-team1,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
111
+ manzim7,specification-phase-exercise-team1,09/01/2026,10/16/2026,1,5,1,1,0,manzim7,0,0,0,0,mm13507@nyu.edu,,0,0
112
+ uwtz,specification-phase-exercise-team1,09/01/2026,10/16/2026,8,189,37,8,1,uwtz,0,0,0,0,86273231+uwtz@users.noreply.github.com,,8,8
113
+ SeanA12,specification-phase-exercise-GitLost,09/01/2026,10/16/2026,2,36,2,2,2,SeanA12,2,2,0,0,106447402+SeanA12@users.noreply.github.com; seanalliman112@gmai.com,,0,0
114
+ bloombar,specification-phase-exercise-GitLost,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
115
+ layan-al,specification-phase-exercise-GitLost,09/01/2026,10/16/2026,4,161,5,4,0,layan-al,5,4,1,0,103759519+layan-al@users.noreply.github.com,,0,0
116
+ ogngnaoh,specification-phase-exercise-GitLost,09/01/2026,10/16/2026,0,0,0,0,4,ogngnaoh,0,0,0,0,ngoviethoang9@gmail.com,,0,0
117
+ rayyang262,specification-phase-exercise-GitLost,09/01/2026,10/16/2026,1,2,2,1,0,rayyang262,0,0,0,0,rayyang2026@gmail.com,,1,1
118
+ BrianDai22,specification-phase-exercise-mihir-brian-yash-linh-theo,09/01/2026,10/16/2026,3,158,157,4,0,BrianDai22,0,0,0,0,mooprules@gmail.com,,0,0
119
+ Mihir Ganesan,specification-phase-exercise-mihir-brian-yash-linh-theo,09/01/2026,10/16/2026,6,243,35,6,0,,0,0,0,0,mihirganesan@10-17-27-151.dynapool.wireless.nyu.edu; mihirganesan@10-17-58-124.dynapool.wireless.nyu.edu,,0,0
120
+ bloombar,specification-phase-exercise-mihir-brian-yash-linh-theo,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
121
+ theoegoldstine,specification-phase-exercise-mihir-brian-yash-linh-theo,09/01/2026,10/16/2026,4,6,4,4,0,theoegoldstine,0,0,0,0,63026703+theoegoldstine@users.noreply.github.com,,0,4
122
+ yash-pandya9798,specification-phase-exercise-mihir-brian-yash-linh-theo,09/01/2026,10/16/2026,1,8,2,1,0,yash-pandya9798,0,0,0,0,yash.pandya9798@gmail.com,,0,1
123
+ RayyanAhmed21,specification-phase-exercise,09/01/2026,10/16/2026,5,175,18,5,4,RayyanAhmed21,4,4,0,4,ra4073@nyu.edu,,0,1
124
+ bloombar,specification-phase-exercise,09/01/2026,10/16/2026,1,15,0,1,1,bloombar,1,1,0,1,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
125
+ rz3007,specification-phase-exercise,09/01/2026,10/16/2026,3,55,5,3,1,rz3007,1,1,0,1,rz3007@nyu.edu,,1,3
126
+ bloombar,specification-phase-exercise-helloworld,09/01/2026,10/16/2026,1,15,0,1,0,bloombar,0,0,0,0,2209098+bloombar@users.noreply.github.com; amos@cs.nyu.edu,,0,0
@@ -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.5.2"
8
+ version = "1.7.0"
9
9
  authors = [
10
10
  { name="Tobody Tothing", email="nobody.nothing@nowhere.com" },
11
11
  ]
@@ -132,6 +132,38 @@
132
132
  family: OUT
133
133
  phase: 1
134
134
  status: Done
135
+ - id: OUT-6
136
+ title: Aggregated results
137
+ full: -a/--aggregate developer prints one row per developer, summed over every
138
+ repository analyzed, instead of one row per contributor per repository. Its
139
+ columns are username, start_date, end_date, commits, insertions, deletions,
140
+ files, merges, repositories, where repositories is the number of
141
+ repositories in which the developer has any activity in the date range (a
142
+ non-zero count, git or GitHub, as for -c); there is no repository column.
143
+ With --github, the columns github_username, prs_opened, prs_merged,
144
+ prs_closed_unmerged, prs_self_merged, git_email, github_email,
145
+ pushes_to_default, web_commits follow. Rows are combined when they share a
146
+ GitHub username (ignoring case), or, for rows without…
147
+ section: §4 Output
148
+ family: OUT
149
+ phase: 1
150
+ status: Done
151
+ - id: OUT-7
152
+ title: Results by day
153
+ full: -a/--aggregate day prints one row per day on which anything was counted,
154
+ summed over every contributor and repository analyzed, in date order. Its
155
+ columns are date, commits, insertions, deletions, files, merges,
156
+ repositories, contributors, where date is the day in mm/dd/yyyy format,
157
+ repositories is the number of repositories with any activity that day and
158
+ contributors the number of developers with any activity that day (a
159
+ developer is identified as in OUT-6, so one person active in several
160
+ repositories counts once, while one matched to a GitHub user in one
161
+ repository but not in another counts twice). With --github, the columns
162
+ prs_opened, prs_merged, prs_closed_unmerged, prs_self_merged…
163
+ section: §4 Output
164
+ family: OUT
165
+ phase: 1
166
+ status: Done
135
167
  - id: PKG-1
136
168
  title: Installable package
137
169
  full: The tool installs with pip install gitlogstats, which provides a
@@ -5,6 +5,7 @@ import datetime
5
5
  import re
6
6
  import sys
7
7
  from . import GitLogsParser
8
+ from . import aggregate
8
9
  from .clones import remove_clones
9
10
  from .git_logs_parser import FIELDNAMES
10
11
  from .github import (
@@ -14,7 +15,7 @@ from .github import (
14
15
  GitHubUnavailable,
15
16
  check_gh,
16
17
  )
17
- from .github_stats import github_rows
18
+ from .github_stats import github_day_rows, github_rows
18
19
 
19
20
 
20
21
  def valid_date(value):
@@ -146,6 +147,15 @@ def main():
146
147
  default=False,
147
148
  action="store_true",
148
149
  )
150
+ parser.add_argument(
151
+ "-a",
152
+ "--aggregate",
153
+ help="Combine the results into one row per developer, summed over all "
154
+ "repositories, one row per repository, summed over its contributors, "
155
+ "or one row per day with activity, summed over everyone",
156
+ default=None,
157
+ choices=aggregate.MODES,
158
+ )
149
159
  parser.add_argument(
150
160
  "--github",
151
161
  help="Also report GitHub pull requests opened, merged, closed and "
@@ -244,21 +254,37 @@ def main():
244
254
  verbose=args.verbose,
245
255
  clean=False if args.github else args.clean,
246
256
  )
247
- rows = last_parser.parse()
248
- if args.github:
249
- rows = github_rows(
250
- last_parser, rows, repo_url, last_parser.get_identities(), args
251
- )
257
+ if args.aggregate == aggregate.DAY:
258
+ # OUT-7: rows for single days, each with its date
259
+ if args.github:
260
+ rows = github_day_rows(
261
+ last_parser, repo_url, last_parser.get_identities(), args
262
+ )
263
+ else:
264
+ rows = last_parser.day_rows()
265
+ # an idle contributor adds nothing to a day; keep memory small
266
+ rows = [row for row in rows if aggregate.is_active(row)]
267
+ else:
268
+ rows = last_parser.parse()
269
+ if args.github:
270
+ rows = github_rows(
271
+ last_parser, rows, repo_url, last_parser.get_identities(), args
272
+ )
252
273
  all_results.extend(rows) # collect across repos
253
274
 
254
275
  # emit a single table (or JSON array) covering all repositories
255
276
  if last_parser is not None:
256
- # an empty CSV table still gets the GitHub columns with --github
257
- fieldnames = (
258
- FIELDNAMES + GITHUB_FIELDNAMES + EMAIL_FIELDNAMES + ACTIVITY_FIELDNAMES
259
- if args.github
260
- else None
261
- )
277
+ # an empty CSV table still gets the columns it would have had
278
+ if args.aggregate:
279
+ # OUT-6, OUT-7: one row per developer, repository or day
280
+ all_results = aggregate.aggregate(all_results, args.aggregate, args.github)
281
+ fieldnames = aggregate.fieldnames(args.aggregate, args.github)
282
+ elif args.github:
283
+ fieldnames = (
284
+ FIELDNAMES + GITHUB_FIELDNAMES + EMAIL_FIELDNAMES + ACTIVITY_FIELDNAMES
285
+ )
286
+ else:
287
+ fieldnames = None
262
288
  output = last_parser.format_results(all_results, args.format, fieldnames)
263
289
  # end with exactly one newline: CSV and Markdown output already has one
264
290
  print(output, end="" if output.endswith("\n") else "\n")
@@ -0,0 +1,195 @@
1
+ """Combine result rows by developer or by repository (OUT-6), or by day (OUT-7).
2
+
3
+ Everything here is pure logic over the rows the rest of the tool produces: no
4
+ git, no network. Each aggregated row sums the counts of the rows it combines.
5
+ """
6
+
7
+ import datetime
8
+
9
+ from gitlogstats.git_logs_parser import FIELDNAMES
10
+ from gitlogstats.github import (
11
+ ACTIVITY_FIELDNAMES,
12
+ EMAIL_FIELDNAMES,
13
+ GITHUB_FIELDNAMES,
14
+ )
15
+ from gitlogstats.identity import join_emails
16
+
17
+ DEVELOPER = "developer"
18
+ REPOSITORY = "repository"
19
+ DAY = "day"
20
+ MODES = [DEVELOPER, REPOSITORY, DAY]
21
+
22
+ # the git counts (commits to merges), summed in every mode
23
+ GIT_COUNTS = FIELDNAMES[4:]
24
+
25
+ # the GitHub pull-request counts (CLI-3), summed in every mode
26
+ PR_COUNTS = GITHUB_FIELDNAMES[1:]
27
+
28
+ # every count; a row with any of them non-zero is active, as for -c (FILT-4)
29
+ ALL_COUNTS = GIT_COUNTS + PR_COUNTS + ACTIVITY_FIELDNAMES
30
+
31
+
32
+ def fieldnames(mode, github):
33
+ """
34
+ The output columns of an aggregated table, in order.
35
+ @param mode: DEVELOPER, REPOSITORY or DAY
36
+ @param github: whether the GitHub columns are reported (--github)
37
+ @returns: a list of column names
38
+ """
39
+ dates = ["start_date", "end_date"]
40
+ if mode == DEVELOPER:
41
+ columns = ["username"] + dates + GIT_COUNTS + ["repositories"]
42
+ if github:
43
+ columns += ["github_username"] + PR_COUNTS + EMAIL_FIELDNAMES
44
+ columns += ACTIVITY_FIELDNAMES
45
+ elif mode == DAY:
46
+ columns = ["date"] + GIT_COUNTS + ["repositories", "contributors"]
47
+ if github:
48
+ columns += PR_COUNTS + ACTIVITY_FIELDNAMES
49
+ else:
50
+ columns = ["repository"] + dates + GIT_COUNTS + ["contributors"]
51
+ if github:
52
+ columns += PR_COUNTS + ACTIVITY_FIELDNAMES
53
+ return columns
54
+
55
+
56
+ def developer_key(row):
57
+ """
58
+ The developer a row belongs to: its GitHub user, ignoring case, when it
59
+ has one, otherwise its exact git username.
60
+ @param row: a result row
61
+ @returns: a hashable key
62
+ """
63
+ login = row.get("github_username")
64
+ if login:
65
+ return ("github", login.casefold())
66
+ return ("git", row["username"])
67
+
68
+
69
+ def is_active(row):
70
+ """
71
+ Whether a row has any activity: a non-zero count, git or GitHub.
72
+ @param row: a result row
73
+ @returns: a bool
74
+ """
75
+ return any(row.get(field) for field in ALL_COUNTS)
76
+
77
+
78
+ def main_username(rows):
79
+ """
80
+ The git username of a developer's rows: the one with the most commits,
81
+ ties alphabetical, as when combining identities in one repository (STAT-11).
82
+ @param rows: the rows of one developer
83
+ @returns: a username, or "" if every row is GitHub-only
84
+ """
85
+ named = [row for row in rows if row["username"]]
86
+ if not named:
87
+ return ""
88
+ return min(named, key=lambda row: (-(row["commits"] or 0), row["username"]))[
89
+ "username"
90
+ ]
91
+
92
+
93
+ def sum_values(values):
94
+ """
95
+ Add up counts. An unavailable count (None, CLI-3) is skipped, so the total
96
+ is None only when every count is unavailable.
97
+ @param values: numbers or None
98
+ @returns: their sum, or None
99
+ """
100
+ present = [value for value in values if value is not None]
101
+ return sum(present) if present else None
102
+
103
+
104
+ def first_text(values):
105
+ """
106
+ The first non-empty text. When there is none, "" if any value is
107
+ available, or None if every value is unavailable.
108
+ @param values: strings or None
109
+ @returns: a string, or None
110
+ """
111
+ values = list(values)
112
+ for value in values:
113
+ if value:
114
+ return value
115
+ return None if all(value is None for value in values) else ""
116
+
117
+
118
+ def merge_emails(values):
119
+ """
120
+ Join '; '-separated email lists into one sorted list without repeats
121
+ (STAT-13), or None if every list is unavailable.
122
+ @param values: '; '-separated strings, "" or None
123
+ @returns: a '; '-separated string, or None
124
+ """
125
+ values = list(values)
126
+ if all(value is None for value in values):
127
+ return None
128
+ return join_emails(
129
+ email for value in values if value for email in value.split("; ")
130
+ )
131
+
132
+
133
+ def combine(rows, columns):
134
+ """
135
+ Build the aggregated row of one group.
136
+ @param rows: the rows of one developer, repository or day
137
+ @param columns: the output columns, from fieldnames()
138
+ @returns: one row with those columns
139
+ """
140
+ active = [row for row in rows if is_active(row)]
141
+ out = {}
142
+ for field in columns:
143
+ if field == "username":
144
+ out[field] = main_username(rows)
145
+ elif field in ("repository", "start_date", "end_date", "date"):
146
+ out[field] = rows[0][field]
147
+ elif field == "repositories":
148
+ out[field] = len({row["repository"] for row in active})
149
+ elif field == "contributors":
150
+ out[field] = len({developer_key(row) for row in active})
151
+ elif field == "github_username":
152
+ out[field] = first_text(row[field] for row in rows)
153
+ elif field in EMAIL_FIELDNAMES:
154
+ out[field] = merge_emails(row[field] for row in rows)
155
+ else:
156
+ out[field] = sum_values(row[field] for row in rows)
157
+ return out
158
+
159
+
160
+ def aggregate(rows, mode, github):
161
+ """
162
+ Combine result rows into one row per developer, repository or day. Rows
163
+ are sorted by username (or GitHub username, for a developer with no git
164
+ username) or by repository, ignoring case, or by date. Days only count
165
+ active rows, so a day without activity is left out (OUT-7).
166
+ @param rows: result rows of every repository, after -u and -c; in DAY
167
+ mode, rows for single days, each with a "date" key
168
+ @param mode: DEVELOPER, REPOSITORY or DAY
169
+ @param github: whether the rows carry the GitHub columns (--github)
170
+ @returns: the aggregated rows, sorted
171
+ """
172
+ groups = {}
173
+ for row in rows:
174
+ if mode == DEVELOPER:
175
+ key = developer_key(row)
176
+ elif mode == REPOSITORY:
177
+ key = row["repository"]
178
+ elif is_active(row):
179
+ key = row["date"]
180
+ else:
181
+ continue
182
+ groups.setdefault(key, []).append(row)
183
+ columns = fieldnames(mode, github)
184
+ combined = [combine(group, columns) for group in groups.values()]
185
+
186
+ def sort_key(row):
187
+ if mode == DAY:
188
+ return (datetime.datetime.strptime(row["date"], "%m/%d/%Y"), "")
189
+ if mode == DEVELOPER:
190
+ name = row["username"] or row.get("github_username") or ""
191
+ else:
192
+ name = row["repository"]
193
+ return (name.casefold(), name)
194
+
195
+ return sorted(combined, key=sort_key)