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  }