Merge branch 'go-gitea:main' into pacman-packages

This commit is contained in:
Danila Fominykh 2023-09-06 16:07:59 -03:00 committed by GitHub
commit 934d1197b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 1086 additions and 582 deletions

View File

@ -2564,6 +2564,8 @@ LEVEL = Info
;; ;;
;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. ;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
;DEFAULT_ACTIONS_URL = github ;DEFAULT_ACTIONS_URL = github
;; Default artifact retention time in days, default is 90 days
;ARTIFACT_RETENTION_DAYS = 90
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -955,6 +955,12 @@ Default templates for project boards:
- `SCHEDULE`: **@midnight** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts. - `SCHEDULE`: **@midnight** : Interval as a duration between each synchronization, it will always attempt synchronization when the instance starts.
- `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false. - `UPDATE_EXISTING`: **true**: Create new users, update existing user data and disable users that are not in external source anymore (default) or only create new users if UPDATE_EXISTING is set to false.
## Cron - Cleanup Expired Actions Assets (`cron.cleanup_actions`)
- `ENABLED`: **true**: Enable cleanup expired actions assets job.
- `RUN_AT_START`: **true**: Run job at start time (if ENABLED).
- `SCHEDULE`: **@midnight** : Cron syntax for the job.
### Extended cron tasks (not enabled by default) ### Extended cron tasks (not enabled by default)
#### Cron - Garbage collect all repositories (`cron.git_gc_repos`) #### Cron - Garbage collect all repositories (`cron.git_gc_repos`)
@ -1381,6 +1387,7 @@ PROXY_HOSTS = *.github.com
- `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. - `DEFAULT_ACTIONS_URL`: **github**: Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance.
- `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `STORAGE_TYPE`: **local**: Storage type for actions logs, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
- `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio` - `MINIO_BASE_PATH`: **actions_log/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
- `ARTIFACT_RETENTION_DAYS`: **90**: Number of days to keep artifacts. Set to 0 to disable artifact retention. Default is 90 days if not set.
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path. `DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`. For example, `uses: actions/checkout@v3` means `https://github.com/actions/checkout@v3` since the value of `DEFAULT_ACTIONS_URL` is `github`.

View File

@ -29,6 +29,8 @@ server {
location / { location / {
client_max_body_size 512M; client_max_body_size 512M;
proxy_pass http://localhost:3000; proxy_pass http://localhost:3000;
proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -51,6 +51,15 @@ Open "Windows Services", search for the service named "gitea", right-click it an
"Run". If everything is OK, Gitea will be reachable on `http://localhost:3000` (or the port "Run". If everything is OK, Gitea will be reachable on `http://localhost:3000` (or the port
that was configured). that was configured).
## Service startup type
It was observed that on loaded systems during boot Gitea service may fail to start with timeout records in Windows Event Log.
In that case change startup type to `Automatic-Delayed`. This can be done during service creation, or by running config command
```
sc.exe config gitea start= delayed-auto
```
## Adding startup dependencies ## Adding startup dependencies
To add a startup dependency to the Gitea Windows service (eg Mysql, Mariadb), as an Administrator, then run the following command: To add a startup dependency to the Gitea Windows service (eg Mysql, Mariadb), as an Administrator, then run the following command:

View File

@ -15,6 +15,6 @@ menu:
# Profile READMEs # Profile READMEs
To display a markdown file in your Gitea profile page, simply make a repository named ".profile" and edit the README.md file inside. Gitea will automatically pull this file in and display it above your repositories. To display a Markdown file in your Gitea profile page, simply create a repository named `.profile` and add a new file called `README.md`. Gitea will automatically display the contents of the file on your profile, above your repositories.
Note. You are welcome to make this repository private. Doing so will hide your source files from public viewing and allow you to privitize certain files. However, the README.md file will be the only file present on your profile. If you wish to have an entirely private .profile repository, remove or rename the README.md file. Making the `.profile` repository private will hide the Profile README.

View File

@ -9,19 +9,21 @@ package actions
import ( import (
"context" "context"
"errors" "errors"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// ArtifactStatus is the status of an artifact, uploading, expired or need-delete
type ArtifactStatus int64
const ( const (
// ArtifactStatusUploadPending is the status of an artifact upload that is pending ArtifactStatusUploadPending ArtifactStatus = iota + 1 // 1 ArtifactStatusUploadPending is the status of an artifact upload that is pending
ArtifactStatusUploadPending = 1 ArtifactStatusUploadConfirmed // 2 ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
// ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed ArtifactStatusUploadError // 3 ArtifactStatusUploadError is the status of an artifact upload that is errored
ArtifactStatusUploadConfirmed = 2 ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired
// ArtifactStatusUploadError is the status of an artifact upload that is errored
ArtifactStatusUploadError = 3
) )
func init() { func init() {
@ -45,9 +47,10 @@ type ActionArtifact struct {
Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired
} }
func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string) (*ActionArtifact, error) { func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string, expiredDays int64) (*ActionArtifact, error) {
if err := t.LoadJob(ctx); err != nil { if err := t.LoadJob(ctx); err != nil {
return nil, err return nil, err
} }
@ -61,7 +64,8 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
RepoID: t.RepoID, RepoID: t.RepoID,
OwnerID: t.OwnerID, OwnerID: t.OwnerID,
CommitSHA: t.CommitSHA, CommitSHA: t.CommitSHA,
Status: ArtifactStatusUploadPending, Status: int64(ArtifactStatusUploadPending),
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays),
} }
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil { if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
return nil, err return nil, err
@ -126,15 +130,16 @@ func ListUploadedArtifactsByRunID(ctx context.Context, runID int64) ([]*ActionAr
type ActionArtifactMeta struct { type ActionArtifactMeta struct {
ArtifactName string ArtifactName string
FileSize int64 FileSize int64
Status int64
} }
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run // ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) { func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
arts := make([]*ActionArtifactMeta, 0, 10) arts := make([]*ActionArtifactMeta, 0, 10)
return arts, db.GetEngine(ctx).Table("action_artifact"). return arts, db.GetEngine(ctx).Table("action_artifact").
Where("run_id=? AND status=?", runID, ArtifactStatusUploadConfirmed). Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired).
GroupBy("artifact_name"). GroupBy("artifact_name").
Select("artifact_name, sum(file_size) as file_size"). Select("artifact_name, sum(file_size) as file_size, max(status) as status").
Find(&arts) Find(&arts)
} }
@ -149,3 +154,16 @@ func ListArtifactsByRunIDAndName(ctx context.Context, runID int64, name string)
arts := make([]*ActionArtifact, 0, 10) arts := make([]*ActionArtifact, 0, 10)
return arts, db.GetEngine(ctx).Where("run_id=? AND artifact_name=?", runID, name).Find(&arts) return arts, db.GetEngine(ctx).Where("run_id=? AND artifact_name=?", runID, name).Find(&arts)
} }
// ListNeedExpiredArtifacts returns all need expired artifacts but not deleted
func ListNeedExpiredArtifacts(ctx context.Context) ([]*ActionArtifact, error) {
arts := make([]*ActionArtifact, 0, 10)
return arts, db.GetEngine(ctx).
Where("expired_unix < ? AND status = ?", timeutil.TimeStamp(time.Now().Unix()), ArtifactStatusUploadConfirmed).Find(&arts)
}
// SetArtifactExpired sets an artifact to expired
func SetArtifactExpired(ctx context.Context, artifactID int64) error {
_, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)})
return err
}

View File

@ -528,6 +528,8 @@ var migrations = []Migration{
NewMigration("Add Version to ActionRun table", v1_21.AddVersionToActionRunTable), NewMigration("Add Version to ActionRun table", v1_21.AddVersionToActionRunTable),
// v273 -> v274 // v273 -> v274
NewMigration("Add Action Schedule Table", v1_21.AddActionScheduleTable), NewMigration("Add Action Schedule Table", v1_21.AddActionScheduleTable),
// v274 -> v275
NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,36 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_21 //nolint
import (
"time"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddExpiredUnixColumnInActionArtifactTable(x *xorm.Engine) error {
type ActionArtifact struct {
ExpiredUnix timeutil.TimeStamp `xorm:"index"` // time when the artifact will be expired
}
if err := x.Sync(new(ActionArtifact)); err != nil {
return err
}
return updateArtifactsExpiredUnixTo90Days(x)
}
func updateArtifactsExpiredUnixTo90Days(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
expiredTime := time.Now().AddDate(0, 0, 90).Unix()
if _, err := sess.Exec(`UPDATE action_artifact SET expired_unix=? WHERE status='2' AND expired_unix is NULL`, expiredTime); err != nil {
return err
}
return sess.Commit()
}

View File

@ -250,6 +250,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return return
} }
} }
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects) ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages) ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)

View File

@ -471,6 +471,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
} }
ctx.Repo.Owner = owner ctx.Repo.Owner = owner
ctx.ContextUser = owner ctx.ContextUser = owner
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Username"] = ctx.Repo.Owner.Name
// redirect link to wiki // redirect link to wiki

View File

