Compare commits

...

119 Commits

Author SHA1 Message Date
Jeff Carr 7e1804f6e3 needed FindByUuid() 2025-03-12 09:21:29 -05:00
Jeff Carr 8ceec9210d track applied or upstream for patches 2025-03-11 12:00:38 -05:00
Jeff Carr cf6db578a4 store ctime 2025-03-10 23:26:57 -05:00
Jeff Carr da485dcc3f generate a patchset table gui 2025-03-10 13:52:46 -05:00
Jeff Carr 5377a89d2c autogenpb gui code for patchsets 2025-03-10 09:12:08 -05:00
Jeff Carr f7d6dfa6a7 clearer text 2025-03-03 23:49:51 -06:00
Jeff Carr 281ffbc75b minor cleanups 2025-03-02 07:49:45 -06:00
Jeff Carr 4193f41847 move to better patchset protobuf 2025-03-02 04:13:27 -06:00
Jeff Carr 9d5bae8a14 add a 'forge mode' concept 2025-03-02 03:03:45 -06:00
Jeff Carr f540aab434 remove zoopb from forge 2025-02-22 06:53:42 -06:00
Jeff Carr a170250cb4 wrong logic 2025-02-21 17:17:05 -06:00
Jeff Carr 2fc67512c8 changes from gitpb 2025-02-21 09:34:24 -06:00
Jeff Carr eaadfa21d3 add a specific http POST option 2025-02-16 12:05:35 -06:00
Jeff Carr 22ebf174c8 show the lasttag 2025-02-15 18:18:30 -06:00
Jeff Carr 9a87c93bad quiet output 2025-02-15 12:31:00 -06:00
Jeff Carr b7b18626d8 more on Machine.Init() 2025-02-15 12:21:36 -06:00
Jeff Carr 7900b1416e add IdentifyProtobuf() 2025-02-15 05:35:01 -06:00
Jeff Carr 5c84b9ab66 needed to bypass go-deb problems 2025-02-14 20:43:47 -06:00
Jeff Carr c09e292a66 something to do abitrary dirs 2025-02-14 18:39:36 -06:00
Jeff Carr 3278f6400e add GetHome() 2025-02-10 23:30:15 -06:00
Jeff Carr 83ad663fc0 must send an error on nil 2025-02-09 15:05:23 -06:00
Jeff Carr f70f54615f fix nil 2025-02-09 14:48:47 -06:00
Jeff Carr 018772dbfb more things to check if release is needed 2025-02-08 06:32:59 -06:00
Jeff Carr 9baa477990 add a date and a uuid to the patchset 2025-02-02 15:07:23 -06:00
Your Name ab01c2cd60 auto formatting using autogenpb 2024-01-01 12:00:00 -06:00
Jeff Carr c89f101fb2 namechange 2025-02-01 06:58:27 -06:00
Jeff Carr 23d7ad1581 various updates 2025-02-01 06:57:57 -06:00
Jeff Carr ec4acd425c save the path to the config dir 2025-01-31 13:47:45 -06:00
Jeff Carr 0614066fdb add 'state' 2025-01-30 18:00:30 -06:00
Your Name b60279b19a a better protobuf file to switch to later 2024-01-01 12:00:00 -06:00
Jeff Carr 95fcacfde0 add a MakePatchset() 2025-01-30 07:42:25 -06:00
Jeff Carr b6a71a515f url via ENV 2025-01-30 06:21:15 -06:00
Jeff Carr 47ee3f1493 rm old code 2025-01-30 04:44:15 -06:00
Jeff Carr 329710f9e7 rm old code 2025-01-30 02:24:34 -06:00
Jeff Carr f7b5e1a83e try a way to track the times so they can be throttled 2025-01-30 01:15:15 -06:00
Jeff Carr 7c37e3841a move this here from go-mod-sum 2025-01-29 21:24:26 -06:00
Jeff Carr f146bf4ef0 quiet lots of debugging output 2025-01-29 20:00:49 -06:00
Jeff Carr d9d90e9e12 ignore stuff 2025-01-29 12:25:06 -06:00
Jeff Carr 393b91c415 store the new hash 2025-01-29 12:24:42 -06:00
Jeff Carr b412e50df0 save git commit msg and repo namespace 2025-01-29 12:24:42 -06:00
Jeff Carr aa06450042 minor 2025-01-29 12:24:42 -06:00
Jeff Carr 121e9f08da print how many patches there were 2025-01-29 12:24:42 -06:00
Jeff Carr df19b5b8f8 cleanup debugging output 2025-01-29 12:24:42 -06:00
Jeff Carr 0efc3c67ca turn of the verbose logging 2025-01-29 12:24:42 -06:00
Jeff Carr 667257595d add submit patchset 2025-01-29 12:24:42 -06:00
Jeff Carr 76a0347fdf mv 2025-01-29 12:24:42 -06:00
Jeff Carr 66738e4300 debugging releaser 2025-01-20 07:59:14 -06:00
Jeff Carr 3e4b1ddc83 cleanup protofile in total violaton of protobuf rules 2025-01-20 05:09:46 -06:00
Jeff Carr 9ec7b4394f make HttpPost() global 2025-01-20 03:31:19 -06:00
Jeff Carr 1191b9b65d moved GetPatchsets() here from forge 2025-01-20 03:23:33 -06:00
Jeff Carr 52b8a4e312 need better handling here 2025-01-20 01:40:32 -06:00
Jeff Carr b770759167 rm old code 2025-01-19 16:07:50 -06:00
Jeff Carr 0898c24f45 trying to debug the logic since it's failing 2025-01-19 11:52:20 -06:00
Jeff Carr 58c64cd53b might show branch age in table finally 2025-01-19 10:48:50 -06:00
Jeff Carr f29f25b9b7 hide uerr in table 2025-01-19 09:25:39 -06:00
Jeff Carr 4328692039 redo with rill 2025-01-19 04:32:20 -06:00
Jeff Carr 244bf612f9 load repos output is cleaner 2025-01-19 03:18:47 -06:00
Jeff Carr f4ac491490 debugging, but runs ok 2025-01-19 02:37:31 -06:00
Jeff Carr bdf9d97cf9 reorder target to the end 2025-01-19 00:36:32 -06:00
Jeff Carr a822e1e4f0 switch some more things to use rill 2025-01-18 23:26:19 -06:00
Jeff Carr cee7e25f3d rill is awesome. thank Viktor 2025-01-18 11:11:16 -06:00
Jeff Carr 9b8cb52b7b show deb package name if different than the standard 2025-01-18 10:34:38 -06:00
Jeff Carr 538531f503 this is a dumb idea. never do this. fix the repo. 2025-01-18 04:48:34 -06:00
Jeff Carr e8f29e593d more human readable but standard version timestamp 2025-01-18 03:14:41 -06:00
Jeff Carr b8d0864c37 show abnormal branch 2025-01-17 13:20:40 -06:00
Jeff Carr 156af56859 init patches 2025-01-17 10:59:28 -06:00
Jeff Carr 8d275ff054 less aoutput 2025-01-17 05:30:42 -06:00
Jeff Carr 0f232fe342 more accurate repo Age() 2025-01-17 04:47:47 -06:00
Jeff Carr 9e81be86da set the uuid and version 2025-01-17 02:51:34 -06:00
Jeff Carr 2398e30048 add xterm settings to the config 2025-01-16 10:26:19 -06:00
Jeff Carr 49a06843e9 debugging a hung mutex lock 2025-01-13 08:12:39 -06:00
Jeff Carr 9b9c51d964 maybe marshal() failed do to mutex entries? notsure 2025-01-13 04:14:30 -06:00
Jeff Carr 23887a155e fixes for new autogenpb 2025-01-12 20:05:46 -06:00
Jeff Carr 0600f54488 remove HANDMADE iterator after fixing autogenpb 2025-01-12 06:36:11 -06:00
Jeff Carr 0ea93faef2 can't use go:generate. boo. it requires go.mod and go.sum 2025-01-11 06:08:05 -06:00
Jeff Carr 48b19f1e70 new protobuf file 2025-01-11 05:55:42 -06:00
Jeff Carr 1e38cacfa7 no going back now 2025-01-10 04:39:33 -06:00
Jeff Carr 40c340c626 requires sort 2025-01-09 21:15:52 -06:00
Jeff Carr f9dd82cdcc work on new protobuf for patchsets 2025-01-08 10:11:17 -06:00
Jeff Carr 4f84a4e584 rename vars 2025-01-08 04:08:12 -06:00
Jeff Carr 8aff3f13b2 fix the dumb names 2025-01-08 03:12:53 -06:00
Jeff Carr 26cf5055a2 save the last Reload() time 2025-01-08 02:39:08 -06:00
Jeff Carr 14e5bf5fbd add a button for the table 2025-01-08 01:16:38 -06:00
Jeff Carr e0c0d3d9e6 haha. it's like COBOL in GO 2025-01-08 00:52:32 -06:00
Jeff Carr bd3e924e2b finally pull the origin repo name from the right place 2025-01-07 22:29:17 -06:00
Jeff Carr e6e70ccaa5 add a way to force git checkout anyway 2025-01-07 21:22:54 -06:00
Jeff Carr 67ae8d8773 use go generate. also fix verbose Build() flags 2025-01-07 17:44:23 -06:00
Jeff Carr e333ca726b also show debname 2025-01-06 15:32:18 -06:00
Jeff Carr 6cbd7e67af attempt custom branch names 2025-01-05 19:45:29 -06:00
Jeff Carr 7e5db53e9d slim new user defaults 2025-01-05 18:41:46 -06:00
Jeff Carr b0662fb61a add commit hash and diff file list to the protobuf 2025-01-05 04:54:31 -06:00
Jeff Carr ac57825c10 start adding something to check the hashes 2025-01-05 03:21:47 -06:00
Jeff Carr 338018376b ConfigLoad() was totally broken 2025-01-05 01:20:12 -06:00
Jeff Carr 275a7db0e0 oops 2024-12-28 19:36:17 -06:00
Jeff Carr f2ba4cab9c wrong path. funcknuts 2024-12-28 03:01:00 -06:00
Jeff Carr c7066e1766 filepath.HasPrefix() doesn't work. why? Microsoft sucks
for those of you that are new here and care about
	writing free software. this is something to remind you
	to NEVER EVER EVER trust Microsoft to do anything but
	try to make things worse. The are opposed and will always
	be opposed to making Linux work. The suck and are horrible
	villians in this story. Never trust them and do not
	support them. Always use the GPL vs BSD license for this
	reason also. They will steal anything BSD and use it
	in their proprietary products and not give anything back
	to anyone. They are complete jerks.
