mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Refactor Cron and merge dashboard tasks (#10745)
* Refactor Cron and merge dashboard tasks * Merge Cron and Dashboard tasks * Make every cron task report a system notice on completion * Refactor the creation of these tasks * Ensure that execution counts of tasks is correct * Allow cron tasks to be started from the cron page * golangci-lint fixes * Enforce that only one task with the same name can be registered Signed-off-by: Andrew Thornton <art27@cantab.net> * fix name check Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @guillep2k * as per @lafriks Signed-off-by: Andrew Thornton <art27@cantab.net> * Add git.CommandContext variants Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		@@ -151,7 +151,7 @@ func TestLDAPUserSync(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer prepareTestEnv(t)()
 | 
						defer prepareTestEnv(t)()
 | 
				
			||||||
	addAuthSourceLDAP(t, "")
 | 
						addAuthSourceLDAP(t, "")
 | 
				
			||||||
	models.SyncExternalUsers(context.Background())
 | 
						models.SyncExternalUsers(context.Background(), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	session := loginUser(t, "user1")
 | 
						session := loginUser(t, "user1")
 | 
				
			||||||
	// Check if users exists
 | 
						// Check if users exists
 | 
				
			||||||
@@ -216,7 +216,7 @@ func TestLDAPUserSSHKeySync(t *testing.T) {
 | 
				
			|||||||
	defer prepareTestEnv(t)()
 | 
						defer prepareTestEnv(t)()
 | 
				
			||||||
	addAuthSourceLDAP(t, "sshPublicKey")
 | 
						addAuthSourceLDAP(t, "sshPublicKey")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	models.SyncExternalUsers(context.Background())
 | 
						models.SyncExternalUsers(context.Background(), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if users has SSH keys synced
 | 
						// Check if users has SSH keys synced
 | 
				
			||||||
	for _, u := range gitLDAPUsers {
 | 
						for _, u := range gitLDAPUsers {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,8 @@ type NoticeType int
 | 
				
			|||||||
const (
 | 
					const (
 | 
				
			||||||
	//NoticeRepository type
 | 
						//NoticeRepository type
 | 
				
			||||||
	NoticeRepository NoticeType = iota + 1
 | 
						NoticeRepository NoticeType = iota + 1
 | 
				
			||||||
 | 
						// NoticeTask type
 | 
				
			||||||
 | 
						NoticeTask
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Notice represents a system notice for admin.
 | 
					// Notice represents a system notice for admin.
 | 
				
			||||||
@@ -36,11 +38,14 @@ func (n *Notice) TrStr() string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateNotice creates new system notice.
 | 
					// CreateNotice creates new system notice.
 | 
				
			||||||
func CreateNotice(tp NoticeType, desc string) error {
 | 
					func CreateNotice(tp NoticeType, desc string, args ...interface{}) error {
 | 
				
			||||||
	return createNotice(x, tp, desc)
 | 
						return createNotice(x, tp, desc, args...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createNotice(e Engine, tp NoticeType, desc string) error {
 | 
					func createNotice(e Engine, tp NoticeType, desc string, args ...interface{}) error {
 | 
				
			||||||
 | 
						if len(args) > 0 {
 | 
				
			||||||
 | 
							desc = fmt.Sprintf(desc, args...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	n := &Notice{
 | 
						n := &Notice{
 | 
				
			||||||
		Type:        tp,
 | 
							Type:        tp,
 | 
				
			||||||
		Description: desc,
 | 
							Description: desc,
 | 
				
			||||||
@@ -50,8 +55,8 @@ func createNotice(e Engine, tp NoticeType, desc string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateRepositoryNotice creates new system notice with type NoticeRepository.
 | 
					// CreateRepositoryNotice creates new system notice with type NoticeRepository.
 | 
				
			||||||
func CreateRepositoryNotice(desc string) error {
 | 
					func CreateRepositoryNotice(desc string, args ...interface{}) error {
 | 
				
			||||||
	return createNotice(x, NoticeRepository, desc)
 | 
						return createNotice(x, NoticeRepository, desc, args...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RemoveAllWithNotice removes all directories in given path and
 | 
					// RemoveAllWithNotice removes all directories in given path and
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -561,11 +560,11 @@ func RemoveDeletedBranch(repoID int64, branch string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RemoveOldDeletedBranches removes old deleted branches
 | 
					// RemoveOldDeletedBranches removes old deleted branches
 | 
				
			||||||
func RemoveOldDeletedBranches(ctx context.Context) {
 | 
					func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
 | 
				
			||||||
	// Nothing to do for shutdown or terminate
 | 
						// Nothing to do for shutdown or terminate
 | 
				
			||||||
	log.Trace("Doing: DeletedBranchesCleanup")
 | 
						log.Trace("Doing: DeletedBranchesCleanup")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deleteBefore := time.Now().Add(-setting.Cron.DeletedBranchesCleanup.OlderThan)
 | 
						deleteBefore := time.Now().Add(-olderThan)
 | 
				
			||||||
	_, err := x.Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
 | 
						_, err := x.Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("DeletedBranchesCleanup: %v", err)
 | 
							log.Error("DeletedBranchesCleanup: %v", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -85,6 +85,28 @@ func (err ErrSSHDisabled) Error() string {
 | 
				
			|||||||
	return "SSH is disabled"
 | 
						return "SSH is disabled"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrCancelled represents an error due to context cancellation
 | 
				
			||||||
 | 
					type ErrCancelled struct {
 | 
				
			||||||
 | 
						Message string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrCancelled checks if an error is a ErrCancelled.
 | 
				
			||||||
 | 
					func IsErrCancelled(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrCancelled)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrCancelled) Error() string {
 | 
				
			||||||
 | 
						return "Cancelled: " + err.Message
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrCancelledf returns an ErrCancelled for the provided format and args
 | 
				
			||||||
 | 
					func ErrCancelledf(format string, args ...interface{}) error {
 | 
				
			||||||
 | 
						return ErrCancelled{
 | 
				
			||||||
 | 
							fmt.Sprintf(format, args...),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  ____ ___
 | 
					//  ____ ___
 | 
				
			||||||
// |    |   \______ ___________
 | 
					// |    |   \______ ___________
 | 
				
			||||||
// |    |   /  ___// __ \_  __ \
 | 
					// |    |   /  ___// __ \_  __ \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1853,35 +1853,44 @@ func GetPrivateRepositoryCount(u *User) (int64, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeleteRepositoryArchives deletes all repositories' archives.
 | 
					// DeleteRepositoryArchives deletes all repositories' archives.
 | 
				
			||||||
func DeleteRepositoryArchives() error {
 | 
					func DeleteRepositoryArchives(ctx context.Context) error {
 | 
				
			||||||
	return x.
 | 
						return x.
 | 
				
			||||||
		Where("id > 0").
 | 
							Where("id > 0").
 | 
				
			||||||
		Iterate(new(Repository),
 | 
							Iterate(new(Repository),
 | 
				
			||||||
			func(idx int, bean interface{}) error {
 | 
								func(idx int, bean interface{}) error {
 | 
				
			||||||
				repo := bean.(*Repository)
 | 
									repo := bean.(*Repository)
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-ctx.Done():
 | 
				
			||||||
 | 
										return ErrCancelledf("before deleting repository archives for %s", repo.FullName())
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives"))
 | 
									return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives"))
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeleteOldRepositoryArchives deletes old repository archives.
 | 
					// DeleteOldRepositoryArchives deletes old repository archives.
 | 
				
			||||||
func DeleteOldRepositoryArchives(ctx context.Context) {
 | 
					func DeleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration) error {
 | 
				
			||||||
	log.Trace("Doing: ArchiveCleanup")
 | 
						log.Trace("Doing: ArchiveCleanup")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := x.Where("id > 0").Iterate(new(Repository), func(idx int, bean interface{}) error {
 | 
						if err := x.Where("id > 0").Iterate(new(Repository), func(idx int, bean interface{}) error {
 | 
				
			||||||
		return deleteOldRepositoryArchives(ctx, idx, bean)
 | 
							return deleteOldRepositoryArchives(ctx, olderThan, idx, bean)
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		log.Error("ArchiveClean: %v", err)
 | 
							log.Trace("Error: ArchiveClean: %v", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace("Finished: ArchiveCleanup")
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func deleteOldRepositoryArchives(ctx context.Context, idx int, bean interface{}) error {
 | 
					func deleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration, idx int, bean interface{}) error {
 | 
				
			||||||
	repo := bean.(*Repository)
 | 
						repo := bean.(*Repository)
 | 
				
			||||||
	basePath := filepath.Join(repo.RepoPath(), "archives")
 | 
						basePath := filepath.Join(repo.RepoPath(), "archives")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, ty := range []string{"zip", "targz"} {
 | 
						for _, ty := range []string{"zip", "targz"} {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			return fmt.Errorf("Aborted due to shutdown:\nin delete of old repository archives %v\nat delete file %s", repo, ty)
 | 
								return ErrCancelledf("before deleting old repository archives with filetype %s for %s", ty, repo.FullName())
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1904,12 +1913,12 @@ func deleteOldRepositoryArchives(ctx context.Context, idx int, bean interface{})
 | 
				
			|||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan)
 | 
							minimumOldestTime := time.Now().Add(-olderThan)
 | 
				
			||||||
		for _, info := range files {
 | 
							for _, info := range files {
 | 
				
			||||||
			if info.ModTime().Before(minimumOldestTime) && !info.IsDir() {
 | 
								if info.ModTime().Before(minimumOldestTime) && !info.IsDir() {
 | 
				
			||||||
				select {
 | 
									select {
 | 
				
			||||||
				case <-ctx.Done():
 | 
									case <-ctx.Done():
 | 
				
			||||||
					return fmt.Errorf("Aborted due to shutdown:\nin delete of old repository archives %v\nat delete file %s - %s", repo, ty, info.Name())
 | 
										return ErrCancelledf("before deleting old repository archive file %s with filetype %s for %s", info.Name(), ty, repo.FullName())
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				toDelete := filepath.Join(path, info.Name())
 | 
									toDelete := filepath.Join(path, info.Name())
 | 
				
			||||||
@@ -1936,13 +1945,13 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, result := range results {
 | 
						for _, result := range results {
 | 
				
			||||||
 | 
							id := com.StrTo(result["id"]).MustInt64()
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			log.Warn("CheckRepoStats: Aborting due to shutdown")
 | 
								log.Warn("CheckRepoStats: Cancelled before checking %s for Repo[%d]", checker.desc, id)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		id := com.StrTo(result["id"]).MustInt64()
 | 
					 | 
				
			||||||
		log.Trace("Updating %s: %d", checker.desc, id)
 | 
							log.Trace("Updating %s: %d", checker.desc, id)
 | 
				
			||||||
		_, err = x.Exec(checker.correctSQL, id, id)
 | 
							_, err = x.Exec(checker.correctSQL, id, id)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -1952,7 +1961,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CheckRepoStats checks the repository stats
 | 
					// CheckRepoStats checks the repository stats
 | 
				
			||||||
func CheckRepoStats(ctx context.Context) {
 | 
					func CheckRepoStats(ctx context.Context) error {
 | 
				
			||||||
	log.Trace("Doing: CheckRepoStats")
 | 
						log.Trace("Doing: CheckRepoStats")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	checkers := []*repoChecker{
 | 
						checkers := []*repoChecker{
 | 
				
			||||||
@@ -1987,13 +1996,13 @@ func CheckRepoStats(ctx context.Context) {
 | 
				
			|||||||
			"issue count 'num_comments'",
 | 
								"issue count 'num_comments'",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for i := range checkers {
 | 
						for _, checker := range checkers {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			log.Warn("CheckRepoStats: Aborting due to shutdown")
 | 
								log.Warn("CheckRepoStats: Cancelled before %s", checker.desc)
 | 
				
			||||||
			return
 | 
								return ErrCancelledf("before checking %s", checker.desc)
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			repoStatsCheck(ctx, checkers[i])
 | 
								repoStatsCheck(ctx, checker)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2004,13 +2013,13 @@ func CheckRepoStats(ctx context.Context) {
 | 
				
			|||||||
		log.Error("Select %s: %v", desc, err)
 | 
							log.Error("Select %s: %v", desc, err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		for _, result := range results {
 | 
							for _, result := range results {
 | 
				
			||||||
 | 
								id := com.StrTo(result["id"]).MustInt64()
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				log.Warn("CheckRepoStats: Aborting due to shutdown")
 | 
									log.Warn("CheckRepoStats: Cancelled during %s for repo ID %d", desc, id)
 | 
				
			||||||
				return
 | 
									return ErrCancelledf("during %s for repo ID %d", desc, id)
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			id := com.StrTo(result["id"]).MustInt64()
 | 
					 | 
				
			||||||
			log.Trace("Updating %s: %d", desc, id)
 | 
								log.Trace("Updating %s: %d", desc, id)
 | 
				
			||||||
			_, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id)
 | 
								_, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
@@ -2027,13 +2036,13 @@ func CheckRepoStats(ctx context.Context) {
 | 
				
			|||||||
		log.Error("Select %s: %v", desc, err)
 | 
							log.Error("Select %s: %v", desc, err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		for _, result := range results {
 | 
							for _, result := range results {
 | 
				
			||||||
 | 
								id := com.StrTo(result["id"]).MustInt64()
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				log.Warn("CheckRepoStats: Aborting due to shutdown")
 | 
									log.Warn("CheckRepoStats: Cancelled")
 | 
				
			||||||
				return
 | 
									return ErrCancelledf("during %s for repo ID %d", desc, id)
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			id := com.StrTo(result["id"]).MustInt64()
 | 
					 | 
				
			||||||
			log.Trace("Updating %s: %d", desc, id)
 | 
								log.Trace("Updating %s: %d", desc, id)
 | 
				
			||||||
			_, err = x.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id)
 | 
								_, err = x.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
@@ -2050,13 +2059,13 @@ func CheckRepoStats(ctx context.Context) {
 | 
				
			|||||||
		log.Error("Select repository count 'num_forks': %v", err)
 | 
							log.Error("Select repository count 'num_forks': %v", err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		for _, result := range results {
 | 
							for _, result := range results {
 | 
				
			||||||
 | 
								id := com.StrTo(result["id"]).MustInt64()
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				log.Warn("CheckRepoStats: Aborting due to shutdown")
 | 
									log.Warn("CheckRepoStats: Cancelled")
 | 
				
			||||||
				return
 | 
									return ErrCancelledf("during %s for repo ID %d", desc, id)
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			id := com.StrTo(result["id"]).MustInt64()
 | 
					 | 
				
			||||||
			log.Trace("Updating repository count 'num_forks': %d", id)
 | 
								log.Trace("Updating repository count 'num_forks': %d", id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			repo, err := GetRepositoryByID(id)
 | 
								repo, err := GetRepositoryByID(id)
 | 
				
			||||||
@@ -2079,6 +2088,7 @@ func CheckRepoStats(ctx context.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// ***** END: Repository.NumForks *****
 | 
						// ***** END: Repository.NumForks *****
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetArchiveRepoState sets if a repo is archived
 | 
					// SetArchiveRepoState sets if a repo is archived
 | 
				
			||||||
@@ -2189,12 +2199,17 @@ func (repo *Repository) generateRandomAvatar(e Engine) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
 | 
					// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
 | 
				
			||||||
func RemoveRandomAvatars() error {
 | 
					func RemoveRandomAvatars(ctx context.Context) error {
 | 
				
			||||||
	return x.
 | 
						return x.
 | 
				
			||||||
		Where("id > 0").BufferSize(setting.Database.IterateBufferSize).
 | 
							Where("id > 0").BufferSize(setting.Database.IterateBufferSize).
 | 
				
			||||||
		Iterate(new(Repository),
 | 
							Iterate(new(Repository),
 | 
				
			||||||
			func(idx int, bean interface{}) error {
 | 
								func(idx int, bean interface{}) error {
 | 
				
			||||||
				repository := bean.(*Repository)
 | 
									repository := bean.(*Repository)
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-ctx.Done():
 | 
				
			||||||
 | 
										return ErrCancelledf("before random avatars removed for %s", repository.FullName())
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				stringifiedID := strconv.FormatInt(repository.ID, 10)
 | 
									stringifiedID := strconv.FormatInt(repository.ID, 10)
 | 
				
			||||||
				if repository.Avatar == stringifiedID {
 | 
									if repository.Avatar == stringifiedID {
 | 
				
			||||||
					return repository.DeleteAvatar()
 | 
										return repository.DeleteAvatar()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1321,16 +1321,30 @@ func DeleteUser(u *User) (err error) {
 | 
				
			|||||||
	return sess.Commit()
 | 
						return sess.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeleteInactivateUsers deletes all inactivate users and email addresses.
 | 
					// DeleteInactiveUsers deletes all inactive users and email addresses.
 | 
				
			||||||
func DeleteInactivateUsers() (err error) {
 | 
					func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err error) {
 | 
				
			||||||
	users := make([]*User, 0, 10)
 | 
						users := make([]*User, 0, 10)
 | 
				
			||||||
	if err = x.
 | 
						if olderThan > 0 {
 | 
				
			||||||
		Where("is_active = ?", false).
 | 
							if err = x.
 | 
				
			||||||
		Find(&users); err != nil {
 | 
								Where("is_active = ? and created_unix < ?", false, time.Now().Add(-olderThan).Unix()).
 | 
				
			||||||
		return fmt.Errorf("get all inactive users: %v", err)
 | 
								Find(&users); err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("get all inactive users: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if err = x.
 | 
				
			||||||
 | 
								Where("is_active = ?", false).
 | 
				
			||||||
 | 
								Find(&users); err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("get all inactive users: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// FIXME: should only update authorized_keys file once after all deletions.
 | 
						// FIXME: should only update authorized_keys file once after all deletions.
 | 
				
			||||||
	for _, u := range users {
 | 
						for _, u := range users {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return ErrCancelledf("Before delete inactive user %s", u.Name)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		if err = DeleteUser(u); err != nil {
 | 
							if err = DeleteUser(u); err != nil {
 | 
				
			||||||
			// Ignore users that were set inactive by admin.
 | 
								// Ignore users that were set inactive by admin.
 | 
				
			||||||
			if IsErrUserOwnRepos(err) || IsErrUserHasOrgs(err) {
 | 
								if IsErrUserOwnRepos(err) || IsErrUserHasOrgs(err) {
 | 
				
			||||||
@@ -1814,25 +1828,23 @@ func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []str
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SyncExternalUsers is used to synchronize users with external authorization source
 | 
					// SyncExternalUsers is used to synchronize users with external authorization source
 | 
				
			||||||
func SyncExternalUsers(ctx context.Context) {
 | 
					func SyncExternalUsers(ctx context.Context, updateExisting bool) error {
 | 
				
			||||||
	log.Trace("Doing: SyncExternalUsers")
 | 
						log.Trace("Doing: SyncExternalUsers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ls, err := LoginSources()
 | 
						ls, err := LoginSources()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Error("SyncExternalUsers: %v", err)
 | 
							log.Error("SyncExternalUsers: %v", err)
 | 
				
			||||||
		return
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	updateExisting := setting.Cron.SyncExternalUsers.UpdateExisting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, s := range ls {
 | 
						for _, s := range ls {
 | 
				
			||||||
		if !s.IsActived || !s.IsSyncEnabled {
 | 
							if !s.IsActived || !s.IsSyncEnabled {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			log.Warn("SyncExternalUsers: Aborted due to shutdown before update of %s", s.Name)
 | 
								log.Warn("SyncExternalUsers: Cancelled before update of %s", s.Name)
 | 
				
			||||||
			return
 | 
								return ErrCancelledf("Before update of %s", s.Name)
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1850,12 +1862,12 @@ func SyncExternalUsers(ctx context.Context) {
 | 
				
			|||||||
				Find(&users)
 | 
									Find(&users)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error("SyncExternalUsers: %v", err)
 | 
									log.Error("SyncExternalUsers: %v", err)
 | 
				
			||||||
				return
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				log.Warn("SyncExternalUsers: Aborted due to shutdown before update of %s", s.Name)
 | 
									log.Warn("SyncExternalUsers: Cancelled before update of %s", s.Name)
 | 
				
			||||||
				return
 | 
									return ErrCancelledf("Before update of %s", s.Name)
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1877,7 +1889,7 @@ func SyncExternalUsers(ctx context.Context) {
 | 
				
			|||||||
			for _, su := range sr {
 | 
								for _, su := range sr {
 | 
				
			||||||
				select {
 | 
									select {
 | 
				
			||||||
				case <-ctx.Done():
 | 
									case <-ctx.Done():
 | 
				
			||||||
					log.Warn("SyncExternalUsers: Aborted due to shutdown at update of %s before completed update of users", s.Name)
 | 
										log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", s.Name)
 | 
				
			||||||
					// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
 | 
										// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
 | 
				
			||||||
					if sshKeysNeedUpdate {
 | 
										if sshKeysNeedUpdate {
 | 
				
			||||||
						err = RewriteAllPublicKeys()
 | 
											err = RewriteAllPublicKeys()
 | 
				
			||||||
@@ -1885,7 +1897,7 @@ func SyncExternalUsers(ctx context.Context) {
 | 
				
			|||||||
							log.Error("RewriteAllPublicKeys: %v", err)
 | 
												log.Error("RewriteAllPublicKeys: %v", err)
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					return
 | 
										return ErrCancelledf("During update of %s before completed update of users", s.Name)
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if len(su.Username) == 0 {
 | 
									if len(su.Username) == 0 {
 | 
				
			||||||
@@ -1980,8 +1992,8 @@ func SyncExternalUsers(ctx context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				log.Warn("SyncExternalUsers: Aborted due to shutdown at update of %s before delete users", s.Name)
 | 
									log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", s.Name)
 | 
				
			||||||
				return
 | 
									return ErrCancelledf("During update of %s before delete users", s.Name)
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2008,4 +2020,5 @@ func SyncExternalUsers(ctx context.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// AdminDashboardForm form for admin dashboard operations
 | 
					// AdminDashboardForm form for admin dashboard operations
 | 
				
			||||||
type AdminDashboardForm struct {
 | 
					type AdminDashboardForm struct {
 | 
				
			||||||
	Op int `binding:"required"`
 | 
						Op string `binding:"required"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate validates form fields
 | 
					// Validate validates form fields
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,143 +9,86 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/graceful"
 | 
						"code.gitea.io/gitea/modules/graceful"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/migrations"
 | 
					 | 
				
			||||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/sync"
 | 
						"code.gitea.io/gitea/modules/sync"
 | 
				
			||||||
	mirror_service "code.gitea.io/gitea/services/mirror"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gogs/cron"
 | 
						"github.com/gogs/cron"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	mirrorUpdate            = "mirror_update"
 | 
					 | 
				
			||||||
	gitFsck                 = "git_fsck"
 | 
					 | 
				
			||||||
	checkRepos              = "check_repos"
 | 
					 | 
				
			||||||
	archiveCleanup          = "archive_cleanup"
 | 
					 | 
				
			||||||
	syncExternalUsers       = "sync_external_users"
 | 
					 | 
				
			||||||
	deletedBranchesCleanup  = "deleted_branches_cleanup"
 | 
					 | 
				
			||||||
	updateMigrationPosterID = "update_migration_post_id"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var c = cron.New()
 | 
					var c = cron.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Prevent duplicate running tasks.
 | 
					// Prevent duplicate running tasks.
 | 
				
			||||||
var taskStatusTable = sync.NewStatusTable()
 | 
					var taskStatusTable = sync.NewStatusTable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Func defines a cron function body
 | 
					 | 
				
			||||||
type Func func()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// WithUnique wrap a cron func with an unique running check
 | 
					 | 
				
			||||||
func WithUnique(name string, body func(context.Context)) Func {
 | 
					 | 
				
			||||||
	return func() {
 | 
					 | 
				
			||||||
		if !taskStatusTable.StartIfNotRunning(name) {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		defer taskStatusTable.Stop(name)
 | 
					 | 
				
			||||||
		graceful.GetManager().RunWithShutdownContext(body)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewContext begins cron tasks
 | 
					// NewContext begins cron tasks
 | 
				
			||||||
// Each cron task is run within the shutdown context as a running server
 | 
					// Each cron task is run within the shutdown context as a running server
 | 
				
			||||||
// AtShutdown the cron server is stopped
 | 
					// AtShutdown the cron server is stopped
 | 
				
			||||||
func NewContext() {
 | 
					func NewContext() {
 | 
				
			||||||
	var (
 | 
						initBasicTasks()
 | 
				
			||||||
		entry *cron.Entry
 | 
						initExtendedTasks()
 | 
				
			||||||
		err   error
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	if setting.Cron.UpdateMirror.Enabled {
 | 
					 | 
				
			||||||
		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, WithUnique(mirrorUpdate, mirror_service.Update))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatal("Cron[Update mirrors]: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if setting.Cron.UpdateMirror.RunAtStart {
 | 
					 | 
				
			||||||
			entry.Prev = time.Now()
 | 
					 | 
				
			||||||
			entry.ExecTimes++
 | 
					 | 
				
			||||||
			go WithUnique(mirrorUpdate, mirror_service.Update)()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if setting.Cron.RepoHealthCheck.Enabled {
 | 
					 | 
				
			||||||
		entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, WithUnique(gitFsck, func(ctx context.Context) {
 | 
					 | 
				
			||||||
			if err := repo_module.GitFsck(ctx); err != nil {
 | 
					 | 
				
			||||||
				log.Error("GitFsck: %s", err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatal("Cron[Repository health check]: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if setting.Cron.RepoHealthCheck.RunAtStart {
 | 
					 | 
				
			||||||
			entry.Prev = time.Now()
 | 
					 | 
				
			||||||
			entry.ExecTimes++
 | 
					 | 
				
			||||||
			go WithUnique(gitFsck, func(ctx context.Context) {
 | 
					 | 
				
			||||||
				if err := repo_module.GitFsck(ctx); err != nil {
 | 
					 | 
				
			||||||
					log.Error("GitFsck: %s", err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			})()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if setting.Cron.CheckRepoStats.Enabled {
 | 
					 | 
				
			||||||
		entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, WithUnique(checkRepos, models.CheckRepoStats))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatal("Cron[Check repository statistics]: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if setting.Cron.CheckRepoStats.RunAtStart {
 | 
					 | 
				
			||||||
			entry.Prev = time.Now()
 | 
					 | 
				
			||||||
			entry.ExecTimes++
 | 
					 | 
				
			||||||
			go WithUnique(checkRepos, models.CheckRepoStats)()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if setting.Cron.ArchiveCleanup.Enabled {
 | 
					 | 
				
			||||||
		entry, err = c.AddFunc("Clean up old repository archives", setting.Cron.ArchiveCleanup.Schedule, WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatal("Cron[Clean up old repository archives]: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if setting.Cron.ArchiveCleanup.RunAtStart {
 | 
					 | 
				
			||||||
			entry.Prev = time.Now()
 | 
					 | 
				
			||||||
			entry.ExecTimes++
 | 
					 | 
				
			||||||
			go WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if setting.Cron.SyncExternalUsers.Enabled {
 | 
					 | 
				
			||||||
		entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, WithUnique(syncExternalUsers, models.SyncExternalUsers))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatal("Cron[Synchronize external users]: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if setting.Cron.SyncExternalUsers.RunAtStart {
 | 
					 | 
				
			||||||
			entry.Prev = time.Now()
 | 
					 | 
				
			||||||
			entry.ExecTimes++
 | 
					 | 
				
			||||||
			go WithUnique(syncExternalUsers, models.SyncExternalUsers)()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if setting.Cron.DeletedBranchesCleanup.Enabled {
 | 
					 | 
				
			||||||
		entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Fatal("Cron[Remove old deleted branches]: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if setting.Cron.DeletedBranchesCleanup.RunAtStart {
 | 
					 | 
				
			||||||
			entry.Prev = time.Now()
 | 
					 | 
				
			||||||
			entry.ExecTimes++
 | 
					 | 
				
			||||||
			go WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	entry, err = c.AddFunc("Update migrated repositories' issues and comments' posterid", setting.Cron.UpdateMigrationPosterID.Schedule, WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID))
 | 
						lock.Lock()
 | 
				
			||||||
	if err != nil {
 | 
						for _, task := range tasks {
 | 
				
			||||||
		log.Fatal("Cron[Update migrated repositories]: %v", err)
 | 
							if task.IsEnabled() && task.DoRunAtStart() {
 | 
				
			||||||
 | 
								go task.Run()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	entry.Prev = time.Now()
 | 
					 | 
				
			||||||
	entry.ExecTimes++
 | 
					 | 
				
			||||||
	go WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID)()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.Start()
 | 
						c.Start()
 | 
				
			||||||
	graceful.GetManager().RunAtShutdown(context.Background(), c.Stop)
 | 
						started = true
 | 
				
			||||||
 | 
						lock.Unlock()
 | 
				
			||||||
 | 
						graceful.GetManager().RunAtShutdown(context.Background(), func() {
 | 
				
			||||||
 | 
							c.Stop()
 | 
				
			||||||
 | 
							lock.Lock()
 | 
				
			||||||
 | 
							started = false
 | 
				
			||||||
 | 
							lock.Unlock()
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListTasks returns all running cron tasks.
 | 
					// TaskTableRow represents a task row in the tasks table
 | 
				
			||||||
func ListTasks() []*cron.Entry {
 | 
					type TaskTableRow struct {
 | 
				
			||||||
	return c.Entries()
 | 
						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
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								modules/cron/setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								modules/cron/setting.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BaseConfig represents the basic config for a Cron task
 | 
				
			||||||
 | 
					type BaseConfig struct {
 | 
				
			||||||
 | 
						Enabled    bool
 | 
				
			||||||
 | 
						RunAtStart bool
 | 
				
			||||||
 | 
						Schedule   string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										166
									
								
								modules/cron/tasks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								modules/cron/tasks.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
				
			|||||||
 | 
					// 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/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 models.IsErrCancelled(err) {
 | 
				
			||||||
 | 
									message := err.(models.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 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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										118
									
								
								modules/cron/tasks_basic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								modules/cron/tasks_basic.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					// 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/modules/migrations"
 | 
				
			||||||
 | 
						repository_service "code.gitea.io/gitea/modules/repository"
 | 
				
			||||||
 | 
						mirror_service "code.gitea.io/gitea/services/mirror"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func registerUpdateMirrorTask() {
 | 
				
			||||||
 | 
						RegisterTaskFatal("update_mirrors", &BaseConfig{
 | 
				
			||||||
 | 
							Enabled:    true,
 | 
				
			||||||
 | 
							RunAtStart: false,
 | 
				
			||||||
 | 
							Schedule:   "@every 10m",
 | 
				
			||||||
 | 
						}, 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:   "@every 24h",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							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:   "@every 24h",
 | 
				
			||||||
 | 
						}, 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:   "@every 24h",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							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:   "@every 24h",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							UpdateExisting: true,
 | 
				
			||||||
 | 
						}, func(ctx context.Context, _ *models.User, config Config) error {
 | 
				
			||||||
 | 
							realConfig := config.(*UpdateExistingConfig)
 | 
				
			||||||
 | 
							return models.SyncExternalUsers(ctx, realConfig.UpdateExisting)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func registerDeletedBranchesCleanup() {
 | 
				
			||||||
 | 
						RegisterTaskFatal("deleted_branches_cleanup", &OlderThanConfig{
 | 
				
			||||||
 | 
							BaseConfig: BaseConfig{
 | 
				
			||||||
 | 
								Enabled:    true,
 | 
				
			||||||
 | 
								RunAtStart: true,
 | 
				
			||||||
 | 
								Schedule:   "@every 24h",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							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:   "@every 24h",
 | 
				
			||||||
 | 
						}, func(ctx context.Context, _ *models.User, _ Config) error {
 | 
				
			||||||
 | 
							return migrations.UpdateMigrationPosterID(ctx)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func initBasicTasks() {
 | 
				
			||||||
 | 
						registerUpdateMirrorTask()
 | 
				
			||||||
 | 
						registerRepoHealthCheck()
 | 
				
			||||||
 | 
						registerCheckRepoStats()
 | 
				
			||||||
 | 
						registerArchiveCleanup()
 | 
				
			||||||
 | 
						registerSyncExternalUsers()
 | 
				
			||||||
 | 
						registerDeletedBranchesCleanup()
 | 
				
			||||||
 | 
						registerUpdateMigrationPosterID()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										119
									
								
								modules/cron/tasks_extended.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								modules/cron/tasks_extended.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					// 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"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 models.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 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 initExtendedTasks() {
 | 
				
			||||||
 | 
						registerDeleteInactiveUsers()
 | 
				
			||||||
 | 
						registerDeleteRepositoryArchives()
 | 
				
			||||||
 | 
						registerGarbageCollectRepositories()
 | 
				
			||||||
 | 
						registerRewriteAllPublicKeys()
 | 
				
			||||||
 | 
						registerRepositoryUpdateHook()
 | 
				
			||||||
 | 
						registerReinitMissingRepositories()
 | 
				
			||||||
 | 
						registerDeleteMissingRepositories()
 | 
				
			||||||
 | 
						registerRemoveRandomAvatars()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -45,22 +45,32 @@ func (c *Command) String() string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// NewCommand creates and returns a new Git Command based on given command and arguments.
 | 
					// NewCommand creates and returns a new Git Command based on given command and arguments.
 | 
				
			||||||
func NewCommand(args ...string) *Command {
 | 
					func NewCommand(args ...string) *Command {
 | 
				
			||||||
 | 
						return NewCommandContext(DefaultContext, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCommandContext creates and returns a new Git Command based on given command and arguments.
 | 
				
			||||||
 | 
					func NewCommandContext(ctx context.Context, args ...string) *Command {
 | 
				
			||||||
	// Make an explicit copy of GlobalCommandArgs, otherwise append might overwrite it
 | 
						// Make an explicit copy of GlobalCommandArgs, otherwise append might overwrite it
 | 
				
			||||||
	cargs := make([]string, len(GlobalCommandArgs))
 | 
						cargs := make([]string, len(GlobalCommandArgs))
 | 
				
			||||||
	copy(cargs, GlobalCommandArgs)
 | 
						copy(cargs, GlobalCommandArgs)
 | 
				
			||||||
	return &Command{
 | 
						return &Command{
 | 
				
			||||||
		name:          GitExecutable,
 | 
							name:          GitExecutable,
 | 
				
			||||||
		args:          append(cargs, args...),
 | 
							args:          append(cargs, args...),
 | 
				
			||||||
		parentContext: DefaultContext,
 | 
							parentContext: ctx,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
 | 
					// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
 | 
				
			||||||
func NewCommandNoGlobals(args ...string) *Command {
 | 
					func NewCommandNoGlobals(args ...string) *Command {
 | 
				
			||||||
 | 
						return NewCommandContextNoGlobals(DefaultContext, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
 | 
				
			||||||
 | 
					func NewCommandContextNoGlobals(ctx context.Context, args ...string) *Command {
 | 
				
			||||||
	return &Command{
 | 
						return &Command{
 | 
				
			||||||
		name:          GitExecutable,
 | 
							name:          GitExecutable,
 | 
				
			||||||
		args:          args,
 | 
							args:          args,
 | 
				
			||||||
		parentContext: DefaultContext,
 | 
							parentContext: ctx,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -150,11 +150,11 @@ func Init(ctx context.Context) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Fsck verifies the connectivity and validity of the objects in the database
 | 
					// Fsck verifies the connectivity and validity of the objects in the database
 | 
				
			||||||
func Fsck(repoPath string, timeout time.Duration, args ...string) error {
 | 
					func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
 | 
				
			||||||
	// Make sure timeout makes sense.
 | 
						// Make sure timeout makes sense.
 | 
				
			||||||
	if timeout <= 0 {
 | 
						if timeout <= 0 {
 | 
				
			||||||
		timeout = -1
 | 
							timeout = -1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, err := NewCommand("fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
 | 
						_, err := NewCommandContext(ctx, "fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,17 +13,19 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateMigrationPosterID updates all migrated repositories' issues and comments posterID
 | 
					// UpdateMigrationPosterID updates all migrated repositories' issues and comments posterID
 | 
				
			||||||
func UpdateMigrationPosterID(ctx context.Context) {
 | 
					func UpdateMigrationPosterID(ctx context.Context) error {
 | 
				
			||||||
	for _, gitService := range structs.SupportedFullGitService {
 | 
						for _, gitService := range structs.SupportedFullGitService {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			log.Warn("UpdateMigrationPosterID aborted due to shutdown before %s", gitService.Name())
 | 
								log.Warn("UpdateMigrationPosterID aborted before %s", gitService.Name())
 | 
				
			||||||
 | 
								return models.ErrCancelledf("during UpdateMigrationPosterID before %s", gitService.Name())
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if err := updateMigrationPosterIDByGitService(ctx, gitService); err != nil {
 | 
							if err := updateMigrationPosterIDByGitService(ctx, gitService); err != nil {
 | 
				
			||||||
			log.Error("updateMigrationPosterIDByGitService failed: %v", err)
 | 
								log.Error("updateMigrationPosterIDByGitService failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServiceType) error {
 | 
					func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServiceType) error {
 | 
				
			||||||
@@ -37,7 +39,7 @@ func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServ
 | 
				
			|||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			log.Warn("UpdateMigrationPosterIDByGitService(%s) aborted due to shutdown", tp.Name())
 | 
								log.Warn("UpdateMigrationPosterIDByGitService(%s) cancelled", tp.Name())
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -54,7 +56,7 @@ func updateMigrationPosterIDByGitService(ctx context.Context, tp structs.GitServ
 | 
				
			|||||||
		for _, user := range users {
 | 
							for _, user := range users {
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				log.Warn("UpdateMigrationPosterIDByGitService(%s) aborted due to shutdown", tp.Name())
 | 
									log.Warn("UpdateMigrationPosterIDByGitService(%s) cancelled", tp.Name())
 | 
				
			||||||
				return nil
 | 
									return nil
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,19 +7,19 @@ package repository
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/unknwon/com"
 | 
						"github.com/unknwon/com"
 | 
				
			||||||
	"xorm.io/builder"
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GitFsck calls 'git fsck' to check repository health.
 | 
					// GitFsck calls 'git fsck' to check repository health.
 | 
				
			||||||
func GitFsck(ctx context.Context) error {
 | 
					func GitFsck(ctx context.Context, timeout time.Duration, args []string) error {
 | 
				
			||||||
	log.Trace("Doing: GitFsck")
 | 
						log.Trace("Doing: GitFsck")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.Iterate(
 | 
						if err := models.Iterate(
 | 
				
			||||||
@@ -27,24 +27,24 @@ func GitFsck(ctx context.Context) error {
 | 
				
			|||||||
		new(models.Repository),
 | 
							new(models.Repository),
 | 
				
			||||||
		builder.Expr("id>0 AND is_fsck_enabled=?", true),
 | 
							builder.Expr("id>0 AND is_fsck_enabled=?", true),
 | 
				
			||||||
		func(idx int, bean interface{}) error {
 | 
							func(idx int, bean interface{}) error {
 | 
				
			||||||
 | 
								repo := bean.(*models.Repository)
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				return fmt.Errorf("Aborted due to shutdown")
 | 
									return models.ErrCancelledf("before fsck of %s", repo.FullName())
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			repo := bean.(*models.Repository)
 | 
								log.Trace("Running health check on repository %v", repo)
 | 
				
			||||||
			repoPath := repo.RepoPath()
 | 
								repoPath := repo.RepoPath()
 | 
				
			||||||
			log.Trace("Running health check on repository %s", repoPath)
 | 
								if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil {
 | 
				
			||||||
			if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil {
 | 
									log.Warn("Failed to health check repository (%v): %v", repo, err)
 | 
				
			||||||
				desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err)
 | 
									if err = models.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
 | 
				
			||||||
				log.Warn(desc)
 | 
					 | 
				
			||||||
				if err = models.CreateRepositoryNotice(desc); err != nil {
 | 
					 | 
				
			||||||
					log.Error("CreateRepositoryNotice: %v", err)
 | 
										log.Error("CreateRepositoryNotice: %v", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	); err != nil {
 | 
						); err != nil {
 | 
				
			||||||
 | 
							log.Trace("Error: GitFsck: %v", err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,32 +53,43 @@ func GitFsck(ctx context.Context) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
 | 
					// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
 | 
				
			||||||
func GitGcRepos(ctx context.Context) error {
 | 
					func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) error {
 | 
				
			||||||
	log.Trace("Doing: GitGcRepos")
 | 
						log.Trace("Doing: GitGcRepos")
 | 
				
			||||||
	args := append([]string{"gc"}, setting.Git.GCArgs...)
 | 
						args = append([]string{"gc"}, args...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.Iterate(
 | 
						if err := models.Iterate(
 | 
				
			||||||
		models.DefaultDBContext(),
 | 
							models.DefaultDBContext(),
 | 
				
			||||||
		new(models.Repository),
 | 
							new(models.Repository),
 | 
				
			||||||
		builder.Gt{"id": 0},
 | 
							builder.Gt{"id": 0},
 | 
				
			||||||
		func(idx int, bean interface{}) error {
 | 
							func(idx int, bean interface{}) error {
 | 
				
			||||||
 | 
								repo := bean.(*models.Repository)
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				return fmt.Errorf("Aborted due to shutdown")
 | 
									return models.ErrCancelledf("before GC of %s", repo.FullName())
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								log.Trace("Running git gc on %v", repo)
 | 
				
			||||||
			repo := bean.(*models.Repository)
 | 
								command := git.NewCommandContext(ctx, args...).
 | 
				
			||||||
			if err := repo.GetOwner(); err != nil {
 | 
									SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
 | 
				
			||||||
				return err
 | 
								var stdout string
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
								if timeout > 0 {
 | 
				
			||||||
 | 
									var stdoutBytes []byte
 | 
				
			||||||
 | 
									stdoutBytes, err = command.RunInDirTimeout(
 | 
				
			||||||
 | 
										timeout,
 | 
				
			||||||
 | 
										repo.RepoPath())
 | 
				
			||||||
 | 
									stdout = string(stdoutBytes)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									stdout, err = command.RunInDir(repo.RepoPath())
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if stdout, err := git.NewCommand(args...).
 | 
					
 | 
				
			||||||
				SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())).
 | 
								if err != nil {
 | 
				
			||||||
				RunInDirTimeout(
 | 
					 | 
				
			||||||
					time.Duration(setting.Git.Timeout.GC)*time.Second,
 | 
					 | 
				
			||||||
					repo.RepoPath()); err != nil {
 | 
					 | 
				
			||||||
				log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
 | 
									log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
 | 
				
			||||||
				return fmt.Errorf("Repository garbage collection failed: Error: %v", err)
 | 
									desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
 | 
				
			||||||
 | 
									if err = models.CreateRepositoryNotice(desc); err != nil {
 | 
				
			||||||
 | 
										log.Error("CreateRepositoryNotice: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@@ -90,7 +101,7 @@ func GitGcRepos(ctx context.Context) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func gatherMissingRepoRecords() ([]*models.Repository, error) {
 | 
					func gatherMissingRepoRecords(ctx context.Context) ([]*models.Repository, error) {
 | 
				
			||||||
	repos := make([]*models.Repository, 0, 10)
 | 
						repos := make([]*models.Repository, 0, 10)
 | 
				
			||||||
	if err := models.Iterate(
 | 
						if err := models.Iterate(
 | 
				
			||||||
		models.DefaultDBContext(),
 | 
							models.DefaultDBContext(),
 | 
				
			||||||
@@ -98,24 +109,33 @@ func gatherMissingRepoRecords() ([]*models.Repository, error) {
 | 
				
			|||||||
		builder.Gt{"id": 0},
 | 
							builder.Gt{"id": 0},
 | 
				
			||||||
		func(idx int, bean interface{}) error {
 | 
							func(idx int, bean interface{}) error {
 | 
				
			||||||
			repo := bean.(*models.Repository)
 | 
								repo := bean.(*models.Repository)
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case <-ctx.Done():
 | 
				
			||||||
 | 
									return models.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			if !com.IsDir(repo.RepoPath()) {
 | 
								if !com.IsDir(repo.RepoPath()) {
 | 
				
			||||||
				repos = append(repos, repo)
 | 
									repos = append(repos, repo)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	); err != nil {
 | 
						); err != nil {
 | 
				
			||||||
		if err2 := models.CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil {
 | 
							if strings.HasPrefix("Aborted gathering missing repo", err.Error()) {
 | 
				
			||||||
			return nil, fmt.Errorf("CreateRepositoryNotice: %v", err)
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if err2 := models.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
 | 
				
			||||||
 | 
								log.Error("CreateRepositoryNotice: %v", err2)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return repos, nil
 | 
						return repos, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeleteMissingRepositories deletes all repository records that lost Git files.
 | 
					// DeleteMissingRepositories deletes all repository records that lost Git files.
 | 
				
			||||||
func DeleteMissingRepositories(doer *models.User) error {
 | 
					func DeleteMissingRepositories(ctx context.Context, doer *models.User) error {
 | 
				
			||||||
	repos, err := gatherMissingRepoRecords()
 | 
						repos, err := gatherMissingRepoRecords(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("gatherMissingRepoRecords: %v", err)
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(repos) == 0 {
 | 
						if len(repos) == 0 {
 | 
				
			||||||
@@ -123,10 +143,16 @@ func DeleteMissingRepositories(doer *models.User) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, repo := range repos {
 | 
						for _, repo := range repos {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return models.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName())
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
 | 
							log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
 | 
				
			||||||
		if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
 | 
							if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
 | 
				
			||||||
			if err2 := models.CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil {
 | 
								log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err)
 | 
				
			||||||
				return fmt.Errorf("CreateRepositoryNotice: %v", err)
 | 
								if err2 := models.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
 | 
				
			||||||
 | 
									log.Error("CreateRepositoryNotice: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -134,10 +160,10 @@ func DeleteMissingRepositories(doer *models.User) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ReinitMissingRepositories reinitializes all repository records that lost Git files.
 | 
					// ReinitMissingRepositories reinitializes all repository records that lost Git files.
 | 
				
			||||||
func ReinitMissingRepositories() error {
 | 
					func ReinitMissingRepositories(ctx context.Context) error {
 | 
				
			||||||
	repos, err := gatherMissingRepoRecords()
 | 
						repos, err := gatherMissingRepoRecords(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("gatherMissingRepoRecords: %v", err)
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(repos) == 0 {
 | 
						if len(repos) == 0 {
 | 
				
			||||||
@@ -145,10 +171,16 @@ func ReinitMissingRepositories() error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, repo := range repos {
 | 
						for _, repo := range repos {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								return models.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName())
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
 | 
							log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
 | 
				
			||||||
		if err := git.InitRepository(repo.RepoPath(), true); err != nil {
 | 
							if err := git.InitRepository(repo.RepoPath(), true); err != nil {
 | 
				
			||||||
			if err2 := models.CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil {
 | 
								log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
 | 
				
			||||||
				return fmt.Errorf("CreateRepositoryNotice: %v", err)
 | 
								if err2 := models.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
 | 
				
			||||||
 | 
									log.Error("CreateRepositoryNotice: %v", err2)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -160,17 +160,18 @@ func SyncRepositoryHooks(ctx context.Context) error {
 | 
				
			|||||||
		new(models.Repository),
 | 
							new(models.Repository),
 | 
				
			||||||
		builder.Gt{"id": 0},
 | 
							builder.Gt{"id": 0},
 | 
				
			||||||
		func(idx int, bean interface{}) error {
 | 
							func(idx int, bean interface{}) error {
 | 
				
			||||||
 | 
								repo := bean.(*models.Repository)
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
				return fmt.Errorf("Aborted due to shutdown")
 | 
									return models.ErrCancelledf("before sync repository hooks for %s", repo.FullName())
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := createDelegateHooks(bean.(*models.Repository).RepoPath()); err != nil {
 | 
								if err := createDelegateHooks(repo.RepoPath()); err != nil {
 | 
				
			||||||
				return fmt.Errorf("SyncRepositoryHook: %v", err)
 | 
									return fmt.Errorf("SyncRepositoryHook: %v", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if bean.(*models.Repository).HasWiki() {
 | 
								if repo.HasWiki() {
 | 
				
			||||||
				if err := createDelegateHooks(bean.(*models.Repository).WikiPath()); err != nil {
 | 
									if err := createDelegateHooks(repo.WikiPath()); err != nil {
 | 
				
			||||||
					return fmt.Errorf("SyncRepositoryHook: %v", err)
 | 
										return fmt.Errorf("SyncRepositoryHook: %v", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,129 +4,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package setting
 | 
					package setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					// GetCronSettings maps the cron subsection to the provided config
 | 
				
			||||||
	"time"
 | 
					func GetCronSettings(name string, config interface{}) (interface{}, error) {
 | 
				
			||||||
 | 
						err := Cfg.Section("cron." + name).MapTo(config)
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						return config, err
 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Cron tasks
 | 
					 | 
				
			||||||
	Cron = struct {
 | 
					 | 
				
			||||||
		UpdateMirror struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
		} `ini:"cron.update_mirrors"`
 | 
					 | 
				
			||||||
		RepoHealthCheck struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
			Timeout    time.Duration
 | 
					 | 
				
			||||||
			Args       []string `delim:" "`
 | 
					 | 
				
			||||||
		} `ini:"cron.repo_health_check"`
 | 
					 | 
				
			||||||
		CheckRepoStats struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
		} `ini:"cron.check_repo_stats"`
 | 
					 | 
				
			||||||
		ArchiveCleanup struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
			OlderThan  time.Duration
 | 
					 | 
				
			||||||
		} `ini:"cron.archive_cleanup"`
 | 
					 | 
				
			||||||
		SyncExternalUsers struct {
 | 
					 | 
				
			||||||
			Enabled        bool
 | 
					 | 
				
			||||||
			RunAtStart     bool
 | 
					 | 
				
			||||||
			Schedule       string
 | 
					 | 
				
			||||||
			UpdateExisting bool
 | 
					 | 
				
			||||||
		} `ini:"cron.sync_external_users"`
 | 
					 | 
				
			||||||
		DeletedBranchesCleanup struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
			OlderThan  time.Duration
 | 
					 | 
				
			||||||
		} `ini:"cron.deleted_branches_cleanup"`
 | 
					 | 
				
			||||||
		UpdateMigrationPosterID struct {
 | 
					 | 
				
			||||||
			Schedule string
 | 
					 | 
				
			||||||
		} `ini:"cron.update_migration_poster_id"`
 | 
					 | 
				
			||||||
	}{
 | 
					 | 
				
			||||||
		UpdateMirror: struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Enabled:    true,
 | 
					 | 
				
			||||||
			RunAtStart: false,
 | 
					 | 
				
			||||||
			Schedule:   "@every 10m",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		RepoHealthCheck: struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
			Timeout    time.Duration
 | 
					 | 
				
			||||||
			Args       []string `delim:" "`
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Enabled:    true,
 | 
					 | 
				
			||||||
			RunAtStart: false,
 | 
					 | 
				
			||||||
			Schedule:   "@every 24h",
 | 
					 | 
				
			||||||
			Timeout:    60 * time.Second,
 | 
					 | 
				
			||||||
			Args:       []string{},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		CheckRepoStats: struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Enabled:    true,
 | 
					 | 
				
			||||||
			RunAtStart: true,
 | 
					 | 
				
			||||||
			Schedule:   "@every 24h",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		ArchiveCleanup: struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
			OlderThan  time.Duration
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Enabled:    true,
 | 
					 | 
				
			||||||
			RunAtStart: true,
 | 
					 | 
				
			||||||
			Schedule:   "@every 24h",
 | 
					 | 
				
			||||||
			OlderThan:  24 * time.Hour,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		SyncExternalUsers: struct {
 | 
					 | 
				
			||||||
			Enabled        bool
 | 
					 | 
				
			||||||
			RunAtStart     bool
 | 
					 | 
				
			||||||
			Schedule       string
 | 
					 | 
				
			||||||
			UpdateExisting bool
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Enabled:        true,
 | 
					 | 
				
			||||||
			RunAtStart:     false,
 | 
					 | 
				
			||||||
			Schedule:       "@every 24h",
 | 
					 | 
				
			||||||
			UpdateExisting: true,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		DeletedBranchesCleanup: struct {
 | 
					 | 
				
			||||||
			Enabled    bool
 | 
					 | 
				
			||||||
			RunAtStart bool
 | 
					 | 
				
			||||||
			Schedule   string
 | 
					 | 
				
			||||||
			OlderThan  time.Duration
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Enabled:    true,
 | 
					 | 
				
			||||||
			RunAtStart: true,
 | 
					 | 
				
			||||||
			Schedule:   "@every 24h",
 | 
					 | 
				
			||||||
			OlderThan:  24 * time.Hour,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		UpdateMigrationPosterID: struct {
 | 
					 | 
				
			||||||
			Schedule string
 | 
					 | 
				
			||||||
		}{
 | 
					 | 
				
			||||||
			Schedule: "@every 24h",
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func newCron() {
 | 
					 | 
				
			||||||
	if err := Cfg.Section("cron").MapTo(&Cron); err != nil {
 | 
					 | 
				
			||||||
		log.Fatal("Failed to map Cron settings: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -981,7 +981,6 @@ func NewContext() {
 | 
				
			|||||||
	u.Path = path.Join(u.Path, "api", "swagger")
 | 
						u.Path = path.Join(u.Path, "api", "swagger")
 | 
				
			||||||
	API.SwaggerURL = u.String()
 | 
						API.SwaggerURL = u.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	newCron()
 | 
					 | 
				
			||||||
	newGit()
 | 
						newGit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sec = Cfg.Section("mirror")
 | 
						sec = Cfg.Section("mirror")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1842,26 +1842,37 @@ dashboard.operation_switch = Switch
 | 
				
			|||||||
dashboard.operation_run = Run
 | 
					dashboard.operation_run = Run
 | 
				
			||||||
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
 | 
					dashboard.clean_unbind_oauth = Clean unbound OAuth connections
 | 
				
			||||||
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
 | 
					dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
 | 
				
			||||||
dashboard.delete_inactivate_accounts = Delete all unactivated accounts
 | 
					dashboard.task.started=Started Task: %s
 | 
				
			||||||
dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted.
 | 
					dashboard.task.process=Task: %s
 | 
				
			||||||
 | 
					dashboard.task.cancelled=Task: %s cancelled: %[3]s 
 | 
				
			||||||
 | 
					dashboard.task.error=Error in Task: %s: %[3]s
 | 
				
			||||||
 | 
					dashboard.task.finished=Task: %s started by %s has finished
 | 
				
			||||||
 | 
					dashboard.task.unknown=Unknown task: %s
 | 
				
			||||||
 | 
					dashboard.cron.started=Started Cron: %[1]s
 | 
				
			||||||
 | 
					dashboard.cron.process=Cron: %[1]s
 | 
				
			||||||
 | 
					dashboard.cron.cancelled=Cron: %s cancelled: %[3]s 
 | 
				
			||||||
 | 
					dashboard.cron.error=Error in Cron: %s: %[3]s
 | 
				
			||||||
 | 
					dashboard.cron.finished=Cron: %[1]s has finished
 | 
				
			||||||
 | 
					dashboard.delete_inactive_accounts = Delete all unactivated accounts
 | 
				
			||||||
 | 
					dashboard.delete_inactive_accounts.started = Delete all unactivated accounts task started.
 | 
				
			||||||
dashboard.delete_repo_archives = Delete all repository archives
 | 
					dashboard.delete_repo_archives = Delete all repository archives
 | 
				
			||||||
dashboard.delete_repo_archives_success = All repository archives have been deleted.
 | 
					dashboard.delete_repo_archives.started = Delete all repository archives task started.
 | 
				
			||||||
dashboard.delete_missing_repos = Delete all repositories missing their Git files
 | 
					dashboard.delete_missing_repos = Delete all repositories missing their Git files
 | 
				
			||||||
dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted.
 | 
					dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
 | 
				
			||||||
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
 | 
					dashboard.delete_generated_repository_avatars = Delete generated repository avatars
 | 
				
			||||||
dashboard.delete_generated_repository_avatars_success = Generated repository avatars were deleted.
 | 
					dashboard.update_mirrors = Update Mirrors
 | 
				
			||||||
 | 
					dashboard.repo_health_check = Health check all repositories
 | 
				
			||||||
 | 
					dashboard.check_repo_stats = Check all repository statistics
 | 
				
			||||||
 | 
					dashboard.archive_cleanup = Delete old repository archives
 | 
				
			||||||
 | 
					dashboard.deleted_branches_cleanup = Clean-up deleted branches
 | 
				
			||||||
 | 
					dashboard.update_migration_poster_id = Update migration poster IDs
 | 
				
			||||||
dashboard.git_gc_repos = Garbage collect all repositories
 | 
					dashboard.git_gc_repos = Garbage collect all repositories
 | 
				
			||||||
dashboard.git_gc_repos_success = All repositories have finished garbage collection.
 | 
					dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys.
 | 
				
			||||||
dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)
 | 
					dashboard.resync_all_sshkeys.desc = (Not needed for the built-in SSH server.)
 | 
				
			||||||
dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated.
 | 
					 | 
				
			||||||
dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories.
 | 
					dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories.
 | 
				
			||||||
dashboard.resync_all_hooks_success = All pre-receive, update and post-receive repository hooks have been resynchronized.
 | 
					 | 
				
			||||||
dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist
 | 
					dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist
 | 
				
			||||||
dashboard.reinit_missing_repos_success = All missing Git repositories for which records existed have been reinitialized.
 | 
					 | 
				
			||||||
dashboard.sync_external_users = Synchronize external user data
 | 
					dashboard.sync_external_users = Synchronize external user data
 | 
				
			||||||
dashboard.sync_external_users_started = External user data synchronization has started.
 | 
					 | 
				
			||||||
dashboard.git_fsck = Execute health checks on all repositories
 | 
					dashboard.git_fsck = Execute health checks on all repositories
 | 
				
			||||||
dashboard.git_fsck_started = Repository health checks have started.
 | 
					 | 
				
			||||||
dashboard.server_uptime = Server Uptime
 | 
					dashboard.server_uptime = Server Uptime
 | 
				
			||||||
dashboard.current_goroutine = Current Goroutines
 | 
					dashboard.current_goroutine = Current Goroutines
 | 
				
			||||||
dashboard.current_memory_usage = Current Memory Usage
 | 
					dashboard.current_memory_usage = Current Memory Usage
 | 
				
			||||||
@@ -2266,6 +2277,7 @@ notices.delete_selected = Delete Selected
 | 
				
			|||||||
notices.delete_all = Delete All Notices
 | 
					notices.delete_all = Delete All Notices
 | 
				
			||||||
notices.type = Type
 | 
					notices.type = Type
 | 
				
			||||||
notices.type_1 = Repository
 | 
					notices.type_1 = Repository
 | 
				
			||||||
 | 
					notices.type_2 = Task
 | 
				
			||||||
notices.desc = Description
 | 
					notices.desc = Description
 | 
				
			||||||
notices.op = Op.
 | 
					notices.op = Op.
 | 
				
			||||||
notices.delete_success = The system notices have been deleted.
 | 
					notices.delete_success = The system notices have been deleted.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,11 +21,9 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/cron"
 | 
						"code.gitea.io/gitea/modules/cron"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/graceful"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/process"
 | 
						"code.gitea.io/gitea/modules/process"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/queue"
 | 
						"code.gitea.io/gitea/modules/queue"
 | 
				
			||||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
	"code.gitea.io/gitea/services/mailer"
 | 
						"code.gitea.io/gitea/services/mailer"
 | 
				
			||||||
@@ -124,22 +122,6 @@ func updateSystemStatus() {
 | 
				
			|||||||
	sysStatus.NumGC = m.NumGC
 | 
						sysStatus.NumGC = m.NumGC
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Operation Operation types.
 | 
					 | 
				
			||||||
type Operation int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	cleanInactivateUser Operation = iota + 1
 | 
					 | 
				
			||||||
	cleanRepoArchives
 | 
					 | 
				
			||||||
	cleanMissingRepos
 | 
					 | 
				
			||||||
	gitGCRepos
 | 
					 | 
				
			||||||
	syncSSHAuthorizedKey
 | 
					 | 
				
			||||||
	syncRepositoryUpdateHook
 | 
					 | 
				
			||||||
	reinitMissingRepository
 | 
					 | 
				
			||||||
	syncExternalUsers
 | 
					 | 
				
			||||||
	gitFsck
 | 
					 | 
				
			||||||
	deleteGeneratedRepositoryAvatars
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Dashboard show admin panel dashboard
 | 
					// Dashboard show admin panel dashboard
 | 
				
			||||||
func Dashboard(ctx *context.Context) {
 | 
					func Dashboard(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 | 
						ctx.Data["Title"] = ctx.Tr("admin.dashboard")
 | 
				
			||||||
@@ -162,48 +144,13 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
 | 
				
			|||||||
	ctx.Data["SysStatus"] = sysStatus
 | 
						ctx.Data["SysStatus"] = sysStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Run operation.
 | 
						// Run operation.
 | 
				
			||||||
	if form.Op > 0 {
 | 
						if form.Op != "" {
 | 
				
			||||||
		var err error
 | 
							task := cron.GetTask(form.Op)
 | 
				
			||||||
		var success string
 | 
							if task != nil {
 | 
				
			||||||
		shutdownCtx := graceful.GetManager().ShutdownContext()
 | 
								go task.RunWithUser(ctx.User, nil)
 | 
				
			||||||
 | 
								ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
 | 
				
			||||||
		switch Operation(form.Op) {
 | 
					 | 
				
			||||||
		case cleanInactivateUser:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.delete_inactivate_accounts_success")
 | 
					 | 
				
			||||||
			err = models.DeleteInactivateUsers()
 | 
					 | 
				
			||||||
		case cleanRepoArchives:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.delete_repo_archives_success")
 | 
					 | 
				
			||||||
			err = models.DeleteRepositoryArchives()
 | 
					 | 
				
			||||||
		case cleanMissingRepos:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.delete_missing_repos_success")
 | 
					 | 
				
			||||||
			err = repo_module.DeleteMissingRepositories(ctx.User)
 | 
					 | 
				
			||||||
		case gitGCRepos:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.git_gc_repos_success")
 | 
					 | 
				
			||||||
			err = repo_module.GitGcRepos(shutdownCtx)
 | 
					 | 
				
			||||||
		case syncSSHAuthorizedKey:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success")
 | 
					 | 
				
			||||||
			err = models.RewriteAllPublicKeys()
 | 
					 | 
				
			||||||
		case syncRepositoryUpdateHook:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.resync_all_hooks_success")
 | 
					 | 
				
			||||||
			err = repo_module.SyncRepositoryHooks(shutdownCtx)
 | 
					 | 
				
			||||||
		case reinitMissingRepository:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.reinit_missing_repos_success")
 | 
					 | 
				
			||||||
			err = repo_module.ReinitMissingRepositories()
 | 
					 | 
				
			||||||
		case syncExternalUsers:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.sync_external_users_started")
 | 
					 | 
				
			||||||
			go graceful.GetManager().RunWithShutdownContext(models.SyncExternalUsers)
 | 
					 | 
				
			||||||
		case gitFsck:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.git_fsck_started")
 | 
					 | 
				
			||||||
			err = repo_module.GitFsck(shutdownCtx)
 | 
					 | 
				
			||||||
		case deleteGeneratedRepositoryAvatars:
 | 
					 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success")
 | 
					 | 
				
			||||||
			err = models.RemoveRandomAvatars()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			ctx.Flash.Error(err.Error())
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.Flash.Success(success)
 | 
								ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/markup"
 | 
						"code.gitea.io/gitea/modules/markup"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/markup/external"
 | 
						"code.gitea.io/gitea/modules/markup/external"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/notification"
 | 
						"code.gitea.io/gitea/modules/notification"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/options"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/ssh"
 | 
						"code.gitea.io/gitea/modules/ssh"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/task"
 | 
						"code.gitea.io/gitea/modules/task"
 | 
				
			||||||
@@ -33,7 +34,9 @@ import (
 | 
				
			|||||||
	mirror_service "code.gitea.io/gitea/services/mirror"
 | 
						mirror_service "code.gitea.io/gitea/services/mirror"
 | 
				
			||||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
						pull_service "code.gitea.io/gitea/services/pull"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitea.com/macaron/i18n"
 | 
				
			||||||
	"gitea.com/macaron/macaron"
 | 
						"gitea.com/macaron/macaron"
 | 
				
			||||||
 | 
						unknwoni18n "github.com/unknwon/i18n"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func checkRunMode() {
 | 
					func checkRunMode() {
 | 
				
			||||||
@@ -79,6 +82,33 @@ func initDBEngine(ctx context.Context) (err error) {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitLocales loads the locales
 | 
				
			||||||
 | 
					func InitLocales() {
 | 
				
			||||||
 | 
						localeNames, err := options.Dir("locale")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Failed to list locale files: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						localFiles := make(map[string][]byte)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, name := range localeNames {
 | 
				
			||||||
 | 
							localFiles[name], err = options.Locale(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatal("Failed to load %s locale file. %v", name, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						i18n.I18n(i18n.Options{
 | 
				
			||||||
 | 
							SubURL:       setting.AppSubURL,
 | 
				
			||||||
 | 
							Files:        localFiles,
 | 
				
			||||||
 | 
							Langs:        setting.Langs,
 | 
				
			||||||
 | 
							Names:        setting.Names,
 | 
				
			||||||
 | 
							DefaultLang:  "en-US",
 | 
				
			||||||
 | 
							Redirect:     false,
 | 
				
			||||||
 | 
							CookieDomain: setting.SessionConfig.Domain,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GlobalInit is for global configuration reload-able.
 | 
					// GlobalInit is for global configuration reload-able.
 | 
				
			||||||
func GlobalInit(ctx context.Context) {
 | 
					func GlobalInit(ctx context.Context) {
 | 
				
			||||||
	setting.NewContext()
 | 
						setting.NewContext()
 | 
				
			||||||
@@ -91,6 +121,11 @@ func GlobalInit(ctx context.Context) {
 | 
				
			|||||||
	log.Trace("Custom path: %s", setting.CustomPath)
 | 
						log.Trace("Custom path: %s", setting.CustomPath)
 | 
				
			||||||
	log.Trace("Log path: %s", setting.LogRootPath)
 | 
						log.Trace("Log path: %s", setting.LogRootPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup i18n
 | 
				
			||||||
 | 
						InitLocales()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Info("%s", unknwoni18n.Tr("en-US", "admin.dashboard.delete_repo_archives"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	NewServices()
 | 
						NewServices()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if setting.InstallLock {
 | 
						if setting.InstallLock {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -296,7 +296,7 @@ func Password(m *models.Mirror) string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Update checks and updates mirror repositories.
 | 
					// Update checks and updates mirror repositories.
 | 
				
			||||||
func Update(ctx context.Context) {
 | 
					func Update(ctx context.Context) error {
 | 
				
			||||||
	log.Trace("Doing: Update")
 | 
						log.Trace("Doing: Update")
 | 
				
			||||||
	if err := models.MirrorsIterate(func(idx int, bean interface{}) error {
 | 
						if err := models.MirrorsIterate(func(idx int, bean interface{}) error {
 | 
				
			||||||
		m := bean.(*models.Mirror)
 | 
							m := bean.(*models.Mirror)
 | 
				
			||||||
@@ -306,14 +306,17 @@ func Update(ctx context.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-ctx.Done():
 | 
				
			||||||
			return fmt.Errorf("Aborted due to shutdown")
 | 
								return fmt.Errorf("Aborted")
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			mirrorQueue.Add(m.RepoID)
 | 
								mirrorQueue.Add(m.RepoID)
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		log.Error("Update: %v", err)
 | 
							log.Trace("Update: %v", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						log.Trace("Finished: Update")
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SyncMirrors checks and syncs mirrors.
 | 
					// SyncMirrors checks and syncs mirrors.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,49 +15,50 @@
 | 
				
			|||||||
			{{.i18n.Tr "admin.dashboard.operations"}}
 | 
								{{.i18n.Tr "admin.dashboard.operations"}}
 | 
				
			||||||
		</h4>
 | 
							</h4>
 | 
				
			||||||
		<div class="ui attached table segment">
 | 
							<div class="ui attached table segment">
 | 
				
			||||||
			<form method="post" action="{{AppSubUrl}}/admin">
 | 
								<form method="post" action="{{.AppSubUrl}}/admin">
 | 
				
			||||||
				{{.CsrfTokenHtml}}
 | 
									{{.CsrfTokenHtml}}
 | 
				
			||||||
				<table class="ui very basic table">
 | 
									<table class="ui very basic table">
 | 
				
			||||||
					<tbody>
 | 
										<tbody>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.delete_inactivate_accounts"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.delete_inactive_accounts"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="1">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="delete_inactive_accounts">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.delete_repo_archives"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.delete_repo_archives"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="2">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="delete_repo_archives">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.delete_missing_repos"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.delete_missing_repos"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="3">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="delete_missing_repos">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.git_gc_repos"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.git_gc_repos"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="4">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="git_gc_repos">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.resync_all_sshkeys"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.resync_all_sshkeys"}}<br/>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="5">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												{{.i18n.Tr "admin.dashboard.resync_all_sshkeys.desc"}}</td>
 | 
				
			||||||
 | 
												<td><button type="submit" class="ui green button" name="op" value="resync_all_sshkeys">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.resync_all_hooks"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.resync_all_hooks"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="6">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="resync_all_hooks">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.reinit_missing_repos"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.reinit_missing_repos"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="7">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="reinit_missing_repos">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.sync_external_users"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.sync_external_users"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="8">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="sync_external_users">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.git_fsck"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.git_fsck"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="9">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="git_fsck">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.i18n.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
 | 
												<td>{{.i18n.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
 | 
				
			||||||
							<td><button type="submit" class="ui green button" name="op" value="10">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-triangle-right" 16}} {{.i18n.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
					</tbody>
 | 
										</tbody>
 | 
				
			||||||
				</table>
 | 
									</table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,28 +7,33 @@
 | 
				
			|||||||
			{{.i18n.Tr "admin.monitor.cron"}}
 | 
								{{.i18n.Tr "admin.monitor.cron"}}
 | 
				
			||||||
		</h4>
 | 
							</h4>
 | 
				
			||||||
		<div class="ui attached table segment">
 | 
							<div class="ui attached table segment">
 | 
				
			||||||
			<table class="ui very basic striped table">
 | 
								<form method="post" action="{{.AppSubUrl}}/admin">
 | 
				
			||||||
				<thead>
 | 
									{{.CsrfTokenHtml}}
 | 
				
			||||||
					<tr>
 | 
									<table class="ui very basic striped table">
 | 
				
			||||||
						<th>{{.i18n.Tr "admin.monitor.name"}}</th>
 | 
										<thead>
 | 
				
			||||||
						<th>{{.i18n.Tr "admin.monitor.schedule"}}</th>
 | 
					 | 
				
			||||||
						<th>{{.i18n.Tr "admin.monitor.next"}}</th>
 | 
					 | 
				
			||||||
						<th>{{.i18n.Tr "admin.monitor.previous"}}</th>
 | 
					 | 
				
			||||||
						<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th>
 | 
					 | 
				
			||||||
					</tr>
 | 
					 | 
				
			||||||
				</thead>
 | 
					 | 
				
			||||||
				<tbody>
 | 
					 | 
				
			||||||
					{{range .Entries}}
 | 
					 | 
				
			||||||
						<tr>
 | 
											<tr>
 | 
				
			||||||
							<td>{{.Description}}</td>
 | 
												<th></th>
 | 
				
			||||||
							<td>{{.Spec}}</td>
 | 
												<th>{{.i18n.Tr "admin.monitor.name"}}</th>
 | 
				
			||||||
							<td>{{DateFmtLong .Next}}</td>
 | 
												<th>{{.i18n.Tr "admin.monitor.schedule"}}</th>
 | 
				
			||||||
							<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
 | 
												<th>{{.i18n.Tr "admin.monitor.next"}}</th>
 | 
				
			||||||
							<td>{{.ExecTimes}}</td>
 | 
												<th>{{.i18n.Tr "admin.monitor.previous"}}</th>
 | 
				
			||||||
 | 
												<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
					{{end}}
 | 
										</thead>
 | 
				
			||||||
				</tbody>
 | 
										<tbody>
 | 
				
			||||||
			</table>
 | 
											{{range .Entries}}
 | 
				
			||||||
 | 
												<tr>
 | 
				
			||||||
 | 
													<td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.i18n.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right" 16}}</button></td>
 | 
				
			||||||
 | 
													<td>{{$.i18n.Tr (printf "admin.dashboard.%s" .Name)}}</td>
 | 
				
			||||||
 | 
													<td>{{.Spec}}</td>
 | 
				
			||||||
 | 
													<td>{{DateFmtLong .Next}}</td>
 | 
				
			||||||
 | 
													<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
 | 
				
			||||||
 | 
													<td>{{.ExecTimes}}</td>
 | 
				
			||||||
 | 
												</tr>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
										</tbody>
 | 
				
			||||||
 | 
									</table>
 | 
				
			||||||
 | 
								</form>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<h4 class="ui top attached header">
 | 
							<h4 class="ui top attached header">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user