User Profile Statistics¶
The profile package provides comprehensive GitHub user contribution statistics, aggregating data from multiple API sources into a unified view similar to what GitHub shows on user profile pages.
Overview¶
The profile package combines data from:
- GraphQL API: Contribution counts, commit history, repositories contributed to
- REST API: Contributor stats, release counts
It provides:
| Feature | Description |
|---|---|
UserProfile |
Comprehensive stats combining all data sources |
ContributionCalendar |
Weekly/daily contribution grid with streak tracking |
ActivityTimeline |
Monthly activity feed with GitHub-style summaries |
Token Requirements¶
The profile package requires authentication. See Authentication for detailed requirements.
For public data (like viewing any user's public contributions):
| Token Type | Configuration |
|---|---|
| Fine-grained PAT | Repository access: "Public Repositories (read-only)", no permissions needed |
| Classic PAT | No scopes required |
For private repository data:
| Token Type | Configuration |
|---|---|
| Fine-grained PAT | Select repositories, Contents: Read-only |
| Classic PAT | repo scope |
Quick Start¶
package main
import (
"context"
"fmt"
"time"
"github.com/google/go-github/v82/github"
"github.com/grokify/gogithub/graphql"
"github.com/grokify/gogithub/profile"
)
func main() {
ctx := context.Background()
token := "your-github-token"
// Create clients
restClient := github.NewClient(nil).WithAuthToken(token)
gqlClient := graphql.NewClient(ctx, token)
// Fetch profile for last year
from := time.Now().AddDate(-1, 0, 0)
to := time.Now()
p, err := profile.GetUserProfile(ctx, restClient, gqlClient, "octocat", from, to, nil)
if err != nil {
panic(err)
}
fmt.Println(p.Summary())
// octocat: 150 commits (+10000/-3000) in 12 repos, 25 PRs, 10 issues, 50 reviews
}
UserProfile¶
The UserProfile type contains comprehensive contribution statistics:
type UserProfile struct {
Username string
From time.Time
To time.Time
// Summary counts
TotalCommits int
TotalIssues int
TotalPRs int
TotalReviews int
TotalReposCreated int
RestrictedContributions int // Private contributions
// Code changes
TotalAdditions int
TotalDeletions int
// Repository data
ReposContributedTo int
RepoStats []RepoContribution
// Time-series data
Calendar *ContributionCalendar
Activity *ActivityTimeline
}
Options¶
Configure what data to fetch:
opts := &profile.Options{
// Filter by repository visibility
Visibility: graphql.VisibilityAll, // or VisibilityPublic, VisibilityPrivate
// Fetch release counts (slower, requires additional API calls)
IncludeReleases: true,
// Limit how many repos to fetch releases for (0 = no limit)
MaxReleaseFetchRepos: 10,
}
p, err := profile.GetUserProfile(ctx, restClient, gqlClient, "octocat", from, to, opts)
Helper Methods¶
// Text summary
fmt.Println(p.Summary())
// Top repositories by commits
for _, repo := range p.TopReposByCommits(5) {
fmt.Printf("%s: %d commits\n", repo.FullName, repo.Commits)
}
// Top repositories by lines added
for _, repo := range p.TopReposByAdditions(5) {
fmt.Printf("%s: +%d lines\n", repo.FullName, repo.Additions)
}
// Filter by visibility
publicRepos := p.PublicRepos()
privateRepos := p.PrivateRepos()
Contribution Calendar¶
The ContributionCalendar represents the contribution grid shown on GitHub profiles:
type ContributionCalendar struct {
TotalContributions int
Weeks []CalendarWeek
}
type CalendarWeek struct {
StartDate time.Time // Sunday of this week
Days [7]CalendarDay // Sunday through Saturday
}
type CalendarDay struct {
Date time.Time
Weekday time.Weekday
ContributionCount int
Level ContributionLevel // 0-4 intensity
}
Contribution Levels¶
Levels approximate GitHub's visual intensity:
| Level | Count | Description |
|---|---|---|
| 0 | 0 | No contributions |
| 1 | 1-3 | Low |
| 2 | 4-6 | Medium |
| 3 | 7-9 | High |
| 4 | 10+ | Maximum |
Calendar Methods¶
cal := p.Calendar
// Overall stats
fmt.Printf("Total: %d contributions\n", cal.TotalContributions)
fmt.Printf("Active days: %d\n", cal.DaysWithContributions())
// Streaks
fmt.Printf("Longest streak: %d days\n", cal.LongestStreak())
fmt.Printf("Current streak: %d days\n", cal.CurrentStreak())
// Date range
first, last := cal.GetDateRange()
fmt.Printf("From %s to %s\n", first.Format("2006-01-02"), last.Format("2006-01-02"))
// Lookup specific date
day := cal.GetDay(time.Now())
if day != nil {
fmt.Printf("Today: %d contributions (level %d)\n", day.ContributionCount, day.Level)
}
// Get week containing a date
week := cal.GetWeek(time.Now())
if week != nil {
fmt.Printf("This week: %d contributions\n", week.TotalForWeek())
}
Activity Timeline¶
The ActivityTimeline provides monthly activity breakdowns similar to GitHub's profile activity feed:
type ActivityTimeline struct {
Username string
From time.Time
To time.Time
Months []MonthlyActivity
}
type MonthlyActivity struct {
Year int
Month time.Month
// Contribution counts
Commits int
Issues int
PRs int
Reviews int
// Code changes
Additions int
Deletions int
// Repository breakdown
CommitsByRepo map[string]int // "owner/repo" -> count
IssueRepos []string
PRRepos []string
ReposCreated []string
}
GitHub-Style Summaries¶
for _, m := range p.Activity.Months {
fmt.Printf("\n%s %d:\n", m.MonthName(), m.Year)
// These return GitHub-style summary strings
if s := m.CommitSummary(); s != "" {
fmt.Printf(" - %s\n", s) // "Created 42 commits in 5 repositories"
}
if s := m.PRSummary(); s != "" {
fmt.Printf(" - %s\n", s) // "Opened 3 pull requests in 2 repositories"
}
if s := m.IssueSummary(); s != "" {
fmt.Printf(" - %s\n", s) // "Opened 5 issues in 3 repositories"
}
if s := m.ReviewSummary(); s != "" {
fmt.Printf(" - %s\n", s) // "Reviewed 10 pull requests"
}
if s := m.RepoCreatedSummary(); s != "" {
fmt.Printf(" - %s\n", s) // "Created 1 repository: user/newrepo"
}
}
Timeline Methods¶
timeline := p.Activity
// Aggregates
fmt.Printf("Total commits: %d\n", timeline.TotalCommits())
fmt.Printf("Total contributions: %d\n", timeline.TotalContributions())
fmt.Printf("Months with activity: %d\n", timeline.MonthsWithActivity())
fmt.Printf("Average per month: %.1f\n", timeline.AverageMonthlyContributions())
// Most active month
if most := timeline.MostActiveMonth(); most != nil {
fmt.Printf("Most active: %s %d\n", most.MonthName(), most.Year)
}
// Get specific month
if jan := timeline.GetMonth(2024, time.January); jan != nil {
fmt.Printf("January 2024: %d commits\n", jan.Commits)
}
// Sorting
timeline.SortByDate() // Oldest first
timeline.SortByDateDesc() // Newest first (default)
Top Repositories¶
for _, m := range p.Activity.Months {
fmt.Printf("%s %d top repos:\n", m.MonthName(), m.Year)
for _, repo := range m.TopCommitRepos(3) {
fmt.Printf(" %s: %d commits\n", repo.Repo, repo.Commits)
}
}
Complete Example¶
package main
import (
"context"
"fmt"
"time"
"github.com/google/go-github/v82/github"
"github.com/grokify/gogithub/graphql"
"github.com/grokify/gogithub/profile"
)
func main() {
ctx := context.Background()
token := "your-github-token"
restClient := github.NewClient(nil).WithAuthToken(token)
gqlClient := graphql.NewClient(ctx, token)
// Fetch profile for 2024
from := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)
opts := &profile.Options{
Visibility: graphql.VisibilityAll,
IncludeReleases: true,
MaxReleaseFetchRepos: 5,
}
p, err := profile.GetUserProfile(ctx, restClient, gqlClient, "grokify", from, to, opts)
if err != nil {
panic(err)
}
// === Summary ===
fmt.Println("=== Profile Summary ===")
fmt.Println(p.Summary())
// === Calendar Stats ===
fmt.Println("\n=== Calendar Stats ===")
fmt.Printf("Total contributions: %d\n", p.Calendar.TotalContributions)
fmt.Printf("Days with activity: %d\n", p.Calendar.DaysWithContributions())
fmt.Printf("Longest streak: %d days\n", p.Calendar.LongestStreak())
fmt.Printf("Current streak: %d days\n", p.Calendar.CurrentStreak())
// === Top Repos ===
fmt.Println("\n=== Top 5 Repositories ===")
for i, repo := range p.TopReposByCommits(5) {
fmt.Printf("%d. %s: %d commits (+%d/-%d)\n",
i+1, repo.FullName, repo.Commits, repo.Additions, repo.Deletions)
if repo.Releases > 0 {
fmt.Printf(" Releases: %d\n", repo.Releases)
}
}
// === Monthly Activity ===
fmt.Println("\n=== Monthly Activity ===")
for _, m := range p.Activity.Months {
if m.TotalContributions() == 0 {
continue
}
fmt.Printf("\n%s %d:\n", m.MonthName(), m.Year)
if s := m.CommitSummary(); s != "" {
fmt.Printf(" - %s\n", s)
}
if s := m.PRSummary(); s != "" {
fmt.Printf(" - %s\n", s)
}
if m.Additions > 0 || m.Deletions > 0 {
fmt.Printf(" - Code: +%d/-%d lines\n", m.Additions, m.Deletions)
}
}
}
Contributor Stats (REST API)¶
For per-repository contributor statistics (like GitHub's /graphs/contributors page), use the repo package:
import "github.com/grokify/gogithub/repo"
// Get all contributors for a repository
stats, err := repo.ListContributorStats(ctx, restClient, "owner", "repo")
// Get stats for a specific user
userStats, err := repo.GetContributorStats(ctx, restClient, "owner", "repo", "username")
// Get summarized stats
summary, err := repo.GetContributorSummary(ctx, restClient, "owner", "repo", "username")
if summary != nil {
fmt.Printf("%s: %d commits (+%d/-%d)\n",
summary.Username, summary.TotalCommits,
summary.TotalAdditions, summary.TotalDeletions)
fmt.Printf("First commit: %s\n", summary.FirstCommit.Format("2006-01-02"))
fmt.Printf("Last commit: %s\n", summary.LastCommit.Format("2006-01-02"))
}
202 Accepted Handling
GitHub may return 202 Accepted while computing statistics. The ListContributorStats function automatically retries with exponential backoff until stats are ready.