chronicle
¶
Full name: tenets.core.git.chronicle
chronicle¶
Chronicle module for git history analysis.
This module provides functionality for analyzing and summarizing git repository history, including commit patterns, contributor activity, and development trends. It extracts historical insights to help understand project evolution and team dynamics over time.
The chronicle functionality provides a narrative view of repository changes, making it easy to understand what happened, when, and by whom.
Classes¶
CommitSummarydataclass
¶
CommitSummary(sha: str, author: str, email: str, date: datetime, message: str, files_changed: int = 0, lines_added: int = 0, lines_removed: int = 0, is_merge: bool = False, is_revert: bool = False, tags: List[str] = list(), branch: Optional[str] = None, issue_refs: List[str] = list(), pr_refs: List[str] = list())
Summary information for a single commit.
Provides a concise representation of a commit with key information for historical analysis and reporting.
ATTRIBUTE | DESCRIPTION |
---|---|
sha | Commit SHA (short form) TYPE: |
author | Commit author name TYPE: |
email | Author email TYPE: |
date | Commit date TYPE: |
message | Commit message (first line) TYPE: |
files_changed | Number of files changed TYPE: |
lines_added | Lines added TYPE: |
lines_removed | Lines removed TYPE: |
is_merge | Whether this is a merge commit TYPE: |
is_revert | Whether this is a revert commit TYPE: |
tags | Associated tags |
branch | Branch name if available |
issue_refs | Referenced issue numbers |
pr_refs | Referenced PR numbers |
Attributes¶
net_linesproperty
¶
Calculate net lines changed.
RETURNS | DESCRIPTION |
---|---|
int | Lines added minus lines removed TYPE: |
commit_typeproperty
¶
Determine commit type from message.
RETURNS | DESCRIPTION |
---|---|
str | Commit type (feat, fix, docs, etc.) TYPE: |
Functions¶
to_dict¶
Convert to dictionary representation.
RETURNS | DESCRIPTION |
---|---|
Dict[str, Any] | Dict[str, Any]: Dictionary representation |
Source code in tenets/core/git/chronicle.py
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary representation.
Returns:
Dict[str, Any]: Dictionary representation
"""
return {
"sha": self.sha,
"author": self.author,
"email": self.email,
"date": self.date.isoformat(),
"message": self.message,
"files_changed": self.files_changed,
"lines_added": self.lines_added,
"lines_removed": self.lines_removed,
"type": self.commit_type,
"is_merge": self.is_merge,
"is_revert": self.is_revert,
"tags": self.tags,
"branch": self.branch,
"issue_refs": self.issue_refs,
"pr_refs": self.pr_refs,
}
DayActivitydataclass
¶
DayActivity(date: datetime, commits: List[CommitSummary] = list(), total_commits: int = 0, unique_authors: Set[str] = set(), lines_added: int = 0, lines_removed: int = 0, files_touched: Set[str] = set(), commit_types: Dict[str, int] = dict(), peak_hour: Optional[int] = None, first_commit_time: Optional[datetime] = None, last_commit_time: Optional[datetime] = None)
Activity summary for a single day.
Aggregates all repository activity for a specific day to provide daily development rhythm insights.
ATTRIBUTE | DESCRIPTION |
---|---|
date | Date of activity TYPE: |
commits | List of commits on this day TYPE: |
total_commits | Total commit count TYPE: |
unique_authors | Set of unique authors |
lines_added | Total lines added TYPE: |
lines_removed | Total lines removed TYPE: |
files_touched | Set of files modified |
commit_types | Distribution of commit types |
peak_hour | Hour with most commits |
first_commit_time | Time of first commit |
last_commit_time | Time of last commit |
ChronicleReportdataclass
¶
ChronicleReport(period_start: datetime, period_end: datetime, total_commits: int = 0, total_contributors: int = 0, commits: List[CommitSummary] = list(), daily_activity: List[DayActivity] = list(), contributor_stats: Dict[str, Dict[str, Any]] = dict(), commit_type_distribution: Dict[str, int] = dict(), file_change_frequency: List[Tuple[str, int]] = list(), hot_periods: List[Dict[str, Any]] = list(), quiet_periods: List[Dict[str, Any]] = list(), significant_events: List[Dict[str, Any]] = list(), trends: List[str] = list(), summary: str = '')
Comprehensive chronicle report of repository history.
Provides a complete narrative view of repository evolution including commits, contributors, trends, and significant events.
ATTRIBUTE | DESCRIPTION |
---|---|
period_start | Start of chronicle period TYPE: |
period_end | End of chronicle period TYPE: |
total_commits | Total commits in period TYPE: |
total_contributors | Total unique contributors TYPE: |
commits | List of commit summaries TYPE: |
daily_activity | Daily activity breakdown TYPE: |
contributor_stats | Statistics by contributor |
commit_type_distribution | Distribution of commit types |
file_change_frequency | Most frequently changed files |
hot_periods | Periods of high activity |
quiet_periods | Periods of low activity |
significant_events | Notable events (releases, major changes) |
trends | Identified trends in development |
summary | Executive summary of the period TYPE: |
Attributes¶
most_active_dayproperty
¶
Get the most active day.
RETURNS | DESCRIPTION |
---|---|
Optional[DayActivity] | Optional[DayActivity]: Most active day or None |
activity_levelproperty
¶
Determine overall activity level.
RETURNS | DESCRIPTION |
---|---|
str | Activity level (high, moderate, low) TYPE: |
Functions¶
to_dict¶
Convert to dictionary representation.
RETURNS | DESCRIPTION |
---|---|
Dict[str, Any] | Dict[str, Any]: Dictionary representation |
Source code in tenets/core/git/chronicle.py
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary representation.
Returns:
Dict[str, Any]: Dictionary representation
"""
return {
"period": {
"start": self.period_start.isoformat(),
"end": self.period_end.isoformat(),
"days": (self.period_end - self.period_start).days,
},
"summary": {
"total_commits": self.total_commits,
"total_contributors": self.total_contributors,
"avg_commits_per_day": (
self.total_commits / max(1, (self.period_end - self.period_start).days)
),
"narrative": self.summary,
},
"commit_types": self.commit_type_distribution,
"top_contributors": list(self.contributor_stats.items())[:10],
"top_files": self.file_change_frequency[:20],
"hot_periods": self.hot_periods[:5],
"quiet_periods": self.quiet_periods[:5],
"significant_events": self.significant_events,
"trends": self.trends,
}
Chronicle¶
Main chronicle analyzer for git repositories.
Analyzes git history to create a narrative view of repository evolution, identifying patterns, trends, and significant events.
ATTRIBUTE | DESCRIPTION |
---|---|
config | Configuration object |
logger | Logger instance |
git_analyzer | Git analyzer instance TYPE: |
Initialize chronicle analyzer.
PARAMETER | DESCRIPTION |
---|---|
config | TenetsConfig instance TYPE: |
Source code in tenets/core/git/chronicle.py
Functions¶
analyze¶
analyze(repo_path: Path, since: Optional[str] = None, until: Optional[str] = None, author: Optional[str] = None, branch: Optional[str] = None, include_merges: bool = True, include_stats: bool = True, max_commits: int = 1000) -> ChronicleReport
Analyze repository history and create chronicle report.
Creates a comprehensive narrative of repository evolution including commits, contributors, trends, and significant events.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to git repository TYPE: |
since | Start date or relative time (e.g., "2 weeks ago") |
until | End date or relative time |
author | Filter by specific author |
branch | Specific branch to analyze |
include_merges | Whether to include merge commits TYPE: |
include_stats | Whether to include detailed statistics TYPE: |
max_commits | Maximum commits to analyze TYPE: |
RETURNS | DESCRIPTION |
---|---|
ChronicleReport | Comprehensive chronicle analysis TYPE: |
Example
chronicle = Chronicle(config) report = chronicle.analyze( ... Path("."), ... since="1 month ago", ... include_stats=True ... ) print(report.summary)
Source code in tenets/core/git/chronicle.py
def analyze(
self,
repo_path: Path,
since: Optional[str] = None,
until: Optional[str] = None,
author: Optional[str] = None,
branch: Optional[str] = None,
include_merges: bool = True,
include_stats: bool = True,
max_commits: int = 1000,
) -> ChronicleReport:
"""Analyze repository history and create chronicle report.
Creates a comprehensive narrative of repository evolution including
commits, contributors, trends, and significant events.
Args:
repo_path: Path to git repository
since: Start date or relative time (e.g., "2 weeks ago")
until: End date or relative time
author: Filter by specific author
branch: Specific branch to analyze
include_merges: Whether to include merge commits
include_stats: Whether to include detailed statistics
max_commits: Maximum commits to analyze
Returns:
ChronicleReport: Comprehensive chronicle analysis
Example:
>>> chronicle = Chronicle(config)
>>> report = chronicle.analyze(
... Path("."),
... since="1 month ago",
... include_stats=True
... )
>>> print(report.summary)
"""
self.logger.debug(f"Analyzing chronicle for {repo_path}")
# Initialize git analyzer
self.git_analyzer = GitAnalyzer(repo_path)
if not self.git_analyzer.is_repo():
self.logger.warning(f"Not a git repository: {repo_path}")
return ChronicleReport(
period_start=datetime.now(),
period_end=datetime.now(),
summary="No git repository found",
)
# Parse time period
period_start, period_end = self._parse_time_period(since, until)
# Initialize report
report = ChronicleReport(period_start=period_start, period_end=period_end)
# Get commits
commits = self._get_commits(
period_start, period_end, author, branch, include_merges, max_commits
)
if not commits:
report.summary = "No commits found in the specified period"
return report
# Process commits sequentially
# Note: Parallelization was attempted but GitPython commit objects
# are not thread-safe and accessing commit.stats is very expensive
for commit in commits:
commit_summary = self._process_commit(commit, include_stats)
report.commits.append(commit_summary)
# Sort commits by date
report.commits.sort(key=lambda c: c.date)
# Update basic stats
report.total_commits = len(report.commits)
# Analyze daily activity
report.daily_activity = self._analyze_daily_activity(report.commits)
# Analyze contributors
report.contributor_stats = self._analyze_contributors(report.commits)
report.total_contributors = len(report.contributor_stats)
# Analyze commit types
report.commit_type_distribution = self._analyze_commit_types(report.commits)
# Analyze file changes
if include_stats:
report.file_change_frequency = self._analyze_file_changes(commits)
# Identify hot and quiet periods
report.hot_periods = self._identify_hot_periods(report.daily_activity)
report.quiet_periods = self._identify_quiet_periods(report.daily_activity)
# Identify significant events
report.significant_events = self._identify_significant_events(report.commits)
# Identify trends
report.trends = self._identify_trends(report)
# Generate summary
report.summary = self._generate_summary(report)
self.logger.debug(
f"Chronicle analysis complete: {report.total_commits} commits, "
f"{report.total_contributors} contributors"
)
return report
ChronicleBuilder¶
High-level builder that assembles a simple chronicle dict for CLI.
This composes the existing Chronicle and GitAnalyzer without duplicating analysis logic. It converts inputs to what Chronicle expects and returns a compact, CLI-friendly dictionary.
The CLI tests patch this class, but we provide a functional default for real usage.
Source code in tenets/core/git/chronicle.py
Functions¶
build_chronicle¶
build_chronicle(repo_path: Path, *, since: Optional[object] = None, until: Optional[object] = None, branch: Optional[str] = None, authors: Optional[List[str]] = None, include_merges: bool = True, limit: Optional[int] = None) -> Dict[str, Any]
Build a chronicle summary for the given repository.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to a git repository TYPE: |
since | Start time (datetime or relative/ISO string) |
until | End time (datetime or relative/ISO string) |
branch | Branch name to analyze |
authors | Optional author filters (currently advisory) |
include_merges | Include merge commits TYPE: |
limit | Max commits to analyze (advisory to Chronicle) |
RETURNS | DESCRIPTION |
---|---|
Dict[str, Any] | A dictionary with keys expected by the CLI views. |
Source code in tenets/core/git/chronicle.py
def build_chronicle(
self,
repo_path: Path,
*,
since: Optional[object] = None,
until: Optional[object] = None,
branch: Optional[str] = None,
authors: Optional[List[str]] = None,
include_merges: bool = True,
limit: Optional[int] = None,
) -> Dict[str, Any]:
"""Build a chronicle summary for the given repository.
Args:
repo_path: Path to a git repository
since: Start time (datetime or relative/ISO string)
until: End time (datetime or relative/ISO string)
branch: Branch name to analyze
authors: Optional author filters (currently advisory)
include_merges: Include merge commits
limit: Max commits to analyze (advisory to Chronicle)
Returns:
A dictionary with keys expected by the CLI views.
"""
# Normalize time parameters to strings for Chronicle
def _to_str(t: Optional[object]) -> Optional[str]:
if t is None:
return None
if isinstance(t, str):
return t
try:
from datetime import datetime as _dt
if isinstance(t, _dt):
return t.isoformat()
except Exception:
pass
# Fallback to string repr
return str(t)
since_s = _to_str(since)
until_s = _to_str(until)
# Run detailed analysis via Chronicle
chron = Chronicle(self.config)
report = chron.analyze(
repo_path,
since=since_s,
until=until_s,
author=(authors[0] if authors else None), # basic filter support
branch=branch,
include_merges=include_merges,
include_stats=False, # Disabled for performance - commit.stats is very expensive
max_commits=limit or 1000,
)
# Summarize fields commonly displayed by CLI
period = (
f"{report.period_start.date().isoformat()} to {report.period_end.date().isoformat()}"
)
files_changed = (
len({p[0] for p in report.file_change_frequency}) if report.file_change_frequency else 0
)
# Lightweight activity signal (placeholder using totals)
activity = {
"trend": 0.0, # real trend computation is beyond this builder
"current_velocity": report.total_commits,
"commits_this_week": (
sum(d.total_commits for d in report.daily_activity[-7:])
if report.daily_activity
else 0
),
}
return {
"period": period,
"total_commits": report.total_commits,
"files_changed": files_changed,
"activity": activity,
# Include a small slice of richer data for reports
"commit_types": report.commit_type_distribution,
"top_files": report.file_change_frequency[:10],
"top_contributors": sorted(
((a, s.get("commits", 0)) for a, s in report.contributor_stats.items()),
key=lambda x: x[1],
reverse=True,
)[:5],
# Preserve the original report for advanced formatting if needed
"_report": report,
}
Functions¶
create_chronicle¶
create_chronicle(repo_path: Path, since: Optional[str] = None, config: Optional[TenetsConfig] = None, **kwargs: Any) -> ChronicleReport
Convenience function to create a repository chronicle.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to repository TYPE: |
since | Start time for chronicle |
config | Optional configuration TYPE: |
**kwargs | Additional arguments for chronicle TYPE: |
RETURNS | DESCRIPTION |
---|---|
ChronicleReport | Chronicle analysis TYPE: |
Example
from tenets.core.git.chronicle import create_chronicle report = create_chronicle(Path("."), since="1 month ago") print(report.summary)
Source code in tenets/core/git/chronicle.py
def create_chronicle(
repo_path: Path,
since: Optional[str] = None,
config: Optional[TenetsConfig] = None,
**kwargs: Any,
) -> ChronicleReport:
"""Convenience function to create a repository chronicle.
Args:
repo_path: Path to repository
since: Start time for chronicle
config: Optional configuration
**kwargs: Additional arguments for chronicle
Returns:
ChronicleReport: Chronicle analysis
Example:
>>> from tenets.core.git.chronicle import create_chronicle
>>> report = create_chronicle(Path("."), since="1 month ago")
>>> print(report.summary)
"""
if config is None:
config = TenetsConfig()
chronicle = Chronicle(config)
return chronicle.analyze(repo_path, since=since, **kwargs)