Skip to content

Commit d8bc98b

Browse files
committed
feature, test: added new statistics for commit types
1 parent 456e5c5 commit d8bc98b

2 files changed

Lines changed: 131 additions & 4 deletions

File tree

git_analytics/analyzers/commit_type.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,40 @@ class CommitType(Enum):
2323

2424
@dataclass
2525
class Result(AnalyticsResult):
26-
items: Dict[date, Dict[CommitType, int]]
26+
timeseries: Dict[date, Dict[CommitType, int]]
27+
total_counter: Counter
28+
author_total_counter: Dict[str, Counter]
2729

2830

2931
TYPE_COMMIT_LIST: tuple = tuple(ct.value for ct in CommitType)
3032

3133

3234
def _get_type_list(commit_message: str):
33-
result = [tag for tag in TYPE_COMMIT_LIST if tag in commit_message]
35+
result = [tag for tag in TYPE_COMMIT_LIST if tag in commit_message.lower()]
3436
if result:
3537
return result
36-
return [CommitType.UNKNOWN]
38+
return [CommitType.UNKNOWN.value]
3739

3840

3941
class CommitTypeAnalyzer(CommitAnalyzer):
4042
name = "commit_type"
4143

4244
def __init__(self) -> None:
4345
self._by_date: Dict[date, Counter] = defaultdict(Counter)
46+
self._total_counter: Counter = Counter()
47+
self._author_total_counter: Dict[str, Counter] = defaultdict(Counter)
4448

4549
def process(self, commit: AnalyticsCommit) -> None:
4650
commit_date = commit.committed_datetime.date()
4751
commit_types = _get_type_list(commit.message)
4852
for commit_type in commit_types:
4953
self._by_date[commit_date][commit_type] += 1
54+
self._total_counter[commit_type] += 1
55+
self._author_total_counter[commit.commit_author][commit_type] += 1
5056

5157
def result(self) -> Result:
52-
return Result(items={dt: dict(counter) for dt, counter in self._by_date.items()})
58+
return Result(
59+
timeseries={dt: dict(counter) for dt, counter in self._by_date.items()},
60+
total_counter=self._total_counter,
61+
author_total_counter=self._author_total_counter,
62+
)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import pytest
2+
3+
from datetime import date
4+
5+
from git_analytics.analyzers.commit_type import CommitTypeAnalyzer, _get_type_list
6+
from tests.fakes import FAKE_COMMITS, FakeCommitSource
7+
8+
9+
@pytest.mark.parametrize(
10+
"commit_message, expected_types",
11+
[
12+
("[JIRA-101] feature: implement checkout page", ["feature"]),
13+
("fix: handle null user session | ABC-77", ["fix"]),
14+
("#452 docs: update README install section", ["docs"]),
15+
("style: reformat css grid | #98", ["style"]),
16+
("refactor: split cart service into modules | PROJ-204", ["refactor"]),
17+
("test: add unit tests for price calculator #311", ["test"]),
18+
("chore: bump dependencies to new version | LIB-55", ["chore"]),
19+
("wip: async payment flow proof-of-concept | #1200", ["wip"]),
20+
("merge: branch 'dev' into 'main' | MERGE-15", ["merge"]),
21+
("unknown: tweak timeout value | OPS-33", ["unknown"]),
22+
("FEATURE: add order history page | #909", ["feature"]),
23+
("[TKT-44] FIX: timezone bug in reports", ["fix"]),
24+
("DOCS: add API usage examples #700", ["docs"]),
25+
("STYLE: lint fixes and import sort | LINT-3", ["fix", "style"]),
26+
("REFACTOR: extract email sender interface | CORE-20", ["refactor"]),
27+
("[QA-12] TEST: integration tests for webhook", ["test"]),
28+
("CHORE: rotate staging secrets | #845", ["chore"]),
29+
("WIP: redesign header navigation | UI-61", ["wip"]),
30+
("[REL-2] MERGE: prepare release branch", ["merge"]),
31+
("UNKNOWN: quick experiment with caching | #321", ["unknown"]),
32+
("PROJ-888: feature add dark mode toggle", ["feature"]),
33+
("fix race condition in job runner | TASK-61", ["fix"]),
34+
("docs add contribution guide | #12", ["docs"]),
35+
("style align buttons on mobile | APP-5", ["style"]),
36+
("refactor | simplify auth middleware #670", ["refactor"]),
37+
("test | snapshot tests for components | FE-42", ["test"]),
38+
("chore cleanup old flags #400", ["chore"]),
39+
("wip migrate to postgres 16 | DB-160", ["wip"]),
40+
("merge revert accidental changes | #2004", ["merge"]),
41+
("unknown minor tweak in env loader | ENV-7", ["unknown"]),
42+
("#1500 | feature: SSO login via OAuth", ["feature"]),
43+
("ABC-321 | fix: N+1 query in orders", ["fix"]),
44+
("#77 | docs: ADR for architecture decisions", ["docs"]),
45+
("XYZ-19 | style: apply prettier defaults", ["style"]),
46+
("#901 | refactor: extract price rules engine", ["refactor"]),
47+
("PAY-24 | test: contract tests for provider", ["test"]),
48+
("#66 | chore: update Makefile targets", ["chore"]),
49+
("OPS-88 | wip: k8s manifests for staging", ["wip"]),
50+
("#555 | merge: sync fork with upstream", ["merge"]),
51+
("CI-11 | unknown: temp debug logs", ["unknown"]),
52+
("feature: add CSV export | REPORTS-14", ["feature"]),
53+
("[BUG-909] fix: off-by-one in pagination", ["fix"]),
54+
("docs: add FAQ section | #740", ["docs"]),
55+
("style: remove dead scss variables #213", ["style"]),
56+
("refactor: replace globals with DI | ARCH-3", ["refactor"]),
57+
("test: e2e checkout happy path | #1010", ["test"]),
58+
("chore: rename default branch to main | REPO-1", ["chore"]),
59+
("wip: multi-tenant groundwork | #602", ["wip"]),
60+
("merge hotfix into release/0.2.0 | REL-020", ["fix", "merge"]),
61+
("add small perf tweak | PERF-9 | unknown", ["unknown"]),
62+
("#120 test, docs: update API spec and add unit tests", ["docs", "test"]),
63+
("fix + refactor: cleanup auth flow and resolve session bug | AUTH-45", ["fix", "refactor"]),
64+
("docs & style: polish README and fix markdown formatting | #77", ["fix", "docs", "style"]),
65+
("[UI-22] test/refactor: split components and adjust snapshots", ["refactor", "test"]),
66+
("feature, docs: implement new search and update user guide | #908", ["feature", "docs"]),
67+
("refactor + chore: restructure configs and remove legacy flags | OPS-12", ["refactor", "chore"]),
68+
("test & fix: cover edge cases and resolve race condition | #311", ["fix", "test"]),
69+
("docs/refactor: move ADRs into separate folder | ARCH-33", ["docs", "refactor"]),
70+
("fix, style: adjust lint rules and resolve formatting bugs | LINT-55", ["fix", "style"]),
71+
("wip + test: draft for analytics module with early unit tests | #700", ["test", "wip"]),
72+
],
73+
)
74+
def test_commit_type_get_type_list(commit_message, expected_types):
75+
assert _get_type_list(commit_message) == expected_types
76+
77+
78+
def test_commit_type_timeseries():
79+
source = FakeCommitSource(FAKE_COMMITS)
80+
81+
analyzer = CommitTypeAnalyzer()
82+
for commit in source.iter_commits():
83+
analyzer.process(commit)
84+
result = analyzer.result()
85+
86+
assert len(result.timeseries) == 25
87+
assert result.timeseries[date(2025, 5, 2)] == {"unknown": 1, "style": 1, "test": 1}
88+
assert result.timeseries[date(2025, 4, 2)] == {"unknown": 2, "fix": 1}
89+
assert result.timeseries[date(2025, 2, 2)] == {"chore": 1, "refactor": 1, "fix": 1}
90+
assert result.timeseries[date(2025, 1, 2)] == {"unknown": 2}
91+
assert result.timeseries[date(2025, 1, 1)] == {"unknown": 1}
92+
assert result.timeseries[date(2024, 9, 2)] == {"unknown": 4, "fix": 1, "refactor": 1}
93+
assert result.timeseries[date(2024, 1, 1)] == {"unknown": 4}
94+
95+
96+
def test_commit_type_total_counter():
97+
source = FakeCommitSource(FAKE_COMMITS)
98+
99+
analyzer = CommitTypeAnalyzer()
100+
for commit in source.iter_commits():
101+
analyzer.process(commit)
102+
result = analyzer.result()
103+
104+
assert sum(result.total_counter.values()) == 40
105+
assert result.total_counter == {"unknown": 23, "refactor": 5, "fix": 5, "chore": 3, "style": 2, "test": 2}
106+
107+
108+
def test_commit_type_author_total_counter():
109+
source = FakeCommitSource(FAKE_COMMITS)
110+
111+
analyzer = CommitTypeAnalyzer()
112+
for commit in source.iter_commits():
113+
analyzer.process(commit)
114+
result = analyzer.result()
115+
116+
assert result.author_total_counter.keys() == {"Alice", "Bob", "Carol", "Dave", "Oscar"}
117+
assert sum(sum(v.values()) for v in result.author_total_counter.values()) == 40

0 commit comments

Comments
 (0)