feat: Basic git commit counter

This commit is contained in:
Samuel Ortion 2023-09-02 19:57:48 +02:00
parent 798c1d937a
commit 9391298354
9 changed files with 518 additions and 15 deletions

160
.gitignore vendored
View File

@ -1,2 +1,160 @@
__pycache__
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View File

@ -1,3 +1,5 @@
pandas
numpy
GitPython
GitPython
typer[all]
calmap

30
setup.cfg Normal file
View File

@ -0,0 +1,30 @@
[metadata]
name = gitstats
version = attr: gitstats.__version__
author = Samuel Ortion
author_email = samuel+git@ortion.fr
keywords = git, contributions
license = GPLv3
license_files = LICENSE
long_description = file: README.md
long_description_content_type = text/markdown
classifiers =
Development Status :: 3 - Alpha
Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3
Programming Language :: Python :: 3
Topic :: Scientific/Engineering :: Bio-Informatics
[options]
packages = find:
package_dir =
=src
python_require = >= 3.7 # What is the minimum version of python required?
install_required = file: requirements.txt
[options.entry_points]
console_scripts =
git.stats=gitstats.__main__:main
[options.packages.find]
where = src

5
setup.py Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env python3
from setuptools import setup
setup()

1
src/gitstats/__init__.py Normal file
View File

@ -0,0 +1 @@
__version__ = "0.1.0"

57
src/gitstats/__main__.py Normal file
View File

@ -0,0 +1,57 @@
import logging
from collections import defaultdict
import typer
from typing import Optional
from git import Repo
import matplotlib.pyplot as plt
import pandas as pd
from . import commits
from . import plots
app = typer.Typer()
@app.command()
def count(dir_name: str, year: int = 2023, author_name: str = None, output_csv: Optional[str] = None) -> pd.DataFrame:
# Count the number of commits each days for git repositories in the folder
commit_date_counter = defaultdict(int)
for repo_dir in commits.iter_repos(dir_name):
repo = Repo(repo_dir)
if repo.heads != []:
repo_commit_date_counter = commits.repo_daily_commits(repo, year, author_name)
for key, count in repo_commit_date_counter.items():
commit_date_counter[key] += 1
# Construct a pandas DataFrame with this data
df = pd.DataFrame({"date": commit_date_counter.keys(), "count": commit_date_counter.values()})
df["date"] = pd.to_datetime(df["date"])
if output_csv is not None:
df.to_csv(output_csv, sep=";", index=False)
return df
@app.command()
def plot(input_csv: str, output_plot: Optional[str] = None):
df = pd.read_csv(input_csv, sep=";")
df["date"] = pd.to_datetime(df["date"])
plots.year_of_contributions(df)
plt.show()
@app.command()
def pipe(dir: str = ".", author: Optional[str] = None, year: Optional[int] = None, output_csv: Optional[str] = None, output_plot: Optional[str] = None):
df = count(dir_name=dir, year=year, author_name=author, output_csv=output_csv)
if output_csv:
df.to_csv(output_csv, sep=";")
plots.year_of_contributions(df)
if output_plot:
plt.savefig(output_plot)
def main():
app()
if __name__ == "__main__":
main()

View File

