Browse Source

bug-fixes

Nostr-Signature: 32794e047f06902ad610f918834efb113f41eace26a53a3f0fad083b9d8323dc 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 3859f0de3de0f8a742b6fbe7709c5a5625f4d5612a936fd81f38a7e1231ee810b50a69c1ed5d23c8a6670b4cbc9ea3d4bd39d6fa9e6207802f45995689b924a9
main
Silberengel 2 weeks ago
parent
commit
a949fe6641
  1. 2
      nostr/commit-signatures.jsonl
  2. 49
      src/lib/services/git/announcement-manager.ts
  3. 192
      src/lib/services/git/repo-manager.ts
  4. 31
      src/routes/api/repos/[npub]/[repo]/clone/+server.ts
  5. 20
      src/routes/repos/[npub]/[repo]/services/repo-operations.ts

2
nostr/commit-signatures.jsonl

@ -118,3 +118,5 @@
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772264490,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","polling update"]],"content":"Signed commit: polling update","id":"42c1a2a63a4568c65d82d78701451b3b4363bdf9c8c57e804535b5f3f0d7b6fc","sig":"8e5f32ecb79da876ac41eba04c3b1541b21d039ae50d1b9fefa630d35f31c97dd29af64e4b695742fa7d4eaec17db8f4a066b4db99ce628aed596971975d4a87"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772264490,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","polling update"]],"content":"Signed commit: polling update","id":"42c1a2a63a4568c65d82d78701451b3b4363bdf9c8c57e804535b5f3f0d7b6fc","sig":"8e5f32ecb79da876ac41eba04c3b1541b21d039ae50d1b9fefa630d35f31c97dd29af64e4b695742fa7d4eaec17db8f4a066b4db99ce628aed596971975d4a87"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772267611,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor API"]],"content":"Signed commit: refactor API","id":"934f8809638cea0bc7b8158fca959bc60880e0cae9ab8ff653687313adcd2f57","sig":"c9d8e5b821ae8182f8d39599c50fd0a4db6040ead1d8d83730a608a1d94d5078770a6ccbfc525a98691e98fabd9f9d24f0298680fb564c6b76c2f34bed9889b5"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772267611,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor API"]],"content":"Signed commit: refactor API","id":"934f8809638cea0bc7b8158fca959bc60880e0cae9ab8ff653687313adcd2f57","sig":"c9d8e5b821ae8182f8d39599c50fd0a4db6040ead1d8d83730a608a1d94d5078770a6ccbfc525a98691e98fabd9f9d24f0298680fb564c6b76c2f34bed9889b5"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772269280,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","api refactor part 2"]],"content":"Signed commit: api refactor part 2","id":"ece894a60057bba46ebd4ac0dca2aca55ffce05e44671fe07b29516809fc86f6","sig":"176706a271659834e441ea5eab4bb1480667dad4468fe8315803284f4a183debf595523dd33d0d3cabe0c35013f4a72b9169b5f10afefaf8a82a721d8b0f3b08"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772269280,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","api refactor part 2"]],"content":"Signed commit: api refactor part 2","id":"ece894a60057bba46ebd4ac0dca2aca55ffce05e44671fe07b29516809fc86f6","sig":"176706a271659834e441ea5eab4bb1480667dad4468fe8315803284f4a183debf595523dd33d0d3cabe0c35013f4a72b9169b5f10afefaf8a82a721d8b0f3b08"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772270859,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes and fallback relay"]],"content":"Signed commit: bug-fixes and fallback relay","id":"1d85d0c5e1451c90bca5d59e08043f29adeaad4db4ac5495c8e9a4247775780f","sig":"a1960b76c78db9f64dad20378d26f500ffc09f1f6d137314db548470202712222a1d391f682146ba281fd23355c574fcbb260310db61b3458bba3dec0c724a18"}
{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772271656,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"f4a5e0d3e2aa7d0d99803f26008ab68e40551e36362bb6d04acf639c5b78d959","sig":"59da9e59a6fb5648f4c889e0045b571e0d2d66a555100d60dec373455309a640bea89e4bb3a42a0e502aa4d2091e4b698203721e79b346ff30e6b2bcdc5f48b3"}

