1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2025-07-19 17:49:39 +02:00

git/commit: re-implement submodules file reader (#8438)

Reimplement the submodules parser to not depend on the go-git dependency.

See #8222 for the full refactor context.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8438
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: oliverpool <git@olivier.pfad.fr>
Co-committed-by: oliverpool <git@olivier.pfad.fr>
This commit is contained in:
oliverpool 2025-07-15 00:20:00 +02:00 committed by Gusted
parent 48cc6e684a
commit 5158493ba6
17 changed files with 220 additions and 166 deletions

View file

@ -6,38 +6,124 @@ package git
import (
"fmt"
"io"
"net"
"net/url"
"path"
"regexp"
"strings"
"forgejo.org/modules/setting"
"forgejo.org/modules/util"
"gopkg.in/ini.v1" //nolint:depguard // used to read .gitmodules
)
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
// SubModule submodule is a reference on git repository
type SubModule struct {
Name string
URL string
}
// SubModuleFile represents a file with submodule type.
type SubModuleFile struct {
*Commit
refURL string
refID string
}
// NewSubModuleFile create a new submodule file
func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile {
return &SubModuleFile{
Commit: c,
refURL: refURL,
refID: refID,
// GetSubmodule returns the Submodule of a given path
func (c *Commit) GetSubmodule(path string, entry *TreeEntry) (Submodule, error) {
err := c.readSubmodules()
if err != nil {
// the .gitmodules file exists but could not be read or parsed
return Submodule{}, err
}
sm, ok := c.submodules[path]
if !ok {
// no info found in .gitmodules: fallback to what we can provide
return Submodule{
Path: path,
Commit: entry.ID,
}, nil
}
sm.Commit = entry.ID
return sm, nil
}
// readSubmodules populates the submodules field by reading the .gitmodules file
func (c *Commit) readSubmodules() error {
if c.submodules != nil {
return nil
}
entry, err := c.GetTreeEntryByPath(".gitmodules")
if err != nil {
if IsErrNotExist(err) {
c.submodules = make(map[string]Submodule)
return nil
}
return err
}
rc, _, err := entry.Blob().NewTruncatedReader(10 * 1024)
if err != nil {
return err
}
defer rc.Close()
c.submodules, err = parseSubmoduleContent(rc)
return err
}
func parseSubmoduleContent(r io.Reader) (map[string]Submodule, error) {
// https://git-scm.com/docs/gitmodules#_description
// The .gitmodules file, located in the top-level directory of a Git working tree
// is a text file with a syntax matching the requirements of git-config[1].
// https://git-scm.com/docs/git-config#_configuration_file
cfg := ini.Empty(ini.LoadOptions{
InsensitiveKeys: true, // "The variable names are case-insensitive", but "Subsection names are case sensitive"
})
err := cfg.Append(r)
if err != nil {
return nil, err
}
sections := cfg.Sections()
submodule := make(map[string]Submodule, len(sections))
for _, s := range sections {
sm := parseSubmoduleSection(s)
if sm.Path == "" || sm.URL == "" {
continue
}
submodule[sm.Path] = sm
}
return submodule, nil
}
func parseSubmoduleSection(s *ini.Section) Submodule {
section, name, _ := strings.Cut(s.Name(), " ")
if !util.ASCIIEqualFold("submodule", section) { // See https://codeberg.org/forgejo/forgejo/pulls/8438#issuecomment-5805251
return Submodule{}
}
_ = name
sm := Submodule{}
if key, _ := s.GetKey("path"); key != nil {
sm.Path = key.Value()
}
if key, _ := s.GetKey("url"); key != nil {
sm.URL = key.Value()
}
return sm
}
// Submodule represents a parsed git submodule reference.
type Submodule struct {
Path string // path property
URL string // upstream URL
Commit ObjectID // upstream Commit-ID
}
// ResolveUpstreamURL resolves the upstream URL relative to the repo URL.
func (sm Submodule) ResolveUpstreamURL(repoURL string) string {
repoFullName := strings.TrimPrefix(repoURL, setting.AppURL) // currently hacky, but can be dropped when refactoring getRefURL
return getRefURL(sm.URL, setting.AppURL, repoFullName, setting.SSH.Domain)
}
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
if refURL == "" {
return ""
@ -53,7 +139,7 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
urlPrefix = strings.TrimSuffix(urlPrefix, "/")
// FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules
// FIXME: Need to consider branch - which will require changes in parseSubmoduleSection
// Relative url prefix check (according to git submodule documentation)
if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") {
return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI))
@ -107,13 +193,3 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
return ""
}
// RefURL guesses and returns reference URL.
func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
}
// RefID returns reference ID.
func (sf *SubModuleFile) RefID() string {
return sf.refID
}