summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-27 21:57:02 +0300
committerPaul Buetow <paul@buetow.org>2026-05-27 21:57:02 +0300
commit9dc3512e030604ca0c63919e41c0e5a4987b117a (patch)
tree75f7538766a4a6a26bd3203d74507ae15e554f25
parent0f051cc7feb77dc0bc579ed5575f7d397ba6534d (diff)
showcase: activity check uses all-branch last-commit date to avoid false inactivity
Problem: projects with active development on a non-default branch (e.g. all recent work on 'master' while the workdir HEAD is on 'screenshots') were being greyed out in the rank-history SVG despite recent commits. Fix: - Add LastActivityDate string to RepoMetadata, computed with 'git log --all -1 --pretty=format:%ai' (all local branches, no fetch). Code stats (AvgCommitAge, CommitCount, LOC, score) remain HEAD-only per the configured-branch rules. - SVG inactivity check uses LastActivityDate (falling back to LastCommitDate if the field is absent in older cache entries). - getLastActivityDate() added to metadata.go alongside getLastCommitDate(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/showcase/metadata.go60
-rw-r--r--internal/showcase/rank_history_svg.go24
2 files changed, 62 insertions, 22 deletions
diff --git a/internal/showcase/metadata.go b/internal/showcase/metadata.go
index 038dc08..c6c756b 100644
--- a/internal/showcase/metadata.go
+++ b/internal/showcase/metadata.go
@@ -22,20 +22,21 @@ type LanguageStats struct {
// RepoMetadata holds metadata about a repository
type RepoMetadata struct {
- Languages []LanguageStats // Programming languages with usage statistics
- Documentation []LanguageStats // Documentation/text files with usage statistics
- CommitCount int
- LinesOfCode int // Lines of code (excluding documentation)
- LinesOfDocs int // Lines of documentation
- FirstCommitDate string
- LastCommitDate string
- License string
- AvgCommitAge float64 // Average age of last 42 commits in days
- TagCount int // Total number of git tags in the repository
- Score float64 // Project score combining recent activity, reduced LOC weight, tag count, and release status
- LatestTag string // Latest version tag (empty if no tags)
- LatestTagDate string // Date of the latest tag (empty if no tags)
- HasReleases bool // Whether the project has any releases/tags
+ Languages []LanguageStats // Programming languages with usage statistics
+ Documentation []LanguageStats // Documentation/text files with usage statistics
+ CommitCount int
+ LinesOfCode int // Lines of code (excluding documentation)
+ LinesOfDocs int // Lines of documentation
+ FirstCommitDate string
+ LastCommitDate string
+ LastActivityDate string // Most recent commit on any local branch (--all); used for activity checks
+ License string
+ AvgCommitAge float64 // Average age of last 42 commits in days (HEAD only; used for score)
+ TagCount int // Total number of git tags in the repository
+ Score float64 // Project score combining recent activity, reduced LOC weight, tag count, and release status
+ LatestTag string // Latest version tag (empty if no tags)
+ LatestTagDate string // Date of the latest tag (empty if no tags)
+ HasReleases bool // Whether the project has any releases/tags
}
// extractRepoMetadata extracts metadata from a repository
@@ -83,6 +84,18 @@ func extractRepoMetadata(repoPath string) (*RepoMetadata, error) {
}
metadata.LastCommitDate = lastDate
+ // LastActivityDate is the most recent commit across ALL local branches so
+ // that projects with active development on non-default branches (e.g. a
+ // "develop" branch while HEAD is an old "master") are not falsely marked
+ // inactive. Code stats (LOC, AvgCommitAge, score) remain HEAD-only per
+ // the configured branch rules.
+ activityDate, err := getLastActivityDate(repoPath)
+ if err != nil {
+ fmt.Printf("Warning: Failed to get last activity date: %v\n", err)
+ activityDate = lastDate // fall back to HEAD-based date
+ }
+ metadata.LastActivityDate = activityDate
+
// Check for license file
license := detectLicense(repoPath)
metadata.License = license
@@ -214,6 +227,25 @@ func getLastCommitDate(repoPath string) (string, error) {
return "", fmt.Errorf("no commits found")
}
+// getLastActivityDate returns the date of the most recent commit across all
+// local branches (--all). This is intentionally local-only: no network
+// fetch is performed, so it reflects the state of the last sync. It is used
+// solely for the inactivity check (greying in the rank-history SVG) so that
+// projects with active work on non-default branches are not falsely flagged.
+// Code stats (LOC, AvgCommitAge, score) remain HEAD-only as per config.
+func getLastActivityDate(repoPath string) (string, error) {
+ cmd := exec.Command("git", "-C", repoPath, "log", "--all", "-1", "--pretty=format:%ai")
+ output, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ parts := strings.Fields(string(output))
+ if len(parts) > 0 {
+ return parts[0], nil
+ }
+ return "", fmt.Errorf("no commits found across all branches")
+}
+
// detectLicense checks for common license files
func detectLicense(repoPath string) string {
licenseFiles := []string{
diff --git a/internal/showcase/rank_history_svg.go b/internal/showcase/rank_history_svg.go
index 7a0386d..5022cd3 100644
--- a/internal/showcase/rank_history_svg.go
+++ b/internal/showcase/rank_history_svg.go
@@ -207,15 +207,23 @@ func GenerateRankHistorySVG(summaries []ProjectSummary) string {
continue // skip projects that have never appeared in any snapshot
}
- // Mirror the inactivity check from formatGemtext: avg commit age > 730 days
- // AND the most recent commit was also over a year ago. Inactive projects
- // are still drawn but as grey lines so they do not compete visually with
- // active ones; they turn coloured only on legend-entry hover.
+ // A project is inactive when its average commit age (HEAD) exceeds 730
+ // days AND no commit on ANY local branch is younger than 365 days.
+ // Using LastActivityDate (all-branches) avoids false positives for
+ // projects whose default branch is old but development continues on
+ // another branch (e.g. a "develop" or "master" branch).
+ // Code stats (AvgCommitAge, score) remain HEAD-only per config rules.
inactive := false
- if s.Metadata != nil && s.Metadata.AvgCommitAge > 730 && s.Metadata.LastCommitDate != "" {
- if last, err := time.Parse("2006-01-02", s.Metadata.LastCommitDate); err == nil {
- if time.Since(last).Hours()/24 > 365 {
- inactive = true
+ if s.Metadata != nil && s.Metadata.AvgCommitAge > 730 {
+ activityDate := s.Metadata.LastActivityDate
+ if activityDate == "" {
+ activityDate = s.Metadata.LastCommitDate // fallback if field absent
+ }
+ if activityDate != "" {
+ if last, err := time.Parse("2006-01-02", activityDate); err == nil {
+ if time.Since(last).Hours()/24 > 365 {
+ inactive = true
+ }
}
}
}