@ -22,7 +22,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/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues" issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -156,142 +155,6 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
return nil return nil
} }
// CreateRepoOptions contains the create repository options
type CreateRepoOptions struct {
Name string
Description string
OriginalURL string
GitServiceType api.GitServiceType
Gitignores string
IssueLabels string
License string
Readme string
DefaultBranch string
IsPrivate bool
IsMirror bool
IsTemplate bool
AutoInit bool
Status repo_model.RepositoryStatus
TrustModel repo_model.TrustModelType
MirrorInterval string
}
// CreateRepository creates a repository for the user/organization.
func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
if !doer.IsAdmin && !u.CanCreateRepo() {
return nil, repo_model.ErrReachLimitOfRepo{
Limit: u.MaxRepoCreation,
}
}
if len(opts.DefaultBranch) == 0 {
opts.DefaultBranch = setting.Repository.DefaultBranch
}
// Check if label template exist
if len(opts.IssueLabels) > 0 {
if _, err := LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil {
return nil, err
}
}
repo := &repo_model.Repository{
OwnerID: u.ID,
Owner: u,
OwnerName: u.Name,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
OriginalURL: opts.OriginalURL,
OriginalServiceType: opts.GitServiceType,
IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror,
IsTemplate: opts.IsTemplate,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
TrustModel: opts.TrustModel,
IsMirror: opts.IsMirror,
DefaultBranch: opts.DefaultBranch,
}
var rollbackRepo *repo_model.Repository
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil {
return err
}
// No need for init mirror.
if opts.IsMirror {
return nil
}
repoPath := repo_model.RepoPath(u.Name, repo.Name)
isExist, err := util.IsExist(repoPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
return err
}
if isExist {
// repo already exists - We have two or three options.
// 1. We fail stating that the directory exists
// 2. We create the db repository to go with this data and adopt the git repo
// 3. We delete it and start afresh
//
// Previously Gitea would just delete and start afresh - this was naughty.
// So we will now fail and delegate to other functionality to adopt or delete
log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
return repo_model.ErrRepoFilesAlreadyExist{
Uname: u.Name,
Name: repo.Name,
}
}
if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil {
if err2 := util.RemoveAll(repoPath); err2 != nil {
log.Error("initRepository: %v", err)
return fmt.Errorf(
"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2)
}
return fmt.Errorf("initRepository: %w", err)
}
// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err = InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("InitializeLabels: %w", err)
}
}
if err := CheckDaemonExportOK(ctx, repo); err != nil {
return fmt.Errorf("checkDaemonExportOK: %w", err)
}
if stdout, _, err := git.NewCommand(ctx, "update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
}
return nil
}); err != nil {
if rollbackRepo != nil {
if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
}
return nil, err
}
return repo, nil
}
const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
// getDirectorySize returns the disk consumption for a given path // getDirectorySize returns the disk consumption for a given path

View File

@ -4,151 +4,16 @@
package repository package repository
import ( import (
"fmt"
"testing" "testing"
"code.gitea.io/gitea/models"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestIncludesAllRepositoriesTeams(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
testTeamRepositories := func(teamID int64, repoIds []int64) {
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name)
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
for i, rid := range repoIds {
if rid > 0 {
assert.True(t, models.HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i)
}
}
}
// Get an admin user.
user, err := user_model.GetUserByID(db.DefaultContext, 1)
assert.NoError(t, err, "GetUserByID")
// Create org.
org := &organization.Organization{
Name: "All_repo",
IsActive: true,
Type: user_model.UserTypeOrganization,
Visibility: structs.VisibleTypePublic,
}
assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization")
// Check Owner team.
ownerTeam, err := org.GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err, "GetOwnerTeam")
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
// Create repos.
repoIds := make([]int64, 0)
for i := 0; i < 3; i++ {
r, err := CreateRepository(user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
assert.NoError(t, err, "CreateRepository %d", i)
if r != nil {
repoIds = append(repoIds, r.ID)
}
}
// Get fresh copy of Owner team after creating repos.
ownerTeam, err = org.GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err, "GetOwnerTeam")
// Create teams and check repositories.
teams := []*organization.Team{
ownerTeam,
{
OrgID: org.ID,
Name: "team one",
AccessMode: perm.AccessModeRead,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 2",
AccessMode: perm.AccessModeRead,
IncludesAllRepositories: false,
},
{
OrgID: org.ID,
Name: "team three",
AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 4",
AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: false,
},
}
teamRepos := [][]int64{
repoIds,
repoIds,
{},
repoIds,
{},
}
for i, team := range teams {
if i > 0 { // first team is Owner.
assert.NoError(t, models.NewTeam(team), "%s: NewTeam", team.Name)
}
testTeamRepositories(team.ID, teamRepos[i])
}
// Update teams and check repositories.
teams[3].IncludesAllRepositories = false
teams[4].IncludesAllRepositories = true
teamRepos[4] = repoIds
for i, team := range teams {
assert.NoError(t, models.UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name)
testTeamRepositories(team.ID, teamRepos[i])
}
// Create repo and check teams repositories.
r, err := CreateRepository(user, org.AsUser(), CreateRepoOptions{Name: "repo-last"})
assert.NoError(t, err, "CreateRepository last")
if r != nil {
repoIds = append(repoIds, r.ID)
}
teamRepos[0] = repoIds
teamRepos[1] = repoIds
teamRepos[4] = repoIds
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Remove repo and check teams repositories.
assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository")
teamRepos[0] = repoIds[1:]
teamRepos[1] = repoIds[1:]
teamRepos[3] = repoIds[1:3]
teamRepos[4] = repoIds[1:]
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Wipe created items.
for i, rid := range repoIds {
if i > 0 { // first repo already deleted.
assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i)
}
}
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization")
}
func TestUpdateRepositoryVisibilityChanged(t *testing.T) { func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -241,7 +241,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
defaultBranch = templateRepo.DefaultBranch defaultBranch = templateRepo.DefaultBranch
} }
return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) return InitRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch)
} }
func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) {
@ -356,7 +356,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
} }
} }
if err = checkInitRepository(ctx, owner.Name, generateRepo.Name); err != nil { if err = CheckInitRepository(ctx, owner.Name, generateRepo.Name); err != nil {
return generateRepo, err return generateRepo, err
} }

View File

