feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(web): add /api/blame endpoint - #1158

Merged
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api
Apr 29, 2026
Merged

feat(web): add /api/blame endpoint#1158
brendan-kellam merged 3 commits into
mainfrom
brendan/file-blame-api

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a getFileBlame git helper (packages/web/src/features/git/getFileBlameApi.ts) that runs git blame --porcelain and parses the output into contiguous line ranges + deduplicated commit metadata.
  • Exposes it as a public REST endpoint GET /api/blame, mirroring the shape and conventions of /api/source.
  • Registers OpenAPI schemas (PublicFileBlameRequest, PublicFileBlameResponse) and adds the endpoint to the API Reference nav.
  • Adds a new user.fetched_file_blame audit event (mirroring user.fetched_file_source).

Response shape

{
ranges: Array<{hash: string;startLine: number;lineCount: number}>;
commits: Record<hash,{hash: string;date: string;// ISO 8601message: string;authorName: string;authorEmail: string;previous?: {hash: string;path: string};// points to next step backwards in blame walk}>;}

Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing commitSchema (hash, date, message).

The previous pointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added public GET /api/blame endpoint to retrieve per-line blame with commit-attributed ranges and deduplicated commit metadata; supports optional ref for history traversal.
  • Documentation

    • API reference and navigation updated to include the blame endpoint; changelog and docs updated.
  • Audit

    • Audit event emitted when file blame is fetched (user.fetched_file_blame).

Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain`
and parses the output into contiguous line ranges plus deduplicated commit
metadata (hash, date, message, author, optional `previous` pointer for
walking back through history).
Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the
shape of `/api/source`. Registers OpenAPI schemas, updates the API
Reference nav, and adds a `user.fetched_file_blame` audit event.
API-only; the CodeMirror gutter UI is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify

mintlifyBot commented Apr 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

ProjectStatusPreviewUpdated (UTC)
sourcebot🟢 ReadyView PreviewApr 29, 2026, 8:31 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates.

Changes

Cohort / File(s)Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/configuration/audit-logs.mdx
Documented the new /api/blame endpoint and added user.fetched_file_blame to audit action types and API nav.
API Route
packages/web/src/app/api/(server)/blame/route.ts
New Next.js route handler GET /api/blame that validates query params and delegates to getFileBlame, returning standardized ServiceError or JSON response.
Core Blame Logic
packages/web/src/features/git/getFileBlameApi.ts
Implements getFileBlame, repository/ref/path validation, executes git blame --porcelain, maps common failures to service errors, parses porcelain into ranges and commits, and emits audit events when applicable.
Schemas
packages/web/src/features/git/schemas.ts, packages/web/src/openapi/publicApiSchemas.ts
Adds Zod schemas for file blame request/response, blame ranges, commit metadata (with optional previous), and exposes OpenAPI-wrapped public schemas.
OpenAPI Registration
docs/api-reference/sourcebot-public.openapi.json, packages/web/src/openapi/publicApiDocument.ts
Registers GET /api/blame in the public OpenAPI document using the new request/response schemas and standard error responses.
Feature Export
packages/web/src/features/git/index.ts
Exports the new blame API module from the git feature barrel.

Sequence Diagram

sequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FR] Git blame support #650: Implements the server- and API-level Git blame feature (porcelain parsing, response schemas, GET /api/blame) described by that request.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main change—adding a new /api/blame endpoint—which aligns with the core feature being introduced across multiple files and the PR objectives.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/file-blame-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88abb785-1145-462f-ae97-1f92a1109d56

📥 Commits

Reviewing files that changed from the base of the PR and between b537325 and c028f58.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/api-reference/sourcebot-public.openapi.json
  • docs/docs.json
  • docs/docs/configuration/audit-logs.mdx
  • packages/web/src/app/api/(server)/blame/route.ts
  • packages/web/src/features/git/getFileBlameApi.ts
  • packages/web/src/features/git/index.ts
  • packages/web/src/features/git/schemas.ts
  • packages/web/src/openapi/publicApiDocument.ts
  • packages/web/src/openapi/publicApiSchemas.ts

Comment threadpackages/web/src/features/git/getFileBlameApi.ts Outdated
Comment threadpackages/web/src/features/git/getFileBlameApi.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)

170-180: ⚠️ Potential issue | 🟠 Major

Avoid returning raw git/parser errors to public clients.

On Line 180 and Line 187, internal error text is surfaced directly through unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.

Proposed hardening diff
 import { getRepoPath } from '@sourcebot/shared';
+import { createLogger } from '@sourcebot/shared';
@@
type CommitMeta = FileBlameResponse['commits'][string];
+const logger = createLogger('getFileBlameApi');
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) {
return fileNotFound(filePath, repoName);
}
if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) {
return unresolvedGitRef(gitRef);
}
- return unexpectedError(errorMessage);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed');+ return unexpectedError('Failed to compute file blame.');
}
@@
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
- return unexpectedError(`Failed to parse git blame output: ${errorMessage}`);+ logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output');+ return unexpectedError('Failed to parse file blame output.');
}

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The
handler around the git.raw(['blame'...]) call currently passes raw git/parser
messages into unexpectedError(errorMessage) which leaks internals; instead,
capture the error (error / errorMessage), write the full details to server logs
(e.g., logger.error or processLogger.error) and call unexpectedError with a
generic, client-safe message like "Unable to process file blame" or "Internal
error while retrieving blame" so clients don't receive backend internals; keep
existing branches that return fileNotFound(filePath, repoName) and
unresolvedGitRef(gitRef) for known conditions, only replace the final return
unexpectedError(errorMessage) with a logged internal error plus a generic
unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c028f58 and 641779b.

📒 Files selected for processing (1)
  • packages/web/src/features/git/getFileBlameApi.ts

@brendan-kellam
brendan-kellam merged commit cbf50e7 into mainApr 29, 2026
10 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/file-blame-api branch April 29, 2026 20:44
@github-actionsgithub-actionsBot mentioned this pull request Apr 29, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@brendan-kellam