Skip to content

Commit 2994b2a

Browse files
authored
Merge pull request #9 from git-quick-stats/2-add-activity-calendar-by-author
activity calendar by author
2 parents e0e9d1f + 276dd53 commit 2994b2a

File tree

9 files changed

+141
-0
lines changed

9 files changed

+141
-0
lines changed

git_py_stats/arg_parser.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ def parse_arguments(argv: Optional[List[str]] = None) -> Namespace:
166166
help="Displays a list of commits per timezone by author",
167167
)
168168

169+
# Calendar Options
170+
parser.add_argument(
171+
"-k",
172+
"--commits-calendar-by-author",
173+
metavar='"AUTHOR NAME"',
174+
type=str,
175+
help="Show a calendar of commits by author",
176+
)
177+
169178
# Suggest Options
170179
parser.add_argument(
171180
"-r",

git_py_stats/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def get_config() -> Dict[str, Union[str, int]]:
3939
_GIT_LOG_OPTIONS (str): Additional git log options. Default is empty.
4040
_MENU_THEME (str): Toggles between the default theme and legacy theme.
4141
- 'legacy' to set the legacy theme
42+
- 'none' to disable the menu theme
4243
- Empty to set the default theme
4344
4445
Args:

git_py_stats/generate_cmds.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import json
88
from typing import Optional, Dict, Any, List, Union
99
from datetime import datetime, timedelta
10+
from collections import defaultdict
1011

1112
from git_py_stats.git_operations import run_git_command
1213

@@ -305,6 +306,94 @@ def changelogs(config: Dict[str, Union[str, int]], author: Optional[str] = None)
305306
next_date = date # Update next_date for the next iteration
306307

307308

309+
def commits_calendar_by_author(config: Dict[str, Union[str, int]], author: Optional[str]) -> None:
310+
"""
311+
Displays a calendar of commits by author
312+
313+
Args:
314+
config: Dict[str, Union[str, int]]: Config dictionary holding env vars.
315+
author: Optional[str]: The author's name to filter commits by.
316+
317+
Returns:
318+
None
319+
"""
320+
321+
# Initialize variables similar to the Bash version
322+
author_option = f"--author={author}" if author else ""
323+
324+
# Grab the config options from our config.py.
325+
# config.py should give fallbacks for these, but for sanity,
326+
# lets also provide some defaults just in case.
327+
merges = config.get("merges", "--no-merges")
328+
since = config.get("since", "")
329+
until = config.get("until", "")
330+
log_options = config.get("log_options", "")
331+
pathspec = config.get("pathspec", "")
332+
333+
# Original git command:
334+
# git -c log.showSignature=false log --use-mailmap $_merges \
335+
# --date=iso --author="$author" "$_since" "$_until" $_log_options \
336+
# --pretty='%ad' $_pathspec
337+
cmd = [
338+
"git",
339+
"-c",
340+
"log.showSignature=false",
341+
"log",
342+
"--use-mailmap",
343+
"--date=iso",
344+
f"--author={author}",
345+
"--pretty=%ad",
346+
]
347+
348+
if author_option:
349+
cmd.append(author_option)
350+
351+
cmd.extend([since, until, log_options, merges, pathspec])
352+
353+
# Remove any empty space from the cmd
354+
cmd = [arg for arg in cmd if arg]
355+
356+
print(f"Commit Activity Calendar for '{author}'")
357+
358+
# Get commit dates
359+
output = run_git_command(cmd)
360+
if not output:
361+
print("No commits found.")
362+
return
363+
364+
print("\n Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")
365+
366+
count = defaultdict(lambda: defaultdict(int))
367+
for line in output.strip().split("\n"):
368+
try:
369+
date_str = line.strip().split(" ")[0]
370+
date_obj = datetime.strptime(date_str, "%Y-%m-%d")
371+
weekday = date_obj.isoweekday() # 1=Mon, ..., 7=Sun
372+
month = date_obj.month
373+
count[weekday][month] += 1
374+
except ValueError:
375+
continue
376+
377+
# Print the calendar
378+
weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
379+
for d in range(1, 8):
380+
row = f"{weekdays[d-1]:<5} "
381+
for m in range(1, 13):
382+
c = count[d][m]
383+
if c == 0:
384+
out = "..."
385+
elif c <= 9:
386+
out = "░░░"
387+
elif c <= 19:
388+
out = "▒▒▒"
389+
else:
390+
out = "▓▓▓"
391+
row += out + (" " if m < 12 else "")
392+
print(row)
393+
394+
print("\nLegend: ... = 0 ░░░ = 1–9 ▒▒▒ = 10–19 ▓▓▓ = 20+ commits")
395+
396+
308397
def my_daily_status(config: Dict[str, Union[str, int]]) -> None:
309398
"""
310399
Displays the user's commits from the last day.

git_py_stats/interactive_mode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def handle_interactive_mode(config: Dict[str, Union[str, int]]) -> None:
4444
"20": lambda: list_cmds.git_commits_per_timezone(config),
4545
"21": lambda: list_cmds.git_commits_per_timezone(config, input("Enter author name: ")),
4646
"22": lambda: suggest_cmds.suggest_reviewers(config),
47+
"23": lambda: generate_cmds.commits_calendar_by_author(
48+
config, input("Enter author name: ")
49+
),
4750
}
4851

4952
while True:

git_py_stats/menu.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def interactive_menu(config: Dict[str, Union[str, int]]) -> str:
7272
print(f"{NUMS} 21){TEXT} Git commits per timezone by author")
7373
print(f"\n{TITLES} Suggest:{NORMAL}")
7474
print(f"{NUMS} 22){TEXT} Code reviewers (based on git history)")
75+
print(f"\n{TITLES} Calendar:{NORMAL}")
76+
print(f"{NUMS} 23){TEXT} Activity calendar by author")
7577
print(f"\n{HELP_TXT}Please enter a menu option or {EXIT_TXT}press Enter to exit.{NORMAL}")
7678

7779
choice = input(f"{TEXT}> {NORMAL}")

git_py_stats/non_interactive_mode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ def handle_non_interactive_mode(args: Namespace, config: Dict[str, Union[str, in
5050
config, args.commits_by_author_by_timezone
5151
),
5252
"suggest_reviewers": lambda: suggest_cmds.suggest_reviewers(config),
53+
"commits_calendar_by_author": lambda: generate_cmds.commits_calendar_by_author(
54+
config, args.commits_calendar_by_author
55+
),
5356
}
5457

5558
# Call the appropriate function based on the command-line argument

git_py_stats/tests/test_generate_cmds.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,27 @@ def test_changelogs_with_author(self, mock_print, mock_run_git_command):
105105

106106
self.assertTrue(mock_print.called)
107107

108+
@patch("git_py_stats.generate_cmds.run_git_command")
109+
@patch("builtins.print")
110+
def test_commits_calendar_by_author(self, mock_print, mock_run_git_command):
111+
"""
112+
Test commits_calendar_by_author function with an author specified.
113+
"""
114+
# Mock git command outputs
115+
mock_run_git_command.side_effect = [
116+
"", # git diff output (no changes)
117+
"John Doe", # git config user.name
118+
"", # git log output (no commits)
119+
]
120+
121+
generate_cmds.commits_calendar_by_author(self.mock_config, author="John Doe")
122+
123+
# Verify that the author option was included in the command
124+
called_cmd = mock_run_git_command.call_args_list[0][0][0]
125+
self.assertIn("--author=John Doe", called_cmd)
126+
127+
self.assertTrue(mock_print.called)
128+
108129
@patch("git_py_stats.generate_cmds.run_git_command")
109130
@patch("builtins.print")
110131
def test_my_daily_status(self, mock_print, mock_run_git_command):

git_py_stats/tests/test_non_interactive_mode.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def setUp(self):
4646
"commits_by_timezone": False,
4747
"commits_by_author_by_timezone": None,
4848
"suggest_reviewers": False,
49+
"commits_calendar_by_author": None,
4950
}
5051

5152
@patch("git_py_stats.non_interactive_mode.generate_cmds.detailed_git_stats")
@@ -104,6 +105,14 @@ def test_json_output(self, mock_save_json):
104105
non_interactive_mode.handle_non_interactive_mode(args, self.mock_config)
105106
mock_save_json.assert_called_once_with(self.mock_config)
106107

108+
@patch("git_py_stats.non_interactive_mode.generate_cmds.commits_calendar_by_author")
109+
def test_commits_calendar_by_author(self, mock_commits_calendar_by_author):
110+
args_dict = self.all_args.copy()
111+
args_dict["commits_calendar_by_author"] = "John Doe"
112+
args = Namespace(**args_dict)
113+
non_interactive_mode.handle_non_interactive_mode(args, self.mock_config)
114+
mock_commits_calendar_by_author.assert_called_once_with(self.mock_config, "John Doe")
115+
107116
@patch("git_py_stats.non_interactive_mode.list_cmds.branch_tree")
108117
def test_branch_tree(self, mock_branch_tree):
109118
args_dict = self.all_args.copy()

man/git-py-stats.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ Display a list of commits by timezone by author.
9898
.B \-r, \--suggest-reviewers
9999
Suggest code reviewers based on contribution history.
100100

101+
.TP
102+
.B \-k, \--commits-calendar-by-author "AUTHOR NAME"
103+
Display a calendar of commits by author.
104+
101105
.TP
102106
.B \-h, \--help
103107
Show this help message and exit.

0 commit comments

Comments
 (0)