Skip to content

Commit 165d8dc

Browse files
mujacicaclaude
andcommitted
feat(perforce): Implement get_file() for source context
Implement get_file() in PerforceClient using p4 print to fetch file contents from the depot. This enables the SCM source context feature to display inline code in stack traces for Perforce-hosted projects. Converts P4Exception to ApiError(404) so the source context caller handles errors consistently with other integrations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ddb570e commit 165d8dc

1 file changed

Lines changed: 41 additions & 3 deletions

File tree

src/sentry/integrations/perforce/client.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,10 +608,48 @@ def get_file(
608608
self, repo: Repository, path: str, ref: str | None, codeowners: bool = False
609609
) -> str:
610610
"""
611-
Get file contents from Perforce depot.
612-
Required by abstract base class but not used (CODEOWNERS).
611+
Get file contents from Perforce depot using ``p4 print``.
612+
613+
API docs: https://www.perforce.com/manuals/cmdref/Content/CmdRef/p4_print.html
614+
615+
Args:
616+
repo: Repository object containing depot path config
617+
path: File path relative to depot root
618+
ref: Changelist number or file revision (e.g. "42" → ``@42``,
619+
or already contains ``#``/``@``). None for head revision.
620+
codeowners: Not used for Perforce
621+
622+
Returns:
623+
File contents as a UTF-8 string
624+
625+
Raises:
626+
ApiError(404): File not found in depot
627+
ApiError(500): Perforce connection or command error
613628
"""
614-
raise NotImplementedError("get_file is not supported for Perforce")
629+
from sentry.shared_integrations.exceptions import ApiError
630+
631+
with self._connect() as p4:
632+
depot_path = self.build_depot_path(repo, path)
633+
634+
# Append revision/changelist specifier if provided
635+
if ref and "#" not in depot_path and "@" not in depot_path:
636+
depot_path = f"{depot_path}@{ref}"
637+
638+
try:
639+
result = p4.run("print", depot_path)
640+
except P4Exception as e:
641+
raise ApiError(str(e), code=404)
642+
643+
# p4 print returns a list: first element is file metadata dict,
644+
# remaining elements are file content strings/bytes
645+
if len(result) < 2:
646+
raise ApiError(f"File not found: {depot_path}", code=404)
647+
648+
content_parts = result[1:]
649+
return "".join(
650+
part.decode("utf-8", errors="replace") if isinstance(part, bytes) else part
651+
for part in content_parts
652+
)
615653

616654
def create_comment(self, repo: str, issue_id: str, data: dict[str, Any]) -> Any:
617655
"""Create comment. Not applicable for Perforce."""

0 commit comments

Comments
 (0)