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