49
src/lib/services/git/announcement-manager.ts

@ -190,7 +190,7 @@ export class AnnouncementManager {
* Ensure announcement event is saved to nostr/repo-events.jsonl in the repository * Ensure announcement event is saved to nostr/repo-events.jsonl in the repository
* Only saves if not already present (avoids redundant entries) * Only saves if not already present (avoids redundant entries)
*/ */
async ensureAnnouncementInRepo(repoPath: string, event: NostrEvent, selfTransferEvent?: NostrEvent): Promise<void> { async ensureAnnouncementInRepo(repoPath: string, event: NostrEvent, selfTransferEvent?: NostrEvent, preferredDefaultBranch?: string): Promise<void> {
let isEmpty = false; let isEmpty = false;
try { try {
// Create a temporary working directory // Create a temporary working directory
@ -218,8 +218,8 @@ export class AnnouncementManager {
if (isEmpty) { if (isEmpty) {
// Repo is empty - initialize worktree and create initial branch // Repo is empty - initialize worktree and create initial branch
// Use default branch from environment or try 'main' first, then 'master' // Use preferred branch, then environment, then try 'main' first, then 'master'
const defaultBranch = process.env.DEFAULT_BRANCH || 'main'; const defaultBranch = preferredDefaultBranch || process.env.DEFAULT_BRANCH || 'main';
// Initialize git in workdir // Initialize git in workdir
workGit = simpleGit(workDir); workGit = simpleGit(workDir);
@ -233,6 +233,45 @@ export class AnnouncementManager {
await git.clone(repoPath, workDir); await git.clone(repoPath, workDir);
// Create workGit instance after clone // Create workGit instance after clone
workGit = simpleGit(workDir); workGit = simpleGit(workDir);
// Determine the correct default branch to commit to
let targetBranch = preferredDefaultBranch || process.env.DEFAULT_BRANCH || 'main';
// Check if the preferred branch exists, if not try to find the actual default branch
try {
const branches = await workGit.branch(['-a']);
const branchList = branches.all
.map(b => b.replace(/^remotes\/origin\//, '').replace(/^remotes\//, '').replace(/^refs\/heads\//, ''))
.filter(b => b && !b.includes('HEAD'));
// If preferred branch exists, use it; otherwise try main/master, then first branch
if (preferredDefaultBranch && branchList.includes(preferredDefaultBranch)) {
targetBranch = preferredDefaultBranch;
} else if (branchList.includes('main')) {
targetBranch = 'main';
} else if (branchList.includes('master')) {
targetBranch = 'master';
} else if (branchList.length > 0) {
targetBranch = branchList[0];
}
// Checkout the target branch
try {
await workGit.checkout(targetBranch);
logger.debug({ repoPath, targetBranch }, 'Checked out target branch for announcement commit');
} catch (checkoutErr) {
// If checkout fails, try to create the branch
logger.debug({ repoPath, targetBranch, error: checkoutErr }, 'Failed to checkout branch, will try to create it');
try {
await workGit.checkout(['-b', targetBranch]);
logger.debug({ repoPath, targetBranch }, 'Created and checked out new branch for announcement commit');
} catch (createErr) {
logger.warn({ repoPath, targetBranch, error: createErr }, 'Failed to create branch, will commit to current branch');
}
}
} catch (branchErr) {
logger.warn({ repoPath, error: branchErr }, 'Failed to determine or checkout branch, will commit to current branch');
}
} }
// Check if announcement already exists in nostr/repo-events.jsonl // Check if announcement already exists in nostr/repo-events.jsonl
@ -316,8 +355,8 @@ export class AnnouncementManager {
logger.info({ repoPath, commitHash, objectCount: objectEntries.length }, 'Objects verified after commit'); logger.info({ repoPath, commitHash, objectCount: objectEntries.length }, 'Objects verified after commit');
// Push back to bare repo // Push back to bare repo
// Use default branch from environment or try 'main' first, then 'master' // Use preferred branch, then environment, then try 'main' first, then 'master'
const defaultBranch = process.env.DEFAULT_BRANCH || 'main'; const defaultBranch = preferredDefaultBranch || process.env.DEFAULT_BRANCH || 'main';
if (isEmpty) { if (isEmpty) {
// For empty repos, directly copy objects and update refs (more reliable than push) // For empty repos, directly copy objects and update refs (more reliable than push)

192
src/lib/services/git/repo-manager.ts

@ -71,8 +71,9 @@ export class RepoManager {
* @param selfTransferEvent - Optional self-transfer event to include in initial commit * @param selfTransferEvent - Optional self-transfer event to include in initial commit
* @param isExistingRepo - Whether this is an existing repo being added to the server * @param isExistingRepo - Whether this is an existing repo being added to the server
* @param allowMissingDomainUrl - In development, allow provisioning even if domain URL isn't in announcement * @param allowMissingDomainUrl - In development, allow provisioning even if domain URL isn't in announcement
* @param preferredDefaultBranch - Preferred default branch name (e.g., from user settings)
*/ */
async provisionRepo(event: NostrEvent, selfTransferEvent?: NostrEvent, isExistingRepo: boolean = false, allowMissingDomainUrl: boolean = false): Promise<void> { async provisionRepo(event: NostrEvent, selfTransferEvent?: NostrEvent, isExistingRepo: boolean = false, allowMissingDomainUrl: boolean = false, preferredDefaultBranch?: string): Promise<void> {
const cloneUrls = this.urlParser.extractCloneUrls(event); const cloneUrls = this.urlParser.extractCloneUrls(event);
let domainUrl = cloneUrls.find(url => url.includes(this.domain)); let domainUrl = cloneUrls.find(url => url.includes(this.domain));
@ -161,16 +162,22 @@ export class RepoManager {
if (!hasBranches) { if (!hasBranches) {
// No branches exist - create initial branch and README (which includes announcement) // No branches exist - create initial branch and README (which includes announcement)
await this.createInitialBranchAndReadme(repoPath.fullPath, repoPath.npub, repoPath.repoName, event); await this.createInitialBranchAndReadme(repoPath.fullPath, repoPath.npub, repoPath.repoName, event, preferredDefaultBranch);
} else { } else {
// Branches exist (from sync) - ensure announcement is committed to the default branch // Branches exist (from sync) - ensure README exists and announcement is committed to the default branch
// Check if README exists, and create it if missing
await this.ensureReadmeExists(repoPath.fullPath, repoPath.npub, repoPath.repoName, event, preferredDefaultBranch);
// Ensure announcement is committed to the default branch
// This must happen after syncing so we can commit it to the existing default branch // This must happen after syncing so we can commit it to the existing default branch
// Non-blocking: fire and forget - we have the announcement from relays, so this is just for offline papertrail // Make it blocking so the commit is complete before returning
this.announcementManager.ensureAnnouncementInRepo(repoPath.fullPath, event, selfTransferEvent) try {
.catch((err) => { await this.announcementManager.ensureAnnouncementInRepo(repoPath.fullPath, event, selfTransferEvent, preferredDefaultBranch);
logger.warn({ error: err, repoPath: repoPath.fullPath, eventId: event.id }, logger.info({ repoPath: repoPath.fullPath, eventId: event.id }, 'Announcement committed to repository');
'Failed to save announcement to repo (non-blocking, announcement available from relays)'); } catch (err) {
}); logger.warn({ error: err, repoPath: repoPath.fullPath, eventId: event.id },
'Failed to save announcement to repo (announcement available from relays)');
}
} }
} else { } else {
// For existing repos, check if announcement exists in repo // For existing repos, check if announcement exists in repo
@ -204,12 +211,13 @@ export class RepoManager {
repoPath: string, repoPath: string,
npub: string, npub: string,
repoName: string, repoName: string,
announcementEvent: NostrEvent announcementEvent: NostrEvent,
preferredDefaultBranch?: string
): Promise<void> { ): Promise<void> {
try { try {
// Get default branch from git config, environment, or use 'master' // Get default branch from preferred branch, git config, environment, or use 'master'
// Check git's init.defaultBranch config first (respects user's git settings) // Check preferred branch first (from user settings), then git's init.defaultBranch config
let defaultBranch = process.env.DEFAULT_BRANCH || 'master'; let defaultBranch = preferredDefaultBranch || process.env.DEFAULT_BRANCH || 'master';
try { try {
const git = simpleGit(); const git = simpleGit();
@ -233,17 +241,24 @@ export class RepoManager {
// If branches exist, check if one matches our default branch preference // If branches exist, check if one matches our default branch preference
if (existingBranches.length > 0) { if (existingBranches.length > 0) {
// Prefer existing branches that match common defaults // If we have a preferred branch and it exists, use it
const preferredBranches = [defaultBranch, 'main', 'master', 'dev']; if (preferredDefaultBranch && existingBranches.includes(preferredDefaultBranch)) {
for (const preferred of preferredBranches) { defaultBranch = preferredDefaultBranch;
if (existingBranches.includes(preferred)) { } else {
defaultBranch = preferred; // Prefer existing branches that match common defaults, prioritizing preferred branch
break; const preferredBranches = preferredDefaultBranch
? [preferredDefaultBranch, defaultBranch, 'main', 'master', 'dev']
: [defaultBranch, 'main', 'master', 'dev'];
for (const preferred of preferredBranches) {
if (existingBranches.includes(preferred)) {
defaultBranch = preferred;
break;
}
}
// If no match, use the first existing branch
if (!existingBranches.includes(defaultBranch)) {
defaultBranch = existingBranches[0];
} }
}
// If no match, use the first existing branch
if (!existingBranches.includes(defaultBranch)) {
defaultBranch = existingBranches[0];
} }
} }
} catch { } catch {
@ -337,6 +352,130 @@ Your commits will all be signed by your Nostr keys and saved to the event files
} }
} }
/**
* Ensure README.md exists in the repository, creating it if missing
* This is called when branches exist from sync but README might be missing
*/
private async ensureReadmeExists(
repoPath: string,
npub: string,
repoName: string,
announcementEvent: NostrEvent,
preferredDefaultBranch?: string
): Promise<void> {
try {
// Get default branch
const { FileManager } = await import('./file-manager.js');
const fileManager = new FileManager(this.repoRoot);
let defaultBranch = preferredDefaultBranch;
if (!defaultBranch) {
try {
defaultBranch = await fileManager.getDefaultBranch(npub, repoName);
} catch {
// If getDefaultBranch fails, try to determine from git
const repoGit = simpleGit(repoPath);
try {
const branches = await repoGit.branch(['-a']);
const branchList = branches.all
.map(b => b.replace(/^remotes\/origin\//, '').replace(/^remotes\//, '').replace(/^refs\/heads\//, ''))
.filter(b => b && !b.includes('HEAD'));
if (branchList.length > 0) {
// Prefer preferred branch, then main, then master, then first branch
if (preferredDefaultBranch && branchList.includes(preferredDefaultBranch)) {
defaultBranch = preferredDefaultBranch;
} else if (branchList.includes('main')) {
defaultBranch = 'main';
} else if (branchList.includes('master')) {
defaultBranch = 'master';
} else {
defaultBranch = branchList[0];
}
}
} catch {
defaultBranch = preferredDefaultBranch || process.env.DEFAULT_BRANCH || 'master';
}
}
}
if (!defaultBranch) {
defaultBranch = preferredDefaultBranch || process.env.DEFAULT_BRANCH || 'master';
}
// Check if README.md already exists
const workDir = await fileManager.getWorktree(repoPath, defaultBranch, npub, repoName);
const { readFile: readFileFs, writeFile: writeFileFs } = await import('fs/promises');
const { join } = await import('path');
const readmePath = join(workDir, 'README.md');
try {
await readFileFs(readmePath, 'utf-8');
// README exists, nothing to do
await fileManager.removeWorktree(repoPath, workDir);
logger.debug({ npub, repoName, branch: defaultBranch }, 'README.md already exists');
return;
} catch {
// README doesn't exist, create it
}
// Get repo name from d-tag or use repoName from path
const dTag = announcementEvent.tags.find(t => t[0] === 'd')?.[1] || repoName;
// Get name tag for README title, fallback to d-tag
const nameTag = announcementEvent.tags.find(t => t[0] === 'name')?.[1] || dTag;
// Get author info from user profile (fetch from relays)
const { fetchUserProfile, extractProfileData, getUserName, getUserEmail } = await import('../../utils/user-profile.js');
const { nip19 } = await import('nostr-tools');
const { DEFAULT_NOSTR_RELAYS } = await import('../../config.js');
const userNpub = nip19.npubEncode(announcementEvent.pubkey);
const profileEvent = await fetchUserProfile(announcementEvent.pubkey, DEFAULT_NOSTR_RELAYS);
const profile = extractProfileData(profileEvent);
const authorName = getUserName(profile, announcementEvent.pubkey, userNpub);
const authorEmail = getUserEmail(profile, announcementEvent.pubkey, userNpub);
// Create README.md content
const readmeContent = `# ${nameTag}
Welcome to your new GitRepublic repo.
You can use this read-me file to explain the purpose of this repo to everyone who looks at it. You can also make a ReadMe.adoc file and delete this one, if you prefer. GitRepublic supports both markups.
Your commits will all be signed by your Nostr keys and saved to the event files in the ./nostr folder.
`;
// Write README.md
await writeFileFs(readmePath, readmeContent, 'utf-8');
// Stage and commit README.md
const workGit = simpleGit(workDir);
await workGit.add('README.md');
// Configure git user.name and user.email for this repository
try {
await workGit.addConfig('user.name', 'GitRepublic', false, 'local');
await workGit.addConfig('user.email', 'gitrepublic@gitrepublic.web', false, 'local');
} catch (configError) {
logger.warn({ repoPath, npub, repoName, error: configError }, 'Failed to set git config');
}
// Commit README.md
await workGit.commit('Add README.md', ['README.md'], {
'--author': `${authorName} <${authorEmail}>`
});
// Clean up worktree
await fileManager.removeWorktree(repoPath, workDir);
logger.info({ npub, repoName, branch: defaultBranch }, 'Created README.md in existing repository');
} catch (err) {
// Log but don't fail - README creation is nice-to-have
const sanitizedErr = sanitizeError(err);
logger.warn({ error: sanitizedErr, repoPath, npub, repoName }, 'Failed to ensure README exists, continuing anyway');
}
}
/** /**
* Sync repository from multiple remote URLs (parallelized for efficiency) * Sync repository from multiple remote URLs (parallelized for efficiency)
*/ */
@ -370,7 +509,8 @@ Your commits will all be signed by your Nostr keys and saved to the event files
async fetchRepoOnDemand( async fetchRepoOnDemand(
npub: string, npub: string,
repoName: string, repoName: string,
announcementEvent?: NostrEvent announcementEvent?: NostrEvent,
preferredDefaultBranch?: string
): Promise<{ success: boolean; needsAnnouncement?: boolean; announcement?: NostrEvent; error?: string; cloneUrls?: string[]; remoteUrls?: string[] }> { ): Promise<{ success: boolean; needsAnnouncement?: boolean; announcement?: NostrEvent; error?: string; cloneUrls?: string[]; remoteUrls?: string[] }> {
const repoPath = join(this.repoRoot, npub, `${repoName}.git`); const repoPath = join(this.repoRoot, npub, `${repoName}.git`);
@ -518,7 +658,7 @@ Your commits will all be signed by your Nostr keys and saved to the event files
// No remote URLs - this is an empty repo, provision it instead // No remote URLs - this is an empty repo, provision it instead
logger.info({ npub, repoName, cloneUrls, announcementEventId: announcementEvent.id }, 'No remote clone URLs found - provisioning empty repository'); logger.info({ npub, repoName, cloneUrls, announcementEventId: announcementEvent.id }, 'No remote clone URLs found - provisioning empty repository');
try { try {
await this.provisionRepo(announcementEvent, undefined, false); await this.provisionRepo(announcementEvent, undefined, false, false, preferredDefaultBranch);
logger.info({ npub, repoName }, 'Empty repository provisioned successfully'); logger.info({ npub, repoName }, 'Empty repository provisioned successfully');
return { success: true, cloneUrls, remoteUrls: [] }; return { success: true, cloneUrls, remoteUrls: [] };
} catch (err) { } catch (err) {
@ -539,7 +679,7 @@ Your commits will all be signed by your Nostr keys and saved to the event files
logger.info({ npub, repoName, url: remoteUrls[0] }, 'Localhost URL specified but repo does not exist locally - provisioning instead'); logger.info({ npub, repoName, url: remoteUrls[0] }, 'Localhost URL specified but repo does not exist locally - provisioning instead');
try { try {
// In development, allow provisioning even if domain URL isn't in announcement // In development, allow provisioning even if domain URL isn't in announcement
await this.provisionRepo(announcementEvent, undefined, false, true); await this.provisionRepo(announcementEvent, undefined, false, true, preferredDefaultBranch);
logger.info({ npub, repoName }, 'Repository provisioned successfully on localhost'); logger.info({ npub, repoName }, 'Repository provisioned successfully on localhost');
return { success: true, cloneUrls, remoteUrls: [] }; return { success: true, cloneUrls, remoteUrls: [] };
} catch (err) { } catch (err) {

31
src/routes/api/repos/[npub]/[repo]/clone/+server.ts

@ -51,6 +51,30 @@ export const POST: RequestHandler = async (event) => {
hasUnlimitedAccess: userLevel ? hasUnlimitedAccess(userLevel.level) : false hasUnlimitedAccess: userLevel ? hasUnlimitedAccess(userLevel.level) : false
}, 'Checking user access level for clone operation'); }, 'Checking user access level for clone operation');
// Extract defaultBranch from request body if present (before body is consumed)
let preferredDefaultBranch: string | undefined;
const contentType = event.request.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
try {
// Clone the request to read body without consuming it
const clonedRequest = event.request.clone();
const bodyText = await clonedRequest.text().catch(() => '');
if (bodyText) {
try {
const body = JSON.parse(bodyText);
if (body.defaultBranch && typeof body.defaultBranch === 'string') {
preferredDefaultBranch = body.defaultBranch;
logger.debug({ preferredDefaultBranch }, 'Extracted defaultBranch from request body');
}
} catch {
// Not valid JSON or missing defaultBranch - continue
}
}
} catch {
// Body reading failed - continue
}
}
// If cache is empty, try to verify from NIP-98 auth header first (doesn't consume body), then proof event in body // If cache is empty, try to verify from NIP-98 auth header first (doesn't consume body), then proof event in body
if (!userLevel || !hasUnlimitedAccess(userLevel.level)) { if (!userLevel || !hasUnlimitedAccess(userLevel.level)) {
let verification: { valid: boolean; error?: string; relay?: string; relayDown?: boolean } | null = null; let verification: { valid: boolean; error?: string; relay?: string; relayDown?: boolean } | null = null;
@ -66,7 +90,6 @@ export const POST: RequestHandler = async (event) => {
// If auth header didn't work, try to get proof event from request body (if content-type is JSON) // If auth header didn't work, try to get proof event from request body (if content-type is JSON)
// Note: This consumes the body, but only if auth header is not present // Note: This consumes the body, but only if auth header is not present
if (!verification) { if (!verification) {
const contentType = event.request.headers.get('content-type') || '';
if (contentType.includes('application/json')) { if (contentType.includes('application/json')) {
try { try {
// Read body only if auth header verification failed // Read body only if auth header verification failed
@ -85,6 +108,10 @@ export const POST: RequestHandler = async (event) => {
logger.warn({ userPubkeyHex: userPubkeyHex.slice(0, 16) + '...' }, 'Invalid proof event in request body'); logger.warn({ userPubkeyHex: userPubkeyHex.slice(0, 16) + '...' }, 'Invalid proof event in request body');
} }
} }
// Also extract defaultBranch if not already extracted
if (!preferredDefaultBranch && body.defaultBranch && typeof body.defaultBranch === 'string') {
preferredDefaultBranch = body.defaultBranch;
}
} catch (parseErr) { } catch (parseErr) {
// Not valid JSON or missing proofEvent - continue // Not valid JSON or missing proofEvent - continue
logger.debug({ error: parseErr }, 'Request body is not valid JSON or missing proofEvent'); logger.debug({ error: parseErr }, 'Request body is not valid JSON or missing proofEvent');
@ -276,7 +303,7 @@ export const POST: RequestHandler = async (event) => {
}, 'Repository announcement clone URLs'); }, 'Repository announcement clone URLs');
// Attempt to clone the repository // Attempt to clone the repository
const result = await repoManager.fetchRepoOnDemand(npub, repo, announcementEvent); const result = await repoManager.fetchRepoOnDemand(npub, repo, announcementEvent, preferredDefaultBranch);
if (!result.success) { if (!result.success) {
if (result.needsAnnouncement) { if (result.needsAnnouncement) {

20
src/routes/repos/[npub]/[repo]/services/repo-operations.ts

@ -193,11 +193,27 @@ export async function cloneRepository(
logger.debug({ error: proofErr }, '[Clone] Failed to create proof event, will rely on cache'); logger.debug({ error: proofErr }, '[Clone] Failed to create proof event, will rely on cache');
} }
// Send clone request with proof event in body (if available) // Get user's default branch preference from settings
const requestBody: { proofEvent?: NostrEvent } = {}; let defaultBranch: string | undefined;
try {
const { settingsStore } = await import('$lib/services/settings-store.js');
const settings = await settingsStore.getSettings();
if (settings.defaultBranch) {
defaultBranch = settings.defaultBranch;
logger.debug({ defaultBranch }, '[Clone] Using default branch from user settings');
}
} catch (settingsErr) {
logger.debug({ error: settingsErr }, '[Clone] Failed to get default branch from settings');
}
// Send clone request with proof event and defaultBranch in body (if available)
const requestBody: { proofEvent?: NostrEvent; defaultBranch?: string } = {};
if (proofEvent) { if (proofEvent) {
requestBody.proofEvent = proofEvent; requestBody.proofEvent = proofEvent;
} }
if (defaultBranch) {
requestBody.defaultBranch = defaultBranch;
}
const data = await apiPost<{ alreadyExists?: boolean }>(`/api/repos/${state.npub}/${state.repo}/clone`, requestBody); const data = await apiPost<{ alreadyExists?: boolean }>(`/api/repos/${state.npub}/${state.repo}/clone`, requestBody);

Loading…
Cancel
Save