You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

159 lines
7.1 KiB

/**
* API endpoint for getting commit history
*/
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { fileManager, repoManager, nostrClient } from '$lib/services/service-registry.js';
import { createRepoGetHandler } from '$lib/utils/api-handlers.js';
import type { RepoRequestContext } from '$lib/utils/api-context.js';
import { handleApiError, handleNotFoundError } from '$lib/utils/error-handler.js';
import { KIND } from '$lib/types/nostr.js';
import { join, resolve } from 'path';
import { existsSync } from 'fs';
import { repoCache, RepoCache } from '$lib/services/git/repo-cache.js';
import logger from '$lib/services/logger.js';
import { eventCache } from '$lib/services/nostr/event-cache.js';
import { fetchRepoAnnouncementsWithCache, findRepoAnnouncement } from '$lib/utils/nostr-utils.js';
// Resolve GIT_REPO_ROOT to absolute path (handles both relative and absolute paths)
const repoRootEnv = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT
? process.env.GIT_REPO_ROOT
: '/repos';
const repoRoot = resolve(repoRootEnv);
export const GET: RequestHandler = createRepoGetHandler(
async (context: RepoRequestContext) => {
const repoPath = join(repoRoot, context.npub, `${context.repo}.git`);
// If repo doesn't exist, try to fetch it on-demand
if (!existsSync(repoPath)) {
try {
// Fetch repository announcement from Nostr (case-insensitive) with caching
const allEvents = await fetchRepoAnnouncementsWithCache(nostrClient, context.repoOwnerPubkey, eventCache);
const announcement = findRepoAnnouncement(allEvents, context.repo);
if (announcement) {
// Try API-based fetching first (no cloning)
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(announcement, context.npub, context.repo);
if (apiData && apiData.commits) {
// Return API data directly without cloning
const limit = context.limit || 50;
return json(apiData.commits.slice(0, limit));
}
// API fetch failed - repo is not cloned and API fetch didn't work
throw handleNotFoundError(
'Repository is not cloned locally and could not be fetched via API. Privileged users can clone this repository using the "Clone to Server" button.',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
} else {
throw handleNotFoundError(
'Repository announcement not found in Nostr',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
}
} catch (err) {
// Check if repo was created by another concurrent request
if (existsSync(repoPath)) {
// Repo exists now, clear cache and continue with normal flow
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
} else {
// If fetching fails, return 404
throw handleNotFoundError(
'Repository not found',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
}
}
}
// Double-check repo exists (should be true if we got here)
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository not found',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
}
// Get default branch if not specified
// Normalize 'null' string to undefined (defensive check)
let branch = (context.branch && context.branch !== 'null') ? context.branch : undefined;
if (!branch) {
try {
branch = await fileManager.getDefaultBranch(context.npub, context.repo);
} catch {
branch = 'main'; // Fallback
}
}
const limit = context.limit || 50;
const path = context.path;
try {
const commits = await fileManager.getCommitHistory(context.npub, context.repo, branch, limit, path);
// If repo exists but has no commits (empty repo), try API fallback
if (commits.length === 0) {
logger.debug({ npub: context.npub, repo: context.repo, branch }, 'Repo exists but is empty, attempting API fallback for commits');
try {
const allEvents = await fetchRepoAnnouncementsWithCache(nostrClient, context.repoOwnerPubkey, eventCache);
const announcement = findRepoAnnouncement(allEvents, context.repo);
if (announcement) {
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(announcement, context.npub, context.repo);
if (apiData && apiData.commits && apiData.commits.length > 0) {
logger.info({ npub: context.npub, repo: context.repo, commitCount: apiData.commits.length }, 'Successfully fetched commits via API fallback for empty repo');
return json(apiData.commits.slice(0, limit));
}
}
} catch (apiErr) {
logger.debug({ error: apiErr, npub: context.npub, repo: context.repo }, 'API fallback failed for empty repo, returning empty commits');
}
}
return json(commits);
} catch (err) {
// If error occurs, try API fallback before giving up
logger.debug({ error: err, npub: context.npub, repo: context.repo }, '[Commits] Error getting commit history, attempting API fallback');
try {
const allEvents = await fetchRepoAnnouncementsWithCache(nostrClient, context.repoOwnerPubkey, eventCache);
const announcement = findRepoAnnouncement(allEvents, context.repo);
if (announcement) {
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(announcement, context.npub, context.repo);
if (apiData && apiData.commits && apiData.commits.length > 0) {
logger.info({ npub: context.npub, repo: context.repo, commitCount: apiData.commits.length }, 'Successfully fetched commits via API fallback after error');
return json(apiData.commits.slice(0, limit));
}
}
} catch (apiErr) {
logger.debug({ error: apiErr, npub: context.npub, repo: context.repo }, 'API fallback failed after error');
}
// Log the actual error for debugging
logger.error({ error: err, npub: context.npub, repo: context.repo }, '[Commits] Error getting commit history');
// Check if it's a "not found" error
if (err instanceof Error && err.message.includes('not found')) {
throw handleNotFoundError(
err.message,
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
}
// Otherwise, it's a server error
throw handleApiError(
err,
{ operation: 'getCommits', npub: context.npub, repo: context.repo },
'Failed to get commit history'
);
}
},
{ operation: 'getCommits', requireRepoExists: false, requireRepoAccess: false } // Commits should be publicly accessible for public repos
);