Skip to content

fix: [CODE-5090]: fix forked repo output when upstream is soft deleted#99

Open
abhinavcode wants to merge 1 commit into
mainfrom
mg/fork/fix_repo_output_deleted_upstream
Open

fix: [CODE-5090]: fix forked repo output when upstream is soft deleted#99
abhinavcode wants to merge 1 commit into
mainfrom
mg/fork/fix_repo_output_deleted_upstream

Conversation

@abhinavcode

@abhinavcode abhinavcode commented Feb 25, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where repositories with missing or unavailable upstream fork references would fail to load; such repositories now display correctly and their upstream reference is treated as absent rather than causing an error.
    • Other repository loading behavior remains unchanged; errors unrelated to missing upstream forks continue to surface as before.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Modified GetRepoOutput to treat missing upstream forks as non-fatal by resetting ForkID to 0 when fork lookup returns ErrResourceNotFound; other errors still propagate. Added gitnessstore import alias to check for the specific error.

Changes

Cohort / File(s) Summary
Fork error handling
app/api/controller/repo/helper.go
Enhanced GetRepoOutput to gracefully handle missing upstream forks by resetting ForkID to 0 instead of propagating ErrResourceNotFound; added gitnessstore import alias for error type checking.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 I sniffed the fork and found a gap,
I nudged the number, set it back,
No error stamp, no mournful clack,
A gentle zero in its track,
Hopping onward, lighter pack.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing forked repository output when the upstream is soft deleted.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mg/fork/fix_repo_output_deleted_upstream

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/api/controller/repo/helper.go (1)

150-173: ⚠️ Potential issue | 🟠 Major

GetRepoOutputWithAccess has the same bug — soft-deleted upstream still causes a fatal error here.

The PR fixes the soft-delete case in GetRepoOutput but the sibling GetRepoOutputWithAccess uses an identical repoFinder.FindByID lookup (Line 160) with no ErrResourceNotFound guard. Any caller routed through this path will still receive an error when the upstream is soft-deleted.

