Better Hub provides advanced code analysis capabilities including syntax highlighting and merge conflict detection.
POST /api/highlight-code
Generate syntax-highlighted tokens for code display using Shiki.
Source code to highlight (max 500,000 characters)
Filename used to determine language syntax (e.g., “app.ts”, “README.md”)
Response
Array of syntax-highlighted token arrays, one per lineEach token contains:
content: string - The text content
color: string - Hex color code
fontStyle: number - Font style flags
const response = await fetch('/api/highlight-code', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code: `function hello(name: string) {\n return \`Hello, \${name}!\`;\n}`,
filename: 'hello.ts'
})
});
const data = await response.json();
/*
{
tokens: [
[
{ content: "function", color: "#C586C0", fontStyle: 0 },
{ content: " ", color: "#D4D4D4", fontStyle: 0 },
{ content: "hello", color: "#DCDCAA", fontStyle: 0 },
// ... more tokens
],
// ... more lines
]
}
*/
This endpoint has a maximum code length of 500,000 characters. For larger files, consider chunking or using a different approach.
GET /api/merge-conflicts
Analyze merge conflicts between two branches using three-way merge algorithm.
Base branch name (e.g., “main”)
Head branch name. For fork PRs, use format "owner:branch"
Response
SHA of the merge base commit (common ancestor)
Array of files with conflict analysisWhether the file has merge conflicts
Whether conflicts were automatically resolved
Array of merge hunksEach hunk has:
type: "clean" | "conflict"
resolvedLines: string[] - For clean hunks
baseLines: string[] - For conflicts
headLines: string[] - For conflicts
const params = new URLSearchParams({
owner: 'acme',
repo: 'app',
base: 'main',
head: 'feature/auth'
});
const response = await fetch(`/api/merge-conflicts?${params}`);
const data = await response.json();
/*
{
mergeBaseSha: "abc123def456",
baseBranch: "main",
headBranch: "feature/auth",
files: [
{
path: "src/auth.ts",
hasConflicts: true,
autoResolved: false,
hunks: [
{
type: "clean",
resolvedLines: [
"import { User } from './types';",
""
]
},
{
type: "conflict",
baseLines: [
"export function authenticate(token: string) {",
" return validateToken(token);",
"}"
],
headLines: [
"export async function authenticate(token: string) {",
" return await validateTokenAsync(token);",
"}"
]
}
]
},
{
path: "src/utils.ts",
hasConflicts: false,
autoResolved: true,
hunks: [
{
type: "clean",
resolvedLines: [
"export function formatDate(date: Date) {",
" return date.toISOString();",
"}"
]
}
]
}
]
}
*/
This endpoint analyzes up to 30 files per request. For PRs with more changed files, only the first 30 will be analyzed.
Understanding Merge Conflicts
Three-Way Merge Algorithm
The merge conflict analysis uses a three-way merge algorithm that compares:
- Merge Base (Ancestor): The common ancestor commit of both branches
- Base Branch: Your target branch (e.g.,
main)
- Head Branch: Your source branch with changes
Conflict Types
Clean Hunks - No conflicts, automatically merged:
- Changes only in base branch
- Changes only in head branch
- Identical changes in both branches
- New files
Conflict Hunks - Manual resolution required:
- Different changes to the same lines in both branches
- One branch modified, other deleted the same lines
Auto-Resolution
Files are marked as autoResolved: true when all hunks are clean, meaning:
- No manual intervention needed
- Safe to auto-merge
- Changes don’t overlap
Example Use Cases
// Before merging a PR, check for conflicts
const conflicts = await fetch(
`/api/merge-conflicts?owner=acme&repo=app&base=main&head=feature/auth`
).then(r => r.json());
const hasConflicts = conflicts.files.some(f => f.hasConflicts);
if (hasConflicts) {
console.log('Manual resolution required');
conflicts.files
.filter(f => f.hasConflicts)
.forEach(file => {
console.log(`Conflicts in ${file.path}`);
});
} else {
console.log('Safe to merge');
}
Integration Tips
Pre-merge Validation
async function canMergePR(owner: string, repo: string, pr: number) {
// Get PR details first
const prData = await fetchPRDetails(owner, repo, pr);
// Check for conflicts
const conflicts = await fetch(
`/api/merge-conflicts?` +
`owner=${owner}&repo=${repo}&` +
`base=${prData.base}&head=${prData.head}`
).then(r => r.json());
return !conflicts.files.some(f => f.hasConflicts);
}
Conflict Resolution Workflow
async function getConflictResolutionStatus(owner, repo, base, head) {
const result = await fetch(
`/api/merge-conflicts?owner=${owner}&repo=${repo}&base=${base}&head=${head}`
).then(r => r.json());
const stats = {
totalFiles: result.files.length,
filesWithConflicts: result.files.filter(f => f.hasConflicts).length,
autoResolved: result.files.filter(f => f.autoResolved).length,
needsManualReview: result.files.filter(f => !f.autoResolved).length
};
return stats;
}