2024-12-27 23:03:00 -06:00
Jeff Carr f4837c807c user branch name settings 2024-12-27 14:37:20 -06:00
Jeff Carr e9776796dd todo: make go.* files git metadata 2024-12-18 20:09:03 -06:00
Jeff Carr 934daa5a3b maybe works somewhat 2024-12-18 19:36:05 -06:00
Jeff Carr a97b66e8f2 rm debugging 2024-12-18 18:51:39 -06:00
Jeff Carr 4582a76081 finally re-organize and rename Clone to GoClone() 2024-12-18 17:59:20 -06:00
Jeff Carr 24c96ccaa7 wasn't reloading versions 2024-12-17 23:01:02 -06:00
Jeff Carr 272f965eec process on a *gitpb.Repos 2024-12-17 21:58:31 -06:00
Jeff Carr c4f9430e46 keep trying to fix init() and update() 2024-12-17 21:14:39 -06:00
Jeff Carr 7cdb2bf6a0 rill repo.Update() 2024-12-17 20:48:08 -06:00
Jeff Carr 23b2b26643 formatting for releases 2024-12-17 13:13:34 -06:00
Jeff Carr 7db8376213 todo: fix this error, but not now 2024-12-17 07:09:39 -06:00
Jeff Carr ee7e8d5b2b lots of changes in gitpb 2024-12-17 06:37:00 -06:00
Jeff Carr b2e51ccb9e improve Init() 2024-12-17 01:15:17 -06:00
Jeff Carr 1369d2df31 save mtime's on Init() 2024-12-17 00:21:20 -06:00
Jeff Carr 075fce61e5 start cleaning up git interactions 2024-12-17 00:00:27 -06:00
Jeff Carr a9286af8fd minor new funcs 2024-12-16 03:04:21 -06:00
Jeff Carr 6922059a0b sortcut for finding Getwd() repo 2024-12-16 00:19:23 -06:00
Jeff Carr 0b09db58dd todo: need to fix this for go.work env 2024-12-15 21:27:29 -06:00
Jeff Carr a31f675e12 still trying to perfect this. todo: make gitpb perfect 2024-12-15 15:52:46 -06:00
Jeff Carr e3608b784e trying to fix clone 2024-12-15 12:14:36 -06:00
Jeff Carr 340872788e clone() and misc other fixes and improvements 2024-12-15 08:48:06 -06:00
Jeff Carr e796788e22 attempt to symlink plugin files 2024-12-14 22:32:11 -06:00
Jeff Carr b55548a58b remove os.Exit() as things are more stable now 2024-12-14 16:29:39 -06:00
36 changed files with 2223 additions and 964 deletions

6
.gitignore vendored
View File

@ -1,5 +1,5 @@
go.* go.*
*.swp
*.patch
*.mbox
*.pb.go *.pb.go
forgeConfig/forgeConfig

View File

@ -5,7 +5,11 @@
# go install # go install
all: goimports forgeConfig.pb.go uuid.pb.go patch.pb.go vet all: forgeConfig.pb.go patchset.pb.go goimports vet
generate: clean
autogenpb --proto patchset.proto
autogenpb --proto forgeConfig.proto
vet: vet:
@GO111MODULE=off go vet @GO111MODULE=off go vet
@ -15,21 +19,13 @@ vet:
goimports: goimports:
goimports -w *.go goimports -w *.go
redomod:
rm -f go.*
GO111MODULE= go mod init
GO111MODULE= go mod tidy
go mod edit -go=1.20
clean: clean:
rm -f *.pb.go rm -f *.pb.go *.patch
-rm -f go.* -rm -f go.*
go-mod-clean --purge
forgeConfig.pb.go: forgeConfig.proto forgeConfig.pb.go: forgeConfig.proto
autogenpb --proto forgeConfig.proto autogenpb --proto forgeConfig.proto
uuid.pb.go: uuid.proto patchset.pb.go: patchset.proto
autogenpb --proto uuid.proto autogenpb --proto patchset.proto
patch.pb.go: patch.proto
autogenpb --proto patch.proto

View File