@ -108,12 +108,7 @@ done
} }
// CreateDelegateHooks creates all the hooks scripts for the repo // CreateDelegateHooks creates all the hooks scripts for the repo
func CreateDelegateHooks(repoPath string) error { func CreateDelegateHooks(repoPath string) (err error) {
return createDelegateHooks(repoPath)
}
// createDelegateHooks creates all the hooks scripts for the repo
func createDelegateHooks(repoPath string) (err error) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates() hookNames, hookTpls, giteaHookTpls := getHookTemplates()
hookDir := filepath.Join(repoPath, "hooks") hookDir := filepath.Join(repoPath, "hooks")

View File

@ -4,7 +4,6 @@
package repository package repository
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"os" "os"
@ -21,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
) )
@ -126,95 +124,8 @@ func LoadRepoConfig() error {
return nil return nil
} }
func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { // InitRepoCommit temporarily changes with work directory.
commitTimeStr := time.Now().Format(time.RFC3339) func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
authorSig := repo.Owner.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+authorSig.Name,
"GIT_COMMITTER_EMAIL="+authorSig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Clone to temporary path and do the init commit.
if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir).
SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)).
RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil {
log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
return fmt.Errorf("git clone: %w", err)
}
// README
data, err := options.Readme(opts.Readme)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
}
cloneLink := repo.CloneLink()
match := map[string]string{
"Name": repo.Name,
"Description": repo.Description,
"CloneURL.SSH": cloneLink.SSH,
"CloneURL.HTTPS": cloneLink.HTTPS,
"OwnerName": repo.OwnerName,
}
res, err := vars.Expand(string(data), match)
if err != nil {
// here we could just log the error and continue the rendering
log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err)
}
if err = os.WriteFile(filepath.Join(tmpDir, "README.md"),
[]byte(res), 0o644); err != nil {
return fmt.Errorf("write README.md: %w", err)
}
// .gitignore
if len(opts.Gitignores) > 0 {
var buf bytes.Buffer
names := strings.Split(opts.Gitignores, ",")
for _, name := range names {
data, err = options.Gitignore(name)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
}
buf.WriteString("# ---> " + name + "\n")
buf.Write(data)
buf.WriteString("\n")
}
if buf.Len() > 0 {
if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0o644); err != nil {
return fmt.Errorf("write .gitignore: %w", err)
}
}
}
// LICENSE
if len(opts.License) > 0 {
data, err = getLicense(opts.License, &licenseValues{
Owner: repo.OwnerName,
Email: authorSig.Email,
Repo: repo.Name,
Year: time.Now().Format("2006"),
})
if err != nil {
return fmt.Errorf("getLicense[%s]: %w", opts.License, err)
}
if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0o644); err != nil {
return fmt.Errorf("write LICENSE: %w", err)
}
}
return nil
}
// initRepoCommit temporarily changes with work directory.
func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
commitTimeStr := time.Now().Format(time.RFC3339) commitTimeStr := time.Now().Format(time.RFC3339)
sig := u.NewGitSig() sig := u.NewGitSig()
@ -277,7 +188,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
return nil return nil
} }
func checkInitRepository(ctx context.Context, owner, name string) (err error) { func CheckInitRepository(ctx context.Context, owner, name string) (err error) {
// Somehow the directory could exist. // Somehow the directory could exist.
repoPath := repo_model.RepoPath(owner, name) repoPath := repo_model.RepoPath(owner, name)
isExist, err := util.IsExist(repoPath) isExist, err := util.IsExist(repoPath)
@ -295,77 +206,12 @@ func checkInitRepository(ctx context.Context, owner, name string) (err error) {
// Init git bare new repository. // Init git bare new repository.
if err = git.InitRepository(ctx, repoPath, true); err != nil { if err = git.InitRepository(ctx, repoPath, true); err != nil {
return fmt.Errorf("git.InitRepository: %w", err) return fmt.Errorf("git.InitRepository: %w", err)
} else if err = createDelegateHooks(repoPath); err != nil { } else if err = CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err) return fmt.Errorf("createDelegateHooks: %w", err)
} }
return nil return nil
} }
// InitRepository initializes README and .gitignore if needed.
func initRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) {
if err = checkInitRepository(ctx, repo.OwnerName, repo.Name); err != nil {
return err
}
// Initialize repository according to user's choice.
if opts.AutoInit {
tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name)
if err != nil {
return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.RepoPath(), err)
}
defer func() {
if err := util.RemoveAll(tmpDir); err != nil {
log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err)
}
}()
if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil {
return fmt.Errorf("prepareRepoCommit: %w", err)
}
// Apply changes and commit.
if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
return fmt.Errorf("initRepoCommit: %w", err)
}
}
// Re-fetch the repository from database before updating it (else it would
// override changes that were done earlier with sql)
if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
return fmt.Errorf("getRepositoryByID: %w", err)
}
if !opts.AutoInit {
repo.IsEmpty = true
}
repo.DefaultBranch = setting.Repository.DefaultBranch
if len(opts.DefaultBranch) > 0 {
repo.DefaultBranch = opts.DefaultBranch
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
return fmt.Errorf("openRepository: %w", err)
}
defer gitRepo.Close()
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
if !repo.IsEmpty {
if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
return fmt.Errorf("SyncRepoBranches: %w", err)
}
}
}
if err = UpdateRepository(ctx, repo, false); err != nil {
return fmt.Errorf("updateRepository: %w", err)
}
return nil
}
// InitializeLabels adds a label set to a repository using a template // InitializeLabels adds a label set to a repository using a template
func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error {
list, err := LoadTemplateLabelsByDisplayName(labelTemplate) list, err := LoadTemplateLabelsByDisplayName(labelTemplate)

View File

@ -13,14 +13,14 @@ import (
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
) )
type licenseValues struct { type LicenseValues struct {
Owner string Owner string
Email string Email string
Repo string Repo string
Year string Year string
} }
func getLicense(name string, values *licenseValues) ([]byte, error) { func GetLicense(name string, values *LicenseValues) ([]byte, error) {
data, err := options.License(name) data, err := options.License(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) return nil, fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
@ -28,7 +28,7 @@ func getLicense(name string, values *licenseValues) ([]byte, error) {
return fillLicensePlaceholder(name, values, data), nil return fillLicensePlaceholder(name, values, data), nil
} }
func fillLicensePlaceholder(name string, values *licenseValues, origin []byte) []byte { func fillLicensePlaceholder(name string, values *LicenseValues, origin []byte) []byte {
placeholder := getLicensePlaceholder(name) placeholder := getLicensePlaceholder(name)
scanner := bufio.NewScanner(bytes.NewReader(origin)) scanner := bufio.NewScanner(bytes.NewReader(origin))

View File

@ -13,7 +13,7 @@ import (
func Test_getLicense(t *testing.T) { func Test_getLicense(t *testing.T) {
type args struct { type args struct {
name string name string
values *licenseValues values *LicenseValues
} }
tests := []struct { tests := []struct {
name string name string
@ -25,7 +25,7 @@ func Test_getLicense(t *testing.T) {
name: "regular", name: "regular",
args: args{ args: args{
name: "MIT", name: "MIT",
values: &licenseValues{Owner: "Gitea", Year: "2023"}, values: &LicenseValues{Owner: "Gitea", Year: "2023"},
}, },
want: `MIT License want: `MIT License
@ -49,11 +49,11 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := getLicense(tt.args.name, tt.args.values) got, err := GetLicense(tt.args.name, tt.args.values)
if !tt.wantErr(t, err, fmt.Sprintf("getLicense(%v, %v)", tt.args.name, tt.args.values)) { if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) {
return return
} }
assert.Equalf(t, tt.want, string(got), "getLicense(%v, %v)", tt.args.name, tt.args.values) assert.Equalf(t, tt.want, string(got), "GetLicense(%v, %v)", tt.args.name, tt.args.values)
}) })
} }
} }
@ -61,7 +61,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
func Test_fillLicensePlaceholder(t *testing.T) { func Test_fillLicensePlaceholder(t *testing.T) {
type args struct { type args struct {
name string name string
values *licenseValues values *LicenseValues
origin string origin string
} }
tests := []struct { tests := []struct {
@ -73,7 +73,7 @@ func Test_fillLicensePlaceholder(t *testing.T) {
name: "owner", name: "owner",
args: args{ args: args{
name: "regular", name: "regular",
values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
origin: ` origin: `
<name of author> <name of author>
<owner> <owner>
@ -104,7 +104,7 @@ Gitea
name: "email", name: "email",
args: args{ args: args{
name: "regular", name: "regular",
values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
origin: ` origin: `
[EMAIL] [EMAIL]
`, `,
@ -117,7 +117,7 @@ teabot@gitea.io
name: "repo", name: "repo",
args: args{ args: args{
name: "regular", name: "regular",
values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
origin: ` origin: `
<program> <program>
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
@ -132,7 +132,7 @@ gitea
name: "year", name: "year",
args: args{ args: args{
name: "regular", name: "regular",
values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
origin: ` origin: `
<year> <year>
[YEAR] [YEAR]
@ -155,7 +155,7 @@ gitea
name: "0BSD", name: "0BSD",
args: args{ args: args{
name: "0BSD", name: "0BSD",
values: &licenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"},
origin: ` origin: `
Copyright (C) YEAR by AUTHOR EMAIL Copyright (C) YEAR by AUTHOR EMAIL

View File

@ -256,11 +256,11 @@ func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error {
// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) { func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
repoPath := repo.RepoPath() repoPath := repo.RepoPath()
if err := createDelegateHooks(repoPath); err != nil { if err := CreateDelegateHooks(repoPath); err != nil {
return repo, fmt.Errorf("createDelegateHooks: %w", err) return repo, fmt.Errorf("createDelegateHooks: %w", err)
} }
if repo.HasWiki() { if repo.HasWiki() {
if err := createDelegateHooks(repo.WikiPath()); err != nil { if err := CreateDelegateHooks(repo.WikiPath()); err != nil {
return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err) return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
} }
} }

View File

@ -13,10 +13,11 @@ import (
// Actions settings // Actions settings
var ( var (
Actions = struct { Actions = struct {
LogStorage *Storage // how the created logs should be stored LogStorage *Storage // how the created logs should be stored
ArtifactStorage *Storage // how the created artifacts should be stored ArtifactStorage *Storage // how the created artifacts should be stored
Enabled bool ArtifactRetentionDays int64 `ini:"ARTIFACT_RETENTION_DAYS"`
DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"` Enabled bool
DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"`
}{ }{
Enabled: false, Enabled: false,
DefaultActionsURL: defaultActionsURLGitHub, DefaultActionsURL: defaultActionsURLGitHub,
@ -76,5 +77,10 @@ func loadActionsFrom(rootCfg ConfigProvider) error {
Actions.ArtifactStorage, err = getStorage(rootCfg, "actions_artifacts", "", actionsSec) Actions.ArtifactStorage, err = getStorage(rootCfg, "actions_artifacts", "", actionsSec)
// default to 90 days in Github Actions
if Actions.ArtifactRetentionDays <= 0 {
Actions.ArtifactRetentionDays = 90
}
return err return err
} }

View File

@ -1764,6 +1764,7 @@ pulls.rebase_conflict_summary = Error Message
pulls.unrelated_histories = Merge Failed: The merge head and base do not share a common history. Hint: Try a different strategy pulls.unrelated_histories = Merge Failed: The merge head and base do not share a common history. Hint: Try a different strategy
pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again. pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again.
pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again. pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again.
pulls.has_merged = Failed: The pull request has been merged, you cannot merge again or change the target branch.
pulls.push_rejected = Merge Failed: The push was rejected. Review the Git Hooks for this repository. pulls.push_rejected = Merge Failed: The push was rejected. Review the Git Hooks for this repository.
pulls.push_rejected_summary = Full Rejection Message pulls.push_rejected_summary = Full Rejection Message
pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message.<br>Review the Git Hooks for this repository pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message.<br>Review the Git Hooks for this repository
@ -2730,6 +2731,7 @@ dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for w
dashboard.sync_external_users = Synchronize external user data dashboard.sync_external_users = Synchronize external user data
dashboard.cleanup_hook_task_table = Cleanup hook_task table dashboard.cleanup_hook_task_table = Cleanup hook_task table
dashboard.cleanup_packages = Cleanup expired packages dashboard.cleanup_packages = Cleanup expired packages
dashboard.cleanup_actions = Cleanup actions expired logs and artifacts
dashboard.server_uptime = Server Uptime dashboard.server_uptime = Server Uptime
dashboard.current_goroutine = Current Goroutines dashboard.current_goroutine = Current Goroutines
dashboard.current_memory_usage = Current Memory Usage dashboard.current_memory_usage = Current Memory Usage

12
package-lock.json generated
View File

@ -27,7 +27,6 @@
"escape-goat": "4.0.0", "escape-goat": "4.0.0",
"fast-glob": "3.3.1", "fast-glob": "3.3.1",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery.are-you-sure": "1.9.0",
"katex": "0.16.8", "katex": "0.16.8",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0", "lightningcss-loader": "2.1.0",
@ -6466,17 +6465,6 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
}, },
"node_modules/jquery.are-you-sure": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/jquery.are-you-sure/-/jquery.are-you-sure-1.9.0.tgz",
"integrity": "sha512-2r0uFx8CyAopjeHGOdvvwpFP921TnW1+v1uJXcAWQYHYGB1tryTDhQY+5u6HsVeMwbWiRTKVZFWnLaFpDvIqZQ==",
"dependencies": {
"jquery": ">=1.4.2"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/js-levenshtein-esm": { "node_modules/js-levenshtein-esm": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz",

View File

@ -26,7 +26,6 @@
"escape-goat": "4.0.0", "escape-goat": "4.0.0",
"fast-glob": "3.3.1", "fast-glob": "3.3.1",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery.are-you-sure": "1.9.0",
"katex": "0.16.8", "katex": "0.16.8",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0", "lightningcss-loader": "2.1.0",

View File

@ -170,8 +170,9 @@ func (ar artifactRoutes) buildArtifactURL(runID int64, artifactHash, suffix stri
} }
type getUploadArtifactRequest struct { type getUploadArtifactRequest struct {
Type string Type string
Name string Name string
RetentionDays int64
} }
type getUploadArtifactResponse struct { type getUploadArtifactResponse struct {
@ -192,10 +193,16 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
return return
} }
// set retention days
retentionQuery := ""
if req.RetentionDays > 0 {
retentionQuery = fmt.Sprintf("?retentionDays=%d", req.RetentionDays)
}
// use md5(artifact_name) to create upload url // use md5(artifact_name) to create upload url
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name))) artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
resp := getUploadArtifactResponse{ resp := getUploadArtifactResponse{
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"), FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"+retentionQuery),
} }
log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL) log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL)
ctx.JSON(http.StatusOK, resp) ctx.JSON(http.StatusOK, resp)
@ -219,8 +226,21 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
return return
} }
// get artifact retention days
expiredDays := setting.Actions.ArtifactRetentionDays
if queryRetentionDays := ctx.Req.URL.Query().Get("retentionDays"); queryRetentionDays != "" {
expiredDays, err = strconv.ParseInt(queryRetentionDays, 10, 64)
if err != nil {
log.Error("Error parse retention days: %v", err)
ctx.Error(http.StatusBadRequest, "Error parse retention days")
return
}
}
log.Debug("[artifact] upload chunk, name: %s, path: %s, size: %d, retention days: %d",
artifactName, artifactPath, fileRealTotalSize, expiredDays)
// create or get artifact with name and path // create or get artifact with name and path
artifact, err := actions.CreateArtifact(ctx, task, artifactName, artifactPath) artifact, err := actions.CreateArtifact(ctx, task, artifactName, artifactPath, expiredDays)
if err != nil { if err != nil {
log.Error("Error create or get artifact: %v", err) log.Error("Error create or get artifact: %v", err)
ctx.Error(http.StatusInternalServerError, "Error create or get artifact") ctx.Error(http.StatusInternalServerError, "Error create or get artifact")

View File

@ -179,7 +179,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
// save storage path to artifact // save storage path to artifact
log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath) log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath)
artifact.StoragePath = storagePath artifact.StoragePath = storagePath
artifact.Status = actions.ArtifactStatusUploadConfirmed artifact.Status = int64(actions.ArtifactStatusUploadConfirmed)
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
return fmt.Errorf("update artifact error: %v", err) return fmt.Errorf("update artifact error: %v", err)
} }

View File

@ -9,7 +9,6 @@ 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/context" "code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
@ -109,7 +108,7 @@ func AdoptRepository(ctx *context.APIContext) {
ctx.NotFound() ctx.NotFound()
return return
} }
if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{
Name: repoName, Name: repoName,
IsPrivate: true, IsPrivate: true,
}); err != nil { }); err != nil {

View File

@ -22,7 +22,6 @@ import (
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -31,6 +30,7 @@ import (
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
repo_service "code.gitea.io/gitea/services/repository"
) )
// Migrate migrate remote git repository to gitea // Migrate migrate remote git repository to gitea
@ -170,7 +170,7 @@ func Migrate(ctx *context.APIContext) {
opts.Releases = false opts.Releases = false
} }
repo, err := repo_module.CreateRepository(ctx.Doer, repoOwner, repo_module.CreateRepoOptions{ repo, err := repo_service.CreateRepositoryDirectly(ctx.Doer, repoOwner, repo_service.CreateRepoOptions{
Name: opts.RepoName, Name: opts.RepoName,
Description: opts.Description, Description: opts.Description,
OriginalURL: form.CloneAddr, OriginalURL: form.CloneAddr,

View File

@ -240,7 +240,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
return return
} }
repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_module.CreateRepoOptions{ repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_service.CreateRepoOptions{
Name: opt.Name, Name: opt.Name,
Description: opt.Description, Description: opt.Description,
IssueLabels: opt.IssueLabels, IssueLabels: opt.IssueLabels,

View File

@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/explore" "code.gitea.io/gitea/routers/web/explore"
@ -144,7 +143,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
if has || !isDir { if has || !isDir {
// Fallthrough to failure mode // Fallthrough to failure mode
} else if action == "adopt" { } else if action == "adopt" {
if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{
Name: dirSplit[1], Name: dirSplit[1],
IsPrivate: true, IsPrivate: true,
}); err != nil { }); err != nil {

View File

@ -152,7 +152,6 @@ func Home(ctx *context.Context) {
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "language", "Language") pager.AddParam(ctx, "language", "Language")
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0 ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
) )
const ( const (
@ -52,6 +53,12 @@ func Members(ctx *context.Context) {
return return
} }
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
pager := context.NewPagination(int(total), setting.UI.MembersPagingNum, page, 5) pager := context.NewPagination(int(total), setting.UI.MembersPagingNum, page, 5)
opts.ListOptions.Page = page opts.ListOptions.Page = page
opts.ListOptions.PageSize = setting.UI.MembersPagingNum opts.ListOptions.PageSize = setting.UI.MembersPagingNum
@ -62,7 +69,6 @@ func Members(ctx *context.Context) {
} }
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.Data["Members"] = members ctx.Data["Members"] = members
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["MembersIsPublicMember"] = membersIsPublic ctx.Data["MembersIsPublicMember"] = membersIsPublic
ctx.Data["MembersIsUserOrgOwner"] = organization.IsUserOrgOwner(members, org.ID) ctx.Data["MembersIsUserOrgOwner"] = organization.IsUserOrgOwner(members, org.ID)
ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus() ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus()

View File

@ -20,6 +20,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting" user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org" org_service "code.gitea.io/gitea/services/org"
@ -46,6 +47,13 @@ func Settings(ctx *context.Context) {
ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility
ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess
ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["ContextUser"] = ctx.ContextUser
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsOptions) ctx.HTML(http.StatusOK, tplSettingsOptions)
} }
@ -189,6 +197,12 @@ func SettingsDelete(ctx *context.Context) {
return return
} }
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsDelete) ctx.HTML(http.StatusOK, tplSettingsDelete)
} }
@ -207,6 +221,12 @@ func Webhooks(ctx *context.Context) {
return return
} }
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.Data["Webhooks"] = ws ctx.Data["Webhooks"] = ws
ctx.HTML(http.StatusOK, tplSettingsHooks) ctx.HTML(http.StatusOK, tplSettingsHooks)
} }
@ -228,5 +248,12 @@ func Labels(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsOrgSettingsLabels"] = true ctx.Data["PageIsOrgSettingsLabels"] = true
ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsLabels) ctx.HTML(http.StatusOK, tplSettingsLabels)
} }

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting" user_setting "code.gitea.io/gitea/routers/web/user/setting"
) )
@ -41,6 +42,12 @@ func Applications(ctx *context.Context) {
} }
ctx.Data["Applications"] = apps ctx.Data["Applications"] = apps
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsApplications) ctx.HTML(http.StatusOK, tplSettingsApplications)
} }

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
shared "code.gitea.io/gitea/routers/web/shared/packages" shared "code.gitea.io/gitea/routers/web/shared/packages"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
) )
const ( const (
@ -24,6 +25,12 @@ func Packages(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true ctx.Data["PageIsSettingsPackages"] = true
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
shared.SetPackagesContext(ctx, ctx.ContextUser) shared.SetPackagesContext(ctx, ctx.ContextUser)
ctx.HTML(http.StatusOK, tplSettingsPackages) ctx.HTML(http.StatusOK, tplSettingsPackages)
@ -34,6 +41,12 @@ func PackagesRuleAdd(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true ctx.Data["PageIsSettingsPackages"] = true
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
shared.SetRuleAddContext(ctx) shared.SetRuleAddContext(ctx)
ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit) ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit)
@ -44,6 +57,12 @@ func PackagesRuleEdit(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true ctx.Data["PageIsSettingsPackages"] = true
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
shared.SetRuleEditContext(ctx, ctx.ContextUser) shared.SetRuleEditContext(ctx, ctx.ContextUser)
ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit) ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit)
@ -80,6 +99,12 @@ func PackagesRulePreview(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true ctx.Data["PageIsSettingsPackages"] = true
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
shared.SetRulePreviewContext(ctx, ctx.ContextUser) shared.SetRulePreviewContext(ctx, ctx.ContextUser)
ctx.HTML(http.StatusOK, tplSettingsPackagesRulePreview) ctx.HTML(http.StatusOK, tplSettingsPackagesRulePreview)

View File

@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/routers/utils"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org" org_service "code.gitea.io/gitea/services/org"
@ -56,7 +57,12 @@ func Teams(ctx *context.Context) {
} }
} }
ctx.Data["Teams"] = ctx.Org.Teams ctx.Data["Teams"] = ctx.Org.Teams
ctx.Data["ContextUser"] = ctx.ContextUser
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplTeams) ctx.HTML(http.StatusOK, tplTeams)
} }

