code.gitea.io/gitea@v1.22.3/services/agit/agit.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package agit 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "strconv" 11 "strings" 12 13 issues_model "code.gitea.io/gitea/models/issues" 14 repo_model "code.gitea.io/gitea/models/repo" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/git" 17 "code.gitea.io/gitea/modules/log" 18 "code.gitea.io/gitea/modules/private" 19 "code.gitea.io/gitea/modules/setting" 20 notify_service "code.gitea.io/gitea/services/notify" 21 pull_service "code.gitea.io/gitea/services/pull" 22 ) 23 24 // ProcReceive handle proc receive work 25 func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) { 26 results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs)) 27 topicBranch := opts.GitPushOptions["topic"] 28 forcePush, _ := strconv.ParseBool(opts.GitPushOptions["force-push"]) 29 title := strings.TrimSpace(opts.GitPushOptions["title"]) 30 description := strings.TrimSpace(opts.GitPushOptions["description"]) // TODO: Add more options? 31 objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) 32 userName := strings.ToLower(opts.UserName) 33 34 pusher, err := user_model.GetUserByID(ctx, opts.UserID) 35 if err != nil { 36 return nil, fmt.Errorf("failed to get user. Error: %w", err) 37 } 38 39 for i := range opts.OldCommitIDs { 40 if opts.NewCommitIDs[i] == objectFormat.EmptyObjectID().String() { 41 results = append(results, private.HookProcReceiveRefResult{ 42 OriginalRef: opts.RefFullNames[i], 43 OldOID: opts.OldCommitIDs[i], 44 NewOID: opts.NewCommitIDs[i], 45 Err: "Can't delete not exist branch", 46 }) 47 continue 48 } 49 50 if !opts.RefFullNames[i].IsFor() { 51 results = append(results, private.HookProcReceiveRefResult{ 52 IsNotMatched: true, 53 OriginalRef: opts.RefFullNames[i], 54 }) 55 continue 56 } 57 58 baseBranchName := opts.RefFullNames[i].ForBranchName() 59 curentTopicBranch := "" 60 if !gitRepo.IsBranchExist(baseBranchName) { 61 // try match refs/for/<target-branch>/<topic-branch> 62 for p, v := range baseBranchName { 63 if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 { 64 curentTopicBranch = baseBranchName[p+1:] 65 baseBranchName = baseBranchName[:p] 66 break 67 } 68 } 69 } 70 71 if len(topicBranch) == 0 && len(curentTopicBranch) == 0 { 72 results = append(results, private.HookProcReceiveRefResult{ 73 OriginalRef: opts.RefFullNames[i], 74 OldOID: opts.OldCommitIDs[i], 75 NewOID: opts.NewCommitIDs[i], 76 Err: "topic-branch is not set", 77 }) 78 continue 79 } 80 81 if len(curentTopicBranch) == 0 { 82 curentTopicBranch = topicBranch 83 } 84 85 // because different user maybe want to use same topic, 86 // So it's better to make sure the topic branch name 87 // has user name prefix 88 var headBranch string 89 if !strings.HasPrefix(curentTopicBranch, userName+"/") { 90 headBranch = userName + "/" + curentTopicBranch 91 } else { 92 headBranch = curentTopicBranch 93 } 94 95 pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, repo.ID, headBranch, baseBranchName, issues_model.PullRequestFlowAGit) 96 if err != nil { 97 if !issues_model.IsErrPullRequestNotExist(err) { 98 return nil, fmt.Errorf("failed to get unmerged agit flow pull request in repository: %s Error: %w", repo.FullName(), err) 99 } 100 101 var commit *git.Commit 102 if title == "" || description == "" { 103 commit, err = gitRepo.GetCommit(opts.NewCommitIDs[i]) 104 if err != nil { 105 return nil, fmt.Errorf("failed to get commit %s in repository: %s Error: %w", opts.NewCommitIDs[i], repo.FullName(), err) 106 } 107 } 108 109 // create a new pull request 110 if title == "" { 111 title = strings.Split(commit.CommitMessage, "\n")[0] 112 } 113 if description == "" { 114 _, description, _ = strings.Cut(commit.CommitMessage, "\n\n") 115 } 116 if description == "" { 117 description = title 118 } 119 120 prIssue := &issues_model.Issue{ 121 RepoID: repo.ID, 122 Title: title, 123 PosterID: pusher.ID, 124 Poster: pusher, 125 IsPull: true, 126 Content: description, 127 } 128 129 pr := &issues_model.PullRequest{ 130 HeadRepoID: repo.ID, 131 BaseRepoID: repo.ID, 132 HeadBranch: headBranch, 133 HeadCommitID: opts.NewCommitIDs[i], 134 BaseBranch: baseBranchName, 135 HeadRepo: repo, 136 BaseRepo: repo, 137 MergeBase: "", 138 Type: issues_model.PullRequestGitea, 139 Flow: issues_model.PullRequestFlowAGit, 140 } 141 142 if err := pull_service.NewPullRequest(ctx, repo, prIssue, []int64{}, []string{}, pr, []int64{}); err != nil { 143 return nil, err 144 } 145 146 log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID) 147 148 results = append(results, private.HookProcReceiveRefResult{ 149 Ref: pr.GetGitRefName(), 150 OriginalRef: opts.RefFullNames[i], 151 OldOID: objectFormat.EmptyObjectID().String(), 152 NewOID: opts.NewCommitIDs[i], 153 IsCreatePR: true, 154 URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index), 155 ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx), 156 HeadBranch: headBranch, 157 }) 158 continue 159 } 160 161 // update exist pull request 162 if err := pr.LoadBaseRepo(ctx); err != nil { 163 return nil, fmt.Errorf("unable to load base repository for PR[%d] Error: %w", pr.ID, err) 164 } 165 166 oldCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) 167 if err != nil { 168 return nil, fmt.Errorf("unable to get ref commit id in base repository for PR[%d] Error: %w", pr.ID, err) 169 } 170 171 if oldCommitID == opts.NewCommitIDs[i] { 172 results = append(results, private.HookProcReceiveRefResult{ 173 OriginalRef: opts.RefFullNames[i], 174 OldOID: opts.OldCommitIDs[i], 175 NewOID: opts.NewCommitIDs[i], 176 Err: "new commit is same with old commit", 177 }) 178 continue 179 } 180 181 if !forcePush { 182 output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1"). 183 AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]). 184 RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) 185 if err != nil { 186 return nil, fmt.Errorf("failed to detect force push: %w", err) 187 } else if len(output) > 0 { 188 results = append(results, private.HookProcReceiveRefResult{ 189 OriginalRef: opts.RefFullNames[i], 190 OldOID: opts.OldCommitIDs[i], 191 NewOID: opts.NewCommitIDs[i], 192 Err: "request `force-push` push option", 193 }) 194 continue 195 } 196 } 197 198 pr.HeadCommitID = opts.NewCommitIDs[i] 199 if err = pull_service.UpdateRef(ctx, pr); err != nil { 200 return nil, fmt.Errorf("failed to update pull ref. Error: %w", err) 201 } 202 203 pull_service.AddToTaskQueue(ctx, pr) 204 err = pr.LoadIssue(ctx) 205 if err != nil { 206 return nil, fmt.Errorf("failed to load pull issue. Error: %w", err) 207 } 208 comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i]) 209 if err == nil && comment != nil { 210 notify_service.PullRequestPushCommits(ctx, pusher, pr, comment) 211 } 212 notify_service.PullRequestSynchronized(ctx, pusher, pr) 213 isForcePush := comment != nil && comment.IsForcePush 214 215 results = append(results, private.HookProcReceiveRefResult{ 216 OldOID: oldCommitID, 217 NewOID: opts.NewCommitIDs[i], 218 Ref: pr.GetGitRefName(), 219 OriginalRef: opts.RefFullNames[i], 220 IsForcePush: isForcePush, 221 IsCreatePR: false, 222 URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index), 223 ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx), 224 }) 225 } 226 227 return results, nil 228 } 229 230 // UserNameChanged handle user name change for agit flow pull 231 func UserNameChanged(ctx context.Context, user *user_model.User, newName string) error { 232 pulls, err := issues_model.GetAllUnmergedAgitPullRequestByPoster(ctx, user.ID) 233 if err != nil { 234 return err 235 } 236 237 newName = strings.ToLower(newName) 238 239 for _, pull := range pulls { 240 pull.HeadBranch = strings.TrimPrefix(pull.HeadBranch, user.LowerName+"/") 241 pull.HeadBranch = newName + "/" + pull.HeadBranch 242 if err = pull.UpdateCols(ctx, "head_branch"); err != nil { 243 return err 244 } 245 } 246 247 return nil 248 }