mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-02 18:54:59 +00:00
feat: Add --stats option to show code changes and aider contribution statistics
This commit is contained in:
parent
3caab85931
commit
3d4fb68172
3 changed files with 113 additions and 0 deletions
|
@ -553,6 +553,13 @@ def get_parser(default_config_files, git_root):
|
||||||
help="Run tests, fix problems found and then exit",
|
help="Run tests, fix problems found and then exit",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
group.add_argument(
|
||||||
|
"--stats",
|
||||||
|
metavar="REVISIONS",
|
||||||
|
nargs="?",
|
||||||
|
const="",
|
||||||
|
help="Show code changes statistics between revisions",
|
||||||
|
)
|
||||||
|
|
||||||
##########
|
##########
|
||||||
group = parser.add_argument_group("Analytics")
|
group = parser.add_argument_group("Analytics")
|
||||||
|
|
|
@ -1484,6 +1484,107 @@ class Commands:
|
||||||
"Toggle multiline mode (swaps behavior of Enter and Meta+Enter)"
|
"Toggle multiline mode (swaps behavior of Enter and Meta+Enter)"
|
||||||
self.io.toggle_multiline_mode()
|
self.io.toggle_multiline_mode()
|
||||||
|
|
||||||
|
def cmd_stats(self, args):
|
||||||
|
"""Show statistics about code changes and aider's contributions by counting lines of code through git blame.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
/stats Compare against main/master branch
|
||||||
|
/stats <revision> Compare against specific revision
|
||||||
|
/stats rev1..rev2 Compare between two specific revisions
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
/stats Show stats vs main/master branch
|
||||||
|
/stats HEAD~5 Show stats vs 5 commits ago
|
||||||
|
/stats v1.0.0 Show stats vs version 1.0.0
|
||||||
|
/stats main..HEAD Show stats between main and current HEAD
|
||||||
|
|
||||||
|
Lines are attributed to aider when the git author or committer contains "(aider)".
|
||||||
|
Binary files (images, audio, etc.) are excluded from the analysis.
|
||||||
|
"""
|
||||||
|
if not self.coder.repo:
|
||||||
|
self.io.tool_error("No git repository found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the revision range
|
||||||
|
if not args:
|
||||||
|
# Default to comparing against main/master branch
|
||||||
|
for default_branch in ["main", "master"]:
|
||||||
|
try:
|
||||||
|
self.coder.repo.repo.rev_parse(default_branch)
|
||||||
|
args = default_branch
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
if not args:
|
||||||
|
self.io.tool_error("No main or master branch found. Please specify a revision.")
|
||||||
|
return
|
||||||
|
source_revision, target_revision = args.split("..") if ".." in args else (args, "HEAD")
|
||||||
|
|
||||||
|
# Get files changed between revisions
|
||||||
|
diff_files = self.coder.repo.repo.git.diff(
|
||||||
|
"--name-only", f"{source_revision}..{target_revision}"
|
||||||
|
).splitlines()
|
||||||
|
# Filter out media files
|
||||||
|
files = [f for f in diff_files if not any(f.lower().endswith(ext) for ext in (
|
||||||
|
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.ico', '.svg', # images
|
||||||
|
'.mp3', '.wav', '.ogg', '.m4a', '.flac', # audio
|
||||||
|
'.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', # video
|
||||||
|
'.pdf', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', # documents
|
||||||
|
'.zip', '.tar', '.gz', '.7z', '.rar', # archives
|
||||||
|
'.ttf', '.otf', '.woff', '.woff2', '.eot' # fonts
|
||||||
|
))]
|
||||||
|
self.io.tool_output(f"Found {len(files)} non-binary tracked files in the repository.")
|
||||||
|
|
||||||
|
total_lines = 0
|
||||||
|
aider_lines = 0
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
try:
|
||||||
|
# Run git blame for each file
|
||||||
|
blame_output = self.coder.repo.repo.git.blame(
|
||||||
|
f"{source_revision}..{target_revision}", "-M", "-C", "--line-porcelain", "--", file
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse blame output
|
||||||
|
for line in blame_output.split('filename'):
|
||||||
|
total_lines += 1
|
||||||
|
for field in line.split('\n'):
|
||||||
|
# Check author and committer lines for aider attribution
|
||||||
|
author_match = False
|
||||||
|
committer_match = False
|
||||||
|
if field.startswith("author ") or field.startswith("committer "):
|
||||||
|
author_match = "(aider)" in field.lower()
|
||||||
|
committer_match = "(aider)" in field.lower()
|
||||||
|
if author_match or committer_match:
|
||||||
|
aider_lines += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if "no such path" not in str(e).lower():
|
||||||
|
self.io.tool_error(f"Error processing {file}: {e}")
|
||||||
|
|
||||||
|
# Calculate percentages
|
||||||
|
if total_lines > 0:
|
||||||
|
aider_percentage = (aider_lines / total_lines) * 100
|
||||||
|
human_lines = total_lines - aider_lines
|
||||||
|
human_percentage = (human_lines / total_lines) * 100
|
||||||
|
|
||||||
|
# Display results
|
||||||
|
self.io.tool_output("\nCode contribution statistics:")
|
||||||
|
self.io.tool_output(f"Total lines of code: {total_lines:,}")
|
||||||
|
self.io.tool_output(
|
||||||
|
f"Human-written code: {human_lines:,} lines ({human_percentage:.1f}%)"
|
||||||
|
)
|
||||||
|
self.io.tool_output(
|
||||||
|
f"Aider-written code: {aider_lines:,} lines ({aider_percentage:.1f}%)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.io.tool_output("No lines of code found in the repository.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.io.tool_error(f"Error analyzing aider statistics: {e}")
|
||||||
|
|
||||||
|
|
||||||
def cmd_copy(self, args):
|
def cmd_copy(self, args):
|
||||||
"Copy the last assistant message to the clipboard"
|
"Copy the last assistant message to the clipboard"
|
||||||
all_messages = self.coder.done_messages + self.coder.cur_messages
|
all_messages = self.coder.done_messages + self.coder.cur_messages
|
||||||
|
|
|
@ -1142,6 +1142,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
||||||
analytics.event("exit", reason="Completed --message-file")
|
analytics.event("exit", reason="Completed --message-file")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if args.stats:
|
||||||
|
commands.cmd_stats(args.stats)
|
||||||
|
analytics.event("exit", reason="Completed --stats")
|
||||||
|
return
|
||||||
|
|
||||||
if args.exit:
|
if args.exit:
|
||||||
analytics.event("exit", reason="Exit flag set")
|
analytics.event("exit", reason="Exit flag set")
|
||||||
return
|
return
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue