mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-26 10:41:55 +00:00
before: gitrepo vs git packages after: git package fully handle all git operations by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide path details. benefits: 1. remove all unnecessary wrappers, developers no need to struggle with "which package should be used" 2. simplify code, RepositoryFacade can (will) be used everywhere, all "path" details are (will be) hidden
201 lines
6.4 KiB
Go
201 lines
6.4 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.dev/models/db"
|
|
repo_model "gitea.dev/models/repo"
|
|
system_model "gitea.dev/models/system"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/git/gitcmd"
|
|
"gitea.dev/modules/log"
|
|
repo_module "gitea.dev/modules/repository"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// GitFsckRepos calls 'git fsck' to check repository health.
|
|
func GitFsckRepos(ctx context.Context, timeout time.Duration, args gitcmd.TrustedCmdArgs) error {
|
|
log.Trace("Doing: GitFsck")
|
|
|
|
if err := db.Iterate(
|
|
ctx,
|
|
builder.Expr("id>0 AND is_fsck_enabled=?", true),
|
|
func(ctx context.Context, repo *repo_model.Repository) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return db.ErrCancelledf("before fsck of %s", repo.FullName())
|
|
default:
|
|
}
|
|
return GitFsckRepo(ctx, repo, timeout, args)
|
|
},
|
|
); err != nil {
|
|
log.Trace("Error: GitFsck: %v", err)
|
|
return err
|
|
}
|
|
|
|
log.Trace("Finished: GitFsck")
|
|
return nil
|
|
}
|
|
|
|
// GitFsckRepo calls 'git fsck' to check an individual repository's health.
|
|
func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args gitcmd.TrustedCmdArgs) error {
|
|
log.Trace("Running health check on repository %-v", repo.FullName())
|
|
if err := git.Fsck(ctx, repo, timeout, args); err != nil {
|
|
log.Warn("Failed to health check repository (%-v): %v", repo.FullName(), err)
|
|
if err = system_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
|
|
log.Error("CreateRepositoryNotice: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
|
|
func GitGcRepos(ctx context.Context, timeout time.Duration, args gitcmd.TrustedCmdArgs) error {
|
|
log.Trace("Doing: GitGcRepos")
|
|
|
|
if err := db.Iterate(
|
|
ctx,
|
|
builder.Gt{"id": 0},
|
|
func(ctx context.Context, repo *repo_model.Repository) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return db.ErrCancelledf("before GC of %s", repo.FullName())
|
|
default:
|
|
}
|
|
// we can ignore the error here because it will be logged in GitGCRepo
|
|
_ = GitGcRepo(ctx, repo, timeout, args)
|
|
return nil
|
|
},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Trace("Finished: GitGcRepos")
|
|
return nil
|
|
}
|
|
|
|
// GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository
|
|
func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args gitcmd.TrustedCmdArgs) error {
|
|
log.Trace("Running git gc on %-v", repo)
|
|
command := gitcmd.NewCommand("gc").AddArguments(args...)
|
|
var stdout string
|
|
var err error
|
|
stdout, _, err = command.WithRepo(repo).RunStdString(ctx)
|
|
if err != nil {
|
|
log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
|
desc := fmt.Sprintf("Repository garbage collection failed (%s). Stdout: %s\nError: %v", repo.FullName(), stdout, err)
|
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
|
log.Error("CreateRepositoryNotice: %v", err)
|
|
}
|
|
return fmt.Errorf("repository garbage collection failed in repo: %s, error: %w", repo.FullName(), err)
|
|
}
|
|
|
|
// Now update the size of the repository
|
|
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
|
|
log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
|
|
desc := fmt.Sprintf("Updating size as part of garbage collection failed (%s). Stdout: %s\nError: %v", repo.FullName(), stdout, err)
|
|
if err := system_model.CreateRepositoryNotice(desc); err != nil {
|
|
log.Error("CreateRepositoryNotice: %v", err)
|
|
}
|
|
return fmt.Errorf("updating size as part of garbage collection failed in repo: %s, error: %w", repo.FullName(), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func gatherMissingRepoRecords(ctx context.Context) (repo_model.RepositoryList, error) {
|
|
repos := make([]*repo_model.Repository, 0, 10)
|
|
if err := db.Iterate(
|
|
ctx,
|
|
builder.Gt{"id": 0},
|
|
func(ctx context.Context, repo *repo_model.Repository) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
|
|
default:
|
|
}
|
|
exist, err := git.IsRepositoryExist(ctx, repo)
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err)
|
|
}
|
|
if !exist {
|
|
repos = append(repos, repo)
|
|
}
|
|
return nil
|
|
},
|
|
); err != nil {
|
|
if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") {
|
|
return nil, err
|
|
}
|
|
if err2 := system_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
|
|
log.Error("CreateRepositoryNotice: %v", err2)
|
|
}
|
|
return nil, err
|
|
}
|
|
return repos, nil
|
|
}
|
|
|
|
// DeleteMissingRepositories deletes all repository records that lost Git files.
|
|
func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error {
|
|
repos, err := gatherMissingRepoRecords(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(repos) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, repo := range repos {
|
|
select {
|
|
case <-ctx.Done():
|
|
return db.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName())
|
|
default:
|
|
}
|
|
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
|
if err := DeleteRepositoryDirectly(ctx, repo.ID); err != nil {
|
|
log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
|
|
if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository (%s) [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
|
log.Error("CreateRepositoryNotice: %v", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReinitMissingRepositories reinitializes all repository records that lost Git files.
|
|
func ReinitMissingRepositories(ctx context.Context) error {
|
|
repos, err := gatherMissingRepoRecords(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(repos) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, repo := range repos {
|
|
select {
|
|
case <-ctx.Done():
|
|
return db.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName())
|
|
default:
|
|
}
|
|
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
|
|
if err := git.InitRepository(ctx, repo, repo.ObjectFormatName); err != nil {
|
|
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.FullName(), err)
|
|
if err2 := system_model.CreateRepositoryNotice("InitRepository (%s) [%d]: %v", repo.FullName(), repo.ID, err); err2 != nil {
|
|
log.Error("CreateRepositoryNotice: %v", err2)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|