Compare commits
151 Commits
Author | SHA1 | Date |
---|---|---|
|
7e1804f6e3 | |
|
8ceec9210d | |
|
cf6db578a4 | |
|
da485dcc3f | |
|
5377a89d2c | |
|
f7d6dfa6a7 | |
|
281ffbc75b | |
|
4193f41847 | |
|
9d5bae8a14 | |
|
f540aab434 | |
|
a170250cb4 | |
|
2fc67512c8 | |
|
eaadfa21d3 | |
|
22ebf174c8 | |
|
9a87c93bad | |
|
b7b18626d8 | |
|
7900b1416e | |
|
5c84b9ab66 | |
|
c09e292a66 | |
|
3278f6400e | |
|
83ad663fc0 | |
|
f70f54615f | |
|
018772dbfb | |
|
9baa477990 | |
|
ab01c2cd60 | |
|
c89f101fb2 | |
|
23d7ad1581 | |
|
ec4acd425c | |
|
0614066fdb | |
|
b60279b19a | |
|
95fcacfde0 | |
|
b6a71a515f | |
|
47ee3f1493 | |
|
329710f9e7 | |
|
f7b5e1a83e | |
|
7c37e3841a | |
|
f146bf4ef0 | |
|
d9d90e9e12 | |
|
393b91c415 | |
|
b412e50df0 | |
|
aa06450042 | |
|
121e9f08da | |
|
df19b5b8f8 | |
|
0efc3c67ca | |
|
667257595d | |
|
76a0347fdf | |
|
66738e4300 | |
|
3e4b1ddc83 | |
|
9ec7b4394f | |
|
1191b9b65d | |
|
52b8a4e312 | |
|
b770759167 | |
|
0898c24f45 | |
|
58c64cd53b | |
|
f29f25b9b7 | |
|
4328692039 | |
|
244bf612f9 | |
|
f4ac491490 | |
|
bdf9d97cf9 | |
|
a822e1e4f0 | |
|
cee7e25f3d | |
|
9b8cb52b7b | |
|
538531f503 | |
|
e8f29e593d | |
|
b8d0864c37 | |
|
156af56859 | |
|
8d275ff054 | |
|
0f232fe342 | |
|
9e81be86da | |
|
2398e30048 | |
|
49a06843e9 | |
|
9b9c51d964 | |
|
23887a155e | |
|
0600f54488 | |
|
0ea93faef2 | |
|
48b19f1e70 | |
|
1e38cacfa7 | |
|
40c340c626 | |
|
f9dd82cdcc | |
|
4f84a4e584 | |
|
8aff3f13b2 | |
|
26cf5055a2 | |
|
14e5bf5fbd | |
|
e0c0d3d9e6 | |
|
bd3e924e2b | |
|
e6e70ccaa5 | |
|
67ae8d8773 | |
|
e333ca726b | |
|
6cbd7e67af | |
|
7e5db53e9d | |
|
b0662fb61a | |
|
ac57825c10 | |
|
338018376b | |
|
275a7db0e0 | |
|
f2ba4cab9c | |
|
c7066e1766 | |
|
f4837c807c | |
|
e9776796dd | |
|
934daa5a3b | |
|
a97b66e8f2 | |
|
4582a76081 | |
|
24c96ccaa7 | |
|
272f965eec | |
|
c4f9430e46 | |
|
7cdb2bf6a0 | |
|
23b2b26643 | |
|
7db8376213 | |
|
ee7e8d5b2b | |
|
b2e51ccb9e | |
|
1369d2df31 | |
|
075fce61e5 | |
|
a9286af8fd | |
|
6922059a0b | |
|
0b09db58dd | |
|
a31f675e12 | |
|
e3608b784e | |
|
340872788e | |
|
e796788e22 | |
|
b55548a58b | |
|
5673e2cc2e | |
|
b1d6923ca2 | |
|
d20ce6b0e8 | |
|
b715fdd11a | |
|
4a2568fea3 | |
|
9873bc3d57 | |
|
4e94089128 | |
|
4928804bf4 | |
|
6d25d1b1cb | |
|
b7e36449de | |
|
dae2653975 | |
|
0235d4d096 | |
|
10e1f545bb | |
|
7512463a57 | |
|
757fd9ba55 | |
|
c08079fc2f | |
|
7f1f8d4028 | |
|
e25095f2e7 | |
|
34a287a38e | |
|
f98179971e | |
|
4d221c5220 | |
|
a58234f82c | |
|
5d5f691570 | |
|
3600dbed8c | |
|
dfae92e3c3 | |
|
6342113c2a | |
|
cbdcfa0589 | |
|
189d305fee | |
|
86e0a1f988 | |
|
f5d41d782a | |
|
be026e8edc | |
|
dc3fe4bd2e |
|
@ -1,5 +1,5 @@
|
|||
go.*
|
||||
|
||||
*.swp
|
||||
*.patch
|
||||
*.mbox
|
||||
*.pb.go
|
||||
|
||||
forgeConfig/forgeConfig
|
||||
|
|
26
Makefile
26
Makefile
|
@ -5,7 +5,11 @@
|
|||
# go install
|
||||
|
||||
|
||||
all: forgeConfig.pb.go vet
|
||||
all: forgeConfig.pb.go patchset.pb.go goimports vet
|
||||
|
||||
generate: clean
|
||||
autogenpb --proto patchset.proto
|
||||
autogenpb --proto forgeConfig.proto
|
||||
|
||||
vet:
|
||||
@GO111MODULE=off go vet
|
||||
|
@ -14,24 +18,14 @@ vet:
|
|||
# autofixes your import headers in your golang files
|
||||
goimports:
|
||||
goimports -w *.go
|
||||
make -C forgeConfig goimports
|
||||
|
||||
redomod:
|
||||
rm -f go.*
|
||||
GO111MODULE= go mod init
|
||||
GO111MODULE= go mod tidy
|
||||
go mod edit -go=1.20
|
||||
|
||||
clean:
|
||||
rm -f *.pb.go
|
||||
rm -f *.pb.go *.patch
|
||||
-rm -f go.*
|
||||
make -C forgeConfig clean
|
||||
|
||||
build:
|
||||
make -C forgeConfig
|
||||
|
||||
install:
|
||||
make -C forgeConfig install
|
||||
go-mod-clean --purge
|
||||
|
||||
forgeConfig.pb.go: forgeConfig.proto
|
||||
autogenpb --proto forgeConfig.proto
|
||||
|
||||
patchset.pb.go: patchset.proto
|
||||
autogenpb --proto patchset.proto
|
||||
|
|
232
build.go
232
build.go
|
@ -1,87 +1,229 @@
|
|||
package forgepb
|
||||
|
||||
// for golang repos, this is an attempt to build the package
|
||||
// there might be some 'standard' ways to implement a build
|
||||
// that make sense.
|
||||
|
||||
//
|
||||
// Additions to 'go build' that are attempted here:
|
||||
//
|
||||
// * detect packages that are plugins
|
||||
// * autogen packages that have .proto protobuf files
|
||||
// * define some 'standard' ldflags
|
||||
// * run autogenpb packages that have .proto protobuf files
|
||||
// * use some 'standard' ldflags (VERISON, BUILDTIME)
|
||||
//
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (f *Forge) Build(repo *gitpb.Repo, userFlags []string) error {
|
||||
// always assume all sources have been downloaded
|
||||
// todo: detect when in ~/go/src vs go.work mode
|
||||
return f.doBuild(repo, userFlags, "build")
|
||||
}
|
||||
|
||||
func (f *Forge) Install(repo *gitpb.Repo, userFlags []string) error {
|
||||
return f.doBuild(repo, userFlags, "install")
|
||||
}
|
||||
|
||||
// userflags are intended for "-v" and "-x" right now
|
||||
func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) error {
|
||||
if repo == nil {
|
||||
log.Warn("forge.doBuild repo == nil")
|
||||
return errors.New("forge.doBuild repo == nil")
|
||||
}
|
||||
if f.IsGoWork() {
|
||||
// when building using a go.work file, never use GO111MODULE=off
|
||||
os.Unsetenv("GO111MODULE")
|
||||
} else {
|
||||
// when building from ~/go/src, always use GO111MODULE=off
|
||||
os.Setenv("GO111MODULE", "off")
|
||||
defer os.Unsetenv("GO111MODULE")
|
||||
}
|
||||
|
||||
if f.IsGoWork() {
|
||||
// there must be a valid go.mod file if compiling with go.work
|
||||
if err := repo.ValidGoSum(); err != nil {
|
||||
log.Warn("forge.doBuild() failed. run go-mod-clean here?")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if repo.Exists(".forge") {
|
||||
log.Info("custom build instructions")
|
||||
data, _ := repo.ReadFile(".forge")
|
||||
log.Info(".forge =", string(data))
|
||||
log.Info("todo: do the custom build instructions")
|
||||
basedir, filename := filepath.Split(repo.GetGoPath())
|
||||
log.Info("touching filename", basedir, filename)
|
||||
repo.RunVerbose([]string{"touch", filename})
|
||||
return nil
|
||||
}
|
||||
|
||||
// if not GoPrimitive, autogen each dependent git repo
|
||||
if repo.GoDepsLen() != 0 {
|
||||
// build the protobuf files in all protobuf repos
|
||||
all := repo.GoDeps.SortByGoPath()
|
||||
for all.Scan() {
|
||||
t := all.Next()
|
||||
found := f.FindByGoPath(t.GetGoPath())
|
||||
if found.GetRepoType() == "protobuf" {
|
||||
if err := f.runAutogenpb(found); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the version
|
||||
version := repo.GetCurrentBranchVersion()
|
||||
|
||||
loop := repo.Tags.SortByRefname()
|
||||
for loop.Scan() {
|
||||
t := loop.Next()
|
||||
log.Info("Build() tag:", t.Refname)
|
||||
if version == "" {
|
||||
version = "forgeErr"
|
||||
}
|
||||
|
||||
if repo.GoDeps == nil {
|
||||
repo.RedoGoMod()
|
||||
}
|
||||
if repo.GoDeps.Len() == 0 {
|
||||
log.Info("redo go.mod", repo.GetGoPath())
|
||||
repo.RedoGoMod()
|
||||
f.Repos.ConfigSave()
|
||||
}
|
||||
loop1 := repo.GoDeps.SortByGoPath()
|
||||
for loop1.Scan() {
|
||||
t := loop1.Next()
|
||||
log.Info("Build() dep:", t.GetGoPath(), t.GetVersion())
|
||||
}
|
||||
loop2 := repo.Published.SortByGoPath()
|
||||
for loop2.Scan() {
|
||||
t := loop2.Next()
|
||||
log.Info("Build() pub:", t.GetGoPath(), t.GetVersion())
|
||||
}
|
||||
log.Info("Build() dep len:", repo.GoDeps.Len())
|
||||
os.Exit(-1)
|
||||
|
||||
if repo.CheckDirty() {
|
||||
version = version + "-dirty"
|
||||
}
|
||||
cmd := []string{"go", "build", "-v"}
|
||||
cmd := []string{"go"}
|
||||
if repo.GetRepoType() == "plugin" {
|
||||
if goWhat == "install" {
|
||||
log.Info("Can not go install plugins yet. just building to ~/go/lib/")
|
||||
}
|
||||
cmd = append(cmd, "build")
|
||||
} else {
|
||||
cmd = append(cmd, goWhat)
|
||||
}
|
||||
|
||||
_, fname := filepath.Split(repo.FullPath)
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
soname := fname + "." + version + ".so"
|
||||
linkname := fname + ".so"
|
||||
sopath := filepath.Join(homeDir, "go/lib/go-gui")
|
||||
// if this is a plugin, use buildmode=plugin
|
||||
if repo.GetRepoType() == "plugin" {
|
||||
if goWhat == "install" {
|
||||
fullname := filepath.Join(sopath, soname)
|
||||
cmd = append(cmd, "-buildmode=plugin", "-o", fullname)
|
||||
} else {
|
||||
cmd = append(cmd, "-buildmode=plugin", "-o", soname)
|
||||
}
|
||||
}
|
||||
for _, flag := range userFlags {
|
||||
cmd = append(cmd, flag)
|
||||
}
|
||||
|
||||
// set standard ldflag options
|
||||
now := time.Now()
|
||||
datestamp := now.UTC().Format("2006/01/02_1504_UTC")
|
||||
log.Info("datestamp =", datestamp)
|
||||
// datestamp := now.UTC().Format("2006/01/02_1504_UTC")
|
||||
datestamp := now.UTC().Format("2006-01-02_15:04:05_UTC") // 2006-01-02 15:04:05 UTC
|
||||
// log.Info("datestamp =", datestamp)
|
||||
// add some standard golang flags
|
||||
ldflags := "-X main.VERSION=" + version + " "
|
||||
ldflags += "-X main.BUILDTIME=" + datestamp + " "
|
||||
ldflags += "-X main.GUIVERSION=" + version + "" // todo: git this from the filesystem
|
||||
cmd = append(cmd, "-ldflags", ldflags)
|
||||
|
||||
// add any flags from the command line
|
||||
// this might not actually work
|
||||
// todo: test this
|
||||
for _, flag := range userFlags {
|
||||
cmd = append(cmd, "-ldflags", "-X "+flag)
|
||||
testenv := os.Getenv("GO111MODULE")
|
||||
if testenv == "off" {
|
||||
log.Info("GO111MODULE=off", "f.goWork =", f.IsGoWork(), "f.gosrc =", f.GetGoSrc())
|
||||
} else {
|
||||
log.Info("GO111MODULE=", testenv, "f.goWork =", f.IsGoWork(), "f.gosrc =", f.GetGoSrc())
|
||||
}
|
||||
log.Info("running:", repo.FullPath)
|
||||
log.Info("running:", cmd)
|
||||
result := repo.RunRealtime(cmd)
|
||||
if result.Exit != 0 {
|
||||
// build failed
|
||||
log.DaemonMode(true)
|
||||
log.Info(strings.Join(result.Stdout, "\n"))
|
||||
log.Info(strings.Join(result.Stderr, "\n"))
|
||||
log.Info("result.Error =", result.Error)
|
||||
log.Info("result.Exit =", result.Exit)
|
||||
log.DaemonMode(false)
|
||||
log.Warn("go build failed", cmd)
|
||||
/*
|
||||
pwd, _ := os.Getwd()
|
||||
log.Warn("go build pwd", pwd)
|
||||
res2 := shell.RunEcho(cmd)
|
||||
if res2.Exit == 0 {
|
||||
log.Info("again failed", res2.Exit)
|
||||
log.Info("again failed cmd", strings.Join(cmd, "a"))
|
||||
log.Info("again failed", strings.Join(res2.Stdout, "\n"))
|
||||
}
|
||||
*/
|
||||
return errors.New("go " + goWhat + " failed: " + fmt.Sprint(result.Error))
|
||||
}
|
||||
// make symlinks
|
||||
if repo.GetRepoType() == "plugin" {
|
||||
cmd := []string{"ln", "-sf", soname, linkname}
|
||||
if goWhat == "install" {
|
||||
shell.PathRun(sopath, cmd)
|
||||
} else {
|
||||
repo.Run(cmd)
|
||||
}
|
||||
}
|
||||
log.Info(strings.Join(result.Stdout, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if r := repo.Run(cmd); r.Error == nil {
|
||||
log.Warn("go build worked")
|
||||
func (f *Forge) runAutogenpb(repo *gitpb.Repo) error {
|
||||
// log.Info("run autogenpb here:", repo.GetGoPath())
|
||||
files, err := repo.GetProtoFiles()
|
||||
if err != nil {
|
||||
log.Info("gitpb.GetProtoFiles()", err)
|
||||
return err
|
||||
}
|
||||
for _, s := range files {
|
||||
_, filename := filepath.Split(s)
|
||||
pbfile := strings.TrimSuffix(filename, ".proto") + ".pb.go"
|
||||
cmd := []string{"autogenpb", "--proto", filename}
|
||||
if repo.Exists(pbfile) {
|
||||
// log.Info("skip running:", cmd)
|
||||
continue
|
||||
}
|
||||
log.Info("running:", cmd)
|
||||
r := repo.RunRealtime(cmd)
|
||||
if r.Error != nil {
|
||||
log.Warn("")
|
||||
log.Warn("autogenpb error", r.Error)
|
||||
log.Warn("")
|
||||
log.Warn("You might be missing autogenpb?")
|
||||
log.Warn("")
|
||||
log.Warn("To install it run: go install go.wit.com/apps/autogenpb@latest")
|
||||
log.Warn("")
|
||||
log.Sleep(2)
|
||||
return r.Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("go build failed: " + fmt.Sprint(r.Error))
|
||||
}
|
||||
|
||||
// sortcut to find
|
||||
func (f *Forge) FindWorkingDirRepo() *gitpb.Repo {
|
||||
pwd, _ := os.Getwd()
|
||||
basedir := strings.TrimPrefix(pwd, f.GetGoSrc())
|
||||
basedir = strings.Trim(basedir, "/")
|
||||
return f.FindByGoPath(basedir)
|
||||
}
|
||||
|
||||
// Never do this. this is stupid. fix your repo
|
||||
// Never try to version & release broken repos
|
||||
// leave this code here as a reminder to never attempt this
|
||||
func (f *Forge) forgeIgnoreGoMod(repo *gitpb.Repo) bool {
|
||||
if !repo.Exists(".forge") {
|
||||
return false
|
||||
}
|
||||
log.Info("custom build instructions")
|
||||
data, _ := repo.ReadFile(".forge")
|
||||
log.Info(".forge =", string(data))
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.Contains(line, "forge:ignore:gomod") { // this is stupid
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// This will recreate your go.sum and go.mod files
|
||||
var cleanVerbose bool = false
|
||||
|
||||
// checks to see if every 'master' git branch version
|
||||
// matches the go.sum file
|
||||
func (f *Forge) CleanGoDepsCheckOk(check *gitpb.Repo) error {
|
||||
var err error = nil
|
||||
var fixes [][]string
|
||||
log.Printf("current repo %s go dependancy count: %d", check.GetGoPath(), check.GoDepsLen())
|
||||
if check.GoDeps == nil {
|
||||
return errors.New("check.GoDeps == nil")
|
||||
}
|
||||
all := check.GoDeps.SortByGoPath()
|
||||
for all.Scan() {
|
||||
depRepo := all.Next()
|
||||
found := f.FindByGoPath(depRepo.GetGoPath())
|
||||
if found == nil {
|
||||
if f.CheckOverride(depRepo.GetGoPath()) {
|
||||
// skip this gopath because it's probably broken forever
|
||||
continue
|
||||
}
|
||||
log.Info("not found:", depRepo.GetGoPath())
|
||||
err = errors.New("not found: " + depRepo.GetGoPath())
|
||||
continue
|
||||
}
|
||||
// log.Info("found dep", depRepo.GetGoPath())
|
||||
if depRepo.GetVersion() != found.GetMasterVersion() {
|
||||
check := f.FindByGoPath(depRepo.GetGoPath())
|
||||
var ends string
|
||||
if check.CheckDirty() {
|
||||
ends = "(dirty) "
|
||||
}
|
||||
|
||||
if f.Config.IsReadOnly(check.GetGoPath()) {
|
||||
ends += "(ignoring read-only) "
|
||||
if cleanVerbose {
|
||||
log.Printf("%-48s ok error .%s. vs .%s. %s", depRepo.GetGoPath(),
|
||||
depRepo.GetVersion(), found.GetMasterVersion(), ends)
|
||||
}
|
||||
} else {
|
||||
if f.CheckOverride(depRepo.GetGoPath()) {
|
||||
ends += "(override) "
|
||||
if cleanVerbose {
|
||||
log.Printf("%-48s ok error .%s. vs .%s. %s", depRepo.GetGoPath(),
|
||||
depRepo.GetVersion(), found.GetMasterVersion(), ends)
|
||||
// skip this gopath because it's probably broken forever
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
log.Printf("%-48s error %10s vs %10s %s", depRepo.GetGoPath(),
|
||||
depRepo.GetVersion(), found.GetMasterVersion(), ends)
|
||||
errs := fmt.Sprintf("%s error %s vs %s %s", depRepo.GetGoPath(),
|
||||
depRepo.GetVersion(), found.GetMasterVersion(), ends)
|
||||
if ok, _ := ValidGoVersion(found.GetMasterVersion()); ok {
|
||||
// can't go get invalid version numbers
|
||||
cmd := []string{"go", "get", depRepo.GetGoPath() + "@" + found.GetMasterVersion()}
|
||||
fixes = append(fixes, cmd)
|
||||
}
|
||||
err = errors.New(errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, cmd := range fixes {
|
||||
log.Info("try cmd", i, cmd)
|
||||
check.RunRealtime(cmd)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Forge) TrimGoSum(check *gitpb.Repo) error {
|
||||
var stuff map[string]string
|
||||
stuff = make(map[string]string)
|
||||
|
||||
var modver map[string]string
|
||||
modver = make(map[string]string)
|
||||
|
||||
var good map[string]bool
|
||||
good = make(map[string]bool)
|
||||
|
||||
if check == nil {
|
||||
log.Info("boo, check == nil")
|
||||
return errors.New("*repo == nil")
|
||||
}
|
||||
filename := filepath.Join(filepath.Join(check.FullPath, "go.sum"))
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 3 {
|
||||
log.Info("WIERD OR BAD:", line)
|
||||
continue
|
||||
}
|
||||
|
||||
gopath := parts[0]
|
||||
version := parts[1]
|
||||
hash := parts[2]
|
||||
|
||||
if strings.HasSuffix(version, "/go.mod") {
|
||||
if _, ok := stuff[gopath]; ok {
|
||||
if cleanVerbose {
|
||||
log.Info("MATCHED: gopath:", gopath, "version:", version)
|
||||
}
|
||||
modver[gopath] = version + " " + hash
|
||||
good[gopath] = true
|
||||
} else {
|
||||
if cleanVerbose {
|
||||
log.Info("GARBAGE: gopath:", gopath, "version:", version)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if cleanVerbose {
|
||||
log.Info("GOOD : gopath:", gopath, "version:", version)
|
||||
}
|
||||
stuff[gopath] = version + " " + hash
|
||||
}
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(stuff))
|
||||
for k := range stuff {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
// rewrite the go.sum file
|
||||
newfilename := filepath.Join(filepath.Join(check.FullPath, "go.sum"))
|
||||
newf, err := os.OpenFile(newfilename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newf.Close()
|
||||
sort.Strings(keys)
|
||||
for _, gopath := range keys {
|
||||
if good[gopath] {
|
||||
fmt.Fprintf(newf, "%s %s\n", gopath, stuff[gopath])
|
||||
fmt.Fprintf(newf, "%s %s\n", gopath, modver[gopath])
|
||||
check := f.FindByGoPath(gopath)
|
||||
if check == nil {
|
||||
log.Info("gopath does not really exist:", gopath)
|
||||
}
|
||||
}
|
||||
}
|
||||
// fmt.Fprintln(newf, "test")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
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
|
||||
}
|
175
config.go
175
config.go
|
@ -1,3 +1,5 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
// functions to import and export the protobuf
|
||||
|
@ -5,115 +7,103 @@ package forgepb
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.wit.com/log"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// write to ~/.config/forge/ unless ENV{FORGE_HOME} is set
|
||||
func (f *Forge) ConfigSave() error {
|
||||
// f.Config.Lock()
|
||||
// defer f.Config.UnLock()
|
||||
if os.Getenv("FORGE_HOME") == "" {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
fullpath := filepath.Join(homeDir, ".config/forge")
|
||||
os.Setenv("FORGE_HOME", fullpath)
|
||||
var err error
|
||||
// backup the current config files
|
||||
if e := backupConfig(); e != nil {
|
||||
log.Info("forge.BackupConfig() error", e)
|
||||
err = e
|
||||
// continue here? notsure. could be bad either way
|
||||
// out of disk space?
|
||||
}
|
||||
if f.Config != nil {
|
||||
if e := f.Config.ConfigSave(); e != nil {
|
||||
log.Info("forge.Config.ConfigSave() error", e)
|
||||
err = e
|
||||
}
|
||||
}
|
||||
if f.Repos != nil {
|
||||
if f.HasFullScan() {
|
||||
f.Repos.HasFullScan = true
|
||||
t := time.Now()
|
||||
f.Repos.FullScan = timestamppb.New(t)
|
||||
}
|
||||
if e := f.Repos.ConfigSave(); e != nil {
|
||||
log.Info("forge.Repos.ConfigSave() error", e)
|
||||
err = e
|
||||
}
|
||||
}
|
||||
// try to backup the current cluster config files
|
||||
if err := backupConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := f.Config.Marshal()
|
||||
|
||||
// write to ~/.config/forge/ unless ENV{FORGE_CONFIG} is set
|
||||
func (f *ForgeConfigs) ConfigSave() error {
|
||||
data, err := f.Marshal()
|
||||
if err != nil {
|
||||
log.Info("proto.Marshal() failed len", len(data), err)
|
||||
return err
|
||||
}
|
||||
log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data))
|
||||
configWrite("forge.pb", data)
|
||||
// log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data))
|
||||
|
||||
s := f.Config.FormatTEXT()
|
||||
s := f.FormatTEXT()
|
||||
configWrite("forge.text", []byte(s))
|
||||
|
||||
s = f.Config.FormatJSON()
|
||||
s = f.FormatJSON()
|
||||
configWrite("forge.json", []byte(s))
|
||||
|
||||
if f.Repos != nil {
|
||||
f.Repos.ConfigSave()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// load the ~/.config/forge/ files
|
||||
func (c *ForgeConfigs) ConfigLoad() error {
|
||||
if os.Getenv("FORGE_HOME") == "" {
|
||||
if os.Getenv("FORGE_CONFIG") == "" {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
fullpath := filepath.Join(homeDir, ".config/forge")
|
||||
os.Setenv("FORGE_HOME", fullpath)
|
||||
os.Setenv("FORGE_CONFIG", fullpath)
|
||||
}
|
||||
var data []byte
|
||||
var err error
|
||||
// var data []byte
|
||||
// var err error
|
||||
if c == nil {
|
||||
// can't safely do c = new(ForgeConfig) if c is in a struct from the caller. notsure why
|
||||
// TODO: recheck this. it might work now? It's probably still a bad idea(?)
|
||||
return errors.New("It's not safe to run ConfigLoad() on a nil")
|
||||
}
|
||||
|
||||
if data, err = loadFile("forge.pb"); err != nil {
|
||||
// something went wrong loading the file
|
||||
return err
|
||||
}
|
||||
if data != nil {
|
||||
// this means the forge.pb file exists and was read
|
||||
if len(data) == 0 {
|
||||
// todo: error out if the file is empty?
|
||||
// try forge.text & forge.json?
|
||||
}
|
||||
if err = c.Unmarshal(data); err != nil {
|
||||
log.Warn("broken forge.pb config file")
|
||||
return err
|
||||
}
|
||||
log.Info("found", len(c.ForgeConfigs), "entries in ~/.config/forge")
|
||||
return nil
|
||||
}
|
||||
|
||||
// forge.db doesn't exist. try forge.text
|
||||
// this lets the user hand edit the config
|
||||
if data, err = loadFile("forge.text"); err != nil {
|
||||
// something went wrong loading the file
|
||||
return err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
// this means the forge.text file exists and was read
|
||||
if len(data) == 0 {
|
||||
// todo: error out if the file is empty?
|
||||
}
|
||||
if err = c.UnmarshalTEXT(data); err != nil {
|
||||
log.Warn("broken forge.text config file")
|
||||
return err
|
||||
}
|
||||
log.Info("found", len(c.ForgeConfigs), "entries in ~/.config/forge")
|
||||
if err := c.loadText(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// forge.text doesn't exist. try forge.json
|
||||
// this lets the user hand edit the config
|
||||
if data, err = loadFile("forge.json"); err != nil {
|
||||
// something went wrong loading the file
|
||||
return err
|
||||
// this lets the user hand edit the JSON config
|
||||
// probably just deprecate this
|
||||
if data, err := loadFile("forge.json"); err != nil {
|
||||
if data != nil {
|
||||
// this means the forge.json file exists and was read
|
||||
if len(data) != 0 {
|
||||
if err = c.UnmarshalJSON(data); err == nil {
|
||||
log.Info("forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
|
||||
// forge.text file was broken. save on load right away
|
||||
log.Info("attempting forge.ConfigSave()")
|
||||
c.ConfigSave()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
// this means the forge.text file exists and was read
|
||||
if len(data) == 0 {
|
||||
// todo: error out if the file is empty?
|
||||
}
|
||||
if err = c.UnmarshalJSON(data); err != nil {
|
||||
log.Warn("broken forge.json config file")
|
||||
return err
|
||||
}
|
||||
log.Info("found", len(c.ForgeConfigs), "entries in ~/.config/forge")
|
||||
return nil
|
||||
cpath := filepath.Join(os.Getenv("FORGE_CONFIG"), ".")
|
||||
if _, err := os.Stat(cpath); err == nil {
|
||||
log.Info("Something has gone wrong. Your", os.Getenv("FORGE_CONFIG"), "directory exists")
|
||||
log.Info("However, the config files could not be loaded")
|
||||
}
|
||||
|
||||
// first time user. make a template config file
|
||||
|
@ -122,8 +112,29 @@ func (c *ForgeConfigs) ConfigLoad() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *ForgeConfigs) loadText() error {
|
||||
// this lets the user hand edit the config
|
||||
data, err := loadFile("forge.text")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if data == nil {
|
||||
return fmt.Errorf("forge.text data was nil")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("forge.text was empty")
|
||||
}
|
||||
|
||||
// attempt to marshal forge.text
|
||||
if err := c.UnmarshalTEXT(data); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Log(INFO, "forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadFile(filename string) ([]byte, error) {
|
||||
fullname := filepath.Join(os.Getenv("FORGE_HOME"), filename)
|
||||
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
|
||||
data, err := os.ReadFile(fullname)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// if file does not exist, just return nil. this
|
||||
|
@ -139,9 +150,9 @@ func loadFile(filename string) ([]byte, error) {
|
|||
}
|
||||
|
||||
func configWrite(filename string, data []byte) error {
|
||||
fullname := filepath.Join(os.Getenv("FORGE_HOME"), filename)
|
||||
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
|
||||
|
||||
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
|
||||
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
defer cfgfile.Close()
|
||||
if err != nil {
|
||||
log.Warn("open config file :", err)
|
||||
|
@ -149,13 +160,21 @@ func configWrite(filename string, data []byte) error {
|
|||
}
|
||||
if filename == "forge.text" {
|
||||
// add header
|
||||
cfgfile.Write([]byte("# this file is automatically re-generated from forge.pb, however,\n"))
|
||||
cfgfile.Write([]byte("# if you want to edit it by hand, you can:\n"))
|
||||
cfgfile.Write([]byte("# stop forge; remove forge.pb; edit forge.text; start forge\n"))
|
||||
cfgfile.Write([]byte("# this will cause the default behavior to fallback to parsing this file for the config\n"))
|
||||
cfgfile.Write([]byte("\n"))
|
||||
cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n"))
|
||||
cfgfile.Write([]byte("# git repos you have write access to. That is, where you can run 'git push'\n"))
|
||||
cfgfile.Write([]byte("\n"))
|
||||
}
|
||||
if filename == "forge.json" {
|
||||
// add header
|
||||
cfgfile.Write([]byte("\n"))
|
||||
cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n"))
|
||||
cfgfile.Write([]byte("# git repos you have write access to. That is, where you can run 'git push'\n"))
|
||||
cfgfile.Write([]byte("\n"))
|
||||
cfgfile.Write([]byte("# this file is parsed only if forge.text is missing\n"))
|
||||
cfgfile.Write([]byte("# also, these comment lines don't work in json files and have to be removed for Marshal() to work\n"))
|
||||
cfgfile.Write([]byte("# probably, JSON syntax for this is just going to be deprecated for the TEXT syntax\n"))
|
||||
cfgfile.Write([]byte("\n"))
|
||||
}
|
||||
cfgfile.Write(data)
|
||||
return nil
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
@ -15,12 +14,8 @@ import (
|
|||
|
||||
func backupConfig() error {
|
||||
// make a new dir to backup the files
|
||||
now := time.Now()
|
||||
// timestamp := now.Format("2022.07.18.190545") // 50yr shout out to K&R
|
||||
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
|
||||
srcDir := filepath.Join(os.Getenv("FORGE_HOME"))
|
||||
destDir := filepath.Join(os.Getenv("FORGE_HOME"), timestamp)
|
||||
|
||||
srcDir := filepath.Join(os.Getenv("FORGE_CONFIG"))
|
||||
destDir := filepath.Join(os.Getenv("FORGE_CONFIG"), "backup")
|
||||
return backupFiles(srcDir, destDir)
|
||||
}
|
||||
|
||||
|
@ -44,7 +39,7 @@ func backupFiles(srcDir string, destDir string) error {
|
|||
continue
|
||||
}
|
||||
|
||||
log.Println("backing up file", entry.Name())
|
||||
// log.Println("backing up file", entry.Name())
|
||||
srcPath := filepath.Join(srcDir, entry.Name())
|
||||
destPath := filepath.Join(destDir, entry.Name())
|
||||
|
||||
|
@ -64,6 +59,9 @@ func copyFile(src, dest string) error {
|
|||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
now := time.Now()
|
||||
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
|
||||
dest = dest + timestamp
|
||||
destFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -2,8 +2,6 @@ package forgepb
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (all *ForgeConfigs) sampleConfig() {
|
||||
|
@ -11,53 +9,7 @@ func (all *ForgeConfigs) sampleConfig() {
|
|||
new1.GoPath = "go.wit.com"
|
||||
new1.Writable = true
|
||||
new1.Directory = true
|
||||
if all.Append(new1) {
|
||||
log.Info("added", new1.GoPath, "ok")
|
||||
} else {
|
||||
log.Info("added", new1.GoPath, "failed")
|
||||
}
|
||||
|
||||
new1 = new(ForgeConfig)
|
||||
new1.GoPath = "go.wit.com/apps/zookeeper"
|
||||
new1.DebName = "zookeeper-go"
|
||||
if all.Append(new1) {
|
||||
log.Info("added", new1.GoPath, "ok")
|
||||
} else {
|
||||
log.Info("added", new1.GoPath, "failed")
|
||||
}
|
||||
|
||||
new1 = new(ForgeConfig)
|
||||
new1.GoPath = "go.wit.com/apps/wit-package"
|
||||
new1.Private = true
|
||||
if all.Append(new1) {
|
||||
log.Info("added", new1.GoPath, "ok")
|
||||
} else {
|
||||
log.Info("added", new1.GoPath, "failed")
|
||||
}
|
||||
|
||||
new1 = new(ForgeConfig)
|
||||
new1.GoPath = "go.wit.com/apps/networkQuality"
|
||||
new1.DebName = "networkquality"
|
||||
new1.ReadOnly = true
|
||||
if all.Append(new1) {
|
||||
log.Info("added", new1.GoPath, "ok")
|
||||
} else {
|
||||
log.Info("added", new1.GoPath, "failed")
|
||||
}
|
||||
|
||||
new2 := new(ForgeConfig)
|
||||
new2.GoPath = "go.wit.com/apps/go-clone"
|
||||
if all.Append(new2) {
|
||||
log.Info("added", new2.GoPath, "ok")
|
||||
} else {
|
||||
log.Info("added", new2.GoPath, "failed")
|
||||
}
|
||||
|
||||
if all.Append(new2) {
|
||||
log.Info("added", new2.GoPath, "ok (this is bad)")
|
||||
} else {
|
||||
log.Info("added", new2.GoPath, "failed (but ok)")
|
||||
}
|
||||
all.Append(new1)
|
||||
|
||||
fmt.Println("first time user. adding an example config file with", len(all.ForgeConfigs), "repos")
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
package forgepb
|
||||
|
||||
/*
|
||||
check settings for a particular gopath
|
||||
this provides checks for:
|
||||
lookup settings for a particular *gitpb.Repo or gopath string
|
||||
|
||||
IsReadOnly() // user can't push commits
|
||||
IsPrivate() // repo can't be published to the pkg.go.dev system
|
||||
user settings are configured in ~/.config/forge/forge.text
|
||||
|
||||
// searchs by string
|
||||
Configs.IsReadOnly(path) // user can't push commits
|
||||
Configs.IsWritable(path) // the opposite, but maybe different so I put both here
|
||||
|
||||
IsPrivate(repo) // repo can't be published to the pkg.go.dev system
|
||||
DebName() // for 'zookeeper' returns 'zookeeper-go'
|
||||
|
||||
This code is practical, not perfect
|
||||
*/
|
||||
|
||||
import (
|
||||
|
@ -14,12 +20,6 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
func (f *Forge) SortByGoPath() *ForgeConfigIterator {
|
||||
return f.Config.SortByPath()
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func (all *ForgeConfigs) UpdateGoPath(name string, gopath string) bool {
|
||||
oldr := all.DeleteByGoPath(name)
|
||||
|
@ -36,12 +36,12 @@ func (all *ForgeConfigs) UpdateGoPath(name string, gopath string) bool {
|
|||
|
||||
// returns true if gopath is readonly()
|
||||
// will attempt to match IsWritable("foo") against anything ending in "foo"
|
||||
func (f *Forge) IsReadOnly(gopath string) bool {
|
||||
func (fc *ForgeConfigs) IsReadOnly(gopath string) bool {
|
||||
var match *ForgeConfig
|
||||
|
||||
loop := f.Config.SortByGoPath() // get the list of repos
|
||||
for loop.Scan() {
|
||||
r := loop.Next()
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == gopath {
|
||||
// exact gopath match
|
||||
if r.Writable {
|
||||
|
@ -96,13 +96,13 @@ func (f *Forge) IsReadOnly(gopath string) bool {
|
|||
// this let's you check a git tag version against a package .deb version
|
||||
// allows gopath's to not need to match the .deb name
|
||||
// this is important in lots of cases! It is normal and happens often enough.
|
||||
func (f *Forge) DebName(gopath string) string {
|
||||
func (fc *ForgeConfigs) DebName(gopath string) string {
|
||||
// get "zookeeper" from "go.wit.com/apps/zookeeper"
|
||||
normalBase := filepath.Base(gopath)
|
||||
|
||||
loop := f.Config.SortByGoPath()
|
||||
for loop.Scan() {
|
||||
r := loop.Next()
|
||||
all := fc.SortByGoPath()
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == gopath {
|
||||
// returns "zookeeper-go" for "go.wit.com/apps/zookeeper"
|
||||
if r.DebName != "" {
|
||||
|
@ -123,15 +123,15 @@ func (f *Forge) DebName(gopath string) string {
|
|||
//
|
||||
// IsPrivate("go.foo.com/jcarr/foo") returns true if private
|
||||
// IsPrivate("foo") also returns true if "go.bar.com/jcarr/foo" is private
|
||||
func (f *Forge) IsPrivate(thing string) bool {
|
||||
func (fc *ForgeConfigs) IsPrivate(thing string) bool {
|
||||
var match *ForgeConfig
|
||||
|
||||
// sort by path means the simple 'match' logic
|
||||
// here works in the sense the last directory match
|
||||
// is the one that is used
|
||||
loop := f.Config.SortByGoPath() // get the list of repos
|
||||
for loop.Scan() {
|
||||
r := loop.Next()
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == thing {
|
||||
// if private is set here, then ok, otherwise
|
||||
// still check if a Directory match exists
|
||||
|
@ -167,12 +167,12 @@ func (f *Forge) IsPrivate(thing string) bool {
|
|||
// file that lets you set things as favorites
|
||||
// so you can just go-clone a bunch of common things
|
||||
// on a new box or after you reset/delete your ~/go/src dir
|
||||
func (f *Forge) IsFavorite(thing string) bool {
|
||||
func (fc *ForgeConfigs) IsFavorite(thing string) bool {
|
||||
var match *ForgeConfig
|
||||
|
||||
loop := f.Config.SortByGoPath() // get the list of repos
|
||||
for loop.Scan() {
|
||||
r := loop.Next()
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == thing {
|
||||
if r.Favorite {
|
||||
return true
|
||||
|
@ -196,3 +196,129 @@ func (f *Forge) IsFavorite(thing string) bool {
|
|||
|
||||
return match.Favorite
|
||||
}
|
||||
|
||||
// IsWritable() checks your .config/forge/ settings
|
||||
// looks for an exact match, then
|
||||
// looks for a directory match
|
||||
func (fc *ForgeConfigs) IsWritable(thing string) bool {
|
||||
var match *ForgeConfig
|
||||
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == thing {
|
||||
if r.Writable {
|
||||
return true
|
||||
}
|
||||
}
|
||||
base := filepath.Base(r.GoPath)
|
||||
if base == thing {
|
||||
if r.Writable {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if r.Directory {
|
||||
if strings.HasPrefix(thing, r.GoPath) {
|
||||
match = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if match == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.Writable
|
||||
}
|
||||
|
||||
// allows custom user branch names in the forge config
|
||||
func (fc *ForgeConfigs) FindUserBranch(thing string) string {
|
||||
var match *ForgeConfig
|
||||
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == thing {
|
||||
if r.UserBranchName != "" {
|
||||
return r.UserBranchName
|
||||
}
|
||||
}
|
||||
base := filepath.Base(r.GoPath)
|
||||
if base == thing {
|
||||
if r.UserBranchName != "" {
|
||||
return r.UserBranchName
|
||||
}
|
||||
}
|
||||
if r.Directory {
|
||||
if strings.HasPrefix(thing, r.GoPath) {
|
||||
match = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if match == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return match.UserBranchName
|
||||
}
|
||||
|
||||
// allows custom devel branch names in the forge config
|
||||
func (fc *ForgeConfigs) FindDevelBranch(thing string) string {
|
||||
var match *ForgeConfig
|
||||
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == thing {
|
||||
if r.DevelBranchName != "" {
|
||||
return r.DevelBranchName
|
||||
}
|
||||
}
|
||||
base := filepath.Base(r.GoPath)
|
||||
if base == thing {
|
||||
if r.DevelBranchName != "" {
|
||||
return r.DevelBranchName
|
||||
}
|
||||
}
|
||||
if r.Directory {
|
||||
if strings.HasPrefix(thing, r.GoPath) {
|
||||
match = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if match == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return match.DevelBranchName
|
||||
}
|
||||
|
||||
// allows custom devel branch names in the forge config
|
||||
func (fc *ForgeConfigs) FindMasterBranch(thing string) string {
|
||||
var match *ForgeConfig
|
||||
|
||||
all := fc.SortByGoPath() // get the list of repos
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
if r.GoPath == thing {
|
||||
if r.MasterBranchName != "" {
|
||||
return r.MasterBranchName
|
||||
}
|
||||
}
|
||||
base := filepath.Base(r.GoPath)
|
||||
if base == thing {
|
||||
if r.MasterBranchName != "" {
|
||||
return r.MasterBranchName
|
||||
}
|
||||
}
|
||||
if r.Directory {
|
||||
if strings.HasPrefix(thing, r.GoPath) {
|
||||
match = r
|
||||
}
|
||||
}
|
||||
}
|
||||
if match == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return match.MasterBranchName
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// DOES NOT MODIFY ANYTHING
|
||||
//
|
||||
// this is a final check to make sure, before pushing
|
||||
// a golang repo, that the go.sum file has the correct
|
||||
// and current version of every package
|
||||
//
|
||||
// it re-scans the go.sum file. DOES NOT MODIFY ANYTHING
|
||||
// this is the last thing to run to double check everything
|
||||
// before 'git tag' or git push --tags
|
||||
func (f *Forge) FinalGoDepsCheckOk(check *gitpb.Repo, verbose bool) error {
|
||||
if check == nil {
|
||||
return errors.New("FinalGoDepsCheckOk() boo, check == nil")
|
||||
}
|
||||
|
||||
// parse the go.mod and go.sum files
|
||||
if !check.ParseGoSum() {
|
||||
return fmt.Errorf("forge.ParseGoSum() failed. go.mod & go.sum are broken")
|
||||
}
|
||||
|
||||
if check.GetGoPrimitive() {
|
||||
return nil
|
||||
}
|
||||
|
||||
deps := check.GoDeps.SortByGoPath()
|
||||
for deps.Scan() {
|
||||
depRepo := deps.Next()
|
||||
found := f.FindByGoPath(depRepo.GetGoPath())
|
||||
if found == nil {
|
||||
if f.CheckOverride(depRepo.GetGoPath()) {
|
||||
// skip this gopath because it's probably broken forever
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("dep not found: %s", depRepo.GetGoPath())
|
||||
}
|
||||
if depRepo.GetVersion() == found.GetMasterVersion() {
|
||||
// log.Printf("%-48s error ?? %-10s vs %-10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
|
||||
continue
|
||||
}
|
||||
// log.Info("found dep", depRepo.GetGoPath())
|
||||
if depRepo.GetVersion() != found.GetTargetVersion() {
|
||||
check := f.FindByGoPath(depRepo.GetGoPath())
|
||||
if f.Config.IsReadOnly(check.GetGoPath()) {
|
||||
if verbose {
|
||||
log.Printf("%-48s ok error .%s. vs .%s. (ignoring read-only repo)\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
|
||||
}
|
||||
} else {
|
||||
if f.CheckOverride(depRepo.GetGoPath()) {
|
||||
if verbose {
|
||||
log.Printf("%-48s ok error .%s. vs .%s. (forge.CheckOverride())\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
|
||||
}
|
||||
// skip this gopath because it's probably broken forever
|
||||
continue
|
||||
} else {
|
||||
// log.Printf("%-48s error ?? %-10s vs %-10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
|
||||
// log.Printf("%-48s error %10s vs %10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
|
||||
return fmt.Errorf("%-48s error %10s vs %10s", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Forge) CheckOverride(gopath string) bool {
|
||||
if gopath == "cloud.google.com/go" {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
if gopath == "bou.ke/monkey" {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
if gopath == "github.com/posener/complete/v2" {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(gopath, "github.com/go-gl") {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(gopath, "google.golang.org") {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(gopath, "go.opencensus.io") {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(gopath, "github.com/nicksnyder/go-i18n") {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
// fuckit for now. just blacklist github.com
|
||||
if strings.HasPrefix(gopath, "github.com/") {
|
||||
// log.Info("CheckOverride() is ignoring", gopath)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *Forge) TestGoDepsCheckOk(godeps *gitpb.GoDeps, verbose bool) error {
|
||||
if godeps == nil {
|
||||
return errors.New("forge.TestGoDepsCheckOk() godeps == nil")
|
||||
}
|
||||
all := godeps.SortByGoPath()
|
||||
for all.Scan() {
|
||||
depRepo := all.Next()
|
||||
found := f.FindByGoPath(depRepo.GetGoPath())
|
||||
if found == nil {
|
||||
if f.CheckOverride(depRepo.GetGoPath()) {
|
||||
// skip this gopath because it's probably broken forever
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("dep not found: %s", depRepo.GetGoPath())
|
||||
}
|
||||
if depRepo.GetVersion() == found.GetMasterVersion() {
|
||||
// log.Printf("%-48s error ?? %-10s vs %-10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
|
||||
continue
|
||||
}
|
||||
// log.Info("found dep", depRepo.GetGoPath())
|
||||
if depRepo.GetVersion() != found.GetTargetVersion() {
|
||||
check := f.FindByGoPath(depRepo.GetGoPath())
|
||||
if f.Config.IsReadOnly(check.GetGoPath()) {
|
||||
if verbose {
|
||||
log.Printf("%-48s ok error .%s. vs .%s. (ignoring read-only repo)\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
|
||||
}
|
||||
} else {
|
||||
if f.CheckOverride(depRepo.GetGoPath()) {
|
||||
if verbose {
|
||||
log.Printf("%-48s ok error .%s. vs .%s. (forge.CheckOverride())\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
|
||||
}
|
||||
// skip this gopath because it's probably broken forever
|
||||
continue
|
||||
} else {
|
||||
// log.Printf("%-48s error ?? %-10s vs %-10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
|
||||
// log.Printf("%-48s error %10s vs %10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
|
||||
return fmt.Errorf("%-48s error %10s vs %10s", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package forgepb;
|
||||
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
// `autogenpb:uuid:7267f5d5-954b-44b7-9f67-2eb808791355` // todo: add file support
|
||||
|
||||
// define 3 branches. that is all that is supported
|
||||
// the term 'master' is used in the code because 'main' is a reserved word in golang already
|
||||
// allow 'read only' and 'private' flags
|
||||
// package names sometimes must be different than the binary name
|
||||
// for example 'zookeeper' is packaged as 'zookeeper-go'
|
||||
// due to the prior apache foundation project. This happens and is ok!
|
||||
message ForgeConfig {
|
||||
string goPath = 1; // `autogenpb:unique` // Examples: 'go.wit.com/apps/go-clone' or "~/mythings" or "/home/src/foo"
|
||||
message ForgeConfig { // `autogenpb:nomutex`
|
||||
string goPath = 1; // `autogenpb:unique` `autogenpb:sort` // Examples: 'go.wit.com/apps/go-clone' or "~/mythings" or "/home/src/foo"
|
||||
|
||||
bool writable = 2; // if you have write access to the repo
|
||||
bool readOnly = 3; // the opposite, but needed for now because I don't know what I'm doing
|
||||
|
@ -25,15 +29,34 @@ message ForgeConfig {
|
|||
string userBranchName = 10; // whatever your username branch is
|
||||
|
||||
string debName = 11; // the actual name used with 'apt install' (or distro apt equivalent.
|
||||
// todo: appeal to everyone to alias 'apt' on rhat, gentoo, arch, etc to alias 'apt install'
|
||||
// todo: appeal to everyone to alias 'apt' on fedora, gentoo, arch, etc to alias 'apt install'
|
||||
// so we can make easier instructions for new linux users. KISS
|
||||
|
||||
google.protobuf.Timestamp verstamp = 12; // the git commit timestamp of the version
|
||||
string goSrc = 13; // is ~/go/src unless a go.work file is found
|
||||
}
|
||||
|
||||
// TODO: autogen 'sort', 'marshal'
|
||||
message ForgeConfigs { // `autogenpb:marshal`
|
||||
string uuid = 1; // could be useful for /usr/share/file/magic someday?
|
||||
string version = 2; // could be used for protobuf schema change violations?
|
||||
repeated ForgeConfig ForgeConfigs = 3;
|
||||
// todo: fix autogenpb to look for enum
|
||||
enum ForgeMode {
|
||||
MASTER = 0; // "release mode"
|
||||
DEVEL = 1; // "patch mode"
|
||||
USER = 2; // "work mode"
|
||||
}
|
||||
|
||||
message ForgeConfigs { // `autogenpb:marshal` `autogenpb:nomutex`
|
||||
string uuid = 1; // `autogenpb:uuid:1941cd4f-1cfd-4bf6-aa75-c2c391907e81`
|
||||
string version = 2; // `autogenpb:version:v0.0.47`
|
||||
repeated ForgeConfig ForgeConfigs = 3;
|
||||
string username = 4; // what to use for the user branch (default ENV{USER})
|
||||
string xterm = 5; // what xterm the user wants as the default
|
||||
repeated string xtermArgv = 6; // the argv line for xterm
|
||||
string defaultGui = 7; // default GUI plugin to use
|
||||
ForgeMode mode = 8; // what "mode" forge is in
|
||||
}
|
||||
|
||||
// this generic message is used by autogen to identify and
|
||||
// then dump the uuid and version from any arbitrary .pb file
|
||||
message Identify { // `autogenpb:marshal`
|
||||
string uuid = 1; //
|
||||
string version = 2; //
|
||||
}
|
||||
|
|
38
goSrcFind.go
38
goSrcFind.go
|
@ -9,27 +9,37 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (f *Forge) GetHome() string {
|
||||
return f.goSrc
|
||||
}
|
||||
|
||||
// look for a go.work file
|
||||
// otherwise use ~/go/src
|
||||
func FindGoSrc() (string, error) {
|
||||
func (f *Forge) findGoSrc() (string, error) {
|
||||
pwd, err := os.Getwd()
|
||||
// startpwd, _ := os.Getwd()
|
||||
if err == nil {
|
||||
// Check for go.work in the current directory and then move up until root
|
||||
if pwd, err := digup(pwd); err == nil {
|
||||
log.Info("using go.work file in directory", pwd)
|
||||
f.goWork = true
|
||||
// found an existing go.work file
|
||||
// os.Chdir(pwd)
|
||||
return pwd, nil
|
||||
} else {
|
||||
// if there is an error looking for the go.work file
|
||||
// default to using ~/go/src
|
||||
// log.Info("forge.digup() err", pwd, err)
|
||||
}
|
||||
} else {
|
||||
// this shouldn't really happen. maybe your working directory got deleted
|
||||
log.Info("forge.findGoSrc() os.Getwd() was probably deleted", pwd, err)
|
||||
}
|
||||
|
||||
// there are no go.work files, resume the ~/go/src behavior from prior to golang 1.22
|
||||
pwd, err = useGoSrc()
|
||||
log.Info("using ~/go/src directory", pwd)
|
||||
return pwd, err
|
||||
}
|
||||
|
||||
|
@ -41,9 +51,23 @@ func useGoSrc() (string, error) {
|
|||
return "", err
|
||||
}
|
||||
pwd := filepath.Join(homeDir, "go/src")
|
||||
shell.Mkdir(pwd)
|
||||
// os.Chdir(pwd)
|
||||
return pwd, nil
|
||||
err = os.MkdirAll(pwd, os.ModePerm)
|
||||
return pwd, err
|
||||
}
|
||||
|
||||
func (f *Forge) goWorkExists() bool {
|
||||
var err error
|
||||
workFilePath := filepath.Join(f.GetGoSrc(), "go.work")
|
||||
if _, err = os.Stat(workFilePath); err == nil {
|
||||
// log.Info("f.goWorkExists() found", workFilePath)
|
||||
return true
|
||||
} else if !os.IsNotExist(err) {
|
||||
// log.Info("f.goWorkExists() missing", workFilePath)
|
||||
return false
|
||||
}
|
||||
// probably false, but some other error
|
||||
// log.Info("f.goWorkExists() os.Stat() error", err, workFilePath)
|
||||
return false
|
||||
}
|
||||
|
||||
func digup(path string) (string, error) {
|
||||
|
|
143
goSrcScan.go
143
goSrcScan.go
|
@ -2,6 +2,7 @@ package forgepb
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -12,43 +13,107 @@ import (
|
|||
)
|
||||
|
||||
func (f *Forge) ScanGoSrc() (bool, error) {
|
||||
dirs, err := gitDirectories(f.goSrc)
|
||||
dirs, err := gitDirectoriesNew(f.goSrc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var gopaths []string
|
||||
for _, dir := range dirs {
|
||||
// log.Info("forge.ScanGoSrc()", dir)
|
||||
if strings.HasPrefix(dir, f.goSrc) {
|
||||
gopath := strings.TrimPrefix(dir, f.goSrc)
|
||||
gopath = strings.Trim(gopath, "/")
|
||||
if r := f.FindByGoPath(gopath); r != nil {
|
||||
// log.Info("already have", gopath)
|
||||
continue
|
||||
}
|
||||
gopaths = append(gopaths, gopath)
|
||||
} else {
|
||||
log.Log(FORGEPBWARN, "ScanGoSrc() bad:", dir)
|
||||
log.Log(WARN, "ScanGoSrc() bad:", dir)
|
||||
return false, errors.New("forgepb.ScanGoSrc() bad dir: " + dir)
|
||||
}
|
||||
}
|
||||
f.rillScanDirs(gopaths)
|
||||
|
||||
/*
|
||||
for _, dir := range dirs {
|
||||
if strings.HasPrefix(dir, f.goSrc) {
|
||||
gopath := strings.TrimPrefix(dir, f.goSrc)
|
||||
gopath = strings.Trim(gopath, "/")
|
||||
repo, err := f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
} else {
|
||||
log.Log(FORGEPBWARN, "ScanGoSrc() bad:", dir)
|
||||
newcount, err := f.rillScanDirs(gopaths)
|
||||
if err != nil {
|
||||
log.Info("go src dir problem. exit for now?", err)
|
||||
return false, err
|
||||
}
|
||||
if newcount != 0 {
|
||||
log.Info("forge go src scan found", newcount, "repos")
|
||||
f.SetConfigSave(true)
|
||||
}
|
||||
*/
|
||||
return true, err
|
||||
}
|
||||
|
||||
func gitDirectories(srcDir string) ([]string, error) {
|
||||
func (f *Forge) ScanDir(dir string) *gitpb.Repo {
|
||||
// repo, err := f.NewGoRepo(gopath, "")
|
||||
repo, err := f.Repos.NewGoRepo(dir, "")
|
||||
log.Info("need to implement ScanDir()", dir, err)
|
||||
return repo
|
||||
}
|
||||
|
||||
// doesn't enter the directory any further when it finds a .git/
|
||||
// not stupid like my old version
|
||||
func gitDirectoriesNew(srcDir string) ([]string, error) {
|
||||
var all []string
|
||||
var trip bool
|
||||
err := filepath.WalkDir(srcDir, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
// Handle possible errors, like permission issues
|
||||
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
// log.Info("path is dir", path)
|
||||
} else {
|
||||
_, fname := filepath.Split(path)
|
||||
switch fname {
|
||||
case "repos.pb":
|
||||
case "go.work":
|
||||
case "go.work.last":
|
||||
default:
|
||||
// todo: figure out a way to do padding for init()
|
||||
if trip == false {
|
||||
log.Info("WARNING:")
|
||||
}
|
||||
log.Info("WARNING: you have an untracked file outside of any .git repository:", path)
|
||||
trip = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
gitdir := filepath.Join(path, ".git")
|
||||
_, err2 := os.Stat(gitdir)
|
||||
if !os.IsNotExist(err2) {
|
||||
all = append(all, path)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
//
|
||||
// probably always leave this here forever
|
||||
// this check, along with CheckDirty() makes sure you can safely delete ~/go/src or the go.work directory
|
||||
// because everything is either checked in or deleted. An important thing to know!
|
||||
if trip {
|
||||
log.Info("WARNING:")
|
||||
log.Info("WARNING: there isn't a way to disable this warning yet")
|
||||
log.Info("WARNING: probably this is a good thing however. you don't want to leave files outside of git repos here")
|
||||
log.Info("WARNING: so this warning should probably stay")
|
||||
log.Info("WARNING:")
|
||||
log.Info("WARNING: this also might mean you put these files here because you are actively working on them")
|
||||
log.Info("WARNING: and you don't want to forget about them")
|
||||
log.Info("WARNING:")
|
||||
}
|
||||
return all, err
|
||||
}
|
||||
|
||||
func gitDirectoriesOld(srcDir string) ([]string, error) {
|
||||
var all []string
|
||||
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Log(FORGEPBWARN, "Error accessing path:", path, err)
|
||||
log.Log(WARN, "Error accessing path:", path, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -61,7 +126,7 @@ func gitDirectories(srcDir string) ([]string, error) {
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
log.Log(FORGEPBWARN, "Error walking the path:", srcDir, err)
|
||||
log.Log(WARN, "Error walking the path:", srcDir, err)
|
||||
}
|
||||
|
||||
return all, err
|
||||
|
@ -77,51 +142,35 @@ func IsGitDir(dir string) bool {
|
|||
return info.IsDir()
|
||||
}
|
||||
|
||||
/*
|
||||
// rill is awesome. long live rill
|
||||
func rillAddDirs(gopaths []string) {
|
||||
// Convert a slice of user IDs into a channel
|
||||
ids := rill.FromSlice(gopaths, nil)
|
||||
|
||||
// Read users from the API.
|
||||
// Concurrency = 20
|
||||
dirs := rill.Map(ids, 20, func(id string) (*repolist.RepoRow, error) {
|
||||
return me.repos.View.FindByName(id), nil
|
||||
})
|
||||
|
||||
// Activate users.
|
||||
// Concurrency = 10
|
||||
err := rill.ForEach(dirs, 10, func(repo *repolist.RepoRow) error {
|
||||
fmt.Printf("Repo found : %s\n", repo.GoPath())
|
||||
repo.Run([]string{"git", "pull"})
|
||||
return nil
|
||||
})
|
||||
|
||||
// Handle errors
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
*/
|
||||
|
||||
// rill is awesome. long live rill
|
||||
// attempt scan with rill
|
||||
func (f *Forge) rillScanDirs(gopaths []string) error {
|
||||
func (f *Forge) rillScanDirs(gopaths []string) (int, error) {
|
||||
// Convert a slice of user IDs into a channel
|
||||
ids := rill.FromSlice(gopaths, nil)
|
||||
|
||||
// Read users from the API.
|
||||
// Concurrency = 20
|
||||
dirs := rill.Map(ids, 20, func(id string) (*gitpb.Repo, error) {
|
||||
return f.Repos.NewGoPath(f.goSrc, id)
|
||||
return f.checkpath(id, "")
|
||||
})
|
||||
|
||||
var counter int
|
||||
// Activate users.
|
||||
// Concurrency = 10
|
||||
err := rill.ForEach(dirs, 10, func(repo *gitpb.Repo) error {
|
||||
// could do something here
|
||||
// fmt.Printf("Repo found : %s\n", repo.GoPath)
|
||||
// repo.Run([]string{"git", "pull"})
|
||||
counter += 1
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
return counter, err
|
||||
}
|
||||
|
||||
func (f *Forge) checkpath(gopath string, url string) (*gitpb.Repo, error) {
|
||||
fullpath := filepath.Join(f.GetGoSrc(), gopath)
|
||||
log.Info("forge creating protobuf for", fullpath)
|
||||
repo, err := f.NewGoRepo(gopath, "")
|
||||
if err != nil {
|
||||
log.Info("\tprotobuf error", gopath, err)
|
||||
}
|
||||
return repo, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// very much a hack job
|
||||
func (f *Forge) MakeGoWork() error {
|
||||
if f.IsGoWork() {
|
||||
// a go.work file was found
|
||||
} else {
|
||||
// you can use a go.work file in ~/go/src , but you probably shouldn't unless something
|
||||
// has gone terribly wrong
|
||||
return errors.New("if you want a go.work file in ~/go/src/, touch it first")
|
||||
}
|
||||
filename := filepath.Join(f.GetGoSrc(), "go.work")
|
||||
workf, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer workf.Close()
|
||||
|
||||
fmt.Fprintln(workf, "go 1.21") // fix this
|
||||
fmt.Fprintln(workf, "")
|
||||
fmt.Fprintln(workf, "use (")
|
||||
|
||||
all := f.Repos.SortByFullPath()
|
||||
for all.Scan() {
|
||||
repo := all.Next()
|
||||
/*
|
||||
if !repo.IsGoLang() == "" {
|
||||
// skip repos that aren't go
|
||||
// todo: handle non-flat repos?
|
||||
log.Info("skip non-go", repo.GetGoPath)
|
||||
continue
|
||||
}
|
||||
*/
|
||||
if repo.GetGoPath() == "" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(workf, "\t"+repo.GetGoPath())
|
||||
/*
|
||||
if repo.pb.Exists("go.mod") {
|
||||
// log.Info("ADDING REPO", goSrcDir, repo.GetGoPath())
|
||||
} else {
|
||||
fmt.Fprintln(workf, "\t"+repo.GetGoPath)
|
||||
log.Log(REPO, "missing go.mod for", repo.GetGoPath())
|
||||
}
|
||||
*/
|
||||
}
|
||||
fmt.Fprintln(workf, ")")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/user"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
/*
|
||||
func (f *Forge) HttpPostMachine(url string) ([]byte, error) {
|
||||
if f.Machine == nil {
|
||||
// run f.InitMachine() here?
|
||||
log.Info("you must run f.InitMachine()")
|
||||
return nil, fmt.Errorf("you must run f.InitMachine()")
|
||||
}
|
||||
if f.Machine.Hostname == "" {
|
||||
log.Info("WTF. hostname is blank")
|
||||
} else {
|
||||
log.Info("GOOD. hostname is set to", f.Machine.Hostname)
|
||||
}
|
||||
log.Info("GOOD2. hostname is set to", f.Machine.Hostname)
|
||||
msg, err := f.Machine.Marshal()
|
||||
if err != nil {
|
||||
log.Info("proto.Marshal() failed:", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Info("GOOD3. hostname is set to", f.Machine.Hostname)
|
||||
|
||||
check := new(zoopb.Machine)
|
||||
check.Unmarshal(msg)
|
||||
if check == nil {
|
||||
log.Info("WTF. check == nil")
|
||||
}
|
||||
log.Info("good? check.hostname =", check.Hostname)
|
||||
return f.HttpPost(url, msg)
|
||||
}
|
||||
*/
|
||||
|
||||
func (f *Forge) HttpPost(url string, data []byte) ([]byte, error) {
|
||||
var err error
|
||||
var req *http.Request
|
||||
|
||||
req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
|
||||
// log.Info("httpPost() with len", len(data), "url", url)
|
||||
|
||||
usr, _ := user.Current()
|
||||
req.Header.Set("author", usr.Username)
|
||||
/*
|
||||
if f.Machine == nil {
|
||||
// run f.InitMachine() here?
|
||||
log.Info("you must run f.InitMachine()")
|
||||
return nil, fmt.Errorf("you must run f.InitMachine()")
|
||||
}
|
||||
*/
|
||||
req.Header.Set("hostname", "fixme:hostname")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return []byte("client.Do(req) error"), err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// log.Info("httpPost() with len", len(data))
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return body, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
82
human.go
82
human.go
|
@ -1,8 +1,12 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
|
@ -20,30 +24,92 @@ func standardHeader() string {
|
|||
func (f *Forge) standardHeader(r *ForgeConfig) string {
|
||||
var flags string
|
||||
var readonly string
|
||||
if f.IsPrivate(r.GoPath) {
|
||||
if f.Config.IsPrivate(r.GoPath) {
|
||||
flags += "(private) "
|
||||
}
|
||||
if f.IsFavorite(r.GoPath) {
|
||||
if f.Config.IsFavorite(r.GoPath) {
|
||||
flags += "(favorite) "
|
||||
}
|
||||
if f.IsReadOnly(r.GoPath) {
|
||||
if f.Config.IsReadOnly(r.GoPath) {
|
||||
readonly = ""
|
||||
} else {
|
||||
readonly = "r/w"
|
||||
}
|
||||
if r.MasterBranchName != "" {
|
||||
flags += "(master=" + r.MasterBranchName + ") "
|
||||
}
|
||||
if r.DevelBranchName != "" {
|
||||
flags += "(devel=" + r.DevelBranchName + ") "
|
||||
}
|
||||
if r.UserBranchName != "" {
|
||||
flags += "(user=" + r.UserBranchName + ") "
|
||||
}
|
||||
if r.DebName != "" {
|
||||
flags += "(deb=" + r.DebName + ") "
|
||||
}
|
||||
return fmt.Sprintf("%-4s %-40s %s", readonly, r.GoPath, flags)
|
||||
}
|
||||
|
||||
// print a human readable table to STDOUT
|
||||
func (f *Forge) ConfigPrintTable() {
|
||||
if f == nil {
|
||||
log.Info("WTF forge == nil")
|
||||
panic("WTF forge == nil")
|
||||
return
|
||||
}
|
||||
log.Info(standardHeader())
|
||||
loop := f.Config.SortByGoPath()
|
||||
for loop.Scan() {
|
||||
r := loop.Next()
|
||||
all := f.Config.SortByGoPath()
|
||||
for all.Scan() {
|
||||
r := all.Next()
|
||||
log.Info(f.standardHeader(r))
|
||||
}
|
||||
}
|
||||
|
||||
// show information while doing golang releases
|
||||
func (f *Forge) StandardReleaseHeader(repo *gitpb.Repo, state string) string {
|
||||
// tag := repo.NewestTag()
|
||||
// gitAge, _ := tag.GetDate()
|
||||
dur := repo.NewestAge()
|
||||
|
||||
curname := repo.GetCurrentBranchName()
|
||||
|
||||
lastTag := repo.GetLastTag()
|
||||
target := repo.GetTargetVersion()
|
||||
master := repo.GetMasterVersion()
|
||||
user := repo.GetUserVersion()
|
||||
|
||||
header := fmt.Sprintf("%-35s %5s %-10s %-10s %-10s %-20s %-20s %-15s",
|
||||
repo.GetGoPath(), shell.FormatDuration(dur), curname,
|
||||
lastTag, target, master, user,
|
||||
state)
|
||||
return header
|
||||
}
|
||||
|
||||
func ReleaseReportHeader() string {
|
||||
return fmt.Sprintf("%-35s %5s %-10s %-10s %-10s %-20s %-20s %-15s",
|
||||
"REPO", "AGE", "CUR BR",
|
||||
"LAST", "TARGET",
|
||||
"MASTER", "USER",
|
||||
"STATE")
|
||||
}
|
||||
|
||||
func (f *Forge) PrintReleaseReport(repos *gitpb.Repos) int {
|
||||
var count int
|
||||
|
||||
log.Info(ReleaseReportHeader())
|
||||
|
||||
loop := repos.SortByFullPath()
|
||||
for loop.Scan() {
|
||||
check := loop.Next()
|
||||
count += 1
|
||||
if check == nil {
|
||||
// wtf
|
||||
continue
|
||||
}
|
||||
var state string
|
||||
if check.CheckDirty() {
|
||||
state = "(dirty)"
|
||||
}
|
||||
log.Info(f.StandardReleaseHeader(check, state))
|
||||
}
|
||||
log.Info(fmt.Sprintf("total repo count = %d", count))
|
||||
return count
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (f *Forge) HumanPrintRepo(check *gitpb.Repo) {
|
||||
if check == nil {
|
||||
log.Info("forge: you sent me nil")
|
||||
return
|
||||
}
|
||||
|
||||
if check.GetTargetVersion() == "" {
|
||||
log.Info("TargetVersion == blank")
|
||||
}
|
||||
if check.GetTargetVersion() == check.GetCurrentVersion() {
|
||||
log.Info("IsReleased() == true. do not release this a second time")
|
||||
} else {
|
||||
log.Info("IsReleased() == false")
|
||||
}
|
||||
if check.CheckDirty() {
|
||||
log.Info("CheckDirty() == true. do not release dirty repos")
|
||||
} else {
|
||||
log.Info("CheckDirty() == false")
|
||||
}
|
||||
if check.GetGoPrimitive() {
|
||||
log.Info("IsPrimitive() == true")
|
||||
} else {
|
||||
log.Info("IsPrimitive() == false")
|
||||
}
|
||||
if f.Config.IsPrivate(check.GetGoPath()) {
|
||||
log.Info("IsPrivate() == true")
|
||||
} else {
|
||||
log.Info("IsPrivate() == false")
|
||||
}
|
||||
if ok, compiled, err := check.IsProtobuf(); ok {
|
||||
log.Info(log.Sprint("IsProtobuf() == true compiled protobuf files = ", compiled))
|
||||
if err != nil {
|
||||
log.Info("IsProtobuf() ERROR = ", err)
|
||||
}
|
||||
for _, s := range compiled {
|
||||
log.Info("\tcompiled file found:", s)
|
||||
}
|
||||
} else {
|
||||
log.Info("IsProtobuf() == false")
|
||||
if err != nil {
|
||||
log.Info("IsProtobuf() ERROR = ", err)
|
||||
}
|
||||
}
|
||||
log.Info("git master name ==", check.GetMasterBranchName())
|
||||
log.Info("git devel name ==", check.GetDevelBranchName())
|
||||
log.Info("git user name ==", check.GetUserBranchName())
|
||||
log.Info("git current name ==", check.GetCurrentBranchName())
|
||||
|
||||
// testNext(check)
|
||||
|
||||
found := new(gitpb.Repos)
|
||||
if !found.AppendByGoPath(check) {
|
||||
log.Info("forgepb check. repo already existed", check.FullPath, check.GetGoPath())
|
||||
} else {
|
||||
log.Info("forgepb check. repo was new", check.FullPath, check.GetGoPath())
|
||||
}
|
||||
f.PrintHumanTable(found)
|
||||
|
||||
printTime("Last Pull", check.Times.LastPull.AsTime())
|
||||
printTime("Last Dirty", check.Times.LastDirty.AsTime())
|
||||
printTime("dir mtime", check.Times.MtimeDir.AsTime())
|
||||
printTime("HEAD mtime", check.Times.MtimeHead.AsTime())
|
||||
printTime("Index mtime", check.Times.MtimeIndex.AsTime())
|
||||
printTime("fetch", check.Times.MtimeFetch.AsTime())
|
||||
printTime("last go.sum", check.Times.LastGoDep.AsTime())
|
||||
printTime("last commit", check.Times.NewestCommit.AsTime())
|
||||
|
||||
now := time.Now()
|
||||
dur := now.Sub(check.Times.LastUpdate.AsTime())
|
||||
log.Printf("Repo Last Reload: %s\n", shell.FormatDuration(dur))
|
||||
}
|
||||
|
||||
func (f *Forge) testGoRepo(check *gitpb.Repo) {
|
||||
data, _ := os.ReadFile(filepath.Join(check.FullPath, "go.mod"))
|
||||
log.Info(string(data))
|
||||
|
||||
if err := f.FinalGoDepsCheckOk(check, true); err == nil {
|
||||
log.Info("forge.FinalGoDepsCheck(check) worked!")
|
||||
} else {
|
||||
log.Info("forge.FinalGoDepsCheck(check) failed. boo.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func printTime(s string, t time.Time) {
|
||||
now := time.Now()
|
||||
dur := now.Sub(t)
|
||||
if dur < (time.Hour * 24) {
|
||||
log.Printf("%s mtime last changed %s\n", s, shell.FormatDuration(dur))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// you can replace all of COBOL with this amount of GO
|
||||
|
||||
// ah yes, COBOL. what an ancient throwback. for those that know
|
||||
// then you know exactly what is in this file. For those that don't, here it is:
|
||||
|
||||
// All this does is output human readable text formatted to be viewable on
|
||||
// a console with a fixed with font. AKA: a typerwriter. Which is exactly
|
||||
// what COBOL did in the 1970's (60s? notsure) And the 80s.
|
||||
|
||||
// So, you want to dump out stuff on the console. Let's see. Something like
|
||||
|
||||
/*
|
||||
forge --favorites
|
||||
|
||||
go.wit.com/apps/myapp v0.2.0 (installed)
|
||||
go.wit.com/lib/somethingfun v0.0.7 (not downloaded)
|
||||
*/
|
||||
|
||||
// anyway, you get the idea. This is also called COBOL because it does on
|
||||
// thing and truncates every line output to the columns you see with stty -a
|
||||
// my monitor is huge, so it's not going to work at 80x24. 160x48 is better
|
||||
// actually, I'd predict some of these will probably end up 240 wide
|
||||
// long live good eyesight and 4K monitors!
|
||||
|
||||
func (f *Forge) PrintHumanTable(allr *gitpb.Repos) {
|
||||
log.DaemonMode(true)
|
||||
|
||||
var count int
|
||||
// log.Info(standardStart5("gopath", "cur name", "master", "user", "repo type"))
|
||||
log.Info(standardTable10("repopath", "cur br", "age", "master", "devel", "user", "curver", "lasttag", "next", "repo type"))
|
||||
all := allr.SortByFullPath()
|
||||
for all.Scan() {
|
||||
repo := all.Next()
|
||||
f.printRepoToTable(repo)
|
||||
count += 1
|
||||
}
|
||||
log.Info("Total git repositories:", count)
|
||||
}
|
||||
|
||||
// also shows which files are dirty
|
||||
func (f *Forge) PrintHumanTableDirty(allr *gitpb.Repos) {
|
||||
log.DaemonMode(true)
|
||||
|
||||
var count int
|
||||
// log.Info(standardStart5("gopath", "cur name", "master", "user", "repo type"))
|
||||
log.Info(standardTable10("repopath", "cur br", "age", "master", "devel", "user", "curver", "lasttag", "next", "repo type"))
|
||||
// all := allr.SortByFullPath()
|
||||
all := allr.All()
|
||||
for all.Scan() {
|
||||
repo := all.Next()
|
||||
f.printRepoToTable(repo)
|
||||
if len(repo.DirtyList) != 0 {
|
||||
for _, line := range repo.DirtyList {
|
||||
log.Info("\t", line)
|
||||
}
|
||||
}
|
||||
var mver string = repo.GetMasterVersion()
|
||||
repo.Tags.GetAge(mver)
|
||||
|
||||
count += 1
|
||||
}
|
||||
log.Info("Total git repositories:", count)
|
||||
}
|
||||
|
||||
func standardTable5(arg1, arg2, arg3, arg4, arg5 string) string {
|
||||
len1 := 40
|
||||
len2 := 12
|
||||
len3 := 12
|
||||
len4 := 16
|
||||
len5 := 8
|
||||
var s string
|
||||
if len(arg1) > len1 {
|
||||
arg1 = arg1[:len1]
|
||||
}
|
||||
s = "%-" + fmt.Sprintf("%d", len1) + "s "
|
||||
if len(arg2) > len2 {
|
||||
arg2 = arg2[:len2]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len2) + "s "
|
||||
if len(arg3) > len3 {
|
||||
arg3 = arg3[:len3]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len3) + "s "
|
||||
if len(arg4) > len4 {
|
||||
arg4 = arg4[:len4]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len4) + "s "
|
||||
if len(arg5) > len5 {
|
||||
arg5 = arg5[:len5]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len5) + "s"
|
||||
return fmt.Sprintf(s, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
|
||||
func standardTable10(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 string) string {
|
||||
len1 := 40
|
||||
len2 := 12
|
||||
len3 := 6
|
||||
len4 := 12
|
||||
len5 := 16
|
||||
len6 := 16
|
||||
len7 := 16
|
||||
len8 := 12
|
||||
len9 := 12
|
||||
len10 := 8
|
||||
var s string
|
||||
if len(arg1) > len1 {
|
||||
arg1 = arg1[:len1]
|
||||
}
|
||||
s = "%-" + fmt.Sprintf("%d", len1) + "s "
|
||||
if len(arg2) > len2 {
|
||||
arg2 = arg2[:len2]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len2) + "s "
|
||||
if len(arg3) > len3 {
|
||||
arg3 = arg3[:len3]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len3) + "s "
|
||||
if len(arg4) > len4 {
|
||||
arg4 = arg4[:len4]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len4) + "s "
|
||||
|
||||
if len(arg5) > len5 {
|
||||
arg5 = arg5[:len5]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len5) + "s "
|
||||
|
||||
if len(arg6) > len6 {
|
||||
arg6 = arg6[:len6]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len6) + "s "
|
||||
|
||||
if len(arg7) > len7 {
|
||||
arg7 = arg7[:len7]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len7) + "s "
|
||||
|
||||
if len(arg8) > len8 {
|
||||
arg8 = arg8[:len8]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len8) + "s "
|
||||
|
||||
if len(arg9) > len9 {
|
||||
arg9 = arg9[:len9]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len9) + "s "
|
||||
|
||||
if len(arg10) > len10 {
|
||||
arg10 = arg10[:len10]
|
||||
}
|
||||
s += "%-" + fmt.Sprintf("%d", len10) + "s "
|
||||
|
||||
return fmt.Sprintf(s, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
|
||||
}
|
||||
|
||||
func (f *Forge) printRepoToTable(repo *gitpb.Repo) {
|
||||
var end string
|
||||
|
||||
// shortened version numbers
|
||||
var mhort string = repo.GetMasterVersion()
|
||||
var dhort string = repo.GetDevelVersion()
|
||||
var uhort string = repo.GetUserVersion()
|
||||
if uhort == "uerr" {
|
||||
// blank these out
|
||||
uhort = ""
|
||||
}
|
||||
var lasttag string = repo.GetLastTag()
|
||||
var thort string = repo.GetTargetVersion()
|
||||
var chort string = repo.GetCurrentBranchVersion()
|
||||
var cname string = repo.GetCurrentBranchName()
|
||||
|
||||
var gopath string = repo.GetGoPath()
|
||||
var rtype string = repo.GetRepoType()
|
||||
|
||||
// ctime := repo.Tags.GetAge(mhort)
|
||||
// age := shell.FormatDuration(time.Since(ctime))
|
||||
age := shell.FormatDuration(repo.BranchAge(cname))
|
||||
|
||||
if f.Config.IsReadOnly(repo.GetGoPath()) {
|
||||
// end += "(readonly) "
|
||||
} else {
|
||||
end += "(rw) "
|
||||
}
|
||||
|
||||
if repo.IsDirty() {
|
||||
age = ""
|
||||
end += "(dirty) "
|
||||
}
|
||||
|
||||
start := standardTable10(gopath, cname, age, mhort, dhort, uhort, chort, lasttag, thort, rtype)
|
||||
|
||||
if rtype == "protobuf" {
|
||||
if repo.GoInfo.GoBinary {
|
||||
end += "(binary) "
|
||||
}
|
||||
}
|
||||
|
||||
if repo.GetMasterBranchName() != "master" && repo.GetMasterBranchName() != "main" {
|
||||
end += "(m:" + repo.GetMasterBranchName() + ") "
|
||||
}
|
||||
|
||||
if repo.GetDevelBranchName() != "devel" {
|
||||
end += "(d:" + repo.GetDevelBranchName() + ") "
|
||||
}
|
||||
|
||||
if repo.GetUserBranchName() != f.Config.Username {
|
||||
end += "(u:" + repo.GetUserBranchName() + ") "
|
||||
}
|
||||
|
||||
debname := f.Config.DebName(repo.GetGoPath())
|
||||
if debname != filepath.Base(gopath) {
|
||||
end += "(deb:" + debname + ") "
|
||||
}
|
||||
|
||||
switch repo.GetState() {
|
||||
case "PERFECT":
|
||||
case "unchanged":
|
||||
case "dirty":
|
||||
case "unknown branches":
|
||||
if repo.CurrentTag == nil {
|
||||
end += "(" + repo.GetState() + ") "
|
||||
} else {
|
||||
end += "(unknown branch " + repo.CurrentTag.Refname + ") "
|
||||
}
|
||||
// end += "(invalid tag) "
|
||||
default:
|
||||
end += "(" + repo.GetState() + ") "
|
||||
}
|
||||
|
||||
log.Info(start, end)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// print the protobuf in human form
|
||||
func IdentifyProtobuf(data []byte) error {
|
||||
var pb *Identify
|
||||
pb = new(Identify)
|
||||
if err := pb.Unmarshal(data); err != nil {
|
||||
log.Info("data can't be identified as a standard protobuf. len =", len(data))
|
||||
return err
|
||||
}
|
||||
log.Info("Identify protobuf file uuid =", pb.Uuid, "version =", pb.Version)
|
||||
return nil
|
||||
}
|
200
init.go
200
init.go
|
@ -1,53 +1,181 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/lib/protobuf/zoopb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// set FORGE_GOSRC env if not already set
|
||||
func init() {
|
||||
gosrc := os.Getenv("FORGE_GOSRC")
|
||||
if gosrc != "" {
|
||||
// already set. ignore init()
|
||||
// todo: use initOnce
|
||||
// cache.go has Do()
|
||||
// f.initOnce.Do(f.initWork)
|
||||
func Init() *Forge {
|
||||
f := InitPB()
|
||||
|
||||
/*
|
||||
f.Machine = new(zoopb.Machine)
|
||||
if err := f.Machine.ConfigLoad(); err != nil {
|
||||
log.Log(WARN, "zoopb.ConfigLoad() failed", err)
|
||||
}
|
||||
goSrcDir, err := FindGoSrc()
|
||||
*/
|
||||
if f.Config.Username == "" {
|
||||
usr, _ := user.Current()
|
||||
f.Config.Username = usr.Username
|
||||
f.SetConfigSave(true)
|
||||
}
|
||||
|
||||
if f.Config.Xterm == "" {
|
||||
f.Config.Xterm = "xterm"
|
||||
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
|
||||
f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
|
||||
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
|
||||
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
|
||||
f.SetConfigSave(true)
|
||||
}
|
||||
|
||||
// f.Machine.InitWit()
|
||||
|
||||
if f.hasFullScan {
|
||||
// duplicate time checking below. which one to keep?
|
||||
if f.FullScanAge() > time.Minute {
|
||||
log.Log(INFO, "forgepb.Scan() skipping scan. been run a minute ago", f.FullScanAge())
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
start := f.Repos.Len()
|
||||
f.ScanGoSrc()
|
||||
f.FullScanRan()
|
||||
end := f.Repos.Len()
|
||||
if (end - start) == 0 {
|
||||
log.Log(INFO, "forgepb.Scan() Scan did not find new git repositories. Total =", end)
|
||||
if f.FullScanAge() > time.Minute {
|
||||
f.rillUpdate(20, 10)
|
||||
}
|
||||
} else {
|
||||
log.Log(INFO, "forgepb.Scan() Scan found", end-start, "new git repositories. Total =", end)
|
||||
f.rillUpdate(20, 10)
|
||||
}
|
||||
|
||||
if f.configSave {
|
||||
// taking this out to debug Marshal() panic
|
||||
// os.Exit(-1)
|
||||
f.ConfigSave()
|
||||
f.configSave = false
|
||||
}
|
||||
log.Log(INFO, "update() check took", shell.FormatDuration(time.Since(now)))
|
||||
return f
|
||||
}
|
||||
|
||||
func DetermineGoPath() *Forge {
|
||||
f := new(Forge)
|
||||
|
||||
// TODO: rethink this but it works for now
|
||||
gosrc := os.Getenv("FORGE_GOSRC")
|
||||
if gosrc == "" {
|
||||
goSrcDir, err := f.findGoSrc()
|
||||
if err != nil {
|
||||
log.Warn("forge init() FindGoSrc()", err)
|
||||
panic("forge init() FindGoSrc()")
|
||||
log.Log(WARN, "forge init() findGoSrc()", err)
|
||||
}
|
||||
os.Setenv("FORGE_GOSRC", goSrcDir)
|
||||
}
|
||||
|
||||
func Init() *Forge {
|
||||
f := new(Forge)
|
||||
|
||||
// cache.go has Do()
|
||||
// f.initOnce.Do(f.initWork)
|
||||
|
||||
f.Config = new(ForgeConfigs)
|
||||
|
||||
// load the ~/.config/forge/ config
|
||||
if err := f.Config.ConfigLoad(); err != nil {
|
||||
log.Warn("forgepb.ConfigLoad() failed", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
f.Repos = new(gitpb.Repos)
|
||||
f.Repos.ConfigLoad()
|
||||
f.Machine = new(zoopb.Machine)
|
||||
|
||||
if err := f.Machine.ConfigLoad(); err != nil {
|
||||
log.Warn("zoopb.ConfigLoad() failed", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
f.Machine.InitWit()
|
||||
|
||||
f.goSrc = os.Getenv("FORGE_GOSRC")
|
||||
f.ScanGoSrc()
|
||||
log.Info("forge.Init() found", f.Repos.Len(), "repos in", f.goSrc)
|
||||
|
||||
// also rethink this, but maybe this is the right thing to do
|
||||
if os.Getenv("FORGE_CONFIG") == "" {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
fullpath := filepath.Join(homeDir, ".config/forge")
|
||||
os.Setenv("FORGE_CONFIG", fullpath)
|
||||
}
|
||||
|
||||
f.configDir = os.Getenv("FORGE_CONFIG")
|
||||
|
||||
// check again for go.work // user could have a go.work file in ~/go/src
|
||||
if f.goWorkExists() {
|
||||
f.goWork = true
|
||||
}
|
||||
|
||||
// print out the settings that will be used
|
||||
log.Log(INFO, "forgepb.Init() FORGE_CONFIG", os.Getenv("FORGE_CONFIG"))
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Forge) InitPB() {
|
||||
// load the ~/.config/forge/ config
|
||||
f.Config = new(ForgeConfigs)
|
||||
if err := f.Config.ConfigLoad(); err != nil {
|
||||
log.Log(WARN, "forgepb.ConfigLoad() failed", err)
|
||||
}
|
||||
|
||||
if f.IsGoWork() {
|
||||
log.Log(INFO, "forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = true)")
|
||||
} else {
|
||||
log.Log(INFO, "forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = false)")
|
||||
}
|
||||
|
||||
f.Repos = gitpb.NewRepos()
|
||||
f.Repos.ConfigLoad()
|
||||
if f.Repos.HasFullScan {
|
||||
f.hasFullScan = true
|
||||
}
|
||||
|
||||
if os.Getenv("FORGE_URL") != "" {
|
||||
forgeURL = os.Getenv("FORGE_URL")
|
||||
log.Info("got forge url", forgeURL)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Forge) InitMachine() {
|
||||
/*
|
||||
f.Machine = new(zoopb.Machine)
|
||||
if err := f.Machine.ConfigLoad(); err != nil {
|
||||
log.Log(WARN, "zoopb.ConfigLoad() failed", err)
|
||||
f.Machine.InitWit()
|
||||
}
|
||||
*/
|
||||
|
||||
if f.Config.Username == "" {
|
||||
usr, _ := user.Current()
|
||||
f.Config.Username = usr.Username
|
||||
}
|
||||
|
||||
/*
|
||||
if f.Machine.Hostname == "" {
|
||||
r, err := shell.RunVerbose([]string{"hostname", "-f"})
|
||||
if err == nil {
|
||||
tmp := strings.Join(r.Stdout, "\n")
|
||||
f.Machine.Hostname = strings.TrimSpace(tmp)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// only init's the protobuf. intended to not scan or change anything
|
||||
func InitPB() *Forge {
|
||||
f := DetermineGoPath()
|
||||
f.InitPB()
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Forge) SetConfigSave(b bool) {
|
||||
f.configSave = b
|
||||
}
|
||||
|
||||
// saves the config if there have been changes
|
||||
func (f *Forge) Exit() {
|
||||
// log.Info("forge.configSave =", f.configSave)
|
||||
if f.configSave {
|
||||
f.ConfigSave()
|
||||
}
|
||||
// log.Info("forge.Exit() ok")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
|
8
log.go
8
log.go
|
@ -4,13 +4,13 @@ import (
|
|||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
var FORGEPB *log.LogFlag
|
||||
var FORGEPBWARN *log.LogFlag
|
||||
var INFO *log.LogFlag
|
||||
var WARN *log.LogFlag
|
||||
|
||||
func init() {
|
||||
full := "go.wit.com/lib/protobuf/forgepb"
|
||||
short := "forgepb"
|
||||
|
||||
FORGEPB = log.NewFlag("FORGEPB", false, full, short, "general forgepb things")
|
||||
FORGEPBWARN = log.NewFlag("FORGEPBWARN", true, full, short, "forgepb warnings")
|
||||
INFO = log.NewFlag("INFO", false, full, short, "general forgepb things")
|
||||
WARN = log.NewFlag("WARN", true, full, short, "forgepb warnings")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
// TODO: implement i18n with the protobuf's
|
||||
func (f *Forge) GetMode() string {
|
||||
switch f.Config.Mode {
|
||||
case ForgeMode_MASTER:
|
||||
return "Release Mode (master branch)"
|
||||
case ForgeMode_DEVEL:
|
||||
return "Patch Mode (devel branch)"
|
||||
case ForgeMode_USER:
|
||||
return "Hack Mode (user branch)"
|
||||
default:
|
||||
return f.Config.Mode.String()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
var forgeURL string = "https://go.wit.com/"
|
||||
|
||||
func (f *Forge) GetPatchesets() (*Patchsets, error) {
|
||||
url := forgeURL + "GetPatchsets"
|
||||
log.Info("GetPatchsets() url", url)
|
||||
body, err := f.HttpPost(url, nil)
|
||||
if err != nil {
|
||||
log.Info("httpPost() failed:", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Info("GetPatchets() len(body)", len(body))
|
||||
var psets *Patchsets
|
||||
psets = new(Patchsets)
|
||||
err = psets.Unmarshal(body)
|
||||
if err != nil {
|
||||
log.Info("Unmarshal failed", err)
|
||||
return nil, err
|
||||
}
|
||||
/*
|
||||
filename := filepath.Join("/tmp", pbfile)
|
||||
f, _ := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
f.Write(body)
|
||||
f.Close()
|
||||
*/
|
||||
return psets, nil
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// creates a patchset
|
||||
// works from the user branches against the devel branches
|
||||
func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
|
||||
pset := new(Patchset)
|
||||
pset.Name = name
|
||||
pset.Ctime = timestamppb.New(time.Now())
|
||||
pset.Uuid = uuid.New().String()
|
||||
if os.Getenv("GIT_AUTHOR_NAME") == "" {
|
||||
return nil, fmt.Errorf("GIT_AUTHOR_NAME not set")
|
||||
} else {
|
||||
pset.GitAuthorName = os.Getenv("GIT_AUTHOR_NAME")
|
||||
}
|
||||
if os.Getenv("GIT_AUTHOR_EMAIL") == "" {
|
||||
return nil, fmt.Errorf("GIT_AUTHOR_EMAIL not set")
|
||||
} else {
|
||||
pset.GitAuthorEmail = os.Getenv("GIT_AUTHOR_EMAIL")
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp("", "forge")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
pset.TmpDir = dir
|
||||
|
||||
all := f.Repos.SortByFullPath()
|
||||
for all.Scan() {
|
||||
repo := all.Next()
|
||||
|
||||
if !repo.IsLocalBranch(repo.GetUserBranchName()) {
|
||||
// log.Info("repo doesn't have user branch", repo.GetGoPath())
|
||||
continue
|
||||
}
|
||||
if !repo.IsLocalBranch(repo.GetDevelBranchName()) {
|
||||
// log.Info("repo doesn't have devel branch", repo.GetGoPath())
|
||||
continue
|
||||
}
|
||||
|
||||
// make a patchset from user to devel
|
||||
// TODO: verify branches are otherwise exact
|
||||
pset.StartBranchName = repo.GetDevelBranchName()
|
||||
pset.EndBranchName = repo.GetUserBranchName()
|
||||
|
||||
err := pset.makePatchSetNew(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pset, nil
|
||||
}
|
||||
|
||||
func (f *Forge) SubmitDevelPatchSet(name string) (*Patchset, error) {
|
||||
pset, err := f.MakeDevelPatchSet(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := f.submitPatchset(pset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pset, nil
|
||||
}
|
||||
|
||||
func (f *Forge) MakeMasterPatchSet() (*Patchset, error) {
|
||||
pset := new(Patchset)
|
||||
dir, err := os.MkdirTemp("", "forge")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
pset.TmpDir = dir
|
||||
|
||||
all := f.Repos.SortByFullPath()
|
||||
for all.Scan() {
|
||||
repo := all.Next()
|
||||
startb := repo.GetMasterBranchName()
|
||||
endb := repo.GetUserBranchName()
|
||||
|
||||
if startb == "" {
|
||||
continue
|
||||
}
|
||||
if endb == "" {
|
||||
continue
|
||||
}
|
||||
// log.Info("repo", repo.GetGoPath(), startb, "..", endb)
|
||||
pset.StartBranchName = startb
|
||||
pset.EndBranchName = endb
|
||||
err := pset.makePatchSetNew(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pset, nil
|
||||
}
|
||||
|
||||
func (pset *Patchset) makePatchSetNew(repo *gitpb.Repo) error {
|
||||
startBranch := pset.StartBranchName
|
||||
endBranch := pset.EndBranchName
|
||||
repoDir := filepath.Join(pset.TmpDir, repo.GetGoPath())
|
||||
err := os.MkdirAll(repoDir, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// maybe better? maybe worse?
|
||||
// git format-patch -o patches --stdout <commit-range> > my-patch.mbox
|
||||
// git format-patch --stdout -5 > my-patch.mbox # last 5 patches
|
||||
// git am < my-patch.mbox
|
||||
// git format-patch branch1..branch2
|
||||
// export GIT_COMMITTER_DATE="2024-01-01T12:00:00"
|
||||
// export GIT_AUTHOR_DATE="2024-01-01T12:00:00"
|
||||
// export GIT_COMMITTER_NAME="Your Name"
|
||||
// export GIT_COMMITTER_EMAIL="your.email@example.com"
|
||||
// export GIT_AUTHOR_NAME="Your Name"
|
||||
// export GIT_AUTHOR_EMAIL="your.email@example.com"
|
||||
// git am < patch.mbox
|
||||
|
||||
cmd := []string{"git", "format-patch", "-o", repoDir, startBranch + ".." + endBranch}
|
||||
r := repo.Run(cmd)
|
||||
if r.Error != nil {
|
||||
log.Info("git format-patch", repo.FullPath)
|
||||
log.Info("git format-patch", cmd)
|
||||
log.Info("git format-patch error", r.Error)
|
||||
return r.Error
|
||||
}
|
||||
if r.Exit != 0 {
|
||||
log.Info("git format-patch", repo.FullPath)
|
||||
log.Info("git format-patch", cmd)
|
||||
log.Info("git format-patch exit", r.Exit)
|
||||
return errors.New(fmt.Sprintf("git returned %d", r.Exit))
|
||||
}
|
||||
if len(r.Stdout) == 0 {
|
||||
// git created no files to add
|
||||
return nil
|
||||
}
|
||||
|
||||
err = pset.addPatchFiles(repo)
|
||||
pset.Ctime = timestamppb.New(time.Now())
|
||||
return err
|
||||
}
|
||||
|
||||
// process each file in pDir/
|
||||
func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
|
||||
psetDir := repo.GetGoPath()
|
||||
tmpDir := p.TmpDir
|
||||
// log.Info("ADD PATCH FILES ADDED DIR", tmpDir)
|
||||
fullDir := filepath.Join(tmpDir, psetDir)
|
||||
var baderr error
|
||||
filepath.Walk(fullDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
// Handle possible errors, like permission issues
|
||||
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
|
||||
baderr = err
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// log.Info("IS THIS A FULL PATH ?", path)
|
||||
// log.Info("trim this from path ?", fullDir)
|
||||
// log.Info("trim this from path ?", psetDir)
|
||||
// log.Info("trim this from path ?", tmpDir)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Info("addPatchFile() failed", path)
|
||||
baderr = err
|
||||
return err
|
||||
}
|
||||
patch := new(Patch)
|
||||
patch.Filename, _ = filepath.Rel(p.TmpDir, path)
|
||||
patch.Data = data
|
||||
patch.parseData()
|
||||
patch.StartHash = repo.DevelHash()
|
||||
patch.NewHash = "na"
|
||||
patch.RepoNamespace = repo.GetGoPath()
|
||||
if p.Patches == nil {
|
||||
p.Patches = new(Patches)
|
||||
}
|
||||
p.Patches.Append(patch)
|
||||
p.Patches.Uuid = uuid.New().String()
|
||||
// log.Info("ADDED PATCH FILE", path)
|
||||
return nil
|
||||
})
|
||||
return baderr
|
||||
}
|
||||
|
||||
// looks at the git format-patch output
|
||||
// saves the commit Hash
|
||||
// saves the diff lines
|
||||
func (p *Patch) parseData() string {
|
||||
lines := strings.Split(string(p.Data), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
switch fields[0] {
|
||||
case "From":
|
||||
p.CommitHash = fields[1]
|
||||
case "Subject:":
|
||||
p.Comment = line
|
||||
case "diff":
|
||||
p.Files = append(p.Files, line)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// just an example of how to walk only directories
|
||||
func onlyWalkDirs(pDir string) error {
|
||||
log.Info("DIR", pDir)
|
||||
// var all []string
|
||||
var baderr error
|
||||
filepath.WalkDir(pDir, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
// Handle possible errors, like permission issues
|
||||
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
|
||||
baderr = err
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("TESTING DIR", path)
|
||||
if d.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
log.Info("NEVER GETS HERE? WHAT IS THIS?", path)
|
||||
return nil
|
||||
})
|
||||
return baderr
|
||||
}
|
||||
|
||||
func (f *Forge) submitPatchset(pset *Patchset) error {
|
||||
var url string
|
||||
url = forgeURL + "patchset"
|
||||
msg, err := pset.Marshal()
|
||||
if err != nil {
|
||||
log.Info("proto.Marshal() failed:", err)
|
||||
return err
|
||||
}
|
||||
log.Info("proto.Marshal() msg len", len(msg))
|
||||
body, err := f.HttpPost(url, msg)
|
||||
if err != nil {
|
||||
log.Info("httpPost() failed:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
test := strings.TrimSpace(string(body))
|
||||
lines := strings.Split(test, "\n")
|
||||
count := 0
|
||||
for _, line := range lines {
|
||||
log.Info("got back:", line)
|
||||
count += 1
|
||||
}
|
||||
log.Info("Total patches:", count)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package forgepb
|
||||
|
||||
// functions to import and export the protobuf
|
||||
// data to and from config files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (f *Forge) SendPatchSet(pset *Patchset) error {
|
||||
var err error
|
||||
data, err := pset.Marshal()
|
||||
if err != nil {
|
||||
log.Info("proto.Marshal() pset(len) error", len(data), err)
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
|
||||
cfgfile := "patchset/patchset." + timestamp + ".pb"
|
||||
log.Info("proto.Marshal() pset(len)", len(data))
|
||||
configWrite(cfgfile, data)
|
||||
|
||||
return errors.New("don't know how to send yet")
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package forgepb;
|
||||
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
// Forge doesn't need this kind of specificity
|
||||
// but this is what the patch files contain and how git sees them
|
||||
// message Blob {
|
||||
// string hunkLine = 1;
|
||||
// bytes data = 2;
|
||||
// }
|
||||
//
|
||||
// message File {
|
||||
// string filename = 1;
|
||||
// string hashLine = 2;
|
||||
// repeated Blob Blobs = 3;
|
||||
// }
|
||||
//
|
||||
// message Patch {
|
||||
// repeated File Files = 1;
|
||||
// string repoNamespace = 2;
|
||||
// string gH = 3;
|
||||
// string gaI = 4;
|
||||
// string gcI = 5;
|
||||
// }
|
||||
|
||||
// git log -1 --format="%H %aI %cI %an %ae %cn %ce"
|
||||
message Patch {
|
||||
string repoNamespace = 1; // the base repo git URL
|
||||
bytes data = 2; // the raw data of the whole patch
|
||||
string gH = 3; // after some deliberation, I think I'll just try variable names
|
||||
string gT = 4;
|
||||
string gP = 5;
|
||||
string gs = 6;
|
||||
string gaI = 7; // that exactly match what git uses.
|
||||
string gan = 8;
|
||||
string gae = 9;
|
||||
string gcI = 10;
|
||||
string gcn = 11;
|
||||
string gce = 12;
|
||||
string gN = 13;
|
||||
string gGG = 14;
|
||||
string gGS = 15;
|
||||
string gGK = 16;
|
||||
string newHash = 17; // new hash
|
||||
string state = 18; // the 'state' of the patch
|
||||
string filename = 19; // `autogenpb:unique` `autogenpb:sort`
|
||||
string startHash = 20; // the start commit hash
|
||||
string commitHash = 21; // the git commit hash of this patch
|
||||
string comment = 22; // the git commit message (in patch form)
|
||||
repeated string Files = 23; // the filenames this patch changes
|
||||
google.protobuf.Timestamp ctime = 24; // create time of the patch
|
||||
bool applied = 25; // have you applied this patch?
|
||||
bool upstream = 26; // has this patch been applied upstream?
|
||||
}
|
||||
|
||||
message Patches { // this is a "PATCH: [1/x]" series `autogenpb:gui:Patch`
|
||||
string uuid = 1; // `autogenpb:uuid:be926ad9-1111-484c-adf2-d96eeabf3079`
|
||||
string version = 2; // `autogenpb:version:v0.0.45`
|
||||
repeated Patch Patches = 3;
|
||||
}
|
||||
|
||||
message Patchset { // `autogenpb:marshal`
|
||||
Patches patches = 1; //
|
||||
string name = 2; // `autogenpb:sort`
|
||||
string comment = 3; //
|
||||
string gitAuthorName = 4; // `autogenpb:sort`
|
||||
string gitAuthorEmail = 5; //
|
||||
google.protobuf.Timestamp ctime = 6; // create time of the patchset
|
||||
string tmpDir = 7; // temp dir
|
||||
string startBranchName = 8; //
|
||||
string endBranchName = 9; //
|
||||
string startBranchHash = 10; //
|
||||
string endBranchHash = 11; //
|
||||
string state = 12; // the state of the patch
|
||||
string uuid = 13; // `autogenpb:sort` `autogenpb:unique`
|
||||
}
|
||||
|
||||
message Patchsets { // `autogenpb:marshal` `autogenpb:gui`
|
||||
string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079`
|
||||
string version = 2; // `autogenpb:version:v0.0.45`
|
||||
repeated Patchset Patchsets = 3;
|
||||
}
|
278
repoClone.go
278
repoClone.go
|
@ -1,278 +0,0 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
/*
|
||||
// guess the paths. returns
|
||||
|
||||
// realpath : the actual path on the filesystem
|
||||
// goSrcPath : this could be ~/go/src, or where the go.work file is
|
||||
|
||||
// goPath : go.wit.com/lib/gui/repostatus (for example)
|
||||
// true/false if the repo is a golang repo
|
||||
func (f *Forge) guessPaths(path string) (string, string, string, bool, error) {
|
||||
var realpath, goSrcDir string
|
||||
var isGoLang bool = false
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Log(FORGEPBWARN, "Error getting home directory:", err)
|
||||
return path, realpath, goSrcDir, false, err
|
||||
}
|
||||
goSrcDir = filepath.Join(homeDir, "go/src")
|
||||
|
||||
// allow arbitrary paths, otherwise, assume the repo is in ~/go/src
|
||||
// unless REPO_WORK_PATH was set. to over-ride ~/go/src
|
||||
// todo, look for go.work
|
||||
if os.Getenv("REPO_WORK_PATH") == "" {
|
||||
os.Setenv("REPO_WORK_PATH", goSrcDir)
|
||||
} else {
|
||||
goSrcDir = os.Getenv("REPO_WORK_PATH")
|
||||
}
|
||||
|
||||
// todo: this is dumb
|
||||
if strings.HasPrefix(path, "/") {
|
||||
realpath = path
|
||||
} else if strings.HasPrefix(path, "~") {
|
||||
tmp := strings.TrimPrefix(path, "~")
|
||||
realpath = filepath.Join(homeDir, tmp)
|
||||
} else {
|
||||
realpath = filepath.Join(goSrcDir, path)
|
||||
isGoLang = true
|
||||
}
|
||||
|
||||
if os.Getenv("REPO_AUTO_CLONE") == "true" {
|
||||
err := f.Clone(goSrcDir, path)
|
||||
if err != nil {
|
||||
// directory doesn't exist. exit with nil and error nil
|
||||
return path, realpath, goSrcDir, false, errors.New("git clone")
|
||||
}
|
||||
}
|
||||
|
||||
if !IsDirectory(realpath) {
|
||||
log.Log(FORGEPBWARN, "directory doesn't exist", realpath)
|
||||
// directory doesn't exist. exit with nil and error nil
|
||||
return path, realpath, goSrcDir, false, errors.New(realpath + " does not exist")
|
||||
}
|
||||
|
||||
filename := filepath.Join(realpath, ".git/config")
|
||||
|
||||
_, err = os.Open(filename)
|
||||
if err != nil {
|
||||
// log.Log(WARN, "Error reading .git/config:", filename, err)
|
||||
// log.Log(WARN, "TODO: find .git/config in parent directory")
|
||||
return path, realpath, goSrcDir, false, err
|
||||
}
|
||||
return path, realpath, goSrcDir, isGoLang, nil
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: make some config file for things like this
|
||||
// can be used to work around temporary problems
|
||||
func clonePathHack(dirname string, basedir string, gopath string) error {
|
||||
// newdir = helloworld
|
||||
// basedir = /home/jcarr/go/src/go.wit.com/apps
|
||||
// giturl = https://gitea.wit.com/gui/helloworld
|
||||
// func cloneActual(newdir, basedir, giturl string) error {
|
||||
|
||||
switch gopath {
|
||||
case "golang.org/x/crypto":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/crypto")
|
||||
case "golang.org/x/mod":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/mod")
|
||||
case "golang.org/x/net":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/net")
|
||||
case "golang.org/x/sys":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/sys")
|
||||
case "golang.org/x/sync":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/sync")
|
||||
case "golang.org/x/term":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/term")
|
||||
case "golang.org/x/text":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/text")
|
||||
case "golang.org/x/tools":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/tools")
|
||||
case "golang.org/x/xerrors":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/xerrors")
|
||||
case "google.golang.org/protobuf":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/protobuf")
|
||||
case "google.golang.org/genproto":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/genproto")
|
||||
case "google.golang.org/api":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/api")
|
||||
case "google.golang.org/grpc":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/grpc")
|
||||
case "google.golang.org/appengine":
|
||||
return cloneActual(dirname, basedir, "https://"+"go.googlesource.com/appengine")
|
||||
}
|
||||
|
||||
return errors.New("no gopath override here")
|
||||
}
|
||||
|
||||
// attempt to git clone if the go path doesn't exist
|
||||
// does a git clone, if it works, returns true
|
||||
// workdir = /home/jcarr/go/src/
|
||||
// gopath = go.wit.com/apps/helloworld
|
||||
func (f *Forge) Clone(gopath string) (*gitpb.Repo, error) {
|
||||
var err error
|
||||
pb, err := f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
if err == nil {
|
||||
return pb, err
|
||||
}
|
||||
workdir := f.goSrc
|
||||
fullpath := filepath.Join(workdir, gopath)
|
||||
dirname := filepath.Base(fullpath)
|
||||
|
||||
basedir := strings.TrimSuffix(fullpath, dirname)
|
||||
|
||||
url := "https://" + gopath
|
||||
log.Info("trying git clone")
|
||||
log.Info("gopath =", gopath)
|
||||
|
||||
// try a direct git clone against the gopath
|
||||
// cloneActual("helloworld", "/home/jcarr/go/src/go.wit.com/apps", "https://go.wit.com/apps/helloworld")
|
||||
if err = cloneActual(dirname, basedir, url); err == nil {
|
||||
// git clone worked!
|
||||
return f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
}
|
||||
log.Info("direct attempt at git clone failed", url)
|
||||
|
||||
// if direct git clone doesn't work, look for a redirect
|
||||
// go directly to the URL as that is autoritive. If that failes
|
||||
// try the go package system as maybe the git site no longer exists
|
||||
if url, err = findGoImport(url); err != nil {
|
||||
log.Info("findGoImport() DID NOT WORK", url)
|
||||
log.Info("findGoImport() DID NOT WORK", err)
|
||||
} else {
|
||||
if err := cloneActual(dirname, basedir, url); err == nil {
|
||||
// git clone worked!
|
||||
return f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
}
|
||||
}
|
||||
log.Info("git clone from 'go-import' info failed", url)
|
||||
|
||||
// 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 url, err = runGoList(gopath); err != nil {
|
||||
log.Info("go list failed", err)
|
||||
} else {
|
||||
if err := cloneActual(dirname, basedir, url); err == nil {
|
||||
// git clone worked!
|
||||
return f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
}
|
||||
}
|
||||
log.Info("git clone from 'git list' info failed", url)
|
||||
|
||||
// try to parse a redirect
|
||||
|
||||
if err = clonePathHack(dirname, basedir, gopath); err == nil {
|
||||
// WTF didn't go-import or go list work?
|
||||
return f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
}
|
||||
|
||||
return nil, errors.New("can not find git sources for gopath " + gopath)
|
||||
}
|
||||
|
||||
// newdir = helloworld
|
||||
// basedir = /home/jcarr/go/src/go.wit.com/apps
|
||||
// giturl = https://gitea.wit.com/gui/helloworld
|
||||
func cloneActual(newdir, basedir, giturl string) error {
|
||||
log.Info("cloneActual() newdir =", newdir)
|
||||
log.Info("cloneActual() basedir =", basedir)
|
||||
log.Info("cloneActual() giturl =", giturl)
|
||||
if !IsDirectory(basedir) {
|
||||
os.MkdirAll(basedir, 0750)
|
||||
}
|
||||
err := os.Chdir(basedir)
|
||||
if err != nil {
|
||||
log.Warn("chdir failed", basedir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := []string{"git", "clone", "--verbose", "--progress", giturl, newdir}
|
||||
log.Info("Running:", cmd)
|
||||
r := shell.PathRunRealtime(basedir, cmd)
|
||||
if r.Error != nil {
|
||||
log.Warn("git clone error", r.Error)
|
||||
return r.Error
|
||||
}
|
||||
|
||||
fullpath := filepath.Join(basedir, newdir)
|
||||
if !IsDirectory(fullpath) {
|
||||
log.Info("git clone failed", giturl)
|
||||
return errors.New("git clone failed " + giturl)
|
||||
}
|
||||
gitdir := filepath.Join(fullpath, ".git")
|
||||
if IsDirectory(gitdir) {
|
||||
log.Info("git cloned worked to", fullpath)
|
||||
return nil
|
||||
}
|
||||
// git clone didn't really work but did make a directory
|
||||
log.Info("fullpath is probably empty", fullpath)
|
||||
panic("crapnuts. rmdir fullpath here?")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func IsDirectory(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
287
repoNew.go
287
repoNew.go
|
@ -1,88 +1,241 @@
|
|||
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
|
||||
|
||||
package forgepb
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (f *Forge) NewGoPath(gopath string) (*gitpb.Repo, error) {
|
||||
newr, err := f.Repos.NewGoPath(f.goSrc, gopath)
|
||||
func (f *Forge) NewGoRepo(gopath string, url string) (*gitpb.Repo, error) {
|
||||
fullpath := filepath.Join(f.GetGoSrc(), gopath)
|
||||
test := f.Repos.FindByFullPath(fullpath)
|
||||
if test != nil {
|
||||
return test, nil
|
||||
}
|
||||
repo, err := f.Repos.NewGoRepo(fullpath, gopath)
|
||||
if err != nil {
|
||||
log.Log(FORGEPBWARN, "init failed", err)
|
||||
panic("crapnuts")
|
||||
log.Info("WARNING. NEW FAILED", fullpath)
|
||||
return nil, err
|
||||
}
|
||||
log.Info("init worked for", newr.GoPath)
|
||||
// slices.Reverse(f.Repos.Repos)
|
||||
|
||||
repo.URL = url
|
||||
f.VerifyBranchNames(repo)
|
||||
if f.Config.IsReadOnly(repo.GetGoPath()) {
|
||||
repo.ReadOnly = true
|
||||
}
|
||||
repo.Reload()
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func isValidSemVer(version string) bool {
|
||||
// Regular expression for semantic versioning
|
||||
regex := `^v(\d+)\.(\d+)\.(\d+)$`
|
||||
matched, _ := regexp.MatchString(regex, version)
|
||||
return matched
|
||||
}
|
||||
|
||||
// golang versions MUST be vX.X.X
|
||||
// it can not be vX.X and it also can not be v0.0.0
|
||||
// verifies the version is format v3.2.1
|
||||
// this is retardedly wrong. it needs to be done right
|
||||
func (f *Forge) ValidGoVersion(ver string) (bool, error) {
|
||||
return ValidGoVersion(ver)
|
||||
}
|
||||
|
||||
func ValidGoVersion(ver string) (bool, error) {
|
||||
if ver == "v0.0.0" {
|
||||
return false, fmt.Errorf("golang does not allow version v0.0.0")
|
||||
}
|
||||
if !strings.HasPrefix(ver, "v") {
|
||||
return false, fmt.Errorf("(%s) invalid. golang versions must start with a 'v'", ver)
|
||||
}
|
||||
xver := ver[1:]
|
||||
parts := strings.Split(xver, ".")
|
||||
if len(parts) != 3 {
|
||||
return false, fmt.Errorf("(%s) invalid. golang versions must have exactly 3 numbers (v1.2.3)", ver)
|
||||
}
|
||||
if isValidSemVer(ver) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// figure out what the name of the git master branch is
|
||||
// also check the forge config
|
||||
func (f *Forge) findMasterBranch(repo *gitpb.Repo) {
|
||||
// check the forge config first
|
||||
if bname := f.Config.FindMasterBranch(repo.GetGoPath()); bname != "" {
|
||||
log.Info("Using master branch name from forge config:", bname)
|
||||
repo.SetMasterBranchName(bname)
|
||||
return
|
||||
}
|
||||
// todo: fix this after .git parsing is better or libgit2 is being used
|
||||
headfile := filepath.Join(repo.GetFullPath(), ".git/refs/remotes/origin/HEAD")
|
||||
if data, err := os.ReadFile(headfile); err == nil {
|
||||
s := string(data)
|
||||
if strings.HasPrefix(s, "ref: ") {
|
||||
fields := strings.Fields(s)
|
||||
_, bname := filepath.Split(fields[1])
|
||||
// log.Info("Using master branch name from .git/ HEAD:", bname)
|
||||
repo.SetMasterBranchName(bname)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// try to guess what the 'master' branch is
|
||||
if newr.IsBranch("guimaster") {
|
||||
newr.SetMasterBranchName("guimaster")
|
||||
} else if newr.IsBranch("master") {
|
||||
newr.SetMasterBranchName("master")
|
||||
} else if newr.IsBranch("main") {
|
||||
newr.SetMasterBranchName("main")
|
||||
} else {
|
||||
newr.SetMasterBranchName("masterFIXME")
|
||||
}
|
||||
|
||||
if newr.IsBranch("guidevel") {
|
||||
newr.SetDevelBranchName("guidevel")
|
||||
} else if newr.IsBranch("devel") {
|
||||
newr.SetDevelBranchName("devel")
|
||||
} else {
|
||||
newr.SetDevelBranchName("develFIXME")
|
||||
}
|
||||
|
||||
usr, _ := user.Current()
|
||||
uname := usr.Username
|
||||
if newr.IsBranch(uname) {
|
||||
newr.SetUserBranchName(uname)
|
||||
} else {
|
||||
newr.SetUserBranchName(uname + "FIXME")
|
||||
}
|
||||
f.Repos.ConfigSave()
|
||||
panic("forgepb got here")
|
||||
|
||||
// return newr, err
|
||||
}
|
||||
|
||||
func (f *Forge) VerifyBranchNames(newr *gitpb.Repo) {
|
||||
// log.Info("init worked for", newr.GoPath)
|
||||
|
||||
if newr.GetMasterBranchName() == "" {
|
||||
// try to guess what the 'master' branch is
|
||||
if newr.IsBranch("guimaster") {
|
||||
newr.SetMasterBranchName("guimaster")
|
||||
} else if newr.IsBranch("master") {
|
||||
newr.SetMasterBranchName("master")
|
||||
} else if newr.IsBranch("main") {
|
||||
newr.SetMasterBranchName("main")
|
||||
} else {
|
||||
newr.SetMasterBranchName("masterFIXME")
|
||||
}
|
||||
}
|
||||
|
||||
if f.IsReadOnly(newr.GoPath) {
|
||||
if repo.IsBranch("master") {
|
||||
repo.SetMasterBranchName("master")
|
||||
return
|
||||
}
|
||||
|
||||
if newr.GetDevelBranchName() == "" {
|
||||
if newr.IsBranch("guidevel") {
|
||||
newr.SetDevelBranchName("guidevel")
|
||||
} else if newr.IsBranch("devel") {
|
||||
newr.SetDevelBranchName("devel")
|
||||
if repo.IsBranch("main") {
|
||||
repo.SetMasterBranchName("main")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: figure out the name from git
|
||||
repo.SetMasterBranchName("master")
|
||||
/* no longer checkout on Init()
|
||||
if repo.CheckoutMaster() {
|
||||
} else {
|
||||
newr.SetDevelBranchName("develFIXME")
|
||||
cmd := []string{"git", "branch", "master"}
|
||||
repo.Run(cmd)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// figure out what the name of the git devel branch is
|
||||
// also check the forge config
|
||||
func (f *Forge) findDevelBranch(repo *gitpb.Repo) {
|
||||
// check the forge config first
|
||||
if bname := f.Config.FindDevelBranch(repo.GetGoPath()); bname != "" {
|
||||
repo.SetDevelBranchName(bname)
|
||||
/* no longer checkout on Init()
|
||||
if repo.CheckoutDevel() {
|
||||
} else {
|
||||
cmd := []string{"git", "branch", bname}
|
||||
repo.Run(cmd)
|
||||
}
|
||||
*/
|
||||
return
|
||||
}
|
||||
|
||||
if repo.IsBranch("devel") {
|
||||
repo.SetDevelBranchName("devel")
|
||||
return
|
||||
}
|
||||
|
||||
repo.SetDevelBranchName("devel")
|
||||
/* no longer checkout on Init()
|
||||
if repo.CheckoutDevel() {
|
||||
} else {
|
||||
cmd := []string{"git", "branch", "devel"}
|
||||
repo.Run(cmd)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// this is still in flux
|
||||
func (f *Forge) VerifyBranchNames(repo *gitpb.Repo) {
|
||||
// log.Info("init worked for", repo.GoPath)
|
||||
|
||||
if repo.GetMasterBranchName() == "" {
|
||||
f.findMasterBranch(repo)
|
||||
}
|
||||
|
||||
if repo.GetDevelBranchName() == "" {
|
||||
f.findDevelBranch(repo)
|
||||
}
|
||||
|
||||
if repo.GetUserBranchName() == "" {
|
||||
uname := f.configUserBranchName(repo)
|
||||
if repo.IsBranch(uname) {
|
||||
repo.SetUserBranchName(uname)
|
||||
} else {
|
||||
// forcing for now. todo: warn users
|
||||
repo.SetUserBranchName(uname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if newr.GetUserBranchName() == "" {
|
||||
usr, _ := user.Current()
|
||||
uname := usr.Username
|
||||
if newr.IsBranch(uname) {
|
||||
newr.SetUserBranchName(uname)
|
||||
} else {
|
||||
newr.SetUserBranchName(uname + "FIXME")
|
||||
// what name should be used for the user branch?
|
||||
func (f *Forge) configUserBranchName(repo *gitpb.Repo) string {
|
||||
// look in the repo record for the username
|
||||
uname := repo.GetUserBranchName()
|
||||
if uname != "" {
|
||||
return uname
|
||||
}
|
||||
|
||||
// query the the forge config for a custom User Branch Name
|
||||
uname = f.Config.FindUserBranch(repo.GetGoPath())
|
||||
if uname != "" {
|
||||
return uname
|
||||
}
|
||||
|
||||
// use the os.Username
|
||||
uname = f.Config.Username
|
||||
return uname
|
||||
}
|
||||
|
||||
// todo: check the forge config
|
||||
func (f *Forge) configDevelBranchName(repo *gitpb.Repo) string {
|
||||
if repo.GetDevelBranchName() != "" {
|
||||
return repo.GetDevelBranchName()
|
||||
}
|
||||
if repo.IsBranch("guidevel") {
|
||||
return "guidevel"
|
||||
}
|
||||
if repo.IsBranch("devel") {
|
||||
return "devel"
|
||||
}
|
||||
return "devel"
|
||||
}
|
||||
|
||||
func (f *Forge) AddFullPath(fulldir string) *gitpb.Repo {
|
||||
repo := f.Repos.FindByFullPath(fulldir)
|
||||
if repo != nil {
|
||||
return repo
|
||||
}
|
||||
log.Info("don't know how to add full paths yet", fulldir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Forge) FindByGoPath(gopath string) *gitpb.Repo {
|
||||
fullpath := filepath.Join(f.GetGoSrc(), gopath)
|
||||
return f.Repos.FindByFullPath(fullpath)
|
||||
}
|
||||
|
||||
// tries any parent
|
||||
func (f *Forge) FindAnyPath(dir string) *gitpb.Repo {
|
||||
dir = filepath.Clean(dir)
|
||||
repo := f.Repos.FindByFullPath(dir)
|
||||
if repo != nil {
|
||||
log.Info("FindAnyPath() worked = ", dir)
|
||||
return repo
|
||||
}
|
||||
if dir == "" {
|
||||
return nil
|
||||
}
|
||||
if dir == "/" {
|
||||
return nil
|
||||
}
|
||||
basedir, newdir := filepath.Split(dir)
|
||||
if newdir == "" {
|
||||
// is this correct? not sure what stupid windows does
|
||||
return nil
|
||||
}
|
||||
return f.FindAnyPath(basedir)
|
||||
}
|
||||
|
||||
func (f *Forge) DeleteByGoPath(gopath string) bool {
|
||||
fullpath := filepath.Join(f.GetGoSrc(), gopath)
|
||||
return f.Repos.DeleteByFullPath(fullpath)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/destel/rill"
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// rill is awesome. long live rill
|
||||
// attempt scan with rill
|
||||
func (f *Forge) rillUpdate(pool1 int, pool2 int) (int, error) {
|
||||
var repos []*gitpb.Repo
|
||||
all := f.Repos.SortByFullPath()
|
||||
for all.Scan() {
|
||||
repo := all.Next()
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
|
||||
// Convert a slice of user IDs into a channel
|
||||
ids := rill.FromSlice(repos, nil)
|
||||
|
||||
// Read users from the API.
|
||||
// Concurrency = 20
|
||||
rills := rill.Map(ids, pool1, func(repo *gitpb.Repo) (*gitpb.Repo, error) {
|
||||
return repo, nil
|
||||
})
|
||||
|
||||
var counter int
|
||||
// Activate users.
|
||||
// Concurrency = 10
|
||||
err := rill.ForEach(rills, pool2, func(repo *gitpb.Repo) error {
|
||||
counter += 1
|
||||
// log.Info("rill.ForEach() gopath=", repo.GetGoPath())
|
||||
return f.updateRepo(repo)
|
||||
})
|
||||
|
||||
return counter, err
|
||||
}
|
||||
|
||||
func (f *Forge) updateRepo(repo *gitpb.Repo) error {
|
||||
if !repo.IsValidDir() {
|
||||
log.Printf("%10s %-50s gopath=%s\n", "git dir is missing:", repo.FullPath, repo.GetGoPath())
|
||||
f.Repos.DeleteByFullPath(repo.FullPath)
|
||||
f.configSave = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if repo.DidRepoChange() {
|
||||
f.configSave = true
|
||||
log.Info("repo changed ", repo.FullPath, repo.StateChange)
|
||||
if err := repo.Reload(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// log.Info("repo did not change", repo.FullPath, repo.StateChange)
|
||||
}
|
||||
if f.Config.IsReadOnly(repo.GetGoPath()) {
|
||||
if repo.ReadOnly {
|
||||
} else {
|
||||
log.Info("readonly flag on repo is wrong", repo.GetGoPath())
|
||||
repo.ReadOnly = true
|
||||
f.configSave = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var RillX int = 10
|
||||
var RillY int = 10
|
||||
|
||||
// x is the size of the queued up pool (shouldn't matter here for this I think)
|
||||
// y is how many simultanous functions will run
|
||||
// todo: tune and compute x,y by # of CPUs and disk io
|
||||
// todo: store x,y in forge config ? (or compute them. notsure)
|
||||
func (f *Forge) RillReload() int {
|
||||
var all []*gitpb.Repo
|
||||
tmp := f.Repos.All()
|
||||
for tmp.Scan() {
|
||||
repo := tmp.Next()
|
||||
if !repo.IsValidDir() {
|
||||
log.Printf("%s %-50s", "got an invalid repo in forgepb.RillFuncError()", repo.GetGoPath())
|
||||
continue
|
||||
}
|
||||
all = append(all, repo)
|
||||
}
|
||||
// Convert a slice of user IDs into a channel
|
||||
ids := rill.FromSlice(all, nil)
|
||||
|
||||
var counter int
|
||||
// Read users from the API.
|
||||
// Concurrency = 20
|
||||
dirs := rill.Map(ids, RillX, func(repo *gitpb.Repo) (*gitpb.Repo, error) {
|
||||
return repo, nil
|
||||
})
|
||||
|
||||
rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error {
|
||||
if !repo.DidRepoChange() {
|
||||
return nil
|
||||
}
|
||||
f.configSave = true
|
||||
repo.Reload()
|
||||
counter += 1
|
||||
return nil
|
||||
})
|
||||
|
||||
return counter
|
||||
}
|
||||
|
||||
// x is the size of the queued up pool (shouldn't matter here for this I think)
|
||||
// y is how many simultanous functions will run
|
||||
// todo: tune and compute x,y by # of CPUs and disk io
|
||||
// todo: store x,y in forge config ? (or compute them. notsure)
|
||||
func (f *Forge) RillFuncError(rillf func(*gitpb.Repo) error) int {
|
||||
var all []*gitpb.Repo
|
||||
tmp := f.Repos.All()
|
||||
for tmp.Scan() {
|
||||
repo := tmp.Next()
|
||||
if !repo.IsValidDir() {
|
||||
log.Printf("%s %-50s", "got an invalid repo in forgepb.RillFuncError()", repo.GetGoPath())
|
||||
continue
|
||||
}
|
||||
all = append(all, repo)
|
||||
}
|
||||
// Convert a slice of user IDs into a channel
|
||||
ids := rill.FromSlice(all, nil)
|
||||
|
||||
var counter int
|
||||
var watch int = 10
|
||||
var meMu sync.Mutex
|
||||
|
||||
// Read users from the API.
|
||||
// Concurrency = 20
|
||||
dirs := rill.Map(ids, RillX, func(id *gitpb.Repo) (*gitpb.Repo, error) {
|
||||
return id, nil
|
||||
})
|
||||
|
||||
err := rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error {
|
||||
meMu.Lock()
|
||||
counter += 1
|
||||
if counter > watch {
|
||||
// log.Info("Processed", watch, "repos") // this doesn't work
|
||||
watch += 50
|
||||
}
|
||||
meMu.Unlock()
|
||||
return rillf(repo)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Info("rill.ForEach() error:", err)
|
||||
}
|
||||
|
||||
return counter
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package forgepb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// git clone (also downloads git notes)
|
||||
// newdir = helloworld
|
||||
// basedir = /home/jcarr/go/src/go.wit.com/apps
|
||||
// giturl = https://gitea.wit.com/gui/helloworld
|
||||
func RunGitClone(newdir, basedir, giturl string) error {
|
||||
log.Info("runGitClone() newdir =", newdir)
|
||||
log.Info("runGitClone() basedir =", basedir)
|
||||
log.Info("runGitClone() giturl =", giturl)
|
||||
if !shell.IsDir(basedir) {
|
||||
os.MkdirAll(basedir, os.ModePerm)
|
||||
}
|
||||
err := os.Chdir(basedir)
|
||||
if err != nil {
|
||||
// log.Warn("chdir failed", basedir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := []string{"git", "clone", "--verbose", "--progress", giturl, newdir}
|
||||
log.Info("Running:", basedir, cmd)
|
||||
r := shell.PathRunRealtime(basedir, cmd)
|
||||
if r.Error != nil {
|
||||
// log.Warn("git clone error", r.Error)
|
||||
return r.Error
|
||||
}
|
||||
|
||||
fullpath := filepath.Join(basedir, newdir)
|
||||
if !shell.IsDir(fullpath) {
|
||||
// log.Info("git clone failed", giturl)
|
||||
return fmt.Errorf("git clone %s failed to create a directory", giturl)
|
||||
}
|
||||
gitdir := filepath.Join(fullpath, ".git")
|
||||
if shell.IsDir(gitdir) {
|
||||
// log.Info("git cloned worked to", fullpath)
|
||||
// also clone notes -- this can store the go.mod and go.sum files
|
||||
cmd := []string{"git", "fetch", "origin", "refs/notes/*:refs/notes/*"}
|
||||
shell.PathRunRealtime(fullpath, cmd)
|
||||
return nil
|
||||
}
|
||||
// git clone didn't really work but did make a directory
|
||||
log.Info("fullpath is probably empty", fullpath)
|
||||
return errors.New("crapnuts. rmdir fullpath here? " + fullpath)
|
||||
}
|
33
structs.go
33
structs.go
|
@ -2,9 +2,9 @@ package forgepb
|
|||
|
||||
import (
|
||||
sync "sync"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/protobuf/gitpb"
|
||||
"go.wit.com/lib/protobuf/zoopb"
|
||||
)
|
||||
|
||||
// maybe an interface someday?
|
||||
|
@ -12,13 +12,38 @@ type Forge struct {
|
|||
// one-time initialized data
|
||||
initOnce sync.Once
|
||||
initErr error // init error, if any
|
||||
|
||||
goSrc string // the path to go/src
|
||||
configDir string // normally ~/.config/forge
|
||||
goWork bool // means the user is currently using a go.work file
|
||||
Config *ForgeConfigs // config repos for readonly, private, etc
|
||||
Repos *gitpb.Repos
|
||||
Machine *zoopb.Machine
|
||||
Repos *gitpb.Repos // the repo protobufs
|
||||
// Machine *zoopb.Machine // things for virtigo to track vm's
|
||||
configSave bool // if you need to save the config because things changed
|
||||
hasFullScan bool // track last scan so it can be throttled
|
||||
fullscan time.Time // time of the last scan so it can be throttled
|
||||
}
|
||||
|
||||
func (f *Forge) GetGoSrc() string {
|
||||
return f.goSrc
|
||||
}
|
||||
|
||||
func (f *Forge) GetConfigDir() string {
|
||||
return f.configDir
|
||||
}
|
||||
|
||||
func (f *Forge) IsGoWork() bool {
|
||||
return f.goWork
|
||||
}
|
||||
|
||||
func (f *Forge) HasFullScan() bool {
|
||||
return f.Repos.HasFullScan
|
||||
}
|
||||
|
||||
func (f *Forge) FullScanRan() {
|
||||
f.fullscan = time.Now()
|
||||
}
|
||||
|
||||
func (f *Forge) FullScanAge() time.Duration {
|
||||
fs := f.Repos.FullScan.AsTime()
|
||||
return time.Since(fs)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue