forked from benchmark-action/github-action-benchmark
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Ankitml/git graph awareness #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ankitml
wants to merge
10
commits into
master
Choose a base branch
from
ankitml/git-graph-awareness
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
239cd5d
feat: Implement git-graph aware benchmark sorting
ankitml 58cb4b5
test: Add unit tests for git-graph functionality
ankitml 39095e0
gitgraph
ankitml 9f0ddca
CI check
ankitml daa027d
Update src/gitGraph.ts
ankitml 1d2ae9f
stu's comment
ankitml ca7156c
lint
ankitml 941cedb
more fixes
ankitml 89af8c3
comments
ankitml 15b0628
better organization
ankitml File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| import * as github from '@actions/github'; | ||
| import { execSync } from 'child_process'; | ||
| import * as core from '@actions/core'; | ||
| import { Benchmark } from './extract'; | ||
|
|
||
| export class GitGraphAnalyzer { | ||
| private readonly gitCliAvailable: boolean; | ||
|
|
||
| constructor() { | ||
| // Check if we're in GitHub Actions environment (git CLI available) | ||
| this.gitCliAvailable = process.env.GITHUB_ACTIONS === 'true' && Boolean(process.env.GITHUB_WORKSPACE); | ||
| } | ||
|
|
||
| /** | ||
| * Get current branch from GitHub context | ||
| */ | ||
| getCurrentBranch(): string { | ||
| const context = github.context; | ||
|
|
||
| // For pull requests, get the head branch | ||
| if (context.payload.pull_request) { | ||
| return context.payload.pull_request.head.ref; | ||
| } | ||
|
|
||
| // For pushes, get the branch from ref | ||
| if (context.ref) { | ||
| // Remove 'refs/heads/' prefix if present | ||
| return context.ref.replace('refs/heads/', ''); | ||
| } | ||
|
|
||
| // Fallback to 'main' if we can't determine branch | ||
| return 'main'; | ||
| } | ||
|
|
||
| /** | ||
| * Get git ancestry using topological order (only works in GitHub Actions environment) | ||
| */ | ||
ankitml marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| getBranchAncestry(branch: string): string[] { | ||
| if (!this.gitCliAvailable) { | ||
| core.warning('Git CLI not available, cannot determine ancestry'); | ||
| return []; | ||
| } | ||
|
|
||
| try { | ||
| const output = execSync(`git log --oneline --topo-order ${branch}`, { | ||
| encoding: 'utf8', | ||
| cwd: process.env.GITHUB_WORKSPACE ?? process.cwd(), | ||
| }); | ||
|
|
||
| return output | ||
| .split('\n') | ||
| .filter((line) => line.trim()) | ||
| .map((line) => line.split(' ')[0]); // Extract SHA from "sha message" | ||
| } catch (error) { | ||
| core.warning(`Failed to get ancestry for branch ${branch}: ${error}`); | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Find previous benchmark commit based on git graph structure | ||
| */ | ||
| findPreviousBenchmark(suites: Benchmark[], currentSha: string, branch: string): Benchmark | null { | ||
| const ancestry = this.getBranchAncestry(branch); | ||
|
|
||
| if (ancestry.length === 0) { | ||
| core.warning(`No ancestry found for branch ${branch}, falling back to execution time ordering`); | ||
| return this.findPreviousByExecutionTime(suites, currentSha); | ||
| } | ||
|
|
||
| // Find position of current commit in ancestry | ||
| const currentIndex = ancestry.indexOf(currentSha); | ||
| if (currentIndex === -1) { | ||
| core.warning(`Current commit ${currentSha} not found in ancestry, falling back to execution time ordering`); | ||
| return this.findPreviousByExecutionTime(suites, currentSha); | ||
| } | ||
|
|
||
| // Look for next commit in ancestry that exists in benchmarks | ||
| for (let i = currentIndex + 1; i < ancestry.length; i++) { | ||
| const previousSha = ancestry[i]; | ||
| const previousBenchmark = suites.find((suite) => suite.commit.id === previousSha); | ||
|
|
||
| if (previousBenchmark) { | ||
| core.debug(`Found previous benchmark: ${previousSha} based on git ancestry`); | ||
| return previousBenchmark; | ||
| } | ||
| } | ||
|
|
||
| // Fallback: no previous commit found in ancestry | ||
| core.debug('No previous benchmark found in git ancestry'); | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Sort benchmark data by commit timestamp (for GitHub Pages visualization) | ||
| * This doesn't need git CLI - just uses the commit timestamps already stored | ||
| */ | ||
| sortByGitOrder(suites: Benchmark[]): Benchmark[] { | ||
| if (suites.length === 0) return suites; | ||
|
|
||
| // For GitHub Pages, we don't have git CLI, so sort by commit timestamp | ||
| // This gives a reasonable approximation of git order | ||
| const sortedSuites = [...suites].sort((a, b) => { | ||
| const timestampA = new Date(a.commit.timestamp ?? '1970-01-01T00:00:00Z').getTime(); | ||
| const timestampB = new Date(b.commit.timestamp ?? '1970-01-01T00:00:00Z').getTime(); | ||
| return timestampA - timestampB; | ||
| }); | ||
|
|
||
| core.debug('Sorted benchmarks by commit timestamp (GitHub Pages mode)'); | ||
| return sortedSuites; | ||
| } | ||
|
|
||
| /** | ||
| * Advanced sorting using git CLI (only for GitHub Actions) | ||
| */ | ||
| sortByGitOrderWithCLI(suites: Benchmark[]): Benchmark[] { | ||
| if (!this.gitCliAvailable) { | ||
| return this.sortByGitOrder(suites); | ||
| } | ||
|
|
||
| if (suites.length === 0) return suites; | ||
|
|
||
| // Create a map of SHA to benchmark for quick lookup | ||
| const benchmarkMap = new Map<string, Benchmark>(); | ||
| for (const suite of suites) { | ||
| benchmarkMap.set(suite.commit.id, suite); | ||
| } | ||
|
|
||
| // Get ancestry from all commits (use the branch of the first commit) | ||
| const firstSuite = suites[0]; | ||
| const branch = this.detectBranchForCommit(firstSuite.commit.id); | ||
| const ancestry = this.getBranchAncestry(branch); | ||
ankitml marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (ancestry.length === 0) { | ||
| core.warning('Could not determine git ancestry, falling back to timestamp sort'); | ||
| return this.sortByGitOrder(suites); | ||
| } | ||
|
|
||
| // Sort benchmarks according to git ancestry | ||
| const sortedSuites: Benchmark[] = []; | ||
| for (const sha of ancestry) { | ||
| const benchmark = benchmarkMap.get(sha); | ||
| if (benchmark) { | ||
| sortedSuites.push(benchmark); | ||
| } | ||
| } | ||
|
|
||
| // Add any benchmarks not found in ancestry (shouldn't happen, but be safe) | ||
| for (const suite of suites) { | ||
| if (!sortedSuites.includes(suite)) { | ||
| sortedSuites.push(suite); | ||
| } | ||
| } | ||
ankitml marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| core.debug(`Sorted ${sortedSuites.length} benchmarks using git CLI ancestry`); | ||
| return sortedSuites; | ||
| } | ||
|
|
||
| /** | ||
| * Fallback method: find previous by execution time (original logic) | ||
| */ | ||
| private findPreviousByExecutionTime(suites: Benchmark[], currentSha: string): Benchmark | null { | ||
| for (const suite of [...suites].reverse()) { | ||
| if (suite.commit.id !== currentSha) { | ||
| return suite; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Attempt to detect which branch a commit belongs to | ||
| */ | ||
| private detectBranchForCommit(sha: string): string { | ||
| if (!this.gitCliAvailable) { | ||
| return 'main'; // Default for GitHub Pages | ||
| } | ||
|
|
||
| try { | ||
| // Try to find if commit is on current branch first | ||
| const currentBranch = this.getCurrentBranch(); | ||
| const output = execSync(`git branch --contains ${sha}`, { | ||
| encoding: 'utf8', | ||
| cwd: process.env.GITHUB_WORKSPACE ?? process.cwd(), | ||
| }); | ||
|
|
||
| if (output.includes(currentBranch)) { | ||
| return currentBranch; | ||
| } | ||
|
|
||
| // Default to main if we can't determine | ||
| return 'main'; | ||
| } catch (error) { | ||
| core.warning(`Could not detect branch for commit ${sha}, defaulting to 'main'`); | ||
| return 'main'; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.