code.gitea.io/gitea@v1.21.7/services/repository/check.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repository
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  	"time"
    11  
    12  	"code.gitea.io/gitea/models/db"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	system_model "code.gitea.io/gitea/models/system"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/git"
    17  	"code.gitea.io/gitea/modules/log"
    18  	repo_module "code.gitea.io/gitea/modules/repository"
    19  	"code.gitea.io/gitea/modules/util"
    20  
    21  	"xorm.io/builder"
    22  )
    23  
    24  // GitFsckRepos calls 'git fsck' to check repository health.
    25  func GitFsckRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdArgs) error {
    26  	log.Trace("Doing: GitFsck")
    27  
    28  	if err := db.Iterate(
    29  		ctx,
    30  		builder.Expr("id>0 AND is_fsck_enabled=?", true),
    31  		func(ctx context.Context, repo *repo_model.Repository) error {
    32  			select {
    33  			case <-ctx.Done():
    34  				return db.ErrCancelledf("before fsck of %s", repo.FullName())
    35  			default:
    36  			}
    37  			return GitFsckRepo(ctx, repo, timeout, args)
    38  		},
    39  	); err != nil {
    40  		log.Trace("Error: GitFsck: %v", err)
    41  		return err
    42  	}
    43  
    44  	log.Trace("Finished: GitFsck")
    45  	return nil
    46  }
    47  
    48  // GitFsckRepo calls 'git fsck' to check an individual repository's health.
    49  func GitFsckRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error {
    50  	log.Trace("Running health check on repository %-v", repo)
    51  	repoPath := repo.RepoPath()
    52  	if err := git.Fsck(ctx, repoPath, timeout, args); err != nil {
    53  		log.Warn("Failed to health check repository (%-v): %v", repo, err)
    54  		if err = system_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
    55  			log.Error("CreateRepositoryNotice: %v", err)
    56  		}
    57  	}
    58  	return nil
    59  }
    60  
    61  // GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository
    62  func GitGcRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdArgs) error {
    63  	log.Trace("Doing: GitGcRepos")
    64  
    65  	if err := db.Iterate(
    66  		ctx,
    67  		builder.Gt{"id": 0},
    68  		func(ctx context.Context, repo *repo_model.Repository) error {
    69  			select {
    70  			case <-ctx.Done():
    71  				return db.ErrCancelledf("before GC of %s", repo.FullName())
    72  			default:
    73  			}
    74  			// we can ignore the error here because it will be logged in GitGCRepo
    75  			_ = GitGcRepo(ctx, repo, timeout, args)
    76  			return nil
    77  		},
    78  	); err != nil {
    79  		return err
    80  	}
    81  
    82  	log.Trace("Finished: GitGcRepos")
    83  	return nil
    84  }
    85  
    86  // GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository
    87  func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error {
    88  	log.Trace("Running git gc on %-v", repo)
    89  	command := git.NewCommand(ctx, "gc").AddArguments(args...).
    90  		SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName()))
    91  	var stdout string
    92  	var err error
    93  	stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()})
    94  
    95  	if err != nil {
    96  		log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
    97  		desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
    98  		if err := system_model.CreateRepositoryNotice(desc); err != nil {
    99  			log.Error("CreateRepositoryNotice: %v", err)
   100  		}
   101  		return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
   102  	}
   103  
   104  	// Now update the size of the repository
   105  	if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
   106  		log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err)
   107  		desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
   108  		if err := system_model.CreateRepositoryNotice(desc); err != nil {
   109  			log.Error("CreateRepositoryNotice: %v", err)
   110  		}
   111  		return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err)
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func gatherMissingRepoRecords(ctx context.Context) (repo_model.RepositoryList, error) {
   118  	repos := make([]*repo_model.Repository, 0, 10)
   119  	if err := db.Iterate(
   120  		ctx,
   121  		builder.Gt{"id": 0},
   122  		func(ctx context.Context, repo *repo_model.Repository) error {
   123  			select {
   124  			case <-ctx.Done():
   125  				return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName())
   126  			default:
   127  			}
   128  			isDir, err := util.IsDir(repo.RepoPath())
   129  			if err != nil {
   130  				return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err)
   131  			}
   132  			if !isDir {
   133  				repos = append(repos, repo)
   134  			}
   135  			return nil
   136  		},
   137  	); err != nil {
   138  		if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") {
   139  			return nil, err
   140  		}
   141  		if err2 := system_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
   142  			log.Error("CreateRepositoryNotice: %v", err2)
   143  		}
   144  		return nil, err
   145  	}
   146  	return repos, nil
   147  }
   148  
   149  // DeleteMissingRepositories deletes all repository records that lost Git files.
   150  func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error {
   151  	repos, err := gatherMissingRepoRecords(ctx)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	if len(repos) == 0 {
   157  		return nil
   158  	}
   159  
   160  	for _, repo := range repos {
   161  		select {
   162  		case <-ctx.Done():
   163  			return db.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName())
   164  		default:
   165  		}
   166  		log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
   167  		if err := DeleteRepositoryDirectly(ctx, doer, repo.OwnerID, repo.ID); err != nil {
   168  			log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err)
   169  			if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
   170  				log.Error("CreateRepositoryNotice: %v", err)
   171  			}
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  // ReinitMissingRepositories reinitializes all repository records that lost Git files.
   178  func ReinitMissingRepositories(ctx context.Context) error {
   179  	repos, err := gatherMissingRepoRecords(ctx)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	if len(repos) == 0 {
   185  		return nil
   186  	}
   187  
   188  	for _, repo := range repos {
   189  		select {
   190  		case <-ctx.Done():
   191  			return db.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName())
   192  		default:
   193  		}
   194  		log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
   195  		if err := git.InitRepository(ctx, repo.RepoPath(), true); err != nil {
   196  			log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
   197  			if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
   198  				log.Error("CreateRepositoryNotice: %v", err2)
   199  			}
   200  		}
   201  	}
   202  	return nil
   203  }