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  }