View File

@ -486,8 +486,9 @@ type ArtifactsViewResponse struct {
} }
type ArtifactsViewItem struct { type ArtifactsViewItem struct {
Name string `json:"name"` Name string `json:"name"`
Size int64 `json:"size"` Size int64 `json:"size"`
Status string `json:"status"`
} }
func ArtifactsView(ctx *context_module.Context) { func ArtifactsView(ctx *context_module.Context) {
@ -510,9 +511,14 @@ func ArtifactsView(ctx *context_module.Context) {
Artifacts: make([]*ArtifactsViewItem, 0, len(artifacts)), Artifacts: make([]*ArtifactsViewItem, 0, len(artifacts)),
} }
for _, art := range artifacts { for _, art := range artifacts {
status := "completed"
if art.Status == int64(actions_model.ArtifactStatusExpired) {
status = "expired"
}
artifactsResponse.Artifacts = append(artifactsResponse.Artifacts, &ArtifactsViewItem{ artifactsResponse.Artifacts = append(artifactsResponse.Artifacts, &ArtifactsViewItem{
Name: art.ArtifactName, Name: art.ArtifactName,
Size: art.FileSize, Size: art.FileSize,
Status: status,
}) })
} }
ctx.JSON(http.StatusOK, artifactsResponse) ctx.JSON(http.StatusOK, artifactsResponse)

View File

@ -58,7 +58,6 @@ func Packages(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["IsPackagesPage"] = true ctx.Data["IsPackagesPage"] = true
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["Query"] = query ctx.Data["Query"] = query
ctx.Data["PackageType"] = packageType ctx.Data["PackageType"] = packageType
ctx.Data["AvailableTypes"] = packages.TypeList ctx.Data["AvailableTypes"] = packages.TypeList

View File

@ -275,7 +275,7 @@ func CreatePost(ctx *context.Context) {
return return
} }
} else { } else {
repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{
Name: form.RepoName, Name: form.RepoName,
Description: form.Description, Description: form.Description,
Gitignores: form.Gitignores, Gitignores: form.Gitignores,
@ -378,10 +378,6 @@ func RedirectDownload(ctx *context.Context) {
curRepo := ctx.Repo.Repository curRepo := ctx.Repo.Repository
releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames) releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
ctx.Error(http.StatusNotFound)
return
}
ctx.ServerError("RedirectDownload", err) ctx.ServerError("RedirectDownload", err)
return return
} }
@ -396,6 +392,23 @@ func RedirectDownload(ctx *context.Context) {
ServeAttachment(ctx, att.UUID) ServeAttachment(ctx, att.UUID)
return return
} }
} else if len(releases) == 0 && vTag == "latest" {
// GitHub supports the alias "latest" for the latest release
// We only fetch the latest release if the tag is "latest" and no release with the tag "latest" exists
release, err := repo_model.GetLatestReleaseByRepoID(ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusNotFound)
return
}
att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName)
if err != nil {
ctx.Error(http.StatusNotFound)
return
}
if att != nil {
ServeAttachment(ctx, att.UUID)
return
}
} }
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
} }