@ -2,24 +2,56 @@
Generate a CSV file with daily stats commit count from a set of repositories
"""
from git import Repo
import datetime
import os
import logging
from collections import defaultdict
from typing import Iterable, Optional
from git import Repo
REPO = "."
repo = Repo(REPO)
logger = logging.getLogger(__name__)
def repo_daily_commits(repo: Repo, year: int):
def wrapper(gen: Iterable, repo: Repo):
while True:
try:
yield next(gen)
except StopIteration:
break
except Exception as e:
logger.error(e)
logger.error(f"Occured in this repo: {repo.working_dir}")
logger.error("Will probably omit anterior commits")
def repo_daily_commits(repo: Repo, year: Optional[int] = None, author_name: Optional[str] = None):
"""Count how many commit was performed for each days in the year"""
data = {}
for commit in repo.iter_commits(all=True):
timestamp = commit.authored_date
date = datetime.date.fromtimestamp(timestamp)
if int(date.year) != year:
continue
if date not in data:
data[date] = 0
data[date] += 1
if author_name is not None:
author_name = author_name.lower()
other_author_names: set = set()
data = defaultdict(int)
for commit in wrapper(repo.iter_commits(all=True), repo):
if author_name is not None:
commit_author: str = commit.author.name
if commit_author.lower() != author_name:
other_author_names.add(commit_author)
commit_timestamp: int = commit.authored_date
commit_date: datetime.date = datetime.date.fromtimestamp(
commit_timestamp)
if year is not None:
if commit_date.year != year:
continue
data[commit_date] += 1
if author_name is not None:
# Warn for all other author names
for other_author_name in other_author_names:
logger.info(f"Other commit author in this folder of repositories:\t{other_author_name}")
return data
print(repo_daily_commits(repo, 2023))
def iter_repos(dir_name: str):
pys = []
for root, dirs, files in os.walk(dir_name):
for directory in dirs:
if directory == '.git':
yield os.path.join(root, directory)

20
src/gitstats/plots.py Normal file
View File

@ -0,0 +1,20 @@
"""Plot contribution heatmap using calmap
ref. https://builtin.com/data-science/github-contribution-plot"""
import calmap
import pandas as pd
import matplotlib.pyplot as plt
def year_of_contributions(df: pd.DataFrame):
events = pd.Series(df["count"], index=df["date"])
fig = plt.figure(figsize=(20,8))
ax = fig.add_subplot(111)
cax = calmap.yearplot(events, # cmap='YlGn',
fillcolor='lightgrey',
daylabels="MTWTFSS",
dayticks=[0, 2, 4, 6],
linewidth=2,
ax=ax
)
# fig.colorbar(cax.get_children()[1], ax=cax, orientation='horizontal') # FIXME: this is ugly.

198
tmp.csv Normal file
View File

