/
githubmirror
/
hugo
Обзор
Документация
Войти
/
githubmirror
/
hugo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
hugolib/gitinfo.go
261 строка
7 KB
Bjørn Erik Pedersen
hugolib: Fix Page.GitInfo for modules with go.mod in a repo subdirectory
26 май 2026, 01:28
26 май 2026, 01:28
df54219
Код
Авторство
О чём код?
// Copyright 2019 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package hugolib import ( "bytes" "fmt" "io" "os" "path/filepath" "strings" "sync" "github.com/bep/gitmap" "github.com/gohugoio/hugo/cache/filecache" "github.com/gohugoio/hugo/common/hashing" "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/modules" "github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/source" ) type gitInfo struct { contentDir string repo *gitmap.GitRepo // Per-module git repos keyed by module dir. moduleRepoLoaders map[string]func() (*gitmap.GitRepo, error) logger loggers.Logger } func (g *gitInfo) forPage(p page.Page) *source.GitInfo { if p.File() == nil { return nil } mod := p.File().FileInfo().Meta().Module if mod != nil && mod.IsGoMod() { mm := mod.(modules.Module) if loadRepo, ok := g.moduleRepoLoaders[mm.Dir()]; ok { repo, err := loadRepo() if err != nil { g.logger.Warnf("Failed to load git repo for module %s: %v", mm.Path(), err) return nil } return g.gitInfoFromModuleRepo(p, mm, repo) } } // Fall back to local git info. if g.repo == nil { return nil } name := strings.TrimPrefix(filepath.ToSlash(p.File().Filename()), g.contentDir) name = strings.TrimPrefix(name, "/") gi, found := g.repo.Files[name] if !found { return nil } return gi } func (g *gitInfo) gitInfoFromModuleRepo(p page.Page, mod modules.Module, repo *gitmap.GitRepo) *source.GitInfo { filename := filepath.ToSlash(p.File().Filename()) modDir := filepath.ToSlash(mod.Dir()) filePath := strings.TrimPrefix(filename, modDir) filePath = strings.TrimPrefix(filePath, "/") // If the module's go.mod lives in a subdirectory of the git repo, // the blobless clone keys files by their path relative to the repo root, // so we need to prepend the subdir. if subdir := strings.Trim(mod.Origin().Subdir, "/"); subdir != "" { filePath = subdir + "/" + filePath } gi, found := repo.Files[filePath] if !found { return nil } return gi } // isGitModule returns true if the module has a git VCS origin. func isGitModule(m modules.Module) bool { origin := m.Origin() return origin.VCS == "git" && origin.URL != "" } type gitInfoConfig struct { Deps *deps.Deps Modules modules.Modules GitInfoCache *filecache.Cache Logger loggers.Logger } func newGitInfo(cfg gitInfoConfig) (*gitInfo, error) { g := &gitInfo{ moduleRepoLoaders: make(map[string]func() (*gitmap.GitRepo, error)), logger: cfg.Logger, } var hasGitModules bool var projectIsGitModule bool for _, mod := range cfg.Modules { if !isGitModule(mod) { continue } hasGitModules = true if mod.Owner() == nil && !mod.Origin().IsZero() { // TODO(bep) I'm not sure if this will ever be true. Needs some investigation, // as I'm sure there will be cases where this could be useful. projectIsGitModule = true } } if hasGitModules { if err := g.loadModuleRepos(cfg); err != nil { return nil, err } } if projectIsGitModule { return g, nil } // Load local git repo info. gitRepo, err := mapLocalRepo(cfg) if err != nil { if hasGitModules { // We added GitInfo module support in Hugo v0.157.0 and need some real world experience, // but for now, don't fail if the local repo is not a Git repo, but there are Git modules. // I'm not sure we should even warn, but let's do that for now. cfg.Logger.Warnf("Failed to read local Git log: %v", err) return g, nil } return nil, err } g.contentDir = gitRepo.TopLevelAbsPath g.repo = gitRepo return g, nil } func (g *gitInfo) loadModuleRepos(cfg gitInfoConfig) error { for _, mod := range cfg.Modules { if !isGitModule(mod) { continue } loadGitInfo := sync.OnceValues(func() (*gitmap.GitRepo, error) { origin := mod.Origin() cloneDir, err := ensureClone(cfg, origin) if err != nil { return nil, fmt.Errorf("failed to clone %s: %v", origin.URL, err) } repo, err := mapModuleRepo(cfg, cloneDir, origin.Hash) if err != nil { return nil, fmt.Errorf("failed to map git repo for module %s: %v", mod.Path(), err) } return repo, nil }) g.moduleRepoLoaders[mod.Dir()] = loadGitInfo } return nil } // ensureClone ensures a blobless clone of the module's origin repo exists in the cache. func ensureClone(cfg gitInfoConfig, origin modules.ModuleOrigin) (string, error) { key := "repo_" + hashing.XxHashFromStringHexEncoded(origin.URL) info, err := cfg.GitInfoCache.GetOrCreateInfo(key, func(id string) error { cloneDir := cfg.GitInfoCache.AbsFilenameFromID(id) if err := os.MkdirAll(cloneDir, 0o777); err != nil { return err } var stderr bytes.Buffer args := []any{ "clone", "--filter=blob:none", "--no-checkout", origin.URL, cloneDir, hexec.WithStdout(io.Discard), hexec.WithStderr(&stderr), } cfg.Logger.Infof("Cloning gitinfo for repo %s into cache", origin.URL) cmd, err := cfg.Deps.ExecHelper.New("git", args...) if err != nil { return fmt.Errorf("git clone: %w", err) } if err := cmd.Run(); err != nil { return fmt.Errorf("git clone %s: %w: %s", origin.URL, err, stderr.String()) } return nil }) if err != nil { return "", err } return cfg.GitInfoCache.AbsFilenameFromID(info.Name), nil } func mapModuleRepo(cfg gitInfoConfig, repoDir, revision string) (*gitmap.GitRepo, error) { opts := gitmap.Options{ Repository: repoDir, Revision: revision, GetGitCommandFunc: func(stdout, stderr io.Writer, args ...string) (gitmap.Runner, error) { var argsv []any for _, arg := range args { argsv = append(argsv, arg) } argsv = append( argsv, hexec.WithStdout(stdout), hexec.WithStderr(stderr), ) return cfg.Deps.ExecHelper.New("git", argsv...) }, } return gitmap.Map(opts) } func mapLocalRepo(cfg gitInfoConfig) (*gitmap.GitRepo, error) { opts := gitmap.Options{ Repository: cfg.Deps.Conf.BaseConfig().WorkingDir, GetGitCommandFunc: func(stdout, stderr io.Writer, args ...string) (gitmap.Runner, error) { var argsv []any for _, arg := range args { argsv = append(argsv, arg) } argsv = append( argsv, hexec.WithStdout(stdout), hexec.WithStderr(stderr), ) return cfg.Deps.ExecHelper.New("git", argsv...) }, } return gitmap.Map(opts) }