1// Copyright 2018 The Gitea Authors.
2// Copyright 2014 The Gogs Authors.
3// All rights reserved.
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file.
6
7package repo
8
9import (
10	"errors"
11	"fmt"
12	"html"
13	"net/http"
14	"net/url"
15	"strconv"
16	"strings"
17	"time"
18
19	"code.gitea.io/gitea/models"
20	"code.gitea.io/gitea/models/db"
21	repo_model "code.gitea.io/gitea/models/repo"
22	"code.gitea.io/gitea/models/unit"
23	user_model "code.gitea.io/gitea/models/user"
24	"code.gitea.io/gitea/modules/base"
25	"code.gitea.io/gitea/modules/context"
26	"code.gitea.io/gitea/modules/git"
27	"code.gitea.io/gitea/modules/log"
28	"code.gitea.io/gitea/modules/notification"
29	"code.gitea.io/gitea/modules/setting"
30	"code.gitea.io/gitea/modules/structs"
31	"code.gitea.io/gitea/modules/upload"
32	"code.gitea.io/gitea/modules/util"
33	"code.gitea.io/gitea/modules/web"
34	"code.gitea.io/gitea/modules/web/middleware"
35	"code.gitea.io/gitea/routers/utils"
36	"code.gitea.io/gitea/services/forms"
37	"code.gitea.io/gitea/services/gitdiff"
38	pull_service "code.gitea.io/gitea/services/pull"
39	repo_service "code.gitea.io/gitea/services/repository"
40)
41
42const (
43	tplFork        base.TplName = "repo/pulls/fork"
44	tplCompareDiff base.TplName = "repo/diff/compare"
45	tplPullCommits base.TplName = "repo/pulls/commits"
46	tplPullFiles   base.TplName = "repo/pulls/files"
47
48	pullRequestTemplateKey = "PullRequestTemplate"
49)
50
51var (
52	pullRequestTemplateCandidates = []string{
53		"PULL_REQUEST_TEMPLATE.md",
54		"pull_request_template.md",
55		".gitea/PULL_REQUEST_TEMPLATE.md",
56		".gitea/pull_request_template.md",
57		".github/PULL_REQUEST_TEMPLATE.md",
58		".github/pull_request_template.md",
59	}
60)
61
62func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
63	repo, err := repo_model.GetRepositoryByID(repoID)
64	if err != nil {
65		if repo_model.IsErrRepoNotExist(err) {
66			ctx.NotFound("GetRepositoryByID", nil)
67		} else {
68			ctx.ServerError("GetRepositoryByID", err)
69		}
70		return nil
71	}
72
73	perm, err := models.GetUserRepoPermission(repo, ctx.User)
74	if err != nil {
75		ctx.ServerError("GetUserRepoPermission", err)
76		return nil
77	}
78
79	if !perm.CanRead(unit.TypeCode) {
80		log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
81			"User in repo has Permissions: %-+v",
82			ctx.User,
83			unit.TypeCode,
84			ctx.Repo,
85			perm)
86		ctx.NotFound("getRepository", nil)
87		return nil
88	}
89	return repo
90}
91
92func getForkRepository(ctx *context.Context) *repo_model.Repository {
93	forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid"))
94	if ctx.Written() {
95		return nil
96	}
97
98	if forkRepo.IsEmpty {
99		log.Trace("Empty repository %-v", forkRepo)
100		ctx.NotFound("getForkRepository", nil)
101		return nil
102	}
103
104	if err := forkRepo.GetOwner(db.DefaultContext); err != nil {
105		ctx.ServerError("GetOwner", err)
106		return nil
107	}
108
109	ctx.Data["repo_name"] = forkRepo.Name
110	ctx.Data["description"] = forkRepo.Description
111	ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate
112	canForkToUser := forkRepo.OwnerID != ctx.User.ID && !repo_model.HasForkedRepo(ctx.User.ID, forkRepo.ID)
113
114	ctx.Data["ForkRepo"] = forkRepo
115
116	ownedOrgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.User.ID)
117	if err != nil {
118		ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
119		return nil
120	}
121	var orgs []*models.Organization
122	for _, org := range ownedOrgs {
123		if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, forkRepo.ID) {
124			orgs = append(orgs, org)
125		}
126	}
127
128	var traverseParentRepo = forkRepo
129	for {
130		if ctx.User.ID == traverseParentRepo.OwnerID {
131			canForkToUser = false
132		} else {
133			for i, org := range orgs {
134				if org.ID == traverseParentRepo.OwnerID {
135					orgs = append(orgs[:i], orgs[i+1:]...)
136					break
137				}
138			}
139		}
140
141		if !traverseParentRepo.IsFork {
142			break
143		}
144		traverseParentRepo, err = repo_model.GetRepositoryByID(traverseParentRepo.ForkID)
145		if err != nil {
146			ctx.ServerError("GetRepositoryByID", err)
147			return nil
148		}
149	}
150
151	ctx.Data["CanForkToUser"] = canForkToUser
152	ctx.Data["Orgs"] = orgs
153
154	if canForkToUser {
155		ctx.Data["ContextUser"] = ctx.User
156	} else if len(orgs) > 0 {
157		ctx.Data["ContextUser"] = orgs[0]
158	}
159
160	return forkRepo
161}
162
163// Fork render repository fork page
164func Fork(ctx *context.Context) {
165	ctx.Data["Title"] = ctx.Tr("new_fork")
166
167	getForkRepository(ctx)
168	if ctx.Written() {
169		return
170	}
171
172	ctx.HTML(http.StatusOK, tplFork)
173}
174
175// ForkPost response for forking a repository
176func ForkPost(ctx *context.Context) {
177	form := web.GetForm(ctx).(*forms.CreateRepoForm)
178	ctx.Data["Title"] = ctx.Tr("new_fork")
179
180	ctxUser := checkContextUser(ctx, form.UID)
181	if ctx.Written() {
182		return
183	}
184
185	forkRepo := getForkRepository(ctx)
186	if ctx.Written() {
187		return
188	}
189
190	ctx.Data["ContextUser"] = ctxUser
191
192	if ctx.HasError() {
193		ctx.HTML(http.StatusOK, tplFork)
194		return
195	}
196
197	var err error
198	var traverseParentRepo = forkRepo
199	for {
200		if ctxUser.ID == traverseParentRepo.OwnerID {
201			ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
202			return
203		}
204		repo := repo_model.GetForkedRepo(ctxUser.ID, traverseParentRepo.ID)
205		if repo != nil {
206			ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
207			return
208		}
209		if !traverseParentRepo.IsFork {
210			break
211		}
212		traverseParentRepo, err = repo_model.GetRepositoryByID(traverseParentRepo.ForkID)
213		if err != nil {
214			ctx.ServerError("GetRepositoryByID", err)
215			return
216		}
217	}
218
219	// Check if user is allowed to create repo's on the organization.
220	if ctxUser.IsOrganization() {
221		isAllowedToFork, err := models.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.User.ID)
222		if err != nil {
223			ctx.ServerError("CanCreateOrgRepo", err)
224			return
225		} else if !isAllowedToFork {
226			ctx.Error(http.StatusForbidden)
227			return
228		}
229	}
230
231	repo, err := repo_service.ForkRepository(ctx.User, ctxUser, repo_service.ForkRepoOptions{
232		BaseRepo:    forkRepo,
233		Name:        form.RepoName,
234		Description: form.Description,
235	})
236	if err != nil {
237		ctx.Data["Err_RepoName"] = true
238		switch {
239		case repo_model.IsErrRepoAlreadyExist(err):
240			ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
241		case db.IsErrNameReserved(err):
242			ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplFork, &form)
243		case db.IsErrNamePatternNotAllowed(err):
244			ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
245		default:
246			ctx.ServerError("ForkPost", err)
247		}
248		return
249	}
250
251	log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
252	ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
253}
254
255func checkPullInfo(ctx *context.Context) *models.Issue {
256	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
257	if err != nil {
258		if models.IsErrIssueNotExist(err) {
259			ctx.NotFound("GetIssueByIndex", err)
260		} else {
261			ctx.ServerError("GetIssueByIndex", err)
262		}
263		return nil
264	}
265	if err = issue.LoadPoster(); err != nil {
266		ctx.ServerError("LoadPoster", err)
267		return nil
268	}
269	if err := issue.LoadRepo(); err != nil {
270		ctx.ServerError("LoadRepo", err)
271		return nil
272	}
273	ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
274	ctx.Data["Issue"] = issue
275
276	if !issue.IsPull {
277		ctx.NotFound("ViewPullCommits", nil)
278		return nil
279	}
280
281	if err = issue.LoadPullRequest(); err != nil {
282		ctx.ServerError("LoadPullRequest", err)
283		return nil
284	}
285
286	if err = issue.PullRequest.LoadHeadRepo(); err != nil {
287		ctx.ServerError("LoadHeadRepo", err)
288		return nil
289	}
290
291	if ctx.IsSigned {
292		// Update issue-user.
293		if err = issue.ReadBy(ctx.User.ID); err != nil {
294			ctx.ServerError("ReadBy", err)
295			return nil
296		}
297	}
298
299	return issue
300}
301
302func setMergeTarget(ctx *context.Context, pull *models.PullRequest) {
303	if ctx.Repo.Owner.Name == pull.MustHeadUserName() {
304		ctx.Data["HeadTarget"] = pull.HeadBranch
305	} else if pull.HeadRepo == nil {
306		ctx.Data["HeadTarget"] = pull.MustHeadUserName() + ":" + pull.HeadBranch
307	} else {
308		ctx.Data["HeadTarget"] = pull.MustHeadUserName() + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
309	}
310	ctx.Data["BaseTarget"] = pull.BaseBranch
311	ctx.Data["HeadBranchHTMLURL"] = pull.GetHeadBranchHTMLURL()
312	ctx.Data["BaseBranchHTMLURL"] = pull.GetBaseBranchHTMLURL()
313}
314
315// PrepareMergedViewPullInfo show meta information for a merged pull request view page
316func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
317	pull := issue.PullRequest
318
319	setMergeTarget(ctx, pull)
320	ctx.Data["HasMerged"] = true
321
322	var baseCommit string
323	// Some migrated PR won't have any Base SHA and lose history, try to get one
324	if pull.MergeBase == "" {
325		var commitSHA, parentCommit string
326		// If there is a head or a patch file, and it is readable, grab info
327		commitSHA, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitRefName())
328		if err != nil {
329			// Head File does not exist, try the patch
330			commitSHA, err = ctx.Repo.GitRepo.ReadPatchCommit(pull.Index)
331			if err == nil {
332				// Recreate pull head in files for next time
333				if err := ctx.Repo.GitRepo.SetReference(pull.GetGitRefName(), commitSHA); err != nil {
334					log.Error("Could not write head file", err)
335				}
336			} else {
337				// There is no history available
338				log.Trace("No history file available for PR %d", pull.Index)
339			}
340		}
341		if commitSHA != "" {
342			// Get immediate parent of the first commit in the patch, grab history back
343			parentCommit, err = git.NewCommandContext(ctx, "rev-list", "-1", "--skip=1", commitSHA).RunInDir(ctx.Repo.GitRepo.Path)
344			if err == nil {
345				parentCommit = strings.TrimSpace(parentCommit)
346			}
347			// Special case on Git < 2.25 that doesn't fail on immediate empty history
348			if err != nil || parentCommit == "" {
349				log.Info("No known parent commit for PR %d, error: %v", pull.Index, err)
350				// bring at least partial history if it can work
351				parentCommit = commitSHA
352			}
353		}
354		baseCommit = parentCommit
355	} else {
356		// Keep an empty history or original commit
357		baseCommit = pull.MergeBase
358	}
359
360	compareInfo, err := ctx.Repo.GitRepo.GetCompareInfo(ctx.Repo.Repository.RepoPath(),
361		baseCommit, pull.GetGitRefName(), false, false)
362	if err != nil {
363		if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
364			ctx.Data["IsPullRequestBroken"] = true
365			ctx.Data["BaseTarget"] = pull.BaseBranch
366			ctx.Data["NumCommits"] = 0
367			ctx.Data["NumFiles"] = 0
368			return nil
369		}
370
371		ctx.ServerError("GetCompareInfo", err)
372		return nil
373	}
374	ctx.Data["NumCommits"] = len(compareInfo.Commits)
375	ctx.Data["NumFiles"] = compareInfo.NumFiles
376
377	if len(compareInfo.Commits) != 0 {
378		sha := compareInfo.Commits[0].ID.String()
379		commitStatuses, _, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, sha, db.ListOptions{})
380		if err != nil {
381			ctx.ServerError("GetLatestCommitStatus", err)
382			return nil
383		}
384		if len(commitStatuses) != 0 {
385			ctx.Data["LatestCommitStatuses"] = commitStatuses
386			ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
387		}
388	}
389
390	return compareInfo
391}
392
393// PrepareViewPullInfo show meta information for a pull request preview page
394func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.CompareInfo {
395	ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
396
397	repo := ctx.Repo.Repository
398	pull := issue.PullRequest
399
400	if err := pull.LoadHeadRepo(); err != nil {
401		ctx.ServerError("LoadHeadRepo", err)
402		return nil
403	}
404
405	if err := pull.LoadBaseRepo(); err != nil {
406		ctx.ServerError("LoadBaseRepo", err)
407		return nil
408	}
409
410	setMergeTarget(ctx, pull)
411
412	if err := pull.LoadProtectedBranch(); err != nil {
413		ctx.ServerError("LoadProtectedBranch", err)
414		return nil
415	}
416	ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
417
418	baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath())
419	if err != nil {
420		ctx.ServerError("OpenRepository", err)
421		return nil
422	}
423	defer baseGitRepo.Close()
424
425	if !baseGitRepo.IsBranchExist(pull.BaseBranch) {
426		ctx.Data["IsPullRequestBroken"] = true
427		ctx.Data["BaseTarget"] = pull.BaseBranch
428		ctx.Data["HeadTarget"] = pull.HeadBranch
429
430		sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
431		if err != nil {
432			ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
433			return nil
434		}
435		commitStatuses, _, err := models.GetLatestCommitStatus(repo.ID, sha, db.ListOptions{})
436		if err != nil {
437			ctx.ServerError("GetLatestCommitStatus", err)
438			return nil
439		}
440		if len(commitStatuses) > 0 {
441			ctx.Data["LatestCommitStatuses"] = commitStatuses
442			ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
443		}
444
445		compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
446			pull.MergeBase, pull.GetGitRefName(), false, false)
447		if err != nil {
448			if strings.Contains(err.Error(), "fatal: Not a valid object name") {
449				ctx.Data["IsPullRequestBroken"] = true
450				ctx.Data["BaseTarget"] = pull.BaseBranch
451				ctx.Data["NumCommits"] = 0
452				ctx.Data["NumFiles"] = 0
453				return nil
454			}
455
456			ctx.ServerError("GetCompareInfo", err)
457			return nil
458		}
459
460		ctx.Data["NumCommits"] = len(compareInfo.Commits)
461		ctx.Data["NumFiles"] = compareInfo.NumFiles
462		return compareInfo
463	}
464
465	var headBranchExist bool
466	var headBranchSha string
467	// HeadRepo may be missing
468	if pull.HeadRepo != nil {
469		headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath())
470		if err != nil {
471			ctx.ServerError("OpenRepository", err)
472			return nil
473		}
474		defer headGitRepo.Close()
475
476		if pull.Flow == models.PullRequestFlowGithub {
477			headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
478		} else {
479			headBranchExist = git.IsReferenceExist(ctx, baseGitRepo.Path, pull.GetGitRefName())
480		}
481
482		if headBranchExist {
483			if pull.Flow != models.PullRequestFlowGithub {
484				headBranchSha, err = baseGitRepo.GetRefCommitID(pull.GetGitRefName())
485			} else {
486				headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
487			}
488			if err != nil {
489				ctx.ServerError("GetBranchCommitID", err)
490				return nil
491			}
492		}
493	}
494
495	if headBranchExist {
496		ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(pull, ctx.User)
497		if err != nil {
498			ctx.ServerError("IsUserAllowedToUpdate", err)
499			return nil
500		}
501		ctx.Data["GetCommitMessages"] = pull_service.GetSquashMergeCommitMessages(pull)
502	}
503
504	sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
505	if err != nil {
506		if git.IsErrNotExist(err) {
507			ctx.Data["IsPullRequestBroken"] = true
508			if pull.IsSameRepo() {
509				ctx.Data["HeadTarget"] = pull.HeadBranch
510			} else if pull.HeadRepo == nil {
511				ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch
512			} else {
513				ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
514			}
515			ctx.Data["BaseTarget"] = pull.BaseBranch
516			ctx.Data["NumCommits"] = 0
517			ctx.Data["NumFiles"] = 0
518			return nil
519		}
520		ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
521		return nil
522	}
523
524	commitStatuses, _, err := models.GetLatestCommitStatus(repo.ID, sha, db.ListOptions{})
525	if err != nil {
526		ctx.ServerError("GetLatestCommitStatus", err)
527		return nil
528	}
529	if len(commitStatuses) > 0 {
530		ctx.Data["LatestCommitStatuses"] = commitStatuses
531		ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
532	}
533
534	if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
535		ctx.Data["is_context_required"] = func(context string) bool {
536			for _, c := range pull.ProtectedBranch.StatusCheckContexts {
537				if c == context {
538					return true
539				}
540			}
541			return false
542		}
543		ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
544	}
545
546	ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
547	ctx.Data["HeadBranchCommitID"] = headBranchSha
548	ctx.Data["PullHeadCommitID"] = sha
549
550	if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha {
551		ctx.Data["IsPullRequestBroken"] = true
552		if pull.IsSameRepo() {
553			ctx.Data["HeadTarget"] = pull.HeadBranch
554		} else if pull.HeadRepo == nil {
555			ctx.Data["HeadTarget"] = "<deleted>:" + pull.HeadBranch
556		} else {
557			ctx.Data["HeadTarget"] = pull.HeadRepo.OwnerName + ":" + pull.HeadBranch
558		}
559	}
560
561	compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
562		git.BranchPrefix+pull.BaseBranch, pull.GetGitRefName(), false, false)
563	if err != nil {
564		if strings.Contains(err.Error(), "fatal: Not a valid object name") {
565			ctx.Data["IsPullRequestBroken"] = true
566			ctx.Data["BaseTarget"] = pull.BaseBranch
567			ctx.Data["NumCommits"] = 0
568			ctx.Data["NumFiles"] = 0
569			return nil
570		}
571
572		ctx.ServerError("GetCompareInfo", err)
573		return nil
574	}
575
576	if compareInfo.HeadCommitID == compareInfo.MergeBase {
577		ctx.Data["IsNothingToCompare"] = true
578	}
579
580	if pull.IsWorkInProgress() {
581		ctx.Data["IsPullWorkInProgress"] = true
582		ctx.Data["WorkInProgressPrefix"] = pull.GetWorkInProgressPrefix()
583	}
584
585	if pull.IsFilesConflicted() {
586		ctx.Data["IsPullFilesConflicted"] = true
587		ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
588	}
589
590	ctx.Data["NumCommits"] = len(compareInfo.Commits)
591	ctx.Data["NumFiles"] = compareInfo.NumFiles
592	return compareInfo
593}
594
595// ViewPullCommits show commits for a pull request
596func ViewPullCommits(ctx *context.Context) {
597	ctx.Data["PageIsPullList"] = true
598	ctx.Data["PageIsPullCommits"] = true
599
600	issue := checkPullInfo(ctx)
601	if ctx.Written() {
602		return
603	}
604	pull := issue.PullRequest
605
606	var prInfo *git.CompareInfo
607	if pull.HasMerged {
608		prInfo = PrepareMergedViewPullInfo(ctx, issue)
609	} else {
610		prInfo = PrepareViewPullInfo(ctx, issue)
611	}
612
613	if ctx.Written() {
614		return
615	} else if prInfo == nil {
616		ctx.NotFound("ViewPullCommits", nil)
617		return
618	}
619
620	ctx.Data["Username"] = ctx.Repo.Owner.Name
621	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
622
623	commits := models.ConvertFromGitCommit(prInfo.Commits, ctx.Repo.Repository)
624	ctx.Data["Commits"] = commits
625	ctx.Data["CommitCount"] = len(commits)
626
627	getBranchData(ctx, issue)
628	ctx.HTML(http.StatusOK, tplPullCommits)
629}
630
631// ViewPullFiles render pull request changed files list page
632func ViewPullFiles(ctx *context.Context) {
633	ctx.Data["PageIsPullList"] = true
634	ctx.Data["PageIsPullFiles"] = true
635
636	issue := checkPullInfo(ctx)
637	if ctx.Written() {
638		return
639	}
640	pull := issue.PullRequest
641
642	var (
643		startCommitID string
644		endCommitID   string
645		gitRepo       = ctx.Repo.GitRepo
646	)
647
648	var prInfo *git.CompareInfo
649	if pull.HasMerged {
650		prInfo = PrepareMergedViewPullInfo(ctx, issue)
651	} else {
652		prInfo = PrepareViewPullInfo(ctx, issue)
653	}
654
655	if ctx.Written() {
656		return
657	} else if prInfo == nil {
658		ctx.NotFound("ViewPullFiles", nil)
659		return
660	}
661
662	headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName())
663	if err != nil {
664		ctx.ServerError("GetRefCommitID", err)
665		return
666	}
667
668	startCommitID = prInfo.MergeBase
669	endCommitID = headCommitID
670
671	ctx.Data["Username"] = ctx.Repo.Owner.Name
672	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
673	ctx.Data["AfterCommitID"] = endCommitID
674
675	fileOnly := ctx.FormBool("file-only")
676
677	maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
678	files := ctx.FormStrings("files")
679	if fileOnly && (len(files) == 2 || len(files) == 1) {
680		maxLines, maxFiles = -1, -1
681	}
682
683	diff, err := gitdiff.GetDiff(gitRepo,
684		&gitdiff.DiffOptions{
685			BeforeCommitID:     startCommitID,
686			AfterCommitID:      endCommitID,
687			SkipTo:             ctx.FormString("skip-to"),
688			MaxLines:           maxLines,
689			MaxLineCharacters:  setting.Git.MaxGitDiffLineCharacters,
690			MaxFiles:           maxFiles,
691			WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
692		}, ctx.FormStrings("files")...)
693	if err != nil {
694		ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err)
695		return
696	}
697
698	if err = diff.LoadComments(issue, ctx.User); err != nil {
699		ctx.ServerError("LoadComments", err)
700		return
701	}
702
703	if err = pull.LoadProtectedBranch(); err != nil {
704		ctx.ServerError("LoadProtectedBranch", err)
705		return
706	}
707
708	if pull.ProtectedBranch != nil {
709		glob := pull.ProtectedBranch.GetProtectedFilePatterns()
710		if len(glob) != 0 {
711			for _, file := range diff.Files {
712				file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name)
713			}
714		}
715	}
716
717	ctx.Data["Diff"] = diff
718	ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
719
720	baseCommit, err := ctx.Repo.GitRepo.GetCommit(startCommitID)
721	if err != nil {
722		ctx.ServerError("GetCommit", err)
723		return
724	}
725	commit, err := gitRepo.GetCommit(endCommitID)
726	if err != nil {
727		ctx.ServerError("GetCommit", err)
728		return
729	}
730
731	if ctx.IsSigned && ctx.User != nil {
732		if ctx.Data["CanMarkConversation"], err = models.CanMarkConversation(issue, ctx.User); err != nil {
733			ctx.ServerError("CanMarkConversation", err)
734			return
735		}
736	}
737
738	setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
739
740	ctx.Data["RequireHighlightJS"] = true
741	ctx.Data["RequireTribute"] = true
742	if ctx.Data["Assignees"], err = models.GetRepoAssignees(ctx.Repo.Repository); err != nil {
743		ctx.ServerError("GetAssignees", err)
744		return
745	}
746	handleTeamMentions(ctx)
747	if ctx.Written() {
748		return
749	}
750	ctx.Data["CurrentReview"], err = models.GetCurrentReview(ctx.User, issue)
751	if err != nil && !models.IsErrReviewNotExist(err) {
752		ctx.ServerError("GetCurrentReview", err)
753		return
754	}
755	getBranchData(ctx, issue)
756	ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
757	ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
758
759	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
760	upload.AddUploadContext(ctx, "comment")
761
762	ctx.HTML(http.StatusOK, tplPullFiles)
763}
764
765// UpdatePullRequest merge PR's baseBranch into headBranch
766func UpdatePullRequest(ctx *context.Context) {
767	issue := checkPullInfo(ctx)
768	if ctx.Written() {
769		return
770	}
771	if issue.IsClosed {
772		ctx.NotFound("MergePullRequest", nil)
773		return
774	}
775	if issue.PullRequest.HasMerged {
776		ctx.NotFound("MergePullRequest", nil)
777		return
778	}
779
780	rebase := ctx.FormString("style") == "rebase"
781
782	if err := issue.PullRequest.LoadBaseRepo(); err != nil {
783		ctx.ServerError("LoadBaseRepo", err)
784		return
785	}
786	if err := issue.PullRequest.LoadHeadRepo(); err != nil {
787		ctx.ServerError("LoadHeadRepo", err)
788		return
789	}
790
791	allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
792	if err != nil {
793		ctx.ServerError("IsUserAllowedToMerge", err)
794		return
795	}
796
797	// ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
798	if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) {
799		ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
800		ctx.Redirect(issue.Link())
801		return
802	}
803
804	// default merge commit message
805	message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
806
807	if err = pull_service.Update(issue.PullRequest, ctx.User, message, rebase); err != nil {
808		if models.IsErrMergeConflicts(err) {
809			conflictError := err.(models.ErrMergeConflicts)
810			flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{
811				"Message": ctx.Tr("repo.pulls.merge_conflict"),
812				"Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
813				"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
814			})
815			if err != nil {
816				ctx.ServerError("UpdatePullRequest.HTMLString", err)
817				return
818			}
819			ctx.Flash.Error(flashError)
820			ctx.Redirect(issue.Link())
821			return
822		} else if models.IsErrRebaseConflicts(err) {
823			conflictError := err.(models.ErrRebaseConflicts)
824			flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{
825				"Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
826				"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
827				"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
828			})
829			if err != nil {
830				ctx.ServerError("UpdatePullRequest.HTMLString", err)
831				return
832			}
833			ctx.Flash.Error(flashError)
834			ctx.Redirect(issue.Link())
835			return
836
837		}
838		ctx.Flash.Error(err.Error())
839		ctx.Redirect(issue.Link())
840		return
841	}
842
843	time.Sleep(1 * time.Second)
844
845	ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success"))
846	ctx.Redirect(issue.Link())
847}
848
849// MergePullRequest response for merging pull request
850func MergePullRequest(ctx *context.Context) {
851	form := web.GetForm(ctx).(*forms.MergePullRequestForm)
852	issue := checkPullInfo(ctx)
853	if ctx.Written() {
854		return
855	}
856	if issue.IsClosed {
857		if issue.IsPull {
858			ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
859			ctx.Redirect(issue.Link())
860			return
861		}
862		ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
863		ctx.Redirect(issue.Link())
864		return
865	}
866
867	pr := issue.PullRequest
868
869	allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.User)
870	if err != nil {
871		ctx.ServerError("IsUserAllowedToMerge", err)
872		return
873	}
874	if !allowedMerge {
875		ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
876		ctx.Redirect(issue.Link())
877		return
878	}
879
880	if pr.HasMerged {
881		ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
882		ctx.Redirect(issue.Link())
883		return
884	}
885
886	// handle manually-merged mark
887	if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged {
888		if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
889			if models.IsErrInvalidMergeStyle(err) {
890				ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
891				ctx.Redirect(issue.Link())
892				return
893			} else if strings.Contains(err.Error(), "Wrong commit ID") {
894				ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
895				ctx.Redirect(issue.Link())
896				return
897			}
898
899			ctx.ServerError("MergedManually", err)
900			return
901		}
902
903		ctx.Redirect(issue.Link())
904		return
905	}
906
907	if !pr.CanAutoMerge() {
908		ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
909		ctx.Redirect(issue.Link())
910		return
911	}
912
913	if pr.IsWorkInProgress() {
914		ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
915		ctx.Redirect(issue.Link())
916		return
917	}
918
919	if err := pull_service.CheckPRReadyToMerge(pr, false); err != nil {
920		if !models.IsErrNotAllowedToMerge(err) {
921			ctx.ServerError("Merge PR status", err)
922			return
923		}
924		if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil {
925			ctx.ServerError("IsUserRepoAdmin", err)
926			return
927		} else if !isRepoAdmin {
928			ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
929			ctx.Redirect(issue.Link())
930			return
931		}
932	}
933
934	if ctx.HasError() {
935		ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
936		ctx.Redirect(issue.Link())
937		return
938	}
939
940	message := strings.TrimSpace(form.MergeTitleField)
941	if len(message) == 0 {
942		if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleMerge {
943			message = pr.GetDefaultMergeMessage()
944		}
945		if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleRebaseMerge {
946			message = pr.GetDefaultMergeMessage()
947		}
948		if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleSquash {
949			message = pr.GetDefaultSquashMessage()
950		}
951	}
952
953	form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
954	if len(form.MergeMessageField) > 0 {
955		message += "\n\n" + form.MergeMessageField
956	}
957
958	pr.Issue = issue
959	pr.Issue.Repo = ctx.Repo.Repository
960
961	noDeps, err := models.IssueNoDependenciesLeft(issue)
962	if err != nil {
963		return
964	}
965
966	if !noDeps {
967		ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
968		ctx.Redirect(issue.Link())
969		return
970	}
971
972	if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil {
973		if models.IsErrInvalidMergeStyle(err) {
974			ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
975			ctx.Redirect(issue.Link())
976			return
977		} else if models.IsErrMergeConflicts(err) {
978			conflictError := err.(models.ErrMergeConflicts)
979			flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{
980				"Message": ctx.Tr("repo.editor.merge_conflict"),
981				"Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
982				"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
983			})
984			if err != nil {
985				ctx.ServerError("MergePullRequest.HTMLString", err)
986				return
987			}
988			ctx.Flash.Error(flashError)
989			ctx.Redirect(issue.Link())
990			return
991		} else if models.IsErrRebaseConflicts(err) {
992			conflictError := err.(models.ErrRebaseConflicts)
993			flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{
994				"Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
995				"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
996				"Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + "<br>" + utils.SanitizeFlashErrorString(conflictError.StdOut),
997			})
998			if err != nil {
999				ctx.ServerError("MergePullRequest.HTMLString", err)
1000				return
1001			}
1002			ctx.Flash.Error(flashError)
1003			ctx.Redirect(issue.Link())
1004			return
1005		} else if models.IsErrMergeUnrelatedHistories(err) {
1006			log.Debug("MergeUnrelatedHistories error: %v", err)
1007			ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
1008			ctx.Redirect(issue.Link())
1009			return
1010		} else if git.IsErrPushOutOfDate(err) {
1011			log.Debug("MergePushOutOfDate error: %v", err)
1012			ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date"))
1013			ctx.Redirect(issue.Link())
1014			return
1015		} else if models.IsErrSHADoesNotMatch(err) {
1016			log.Debug("MergeHeadOutOfDate error: %v", err)
1017			ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date"))
1018			ctx.Redirect(issue.Link())
1019			return
1020		} else if git.IsErrPushRejected(err) {
1021			log.Debug("MergePushRejected error: %v", err)
1022			pushrejErr := err.(*git.ErrPushRejected)
1023			message := pushrejErr.Message
1024			if len(message) == 0 {
1025				ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
1026			} else {
1027				flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{
1028					"Message": ctx.Tr("repo.pulls.push_rejected"),
1029					"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
1030					"Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
1031				})
1032				if err != nil {
1033					ctx.ServerError("MergePullRequest.HTMLString", err)
1034					return
1035				}
1036				ctx.Flash.Error(flashError)
1037			}
1038			ctx.Redirect(issue.Link())
1039			return
1040		}
1041		ctx.ServerError("Merge", err)
1042		return
1043	}
1044
1045	if err := stopTimerIfAvailable(ctx.User, issue); err != nil {
1046		ctx.ServerError("CreateOrStopIssueStopwatch", err)
1047		return
1048	}
1049
1050	log.Trace("Pull request merged: %d", pr.ID)
1051
1052	if form.DeleteBranchAfterMerge {
1053		// Don't cleanup when other pr use this branch as head branch
1054		exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch)
1055		if err != nil {
1056			ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
1057			return
1058		}
1059		if exist {
1060			ctx.Redirect(issue.Link())
1061			return
1062		}
1063
1064		var headRepo *git.Repository
1065		if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
1066			headRepo = ctx.Repo.GitRepo
1067		} else {
1068			headRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath())
1069			if err != nil {
1070				ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
1071				return
1072			}
1073			defer headRepo.Close()
1074		}
1075		deleteBranch(ctx, pr, headRepo)
1076	}
1077
1078	ctx.Redirect(issue.Link())
1079}
1080
1081func stopTimerIfAvailable(user *user_model.User, issue *models.Issue) error {
1082
1083	if models.StopwatchExists(user.ID, issue.ID) {
1084		if err := models.CreateOrStopIssueStopwatch(user, issue); err != nil {
1085			return err
1086		}
1087	}
1088
1089	return nil
1090}
1091
1092// CompareAndPullRequestPost response for creating pull request
1093func CompareAndPullRequestPost(ctx *context.Context) {
1094	form := web.GetForm(ctx).(*forms.CreateIssueForm)
1095	ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
1096	ctx.Data["PageIsComparePull"] = true
1097	ctx.Data["IsDiffCompare"] = true
1098	ctx.Data["IsRepoToolbarCommits"] = true
1099	ctx.Data["RequireTribute"] = true
1100	ctx.Data["RequireHighlightJS"] = true
1101	ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
1102	ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
1103	upload.AddUploadContext(ctx, "comment")
1104	ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypePullRequests)
1105
1106	var (
1107		repo        = ctx.Repo.Repository
1108		attachments []string
1109	)
1110
1111	ci := ParseCompareInfo(ctx)
1112	defer func() {
1113		if ci != nil && ci.HeadGitRepo != nil {
1114			ci.HeadGitRepo.Close()
1115		}
1116	}()
1117	if ctx.Written() {
1118		return
1119	}
1120
1121	labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
1122	if ctx.Written() {
1123		return
1124	}
1125
1126	if setting.Attachment.Enabled {
1127		attachments = form.Files
1128	}
1129
1130	if ctx.HasError() {
1131		middleware.AssignForm(form, ctx.Data)
1132
1133		// This stage is already stop creating new pull request, so it does not matter if it has
1134		// something to compare or not.
1135		PrepareCompareDiff(ctx, ci,
1136			gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
1137		if ctx.Written() {
1138			return
1139		}
1140
1141		if len(form.Title) > 255 {
1142			var trailer string
1143			form.Title, trailer = util.SplitStringAtByteN(form.Title, 255)
1144
1145			form.Content = trailer + "\n\n" + form.Content
1146		}
1147		middleware.AssignForm(form, ctx.Data)
1148
1149		ctx.HTML(http.StatusOK, tplCompareDiff)
1150		return
1151	}
1152
1153	if util.IsEmptyString(form.Title) {
1154		PrepareCompareDiff(ctx, ci,
1155			gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)))
1156		if ctx.Written() {
1157			return
1158		}
1159
1160		ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplCompareDiff, form)
1161		return
1162	}
1163
1164	pullIssue := &models.Issue{
1165		RepoID:      repo.ID,
1166		Repo:        repo,
1167		Title:       form.Title,
1168		PosterID:    ctx.User.ID,
1169		Poster:      ctx.User,
1170		MilestoneID: milestoneID,
1171		IsPull:      true,
1172		Content:     form.Content,
1173	}
1174	pullRequest := &models.PullRequest{
1175		HeadRepoID: ci.HeadRepo.ID,
1176		BaseRepoID: repo.ID,
1177		HeadBranch: ci.HeadBranch,
1178		BaseBranch: ci.BaseBranch,
1179		HeadRepo:   ci.HeadRepo,
1180		BaseRepo:   repo,
1181		MergeBase:  ci.CompareInfo.MergeBase,
1182		Type:       models.PullRequestGitea,
1183	}
1184	// FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
1185	// instead of 500.
1186
1187	if err := pull_service.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, assigneeIDs); err != nil {
1188		if models.IsErrUserDoesNotHaveAccessToRepo(err) {
1189			ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error())
1190			return
1191		} else if git.IsErrPushRejected(err) {
1192			pushrejErr := err.(*git.ErrPushRejected)
1193			message := pushrejErr.Message
1194			if len(message) == 0 {
1195				ctx.Flash.Error(ctx.Tr("repo.pulls.push_rejected_no_message"))
1196			} else {
1197				flashError, err := ctx.RenderToString(tplAlertDetails, map[string]interface{}{
1198					"Message": ctx.Tr("repo.pulls.push_rejected"),
1199					"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
1200					"Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
1201				})
1202				if err != nil {
1203					ctx.ServerError("CompareAndPullRequest.HTMLString", err)
1204					return
1205				}
1206				ctx.Flash.Error(flashError)
1207			}
1208			ctx.Redirect(pullIssue.Link())
1209			return
1210		}
1211		ctx.ServerError("NewPullRequest", err)
1212		return
1213	}
1214
1215	log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
1216	ctx.Redirect(pullIssue.Link())
1217}
1218
1219// CleanUpPullRequest responses for delete merged branch when PR has been merged
1220func CleanUpPullRequest(ctx *context.Context) {
1221	issue := checkPullInfo(ctx)
1222	if ctx.Written() {
1223		return
1224	}
1225
1226	pr := issue.PullRequest
1227
1228	// Don't cleanup unmerged and unclosed PRs
1229	if !pr.HasMerged && !issue.IsClosed {
1230		ctx.NotFound("CleanUpPullRequest", nil)
1231		return
1232	}
1233
1234	// Don't cleanup when there are other PR's that use this branch as head branch.
1235	exist, err := models.HasUnmergedPullRequestsByHeadInfo(pr.HeadRepoID, pr.HeadBranch)
1236	if err != nil {
1237		ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
1238		return
1239	}
1240	if exist {
1241		ctx.NotFound("CleanUpPullRequest", nil)
1242		return
1243	}
1244
1245	if err := pr.LoadHeadRepo(); err != nil {
1246		ctx.ServerError("LoadHeadRepo", err)
1247		return
1248	} else if pr.HeadRepo == nil {
1249		// Forked repository has already been deleted
1250		ctx.NotFound("CleanUpPullRequest", nil)
1251		return
1252	} else if err = pr.LoadBaseRepo(); err != nil {
1253		ctx.ServerError("LoadBaseRepo", err)
1254		return
1255	} else if err = pr.HeadRepo.GetOwner(db.DefaultContext); err != nil {
1256		ctx.ServerError("HeadRepo.GetOwner", err)
1257		return
1258	}
1259
1260	perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User)
1261	if err != nil {
1262		ctx.ServerError("GetUserRepoPermission", err)
1263		return
1264	}
1265	if !perm.CanWrite(unit.TypeCode) {
1266		ctx.NotFound("CleanUpPullRequest", nil)
1267		return
1268	}
1269
1270	fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
1271
1272	var gitBaseRepo *git.Repository
1273
1274	// Assume that the base repo is the current context (almost certainly)
1275	if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.BaseRepoID && ctx.Repo.GitRepo != nil {
1276		gitBaseRepo = ctx.Repo.GitRepo
1277	} else {
1278		// If not just open it
1279		gitBaseRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath())
1280		if err != nil {
1281			ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err)
1282			return
1283		}
1284		defer gitBaseRepo.Close()
1285	}
1286
1287	// Now assume that the head repo is the same as the base repo (reasonable chance)
1288	gitRepo := gitBaseRepo
1289	// But if not: is it the same as the context?
1290	if pr.BaseRepoID != pr.HeadRepoID && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
1291		gitRepo = ctx.Repo.GitRepo
1292	} else if pr.BaseRepoID != pr.HeadRepoID {
1293		// Otherwise just load it up
1294		gitRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath())
1295		if err != nil {
1296			ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
1297			return
1298		}
1299		defer gitRepo.Close()
1300	}
1301
1302	defer func() {
1303		ctx.JSON(http.StatusOK, map[string]interface{}{
1304			"redirect": issue.Link(),
1305		})
1306	}()
1307
1308	// Check if branch has no new commits
1309	headCommitID, err := gitBaseRepo.GetRefCommitID(pr.GetGitRefName())
1310	if err != nil {
1311		log.Error("GetRefCommitID: %v", err)
1312		ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
1313		return
1314	}
1315	branchCommitID, err := gitRepo.GetBranchCommitID(pr.HeadBranch)
1316	if err != nil {
1317		log.Error("GetBranchCommitID: %v", err)
1318		ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
1319		return
1320	}
1321	if headCommitID != branchCommitID {
1322		ctx.Flash.Error(ctx.Tr("repo.branch.delete_branch_has_new_commits", fullBranchName))
1323		return
1324	}
1325
1326	deleteBranch(ctx, pr, gitRepo)
1327}
1328
1329func deleteBranch(ctx *context.Context, pr *models.PullRequest, gitRepo *git.Repository) {
1330	fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch
1331	if err := repo_service.DeleteBranch(ctx.User, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil {
1332		switch {
1333		case git.IsErrBranchNotExist(err):
1334			ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
1335		case errors.Is(err, repo_service.ErrBranchIsDefault):
1336			ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
1337		case errors.Is(err, repo_service.ErrBranchIsProtected):
1338			ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
1339		default:
1340			log.Error("DeleteBranch: %v", err)
1341			ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
1342		}
1343		return
1344	}
1345
1346	if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, pr.IssueID, pr.HeadBranch); err != nil {
1347		// Do not fail here as branch has already been deleted
1348		log.Error("DeleteBranch: %v", err)
1349	}
1350
1351	ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
1352}
1353
1354// DownloadPullDiff render a pull's raw diff
1355func DownloadPullDiff(ctx *context.Context) {
1356	DownloadPullDiffOrPatch(ctx, false)
1357}
1358
1359// DownloadPullPatch render a pull's raw patch
1360func DownloadPullPatch(ctx *context.Context) {
1361	DownloadPullDiffOrPatch(ctx, true)
1362}
1363
1364// DownloadPullDiffOrPatch render a pull's raw diff or patch
1365func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
1366	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
1367	if err != nil {
1368		if models.IsErrPullRequestNotExist(err) {
1369			ctx.NotFound("GetPullRequestByIndex", err)
1370		} else {
1371			ctx.ServerError("GetPullRequestByIndex", err)
1372		}
1373		return
1374	}
1375
1376	binary := ctx.FormBool("binary")
1377
1378	if err := pull_service.DownloadDiffOrPatch(pr, ctx, patch, binary); err != nil {
1379		ctx.ServerError("DownloadDiffOrPatch", err)
1380		return
1381	}
1382}
1383
1384// UpdatePullRequestTarget change pull request's target branch
1385func UpdatePullRequestTarget(ctx *context.Context) {
1386	issue := GetActionIssue(ctx)
1387	pr := issue.PullRequest
1388	if ctx.Written() {
1389		return
1390	}
1391	if !issue.IsPull {
1392		ctx.Error(http.StatusNotFound)
1393		return
1394	}
1395
1396	if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) {
1397		ctx.Error(http.StatusForbidden)
1398		return
1399	}
1400
1401	targetBranch := ctx.FormTrim("target_branch")
1402	if len(targetBranch) == 0 {
1403		ctx.Error(http.StatusNoContent)
1404		return
1405	}
1406
1407	if err := pull_service.ChangeTargetBranch(pr, ctx.User, targetBranch); err != nil {
1408		if models.IsErrPullRequestAlreadyExists(err) {
1409			err := err.(models.ErrPullRequestAlreadyExists)
1410
1411			RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
1412			errorMessage := ctx.Tr("repo.pulls.has_pull_request", html.EscapeString(ctx.Repo.RepoLink+"/pulls/"+strconv.FormatInt(err.IssueID, 10)), html.EscapeString(RepoRelPath), err.IssueID) // FIXME: Creates url insidde locale string
1413
1414			ctx.Flash.Error(errorMessage)
1415			ctx.JSON(http.StatusConflict, map[string]interface{}{
1416				"error":      err.Error(),
1417				"user_error": errorMessage,
1418			})
1419		} else if models.IsErrIssueIsClosed(err) {
1420			errorMessage := ctx.Tr("repo.pulls.is_closed")
1421
1422			ctx.Flash.Error(errorMessage)
1423			ctx.JSON(http.StatusConflict, map[string]interface{}{
1424				"error":      err.Error(),
1425				"user_error": errorMessage,
1426			})
1427		} else if models.IsErrPullRequestHasMerged(err) {
1428			errorMessage := ctx.Tr("repo.pulls.has_merged")
1429
1430			ctx.Flash.Error(errorMessage)
1431			ctx.JSON(http.StatusConflict, map[string]interface{}{
1432				"error":      err.Error(),
1433				"user_error": errorMessage,
1434			})
1435		} else if models.IsErrBranchesEqual(err) {
1436			errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
1437
1438			ctx.Flash.Error(errorMessage)
1439			ctx.JSON(http.StatusBadRequest, map[string]interface{}{
1440				"error":      err.Error(),
1441				"user_error": errorMessage,
1442			})
1443		} else {
1444			ctx.ServerError("UpdatePullRequestTarget", err)
1445		}
1446		return
1447	}
1448	notification.NotifyPullRequestChangeTargetBranch(ctx.User, pr, targetBranch)
1449
1450	ctx.JSON(http.StatusOK, map[string]interface{}{
1451		"base_branch": pr.BaseBranch,
1452	})
1453}
1454