Skip to content

Commit 846d9b1

Browse files
committed
Major feature increase
* Implemented the following features: - My Daily Stats - Branch View - All Branches - All Contributors * Adjusted README.md, fixed texts where necessary, minor cleanup
1 parent 9f0c0b2 commit 846d9b1

File tree

6 files changed

+260
-60
lines changed

6 files changed

+260
-60
lines changed

README.md

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,22 @@ exists essentially 1:1 functionality between the two projects. Stubbed means
5151
`git-py-stats` has the feature available, but it might not match the original
5252
project's version. Not Yet Implemented means it does not exist yet:
5353

54-
| Feature | Status | Description |
55-
|-------------------------------------------------|-------------------------|---------------------------------------------------------|
56-
| **UI** | Completed ✔️ | General UI when launching interactive mode |
57-
| **Interactive Mode** | Completed ✔️ | Enables interactive sessions for user inputs. |
58-
| **Non-interactive Mode** | Completed ✔️ | Allows usage without interactive prompts. |
59-
| **Contribution Stats** | Completed ✔️ | Displays overall contribution statistics. |
60-
| **Contribution Stats by Author** | Completed ✔️ | Shows contribution stats by individual authors. |
61-
| **Changelogs** | Completed ✔️ | Lists commit logs over 10 last days of commits. |
62-
| **Changelogs by Author** | Completed ✔️ | Filters changelogs based on the author. |
63-
| **Code Reviewers** | Completed ✔️ | Identifies code reviewers based on contribution. |
64-
| **My Daily Stats** | Stubbed 🛠️ | Tracks daily statistics customized for the user. |
65-
| **Output Daily Stats by Branch in CSV** | Stubbed 🛠️ | Exports daily branch stats in CSV format. |
66-
| **Save Git Log Output in JSON Format** | Stubbed 🛠️ | Stores git logs in JSON. |
67-
| **Branch Tree View** | Stubbed 🛠️ | Visual representation of the branch hierarchy. |
68-
| **All Branches (Sorted by Most Recent Commit)** | Stubbed 🛠️ | Lists all branches ordered by latest commit date. |
69-
| **All Contributors (Sorted by Name)** | Stubbed 🛠️ | Displays all contributors sorted alphabetically. |
54+
| Feature | Status | Description |
55+
|-------------------------------------------------|------------------------|---------------------------------------------------------|
56+
| **UI** | Completed ✔️ | General UI when launching interactive mode |
57+
| **Interactive Mode** | Completed ✔️ | Enables interactive sessions for user inputs. |
58+
| **Non-interactive Mode** | Completed ✔️ | Allows usage without interactive prompts. |
59+
| **Contribution Stats** | Completed ✔️ | Displays overall contribution statistics. |
60+
| **Contribution Stats by Author** | Completed ✔️ | Shows contribution stats by individual authors. |
61+
| **Changelogs** | Completed ✔️ | Lists commit logs over 10 last days of commits. |
62+
| **Changelogs by Author** | Completed ✔️ | Filters changelogs based on the author. |
63+
| **Code Reviewers** | Completed ✔️ | Identifies code reviewers based on contribution. |
64+
| **My Daily Stats** | Completed ✔️ | Tracks daily statistics customized for the user. |
65+
| **Output Daily Stats by Branch in CSV** | Completed ✔️ | Exports daily branch stats in CSV format. |
66+
| **Save Git Log Output in JSON Format** | Completed ✔️ | Stores git logs in JSON. |
67+
| **Branch Tree View** | Completed ✔️ | Visual representation of the branch hierarchy. |
68+
| **All Branches (Sorted by Most Recent Commit)** | Completed ✔️ | Lists all branches ordered by latest commit date. |
69+
| **All Contributors (Sorted by Name)** | Completed ✔️ | Displays all contributors sorted alphabetically. |
7070
| **New Contributors (Sorted by Email)** | Stubbed 🛠️ | Lists new contributors sorted by their email addresses. |
7171
| **Git Commits per Author** | Stubbed 🛠️ | Counts commits made by each author. |
7272
| **Git Commits per Date** | Stubbed 🛠️ | Counts commits based on the date. |
@@ -89,6 +89,22 @@ project's version. Not Yet Implemented means it does not exist yet:
8989
| **macOS Package install** | Not Yet Implemented ❌ | Allows macOS users to install via brew. |
9090
| **Docker Development Image** | Not Yet Implemented ❌ | Provides a Docker development image for CI/CD. |
9191

