diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index 47d8245..b30fca7 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -12,6 +12,7 @@ import { } from "../github/context"; import { GITEA_SERVER_URL } from "../github/api/config"; import { checkAndDeleteEmptyBranch } from "../github/operations/branch-cleanup"; +import { branchHasChanges, fetchBranch, branchExists, remoteBranchExists } from "../github/utils/local-git"; async function run() { try { @@ -105,32 +106,71 @@ async function run() { const containsPRUrl = currentBody.match(prUrlPattern); if (!containsPRUrl) { - // Use direct SHA comparison for all Git platforms - console.log("Using SHA comparison for PR link check"); + // Check if we're using Gitea or GitHub + const giteaApiUrl = process.env.GITEA_API_URL?.trim(); + const isGitea = giteaApiUrl && + giteaApiUrl !== "" && + !giteaApiUrl.includes("api.github.com") && + !giteaApiUrl.includes("github.com"); - try { - // Get the branch info to see if it exists and has commits - const branchResponse = await client.api.getBranch( - owner, - repo, - claudeBranch, - ); + if (isGitea) { + // Use local git commands for Gitea + console.log("Using local git commands for PR link check (Gitea mode)"); - // Get base branch info for comparison - const baseResponse = await client.api.getBranch( - owner, - repo, - baseBranch, - ); + try { + // Fetch latest changes from remote + await fetchBranch(claudeBranch); + await fetchBranch(baseBranch); - const branchSha = branchResponse.data.commit.sha; - const baseSha = baseResponse.data.commit.sha; + // Check if branch exists and has changes + const { hasChanges, branchSha, baseSha } = await branchHasChanges(claudeBranch, baseBranch); - // If SHAs are different, assume there are changes and add PR link - if (branchSha !== baseSha) { - console.log( - `Branch ${claudeBranch} appears to have changes (different SHA from base)`, - ); + if (branchSha && baseSha) { + if (hasChanges) { + console.log( + `Branch ${claudeBranch} appears to have changes (different SHA from base)`, + ); + const entityType = context.isPR ? "PR" : "Issue"; + const prTitle = encodeURIComponent( + `${entityType} #${context.entityNumber}: Changes from Claude`, + ); + const prBody = encodeURIComponent( + `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, + ); + const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; + prLink = `\n[Create a PR](${prUrl})`; + } else { + console.log( + `Branch ${claudeBranch} has same SHA as base, no PR link needed`, + ); + } + } else { + // If we can't get SHAs, check if branch exists at all + const localExists = await branchExists(claudeBranch); + const remoteExists = await remoteBranchExists(claudeBranch); + + if (localExists || remoteExists) { + console.log(`Branch ${claudeBranch} exists but SHA comparison failed, adding PR link to be safe`); + const entityType = context.isPR ? "PR" : "Issue"; + const prTitle = encodeURIComponent( + `${entityType} #${context.entityNumber}: Changes from Claude`, + ); + const prBody = encodeURIComponent( + `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, + ); + const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; + prLink = `\n[Create a PR](${prUrl})`; + } else { + console.log( + `Branch ${claudeBranch} does not exist yet - no PR link needed`, + ); + prLink = ""; + } + } + } catch (error: any) { + console.error("Error checking branch with git commands:", error); + // For errors, add PR link to be safe + console.log("Adding PR link as fallback due to git command error"); const entityType = context.isPR ? "PR" : "Issue"; const prTitle = encodeURIComponent( `${entityType} #${context.entityNumber}: Changes from Claude`, @@ -140,33 +180,71 @@ async function run() { ); const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; prLink = `\n[Create a PR](${prUrl})`; - } else { - console.log( - `Branch ${claudeBranch} has same SHA as base, no PR link needed`, - ); } - } catch (error: any) { - console.error("Error checking branch:", error); + } else { + // Use API calls for GitHub + console.log("Using API calls for PR link check (GitHub mode)"); - // Handle 404 specifically - branch doesn't exist - if (error.status === 404) { - console.log( - `Branch ${claudeBranch} does not exist yet - no PR link needed`, + try { + // Get the branch info to see if it exists and has commits + const branchResponse = await client.api.getBranch( + owner, + repo, + claudeBranch, ); - // Don't add PR link since branch doesn't exist - prLink = ""; - } else { - // For other errors, add PR link to be safe - console.log("Adding PR link as fallback due to non-404 error"); - const entityType = context.isPR ? "PR" : "Issue"; - const prTitle = encodeURIComponent( - `${entityType} #${context.entityNumber}: Changes from Claude`, + + // Get base branch info for comparison + const baseResponse = await client.api.getBranch( + owner, + repo, + baseBranch, ); - const prBody = encodeURIComponent( - `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, - ); - const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; - prLink = `\n[Create a PR](${prUrl})`; + + const branchSha = branchResponse.data.commit.sha; + const baseSha = baseResponse.data.commit.sha; + + // If SHAs are different, assume there are changes and add PR link + if (branchSha !== baseSha) { + console.log( + `Branch ${claudeBranch} appears to have changes (different SHA from base)`, + ); + const entityType = context.isPR ? "PR" : "Issue"; + const prTitle = encodeURIComponent( + `${entityType} #${context.entityNumber}: Changes from Claude`, + ); + const prBody = encodeURIComponent( + `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, + ); + const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; + prLink = `\n[Create a PR](${prUrl})`; + } else { + console.log( + `Branch ${claudeBranch} has same SHA as base, no PR link needed`, + ); + } + } catch (error: any) { + console.error("Error checking branch:", error); + + // Handle 404 specifically - branch doesn't exist + if (error.status === 404) { + console.log( + `Branch ${claudeBranch} does not exist yet - no PR link needed`, + ); + // Don't add PR link since branch doesn't exist + prLink = ""; + } else { + // For other errors, add PR link to be safe + console.log("Adding PR link as fallback due to non-404 error"); + const entityType = context.isPR ? "PR" : "Issue"; + const prTitle = encodeURIComponent( + `${entityType} #${context.entityNumber}: Changes from Claude`, + ); + const prBody = encodeURIComponent( + `This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`, + ); + const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`; + prLink = `\n[Create a PR](${prUrl})`; + } } } } diff --git a/src/github/operations/branch-cleanup.ts b/src/github/operations/branch-cleanup.ts index fc20989..4280145 100644 --- a/src/github/operations/branch-cleanup.ts +++ b/src/github/operations/branch-cleanup.ts @@ -1,5 +1,6 @@ import type { GitHubClient } from "../api/client"; import { GITEA_SERVER_URL } from "../api/config"; +import { branchHasChanges, fetchBranch, branchExists, remoteBranchExists } from "../utils/local-git"; export async function checkAndDeleteEmptyBranch( client: GitHubClient, @@ -12,51 +13,108 @@ export async function checkAndDeleteEmptyBranch( let shouldDeleteBranch = false; if (claudeBranch) { - // Use direct SHA comparison for both GitHub and Gitea - console.log("Using SHA comparison for branch check"); + // Check if we're using Gitea or GitHub + const giteaApiUrl = process.env.GITEA_API_URL?.trim(); + const isGitea = giteaApiUrl && + giteaApiUrl !== "" && + !giteaApiUrl.includes("api.github.com") && + !giteaApiUrl.includes("github.com"); - try { - // Get the branch info to see if it exists and has commits - const branchResponse = await client.api.getBranch( - owner, - repo, - claudeBranch, - ); + if (isGitea) { + // Use local git operations for Gitea + console.log("Using local git commands for branch check (Gitea mode)"); - // Get base branch info for comparison - const baseResponse = await client.api.getBranch(owner, repo, baseBranch); + try { + // Fetch latest changes from remote + await fetchBranch(claudeBranch); + await fetchBranch(baseBranch); - const branchSha = branchResponse.data.commit.sha; - const baseSha = baseResponse.data.commit.sha; + // Check if branch exists and has changes + const { hasChanges, branchSha, baseSha } = await branchHasChanges(claudeBranch, baseBranch); - // If SHAs are different, assume there are commits - if (branchSha !== baseSha) { - console.log( - `Branch ${claudeBranch} appears to have commits (different SHA from base)`, - ); + if (branchSha && baseSha) { + if (hasChanges) { + console.log( + `Branch ${claudeBranch} appears to have commits (different SHA from base)`, + ); + const branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; + branchLink = `\n[View branch](${branchUrl})`; + } else { + console.log( + `Branch ${claudeBranch} has same SHA as base, marking for deletion`, + ); + shouldDeleteBranch = true; + } + } else { + // If we can't get SHAs, check if branch exists at all + const localExists = await branchExists(claudeBranch); + const remoteExists = await remoteBranchExists(claudeBranch); + + if (localExists || remoteExists) { + console.log(`Branch ${claudeBranch} exists but SHA comparison failed, assuming it has commits`); + const branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; + branchLink = `\n[View branch](${branchUrl})`; + } else { + console.log( + `Branch ${claudeBranch} does not exist yet - this is normal during workflow`, + ); + branchLink = ""; + } + } + } catch (error: any) { + console.error("Error checking branch with git commands:", error); + // For errors, assume the branch has commits to be safe + console.log("Assuming branch exists due to git command error"); const branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; branchLink = `\n[View branch](${branchUrl})`; - } else { - console.log( - `Branch ${claudeBranch} has same SHA as base, marking for deletion`, - ); - shouldDeleteBranch = true; } - } catch (error: any) { - console.error("Error checking branch:", error); + } else { + // Use API calls for GitHub + console.log("Using API calls for branch check (GitHub mode)"); - // Handle 404 specifically - branch doesn't exist - if (error.status === 404) { - console.log( - `Branch ${claudeBranch} does not exist yet - this is normal during workflow`, + try { + // Get the branch info to see if it exists and has commits + const branchResponse = await client.api.getBranch( + owner, + repo, + claudeBranch, ); - // Don't add branch link since branch doesn't exist - branchLink = ""; - } else { - // For other errors, assume the branch has commits to be safe - console.log("Assuming branch exists due to non-404 error"); - const branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; - branchLink = `\n[View branch](${branchUrl})`; + + // Get base branch info for comparison + const baseResponse = await client.api.getBranch(owner, repo, baseBranch); + + const branchSha = branchResponse.data.commit.sha; + const baseSha = baseResponse.data.commit.sha; + + // If SHAs are different, assume there are commits + if (branchSha !== baseSha) { + console.log( + `Branch ${claudeBranch} appears to have commits (different SHA from base)`, + ); + const branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; + branchLink = `\n[View branch](${branchUrl})`; + } else { + console.log( + `Branch ${claudeBranch} has same SHA as base, marking for deletion`, + ); + shouldDeleteBranch = true; + } + } catch (error: any) { + console.error("Error checking branch:", error); + + // Handle 404 specifically - branch doesn't exist + if (error.status === 404) { + console.log( + `Branch ${claudeBranch} does not exist yet - this is normal during workflow`, + ); + // Don't add branch link since branch doesn't exist + branchLink = ""; + } else { + // For other errors, assume the branch has commits to be safe + console.log("Assuming branch exists due to non-404 error"); + const branchUrl = `${GITEA_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`; + branchLink = `\n[View branch](${branchUrl})`; + } } } } diff --git a/src/github/utils/local-git.ts b/src/github/utils/local-git.ts new file mode 100644 index 0000000..551831e --- /dev/null +++ b/src/github/utils/local-git.ts @@ -0,0 +1,84 @@ +#!/usr/bin/env bun + +import { $ } from "bun"; + +/** + * Check if a branch exists locally using git commands + */ +export async function branchExists(branchName: string): Promise { + try { + await $`git show-ref --verify --quiet refs/heads/${branchName}`; + return true; + } catch { + return false; + } +} + +/** + * Check if a remote branch exists using git commands + */ +export async function remoteBranchExists(branchName: string): Promise { + try { + await $`git show-ref --verify --quiet refs/remotes/origin/${branchName}`; + return true; + } catch { + return false; + } +} + +/** + * Get the SHA of a branch using git commands + */ +export async function getBranchSha(branchName: string): Promise { + try { + // Try local branch first + if (await branchExists(branchName)) { + const result = await $`git rev-parse refs/heads/${branchName}`; + return result.text().trim(); + } + + // Try remote branch if local doesn't exist + if (await remoteBranchExists(branchName)) { + const result = await $`git rev-parse refs/remotes/origin/${branchName}`; + return result.text().trim(); + } + + return null; + } catch (error) { + console.error(`Error getting SHA for branch ${branchName}:`, error); + return null; + } +} + +/** + * Check if a branch has commits different from base branch + */ +export async function branchHasChanges(branchName: string, baseBranch: string): Promise<{ hasChanges: boolean; branchSha: string | null; baseSha: string | null }> { + try { + const branchSha = await getBranchSha(branchName); + const baseSha = await getBranchSha(baseBranch); + + if (!branchSha || !baseSha) { + return { hasChanges: false, branchSha, baseSha }; + } + + const hasChanges = branchSha !== baseSha; + return { hasChanges, branchSha, baseSha }; + } catch (error) { + console.error(`Error comparing branches ${branchName} and ${baseBranch}:`, error); + return { hasChanges: false, branchSha: null, baseSha: null }; + } +} + +/** + * Fetch latest changes from remote to ensure we have up-to-date branch info + */ +export async function fetchBranch(branchName: string): Promise { + try { + await $`git fetch origin ${branchName}`; + return true; + } catch (error) { + console.log(`Could not fetch branch ${branchName} from remote (may not exist yet)`); + return false; + } +} \ No newline at end of file