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_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.
- `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)
#### 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.
- `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`
- `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.
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 / {
client_max_body_size 512M;
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 X-Real-IP $remote_addr;
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
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
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
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 (
"context"
"errors"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
)
// ArtifactStatus is the status of an artifact, uploading, expired or need-delete
type ArtifactStatus int64
const (
// ArtifactStatusUploadPending is the status of an artifact upload that is pending
ArtifactStatusUploadPending = 1
// ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
ArtifactStatusUploadConfirmed = 2
// ArtifactStatusUploadError is the status of an artifact upload that is errored
ArtifactStatusUploadError = 3
ArtifactStatusUploadPending ArtifactStatus = iota + 1 // 1 ArtifactStatusUploadPending is the status of an artifact upload that is pending
ArtifactStatusUploadConfirmed // 2 ArtifactStatusUploadConfirmed is the status of an artifact upload that is confirmed
ArtifactStatusUploadError // 3 ArtifactStatusUploadError is the status of an artifact upload that is errored
ArtifactStatusExpired // 4, ArtifactStatusExpired is the status of an artifact that is expired
)
func init() {
@ -45,9 +47,10 @@ type ActionArtifact struct {
Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
CreatedUnix timeutil.TimeStamp `xorm:"created"`
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 {
return nil, err
}
@ -61,7 +64,8 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
RepoID: t.RepoID,
OwnerID: t.OwnerID,
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 {
return nil, err
@ -126,15 +130,16 @@ func ListUploadedArtifactsByRunID(ctx context.Context, runID int64) ([]*ActionAr
type ActionArtifactMeta struct {
ArtifactName string
FileSize int64
Status int64
}
// ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run
func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) {
arts := make([]*ActionArtifactMeta, 0, 10)
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").
Select("artifact_name, sum(file_size) as file_size").
Select("artifact_name, sum(file_size) as file_size, max(status) as status").
Find(&arts)
}
@ -149,3 +154,16 @@ func ListArtifactsByRunIDAndName(ctx context.Context, runID int64, name string)
arts := make([]*ActionArtifact, 0, 10)
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),
// v273 -> v274
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

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
}
}
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
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.ContextUser = owner
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["Username"] = ctx.Repo.Owner.Name
// redirect link to wiki

View File

@ -22,7 +22,6 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -156,142 +155,6 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
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
// getDirectorySize returns the disk consumption for a given path

View File

@ -4,151 +4,16 @@
package repository
import (
"fmt"
"testing"
"code.gitea.io/gitea/models"
activities_model "code.gitea.io/gitea/models/activities"
"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"
"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 := 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) {
assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -241,7 +241,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
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) {
@ -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
}

View File

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

View File

@ -4,7 +4,6 @@
package repository
import (
"bytes"
"context"
"fmt"
"os"
@ -21,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
)
@ -126,95 +124,8 @@ func LoadRepoConfig() error {
return nil
}
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 = 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) {
// 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)
sig := u.NewGitSig()
@ -277,7 +188,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
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.
repoPath := repo_model.RepoPath(owner, name)
isExist, err := util.IsExist(repoPath)
@ -295,77 +206,12 @@ func checkInitRepository(ctx context.Context, owner, name string) (err error) {
// Init git bare new repository.
if err = git.InitRepository(ctx, repoPath, true); err != nil {
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 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
func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error {
list, err := LoadTemplateLabelsByDisplayName(labelTemplate)

View File

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

View File

@ -13,7 +13,7 @@ import (
func Test_getLicense(t *testing.T) {
type args struct {
name string
values *licenseValues
values *LicenseValues
}
tests := []struct {
name string
@ -25,7 +25,7 @@ func Test_getLicense(t *testing.T) {
name: "regular",
args: args{
name: "MIT",
values: &licenseValues{Owner: "Gitea", Year: "2023"},
values: &LicenseValues{Owner: "Gitea", Year: "2023"},
},
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 {
t.Run(tt.name, func(t *testing.T) {
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)) {
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)) {
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) {
type args struct {
name string
values *licenseValues
values *LicenseValues
origin string
}
tests := []struct {
@ -73,7 +73,7 @@ func Test_fillLicensePlaceholder(t *testing.T) {
name: "owner",
args: args{
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: `
<name of author>
<owner>
@ -104,7 +104,7 @@ Gitea
name: "email",
args: args{
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: `
[EMAIL]
`,
@ -117,7 +117,7 @@ teabot@gitea.io
name: "repo",
args: args{
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: `
<program>
<one line to give the program's name and a brief idea of what it does.>
@ -132,7 +132,7 @@ gitea
name: "year",
args: args{
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: `
<year>
[YEAR]
@ -155,7 +155,7 @@ gitea
name: "0BSD",
args: args{
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: `
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.
func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
repoPath := repo.RepoPath()
if err := createDelegateHooks(repoPath); err != nil {
if err := CreateDelegateHooks(repoPath); err != nil {
return repo, fmt.Errorf("createDelegateHooks: %w", err)
}
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)
}
}

View File

@ -13,10 +13,11 @@ import (
// Actions settings
var (
Actions = struct {
LogStorage *Storage // how the created logs should be stored
ArtifactStorage *Storage // how the created artifacts should be stored
Enabled bool
DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"`
LogStorage *Storage // how the created logs should be stored
ArtifactStorage *Storage // how the created artifacts should be stored
ArtifactRetentionDays int64 `ini:"ARTIFACT_RETENTION_DAYS"`
Enabled bool
DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"`
}{
Enabled: false,
DefaultActionsURL: defaultActionsURLGitHub,
@ -76,5 +77,10 @@ func loadActionsFrom(rootCfg ConfigProvider) error {
Actions.ArtifactStorage, err = getStorage(rootCfg, "actions_artifacts", "", actionsSec)
// default to 90 days in Github Actions
if Actions.ArtifactRetentionDays <= 0 {
Actions.ArtifactRetentionDays = 90
}
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.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.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_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
@ -2730,6 +2731,7 @@ dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for w
dashboard.sync_external_users = Synchronize external user data
dashboard.cleanup_hook_task_table = Cleanup hook_task table
dashboard.cleanup_packages = Cleanup expired packages
dashboard.cleanup_actions = Cleanup actions expired logs and artifacts
dashboard.server_uptime = Server Uptime
dashboard.current_goroutine = Current Goroutines
dashboard.current_memory_usage = Current Memory Usage

12
package-lock.json generated
View File

@ -27,7 +27,6 @@
"escape-goat": "4.0.0",
"fast-glob": "3.3.1",
"jquery": "3.7.1",
"jquery.are-you-sure": "1.9.0",
"katex": "0.16.8",
"license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",
@ -6466,17 +6465,6 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"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": {
"version": "1.2.0",
"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",
"fast-glob": "3.3.1",
"jquery": "3.7.1",
"jquery.are-you-sure": "1.9.0",
"katex": "0.16.8",
"license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0",

View File

@ -170,8 +170,9 @@ func (ar artifactRoutes) buildArtifactURL(runID int64, artifactHash, suffix stri
}
type getUploadArtifactRequest struct {
Type string
Name string
Type string
Name string
RetentionDays int64
}
type getUploadArtifactResponse struct {
@ -192,10 +193,16 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
return
}
// set retention days
retentionQuery := ""
if req.RetentionDays > 0 {
retentionQuery = fmt.Sprintf("?retentionDays=%d", req.RetentionDays)
}
// use md5(artifact_name) to create upload url
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
resp := getUploadArtifactResponse{
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"),
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"+retentionQuery),
}
log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL)
ctx.JSON(http.StatusOK, resp)
@ -219,8 +226,21 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
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
artifact, err := actions.CreateArtifact(ctx, task, artifactName, artifactPath)
artifact, err := actions.CreateArtifact(ctx, task, artifactName, artifactPath, expiredDays)
if err != nil {
log.Error("Error create or get artifact: %v", err)
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
log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath)
artifact.StoragePath = storagePath
artifact.Status = actions.ArtifactStatusUploadConfirmed
artifact.Status = int64(actions.ArtifactStatusUploadConfirmed)
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
return fmt.Errorf("update artifact error: %v", err)
}

View File

@ -9,7 +9,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository"
@ -109,7 +108,7 @@ func AdoptRepository(ctx *context.APIContext) {
ctx.NotFound()
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,
IsPrivate: true,
}); err != nil {

View File

@ -22,7 +22,6 @@ import (
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
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/util"
@ -31,6 +30,7 @@ import (
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/migrations"
notify_service "code.gitea.io/gitea/services/notify"
repo_service "code.gitea.io/gitea/services/repository"
)
// Migrate migrate remote git repository to gitea
@ -170,7 +170,7 @@ func Migrate(ctx *context.APIContext) {
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,
Description: opts.Description,
OriginalURL: form.CloneAddr,

View File

@ -240,7 +240,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
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,
Description: opt.Description,
IssueLabels: opt.IssueLabels,

View File

@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"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/util"
"code.gitea.io/gitea/routers/web/explore"
@ -144,7 +143,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
if has || !isDir {
// Fallthrough to failure mode
} 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],
IsPrivate: true,
}); err != nil {

View File

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

View File

@ -20,6 +20,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"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"
"code.gitea.io/gitea/services/forms"
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["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess
ctx.Data["ContextUser"] = ctx.ContextUser
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsOptions)
}
@ -189,6 +197,12 @@ func SettingsDelete(ctx *context.Context) {
return
}
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsDelete)
}
@ -207,6 +221,12 @@ func Webhooks(ctx *context.Context) {
return
}
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.Data["Webhooks"] = ws
ctx.HTML(http.StatusOK, tplSettingsHooks)
}
@ -228,5 +248,12 @@ func Labels(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsOrgSettingsLabels"] = true
ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsLabels)
}

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"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"
)
@ -41,6 +42,12 @@ func Applications(ctx *context.Context) {
}
ctx.Data["Applications"] = apps
err = shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplSettingsApplications)
}

View File

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

View File

@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"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/forms"
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["ContextUser"] = ctx.ContextUser
err := shared_user.LoadHeaderCount(ctx)
if err != nil {
ctx.ServerError("LoadHeaderCount", err)
return
}
ctx.HTML(http.StatusOK, tplTeams)
}

View File

@ -486,8 +486,9 @@ type ArtifactsViewResponse struct {
}
type ArtifactsViewItem struct {
Name string `json:"name"`
Size int64 `json:"size"`
Name string `json:"name"`
Size int64 `json:"size"`
Status string `json:"status"`
}
func ArtifactsView(ctx *context_module.Context) {
@ -510,9 +511,14 @@ func ArtifactsView(ctx *context_module.Context) {
Artifacts: make([]*ArtifactsViewItem, 0, len(artifacts)),
}
for _, art := range artifacts {
status := "completed"
if art.Status == int64(actions_model.ArtifactStatusExpired) {
status = "expired"
}
artifactsResponse.Artifacts = append(artifactsResponse.Artifacts, &ArtifactsViewItem{
Name: art.ArtifactName,
Size: art.FileSize,
Name: art.ArtifactName,
Size: art.FileSize,
Status: status,
})
}
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["IsPackagesPage"] = true
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["Query"] = query
ctx.Data["PackageType"] = packageType
ctx.Data["AvailableTypes"] = packages.TypeList

View File

@ -275,7 +275,7 @@ func CreatePost(ctx *context.Context) {
return
}
} 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,
Description: form.Description,
Gitignores: form.Gitignores,
@ -378,10 +378,6 @@ func RedirectDownload(ctx *context.Context) {
curRepo := ctx.Repo.Repository
releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
ctx.Error(http.StatusNotFound)
return
}
ctx.ServerError("RedirectDownload", err)
return
}
@ -396,6 +392,23 @@ func RedirectDownload(ctx *context.Context) {
ServeAttachment(ctx, att.UUID)
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)
}

View File

@ -22,7 +22,6 @@ import (
func prepareContextForCommonProfile(ctx *context.Context) {
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["EnableFeed"] = setting.Other.EnableFeed
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["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore.code")
ctx.Data["ContextUser"] = ctx.ContextUser
language := ctx.FormTrim("l")
keyword := ctx.FormTrim("q")

View File

@ -390,6 +390,12 @@ func PackageSettings(ctx *context.Context) {
ctx.Data["Repos"] = repos
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)
}

View File

@ -9,7 +9,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"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/util"
repo_service "code.gitea.io/gitea/services/repository"
@ -45,7 +44,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
if has || !isDir {
// Fallthrough to failure mode
} 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,
IsPrivate: true,
}); err != nil {

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/forms"
)
@ -25,6 +26,15 @@ type OAuth2CommonHandlers struct {
func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) {
app := ctx.Data["App"].(*auth.OAuth2Application)
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)
}

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.Data["ContextUser"] = ctx.ContextUser
}
}

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/migrations"
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() {
if setting.Mirror.Enabled {
registerUpdateMirrorTask()
@ -172,4 +187,7 @@ func initBasicTasks() {
if setting.Packages.Enabled {
registerCleanupPackages()
}
if setting.Actions.Enabled {
registerActionsCleanup()
}
}

View File

@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/uri"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/google/uuid"
)
@ -99,7 +100,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
var r *repo_model.Repository
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,
Description: repo.Description,
OriginalURL: repo.OriginalURL,

View File

@ -19,10 +19,10 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
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/structs"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
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)
if err != nil {
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,
})
if err != nil {

View File

@ -27,7 +27,7 @@ import (
)
// 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() {
return nil, repo_model.ErrReachLimitOfRepo{
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.
func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) {
repo, err := repo_module.CreateRepository(doer, owner, opts)
func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) {
repo, err := CreateRepositoryDirectly(doer, owner, opts)
if err != nil {
// No need to rollback here we should do this in CreateRepository...
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,
IsPrivate: setting.Repository.DefaultPushCreatePrivate,
})

View File

@ -14,12 +14,12 @@ import (
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
"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/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
)
// 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
}
repo, err := repo_module.CreateRepository(doer, u, repo_module.CreateRepoOptions{
repo, err := repo_service.CreateRepositoryDirectly(doer, u, repo_service.CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: opts.OriginalURL,

View File

@ -1,33 +1,13 @@
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin settings new webhook")}}
<div class="admin-setting-content">
<h4 class="ui top attached header">
{{if .PageIsAdminDefaultHooksNew}}
{{.locale.Tr "admin.defaulthooks.add_webhook"}}
{{else if .PageIsAdminSystemHooksNew}}
{{.locale.Tr "admin.systemhooks.add_webhook"}}
{{else if .Webhook.IsSystemWebhook}}
{{.locale.Tr "admin.systemhooks.update_webhook"}}
{{else}}
{{.locale.Tr "admin.defaulthooks.update_webhook"}}
{{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" .}}
{{$CustomHeaderTitle := .locale.Tr "admin.defaulthooks.update_webhook"}}
{{if .PageIsAdminDefaultHooksNew}}
{{$CustomHeaderTitle = .locale.Tr "admin.defaulthooks.add_webhook"}}
{{else if .PageIsAdminSystemHooksNew}}
{{$CustomHeaderTitle = .locale.Tr "admin.systemhooks.add_webhook"}}
{{else if .Webhook.IsSystemWebhook}}
{{$CustomHeaderTitle = .locale.Tr "admin.systemhooks.update_webhook"}}
{{end}}
{{template "webhook/new" (dict "ctxData" . "CustomHeaderTitle" $CustomHeaderTitle)}}
</div>
{{template "admin/layout_footer" .}}

View File

@ -1,5 +1,7 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings new webhook")}}
<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>
{{template "org/settings/layout_footer" .}}

View File

@ -1,5 +1,7 @@
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings new webhook")}}
<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>
{{template "repo/settings/layout_footer" .}}

View File

@ -63,7 +63,7 @@
id="secret-name"
name="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"}}"
>
</div>

View File

@ -1,5 +1,7 @@
{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings new webhook")}}
<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>
{{template "user/settings/layout_footer" .}}

View File

@ -1,20 +1,20 @@
<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">
{{template "shared/webhook/icon" .}}
{{template "shared/webhook/icon" .ctxData}}
</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" .}}
{{template "repo/settings/webhook/gitea" .ctxData}}
{{template "repo/settings/webhook/gogs" .ctxData}}
{{template "repo/settings/webhook/slack" .ctxData}}
{{template "repo/settings/webhook/discord" .ctxData}}
{{template "repo/settings/webhook/dingtalk" .ctxData}}
{{template "repo/settings/webhook/telegram" .ctxData}}
{{template "repo/settings/webhook/msteams" .ctxData}}
{{template "repo/settings/webhook/feishu" .ctxData}}
{{template "repo/settings/webhook/matrix" .ctxData}}
{{template "repo/settings/webhook/wechatwork" .ctxData}}
{{template "repo/settings/webhook/packagist" .ctxData}}
</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"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
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
// 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",
Description: "test pull-request-target event",
AutoInit: true,

View File

@ -18,8 +18,9 @@ type uploadArtifactResponse struct {
}
type getUploadArtifactRequest struct {
Type string
Name string
Type string
Name string
RetentionDays int64
}
func TestActionsArtifactUploadSingleFile(t *testing.T) {
@ -252,3 +253,40 @@ func TestActionsArtifactDownloadMultiFiles(t *testing.T) {
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"
mirror_service "code.gitea.io/gitea/services/mirror"
release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
@ -38,7 +39,7 @@ func TestMirrorPull(t *testing.T) {
Releases: false,
}
mirrorRepo, err := repository.CreateRepository(user, user, repository.CreateRepoOptions{
mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
IsPrivate: opts.Private,

View File

@ -17,10 +17,10 @@ import (
user_model "code.gitea.io/gitea/models/user"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests"
"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})
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",
})
assert.NoError(t, err)

View File

@ -25,7 +25,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
@ -356,7 +355,7 @@ func TestConflictChecking(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
// 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",
Description: "Tempo repo",
AutoInit: true,

View File

@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
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 {
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",
Description: "repo-tmp-pr-update description",
AutoInit: true,

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import $ from 'jquery';
import 'jquery.are-you-sure';
import '../vendor/jquery.are-you-sure.js';
import {clippie} from 'clippie';
import {createDropzone} from './dropzone.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');
},
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
},
emitError: true,