92+
## Changes from Original
93+
94+
While this project aims to be feature-complete and 1:1 with the `git-quick-stats`,
95+
there may be instances where this version differs from the base project by design.
96+
The following is a list of differences that this project will maintain compared to
97+
the parent project:
98+
99+
* Author and branch names can be passed via cmdline without interaction by
100+
the user. This means you can now do `git-py-stats -L "John Doe"` instead of
101+
being prompted to enter the name after executing the non-interactive cmd.
102+
* CSV output is now saved to a file instead of printing out to the terminal.
103+
This file will be saved to wherever the process was executed. The name will
104+
be `git_daily_stats.csv`
105+
* JSON output is saved to a file wherever the process was executed instead of
106+
one that is provided by the user. The name will be `git_log.json`
107+
92108
## Requirements
93109

94110
- **Python 3.6+**: Git Py Stats requires Python 3.6 or higher installed on your system.

git_py_stats/generate_cmds.py

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from datetime import datetime, timedelta
88
import json
99
import os
10+
import re
1011
from typing import Optional, Dict, Any, List
1112
from git_py_stats.git_operations import run_git_command
1213

@@ -230,18 +231,86 @@ def my_daily_status() -> None:
230231
"""
231232
Displays the user's commits from the last day.
232233
"""
233-
234-
try:
235-
user = os.getlogin()
236-
except OSError:
237-
user = os.environ.get('USER', 'unknown')
238-
cmd = ['git', 'log', '--author', user, '--since=1.day', '--oneline']
239-
output = run_git_command(cmd)
240-
if output:
241-
print("My daily status:")
242-
print(output)
234+
235+
print('My daily status:')
236+
237+
# Equivalent Bash Command:
238+
# git diff --shortstat '@{0 day ago}' | sort -nr | tr ',' '\n' \
239+
# | LC_ALL=C awk '{ args[NR] = $0; } END { for (i = 1; i <= NR; ++i) \
240+
# { printf "\t%s\n", args[i] } }'
241+
242+
# Mimic 'git diff --shortstat "@{0 day ago}"'
243+
diff_cmd = ['git', 'diff', '--shortstat', '@{0 day ago}']
244+
diff_output = run_git_command(diff_cmd)
245+
246+
# Process diff output:
247+
if diff_output:
248+
# Replace commas with newlines
249+
diff_lines = [line.strip() for line in diff_output.split(',')]
250+
251+
# Print each line prefixed with a tab
252+
for line in diff_lines:
253+
print(f"\t{line}")
254+
else:
255+
# If no diff stats are available, indicate no changes
256+
print("\tNo changes in the last day.")
257+
258+
# Count Commits
259+
# Equivalent Bash Command:
260+
# git -c log.showSignature=false log --use-mailmap \
261+
# --author="$(git config user.name)" $_merges \
262+
# --since=$(date "+%Y-%m-%dT00:00:00") \
263+
# --until=$(date "+%Y-%m-%dT23:59:59") --reverse $_log_options \
264+
# | grep -cE "commit [a-f0-9]{40}"
265+
266+
# Get the user's name
267+
# Lets also handle the case if the user's name is not set correctly
268+
git_user = run_git_command(['git', 'config', 'user.name'])
269+
if not git_user:
270+
git_user = 'unknown'
271+
272+
# Define global variables with default values
273+
# TODO: Refactor these to be configurable
274+
merges_option = '--no-merges'
275+
log_options = ''
276+
277+
# Get today's date in the format 'YYYY-MM-DD' to match the original cmd
278+
today = datetime.now().strftime('%Y-%m-%d')
279+
since = f"{today}T00:00:00"
280+
until = f"{today}T23:59:59"
281+
282+
# Build the final git log command
283+
log_cmd = [
284+
'git', '-c', 'log.showSignature=false', 'log',
285+
'--use-mailmap',
286+
'--author', git_user,
287+
merges_option,
288+
'--since', since,
289+
'--until', until,
290+
'--reverse'
291+
]
292+
293+
# Added to handle log options in the future
294+
if log_options:
295+
log_cmd.extend(log_options.split())
296+
297+
# Execute the git log command
298+
log_output = run_git_command(log_cmd)
299+
300+
# Bash version uses grep to count lines matching the hash pattern
301+
# "commit [a-f0-9]{40}"
302+
# We can use re to mimic this in Python
303+
# TODO: Revisit this. We might be able to do --pretty=format:%H to avoid
304+
# having to use a regex to handle this portion. This could be
305+
# an improvement to feed back to the original project
306+
if log_output:
307+
commit_pattern = re.compile(r'^commit [a-f0-9]{40}$', re.MULTILINE)
308+
commit_count = len(commit_pattern.findall(log_output))
243309
else:
244-
print('No commits in the last day.')
310+
commit_count = 0
311+
312+
# Print the commit count, prefixed with a tab
313+
print(f"\t{commit_count} commits")
245314

