creating activity log from private repos
For some reason, i work across bitbucket, github, private repos and i wanted to build a actiity log that would mix bitbucket and github. Although for security purpose, i could not use my main org repo for activity but it was a fun exercise to do anyway.
key considerations
- use selective repos based on org policy
- instead of counting commits as indicator, assign levels based on activity (# commits). I chose not to keep relative levels since it kind of defeats the prurpose to trying to be active everyday.
- ability to add any git repo (github, bitbucket and private)
- v0.dev was pretty helpful in building the ActivityGrid comopnent UI
Set things up
Main executor that reads from config and knows which sources to use
js
import { GitHubFetcher } from './github-commits.mjs';
import { BitbucketFetcher } from './bitbucket-commits.mjs';
import { GikiRepo } from './giki-private-commits.mjs';
// Configuration
const CONFIG = {
github: {
enabled: process.env.NEXT_PUBLIC_ENABLE_GITHUB === 'true',
username: process.env.NEXT_PUBLIC_GITHUB_USERNAME
},
bitbucket: {
enabled: process.env.NEXT_PUBLIC_ENABLE_BITBUCKET === 'true'
},
gikiPrivate: {
enabled: process.env.GIKI_PRIVATE === 'true'
},
dateRange: {
days: 365
}
};
async function fetchGitHubData(startDate) {
if (!process.env.GITHUB_TOKEN) {
return {};
}
console.log('Reading GitHub commits...');
const github = new GitHubFetcher(process.env.GITHUB_TOKEN, CONFIG.github.username);
return github.fetchCommits(startDate);
}
function getActivityLevel(count, maxCount) {
if (count === 0) return 0;
const percentile = (count / maxCount) * 100;
if (percentile <= 15) return 1;
if (percentile <= 35) return 2;
if (percentile <= 65) return 3;
return 4;
}
const dailyTotals = allDates.map(date => {
return (githubCommits[date] || 0) + (bitbucketCommits[date] || 0);
});
const maxDailyCommits = Math.max(...dailyTotals, 1); // Avoid division by zero
console.log('\nActivity Statistics:');
console.log('Max commits in a day:', maxDailyCommits);
console.log('Total days with activity:', dailyTotals.filter(x => x > 0).length);
console.log('Average daily commits:', (dailyTotals.reduce((a, b) => a + b, 0) / dailyTotals.length).toFixed(2));
const commitData = {};
allDates.forEach(date => {
const total = (githubCommits[date] || 0) + (bitbucketCommits[date] || 0);
commitData[date] = {
level: getActivityLevel(total, maxDailyCommits)
};
});
in implementor for every git
js
async fetchCommits(startDate) {
try {
console.log('🔍 Fetching Bitbucket repositories...');
const repos = await this.fetchRepositories();
console.log(`Found ${repos.length} repositories`);
let allCommits = {};
for (const repo of repos) {
console.log(`\n📂 Processing repository: ${repo.name}`);
// First get all branches
const branches = await this.fetchBranches(repo.full_name);
console.log(`Found ${branches.length} branches in ${repo.name}`);
// Fetch commits from each branch
for (const branch of branches) {
console.log(` Fetching commits from branch: ${branch.name}`);
const branchCommits = await this.fetchBranchCommits(repo.full_name, branch.name, startDate);
// Merge commits into overall counts
Object.entries(branchCommits).forEach(([date, count]) => {
allCommits[date] = (allCommits[date] || 0) + count;
});
}
}
return allCommits;
} catch (error) {
console.error('Error in main fetch process:', error);
return {};
}
}
fetch branches and process the commits
js
async fetchBranchCommits(repoFullName, branchName, startDate) {
try {
let commits = {};
let url = `https://api.bitbucket.org/2.0/repositories/${repoFullName}/commits/${branchName}`;
while (url) {
const response = await fetch(url, {
headers: {
'Authorization': this.authHeader
}
});
if (!response.ok) {
throw new Error(`Error fetching commits: ${response.statusText}`);
}
const data = await response.json();
// Process commits
if (data.values) {
const processedCommits = this.processCommits(data.values, startDate);
Object.entries(processedCommits).forEach(([date, count]) => {
commits[date] = (commits[date] || 0) + count;
});
}
// Check if we need to continue fetching (pagination)
url = data.next;
}
return commits;
} catch (error) {
console.error(`Error fetching commits for ${repoFullName}/${branchName}:`, error.message);
return {};
}
}
processCommits(commits = [], startDate) {
const commitsByDate = {};
const startDateTime = new Date(startDate).getTime();
commits.forEach(commit => {
const date = new Date(commit.date).toISOString().split('T')[0];
if (new Date(date).getTime() >= startDateTime) {
commitsByDate[date] = (commitsByDate[date] || 0) + 1;
}
});
return commitsByDate;
}