code.gitea.io/gitea@v1.22.3/services/doctor/misc.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package doctor 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "os/exec" 11 "path" 12 "strings" 13 14 "code.gitea.io/gitea/models" 15 "code.gitea.io/gitea/models/db" 16 repo_model "code.gitea.io/gitea/models/repo" 17 user_model "code.gitea.io/gitea/models/user" 18 "code.gitea.io/gitea/modules/git" 19 "code.gitea.io/gitea/modules/gitrepo" 20 "code.gitea.io/gitea/modules/log" 21 "code.gitea.io/gitea/modules/repository" 22 "code.gitea.io/gitea/modules/setting" 23 "code.gitea.io/gitea/modules/structs" 24 "code.gitea.io/gitea/modules/util" 25 26 lru "github.com/hashicorp/golang-lru/v2" 27 "xorm.io/builder" 28 ) 29 30 func iterateRepositories(ctx context.Context, each func(*repo_model.Repository) error) error { 31 err := db.Iterate( 32 ctx, 33 builder.Gt{"id": 0}, 34 func(ctx context.Context, bean *repo_model.Repository) error { 35 return each(bean) 36 }, 37 ) 38 return err 39 } 40 41 func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error { 42 path, err := exec.LookPath(setting.ScriptType) 43 if err != nil { 44 logger.Critical("ScriptType \"%q\" is not on the current PATH. Error: %v", setting.ScriptType, err) 45 return fmt.Errorf("ScriptType \"%q\" is not on the current PATH. Error: %w", setting.ScriptType, err) 46 } 47 logger.Info("ScriptType %s is on the current PATH at %s", setting.ScriptType, path) 48 return nil 49 } 50 51 func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error { 52 if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { 53 results, err := repository.CheckDelegateHooks(repo.RepoPath()) 54 if err != nil { 55 logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err) 56 return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err) 57 } 58 if len(results) > 0 && autofix { 59 logger.Warn("Regenerated hooks for %s", repo.FullName()) 60 if err := repository.CreateDelegateHooks(repo.RepoPath()); err != nil { 61 logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err) 62 return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err) 63 } 64 } 65 for _, result := range results { 66 logger.Warn(result) 67 } 68 return nil 69 }); err != nil { 70 logger.Critical("Errors noted whilst checking delegate hooks.") 71 return err 72 } 73 return nil 74 } 75 76 func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) error { 77 if autofix { 78 if err := models.DoctorUserStarNum(ctx); err != nil { 79 logger.Critical("Unable update User Stars numbers") 80 return err 81 } 82 logger.Info("Updated User Stars numbers.") 83 } else { 84 logger.Info("No check available for User Stars numbers (skipped)") 85 } 86 return nil 87 } 88 89 func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool) error { 90 numRepos := 0 91 numNeedUpdate := 0 92 93 if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { 94 numRepos++ 95 r, err := gitrepo.OpenRepository(ctx, repo) 96 if err != nil { 97 return err 98 } 99 defer r.Close() 100 101 if autofix { 102 _, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunStdString(&git.RunOpts{Dir: r.Path}) 103 return err 104 } 105 106 value, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunStdString(&git.RunOpts{Dir: r.Path}) 107 if err != nil { 108 return err 109 } 110 111 result, valid := git.ParseBool(strings.TrimSpace(value)) 112 if !result || !valid { 113 numNeedUpdate++ 114 logger.Info("%s: does not have receive.advertisePushOptions set correctly: %q", repo.FullName(), value) 115 } 116 return nil 117 }); err != nil { 118 logger.Critical("Unable to EnablePushOptions: %v", err) 119 return err 120 } 121 122 if autofix { 123 logger.Info("Enabled push options for %d repositories.", numRepos) 124 } else { 125 logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate) 126 } 127 128 return nil 129 } 130 131 func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) error { 132 numRepos := 0 133 numNeedUpdate := 0 134 cache, err := lru.New[int64, any](512) 135 if err != nil { 136 logger.Critical("Unable to create cache: %v", err) 137 return err 138 } 139 if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { 140 numRepos++ 141 142 if owner, has := cache.Get(repo.OwnerID); has { 143 repo.Owner = owner.(*user_model.User) 144 } else { 145 if err := repo.LoadOwner(ctx); err != nil { 146 return err 147 } 148 cache.Add(repo.OwnerID, repo.Owner) 149 } 150 151 // Create/Remove git-daemon-export-ok for git-daemon... 152 daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) 153 isExist, err := util.IsExist(daemonExportFile) 154 if err != nil { 155 log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) 156 return err 157 } 158 isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic 159 160 if isPublic != isExist { 161 numNeedUpdate++ 162 if autofix { 163 if !isPublic && isExist { 164 if err = util.Remove(daemonExportFile); err != nil { 165 log.Error("Failed to remove %s: %v", daemonExportFile, err) 166 } 167 } else if isPublic && !isExist { 168 if f, err := os.Create(daemonExportFile); err != nil { 169 log.Error("Failed to create %s: %v", daemonExportFile, err) 170 } else { 171 f.Close() 172 } 173 } 174 } 175 } 176 return nil 177 }); err != nil { 178 logger.Critical("Unable to checkDaemonExport: %v", err) 179 return err 180 } 181 182 if autofix { 183 logger.Info("Updated git-daemon-export-ok files for %d of %d repositories.", numNeedUpdate, numRepos) 184 } else { 185 logger.Info("Checked %d repositories, %d need updates.", numRepos, numNeedUpdate) 186 } 187 188 return nil 189 } 190 191 func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error { 192 numRepos := 0 193 numNeedUpdate := 0 194 numWritten := 0 195 if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { 196 numRepos++ 197 198 commitGraphExists := func() (bool, error) { 199 // Check commit-graph exists 200 commitGraphFile := path.Join(repo.RepoPath(), `objects/info/commit-graph`) 201 isExist, err := util.IsExist(commitGraphFile) 202 if err != nil { 203 logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err) 204 return false, err 205 } 206 207 if !isExist { 208 commitGraphsDir := path.Join(repo.RepoPath(), `objects/info/commit-graphs`) 209 isExist, err = util.IsExist(commitGraphsDir) 210 if err != nil { 211 logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err) 212 return false, err 213 } 214 } 215 return isExist, nil 216 } 217 218 isExist, err := commitGraphExists() 219 if err != nil { 220 return err 221 } 222 if !isExist { 223 numNeedUpdate++ 224 if autofix { 225 if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil { 226 logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err) 227 return err 228 } 229 isExist, err := commitGraphExists() 230 if err != nil { 231 return err 232 } 233 if isExist { 234 numWritten++ 235 logger.Info("Commit-graph written: %s", repo.FullName()) 236 } else { 237 logger.Warn("No commit-graph written: %s", repo.FullName()) 238 } 239 } 240 } 241 return nil 242 }); err != nil { 243 logger.Critical("Unable to checkCommitGraph: %v", err) 244 return err 245 } 246 247 if autofix { 248 logger.Info("Wrote commit-graph files for %d of %d repositories.", numWritten, numRepos) 249 } else { 250 logger.Info("Checked %d repositories, %d without commit-graphs.", numRepos, numNeedUpdate) 251 } 252 253 return nil 254 } 255 256 func init() { 257 Register(&Check{ 258 Title: "Check if SCRIPT_TYPE is available", 259 Name: "script-type", 260 IsDefault: false, 261 Run: checkScriptType, 262 Priority: 5, 263 }) 264 Register(&Check{ 265 Title: "Check if hook files are up-to-date and executable", 266 Name: "hooks", 267 IsDefault: false, 268 Run: checkHooks, 269 Priority: 6, 270 }) 271 Register(&Check{ 272 Title: "Recalculate Stars number for all user", 273 Name: "recalculate-stars-number", 274 IsDefault: false, 275 Run: checkUserStarNum, 276 Priority: 6, 277 }) 278 Register(&Check{ 279 Title: "Check that all git repositories have receive.advertisePushOptions set to true", 280 Name: "enable-push-options", 281 IsDefault: false, 282 Run: checkEnablePushOptions, 283 Priority: 7, 284 }) 285 Register(&Check{ 286 Title: "Check git-daemon-export-ok files", 287 Name: "check-git-daemon-export-ok", 288 IsDefault: false, 289 Run: checkDaemonExport, 290 Priority: 8, 291 }) 292 Register(&Check{ 293 Title: "Check commit-graphs", 294 Name: "check-commit-graphs", 295 IsDefault: false, 296 Run: checkCommitGraph, 297 Priority: 9, 298 }) 299 }