Skip to content

Commit a147d6d

Browse files
committed
update
1 parent 1876071 commit a147d6d

File tree

4 files changed

+181
-143
lines changed

4 files changed

+181
-143
lines changed

.github/workflows/gh_statistics_bot.yml

Lines changed: 181 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -59,89 +59,107 @@ jobs:
5959
}
6060
}
6161
62-
// Get commit statistics using GitHub's recommended approach - optimized
62+
// Cache for branch activity to avoid repeated API calls
63+
let branchActivityCache = null;
64+
65+
// Get active branches for the entire year (cache this)
66+
async function getActiveBranchesForYear() {
67+
if (branchActivityCache) {
68+
console.log('*** USING CACHED BRANCH ACTIVITY ***');
69+
return branchActivityCache;
70+
}
71+
72+
console.log('*** BUILDING BRANCH ACTIVITY CACHE FOR FULL YEAR ***');
73+
74+
// Get all branches
75+
const branches = await github.paginate(github.rest.repos.listBranches, {
76+
owner: context.repo.owner,
77+
repo: context.repo.repo,
78+
per_page: 100,
79+
});
80+
81+
console.log(`*** FOUND ${branches.length} BRANCHES ***`);
82+
83+
// Get the date range for the entire year we're analyzing
84+
const now = new Date();
85+
const yearStart = new Date(now.getFullYear(), now.getMonth() - 12, 1);
86+
const yearEnd = new Date();
87+
88+
console.log(`*** CHECKING ACTIVITY FROM ${yearStart.toISOString()} TO ${yearEnd.toISOString()} ***`);
89+
90+
const activeBranches = [];
91+
let branchesChecked = 0;
92+
93+
for (const branch of branches) {
94+
try {
95+
branchesChecked++;
96+
if (branchesChecked % 20 === 0) {
97+
console.log(`*** Checked ${branchesChecked}/${branches.length} branches for yearly activity ***`);
98+
}
99+
100+
// Check if branch has ANY commits in the entire year
101+
const branchCommits = await github.rest.repos.listCommits({
102+
owner: context.repo.owner,
103+
repo: context.repo.repo,
104+
sha: branch.name,
105+
since: yearStart.toISOString(),
106+
until: yearEnd.toISOString(),
107+
per_page: 1,
108+
});
109+
110+
if (branchCommits.data.length > 0) {
111+
activeBranches.push(branch);
112+
console.log(`*** Branch ${branch.name} has commits in the year ***`);
113+
}
114+
115+
} catch (error) {
116+
console.log(`*** Error checking ${branch.name}: ${error.message} ***`);
117+
}
118+
119+
await new Promise(resolve => setTimeout(resolve, 50));
120+
}
121+
122+
console.log(`*** FOUND ${activeBranches.length} ACTIVE BRANCHES FOR THE YEAR ***`);
123+
branchActivityCache = activeBranches;
124+
return activeBranches;
125+
}
126+
127+
// Get commit statistics using cached active branches
63128
async function getMonthCommitStats(since, until) {
64-
console.log(`=== GETTING COMMITS FROM ACTIVE BRANCHES ${since} TO ${until} ===`);
129+
console.log(`=== GETTING COMMITS FROM CACHED ACTIVE BRANCHES ${since} TO ${until} ===`);
65130
66131
try {
67-
// Step 1: Get all branches in the repository
68-
console.log('*** STEP 1: LISTING ALL BRANCHES ***');
69-
const branches = await github.paginate(github.rest.repos.listBranches, {
70-
owner: context.repo.owner,
71-
repo: context.repo.repo,
72-
per_page: 100,
73-
});
132+
// Get the list of active branches for the year (cached after first call)
133+
const activeBranches = await getActiveBranchesForYear();
74134
75-
console.log(`*** FOUND ${branches.length} BRANCHES ***`);
76-
77-
// Get default branch for main commit counting
135+
// Get default branch info
78136
const repo = await github.rest.repos.get({
79137
owner: context.repo.owner,
80138
repo: context.repo.repo,
81139
});
82140
const defaultBranch = repo.data.default_branch;
83-
console.log(`*** DEFAULT BRANCH: ${defaultBranch} ***`);
84141
85-
// Step 2: Check each branch for commits in the date range (with early exit)
86-
console.log('*** STEP 2: FINDING BRANCHES WITH COMMITS IN DATE RANGE ***');
87-
const activeBranches = [];
88-
let branchesChecked = 0;
142+
console.log(`*** PROCESSING ${activeBranches.length} ACTIVE BRANCHES FOR THIS MONTH ***`);
89143
90-
for (const branch of branches) {
91-
try {
92-
branchesChecked++;
93-
if (branchesChecked % 20 === 0) {
94-
console.log(`*** Checked ${branchesChecked}/${branches.length} branches ***`);
95-
}
96-
97-
// Get just the first page to check if branch has commits in date range
98-
const branchCommits = await github.rest.repos.listCommits({
99-
owner: context.repo.owner,
100-
repo: context.repo.repo,
101-
sha: branch.name,
102-
since,
103-
until,
104-
per_page: 1, // Just check if any commits exist
105-
});
106-
107-
if (branchCommits.data.length > 0) {
108-
activeBranches.push({
109-
branch: branch,
110-
hasCommits: true
111-
});
112-
console.log(`*** Branch ${branch.name} has commits in date range ***`);
113-
}
114-
115-
} catch (error) {
116-
console.log(`*** Error checking ${branch.name}: ${error.message} ***`);
117-
}
118-
119-
// Small delay to avoid rate limiting
120-
await new Promise(resolve => setTimeout(resolve, 50));
121-
}
122-
123-
console.log(`*** FOUND ${activeBranches.length} ACTIVE BRANCHES WITH COMMITS ***`);
124-
125-
// Step 3: Get full commit data only from active branches
126-
console.log('*** STEP 3: FETCHING ALL COMMITS FROM ACTIVE BRANCHES ***');
127144
const allCommitShas = new Set();
128145
const allAuthors = new Set();
129146
let mainBranchCommits = [];
130147
131-
for (const activeBranch of activeBranches) {
148+
// Now only check the active branches for this specific month
149+
for (const branch of activeBranches) {
132150
try {
133-
console.log(`*** Fetching all commits from active branch: ${activeBranch.branch.name} ***`);
134-
135151
const branchCommits = await github.paginate(github.rest.repos.listCommits, {
136152
owner: context.repo.owner,
137153
repo: context.repo.repo,
138-
sha: activeBranch.branch.name,
154+
sha: branch.name,
139155
since,
140156
until,
141157
per_page: 100,
142158
});
143159
144-
console.log(`*** Found ${branchCommits.length} commits on ${activeBranch.branch.name} ***`);
160+
if (branchCommits.length > 0) {
161+
console.log(`*** Found ${branchCommits.length} commits on ${branch.name} for this month ***`);
162+
}
145163
146164
// Add to our sets (automatically handles duplicates)
147165
branchCommits.forEach(commit => {
@@ -152,31 +170,28 @@ jobs:
152170
});
153171
154172
// Keep main branch commits separate
155-
if (activeBranch.branch.name === defaultBranch) {
173+
if (branch.name === defaultBranch) {
156174
mainBranchCommits = branchCommits;
157175
}
158176
159177
} catch (error) {
160-
console.log(`*** Error fetching commits from ${activeBranch.branch.name}: ${error.message} ***`);
178+
console.log(`*** Error fetching commits from ${branch.name}: ${error.message} ***`);
161179
}
162180
163-
// Small delay to avoid rate limiting
164181
await new Promise(resolve => setTimeout(resolve, 100));
165182
}
166183
167-
console.log(`*** PROCESSED ${activeBranches.length} ACTIVE BRANCHES ***`);
168-
console.log(`*** TOTAL UNIQUE COMMITS ACROSS ALL ACTIVE BRANCHES: ${allCommitShas.size} ***`);
184+
console.log(`*** TOTAL UNIQUE COMMITS ACROSS ACTIVE BRANCHES: ${allCommitShas.size} ***`);
169185
console.log(`*** COMMITS ON MAIN BRANCH: ${mainBranchCommits.length} ***`);
170186
console.log(`*** TOTAL UNIQUE AUTHORS: ${allAuthors.size} ***`);
171187
console.log(`*** AUTHORS: ${Array.from(allAuthors).join(', ')} ***`);
172188
173-
// Step 4: Get detailed stats from main branch commits
174-
console.log('*** STEP 4: GETTING DETAILED STATS FROM MAIN BRANCH ***');
189+
// Get detailed stats from main branch commits
175190
let totalAdditions = 0;
176191
let totalDeletions = 0;
177192
let mainFileChanges = 0;
178193
179-
for (const commit of mainBranchCommits.slice(0, 50)) { // Limit to avoid rate limits
194+
for (const commit of mainBranchCommits.slice(0, 50)) {
180195
try {
181196
const commitDetail = await github.rest.repos.getCommit({
182197
owner: context.repo.owner,
@@ -203,11 +218,11 @@ jobs:
203218
netLines: totalAdditions - totalDeletions,
204219
};
205220
206-
console.log('*** FINAL OPTIMIZED RESULT ***', JSON.stringify(result, null, 2));
221+
console.log('*** CACHED OPTIMIZED RESULT ***', JSON.stringify(result, null, 2));
207222
return result;
208223
209224
} catch (error) {
210-
console.log('*** ERROR WITH OPTIMIZED APPROACH ***', error.message);
225+
console.log('*** ERROR WITH CACHED APPROACH ***', error.message);
211226
return {
212227
totalCommitsAllBranches: 0,
213228
totalCommitsMain: 0,
@@ -311,7 +326,7 @@ jobs:
311326
</svg>`;
312327
}
313328
314-
// Commit charts to repository
329+
// Commit charts to repository and create PR
315330
async function commitChartsToRepo(monthlyData, targetBranch) {
316331
const totalCommits = monthlyData.map(d => d.stats?.totalCommitsAllBranches || d.stats?.totalCommits || 0);
317332
const mergedPRs = monthlyData.map(d => d.prStats?.merged || 0);
@@ -326,13 +341,51 @@ jobs:
326341
const linesAddedSvg = createSVGChart(linesAdded, 'Total Lines Added', '#22c55e', monthLabels);
327342
const activeContributorsSvg = createSVGChart(activeContributors, 'Active Contributors', '#8b5cf6', monthLabels);
328343
344+
// Create a new branch for the PR
345+
const now = new Date();
346+
const prBranchName = `update-stats-charts-${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}`;
347+
348+
console.log(`*** CREATING NEW BRANCH: ${prBranchName} ***`);
349+
350+
try {
351+
// Get the default branch reference
352+
const defaultBranch = await github.rest.repos.get({
353+
owner: context.repo.owner,
354+
repo: context.repo.repo,
355+
});
356+
357+
const mainBranchRef = await github.rest.git.getRef({
358+
owner: context.repo.owner,
359+
repo: context.repo.repo,
360+
ref: `heads/${defaultBranch.data.default_branch}`,
361+
});
362+
363+
// Create new branch
364+
await github.rest.git.createRef({
365+
owner: context.repo.owner,
366+
repo: context.repo.repo,
367+
ref: `refs/heads/${prBranchName}`,
368+
sha: mainBranchRef.data.object.sha,
369+
});
370+
371+
console.log(`*** CREATED BRANCH: ${prBranchName} ***`);
372+
373+
} catch (error) {
374+
if (error.message.includes('Reference already exists')) {
375+
console.log(`*** BRANCH ${prBranchName} ALREADY EXISTS, USING EXISTING BRANCH ***`);
376+
} else {
377+
throw error;
378+
}
379+
}
380+
329381
const chartFiles = [
330-
{ path: 'docs/assets/total-commits-chart.svg', content: totalCommitsSvg },
331-
{ path: 'docs/assets/merged-prs-chart.svg', content: mergedPRsSvg },
332-
{ path: 'docs/assets/lines-added-chart.svg', content: linesAddedSvg },
333-
{ path: 'docs/assets/active-contributors-chart.svg', content: activeContributorsSvg }
382+
{ path: 'docs/stats/total-commits-chart.svg', content: totalCommitsSvg },
383+
{ path: 'docs/stats/merged-prs-chart.svg', content: mergedPRsSvg },
384+
{ path: 'docs/stats/lines-added-chart.svg', content: linesAddedSvg },
385+
{ path: 'docs/stats/active-contributors-chart.svg', content: activeContributorsSvg }
334386
];
335387
388+
// Commit files to the new branch
336389
for (const file of chartFiles) {
337390
try {
338391
const base64Content = Buffer.from(file.content).toString('base64');
@@ -344,7 +397,7 @@ jobs:
344397
owner: context.repo.owner,
345398
repo: context.repo.repo,
346399
path: file.path,
347-
ref: targetBranch
400+
ref: prBranchName
348401
});
349402
existingFileSha = existingFile.data.sha;
350403
} catch (error) {
@@ -357,7 +410,7 @@ jobs:
357410
path: file.path,
358411
message: `Update repository activity chart: ${file.path}`,
359412
content: base64Content,
360-
branch: targetBranch
413+
branch: prBranchName
361414
};
362415
363416
if (existingFileSha) {
@@ -370,6 +423,53 @@ jobs:
370423
console.log(`Error updating ${file.path}:`, error.message);
371424
}
372425
}
426+
427+
// Create the PR
428+
const latestMonth = monthlyData[monthlyData.length - 1];
429+
const prTitle = `📊 Update Repository Statistics Charts - ${latestMonth.month}`;
430+
const prBody = `## 📊 Monthly Repository Statistics Update
431+
432+
This PR automatically updates our repository activity charts with the latest data for **${latestMonth.month}**.
433+
434+
### 📈 Updated Charts
435+
- 🔵 **Total Commits (All Branches):** ${latestMonth.stats?.totalCommitsAllBranches || latestMonth.stats?.totalCommits || 0} commits
436+
- 🟢 **Merged Pull Requests:** ${latestMonth.prStats?.merged || 0} PRs
437+
- 🟣 **Total Lines Added:** ${(latestMonth.stats?.linesAdded || 0).toLocaleString()} lines
438+
- 🟠 **Active Contributors:** ${latestMonth.stats?.totalAuthors || latestMonth.stats?.uniqueContributors || 0} developers
439+
440+
### 📁 Files Updated
441+
- \`docs/stats/total-commits-chart.svg\`
442+
- \`docs/stats/merged-prs-chart.svg\`
443+
- \`docs/stats/lines-added-chart.svg\`
444+
- \`docs/stats/active-contributors-chart.svg\`
445+
446+
### 🤖 About
447+
This PR was automatically generated by our monthly statistics bot. The charts show 12 months of repository activity trends and can be embedded in documentation to showcase project health and development velocity.
448+
449+
---
450+
*Generated automatically on ${new Date().toLocaleDateString()}*`;
451+
452+
try {
453+
const pullRequest = await github.rest.pulls.create({
454+
owner: context.repo.owner,
455+
repo: context.repo.repo,
456+
title: prTitle,
457+
head: prBranchName,
458+
base: defaultBranch.data.default_branch,
459+
body: prBody,
460+
});
461+
462+
console.log(`*** CREATED PR: ${pullRequest.data.html_url} ***`);
463+
return pullRequest.data.html_url;
464+
465+
} catch (error) {
466+
if (error.message.includes('A pull request already exists')) {
467+
console.log(`*** PR ALREADY EXISTS FOR BRANCH ${prBranchName} ***`);
468+
return `PR already exists for ${prBranchName}`;
469+
} else {
470+
throw error;
471+
}
472+
}
373473
}
374474
375475
// Main execution
@@ -418,9 +518,9 @@ jobs:
418518
await new Promise(resolve => setTimeout(resolve, 500));
419519
}
420520
421-
// Create and commit SVG charts
521+
// Create and commit SVG charts, then create PR
422522
console.log('Generating and committing SVG charts...');
423-
await commitChartsToRepo(monthlyDataArray, targetBranch);
523+
const prUrl = await commitChartsToRepo(monthlyDataArray, targetBranch);
424524
425525
// Get language stats
426526
const languageStats = await getLanguageStats();
@@ -429,7 +529,7 @@ jobs:
429529
const latestMonthData = monthlyDataArray[monthlyDataArray.length - 1];
430530
console.log(`*** LATEST MONTH FOR MATTERMOST ***`, JSON.stringify(latestMonthData, null, 2));
431531
432-
// Post to Mattermost
532+
// Post to Mattermost with PR link
433533
const mattermostPayload = {
434534
text: `📊 **${latestMonthData.month} Repository Activity**`,
435535
attachments: [{
@@ -446,8 +546,9 @@ jobs:
446546
`**Top Contributors:** ${(latestMonthData.stats?.authorsList || []).slice(0, 3).join(', ') || 'None'}\n` +
447547
`**Languages:** ${(languageStats || []).map(l => `${l.language} (${l.percentage}%)`).join(', ') || 'N/A'}\n\n` +
448548
`*Excluding merges, ${latestMonthData.stats?.totalAuthors || 0} authors pushed ${latestMonthData.stats?.totalCommitsMain || 0} commits to main and ${latestMonthData.stats?.totalCommitsAllBranches || 0} commits to all branches*\n\n` +
549+
`**📊 Charts Updated:** ${typeof prUrl === 'string' && prUrl.startsWith('http') ? `[View PR](${prUrl})` : 'Charts updated in docs/stats/'}\n` +
449550
`Repository: ${context.repo.owner}/${context.repo.repo}`,
450-
footer: 'Charts updated in docs/assets/ for documentation'
551+
footer: 'Monthly statistics generated automatically'
451552
}]
452553
};
453554

0 commit comments

Comments
 (0)