View File

@ -22,7 +22,6 @@ import (
func prepareContextForCommonProfile(ctx *context.Context) { func prepareContextForCommonProfile(ctx *context.Context) {
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["EnableFeed"] = setting.Other.EnableFeed ctx.Data["EnableFeed"] = setting.Other.EnableFeed
ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink()
} }

View File

@ -30,7 +30,6 @@ func CodeSearch(ctx *context.Context) {
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore.code") ctx.Data["Title"] = ctx.Tr("explore.code")
ctx.Data["ContextUser"] = ctx.ContextUser
language := ctx.FormTrim("l") language := ctx.FormTrim("l")
keyword := ctx.FormTrim("q") keyword := ctx.FormTrim("q")

View File

@ -390,6 +390,12 @@ func PackageSettings(ctx *context.Context) {
ctx.Data["Repos"] = repos ctx.Data["Repos"] = repos
ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin()
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplPackagesSettings) ctx.HTML(http.StatusOK, tplPackagesSettings)
} }

View File

@ -9,7 +9,6 @@ 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/context" "code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
@ -45,7 +44,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
if has || !isDir { if has || !isDir {
// Fallthrough to failure mode // Fallthrough to failure mode
} else if action == "adopt" && allowAdopt { } else if action == "adopt" && allowAdopt {
if _, err := repo_service.AdoptRepository(ctx, ctxUser, ctxUser, repo_module.CreateRepoOptions{ if _, err := repo_service.AdoptRepository(ctx, ctxUser, ctxUser, repo_service.CreateRepoOptions{
Name: dir, Name: dir,
IsPrivate: true, IsPrivate: true,
}); err != nil { }); err != nil {

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
) )
@ -25,6 +26,15 @@ type OAuth2CommonHandlers struct {
func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) { func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) {
app := ctx.Data["App"].(*auth.OAuth2Application) app := ctx.Data["App"].(*auth.OAuth2Application)
ctx.Data["FormActionPath"] = fmt.Sprintf("%s/%d", oa.BasePathEditPrefix, app.ID) ctx.Data["FormActionPath"] = fmt.Sprintf("%s/%d", oa.BasePathEditPrefix, app.ID)
if ctx.ContextUser.IsOrganization() {
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
}
ctx.HTML(http.StatusOK, oa.TplAppEdit) ctx.HTML(http.StatusOK, oa.TplAppEdit)
} }

View File

@ -0,0 +1,42 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"context"
"time"
"code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
)
// Cleanup removes expired actions logs, data and artifacts
func Cleanup(taskCtx context.Context, olderThan time.Duration) error {
// TODO: clean up expired actions logs
// clean up expired artifacts
return CleanupArtifacts(taskCtx)
}
// CleanupArtifacts removes expired artifacts and set records expired status
func CleanupArtifacts(taskCtx context.Context) error {
artifacts, err := actions.ListNeedExpiredArtifacts(taskCtx)
if err != nil {
return err
}
log.Info("Found %d expired artifacts", len(artifacts))
for _, artifact := range artifacts {
if err := storage.ActionsArtifacts.Delete(artifact.StoragePath); err != nil {
log.Error("Cannot delete artifact %d: %v", artifact.ID, err)
continue
}
if err := actions.SetArtifactExpired(taskCtx, artifact.ID); err != nil {
log.Error("Cannot set artifact %d expired: %v", artifact.ID, err)
continue
}
log.Info("Artifact %d set expired", artifact.ID)
}
return nil
}

View File