246315

247316
def output_daily_stats_csv() -> None:

git_py_stats/interactive_mode.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ def handle_interactive_mode() -> None:
1515
'5': generate_cmds.my_daily_status,
1616
'6': generate_cmds.output_daily_stats_csv,
1717
'7': generate_cmds.save_git_log_output_json,
18-
'8': list_cmds.branch_tree_view,
19-
'9': list_cmds.all_branches_sorted,
20-
'10': list_cmds.all_contributors,
18+
'8': list_cmds.branch_tree,
19+
'9': list_cmds.branches_by_date,
20+
'10': list_cmds.contributors,
2121
'11': list_cmds.new_contributors,
2222
'12': list_cmds.git_commits_per_author,
2323
'13': list_cmds.git_commits_per_date,

git_py_stats/list_cmds.py

Lines changed: 138 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,175 @@
33
"""
44

55
import collections
6-
import datetime
6+
from datetime import datetime, timezone
77
from typing import Dict, Optional
88

99
from git_py_stats.git_operations import run_git_command
1010

1111

12-
def branch_tree_view() -> None:
12+
def branch_tree() -> None:
1313
"""
1414
Displays a visual graph of recent commits across all branches.
1515
"""
16+
17+
# Since can be hardcoded for now. It'll be based on the earliest commit
18+
# in the repo
19+
earliest_commit_date = run_git_command(['git', 'log', '--reverse', '--format=%ad'])
20+
if earliest_commit_date:
21+
# Take the first line as the earliest commit date
22+
first_commit_date = earliest_commit_date.split('\n')[0]
23+
since = f"--since='{first_commit_date}'"
24+
else:
25+
# If no commits, set since to an empty string
26+
since = ''
27+
28+
# Until will be current system's date and time
29+
now = datetime.now(timezone.utc).astimezone()
30+
until_formatted = now.strftime('%a, %d %b %Y %H:%M:%S %Z')
31+
until = f"--until='{until_formatted}'"
1632

17-
cmd = ['git', 'log', '--graph', '--oneline', '--all', '-n', '10']
33+
# Empty log options for now
34+
log_options = ''
35+
36+
# Hardcoded limit
37+
limit=10
38+
39+
# Format string for git --format so it gets interpreted correctly
40+
format_str = "--format=--+ Commit: %h%n | Date: %aD (%ar)%n | Message: %s %d%n + Author: %aN %n"
41+
42+
# Perform final git command
43+
cmd = [
44+
'git', '-c', 'log.showSignature=false', 'log',
45+
'--use-mailmap',
46+
'--graph',
47+
'--abbrev-commit',
48+
since,
49+
until,
50+
'--decorate',
51+
format_str,
52+
'--all'
53+
]
54+
1855
output = run_git_command(cmd)
56+
57+
# handle the head -n $((_limit*5)) portion
1958
if output:
20-
print("Branch tree view (last 10 commits):")
21-
print(output)
59+
print('Branching tree view:\n')
60+
lines = output.split('\n')
61+
total_lines = limit * 5
62+
limited_lines = lines[:total_lines]
63+
64+
for line in limited_lines:
65+
print(f"{line}")
66+
67+
commit_count = sum(1 for line in limited_lines if line.strip().startswith('--+ Commit:'))
2268
else:
2369
print('No data available.')
2470

2571

26-
def all_branches_sorted() -> None:
72+
def branches_by_date() -> None:
2773
"""
2874
Lists branches sorted by the latest commit date.
2975
"""
30-
31-
cmd = [
32-
'git', 'for-each-ref', '--sort=-committerdate', '--format', '%(refname:short)',
33-
'refs/heads/'
34-
]
76+
77+
# Original command:
78+
# git for-each-ref --sort=committerdate refs/heads/ \
79+
# --format='[%(authordate:relative)] %(authorname) %(refname:short)' | cat -n
80+
# TODO: Wouldn't git log --pretty=format:'%ad' --date=short be better here?
81+
# Then we could pipe it through sort, uniq -c, sort -nr, etc.
82+
# Possibly feed back into the parent project
83+
format_str = "[%(authordate:relative)] %(authorname) %(refname:short)"
84+
cmd = ['git', 'for-each-ref', '--sort=committerdate', 'refs/heads/', f'--format={format_str}']
85+
3586
output = run_git_command(cmd)
3687
if output:
37-
print("All branches sorted by most recent commits:")
38-
print(output)
88+
# Split the output into lines
89+
lines = output.split('\n')
90+
91+
# Number the lines similar to 'cat -n'
92+
numbered_lines = [f"{idx + 1} {line}" for idx, line in enumerate(lines)]
93+
94+
# Output numbered lines
95+
print('All branches (sorted by most recent commit):\n')
96+
for line in numbered_lines:
97+
print(f'\t{line}')
3998
else:
40-
print('No branches available.')
99+
print('No commits found.')
41100

42101

43-
def all_contributors() -> None:
102+
def contributors() -> None:
44103
"""
45104
Lists all contributors alphabetically.
46105
"""
47-
48-
cmd = ['git', 'shortlog', '-sn', '--all']
106+
107+
# Hardcode variables
108+
# TODO: Make these configurable by the user
109+
earliest_commit_date = run_git_command(['git', 'log', '--reverse', '--format=%ad'])
110+
if earliest_commit_date:
111+
# Take the first line as the earliest commit date
112+
first_commit_date = earliest_commit_date.split('\n')[0]
113+
since = f"--since='{first_commit_date}'"
114+
else:
115+
# If no commits, set since to an empty string
116+
since = ''
117+
118+
119+
now = datetime.now(timezone.utc).astimezone()
120+
until_formatted = now.strftime('%a, %d %b %Y %H:%M:%S %Z')
121+
until = f"--until={until_formatted}"
122+
123+
pathspec = "" # No pathspec filtering
124+
125+
merges = "--no-merges"
126+
limit = 50
127+
log_options = ""
128+
129+
# Original command
130+
# git -c log.showSignature=false log --use-mailmap $_merges "$_since" "$_until" \
131+
# --format='%aN' $_log_options $_pathspec | sort -u | cat -n
132+
cmd = [
133+
'git',
134+
'-c', 'log.showSignature=false',
135+
'log',
136+
'--use-mailmap',
137+
merges,
138+
since,
139+
until,
140+
'--format=%aN',
141+
log_options
142+
]
143+
144+
# Append pathspec only if it's not empty. Currently hardcoded
145+
if pathspec:
146+
cmd.append(pathspec)
147+
148+
# Remove any empty strings from the command to prevent Git misinterpretation
149+
# Breaks without this
150+
cmd = [arg for arg in cmd if arg]
151+
152+
# Execute the Git command
49153
output = run_git_command(cmd)
50154
if output:
51-
print("All contributors:")
52-
contributors = [line.strip() for line in output.split('\n')]
53-
contributors.sort(key=lambda x: x.split('\t')[1])
54-
for contributor in contributors:
55-
print(contributor)
155+
print('All contributors (sorted by name):\n')
156+
# Split the output into individual author names
157+
authors = [line.strip() for line in output.split('\n') if line.strip()]
158+
159+
# Remove duplicates by converting the list to a set
160+
unique_authors = set(authors)
161+
162+
# Sort the unique authors alphabetically
163+
sorted_authors = sorted(unique_authors)
164+
165+
# Apply the limit
166+
limited_authors = sorted_authors[:limit]
167+
168+
# Number the authors similar to 'cat -n' and print
169+
numbered_authors = [f"{idx + 1} {author}" for idx, author in enumerate(limited_authors)]
170+
for author in numbered_authors:
171+
print(f'\t{author}')
56172
else:
57173
print('No contributors found.')
58174

59-
60175
def new_contributors() -> None:
61176
"""
62177
Lists contributors sorted by email.

git_py_stats/non_interactive_mode.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def handle_non_interactive_mode(args) -> None:
2020
'my_daily_stats': generate_cmds.my_daily_status,
2121
'csv_output_by_branch': generate_cmds.output_daily_stats_csv,
2222
'json_output': generate_cmds.save_git_log_output_json,
23-
'branch_tree': list_cmds.branch_tree_view,
24-
'branches_by_date': list_cmds.all_branches_sorted,
25-
'contributors': list_cmds.all_contributors,
23+
'branch_tree': list_cmds.branch_tree,
24+
'branches_by_date': list_cmds.branches_by_date,
25+
'contributors': list_cmds.contributors,
2626
'new_contributors': list_cmds.new_contributors,
2727
'commits_per_author': list_cmds.git_commits_per_author,
2828
'commits_per_day': list_cmds.git_commits_per_date,

0 commit comments

Comments
 (0)