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 }