@ -0,0 +1,198 @@
;date;count
0;2023-04-12;5
1;2023-01-12;3
2;2023-03-28;4
3;2023-01-28;3
4;2023-01-16;1
5;2023-01-13;2
6;2023-03-29;6
7;2023-04-23;4
8;2023-02-23;2
9;2023-02-02;3
10;2023-01-15;5
11;2023-01-14;4
12;2023-08-26;4
13;2023-05-29;4
14;2023-03-25;2
15;2023-01-08;3
16;2023-01-06;4
17;2023-01-05;3
18;2023-01-04;3
19;2023-06-23;2
20;2023-06-19;2
21;2023-06-15;5
22;2023-05-31;4
23;2023-05-28;2
24;2023-05-17;3
25;2023-05-14;3
26;2023-05-08;4
27;2023-05-07;4
28;2023-05-06;2
29;2023-04-19;2
30;2023-04-18;3
31;2023-04-09;4
32;2023-04-01;3
33;2023-03-12;3
34;2023-03-11;2
35;2023-03-05;2
36;2023-01-19;1
37;2023-04-03;3
38;2023-02-07;3
39;2023-02-04;4
40;2023-02-18;3
41;2023-02-17;4
42;2023-01-23;2
43;2023-01-17;3
44;2023-01-09;2
45;2023-08-11;2
46;2023-07-25;2
47;2023-08-07;1
48;2023-08-05;2
49;2023-07-31;3
50;2023-07-19;1
51;2023-07-13;1
52;2023-07-12;1
53;2023-07-04;3
54;2023-06-14;1
55;2023-06-11;2
56;2023-06-01;2
57;2023-05-15;1
58;2023-05-12;1
59;2023-05-27;2
60;2023-05-26;3
61;2023-05-22;3
62;2023-05-20;2
63;2023-05-16;2
64;2023-05-13;1
65;2023-05-09;3
66;2023-05-01;4
67;2023-04-29;2
68;2023-04-10;3
69;2023-04-20;3
70;2023-04-25;3
71;2023-04-11;4
72;2023-04-04;4
73;2023-03-31;3
74;2023-03-23;2
75;2023-03-13;1
76;2023-03-22;1
77;2023-03-16;2
78;2023-03-15;1
79;2023-03-14;2
80;2023-03-20;2
81;2023-03-08;1
82;2023-02-28;2
83;2023-02-27;3
84;2023-02-26;3
85;2023-02-22;1
86;2023-02-20;2
87;2023-02-19;6
88;2023-02-13;3
89;2023-02-10;3
90;2023-02-08;3
91;2023-02-06;2
92;2023-02-05;8
93;2023-02-03;2
94;2023-02-01;2
95;2023-01-30;1
96;2023-01-29;3
97;2023-01-27;1
98;2023-01-22;2
99;2023-01-21;1
100;2023-01-18;2
101;2023-07-06;2
102;2023-04-21;4
103;2023-02-12;2
104;2023-01-24;1
105;2023-08-19;1
106;2023-06-29;1
107;2023-06-07;3
108;2023-08-04;2
109;2023-08-28;2
110;2023-08-25;2
111;2023-08-23;1
112;2023-04-07;4
113;2023-05-05;1
114;2023-04-26;2
115;2023-03-04;1
116;2023-03-03;3
117;2023-03-02;1
118;2023-04-16;2
119;2023-04-08;3
120;2023-04-06;2
121;2023-01-02;3
122;2023-08-01;2
123;2023-07-28;1
124;2023-07-24;3
125;2023-07-21;2
126;2023-07-17;1
127;2023-07-14;1
128;2023-07-03;2
129;2023-06-26;2
130;2023-06-22;1
131;2023-06-12;1
132;2023-06-08;1
133;2023-06-03;2
134;2023-05-23;3
135;2023-05-21;2
136;2023-05-10;1
137;2023-05-02;2
138;2023-04-28;1
139;2023-04-22;2
140;2023-04-17;2
141;2023-04-15;3
142;2023-04-05;2
143;2023-04-02;2
144;2023-03-30;2
145;2023-03-27;4
146;2023-03-26;4
147;2023-03-24;2
148;2023-03-10;1
149;2023-02-25;2
150;2023-02-24;2
151;2023-02-21;1
152;2023-02-15;2
153;2023-02-14;1
154;2023-01-26;1
155;2023-01-11;3
156;2023-01-10;2
157;2023-01-03;3
158;2023-01-01;1
159;2023-07-29;5
160;2023-09-01;4
161;2023-08-31;1
162;2023-08-29;2
163;2023-08-15;1
164;2023-09-02;3
165;2023-02-11;2
166;2023-05-19;1
167;2023-06-06;2
168;2023-07-27;1
169;2023-06-21;1
170;2023-06-05;1
171;2023-06-04;1
172;2023-05-03;1
173;2023-04-24;2
174;2023-04-14;2
175;2023-04-13;1
176;2023-02-09;2
177;2023-08-09;1
178;2023-08-08;2
179;2023-08-06;1
180;2023-08-12;1
181;2023-08-03;1
182;2023-07-30;1
183;2023-07-23;1
184;2023-07-05;1
185;2023-06-25;1
186;2023-06-18;1
187;2023-06-17;1
188;2023-05-30;1
189;2023-05-11;1
190;2023-03-09;1
191;2023-03-06;1
192;2023-02-16;1
193;2023-01-20;1
194;2023-07-16;1
195;2023-07-08;1
196;2023-08-24;2
1 date count
2 0 2023-04-12 5
3 1 2023-01-12 3
4 2 2023-03-28 4
5 3 2023-01-28 3
6 4 2023-01-16 1
7 5 2023-01-13 2
8 6 2023-03-29 6
9 7 2023-04-23 4
10 8 2023-02-23 2
11 9 2023-02-02 3
12 10 2023-01-15 5
13 11 2023-01-14 4
14 12 2023-08-26 4
15 13 2023-05-29 4
16 14 2023-03-25 2
17 15 2023-01-08 3
18 16 2023-01-06 4
19 17 2023-01-05 3
20 18 2023-01-04 3
21 19 2023-06-23 2
22 20 2023-06-19 2
23 21 2023-06-15 5
24 22 2023-05-31 4
25 23 2023-05-28 2
26 24 2023-05-17 3
27 25 2023-05-14 3
28 26 2023-05-08 4
29 27 2023-05-07 4
30 28 2023-05-06 2
31 29 2023-04-19 2
32 30 2023-04-18 3
33 31 2023-04-09 4
34 32 2023-04-01 3
35 33 2023-03-12 3
36 34 2023-03-11 2
37 35 2023-03-05 2
38 36 2023-01-19 1
39 37 2023-04-03 3
40 38 2023-02-07 3
41 39 2023-02-04 4
42 40 2023-02-18 3
43 41 2023-02-17 4
44 42 2023-01-23 2
45 43 2023-01-17 3
46 44 2023-01-09 2
47 45 2023-08-11 2
48 46 2023-07-25 2
49 47 2023-08-07 1
50 48 2023-08-05 2
51 49 2023-07-31 3
52 50 2023-07-19 1
53 51 2023-07-13 1
54 52 2023-07-12 1
55 53 2023-07-04 3
56 54 2023-06-14 1
57 55 2023-06-11 2
58 56 2023-06-01 2
59 57 2023-05-15 1
60 58 2023-05-12 1
61 59 2023-05-27 2
62 60 2023-05-26 3
63 61 2023-05-22 3
64 62 2023-05-20 2
65 63 2023-05-16 2
66 64 2023-05-13 1
67 65 2023-05-09 3
68 66 2023-05-01 4
69 67 2023-04-29 2
70 68 2023-04-10 3
71 69 2023-04-20 3
72 70 2023-04-25 3
73 71 2023-04-11 4
74 72 2023-04-04 4
75 73 2023-03-31 3
76 74 2023-03-23 2
77 75 2023-03-13 1
78 76 2023-03-22 1
79 77 2023-03-16 2
80 78 2023-03-15 1
81 79 2023-03-14 2
82 80 2023-03-20 2
83 81 2023-03-08 1
84 82 2023-02-28 2
85 83 2023-02-27 3
86 84 2023-02-26 3
87 85 2023-02-22 1
88 86 2023-02-20 2
89 87 2023-02-19 6
90 88 2023-02-13 3
91 89 2023-02-10 3
92 90 2023-02-08 3
93 91 2023-02-06 2
94 92 2023-02-05 8
95 93 2023-02-03 2
96 94 2023-02-01 2
97 95 2023-01-30 1
98 96 2023-01-29 3
99 97 2023-01-27 1
100 98 2023-01-22 2
101 99 2023-01-21 1
102 100 2023-01-18 2
103 101 2023-07-06 2
104 102 2023-04-21 4
105 103 2023-02-12 2
106 104 2023-01-24 1
107 105 2023-08-19 1
108 106 2023-06-29 1
109 107 2023-06-07 3
110 108 2023-08-04 2
111 109 2023-08-28 2
112 110 2023-08-25 2
113 111 2023-08-23 1
114 112 2023-04-07 4
115 113 2023-05-05 1
116 114 2023-04-26 2
117 115 2023-03-04 1
118 116 2023-03-03 3
119 117 2023-03-02 1
120 118 2023-04-16 2
121 119 2023-04-08 3
122 120 2023-04-06 2
123 121 2023-01-02 3
124 122 2023-08-01 2
125 123 2023-07-28 1
126 124 2023-07-24 3
127 125 2023-07-21 2
128 126 2023-07-17 1
129 127 2023-07-14 1
130 128 2023-07-03 2
131 129 2023-06-26 2
132 130 2023-06-22 1
133 131 2023-06-12 1
134 132 2023-06-08 1
135 133 2023-06-03 2
136 134 2023-05-23 3
137 135 2023-05-21 2
138 136 2023-05-10 1
139 137 2023-05-02 2
140 138 2023-04-28 1
141 139 2023-04-22 2
142 140 2023-04-17 2
143 141 2023-04-15 3
144 142 2023-04-05 2
145 143 2023-04-02 2
146 144 2023-03-30 2
147 145 2023-03-27 4
148 146 2023-03-26 4
149 147 2023-03-24 2
150 148 2023-03-10 1
151 149 2023-02-25 2
152 150 2023-02-24 2
153 151 2023-02-21 1
154 152 2023-02-15 2
155 153 2023-02-14 1
156 154 2023-01-26 1
157 155 2023-01-11 3
158 156 2023-01-10 2
159 157 2023-01-03 3
160 158 2023-01-01 1
161 159 2023-07-29 5
162 160 2023-09-01 4
163 161 2023-08-31 1
164 162 2023-08-29 2
165 163 2023-08-15 1
166 164 2023-09-02 3
167 165 2023-02-11 2
168 166 2023-05-19 1
169 167 2023-06-06 2
170 168 2023-07-27 1
171 169 2023-06-21 1
172 170 2023-06-05 1
173 171 2023-06-04 1
174 172 2023-05-03 1
175 173 2023-04-24 2
176 174 2023-04-14 2
177 175 2023-04-13 1
178 176 2023-02-09 2
179 177 2023-08-09 1
180 178 2023-08-08 2
181 179 2023-08-06 1
182 180 2023-08-12 1
183 181 2023-08-03 1
184 182 2023-07-30 1
185 183 2023-07-23 1
186 184 2023-07-05 1
187 185 2023-06-25 1
188 186 2023-06-18 1
189 187 2023-06-17 1
190 188 2023-05-30 1
191 189 2023-05-11 1
192 190 2023-03-09 1
193 191 2023-03-06 1
194 192 2023-02-16 1
195 193 2023-01-20 1
196 194 2023-07-16 1
197 195 2023-07-08 1
198 196 2023-08-24 2