code.gitea.io/gitea@v1.22.3/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 if err != nil { 95 log.Error("Repository garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err) 96 desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) 97 if err := system_model.CreateRepositoryNotice(desc); err != nil { 98 log.Error("CreateRepositoryNotice: %v", err) 99 } 100 return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %w", repo.FullName(), err) 101 } 102 103 // Now update the size of the repository 104 if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { 105 log.Error("Updating size as part of garbage collection failed for %-v. Stdout: %s\nError: %v", repo, stdout, err) 106 desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) 107 if err := system_model.CreateRepositoryNotice(desc); err != nil { 108 log.Error("CreateRepositoryNotice: %v", err) 109 } 110 return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %w", repo.FullName(), err) 111 } 112 113 return nil 114 } 115 116 func gatherMissingRepoRecords(ctx context.Context) (repo_model.RepositoryList, error) { 117 repos := make([]*repo_model.Repository, 0, 10) 118 if err := db.Iterate( 119 ctx, 120 builder.Gt{"id": 0}, 121 func(ctx context.Context, repo *repo_model.Repository) error { 122 select { 123 case <-ctx.Done(): 124 return db.ErrCancelledf("during gathering missing repo records before checking %s", repo.FullName()) 125 default: 126 } 127 isDir, err := util.IsDir(repo.RepoPath()) 128 if err != nil { 129 return fmt.Errorf("Unable to check dir for %s. %w", repo.FullName(), err) 130 } 131 if !isDir { 132 repos = append(repos, repo) 133 } 134 return nil 135 }, 136 ); err != nil { 137 if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") { 138 return nil, err 139 } 140 if err2 := system_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil { 141 log.Error("CreateRepositoryNotice: %v", err2) 142 } 143 return nil, err 144 } 145 return repos, nil 146 } 147 148 // DeleteMissingRepositories deletes all repository records that lost Git files. 149 func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error { 150 repos, err := gatherMissingRepoRecords(ctx) 151 if err != nil { 152 return err 153 } 154 155 if len(repos) == 0 { 156 return nil 157 } 158 159 for _, repo := range repos { 160 select { 161 case <-ctx.Done(): 162 return db.ErrCancelledf("during DeleteMissingRepositories before %s", repo.FullName()) 163 default: 164 } 165 log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) 166 if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil { 167 log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err) 168 if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil { 169 log.Error("CreateRepositoryNotice: %v", err) 170 } 171 } 172 } 173 return nil 174 } 175 176 // ReinitMissingRepositories reinitializes all repository records that lost Git files. 177 func ReinitMissingRepositories(ctx context.Context) error { 178 repos, err := gatherMissingRepoRecords(ctx) 179 if err != nil { 180 return err 181 } 182 183 if len(repos) == 0 { 184 return nil 185 } 186 187 for _, repo := range repos { 188 select { 189 case <-ctx.Done(): 190 return db.ErrCancelledf("during ReinitMissingRepositories before %s", repo.FullName()) 191 default: 192 } 193 log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) 194 if err := git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil { 195 log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err) 196 if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil { 197 log.Error("CreateRepositoryNotice: %v", err2) 198 } 199 } 200 } 201 return nil 202 }