🐛 Proposed fix — apply the same guard in `GetRepoOutputWithAccess`
 func GetRepoOutputWithAccess(
 	ctx context.Context,
 	repoFinder refcache.RepoFinder,
 	isPublic bool,
 	repo *types.Repository,
 ) (*RepositoryOutput, error) {
+	repoCopy := *repo
+
 	var upstreamRepo *types.RepositoryCore
-	if repo.ForkID != 0 {
-		var err error
-
-		upstreamRepo, err = repoFinder.FindByID(ctx, repo.ForkID)
-		if err != nil {
-			return nil, fmt.Errorf("failed to find repo fork %d: %w", repo.ForkID, err)
+	if repoCopy.ForkID != 0 {
+		var err error
+		upstreamRepo, err = repoFinder.FindByID(ctx, repoCopy.ForkID)
+		if errors.Is(err, gitnessstore.ErrResourceNotFound) {
+			repoCopy.ForkID = 0
+		} else if err != nil {
+			return nil, fmt.Errorf("failed to find repo fork %d: %w", repoCopy.ForkID, err)
 		}
 	}

 	return &RepositoryOutput{
-		Repository: *repo,
+		Repository: repoCopy,
 		IsPublic:   isPublic,
-		Importing:  slices.Contains(importingStates, repo.State),
-		Archived:   repo.State == enum.RepoStateArchived,
+		Importing:  slices.Contains(importingStates, repoCopy.State),
+		Archived:   repoCopy.State == enum.RepoStateArchived,
 		Upstream:   upstreamRepo,
 	}, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/controller/repo/helper.go` around lines 150 - 173,
GetRepoOutputWithAccess currently calls repoFinder.FindByID for the upstream (in
the upstreamRepo lookup) and returns an error if that call fails; mirror the fix
applied to GetRepoOutput by special-casing the soft-delete/not-found error: call
repoFinder.FindByID(ctx, repo.ForkID) inside the existing block, and if it
returns refcache.ErrResourceNotFound (or the package's not-found sentinel) treat
upstreamRepo as nil and clear the error, otherwise propagate the error as
before; update the error handling around the upstreamRepo assignment in
GetRepoOutputWithAccess accordingly.
🧹 Nitpick comments (1)
app/api/controller/repo/helper.go (1)

132-138: Mutating repo.ForkID on the caller's pointer is a hidden side effect.

repo is *types.Repository, so repo.ForkID = 0 modifies the caller's object, not just a local copy. The output struct is self-consistent because Repository: *repo on line 142 copies the already-mutated value, but any code in the caller that inspects repo.ForkID after this call will silently observe 0.

Consider clearing only the local view by working on a copy, or explicitly documenting this mutation contract:

♻️ Proposed fix — operate on a shallow copy to avoid mutating the caller
 func GetRepoOutput(
 	ctx context.Context,
 	publicAccess publicaccess.Service,
 	repoFinder refcache.RepoFinder,
 	repo *types.Repository,
 ) (*RepositoryOutput, error) {
 	isPublic, err := publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path)
 	if err != nil {
 		return nil, fmt.Errorf("failed to check if repo is public: %w", err)
 	}

+	// Work on a local copy so we don't mutate the caller's struct.
+	repoCopy := *repo

 	var upstreamRepo *types.RepositoryCore
-	if repo.ForkID != 0 {
-		upstreamRepo, err = repoFinder.FindByID(ctx, repo.ForkID)
+	if repoCopy.ForkID != 0 {
+		upstreamRepo, err = repoFinder.FindByID(ctx, repoCopy.ForkID)
 		if errors.Is(err, gitnessstore.ErrResourceNotFound) {
-			repo.ForkID = 0
+			repoCopy.ForkID = 0
 		} else if err != nil {
-			return nil, fmt.Errorf("failed to find repo fork %d: %w", repo.ForkID, err)
+			return nil, fmt.Errorf("failed to find repo fork %d: %w", repoCopy.ForkID, err)
 		}
 	}

 	return &RepositoryOutput{
-		Repository: *repo,
+		Repository: repoCopy,
 		IsPublic:   isPublic,
-		Importing:  slices.Contains(importingStates, repo.State),
-		Archived:   repo.State == enum.RepoStateArchived,
+		Importing:  slices.Contains(importingStates, repoCopy.State),
+		Archived:   repoCopy.State == enum.RepoStateArchived,
 		Upstream:   upstreamRepo,
 	}, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/controller/repo/helper.go` around lines 132 - 138, The code mutates
the caller's Repository by setting repo.ForkID = 0 when the upstream is not
found; avoid this hidden side effect by working on a shallow copy instead: copy
the incoming pointer into a local value (e.g., r := *repo), use r.ForkID for the
FindByID logic and set r.ForkID = 0 only on the local copy, and return the copy
(or use its fields) instead of modifying the original; alternatively, if
mutation is intended, add an explicit comment and update the function contract
to document that repo.ForkID may be cleared.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/api/controller/repo/helper.go`:
- Around line 150-173: GetRepoOutputWithAccess currently calls
repoFinder.FindByID for the upstream (in the upstreamRepo lookup) and returns an
error if that call fails; mirror the fix applied to GetRepoOutput by
special-casing the soft-delete/not-found error: call repoFinder.FindByID(ctx,
repo.ForkID) inside the existing block, and if it returns
refcache.ErrResourceNotFound (or the package's not-found sentinel) treat
upstreamRepo as nil and clear the error, otherwise propagate the error as
before; update the error handling around the upstreamRepo assignment in
GetRepoOutputWithAccess accordingly.

---

Nitpick comments:
In `@app/api/controller/repo/helper.go`:
- Around line 132-138: The code mutates the caller's Repository by setting
repo.ForkID = 0 when the upstream is not found; avoid this hidden side effect by
working on a shallow copy instead: copy the incoming pointer into a local value
(e.g., r := *repo), use r.ForkID for the FindByID logic and set r.ForkID = 0
only on the local copy, and return the copy (or use its fields) instead of
modifying the original; alternatively, if mutation is intended, add an explicit
comment and update the function contract to document that repo.ForkID may be
cleared.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c36d185 and f2fb205.

📒 Files selected for processing (1)
  • app/api/controller/repo/helper.go

@abhinavcode abhinavcode force-pushed the mg/fork/fix_repo_output_deleted_upstream branch from f2fb205 to c700e10 Compare February 25, 2026 14:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/api/controller/repo/helper.go (1)

152-175: ⚠️ Potential issue | 🟠 Major

GetRepoOutputWithAccess has the same bug and needs the same fix.

The fork-lookup in GetRepoOutputWithAccess (lines 162-165) still hard-errors on ErrResourceNotFound, so any code path that calls this function for a forked repo with a soft-deleted upstream will continue to fail. The fix applied to GetRepoOutput should be mirrored here. This affects 8 call sites across repo creation, forking, importing, and public access operations.

🐛 Proposed fix for `GetRepoOutputWithAccess`
 func GetRepoOutputWithAccess(
 	ctx context.Context,
 	repoFinder refcache.RepoFinder,
 	isPublic bool,
 	repo *types.Repository,
 ) (*RepositoryOutput, error) {
+	repoClone := *repo
+
 	var upstreamRepo *types.RepositoryCore
 	if repo.ForkID != 0 {
 		var err error

 		upstreamRepo, err = repoFinder.FindByID(ctx, repo.ForkID)
-		if err != nil {
+		if errors.Is(err, gitnessstore.ErrResourceNotFound) {
+			repoClone.ForkID = 0
+		} else if err != nil {
 			return nil, fmt.Errorf("failed to find repo fork %d: %w", repo.ForkID, err)
 		}
 	}

 	return &RepositoryOutput{
-		Repository: *repo,
+		Repository: repoClone,
 		IsPublic:   isPublic,
 		Importing:  slices.Contains(importingStates, repo.State),
 		Archived:   repo.State == enum.RepoStateArchived,
 		Upstream:   upstreamRepo,
 	}, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/controller/repo/helper.go` around lines 152 - 175,
GetRepoOutputWithAccess currently hard-fails when repoFinder.FindByID returns
ErrResourceNotFound for a forked repo; update the upstream lookup in
GetRepoOutputWithAccess to mirror GetRepoOutput: call repoFinder.FindByID, and
if it returns ErrResourceNotFound treat it as non-fatal (leave upstreamRepo nil)
while returning other errors as before. Locate the block that calls
repoFinder.FindByID (inside GetRepoOutputWithAccess) and change error handling
to check for refcache.ErrResourceNotFound (or the same sentinel used in
GetRepoOutput) and only wrap/return the error for unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/api/controller/repo/helper.go`:
- Around line 152-175: GetRepoOutputWithAccess currently hard-fails when
repoFinder.FindByID returns ErrResourceNotFound for a forked repo; update the
upstream lookup in GetRepoOutputWithAccess to mirror GetRepoOutput: call
repoFinder.FindByID, and if it returns ErrResourceNotFound treat it as non-fatal
(leave upstreamRepo nil) while returning other errors as before. Locate the
block that calls repoFinder.FindByID (inside GetRepoOutputWithAccess) and change
error handling to check for refcache.ErrResourceNotFound (or the same sentinel
used in GetRepoOutput) and only wrap/return the error for unexpected errors.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2fb205 and c700e10.

📒 Files selected for processing (1)
  • app/api/controller/repo/helper.go

Sign up for free to 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.

2 participants