package forgepb import ( "errors" "fmt" "io/ioutil" "net/http" "path/filepath" "strings" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" ) // will not violate filesystem namespace // always returns the path or a parent path // // attemps to exec git clone based off of a golang path // will transferse parent directories in case the path // is a child of a git repo // // returns *gitpb.Repo if already cloned // // example gopath = go.wit.com/apps/go-clone // or "go.googlesource.com/go/src/cmd/internal/pkgpath/" returns repo for "go.googlesource.com/go" func (f *Forge) GoClone(gopath string) (*gitpb.Repo, error) { // will match /root/go/src/go.wit.com/apps/go-clone/something/inside // and return the *gitpb.Repo for "go.wit.com/apps/go-clone" fullpath := filepath.Join(f.goSrc, gopath) if pb := f.FindAnyPath(fullpath); pb != nil { // repo already exists return pb, nil } // try a direct git clone against the gopath // if this doesn't work, probably the package is abandoned and you // are probably using something old, broken, or wrong if repo, err := f.urlClone(gopath, "https://"+gopath); repo != nil { return repo, err } // check for parent git repos if repo, err := f.goClonePop(gopath); repo != nil { return repo, err } // query the golang package system for the last known location // NOTE: take time to thank the go developers and google for designing this wonderful system if pkgurl, err := runGoList(gopath); err == nil { if repo, err := f.urlClone(gopath, pkgurl); repo != nil { return repo, err } } // todo: emit some sort of warning? // hacks if repo, err := f.clonePathHack(gopath); repo != nil { return repo, err } return nil, errors.New("can not find git sources for gopath " + gopath) } // this is obvious a experiemental hack // todo: make a config file for this? func overridePath(gopath string) string { switch gopath { case "golang.org/x/crypto": return "https://" + "go.googlesource.com/crypto" case "golang.org/x/mod": return "https://" + "go.googlesource.com/mod" case "golang.org/x/net": return "https://" + "go.googlesource.com/net" case "golang.org/x/sys": return "https://" + "go.googlesource.com/sys" case "golang.org/x/sync": return "https://" + "go.googlesource.com/sync" case "golang.org/x/term": return "https://" + "go.googlesource.com/term" case "golang.org/x/text": return "https://" + "go.googlesource.com/text" case "golang.org/x/tools": return "https://" + "go.googlesource.com/tools" case "golang.org/x/xerrors": return "https://" + "go.googlesource.com/xerrors" case "google.golang.org/protobuf": return "https://" + "go.googlesource.com/protobuf" case "google.golang.org/genproto": return "https://" + "go.googlesource.com/genproto" case "google.golang.org/api": return "https://" + "go.googlesource.com/api" case "google.golang.org/grpc": return "https://" + "go.googlesource.com/grpc" case "google.golang.org/appengine": return "https://" + "go.googlesource.com/appengine" } if strings.HasPrefix(gopath, "github.com/go-gl/glfw") { return "https://github.com/go-gl/glfw" } return "" } // TODO: make some config file for things like this // can be used to work around temporary problems func (f *Forge) clonePathHack(gopath string) (*gitpb.Repo, error) { // newdir = helloworld // basedir = /home/jcarr/go/src/go.wit.com/apps // giturl = https://gitea.wit.com/gui/helloworld url := overridePath(gopath) if url == "" { return nil, errors.New("no gopath override here") } return f.urlClone(gopath, url) } // for: github.com/gdamore/tcell/v2 // tries git clone github.com/gdamore/tcell/v2 // then git clone github.com/gdamore/tcell // then git clone github.com/gdamore , etc func (f *Forge) goClonePop(gopath string) (*gitpb.Repo, error) { log.Info("forge.goClonePop() trying", gopath) if gopath == "" { return nil, nil } newpath, newdir := filepath.Split(gopath) if newdir == "" { // nothing to chop return nil, nil } if repo, _ := f.urlClone(newpath, "https://"+newpath); repo != nil { return repo, nil } if repo, err := f.goClonePop(newpath); repo != nil { return repo, err } return nil, fmt.Errorf("forge.goClonePop() did not work %s", gopath) } // clone a URL directly, also try cloning if 'go-import' is sent // newdir = helloworld // basedir = /home/jcarr/go/src/go.wit.com/apps // giturl = https://gitea.wit.com/gui/helloworld func (f *Forge) urlClone(gopath, giturl string) (*gitpb.Repo, error) { var err error fullpath := filepath.Join(f.goSrc, gopath) basedir, newdir := filepath.Split(fullpath) // clone the URL directly if err = RunGitClone(newdir, basedir, giturl); err == nil { return f.NewGoRepo(gopath, giturl) } // see if the URL has go-import for a new URL if giturl, err = findGoImport(giturl); err == nil { if err = RunGitClone(newdir, basedir, giturl); err == nil { return f.NewGoRepo(gopath, giturl) } } // log.Info("git clone from 'go-import' info failed", url) // yes, this misses the first error return nil, err } // check the server for the current go path to git url mapping // for example: // This will check go.wit.com for "go.wit.com/apps/go-clone" // and return where the URL to do git clone should be func findGoImport(url string) (string, error) { resp, err := http.Get(url) if err != nil { return "", err } defer resp.Body.Close() bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } tmp := string(bodyBytes) parts := strings.Split(tmp, "go-import") if len(parts) < 2 { return "", errors.New("missing go-import") } // this is terrible, it doesn't even look for 'content=' // but again, this is just a hack for my own code to be // usuable after the removal in go v1.22 of the go get clone behavior that was in v1.21 parts = strings.Split(parts[1], "\"") var newurl string for { if len(parts) == 0 { break } tmp := strings.TrimSpace(parts[0]) fields := strings.Split(tmp, " ") // log.Info("FIELDS:", fields) if len(fields) == 3 { newurl = fields[2] break } parts = parts[1:] } if newurl == "" { return "", errors.New("missing git content string") } return newurl, nil }