feat: [CODE-4589]: support for fork repo delete (#4644)

* c8c800 support for fork repo delete
This commit is contained in:
Marko Gaćeša 2025-10-24 09:17:43 +00:00 committed by Harness
parent 8663ae2061
commit 0aac751751
26 changed files with 547 additions and 91 deletions

View File

@ -252,7 +252,7 @@ func (c *Controller) reportTagEvent(
// TODO: If it is a new branch, or an update on a branch without any PR, it also sends out an SSE for pr creation.
func (c *Controller) handlePRMessaging(
ctx context.Context,
repo *types.Repository,
sourceRepo *types.Repository,
in hook.PostReceiveInput,
out *hook.Output,
) {
@ -266,14 +266,14 @@ func (c *Controller) handlePRMessaging(
// for now we only care about first branch that was pushed.
branchName := in.RefUpdates[0].Ref[len(gitReferenceNamePrefixBranch):]
c.suggestPullRequest(ctx, repo, branchName, out)
c.suggestPullRequest(ctx, sourceRepo, branchName, out)
// TODO: store latest pushed branch for user in cache and send out SSE
}
func (c *Controller) suggestPullRequest(
ctx context.Context,
repo *types.Repository,
sourceRepo *types.Repository,
branchName string,
out *hook.Output,
) {
@ -281,7 +281,7 @@ func (c *Controller) suggestPullRequest(
prs, err := c.pullreqStore.List(ctx, &types.PullReqFilter{
Page: 1,
Size: 10,
SourceRepoID: repo.ID,
SourceRepoID: sourceRepo.ID,
SourceBranch: branchName,
// we only care about open PRs - merged/closed will lead to "create new PR" message
States: []enum.PullReqState{enum.PullReqStateOpen},
@ -294,7 +294,7 @@ func (c *Controller) suggestPullRequest(
log.Ctx(ctx).Warn().Err(err).Msgf(
"failed to find pullrequests for branch '%s' originating from repo '%s'",
branchName,
repo.Path,
sourceRepo.Path,
)
return
}
@ -302,7 +302,7 @@ func (c *Controller) suggestPullRequest(
slices.Reverse(prs) // Use ascending order for message output.
// For already existing PRs, print them to users terminal for easier access.
msgs, err := c.getOpenPRsMessages(ctx, repo, branchName, prs)
msgs, err := c.getOpenPRsMessages(ctx, sourceRepo, branchName, prs)
if err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("failed to get messages for open pull request")
return
@ -312,7 +312,7 @@ func (c *Controller) suggestPullRequest(
return
}
if branchName == repo.DefaultBranch {
if branchName == sourceRepo.DefaultBranch {
// Don't suggest a pull request if this is a push to the default branch.
return
}
@ -320,13 +320,13 @@ func (c *Controller) suggestPullRequest(
// This is a new PR!
out.Messages = append(out.Messages,
fmt.Sprintf("Create a pull request for %q by visiting:", branchName),
" "+c.urlProvider.GenerateUICompareURL(ctx, repo.Path, repo.DefaultBranch, branchName),
" "+c.urlProvider.GenerateUICompareURL(ctx, sourceRepo.Path, sourceRepo.DefaultBranch, branchName),
)
}
func (c *Controller) getOpenPRsMessages(
ctx context.Context,
repo *types.Repository,
sourceRepo *types.Repository,
branchName string,
prs []*types.PullReq,
) ([]string, error) {
@ -343,8 +343,8 @@ func (c *Controller) getOpenPRsMessages(
}
for i, pr := range prs {
path := repo.Path
if pr.TargetRepoID != pr.SourceRepoID {
path := sourceRepo.Path
if pr.TargetRepoID != *pr.SourceRepoID {
targetRepo, err := c.repoFinder.FindByID(ctx, pr.TargetRepoID)
if err != nil {
return nil, fmt.Errorf("failed to find target repo by ID: %w", err)

View File

@ -64,9 +64,10 @@ func (c *Controller) ChangeTargetBranch(ctx context.Context,
if pr.TargetBranch == in.BranchName {
return pr, nil
}
if pr.TargetRepoID == pr.SourceRepoID && pr.SourceBranch == in.BranchName {
if pr.SourceRepoID != nil && pr.TargetRepoID == *pr.SourceRepoID && pr.SourceBranch == in.BranchName {
return nil,
errors.InvalidArgument("source branch %q is same as new target branch", pr.SourceBranch)
errors.InvalidArgument("Source branch %q is same as new target branch", pr.SourceBranch)
}
readParams := git.CreateReadParams(repo)

View File

@ -35,6 +35,7 @@ import (
"github.com/harness/gitness/git"
gitenum "github.com/harness/gitness/git/enum"
"github.com/harness/gitness/git/sha"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -204,18 +205,28 @@ func (c *Controller) Merge(
return nil, nil, fmt.Errorf("failed to create RPC write params: %w", err)
}
sourceRepo := targetRepo
sourceWriteParams := targetWriteParams
if pr.SourceRepoID != pr.TargetRepoID {
sourceWriteParams, err = controller.CreateRPCInternalWriteParams(ctx, c.urlProvider, session, sourceRepo)
if err != nil {
return nil, nil, fmt.Errorf("failed to create RPC write params: %w", err)
}
var sourceRepo *types.RepositoryCore
var sourceWriteParams git.WriteParams
sourceRepo, err = c.repoFinder.FindByID(ctx, pr.SourceRepoID)
if err != nil {
switch {
case pr.SourceRepoID == nil:
// the source repo is purged
case *pr.SourceRepoID != pr.TargetRepoID:
// if the source repo is nil, it's soft deleted
sourceRepo, err = c.repoFinder.FindByID(ctx, *pr.SourceRepoID)
if err != nil && !errors.Is(err, gitness_store.ErrResourceNotFound) {
return nil, nil, fmt.Errorf("failed to get source repository: %w", err)
}
if sourceRepo != nil {
sourceWriteParams, err = controller.CreateRPCInternalWriteParams(ctx, c.urlProvider, session, sourceRepo)
if err != nil {
return nil, nil, fmt.Errorf("failed to create RPC write params: %w", err)
}
}
default:
sourceRepo = targetRepo
sourceWriteParams = targetWriteParams
}
getHeadRef, err := c.git.GetRef(ctx, git.GetRefParams{
@ -243,7 +254,7 @@ func (c *Controller) Merge(
return nil, nil, fmt.Errorf("failed to list status checks: %w", err)
}
codeOwnerWithApproval, err := c.codeOwners.Evaluate(ctx, sourceRepo, pr, reviewers)
codeOwnerWithApproval, err := c.codeOwners.Evaluate(ctx, targetRepo, pr, reviewers)
// check for error and ignore if it is codeowners file not found else throw error
if err != nil && !errors.Is(err, codeowners.ErrNotFound) {
return nil, nil, fmt.Errorf("CODEOWNERS evaluation failed: %w", err)
@ -267,7 +278,9 @@ func (c *Controller) Merge(
return nil, nil, fmt.Errorf("failed to verify protection rules: %w", err)
}
deleteSourceBranch := pr.TargetRepoID == pr.SourceRepoID && (in.DeleteSourceBranch || ruleOut.DeleteSourceBranch)
// only delete the source branch if it's the source repository is the same as the target repository.
deleteSourceBranch := pr.SourceRepoID != nil && pr.TargetRepoID == *pr.SourceRepoID &&
(in.DeleteSourceBranch || ruleOut.DeleteSourceBranch)
if in.DryRunRules {
err := c.backfillApprovalInfo(ctx, ruleOut.DefaultReviewerApprovals)
@ -472,7 +485,13 @@ func (c *Controller) Merge(
if in.Title == "" {
switch in.Method {
case enum.MergeMethodMerge:
in.Title = fmt.Sprintf("Merge branch '%s' of %s (#%d)", pr.SourceBranch, sourceRepo.Path, pr.Number)
if sourceRepo == nil {
in.Title = fmt.Sprintf("Merge branch '%s' of unknown repository (#%d)",
pr.SourceBranch, pr.Number)
} else {
in.Title = fmt.Sprintf("Merge branch '%s' of %s (#%d)", pr.SourceBranch,
sourceRepo.Path, pr.Number)
}
case enum.MergeMethodSquash:
in.Title = fmt.Sprintf("%s (#%d)", pr.Title, pr.Number)
case enum.MergeMethodRebase, enum.MergeMethodFastForward:
@ -681,9 +700,9 @@ func (c *Controller) Merge(
session.Principal,
audit.NewResource(
audit.ResourceTypeRepository,
sourceRepo.Identifier,
targetRepo.Identifier,
audit.RepoPath,
sourceRepo.Path,
targetRepo.Path,
audit.BypassedResourceType,
audit.BypassedResourceTypePullRequest,
audit.BypassedResourceName,
@ -691,17 +710,17 @@ func (c *Controller) Merge(
audit.ResourceName,
fmt.Sprintf(
audit.BypassPullReqLabelFormat,
sourceRepo.Identifier,
targetRepo.Identifier,
strconv.FormatInt(pr.Number, 10),
),
audit.BypassAction,
audit.BypassActionMerged,
),
audit.ActionBypassed,
paths.Parent(sourceRepo.Path),
paths.Parent(targetRepo.Path),
audit.WithNewObject(audit.PullRequestObject{
PullReq: *pr,
RepoPath: sourceRepo.Path,
RepoPath: targetRepo.Path,
RuleViolations: violations,
}),
)
@ -713,10 +732,10 @@ func (c *Controller) Merge(
err = c.instrumentation.Track(ctx, instrument.Event{
Type: instrument.EventTypeMergePullRequest,
Principal: session.Principal.ToPrincipalInfo(),
Path: sourceRepo.Path,
Path: targetRepo.Path,
Properties: map[instrument.Property]any{
instrument.PropertyRepositoryID: sourceRepo.ID,
instrument.PropertyRepositoryName: sourceRepo.Identifier,
instrument.PropertyRepositoryID: targetRepo.ID,
instrument.PropertyRepositoryName: targetRepo.Identifier,
instrument.PropertyPullRequestID: pr.Number,
instrument.PropertyMergeStrategy: in.Method,
},

View File

@ -317,7 +317,7 @@ func (c *Controller) Create(
IsDraft: in.IsDraft,
Title: in.Title,
Description: in.Description,
SourceRepoID: sourceRepo.ID,
SourceRepoID: &sourceRepo.ID,
SourceBranch: in.SourceBranch,
SourceSHA: sourceSHA.String(),
TargetRepoID: targetRepo.ID,

View File

@ -25,9 +25,11 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
pullreqevents "github.com/harness/gitness/app/events/pullreq"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git"
gitenum "github.com/harness/gitness/git/enum"
"github.com/harness/gitness/git/sha"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -78,14 +80,17 @@ func (c *Controller) State(ctx context.Context,
id := pr.ID
sourceRepo := targetRepo
if pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = c.repoFinder.FindByID(ctx, pr.SourceRepoID)
if err != nil {
var sourceRepo *types.RepositoryCore
if pr.SourceRepoID != nil && *pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = c.repoFinder.FindByID(ctx, *pr.SourceRepoID)
if err != nil && !errors.Is(err, gitness_store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to get source repo by id: %w", err)
}
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, sourceRepo, enum.PermissionRepoView); err != nil {
if sourceRepo != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, sourceRepo, enum.PermissionRepoPush); err != nil {
return nil, fmt.Errorf("failed to acquire access to source repo: %w", err)
}
} else if err = apiauth.CheckRepo(ctx, c.authorizer, session, targetRepo, enum.PermissionRepoPush); err != nil {
@ -121,6 +126,10 @@ func (c *Controller) State(ctx context.Context,
//nolint:nestif // refactor if needed
if pr.State != enum.PullReqStateOpen && in.State == enum.PullReqStateOpen {
if sourceRepo == nil {
return nil, usererror.BadRequest("Source repository doesn't exists.")
}
if sourceSHA, err = c.verifyBranchExistence(ctx, sourceRepo, pr.SourceBranch); err != nil {
return nil, err
}
@ -129,7 +138,7 @@ func (c *Controller) State(ctx context.Context,
return nil, err
}
err = c.checkIfAlreadyExists(ctx, pr.TargetRepoID, pr.SourceRepoID, pr.TargetBranch, pr.SourceBranch)
err = c.checkIfAlreadyExists(ctx, pr.TargetRepoID, *pr.SourceRepoID, pr.TargetBranch, pr.SourceBranch)
if err != nil {
return nil, err
}

View File

@ -22,6 +22,8 @@ import (
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
pullreqevents "github.com/harness/gitness/app/events/pullreq"
"github.com/harness/gitness/errors"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -66,15 +68,17 @@ func (c *Controller) Update(ctx context.Context,
return nil, fmt.Errorf("failed to get pull request by number: %w", err)
}
if pr.SourceRepoID != pr.TargetRepoID {
var sourceRepo *types.RepositoryCore
var sourceRepo *types.RepositoryCore
sourceRepo, err = c.repoFinder.FindByID(ctx, pr.SourceRepoID)
if err != nil {
if pr.SourceRepoID != nil && *pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = c.repoFinder.FindByID(ctx, *pr.SourceRepoID)
if err != nil && !errors.Is(err, gitness_store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to get source repo by id: %w", err)
}
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, sourceRepo, enum.PermissionRepoView); err != nil {
if sourceRepo != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, sourceRepo, enum.PermissionRepoPush); err != nil {
return nil, fmt.Errorf("failed to acquire access to source repo: %w", err)
}
} else if err = apiauth.CheckRepo(ctx, c.authorizer, session, targetRepo, enum.PermissionRepoPush); err != nil {

View File

@ -209,10 +209,9 @@ func (c *Controller) CreateFork(
return fmt.Errorf("failed to create fork repository: %w", err)
}
repoUpstream.NumForks++
err = c.repoStore.Update(ctx, repoUpstream)
err = c.repoStore.UpdateNumForks(ctx, repoUpstream.ID, 1)
if err != nil {
return fmt.Errorf("failed to update upstream repository: %w", err)
return fmt.Errorf("failed to increment number of forks in upstream repository: %w", err)
}
return nil

View File

@ -77,7 +77,24 @@ func (c *Controller) PurgeNoAuth(
}
}
if err := c.repoStore.Purge(ctx, repo.ID, repo.Deleted); err != nil {
err := c.tx.WithTx(ctx, func(ctx context.Context) error {
if err := c.repoStore.ClearForkID(ctx, repo.ID); err != nil {
return fmt.Errorf("failed to clear fork ID of forks: %w", err)
}
if repo.ForkID != 0 {
if err := c.repoStore.UpdateNumForks(ctx, repo.ForkID, -1); err != nil {
return fmt.Errorf("failed to decrement number of forks of the upstream repository: %w", err)
}
}
if err := c.repoStore.Purge(ctx, repo.ID, repo.Deleted); err != nil {
return fmt.Errorf("failed to delete repo from db: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("failed to delete repo from db: %w", err)
}

View File

@ -15,9 +15,9 @@
package events
type Base struct {
PullReqID int64 `json:"pullreq_id"`
SourceRepoID int64 `json:"source_repo_id"`
TargetRepoID int64 `json:"repo_id"`
PrincipalID int64 `json:"principal_id"`
Number int64 `json:"number"`
PullReqID int64 `json:"pullreq_id"`
SourceRepoID *int64 `json:"source_repo_id"`
TargetRepoID int64 `json:"repo_id"`
PrincipalID int64 `json:"principal_id"`
Number int64 `json:"number"`
}

View File

@ -39,13 +39,17 @@ func (s *Service) handleEventPullReqCreated(
sourceBranch := payload.SourceBranch
pullReqID := payload.PullReqID
if sourceRepoID == nil {
return events.NewDiscardEventErrorf("pullreq %d event missing sourceRepoID", pullReqID)
}
logger := log.Ctx(ctx).With().
Int64("source_repo_id", sourceRepoID).
Int64("source_repo_id", *sourceRepoID).
Str("source_branch", sourceBranch).
Int64("pullreq_id", pullReqID).
Logger()
err := s.branchStore.UpdateLastPR(ctx, sourceRepoID, sourceBranch, &pullReqID)
err := s.branchStore.UpdateLastPR(ctx, *sourceRepoID, sourceBranch, &pullReqID)
if err != nil {
return fmt.Errorf("failed to update last PR: %w", err)
}
@ -63,8 +67,13 @@ func (s *Service) handleEventPullReqClosed(
if payload == nil {
return fmt.Errorf("payload is nil")
}
if payload.SourceRepoID == nil {
return events.NewDiscardEventErrorf("pullreq %d event missing sourceRepoID", payload.PullReqID)
}
logger := log.Ctx(ctx).With().
Int64("source_repo_id", payload.SourceRepoID).
Int64("source_repo_id", *payload.SourceRepoID).
Int64("pullreq_id", payload.PullReqID).
Str("source_branch", payload.SourceBranch).
Logger()
@ -72,7 +81,7 @@ func (s *Service) handleEventPullReqClosed(
sourceRepoID := payload.SourceRepoID
sourceBranch := payload.SourceBranch
err := s.branchStore.UpdateLastPR(ctx, sourceRepoID, sourceBranch, nil)
err := s.branchStore.UpdateLastPR(ctx, *sourceRepoID, sourceBranch, nil)
if err != nil {
return fmt.Errorf("failed to update last PR: %w", err)
}
@ -90,8 +99,13 @@ func (s *Service) handleEventPullReqReopened(
if payload == nil {
return fmt.Errorf("payload is nil")
}
if payload.SourceRepoID == nil {
return events.NewDiscardEventErrorf("pullreq %d event missing sourceRepoID", payload.PullReqID)
}
logger := log.Ctx(ctx).With().
Int64("source_repo_id", payload.SourceRepoID).
Int64("source_repo_id", *payload.SourceRepoID).
Int64("pullreq_id", payload.PullReqID).
Str("source_branch", payload.SourceBranch).
Logger()
@ -100,7 +114,7 @@ func (s *Service) handleEventPullReqReopened(
sourceBranch := payload.SourceBranch
pullReqID := payload.PullReqID
err := s.branchStore.UpdateLastPR(ctx, sourceRepoID, sourceBranch, &pullReqID)
err := s.branchStore.UpdateLastPR(ctx, *sourceRepoID, sourceBranch, &pullReqID)
if err != nil {
return fmt.Errorf("failed to update last PR: %w", err)
}

View File

@ -26,7 +26,9 @@ import (
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/events"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/stream"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -556,13 +558,17 @@ func fillPullReqProps(
return nil, fmt.Errorf("failed to fill repo data for target repo: %w", err)
}
if pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err := repoFinder.FindByID(ctx, pr.SourceRepoID)
if err != nil {
return nil, fmt.Errorf("failed to find source repo: %w", err)
}
var sourceRepo *types.RepositoryCore
props[prSourceRepoID] = pr.SourceRepoID
if pr.SourceRepoID != nil && *pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = repoFinder.FindByID(ctx, *pr.SourceRepoID)
if err != nil && !errors.Is(err, gitness_store.ErrResourceNotFound) {
return nil, fmt.Errorf("failed to get source repo by id: %w", err)
}
}
if sourceRepo != nil {
props[prSourceRepoID] = sourceRepo.ID
props[prSourceRepoName] = sourceRepo.Identifier
props[prSourceRepoPath] = sourceRepo.Path
}

View File

@ -316,7 +316,7 @@ func (r *repoImportState) convertPullReq(
UnresolvedCount: 0,
Title: extPullReq.Title,
Description: extPullReq.Body,
SourceRepoID: repo.ID,
SourceRepoID: &repo.ID,
SourceBranch: extPullReq.Head.Name,
SourceSHA: extPullReq.Head.SHA,
TargetRepoID: repo.ID,

View File

@ -27,6 +27,7 @@ import (
"github.com/harness/gitness/git"
gitenum "github.com/harness/gitness/git/enum"
"github.com/harness/gitness/git/sha"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -115,9 +116,15 @@ func (s *Service) updatePullReqOnBranchUpdate(ctx context.Context,
// Pull git objects from the source repo into the target repo if this is a cross repo pull request.
if pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err := s.repoFinder.FindByID(ctx, pr.SourceRepoID)
if err != nil {
if pr.SourceRepoID == nil {
return events.NewDiscardEventError(fmt.Errorf("pull request ID=%d has no source repo ID", pr.ID))
}
if *pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err := s.repoFinder.FindByID(ctx, *pr.SourceRepoID)
if errors.Is(err, gitness_store.ErrResourceNotFound) {
return events.NewDiscardEventError(fmt.Errorf("pull request ID=%d source repo not found ID", pr.ID))
} else if err != nil {
return fmt.Errorf("failed to get source repo git info: %w", err)
}

View File

@ -384,9 +384,13 @@ func (c *ListService) BackfillMetadata(
options types.PullReqMetadataOptions,
) error {
for _, entry := range list {
if entry.PullRequest.SourceRepoID != entry.PullRequest.TargetRepoID {
sourceRepo, err := c.repoFinder.FindByID(ctx, entry.PullRequest.SourceRepoID)
if err != nil {
if entry.PullRequest.SourceRepoID == nil {
entry.PullRequest.SourceRepo = deletedSourceRepo
} else if *entry.PullRequest.SourceRepoID != entry.PullRequest.TargetRepoID {
sourceRepo, err := c.repoFinder.FindByID(ctx, *entry.PullRequest.SourceRepoID)
if errors.Is(err, gitness_store.ErrResourceNotFound) {
sourceRepo = deletedSourceRepo
} else if err != nil {
return fmt.Errorf("failed to fetch source repository: %w", err)
}
@ -447,3 +451,8 @@ func (c *ListService) BackfillMetadataForPullReq(
return c.BackfillMetadata(ctx, list, options)
}
var deletedSourceRepo = &types.RepositoryCore{
Identifier: "<deleted-repo>",
Path: "<deleted-repo>",
}

View File

@ -37,7 +37,7 @@ func (s *Service) handleEventPullReqCreated(ctx context.Context,
if err != nil {
return fmt.Errorf("could not augment pull request info: %w", err)
}
return s.trigger(ctx, event.Payload.SourceRepoID, enum.TriggerActionPullReqCreated, hook)
return s.trigger(ctx, event.Payload.TargetRepoID, enum.TriggerActionPullReqCreated, hook)
}
func (s *Service) handleEventPullReqReopened(ctx context.Context,
@ -52,7 +52,7 @@ func (s *Service) handleEventPullReqReopened(ctx context.Context,
if err != nil {
return fmt.Errorf("could not augment pull request info: %w", err)
}
return s.trigger(ctx, event.Payload.SourceRepoID, enum.TriggerActionPullReqReopened, hook)
return s.trigger(ctx, event.Payload.TargetRepoID, enum.TriggerActionPullReqReopened, hook)
}
func (s *Service) handleEventPullReqBranchUpdated(ctx context.Context,
@ -67,7 +67,7 @@ func (s *Service) handleEventPullReqBranchUpdated(ctx context.Context,
if err != nil {
return fmt.Errorf("could not augment pull request info: %w", err)
}
return s.trigger(ctx, event.Payload.SourceRepoID, enum.TriggerActionPullReqBranchUpdated, hook)
return s.trigger(ctx, event.Payload.TargetRepoID, enum.TriggerActionPullReqBranchUpdated, hook)
}
func (s *Service) handleEventPullReqClosed(ctx context.Context,
@ -82,7 +82,7 @@ func (s *Service) handleEventPullReqClosed(ctx context.Context,
if err != nil {
return fmt.Errorf("could not augment pull request info: %w", err)
}
return s.trigger(ctx, event.Payload.SourceRepoID, enum.TriggerActionPullReqClosed, hook)
return s.trigger(ctx, event.Payload.TargetRepoID, enum.TriggerActionPullReqClosed, hook)
}
func (s *Service) handleEventPullReqMerged(
@ -99,7 +99,7 @@ func (s *Service) handleEventPullReqMerged(
if err != nil {
return fmt.Errorf("could not augment pull request info: %w", err)
}
return s.trigger(ctx, event.Payload.SourceRepoID, enum.TriggerActionPullReqMerged, hook)
return s.trigger(ctx, event.Payload.TargetRepoID, enum.TriggerActionPullReqMerged, hook)
}
// augmentPullReqInfo adds in information into the hook pertaining to the pull request

View File

@ -96,9 +96,13 @@ func (s *Service) triggerForEventWithPullReq(
return fmt.Errorf("failed to get pr target repo: %w", err)
}
if pr.SourceRepoID == nil {
return events.NewDiscardEventErrorf("source repo for PR id '%d' doesn't exist anymore", pr.ID)
}
sourceRepo := targetRepo
if pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = s.findRepositoryForEvent(ctx, pr.SourceRepoID)
if *pr.SourceRepoID != pr.TargetRepoID {
sourceRepo, err = s.findRepositoryForEvent(ctx, *pr.SourceRepoID)
if err != nil {
return fmt.Errorf("failed to get pr source repo: %w", err)
}

View File

@ -48,7 +48,7 @@ func (s *Service) handleEventPullReqCreated(
return s.triggerForEventWithPullReq(ctx, enum.WebhookTriggerPullReqCreated,
event.ID, event.Payload.PrincipalID, event.Payload.PullReqID,
func(principal *types.Principal, pr *types.PullReq, targetRepo, sourceRepo *types.Repository) (any, error) {
commitInfo, err := s.fetchCommitInfoForEvent(ctx, sourceRepo.GitUID, sourceRepo.Path,
commitInfo, err := s.fetchCommitInfoForEvent(ctx, targetRepo.GitUID, targetRepo.Path,
event.Payload.SourceSHA, s.urlProvider)
if err != nil {
return nil, err

View File

@ -138,6 +138,9 @@ func (r RepositoryInfo) MarshalJSON() ([]byte, error) {
// repositoryInfoFrom gets the RepositoryInfo from a types.Repository.
func repositoryInfoFrom(ctx context.Context, repo *types.Repository, urlProvider url.Provider) RepositoryInfo {
if repo == nil {
return RepositoryInfo{}
}
return RepositoryInfo{
ID: repo.ID,
Path: repo.Path,
@ -158,7 +161,7 @@ type PullReqInfo struct {
IsDraft bool `json:"is_draft"`
Title string `json:"title"`
Description string `json:"description"`
SourceRepoID int64 `json:"source_repo_id"`
SourceRepoID *int64 `json:"source_repo_id"`
SourceBranch string `json:"source_branch"`
TargetRepoID int64 `json:"target_repo_id"`
TargetBranch string `json:"target_branch"`

View File

@ -315,6 +315,12 @@ type (
// ListSizeInfos returns a list of all active repo sizes.
ListSizeInfos(ctx context.Context) ([]*types.RepositorySizeInfo, error)
// UpdateNumForks increases or decreases number of forks of the repository.
UpdateNumForks(ctx context.Context, repoID int64, delta int64) error
// ClearForkID clears fork ID of all repositories that have this fork ID.
ClearForkID(ctx context.Context, repoUpstreamID int64) error
}
// SettingsStore defines the settings storage.

View File

@ -0,0 +1,23 @@
DROP INDEX pullreqs_source_repo_branch_target_repo_branch;
ALTER TABLE pullreqs
DROP CONSTRAINT fk_pullreq_source_repo_id;
ALTER TABLE pullreqs
ALTER COLUMN pullreq_source_repo_id SET NOT NULL;
ALTER TABLE pullreqs
ALTER COLUMN pullreq_activity_seq DROP NOT NULL;
ALTER TABLE pullreqs
ALTER COLUMN pullreq_merge_check_status DROP DEFAULT;
ALTER TABLE pullreqs
ADD CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id)
REFERENCES repositories (repo_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE;
CREATE UNIQUE INDEX pullreqs_source_repo_branch_target_repo_branch
ON pullreqs (pullreq_source_repo_id, pullreq_source_branch, pullreq_target_repo_id, pullreq_target_branch)
WHERE pullreq_state = 'open';

View File

@ -0,0 +1,23 @@
DROP INDEX pullreqs_source_repo_branch_target_repo_branch;
ALTER TABLE pullreqs
DROP CONSTRAINT fk_pullreq_source_repo_id;
ALTER TABLE pullreqs
ALTER COLUMN pullreq_source_repo_id DROP NOT NULL;
ALTER TABLE pullreqs
ALTER COLUMN pullreq_activity_seq SET NOT NULL;
ALTER TABLE pullreqs
ALTER COLUMN pullreq_merge_check_status SET DEFAULT 'unchecked'::text;
ALTER TABLE pullreqs
ADD CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id)
REFERENCES repositories (repo_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE SET NULL;
CREATE UNIQUE INDEX pullreqs_source_repo_branch_target_repo_branch
ON pullreqs (pullreq_source_repo_id, pullreq_source_branch, pullreq_target_repo_id, pullreq_target_branch)
WHERE pullreq_state = 'open' and pullreq_source_repo_id IS NOT NULL;

View File

@ -0,0 +1,142 @@
DROP INDEX pullreqs_source_repo_branch_target_repo_branch;
DROP INDEX pullreqs_target_repo_id_number;
CREATE TABLE pullreqs_tmp(
pullreq_id INTEGER PRIMARY KEY AUTOINCREMENT
,pullreq_version INTEGER DEFAULT 0 NOT NULL
,pullreq_created_by INTEGER NOT NULL
,pullreq_created BIGINT NOT NULL
,pullreq_updated BIGINT NOT NULL
,pullreq_edited BIGINT NOT NULL
,pullreq_number INTEGER NOT NULL
,pullreq_state TEXT NOT NULL
,pullreq_is_draft TEXT DEFAULT FALSE NOT NULL
,pullreq_comment_count INTEGER DEFAULT 0 NOT NULL
,pullreq_title TEXT NOT NULL
,pullreq_description TEXT NOT NULL
,pullreq_source_repo_id INTEGER NOT NULL
,pullreq_source_branch TEXT NOT NULL
,pullreq_source_sha TEXT NOT NULL
,pullreq_target_repo_id INTEGER NOT NULL
,pullreq_target_branch TEXT NOT NULL
,pullreq_activity_seq INTEGER DEFAULT 0
,pullreq_merged_by INTEGER
,pullreq_merged BIGINT
,pullreq_merge_method TEXT
,pullreq_merge_check_status TEXT NOT NULL
,pullreq_merge_target_sha TEXT
,pullreq_merge_sha TEXT
,pullreq_merge_conflicts TEXT
,pullreq_merge_base_sha TEXT DEFAULT '' NOT NULL
,pullreq_unresolved_count INTEGER DEFAULT 0 NOT NULL
,pullreq_commit_count INTEGER
,pullreq_file_count INTEGER
,pullreq_closed BIGINT
,pullreq_additions INTEGER
,pullreq_deletions INTEGER
,pullreq_rebase_check_status TEXT DEFAULT 'unchecked' NOT NULL
,pullreq_rebase_conflicts TEXT
,pullreq_merge_violations_bypassed BOOLEAN default NULL
,CONSTRAINT fk_pullreq_created_by FOREIGN KEY (pullreq_created_by)
REFERENCES principals
ON UPDATE NO ACTION
ON DELETE NO ACTION
,CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id)
REFERENCES repositories
ON UPDATE NO ACTION
ON DELETE SET NULL
,CONSTRAINT fk_pullreq_target_repo_id FOREIGN KEY (pullreq_target_repo_id)
REFERENCES repositories
ON UPDATE NO ACTION
ON DELETE CASCADE
,CONSTRAINT fk_pullreq_merged_by FOREIGN KEY (pullreq_merged_by)
REFERENCES principals
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
INSERT INTO pullreqs_tmp(
pullreq_id,
pullreq_version,
pullreq_created_by,
pullreq_created,
pullreq_updated,
pullreq_edited,
pullreq_number,
pullreq_state,
pullreq_is_draft,
pullreq_comment_count,
pullreq_title,
pullreq_description,
pullreq_source_repo_id,
pullreq_source_branch,
pullreq_source_sha,
pullreq_target_repo_id,
pullreq_target_branch,
pullreq_activity_seq,
pullreq_merged_by,
pullreq_merged,
pullreq_merge_method,
pullreq_merge_check_status,
pullreq_merge_target_sha,
pullreq_merge_sha,
pullreq_merge_conflicts,
pullreq_merge_base_sha,
pullreq_unresolved_count,
pullreq_commit_count,
pullreq_file_count,
pullreq_closed,
pullreq_additions,
pullreq_deletions,
pullreq_rebase_check_status,
pullreq_rebase_conflicts,
pullreq_merge_violations_bypassed
)
SELECT
pullreq_id,
pullreq_version,
pullreq_created_by,
pullreq_created,
pullreq_updated,
pullreq_edited,
pullreq_number,
pullreq_state,
pullreq_is_draft,
pullreq_comment_count,
pullreq_title,
pullreq_description,
pullreq_source_repo_id,
pullreq_source_branch,
pullreq_source_sha,
pullreq_target_repo_id,
pullreq_target_branch,
pullreq_activity_seq,
pullreq_merged_by,
pullreq_merged,
pullreq_merge_method,
pullreq_merge_check_status,
pullreq_merge_target_sha,
pullreq_merge_sha,
pullreq_merge_conflicts,
pullreq_merge_base_sha,
pullreq_unresolved_count,
pullreq_commit_count,
pullreq_file_count,
pullreq_closed,
pullreq_additions,
pullreq_deletions,
pullreq_rebase_check_status,
pullreq_rebase_conflicts,
pullreq_merge_violations_bypassed
FROM pullreqs;
DROP TABLE pullreqs;
ALTER TABLE pullreqs_tmp RENAME TO pullreqs;
CREATE UNIQUE INDEX pullreqs_target_repo_id_number
on pullreqs (pullreq_target_repo_id, pullreq_number);
CREATE UNIQUE INDEX pullreqs_source_repo_branch_target_repo_branch
ON pullreqs (pullreq_source_repo_id, pullreq_source_branch, pullreq_target_repo_id, pullreq_target_branch)
WHERE pullreq_state = 'open';

View File

@ -0,0 +1,142 @@
DROP INDEX pullreqs_source_repo_branch_target_repo_branch;
DROP INDEX pullreqs_target_repo_id_number;
CREATE TABLE pullreqs_tmp(
pullreq_id INTEGER PRIMARY KEY AUTOINCREMENT
,pullreq_version INTEGER DEFAULT 0 NOT NULL
,pullreq_created_by INTEGER NOT NULL
,pullreq_created BIGINT NOT NULL
,pullreq_updated BIGINT NOT NULL
,pullreq_edited BIGINT NOT NULL
,pullreq_number INTEGER NOT NULL
,pullreq_state TEXT NOT NULL
,pullreq_is_draft TEXT DEFAULT FALSE NOT NULL
,pullreq_comment_count INTEGER DEFAULT 0 NOT NULL
,pullreq_title TEXT NOT NULL
,pullreq_description TEXT NOT NULL
,pullreq_source_repo_id INTEGER
,pullreq_source_branch TEXT NOT NULL
,pullreq_source_sha TEXT NOT NULL
,pullreq_target_repo_id INTEGER NOT NULL
,pullreq_target_branch TEXT NOT NULL
,pullreq_activity_seq INTEGER NOT NULL DEFAULT 0
,pullreq_merged_by INTEGER
,pullreq_merged BIGINT
,pullreq_merge_method TEXT
,pullreq_merge_check_status TEXT DEFAULT 'unchecked' NOT NULL
,pullreq_merge_target_sha TEXT
,pullreq_merge_sha TEXT
,pullreq_merge_conflicts TEXT
,pullreq_merge_base_sha TEXT DEFAULT '' NOT NULL
,pullreq_unresolved_count INTEGER DEFAULT 0 NOT NULL
,pullreq_commit_count INTEGER
,pullreq_file_count INTEGER
,pullreq_closed BIGINT
,pullreq_additions INTEGER
,pullreq_deletions INTEGER
,pullreq_rebase_check_status TEXT DEFAULT 'unchecked' NOT NULL
,pullreq_rebase_conflicts TEXT
,pullreq_merge_violations_bypassed BOOLEAN default NULL
,CONSTRAINT fk_pullreq_created_by FOREIGN KEY (pullreq_created_by)
REFERENCES principals
ON UPDATE NO ACTION
ON DELETE NO ACTION
,CONSTRAINT fk_pullreq_source_repo_id FOREIGN KEY (pullreq_source_repo_id)
REFERENCES repositories
ON UPDATE NO ACTION
ON DELETE SET NULL
,CONSTRAINT fk_pullreq_target_repo_id FOREIGN KEY (pullreq_target_repo_id)
REFERENCES repositories
ON UPDATE NO ACTION
ON DELETE CASCADE
,CONSTRAINT fk_pullreq_merged_by FOREIGN KEY (pullreq_merged_by)
REFERENCES principals
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
INSERT INTO pullreqs_tmp(
pullreq_id,
pullreq_version,
pullreq_created_by,
pullreq_created,
pullreq_updated,
pullreq_edited,
pullreq_number,
pullreq_state,
pullreq_is_draft,
pullreq_comment_count,
pullreq_title,
pullreq_description,
pullreq_source_repo_id,
pullreq_source_branch,
pullreq_source_sha,
pullreq_target_repo_id,
pullreq_target_branch,
pullreq_activity_seq,
pullreq_merged_by,
pullreq_merged,
pullreq_merge_method,
pullreq_merge_check_status,
pullreq_merge_target_sha,
pullreq_merge_sha,
pullreq_merge_conflicts,
pullreq_merge_base_sha,
pullreq_unresolved_count,
pullreq_commit_count,
pullreq_file_count,
pullreq_closed,
pullreq_additions,
pullreq_deletions,
pullreq_rebase_check_status,
pullreq_rebase_conflicts,
pullreq_merge_violations_bypassed
)
SELECT
pullreq_id,
pullreq_version,
pullreq_created_by,
pullreq_created,
pullreq_updated,
pullreq_edited,
pullreq_number,
pullreq_state,
pullreq_is_draft,
pullreq_comment_count,
pullreq_title,
pullreq_description,
pullreq_source_repo_id,
pullreq_source_branch,
pullreq_source_sha,
pullreq_target_repo_id,
pullreq_target_branch,
pullreq_activity_seq,
pullreq_merged_by,
pullreq_merged,
pullreq_merge_method,
pullreq_merge_check_status,
pullreq_merge_target_sha,
pullreq_merge_sha,
pullreq_merge_conflicts,
pullreq_merge_base_sha,
pullreq_unresolved_count,
pullreq_commit_count,
pullreq_file_count,
pullreq_closed,
pullreq_additions,
pullreq_deletions,
pullreq_rebase_check_status,
pullreq_rebase_conflicts,
pullreq_merge_violations_bypassed
FROM pullreqs;
DROP TABLE pullreqs;
ALTER TABLE pullreqs_tmp RENAME TO pullreqs;
CREATE UNIQUE INDEX pullreqs_target_repo_id_number
on pullreqs (pullreq_target_repo_id, pullreq_number);
CREATE UNIQUE INDEX pullreqs_source_repo_branch_target_repo_branch
ON pullreqs (pullreq_source_repo_id, pullreq_source_branch, pullreq_target_repo_id, pullreq_target_branch)
WHERE pullreq_state = 'open' and pullreq_source_repo_id IS NOT NULL;

View File

@ -74,11 +74,11 @@ type pullReq struct {
Title string `db:"pullreq_title"`
Description string `db:"pullreq_description"`
SourceRepoID int64 `db:"pullreq_source_repo_id"`
SourceBranch string `db:"pullreq_source_branch"`
SourceSHA string `db:"pullreq_source_sha"`
TargetRepoID int64 `db:"pullreq_target_repo_id"`
TargetBranch string `db:"pullreq_target_branch"`
SourceRepoID null.Int `db:"pullreq_source_repo_id"`
SourceBranch string `db:"pullreq_source_branch"`
SourceSHA string `db:"pullreq_source_sha"`
TargetRepoID int64 `db:"pullreq_target_repo_id"`
TargetBranch string `db:"pullreq_target_branch"`
ActivitySeq int64 `db:"pullreq_activity_seq"`
@ -868,7 +868,7 @@ func mapPullReq(pr *pullReq) *types.PullReq {
UnresolvedCount: pr.UnresolvedCount,
Title: pr.Title,
Description: pr.Description,
SourceRepoID: pr.SourceRepoID,
SourceRepoID: pr.SourceRepoID.Ptr(),
SourceBranch: pr.SourceBranch,
SourceSHA: pr.SourceSHA,
TargetRepoID: pr.TargetRepoID,
@ -918,7 +918,7 @@ func mapInternalPullReq(pr *types.PullReq) *pullReq {
UnresolvedCount: pr.UnresolvedCount,
Title: pr.Title,
Description: pr.Description,
SourceRepoID: pr.SourceRepoID,
SourceRepoID: null.IntFromPtr(pr.SourceRepoID),
SourceBranch: pr.SourceBranch,
SourceSHA: pr.SourceSHA,
TargetRepoID: pr.TargetRepoID,

View File

@ -303,7 +303,6 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
,repo_description = :repo_description
,repo_default_branch = :repo_default_branch
,repo_pullreq_seq = :repo_pullreq_seq
,repo_num_forks = :repo_num_forks
,repo_num_pulls = :repo_num_pulls
,repo_num_closed_pulls = :repo_num_closed_pulls
,repo_num_open_pulls = :repo_num_open_pulls
@ -813,6 +812,35 @@ func (s *RepoStore) ListAll(
return s.mapToRepos(ctx, dst)
}
func (s *RepoStore) UpdateNumForks(ctx context.Context, repoID int64, delta int64) error {
query := "UPDATE repositories SET repo_num_forks = repo_num_forks + $1 WHERE repo_id = $2"
if _, err := dbtx.GetAccessor(ctx, s.db).ExecContext(ctx, query, delta, repoID); err != nil {
return database.ProcessSQLErrorf(ctx, err, "failed updating number of forks")
}
return nil
}
func (s *RepoStore) ClearForkID(ctx context.Context, repoUpstreamID int64) error {
stmt := database.Builder.Update("repositories").
Set("repo_fork_id", nil).
Where("repo_fork_id = ?", repoUpstreamID)
sql, args, err := stmt.ToSql()
if err != nil {
return errors.Wrap(err, "failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, s.db)
_, err = db.ExecContext(ctx, sql, args...)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "failed to clear fork ID")
}
return nil
}
func (s *RepoStore) mapToRepo(
ctx context.Context,
in *repository,

View File

@ -41,7 +41,7 @@ type PullReq struct {
Title string `json:"title"`
Description string `json:"description"`
SourceRepoID int64 `json:"source_repo_id"`
SourceRepoID *int64 `json:"source_repo_id"`
SourceBranch string `json:"source_branch"`
SourceSHA string `json:"source_sha"`
TargetRepoID int64 `json:"target_repo_id"`