Move some functions into services/repository (#17660)
This commit is contained in:
parent
447428f446
commit
48ccd325a1
19 changed files with 114 additions and 136 deletions
|
@ -1,94 +0,0 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
|
||||
"github.com/gogs/cron"
|
||||
)
|
||||
|
||||
var c = cron.New()
|
||||
|
||||
// Prevent duplicate running tasks.
|
||||
var taskStatusTable = sync.NewStatusTable()
|
||||
|
||||
// NewContext begins cron tasks
|
||||
// Each cron task is run within the shutdown context as a running server
|
||||
// AtShutdown the cron server is stopped
|
||||
func NewContext() {
|
||||
initBasicTasks()
|
||||
initExtendedTasks()
|
||||
|
||||
lock.Lock()
|
||||
for _, task := range tasks {
|
||||
if task.IsEnabled() && task.DoRunAtStart() {
|
||||
go task.Run()
|
||||
}
|
||||
}
|
||||
|
||||
c.Start()
|
||||
started = true
|
||||
lock.Unlock()
|
||||
graceful.GetManager().RunAtShutdown(context.Background(), func() {
|
||||
c.Stop()
|
||||
lock.Lock()
|
||||
started = false
|
||||
lock.Unlock()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// TaskTableRow represents a task row in the tasks table
|
||||
type TaskTableRow struct {
|
||||
Name string
|
||||
Spec string
|
||||
Next time.Time
|
||||
Prev time.Time
|
||||
ExecTimes int64
|
||||
}
|
||||
|
||||
// TaskTable represents a table of tasks
|
||||
type TaskTable []*TaskTableRow
|
||||
|
||||
// ListTasks returns all running cron tasks.
|
||||
func ListTasks() TaskTable {
|
||||
entries := c.Entries()
|
||||
eMap := map[string]*cron.Entry{}
|
||||
for _, e := range entries {
|
||||
eMap[e.Description] = e
|
||||
}
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
tTable := make([]*TaskTableRow, 0, len(tasks))
|
||||
for _, task := range tasks {
|
||||
spec := "-"
|
||||
var (
|
||||
next time.Time
|
||||
prev time.Time
|
||||
)
|
||||
if e, ok := eMap[task.Name]; ok {
|
||||
spec = e.Spec
|
||||
next = e.Next
|
||||
prev = e.Prev
|
||||
}
|
||||
task.lock.Lock()
|
||||
tTable = append(tTable, &TaskTableRow{
|
||||
Name: task.Name,
|
||||
Spec: spec,
|
||||
Next: next,
|
||||
Prev: prev,
|
||||
ExecTimes: task.ExecTimes,
|
||||
})
|
||||
task.lock.Unlock()
|
||||
}
|
||||
|
||||
return tTable
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"github.com/unknwon/i18n"
|
||||
)
|
||||
|
||||
// Config represents a basic configuration interface that cron task
|
||||
type Config interface {
|
||||
IsEnabled() bool
|
||||
DoRunAtStart() bool
|
||||
GetSchedule() string
|
||||
FormatMessage(name, status string, doer *models.User, args ...interface{}) string
|
||||
DoNoticeOnSuccess() bool
|
||||
}
|
||||
|
||||
// BaseConfig represents the basic config for a Cron task
|
||||
type BaseConfig struct {
|
||||
Enabled bool
|
||||
RunAtStart bool
|
||||
Schedule string
|
||||
NoSuccessNotice bool
|
||||
}
|
||||
|
||||
// OlderThanConfig represents a cron task with OlderThan setting
|
||||
type OlderThanConfig struct {
|
||||
BaseConfig
|
||||
OlderThan time.Duration
|
||||
}
|
||||
|
||||
// UpdateExistingConfig represents a cron task with UpdateExisting setting
|
||||
type UpdateExistingConfig struct {
|
||||
BaseConfig
|
||||
UpdateExisting bool
|
||||
}
|
||||
|
||||
// CleanupHookTaskConfig represents a cron task with settings to cleanup hook_task
|
||||
type CleanupHookTaskConfig struct {
|
||||
BaseConfig
|
||||
CleanupType string
|
||||
OlderThan time.Duration
|
||||
NumberToKeep int
|
||||
}
|
||||
|
||||
// GetSchedule returns the schedule for the base config
|
||||
func (b *BaseConfig) GetSchedule() string {
|
||||
return b.Schedule
|
||||
}
|
||||
|
||||
// IsEnabled returns the enabled status for the config
|
||||
func (b *BaseConfig) IsEnabled() bool {
|
||||
return b.Enabled
|
||||
}
|
||||
|
||||
// DoRunAtStart returns whether the task should be run at the start
|
||||
func (b *BaseConfig) DoRunAtStart() bool {
|
||||
return b.RunAtStart
|
||||
}
|
||||
|
||||
// DoNoticeOnSuccess returns whether a success notice should be posted
|
||||
func (b *BaseConfig) DoNoticeOnSuccess() bool {
|
||||
return !b.NoSuccessNotice
|
||||
}
|
||||
|
||||
// FormatMessage returns a message for the task
|
||||
func (b *BaseConfig) FormatMessage(name, status string, doer *models.User, args ...interface{}) string {
|
||||
realArgs := make([]interface{}, 0, len(args)+2)
|
||||
realArgs = append(realArgs, i18n.Tr("en-US", "admin.dashboard."+name))
|
||||
if doer == nil {
|
||||
realArgs = append(realArgs, "(Cron)")
|
||||
} else {
|
||||
realArgs = append(realArgs, doer.Name)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
realArgs = append(realArgs, args...)
|
||||
}
|
||||
if doer == nil || (doer.ID == -1 && doer.Name == "(Cron)") {
|
||||
return i18n.Tr("en-US", "admin.dashboard.cron."+status, realArgs...)
|
||||
}
|
||||
return i18n.Tr("en-US", "admin.dashboard.task."+status, realArgs...)
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
var lock = sync.Mutex{}
|
||||
var started = false
|
||||
var tasks = []*Task{}
|
||||
var tasksMap = map[string]*Task{}
|
||||
|
||||
// Task represents a Cron task
|
||||
type Task struct {
|
||||
lock sync.Mutex
|
||||
Name string
|
||||
config Config
|
||||
fun func(context.Context, *models.User, Config) error
|
||||
ExecTimes int64
|
||||
}
|
||||
|
||||
// DoRunAtStart returns if this task should run at the start
|
||||
func (t *Task) DoRunAtStart() bool {
|
||||
return t.config.DoRunAtStart()
|
||||
}
|
||||
|
||||
// IsEnabled returns if this task is enabled as cron task
|
||||
func (t *Task) IsEnabled() bool {
|
||||
return t.config.IsEnabled()
|
||||
}
|
||||
|
||||
// GetConfig will return a copy of the task's config
|
||||
func (t *Task) GetConfig() Config {
|
||||
if reflect.TypeOf(t.config).Kind() == reflect.Ptr {
|
||||
// Pointer:
|
||||
return reflect.New(reflect.ValueOf(t.config).Elem().Type()).Interface().(Config)
|
||||
}
|
||||
// Not pointer:
|
||||
return reflect.New(reflect.TypeOf(t.config)).Elem().Interface().(Config)
|
||||
}
|
||||
|
||||
// Run will run the task incrementing the cron counter with no user defined
|
||||
func (t *Task) Run() {
|
||||
t.RunWithUser(&models.User{
|
||||
ID: -1,
|
||||
Name: "(Cron)",
|
||||
LowerName: "(cron)",
|
||||
}, t.config)
|
||||
}
|
||||
|
||||
// RunWithUser will run the task incrementing the cron counter at the time with User
|
||||
func (t *Task) RunWithUser(doer *models.User, config Config) {
|
||||
if !taskStatusTable.StartIfNotRunning(t.Name) {
|
||||
return
|
||||
}
|
||||
t.lock.Lock()
|
||||
if config == nil {
|
||||
config = t.config
|
||||
}
|
||||
t.ExecTimes++
|
||||
t.lock.Unlock()
|
||||
defer func() {
|
||||
taskStatusTable.Stop(t.Name)
|
||||
if err := recover(); err != nil {
|
||||
// Recover a panic within the
|
||||
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
|
||||
log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr)
|
||||
}
|
||||
}()
|
||||
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
|
||||
ctx, cancel := context.WithCancel(baseCtx)
|
||||
defer cancel()
|
||||
pm := process.GetManager()
|
||||
pid := pm.Add(config.FormatMessage(t.Name, "process", doer), cancel)
|
||||
defer pm.Remove(pid)
|
||||
if err := t.fun(ctx, doer, config); err != nil {
|
||||
if db.IsErrCancelled(err) {
|
||||
message := err.(db.ErrCancelled).Message
|
||||
if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "aborted", doer, message)); err != nil {
|
||||
log.Error("CreateNotice: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "error", doer, err)); err != nil {
|
||||
log.Error("CreateNotice: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.DoNoticeOnSuccess() {
|
||||
if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "finished", doer)); err != nil {
|
||||
log.Error("CreateNotice: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// GetTask gets the named task
|
||||
func GetTask(name string) *Task {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
log.Info("Getting %s in %v", name, tasksMap[name])
|
||||
|
||||
return tasksMap[name]
|
||||
}
|
||||
|
||||
// RegisterTask allows a task to be registered with the cron service
|
||||
func RegisterTask(name string, config Config, fun func(context.Context, *models.User, Config) error) error {
|
||||
log.Debug("Registering task: %s", name)
|
||||
_, err := setting.GetCronSettings(name, config)
|
||||
if err != nil {
|
||||
log.Error("Unable to register cron task with name: %s Error: %v", name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
task := &Task{
|
||||
Name: name,
|
||||
config: config,
|
||||
fun: fun,
|
||||
}
|
||||
lock.Lock()
|
||||
locked := true
|
||||
defer func() {
|
||||
if locked {
|
||||
lock.Unlock()
|
||||
}
|
||||
}()
|
||||
if _, has := tasksMap[task.Name]; has {
|
||||
log.Error("A task with this name: %s has already been registered", name)
|
||||
return fmt.Errorf("duplicate task with name: %s", task.Name)
|
||||
}
|
||||
|
||||
if config.IsEnabled() {
|
||||
// We cannot use the entry return as there is no way to lock it
|
||||
if _, err = c.AddJob(name, config.GetSchedule(), task); err != nil {
|
||||
log.Error("Unable to register cron task with name: %s Error: %v", name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tasks = append(tasks, task)
|
||||
tasksMap[task.Name] = task
|
||||
if started && config.IsEnabled() && config.DoRunAtStart() {
|
||||
lock.Unlock()
|
||||
locked = false
|
||||
task.Run()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterTaskFatal will register a task but if there is an error log.Fatal
|
||||
func RegisterTaskFatal(name string, config Config, fun func(context.Context, *models.User, Config) error) {
|
||||
if err := RegisterTask(name, config, fun); err != nil {
|
||||
log.Fatal("Unable to register cron task %s Error: %v", name, err)
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/migrations"
|
||||
repository_service "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||
)
|
||||
|
||||
func registerUpdateMirrorTask() {
|
||||
RegisterTaskFatal("update_mirrors", &BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 10m",
|
||||
NoSuccessNotice: true,
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return mirror_service.Update(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerRepoHealthCheck() {
|
||||
type RepoHealthCheckConfig struct {
|
||||
BaseConfig
|
||||
Timeout time.Duration
|
||||
Args []string `delim:" "`
|
||||
}
|
||||
RegisterTaskFatal("repo_health_check", &RepoHealthCheckConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: false,
|
||||
Schedule: "@midnight",
|
||||
},
|
||||
Timeout: 60 * time.Second,
|
||||
Args: []string{},
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
rhcConfig := config.(*RepoHealthCheckConfig)
|
||||
return repository_service.GitFsck(ctx, rhcConfig.Timeout, rhcConfig.Args)
|
||||
})
|
||||
}
|
||||
|
||||
func registerCheckRepoStats() {
|
||||
RegisterTaskFatal("check_repo_stats", &BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: true,
|
||||
Schedule: "@midnight",
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return models.CheckRepoStats(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerArchiveCleanup() {
|
||||
RegisterTaskFatal("archive_cleanup", &OlderThanConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: true,
|
||||
Schedule: "@midnight",
|
||||
},
|
||||
OlderThan: 24 * time.Hour,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
acConfig := config.(*OlderThanConfig)
|
||||
return models.DeleteOldRepositoryArchives(ctx, acConfig.OlderThan)
|
||||
})
|
||||
}
|
||||
|
||||
func registerSyncExternalUsers() {
|
||||
RegisterTaskFatal("sync_external_users", &UpdateExistingConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: false,
|
||||
Schedule: "@midnight",
|
||||
},
|
||||
UpdateExisting: true,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
realConfig := config.(*UpdateExistingConfig)
|
||||
return auth.SyncExternalUsers(ctx, realConfig.UpdateExisting)
|
||||
})
|
||||
}
|
||||
|
||||
func registerDeletedBranchesCleanup() {
|
||||
RegisterTaskFatal("deleted_branches_cleanup", &OlderThanConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: true,
|
||||
Schedule: "@midnight",
|
||||
},
|
||||
OlderThan: 24 * time.Hour,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
realConfig := config.(*OlderThanConfig)
|
||||
models.RemoveOldDeletedBranches(ctx, realConfig.OlderThan)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func registerUpdateMigrationPosterID() {
|
||||
RegisterTaskFatal("update_migration_poster_id", &BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: true,
|
||||
Schedule: "@midnight",
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return migrations.UpdateMigrationPosterID(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerCleanupHookTaskTable() {
|
||||
RegisterTaskFatal("cleanup_hook_task_table", &CleanupHookTaskConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: false,
|
||||
Schedule: "@midnight",
|
||||
},
|
||||
CleanupType: "OlderThan",
|
||||
OlderThan: 168 * time.Hour,
|
||||
NumberToKeep: 10,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
realConfig := config.(*CleanupHookTaskConfig)
|
||||
return webhook.CleanupHookTaskTable(ctx, webhook.ToHookTaskCleanupType(realConfig.CleanupType), realConfig.OlderThan, realConfig.NumberToKeep)
|
||||
})
|
||||
}
|
||||
|
||||
func initBasicTasks() {
|
||||
registerUpdateMirrorTask()
|
||||
registerRepoHealthCheck()
|
||||
registerCheckRepoStats()
|
||||
registerArchiveCleanup()
|
||||
registerSyncExternalUsers()
|
||||
registerDeletedBranchesCleanup()
|
||||
if !setting.Repository.DisableMigrations {
|
||||
registerUpdateMigrationPosterID()
|
||||
}
|
||||
registerCleanupHookTaskTable()
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/updatechecker"
|
||||
)
|
||||
|
||||
func registerDeleteInactiveUsers() {
|
||||
RegisterTaskFatal("delete_inactive_accounts", &OlderThanConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@annually",
|
||||
},
|
||||
OlderThan: 0 * time.Second,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
olderThanConfig := config.(*OlderThanConfig)
|
||||
return models.DeleteInactiveUsers(ctx, olderThanConfig.OlderThan)
|
||||
})
|
||||
}
|
||||
|
||||
func registerDeleteRepositoryArchives() {
|
||||
RegisterTaskFatal("delete_repo_archives", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@annually",
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return repo_module.DeleteRepositoryArchives(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerGarbageCollectRepositories() {
|
||||
type RepoHealthCheckConfig struct {
|
||||
BaseConfig
|
||||
Timeout time.Duration
|
||||
Args []string `delim:" "`
|
||||
}
|
||||
RegisterTaskFatal("git_gc_repos", &RepoHealthCheckConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
},
|
||||
Timeout: time.Duration(setting.Git.Timeout.GC) * time.Second,
|
||||
Args: setting.Git.GCArgs,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
rhcConfig := config.(*RepoHealthCheckConfig)
|
||||
return repo_module.GitGcRepos(ctx, rhcConfig.Timeout, rhcConfig.Args...)
|
||||
})
|
||||
}
|
||||
|
||||
func registerRewriteAllPublicKeys() {
|
||||
RegisterTaskFatal("resync_all_sshkeys", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
}, func(_ context.Context, _ *models.User, _ Config) error {
|
||||
return models.RewriteAllPublicKeys()
|
||||
})
|
||||
}
|
||||
|
||||
func registerRewriteAllPrincipalKeys() {
|
||||
RegisterTaskFatal("resync_all_sshprincipals", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
}, func(_ context.Context, _ *models.User, _ Config) error {
|
||||
return models.RewriteAllPrincipalKeys()
|
||||
})
|
||||
}
|
||||
|
||||
func registerRepositoryUpdateHook() {
|
||||
RegisterTaskFatal("resync_all_hooks", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return repo_module.SyncRepositoryHooks(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerReinitMissingRepositories() {
|
||||
RegisterTaskFatal("reinit_missing_repos", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return repo_module.ReinitMissingRepositories(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerDeleteMissingRepositories() {
|
||||
RegisterTaskFatal("delete_missing_repos", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
}, func(ctx context.Context, user *models.User, _ Config) error {
|
||||
return repo_module.DeleteMissingRepositories(ctx, user)
|
||||
})
|
||||
}
|
||||
|
||||
func registerRemoveRandomAvatars() {
|
||||
RegisterTaskFatal("delete_generated_repository_avatars", &BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 72h",
|
||||
}, func(ctx context.Context, _ *models.User, _ Config) error {
|
||||
return models.RemoveRandomAvatars(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func registerDeleteOldActions() {
|
||||
RegisterTaskFatal("delete_old_actions", &OlderThanConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: false,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 168h",
|
||||
},
|
||||
OlderThan: 365 * 24 * time.Hour,
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
olderThanConfig := config.(*OlderThanConfig)
|
||||
return models.DeleteOldActions(olderThanConfig.OlderThan)
|
||||
})
|
||||
}
|
||||
|
||||
func registerUpdateGiteaChecker() {
|
||||
type UpdateCheckerConfig struct {
|
||||
BaseConfig
|
||||
HTTPEndpoint string
|
||||
}
|
||||
RegisterTaskFatal("update_checker", &UpdateCheckerConfig{
|
||||
BaseConfig: BaseConfig{
|
||||
Enabled: true,
|
||||
RunAtStart: false,
|
||||
Schedule: "@every 168h",
|
||||
},
|
||||
HTTPEndpoint: "https://dl.gitea.io/gitea/version.json",
|
||||
}, func(ctx context.Context, _ *models.User, config Config) error {
|
||||
updateCheckerConfig := config.(*UpdateCheckerConfig)
|
||||
return updatechecker.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint)
|
||||
})
|
||||
}
|
||||
|
||||
func initExtendedTasks() {
|
||||
registerDeleteInactiveUsers()
|
||||
registerDeleteRepositoryArchives()
|
||||
registerGarbageCollectRepositories()
|
||||
registerRewriteAllPublicKeys()
|
||||
registerRewriteAllPrincipalKeys()
|
||||
registerRepositoryUpdateHook()
|
||||
registerReinitMissingRepositories()
|
||||
registerDeleteMissingRepositories()
|
||||
registerRemoveRandomAvatars()
|
||||
registerDeleteOldActions()
|
||||
registerUpdateGiteaChecker()
|
||||
}
|
|
@ -1,295 +0,0 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// AdoptRepository adopts a repository for the user/organization.
|
||||
func AdoptRepository(doer, u *models.User, opts models.CreateRepoOptions) (*models.Repository, error) {
|
||||
if !doer.IsAdmin && !u.CanCreateRepo() {
|
||||
return nil, models.ErrReachLimitOfRepo{
|
||||
Limit: u.MaxRepoCreation,
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.DefaultBranch) == 0 {
|
||||
opts.DefaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
repo := &models.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,
|
||||
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
|
||||
Status: opts.Status,
|
||||
IsEmpty: !opts.AutoInit,
|
||||
}
|
||||
|
||||
if err := db.WithTx(func(ctx context.Context) error {
|
||||
repoPath := models.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 {
|
||||
return models.ErrRepoNotExist{
|
||||
OwnerName: u.Name,
|
||||
Name: repo.Name,
|
||||
}
|
||||
}
|
||||
|
||||
if err := models.CreateRepository(ctx, doer, u, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := adoptRepository(ctx, repoPath, doer, repo, opts); err != nil {
|
||||
return fmt.Errorf("createDelegateHooks: %v", err)
|
||||
}
|
||||
if err := repo.CheckDaemonExportOK(ctx); err != nil {
|
||||
return fmt.Errorf("checkDaemonExportOK: %v", err)
|
||||
}
|
||||
|
||||
// Initialize Issue Labels if selected
|
||||
if len(opts.IssueLabels) > 0 {
|
||||
if err := models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
|
||||
return fmt.Errorf("InitializeLabels: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if stdout, err := git.NewCommand("update-server-info").
|
||||
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
|
||||
RunInDir(repoPath); err != nil {
|
||||
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
|
||||
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// DeleteUnadoptedRepository deletes unadopted repository files from the filesystem
|
||||
func DeleteUnadoptedRepository(doer, u *models.User, repoName string) error {
|
||||
if err := models.IsUsableRepoName(repoName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoPath := models.RepoPath(u.Name, repoName)
|
||||
isExist, err := util.IsExist(repoPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return models.ErrRepoNotExist{
|
||||
OwnerName: u.Name,
|
||||
Name: repoName,
|
||||
}
|
||||
}
|
||||
|
||||
if exist, err := models.IsRepositoryExist(u, repoName); err != nil {
|
||||
return err
|
||||
} else if exist {
|
||||
return models.ErrRepoAlreadyExist{
|
||||
Uname: u.Name,
|
||||
Name: repoName,
|
||||
}
|
||||
}
|
||||
|
||||
return util.RemoveAll(repoPath)
|
||||
}
|
||||
|
||||
// ListUnadoptedRepositories lists all the unadopted repositories that match the provided query
|
||||
func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, int, error) {
|
||||
globUser, _ := glob.Compile("*")
|
||||
globRepo, _ := glob.Compile("*")
|
||||
|
||||
qsplit := strings.SplitN(query, "/", 2)
|
||||
if len(qsplit) > 0 && len(query) > 0 {
|
||||
var err error
|
||||
globUser, err = glob.Compile(qsplit[0])
|
||||
if err != nil {
|
||||
log.Info("Invalid glob expression '%s' (skipped): %v", qsplit[0], err)
|
||||
}
|
||||
if len(qsplit) > 1 {
|
||||
globRepo, err = glob.Compile(qsplit[1])
|
||||
if err != nil {
|
||||
log.Info("Invalid glob expression '%s' (skipped): %v", qsplit[1], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
start := (opts.Page - 1) * opts.PageSize
|
||||
end := start + opts.PageSize
|
||||
|
||||
repoNamesToCheck := make([]string, 0, opts.PageSize)
|
||||
|
||||
repoNames := make([]string, 0, opts.PageSize)
|
||||
var ctxUser *models.User
|
||||
|
||||
count := 0
|
||||
|
||||
// We're going to iterate by pagesize.
|
||||
root := filepath.Clean(setting.RepoRootPath)
|
||||
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() || path == root {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.ContainsRune(path[len(root)+1:], filepath.Separator) {
|
||||
// Got a new user
|
||||
|
||||
// Clean up old repoNamesToCheck
|
||||
if len(repoNamesToCheck) > 0 {
|
||||
repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{
|
||||
Actor: ctxUser,
|
||||
Private: true,
|
||||
ListOptions: db.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: opts.PageSize,
|
||||
}, LowerNames: repoNamesToCheck})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range repoNamesToCheck {
|
||||
found := false
|
||||
repoLoopCatchup:
|
||||
for i, repo := range repos {
|
||||
if repo.LowerName == name {
|
||||
found = true
|
||||
repos = append(repos[:i], repos[i+1:]...)
|
||||
break repoLoopCatchup
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if count >= start && count < end {
|
||||
repoNames = append(repoNames, fmt.Sprintf("%s/%s", ctxUser.Name, name))
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
repoNamesToCheck = repoNamesToCheck[:0]
|
||||
}
|
||||
|
||||
if !globUser.Match(info.Name()) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
ctxUser, err = models.GetUserByName(info.Name())
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
log.Debug("Missing user: %s", info.Name())
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
name := info.Name()
|
||||
|
||||
if !strings.HasSuffix(name, ".git") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
name = name[:len(name)-4]
|
||||
if models.IsUsableRepoName(name) != nil || strings.ToLower(name) != name || !globRepo.Match(name) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if count < end {
|
||||
repoNamesToCheck = append(repoNamesToCheck, name)
|
||||
if len(repoNamesToCheck) >= opts.PageSize {
|
||||
repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{
|
||||
Actor: ctxUser,
|
||||
Private: true,
|
||||
ListOptions: db.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: opts.PageSize,
|
||||
}, LowerNames: repoNamesToCheck})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range repoNamesToCheck {
|
||||
found := false
|
||||
repoLoop:
|
||||
for i, repo := range repos {
|
||||
if repo.LowerName == name {
|
||||
found = true
|
||||
repos = append(repos[:i], repos[i+1:]...)
|
||||
break repoLoop
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if count >= start && count < end {
|
||||
repoNames = append(repoNames, fmt.Sprintf("%s/%s", ctxUser.Name, name))
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
repoNamesToCheck = repoNamesToCheck[:0]
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
count++
|
||||
return filepath.SkipDir
|
||||
}); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(repoNamesToCheck) > 0 {
|
||||
repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{
|
||||
Actor: ctxUser,
|
||||
Private: true,
|
||||
ListOptions: db.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: opts.PageSize,
|
||||
}, LowerNames: repoNamesToCheck})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, name := range repoNamesToCheck {
|
||||
found := false
|
||||
repoLoop:
|
||||
for i, repo := range repos {
|
||||
if repo.LowerName == name {
|
||||
found = true
|
||||
repos = append(repos[:i], repos[i+1:]...)
|
||||
break repoLoop
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if count >= start && count < end {
|
||||
repoNames = append(repoNames, fmt.Sprintf("%s/%s", ctxUser.Name, name))
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return repoNames, count, nil
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
)
|
||||
|
||||
// DeleteRepositoryArchives deletes all repositories' archives.
|
||||
func DeleteRepositoryArchives(ctx context.Context) error {
|
||||
if err := models.DeleteAllRepoArchives(); err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.Clean(storage.RepoArchives)
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ForkRepository forks a repository
|
||||
func ForkRepository(doer, owner *models.User, opts models.ForkRepoOptions) (_ *models.Repository, err error) {
|
||||
forkedRepo, err := opts.BaseRepo.GetUserFork(owner.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if forkedRepo != nil {
|
||||
return nil, models.ErrForkAlreadyExist{
|
||||
Uname: owner.Name,
|
||||
RepoName: opts.BaseRepo.FullName(),
|
||||
ForkName: forkedRepo.FullName(),
|
||||
}
|
||||
}
|
||||
|
||||
repo := &models.Repository{
|
||||
OwnerID: owner.ID,
|
||||
Owner: owner,
|
||||
OwnerName: owner.Name,
|
||||
Name: opts.Name,
|
||||
LowerName: strings.ToLower(opts.Name),
|
||||
Description: opts.Description,
|
||||
DefaultBranch: opts.BaseRepo.DefaultBranch,
|
||||
IsPrivate: opts.BaseRepo.IsPrivate || opts.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate,
|
||||
IsEmpty: opts.BaseRepo.IsEmpty,
|
||||
IsFork: true,
|
||||
ForkID: opts.BaseRepo.ID,
|
||||
}
|
||||
|
||||
oldRepoPath := opts.BaseRepo.RepoPath()
|
||||
|
||||
needsRollback := false
|
||||
rollbackFn := func() {
|
||||
if !needsRollback {
|
||||
return
|
||||
}
|
||||
|
||||
repoPath := models.RepoPath(owner.Name, repo.Name)
|
||||
|
||||
if exists, _ := util.IsExist(repoPath); !exists {
|
||||
return
|
||||
}
|
||||
|
||||
// As the transaction will be failed and hence database changes will be destroyed we only need
|
||||
// to delete the related repository on the filesystem
|
||||
if errDelete := util.RemoveAll(repoPath); errDelete != nil {
|
||||
log.Error("Failed to remove fork repo")
|
||||
}
|
||||
}
|
||||
|
||||
needsRollbackInPanic := true
|
||||
defer func() {
|
||||
panicErr := recover()
|
||||
if panicErr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if needsRollbackInPanic {
|
||||
rollbackFn()
|
||||
}
|
||||
panic(panicErr)
|
||||
}()
|
||||
|
||||
err = db.WithTx(func(ctx context.Context) error {
|
||||
if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = models.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy lfs files failure should not be ignored
|
||||
if err = models.CopyLFS(ctx, repo, opts.BaseRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
needsRollback = true
|
||||
|
||||
repoPath := models.RepoPath(owner.Name, repo.Name)
|
||||
if stdout, err := git.NewCommandContext(ctx,
|
||||
"clone", "--bare", oldRepoPath, repoPath).
|
||||
SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())).
|
||||
RunInDirTimeout(10*time.Minute, ""); err != nil {
|
||||
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err)
|
||||
return fmt.Errorf("git clone: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.CheckDaemonExportOK(ctx); err != nil {
|
||||
return fmt.Errorf("checkDaemonExportOK: %v", err)
|
||||
}
|
||||
|
||||
if stdout, err := git.NewCommandContext(ctx, "update-server-info").
|
||||
SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
|
||||
RunInDir(repoPath); err != nil {
|
||||
log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
|
||||
return fmt.Errorf("git update-server-info: %v", err)
|
||||
}
|
||||
|
||||
if err = createDelegateHooks(repoPath); err != nil {
|
||||
return fmt.Errorf("createDelegateHooks: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
needsRollbackInPanic = false
|
||||
if err != nil {
|
||||
rollbackFn()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// even if below operations failed, it could be ignored. And they will be retried
|
||||
ctx := db.DefaultContext
|
||||
if err := repo.UpdateSize(ctx); err != nil {
|
||||
log.Error("Failed to update size for repository: %v", err)
|
||||
}
|
||||
if err := models.CopyLanguageStat(opts.BaseRepo, repo); err != nil {
|
||||
log.Error("Copy language stat from oldRepo failed")
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo
|
||||
func ConvertForkToNormalRepository(repo *models.Repository) error {
|
||||
err := db.WithTx(func(ctx context.Context) error {
|
||||
repo, err := models.GetRepositoryByIDCtx(ctx, repo.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !repo.IsFork {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := models.DecrementRepoForkNum(ctx, repo.ForkID); err != nil {
|
||||
log.Error("Unable to decrement repo fork num for old root repo %d of repository %-v whilst converting from fork. Error: %v", repo.ForkID, repo, err)
|
||||
return err
|
||||
}
|
||||
|
||||
repo.IsFork = false
|
||||
repo.ForkID = 0
|
||||
|
||||
if err := models.UpdateRepositoryCtx(ctx, repo, false); err != nil {
|
||||
log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestForkRepository(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
// user 13 has already forked repo10
|
||||
user := unittest.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository)
|
||||
|
||||
fork, err := ForkRepository(user, user, models.ForkRepoOptions{
|
||||
BaseRepo: repo,
|
||||
Name: "test",
|
||||
Description: "test",
|
||||
})
|
||||
assert.Nil(t, fork)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, models.IsErrForkAlreadyExist(err))
|
||||
}
|
|
@ -196,92 +196,6 @@ func checkInitRepository(owner, name string) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func adoptRepository(ctx context.Context, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) {
|
||||
isExist, err := util.IsExist(repoPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
||||
return err
|
||||
}
|
||||
if !isExist {
|
||||
return fmt.Errorf("adoptRepository: path does not already exist: %s", repoPath)
|
||||
}
|
||||
|
||||
if err := createDelegateHooks(repoPath); err != nil {
|
||||
return fmt.Errorf("createDelegateHooks: %v", err)
|
||||
}
|
||||
|
||||
// Re-fetch the repository from database before updating it (else it would
|
||||
// override changes that were done earlier with sql)
|
||||
if repo, err = models.GetRepositoryByIDCtx(ctx, repo.ID); err != nil {
|
||||
return fmt.Errorf("getRepositoryByID: %v", err)
|
||||
}
|
||||
|
||||
repo.IsEmpty = false
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("openRepository: %v", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
if len(opts.DefaultBranch) > 0 {
|
||||
repo.DefaultBranch = opts.DefaultBranch
|
||||
|
||||
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
|
||||
return fmt.Errorf("setDefaultBranch: %v", err)
|
||||
}
|
||||
} else {
|
||||
repo.DefaultBranch, err = gitRepo.GetDefaultBranch()
|
||||
if err != nil {
|
||||
repo.DefaultBranch = setting.Repository.DefaultBranch
|
||||
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
|
||||
return fmt.Errorf("setDefaultBranch: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
repo.DefaultBranch = strings.TrimPrefix(repo.DefaultBranch, git.BranchPrefix)
|
||||
}
|
||||
branches, _, _ := gitRepo.GetBranches(0, 0)
|
||||
found := false
|
||||
hasDefault := false
|
||||
hasMaster := false
|
||||
hasMain := false
|
||||
for _, branch := range branches {
|
||||
if branch == repo.DefaultBranch {
|
||||
found = true
|
||||
break
|
||||
} else if branch == setting.Repository.DefaultBranch {
|
||||
hasDefault = true
|
||||
} else if branch == "master" {
|
||||
hasMaster = true
|
||||
} else if branch == "main" {
|
||||
hasMain = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if hasDefault {
|
||||
repo.DefaultBranch = setting.Repository.DefaultBranch
|
||||
} else if hasMaster {
|
||||
repo.DefaultBranch = "master"
|
||||
} else if hasMain {
|
||||
repo.DefaultBranch = "main"
|
||||
} else if len(branches) > 0 {
|
||||
repo.DefaultBranch = branches[0]
|
||||
} else {
|
||||
repo.IsEmpty = true
|
||||
repo.DefaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
|
||||
return fmt.Errorf("setDefaultBranch: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil {
|
||||
return fmt.Errorf("updateRepository: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitRepository initializes README and .gitignore if needed.
|
||||
func initRepository(ctx context.Context, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) {
|
||||
if err = checkInitRepository(repo.OwnerName, repo.Name); err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue