gitlogstats 1.0.9__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/PKG-INFO +59 -7
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/README.md +58 -6
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/docs/SPEC.md +52 -6
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/pyproject.toml +1 -1
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/scripts/board/manifest.yaml +112 -6
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/src/gitlogstats/__main__.py +32 -4
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/src/gitlogstats/git_logs_parser.py +30 -3
- gitlogstats-1.1.0/src/gitlogstats/github.py +271 -0
- gitlogstats-1.1.0/src/gitlogstats/github_stats.py +152 -0
- gitlogstats-1.1.0/src/gitlogstats/identity.py +251 -0
- gitlogstats-1.1.0/tests/fake_gh.py +92 -0
- gitlogstats-1.1.0/tests/fixtures/github/commit_linked.json +5 -0
- gitlogstats-1.1.0/tests/fixtures/github/commit_unlinked.json +5 -0
- gitlogstats-1.1.0/tests/fixtures/github/contributors.json +4 -0
- gitlogstats-1.1.0/tests/fixtures/github/contributors_page2.json +3 -0
- gitlogstats-1.1.0/tests/fixtures/github/pulls_page1.json +20 -0
- gitlogstats-1.1.0/tests/fixtures/github/pulls_page2.json +29 -0
- gitlogstats-1.1.0/tests/fixtures/github/user_full.json +1 -0
- gitlogstats-1.1.0/tests/fixtures/github/user_private.json +1 -0
- gitlogstats-1.1.0/tests/test_github.py +542 -0
- gitlogstats-1.1.0/tests/test_identity.py +370 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/tests/test_integration.py +582 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/CLAUDE.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/agents/developer-agent.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/agents/spec-reviewer.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/hooks/format-edited.sh +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/hooks/guard-paths.sh +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/hooks/guard-paths.test.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/hooks/verify.sh +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/settings.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/skills/phase-handoff/SKILL.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.claude/skills/stale-check/SKILL.md +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.github/workflows/build.yaml +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.gitignore +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/.prettierrc.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/LICENSE +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/Pipfile +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/Pipfile.lock +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/package-lock.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/package.json +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/repos.txt +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/requirements.txt +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/scripts/board/config.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/scripts/board/derive.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/scripts/board/derive.test.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/scripts/board/sync.mjs +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/src/gitlogstats/__init__.py +0 -0
- {gitlogstats-1.0.9 → gitlogstats-1.1.0}/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.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Track developers' contributions to one or more git repositories within a particular time range.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bloombar/gitlogstats
|
|
6
6
|
Project-URL: Repository, https://github.com/bloombar/gitlogstats.git
|
|
@@ -709,6 +709,8 @@ This script calculates the following, for each developer in each repository:
|
|
|
709
709
|
- number of **files changed**
|
|
710
710
|
- number of **merges** into the analyzed branch
|
|
711
711
|
|
|
712
|
+
With `--github`, it also reports each developer's GitHub **pull requests** opened, merged and closed without merging (see [GitHub pull requests](#github-pull-requests)).
|
|
713
|
+
|
|
712
714
|
The results can be formatted as `csv`, `json`, or `markdown`.
|
|
713
715
|
|
|
714
716
|
## Install
|
|
@@ -722,26 +724,36 @@ e.g. `pip install gitlogstats`, `pip3 install gitlogstats`, depending on your en
|
|
|
722
724
|
The command `gitlogstats --help` shows the usage instructions:
|
|
723
725
|
|
|
724
726
|
```
|
|
725
|
-
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
727
|
+
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
728
|
+
[-e END] [-x EXCLUSIONS] [-f {csv,json,markdown}]
|
|
729
|
+
[-b BRANCH] [-v] [-c] [--github]
|
|
726
730
|
|
|
727
|
-
|
|
731
|
+
options:
|
|
728
732
|
-h, --help show this help message and exit
|
|
729
733
|
-r REPOSITORY, --repository REPOSITORY
|
|
730
734
|
the public URL of the repository whose logs to parse
|
|
731
735
|
-rf REPOFILE, --repofile REPOFILE
|
|
732
|
-
the path to simple text file with a list of repository
|
|
733
|
-
|
|
736
|
+
the path to simple text file with a list of repository
|
|
737
|
+
URLs to parse
|
|
738
|
+
-u USER, --user USER The git username (or email address) to report. Default
|
|
739
|
+
is all contributing users
|
|
734
740
|
-s START, --start START
|
|
735
741
|
Start date in mm/dd/yyyy format
|
|
736
742
|
-e END, --end END End date in mm/dd/yyyy format
|
|
737
743
|
-x EXCLUSIONS, --exclusions EXCLUSIONS
|
|
738
|
-
A comma-separated list of files, patterns or
|
|
744
|
+
A comma-separated list of files, patterns or
|
|
745
|
+
directories to exclude, e.g. --exclusions "foo.zip,
|
|
746
|
+
*.jpg, vendor". Replaces the default list; use "" to
|
|
747
|
+
exclude nothing
|
|
739
748
|
-f {csv,json,markdown}, --format {csv,json,markdown}
|
|
740
749
|
The format in which to output the results
|
|
741
750
|
-b BRANCH, --branch BRANCH
|
|
742
|
-
The branch to
|
|
751
|
+
The branch to parse, if not the default branch
|
|
743
752
|
-v, --verbose Whether to output debugging info
|
|
744
753
|
-c, --clean Remove contributors without any contribuition
|
|
754
|
+
--github Also report GitHub pull requests opened, merged and
|
|
755
|
+
closed per contributor, and match GitHub users to git
|
|
756
|
+
authors. Needs the GitHub CLI (gh), signed in
|
|
745
757
|
```
|
|
746
758
|
|
|
747
759
|
In case `gitlogstats` is not found as a command, even after installing, try `python -m gitlogstats` or `python3 -m gitlogstats` instead.
|
|
@@ -822,6 +834,46 @@ By default, every contributor in a repository's history is listed, with zeros fo
|
|
|
822
834
|
gitlogstats -rf repos.txt -c
|
|
823
835
|
```
|
|
824
836
|
|
|
837
|
+
### GitHub pull requests
|
|
838
|
+
|
|
839
|
+
Add `--github` to also count each contributor's pull requests. Four columns are appended to every output format:
|
|
840
|
+
|
|
841
|
+
| Column | Meaning |
|
|
842
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
843
|
+
| `github_username` | the GitHub account matched to the contributor (never the display name), so you can check the matching |
|
|
844
|
+
| `prs_opened` | pull requests they opened in the date range, whatever their state now (drafts included) |
|
|
845
|
+
| `prs_merged` | pull requests they authored that were merged in the date range, by anyone and in any way (squash too) |
|
|
846
|
+
| `prs_closed_unmerged` | pull requests they authored that were closed without merging in the date range |
|
|
847
|
+
|
|
848
|
+
```
|
|
849
|
+
gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**Requirements.** Only `--github` needs the [GitHub CLI](https://cli.github.com) (`gh`); without the flag nothing changes and GitHub is never contacted. Install `gh`, then run `gh auth login`. The tool checks this before cloning anything. If it fails, nothing is printed to standard output, the exit status is 1 and one of these messages is printed to standard error:
|
|
853
|
+
|
|
854
|
+
```
|
|
855
|
+
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.
|
|
856
|
+
gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
**Empty versus zero.** A zero means GitHub was asked and the person has none. An empty value (`null` in JSON) means GitHub could not be asked for that repository: it is not on github.com, or the request failed (no network, rate limit, a private repository you cannot see). The git statistics are still reported, the reason is printed to standard error, and the other repositories carry on. A contributor with no GitHub account matched has an empty `github_username` and zero pull requests. A GitHub user who opened pull requests but has no commits is listed in a row of their own with an empty `username` and zero git statistics.
|
|
860
|
+
|
|
861
|
+
**Matching GitHub users to git authors.** Each git author name is compared with the GitHub users active in the repository, trying these in order and stopping at the first that finds exactly one user:
|
|
862
|
+
|
|
863
|
+
1. **Email.** The email is a GitHub `noreply` address containing the username, GitHub itself links one of the author's commits to an account, or the email is the public email of a GitHub profile.
|
|
864
|
+
2. **Username.** The author name, or the part of the email before the `@`, is the GitHub username (ignoring case, and treating spaces, hyphens, underscores and dots as alike). The part before the `@` is skipped when it names a role rather than a person (such as `noreply`, `admin` or `info`), or when several author names in the repository share it (`alex@gmail…` and `alex@school…`), since it could belong to either.
|
|
865
|
+
3. **Display name.** The author name is the GitHub profile name (ignoring case, accents, punctuation and word order, so `Grace Hopper` matches `Hopper, Grace`).
|
|
866
|
+
|
|
867
|
+
If a step finds several users it is skipped as ambiguous and the next is tried. All git names matched to one GitHub user become one row: the `username` is the name with the most commits and the git statistics are added up. Use `-v` to see which step matched each name, the ambiguous and unmatched names, and how many `gh` calls were made. A wrong match can be fixed with a `.mailmap` file.
|
|
868
|
+
|
|
869
|
+
**How the other options apply.**
|
|
870
|
+
|
|
871
|
+
- `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed) against the same inclusive local-time range as commits.
|
|
872
|
+
- `-b`: only pull requests whose base branch is that branch are counted; without `-b`, all pull requests count.
|
|
873
|
+
- `-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.
|
|
874
|
+
- `-c`: removes only rows with no git activity and no pull requests.
|
|
875
|
+
- `-x`: has no effect on pull requests.
|
|
876
|
+
|
|
825
877
|
### Formatting the results
|
|
826
878
|
|
|
827
879
|
The results can be formatted as `csv`, `json`, or a `markdown` table. The default is `csv`. Use the `-f` flag to control the output format.
|
|
@@ -12,6 +12,8 @@ This script calculates the following, for each developer in each repository:
|
|
|
12
12
|
- number of **files changed**
|
|
13
13
|
- number of **merges** into the analyzed branch
|
|
14
14
|
|
|
15
|
+
With `--github`, it also reports each developer's GitHub **pull requests** opened, merged and closed without merging (see [GitHub pull requests](#github-pull-requests)).
|
|
16
|
+
|
|
15
17
|
The results can be formatted as `csv`, `json`, or `markdown`.
|
|
16
18
|
|
|
17
19
|
## Install
|
|
@@ -25,26 +27,36 @@ e.g. `pip install gitlogstats`, `pip3 install gitlogstats`, depending on your en
|
|
|
25
27
|
The command `gitlogstats --help` shows the usage instructions:
|
|
26
28
|
|
|
27
29
|
```
|
|
28
|
-
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
30
|
+
usage: gitlogstats [-h] (-r REPOSITORY | -rf REPOFILE) [-u USER] [-s START]
|
|
31
|
+
[-e END] [-x EXCLUSIONS] [-f {csv,json,markdown}]
|
|
32
|
+
[-b BRANCH] [-v] [-c] [--github]
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
options:
|
|
31
35
|
-h, --help show this help message and exit
|
|
32
36
|
-r REPOSITORY, --repository REPOSITORY
|
|
33
37
|
the public URL of the repository whose logs to parse
|
|
34
38
|
-rf REPOFILE, --repofile REPOFILE
|
|
35
|
-
the path to simple text file with a list of repository
|
|
36
|
-
|
|
39
|
+
the path to simple text file with a list of repository
|
|
40
|
+
URLs to parse
|
|
41
|
+
-u USER, --user USER The git username (or email address) to report. Default
|
|
42
|
+
is all contributing users
|
|
37
43
|
-s START, --start START
|
|
38
44
|
Start date in mm/dd/yyyy format
|
|
39
45
|
-e END, --end END End date in mm/dd/yyyy format
|
|
40
46
|
-x EXCLUSIONS, --exclusions EXCLUSIONS
|
|
41
|
-
A comma-separated list of files, patterns or
|
|
47
|
+
A comma-separated list of files, patterns or
|
|
48
|
+
directories to exclude, e.g. --exclusions "foo.zip,
|
|
49
|
+
*.jpg, vendor". Replaces the default list; use "" to
|
|
50
|
+
exclude nothing
|
|
42
51
|
-f {csv,json,markdown}, --format {csv,json,markdown}
|
|
43
52
|
The format in which to output the results
|
|
44
53
|
-b BRANCH, --branch BRANCH
|
|
45
|
-
The branch to
|
|
54
|
+
The branch to parse, if not the default branch
|
|
46
55
|
-v, --verbose Whether to output debugging info
|
|
47
56
|
-c, --clean Remove contributors without any contribuition
|
|
57
|
+
--github Also report GitHub pull requests opened, merged and
|
|
58
|
+
closed per contributor, and match GitHub users to git
|
|
59
|
+
authors. Needs the GitHub CLI (gh), signed in
|
|
48
60
|
```
|
|
49
61
|
|
|
50
62
|
In case `gitlogstats` is not found as a command, even after installing, try `python -m gitlogstats` or `python3 -m gitlogstats` instead.
|
|
@@ -125,6 +137,46 @@ By default, every contributor in a repository's history is listed, with zeros fo
|
|
|
125
137
|
gitlogstats -rf repos.txt -c
|
|
126
138
|
```
|
|
127
139
|
|
|
140
|
+
### GitHub pull requests
|
|
141
|
+
|
|
142
|
+
Add `--github` to also count each contributor's pull requests. Four columns are appended to every output format:
|
|
143
|
+
|
|
144
|
+
| Column | Meaning |
|
|
145
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| `github_username` | the GitHub account matched to the contributor (never the display name), so you can check the matching |
|
|
147
|
+
| `prs_opened` | pull requests they opened in the date range, whatever their state now (drafts included) |
|
|
148
|
+
| `prs_merged` | pull requests they authored that were merged in the date range, by anyone and in any way (squash too) |
|
|
149
|
+
| `prs_closed_unmerged` | pull requests they authored that were closed without merging in the date range |
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
gitlogstats -r https://github.com/bloombar/gitlogstats.git --github
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Requirements.** Only `--github` needs the [GitHub CLI](https://cli.github.com) (`gh`); without the flag nothing changes and GitHub is never contacted. Install `gh`, then run `gh auth login`. The tool checks this before cloning anything. If it fails, nothing is printed to standard output, the exit status is 1 and one of these messages is printed to standard error:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
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.
|
|
159
|
+
gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Empty versus zero.** A zero means GitHub was asked and the person has none. An empty value (`null` in JSON) means GitHub could not be asked for that repository: it is not on github.com, or the request failed (no network, rate limit, a private repository you cannot see). The git statistics are still reported, the reason is printed to standard error, and the other repositories carry on. A contributor with no GitHub account matched has an empty `github_username` and zero pull requests. A GitHub user who opened pull requests but has no commits is listed in a row of their own with an empty `username` and zero git statistics.
|
|
163
|
+
|
|
164
|
+
**Matching GitHub users to git authors.** Each git author name is compared with the GitHub users active in the repository, trying these in order and stopping at the first that finds exactly one user:
|
|
165
|
+
|
|
166
|
+
1. **Email.** The email is a GitHub `noreply` address containing the username, GitHub itself links one of the author's commits to an account, or the email is the public email of a GitHub profile.
|
|
167
|
+
2. **Username.** The author name, or the part of the email before the `@`, is the GitHub username (ignoring case, and treating spaces, hyphens, underscores and dots as alike). The part before the `@` is skipped when it names a role rather than a person (such as `noreply`, `admin` or `info`), or when several author names in the repository share it (`alex@gmail…` and `alex@school…`), since it could belong to either.
|
|
168
|
+
3. **Display name.** The author name is the GitHub profile name (ignoring case, accents, punctuation and word order, so `Grace Hopper` matches `Hopper, Grace`).
|
|
169
|
+
|
|
170
|
+
If a step finds several users it is skipped as ambiguous and the next is tried. All git names matched to one GitHub user become one row: the `username` is the name with the most commits and the git statistics are added up. Use `-v` to see which step matched each name, the ambiguous and unmatched names, and how many `gh` calls were made. A wrong match can be fixed with a `.mailmap` file.
|
|
171
|
+
|
|
172
|
+
**How the other options apply.**
|
|
173
|
+
|
|
174
|
+
- `-s` / `-e`: each pull request count uses its own timestamp (opened, merged or closed) against the same inclusive local-time range as commits.
|
|
175
|
+
- `-b`: only pull requests whose base branch is that branch are counted; without `-b`, all pull requests count.
|
|
176
|
+
- `-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.
|
|
177
|
+
- `-c`: removes only rows with no git activity and no pull requests.
|
|
178
|
+
- `-x`: has no effect on pull requests.
|
|
179
|
+
|
|
128
180
|
### Formatting the results
|
|
129
181
|
|
|
130
182
|
The results can be formatted as `csv`, `json`, or a `markdown` table. The default is `csv`. Use the `-f` flag to control the output format.
|
|
@@ -35,7 +35,7 @@ Repositories are cloned into a `repos/` directory under the current working dire
|
|
|
35
35
|
|
|
36
36
|
#### REPO-4 Branch selection
|
|
37
37
|
|
|
38
|
-
`-b/--branch <name>` checks out the given branch before statistics are computed. Without `-b`, the repository's default branch is used — including when an earlier run left the cached clone on another branch.
|
|
38
|
+
`-b/--branch <name>` checks out the given branch before statistics are computed. Without `-b`, the repository's default branch is used — including when an earlier run left the cached clone on another branch. With `--github`, it also limits which pull requests are counted to those whose base branch is that branch (CLI-3).
|
|
39
39
|
|
|
40
40
|
**Status:** Done
|
|
41
41
|
|
|
@@ -83,17 +83,48 @@ For each contributor, the number of merge commits they authored directly on the
|
|
|
83
83
|
|
|
84
84
|
**Status:** Done
|
|
85
85
|
|
|
86
|
+
#### STAT-8 Pull requests opened
|
|
87
|
+
|
|
88
|
+
With `--github` (CLI-3), for each contributor, the number of pull requests they authored on GitHub whose creation time (`created_at`) falls in the date range, whatever their state now (open, merged or closed; drafts included). Reported in a `prs_opened` column.
|
|
89
|
+
|
|
90
|
+
**Status:** Done
|
|
91
|
+
|
|
92
|
+
#### STAT-9 Pull requests merged
|
|
93
|
+
|
|
94
|
+
With `--github`, for each contributor, the number of pull requests they authored that were merged (`merged_at` set) in the date range, whoever merged them. Reported in a `prs_merged` column. Unlike merges (STAT-7), this counts squash and rebase merges too, since GitHub records them as merged pull requests.
|
|
95
|
+
|
|
96
|
+
**Status:** Done
|
|
97
|
+
|
|
98
|
+
#### STAT-10 Pull requests closed without merging
|
|
99
|
+
|
|
100
|
+
With `--github`, for each contributor, the number of pull requests they authored that were closed without being merged (`state` closed and `merged_at` not set), with a closing time (`closed_at`) in the date range. Reported in a `prs_closed_unmerged` column.
|
|
101
|
+
|
|
102
|
+
**Status:** Done
|
|
103
|
+
|
|
104
|
+
#### STAT-11 Matching GitHub users to git authors
|
|
105
|
+
|
|
106
|
+
With `--github`, each pull-request author (a GitHub username) is matched to the git identities (author name and email) in the repository's history. Each git identity is tried against the GitHub users active in that repository (pull-request authors and the accounts GitHub links to its commits), tier by tier, stopping at the first tier that finds exactly one GitHub user:
|
|
107
|
+
|
|
108
|
+
1. **Email.** The email is a GitHub `noreply` address (`12345+login@users.noreply.github.com` or `login@users.noreply.github.com`); or GitHub links one of the identity's commits to an account (the commit's `author.login`, which covers private emails GitHub knows but does not publish); or the email equals a candidate's public profile email (case-insensitive).
|
|
109
|
+
2. **Username.** The author name, or the email's local part (before `@`), equals a candidate's GitHub username, ignoring case and treating spaces, hyphens, underscores and dots as the same (`Ada Lovelace` matches `Ada-Lovelace`). An email local part is not used when it names a role rather than a person (such as `noreply`, `admin` or `info`), or when it is shared by several author names in the repository (`alex@gmail…` and `alex@school…`), since it could belong to either.
|
|
110
|
+
3. **Display name.** The author name equals a candidate's GitHub display name, ignoring case, accents, repeated spaces and punctuation, and word order (`Grace Hopper` matches `Hopper, Grace`).
|
|
111
|
+
4. **No match.** A GitHub user matched by no git identity is reported in a row of its own, with an empty `username`, its pull-request statistics and zero git statistics.
|
|
112
|
+
|
|
113
|
+
If a tier finds several GitHub users for one identity, that tier is skipped as ambiguous and the next is tried; ambiguous and unmatched identities are listed with `-v`. All git identities matched to the same GitHub user are combined into one row: the `username` is the matched author name with the most commits (ties broken alphabetically), and the git statistics are summed. A git identity matched to no GitHub user keeps its own row with an empty `github_username` and zero pull-request statistics. Every row carries the matched GitHub username (never the display name) in a `github_username` column, so matches can be checked and corrected by hand, e.g. with a `.mailmap` (STAT-6).
|
|
114
|
+
|
|
115
|
+
**Status:** Done
|
|
116
|
+
|
|
86
117
|
### 3. Filtering
|
|
87
118
|
|
|
88
119
|
#### FILT-1 Date range
|
|
89
120
|
|
|
90
|
-
`-s/--start` and `-e/--end` (format `mm/dd/yyyy`) limit the analysis to commits whose commit date falls between the start of the start date and the end of the end date, inclusive, in local time. Defaults: one year ago until today. The output records the requested start and end dates on every row.
|
|
121
|
+
`-s/--start` and `-e/--end` (format `mm/dd/yyyy`) limit the analysis to commits whose commit date falls between the start of the start date and the end of the end date, inclusive, in local time. Defaults: one year ago until today. The output records the requested start and end dates on every row. With `--github`, each pull request count is filtered by its own timestamp in the same range (CLI-3).
|
|
91
122
|
|
|
92
123
|
**Status:** Done
|
|
93
124
|
|
|
94
125
|
#### FILT-2 Single contributor
|
|
95
126
|
|
|
96
|
-
`-u/--user <name>` reports only the contributor whose author name or email address matches exactly. Without `-u`, every contributor in the repository's history is reported.
|
|
127
|
+
`-u/--user <name>` reports only the contributor whose author name or email address matches exactly. Without `-u`, every contributor in the repository's history is reported. With `--github`, it selects the combined row, and a value may also be the GitHub username (ignoring case); see CLI-3.
|
|
97
128
|
|
|
98
129
|
**Status:** Done
|
|
99
130
|
|
|
@@ -105,7 +136,7 @@ For each contributor, the number of merge commits they authored directly on the
|
|
|
105
136
|
|
|
106
137
|
#### FILT-4 Remove inactive contributors
|
|
107
138
|
|
|
108
|
-
By default, contributors with no activity in the date range are listed with zeros. `-c/--clean` removes them.
|
|
139
|
+
By default, contributors with no activity in the date range are listed with zeros. `-c/--clean` removes them. With `--github`, only rows with neither git nor pull-request activity are removed (CLI-3).
|
|
109
140
|
|
|
110
141
|
**Status:** Done
|
|
111
142
|
|
|
@@ -113,7 +144,7 @@ By default, contributors with no activity in the date range are listed with zero
|
|
|
113
144
|
|
|
114
145
|
#### OUT-1 CSV output
|
|
115
146
|
|
|
116
|
-
`-f csv` (the default) prints a header row followed by one row per contributor per repository, with fields `username, repository, start_date, end_date, commits, insertions, deletions, files, merges`. New fields are added at the end so existing column positions do not change. Values containing commas, quotes or line breaks are quoted. The header is printed even when there are no rows, and the output ends with a single newline.
|
|
147
|
+
`-f csv` (the default) prints a header row followed by one row per contributor per repository, with fields `username, repository, start_date, end_date, commits, insertions, deletions, files, merges`. New fields are added at the end so existing column positions do not change. Values containing commas, quotes or line breaks are quoted. The header is printed even when there are no rows, and the output ends with a single newline. With `--github`, the columns `github_username, prs_opened, prs_merged, prs_closed_unmerged` are appended, and are in the header even when there are no rows (CLI-3).
|
|
117
148
|
|
|
118
149
|
**Status:** Done
|
|
119
150
|
|
|
@@ -137,7 +168,7 @@ When several repositories are analyzed, the results are printed once, after all
|
|
|
137
168
|
|
|
138
169
|
#### OUT-5 Verbose diagnostics
|
|
139
170
|
|
|
140
|
-
`-v/--verbose` prints debugging information (directories, the git commands run, contributors found) to standard error, so standard output stays machine-readable.
|
|
171
|
+
`-v/--verbose` prints debugging information (directories, the git commands run, contributors found) to standard error, so standard output stays machine-readable. With `--github`, it also prints how each author name was matched to a GitHub user, the ambiguous and unmatched names, and the number of `gh` API calls made for each repository (CLI-3).
|
|
141
172
|
|
|
142
173
|
**Status:** Done
|
|
143
174
|
|
|
@@ -155,6 +186,17 @@ Invalid dates, and a start date after the end date, are rejected with a usage er
|
|
|
155
186
|
|
|
156
187
|
**Status:** Done
|
|
157
188
|
|
|
189
|
+
#### CLI-3 GitHub pull-request statistics
|
|
190
|
+
|
|
191
|
+
`--github` adds GitHub pull-request statistics (STAT-8 to STAT-11) for repositories hosted on github.com, appending the columns `github_username, prs_opened, prs_merged, prs_closed_unmerged` to every output format. Without `--github`, the output and behaviour are unchanged, GitHub is never contacted and the GitHub CLI (`gh`) is not needed. With `--github`, the GitHub API is called through `gh api`, so `gh` must be installed and signed in. This is checked before any repository is cloned; if it fails, nothing is printed to standard output, the tool exits with status 1, and one plain message on standard error says why and what to do — no stack trace:
|
|
192
|
+
|
|
193
|
+
- `gh` not found: `gitlogstats: --github needs the GitHub CLI (gh), which is not installed. Install it from https://cli.github.com and run 'gh auth login', or run without --github.`
|
|
194
|
+
- `gh` not signed in (`gh auth status` fails): `gitlogstats: --github needs the GitHub CLI (gh) to be signed in. Run 'gh auth login', or run without --github.`
|
|
195
|
+
|
|
196
|
+
The other options apply to pull requests as follows: `-s/-e` filter each count by its own timestamp, using the same inclusive local-time range as FILT-1; `-u` selects the combined row whose author names, emails or GitHub username match exactly (the username ignoring case); `-b` counts only pull requests whose base branch is that branch, and without `-b` every pull request counts; `-c` removes only rows with neither git nor pull-request activity; `-x` does not apply. For a repository not on github.com, or when GitHub cannot be reached for one repository (no network, rate limit, private repository without access), the git statistics are still reported, the four GitHub columns are empty (`null` in JSON) rather than zero, and the reason is printed to standard error.
|
|
197
|
+
|
|
198
|
+
**Status:** Done
|
|
199
|
+
|
|
158
200
|
### 6. Packaging & Quality
|
|
159
201
|
|
|
160
202
|
#### PKG-1 Installable package
|
|
@@ -189,3 +231,7 @@ The CI workflow runs the test suite on every push and stops the release if any t
|
|
|
189
231
|
- **Parallel processing** — clone and analyze many repositories concurrently to speed up large batches.
|
|
190
232
|
- **Per-file breakdown** — optionally report each contributor's changes per file or directory.
|
|
191
233
|
- **Output to file** — write results to a file (e.g. `-o report.csv`) instead of only standard output.
|
|
234
|
+
- **GitHub without the gh CLI** — let `--github` call the GitHub API directly with a `GITHUB_TOKEN`, for users who cannot install `gh`.
|
|
235
|
+
- **GitHub Enterprise hosts** — extend `--github` to repositories on GitHub Enterprise Server, via `gh api --hostname` or a configurable API URL.
|
|
236
|
+
- **Pull-request reviews and mergers** — report who reviewed and who merged each pull request, in addition to who opened it.
|
|
237
|
+
- **GitHub-only mode** — report pull-request statistics without computing git statistics, for faster runs over large batches.
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitlogstats"
|
|
7
7
|
description = "Track developers' contributions to one or more git repositories within a particular time range."
|
|
8
|
-
version = "1.0
|
|
8
|
+
version = "1.1.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Tobody Tothing", email="nobody.nothing@nowhere.com" },
|
|
11
11
|
]
|
|
@@ -17,12 +17,30 @@
|
|
|
17
17
|
family: CLI
|
|
18
18
|
phase: 1
|
|
19
19
|
status: Done
|
|
20
|
+
- id: CLI-3
|
|
21
|
+
title: GitHub pull-request statistics
|
|
22
|
+
full: "--github adds GitHub pull-request statistics (STAT-8 to STAT-11) for
|
|
23
|
+
repositories hosted on github.com, appending the columns github_username,
|
|
24
|
+
prs_opened, prs_merged, prs_closed_unmerged to every output format. Without
|
|
25
|
+
--github, the output and behaviour are unchanged, GitHub is never contacted
|
|
26
|
+
and the GitHub CLI (gh) is not needed. With --github, the GitHub API is
|
|
27
|
+
called through gh api, so gh must be installed and signed in. This is
|
|
28
|
+
checked before any repository is cloned; if it fails, nothing is printed to
|
|
29
|
+
standard output, the tool exits with status 1, and one plain message on
|
|
30
|
+
standard error says why and what to do — no stack trace: gh not found:
|
|
31
|
+
gitlogstats: --github needs the GitHub CLI…"
|
|
32
|
+
section: §5 Command-Line Interface
|
|
33
|
+
family: CLI
|
|
34
|
+
phase: 1
|
|
35
|
+
status: Done
|
|
20
36
|
- id: FILT-1
|
|
21
37
|
title: Date range
|
|
22
38
|
full: "-s/--start and -e/--end (format mm/dd/yyyy) limit the analysis to commits
|
|
23
39
|
whose commit date falls between the start of the start date and the end of
|
|
24
40
|
the end date, inclusive, in local time. Defaults: one year ago until today.
|
|
25
|
-
The output records the requested start and end dates on every row.
|
|
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)."
|
|
26
44
|
section: §3 Filtering
|
|
27
45
|
family: FILT
|
|
28
46
|
phase: 1
|
|
@@ -31,7 +49,8 @@
|
|
|
31
49
|
title: Single contributor
|
|
32
50
|
full: -u/--user <name> reports only the contributor whose author name or email
|
|
33
51
|
address matches exactly. Without -u, every contributor in the repository's
|
|
34
|
-
history is reported.
|
|
52
|
+
history is reported. With --github, it selects the combined row, and a value
|
|
53
|
+
may also be the GitHub username (ignoring case); see CLI-3.
|
|
35
54
|
section: §3 Filtering
|
|
36
55
|
family: FILT
|
|
37
56
|
phase: 1
|
|
@@ -55,7 +74,8 @@
|
|
|
55
74
|
- id: FILT-4
|
|
56
75
|
title: Remove inactive contributors
|
|
57
76
|
full: By default, contributors with no activity in the date range are listed
|
|
58
|
-
with zeros. -c/--clean removes them.
|
|
77
|
+
with zeros. -c/--clean removes them. With --github, only rows with neither
|
|
78
|
+
git nor pull-request activity are removed (CLI-3).
|
|
59
79
|
section: §3 Filtering
|
|
60
80
|
family: FILT
|
|
61
81
|
phase: 1
|
|
@@ -67,7 +87,10 @@
|
|
|
67
87
|
end_date, commits, insertions, deletions, files, merges. New fields are
|
|
68
88
|
added at the end so existing column positions do not change. Values
|
|
69
89
|
containing commas, quotes or line breaks are quoted. The header is printed
|
|
70
|
-
even when there are no rows, and the output ends with a single newline.
|
|
90
|
+
even when there are no rows, and the output ends with a single newline. With
|
|
91
|
+
--github, the columns github_username, prs_opened, prs_merged,
|
|
92
|
+
prs_closed_unmerged are appended, and are in the header even when there are
|
|
93
|
+
no rows (CLI-3).
|
|
71
94
|
section: §4 Output
|
|
72
95
|
family: OUT
|
|
73
96
|
phase: 1
|
|
@@ -101,7 +124,9 @@
|
|
|
101
124
|
title: Verbose diagnostics
|
|
102
125
|
full: -v/--verbose prints debugging information (directories, the git commands
|
|
103
126
|
run, contributors found) to standard error, so standard output stays
|
|
104
|
-
machine-readable.
|
|
127
|
+
machine-readable. With --github, it also prints how each author name was
|
|
128
|
+
matched to a GitHub user, the ambiguous and unmatched names, and the number
|
|
129
|
+
of gh API calls made for each repository (CLI-3).
|
|
105
130
|
section: §4 Output
|
|
106
131
|
family: OUT
|
|
107
132
|
phase: 1
|
|
@@ -172,7 +197,9 @@
|
|
|
172
197
|
title: Branch selection
|
|
173
198
|
full: -b/--branch <name> checks out the given branch before statistics are
|
|
174
199
|
computed. Without -b, the repository's default branch is used — including
|
|
175
|
-
when an earlier run left the cached clone on another branch.
|
|
200
|
+
when an earlier run left the cached clone on another branch. With --github,
|
|
201
|
+
it also limits which pull requests are counted to those whose base branch is
|
|
202
|
+
that branch (CLI-3).
|
|
176
203
|
section: §1 Repository Selection
|
|
177
204
|
family: REPO
|
|
178
205
|
phase: 1
|
|
@@ -255,6 +282,53 @@
|
|
|
255
282
|
family: STAT
|
|
256
283
|
phase: 1
|
|
257
284
|
status: Done
|
|
285
|
+
- id: STAT-8
|
|
286
|
+
title: Pull requests opened
|
|
287
|
+
full: With --github (CLI-3), for each contributor, the number of pull requests
|
|
288
|
+
they authored on GitHub whose creation time (created_at) falls in the date
|
|
289
|
+
range, whatever their state now (open, merged or closed; drafts included).
|
|
290
|
+
Reported in a prs_opened column.
|
|
291
|
+
section: §2 Contribution Statistics
|
|
292
|
+
family: STAT
|
|
293
|
+
phase: 1
|
|
294
|
+
status: Done
|
|
295
|
+
- id: STAT-9
|
|
296
|
+
title: Pull requests merged
|
|
297
|
+
full: With --github, for each contributor, the number of pull requests they
|
|
298
|
+
authored that were merged (merged_at set) in the date range, whoever merged
|
|
299
|
+
them. Reported in a prs_merged column. Unlike merges (STAT-7), this counts
|
|
300
|
+
squash and rebase merges too, since GitHub records them as merged pull
|
|
301
|
+
requests.
|
|
302
|
+
section: §2 Contribution Statistics
|
|
303
|
+
family: STAT
|
|
304
|
+
phase: 1
|
|
305
|
+
status: Done
|
|
306
|
+
- id: STAT-10
|
|
307
|
+
title: Pull requests closed without merging
|
|
308
|
+
full: With --github, for each contributor, the number of pull requests they
|
|
309
|
+
authored that were closed without being merged (state closed and merged_at
|
|
310
|
+
not set), with a closing time (closed_at) in the date range. Reported in a
|
|
311
|
+
prs_closed_unmerged column.
|
|
312
|
+
section: §2 Contribution Statistics
|
|
313
|
+
family: STAT
|
|
314
|
+
phase: 1
|
|
315
|
+
status: Done
|
|
316
|
+
- id: STAT-11
|
|
317
|
+
title: Matching GitHub users to git authors
|
|
318
|
+
full: "With --github, each pull-request author (a GitHub username) is matched to
|
|
319
|
+
the git identities (author name and email) in the repository's history. Each
|
|
320
|
+
git identity is tried against the GitHub users active in that repository
|
|
321
|
+
(pull-request authors and the accounts GitHub links to its commits), tier by
|
|
322
|
+
tier, stopping at the first tier that finds exactly one GitHub user: 1.
|
|
323
|
+
Email. The email is a GitHub noreply address
|
|
324
|
+
(12345+login@users.noreply.github.com or login@users.noreply.github.com); or
|
|
325
|
+
GitHub links one of the identity's commits to an account (the commit's
|
|
326
|
+
author.login, which covers private emails GitHub knows but does not
|
|
327
|
+
publish); or the email equals a candidate's public profile email…"
|
|
328
|
+
section: §2 Contribution Statistics
|
|
329
|
+
family: STAT
|
|
330
|
+
phase: 1
|
|
331
|
+
status: Done
|
|
258
332
|
- id: FUTURE-author-date-filtering
|
|
259
333
|
title: Author-date filtering
|
|
260
334
|
full: Author-date filtering — offer filtering by author date, so rebased or
|
|
@@ -272,6 +346,30 @@
|
|
|
272
346
|
family: FUTURE
|
|
273
347
|
phase: 2
|
|
274
348
|
status: Todo
|
|
349
|
+
- id: FUTURE-github-enterprise-hosts
|
|
350
|
+
title: GitHub Enterprise hosts
|
|
351
|
+
full: GitHub Enterprise hosts — extend --github to repositories on GitHub
|
|
352
|
+
Enterprise Server, via gh api --hostname or a configurable API URL.
|
|
353
|
+
section: §7 Future Work
|
|
354
|
+
family: FUTURE
|
|
355
|
+
phase: 2
|
|
356
|
+
status: Todo
|
|
357
|
+
- id: FUTURE-github-only-mode
|
|
358
|
+
title: GitHub-only mode
|
|
359
|
+
full: GitHub-only mode — report pull-request statistics without computing git
|
|
360
|
+
statistics, for faster runs over large batches.
|
|
361
|
+
section: §7 Future Work
|
|
362
|
+
family: FUTURE
|
|
363
|
+
phase: 2
|
|
364
|
+
status: Todo
|
|
365
|
+
- id: FUTURE-github-without-the-gh-cli
|
|
366
|
+
title: GitHub without the gh CLI
|
|
367
|
+
full: GitHub without the gh CLI — let --github call the GitHub API directly with
|
|
368
|
+
a GITHUB_TOKEN, for users who cannot install gh.
|
|
369
|
+
section: §7 Future Work
|
|
370
|
+
family: FUTURE
|
|
371
|
+
phase: 2
|
|
372
|
+
status: Todo
|
|
275
373
|
- id: FUTURE-output-to-file
|
|
276
374
|
title: Output to file
|
|
277
375
|
full: Output to file — write results to a file (e.g. -o report.csv) instead of
|
|
@@ -304,3 +402,11 @@
|
|
|
304
402
|
family: FUTURE
|
|
305
403
|
phase: 2
|
|
306
404
|
status: Todo
|
|
405
|
+
- id: FUTURE-pull-request-reviews-and-mergers
|
|
406
|
+
title: Pull-request reviews and mergers
|
|
407
|
+
full: Pull-request reviews and mergers — report who reviewed and who merged each
|
|
408
|
+
pull request, in addition to who opened it.
|
|
409
|
+
section: §7 Future Work
|
|
410
|
+
family: FUTURE
|
|
411
|
+
phase: 2
|
|
412
|
+
status: Todo
|
|
@@ -3,7 +3,11 @@ import subprocess
|
|
|
3
3
|
import argparse
|
|
4
4
|
import datetime
|
|
5
5
|
import re
|
|
6
|
+
import sys
|
|
6
7
|
from . import GitLogsParser
|
|
8
|
+
from .git_logs_parser import FIELDNAMES
|
|
9
|
+
from .github import GITHUB_FIELDNAMES, GitHubUnavailable, check_gh
|
|
10
|
+
from .github_stats import github_rows
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
def valid_date(value):
|
|
@@ -135,6 +139,14 @@ def main():
|
|
|
135
139
|
default=False,
|
|
136
140
|
action="store_true",
|
|
137
141
|
)
|
|
142
|
+
parser.add_argument(
|
|
143
|
+
"--github",
|
|
144
|
+
help="Also report GitHub pull requests opened, merged and closed per "
|
|
145
|
+
"contributor, and match GitHub users to git authors. Needs the GitHub CLI "
|
|
146
|
+
"(gh), signed in",
|
|
147
|
+
default=False,
|
|
148
|
+
action="store_true",
|
|
149
|
+
)
|
|
138
150
|
args = parser.parse_args()
|
|
139
151
|
|
|
140
152
|
if datetime.datetime.strptime(args.start, "%m/%d/%Y") > datetime.datetime.strptime(
|
|
@@ -145,6 +157,14 @@ def main():
|
|
|
145
157
|
# split up comma-separated string into list, ignoring empty items
|
|
146
158
|
args.exclusions = [x.strip() for x in re.split(r",", args.exclusions) if x.strip()]
|
|
147
159
|
|
|
160
|
+
# CLI-3: check the GitHub CLI before anything is created or cloned
|
|
161
|
+
if args.github:
|
|
162
|
+
try:
|
|
163
|
+
check_gh()
|
|
164
|
+
except GitHubUnavailable as exc:
|
|
165
|
+
print(exc, file=sys.stderr)
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
|
|
148
168
|
# deal with repofile, if specified
|
|
149
169
|
repository_urls = [args.repository]
|
|
150
170
|
if args.repofile:
|
|
@@ -193,20 +213,28 @@ def main():
|
|
|
193
213
|
["git", "pull"], capture_output=True, check=True
|
|
194
214
|
) # get the latest code from github
|
|
195
215
|
|
|
216
|
+
# with --github, -u and -c are applied after GitHub users are combined
|
|
196
217
|
last_parser = GitLogsParser(
|
|
197
218
|
repo=repo_dir,
|
|
198
219
|
start=args.start,
|
|
199
220
|
end=args.end,
|
|
200
|
-
username=args.user,
|
|
221
|
+
username=None if args.github else args.user,
|
|
201
222
|
exclusions=args.exclusions,
|
|
202
223
|
verbose=args.verbose,
|
|
203
|
-
clean=args.clean,
|
|
224
|
+
clean=False if args.github else args.clean,
|
|
204
225
|
)
|
|
205
|
-
|
|
226
|
+
rows = last_parser.parse()
|
|
227
|
+
if args.github:
|
|
228
|
+
rows = github_rows(
|
|
229
|
+
last_parser, rows, repo_url, last_parser.get_identities(), args
|
|
230
|
+
)
|
|
231
|
+
all_results.extend(rows) # collect across repos
|
|
206
232
|
|
|
207
233
|
# emit a single table (or JSON array) covering all repositories
|
|
208
234
|
if last_parser is not None:
|
|
209
|
-
|
|
235
|
+
# an empty CSV table still gets the GitHub columns with --github
|
|
236
|
+
fieldnames = FIELDNAMES + GITHUB_FIELDNAMES if args.github else None
|
|
237
|
+
output = last_parser.format_results(all_results, args.format, fieldnames)
|
|
210
238
|
# end with exactly one newline: CSV and Markdown output already has one
|
|
211
239
|
print(output, end="" if output.endswith("\n") else "\n")
|
|
212
240
|
|
|
@@ -93,6 +93,25 @@ class GitLogsParser:
|
|
|
93
93
|
self.verboseprint(f"Contributors: {', '.join(contributors)}...")
|
|
94
94
|
return contributors
|
|
95
95
|
|
|
96
|
+
def get_identities(self):
|
|
97
|
+
"""
|
|
98
|
+
Return every identity (author name and email) in this repository's
|
|
99
|
+
history, the same population as get_contributors (STAT-11).
|
|
100
|
+
@returns: {author_name: {email: hash of one commit with that identity}}
|
|
101
|
+
"""
|
|
102
|
+
cmd = ["git", "log", LOG_FORMAT]
|
|
103
|
+
self.verboseprint(f"Running command: {' '.join(cmd)}")
|
|
104
|
+
result = subprocess.run(cmd, capture_output=True, check=True)
|
|
105
|
+
identities = {}
|
|
106
|
+
for record in result.stdout.decode("utf-8", errors="replace").split(RECORD_SEP)[
|
|
107
|
+
1:
|
|
108
|
+
]:
|
|
109
|
+
fields = record.strip("\n").split(FIELD_SEP)
|
|
110
|
+
name = fields[1].strip().strip("'") # as get_contributors does
|
|
111
|
+
if name:
|
|
112
|
+
identities.setdefault(name, {}).setdefault(fields[2], fields[0])
|
|
113
|
+
return identities
|
|
114
|
+
|
|
96
115
|
def date_range_args(self):
|
|
97
116
|
"""
|
|
98
117
|
Build the git log options that limit commits to the date range.
|
|
@@ -275,11 +294,12 @@ class GitLogsParser:
|
|
|
275
294
|
stats.append(entry)
|
|
276
295
|
return stats
|
|
277
296
|
|
|
278
|
-
def format_results(self, results, output_format):
|
|
297
|
+
def format_results(self, results, output_format, fieldnames=None):
|
|
279
298
|
"""
|
|
280
299
|
Format the parsed data in the selected format.
|
|
281
300
|
@param results: A list of dictionaries, with each representing a contributor to the repository.
|
|
282
301
|
@param output_format: The desired output format, e.g. 'csv', 'json', or 'markdown'
|
|
302
|
+
@param fieldnames: the columns of an empty CSV table; defaults to FIELDNAMES
|
|
283
303
|
"""
|
|
284
304
|
self.verboseprint(f"Outputting results in {output_format.upper()} format:...")
|
|
285
305
|
output = ""
|
|
@@ -288,7 +308,10 @@ class GitLogsParser:
|
|
|
288
308
|
# always include the header, so even an empty result is a valid
|
|
289
309
|
# CSV table; quote values that contain commas, quotes, or line breaks
|
|
290
310
|
buffer = io.StringIO()
|
|
291
|
-
|
|
311
|
+
if results:
|
|
312
|
+
fieldnames = list(results[0].keys())
|
|
313
|
+
elif fieldnames is None:
|
|
314
|
+
fieldnames = FIELDNAMES
|
|
292
315
|
writer = csv.DictWriter(buffer, fieldnames=fieldnames, lineterminator="\n")
|
|
293
316
|
writer.writeheader()
|
|
294
317
|
writer.writerows(results)
|
|
@@ -308,7 +331,11 @@ class GitLogsParser:
|
|
|
308
331
|
) # heading separator lines
|
|
309
332
|
for entry in results:
|
|
310
333
|
# each row of values, with any pipes escaped so they do not split cells
|
|
311
|
-
|
|
334
|
+
# (an unavailable value, None, is left blank)
|
|
335
|
+
str_values = [
|
|
336
|
+
("" if val is None else str(val)).replace("|", "\\|")
|
|
337
|
+
for val in entry.values()
|
|
338
|
+
]
|
|
312
339
|
output += "| " + " | ".join(str_values) + " |\n"
|
|
313
340
|
|
|
314
341
|
return output
|