@ -27,6 +27,7 @@ func UserAssignmentWeb() func(ctx *context.Context) {
} }
} }
ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, errorFn) ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, errorFn)
ctx.Data["ContextUser"] = ctx.ContextUser
} }
} }

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror" mirror_service "code.gitea.io/gitea/services/mirror"
@ -156,6 +157,20 @@ func registerCleanupPackages() {
}) })
} }
func registerActionsCleanup() {
RegisterTaskFatal("cleanup_actions", &OlderThanConfig{
BaseConfig: BaseConfig{
Enabled: true,
RunAtStart: true,
Schedule: "@midnight",
},
OlderThan: 24 * time.Hour,
}, func(ctx context.Context, _ *user_model.User, config Config) error {
realConfig := config.(*OlderThanConfig)
return actions.Cleanup(ctx, realConfig.OlderThan)
})
}
func initBasicTasks() { func initBasicTasks() {
if setting.Mirror.Enabled { if setting.Mirror.Enabled {
registerUpdateMirrorTask() registerUpdateMirrorTask()
@ -172,4 +187,7 @@ func initBasicTasks() {
if setting.Packages.Enabled { if setting.Packages.Enabled {
registerCleanupPackages() registerCleanupPackages()
} }
if setting.Actions.Enabled {
registerActionsCleanup()
}
} }

View File

@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/uri" "code.gitea.io/gitea/modules/uri"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -99,7 +100,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
var r *repo_model.Repository var r *repo_model.Repository
if opts.MigrateToRepoID <= 0 { if opts.MigrateToRepoID <= 0 {
r, err = repo_module.CreateRepository(g.doer, owner, repo_module.CreateRepoOptions{ r, err = repo_service.CreateRepositoryDirectly(g.doer, owner, repo_service.CreateRepoOptions{
Name: g.repoName, Name: g.repoName,
Description: repo.Description, Description: repo.Description,
OriginalURL: repo.OriginalURL, OriginalURL: repo.OriginalURL,

View File

@ -19,10 +19,10 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
cargo_module "code.gitea.io/gitea/modules/packages/cargo" cargo_module "code.gitea.io/gitea/modules/packages/cargo"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "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"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
) )
@ -206,7 +206,7 @@ func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.Use
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName) repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
if err != nil { if err != nil {
if errors.Is(err, util.ErrNotExist) { if errors.Is(err, util.ErrNotExist) {
repo, err = repo_module.CreateRepository(doer, owner, repo_module.CreateRepoOptions{ repo, err = repo_service.CreateRepositoryDirectly(doer, owner, repo_service.CreateRepoOptions{
Name: IndexRepositoryName, Name: IndexRepositoryName,
}) })
if err != nil { if err != nil {

View File

@ -27,7 +27,7 @@ import (
) )
// AdoptRepository adopts pre-existing repository files for the user/organization. // AdoptRepository adopts pre-existing repository files for the user/organization.
func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
if !doer.IsAdmin && !u.CanCreateRepo() { if !doer.IsAdmin && !u.CanCreateRepo() {
return nil, repo_model.ErrReachLimitOfRepo{ return nil, repo_model.ErrReachLimitOfRepo{
Limit: u.MaxRepoCreation, Limit: u.MaxRepoCreation,

View File

@ -0,0 +1,315 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/util"
)
// CreateRepoOptions contains the create repository options
type CreateRepoOptions struct {
Name string
Description string
OriginalURL string
GitServiceType api.GitServiceType
Gitignores string
IssueLabels string
License string
Readme string
DefaultBranch string
IsPrivate bool
IsMirror bool
IsTemplate bool
AutoInit bool
Status repo_model.RepositoryStatus
TrustModel repo_model.TrustModelType
MirrorInterval string
}
func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error {
commitTimeStr := time.Now().Format(time.RFC3339)
authorSig := repo.Owner.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+authorSig.Name,
"GIT_COMMITTER_EMAIL="+authorSig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Clone to temporary path and do the init commit.
if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir).
SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)).
RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil {
log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
return fmt.Errorf("git clone: %w", err)
}
// README
data, err := options.Readme(opts.Readme)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
}
cloneLink := repo.CloneLink()
match := map[string]string{
"Name": repo.Name,
"Description": repo.Description,
"CloneURL.SSH": cloneLink.SSH,
"CloneURL.HTTPS": cloneLink.HTTPS,
"OwnerName": repo.OwnerName,
}
res, err := vars.Expand(string(data), match)
if err != nil {
// here we could just log the error and continue the rendering
log.Error("unable to expand template vars for repo README: %s, err: %v", opts.Readme, err)
}
if err = os.WriteFile(filepath.Join(tmpDir, "README.md"),
[]byte(res), 0o644); err != nil {
return fmt.Errorf("write README.md: %w", err)
}
// .gitignore
if len(opts.Gitignores) > 0 {
var buf bytes.Buffer
names := strings.Split(opts.Gitignores, ",")
for _, name := range names {
data, err = options.Gitignore(name)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
}
buf.WriteString("# ---> " + name + "\n")
buf.Write(data)
buf.WriteString("\n")
}
if buf.Len() > 0 {
if err = os.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0o644); err != nil {
return fmt.Errorf("write .gitignore: %w", err)
}
}
}
// LICENSE
if len(opts.License) > 0 {
data, err = repo_module.GetLicense(opts.License, &repo_module.LicenseValues{
Owner: repo.OwnerName,
Email: authorSig.Email,
Repo: repo.Name,
Year: time.Now().Format("2006"),
})
if err != nil {
return fmt.Errorf("getLicense[%s]: %w", opts.License, err)
}
if err = os.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0o644); err != nil {
return fmt.Errorf("write LICENSE: %w", err)
}
}
return nil
}
// InitRepository initializes README and .gitignore if needed.
func initRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) {
if err = repo_module.CheckInitRepository(ctx, repo.OwnerName, repo.Name); err != nil {
return err
}
// Initialize repository according to user's choice.
if opts.AutoInit {
tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name)
if err != nil {
return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.RepoPath(), err)
}
defer func() {
if err := util.RemoveAll(tmpDir); err != nil {
log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err)
}
}()
if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil {
return fmt.Errorf("prepareRepoCommit: %w", err)
}
// Apply changes and commit.
if err = repo_module.InitRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil {
return fmt.Errorf("initRepoCommit: %w", err)
}
}
// Re-fetch the repository from database before updating it (else it would
// override changes that were done earlier with sql)
if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil {
return fmt.Errorf("getRepositoryByID: %w", err)
}
if !opts.AutoInit {
repo.IsEmpty = true
}
repo.DefaultBranch = setting.Repository.DefaultBranch
if len(opts.DefaultBranch) > 0 {
repo.DefaultBranch = opts.DefaultBranch
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
return fmt.Errorf("openRepository: %w", err)
}
defer gitRepo.Close()
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
if !repo.IsEmpty {
if _, err := repo_module.SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
return fmt.Errorf("SyncRepoBranches: %w", err)
}
}
}
if err = UpdateRepository(ctx, repo, false); err != nil {
return fmt.Errorf("updateRepository: %w", err)
}
return nil
}
// CreateRepositoryDirectly creates a repository for the user/organization.
func CreateRepositoryDirectly(doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
if !doer.IsAdmin && !u.CanCreateRepo() {
return nil, repo_model.ErrReachLimitOfRepo{
Limit: u.MaxRepoCreation,
}
}
if len(opts.DefaultBranch) == 0 {
opts.DefaultBranch = setting.Repository.DefaultBranch
}
// Check if label template exist
if len(opts.IssueLabels) > 0 {
if _, err := repo_module.LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil {
return nil, err
}
}
repo := &repo_model.Repository{
OwnerID: u.ID,
Owner: u,
OwnerName: u.Name,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
OriginalURL: opts.OriginalURL,
OriginalServiceType: opts.GitServiceType,
IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror,
IsTemplate: opts.IsTemplate,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
TrustModel: opts.TrustModel,
IsMirror: opts.IsMirror,
DefaultBranch: opts.DefaultBranch,
}
var rollbackRepo *repo_model.Repository
if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error {
if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil {
return err
}
// No need for init mirror.
if opts.IsMirror {
return nil
}
repoPath := repo_model.RepoPath(u.Name, repo.Name)
isExist, err := util.IsExist(repoPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
return err
}
if isExist {
// repo already exists - We have two or three options.
// 1. We fail stating that the directory exists
// 2. We create the db repository to go with this data and adopt the git repo
// 3. We delete it and start afresh
//
// Previously Gitea would just delete and start afresh - this was naughty.
// So we will now fail and delegate to other functionality to adopt or delete
log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
return repo_model.ErrRepoFilesAlreadyExist{
Uname: u.Name,
Name: repo.Name,
}
}
if err = initRepository(ctx, repoPath, doer, repo, opts); err != nil {
if err2 := util.RemoveAll(repoPath); err2 != nil {
log.Error("initRepository: %v", err)
return fmt.Errorf(
"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2)
}
return fmt.Errorf("initRepository: %w", err)
}
// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("InitializeLabels: %w", err)
}
}
if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil {
return fmt.Errorf("checkDaemonExportOK: %w", err)
}
if stdout, _, err := git.NewCommand(ctx, "update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
}
return nil
}); err != nil {
if rollbackRepo != nil {
if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil {
log.Error("Rollback deleteRepository: %v", errDelete)
}
}
return nil, err
}
return repo, nil
}

View File

@ -0,0 +1,148 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"fmt"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestIncludesAllRepositoriesTeams(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
testTeamRepositories := func(teamID int64, repoIds []int64) {
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name)
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name)
for i, rid := range repoIds {
if rid > 0 {
assert.True(t, models.HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i)
}
}
}
// Get an admin user.
user, err := user_model.GetUserByID(db.DefaultContext, 1)
assert.NoError(t, err, "GetUserByID")
// Create org.
org := &organization.Organization{
Name: "All_repo",
IsActive: true,
Type: user_model.UserTypeOrganization,
Visibility: structs.VisibleTypePublic,
}
assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization")
// Check Owner team.
ownerTeam, err := org.GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err, "GetOwnerTeam")
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
// Create repos.
repoIds := make([]int64, 0)
for i := 0; i < 3; i++ {
r, err := CreateRepositoryDirectly(user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
assert.NoError(t, err, "CreateRepository %d", i)
if r != nil {
repoIds = append(repoIds, r.ID)
}
}
// Get fresh copy of Owner team after creating repos.
ownerTeam, err = org.GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err, "GetOwnerTeam")
// Create teams and check repositories.
teams := []*organization.Team{
ownerTeam,
{
OrgID: org.ID,
Name: "team one",
AccessMode: perm.AccessModeRead,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 2",
AccessMode: perm.AccessModeRead,
IncludesAllRepositories: false,
},
{
OrgID: org.ID,
Name: "team three",
AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 4",
AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: false,
},
}
teamRepos := [][]int64{
repoIds,
repoIds,
{},
repoIds,
{},
}
for i, team := range teams {
if i > 0 { // first team is Owner.
assert.NoError(t, models.NewTeam(team), "%s: NewTeam", team.Name)
}
testTeamRepositories(team.ID, teamRepos[i])
}
// Update teams and check repositories.
teams[3].IncludesAllRepositories = false
teams[4].IncludesAllRepositories = true
teamRepos[4] = repoIds
for i, team := range teams {
assert.NoError(t, models.UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name)
testTeamRepositories(team.ID, teamRepos[i])
}
// Create repo and check teams repositories.
r, err := CreateRepositoryDirectly(user, org.AsUser(), CreateRepoOptions{Name: "repo-last"})
assert.NoError(t, err, "CreateRepository last")
if r != nil {
repoIds = append(repoIds, r.ID)
}
teamRepos[0] = repoIds
teamRepos[1] = repoIds
teamRepos[4] = repoIds
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Remove repo and check teams repositories.
assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository")
teamRepos[0] = repoIds[1:]
teamRepos[1] = repoIds[1:]
teamRepos[3] = repoIds[1:3]
teamRepos[4] = repoIds[1:]
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Wipe created items.
for i, rid := range repoIds {
if i > 0 { // first repo already deleted.
assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i)
}
}
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization")
}

View File

@ -40,8 +40,8 @@ type WebSearchResults struct {
} }
// CreateRepository creates a repository for the user/organization. // CreateRepository creates a repository for the user/organization.
func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
repo, err := repo_module.CreateRepository(doer, owner, opts) repo, err := CreateRepositoryDirectly(doer, owner, opts)
if err != nil { if err != nil {
// No need to rollback here we should do this in CreateRepository... // No need to rollback here we should do this in CreateRepository...
return nil, err return nil, err
@ -84,7 +84,7 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN
} }
} }
repo, err := CreateRepository(ctx, authUser, owner, repo_module.CreateRepoOptions{ repo, err := CreateRepository(ctx, authUser, owner, CreateRepoOptions{
Name: repoName, Name: repoName,
IsPrivate: setting.Repository.DefaultPushCreatePrivate, IsPrivate: setting.Repository.DefaultPushCreatePrivate,
}) })

View File

@ -14,12 +14,12 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
) )
// taskQueue is a global queue of tasks // taskQueue is a global queue of tasks
@ -100,7 +100,7 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*adm
return nil, err return nil, err
} }
repo, err := repo_module.CreateRepository(doer, u, repo_module.CreateRepoOptions{ repo, err := repo_service.CreateRepositoryDirectly(doer, u, repo_service.CreateRepoOptions{
Name: opts.RepoName, Name: opts.RepoName,
Description: opts.Description, Description: opts.Description,
OriginalURL: opts.OriginalURL, OriginalURL: opts.OriginalURL,

View File

@ -1,33 +1,13 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin settings new webhook")}} {{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin settings new webhook")}}
<div class="admin-setting-content"> <div class="admin-setting-content">
<h4 class="ui top attached header"> {{$CustomHeaderTitle := .locale.Tr "admin.defaulthooks.update_webhook"}}
{{if .PageIsAdminDefaultHooksNew}} {{if .PageIsAdminDefaultHooksNew}}
{{.locale.Tr "admin.defaulthooks.add_webhook"}} {{$CustomHeaderTitle = .locale.Tr "admin.defaulthooks.add_webhook"}}
{{else if .PageIsAdminSystemHooksNew}} {{else if .PageIsAdminSystemHooksNew}}
{{.locale.Tr "admin.systemhooks.add_webhook"}} {{$CustomHeaderTitle = .locale.Tr "admin.systemhooks.add_webhook"}}
{{else if .Webhook.IsSystemWebhook}} {{else if .Webhook.IsSystemWebhook}}
{{.locale.Tr "admin.systemhooks.update_webhook"}} {{$CustomHeaderTitle = .locale.Tr "admin.systemhooks.update_webhook"}}
{{else}} {{end}}
{{.locale.Tr "admin.defaulthooks.update_webhook"}} {{template "webhook/new" (dict "ctxData" . "CustomHeaderTitle" $CustomHeaderTitle)}}
{{end}}
<div class="ui right">
{{template "shared/webhook/icon" .}}
</div>
</h4>
<div class="ui attached segment">
{{template "repo/settings/webhook/gitea" .}}
{{template "repo/settings/webhook/gogs" .}}
{{template "repo/settings/webhook/slack" .}}
{{template "repo/settings/webhook/discord" .}}
{{template "repo/settings/webhook/dingtalk" .}}
{{template "repo/settings/webhook/telegram" .}}
{{template "repo/settings/webhook/msteams" .}}
{{template "repo/settings/webhook/feishu" .}}
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
{{template "repo/settings/webhook/packagist" .}}
</div>
{{template "repo/settings/webhook/history" .}}
</div> </div>
{{template "admin/layout_footer" .}} {{template "admin/layout_footer" .}}

View File

@ -1,5 +1,7 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings new webhook")}} {{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings new webhook")}}
<div class="org-setting-content"> <div class="org-setting-content">
{{template "webhook/new" .}} {{$CustomHeaderTitle := .locale.Tr "repo.settings.update_webhook"}}
{{if .PageIsSettingsHooksNew}}{{$CustomHeaderTitle = .locale.Tr "repo.settings.add_webhook"}}{{end}}
{{template "webhook/new" (dict "ctxData" . "CustomHeaderTitle" $CustomHeaderTitle)}}
</div> </div>
{{template "org/settings/layout_footer" .}} {{template "org/settings/layout_footer" .}}

View File

@ -1,5 +1,7 @@
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings new webhook")}} {{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings new webhook")}}
<div class="repo-setting-content"> <div class="repo-setting-content">
{{template "webhook/new" .}} {{$CustomHeaderTitle := .locale.Tr "repo.settings.update_webhook"}}
{{if .PageIsSettingsHooksNew}}{{$CustomHeaderTitle = .locale.Tr "repo.settings.add_webhook"}}{{end}}
{{template "webhook/new" (dict "ctxData" . "CustomHeaderTitle" $CustomHeaderTitle)}}
</div> </div>
{{template "repo/settings/layout_footer" .}} {{template "repo/settings/layout_footer" .}}

View File

@ -63,7 +63,7 @@
id="secret-name" id="secret-name"
name="name" name="name"
value="{{.name}}" value="{{.name}}"
pattern="^[a-zA-Z_][a-zA-Z0-9_]*$" pattern="^(?!GITEA_|GITHUB_)[a-zA-Z_][a-zA-Z0-9_]*$"
placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}" placeholder="{{.locale.Tr "secrets.creation.name_placeholder"}}"
> >
</div> </div>

View File

@ -1,5 +1,7 @@
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings new webhook")}} {{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings new webhook")}}
<div class="user-setting-content"> <div class="user-setting-content">
{{template "webhook/new" .}} {{$CustomHeaderTitle := .locale.Tr "repo.settings.update_webhook"}}
{{if .PageIsSettingsHooksNew}}{{$CustomHeaderTitle = .locale.Tr "repo.settings.add_webhook"}}{{end}}
{{template "webhook/new" (dict "ctxData" . "CustomHeaderTitle" $CustomHeaderTitle)}}
</div> </div>
{{template "user/settings/layout_footer" .}} {{template "user/settings/layout_footer" .}}

View File

@ -1,20 +1,20 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{if .PageIsSettingsHooksNew}}{{.locale.Tr "repo.settings.add_webhook"}}{{else}}{{.locale.Tr "repo.settings.update_webhook"}}{{end}} {{.CustomHeaderTitle}}
<div class="ui right"> <div class="ui right">
{{template "shared/webhook/icon" .}} {{template "shared/webhook/icon" .ctxData}}
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
{{template "repo/settings/webhook/gitea" .}} {{template "repo/settings/webhook/gitea" .ctxData}}
{{template "repo/settings/webhook/gogs" .}} {{template "repo/settings/webhook/gogs" .ctxData}}
{{template "repo/settings/webhook/slack" .}} {{template "repo/settings/webhook/slack" .ctxData}}
{{template "repo/settings/webhook/discord" .}} {{template "repo/settings/webhook/discord" .ctxData}}
{{template "repo/settings/webhook/dingtalk" .}} {{template "repo/settings/webhook/dingtalk" .ctxData}}
{{template "repo/settings/webhook/telegram" .}} {{template "repo/settings/webhook/telegram" .ctxData}}
{{template "repo/settings/webhook/msteams" .}} {{template "repo/settings/webhook/msteams" .ctxData}}
{{template "repo/settings/webhook/feishu" .}} {{template "repo/settings/webhook/feishu" .ctxData}}
{{template "repo/settings/webhook/matrix" .}} {{template "repo/settings/webhook/matrix" .ctxData}}
{{template "repo/settings/webhook/wechatwork" .}} {{template "repo/settings/webhook/wechatwork" .ctxData}}
{{template "repo/settings/webhook/packagist" .}} {{template "repo/settings/webhook/packagist" .ctxData}}
</div> </div>
{{template "repo/settings/webhook/history" .}} {{template "repo/settings/webhook/history" .ctxData}}

View File

