[feature:track_progress] Implemented main progress tracking system [feature:git_integration] Added Git history analysis with commit tag parsing [feature:template_engine] Created flexible template engine for documentation generation [feature:feature_tracking] Implemented feature status tracking and updates [feature:changelog_generation] Added automatic changelog generation from commits [feature:code_stats] Added code statistics and metrics collection [feature:roadmap_generation] Implemented roadmap generation [new-feature:launcher_scripts:Created cross-platform launcher scripts for easy execution] [new-feature:executable_build:Added Nuitka-based executable build system] [new-feature:project_installation:Created setup script for easy installation to projects] [status:track_progress_and_feature_statuses] Completed automatic status document generation [status:git_integration] Completed parsing Git logs for status updates [status:doc_generation] Completed auto-generation of status docs and changelog [changelog:Added comprehensive progress tracking system] [changelog:Added feature status tracking with Git integration] [changelog:Added customizable template system for documentation] [changelog:Added automatic changelog generation from commit messages] [changelog:Added code statistics and metrics collection] [changelog:Added cross-platform launcher scripts] [changelog:Added executable build system using Nuitka] [changelog:Added project installation script] [milestone:v1.0] Completed initial release of progress tracking system
239 lines
8.0 KiB
Python
239 lines
8.0 KiB
Python
import os
|
|
import re
|
|
import subprocess
|
|
from datetime import datetime
|
|
from typing import Dict, Any, List, Optional, Tuple
|
|
|
|
|
|
class GitAnalyzer:
|
|
"""Analyze Git repository for project status information."""
|
|
|
|
def __init__(self, repo_path: str = "."):
|
|
"""Initialize with repository path."""
|
|
self.repo_path = repo_path
|
|
|
|
def _run_git_command(self, command: List[str]) -> str:
|
|
"""Run a Git command and return the output."""
|
|
try:
|
|
result = subprocess.run(
|
|
["git"] + command,
|
|
cwd=self.repo_path,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
return result.stdout.strip()
|
|
except subprocess.CalledProcessError:
|
|
return ""
|
|
|
|
def get_repo_info(self) -> Dict[str, Any]:
|
|
"""Get basic repository information."""
|
|
info = {}
|
|
|
|
# Get remote URL
|
|
remote_url = self._run_git_command(["remote", "get-url", "origin"])
|
|
info["remote_url"] = remote_url
|
|
|
|
# Get current branch
|
|
branch = self._run_git_command(["branch", "--show-current"])
|
|
info["branch"] = branch
|
|
|
|
# Get last commit
|
|
last_commit = self._run_git_command(
|
|
["log", "-1", "--pretty=format:%h|%s|%ad|%an", "--date=short"]
|
|
)
|
|
if last_commit:
|
|
parts = last_commit.split("|")
|
|
if len(parts) >= 4:
|
|
info["last_commit"] = {
|
|
"hash": parts[0],
|
|
"message": parts[1],
|
|
"date": parts[2],
|
|
"author": parts[3],
|
|
}
|
|
|
|
return info
|
|
|
|
def analyze_commits(
|
|
self, limit: int = 100, untagged_limit: int = 50
|
|
) -> Dict[str, Any]:
|
|
"""Analyze Git commits for feature updates, changelog entries, etc."""
|
|
result = {
|
|
"feature_updates": {},
|
|
"new_features": {},
|
|
"changelog_entries": [],
|
|
"fixes": [],
|
|
"issues": [],
|
|
"untagged_commits": [],
|
|
"milestones": {},
|
|
"roadmap_items": {},
|
|
}
|
|
|
|
# Regular expressions for parsing commit messages
|
|
status_re = re.compile(r"\[status:(\w+)\]")
|
|
feature_re = re.compile(r"\[feature:(\w+)\]")
|
|
new_feature_re = re.compile(r"\[new-feature:(\w+):(.+?)\]")
|
|
changelog_re = re.compile(r"\[changelog:(.+?)\]")
|
|
fix_re = re.compile(r"\[fix:(.+?)\]")
|
|
issue_re = re.compile(r"\[issue:(.+?)\]")
|
|
milestone_re = re.compile(r"\[milestone:(\w+)\]")
|
|
roadmap_re = re.compile(r"\[roadmap:(\w+):(.+?)\]")
|
|
|
|
# Get Git log
|
|
git_log = self._run_git_command(
|
|
[
|
|
"log",
|
|
f"-n{limit}",
|
|
"--pretty=format:%h|%s|%ad|%an",
|
|
"--date=short",
|
|
]
|
|
)
|
|
|
|
untagged_count = 0
|
|
|
|
# Parse commits
|
|
for line in git_log.splitlines():
|
|
parts = line.split("|")
|
|
if len(parts) < 4:
|
|
continue
|
|
|
|
commit_hash, subject, date, author = parts
|
|
|
|
# Check for tags
|
|
status_match = status_re.search(subject)
|
|
feature_match = feature_re.search(subject)
|
|
new_feature_match = new_feature_re.search(subject)
|
|
changelog_match = changelog_re.search(subject)
|
|
fix_match = fix_re.search(subject)
|
|
issue_match = issue_re.search(subject)
|
|
milestone_match = milestone_re.search(subject)
|
|
roadmap_match = roadmap_re.search(subject)
|
|
|
|
# Process status updates
|
|
if status_match:
|
|
status_key = status_match.group(1)
|
|
result["feature_updates"][status_key] = {
|
|
"status": "completed",
|
|
"date": date,
|
|
"author": author,
|
|
}
|
|
|
|
# Process feature updates
|
|
if feature_match:
|
|
feature_key = feature_match.group(1)
|
|
result["feature_updates"][feature_key] = {
|
|
"status": "completed",
|
|
"date": date,
|
|
"author": author,
|
|
}
|
|
|
|
# Process new features
|
|
if new_feature_match:
|
|
feature_key = new_feature_match.group(1)
|
|
feature_desc = new_feature_match.group(2)
|
|
result["new_features"][feature_key] = {
|
|
"status": "planned",
|
|
"description": feature_desc,
|
|
"date": date,
|
|
"author": author,
|
|
}
|
|
|
|
# Process changelog entries
|
|
if changelog_match:
|
|
result["changelog_entries"].append(
|
|
{
|
|
"message": changelog_match.group(1),
|
|
"date": date,
|
|
"commit": commit_hash,
|
|
"author": author,
|
|
}
|
|
)
|
|
|
|
# Process fixes
|
|
if fix_match:
|
|
result["fixes"].append(
|
|
{
|
|
"message": fix_match.group(1),
|
|
"date": date,
|
|
"commit": commit_hash,
|
|
"author": author,
|
|
}
|
|
)
|
|
|
|
# Process issues
|
|
if issue_match:
|
|
result["issues"].append(
|
|
{
|
|
"message": issue_match.group(1),
|
|
"date": date,
|
|
"commit": commit_hash,
|
|
"author": author,
|
|
}
|
|
)
|
|
|
|
# Process milestones
|
|
if milestone_match:
|
|
milestone_key = milestone_match.group(1)
|
|
if milestone_key not in result["milestones"]:
|
|
result["milestones"][milestone_key] = {
|
|
"first_date": date,
|
|
"last_date": date,
|
|
"commits": [],
|
|
}
|
|
else:
|
|
result["milestones"][milestone_key]["last_date"] = date
|
|
|
|
result["milestones"][milestone_key]["commits"].append(
|
|
commit_hash
|
|
)
|
|
|
|
# Process roadmap items
|
|
if roadmap_match:
|
|
milestone_key = roadmap_match.group(1)
|
|
item_desc = roadmap_match.group(2)
|
|
|
|
if milestone_key not in result["roadmap_items"]:
|
|
result["roadmap_items"][milestone_key] = []
|
|
|
|
result["roadmap_items"][milestone_key].append(
|
|
{
|
|
"description": item_desc,
|
|
"date": date,
|
|
"commit": commit_hash,
|
|
"author": author,
|
|
}
|
|
)
|
|
|
|
# Process untagged commits
|
|
if not any(
|
|
[
|
|
status_match,
|
|
feature_match,
|
|
new_feature_match,
|
|
changelog_match,
|
|
fix_match,
|
|
issue_match,
|
|
milestone_match,
|
|
roadmap_match,
|
|
]
|
|
):
|
|
if untagged_count < untagged_limit:
|
|
# Skip merge commits and very short messages
|
|
if not subject.startswith("Merge ") and len(subject) > 5:
|
|
# Extract the first sentence or up to 100 chars
|
|
commit_desc = subject.split(".")[0]
|
|
if len(commit_desc) > 100:
|
|
commit_desc = commit_desc[:97] + "..."
|
|
|
|
result["untagged_commits"].append(
|
|
{
|
|
"message": commit_desc,
|
|
"date": date,
|
|
"commit": commit_hash,
|
|
"author": author,
|
|
}
|
|
)
|
|
untagged_count += 1
|
|
|
|
return result
|