diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index adc435b42c..ec3d3fbf5c 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -37,17 +37,11 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { ownerName := ctx.Params(":owner") repoName := ctx.Params(":repo") - - // defer getting the repository at this point - as we should only retrieve it if we're going to call update - var ( - repo *repo_model.Repository - gitRepo *git.Repository - ) - defer gitRepo.Close() // it's safe to call Close on a nil pointer - + var repo *repo_model.Repository updates := make([]*repo_module.PushUpdateOptions, 0, len(opts.OldCommitIDs)) wasEmpty := false + // generate updates and put the master/main branch first for i := range opts.OldCommitIDs { refFullName := opts.RefFullNames[i] @@ -87,69 +81,27 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } } + // sync branches to the database, if failed return error to keep branches consistent between disk and database if repo != nil && len(updates) > 0 { - branchesToSync := make([]*repo_module.PushUpdateOptions, 0, len(updates)) - for _, update := range updates { - if !update.RefFullName.IsBranch() { - continue - } - if repo == nil { - repo = loadRepository(ctx, ownerName, repoName) - if ctx.Written() { - return - } - wasEmpty = repo.IsEmpty - } - - if update.IsDelRef() { - if err := git_model.AddDeletedBranch(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil { - log.Error("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - } else { - branchesToSync = append(branchesToSync, update) - - // TODO: should we return the error and return the error when pushing? Currently it will log the error and not prevent the pushing - pull_service.UpdatePullsRefs(ctx, repo, update) - } + syncBranches(ctx, updates, repo, opts.UserID) + if ctx.Written() { + return } - if len(branchesToSync) > 0 { - var err error - gitRepo, err = gitrepo.OpenRepository(ctx, repo) - if err != nil { - log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } + } - var ( - branchNames = make([]string, 0, len(branchesToSync)) - commitIDs = make([]string, 0, len(branchesToSync)) - ) - for _, update := range branchesToSync { - branchNames = append(branchNames, update.RefFullName.BranchName()) - commitIDs = append(commitIDs, update.NewCommitID) - } - - if err := repo_service.SyncBranchesToDB(ctx, repo.ID, opts.UserID, branchNames, commitIDs, gitRepo.GetCommit); err != nil { - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to sync branch to DB in repository: %s/%s Error: %v", ownerName, repoName, err), - }) - return - } - } + // Handle possible Push Options + handlePushOptions(ctx, opts, repo, ownerName, repoName) + if ctx.Written() { + return + } + // push updates to a queue so some notificactions can be handled async + if len(updates) > 0 { if err := repo_service.PushUpdates(updates); err != nil { - log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates)) + log.Error("Failed to Update: %s/%s Total Updates: %d, Error: %v", ownerName, repoName, len(updates), err) for i, update := range updates { log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.RefFullName.BranchName()) } - log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err) ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), @@ -158,6 +110,72 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } } + // generate branch results for end user. i.e. Displaying a link to create a PR + results := generateBranchResults(ctx, opts, repo, ownerName, repoName, wasEmpty) + if ctx.Written() { + return + } + + ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ + Results: results, + RepoWasEmpty: wasEmpty, + }) +} + +func syncBranches(ctx *gitea_context.PrivateContext, updates []*repo_module.PushUpdateOptions, repo *repo_model.Repository, pusherID int64) { + branchesToSync := make([]*repo_module.PushUpdateOptions, 0, len(updates)) + for _, update := range updates { + if !update.RefFullName.IsBranch() { + continue + } + + if update.IsDelRef() { + if err := git_model.AddDeletedBranch(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil { + log.Error("Failed to add deleted branch: %s/%s Error: %v", repo.OwnerName, repo.Name, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to add deleted branch: %s/%s Error: %v", repo.OwnerName, repo.Name, err), + }) + return + } + } else { + branchesToSync = append(branchesToSync, update) + + // TODO: should we return the error and return the error when pushing? Currently it will log the error and not prevent the pushing + pull_service.UpdatePullsRefs(ctx, repo, update) + } + } + if len(branchesToSync) == 0 { + return + } + + gitRepo, err := gitrepo.OpenRepository(ctx, repo) + if err != nil { + log.Error("Failed to open repository: %s/%s Error: %v", repo.OwnerName, repo.Name, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", repo.OwnerName, repo.Name, err), + }) + return + } + defer gitRepo.Close() + + var ( + branchNames = make([]string, 0, len(branchesToSync)) + commitIDs = make([]string, 0, len(branchesToSync)) + ) + for _, update := range branchesToSync { + branchNames = append(branchNames, update.RefFullName.BranchName()) + commitIDs = append(commitIDs, update.NewCommitID) + } + + if err := repo_service.SyncBranchesToDB(ctx, repo.ID, pusherID, branchNames, commitIDs, gitRepo.GetCommit); err != nil { + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to sync branch to DB in repository: %s/%s Error: %v", repo.OwnerName, repo.Name, err), + }) + return + } +} + +func handlePushOptions(ctx *gitea_context.PrivateContext, opts *private.HookOptions, repo *repo_model.Repository, ownerName, repoName string) { isPrivate := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate) isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate) // Handle Push Options @@ -169,7 +187,6 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { // Error handled in loadRepository return } - wasEmpty = repo.IsEmpty } pusher, err := user_model.GetUserByID(ctx, opts.UserID) @@ -217,11 +234,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } } } +} +func generateBranchResults(ctx *gitea_context.PrivateContext, opts *private.HookOptions, repo *repo_model.Repository, ownerName, repoName string, wasEmpty bool) []private.HookPostReceiveBranchResult { results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs)) - - // We have to reload the repo in case its state is changed above - repo = nil var baseRepo *repo_model.Repository // Now handle the pull request notification trailers @@ -230,80 +246,80 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { newCommitID := opts.NewCommitIDs[i] // If we've pushed a branch (and not deleted it) - if !git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() { - // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo - if repo == nil { - repo = loadRepository(ctx, ownerName, repoName) - if ctx.Written() { - return - } + if !refFullName.IsBranch() || git.IsEmptyCommitID(newCommitID) { + continue + } - baseRepo = repo + // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo + if repo == nil { + repo = loadRepository(ctx, ownerName, repoName) + if ctx.Written() { + return nil + } - if repo.IsFork { - if err := repo.GetBaseRepo(ctx); err != nil { - log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err), - RepoWasEmpty: wasEmpty, - }) - return - } - if repo.BaseRepo.AllowsPulls(ctx) { - baseRepo = repo.BaseRepo - } - } + baseRepo = repo - if !baseRepo.AllowsPulls(ctx) { - // We can stop there's no need to go any further - ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ + if repo.IsFork { + if err := repo.GetBaseRepo(ctx); err != nil { + log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err), RepoWasEmpty: wasEmpty, }) - return + return nil + } + if repo.BaseRepo.AllowsPulls(ctx) { + baseRepo = repo.BaseRepo } } - branch := refFullName.BranchName() - - // If our branch is the default branch of an unforked repo - there's no PR to create or refer to - if !repo.IsFork && branch == baseRepo.DefaultBranch { - results = append(results, private.HookPostReceiveBranchResult{}) - continue - } - - pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, issues_model.PullRequestFlowGithub) - if err != nil && !issues_model.IsErrPullRequestNotExist(err) { - log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf( - "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err), + if !baseRepo.AllowsPulls(ctx) { + // We can stop there's no need to go any further + ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ RepoWasEmpty: wasEmpty, }) - return - } - - if pr == nil { - if repo.IsFork { - branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) - } - results = append(results, private.HookPostReceiveBranchResult{ - Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(ctx), - Create: true, - Branch: branch, - URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), - }) - } else { - results = append(results, private.HookPostReceiveBranchResult{ - Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(ctx), - Create: false, - Branch: branch, - URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index), - }) + return nil } } + + branch := refFullName.BranchName() + + // If our branch is the default branch of an unforked repo - there's no PR to create or refer to + if !repo.IsFork && branch == baseRepo.DefaultBranch { + results = append(results, private.HookPostReceiveBranchResult{}) + continue + } + + pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, issues_model.PullRequestFlowGithub) + if err != nil && !issues_model.IsErrPullRequestNotExist(err) { + log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err) + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ + Err: fmt.Sprintf( + "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err), + RepoWasEmpty: wasEmpty, + }) + return nil + } + + if pr == nil { + if repo.IsFork { + branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) + } + results = append(results, private.HookPostReceiveBranchResult{ + Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(ctx), + Create: true, + Branch: branch, + URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)), + }) + } else { + results = append(results, private.HookPostReceiveBranchResult{ + Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(ctx), + Create: false, + Branch: branch, + URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index), + }) + } } - ctx.JSON(http.StatusOK, private.HookPostReceiveResult{ - Results: results, - RepoWasEmpty: wasEmpty, - }) + + return results }