@ -18,7 +18,6 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
actions_module "code.gitea.io/gitea/modules/actions" actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
@ -32,7 +31,7 @@ func TestPullRequestTargetEvent(t *testing.T) {
user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the forked repo user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the forked repo
// create the base repo // create the base repo
baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_module.CreateRepoOptions{ baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
Name: "repo-pull-request-target", Name: "repo-pull-request-target",
Description: "test pull-request-target event", Description: "test pull-request-target event",
AutoInit: true, AutoInit: true,

View File

@ -18,8 +18,9 @@ type uploadArtifactResponse struct {
} }
type getUploadArtifactRequest struct { type getUploadArtifactRequest struct {
Type string Type string
Name string Name string
RetentionDays int64
} }
func TestActionsArtifactUploadSingleFile(t *testing.T) { func TestActionsArtifactUploadSingleFile(t *testing.T) {
@ -252,3 +253,40 @@ func TestActionsArtifactDownloadMultiFiles(t *testing.T) {
assert.Equal(t, resp.Body.String(), body) assert.Equal(t, resp.Body.String(), body)
} }
} }
func TestActionsArtifactUploadWithRetentionDays(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// acquire artifact upload url
req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{
Type: "actions_storage",
Name: "artifact-retention-days",
RetentionDays: 9,
})
req = addTokenAuthHeader(req, "Bearer 8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
resp := MakeRequest(t, req, http.StatusOK)
var uploadResp uploadArtifactResponse
DecodeJSON(t, resp, &uploadResp)
assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
assert.Contains(t, uploadResp.FileContainerResourceURL, "?retentionDays=9")
// get upload url
idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
url := uploadResp.FileContainerResourceURL[idx:] + "&itemPath=artifact-retention-days/abc.txt"
// upload artifact chunk
body := strings.Repeat("A", 1024)
req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body))
req = addTokenAuthHeader(req, "Bearer 8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
req.Header.Add("Content-Range", "bytes 0-1023/1024")
req.Header.Add("x-tfs-filelength", "1024")
req.Header.Add("x-actions-results-md5", "1HsSe8LeLWh93ILaw1TEFQ==") // base64(md5(body))
MakeRequest(t, req, http.StatusOK)
t.Logf("Create artifact confirm")
// confirm artifact upload
req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-retention-days")
req = addTokenAuthHeader(req, "Bearer 8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
MakeRequest(t, req, http.StatusOK)
}

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
mirror_service "code.gitea.io/gitea/services/mirror" mirror_service "code.gitea.io/gitea/services/mirror"
release_service "code.gitea.io/gitea/services/release" release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -38,7 +39,7 @@ func TestMirrorPull(t *testing.T) {
Releases: false, Releases: false,
} }
mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{ mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{
Name: opts.RepoName, Name: opts.RepoName,
Description: opts.Description, Description: opts.Description,
IsPrivate: opts.Private, IsPrivate: opts.Private,

View File

@ -17,10 +17,10 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
gitea_context "code.gitea.io/gitea/modules/context" gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror" mirror_service "code.gitea.io/gitea/services/mirror"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -39,7 +39,7 @@ func testMirrorPush(t *testing.T, u *url.URL) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{ mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{
Name: "test-push-mirror", Name: "test-push-mirror",
}) })
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -25,7 +25,6 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
@ -356,7 +355,7 @@ func TestConflictChecking(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
// Create new clean repo to test conflict checking. // Create new clean repo to test conflict checking.
baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_module.CreateRepoOptions{ baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{
Name: "conflict-checking", Name: "conflict-checking",
Description: "Tempo repo", Description: "Tempo repo",
AutoInit: true, AutoInit: true,

View File

@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
@ -81,7 +80,7 @@ func TestAPIPullUpdateByRebase(t *testing.T) {
} }
func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest { func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest {
baseRepo, err := repo_service.CreateRepository(db.DefaultContext, actor, actor, repo_module.CreateRepoOptions{ baseRepo, err := repo_service.CreateRepository(db.DefaultContext, actor, actor, repo_service.CreateRepoOptions{
Name: "repo-pr-update", Name: "repo-pr-update",
Description: "repo-tmp-pr-update description", Description: "repo-tmp-pr-update description",
AutoInit: true, AutoInit: true,

View File

@ -2024,6 +2024,7 @@ a.ui.basic.label:hover {
top: 0; top: 0;
bottom: 0; bottom: 0;
display: flex; display: flex;
align-items: center;
} }
.ui.attached.header > .ui.right > .button, .ui.attached.header > .ui.right > .button,

View File

@ -2507,9 +2507,10 @@
} }
/* Scoped labels with different colors on left and right */ /* Scoped labels with different colors on left and right */
.scope-parent { .ui.label.scope-parent {
background: none !important; background: none !important;
padding: 0 !important; padding: 0 !important;
gap: 0 !important;
} }
.ui.label.scope-left { .ui.label.scope-left {

View File

@ -1,5 +1,5 @@
import $ from 'jquery'; import $ from 'jquery';
import 'jquery.are-you-sure'; import '../vendor/jquery.are-you-sure.js';
import {clippie} from 'clippie'; import {clippie} from 'clippie';
import {createDropzone} from './dropzone.js'; import {createDropzone} from './dropzone.js';
import {initCompColorPicker} from './comp/ColorPicker.js'; import {initCompColorPicker} from './comp/ColorPicker.js';

195
web_src/js/vendor/jquery.are-you-sure.js vendored Normal file
View File

@ -0,0 +1,195 @@
// Fork of the upstream module. The only changes are the addition of `const` on
// lines 93 and 161 to make it strict mode compatible.
/*!
* jQuery Plugin: Are-You-Sure (Dirty Form Detection)
* https://github.com/codedance/jquery.AreYouSure/
*
* Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Author: chris.dance@papercut.com
* Version: 1.9.0
* Date: 13th August 2014
*/
(function($) {
$.fn.areYouSure = function(options) {
var settings = $.extend(
{
'message' : 'You have unsaved changes!',
'dirtyClass' : 'dirty',
'change' : null,
'silent' : false,
'addRemoveFieldsMarksDirty' : false,
'fieldEvents' : 'change keyup propertychange input',
'fieldSelector': ":input:not(input[type=submit]):not(input[type=button])"
}, options);
var getValue = function($field) {
if ($field.hasClass('ays-ignore')
|| $field.hasClass('aysIgnore')
|| $field.attr('data-ays-ignore')
|| $field.attr('name') === undefined) {
return null;
}
if ($field.is(':disabled')) {
return 'ays-disabled';
}
var val;
var type = $field.attr('type');
if ($field.is('select')) {
type = 'select';
}
switch (type) {
case 'checkbox':
case 'radio':
val = $field.is(':checked');
break;
case 'select':
val = '';
$field.find('option').each(function(o) {
var $option = $(this);
if ($option.is(':selected')) {
val += $option.val();
}
});
break;
default:
val = $field.val();
}
return val;
};
var storeOrigValue = function($field) {
$field.data('ays-orig', getValue($field));
};
var checkForm = function(evt) {
var isFieldDirty = function($field) {
var origValue = $field.data('ays-orig');
if (undefined === origValue) {
return false;
}
return (getValue($field) != origValue);
};
var $form = ($(this).is('form'))
? $(this)
: $(this).parents('form');
// Test on the target first as it's the most likely to be dirty
if (isFieldDirty($(evt.target))) {
setDirtyStatus($form, true);
return;
}
const $fields = $form.find(settings.fieldSelector);
if (settings.addRemoveFieldsMarksDirty) {
// Check if field count has changed
var origCount = $form.data("ays-orig-field-count");
if (origCount != $fields.length) {
setDirtyStatus($form, true);
return;
}
}
// Brute force - check each field
var isDirty = false;
$fields.each(function() {
var $field = $(this);
if (isFieldDirty($field)) {
isDirty = true;
return false; // break
}
});
setDirtyStatus($form, isDirty);
};
var initForm = function($form) {
var fields = $form.find(settings.fieldSelector);
$(fields).each(function() { storeOrigValue($(this)); });
$(fields).unbind(settings.fieldEvents, checkForm);
$(fields).bind(settings.fieldEvents, checkForm);
$form.data("ays-orig-field-count", $(fields).length);
setDirtyStatus($form, false);
};
var setDirtyStatus = function($form, isDirty) {
var changed = isDirty != $form.hasClass(settings.dirtyClass);
$form.toggleClass(settings.dirtyClass, isDirty);
// Fire change event if required
if (changed) {
if (settings.change) settings.change.call($form, $form);
if (isDirty) $form.trigger('dirty.areYouSure', [$form]);
if (!isDirty) $form.trigger('clean.areYouSure', [$form]);
$form.trigger('change.areYouSure', [$form]);
}
};
var rescan = function() {
var $form = $(this);
var fields = $form.find(settings.fieldSelector);
$(fields).each(function() {
var $field = $(this);
if (!$field.data('ays-orig')) {
storeOrigValue($field);
$field.bind(settings.fieldEvents, checkForm);
}
});
// Check for changes while we're here
$form.trigger('checkform.areYouSure');
};
var reinitialize = function() {
initForm($(this));
}
if (!settings.silent && !window.aysUnloadSet) {
window.aysUnloadSet = true;
$(window).bind('beforeunload', function() {
const $dirtyForms = $("form").filter('.' + settings.dirtyClass);
if ($dirtyForms.length == 0) {
return;
}
// Prevent multiple prompts - seen on Chrome and IE
if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) {
if (window.aysHasPrompted) {
return;
}
window.aysHasPrompted = true;
window.setTimeout(function() {window.aysHasPrompted = false;}, 900);
}
return settings.message;
});
}
return this.each(function(elem) {
if (!$(this).is('form')) {
return;
}
var $form = $(this);
$form.submit(function() {
$form.removeClass(settings.dirtyClass);
});
$form.bind('reset', function() { setDirtyStatus($form, false); });
// Add a custom events
$form.bind('rescan.areYouSure', rescan);
$form.bind('reinitialize.areYouSure', reinitialize);
$form.bind('checkform.areYouSure', checkForm);
initForm($form);
});
};
})(jQuery);

View File

@ -206,7 +206,6 @@ export default {
}).join('\n'); }).join('\n');
}, },
override: { override: {
'jquery.are-you-sure@*': {licenseName: 'MIT'}, // https://github.com/codedance/jquery.AreYouSure/pull/147
'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33 'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33
}, },
emitError: true, emitError: true,