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 .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 pandas
numpy 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 Generate a CSV file with daily stats commit count from a set of repositories
""" """
from git import Repo
import datetime import datetime
import os
import logging
from collections import defaultdict
from typing import Iterable, Optional
from git import Repo
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""" """Count how many commit was performed for each days in the year"""
data = {} if author_name is not None:
for commit in repo.iter_commits(all=True): author_name = author_name.lower()
timestamp = commit.authored_date other_author_names: set = set()
date = datetime.date.fromtimestamp(timestamp) data = defaultdict(int)
if int(date.year) != year: for commit in wrapper(repo.iter_commits(all=True), repo):
continue if author_name is not None:
if date not in data: commit_author: str = commit.author.name
data[date] = 0 if commit_author.lower() != author_name:
data[date] += 1 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 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