1// Copyright 2017 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package repo
6
7import (
8	"fmt"
9	"net/http"
10	"strings"
11	"time"
12
13	"code.gitea.io/gitea/models"
14	"code.gitea.io/gitea/models/perm"
15	repo_model "code.gitea.io/gitea/models/repo"
16	"code.gitea.io/gitea/modules/base"
17	"code.gitea.io/gitea/modules/context"
18	"code.gitea.io/gitea/modules/git"
19	"code.gitea.io/gitea/modules/log"
20	"code.gitea.io/gitea/modules/setting"
21	"code.gitea.io/gitea/modules/util"
22	"code.gitea.io/gitea/modules/web"
23	"code.gitea.io/gitea/services/forms"
24	pull_service "code.gitea.io/gitea/services/pull"
25	"code.gitea.io/gitea/services/repository"
26)
27
28// ProtectedBranch render the page to protect the repository
29func ProtectedBranch(ctx *context.Context) {
30	ctx.Data["Title"] = ctx.Tr("repo.settings")
31	ctx.Data["PageIsSettingsBranches"] = true
32
33	protectedBranches, err := models.GetProtectedBranches(ctx.Repo.Repository.ID)
34	if err != nil {
35		ctx.ServerError("GetProtectedBranches", err)
36		return
37	}
38	ctx.Data["ProtectedBranches"] = protectedBranches
39
40	branches := ctx.Data["Branches"].([]string)
41	leftBranches := make([]string, 0, len(branches)-len(protectedBranches))
42	for _, b := range branches {
43		var protected bool
44		for _, pb := range protectedBranches {
45			if b == pb.BranchName {
46				protected = true
47				break
48			}
49		}
50		if !protected {
51			leftBranches = append(leftBranches, b)
52		}
53	}
54
55	ctx.Data["LeftBranches"] = leftBranches
56
57	ctx.HTML(http.StatusOK, tplBranches)
58}
59
60// ProtectedBranchPost response for protect for a branch of a repository
61func ProtectedBranchPost(ctx *context.Context) {
62	ctx.Data["Title"] = ctx.Tr("repo.settings")
63	ctx.Data["PageIsSettingsBranches"] = true
64
65	repo := ctx.Repo.Repository
66
67	switch ctx.FormString("action") {
68	case "default_branch":
69		if ctx.HasError() {
70			ctx.HTML(http.StatusOK, tplBranches)
71			return
72		}
73
74		branch := ctx.FormString("branch")
75		if !ctx.Repo.GitRepo.IsBranchExist(branch) {
76			ctx.Status(404)
77			return
78		} else if repo.DefaultBranch != branch {
79			repo.DefaultBranch = branch
80			if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
81				if !git.IsErrUnsupportedVersion(err) {
82					ctx.ServerError("SetDefaultBranch", err)
83					return
84				}
85			}
86			if err := repo_model.UpdateDefaultBranch(repo); err != nil {
87				ctx.ServerError("SetDefaultBranch", err)
88				return
89			}
90		}
91
92		log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
93
94		ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
95		ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
96	default:
97		ctx.NotFound("", nil)
98	}
99}
100
101// SettingsProtectedBranch renders the protected branch setting page
102func SettingsProtectedBranch(c *context.Context) {
103	branch := c.Params("*")
104	if !c.Repo.GitRepo.IsBranchExist(branch) {
105		c.NotFound("IsBranchExist", nil)
106		return
107	}
108
109	c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + branch
110	c.Data["PageIsSettingsBranches"] = true
111
112	protectBranch, err := models.GetProtectedBranchBy(c.Repo.Repository.ID, branch)
113	if err != nil {
114		if !git.IsErrBranchNotExist(err) {
115			c.ServerError("GetProtectBranchOfRepoByName", err)
116			return
117		}
118	}
119
120	if protectBranch == nil {
121		// No options found, create defaults.
122		protectBranch = &models.ProtectedBranch{
123			BranchName: branch,
124		}
125	}
126
127	users, err := models.GetRepoReaders(c.Repo.Repository)
128	if err != nil {
129		c.ServerError("Repo.Repository.GetReaders", err)
130		return
131	}
132	c.Data["Users"] = users
133	c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",")
134	c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",")
135	c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",")
136	contexts, _ := models.FindRepoRecentCommitStatusContexts(c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
137	for _, ctx := range protectBranch.StatusCheckContexts {
138		var found bool
139		for i := range contexts {
140			if contexts[i] == ctx {
141				found = true
142				break
143			}
144		}
145		if !found {
146			contexts = append(contexts, ctx)
147		}
148	}
149
150	c.Data["branch_status_check_contexts"] = contexts
151	c.Data["is_context_required"] = func(context string) bool {
152		for _, c := range protectBranch.StatusCheckContexts {
153			if c == context {
154				return true
155			}
156		}
157		return false
158	}
159
160	if c.Repo.Owner.IsOrganization() {
161		teams, err := models.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead)
162		if err != nil {
163			c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
164			return
165		}
166		c.Data["Teams"] = teams
167		c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistTeamIDs), ",")
168		c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistTeamIDs), ",")
169		c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistTeamIDs), ",")
170	}
171
172	c.Data["Branch"] = protectBranch
173	c.HTML(http.StatusOK, tplProtectedBranch)
174}
175
176// SettingsProtectedBranchPost updates the protected branch settings
177func SettingsProtectedBranchPost(ctx *context.Context) {
178	f := web.GetForm(ctx).(*forms.ProtectBranchForm)
179	branch := ctx.Params("*")
180	if !ctx.Repo.GitRepo.IsBranchExist(branch) {
181		ctx.NotFound("IsBranchExist", nil)
182		return
183	}
184
185	protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
186	if err != nil {
187		if !git.IsErrBranchNotExist(err) {
188			ctx.ServerError("GetProtectBranchOfRepoByName", err)
189			return
190		}
191	}
192
193	if f.Protected {
194		if protectBranch == nil {
195			// No options found, create defaults.
196			protectBranch = &models.ProtectedBranch{
197				RepoID:     ctx.Repo.Repository.ID,
198				BranchName: branch,
199			}
200		}
201		if f.RequiredApprovals < 0 {
202			ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
203			ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch)))
204		}
205
206		var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
207		switch f.EnablePush {
208		case "all":
209			protectBranch.CanPush = true
210			protectBranch.EnableWhitelist = false
211			protectBranch.WhitelistDeployKeys = false
212		case "whitelist":
213			protectBranch.CanPush = true
214			protectBranch.EnableWhitelist = true
215			protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
216			if strings.TrimSpace(f.WhitelistUsers) != "" {
217				whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
218			}
219			if strings.TrimSpace(f.WhitelistTeams) != "" {
220				whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
221			}
222		default:
223			protectBranch.CanPush = false
224			protectBranch.EnableWhitelist = false
225			protectBranch.WhitelistDeployKeys = false
226		}
227
228		protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
229		if f.EnableMergeWhitelist {
230			if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
231				mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
232			}
233			if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
234				mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
235			}
236		}
237
238		protectBranch.EnableStatusCheck = f.EnableStatusCheck
239		if f.EnableStatusCheck {
240			protectBranch.StatusCheckContexts = f.StatusCheckContexts
241		} else {
242			protectBranch.StatusCheckContexts = nil
243		}
244
245		protectBranch.RequiredApprovals = f.RequiredApprovals
246		protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist
247		if f.EnableApprovalsWhitelist {
248			if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
249				approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
250			}
251			if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
252				approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
253			}
254		}
255		protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
256		protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
257		protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
258		protectBranch.RequireSignedCommits = f.RequireSignedCommits
259		protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
260		protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
261		protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
262
263		err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
264			UserIDs:          whitelistUsers,
265			TeamIDs:          whitelistTeams,
266			MergeUserIDs:     mergeWhitelistUsers,
267			MergeTeamIDs:     mergeWhitelistTeams,
268			ApprovalsUserIDs: approvalsWhitelistUsers,
269			ApprovalsTeamIDs: approvalsWhitelistTeams,
270		})
271		if err != nil {
272			ctx.ServerError("UpdateProtectBranch", err)
273			return
274		}
275		if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
276			ctx.ServerError("CheckPrsForBaseBranch", err)
277			return
278		}
279		ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch))
280		ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch)))
281	} else {
282		if protectBranch != nil {
283			if err := models.DeleteProtectedBranch(ctx.Repo.Repository.ID, protectBranch.ID); err != nil {
284				ctx.ServerError("DeleteProtectedBranch", err)
285				return
286			}
287		}
288		ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", branch))
289		ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
290	}
291}
292
293// RenameBranchPost responses for rename a branch
294func RenameBranchPost(ctx *context.Context) {
295	form := web.GetForm(ctx).(*forms.RenameBranchForm)
296
297	if !ctx.Repo.CanCreateBranch() {
298		ctx.NotFound("RenameBranch", nil)
299		return
300	}
301
302	if ctx.HasError() {
303		ctx.Flash.Error(ctx.GetErrMsg())
304		ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
305		return
306	}
307
308	msg, err := repository.RenameBranch(ctx.Repo.Repository, ctx.User, ctx.Repo.GitRepo, form.From, form.To)
309	if err != nil {
310		ctx.ServerError("RenameBranch", err)
311		return
312	}
313
314	if msg == "target_exist" {
315		ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To))
316		ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
317		return
318	}
319
320	if msg == "from_not_exist" {
321		ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From))
322		ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
323		return
324	}
325
326	ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To))
327	ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
328}
329