Skip to content

Commit b5c7eaa

Browse files
committed
Protected branches with templates
Signed-off-by: jolheiser <[email protected]>
1 parent 9465e60 commit b5c7eaa

File tree

11 files changed

+141
-47
lines changed

11 files changed

+141
-47
lines changed

models/branches.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -311,66 +311,75 @@ type WhitelistOptions struct {
311311
// This function also performs check if whitelist user and team's IDs have been changed
312312
// to avoid unnecessary whitelist delete and regenerate.
313313
func UpdateProtectBranch(repo *Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
314+
return updateProtectBranch(x, repo, protectBranch, opts)
315+
}
316+
317+
func updateProtectBranch(e Engine, repo *Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
314318
if err = repo.GetOwner(); err != nil {
315319
return fmt.Errorf("GetOwner: %v", err)
316320
}
317321

318-
whitelist, err := updateUserWhitelist(repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
322+
whitelist, err := updateUserWhitelist(e, repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
319323
if err != nil {
320324
return err
321325
}
322326
protectBranch.WhitelistUserIDs = whitelist
323327

324-
whitelist, err = updateUserWhitelist(repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
328+
whitelist, err = updateUserWhitelist(e, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
325329
if err != nil {
326330
return err
327331
}
328332
protectBranch.MergeWhitelistUserIDs = whitelist
329333

330-
whitelist, err = updateApprovalWhitelist(repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
334+
whitelist, err = updateApprovalWhitelist(e, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
331335
if err != nil {
332336
return err
333337
}
334338
protectBranch.ApprovalsWhitelistUserIDs = whitelist
335339

336340
// if the repo is in an organization
337-
whitelist, err = updateTeamWhitelist(repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
341+
whitelist, err = updateTeamWhitelist(e, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
338342
if err != nil {
339343
return err
340344
}
341345
protectBranch.WhitelistTeamIDs = whitelist
342346

343-
whitelist, err = updateTeamWhitelist(repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
347+
whitelist, err = updateTeamWhitelist(e, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
344348
if err != nil {
345349
return err
346350
}
347351
protectBranch.MergeWhitelistTeamIDs = whitelist
348352

349-
whitelist, err = updateTeamWhitelist(repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs)
353+
whitelist, err = updateTeamWhitelist(e, repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs)
350354
if err != nil {
351355
return err
352356
}
353357
protectBranch.ApprovalsWhitelistTeamIDs = whitelist
354358

355359
// Make sure protectBranch.ID is not 0 for whitelists
356360
if protectBranch.ID == 0 {
357-
if _, err = x.Insert(protectBranch); err != nil {
361+
if _, err = e.Insert(protectBranch); err != nil {
358362
return fmt.Errorf("Insert: %v", err)
359363
}
360364
return nil
361365
}
362366

363-
if _, err = x.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
367+
if _, err = e.ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
364368
return fmt.Errorf("Update: %v", err)
365369
}
366370

367371
return nil
368372
}
369373

370374
// GetProtectedBranches get all protected branches
371-
func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
375+
func (repo *Repository) getProtectedBranches(e Engine) ([]*ProtectedBranch, error) {
372376
protectedBranches := make([]*ProtectedBranch, 0)
373-
return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})
377+
return protectedBranches, e.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})
378+
}
379+
380+
// GetProtectedBranches get all protected branches
381+
func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
382+
return repo.getProtectedBranches(x)
374383
}
375384

376385
// GetBranchProtection get the branch protection of a branch
@@ -419,15 +428,15 @@ func (repo *Repository) IsProtectedBranchForPush(branchName string, doer *User)
419428

420429
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
421430
// the users from newWhitelist which have explicit read or write access to the repo.
422-
func updateApprovalWhitelist(repo *Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
431+
func updateApprovalWhitelist(e Engine, repo *Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
423432
hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
424433
if !hasUsersChanged {
425434
return currentWhitelist, nil
426435
}
427436

428437
whitelist = make([]int64, 0, len(newWhitelist))
429438
for _, userID := range newWhitelist {
430-
if reader, err := repo.IsReader(userID); err != nil {
439+
if reader, err := repo.isReader(e, userID); err != nil {
431440
return nil, err
432441
} else if !reader {
433442
continue
@@ -440,19 +449,19 @@ func updateApprovalWhitelist(repo *Repository, currentWhitelist, newWhitelist []
440449

441450
// updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with
442451
// the users from newWhitelist which have write access to the repo.
443-
func updateUserWhitelist(repo *Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
452+
func updateUserWhitelist(e Engine, repo *Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
444453
hasUsersChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
445454
if !hasUsersChanged {
446455
return currentWhitelist, nil
447456
}
448457

449458
whitelist = make([]int64, 0, len(newWhitelist))
450459
for _, userID := range newWhitelist {
451-
user, err := GetUserByID(userID)
460+
user, err := getUserByID(e, userID)
452461
if err != nil {
453462
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
454463
}
455-
perm, err := GetUserRepoPermission(repo, user)
464+
perm, err := getUserRepoPermission(e, repo, user)
456465
if err != nil {
457466
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
458467
}
@@ -469,13 +478,13 @@ func updateUserWhitelist(repo *Repository, currentWhitelist, newWhitelist []int6
469478

470479
// updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with
471480
// the teams from newWhitelist which have write access to the repo.
472-
func updateTeamWhitelist(repo *Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
481+
func updateTeamWhitelist(e Engine, repo *Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
473482
hasTeamsChanged := !util.IsSliceInt64Eq(currentWhitelist, newWhitelist)
474483
if !hasTeamsChanged {
475484
return currentWhitelist, nil
476485
}
477486

478-
teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, AccessModeRead)
487+
teams, err := getTeamsWithAccessToRepo(e, repo.OwnerID, repo.ID, AccessModeRead)
479488
if err != nil {
480489
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
481490
}

models/org_team.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1021,8 +1021,12 @@ func removeTeamRepo(e Engine, teamID, repoID int64) error {
10211021

10221022
// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository.
10231023
func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) {
1024+
return getTeamsWithAccessToRepo(x, orgID, repoID, mode)
1025+
}
1026+
1027+
func getTeamsWithAccessToRepo(e Engine, orgID, repoID int64, mode AccessMode) ([]*Team, error) {
10241028
teams := make([]*Team, 0, 5)
1025-
return teams, x.Where("team.authorize >= ?", mode).
1029+
return teams, e.Where("team.authorize >= ?", mode).
10261030
Join("INNER", "team_repo", "team_repo.team_id = team.id").
10271031
And("team_repo.org_id = ?", orgID).
10281032
And("team_repo.repo_id = ?", repoID).

models/repo.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,10 +821,14 @@ func (repo *Repository) GetWriters() (_ []*User, err error) {
821821

822822
// IsReader returns true if user has explicit read access or higher to the repository.
823823
func (repo *Repository) IsReader(userID int64) (bool, error) {
824+
return repo.isReader(x, userID)
825+
}
826+
827+
func (repo *Repository) isReader(e Engine, userID int64) (bool, error) {
824828
if repo.OwnerID == userID {
825829
return true, nil
826830
}
827-
return x.Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, AccessModeRead).Get(&Access{})
831+
return e.Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, AccessModeRead).Get(&Access{})
828832
}
829833

830834
// getUsersWithAccessMode returns users that have at least given access mode to the repository.

models/repo_generate.go

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package models
66

77
import (
8+
"fmt"
89
"strconv"
910
"strings"
1011

@@ -18,20 +19,21 @@ import (
1819

1920
// GenerateRepoOptions contains the template units to generate
2021
type GenerateRepoOptions struct {
21-
Name string
22-
Description string
23-
Private bool
24-
GitContent bool
25-
Topics bool
26-
GitHooks bool
27-
Webhooks bool
28-
Avatar bool
29-
IssueLabels bool
22+
Name string
23+
Description string
24+
Private bool
25+
GitContent bool
26+
Topics bool
27+
GitHooks bool
28+
Webhooks bool
29+
Avatar bool
30+
IssueLabels bool
31+
BranchProtection bool
3032
}
3133

3234
// IsValid checks whether at least one option is chosen for generation
3335
func (gro GenerateRepoOptions) IsValid() bool {
34-
return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // or other items as they are added
36+
return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels || gro.BranchProtection // or other items as they are added
3537
}
3638

3739
// GiteaTemplate holds information about a .gitea/template file
@@ -166,3 +168,49 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository)
166168
}
167169
return nil
168170
}
171+
172+
// GenerateBranchProtection generates branch protection from a template repository
173+
func GenerateBranchProtection(ctx DBContext, doer *User, templateRepo, generateRepo *Repository) error {
174+
branches, err := templateRepo.getProtectedBranches(ctx.e)
175+
if err != nil {
176+
return err
177+
}
178+
179+
for _, branch := range branches {
180+
// Create the branches (other than default, which exists already)
181+
if !strings.EqualFold(generateRepo.DefaultBranch, branch.BranchName) {
182+
if err := git.Push(generateRepo.RepoPath(), git.PushOptions{
183+
Remote: generateRepo.RepoPath(),
184+
Branch: fmt.Sprintf("%s:%s%s", generateRepo.DefaultBranch, git.BranchPrefix, branch.BranchName),
185+
Env: InternalPushingEnvironment(doer, generateRepo),
186+
}); err != nil {
187+
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
188+
return err
189+
}
190+
return fmt.Errorf("push: %v", err)
191+
}
192+
}
193+
194+
// Copy protections
195+
protectBranch := &ProtectedBranch{
196+
RepoID: generateRepo.ID,
197+
BranchName: branch.BranchName,
198+
CanPush: branch.CanPush,
199+
EnableStatusCheck: branch.EnableStatusCheck,
200+
StatusCheckContexts: branch.StatusCheckContexts,
201+
RequiredApprovals: branch.RequiredApprovals,
202+
BlockOnRejectedReviews: branch.BlockOnRejectedReviews,
203+
BlockOnOfficialReviewRequests: branch.BlockOnOfficialReviewRequests,
204+
BlockOnOutdatedBranch: branch.BlockOnOutdatedBranch,
205+
DismissStaleApprovals: branch.DismissStaleApprovals,
206+
RequireSignedCommits: branch.RequireSignedCommits,
207+
ProtectedFilePatterns: branch.ProtectedFilePatterns,
208+
}
209+
210+
if err := updateProtectBranch(ctx.e, generateRepo, protectBranch, WhitelistOptions{}); err != nil {
211+
return err
212+
}
213+
}
214+
215+
return nil
216+
}

modules/auth/repo_form.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,16 @@ type CreateRepoForm struct {
4141
Readme string
4242
Template bool
4343

44-
RepoTemplate int64
45-
GitContent bool
46-
Topics bool
47-
GitHooks bool
48-
Webhooks bool
49-
Avatar bool
50-
Labels bool
51-
TrustModel string
44+
RepoTemplate int64
45+
GitContent bool
46+
Topics bool
47+
GitHooks bool
48+
Webhooks bool
49+
Avatar bool
50+
Labels bool
51+
BranchProtection bool
52+
53+
TrustModel string
5254
}
5355

5456
// Validate validates the fields

modules/repository/generate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template
252252
IsFsckEnabled: templateRepo.IsFsckEnabled,
253253
TemplateID: templateRepo.ID,
254254
TrustModel: templateRepo.TrustModel,
255+
DefaultBranch: templateRepo.DefaultBranch,
255256
}
256257

257258
if err = models.CreateRepository(ctx, doer, owner, generateRepo, false); err != nil {

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ template.webhooks = Webhooks
731731
template.topics = Topics
732732
template.avatar = Avatar
733733
template.issue_labels = Issue Labels
734+
template.branch_protection = Branch Protection
734735
template.one_item = Must select at least one template item
735736
template.invalid = Must select a template repository
736737

routers/repo/repo.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,16 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
205205
var err error
206206
if form.RepoTemplate > 0 {
207207
opts := models.GenerateRepoOptions{
208-
Name: form.RepoName,
209-
Description: form.Description,
210-
Private: form.Private,
211-
GitContent: form.GitContent,
212-
Topics: form.Topics,
213-
GitHooks: form.GitHooks,
214-
Webhooks: form.Webhooks,
215-
Avatar: form.Avatar,
216-
IssueLabels: form.Labels,
208+
Name: form.RepoName,
209+
Description: form.Description,
210+
Private: form.Private,
211+
GitContent: form.GitContent,
212+
Topics: form.Topics,
213+
GitHooks: form.GitHooks,
214+
Webhooks: form.Webhooks,
215+
Avatar: form.Avatar,
216+
IssueLabels: form.Labels,
217+
BranchProtection: form.BranchProtection,
217218
}
218219

219220
if !opts.IsValid() {

services/repository/generate.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ func GenerateRepository(doer, owner *models.User, templateRepo *models.Repositor
2525
if err = repo_module.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil {
2626
return err
2727
}
28+
29+
// Branch Protection
30+
if opts.BranchProtection {
31+
if err := models.GenerateBranchProtection(ctx, doer, templateRepo, generateRepo); err != nil {
32+
return err
33+
}
34+
}
2835
}
2936

3037
// Topics

templates/repo/create.tmpl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
<div class="inline field">
7171
<label>{{.i18n.Tr "repo.template.items"}}</label>
7272
<div class="ui checkbox">
73-
<input class="hidden" name="git_content" type="checkbox" tabindex="0" {{if .git_content}}checked{{end}}>
73+
<input class="hidden" id="git_content" name="git_content" type="checkbox" tabindex="0" {{if .git_content}}checked{{end}}>
7474
<label>{{.i18n.Tr "repo.template.git_content"}}</label>
7575
</div>
7676
<div class="ui checkbox{{if not .SignedUser.CanEditGitHook}} poping up{{end}}"{{if not .SignedUser.CanEditGitHook}} data-content="{{.i18n.Tr "repo.template.git_hooks_tooltip"}}"{{end}}>
@@ -100,6 +100,13 @@
100100
<label>{{.i18n.Tr "repo.template.issue_labels"}}</label>
101101
</div>
102102
</div>
103+
<div class="inline field">
104+
<label></label>
105+
<div class="ui checkbox">
106+
<input class="hidden" id="branch_protection" name="branch_protection" type="checkbox" tabindex="0" {{if .branch_protection}}checked{{end}} {{if not .git_content}}disabled{{end}}>
107+
<label>{{.i18n.Tr "repo.template.branch_protection"}}</label>
108+
</div>
109+
</div>
103110
</div>
104111

105112
<div id="non_template">

web_src/js/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,6 +2291,15 @@ function initWipTitle() {
22912291
});
22922292
}
22932293

2294+
function initTemplateBranchProtection() {
2295+
const $gitContent = $('#git_content');
2296+
const $branchProtection = $('#branch_protection');
2297+
$gitContent.on('change', () => {
2298+
$branchProtection.prop('checked', false);
2299+
$branchProtection.prop('disabled', !$gitContent.is(':checked'));
2300+
});
2301+
}
2302+
22942303
function initTemplateSearch() {
22952304
const $repoTemplate = $('#repo_template');
22962305
const checkTemplate = function () {
@@ -2552,6 +2561,7 @@ $(document).ready(async () => {
25522561
initWipTitle();
25532562
initPullRequestReview();
25542563
initRepoStatusChecker();
2564+
initTemplateBranchProtection();
25552565
initTemplateSearch();
25562566
initContextPopups();
25572567
initTableSort();

0 commit comments

Comments
 (0)