@ -30,6 +30,7 @@ func (f *Forge) Install(repo *gitpb.Repo, userFlags []string) error {
return f.doBuild(repo, userFlags, "install") 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 { func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) error {
if repo == nil { if repo == nil {
log.Warn("forge.doBuild repo == nil") log.Warn("forge.doBuild repo == nil")
@ -47,6 +48,7 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
if f.IsGoWork() { if f.IsGoWork() {
// there must be a valid go.mod file if compiling with go.work // there must be a valid go.mod file if compiling with go.work
if err := repo.ValidGoSum(); err != nil { if err := repo.ValidGoSum(); err != nil {
log.Warn("forge.doBuild() failed. run go-mod-clean here?")
return err return err
} }
} }
@ -56,6 +58,9 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
data, _ := repo.ReadFile(".forge") data, _ := repo.ReadFile(".forge")
log.Info(".forge =", string(data)) log.Info(".forge =", string(data))
log.Info("todo: do the custom build instructions") 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 return nil
} }
@ -65,8 +70,8 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
all := repo.GoDeps.SortByGoPath() all := repo.GoDeps.SortByGoPath()
for all.Scan() { for all.Scan() {
t := all.Next() t := all.Next()
found := f.Repos.FindByGoPath(t.GetGoPath()) found := f.FindByGoPath(t.GetGoPath())
if found.RepoType() == "protobuf" { if found.GetRepoType() == "protobuf" {
if err := f.runAutogenpb(found); err != nil { if err := f.runAutogenpb(found); err != nil {
return err return err
} }
@ -76,12 +81,15 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
// get the version // get the version
version := repo.GetCurrentBranchVersion() version := repo.GetCurrentBranchVersion()
if version == "" {
version = "forgeErr"
}
if repo.CheckDirty() { if repo.CheckDirty() {
version = version + "-dirty" version = version + "-dirty"
} }
cmd := []string{"go"} cmd := []string{"go"}
if repo.RepoType() == "plugin" { if repo.GetRepoType() == "plugin" {
if goWhat == "install" { if goWhat == "install" {
log.Info("Can not go install plugins yet. just building to ~/go/lib/") log.Info("Can not go install plugins yet. just building to ~/go/lib/")
} }
@ -90,23 +98,28 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
cmd = append(cmd, goWhat) cmd = append(cmd, goWhat)
} }
// if this is a plugin, use buildmode=plugin
if repo.RepoType() == "plugin" {
_, fname := filepath.Split(repo.FullPath) _, fname := filepath.Split(repo.FullPath)
soname := fname + "." + version + ".so"
if goWhat == "install" {
homeDir, _ := os.UserHomeDir() homeDir, _ := os.UserHomeDir()
fullname := filepath.Join(homeDir, "go/lib", soname) 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) cmd = append(cmd, "-buildmode=plugin", "-o", fullname)
} else { } else {
cmd = append(cmd, "-buildmode=plugin", "-o", soname) cmd = append(cmd, "-buildmode=plugin", "-o", soname)
} }
} }
cmd = append(cmd, "-v") for _, flag := range userFlags {
cmd = append(cmd, flag)
}
// set standard ldflag options // set standard ldflag options
now := time.Now() now := time.Now()
datestamp := now.UTC().Format("2006/01/02_1504_UTC") // 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) // log.Info("datestamp =", datestamp)
// add some standard golang flags // add some standard golang flags
ldflags := "-X main.VERSION=" + version + " " ldflags := "-X main.VERSION=" + version + " "
@ -114,13 +127,6 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
ldflags += "-X main.GUIVERSION=" + version + "" // todo: git this from the filesystem ldflags += "-X main.GUIVERSION=" + version + "" // todo: git this from the filesystem
cmd = append(cmd, "-ldflags", ldflags) 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") testenv := os.Getenv("GO111MODULE")
if testenv == "off" { if testenv == "off" {
log.Info("GO111MODULE=off", "f.goWork =", f.IsGoWork(), "f.gosrc =", f.GetGoSrc()) log.Info("GO111MODULE=off", "f.goWork =", f.IsGoWork(), "f.gosrc =", f.GetGoSrc())
@ -130,15 +136,16 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
log.Info("running:", repo.FullPath) log.Info("running:", repo.FullPath)
log.Info("running:", cmd) log.Info("running:", cmd)
result := repo.RunRealtime(cmd) result := repo.RunRealtime(cmd)
if result.Exit == 0 { if result.Exit != 0 {
log.Info(strings.Join(result.Stdout, "\n")) // build failed
return nil
} else {
log.DaemonMode(true) log.DaemonMode(true)
log.Info(strings.Join(result.Stdout, "\n")) log.Info(strings.Join(result.Stdout, "\n"))
log.Info(strings.Join(result.Stderr, "\n")) log.Info(strings.Join(result.Stderr, "\n"))
log.Info("result.Error =", result.Error)
log.Info("result.Exit =", result.Exit)
log.DaemonMode(false) log.DaemonMode(false)
log.Warn("go build failed", cmd) log.Warn("go build failed", cmd)
/*
pwd, _ := os.Getwd() pwd, _ := os.Getwd()
log.Warn("go build pwd", pwd) log.Warn("go build pwd", pwd)
res2 := shell.RunEcho(cmd) res2 := shell.RunEcho(cmd)
@ -147,8 +154,20 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
log.Info("again failed cmd", strings.Join(cmd, "a")) log.Info("again failed cmd", strings.Join(cmd, "a"))
log.Info("again failed", strings.Join(res2.Stdout, "\n")) log.Info("again failed", strings.Join(res2.Stdout, "\n"))
} }
*/
return errors.New("go " + goWhat + " failed: " + fmt.Sprint(result.Error)) 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
} }
func (f *Forge) runAutogenpb(repo *gitpb.Repo) error { func (f *Forge) runAutogenpb(repo *gitpb.Repo) error {
@ -182,3 +201,29 @@ func (f *Forge) runAutogenpb(repo *gitpb.Repo) error {
} }
return nil return nil
} }
// 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
}

161
cleanGoSum.go Normal file
View File

@ -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
}

216
clone.go Normal file
View File

@ -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
}

View File

@ -1,3 +1,5 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb package forgepb
// functions to import and export the protobuf // functions to import and export the protobuf
@ -5,11 +7,13 @@ package forgepb
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"go.wit.com/log" "go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
) )
func (f *Forge) ConfigSave() error { func (f *Forge) ConfigSave() error {
@ -28,6 +32,11 @@ func (f *Forge) ConfigSave() error {
} }
} }
if f.Repos != nil { 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 { if e := f.Repos.ConfigSave(); e != nil {
log.Info("forge.Repos.ConfigSave() error", e) log.Info("forge.Repos.ConfigSave() error", e)
err = e err = e
@ -43,8 +52,7 @@ func (f *ForgeConfigs) ConfigSave() error {
log.Info("proto.Marshal() failed len", len(data), err) log.Info("proto.Marshal() failed len", len(data), err)
return err return err
} }
log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data)) // log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data))
configWrite("forge.pb", data)
s := f.FormatTEXT() s := f.FormatTEXT()
configWrite("forge.text", []byte(s)) configWrite("forge.text", []byte(s))
@ -66,48 +74,24 @@ func (c *ForgeConfigs) ConfigLoad() error {
// var err error // var err error
if c == nil { if c == nil {
// can't safely do c = new(ForgeConfig) if c is in a struct from the caller. notsure why // 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") return errors.New("It's not safe to run ConfigLoad() on a nil")
} }
if data, err := loadFile("forge.pb"); err == nil { if err := c.loadText(); err == nil {
if data != nil {
if len(data) != 0 {
if err = c.Unmarshal(data); err == nil {
log.Info("forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
return nil return nil
} }
}
}
}
log.Warn("broken forge.pb config file")
// forge.db doesn't exist. try forge.text
// this lets the user hand edit the config
if data, err := loadFile("forge.text"); err == nil {
if data != nil {
// this means the forge.text file exists and was read
if len(data) != 0 {
if err = c.UnmarshalTEXT(data); err != nil {
log.Info("forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
// forge.pb file was broken. save on load right away
log.Info("attempting forge.ConfigSave()")
c.ConfigSave()
return nil
}
// todo: error out if the file is empty?
}
}
}
// forge.text doesn't exist. try forge.json // forge.text doesn't exist. try forge.json
// this lets the user hand edit the config // this lets the user hand edit the JSON config
// probably just deprecate this
if data, err := loadFile("forge.json"); err != nil { if data, err := loadFile("forge.json"); err != nil {
if data != nil { if data != nil {
// this means the forge.json file exists and was read // this means the forge.json file exists and was read
if len(data) != 0 { if len(data) != 0 {
if err = c.UnmarshalJSON(data); err == nil { if err = c.UnmarshalJSON(data); err == nil {
log.Info("forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge") log.Info("forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
// forge.pb file was broken. save on load right away // forge.text file was broken. save on load right away
log.Info("attempting forge.ConfigSave()") log.Info("attempting forge.ConfigSave()")
c.ConfigSave() c.ConfigSave()
return nil return nil
@ -120,9 +104,6 @@ func (c *ForgeConfigs) ConfigLoad() error {
if _, err := os.Stat(cpath); err == nil { if _, err := os.Stat(cpath); err == nil {
log.Info("Something has gone wrong. Your", os.Getenv("FORGE_CONFIG"), "directory exists") log.Info("Something has gone wrong. Your", os.Getenv("FORGE_CONFIG"), "directory exists")
log.Info("However, the config files could not be loaded") log.Info("However, the config files could not be loaded")
time.Sleep(2 * time.Second)
os.Exit(-1)
// return errors.New("config files can not be loaded from" + os.Getenv("FORGE_CONFIG"))
} }
// first time user. make a template config file // first time user. make a template config file
@ -131,6 +112,27 @@ func (c *ForgeConfigs) ConfigLoad() error {
return nil 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) { func loadFile(filename string) ([]byte, error) {
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename) fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
data, err := os.ReadFile(fullname) data, err := os.ReadFile(fullname)
@ -158,13 +160,21 @@ func configWrite(filename string, data []byte) error {
} }
if filename == "forge.text" { if filename == "forge.text" {
// add header // 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("\n"))
cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\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("# 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) cfgfile.Write(data)
return nil return nil

View File

@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -15,12 +14,8 @@ import (
func backupConfig() error { func backupConfig() error {
// make a new dir to backup the files // 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_CONFIG")) srcDir := filepath.Join(os.Getenv("FORGE_CONFIG"))
destDir := filepath.Join(os.Getenv("FORGE_CONFIG"), timestamp) destDir := filepath.Join(os.Getenv("FORGE_CONFIG"), "backup")
return backupFiles(srcDir, destDir) return backupFiles(srcDir, destDir)
} }
@ -44,7 +39,7 @@ func backupFiles(srcDir string, destDir string) error {
continue continue
} }
log.Println("backing up file", entry.Name()) // log.Println("backing up file", entry.Name())
srcPath := filepath.Join(srcDir, entry.Name()) srcPath := filepath.Join(srcDir, entry.Name())
destPath := filepath.Join(destDir, entry.Name()) destPath := filepath.Join(destDir, entry.Name())
@ -64,6 +59,9 @@ func copyFile(src, dest string) error {
} }
defer srcFile.Close() 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) destFile, err := os.Create(dest)
if err != nil { if err != nil {
return err return err

View File

@ -2,8 +2,6 @@ package forgepb
import ( import (
"fmt" "fmt"
"go.wit.com/log"
) )
func (all *ForgeConfigs) sampleConfig() { func (all *ForgeConfigs) sampleConfig() {
@ -11,53 +9,7 @@ func (all *ForgeConfigs) sampleConfig() {
new1.GoPath = "go.wit.com" new1.GoPath = "go.wit.com"
new1.Writable = true new1.Writable = true
new1.Directory = true new1.Directory = true
if all.Append(new1) { 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)")
}
fmt.Println("first time user. adding an example config file with", len(all.ForgeConfigs), "repos") fmt.Println("first time user. adding an example config file with", len(all.ForgeConfigs), "repos")
} }

View File

@ -11,6 +11,8 @@ package forgepb
IsPrivate(repo) // repo can't be published to the pkg.go.dev system IsPrivate(repo) // repo can't be published to the pkg.go.dev system
DebName() // for 'zookeeper' returns 'zookeeper-go' DebName() // for 'zookeeper' returns 'zookeeper-go'
This code is practical, not perfect
*/ */
import ( import (
@ -227,3 +229,96 @@ func (fc *ForgeConfigs) IsWritable(thing string) bool {
return match.Writable 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
}

View File

@ -1,6 +1,8 @@
package forgepb package forgepb
import ( import (
"errors"
"fmt"
"strings" "strings"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
@ -16,100 +18,137 @@ import (
// it re-scans the go.sum file. DOES NOT MODIFY ANYTHING // it re-scans the go.sum file. DOES NOT MODIFY ANYTHING
// this is the last thing to run to double check everything // this is the last thing to run to double check everything
// before 'git tag' or git push --tags // before 'git tag' or git push --tags
func (f *Forge) FinalGoDepsCheckOk(check *gitpb.Repo) bool { func (f *Forge) FinalGoDepsCheckOk(check *gitpb.Repo, verbose bool) error {
var good bool = true
if check == nil { if check == nil {
log.Info("boo, check == nil") return errors.New("FinalGoDepsCheckOk() boo, check == nil")
return false
}
if !check.Exists("go.mod") {
log.Info("go.mod is missing in", check.GoPath)
return false
} }
// clear out the protobuf and rescan from the file // parse the go.mod and go.sum files
check.GoDeps = nil if !check.ParseGoSum() {
if ok, _ := check.IsPrimitive(); ok { return fmt.Errorf("forge.ParseGoSum() failed. go.mod & go.sum are broken")
return true
}
if ok, err := check.ParseGoSum(); !ok {
log.Info("FinalGoDepsCheckOk() error", err)
return false
} }
if check.GoDepsLen() == 0 { if check.GetGoPrimitive() {
// this is a primitive return nil
check.GoPrimitive = true
return true
} }
log.Printf("current repo %s go dependancy count: %d", check.GetGoPath(), check.GoDepsLen())
deps := check.GoDeps.SortByGoPath() deps := check.GoDeps.SortByGoPath()
for deps.Scan() { for deps.Scan() {
depRepo := deps.Next() depRepo := deps.Next()
found := f.Repos.FindByGoPath(depRepo.GetGoPath()) found := f.FindByGoPath(depRepo.GetGoPath())
if found == nil { if found == nil {
if f.CheckOverride(depRepo.GetGoPath()) { if f.CheckOverride(depRepo.GetGoPath()) {
// skip this gopath because it's probably broken forever // skip this gopath because it's probably broken forever
continue continue
} }
log.Info("not found:", depRepo.GetGoPath()) return fmt.Errorf("dep not found: %s", depRepo.GetGoPath())
good = false }
if depRepo.GetVersion() == found.GetMasterVersion() {
// log.Printf("%-48s error ?? %-10s vs %-10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
continue continue
} }
// log.Info("found dep", depRepo.GetGoPath()) // log.Info("found dep", depRepo.GetGoPath())
if depRepo.GetVersion() != found.GetTargetVersion() { if depRepo.GetVersion() != found.GetTargetVersion() {
check := f.Repos.FindByGoPath(depRepo.GoPath) check := f.FindByGoPath(depRepo.GetGoPath())
if f.Config.IsReadOnly(check.GoPath) { if f.Config.IsReadOnly(check.GetGoPath()) {
log.Printf("%-48s ok error .%s. vs .%s. (ignoring read-only repo)", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion()) if verbose {
log.Printf("%-48s ok error .%s. vs .%s. (ignoring read-only repo)\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion())
}
} else { } else {
if f.CheckOverride(depRepo.GetGoPath()) { if f.CheckOverride(depRepo.GetGoPath()) {
log.Printf("%-48s ok error .%s. vs .%s. (forge.CheckOverride())", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion()) 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 // skip this gopath because it's probably broken forever
continue continue
} else { } else {
log.Printf("%-48s error %10s vs %10s", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetTargetVersion()) // log.Printf("%-48s error ?? %-10s vs %-10s\n", depRepo.GetGoPath(), depRepo.GetVersion(), found.GetMasterVersion())
good = false // 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 good return nil
} }
func (f *Forge) CheckOverride(gopath string) bool { func (f *Forge) CheckOverride(gopath string) bool {
if gopath == "cloud.google.com/go" { if gopath == "cloud.google.com/go" {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
if gopath == "bou.ke/monkey" { if gopath == "bou.ke/monkey" {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
if gopath == "github.com/posener/complete/v2" { if gopath == "github.com/posener/complete/v2" {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
if strings.HasPrefix(gopath, "github.com/go-gl") { if strings.HasPrefix(gopath, "github.com/go-gl") {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
if strings.HasPrefix(gopath, "google.golang.org") { if strings.HasPrefix(gopath, "google.golang.org") {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
if strings.HasPrefix(gopath, "go.opencensus.io") { if strings.HasPrefix(gopath, "go.opencensus.io") {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
if strings.HasPrefix(gopath, "github.com/nicksnyder/go-i18n") { if strings.HasPrefix(gopath, "github.com/nicksnyder/go-i18n") {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
// fuckit for now. just blacklist github.com // fuckit for now. just blacklist github.com
if strings.HasPrefix(gopath, "github.com/") { if strings.HasPrefix(gopath, "github.com/") {
log.Info("CheckOverride() is ignoring", gopath) // log.Info("CheckOverride() is ignoring", gopath)
return true return true
} }
return false 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
}

View File

@ -1,3 +1,5 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
syntax = "proto3"; syntax = "proto3";
package forgepb; package forgepb;
@ -12,8 +14,8 @@ import "google/protobuf/timestamp.proto"; // Import the well-known type for Time
// package names sometimes must be different than the binary name // package names sometimes must be different than the binary name
// for example 'zookeeper' is packaged as 'zookeeper-go' // for example 'zookeeper' is packaged as 'zookeeper-go'
// due to the prior apache foundation project. This happens and is ok! // due to the prior apache foundation project. This happens and is ok!
message ForgeConfig { message ForgeConfig { // `autogenpb:nomutex`
string goPath = 1; // `autogenpb:unique` // Examples: 'go.wit.com/apps/go-clone' or "~/mythings" or "/home/src/foo" 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 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 bool readOnly = 3; // the opposite, but needed for now because I don't know what I'm doing
@ -27,15 +29,34 @@ message ForgeConfig {
string userBranchName = 10; // whatever your username branch is string userBranchName = 10; // whatever your username branch is
string debName = 11; // the actual name used with 'apt install' (or distro apt equivalent. string debName = 11; // the actual name used with 'apt install' (or distro apt equivalent.
// todo: appeal to everyone to alias 'apt' on fedora, 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 // so we can make easier instructions for new linux users. KISS
google.protobuf.Timestamp verstamp = 12; // the git commit timestamp of the version 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 string goSrc = 13; // is ~/go/src unless a go.work file is found
} }
message ForgeConfigs { // `autogenpb:marshal` // todo: fix autogenpb to look for enum
string uuid = 1; // could be useful for /usr/share/file/magic someday? enum ForgeMode {
string version = 2; // could be used for protobuf schema change violations? MASTER = 0; // "release mode"
repeated ForgeConfig ForgeConfigs = 3; 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; //
} }

View File

@ -1,3 +0,0 @@
// Package forgepb describes the protobuf's used by 'go.wit.com/apps/forge'
package forgepb // import "go.wit.com/lib/protobuf/forgepb"
// `go-clean go=1.18`

View File

@ -1,151 +0,0 @@
package forgepb
import (
"errors"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func (f *Forge) GitPull() bool {
f.Repos.RillGitPull(5, 5)
/*
var localonly int
var badmap int
log.Log(FORGEPBWARN, "running git pull everywhere")
var failed int = 0
for all.Scan() {
repo := all.Next()
if out, err := repo.GitPull(); err == nil {
log.Log(FORGEPBWARN, "Ran git pull ok", repo.GetGoPath(), out)
} else {
failed += 1
// repo.DumpTags()
if errors.Is(repostatus.ErrorGitPullOnLocal, err) {
localonly += 1
continue
}
badmap += 1
log.Log(FORGEPBWARN, "bad unknown git error", repo.GetGoPath(), out, err)
}
}
log.Log(FORGEPBWARN, "Ran git pull in all repos. failure count =", failed)
log.Log(FORGEPBWARN, "Ran git pull in all repos. bad errors =", badmap)
if localonly != 0 {
log.Log(FORGEPBWARN, "Ran git pull in all repos. ignored local only branches =", localonly)
}
*/
return true
}
func (f *Forge) CheckoutDevel() bool {
log.Log(FORGEPBWARN, "running git checkout devel everwhere")
var failed int = 0
var count int = 0
all := f.Repos.SortByGoPath()
for all.Scan() {
repo := all.Next()
count += 1
if repo.CheckoutDevel() {
// checkout ok
} else {
dname := repo.GetDevelBranchName()
if err := f.makeBranch(repo, dname); err != nil {
log.Info(repo.GoPath, "can not make devel branch", dname)
failed += 1
}
}
}
log.Log(FORGEPBWARN, "Ran git checkout in", count, "repos. failure count =", failed)
return true
}
func (f *Forge) MakeDevelBranch(repo *gitpb.Repo) error {
dname := repo.GetDevelBranchName()
if dname == "" {
dname = f.configDevelBranchName(repo)
}
if err := f.makeBranch(repo, dname); err != nil {
return err
}
repo.DevelBranchName = dname
return nil
}
func (f *Forge) MakeUserBranch(repo *gitpb.Repo) error {
uname := repo.GetUserBranchName()
if uname == "" {
uname = f.configUserBranchName(repo)
}
if err := f.makeBranch(repo, uname); err != nil {
return err
}
repo.UserBranchName = uname
return nil
}
func (f *Forge) makeBranch(repo *gitpb.Repo, bname string) error {
if repo.IsLocalBranch(bname) {
// branch already exists in refs/heads/
return nil
}
if repo.IsBranch(bname) {
// branch already exists refs/remotes/
return nil
} else {
log.Info("makeBranch() says", bname, "does not exist")
loop := repo.Tags.All()
for loop.Scan() {
t := loop.Next()
log.Info("LocalTagExists() tag:", t.Refname)
}
}
mname := repo.GetMasterBranchName()
cname := repo.GetCurrentBranchName()
if mname != cname {
return errors.New("can only make branches from master branch")
}
cmd := []string{"git", "branch", bname}
if err := repo.StrictRun(cmd); err != nil {
return err
}
return nil
}
func (f *Forge) CheckoutMaster() bool {
log.Log(FORGEPBWARN, "running git checkout master everwhere")
var failed int = 0
var count int = 0
all := f.Repos.SortByGoPath()
for all.Scan() {
repo := all.Next()
count += 1
if repo.CheckoutMaster() {
// checkout ok
} else {
failed += 1
}
}
log.Log(FORGEPBWARN, "Ran git checkout in", count, "repos. failure count =", failed)
return true
}
func (f *Forge) CheckoutUser() bool {
log.Log(FORGEPBWARN, "running git checkout master everwhere")
var failed int = 0
var count int = 0
all := f.Repos.SortByGoPath()
for all.Scan() {
repo := all.Next()
count += 1
if repo.CheckoutUser() {
// checkout ok
} else {
failed += 1
}
}
log.Log(FORGEPBWARN, "Ran git checkout in", count, "repos. failure count =", failed)
return true
}

View File

@ -9,10 +9,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"go.wit.com/lib/gui/shell"
"go.wit.com/log" "go.wit.com/log"
) )
func (f *Forge) GetHome() string {
return f.goSrc
}
// look for a go.work file // look for a go.work file
// otherwise use ~/go/src // otherwise use ~/go/src
func (f *Forge) findGoSrc() (string, error) { func (f *Forge) findGoSrc() (string, error) {
@ -48,9 +51,8 @@ func useGoSrc() (string, error) {
return "", err return "", err
} }
pwd := filepath.Join(homeDir, "go/src") pwd := filepath.Join(homeDir, "go/src")
shell.Mkdir(pwd) err = os.MkdirAll(pwd, os.ModePerm)
// os.Chdir(pwd) return pwd, err
return pwd, nil
} }
func (f *Forge) goWorkExists() bool { func (f *Forge) goWorkExists() bool {

View File

@ -24,28 +24,35 @@ func (f *Forge) ScanGoSrc() (bool, error) {
if strings.HasPrefix(dir, f.goSrc) { if strings.HasPrefix(dir, f.goSrc) {
gopath := strings.TrimPrefix(dir, f.goSrc) gopath := strings.TrimPrefix(dir, f.goSrc)
gopath = strings.Trim(gopath, "/") gopath = strings.Trim(gopath, "/")
if r := f.Repos.FindByGoPath(gopath); r != nil { if r := f.FindByGoPath(gopath); r != nil {
// log.Info("already have", gopath) // log.Info("already have", gopath)
continue continue
} }
gopaths = append(gopaths, gopath) gopaths = append(gopaths, gopath)
} else { } else {
log.Log(FORGEPBWARN, "ScanGoSrc() bad:", dir) log.Log(WARN, "ScanGoSrc() bad:", dir)
return false, errors.New("forgepb.ScanGoSrc() bad dir: " + dir) return false, errors.New("forgepb.ScanGoSrc() bad dir: " + dir)
} }
} }
newcount, err := f.rillScanDirs(gopaths) newcount, err := f.rillScanDirs(gopaths)
if err != nil { if err != nil {
log.Info("go src dir problem. exit for now?", err) log.Info("go src dir problem. exit for now?", err)
os.Exit(-1) return false, err
} }
if newcount != 0 { if newcount != 0 {
log.Info("forge go src scan found", newcount, "repos") log.Info("forge go src scan found", newcount, "repos")
f.Repos.ConfigSave() f.SetConfigSave(true)
} }
return true, err return true, err
} }
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/ // doesn't enter the directory any further when it finds a .git/
// not stupid like my old version // not stupid like my old version
func gitDirectoriesNew(srcDir string) ([]string, error) { func gitDirectoriesNew(srcDir string) ([]string, error) {
@ -65,8 +72,12 @@ func gitDirectoriesNew(srcDir string) ([]string, error) {
switch fname { switch fname {
case "repos.pb": case "repos.pb":
case "go.work": case "go.work":
case "go.work.last":
default: default:
// todo: figure out a way to do padding for init() // 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) log.Info("WARNING: you have an untracked file outside of any .git repository:", path)
trip = true trip = true
} }
@ -102,7 +113,7 @@ func gitDirectoriesOld(srcDir string) ([]string, error) {
var all []string var all []string
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
log.Log(FORGEPBWARN, "Error accessing path:", path, err) log.Log(WARN, "Error accessing path:", path, err)
return nil return nil
} }
@ -115,7 +126,7 @@ func gitDirectoriesOld(srcDir string) ([]string, error) {
}) })
if err != nil { if err != nil {
log.Log(FORGEPBWARN, "Error walking the path:", srcDir, err) log.Log(WARN, "Error walking the path:", srcDir, err)
} }
return all, err return all, err
@ -140,7 +151,7 @@ func (f *Forge) rillScanDirs(gopaths []string) (int, error) {
// Read users from the API. // Read users from the API.
// Concurrency = 20 // Concurrency = 20
dirs := rill.Map(ids, 20, func(id string) (*gitpb.Repo, error) { dirs := rill.Map(ids, 20, func(id string) (*gitpb.Repo, error) {
return f.NewGoPathRepo(id) return f.checkpath(id, "")
}) })
var counter int var counter int
@ -154,36 +165,12 @@ func (f *Forge) rillScanDirs(gopaths []string) (int, error) {
return counter, err return counter, err
} }
func (f *Forge) RillRedoGoMod() int { func (f *Forge) checkpath(gopath string, url string) (*gitpb.Repo, error) {
var all []*gitpb.Repo fullpath := filepath.Join(f.GetGoSrc(), gopath)
tmp := f.Repos.SortByGoPath() log.Info("forge creating protobuf for", fullpath)
for tmp.Scan() { repo, err := f.NewGoRepo(gopath, "")
repo := tmp.Next()
if !repo.IsValid() {
log.Printf("%10s %-50s", "old?", 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, 50, func(id *gitpb.Repo) (*gitpb.Repo, error) {
return id, nil
})
err := rill.ForEach(dirs, 20, func(repo *gitpb.Repo) error {
counter += 1
// repo.RedoGoMod()
return nil
})
if err != nil { if err != nil {
log.Info("rill.ForEach() error:", err) log.Info("\tprotobuf error", gopath, err)
} }
return repo, err
return counter
} }

View File

@ -1,3 +1,5 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb package forgepb
import ( import (
@ -23,28 +25,31 @@ func (f *Forge) MakeGoWork() error {
} }
defer workf.Close() defer workf.Close()
fmt.Fprintln(workf, "go 1.20") // fix this fmt.Fprintln(workf, "go 1.21") // fix this
fmt.Fprintln(workf, "") fmt.Fprintln(workf, "")
fmt.Fprintln(workf, "use (") fmt.Fprintln(workf, "use (")
all := f.Repos.SortByGoPath() all := f.Repos.SortByFullPath()
for all.Scan() { for all.Scan() {
repo := all.Next() repo := all.Next()
/* /*
if !repo.IsGoLang() == "" { if !repo.IsGoLang() == "" {
// skip repos that aren't go // skip repos that aren't go
// todo: handle non-flat repos? // todo: handle non-flat repos?
log.Info("skip non-go", repo.GoPath) log.Info("skip non-go", repo.GetGoPath)
continue continue
} }
*/ */
fmt.Fprintln(workf, "\t"+repo.GoPath) if repo.GetGoPath() == "" {
continue
}
fmt.Fprintln(workf, "\t"+repo.GetGoPath())
/* /*
if repo.pb.Exists("go.mod") { if repo.pb.Exists("go.mod") {
// log.Info("ADDING REPO", goSrcDir, repo.GoPath) // log.Info("ADDING REPO", goSrcDir, repo.GetGoPath())
} else { } else {
fmt.Fprintln(workf, "\t"+repo.GoPath) fmt.Fprintln(workf, "\t"+repo.GetGoPath)
log.Log(REPO, "missing go.mod for", repo.GoPath) log.Log(REPO, "missing go.mod for", repo.GetGoPath())
} }
*/ */
} }

78
http.go Normal file
View File

@ -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
}

View File

@ -1,3 +1,5 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb package forgepb
import ( import (
@ -33,6 +35,18 @@ func (f *Forge) standardHeader(r *ForgeConfig) string {
} else { } else {
readonly = "r/w" 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) return fmt.Sprintf("%-4s %-40s %s", readonly, r.GoPath, flags)
} }
@ -51,20 +65,20 @@ func (f *Forge) ConfigPrintTable() {
// show information while doing golang releases // show information while doing golang releases
func (f *Forge) StandardReleaseHeader(repo *gitpb.Repo, state string) string { func (f *Forge) StandardReleaseHeader(repo *gitpb.Repo, state string) string {
lastTag := repo.GetLastTag()
// tag := repo.NewestTag() // tag := repo.NewestTag()
// gitAge, _ := tag.GetDate() // gitAge, _ := tag.GetDate()
dur := repo.NewestAge() dur := repo.NewestAge()
curname := repo.GetCurrentBranchName() curname := repo.GetCurrentBranchName()
lastTag := repo.GetLastTag()
target := repo.GetTargetVersion()
master := repo.GetMasterVersion() master := repo.GetMasterVersion()
user := repo.GetUserVersion() user := repo.GetUserVersion()
target := repo.GetTargetVersion()
header := fmt.Sprintf("%-35s %5s %-10s %-10s %-10s %-20s %-20s %-15s", header := fmt.Sprintf("%-35s %5s %-10s %-10s %-10s %-20s %-20s %-15s",
repo.GetGoPath(), shell.FormatDuration(dur), curname, repo.GetGoPath(), shell.FormatDuration(dur), curname,
lastTag, target, lastTag, target, master, user,
master, user,
state) state)
return header return header
} }
@ -76,3 +90,26 @@ func ReleaseReportHeader() string {
"MASTER", "USER", "MASTER", "USER",
"STATE") "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
}

105
humanShowRepo.go Normal file
View File

@ -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))
}
}

245
humanTable.go Normal file
View File

@ -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)
}

17
identify.go Normal file
View File

@ -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
}

136
init.go
View File

@ -1,11 +1,15 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb package forgepb
import ( import (
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log" "go.wit.com/log"
) )
@ -15,27 +19,63 @@ import (
func Init() *Forge { func Init() *Forge {
f := InitPB() f := InitPB()
/*
f.Machine = new(zoopb.Machine)
if err := f.Machine.ConfigLoad(); err != nil {
log.Log(WARN, "zoopb.ConfigLoad() failed", err)
}
*/
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() start := f.Repos.Len()
f.ScanGoSrc() f.ScanGoSrc()
f.FullScanRan()
end := f.Repos.Len() end := f.Repos.Len()
if (end - start) == 0 { if (end - start) == 0 {
log.Info("forgepb.Scan() Scan did not find new git repositories.") log.Log(INFO, "forgepb.Scan() Scan did not find new git repositories. Total =", end)
if f.FullScanAge() > time.Minute {
f.rillUpdate(20, 10)
}
} else { } else {
log.Info("forgepb.Scan() Scan found", end-start, "new git repositories.") log.Log(INFO, "forgepb.Scan() Scan found", end-start, "new git repositories. Total =", end)
f.rillUpdate(20, 10)
} }
f.Machine = new(zoopb.Machine) if f.configSave {
// taking this out to debug Marshal() panic
if err := f.Machine.ConfigLoad(); err != nil { // os.Exit(-1)
log.Warn("zoopb.ConfigLoad() failed", err) f.ConfigSave()
os.Exit(-1) f.configSave = false
} }
f.Machine.InitWit() log.Log(INFO, "update() check took", shell.FormatDuration(time.Since(now)))
return f return f
} }
// only init's the protobuf. intended to not scan or change anything func DetermineGoPath() *Forge {
func InitPB() *Forge {
f := new(Forge) f := new(Forge)
// TODO: rethink this but it works for now // TODO: rethink this but it works for now
@ -43,8 +83,7 @@ func InitPB() *Forge {
if gosrc == "" { if gosrc == "" {
goSrcDir, err := f.findGoSrc() goSrcDir, err := f.findGoSrc()
if err != nil { if err != nil {
log.Warn("forge init() findGoSrc()", err) log.Log(WARN, "forge init() findGoSrc()", err)
os.Exit(-1)
} }
os.Setenv("FORGE_GOSRC", goSrcDir) os.Setenv("FORGE_GOSRC", goSrcDir)
} }
@ -57,27 +96,86 @@ func InitPB() *Forge {
os.Setenv("FORGE_CONFIG", fullpath) 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 // check again for go.work // user could have a go.work file in ~/go/src
if f.goWorkExists() { if f.goWorkExists() {
f.goWork = true f.goWork = true
} }
// print out the settings that will be used // print out the settings that will be used
log.Info("forgepb.Init() FORGE_CONFIG", os.Getenv("FORGE_CONFIG")) log.Log(INFO, "forgepb.Init() FORGE_CONFIG", os.Getenv("FORGE_CONFIG"))
return f
}
func (f *Forge) InitPB() {
// load the ~/.config/forge/ config // load the ~/.config/forge/ config
f.Config = new(ForgeConfigs) f.Config = new(ForgeConfigs)
if err := f.Config.ConfigLoad(); err != nil { if err := f.Config.ConfigLoad(); err != nil {
log.Warn("forgepb.ConfigLoad() failed", err) log.Log(WARN, "forgepb.ConfigLoad() failed", err)
os.Exit(-1)
} }
if f.IsGoWork() { if f.IsGoWork() {
log.Info("forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = true)") log.Log(INFO, "forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = true)")
} else { } else {
log.Info("forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = false)") log.Log(INFO, "forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = false)")
} }
f.Repos = new(gitpb.Repos)
f.Repos = gitpb.NewRepos()
f.Repos.ConfigLoad() 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 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
View File

@ -4,13 +4,13 @@ import (
"go.wit.com/log" "go.wit.com/log"
) )
var FORGEPB *log.LogFlag var INFO *log.LogFlag
var FORGEPBWARN *log.LogFlag var WARN *log.LogFlag
func init() { func init() {
full := "go.wit.com/lib/protobuf/forgepb" full := "go.wit.com/lib/protobuf/forgepb"
short := "forgepb" short := "forgepb"
FORGEPB = log.NewFlag("FORGEPB", false, full, short, "general forgepb things") INFO = log.NewFlag("INFO", false, full, short, "general forgepb things")
FORGEPBWARN = log.NewFlag("FORGEPBWARN", true, full, short, "forgepb warnings") WARN = log.NewFlag("WARN", true, full, short, "forgepb warnings")
} }

17
mode.go Normal file
View File

@ -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()
}
}

View File

@ -1,214 +0,0 @@
package forgepb
import (
"errors"
"fmt"
"os"
"path/filepath"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func (f *Forge) MakeDevelPatchSet() (*Patchs, error) {
pset := new(Patchs)
dir, err := os.MkdirTemp("", "forge")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir) // clean up
pset.TmpDir = dir
all := f.Repos.SortByGoPath()
for all.Scan() {
repo := all.Next()
userb := repo.GetUserBranchName()
develb := repo.GetDevelBranchName()
if develb == "" {
continue
}
if userb == "" {
continue
}
pset.StartBranchName = develb
pset.EndBranchName = userb
err := pset.makePatchSetNew(repo)
if err != nil {
return nil, err
}
}
return pset, nil
}
func (f *Forge) MakeMasterPatchSet() (*Patchs, error) {
pset := new(Patchs)
dir, err := os.MkdirTemp("", "forge")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir) // clean up
pset.TmpDir = dir
all := f.Repos.SortByGoPath()
for all.Scan() {
repo := all.Next()
startb := repo.GetMasterBranchName()
endb := repo.GetUserBranchName()
if startb == "" {
continue
}
if endb == "" {
continue
}
// log.Info("repo", repo.GoPath, startb, "..", endb)
pset.StartBranchName = startb
pset.EndBranchName = endb
err := pset.makePatchSetNew(repo)
if err != nil {
return nil, err
}
}
return pset, nil
}
func (pset *Patchs) makePatchSetNew(repo *gitpb.Repo) error {
startBranch := pset.StartBranchName
endBranch := pset.EndBranchName
repoDir := filepath.Join(pset.TmpDir, repo.GoPath)
err := os.MkdirAll(repoDir, 0755)
if err != nil {
return err
}
// git format-patch branch1..branch2
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
}
return pset.addPatchFiles(repoDir)
}
// var pset *Patchs
func (f *Forge) MakePatchSet() (*Patchs, error) {
pset := new(Patchs)
dir, err := os.MkdirTemp("", "forge")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir) // clean up
all := f.Repos.SortByGoPath()
for all.Scan() {
repo := all.Next()
userb := repo.GetUserBranchName()
develb := repo.GetDevelBranchName()
if develb == "" {
continue
}
if userb == "" {
continue
}
repoDir := filepath.Join(dir, repo.GoPath)
err := os.MkdirAll(repoDir, 0755)
if err != nil {
return nil, err
}
// git format-patch branch1..branch2
cmd := []string{"git", "format-patch", "-o", repoDir, develb + ".." + userb}
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 nil, 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 nil, r.Error
}
if len(r.Stdout) == 0 {
continue
}
pset.addPatchFiles(repoDir)
}
return pset, nil
}
// process each file in pDir/
func (p *Patchs) addPatchFiles(pDir string) error {
// log.Info("ADD PATCH FILES ADDED DIR", pDir)
var baderr error
filepath.Walk(pDir, 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("TESTING FILE", path)
data, err := os.ReadFile(path)
if err != nil {
log.Info("addPatchFile() failed", path)
baderr = err
return err
}
patch := new(Patch)
patch.Filename = path
patch.Data = data
p.Patchs = append(p.Patchs, patch)
// log.Info("ADDED PATCH FILE", path)
return nil
})
return baderr
}
// 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
}

View File

@ -1,30 +0,0 @@
syntax = "proto3";
package forgepb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message Patch {
string filename = 1; // `autogenpb:unique`
bytes data = 2; //
string repoPath = 3; // path to the git repo
string branchName = 4; //
string branchHash = 5; //
google.protobuf.Timestamp ctime = 7; // the git commit timestamp of the version
}
message Patchs { // `autogenpb:marshal`
string uuid = 1; // `autogenpb:uuid:0703df95-6a38-4422-994b-c55d3d6001f9` // todo: add file support
string version = 2; // could be used for protobuf schema change violations?
repeated Patch Patchs = 3;
string name = 4; //
string comment = 5; //
string gitAuthorName = 6; //
string gitAuthorEmail = 7; //
google.protobuf.Timestamp ctime = 8; // create time of this patchset
string tmpDir = 9; // temp dir
string startBranchName = 10; //
string endBranchName = 11; //
string startBranchHash = 12; //
string endBranchHash = 13; //
}

34
patchset.Get.go Normal file
View File

@ -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
}

272
patchset.Make.go Normal file
View File

@ -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
}

View File

@ -10,7 +10,7 @@ import (
"go.wit.com/log" "go.wit.com/log"
) )
func (f *Forge) SendPatchSet(pset *Patchs) error { func (f *Forge) SendPatchSet(pset *Patchset) error {
var err error var err error
data, err := pset.Marshal() data, err := pset.Marshal()
if err != nil { if err != nil {

86
patchset.proto Normal file
View File

@ -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;
}

View File

@ -1,219 +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"
)
// 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) (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")
}
if strings.HasPrefix(gopath, "github.com/go-gl/glfw") {
return cloneActual(dirname, basedir, "https://github.com/go-gl/glfw")
}
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 finalurl, err := cloneActual(dirname, basedir, url); err == nil {
// git clone worked!
return f.Repos.NewGoPath(f.goSrc, gopath, finalurl)
}
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 finalurl, err := cloneActual(dirname, basedir, url); err == nil {
// git clone worked!
return f.Repos.NewGoPath(f.goSrc, gopath, finalurl)
}
}
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 finalurl, err := cloneActual(dirname, basedir, url); err == nil {
// git clone worked!
return f.Repos.NewGoPath(f.goSrc, gopath, finalurl)
}
}
log.Info("git clone from 'git list' info failed", url)
// try to parse a redirect
if finalurl, err := clonePathHack(dirname, basedir, gopath); err == nil {
// WTF didn't go-import or go list work?
return f.Repos.NewGoPath(f.goSrc, gopath, finalurl)
}
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) (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 giturl, 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 giturl, r.Error
}
fullpath := filepath.Join(basedir, newdir)
if !IsDirectory(fullpath) {
log.Info("git clone failed", giturl)
return giturl, errors.New("git clone failed " + giturl)
}
gitdir := filepath.Join(fullpath, ".git")
if IsDirectory(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 giturl, nil
}
// git clone didn't really work but did make a directory
log.Info("fullpath is probably empty", fullpath)
return giturl, errors.New("crapnuts. rmdir fullpath here? " + fullpath)
}
// 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()
}

View File

@ -1,87 +1,187 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb package forgepb
import ( import (
"os/user" "fmt"
"os"
"path/filepath"
"regexp"
"strings"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
) )
func (f *Forge) NewGoPathRepo(gopath string) (*gitpb.Repo, error) { func (f *Forge) NewGoRepo(gopath string, url string) (*gitpb.Repo, error) {
repo, err := f.Repos.NewGoPath(f.GetGoSrc(), gopath, "") 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 { if err != nil {
log.Info("WARNING. NEW FAILED", fullpath)
return nil, err return nil, err
} }
// slices.Reverse(f.Repos.Repos)
repo.URL = url
f.VerifyBranchNames(repo) f.VerifyBranchNames(repo)
repo.ParseGoSum() if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true
}
repo.Reload()
return repo, nil return repo, nil
} }
func (f *Forge) VerifyBranchNames(newr *gitpb.Repo) { func isValidSemVer(version string) bool {
// log.Info("init worked for", newr.GoPath) // 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
}
}
if newr.GetMasterBranchName() == "" {
// try to guess what the 'master' branch is // try to guess what the 'master' branch is
if newr.IsBranch("guimaster") { if repo.IsBranch("master") {
newr.SetMasterBranchName("guimaster") repo.SetMasterBranchName("master")
} else if newr.IsBranch("master") {
newr.SetMasterBranchName("master")
} else if newr.IsBranch("main") {
newr.SetMasterBranchName("main")
} else {
// todo, figure out the name from git
newr.SetMasterBranchName("master")
if newr.CheckoutMaster() {
} else {
cmd := []string{"git", "branch", "master"}
newr.Run(cmd)
}
}
}
if f.Config.IsReadOnly(newr.GoPath) {
return return
} }
if newr.GetDevelBranchName() == "" { if repo.IsBranch("main") {
if newr.IsBranch("guidevel") { repo.SetMasterBranchName("main")
newr.SetDevelBranchName("guidevel") return
} else if newr.IsBranch("devel") {
newr.SetDevelBranchName("devel")
} else {
// forcing for now. todo: warn users
newr.SetDevelBranchName("devel")
if newr.CheckoutDevel() {
} else {
cmd := []string{"git", "branch", "devel"}
newr.Run(cmd)
}
}
} }
if newr.GetUserBranchName() == "" { // TODO: figure out the name from git
usr, _ := user.Current() repo.SetMasterBranchName("master")
uname := usr.Username /* no longer checkout on Init()
if newr.IsBranch(uname) { if repo.CheckoutMaster() {
newr.SetUserBranchName(uname) } else {
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 { } else {
// forcing for now. todo: warn users // forcing for now. todo: warn users
newr.SetUserBranchName(uname) repo.SetUserBranchName(uname)
if newr.CheckoutUser() {
} else {
cmd := []string{"git", "branch", uname}
newr.Run(cmd)
}
} }
} }
} }
// todo: check the forge config // what name should be used for the user branch?
func (f *Forge) configUserBranchName(repo *gitpb.Repo) string { func (f *Forge) configUserBranchName(repo *gitpb.Repo) string {
if repo.GetUserBranchName() != "" { // look in the repo record for the username
return repo.GetUserBranchName() uname := repo.GetUserBranchName()
if uname != "" {
return uname
} }
usr, _ := user.Current()
uname := usr.Username // 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 return uname
} }
@ -98,3 +198,44 @@ func (f *Forge) configDevelBranchName(repo *gitpb.Repo) string {
} }
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)
}

155
rill.go Normal file
View File

@ -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
}

54
run.go Normal file
View File

@ -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)
}

View File

@ -2,9 +2,9 @@ package forgepb
import ( import (
sync "sync" sync "sync"
"time"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/lib/protobuf/zoopb"
) )
// maybe an interface someday? // maybe an interface someday?
@ -12,18 +12,38 @@ type Forge struct {
// one-time initialized data // one-time initialized data
initOnce sync.Once initOnce sync.Once
initErr error // init error, if any initErr error // init error, if any
goSrc string // the path to go/src goSrc string // the path to go/src
configDir string // normally ~/.config/forge
goWork bool // means the user is currently using a go.work file goWork bool // means the user is currently using a go.work file
Config *ForgeConfigs // config repos for readonly, private, etc Config *ForgeConfigs // config repos for readonly, private, etc
Repos *gitpb.Repos Repos *gitpb.Repos // the repo protobufs
Machine *zoopb.Machine // 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 { func (f *Forge) GetGoSrc() string {
return f.goSrc return f.goSrc
} }
func (f *Forge) GetConfigDir() string {
return f.configDir
}
func (f *Forge) IsGoWork() bool { func (f *Forge) IsGoWork() bool {
return f.goWork 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)
}

View File

@ -1,10 +0,0 @@
syntax = "proto3";
// experiment to identify .pb files incase things go sideways
package forgepb;
// autogenpb:no-sort
message UuidConfigs { // `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?
}