mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-14 00:01:44 -04:00
Compare commits
No commits in common. "3510d7e33ace5302691b6e96b9bde71148ed6e4c" and "cc1f8cbe96c195aab79761c48bc4ec0bff2b3431" have entirely different histories.
3510d7e33a
...
cc1f8cbe96
4
Makefile
4
Makefile
@ -758,9 +758,9 @@ $(DIST_DIRS):
|
|||||||
|
|
||||||
.PHONY: release-windows
|
.PHONY: release-windows
|
||||||
release-windows: | $(DIST_DIRS)
|
release-windows: | $(DIST_DIRS)
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
|
||||||
ifeq (,$(findstring gogit,$(TAGS)))
|
ifeq (,$(findstring gogit,$(TAGS)))
|
||||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
|
||||||
endif
|
endif
|
||||||
ifeq ($(CI),true)
|
ifeq ($(CI),true)
|
||||||
cp /build/* $(DIST)/binaries
|
cp /build/* $(DIST)/binaries
|
||||||
|
@ -32,15 +32,11 @@ func needsUpdate(dir, filename string) (bool, []byte) {
|
|||||||
|
|
||||||
hasher := sha1.New()
|
hasher := sha1.New()
|
||||||
|
|
||||||
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
info, err := d.Info()
|
_, _ = hasher.Write([]byte(info.Name()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _ = hasher.Write([]byte(d.Name()))
|
|
||||||
_, _ = hasher.Write([]byte(info.ModTime().String()))
|
_, _ = hasher.Write([]byte(info.ModTime().String()))
|
||||||
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
|
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
|
||||||
return nil
|
return nil
|
||||||
|
@ -2213,28 +2213,6 @@ ROUTER = console
|
|||||||
;SCHEDULE = @every 168h
|
;SCHEDULE = @every 168h
|
||||||
;OLDER_THAN = 8760h
|
;OLDER_THAN = 8760h
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Garbage collect LFS pointers in repositories
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;[cron.gc_lfs]
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;ENABLED = false
|
|
||||||
;; Garbage collect LFS pointers in repositories (default false)
|
|
||||||
;RUN_AT_START = false
|
|
||||||
;; Interval as a duration between each gc run (default every 24h)
|
|
||||||
;SCHEDULE = @every 24h
|
|
||||||
;; Only attempt to garbage collect LFSMetaObjects older than this (default 7 days)
|
|
||||||
;OLDER_THAN = 168h
|
|
||||||
;; Only attempt to garbage collect LFSMetaObjects that have not been attempted to be garbage collected for this long (default 3 days)
|
|
||||||
;LAST_UPDATED_MORE_THAN_AGO = 72h
|
|
||||||
; Minimum number of stale LFSMetaObjects to check per repo. Set to `0` to always check all.
|
|
||||||
;NUMBER_TO_CHECK_PER_REPO = 100
|
|
||||||
;Check at least this proportion of LFSMetaObjects per repo. (This may cause all stale LFSMetaObjects to be checked.)
|
|
||||||
;PROPORTION_TO_CHECK_PER_REPO = 0.6
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Git Operation timeout in seconds
|
;; Git Operation timeout in seconds
|
||||||
|
@ -1039,16 +1039,6 @@ Default templates for project boards:
|
|||||||
- `SCHEDULE`: **@every 168h**: Cron syntax to set how often to check.
|
- `SCHEDULE`: **@every 168h**: Cron syntax to set how often to check.
|
||||||
- `OLDER_THAN`: **@every 8760h**: any system notice older than this expression will be deleted from database.
|
- `OLDER_THAN`: **@every 8760h**: any system notice older than this expression will be deleted from database.
|
||||||
|
|
||||||
#### Cron - Garbage collect LFS pointers in repositories ('cron.gc_lfs')
|
|
||||||
|
|
||||||
- `ENABLED`: **false**: Enable service.
|
|
||||||
- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED).
|
|
||||||
- `SCHEDULE`: **@every 24h**: Cron syntax to set how often to check.
|
|
||||||
- `OLDER_THAN`: **168h**: Only attempt to garbage collect LFSMetaObjects older than this (default 7 days)
|
|
||||||
- `LAST_UPDATED_MORE_THAN_AGO`: **72h**: Only attempt to garbage collect LFSMetaObjects that have not been attempted to be garbage collected for this long (default 3 days)
|
|
||||||
- `NUMBER_TO_CHECK_PER_REPO`: **100**: Minimum number of stale LFSMetaObjects to check per repo. Set to `0` to always check all.
|
|
||||||
- `PROPORTION_TO_CHECK_PER_REPO`: **0.6**: Check at least this proportion of LFSMetaObjects per repo. (This may cause all stale LFSMetaObjects to be checked.)
|
|
||||||
|
|
||||||
## Git (`git`)
|
## Git (`git`)
|
||||||
|
|
||||||
- `PATH`: **""**: The path of Git executable. If empty, Gitea searches through the PATH environment.
|
- `PATH`: **""**: The path of Git executable. If empty, Gitea searches through the PATH environment.
|
||||||
|
@ -55,7 +55,7 @@ and shows a link to the repository on the package site (as well as a link to the
|
|||||||
|
|
||||||
| Package owner type | User | Organization |
|
| Package owner type | User | Organization |
|
||||||
|--------------------|------|--------------|
|
|--------------------|------|--------------|
|
||||||
| **read** access | public, if user is public too; otherwise for this user only | public, if org is public, otherwise for org members only |
|
| **read** access | public, if user is public too; otherwise for this user only | public, if org is public, otherwise org members only |
|
||||||
| **write** access | owner only | org members with admin or write access to the org |
|
| **write** access | owner only | org members with admin or write access to the org |
|
||||||
|
|
||||||
N.B.: These access restrictions are [subject to change](https://github.com/go-gitea/gitea/issues/19270), where more finegrained control will be added via a dedicated organization team permission.
|
N.B.: These access restrictions are [subject to change](https://github.com/go-gitea/gitea/issues/19270), where more finegrained control will be added via a dedicated organization team permission.
|
||||||
@ -83,7 +83,7 @@ To download a package from your repository:
|
|||||||
|
|
||||||
## Delete a package
|
## Delete a package
|
||||||
|
|
||||||
You cannot edit a package after you have published it in the Package Registry. Instead, you
|
You cannot edit a package after you published it in the Package Registry. Instead, you
|
||||||
must delete and recreate it.
|
must delete and recreate it.
|
||||||
|
|
||||||
To delete a package from your repository:
|
To delete a package from your repository:
|
||||||
|
@ -6,15 +6,428 @@ package git
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProtectedBranch struct
|
||||||
|
type ProtectedBranch struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
BranchName string `xorm:"UNIQUE(s)"`
|
||||||
|
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
EnableWhitelist bool
|
||||||
|
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||||
|
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||||
|
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||||
|
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||||
|
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
StatusCheckContexts []string `xorm:"JSON TEXT"`
|
||||||
|
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||||
|
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||||
|
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
ProtectedFilePatterns string `xorm:"TEXT"`
|
||||||
|
UnprotectedFilePatterns string `xorm:"TEXT"`
|
||||||
|
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
db.RegisterModel(new(ProtectedBranch))
|
||||||
|
db.RegisterModel(new(DeletedBranch))
|
||||||
|
db.RegisterModel(new(RenamedBranch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProtected returns if the branch is protected
|
||||||
|
func (protectBranch *ProtectedBranch) IsProtected() bool {
|
||||||
|
return protectBranch.ID > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanUserPush returns if some user could push to this protected branch
|
||||||
|
func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, userID int64) bool {
|
||||||
|
if !protectBranch.CanPush {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !protectBranch.EnableWhitelist {
|
||||||
|
if user, err := user_model.GetUserByID(ctx, userID); err != nil {
|
||||||
|
log.Error("GetUserByID: %v", err)
|
||||||
|
return false
|
||||||
|
} else if repo, err := repo_model.GetRepositoryByID(ctx, protectBranch.RepoID); err != nil {
|
||||||
|
log.Error("repo_model.GetRepositoryByID: %v", err)
|
||||||
|
return false
|
||||||
|
} else if writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil {
|
||||||
|
log.Error("HasAccessUnit: %v", err)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return writeAccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if base.Int64sContains(protectBranch.WhitelistUserIDs, userID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(protectBranch.WhitelistTeamIDs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err := organization.IsUserInTeams(ctx, userID, protectBranch.WhitelistTeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("IsUserInTeams: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
|
||||||
|
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
|
||||||
|
if !protectBranch.EnableMergeWhitelist {
|
||||||
|
// Then we need to fall back on whether the user has write permission
|
||||||
|
return permissionInRepo.CanWrite(unit.TypeCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(protectBranch.MergeWhitelistTeamIDs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("IsUserInTeams: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals)
|
||||||
|
func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
|
||||||
|
repo, err := repo_model.GetRepositoryByID(ctx, protectBranch.RepoID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !protectBranch.EnableApprovalsWhitelist {
|
||||||
|
// Anyone with write access is considered official reviewer
|
||||||
|
writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return writeAccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return inTeam, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
|
||||||
|
func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob {
|
||||||
|
return getFilePatterns(protectBranch.ProtectedFilePatterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice
|
||||||
|
func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob {
|
||||||
|
return getFilePatterns(protectBranch.UnprotectedFilePatterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFilePatterns(filePatterns string) []glob.Glob {
|
||||||
|
extarr := make([]glob.Glob, 0, 10)
|
||||||
|
for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") {
|
||||||
|
expr = strings.TrimSpace(expr)
|
||||||
|
if expr != "" {
|
||||||
|
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
||||||
|
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||||
|
} else {
|
||||||
|
extarr = append(extarr, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extarr
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change
|
||||||
|
func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool {
|
||||||
|
glob := protectBranch.GetProtectedFilePatterns()
|
||||||
|
if len(glob) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(changedProtectedFiles) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProtectedFile return if path is protected
|
||||||
|
func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool {
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
patterns = protectBranch.GetProtectedFilePatterns()
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lpath := strings.ToLower(strings.TrimSpace(path))
|
||||||
|
|
||||||
|
r := false
|
||||||
|
for _, pat := range patterns {
|
||||||
|
if pat.Match(lpath) {
|
||||||
|
r = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUnprotectedFile return if path is unprotected
|
||||||
|
func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool {
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
patterns = protectBranch.GetUnprotectedFilePatterns()
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lpath := strings.ToLower(strings.TrimSpace(path))
|
||||||
|
|
||||||
|
r := false
|
||||||
|
for _, pat := range patterns {
|
||||||
|
if pat.Match(lpath) {
|
||||||
|
r = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProtectedBranchBy getting protected branch by ID/Name
|
||||||
|
func GetProtectedBranchBy(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) {
|
||||||
|
rel := &ProtectedBranch{RepoID: repoID, BranchName: branchName}
|
||||||
|
has, err := db.GetByBean(ctx, rel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return rel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhitelistOptions represent all sorts of whitelists used for protected branches
|
||||||
|
type WhitelistOptions struct {
|
||||||
|
UserIDs []int64
|
||||||
|
TeamIDs []int64
|
||||||
|
|
||||||
|
MergeUserIDs []int64
|
||||||
|
MergeTeamIDs []int64
|
||||||
|
|
||||||
|
ApprovalsUserIDs []int64
|
||||||
|
ApprovalsTeamIDs []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateProtectBranch saves branch protection options of repository.
|
||||||
|
// If ID is 0, it creates a new record. Otherwise, updates existing record.
|
||||||
|
// This function also performs check if whitelist user and team's IDs have been changed
|
||||||
|
// to avoid unnecessary whitelist delete and regenerate.
|
||||||
|
func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
|
||||||
|
if err = repo.GetOwner(ctx); err != nil {
|
||||||
|
return fmt.Errorf("GetOwner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
protectBranch.WhitelistUserIDs = whitelist
|
||||||
|
|
||||||
|
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
protectBranch.MergeWhitelistUserIDs = whitelist
|
||||||
|
|
||||||
|
whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
protectBranch.ApprovalsWhitelistUserIDs = whitelist
|
||||||
|
|
||||||
|
// if the repo is in an organization
|
||||||
|
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
protectBranch.WhitelistTeamIDs = whitelist
|
||||||
|
|
||||||
|
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
protectBranch.MergeWhitelistTeamIDs = whitelist
|
||||||
|
|
||||||
|
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
protectBranch.ApprovalsWhitelistTeamIDs = whitelist
|
||||||
|
|
||||||
|
// Make sure protectBranch.ID is not 0 for whitelists
|
||||||
|
if protectBranch.ID == 0 {
|
||||||
|
if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil {
|
||||||
|
return fmt.Errorf("Insert: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
|
||||||
|
return fmt.Errorf("Update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProtectedBranches get all protected branches
|
||||||
|
func GetProtectedBranches(ctx context.Context, repoID int64) ([]*ProtectedBranch, error) {
|
||||||
|
protectedBranches := make([]*ProtectedBranch, 0)
|
||||||
|
return protectedBranches, db.GetEngine(ctx).Find(&protectedBranches, &ProtectedBranch{RepoID: repoID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProtectedBranch checks if branch is protected
|
||||||
|
func IsProtectedBranch(ctx context.Context, repoID int64, branchName string) (bool, error) {
|
||||||
|
protectedBranch := &ProtectedBranch{
|
||||||
|
RepoID: repoID,
|
||||||
|
BranchName: branchName,
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := db.GetEngine(ctx).Exist(protectedBranch)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return has, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||||
|
// the users from newWhitelist which have explicit read or write access to the repo.
|
||||||
|
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||||
|
hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist)
|
||||||
|
if !hasUsersChanged {
|
||||||
|
return currentWhitelist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelist = make([]int64, 0, len(newWhitelist))
|
||||||
|
for _, userID := range newWhitelist {
|
||||||
|
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !reader {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
whitelist = append(whitelist, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with
|
||||||
|
// the users from newWhitelist which have write access to the repo.
|
||||||
|
func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||||
|
hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist)
|
||||||
|
if !hasUsersChanged {
|
||||||
|
return currentWhitelist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelist = make([]int64, 0, len(newWhitelist))
|
||||||
|
for _, userID := range newWhitelist {
|
||||||
|
user, err := user_model.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err)
|
||||||
|
}
|
||||||
|
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !perm.CanWrite(unit.TypeCode) {
|
||||||
|
continue // Drop invalid user ID
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelist = append(whitelist, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with
|
||||||
|
// the teams from newWhitelist which have write access to the repo.
|
||||||
|
func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
||||||
|
hasTeamsChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist)
|
||||||
|
if !hasTeamsChanged {
|
||||||
|
return currentWhitelist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %w", repo.OwnerID, repo.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelist = make([]int64, 0, len(teams))
|
||||||
|
for i := range teams {
|
||||||
|
if util.SliceContains(newWhitelist, teams[i].ID) {
|
||||||
|
whitelist = append(whitelist, teams[i].ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return whitelist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
|
||||||
|
func DeleteProtectedBranch(ctx context.Context, repoID, id int64) (err error) {
|
||||||
|
protectedBranch := &ProtectedBranch{
|
||||||
|
RepoID: repoID,
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected, err := db.GetEngine(ctx).Delete(protectedBranch); err != nil {
|
||||||
|
return err
|
||||||
|
} else if affected != 1 {
|
||||||
|
return fmt.Errorf("delete protected branch ID(%v) failed", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeletedBranch struct
|
// DeletedBranch struct
|
||||||
type DeletedBranch struct {
|
type DeletedBranch struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
@ -26,11 +439,6 @@ type DeletedBranch struct {
|
|||||||
DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
db.RegisterModel(new(DeletedBranch))
|
|
||||||
db.RegisterModel(new(RenamedBranch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDeletedBranch adds a deleted branch to the database
|
// AddDeletedBranch adds a deleted branch to the database
|
||||||
func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error {
|
func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error {
|
||||||
deletedBranch := &DeletedBranch{
|
deletedBranch := &DeletedBranch{
|
||||||
@ -148,25 +556,17 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Update protected branch if needed
|
// 2. Update protected branch if needed
|
||||||
protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
|
protectedBranch, err := GetProtectedBranchBy(ctx, repo.ID, from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if protectedBranch != nil {
|
if protectedBranch != nil {
|
||||||
protectedBranch.RuleName = to
|
protectedBranch.BranchName = to
|
||||||
_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
|
_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
protected, err := IsBranchProtected(ctx, repo.ID, from)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if protected {
|
|
||||||
return ErrBranchIsProtected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update all not merged pull request base branch name
|
// 3. Update all not merged pull request base branch name
|
||||||
|
@ -106,7 +106,7 @@ func TestRenameBranch(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, git_model.UpdateProtectBranch(ctx, repo1, &git_model.ProtectedBranch{
|
assert.NoError(t, git_model.UpdateProtectBranch(ctx, repo1, &git_model.ProtectedBranch{
|
||||||
RepoID: repo1.ID,
|
RepoID: repo1.ID,
|
||||||
RuleName: "master",
|
BranchName: "master",
|
||||||
}, git_model.WhitelistOptions{}))
|
}, git_model.WhitelistOptions{}))
|
||||||
assert.NoError(t, committer.Commit())
|
assert.NoError(t, committer.Commit())
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ func TestRenameBranch(t *testing.T) {
|
|||||||
|
|
||||||
unittest.AssertExistsAndLoadBean(t, &git_model.ProtectedBranch{
|
unittest.AssertExistsAndLoadBean(t, &git_model.ProtectedBranch{
|
||||||
RepoID: repo1.ID,
|
RepoID: repo1.ID,
|
||||||
RuleName: "main",
|
BranchName: "main",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,6 @@ type LFSMetaObject struct {
|
|||||||
RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||||
Existing bool `xorm:"-"`
|
Existing bool `xorm:"-"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -335,45 +334,8 @@ func GetRepoLFSSize(ctx context.Context, repoID int64) (int64, error) {
|
|||||||
return lfsSize, nil
|
return lfsSize, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterateRepositoryIDsWithLFSMetaObjects iterates across the repositories that have LFSMetaObjects
|
|
||||||
func IterateRepositoryIDsWithLFSMetaObjects(ctx context.Context, f func(ctx context.Context, repoID, count int64) error) error {
|
|
||||||
batchSize := setting.Database.IterateBufferSize
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
id := int64(0)
|
|
||||||
type RepositoryCount struct {
|
|
||||||
RepositoryID int64
|
|
||||||
Count int64
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
counts := make([]*RepositoryCount, 0, batchSize)
|
|
||||||
sess.Select("repository_id, COUNT(id) AS count").
|
|
||||||
Table("lfs_meta_object").
|
|
||||||
Where("repository_id > ?", id).
|
|
||||||
GroupBy("repository_id").
|
|
||||||
OrderBy("repository_id ASC")
|
|
||||||
|
|
||||||
if err := sess.Limit(batchSize, 0).Find(&counts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(counts) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, count := range counts {
|
|
||||||
if err := f(ctx, count.RepositoryID, count.Count); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id = counts[len(counts)-1].RepositoryID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateLFSMetaObjectsForRepoOptions provides options for IterateLFSMetaObjectsForRepo
|
|
||||||
type IterateLFSMetaObjectsForRepoOptions struct {
|
type IterateLFSMetaObjectsForRepoOptions struct {
|
||||||
OlderThan time.Time
|
OlderThan time.Time
|
||||||
UpdatedLessRecentlyThan time.Time
|
|
||||||
OrderByUpdated bool
|
|
||||||
LoopFunctionAlwaysUpdates bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterateLFSMetaObjectsForRepo provides a iterator for LFSMetaObjects per Repo
|
// IterateLFSMetaObjectsForRepo provides a iterator for LFSMetaObjects per Repo
|
||||||
@ -386,53 +348,28 @@ func IterateLFSMetaObjectsForRepo(ctx context.Context, repoID int64, f func(cont
|
|||||||
LFSMetaObject
|
LFSMetaObject
|
||||||
}
|
}
|
||||||
|
|
||||||
id := int64(0)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
beans := make([]*CountLFSMetaObject, 0, batchSize)
|
beans := make([]*CountLFSMetaObject, 0, batchSize)
|
||||||
|
// SELECT `lfs_meta_object`.*, COUNT(`l1`.id) as `count` FROM lfs_meta_object INNER JOIN lfs_meta_object AS l1 ON l1.oid = lfs_meta_object.oid WHERE lfs_meta_object.repository_id = ? GROUP BY lfs_meta_object.id
|
||||||
sess := engine.Select("`lfs_meta_object`.*, COUNT(`l1`.oid) AS `count`").
|
sess := engine.Select("`lfs_meta_object`.*, COUNT(`l1`.oid) AS `count`").
|
||||||
Join("INNER", "`lfs_meta_object` AS l1", "`lfs_meta_object`.oid = `l1`.oid").
|
Join("INNER", "`lfs_meta_object` AS l1", "`lfs_meta_object`.oid = `l1`.oid").
|
||||||
Where("`lfs_meta_object`.repository_id = ?", repoID)
|
Where("`lfs_meta_object`.repository_id = ?", repoID)
|
||||||
if !opts.OlderThan.IsZero() {
|
if !opts.OlderThan.IsZero() {
|
||||||
sess.And("`lfs_meta_object`.created_unix < ?", opts.OlderThan)
|
sess.And("`lfs_meta_object`.created_unix < ?", opts.OlderThan)
|
||||||
}
|
}
|
||||||
if !opts.UpdatedLessRecentlyThan.IsZero() {
|
|
||||||
sess.And("`lfs_meta_object`.updated_unix < ?", opts.UpdatedLessRecentlyThan)
|
|
||||||
}
|
|
||||||
sess.GroupBy("`lfs_meta_object`.id")
|
sess.GroupBy("`lfs_meta_object`.id")
|
||||||
if opts.OrderByUpdated {
|
|
||||||
sess.OrderBy("`lfs_meta_object`.updated_unix ASC")
|
|
||||||
} else {
|
|
||||||
sess.And("`lfs_meta_object`.id > ?", id)
|
|
||||||
sess.OrderBy("`lfs_meta_object`.id ASC")
|
|
||||||
}
|
|
||||||
if err := sess.Limit(batchSize, start).Find(&beans); err != nil {
|
if err := sess.Limit(batchSize, start).Find(&beans); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(beans) == 0 {
|
if len(beans) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !opts.LoopFunctionAlwaysUpdates {
|
|
||||||
start += len(beans)
|
start += len(beans)
|
||||||
}
|
|
||||||
|
|
||||||
for _, bean := range beans {
|
for _, bean := range beans {
|
||||||
if err := f(ctx, &bean.LFSMetaObject, bean.Count); err != nil {
|
if err := f(ctx, &bean.LFSMetaObject, bean.Count); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id = beans[len(beans)-1].ID
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkLFSMetaObject updates the updated time for the provided LFSMetaObject
|
|
||||||
func MarkLFSMetaObject(ctx context.Context, id int64) error {
|
|
||||||
obj := &LFSMetaObject{
|
|
||||||
UpdatedUnix: timeutil.TimeStampNow(),
|
|
||||||
}
|
|
||||||
count, err := db.GetEngine(ctx).ID(id).Update(obj)
|
|
||||||
if count != 1 {
|
|
||||||
log.Error("Unexpectedly updated %d LFSMetaObjects with ID: %d", count, id)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
@ -1,501 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
"github.com/gobwas/glob/syntax"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrBranchIsProtected = errors.New("branch is protected")
|
|
||||||
|
|
||||||
// ProtectedBranch struct
|
|
||||||
type ProtectedBranch struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
|
||||||
Repo *repo_model.Repository `xorm:"-"`
|
|
||||||
RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name
|
|
||||||
globRule glob.Glob `xorm:"-"`
|
|
||||||
isPlainName bool `xorm:"-"`
|
|
||||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
EnableWhitelist bool
|
|
||||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
|
||||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
|
||||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
|
||||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
|
||||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
StatusCheckContexts []string `xorm:"JSON TEXT"`
|
|
||||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
|
||||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
|
||||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
|
|
||||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
|
||||||
ProtectedFilePatterns string `xorm:"TEXT"`
|
|
||||||
UnprotectedFilePatterns string `xorm:"TEXT"`
|
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
db.RegisterModel(new(ProtectedBranch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRuleNameSpecial return true if it contains special character
|
|
||||||
func IsRuleNameSpecial(ruleName string) bool {
|
|
||||||
for i := 0; i < len(ruleName); i++ {
|
|
||||||
if syntax.Special(ruleName[i]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (protectBranch *ProtectedBranch) loadGlob() {
|
|
||||||
if protectBranch.globRule == nil {
|
|
||||||
var err error
|
|
||||||
protectBranch.globRule, err = glob.Compile(protectBranch.RuleName, '/')
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Invalid glob rule for ProtectedBranch[%d]: %s %v", protectBranch.ID, protectBranch.RuleName, err)
|
|
||||||
protectBranch.globRule = glob.MustCompile(glob.QuoteMeta(protectBranch.RuleName), '/')
|
|
||||||
}
|
|
||||||
protectBranch.isPlainName = !IsRuleNameSpecial(protectBranch.RuleName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match tests if branchName matches the rule
|
|
||||||
func (protectBranch *ProtectedBranch) Match(branchName string) bool {
|
|
||||||
protectBranch.loadGlob()
|
|
||||||
if protectBranch.isPlainName {
|
|
||||||
return strings.EqualFold(protectBranch.RuleName, branchName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return protectBranch.globRule.Match(branchName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (protectBranch *ProtectedBranch) LoadRepo(ctx context.Context) (err error) {
|
|
||||||
if protectBranch.Repo != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
protectBranch.Repo, err = repo_model.GetRepositoryByID(ctx, protectBranch.RepoID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanUserPush returns if some user could push to this protected branch
|
|
||||||
func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *user_model.User) bool {
|
|
||||||
if !protectBranch.CanPush {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !protectBranch.EnableWhitelist {
|
|
||||||
if err := protectBranch.LoadRepo(ctx); err != nil {
|
|
||||||
log.Error("LoadRepo: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
writeAccess, err := access_model.HasAccessUnit(ctx, user, protectBranch.Repo, unit.TypeCode, perm.AccessModeWrite)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("HasAccessUnit: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return writeAccess
|
|
||||||
}
|
|
||||||
|
|
||||||
if base.Int64sContains(protectBranch.WhitelistUserIDs, user.ID) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(protectBranch.WhitelistTeamIDs) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
in, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.WhitelistTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("IsUserInTeams: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
|
|
||||||
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
|
|
||||||
if !protectBranch.EnableMergeWhitelist {
|
|
||||||
// Then we need to fall back on whether the user has write permission
|
|
||||||
return permissionInRepo.CanWrite(unit.TypeCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if base.Int64sContains(protectBranch.MergeWhitelistUserIDs, userID) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(protectBranch.MergeWhitelistTeamIDs) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
in, err := organization.IsUserInTeams(ctx, userID, protectBranch.MergeWhitelistTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("IsUserInTeams: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals)
|
|
||||||
func IsUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
|
|
||||||
repo, err := repo_model.GetRepositoryByID(ctx, protectBranch.RepoID)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !protectBranch.EnableApprovalsWhitelist {
|
|
||||||
// Anyone with write access is considered official reviewer
|
|
||||||
writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return writeAccess, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if base.Int64sContains(protectBranch.ApprovalsWhitelistUserIDs, user.ID) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inTeam, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ApprovalsWhitelistTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return inTeam, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
|
|
||||||
func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob {
|
|
||||||
return getFilePatterns(protectBranch.ProtectedFilePatterns)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUnprotectedFilePatterns parses a semicolon separated list of unprotected file patterns and returns a glob.Glob slice
|
|
||||||
func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob {
|
|
||||||
return getFilePatterns(protectBranch.UnprotectedFilePatterns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFilePatterns(filePatterns string) []glob.Glob {
|
|
||||||
extarr := make([]glob.Glob, 0, 10)
|
|
||||||
for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") {
|
|
||||||
expr = strings.TrimSpace(expr)
|
|
||||||
if expr != "" {
|
|
||||||
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
|
||||||
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
|
||||||
} else {
|
|
||||||
extarr = append(extarr, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return extarr
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change
|
|
||||||
func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool {
|
|
||||||
glob := protectBranch.GetProtectedFilePatterns()
|
|
||||||
if len(glob) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(changedProtectedFiles) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsProtectedFile return if path is protected
|
|
||||||
func (protectBranch *ProtectedBranch) IsProtectedFile(patterns []glob.Glob, path string) bool {
|
|
||||||
if len(patterns) == 0 {
|
|
||||||
patterns = protectBranch.GetProtectedFilePatterns()
|
|
||||||
if len(patterns) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lpath := strings.ToLower(strings.TrimSpace(path))
|
|
||||||
|
|
||||||
r := false
|
|
||||||
for _, pat := range patterns {
|
|
||||||
if pat.Match(lpath) {
|
|
||||||
r = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUnprotectedFile return if path is unprotected
|
|
||||||
func (protectBranch *ProtectedBranch) IsUnprotectedFile(patterns []glob.Glob, path string) bool {
|
|
||||||
if len(patterns) == 0 {
|
|
||||||
patterns = protectBranch.GetUnprotectedFilePatterns()
|
|
||||||
if len(patterns) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lpath := strings.ToLower(strings.TrimSpace(path))
|
|
||||||
|
|
||||||
r := false
|
|
||||||
for _, pat := range patterns {
|
|
||||||
if pat.Match(lpath) {
|
|
||||||
r = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProtectedBranchRuleByName getting protected branch rule by name
|
|
||||||
func GetProtectedBranchRuleByName(ctx context.Context, repoID int64, ruleName string) (*ProtectedBranch, error) {
|
|
||||||
rel := &ProtectedBranch{RepoID: repoID, RuleName: ruleName}
|
|
||||||
has, err := db.GetByBean(ctx, rel)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !has {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return rel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProtectedBranchRuleByID getting protected branch rule by rule ID
|
|
||||||
func GetProtectedBranchRuleByID(ctx context.Context, repoID, ruleID int64) (*ProtectedBranch, error) {
|
|
||||||
rel := &ProtectedBranch{ID: ruleID, RepoID: repoID}
|
|
||||||
has, err := db.GetByBean(ctx, rel)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !has {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return rel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WhitelistOptions represent all sorts of whitelists used for protected branches
|
|
||||||
type WhitelistOptions struct {
|
|
||||||
UserIDs []int64
|
|
||||||
TeamIDs []int64
|
|
||||||
|
|
||||||
MergeUserIDs []int64
|
|
||||||
MergeTeamIDs []int64
|
|
||||||
|
|
||||||
ApprovalsUserIDs []int64
|
|
||||||
ApprovalsTeamIDs []int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateProtectBranch saves branch protection options of repository.
|
|
||||||
// If ID is 0, it creates a new record. Otherwise, updates existing record.
|
|
||||||
// This function also performs check if whitelist user and team's IDs have been changed
|
|
||||||
// to avoid unnecessary whitelist delete and regenerate.
|
|
||||||
func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
|
|
||||||
if err = repo.GetOwner(ctx); err != nil {
|
|
||||||
return fmt.Errorf("GetOwner: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protectBranch.WhitelistUserIDs = whitelist
|
|
||||||
|
|
||||||
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protectBranch.MergeWhitelistUserIDs = whitelist
|
|
||||||
|
|
||||||
whitelist, err = updateApprovalWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistUserIDs, opts.ApprovalsUserIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protectBranch.ApprovalsWhitelistUserIDs = whitelist
|
|
||||||
|
|
||||||
// if the repo is in an organization
|
|
||||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.WhitelistTeamIDs, opts.TeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protectBranch.WhitelistTeamIDs = whitelist
|
|
||||||
|
|
||||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protectBranch.MergeWhitelistTeamIDs = whitelist
|
|
||||||
|
|
||||||
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ApprovalsWhitelistTeamIDs, opts.ApprovalsTeamIDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
protectBranch.ApprovalsWhitelistTeamIDs = whitelist
|
|
||||||
|
|
||||||
// Make sure protectBranch.ID is not 0 for whitelists
|
|
||||||
if protectBranch.ID == 0 {
|
|
||||||
if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil {
|
|
||||||
return fmt.Errorf("Insert: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
|
|
||||||
return fmt.Errorf("Update: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateApprovalWhitelist checks whether the user whitelist changed and returns a whitelist with
|
|
||||||
// the users from newWhitelist which have explicit read or write access to the repo.
|
|
||||||
func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
|
||||||
hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist)
|
|
||||||
if !hasUsersChanged {
|
|
||||||
return currentWhitelist, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelist = make([]int64, 0, len(newWhitelist))
|
|
||||||
for _, userID := range newWhitelist {
|
|
||||||
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !reader {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
whitelist = append(whitelist, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return whitelist, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with
|
|
||||||
// the users from newWhitelist which have write access to the repo.
|
|
||||||
func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
|
||||||
hasUsersChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist)
|
|
||||||
if !hasUsersChanged {
|
|
||||||
return currentWhitelist, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelist = make([]int64, 0, len(newWhitelist))
|
|
||||||
for _, userID := range newWhitelist {
|
|
||||||
user, err := user_model.GetUserByID(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
|
||||||
}
|
|
||||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !perm.CanWrite(unit.TypeCode) {
|
|
||||||
continue // Drop invalid user ID
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelist = append(whitelist, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return whitelist, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with
|
|
||||||
// the teams from newWhitelist which have write access to the repo.
|
|
||||||
func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, currentWhitelist, newWhitelist []int64) (whitelist []int64, err error) {
|
|
||||||
hasTeamsChanged := !util.SliceSortedEqual(currentWhitelist, newWhitelist)
|
|
||||||
if !hasTeamsChanged {
|
|
||||||
return currentWhitelist, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelist = make([]int64, 0, len(teams))
|
|
||||||
for i := range teams {
|
|
||||||
if util.SliceContains(newWhitelist, teams[i].ID) {
|
|
||||||
whitelist = append(whitelist, teams[i].ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return whitelist, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
|
|
||||||
func DeleteProtectedBranch(ctx context.Context, repoID, id int64) (err error) {
|
|
||||||
protectedBranch := &ProtectedBranch{
|
|
||||||
RepoID: repoID,
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if affected, err := db.GetEngine(ctx).Delete(protectedBranch); err != nil {
|
|
||||||
return err
|
|
||||||
} else if affected != 1 {
|
|
||||||
return fmt.Errorf("delete protected branch ID(%v) failed", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveUserIDFromProtectedBranch remove all user ids from protected branch options
|
|
||||||
func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error {
|
|
||||||
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
|
|
||||||
p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID)
|
|
||||||
p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID)
|
|
||||||
p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID)
|
|
||||||
|
|
||||||
if lenIDs != len(p.WhitelistUserIDs) || lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
|
|
||||||
lenMergeIDs != len(p.MergeWhitelistUserIDs) {
|
|
||||||
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
|
||||||
"whitelist_user_i_ds",
|
|
||||||
"merge_whitelist_user_i_ds",
|
|
||||||
"approvals_whitelist_user_i_ds",
|
|
||||||
).Update(p); err != nil {
|
|
||||||
return fmt.Errorf("updateProtectedBranches: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTeamIDFromProtectedBranch remove all team ids from protected branch options
|
|
||||||
func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, teamID int64) error {
|
|
||||||
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs)
|
|
||||||
p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, teamID)
|
|
||||||
p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, teamID)
|
|
||||||
p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, teamID)
|
|
||||||
|
|
||||||
if lenIDs != len(p.WhitelistTeamIDs) ||
|
|
||||||
lenApprovalIDs != len(p.ApprovalsWhitelistTeamIDs) ||
|
|
||||||
lenMergeIDs != len(p.MergeWhitelistTeamIDs) {
|
|
||||||
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
|
||||||
"whitelist_team_i_ds",
|
|
||||||
"merge_whitelist_team_i_ds",
|
|
||||||
"approvals_whitelist_team_i_ds",
|
|
||||||
).Update(p); err != nil {
|
|
||||||
return fmt.Errorf("updateProtectedBranches: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProtectedBranchRules []*ProtectedBranch
|
|
||||||
|
|
||||||
func (rules ProtectedBranchRules) GetFirstMatched(branchName string) *ProtectedBranch {
|
|
||||||
for _, rule := range rules {
|
|
||||||
if rule.Match(branchName) {
|
|
||||||
return rule
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rules ProtectedBranchRules) sort() {
|
|
||||||
sort.Slice(rules, func(i, j int) bool {
|
|
||||||
rules[i].loadGlob()
|
|
||||||
rules[j].loadGlob()
|
|
||||||
if rules[i].isPlainName {
|
|
||||||
if !rules[j].isPlainName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if rules[j].isPlainName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return rules[i].CreatedUnix < rules[j].CreatedUnix
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindRepoProtectedBranchRules load all repository's protected rules
|
|
||||||
func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedBranchRules, error) {
|
|
||||||
var rules ProtectedBranchRules
|
|
||||||
err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Asc("created_unix").Find(&rules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rules.sort()
|
|
||||||
return rules, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindAllMatchedBranches find all matched branches
|
|
||||||
func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) {
|
|
||||||
// FIXME: how many should we get?
|
|
||||||
branches, _, err := gitRepo.GetBranchNames(0, 9999999)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rule := glob.MustCompile(ruleName)
|
|
||||||
results := make([]string, 0, len(branches))
|
|
||||||
for _, branch := range branches {
|
|
||||||
if rule.Match(branch) {
|
|
||||||
results = append(results, branch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFirstMatchProtectedBranchRule returns the first matched rules
|
|
||||||
func GetFirstMatchProtectedBranchRule(ctx context.Context, repoID int64, branchName string) (*ProtectedBranch, error) {
|
|
||||||
rules, err := FindRepoProtectedBranchRules(ctx, repoID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return rules.GetFirstMatched(branchName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBranchProtected checks if branch is protected
|
|
||||||
func IsBranchProtected(ctx context.Context, repoID int64, branchName string) (bool, error) {
|
|
||||||
rule, err := GetFirstMatchProtectedBranchRule(ctx, repoID, branchName)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return rule != nil, nil
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBranchRuleMatch(t *testing.T) {
|
|
||||||
kases := []struct {
|
|
||||||
Rule string
|
|
||||||
BranchName string
|
|
||||||
ExpectedMatch bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Rule: "release/*",
|
|
||||||
BranchName: "release/v1.17",
|
|
||||||
ExpectedMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "release/**/v1.17",
|
|
||||||
BranchName: "release/test/v1.17",
|
|
||||||
ExpectedMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "release/**/v1.17",
|
|
||||||
BranchName: "release/test/1/v1.17",
|
|
||||||
ExpectedMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "release/*/v1.17",
|
|
||||||
BranchName: "release/test/1/v1.17",
|
|
||||||
ExpectedMatch: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "release/v*",
|
|
||||||
BranchName: "release/v1.16",
|
|
||||||
ExpectedMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "*",
|
|
||||||
BranchName: "release/v1.16",
|
|
||||||
ExpectedMatch: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "**",
|
|
||||||
BranchName: "release/v1.16",
|
|
||||||
ExpectedMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "main",
|
|
||||||
BranchName: "main",
|
|
||||||
ExpectedMatch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rule: "master",
|
|
||||||
BranchName: "main",
|
|
||||||
ExpectedMatch: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, kase := range kases {
|
|
||||||
pb := ProtectedBranch{RuleName: kase.Rule}
|
|
||||||
var should, infact string
|
|
||||||
if !kase.ExpectedMatch {
|
|
||||||
should = " not"
|
|
||||||
} else {
|
|
||||||
infact = " not"
|
|
||||||
}
|
|
||||||
assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName),
|
|
||||||
fmt.Sprintf("%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -164,6 +164,7 @@ type PullRequest struct {
|
|||||||
HeadBranch string
|
HeadBranch string
|
||||||
HeadCommitID string `xorm:"-"`
|
HeadCommitID string `xorm:"-"`
|
||||||
BaseBranch string
|
BaseBranch string
|
||||||
|
ProtectedBranch *git_model.ProtectedBranch `xorm:"-"`
|
||||||
MergeBase string `xorm:"VARCHAR(40)"`
|
MergeBase string `xorm:"VARCHAR(40)"`
|
||||||
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
|
AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
|
||||||
@ -292,6 +293,23 @@ func (pr *PullRequest) LoadIssue(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadProtectedBranch loads the protected branch of the base branch
|
||||||
|
func (pr *PullRequest) LoadProtectedBranch(ctx context.Context) (err error) {
|
||||||
|
if pr.ProtectedBranch == nil {
|
||||||
|
if pr.BaseRepo == nil {
|
||||||
|
if pr.BaseRepoID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pr.BaseRepo, err = repo_model.GetRepositoryByID(ctx, pr.BaseRepoID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pr.ProtectedBranch, err = git_model.GetProtectedBranchBy(ctx, pr.BaseRepo.ID, pr.BaseBranch)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// ReviewCount represents a count of Reviews
|
// ReviewCount represents a count of Reviews
|
||||||
type ReviewCount struct {
|
type ReviewCount struct {
|
||||||
IssueID int64
|
IssueID int64
|
||||||
|
@ -263,17 +263,15 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if err = pr.LoadProtectedBranch(ctx); err != nil {
|
||||||
rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if rule == nil {
|
if pr.ProtectedBranch == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, reviewer := range reviewers {
|
for _, reviewer := range reviewers {
|
||||||
official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer)
|
official, err := git_model.IsUserOfficialReviewer(ctx, pr.ProtectedBranch, reviewer)
|
||||||
if official || err != nil {
|
if official || err != nil {
|
||||||
return official, err
|
return official, err
|
||||||
}
|
}
|
||||||
@ -288,19 +286,18 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
if err = pr.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if pb == nil {
|
if pr.ProtectedBranch == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !pb.EnableApprovalsWhitelist {
|
if !pr.ProtectedBranch.EnableApprovalsWhitelist {
|
||||||
return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil
|
return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.Int64sContains(pb.ApprovalsWhitelistTeamIDs, team.ID), nil
|
return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateReview creates a new review based on opts
|
// CreateReview creates a new review based on opts
|
||||||
|
@ -432,9 +432,6 @@ var migrations = []Migration{
|
|||||||
NewMigration("Update counts of all open milestones", v1_18.UpdateOpenMilestoneCounts),
|
NewMigration("Update counts of all open milestones", v1_18.UpdateOpenMilestoneCounts),
|
||||||
// v230 -> v231
|
// v230 -> v231
|
||||||
NewMigration("Add ConfidentialClient column (default true) to OAuth2Application table", v1_18.AddConfidentialClientColumnToOAuth2ApplicationTable),
|
NewMigration("Add ConfidentialClient column (default true) to OAuth2Application table", v1_18.AddConfidentialClientColumnToOAuth2ApplicationTable),
|
||||||
|
|
||||||
// Gitea 1.18.0 ends at v231
|
|
||||||
|
|
||||||
// v231 -> v232
|
// v231 -> v232
|
||||||
NewMigration("Add index for hook_task", v1_19.AddIndexForHookTask),
|
NewMigration("Add index for hook_task", v1_19.AddIndexForHookTask),
|
||||||
// v232 -> v233
|
// v232 -> v233
|
||||||
@ -449,8 +446,6 @@ var migrations = []Migration{
|
|||||||
NewMigration("Create secrets table", v1_19.CreateSecretsTable),
|
NewMigration("Create secrets table", v1_19.CreateSecretsTable),
|
||||||
// v237 -> v238
|
// v237 -> v238
|
||||||
NewMigration("Drop ForeignReference table", v1_19.DropForeignReferenceTable),
|
NewMigration("Drop ForeignReference table", v1_19.DropForeignReferenceTable),
|
||||||
// v238 -> v239
|
|
||||||
NewMigration("Add updated unix to LFSMetaObject", v1_19.AddUpdatedUnixToLFSMetaObject),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package v1_19 //nolint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddUpdatedUnixToLFSMetaObject adds an updated column to the LFSMetaObject to allow for garbage collection
|
|
||||||
func AddUpdatedUnixToLFSMetaObject(x *xorm.Engine) error {
|
|
||||||
// Drop the table introduced in `v211`, it's considered badly designed and doesn't look like to be used.
|
|
||||||
// See: https://github.com/go-gitea/gitea/issues/21086#issuecomment-1318217453
|
|
||||||
// LFSMetaObject stores metadata for LFS tracked files.
|
|
||||||
type LFSMetaObject struct {
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
Oid string `json:"oid" xorm:"UNIQUE(s) INDEX NOT NULL"`
|
|
||||||
Size int64 `json:"size" xorm:"NOT NULL"`
|
|
||||||
RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
|
||||||
}
|
|
||||||
|
|
||||||
return x.Sync(new(LFSMetaObject))
|
|
||||||
}
|
|
@ -378,6 +378,7 @@ func DeleteTeam(t *organization.Team) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
if err := t.LoadRepositories(ctx); err != nil {
|
if err := t.LoadRepositories(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -390,15 +391,27 @@ func DeleteTeam(t *organization.Team) error {
|
|||||||
// update branch protections
|
// update branch protections
|
||||||
{
|
{
|
||||||
protections := make([]*git_model.ProtectedBranch, 0, 10)
|
protections := make([]*git_model.ProtectedBranch, 0, 10)
|
||||||
err := db.GetEngine(ctx).In("repo_id",
|
err := sess.In("repo_id",
|
||||||
builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
|
builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
|
||||||
Find(&protections)
|
Find(&protections)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("findProtectedBranches: %w", err)
|
return fmt.Errorf("findProtectedBranches: %w", err)
|
||||||
}
|
}
|
||||||
for _, p := range protections {
|
for _, p := range protections {
|
||||||
if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil {
|
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs)
|
||||||
return err
|
p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, t.ID)
|
||||||
|
p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, t.ID)
|
||||||
|
p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, t.ID)
|
||||||
|
if lenIDs != len(p.WhitelistTeamIDs) ||
|
||||||
|
lenApprovalIDs != len(p.ApprovalsWhitelistTeamIDs) ||
|
||||||
|
lenMergeIDs != len(p.MergeWhitelistTeamIDs) {
|
||||||
|
if _, err = sess.ID(p.ID).Cols(
|
||||||
|
"whitelist_team_i_ds",
|
||||||
|
"merge_whitelist_team_i_ds",
|
||||||
|
"approvals_whitelist_team_i_ds",
|
||||||
|
).Update(p); err != nil {
|
||||||
|
return fmt.Errorf("updateProtectedBranches: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,7 +432,7 @@ func DeleteTeam(t *organization.Team) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update organization number of teams.
|
// Update organization number of teams.
|
||||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
|
if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ type BlobSearchOptions struct {
|
|||||||
Digest string
|
Digest string
|
||||||
Tag string
|
Tag string
|
||||||
IsManifest bool
|
IsManifest bool
|
||||||
Repository string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *BlobSearchOptions) toConds() builder.Cond {
|
func (opts *BlobSearchOptions) toConds() builder.Cond {
|
||||||
@ -54,15 +53,6 @@ func (opts *BlobSearchOptions) toConds() builder.Cond {
|
|||||||
|
|
||||||
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
||||||
}
|
}
|
||||||
if opts.Repository != "" {
|
|
||||||
var propsCond builder.Cond = builder.Eq{
|
|
||||||
"package_property.ref_type": packages.PropertyTypePackage,
|
|
||||||
"package_property.name": container_module.PropertyRepository,
|
|
||||||
"package_property.value": opts.Repository,
|
|
||||||
}
|
|
||||||
|
|
||||||
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(propsCond).From("package_property")))
|
|
||||||
}
|
|
||||||
|
|
||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
@ -497,12 +496,8 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
|||||||
// Only show a repo that either has a topic or description.
|
// Only show a repo that either has a topic or description.
|
||||||
subQueryCond := builder.NewCond()
|
subQueryCond := builder.NewCond()
|
||||||
|
|
||||||
// Topic checking. Topics are present.
|
// Topic checking. Topics is non-null.
|
||||||
if setting.Database.UsePostgreSQL { // postgres stores the topics as json and not as text
|
|
||||||
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
|
|
||||||
} else {
|
|
||||||
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
|
||||||
}
|
|
||||||
|
|
||||||
// Description checking. Description not empty.
|
// Description checking. Description not empty.
|
||||||
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
|
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteUser deletes models associated to an user.
|
// DeleteUser deletes models associated to an user.
|
||||||
@ -140,8 +141,20 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, p := range protections {
|
for _, p := range protections {
|
||||||
if err := git_model.RemoveUserIDFromProtectedBranch(ctx, p, u.ID); err != nil {
|
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
|
||||||
return err
|
p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, u.ID)
|
||||||
|
p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, u.ID)
|
||||||
|
p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, u.ID)
|
||||||
|
if lenIDs != len(p.WhitelistUserIDs) ||
|
||||||
|
lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
|
||||||
|
lenMergeIDs != len(p.MergeWhitelistUserIDs) {
|
||||||
|
if _, err = e.ID(p.ID).Cols(
|
||||||
|
"whitelist_user_i_ds",
|
||||||
|
"merge_whitelist_user_i_ds",
|
||||||
|
"approvals_whitelist_user_i_ds",
|
||||||
|
).Update(p); err != nil {
|
||||||
|
return fmt.Errorf("updateProtectedBranches: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,15 +119,14 @@ type CanCommitToBranchResults struct {
|
|||||||
//
|
//
|
||||||
// and branch is not protected for push
|
// and branch is not protected for push
|
||||||
func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
|
func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
|
||||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName)
|
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CanCommitToBranchResults{}, err
|
return CanCommitToBranchResults{}, err
|
||||||
}
|
}
|
||||||
userCanPush := true
|
userCanPush := true
|
||||||
requireSigned := false
|
requireSigned := false
|
||||||
if protectedBranch != nil {
|
if protectedBranch != nil {
|
||||||
protectedBranch.Repo = r.Repository
|
userCanPush = protectedBranch.CanUserPush(ctx, doer.ID)
|
||||||
userCanPush = protectedBranch.CanUserPush(ctx, doer)
|
|
||||||
requireSigned = protectedBranch.RequireSignedCommits
|
requireSigned = protectedBranch.RequireSignedCommits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ package doctor
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -30,20 +29,7 @@ func garbageCollectLFSCheck(ctx context.Context, logger log.Logger, autofix bool
|
|||||||
return fmt.Errorf("LFS support is disabled")
|
return fmt.Errorf("LFS support is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repository.GarbageCollectLFSMetaObjects(ctx, repository.GarbageCollectLFSMetaObjectsOptions{
|
if err := repository.GarbageCollectLFSMetaObjects(ctx, logger, autofix); err != nil {
|
||||||
Logger: logger,
|
|
||||||
AutoFix: autofix,
|
|
||||||
// Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload
|
|
||||||
// and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby
|
|
||||||
// an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid
|
|
||||||
// changes in new branches that might lead to lfs objects becoming temporarily unassociated with git
|
|
||||||
// objects.
|
|
||||||
//
|
|
||||||
// It is likely that a week is potentially excessive but it should definitely be enough that any
|
|
||||||
// unassociated LFS object is genuinely unassociated.
|
|
||||||
OlderThan: time.Now().Add(-24 * time.Hour * 7),
|
|
||||||
// We don't set the UpdatedLessRecentlyThan because we want to do a full GC
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,24 +225,14 @@ func compressOldLogFile(fname string, compressionLevel int) error {
|
|||||||
|
|
||||||
func (log *FileLogger) deleteOldLog() {
|
func (log *FileLogger) deleteOldLog() {
|
||||||
dir := filepath.Dir(log.Filename)
|
dir := filepath.Dir(log.Filename)
|
||||||
_ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) (returnErr error) {
|
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
|
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*log.Maxdays) {
|
||||||
return err
|
|
||||||
}
|
|
||||||
if d.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
info, err := d.Info()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.ModTime().Unix() < (time.Now().Unix() - 60*60*24*log.Maxdays) {
|
|
||||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(log.Filename)) {
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(log.Filename)) {
|
||||||
if err := util.Remove(path); err != nil {
|
if err := util.Remove(path); err != nil {
|
||||||
returnErr = fmt.Errorf("Failed to remove %s: %w", path, err)
|
returnErr = fmt.Errorf("Failed to remove %s: %w", path, err)
|
||||||
|
@ -173,12 +173,12 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
|||||||
// Avoid walking tree if there are no globs
|
// Avoid walking tree if there are no globs
|
||||||
if len(gt.Globs()) > 0 {
|
if len(gt.Globs()) > 0 {
|
||||||
tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/"
|
tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/"
|
||||||
if err := filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error {
|
if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error {
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ func (l *LocalStorage) URL(path, name string) (*url.URL, error) {
|
|||||||
|
|
||||||
// IterateObjects iterates across the objects in the local storage
|
// IterateObjects iterates across the objects in the local storage
|
||||||
func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error {
|
func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error {
|
||||||
return filepath.WalkDir(l.dir, func(path string, d os.DirEntry, err error) error {
|
return filepath.Walk(l.dir, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) er
|
|||||||
if path == l.dir {
|
if path == l.dir {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if d.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
relPath, err := filepath.Rel(l.dir, path)
|
relPath, err := filepath.Rel(l.dir, path)
|
||||||
|
@ -22,9 +22,7 @@ type Branch struct {
|
|||||||
|
|
||||||
// BranchProtection represents a branch protection for a repository
|
// BranchProtection represents a branch protection for a repository
|
||||||
type BranchProtection struct {
|
type BranchProtection struct {
|
||||||
// Deprecated: true
|
|
||||||
BranchName string `json:"branch_name"`
|
BranchName string `json:"branch_name"`
|
||||||
RuleName string `json:"rule_name"`
|
|
||||||
EnablePush bool `json:"enable_push"`
|
EnablePush bool `json:"enable_push"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||||
@ -54,9 +52,7 @@ type BranchProtection struct {
|
|||||||
|
|
||||||
// CreateBranchProtectionOption options for creating a branch protection
|
// CreateBranchProtectionOption options for creating a branch protection
|
||||||
type CreateBranchProtectionOption struct {
|
type CreateBranchProtectionOption struct {
|
||||||
// Deprecated: true
|
|
||||||
BranchName string `json:"branch_name"`
|
BranchName string `json:"branch_name"`
|
||||||
RuleName string `json:"rule_name"`
|
|
||||||
EnablePush bool `json:"enable_push"`
|
EnablePush bool `json:"enable_push"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||||
|
@ -365,7 +365,6 @@ password_pwned_err = Could not complete request to HaveIBeenPwned
|
|||||||
|
|
||||||
[mail]
|
[mail]
|
||||||
view_it_on = View it on %s
|
view_it_on = View it on %s
|
||||||
reply = or reply to this email directly
|
|
||||||
link_not_working_do_paste = Not working? Try copying and pasting it to your browser.
|
link_not_working_do_paste = Not working? Try copying and pasting it to your browser.
|
||||||
hi_user_x = Hi <b>%s</b>,
|
hi_user_x = Hi <b>%s</b>,
|
||||||
|
|
||||||
@ -1825,7 +1824,6 @@ settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check
|
|||||||
settings.site = Website
|
settings.site = Website
|
||||||
settings.update_settings = Update Settings
|
settings.update_settings = Update Settings
|
||||||
settings.branches.update_default_branch = Update Default Branch
|
settings.branches.update_default_branch = Update Default Branch
|
||||||
settings.branches.add_new_rule = Add New Rule
|
|
||||||
settings.advanced_settings = Advanced Settings
|
settings.advanced_settings = Advanced Settings
|
||||||
settings.wiki_desc = Enable Repository Wiki
|
settings.wiki_desc = Enable Repository Wiki
|
||||||
settings.use_internal_wiki = Use Built-In Wiki
|
settings.use_internal_wiki = Use Built-In Wiki
|
||||||
@ -2071,8 +2069,6 @@ settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access
|
|||||||
settings.deploy_key_deletion_success = The deploy key has been removed.
|
settings.deploy_key_deletion_success = The deploy key has been removed.
|
||||||
settings.branches = Branches
|
settings.branches = Branches
|
||||||
settings.protected_branch = Branch Protection
|
settings.protected_branch = Branch Protection
|
||||||
settings.protected_branch.save_rule = Save Rule
|
|
||||||
settings.protected_branch.delete_rule = Delete Rule
|
|
||||||
settings.protected_branch_can_push = Allow push?
|
settings.protected_branch_can_push = Allow push?
|
||||||
settings.protected_branch_can_push_yes = You can push
|
settings.protected_branch_can_push_yes = You can push
|
||||||
settings.protected_branch_can_push_no = You cannot push
|
settings.protected_branch_can_push_no = You cannot push
|
||||||
@ -2107,17 +2103,15 @@ settings.dismiss_stale_approvals = Dismiss stale approvals
|
|||||||
settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed.
|
settings.dismiss_stale_approvals_desc = When new commits that change the content of the pull request are pushed to the branch, old approvals will be dismissed.
|
||||||
settings.require_signed_commits = Require Signed Commits
|
settings.require_signed_commits = Require Signed Commits
|
||||||
settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable.
|
settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable.
|
||||||
settings.protect_branch_name_pattern = Protected Branch Name Pattern
|
|
||||||
settings.protect_protected_file_patterns = Protected file patterns (separated using semicolon '\;'):
|
settings.protect_protected_file_patterns = Protected file patterns (separated using semicolon '\;'):
|
||||||
settings.protect_protected_file_patterns_desc = Protected files that are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon ('\;'). See <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
settings.protect_protected_file_patterns_desc = Protected files that are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon ('\;'). See <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||||
settings.protect_unprotected_file_patterns = Unprotected file patterns (separated using semicolon '\;'):
|
settings.protect_unprotected_file_patterns = Unprotected file patterns (separated using semicolon '\;'):
|
||||||
settings.protect_unprotected_file_patterns_desc = Unprotected files that are allowed to be changed directly if user has write access, bypassing push restriction. Multiple patterns can be separated using semicolon ('\;'). See <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
settings.protect_unprotected_file_patterns_desc = Unprotected files that are allowed to be changed directly if user has write access, bypassing push restriction. Multiple patterns can be separated using semicolon ('\;'). See <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentation for pattern syntax. Examples: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||||
settings.add_protected_branch = Enable protection
|
settings.add_protected_branch = Enable protection
|
||||||
settings.delete_protected_branch = Disable protection
|
settings.delete_protected_branch = Disable protection
|
||||||
settings.update_protect_branch_success = Branch protection for rule '%s' has been updated.
|
settings.update_protect_branch_success = Branch protection for branch '%s' has been updated.
|
||||||
settings.remove_protected_branch_success = Branch protection for rule '%s' has been removed.
|
settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled.
|
||||||
settings.remove_protected_branch_failed = Removing branch protection rule '%s' failed.
|
settings.protected_branch_deletion = Disable Branch Protection
|
||||||
settings.protected_branch_deletion = Delete Branch Protection
|
|
||||||
settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue?
|
settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue?
|
||||||
settings.block_rejected_reviews = Block merge on rejected reviews
|
settings.block_rejected_reviews = Block merge on rejected reviews
|
||||||
settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals.
|
settings.block_rejected_reviews_desc = Merging will not be possible when changes are requested by official reviewers, even if there are enough approvals.
|
||||||
@ -2130,7 +2124,6 @@ settings.default_merge_style_desc = Default merge style for pull requests:
|
|||||||
settings.choose_branch = Choose a branch…
|
settings.choose_branch = Choose a branch…
|
||||||
settings.no_protected_branch = There are no protected branches.
|
settings.no_protected_branch = There are no protected branches.
|
||||||
settings.edit_protected_branch = Edit
|
settings.edit_protected_branch = Edit
|
||||||
settings.protected_branch_required_rule_name = Required rule name
|
|
||||||
settings.protected_branch_required_approvals_min = Required approvals cannot be negative.
|
settings.protected_branch_required_approvals_min = Required approvals cannot be negative.
|
||||||
settings.tags = Tags
|
settings.tags = Tags
|
||||||
settings.tags.protection = Tag Protection
|
settings.tags.protection = Tag Protection
|
||||||
@ -2555,7 +2548,6 @@ dashboard.delete_old_actions = Delete all old actions from database
|
|||||||
dashboard.delete_old_actions.started = Delete all old actions from database started.
|
dashboard.delete_old_actions.started = Delete all old actions from database started.
|
||||||
dashboard.update_checker = Update checker
|
dashboard.update_checker = Update checker
|
||||||
dashboard.delete_old_system_notices = Delete all old system notices from database
|
dashboard.delete_old_system_notices = Delete all old system notices from database
|
||||||
dashboard.gc_lfs = Garbage collect LFS meta objects
|
|
||||||
|
|
||||||
users.user_manage_panel = User Account Management
|
users.user_manage_panel = User Account Management
|
||||||
users.new_account = Create User Account
|
users.new_account = Create User Account
|
||||||
|
@ -33,60 +33,6 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_servic
|
|||||||
|
|
||||||
contentStore := packages_module.NewContentStore()
|
contentStore := packages_module.NewContentStore()
|
||||||
|
|
||||||
uploadVersion, err := getOrCreateUploadVersion(pi)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
|
||||||
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error inserting package blob: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// FIXME: Workaround to be removed in v1.20
|
|
||||||
// https://github.com/go-gitea/gitea/issues/19586
|
|
||||||
if exists {
|
|
||||||
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
|
||||||
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
|
|
||||||
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
|
||||||
exists = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
|
||||||
log.Error("Error saving package blob in content store: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return createFileForBlob(ctx, uploadVersion, pb)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if !exists {
|
|
||||||
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
|
||||||
log.Error("Error deleting package blob from content store: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mountBlob mounts the specific blob to a different package
|
|
||||||
func mountBlob(pi *packages_service.PackageInfo, pb *packages_model.PackageBlob) error {
|
|
||||||
uploadVersion, err := getOrCreateUploadVersion(pi)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
|
||||||
return createFileForBlob(ctx, uploadVersion, pb)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrCreateUploadVersion(pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
|
|
||||||
var uploadVersion *packages_model.PackageVersion
|
var uploadVersion *packages_model.PackageVersion
|
||||||
|
|
||||||
// FIXME: Replace usage of mutex with database transaction
|
// FIXME: Replace usage of mutex with database transaction
|
||||||
@ -137,21 +83,41 @@ func getOrCreateUploadVersion(pi *packages_service.PackageInfo) (*packages_model
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
uploadVersionMutex.Unlock()
|
uploadVersionMutex.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return uploadVersion, err
|
err = db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
}
|
pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error inserting package blob: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// FIXME: Workaround to be removed in v1.20
|
||||||
|
// https://github.com/go-gitea/gitea/issues/19586
|
||||||
|
if exists {
|
||||||
|
err = contentStore.Has(packages_module.BlobHash256Key(pb.HashSHA256))
|
||||||
|
if err != nil && (errors.Is(err, util.ErrNotExist) || errors.Is(err, os.ErrNotExist)) {
|
||||||
|
log.Debug("Package registry inconsistent: blob %s does not exist on file system", pb.HashSHA256)
|
||||||
|
exists = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
if err := contentStore.Save(packages_module.BlobHash256Key(pb.HashSHA256), hsr, hsr.Size()); err != nil {
|
||||||
|
log.Error("Error saving package blob in content store: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error {
|
|
||||||
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256))
|
||||||
|
|
||||||
pf := &packages_model.PackageFile{
|
pf := &packages_model.PackageFile{
|
||||||
VersionID: pv.ID,
|
VersionID: uploadVersion.ID,
|
||||||
BlobID: pb.ID,
|
BlobID: pb.ID,
|
||||||
Name: filename,
|
Name: filename,
|
||||||
LowerName: filename,
|
LowerName: filename,
|
||||||
CompositeKey: packages_model.EmptyFileKey,
|
CompositeKey: packages_model.EmptyFileKey,
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
if pf, err = packages_model.TryInsertFile(ctx, pf); err != nil {
|
||||||
if err == packages_model.ErrDuplicatePackageFile {
|
if err == packages_model.ErrDuplicatePackageFile {
|
||||||
return nil
|
return nil
|
||||||
@ -166,6 +132,17 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if !exists {
|
||||||
|
if err := contentStore.Delete(packages_module.BlobHash256Key(pb.HashSHA256)); err != nil {
|
||||||
|
log.Error("Error deleting package blob from content store: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteBlob(ownerID int64, image, digest string) error {
|
func deleteBlob(ownerID int64, image, digest string) error {
|
||||||
|
@ -195,15 +195,10 @@ func InitiateUploadBlob(ctx *context.Context) {
|
|||||||
from := ctx.FormTrim("from")
|
from := ctx.FormTrim("from")
|
||||||
if mount != "" {
|
if mount != "" {
|
||||||
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
blob, _ := workaroundGetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||||
Repository: from,
|
Image: from,
|
||||||
Digest: mount,
|
Digest: mount,
|
||||||
})
|
})
|
||||||
if blob != nil {
|
if blob != nil {
|
||||||
if err := mountBlob(&packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}, blob.Blob); err != nil {
|
|
||||||
apiError(ctx, http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setResponseHeaders(ctx.Resp, &containerHeaders{
|
setResponseHeaders(ctx.Resp, &containerHeaders{
|
||||||
Location: fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
|
Location: fmt.Sprintf("/v2/%s/%s/blobs/%s", ctx.Package.Owner.LowerName, image, mount),
|
||||||
ContentDigest: mount,
|
ContentDigest: mount,
|
||||||
|
@ -70,7 +70,7 @@ func GetBranch(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
|
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||||
return
|
return
|
||||||
@ -124,7 +124,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
|||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||||
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
||||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
case errors.Is(err, repo_service.ErrBranchIsProtected):
|
||||||
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
|
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
||||||
@ -206,7 +206,7 @@ func CreateBranch(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name)
|
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||||
return
|
return
|
||||||
@ -257,12 +257,6 @@ func ListBranches(ctx *context.APIContext) {
|
|||||||
listOptions := utils.GetListOptions(ctx)
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
|
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
|
||||||
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
skip, _ := listOptions.GetStartEnd()
|
skip, _ := listOptions.GetStartEnd()
|
||||||
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -282,8 +276,11 @@ func ListBranches(ctx *context.APIContext) {
|
|||||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
|
||||||
branchProtection := rules.GetFirstMatched(branches[i].Name)
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
apiBranch, err := convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
|
||||||
@ -331,7 +328,7 @@ func GetBranchProtection(ctx *context.APIContext) {
|
|||||||
|
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
bpName := ctx.Params(":name")
|
bpName := ctx.Params(":name")
|
||||||
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
||||||
return
|
return
|
||||||
@ -367,7 +364,7 @@ func ListBranchProtections(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/BranchProtectionList"
|
// "$ref": "#/responses/BranchProtectionList"
|
||||||
|
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
|
bps, err := git_model.GetProtectedBranches(ctx, repo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
|
||||||
return
|
return
|
||||||
@ -417,18 +414,13 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
|||||||
form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
|
form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
ruleName := form.RuleName
|
// Currently protection must match an actual branch
|
||||||
if ruleName == "" {
|
if !git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), form.BranchName) {
|
||||||
ruleName = form.BranchName //nolint
|
ctx.NotFound()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isPlainRule := !git_model.IsRuleNameSpecial(ruleName)
|
protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, form.BranchName)
|
||||||
var isBranchExist bool
|
|
||||||
if isPlainRule {
|
|
||||||
isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), ruleName)
|
|
||||||
}
|
|
||||||
|
|
||||||
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
|
||||||
return
|
return
|
||||||
@ -502,7 +494,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
|||||||
|
|
||||||
protectBranch = &git_model.ProtectedBranch{
|
protectBranch = &git_model.ProtectedBranch{
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
RuleName: form.RuleName,
|
BranchName: form.BranchName,
|
||||||
CanPush: form.EnablePush,
|
CanPush: form.EnablePush,
|
||||||
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
|
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
|
||||||
EnableMergeWhitelist: form.EnableMergeWhitelist,
|
EnableMergeWhitelist: form.EnableMergeWhitelist,
|
||||||
@ -533,42 +525,13 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isBranchExist {
|
if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
|
||||||
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, form.RuleName); err != nil {
|
ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
|
||||||
ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if !isPlainRule {
|
|
||||||
if ctx.Repo.GitRepo == nil {
|
|
||||||
ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath())
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
ctx.Repo.GitRepo.Close()
|
|
||||||
ctx.Repo.GitRepo = nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
// FIXME: since we only need to recheck files protected rules, we could improve this
|
|
||||||
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, form.RuleName)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, branchName := range matchedBranches {
|
|
||||||
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload from db to get all whitelists
|
// Reload from db to get all whitelists
|
||||||
bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, form.RuleName)
|
bp, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, form.BranchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
||||||
return
|
return
|
||||||
@ -620,7 +583,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
|||||||
form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
|
form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
bpName := ctx.Params(":name")
|
bpName := ctx.Params(":name")
|
||||||
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
||||||
return
|
return
|
||||||
@ -797,49 +760,13 @@ func EditBranchProtection(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isPlainRule := !git_model.IsRuleNameSpecial(bpName)
|
if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
|
||||||
var isBranchExist bool
|
|
||||||
if isPlainRule {
|
|
||||||
isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), bpName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isBranchExist {
|
|
||||||
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, bpName); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
|
ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if !isPlainRule {
|
|
||||||
if ctx.Repo.GitRepo == nil {
|
|
||||||
ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath())
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
ctx.Repo.GitRepo.Close()
|
|
||||||
ctx.Repo.GitRepo = nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: since we only need to recheck files protected rules, we could improve this
|
|
||||||
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, branchName := range matchedBranches {
|
|
||||||
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload from db to ensure get all whitelists
|
// Reload from db to ensure get all whitelists
|
||||||
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
|
||||||
return
|
return
|
||||||
@ -883,7 +810,7 @@ func DeleteBranchProtection(ctx *context.APIContext) {
|
|||||||
|
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
bpName := ctx.Params(":name")
|
bpName := ctx.Params(":name")
|
||||||
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
|
||||||
return
|
return
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
pull_model "code.gitea.io/gitea/models/pull"
|
pull_model "code.gitea.io/gitea/models/pull"
|
||||||
@ -903,7 +902,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||||
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
||||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
case errors.Is(err, repo_service.ErrBranchIsProtected):
|
||||||
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
|
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
||||||
|
@ -156,7 +156,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
protectBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
|
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
|
||||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||||
@ -166,10 +166,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Allow pushes to non-protected branches
|
// Allow pushes to non-protected branches
|
||||||
if protectBranch == nil {
|
if protectBranch == nil || !protectBranch.IsProtected() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
protectBranch.Repo = repo
|
|
||||||
|
|
||||||
// This ref is a protected branch.
|
// This ref is a protected branch.
|
||||||
//
|
//
|
||||||
@ -239,6 +238,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN
|
|||||||
Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err),
|
Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changedProtectedfiles = true
|
changedProtectedfiles = true
|
||||||
@ -251,15 +251,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN
|
|||||||
if ctx.opts.DeployKeyID != 0 {
|
if ctx.opts.DeployKeyID != 0 {
|
||||||
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
|
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
|
||||||
} else {
|
} else {
|
||||||
user, err := user_model.GetUserByID(ctx, ctx.opts.UserID)
|
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, ctx.opts.UserID)
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to GetUserByID for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
|
|
||||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
|
||||||
Err: fmt.Sprintf("Unable to GetUserByID for commits from %s to %s: %v", oldCommitID, newCommitID, err),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. If we're not allowed to push directly
|
// 6. If we're not allowed to push directly
|
||||||
|
@ -99,7 +99,7 @@ func DeleteBranchPost(ctx *context.Context) {
|
|||||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||||
log.Debug("DeleteBranch: Can't delete default branch '%s'", branchName)
|
log.Debug("DeleteBranch: Can't delete default branch '%s'", branchName)
|
||||||
ctx.Flash.Error(ctx.Tr("repo.branch.default_deletion_failed", branchName))
|
ctx.Flash.Error(ctx.Tr("repo.branch.default_deletion_failed", branchName))
|
||||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
case errors.Is(err, repo_service.ErrBranchIsProtected):
|
||||||
log.Debug("DeleteBranch: Can't delete protected branch '%s'", branchName)
|
log.Debug("DeleteBranch: Can't delete protected branch '%s'", branchName)
|
||||||
ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName))
|
ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName))
|
||||||
default:
|
default:
|
||||||
@ -189,9 +189,9 @@ func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, in
|
|||||||
return nil, nil, 0
|
return nil, nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
|
protectedBranches, err := git_model.GetProtectedBranches(ctx, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("FindRepoProtectedBranchRules", err)
|
ctx.ServerError("GetProtectedBranches", err)
|
||||||
return nil, nil, 0
|
return nil, nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +208,7 @@ func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, in
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
|
branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, protectedBranches, repoIDToRepo, repoIDToGitRepo)
|
||||||
if branch == nil {
|
if branch == nil {
|
||||||
return nil, nil, 0
|
return nil, nil, 0
|
||||||
}
|
}
|
||||||
@ -220,7 +220,7 @@ func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, in
|
|||||||
if defaultBranch != nil {
|
if defaultBranch != nil {
|
||||||
// Always add the default branch
|
// Always add the default branch
|
||||||
log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name)
|
log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name)
|
||||||
defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
|
defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, protectedBranches, repoIDToRepo, repoIDToGitRepo)
|
||||||
branches = append(branches, defaultBranchBranch)
|
branches = append(branches, defaultBranchBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, in
|
|||||||
return defaultBranchBranch, branches, totalNumOfBranches
|
return defaultBranchBranch, branches, totalNumOfBranches
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules,
|
func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches []*git_model.ProtectedBranch,
|
||||||
repoIDToRepo map[int64]*repo_model.Repository,
|
repoIDToRepo map[int64]*repo_model.Repository,
|
||||||
repoIDToGitRepo map[int64]*git.Repository,
|
repoIDToGitRepo map[int64]*git.Repository,
|
||||||
) *Branch {
|
) *Branch {
|
||||||
@ -249,8 +249,13 @@ func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
branchName := rawBranch.Name
|
branchName := rawBranch.Name
|
||||||
p := protectedBranches.GetFirstMatched(branchName)
|
var isProtected bool
|
||||||
isProtected := p != nil
|
for _, b := range protectedBranches {
|
||||||
|
if b.BranchName == branchName {
|
||||||
|
isProtected = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
divergence := &git.DivergeObject{
|
divergence := &git.DivergeObject{
|
||||||
Ahead: -1,
|
Ahead: -1,
|
||||||
|
@ -1604,7 +1604,7 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
if perm.CanWrite(unit.TypeCode) {
|
if perm.CanWrite(unit.TypeCode) {
|
||||||
// Check if branch is not protected
|
// Check if branch is not protected
|
||||||
if pull.HeadBranch != pull.HeadRepo.DefaultBranch {
|
if pull.HeadBranch != pull.HeadRepo.DefaultBranch {
|
||||||
if protected, err := git_model.IsBranchProtected(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil {
|
if protected, err := git_model.IsProtectedBranch(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil {
|
||||||
log.Error("IsProtectedBranch: %v", err)
|
log.Error("IsProtectedBranch: %v", err)
|
||||||
} else if !protected {
|
} else if !protected {
|
||||||
canDelete = true
|
canDelete = true
|
||||||
@ -1680,25 +1680,22 @@ func ViewIssue(ctx *context.Context) {
|
|||||||
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
||||||
ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody
|
ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
if err = pull.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("LoadProtectedBranch", err)
|
ctx.ServerError("LoadProtectedBranch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["ShowMergeInstructions"] = true
|
ctx.Data["ShowMergeInstructions"] = true
|
||||||
if pb != nil {
|
if pull.ProtectedBranch != nil {
|
||||||
pb.Repo = pull.BaseRepo
|
|
||||||
var showMergeInstructions bool
|
var showMergeInstructions bool
|
||||||
if ctx.Doer != nil {
|
if ctx.Doer != nil {
|
||||||
showMergeInstructions = pb.CanUserPush(ctx, ctx.Doer)
|
showMergeInstructions = pull.ProtectedBranch.CanUserPush(ctx, ctx.Doer.ID)
|
||||||
}
|
}
|
||||||
ctx.Data["ProtectedBranch"] = pb
|
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pull.ProtectedBranch, pull)
|
||||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pull.ProtectedBranch, pull)
|
||||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pull.ProtectedBranch, pull)
|
||||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pull.ProtectedBranch, pull)
|
||||||
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pull.ProtectedBranch, pull)
|
||||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits
|
||||||
ctx.Data["RequireSigned"] = pb.RequireSignedCommits
|
|
||||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
||||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||||
|
@ -440,12 +440,11 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||||||
|
|
||||||
setMergeTarget(ctx, pull)
|
setMergeTarget(ctx, pull)
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pull.BaseBranch)
|
if err := pull.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("LoadProtectedBranch", err)
|
ctx.ServerError("LoadProtectedBranch", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ctx.Data["EnableStatusCheck"] = pb != nil && pb.EnableStatusCheck
|
ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
|
||||||
|
|
||||||
var baseGitRepo *git.Repository
|
var baseGitRepo *git.Repository
|
||||||
if pull.BaseRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
|
if pull.BaseRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
|
||||||
@ -571,16 +570,16 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
|
|||||||
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
|
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pb != nil && pb.EnableStatusCheck {
|
if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
|
||||||
ctx.Data["is_context_required"] = func(context string) bool {
|
ctx.Data["is_context_required"] = func(context string) bool {
|
||||||
for _, c := range pb.StatusCheckContexts {
|
for _, c := range pull.ProtectedBranch.StatusCheckContexts {
|
||||||
if c == context {
|
if c == context {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts)
|
ctx.Data["RequiredStatusCheckState"] = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
|
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
|
||||||
@ -753,17 +752,16 @@ func ViewPullFiles(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
if err = pull.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("LoadProtectedBranch", err)
|
ctx.ServerError("LoadProtectedBranch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pb != nil {
|
if pull.ProtectedBranch != nil {
|
||||||
glob := pb.GetProtectedFilePatterns()
|
glob := pull.ProtectedBranch.GetProtectedFilePatterns()
|
||||||
if len(glob) != 0 {
|
if len(glob) != 0 {
|
||||||
for _, file := range diff.Files {
|
for _, file := range diff.Files {
|
||||||
file.IsProtected = pb.IsProtectedFile(glob, file.Name)
|
file.IsProtected = pull.ProtectedBranch.IsProtectedFile(glob, file.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1402,7 +1400,7 @@ func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *g
|
|||||||
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
||||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||||
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
||||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
case errors.Is(err, repo_service.ErrBranchIsProtected):
|
||||||
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
||||||
default:
|
default:
|
||||||
log.Error("DeleteBranch: %v", err)
|
log.Error("DeleteBranch: %v", err)
|
||||||
|
@ -56,6 +56,7 @@ const (
|
|||||||
tplGithooks base.TplName = "repo/settings/githooks"
|
tplGithooks base.TplName = "repo/settings/githooks"
|
||||||
tplGithookEdit base.TplName = "repo/settings/githook_edit"
|
tplGithookEdit base.TplName = "repo/settings/githook_edit"
|
||||||
tplDeployKeys base.TplName = "repo/settings/deploy_keys"
|
tplDeployKeys base.TplName = "repo/settings/deploy_keys"
|
||||||
|
tplProtectedBranch base.TplName = "repo/settings/protected_branch"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SettingsCtxData is a middleware that sets all the general context data for the
|
// SettingsCtxData is a middleware that sets all the general context data for the
|
||||||
|
@ -19,33 +19,47 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
"code.gitea.io/gitea/services/repository"
|
"code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// ProtectedBranch render the page to protect the repository
|
||||||
tplProtectedBranch base.TplName = "repo/settings/protected_branch"
|
func ProtectedBranch(ctx *context.Context) {
|
||||||
)
|
|
||||||
|
|
||||||
// ProtectedBranchRules render the page to protect the repository
|
|
||||||
func ProtectedBranchRules(ctx *context.Context) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||||
ctx.Data["PageIsSettingsBranches"] = true
|
ctx.Data["PageIsSettingsBranches"] = true
|
||||||
|
|
||||||
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
|
protectedBranches, err := git_model.GetProtectedBranches(ctx, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetProtectedBranches", err)
|
ctx.ServerError("GetProtectedBranches", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["ProtectedBranches"] = rules
|
ctx.Data["ProtectedBranches"] = protectedBranches
|
||||||
|
|
||||||
|
branches := ctx.Data["Branches"].([]string)
|
||||||
|
leftBranches := make([]string, 0, len(branches)-len(protectedBranches))
|
||||||
|
for _, b := range branches {
|
||||||
|
var protected bool
|
||||||
|
for _, pb := range protectedBranches {
|
||||||
|
if b == pb.BranchName {
|
||||||
|
protected = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !protected {
|
||||||
|
leftBranches = append(leftBranches, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["LeftBranches"] = leftBranches
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplBranches)
|
ctx.HTML(http.StatusOK, tplBranches)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultBranchPost set default branch
|
// ProtectedBranchPost response for protect for a branch of a repository
|
||||||
func SetDefaultBranchPost(ctx *context.Context) {
|
func ProtectedBranchPost(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||||
ctx.Data["PageIsSettingsBranches"] = true
|
ctx.Data["PageIsSettingsBranches"] = true
|
||||||
|
|
||||||
@ -87,24 +101,29 @@ func SetDefaultBranchPost(ctx *context.Context) {
|
|||||||
|
|
||||||
// SettingsProtectedBranch renders the protected branch setting page
|
// SettingsProtectedBranch renders the protected branch setting page
|
||||||
func SettingsProtectedBranch(c *context.Context) {
|
func SettingsProtectedBranch(c *context.Context) {
|
||||||
ruleName := c.FormString("rule_name")
|
branch := c.Params("*")
|
||||||
var rule *git_model.ProtectedBranch
|
if !c.Repo.GitRepo.IsBranchExist(branch) {
|
||||||
if ruleName != "" {
|
c.NotFound("IsBranchExist", nil)
|
||||||
var err error
|
return
|
||||||
rule, err = git_model.GetProtectedBranchRuleByName(c, c.Repo.Repository.ID, ruleName)
|
}
|
||||||
|
|
||||||
|
c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + branch
|
||||||
|
c.Data["PageIsSettingsBranches"] = true
|
||||||
|
|
||||||
|
protectBranch, err := git_model.GetProtectedBranchBy(c, c.Repo.Repository.ID, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !git.IsErrBranchNotExist(err) {
|
||||||
c.ServerError("GetProtectBranchOfRepoByName", err)
|
c.ServerError("GetProtectBranchOfRepoByName", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rule == nil {
|
if protectBranch == nil {
|
||||||
// No options found, create defaults.
|
// No options found, create defaults.
|
||||||
rule = &git_model.ProtectedBranch{}
|
protectBranch = &git_model.ProtectedBranch{
|
||||||
|
BranchName: branch,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["PageIsSettingsBranches"] = true
|
|
||||||
c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + rule.RuleName
|
|
||||||
|
|
||||||
users, err := access_model.GetRepoReaders(c.Repo.Repository)
|
users, err := access_model.GetRepoReaders(c.Repo.Repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -112,11 +131,11 @@ func SettingsProtectedBranch(c *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data["Users"] = users
|
c.Data["Users"] = users
|
||||||
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
|
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",")
|
||||||
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
|
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",")
|
||||||
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
|
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",")
|
||||||
contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
|
contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
|
||||||
for _, ctx := range rule.StatusCheckContexts {
|
for _, ctx := range protectBranch.StatusCheckContexts {
|
||||||
var found bool
|
var found bool
|
||||||
for i := range contexts {
|
for i := range contexts {
|
||||||
if contexts[i] == ctx {
|
if contexts[i] == ctx {
|
||||||
@ -131,7 +150,7 @@ func SettingsProtectedBranch(c *context.Context) {
|
|||||||
|
|
||||||
c.Data["branch_status_check_contexts"] = contexts
|
c.Data["branch_status_check_contexts"] = contexts
|
||||||
c.Data["is_context_required"] = func(context string) bool {
|
c.Data["is_context_required"] = func(context string) bool {
|
||||||
for _, c := range rule.StatusCheckContexts {
|
for _, c := range protectBranch.StatusCheckContexts {
|
||||||
if c == context {
|
if c == context {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -146,47 +165,46 @@ func SettingsProtectedBranch(c *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data["Teams"] = teams
|
c.Data["Teams"] = teams
|
||||||
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
|
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistTeamIDs), ",")
|
||||||
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
|
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistTeamIDs), ",")
|
||||||
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
|
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistTeamIDs), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["Rule"] = rule
|
c.Data["Branch"] = protectBranch
|
||||||
c.HTML(http.StatusOK, tplProtectedBranch)
|
c.HTML(http.StatusOK, tplProtectedBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingsProtectedBranchPost updates the protected branch settings
|
// SettingsProtectedBranchPost updates the protected branch settings
|
||||||
func SettingsProtectedBranchPost(ctx *context.Context) {
|
func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||||
f := web.GetForm(ctx).(*forms.ProtectBranchForm)
|
f := web.GetForm(ctx).(*forms.ProtectBranchForm)
|
||||||
var protectBranch *git_model.ProtectedBranch
|
branch := ctx.Params("*")
|
||||||
if f.RuleName == "" {
|
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name"))
|
ctx.NotFound("IsBranchExist", nil)
|
||||||
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit", ctx.Repo.RepoLink))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
protectBranch, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch)
|
||||||
protectBranch, err = git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !git.IsErrBranchNotExist(err) {
|
||||||
ctx.ServerError("GetProtectBranchOfRepoByName", err)
|
ctx.ServerError("GetProtectBranchOfRepoByName", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Protected {
|
||||||
if protectBranch == nil {
|
if protectBranch == nil {
|
||||||
// No options found, create defaults.
|
// No options found, create defaults.
|
||||||
protectBranch = &git_model.ProtectedBranch{
|
protectBranch = &git_model.ProtectedBranch{
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
RuleName: f.RuleName,
|
BranchName: branch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if f.RequiredApprovals < 0 {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
|
||||||
|
ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch)))
|
||||||
|
}
|
||||||
|
|
||||||
var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
|
var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
|
||||||
protectBranch.RuleName = f.RuleName
|
|
||||||
if f.RequiredApprovals < 0 {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
|
|
||||||
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, f.RuleName))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch f.EnablePush {
|
switch f.EnablePush {
|
||||||
case "all":
|
case "all":
|
||||||
protectBranch.CanPush = true
|
protectBranch.CanPush = true
|
||||||
@ -255,64 +273,22 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
|||||||
ctx.ServerError("UpdateProtectBranch", err)
|
ctx.ServerError("UpdateProtectBranch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
|
||||||
// FIXME: since we only need to recheck files protected rules, we could improve this
|
ctx.ServerError("CheckPrsForBaseBranch", err)
|
||||||
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("FindAllMatchedBranches", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, branchName := range matchedBranches {
|
ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch))
|
||||||
if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
|
ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch)))
|
||||||
ctx.ServerError("CheckPRsForBaseBranch", err)
|
} else {
|
||||||
|
if protectBranch != nil {
|
||||||
|
if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, protectBranch.ID); err != nil {
|
||||||
|
ctx.ServerError("DeleteProtectedBranch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", branch))
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", protectBranch.RuleName))
|
ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
|
||||||
ctx.Redirect(fmt.Sprintf("%s/settings/branches?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteProtectedBranchRulePost delete protected branch rule by id
|
|
||||||
func DeleteProtectedBranchRulePost(ctx *context.Context) {
|
|
||||||
ruleID := ctx.ParamsInt64("id")
|
|
||||||
if ruleID <= 0 {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
|
|
||||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
|
|
||||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if rule == nil {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
|
|
||||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, ruleID); err != nil {
|
|
||||||
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName))
|
|
||||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName))
|
|
||||||
ctx.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameBranchPost responses for rename a branch
|
// RenameBranchPost responses for rename a branch
|
||||||
|
@ -280,17 +280,17 @@ func Repos(ctx *context.Context) {
|
|||||||
repos := map[string]*repo_model.Repository{}
|
repos := map[string]*repo_model.Repository{}
|
||||||
// We're going to iterate by pagesize.
|
// We're going to iterate by pagesize.
|
||||||
root := user_model.UserPath(ctxUser.Name)
|
root := user_model.UserPath(ctxUser.Name)
|
||||||
if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !d.IsDir() || path == root {
|
if !info.IsDir() || path == root {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name := d.Name()
|
name := info.Name()
|
||||||
if !strings.HasSuffix(name, ".git") {
|
if !strings.HasSuffix(name, ".git") {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ func Repos(ctx *context.Context) {
|
|||||||
count++
|
count++
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
ctx.ServerError("filepath.WalkDir", err)
|
ctx.ServerError("filepath.Walk", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,16 +861,10 @@ func RegisterRoutes(m *web.Route) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
m.Group("/branches", func() {
|
m.Group("/branches", func() {
|
||||||
m.Post("/", repo.SetDefaultBranchPost)
|
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
|
||||||
}, repo.MustBeNotEmpty)
|
m.Combo("/*").Get(repo.SettingsProtectedBranch).
|
||||||
|
|
||||||
m.Group("/branches", func() {
|
|
||||||
m.Get("/", repo.ProtectedBranchRules)
|
|
||||||
m.Combo("/edit").Get(repo.SettingsProtectedBranch).
|
|
||||||
Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost)
|
Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost)
|
||||||
m.Post("/{id}/delete", repo.DeleteProtectedBranchRulePost)
|
|
||||||
}, repo.MustBeNotEmpty)
|
}, repo.MustBeNotEmpty)
|
||||||
|
|
||||||
m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo.RenameBranchPost)
|
m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo.RenameBranchPost)
|
||||||
|
|
||||||
m.Group("/tags", func() {
|
m.Group("/tags", func() {
|
||||||
|
@ -310,7 +310,7 @@ Loop:
|
|||||||
return false, "", nil, &ErrWontSign{twofa}
|
return false, "", nil, &ErrWontSign{twofa}
|
||||||
}
|
}
|
||||||
case approved:
|
case approved:
|
||||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pr.BaseBranch)
|
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, pr.BaseBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", nil, err
|
return false, "", nil, err
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isRepoAdmin {
|
if isRepoAdmin {
|
||||||
branch.EffectiveBranchProtectionName = bp.RuleName
|
branch.EffectiveBranchProtectionName = bp.BranchName
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
if user != nil {
|
||||||
@ -87,8 +87,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
bp.Repo = repo
|
branch.UserCanPush = bp.CanUserPush(db.DefaultContext, user.ID)
|
||||||
branch.UserCanPush = bp.CanUserPush(db.DefaultContext, user)
|
|
||||||
branch.UserCanMerge = git_model.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission)
|
branch.UserCanMerge = git_model.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,14 +121,8 @@ func ToBranchProtection(bp *git_model.ProtectedBranch) *api.BranchProtection {
|
|||||||
log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err)
|
log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
branchName := ""
|
|
||||||
if !git_model.IsRuleNameSpecial(bp.RuleName) {
|
|
||||||
branchName = bp.RuleName
|
|
||||||
}
|
|
||||||
|
|
||||||
return &api.BranchProtection{
|
return &api.BranchProtection{
|
||||||
BranchName: branchName,
|
BranchName: bp.BranchName,
|
||||||
RuleName: bp.RuleName,
|
|
||||||
EnablePush: bp.CanPush,
|
EnablePush: bp.CanPush,
|
||||||
EnablePushWhitelist: bp.EnableWhitelist,
|
EnablePushWhitelist: bp.EnableWhitelist,
|
||||||
PushWhitelistUsernames: pushWhitelistUsernames,
|
PushWhitelistUsernames: pushWhitelistUsernames,
|
||||||
|
@ -175,48 +175,6 @@ func registerDeleteOldSystemNotices() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerGCLFS() {
|
|
||||||
if !setting.LFS.StartServer {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
type GCLFSConfig struct {
|
|
||||||
OlderThanConfig
|
|
||||||
LastUpdatedMoreThanAgo time.Duration
|
|
||||||
NumberToCheckPerRepo int64
|
|
||||||
ProportionToCheckPerRepo float64
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterTaskFatal("gc_lfs", &GCLFSConfig{
|
|
||||||
OlderThanConfig: OlderThanConfig{
|
|
||||||
BaseConfig: BaseConfig{
|
|
||||||
Enabled: false,
|
|
||||||
RunAtStart: false,
|
|
||||||
Schedule: "@every 24h",
|
|
||||||
},
|
|
||||||
// Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload
|
|
||||||
// and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby
|
|
||||||
// an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid
|
|
||||||
// changes in new branches that might lead to lfs objects becoming temporarily unassociated with git
|
|
||||||
// objects.
|
|
||||||
//
|
|
||||||
// It is likely that a week is potentially excessive but it should definitely be enough that any
|
|
||||||
// unassociated LFS object is genuinely unassociated.
|
|
||||||
OlderThan: 24 * time.Hour * 7,
|
|
||||||
},
|
|
||||||
// Only GC things that haven't been looked at in the past 3 days
|
|
||||||
LastUpdatedMoreThanAgo: 24 * time.Hour * 3,
|
|
||||||
NumberToCheckPerRepo: 100,
|
|
||||||
ProportionToCheckPerRepo: 0.6,
|
|
||||||
}, func(ctx context.Context, _ *user_model.User, config Config) error {
|
|
||||||
gcLFSConfig := config.(*GCLFSConfig)
|
|
||||||
return repo_service.GarbageCollectLFSMetaObjects(ctx, repo_service.GarbageCollectLFSMetaObjectsOptions{
|
|
||||||
AutoFix: true,
|
|
||||||
OlderThan: time.Now().Add(-gcLFSConfig.OlderThan),
|
|
||||||
UpdatedLessRecentlyThan: time.Now().Add(-gcLFSConfig.LastUpdatedMoreThanAgo),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func initExtendedTasks() {
|
func initExtendedTasks() {
|
||||||
registerDeleteInactiveUsers()
|
registerDeleteInactiveUsers()
|
||||||
registerDeleteRepositoryArchives()
|
registerDeleteRepositoryArchives()
|
||||||
@ -230,5 +188,4 @@ func initExtendedTasks() {
|
|||||||
registerDeleteOldActions()
|
registerDeleteOldActions()
|
||||||
registerUpdateGiteaChecker()
|
registerUpdateGiteaChecker()
|
||||||
registerDeleteOldSystemNotices()
|
registerDeleteOldSystemNotices()
|
||||||
registerGCLFS()
|
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) bindi
|
|||||||
|
|
||||||
// ProtectBranchForm form for changing protected branch settings
|
// ProtectBranchForm form for changing protected branch settings
|
||||||
type ProtectBranchForm struct {
|
type ProtectBranchForm struct {
|
||||||
RuleName string `binding:"Required"`
|
Protected bool
|
||||||
EnablePush string
|
EnablePush string
|
||||||
WhitelistUsers string
|
WhitelistUsers string
|
||||||
WhitelistTeams string
|
WhitelistTeams string
|
||||||
|
@ -274,7 +274,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||||||
"ActionName": actName,
|
"ActionName": actName,
|
||||||
"ReviewComments": reviewComments,
|
"ReviewComments": reviewComments,
|
||||||
"Language": locale.Language(),
|
"Language": locale.Language(),
|
||||||
"CanReply": setting.IncomingEmail.Enabled && commentType != issues_model.CommentTypePullRequestPush,
|
|
||||||
// helper
|
// helper
|
||||||
"locale": locale,
|
"locale": locale,
|
||||||
"Str2html": templates.Str2html,
|
"Str2html": templates.Str2html,
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -127,12 +126,11 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
|
|||||||
|
|
||||||
// isSignedIfRequired check if merge will be signed if required
|
// isSignedIfRequired check if merge will be signed if required
|
||||||
func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) (bool, error) {
|
func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) (bool, error) {
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
if err := pr.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if pb == nil || !pb.RequireSignedCommits {
|
if pr.ProtectedBranch == nil || !pr.ProtectedBranch.RequireSignedCommits {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,8 +348,8 @@ func testPR(id int64) {
|
|||||||
checkAndUpdateStatus(ctx, pr)
|
checkAndUpdateStatus(ctx, pr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPRsForBaseBranch check all pulls with baseBrannch
|
// CheckPrsForBaseBranch check all pulls with bseBrannch
|
||||||
func CheckPRsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName string) error {
|
func CheckPrsForBaseBranch(baseRepo *repo_model.Repository, baseBranchName string) error {
|
||||||
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(baseRepo.ID, baseBranchName)
|
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(baseRepo.ID, baseBranchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -83,11 +83,10 @@ func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requ
|
|||||||
|
|
||||||
// IsPullCommitStatusPass returns if all required status checks PASS
|
// IsPullCommitStatusPass returns if all required status checks PASS
|
||||||
func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
|
func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) {
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
if err := pr.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, errors.Wrap(err, "GetLatestCommitStatus")
|
return false, errors.Wrap(err, "GetLatestCommitStatus")
|
||||||
}
|
}
|
||||||
if pb == nil || !pb.EnableStatusCheck {
|
if pr.ProtectedBranch == nil || !pr.ProtectedBranch.EnableStatusCheck {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,13 +137,12 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
|
|||||||
return "", errors.Wrap(err, "GetLatestCommitStatus")
|
return "", errors.Wrap(err, "GetLatestCommitStatus")
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
if err := pr.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "LoadProtectedBranch")
|
return "", errors.Wrap(err, "LoadProtectedBranch")
|
||||||
}
|
}
|
||||||
var requiredContexts []string
|
var requiredContexts []string
|
||||||
if pb != nil {
|
if pr.ProtectedBranch != nil {
|
||||||
requiredContexts = pb.StatusCheckContexts
|
requiredContexts = pr.ProtectedBranch.StatusCheckContexts
|
||||||
}
|
}
|
||||||
|
|
||||||
return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
|
return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil
|
||||||
|
@ -760,12 +760,12 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
err := pr.LoadProtectedBranch(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.CanWrite(unit.TypeCode) && pb == nil) || (pb != nil && git_model.IsUserMergeWhitelisted(ctx, pb, user.ID, p)) {
|
if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && git_model.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -778,11 +778,10 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||||||
return fmt.Errorf("LoadBaseRepo: %w", err)
|
return fmt.Errorf("LoadBaseRepo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
if err = pr.LoadProtectedBranch(ctx); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("LoadProtectedBranch: %w", err)
|
||||||
return fmt.Errorf("LoadProtectedBranch: %v", err)
|
|
||||||
}
|
}
|
||||||
if pb == nil {
|
if pr.ProtectedBranch == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -796,23 +795,23 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
|
if !issues_model.HasEnoughApprovals(ctx, pr.ProtectedBranch, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return models.ErrDisallowedToMerge{
|
||||||
Reason: "Does not have enough approvals",
|
Reason: "Does not have enough approvals",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
|
if issues_model.MergeBlockedByRejectedReview(ctx, pr.ProtectedBranch, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return models.ErrDisallowedToMerge{
|
||||||
Reason: "There are requested changes",
|
Reason: "There are requested changes",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
|
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pr.ProtectedBranch, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return models.ErrDisallowedToMerge{
|
||||||
Reason: "There are official review requests",
|
Reason: "There are official review requests",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
|
if issues_model.MergeBlockedByOutdatedBranch(pr.ProtectedBranch, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return models.ErrDisallowedToMerge{
|
||||||
Reason: "The head branch is behind the base branch",
|
Reason: "The head branch is behind the base branch",
|
||||||
}
|
}
|
||||||
@ -822,7 +821,7 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
|
if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
|
||||||
return models.ErrDisallowedToMerge{
|
return models.ErrDisallowedToMerge{
|
||||||
Reason: "Changed protected files",
|
Reason: "Changed protected files",
|
||||||
}
|
}
|
||||||
@ -837,9 +836,6 @@ func MergedManually(pr *issues_model.PullRequest, doer *user_model.User, baseGit
|
|||||||
defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
|
defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
|
||||||
|
|
||||||
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
@ -106,8 +106,8 @@ func TestPatch(pr *issues_model.PullRequest) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check for protected files changes
|
// 3. Check for protected files changes
|
||||||
if err = checkPullFilesProtection(ctx, pr, gitRepo); err != nil {
|
if err = checkPullFilesProtection(pr, gitRepo); err != nil {
|
||||||
return fmt.Errorf("pr.CheckPullFilesProtection(): %v", err)
|
return fmt.Errorf("pr.CheckPullFilesProtection(): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pr.ChangedProtectedFiles) > 0 {
|
if len(pr.ChangedProtectedFiles) > 0 {
|
||||||
@ -544,23 +544,23 @@ func CheckUnprotectedFiles(repo *git.Repository, oldCommitID, newCommitID string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkPullFilesProtection check if pr changed protected files and save results
|
// checkPullFilesProtection check if pr changed protected files and save results
|
||||||
func checkPullFilesProtection(ctx context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) error {
|
func checkPullFilesProtection(pr *issues_model.PullRequest, gitRepo *git.Repository) error {
|
||||||
if pr.Status == issues_model.PullRequestStatusEmpty {
|
if pr.Status == issues_model.PullRequestStatusEmpty {
|
||||||
pr.ChangedProtectedFiles = nil
|
pr.ChangedProtectedFiles = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
if err := pr.LoadProtectedBranch(db.DefaultContext); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if pb == nil {
|
if pr.ProtectedBranch == nil {
|
||||||
pr.ChangedProtectedFiles = nil
|
pr.ChangedProtectedFiles = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ())
|
var err error
|
||||||
|
pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.MergeBase, "tracking", pr.ProtectedBranch.GetProtectedFilePatterns(), 10, os.Environ())
|
||||||
if err != nil && !models.IsErrFilePathProtected(err) {
|
if err != nil && !models.IsErrFilePathProtected(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -93,29 +92,20 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
|
|||||||
return false, false, err
|
return false, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pull.LoadBaseRepo(ctx); err != nil {
|
|
||||||
return false, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pr := &issues_model.PullRequest{
|
pr := &issues_model.PullRequest{
|
||||||
HeadRepoID: pull.BaseRepoID,
|
HeadRepoID: pull.BaseRepoID,
|
||||||
HeadRepo: pull.BaseRepo,
|
|
||||||
BaseRepoID: pull.HeadRepoID,
|
BaseRepoID: pull.HeadRepoID,
|
||||||
BaseRepo: pull.HeadRepo,
|
|
||||||
HeadBranch: pull.BaseBranch,
|
HeadBranch: pull.BaseBranch,
|
||||||
BaseBranch: pull.HeadBranch,
|
BaseBranch: pull.HeadBranch,
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
err = pr.LoadProtectedBranch(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, false, err
|
return false, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// can't do rebase on protected branch because need force push
|
// can't do rebase on protected branch because need force push
|
||||||
if pb == nil {
|
if pr.ProtectedBranch == nil {
|
||||||
if err := pr.LoadBaseRepo(ctx); err != nil {
|
|
||||||
return false, false, err
|
|
||||||
}
|
|
||||||
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
|
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
|
||||||
@ -125,12 +115,9 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update function need push permission
|
// Update function need push permission
|
||||||
if pb != nil {
|
if pr.ProtectedBranch != nil && !pr.ProtectedBranch.CanUserPush(ctx, user.ID) {
|
||||||
pb.Repo = pull.BaseRepo
|
|
||||||
if !pb.CanUserPush(ctx, user) {
|
|
||||||
return false, false, nil
|
return false, false, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user)
|
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -303,16 +303,14 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in
|
|||||||
|
|
||||||
// We're going to iterate by pagesize.
|
// We're going to iterate by pagesize.
|
||||||
root := filepath.Clean(setting.RepoRootPath)
|
root := filepath.Clean(setting.RepoRootPath)
|
||||||
if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
|
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !d.IsDir() || path == root {
|
if !info.IsDir() || path == root {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
name := d.Name()
|
|
||||||
|
|
||||||
if !strings.ContainsRune(path[len(root)+1:], filepath.Separator) {
|
if !strings.ContainsRune(path[len(root)+1:], filepath.Separator) {
|
||||||
// Got a new user
|
// Got a new user
|
||||||
if err = checkUnadoptedRepositories(userName, repoNamesToCheck, unadopted); err != nil {
|
if err = checkUnadoptedRepositories(userName, repoNamesToCheck, unadopted); err != nil {
|
||||||
@ -320,14 +318,16 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in
|
|||||||
}
|
}
|
||||||
repoNamesToCheck = repoNamesToCheck[:0]
|
repoNamesToCheck = repoNamesToCheck[:0]
|
||||||
|
|
||||||
if !globUser.Match(name) {
|
if !globUser.Match(info.Name()) {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
|
|
||||||
userName = name
|
userName = info.Name()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name := info.Name()
|
||||||
|
|
||||||
if !strings.HasSuffix(name, ".git") {
|
if !strings.HasSuffix(name, ".git") {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,7 @@ func RenameBranch(repo *repo_model.Repository, doer *user_model.User, gitRepo *g
|
|||||||
// enmuerates all branch related errors
|
// enmuerates all branch related errors
|
||||||
var (
|
var (
|
||||||
ErrBranchIsDefault = errors.New("branch is default")
|
ErrBranchIsDefault = errors.New("branch is default")
|
||||||
|
ErrBranchIsProtected = errors.New("branch is protected")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteBranch delete branch
|
// DeleteBranch delete branch
|
||||||
@ -158,12 +159,13 @@ func DeleteBranch(doer *user_model.User, repo *repo_model.Repository, gitRepo *g
|
|||||||
return ErrBranchIsDefault
|
return ErrBranchIsDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
isProtected, err := git_model.IsBranchProtected(db.DefaultContext, repo.ID, branchName)
|
isProtected, err := git_model.IsProtectedBranch(db.DefaultContext, repo.ID, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isProtected {
|
if isProtected {
|
||||||
return git_model.ErrBranchIsProtected
|
return ErrBranchIsProtected
|
||||||
}
|
}
|
||||||
|
|
||||||
commit, err := gitRepo.GetBranchCommit(branchName)
|
commit, err := gitRepo.GetBranchCommit(branchName)
|
||||||
|
@ -66,18 +66,15 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, opts.OldBranch)
|
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, opts.OldBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if protectedBranch != nil {
|
if protectedBranch != nil && !protectedBranch.CanUserPush(ctx, doer.ID) {
|
||||||
protectedBranch.Repo = repo
|
|
||||||
if !protectedBranch.CanUserPush(ctx, doer) {
|
|
||||||
return models.ErrUserCannotCommit{
|
return models.ErrUserCannotCommit{
|
||||||
UserName: doer.LowerName,
|
UserName: doer.LowerName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if protectedBranch != nil && protectedBranch.RequireSignedCommits {
|
if protectedBranch != nil && protectedBranch.RequireSignedCommits {
|
||||||
_, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), opts.OldBranch)
|
_, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), opts.OldBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -463,18 +463,17 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
|
|||||||
|
|
||||||
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
||||||
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName, treePath string) error {
|
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName, treePath string) error {
|
||||||
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if protectedBranch != nil {
|
if protectedBranch != nil {
|
||||||
protectedBranch.Repo = repo
|
|
||||||
isUnprotectedFile := false
|
isUnprotectedFile := false
|
||||||
glob := protectedBranch.GetUnprotectedFilePatterns()
|
glob := protectedBranch.GetUnprotectedFilePatterns()
|
||||||
if len(glob) != 0 {
|
if len(glob) != 0 {
|
||||||
isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath)
|
isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath)
|
||||||
}
|
}
|
||||||
if !protectedBranch.CanUserPush(ctx, doer) && !isUnprotectedFile {
|
if !protectedBranch.CanUserPush(ctx, doer.ID) && !isUnprotectedFile {
|
||||||
return models.ErrUserCannotCommit{
|
return models.ErrUserCannotCommit{
|
||||||
UserName: doer.LowerName,
|
UserName: doer.LowerName,
|
||||||
}
|
}
|
||||||
|
@ -5,67 +5,49 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/lfs"
|
"code.gitea.io/gitea/modules/lfs"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GarbageCollectLFSMetaObjectsOptions provides options for GarbageCollectLFSMetaObjects function
|
func GarbageCollectLFSMetaObjects(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||||
type GarbageCollectLFSMetaObjectsOptions struct {
|
|
||||||
Logger log.Logger
|
|
||||||
AutoFix bool
|
|
||||||
OlderThan time.Time
|
|
||||||
UpdatedLessRecentlyThan time.Time
|
|
||||||
NumberToCheckPerRepo int64
|
|
||||||
ProportionToCheckPerRepo float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// GarbageCollectLFSMetaObjects garbage collects LFS objects for all repositories
|
|
||||||
func GarbageCollectLFSMetaObjects(ctx context.Context, opts GarbageCollectLFSMetaObjectsOptions) error {
|
|
||||||
log.Trace("Doing: GarbageCollectLFSMetaObjects")
|
log.Trace("Doing: GarbageCollectLFSMetaObjects")
|
||||||
defer log.Trace("Finished: GarbageCollectLFSMetaObjects")
|
|
||||||
|
|
||||||
if !setting.LFS.StartServer {
|
if err := db.Iterate(
|
||||||
if opts.Logger != nil {
|
ctx,
|
||||||
opts.Logger.Info("LFS support is disabled")
|
builder.And(builder.Gt{"id": 0}),
|
||||||
}
|
func(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
return nil
|
return GarbageCollectLFSMetaObjectsForRepo(ctx, repo, logger, autofix)
|
||||||
}
|
},
|
||||||
|
); err != nil {
|
||||||
return git_model.IterateRepositoryIDsWithLFSMetaObjects(ctx, func(ctx context.Context, repoID, count int64) error {
|
|
||||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if newMinimum := int64(float64(count) * opts.ProportionToCheckPerRepo); newMinimum > opts.NumberToCheckPerRepo && opts.NumberToCheckPerRepo != 0 {
|
log.Trace("Finished: GarbageCollectLFSMetaObjects")
|
||||||
opts.NumberToCheckPerRepo = newMinimum
|
return nil
|
||||||
}
|
|
||||||
return GarbageCollectLFSMetaObjectsForRepo(ctx, repo, opts)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GarbageCollectLFSMetaObjectsForRepo garbage collects LFS objects for a specific repository
|
func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.Repository, logger log.Logger, autofix bool) error {
|
||||||
func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.Repository, opts GarbageCollectLFSMetaObjectsOptions) error {
|
if logger != nil {
|
||||||
if opts.Logger != nil {
|
logger.Info("Checking %-v", repo)
|
||||||
opts.Logger.Info("Checking %-v", repo)
|
|
||||||
}
|
}
|
||||||
total, orphaned, collected, deleted := int64(0), 0, 0, 0
|
total, orphaned, collected, deleted := 0, 0, 0, 0
|
||||||
if opts.Logger != nil {
|
if logger != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
if orphaned == 0 {
|
if orphaned == 0 {
|
||||||
opts.Logger.Info("Found %d total LFSMetaObjects in %-v", total, repo)
|
logger.Info("Found %d total LFSMetaObjects in %-v", total, repo)
|
||||||
} else if !opts.AutoFix {
|
} else if !autofix {
|
||||||
opts.Logger.Info("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo)
|
logger.Info("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo)
|
||||||
} else {
|
} else {
|
||||||
opts.Logger.Info("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted)
|
logger.Info("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -78,21 +60,17 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R
|
|||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|
||||||
store := lfs.NewContentStore()
|
store := lfs.NewContentStore()
|
||||||
errStop := errors.New("STOPERR")
|
|
||||||
|
|
||||||
err = git_model.IterateLFSMetaObjectsForRepo(ctx, repo.ID, func(ctx context.Context, metaObject *git_model.LFSMetaObject, count int64) error {
|
return git_model.IterateLFSMetaObjectsForRepo(ctx, repo.ID, func(ctx context.Context, metaObject *git_model.LFSMetaObject, count int64) error {
|
||||||
if opts.NumberToCheckPerRepo > 0 && total > opts.NumberToCheckPerRepo {
|
|
||||||
return errStop
|
|
||||||
}
|
|
||||||
total++
|
total++
|
||||||
pointerSha := git.ComputeBlobHash([]byte(metaObject.Pointer.StringContent()))
|
pointerSha := git.ComputeBlobHash([]byte(metaObject.Pointer.StringContent()))
|
||||||
|
|
||||||
if gitRepo.IsObjectExist(pointerSha.String()) {
|
if gitRepo.IsObjectExist(pointerSha.String()) {
|
||||||
return git_model.MarkLFSMetaObject(ctx, metaObject.ID)
|
return nil
|
||||||
}
|
}
|
||||||
orphaned++
|
orphaned++
|
||||||
|
|
||||||
if !opts.AutoFix {
|
if !autofix {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Non-existent pointer file
|
// Non-existent pointer file
|
||||||
@ -122,19 +100,6 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R
|
|||||||
//
|
//
|
||||||
// It is likely that a week is potentially excessive but it should definitely be enough that any
|
// It is likely that a week is potentially excessive but it should definitely be enough that any
|
||||||
// unassociated LFS object is genuinely unassociated.
|
// unassociated LFS object is genuinely unassociated.
|
||||||
OlderThan: opts.OlderThan,
|
OlderThan: time.Now().Add(-24 * 7 * time.Hour),
|
||||||
UpdatedLessRecentlyThan: opts.UpdatedLessRecentlyThan,
|
|
||||||
OrderByUpdated: true,
|
|
||||||
LoopFunctionAlwaysUpdates: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == errStop {
|
|
||||||
if opts.Logger != nil {
|
|
||||||
opts.Logger.Info("Processing stopped at %d total LFSMetaObjects in %-v", total, repo)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
<p>
|
<p>
|
||||||
---
|
---
|
||||||
<br>
|
<br>
|
||||||
<a href="{{.Link}}">{{.locale.Tr "mail.view_it_on" AppName}}</a>{{if .CanReply}} {{.locale.Tr "mail.reply"}}{{end}}.
|
<a href="{{.Link}}">{{.locale.Tr "mail.view_it_on" AppName}}</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
{{.locale.Tr "packages.settings.delete"}}
|
{{.locale.Tr "packages.settings.delete"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="ui warning message text left word-break">
|
<div class="ui warning message text left">
|
||||||
{{.locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
|
{{.locale.Tr "packages.settings.delete.notice" .PackageDescriptor.Package.Name .PackageDescriptor.Version.Version}}
|
||||||
</div>
|
</div>
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
|
@ -204,7 +204,7 @@
|
|||||||
{{if .IsBlockedByApprovals}}
|
{{if .IsBlockedByApprovals}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<i class="icon icon-octicon">{{svg "octicon-x"}}</i>
|
<i class="icon icon-octicon">{{svg "octicon-x"}}</i>
|
||||||
{{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}}
|
{{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .Issue.PullRequest.ProtectedBranch.RequiredApprovals}}
|
||||||
</div>
|
</div>
|
||||||
{{else if .IsBlockedByRejection}}
|
{{else if .IsBlockedByRejection}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
@ -444,7 +444,7 @@
|
|||||||
{{if .IsBlockedByApprovals}}
|
{{if .IsBlockedByApprovals}}
|
||||||
<div class="item text red">
|
<div class="item text red">
|
||||||
{{svg "octicon-x"}}
|
{{svg "octicon-x"}}
|
||||||
{{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}}
|
{{$.locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .Issue.PullRequest.ProtectedBranch.RequiredApprovals}}
|
||||||
</div>
|
</div>
|
||||||
{{else if .IsBlockedByRejection}}
|
{{else if .IsBlockedByRejection}}
|
||||||
<div class="item text red">
|
<div class="item text red">
|
||||||
|
@ -43,24 +43,31 @@
|
|||||||
|
|
||||||
<h4 class="ui top attached header">
|
<h4 class="ui top attached header">
|
||||||
{{.locale.Tr "repo.settings.protected_branch"}}
|
{{.locale.Tr "repo.settings.protected_branch"}}
|
||||||
<div class="ui right">
|
|
||||||
<a class="ui primary tiny button" href="{{$.Repository.Link}}/settings/branches/edit">{{$.locale.Tr "repo.settings.branches.add_new_rule"}}</a>
|
|
||||||
</div>
|
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="ui attached table segment">
|
<div class="ui attached table segment">
|
||||||
|
<div class="ui grid padded">
|
||||||
|
<div class="eight wide column">
|
||||||
|
<div class="ui fluid dropdown selection" tabindex="0">
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<div class="default text">{{.locale.Tr "repo.settings.choose_branch"}}</div>
|
||||||
|
<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
|
||||||
|
{{range .LeftBranches}}
|
||||||
|
<a class="item" href="{{$.Repository.Link}}/settings/branches/{{. | PathEscapeSegments}}">{{.}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ui grid padded">
|
<div class="ui grid padded">
|
||||||
<div class="sixteen wide column">
|
<div class="sixteen wide column">
|
||||||
<table class="ui single line table padded">
|
<table class="ui single line table padded">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range .ProtectedBranches}}
|
{{range .ProtectedBranches}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><div class="ui basic primary label">{{.RuleName}}</div></td>
|
<td><div class="ui basic primary label">{{.BranchName}}</div></td>
|
||||||
<td class="right aligned">
|
<td class="right aligned"><a class="rm ui button" href="{{$.Repository.Link}}/settings/branches/{{.BranchName | PathEscapeSegments}}">{{$.locale.Tr "repo.settings.edit_protected_branch"}}</a></td>
|
||||||
<a class="rm ui button" href="{{$.Repository.Link}}/settings/branches/edit?rule_name={{.RuleName}}">{{$.locale.Tr "repo.settings.edit_protected_branch"}}</a>
|
|
||||||
<button class="ui red tiny button delete-button" data-url="{{$.Repository.Link}}/settings/branches/{{.ID}}/delete" data-id="{{.ID}}">
|
|
||||||
{{$.locale.Tr "repo.settings.protected_branch.delete_rule"}}</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{{else}}
|
{{else}}
|
||||||
<tr class="center aligned"><td>{{.locale.Tr "repo.settings.no_protected_branch"}}</td></tr>
|
<tr class="center aligned"><td>{{.locale.Tr "repo.settings.no_protected_branch"}}</td></tr>
|
||||||
@ -95,16 +102,4 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui small basic delete modal">
|
|
||||||
<div class="ui header">
|
|
||||||
{{svg "octicon-trash" 16 "mr-2"}}
|
|
||||||
{{.locale.Tr "repo.settings.protected_branch_deletion"}}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<p>{{.locale.Tr "repo.settings.protected_branch_deletion_desc"}}</p>
|
|
||||||
</div>
|
|
||||||
{{template "base/delete_modal_actions" .}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
@ -4,43 +4,42 @@
|
|||||||
{{template "repo/settings/navbar" .}}
|
{{template "repo/settings/navbar" .}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
|
||||||
<h4 class="ui top attached header">
|
<h4 class="ui top attached header">
|
||||||
{{.locale.Tr "repo.settings.branch_protection" (.Rule.RuleName|Escape) | Str2html}}
|
{{.locale.Tr "repo.settings.branch_protection" (.Branch.BranchName|Escape) | Str2html}}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment branch-protection">
|
<div class="ui attached segment branch-protection">
|
||||||
<div class="field">
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
<label for="protected_file_patterns">{{.locale.Tr "repo.settings.protect_branch_name_pattern"}}</label>
|
|
||||||
<input name="rule_name" type="text" value="{{.Rule.RuleName}}">
|
|
||||||
<input name="rule_id" type="hidden" value="{{.Rule.ID}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
|
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div id="protection_box" class="fields">
|
<div class="inline field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input class="enable-protection" name="protected" type="checkbox" data-target="#protection_box" {{if .Branch.IsProtected}}checked{{end}}>
|
||||||
|
<label>{{.locale.Tr "repo.settings.protect_this_branch"}}</label>
|
||||||
|
<p class="help">{{.locale.Tr "repo.settings.protect_this_branch_desc"}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="protection_box" class="fields {{if not .Branch.IsProtected}}disabled{{end}}">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input name="enable_push" type="radio" value="none" class="disable-whitelist" data-target="#whitelist_box" {{if not .Rule.CanPush}}checked{{end}}>
|
<input name="enable_push" type="radio" value="none" class="disable-whitelist" data-target="#whitelist_box" {{if not .Branch.CanPush}}checked{{end}}>
|
||||||
<label>{{.locale.Tr "repo.settings.protect_disable_push"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_disable_push"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_disable_push_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_disable_push_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input name="enable_push" type="radio" value="all" class="disable-whitelist" data-target="#whitelist_box" {{if and (.Rule.CanPush) (not .Rule.EnableWhitelist)}}checked{{end}}>
|
<input name="enable_push" type="radio" value="all" class="disable-whitelist" data-target="#whitelist_box" {{if and (.Branch.CanPush) (not .Branch.EnableWhitelist)}}checked{{end}}>
|
||||||
<label>{{.locale.Tr "repo.settings.protect_enable_push"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_enable_push"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_enable_push_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_enable_push_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input name="enable_push" type="radio" value="whitelist" class="enable-whitelist" data-target="#whitelist_box" {{if and (.Rule.CanPush) (.Rule.EnableWhitelist)}}checked{{end}}>
|
<input name="enable_push" type="radio" value="whitelist" class="enable-whitelist" data-target="#whitelist_box" {{if and (.Branch.CanPush) (.Branch.EnableWhitelist)}}checked{{end}}>
|
||||||
<label>{{.locale.Tr "repo.settings.protect_whitelist_committers"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_whitelist_committers"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_whitelist_committers_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_whitelist_committers_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="whitelist_box" class="fields {{if not .Rule.EnableWhitelist}}disabled{{end}}">
|
<div id="whitelist_box" class="fields {{if not .Branch.EnableWhitelist}}disabled{{end}}">
|
||||||
<div class="whitelist field">
|
<div class="whitelist field">
|
||||||
<label>{{.locale.Tr "repo.settings.protect_whitelist_users"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_whitelist_users"}}</label>
|
||||||
<div class="ui multiple search selection dropdown">
|
<div class="ui multiple search selection dropdown">
|
||||||
@ -77,22 +76,20 @@
|
|||||||
<br>
|
<br>
|
||||||
<div class="whitelist field">
|
<div class="whitelist field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="whitelist_deploy_keys" {{if .Rule.WhitelistDeployKeys}}checked{{end}}>
|
<input type="checkbox" name="whitelist_deploy_keys" {{if .Branch.WhitelistDeployKeys}}checked{{end}}>
|
||||||
<label for="whitelist_deploy_keys">{{.locale.Tr "repo.settings.protect_whitelist_deploy_keys"}}</label>
|
<label for="whitelist_deploy_keys">{{.locale.Tr "repo.settings.protect_whitelist_deploy_keys"}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input class="enable-whitelist" name="enable_merge_whitelist" type="checkbox" data-target="#merge_whitelist_box" {{if .Rule.EnableMergeWhitelist}}checked{{end}}>
|
<input class="enable-whitelist" name="enable_merge_whitelist" type="checkbox" data-target="#merge_whitelist_box" {{if .Branch.EnableMergeWhitelist}}checked{{end}}>
|
||||||
<label>{{.locale.Tr "repo.settings.protect_merge_whitelist_committers"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_merge_whitelist_committers"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_merge_whitelist_committers_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_merge_whitelist_committers_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="merge_whitelist_box" class="fields {{if not .Rule.EnableMergeWhitelist}}disabled{{end}}">
|
<div id="merge_whitelist_box" class="fields {{if not .Branch.EnableMergeWhitelist}}disabled{{end}}">
|
||||||
<div class="whitelist field">
|
<div class="whitelist field">
|
||||||
<label>{{.locale.Tr "repo.settings.protect_merge_whitelist_users"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_merge_whitelist_users"}}</label>
|
||||||
<div class="ui multiple search selection dropdown">
|
<div class="ui multiple search selection dropdown">
|
||||||
@ -130,13 +127,13 @@
|
|||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input class="enable-statuscheck" name="enable_status_check" type="checkbox" data-target="#statuscheck_contexts_box" {{if eq (len .branch_status_check_contexts) 0}}disabled{{end}} {{if .Rule.EnableStatusCheck}}checked{{end}}>
|
<input class="enable-statuscheck" name="enable_status_check" type="checkbox" data-target="#statuscheck_contexts_box" {{if eq (len .branch_status_check_contexts) 0}}disabled{{end}} {{if .Branch.EnableStatusCheck}}checked{{end}}>
|
||||||
<label>{{.locale.Tr "repo.settings.protect_check_status_contexts"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_check_status_contexts"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_check_status_contexts_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_check_status_contexts_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="statuscheck_contexts_box" class="fields {{if not .Rule.EnableStatusCheck}}disabled{{end}}">
|
<div id="statuscheck_contexts_box" class="fields {{if not .Branch.EnableStatusCheck}}disabled{{end}}">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<table class="ui celled table six column">
|
<table class="ui celled table six column">
|
||||||
<thead>
|
<thead>
|
||||||
@ -162,17 +159,17 @@
|
|||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="required-approvals">{{.locale.Tr "repo.settings.protect_required_approvals"}}</label>
|
<label for="required-approvals">{{.locale.Tr "repo.settings.protect_required_approvals"}}</label>
|
||||||
<input name="required_approvals" id="required-approvals" type="number" value="{{.Rule.RequiredApprovals}}">
|
<input name="required_approvals" id="required-approvals" type="number" value="{{.Branch.RequiredApprovals}}">
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_required_approvals_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_required_approvals_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input class="enable-whitelist" name="enable_approvals_whitelist" type="checkbox" data-target="#approvals_whitelist_box" {{if .Rule.EnableApprovalsWhitelist}}checked{{end}}>
|
<input class="enable-whitelist" name="enable_approvals_whitelist" type="checkbox" data-target="#approvals_whitelist_box" {{if .Branch.EnableApprovalsWhitelist}}checked{{end}}>
|
||||||
<label>{{.locale.Tr "repo.settings.protect_approvals_whitelist_enabled"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_approvals_whitelist_enabled"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_approvals_whitelist_enabled_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_approvals_whitelist_enabled_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="approvals_whitelist_box" class="fields {{if not .Rule.EnableApprovalsWhitelist}}disabled{{end}}">
|
<div id="approvals_whitelist_box" class="fields {{if not .Branch.EnableApprovalsWhitelist}}disabled{{end}}">
|
||||||
<div class="whitelist field">
|
<div class="whitelist field">
|
||||||
<label>{{.locale.Tr "repo.settings.protect_approvals_whitelist_users"}}</label>
|
<label>{{.locale.Tr "repo.settings.protect_approvals_whitelist_users"}}</label>
|
||||||
<div class="ui multiple search selection dropdown">
|
<div class="ui multiple search selection dropdown">
|
||||||
@ -209,59 +206,59 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input name="block_on_rejected_reviews" type="checkbox" {{if .Rule.BlockOnRejectedReviews}}checked{{end}}>
|
<input name="block_on_rejected_reviews" type="checkbox" {{if .Branch.BlockOnRejectedReviews}}checked{{end}}>
|
||||||
<label for="block_on_rejected_reviews">{{.locale.Tr "repo.settings.block_rejected_reviews"}}</label>
|
<label for="block_on_rejected_reviews">{{.locale.Tr "repo.settings.block_rejected_reviews"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.block_rejected_reviews_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.block_rejected_reviews_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input name="block_on_official_review_requests" type="checkbox" {{if .Rule.BlockOnOfficialReviewRequests}}checked{{end}}>
|
<input name="block_on_official_review_requests" type="checkbox" {{if .Branch.BlockOnOfficialReviewRequests}}checked{{end}}>
|
||||||
<label for="block_on_official_review_requests">{{.locale.Tr "repo.settings.block_on_official_review_requests"}}</label>
|
<label for="block_on_official_review_requests">{{.locale.Tr "repo.settings.block_on_official_review_requests"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.block_on_official_review_requests_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input name="dismiss_stale_approvals" type="checkbox" {{if .Rule.DismissStaleApprovals}}checked{{end}}>
|
<input name="dismiss_stale_approvals" type="checkbox" {{if .Branch.DismissStaleApprovals}}checked{{end}}>
|
||||||
<label for="dismiss_stale_approvals">{{.locale.Tr "repo.settings.dismiss_stale_approvals"}}</label>
|
<label for="dismiss_stale_approvals">{{.locale.Tr "repo.settings.dismiss_stale_approvals"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.dismiss_stale_approvals_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input name="require_signed_commits" type="checkbox" {{if .Rule.RequireSignedCommits}}checked{{end}}>
|
<input name="require_signed_commits" type="checkbox" {{if .Branch.RequireSignedCommits}}checked{{end}}>
|
||||||
<label for="require_signed_commits">{{.locale.Tr "repo.settings.require_signed_commits"}}</label>
|
<label for="require_signed_commits">{{.locale.Tr "repo.settings.require_signed_commits"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.require_signed_commits_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.require_signed_commits_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input name="block_on_outdated_branch" type="checkbox" {{if .Rule.BlockOnOutdatedBranch}}checked{{end}}>
|
<input name="block_on_outdated_branch" type="checkbox" {{if .Branch.BlockOnOutdatedBranch}}checked{{end}}>
|
||||||
<label for="block_on_outdated_branch">{{.locale.Tr "repo.settings.block_outdated_branch"}}</label>
|
<label for="block_on_outdated_branch">{{.locale.Tr "repo.settings.block_outdated_branch"}}</label>
|
||||||
<p class="help">{{.locale.Tr "repo.settings.block_outdated_branch_desc"}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.block_outdated_branch_desc"}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="protected_file_patterns">{{.locale.Tr "repo.settings.protect_protected_file_patterns"}}</label>
|
<label for="protected_file_patterns">{{.locale.Tr "repo.settings.protect_protected_file_patterns"}}</label>
|
||||||
<input name="protected_file_patterns" id="protected_file_patterns" type="text" value="{{.Rule.ProtectedFilePatterns}}">
|
<input name="protected_file_patterns" id="protected_file_patterns" type="text" value="{{.Branch.ProtectedFilePatterns}}">
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_protected_file_patterns_desc" | Safe}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_protected_file_patterns_desc" | Safe}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="unprotected_file_patterns">{{.locale.Tr "repo.settings.protect_unprotected_file_patterns"}}</label>
|
<label for="unprotected_file_patterns">{{.locale.Tr "repo.settings.protect_unprotected_file_patterns"}}</label>
|
||||||
<input name="unprotected_file_patterns" id="unprotected_file_patterns" type="text" value="{{.Rule.UnprotectedFilePatterns}}">
|
<input name="unprotected_file_patterns" id="unprotected_file_patterns" type="text" value="{{.Branch.UnprotectedFilePatterns}}">
|
||||||
<p class="help">{{.locale.Tr "repo.settings.protect_unprotected_file_patterns_desc" | Safe}}</p>
|
<p class="help">{{.locale.Tr "repo.settings.protect_unprotected_file_patterns_desc" | Safe}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<button class="ui green button">{{$.locale.Tr "repo.settings.protected_branch.save_rule"}}</button>
|
<button class="ui green button">{{$.locale.Tr "repo.settings.update_settings"}}</button>
|
||||||
<button class="ui gray button">{{$.locale.Tr "cancel"}}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
@ -14233,7 +14233,6 @@
|
|||||||
"x-go-name": "BlockOnRejectedReviews"
|
"x-go-name": "BlockOnRejectedReviews"
|
||||||
},
|
},
|
||||||
"branch_name": {
|
"branch_name": {
|
||||||
"description": "Deprecated: true",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "BranchName"
|
"x-go-name": "BranchName"
|
||||||
},
|
},
|
||||||
@ -14311,10 +14310,6 @@
|
|||||||
"format": "int64",
|
"format": "int64",
|
||||||
"x-go-name": "RequiredApprovals"
|
"x-go-name": "RequiredApprovals"
|
||||||
},
|
},
|
||||||
"rule_name": {
|
|
||||||
"type": "string",
|
|
||||||
"x-go-name": "RuleName"
|
|
||||||
},
|
|
||||||
"status_check_contexts": {
|
"status_check_contexts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@ -14777,7 +14772,6 @@
|
|||||||
"x-go-name": "BlockOnRejectedReviews"
|
"x-go-name": "BlockOnRejectedReviews"
|
||||||
},
|
},
|
||||||
"branch_name": {
|
"branch_name": {
|
||||||
"description": "Deprecated: true",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "BranchName"
|
"x-go-name": "BranchName"
|
||||||
},
|
},
|
||||||
@ -14850,10 +14844,6 @@
|
|||||||
"format": "int64",
|
"format": "int64",
|
||||||
"x-go-name": "RequiredApprovals"
|
"x-go-name": "RequiredApprovals"
|
||||||
},
|
},
|
||||||
"rule_name": {
|
|
||||||
"type": "string",
|
|
||||||
"x-go-name": "RuleName"
|
|
||||||
},
|
|
||||||
"status_check_contexts": {
|
"status_check_contexts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -38,21 +38,21 @@ func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPSta
|
|||||||
if resp.Code == http.StatusOK {
|
if resp.Code == http.StatusOK {
|
||||||
var branchProtection api.BranchProtection
|
var branchProtection api.BranchProtection
|
||||||
DecodeJSON(t, resp, &branchProtection)
|
DecodeJSON(t, resp, &branchProtection)
|
||||||
assert.EqualValues(t, branchName, branchProtection.RuleName)
|
assert.EqualValues(t, branchName, branchProtection.BranchName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) {
|
func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) {
|
||||||
token := getUserToken(t, "user2")
|
token := getUserToken(t, "user2")
|
||||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{
|
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{
|
||||||
RuleName: branchName,
|
BranchName: branchName,
|
||||||
})
|
})
|
||||||
resp := MakeRequest(t, req, expectedHTTPStatus)
|
resp := MakeRequest(t, req, expectedHTTPStatus)
|
||||||
|
|
||||||
if resp.Code == http.StatusCreated {
|
if resp.Code == http.StatusCreated {
|
||||||
var branchProtection api.BranchProtection
|
var branchProtection api.BranchProtection
|
||||||
DecodeJSON(t, resp, &branchProtection)
|
DecodeJSON(t, resp, &branchProtection)
|
||||||
assert.EqualValues(t, branchName, branchProtection.RuleName)
|
assert.EqualValues(t, branchName, branchProtection.BranchName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.Bran
|
|||||||
if resp.Code == http.StatusOK {
|
if resp.Code == http.StatusOK {
|
||||||
var branchProtection api.BranchProtection
|
var branchProtection api.BranchProtection
|
||||||
DecodeJSON(t, resp, &branchProtection)
|
DecodeJSON(t, resp, &branchProtection)
|
||||||
assert.EqualValues(t, branchName, branchProtection.RuleName)
|
assert.EqualValues(t, branchName, branchProtection.BranchName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,8 +169,8 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran
|
|||||||
func TestAPIBranchProtection(t *testing.T) {
|
func TestAPIBranchProtection(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
// Branch protection on branch that not exist
|
// Branch protection only on branch that exist
|
||||||
testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusCreated)
|
testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusNotFound)
|
||||||
// Get branch protection on branch that exist but not branch protection
|
// Get branch protection on branch that exist but not branch protection
|
||||||
testAPIGetBranchProtection(t, "master", http.StatusNotFound)
|
testAPIGetBranchProtection(t, "master", http.StatusNotFound)
|
||||||
|
|
||||||
|
@ -256,32 +256,6 @@ func TestPackageContainer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UploadBlob/Mount", func(t *testing.T) {
|
|
||||||
defer tests.PrintCurrentTest(t)()
|
|
||||||
|
|
||||||
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
|
|
||||||
addTokenAuthHeader(req, userToken)
|
|
||||||
MakeRequest(t, req, http.StatusAccepted)
|
|
||||||
|
|
||||||
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, blobDigest))
|
|
||||||
addTokenAuthHeader(req, userToken)
|
|
||||||
resp := MakeRequest(t, req, http.StatusCreated)
|
|
||||||
|
|
||||||
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
|
|
||||||
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
|
||||||
|
|
||||||
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s&from=%s", url, unknownDigest, "unknown/image"))
|
|
||||||
addTokenAuthHeader(req, userToken)
|
|
||||||
MakeRequest(t, req, http.StatusAccepted)
|
|
||||||
|
|
||||||
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s&from=%s/%s", url, blobDigest, user.Name, image))
|
|
||||||
addTokenAuthHeader(req, userToken)
|
|
||||||
resp = MakeRequest(t, req, http.StatusCreated)
|
|
||||||
|
|
||||||
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
|
|
||||||
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
t.Run(fmt.Sprintf("[Tag:%s]", tag), func(t *testing.T) {
|
t.Run(fmt.Sprintf("[Tag:%s]", tag), func(t *testing.T) {
|
||||||
t.Run("UploadManifest", func(t *testing.T) {
|
t.Run("UploadManifest", func(t *testing.T) {
|
||||||
@ -470,6 +444,21 @@ func TestPackageContainer(t *testing.T) {
|
|||||||
assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest))
|
assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("UploadBlob/Mount", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, unknownDigest))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
MakeRequest(t, req, http.StatusAccepted)
|
||||||
|
|
||||||
|
req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads?mount=%s", url, blobDigest))
|
||||||
|
addTokenAuthHeader(req, userToken)
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
|
||||||
|
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("HeadBlob", func(t *testing.T) {
|
t.Run("HeadBlob", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
@ -10,8 +10,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/json"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,16 +43,15 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
|
|||||||
|
|
||||||
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
|
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
|
||||||
// Change master branch to protected
|
// Change master branch to protected
|
||||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
"rule_name": "master",
|
"protected": "on",
|
||||||
"enable_push": "true",
|
|
||||||
})
|
})
|
||||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
// Check if master branch has been locked successfully
|
// Check if master branch has been locked successfully
|
||||||
flashCookie := session.GetCookie("macaron_flash")
|
flashCookie := session.GetCookie("macaron_flash")
|
||||||
assert.NotNil(t, flashCookie)
|
assert.NotNil(t, flashCookie)
|
||||||
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
||||||
|
|
||||||
// Request editor page
|
// Request editor page
|
||||||
req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
|
req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
|
||||||
@ -79,22 +76,16 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
|
|||||||
|
|
||||||
// remove the protected branch
|
// remove the protected branch
|
||||||
csrf = GetCSRF(t, session, "/user2/repo1/settings/branches")
|
csrf = GetCSRF(t, session, "/user2/repo1/settings/branches")
|
||||||
|
|
||||||
// Change master branch to protected
|
// Change master branch to protected
|
||||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/1/delete", map[string]string{
|
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/master", map[string]string{
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
|
"protected": "off",
|
||||||
})
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
res := make(map[string]string)
|
|
||||||
assert.NoError(t, json.NewDecoder(resp.Body).Decode(&res))
|
|
||||||
assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"])
|
|
||||||
|
|
||||||
// Check if master branch has been locked successfully
|
// Check if master branch has been locked successfully
|
||||||
flashCookie = session.GetCookie("macaron_flash")
|
flashCookie = session.GetCookie("macaron_flash")
|
||||||
assert.NotNil(t, flashCookie)
|
assert.NotNil(t, flashCookie)
|
||||||
assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25271%2527%2Bfailed.", flashCookie.Value)
|
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,9 +414,9 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFil
|
|||||||
|
|
||||||
if userToWhitelist == "" {
|
if userToWhitelist == "" {
|
||||||
// Change branch to protected
|
// Change branch to protected
|
||||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
"rule_name": branch,
|
"protected": "on",
|
||||||
"unprotected_file_patterns": unprotectedFilePatterns,
|
"unprotected_file_patterns": unprotectedFilePatterns,
|
||||||
})
|
})
|
||||||
ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
|
ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
@ -424,9 +424,9 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFil
|
|||||||
user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelist)
|
user, err := user_model.GetUserByName(db.DefaultContext, userToWhitelist)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// Change branch to protected
|
// Change branch to protected
|
||||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), map[string]string{
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), url.PathEscape(branch)), map[string]string{
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
"rule_name": branch,
|
"protected": "on",
|
||||||
"enable_push": "whitelist",
|
"enable_push": "whitelist",
|
||||||
"enable_whitelist": "on",
|
"enable_whitelist": "on",
|
||||||
"whitelist_users": strconv.FormatInt(user.ID, 10),
|
"whitelist_users": strconv.FormatInt(user.ID, 10),
|
||||||
@ -437,7 +437,7 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFil
|
|||||||
// Check if master branch has been locked successfully
|
// Check if master branch has been locked successfully
|
||||||
flashCookie := ctx.Session.GetCookie("macaron_flash")
|
flashCookie := ctx.Session.GetCookie("macaron_flash")
|
||||||
assert.NotNil(t, flashCookie)
|
assert.NotNil(t, flashCookie)
|
||||||
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
function displayError(el, err) {
|
function displayError(el, err) {
|
||||||
const target = targetElement(el);
|
const target = targetElement(el);
|
||||||
target.classList.remove('is-loading');
|
target.remove('is-loading');
|
||||||
const errorNode = document.createElement('div');
|
const errorNode = document.createElement('div');
|
||||||
errorNode.setAttribute('class', 'ui message error markup-block-error mono');
|
errorNode.setAttribute('class', 'ui message error markup-block-error mono');
|
||||||
errorNode.textContent = err.str || err.message || String(err);
|
errorNode.textContent = err.str || err.message || String(err);
|
||||||
@ -23,15 +23,13 @@ export async function renderMath() {
|
|||||||
|
|
||||||
for (const el of els) {
|
for (const el of els) {
|
||||||
const source = el.textContent;
|
const source = el.textContent;
|
||||||
const displayMode = el.classList.contains('display');
|
const nodeName = el.classList.contains('display') ? 'p' : 'span';
|
||||||
const nodeName = displayMode ? 'p' : 'span';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tempEl = document.createElement(nodeName);
|
const tempEl = document.createElement(nodeName);
|
||||||
katex.render(source, tempEl, {
|
katex.render(source, tempEl, {
|
||||||
maxSize: 25,
|
maxSize: 25,
|
||||||
maxExpand: 50,
|
maxExpand: 50,
|
||||||
displayMode,
|
|
||||||
});
|
});
|
||||||
targetElement(el).replaceWith(tempEl);
|
targetElement(el).replaceWith(tempEl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user