Compare commits

..

75 Commits

Author SHA1 Message Date
Jeff Carr 2232aa5823 minor 2025-09-23 14:51:09 -05:00
Jeff Carr 8e3008cfe8 remove noise 2025-09-23 14:22:18 -05:00
Jeff Carr 884d8db585 tinkering with set.PrintTable() 2025-09-23 10:17:36 -05:00
Jeff Carr 4eada310b8 make a standard set.PrintTable() 2025-09-23 09:36:15 -05:00
Jeff Carr 643be0abea set PB tables are working 2025-09-23 09:02:19 -05:00
Jeff Carr 81fc15e743 less confusing patch .proto files 2025-09-23 07:44:07 -05:00
Jeff Carr 6bfd8c78ab save config if Username not set 2025-09-22 21:08:29 -05:00
Jeff Carr 060c304a43 forgot to set Username on init() 2025-09-22 21:07:32 -05:00
Jeff Carr 6890e68af3 output on failure 2025-09-22 20:32:34 -05:00
Jeff Carr 5325d874a8 attempt to find PatchId 2025-09-22 16:42:20 -05:00
Jeff Carr a1a524e933 use git's PatchId 2025-09-22 09:29:55 -05:00
Jeff Carr c752d40839 make forge work for a new user 2025-09-21 20:49:32 -05:00
Jeff Carr e953b998ae rm newlline 2025-09-18 08:54:47 -05:00
Jeff Carr 1762042e8b minor 2025-09-14 05:51:05 -05:00
Jeff Carr 98ecc249c0 quiet output 2025-09-13 08:39:20 -05:00
Jeff Carr cb6394f34a finally dump this old code 2025-09-13 08:31:54 -05:00
Jeff Carr 2fccf60335 other stuff 2025-09-13 07:45:13 -05:00
Jeff Carr 2b3bd66ef2 rill in forge.Config 2025-09-13 07:09:40 -05:00
Jeff Carr 27a539b879 finally a rewrite of the old junky scanner 2025-09-13 06:26:55 -05:00
Jeff Carr 0f895e83e6 use config GO library 2025-09-13 05:33:11 -05:00
Jeff Carr ce4af38e8b track changes 2025-09-13 01:31:15 -05:00
Jeff Carr d406ee5f21 finally a smarter forge.ConfigSave() 2025-09-13 00:56:29 -05:00
Jeff Carr bc9509e43b work on a better/faster Reload() 2025-09-13 00:52:44 -05:00
Jeff Carr b21a47434e smarter behavior when in 'Normal' mode 2025-09-12 14:52:45 -05:00
Jeff Carr 07b0fcf3b6 experiment with restricting writing to these 2025-09-12 10:45:09 -05:00
Jeff Carr be1cd7372c typo 2025-09-12 10:21:25 -05:00
Jeff Carr c99f9faf3c only with VERBOSE on 2025-09-12 10:18:55 -05:00
Jeff Carr 692264d1f1 reorder the 'end' var 2025-09-12 10:17:03 -05:00
Jeff Carr 7431308823 only let forge save the config files 2025-09-12 10:13:35 -05:00
Jeff Carr b77555e9fa sure 2025-09-12 02:03:22 -05:00
Jeff Carr e12a1fb496 rm GOSRC 2025-09-11 23:10:43 -05:00
Jeff Carr eda91e3af6 stop using GoSrc() 2025-09-11 22:14:57 -05:00
Jeff Carr 6c3162f7ce rm full scan stuff 2025-09-11 21:47:19 -05:00
Jeff Carr ef00352a0b rm old code 2025-09-11 21:40:15 -05:00
Jeff Carr 235fe57beb keep removing non-protobuf fields 2025-09-11 21:19:54 -05:00
Jeff Carr 81cac13284 start removing old code 2025-09-11 20:54:49 -05:00
Jeff Carr 98407ed8b7 better config output 2025-09-11 20:03:38 -05:00
Jeff Carr 8b41d89ab2 minor 2025-09-11 07:54:18 -05:00
Jeff Carr f2ec2e74ee check mtimes here 2025-09-11 06:57:38 -05:00
Jeff Carr 74313bd867 common forge.Init() 2025-09-11 04:46:47 -05:00
Jeff Carr 15d545f389 back to a common forge.Init() 2025-09-11 04:38:52 -05:00
Jeff Carr c62f48ec16 new init() 2025-09-11 03:05:25 -05:00
Jeff Carr de30c30005 s/namespace/Namespace/ 2025-09-11 02:54:05 -05:00
Jeff Carr b89a5571e9 change config file notes 2025-09-11 02:36:28 -05:00
Jeff Carr b231a2144d trim Init() code 2025-09-11 02:32:00 -05:00
Jeff Carr 98467dec72 using new config package 2025-09-11 02:20:06 -05:00
Jeff Carr dbde2f51b8 cleanup config file handling 2025-09-11 01:53:53 -05:00
Jeff Carr ac107331fc fixes for standard forge ENV values 2025-09-10 22:36:03 -05:00
Jeff Carr e2e30d02d1 code is autogen'd now 2025-09-08 04:35:10 -05:00
Jeff Carr 556c549265 autogen some http functions 2025-09-08 04:03:30 -05:00
Jeff Carr 18796d1a47 http autogen funcs 2025-09-08 03:37:37 -05:00
Jeff Carr da9221ce34 use the common httppb 2025-09-08 01:34:33 -05:00
Jeff Carr a3007ba1b5 correct protoc syntax 2025-09-08 01:05:18 -05:00
Jeff Carr 43281eea6f added logging to the http PB 2025-09-07 21:41:31 -05:00
Jeff Carr 16b0cad836 this is working out so far 2025-09-07 21:41:27 -05:00
Jeff Carr ecf4049947 start using a standard http PB 2025-09-07 21:41:20 -05:00
Jeff Carr e3c8669be4 works at 80x24. I'm rather proud of this app. 2025-09-07 12:05:51 -05:00
Jeff Carr 964aaf823d if this works well, then I am happier 2025-09-06 21:50:57 -05:00
Jeff Carr 9dbc7c6615 add a "normal" state 2025-09-06 19:21:21 -05:00
Jeff Carr 867a4f973e moved to httppb 2025-09-06 18:49:02 -05:00
Jeff Carr 3729df67a5 patches are starting to work 2025-09-06 16:05:26 -05:00
Jeff Carr bf031595e3 patchset http routines 2025-09-06 15:22:31 -05:00
Jeff Carr c0b9518c0d set route in PB 2025-09-05 14:17:30 -05:00
Jeff Carr a9dbfb9201 more work on patches 2025-09-05 12:59:38 -05:00
Jeff Carr 2015f84fb4 add http headers to protobuf 2025-09-05 12:54:27 -05:00
Jeff Carr 822fe38eee work on patch tracking 2025-09-05 12:53:19 -05:00
Jeff Carr 817107dc16 more patch stuff 2025-09-05 01:24:34 -05:00
Jeff Carr 4a27e7702b better Send() function 2025-09-04 22:38:07 -05:00
Jeff Carr 0e05b773cf make a test checkout devel function 2025-09-04 21:34:50 -05:00
Jeff Carr 625dfe2f68 tracking down inconsistancies 2025-09-04 20:39:32 -05:00
Jeff Carr f54f189bcd check if a config file exists for new users 2025-09-04 20:19:03 -05:00
Jeff Carr dbb0d9867d Is the user new? 2025-09-04 20:02:38 -05:00
Jeff Carr 8202a75eea cleaned up init.go more perfectly 2025-09-04 19:17:58 -05:00
Jeff Carr 4cac4386f5 smarter implementation of CheckDirty() 2025-09-04 18:32:10 -05:00
Jeff Carr 89e6fd0805 fix 'nil' panic 2025-09-04 15:10:35 -05:00
29 changed files with 1067 additions and 1239 deletions

View File

@ -5,7 +5,7 @@
# go install # go install
all: forgeConfig.pb.go patchset.pb.go goimports vet all: forgeConfig.pb.go patch.pb.go set.pb.go goimports vet
generate: clean generate: clean
autogenpb --proto patchset.proto autogenpb --proto patchset.proto
@ -27,5 +27,19 @@ clean:
forgeConfig.pb.go: forgeConfig.proto forgeConfig.pb.go: forgeConfig.proto
autogenpb --proto forgeConfig.proto autogenpb --proto forgeConfig.proto
patchset.pb.go: patchset.proto # patchset.pb.go: patchset.proto
autogenpb --proto patchset.proto # autogenpb --proto patchset.proto
patch.pb.go: patch.proto
autogenpb --proto patch.proto
set.pb.go: set.proto
autogenpb --proto set.proto
protoc-test:
cd ~/go/src && protoc \
--proto_path=. \
--go_out=. \
--go_opt=Mgo.wit.com/lib/protobuf/forgepb/patchset.proto=go.wit.com/lib/protobuf/forgepb \
--go_opt=Mgo.wit.com/lib/protobuf/httppb/httpRequest.proto=go.wit.com/lib/protobuf/httppb \
go.wit.com/lib/protobuf/forgepb/patchset.proto

View File

@ -1,10 +1,10 @@
package forgepb package forgepb
import ( import (
"os"
"path/filepath" "path/filepath"
"time" "time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
@ -81,7 +81,7 @@ func (f *Forge) DoAllCheckoutMaster() error {
f.RillFuncError(rillCheckoutMaster) f.RillFuncError(rillCheckoutMaster)
count := f.RillReload() count := f.RillReload()
if count != 0 { if count != 0 {
f.ConfigSave() config.SetChanged("repos", true)
} }
total, count, nope, err := f.IsEverythingOnMaster() total, count, nope, err := f.IsEverythingOnMaster()
@ -111,13 +111,12 @@ func rillCheckoutMaster(repo *gitpb.Repo) error {
// 'giterr' means something is very wrong with this repo // 'giterr' means something is very wrong with this repo
if repo.GetMasterVersion() == "giterr" { if repo.GetMasterVersion() == "giterr" {
repo.CheckoutMaster() repo.CheckoutMaster()
log.Info("master == giterr. BAD REPO", repo.GetFullPath()) log.Info(repo.GetFullPath(), "master == giterr. BAD REPO")
log.Info("master == giterr. BAD REPO", repo.GetFullPath()) log.Info(repo.GetFullPath(), "master == giterr. BAD REPO git describe --tags master --always")
log.Info("master == giterr. BAD REPO", repo.GetFullPath()) log.Info(repo.GetFullPath(), "master == giterr. BAD REPO. todo: figure this out in rillCheckoutMaster()")
cmd := []string{"git", "checkout", "main"} // todo: figure out main // cmd := []string{"git", "checkout", "main"} // todo: figure out main
repo.RunVerbose(cmd) // repo.RunVerbose(cmd)
os.Exit(-1) return log.Errorf("master version can not be determined")
return nil
} }
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() { if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on master // repo is already on master
@ -161,7 +160,7 @@ func (f *Forge) DoAllCheckoutUser(force bool) error {
f.RillFuncError(rillCheckoutUser) f.RillFuncError(rillCheckoutUser)
count := f.RillReload() count := f.RillReload()
if count != 0 { if count != 0 {
f.ConfigSave() config.SetChanged("repos", true)
} }
total, count, nope, err := f.IsEverythingOnUser() total, count, nope, err := f.IsEverythingOnUser()
@ -204,6 +203,51 @@ func (f *Forge) makeUserBranches() error {
return nil return nil
} }
func testReload(repo *gitpb.Repo) error {
if !repo.HasChanged() {
return nil
}
repo.ReloadCheck()
return log.Errorf("repo changed")
}
func (f *Forge) DoAllCheckoutDevelNew(force bool) error {
f.makeDevelBranches()
// first run git checkout
stats := f.RillFuncError(rillCheckoutDevel)
for path, stat := range stats {
dur := stat.End.Sub(stat.Start)
if dur > 1*time.Second {
log.Infof("%s git checkout took a long time (%s)\n", path, shell.FormatDuration(dur))
}
if stat.Err == nil {
// there was no error
continue
}
// didn't change to devel
}
var counter int
// recreate the repo protobuf
stats = f.RillFuncError(testReload)
for path, stat := range stats {
dur := stat.End.Sub(stat.Start)
if dur > 1*time.Second {
log.Infof("%s # Reload took a long time (%s)\n", path, shell.FormatDuration(dur))
}
if stat.Err == nil {
// repo didn't reload
continue
}
// repo reloaded
counter += 1
}
log.Info("reloaded", counter, "repos")
config.SetChanged("repos", true)
return nil
}
// is every repo on the devel branch? // is every repo on the devel branch?
func (f *Forge) DoAllCheckoutDevel(force bool) error { func (f *Forge) DoAllCheckoutDevel(force bool) error {
now := time.Now() now := time.Now()
@ -215,7 +259,7 @@ func (f *Forge) DoAllCheckoutDevel(force bool) error {
f.RillFuncError(rillCheckoutDevel) f.RillFuncError(rillCheckoutDevel)
count := f.RillReload() count := f.RillReload()
if count != 0 { if count != 0 {
f.ConfigSave() config.SetChanged("repos", true)
} }
total, count, nope, err := f.IsEverythingOnDevel() total, count, nope, err := f.IsEverythingOnDevel()

View File

@ -129,9 +129,9 @@ func (f *Forge) doBuild(repo *gitpb.Repo, userFlags []string, goWhat string) err
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())
} else { } else {
log.Info("GO111MODULE=", testenv, "f.goWork =", f.IsGoWork(), "f.gosrc =", f.GetGoSrc()) log.Info("GO111MODULE=", testenv, "f.goWork =", f.IsGoWork())
} }
log.Info("running:", repo.FullPath) log.Info("running:", repo.FullPath)
log.Info("running:", cmd) log.Info("running:", cmd)
@ -202,10 +202,10 @@ func (f *Forge) runAutogenpb(repo *gitpb.Repo) error {
return nil return nil
} }
// sortcut to find // used by guireleaser for now
func (f *Forge) FindWorkingDirRepo() *gitpb.Repo { func (f *Forge) FindWorkingDirRepo() *gitpb.Repo {
pwd, _ := os.Getwd() pwd, _ := os.Getwd()
basedir := strings.TrimPrefix(pwd, f.GetGoSrc()) basedir := strings.TrimPrefix(pwd, f.Config.ReposDir)
basedir = strings.Trim(basedir, "/") basedir = strings.Trim(basedir, "/")
return f.FindByGoPath(basedir) return f.FindByGoPath(basedir)
} }

View File

@ -29,7 +29,7 @@ func (f *Forge) GoClone(gopath string) (*gitpb.Repo, error) {
// will match /root/go/src/go.wit.com/apps/go-clone/something/inside // 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" // and return the *gitpb.Repo for "go.wit.com/apps/go-clone"
fullpath := filepath.Join(f.goSrc, gopath) fullpath := filepath.Join(f.Config.ReposDir, gopath)
if pb := f.FindAnyPath(fullpath); pb != nil { if pb := f.FindAnyPath(fullpath); pb != nil {
// repo already exists // repo already exists
return pb, nil return pb, nil
@ -156,7 +156,7 @@ func (f *Forge) goClonePop(gopath string) (*gitpb.Repo, error) {
func (f *Forge) urlClone(gopath, giturl string) (*gitpb.Repo, error) { func (f *Forge) urlClone(gopath, giturl string) (*gitpb.Repo, error) {
var err error var err error
fullpath := filepath.Join(f.goSrc, gopath) fullpath := filepath.Join(f.Config.ReposDir, gopath)
basedir, newdir := filepath.Split(fullpath) basedir, newdir := filepath.Split(fullpath)
// clone the URL directly // clone the URL directly

198
config.go
View File

@ -2,175 +2,85 @@
package forgepb package forgepb
// functions to import and export the protobuf
// data to and from config files
import ( import (
"errors"
"fmt"
"os" "os"
"path/filepath"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/prep"
"go.wit.com/log" "go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
) )
// returns err1 || err2
func (f *Forge) ConfigSave() error { func (f *Forge) ConfigSave() error {
var err error var err error
// backup the current config files
if e := f.backupConfig(); e != nil { if f.Config == nil {
log.Info("forge.BackupConfig() error", e) return log.Errorf("forge.Config == nil")
err = e
// continue here? notsure. could be bad either way
// out of disk space?
} }
if f.Config != nil {
if config.HasChanged("forge") {
// only let forge save the config files (?)
if prep.AppName() == "forge" || prep.AppName() == "guireleaser" {
log.Info("Okay, this is", prep.AppName())
} else {
log.Info("This is not forge")
return log.Errorf("Only forge can save the config files")
}
if e := f.Config.ConfigSave(); e != nil { if e := f.Config.ConfigSave(); e != nil {
log.Info("forge.Config.ConfigSave() error", e) log.Info("forge.Config.ConfigSave() error", e)
err = e err = e
} }
} }
if f.Repos != nil {
if f.HasFullScan() {
f.Repos.HasFullScan = true
t := time.Now()
f.Repos.FullScan = timestamppb.New(t)
}
if e := f.Repos.ConfigSave(); e != nil {
log.Info("forge.Repos.ConfigSave() error", e)
err = e
}
}
return err return err
} }
// functions to import and export the protobuf
// data to and from config files
// write to ~/.config/forge/ // write to ~/.config/forge/
func (f *ForgeConfigs) ConfigSave() error { func (cfg *ForgeConfigs) ConfigSave() error {
data, err := f.Marshal() var header string
if err != nil { header += "\n"
log.Info("proto.Marshal() failed len", len(data), err) header += "# the forge config file\n"
return err header += "# You can customize things like:\n"
} header += "#\n"
// log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data)) header += "# * which repos you have write access to\n"
header += "# * custom branch names for 'master', 'devel' and 'user'\n"
s := f.FormatTEXT() header += "# * 'favorites' so you can remember which things you like\n"
configWrite("forge.text", []byte(s)) header += "# * sometimes protobuf TEXT can fail so as a backup this also creates a .json file\n"
header += "#\n"
s = f.FormatJSON() header += "\n"
configWrite("forge.json", []byte(s)) return config.ConfigSaveWithHeader(cfg, header)
return nil
} }
// load the ~/.config/forge/ files func (cfg *ForgeConfigs) DumpENV() {
func (c *ForgeConfigs) ConfigLoad(fullpath string) error { log.Infof("CfgPB.Filename = %s\n", cfg.Filename)
// var data []byte log.Infof("CfgPB.ReposPB = %s\n", cfg.ReposPB)
// var err error log.Infof("CfgPB.ReposDir = %s\n", cfg.ReposDir)
if c == nil { log.Infof("CfgPB.PatchDir = %s\n", cfg.PatchDir)
// can't safely do c = new(ForgeConfig) if c is in a struct from the caller. notsure why log.Infof("CfgPB.ForgeURL = %s\n", cfg.ForgeURL)
// TODO: recheck this. it might work now? It's probably still a bad idea(?) if cfg.GoWork {
return errors.New("It's not safe to run ConfigLoad() on a nil") log.Infof("CfgPB.GoWork = %v\n", cfg.GoWork)
} }
log.Infof("CfgPB.Mode = %s\n", cfg.Mode)
// log.Infof("CfgPB.Hostname=%s\n", cfg.Hostname)
if err := c.loadText(); err == nil { if cfg.ReposPB != os.Getenv("FORGE_REPOSPB") {
return nil log.Infof("CfgPB file problem: cfg.ReposPB=%s != FORGE_REPOSPB=%s\n", cfg.ReposPB, os.Getenv("FORGE_REPOSPB"))
} }
// forge.text doesn't exist. try forge.json
// this lets the user hand edit the JSON config
// probably just deprecate this
if data, err := loadFile("forge.json"); err != nil {
if data != nil {
// this means the forge.json file exists and was read
if len(data) != 0 {
if err = c.UnmarshalJSON(data); err == nil {
log.Info("forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
// forge.text file was broken. save on load right away
log.Info("attempting forge.ConfigSave()")
c.ConfigSave()
return nil
}
}
}
}
cpath := filepath.Join(os.Getenv("FORGE_CONFIG"), ".")
if _, err := os.Stat(cpath); err == nil {
log.Info("Something has gone wrong. Your", os.Getenv("FORGE_CONFIG"), "directory exists")
log.Info("However, the config files could not be loaded")
}
// first time user. make a template config file
c.sampleConfig()
return nil
} }
func (c *ForgeConfigs) loadText() error { /*
// this lets the user hand edit the config if f.Config.Xterm == "" {
data, err := loadFile("forge.text") f.Config.Xterm = "xterm"
if err != nil { f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
return err f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
} f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
if data == nil { f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
return fmt.Errorf("forge.text data was nil") f.SetConfigSave(true)
}
if len(data) == 0 {
return fmt.Errorf("forge.text was empty")
} }
*/
// attempt to marshal forge.text func (cfg *ForgeConfigs) InitDefaults() {
if err := c.UnmarshalTEXT(data); err != nil {
return err
}
log.Log(INFO, "forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
return nil
}
func loadFile(filename string) ([]byte, error) {
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
data, err := os.ReadFile(fullname)
if errors.Is(err, os.ErrNotExist) {
// if file does not exist, just return nil. this
// will cause ConfigLoad() to try the next config file like "forge.text"
// because the user might want to edit the .config by hand
return nil, nil
}
if err != nil {
// log.Info("open config file :", err)
return nil, err
}
return data, nil
}
func configWrite(filename string, data []byte) error {
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close()
if err != nil {
log.Warn("open config file :", err)
return err
}
if filename == "forge.text" {
// 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"))
}
if filename == "forge.json" {
// add header
cfgfile.Write([]byte("\n"))
cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n"))
cfgfile.Write([]byte("# git repos you have write access to. That is, where you can run 'git push'\n"))
cfgfile.Write([]byte("\n"))
cfgfile.Write([]byte("# this file is parsed only if forge.text is missing\n"))
cfgfile.Write([]byte("# also, these comment lines don't work in json files and have to be removed for Marshal() to work\n"))
cfgfile.Write([]byte("# probably, JSON syntax for this is just going to be deprecated for the TEXT syntax\n"))
cfgfile.Write([]byte("\n"))
}
cfgfile.Write(data)
return nil
} }

View File

@ -1,74 +0,0 @@
package forgepb
// thank chatgpt for this because why. why write this if you can have it
// kick this out in 30 seconds
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"time"
)
func (f *Forge) backupConfig() error {
// make a new dir to backup the files
srcDir := filepath.Join(f.configDir)
destDir := filepath.Join(f.configDir, "backup")
return backupFiles(srcDir, destDir)
}
func backupFiles(srcDir string, destDir string) error {
// Create the destination directory
err := os.MkdirAll(destDir, os.ModePerm)
if err != nil {
return errors.New(fmt.Sprintf("Failed to create directory: %v", err))
}
// Read the contents of the source directory
entries, err := os.ReadDir(srcDir)
if err != nil {
return errors.New(fmt.Sprintf("Failed to read directory: %v", err))
}
// Iterate over the entries in the source directory
for _, entry := range entries {
// Skip directories and files that do not have the .test extension
if entry.IsDir() {
continue
}
// log.Println("backing up file", entry.Name())
srcPath := filepath.Join(srcDir, entry.Name())
destPath := filepath.Join(destDir, entry.Name())
// Copy the file
if err := copyFile(srcPath, destPath); err != nil {
return errors.New(fmt.Sprintf("Failed to copy file %s: %v", entry.Name(), err))
}
}
return nil
}
// copyFile copies a file from src to dest
func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
now := time.Now()
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
dest = dest + timestamp
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
// Copy the content
_, err = io.Copy(destFile, srcFile)
return err
}

View File

@ -1,15 +0,0 @@
package forgepb
import (
"fmt"
)
func (all *ForgeConfigs) sampleConfig() {
new1 := new(ForgeConfig)
new1.GoPath = "go.wit.com"
new1.Writable = true
new1.Directory = true
all.Append(new1)
fmt.Println("first time user. adding an example config file with", len(all.ForgeConfigs), "repos")
}

View File

@ -6,23 +6,48 @@ package forgepb
import ( import (
"time" "time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func (f *Forge) CheckDirtyQuiet() {
start := f.straightCheckDirty()
now := time.Now()
stats := f.RillRepos(doCheckDirty)
end := f.straightCheckDirty()
diff := end - start
var changed bool
for _, s := range stats {
if s.Err == nil {
} else {
config.SetChanged("repos", true)
changed = true
}
}
if changed {
log.Printf("dirty check (%d dirty repos) (%d total repos) (%d changed) took:%s\n", end, f.Repos.Len(), diff, shell.FormatDuration(time.Since(now)))
}
}
func (f *Forge) CheckDirty() *gitpb.Repos { func (f *Forge) CheckDirty() *gitpb.Repos {
start := f.straightCheckDirty() start := f.straightCheckDirty()
now := time.Now() now := time.Now()
f.RillFuncError(doCheckDirty) stats := f.RillRepos(doCheckDirty)
end := f.straightCheckDirty() end := f.straightCheckDirty()
diff := end - start diff := end - start
log.Printf("dirty check (%d dirty repos) (%d total repos) (%d changed) took:%s\n", end, f.Repos.Len(), diff, shell.FormatDuration(time.Since(now))) log.Printf("dirty check (%d dirty repos) (%d total repos) (%d changed) took:%s\n", end, f.Repos.Len(), diff, shell.FormatDuration(time.Since(now)))
// todo: actually detect if this needs to be changed? for i, s := range stats {
f.SetConfigSave(true) if s.Err == nil {
f.ConfigSave() } else {
log.Info(i, s.Err)
config.SetChanged("repos", true)
}
}
return f.FindDirty() return f.FindDirty()
} }
@ -38,11 +63,27 @@ func (f *Forge) straightCheckDirty() int {
} }
func doCheckDirty(repo *gitpb.Repo) error { func doCheckDirty(repo *gitpb.Repo) error {
repo.CheckDirty()
// reset these in here for now // reset these in here for now
if repo.GetTargetVersion() != "" { if repo.GetTargetVersion() != "" {
repo.TargetVersion = "" repo.TargetVersion = ""
} }
if repo.IsDirty() {
if repo.CheckDirty() {
// nothing changed
} else {
log.Info("Repo changed to clean", repo.FullPath)
return log.Errorf("%s repo changed to clean", repo.FullPath)
}
} else {
if repo.CheckDirty() {
log.Info("Repo changed to dirty", repo.FullPath)
return log.Errorf("%s repo changed to dirty", repo.FullPath)
} else {
// nothing changed
}
}
return nil return nil
} }

View File

@ -42,6 +42,8 @@ enum ForgeMode {
MASTER = 0; // "release mode" MASTER = 0; // "release mode"
DEVEL = 1; // "patch mode" DEVEL = 1; // "patch mode"
USER = 2; // "work mode" USER = 2; // "work mode"
NORMAL = 3; // "normal mode" // use this when you are developing code
CLEAN = 4; // indicates "clean" was run
} }
message ForgeConfigs { // `autogenpb:marshal` `autogenpb:nomutex` message ForgeConfigs { // `autogenpb:marshal` `autogenpb:nomutex`
@ -53,9 +55,17 @@ message ForgeConfigs { // `autogenpb:mar
repeated string xtermArgv = 6; // the argv line for xterm repeated string xtermArgv = 6; // the argv line for xterm
string defaultGui = 7; // default GUI plugin to use string defaultGui = 7; // default GUI plugin to use
ForgeMode mode = 8; // what "mode" forge is in ForgeMode mode = 8; // what "mode" forge is in
string goSrc = 9; // is ~/go/src unless a go.work file is found bool goWork = 9; // true if there is a go.work file
bool pathLock = 10; // the path is locked bool pathLock = 10; // the path is locked
string ReposPB = 11; // where the repos.pb is
string ReposDir = 12; // where the repos are
string PatchDir = 13; // patch dir
string ForgeURL = 14; // forge URL
string Filename = 15; // filename of the config file
int32 rillX = 16; // used by rill
int32 rillY = 17; // used by rill
} }
// this generic message is used by autogen to identify and // this generic message is used by autogen to identify and
// then dump the uuid and version from any arbitrary .pb file // then dump the uuid and version from any arbitrary .pb file
message Identify { // `autogenpb:marshal` message Identify { // `autogenpb:marshal`

View File

@ -1,90 +0,0 @@
package forgepb
// returns whatever your golang source dir is
// If there is a go.work file in your parent, that directory will be returned
// otherwise, return ~/go/src
import (
"fmt"
"os"
"path/filepath"
"go.wit.com/log"
)
func (f *Forge) GetHome() string {
return f.goSrc
}
// look for a go.work file
// otherwise use ~/go/src
func (f *Forge) findGoSrc() (string, error) {
pwd, err := os.Getwd()
// startpwd, _ := os.Getwd()
if err == nil {
// Check for go.work in the current directory and then move up until root
if pwd, err := digup(pwd); err == nil {
f.goWork = true
// found an existing go.work file
// os.Chdir(pwd)
return pwd, nil
} else {
// if there is an error looking for the go.work file
// default to using ~/go/src
// log.Info("forge.digup() err", pwd, err)
}
} else {
// this shouldn't really happen. maybe your working directory got deleted
log.Info("forge.findGoSrc() os.Getwd() was probably deleted", pwd, err)
}
// there are no go.work files, resume the ~/go/src behavior from prior to golang 1.22
pwd, err = useGoSrc()
return pwd, err
}
// this is the 'old way" and works fine for me. I use it because I like the ~/go/src directory
// because I know exactly what is in it: GO stuff & nothing else
func useGoSrc() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
pwd := filepath.Join(homeDir, "go/src")
err = os.MkdirAll(pwd, os.ModePerm)
return pwd, err
}
func (f *Forge) goWorkExists() bool {
var err error
workFilePath := filepath.Join(f.GetGoSrc(), "go.work")
if _, err = os.Stat(workFilePath); err == nil {
// log.Info("f.goWorkExists() found", workFilePath)
return true
} else if !os.IsNotExist(err) {
// log.Info("f.goWorkExists() missing", workFilePath)
return false
}
// probably false, but some other error
// log.Info("f.goWorkExists() os.Stat() error", err, workFilePath)
return false
}
func digup(path string) (string, error) {
for {
workFilePath := filepath.Join(path, "go.work")
if _, err := os.Stat(workFilePath); err == nil {
return path, nil // Found the go.work file
} else if !os.IsNotExist(err) {
return "", err // An error other than not existing
}
parentPath := filepath.Dir(path)
if parentPath == path {
break // Reached the filesystem root
}
path = parentPath
}
return "", fmt.Errorf("no go.work file found")
}

View File

@ -1,201 +0,0 @@
package forgepb
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/destel/rill"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func (f *Forge) ScanGoSrc() (bool, error) {
dirs, err := gitDirectoriesNew(f.goSrc)
if err != nil {
return false, err
}
var gopaths []string
for _, dir := range dirs {
// log.Info("forge.ScanGoSrc()", dir)
if strings.HasPrefix(dir, f.goSrc) {
gopath := strings.TrimPrefix(dir, f.goSrc)
gopath = strings.Trim(gopath, "/")
if r := f.FindByGoPath(gopath); r != nil {
// log.Info("already have", gopath)
continue
}
gopaths = append(gopaths, gopath)
} else {
log.Log(WARN, "ScanGoSrc() bad:", dir)
return false, errors.New("forgepb.ScanGoSrc() bad dir: " + dir)
}
}
newcount, err := f.rillScanDirs(gopaths)
if err != nil {
log.Info("go src dir problem. exit for now?", err)
return false, err
}
if newcount != 0 {
log.Info("forge go src scan found", newcount, "repos")
f.SetConfigSave(true)
}
return true, err
}
// returns a repo protobuf for a directory if the directory is a git repo
func (f *Forge) ScanDir(dir string) *gitpb.Repo {
repo, err := f.Repos.NewRepo(dir, "")
if err != nil {
log.Info("ScanDir() error", dir, err)
return nil
}
return repo
}
// doesn't enter the directory any further when it finds a .git/
// not stupid like my old version
func gitDirectoriesNew(srcDir string) ([]string, error) {
var all []string
var trip bool
err := filepath.WalkDir(srcDir, func(path string, d os.DirEntry, err error) error {
if err != nil {
// Handle possible errors, like permission issues
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
return err
}
if d.IsDir() {
// log.Info("path is dir", path)
} else {
_, fname := filepath.Split(path)
switch fname {
case "repos.pb":
case "go.work":
case "go.work.last":
case "go.work.sum":
default:
// todo: figure out a way to do padding for init()
if trip == false {
log.Info("WARNING:")
}
log.Info("WARNING: you have an untracked file outside of any .git repository:", path)
trip = true
}
return nil
}
gitdir := filepath.Join(path, ".git")
_, err2 := os.Stat(gitdir)
if !os.IsNotExist(err2) {
all = append(all, path)
return filepath.SkipDir
}
return nil
})
//
// probably always leave this here forever
// this check, along with CheckDirty() makes sure you can safely delete ~/go/src or the go.work directory
// because everything is either checked in or deleted. An important thing to know!
if trip {
log.Info("WARNING:")
log.Info("WARNING: there isn't a way to disable this warning yet")
log.Info("WARNING: probably this is a good thing however. you don't want to leave files outside of git repos here")
log.Info("WARNING: so this warning should probably stay")
log.Info("WARNING:")
log.Info("WARNING: this also might mean you put these files here because you are actively working on them")
log.Info("WARNING: and you don't want to forget about them")
log.Info("WARNING:")
}
return all, err
}
func gitDirectoriesOld(srcDir string) ([]string, error) {
var all []string
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Log(WARN, "Error accessing path:", path, err)
return nil
}
// Check if the path is a directory and has a .git subdirectory
if info.IsDir() && IsGitDir(path) {
all = append(all, path)
}
return nil
})
if err != nil {
log.Log(WARN, "Error walking the path:", srcDir, err)
}
return all, err
}
// IsGitDir checks if a .git directory exists inside the given directory
func IsGitDir(dir string) bool {
gitDir := filepath.Join(dir, ".git")
info, err := os.Stat(gitDir)
if os.IsNotExist(err) {
return false
}
return info.IsDir()
}
// rill is awesome. long live rill
// attempt scan with rill
func (f *Forge) rillScanDirs(gopaths []string) (int, error) {
// Convert a slice of user IDs into a channel
ids := rill.FromSlice(gopaths, nil)
// Read users from the API.
// Concurrency = 20
dirs := rill.Map(ids, 20, func(id string) (*gitpb.Repo, error) {
return f.checkpath(id, "")
})
var counter int
// Activate users.
// Concurrency = 10
err := rill.ForEach(dirs, 10, func(repo *gitpb.Repo) error {
counter += 1
return nil
})
return counter, err
}
func (f *Forge) checkpath(gopath string, url string) (*gitpb.Repo, error) {
fullpath := filepath.Join(f.GetGoSrc(), gopath)
log.Info("forge creating protobuf for", fullpath)
repo, err := f.NewGoRepo(gopath, "")
if err != nil {
log.Info("\tprotobuf error", gopath, err)
}
return repo, err
}
// deletes the repo from the protobuf (pray your mutex locks are working)
// re-scans the repo
// returns the new repo
func (f *Forge) ReAdd(repo *gitpb.Repo) (*gitpb.Repo, error) {
if repo == nil {
return nil, log.Errorf("can't delete repo == nil")
}
fullpath := repo.GetFullPath()
ns := repo.GetNamespace()
if !f.Repos.Delete(repo) {
return nil, log.Errorf("delete of repo failed")
}
repo, err := f.AddNamespaceDir(ns, fullpath)
if err != nil {
log.Info("ReAdd() error", fullpath, err)
return nil, err
}
return repo, err
}

View File

@ -18,7 +18,7 @@ func (f *Forge) MakeGoWork() error {
// has gone terribly wrong // has gone terribly wrong
return errors.New("if you want a go.work file in ~/go/src/, touch it first") return errors.New("if you want a go.work file in ~/go/src/, touch it first")
} }
filename := filepath.Join(f.GetGoSrc(), "go.work") filename := filepath.Join(f.Config.ReposDir, "go.work")
workf, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) workf, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil { if err != nil {
return err return err
@ -32,26 +32,10 @@ func (f *Forge) MakeGoWork() error {
all := f.Repos.SortByFullPath() all := f.Repos.SortByFullPath()
for all.Scan() { for all.Scan() {
repo := all.Next() repo := all.Next()
/*
if !repo.IsGoLang() == "" {
// skip repos that aren't go
// todo: handle non-flat repos?
log.Info("skip non-go", repo.GetGoPath)
continue
}
*/
if repo.GetGoPath() == "" { if repo.GetGoPath() == "" {
continue continue
} }
fmt.Fprintln(workf, "\t"+repo.GetGoPath()) fmt.Fprintln(workf, "\t"+repo.GetGoPath())
/*
if repo.pb.Exists("go.mod") {
// log.Info("ADDING REPO", goSrcDir, repo.GetGoPath())
} else {
fmt.Fprintln(workf, "\t"+repo.GetGoPath)
log.Log(REPO, "missing go.mod for", repo.GetGoPath())
}
*/
} }
fmt.Fprintln(workf, ")") fmt.Fprintln(workf, ")")
return nil return nil

83
http.go
View File

@ -5,36 +5,46 @@ package forgepb
import ( import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/url"
"os/user" "os/user"
"strings"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func (f *Forge) HttpPost(url string, data []byte) ([]byte, error) { func (f *Forge) HttpPost(base string, route string, data []byte) ([]byte, error) {
// Fix using url.JoinPath (Best Practice)
baseURL, _ := url.Parse(f.Config.ForgeURL) // "http://forge.grid.wit.com:2520")
finalURL := baseURL.JoinPath(route) // Correctly produces ...:2520/patches
var err error var err error
var req *http.Request var req *http.Request
req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data)) req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data))
// log.Info("httpPost() with len", len(data), "url", url) if req == nil {
return nil, err
}
usr, _ := user.Current() usr, _ := user.Current()
req.Header.Set("author", usr.Username) req.Header.Set("author", usr.Username)
req.Header.Set("hostname", "fixme:hostname") req.Header.Set("hostname", f.hostname)
return rawHttpPost(req)
}
func rawHttpPost(req *http.Request) ([]byte, error) {
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
log.Error(err)
return []byte("client.Do(req) error"), err return []byte("client.Do(req) error"), err
} }
defer resp.Body.Close() defer resp.Body.Close()
// log.Info("httpPost() with len", len(data))
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Error(err)
return body, err return body, err
} }
@ -42,7 +52,7 @@ func (f *Forge) HttpPost(url string, data []byte) ([]byte, error) {
} }
func (f *Forge) LookupPBorig(check *gitpb.Repos) (*gitpb.Repos, error) { func (f *Forge) LookupPBorig(check *gitpb.Repos) (*gitpb.Repos, error) {
url := f.forgeURL + "lookup" url := f.Config.ForgeURL + "lookup"
for repo := range check.IterByFullPath() { for repo := range check.IterByFullPath() {
if repo.Namespace == "" { if repo.Namespace == "" {
@ -53,6 +63,7 @@ func (f *Forge) LookupPBorig(check *gitpb.Repos) (*gitpb.Repos, error) {
return check.SubmitReposPB(url) return check.SubmitReposPB(url)
} }
/*
func (f *Forge) LookupPB(check *gitpb.Repos) (*gitpb.Repos, error) { func (f *Forge) LookupPB(check *gitpb.Repos) (*gitpb.Repos, error) {
url := f.forgeURL + "lookup" url := f.forgeURL + "lookup"
@ -92,3 +103,59 @@ func (f *Forge) UpdatePB(check *gitpb.Repos) (*gitpb.Repos, error) {
return queryPB.SubmitReposPB(url) return queryPB.SubmitReposPB(url)
} }
*/
/*
// HTTPRequestToProto converts an *http.Request to our custom HttpRequest protobuf message.
func (pb *Patches) AddHttpToPB(r *http.Request) error {
if pb == nil {
return log.Errorf("AddHttpToPB() pb was nil")
}
// Convert the header map. http.Header is a map[string][]string.
// We'll just take the first value for each header for simplicity.
headers := make(map[string]string)
for name, values := range r.Header {
if len(values) > 0 {
headers[name] = strings.Join(values, "\n")
}
}
pb.HttpRequest = &httppb.HttpRequest{
Method: r.Method,
URL: r.URL.String(),
Proto: r.Proto,
Headers: headers,
IP: getClientIP(r),
Host: r.Host,
Hostname: r.Header.Get("hostname"),
}
// pb.HttpRequest.Route = cleanURL(r.URL.Path)
return nil
}
*/
func getIpSimple(r *http.Request) string {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
log.Printf("could not split host port: %v", err)
return r.RemoteAddr // Fallback
}
return host
}
// getClientIP inspects the request for common headers to find the true client IP.
func getClientIP(r *http.Request) string {
// Caddy sets the X-Forwarded-For header.
if forwardedFor := r.Header.Get("X-Forwarded-For"); forwardedFor != "" {
// The header can be a comma-separated list of IPs. The first one is the original client.
ips := strings.Split(forwardedFor, ",")
return strings.TrimSpace(ips[0])
}
// Fallback to RemoteAddr if the header is not present.
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return host
}

View File

@ -11,29 +11,8 @@ import (
"go.wit.com/log" "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 // 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 // a console with a fixed with font. AKA: a typerwriter
// 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) { func (f *Forge) PrintHumanTable(allr *gitpb.Repos) {
log.DaemonMode(true) log.DaemonMode(true)
@ -41,8 +20,8 @@ func (f *Forge) PrintHumanTable(allr *gitpb.Repos) {
t := new(tally) t := new(tally)
// print the header // print the header
args := []string{"namespace", "cur br", "age", "master", "devel", "user", "", "lasttag", "next", "repo type"} args := []string{"Namespace", "branch", "age", "user", "devel", "master", "", "lasttag", "next", "repo type"}
sizes := []int{40, 9, 6, 16, 16, 16, 1, 12, 12, 8} sizes := []int{35, 9, 4, 13, 13, 13, 1, 12, 12, 8}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args))) log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
all := allr.SortByFullPath() all := allr.SortByFullPath()
@ -60,8 +39,8 @@ func (f *Forge) PrintForgedTable(allr *gitpb.Repos) {
t := new(tally) t := new(tally)
// print the header // print the header
args := []string{"namespace", "cur br", "age", "master", "devel", "last tag", "", "", "", ""} args := []string{"Namespace", "branch", "age", "master", "devel", "last tag", "", "", "", ""}
sizes := []int{40, 9, 6, 12, 16, 16, 16, 12, 12, 8} sizes := []int{35, 9, 4, 13, 13, 13, 13, 12, 12, 8}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args))) log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
all := allr.SortByFullPath() all := allr.SortByFullPath()
@ -70,7 +49,7 @@ func (f *Forge) PrintForgedTable(allr *gitpb.Repos) {
f.printForgedToTable(repo, sizes) f.printForgedToTable(repo, sizes)
tallyBranchTotals(t, repo) tallyBranchTotals(t, repo)
} }
log.Infof("Total repositories: %d (%d master) (%d devel) (%d user) (%d unknown)\n", t.total, t.master, t.devel, t.user, t.unknown) log.Infof("Total repositories: %d (%d user) (%d devel) (%d master) (%d unknown)\n", t.total, t.user, t.devel, t.master, t.unknown)
} }
func (f *Forge) PrintHumanTableFull(allr *gitpb.Repos) { func (f *Forge) PrintHumanTableFull(allr *gitpb.Repos) {
@ -79,8 +58,8 @@ func (f *Forge) PrintHumanTableFull(allr *gitpb.Repos) {
t := new(tally) t := new(tally)
// print the header // print the header
args := []string{"cur br", "age", "master", "devel", "user", "", "lasttag", "next", "repo type", "namespace"} args := []string{"branch", "age", "user", "devel", "master", "", "lasttag", "next", "repo type", "Namespace"}
sizes := []int{9, 6, 16, 16, 16, 1, 12, 12, 8, 0} sizes := []int{9, 4, 13, 13, 13, 1, 12, 12, 8, 0}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args))) log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
all := allr.SortByFullPath() all := allr.SortByFullPath()
@ -89,7 +68,7 @@ func (f *Forge) PrintHumanTableFull(allr *gitpb.Repos) {
f.printRepoToTable(repo, sizes, true) f.printRepoToTable(repo, sizes, true)
tallyBranchTotals(t, repo) tallyBranchTotals(t, repo)
} }
log.Infof("Total repositories: %d (%d master) (%d devel) (%d user) (%d unknown)\n", t.total, t.master, t.devel, t.user, t.unknown) log.Infof("Total repositories: %d (%d user) (%d devel) (%d master) (%d unknown)\n", t.total, t.user, t.devel, t.master, t.unknown)
} }
// also shows which files are dirty // also shows which files are dirty
@ -99,8 +78,8 @@ func (f *Forge) PrintHumanTableDirty(allr *gitpb.Repos) {
t := new(tally) t := new(tally)
// print the header // print the header
args := []string{"namespace", "cur br", "age", "master", "devel", "user", "", "lasttag", "next", "repo type"} args := []string{"Namespace", "branch", "age", "user", "devel", "master", "", "lasttag", "next", "repo type"}
sizes := []int{40, 9, 6, 16, 16, 16, 1, 12, 12, 8} sizes := []int{35, 9, 4, 13, 13, 13, 1, 12, 12, 8}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args))) log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
for repo := range allr.IterAll() { for repo := range allr.IterAll() {
@ -115,7 +94,7 @@ func (f *Forge) PrintHumanTableDirty(allr *gitpb.Repos) {
tallyBranchTotals(t, repo) tallyBranchTotals(t, repo)
} }
log.Infof("Total repositories: %d (%d master) (%d devel) (%d user) (%d unknown)\n", t.total, t.master, t.devel, t.user, t.unknown) log.Infof("Total repositories: %d (%d user) (%d devel) (%d master) (%d unknown)\n", t.total, t.user, t.devel, t.master, t.unknown)
} }
// used to count which repos are on which branches (master/main, devel, user) // used to count which repos are on which branches (master/main, devel, user)
@ -142,82 +121,20 @@ func tallyBranchTotals(t *tally, repo *gitpb.Repo) {
t.user += 1 t.user += 1
return return
} }
log.Printf("unknown curr=%s master=%s devel=%s user=%s\n", repo.GetCurrentBranchName(), repo.GetMasterBranchName(), repo.GetDevelBranchName(), repo.GetUserBranchName()) log.Printf("unknown curr=%s user=%s devel=%s master=%s\n", repo.GetCurrentBranchName(), repo.GetUserBranchName(), repo.GetDevelBranchName(), repo.GetMasterBranchName())
t.unknown += 1 t.unknown += 1
} }
/*
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 standardTableSize10(sizes []int, args []string) string {
var s string
for i, si := range sizes {
if si == 0 {
s += "%-s "
} else {
s += "%-" + fmt.Sprintf("%d", si) + "s "
if len(args[i]) > sizes[i] {
args[i] = args[i][:sizes[i]]
}
}
}
// there must be a better syntax for this
arg1 := args[0]
arg2 := args[1]
arg3 := args[2]
arg4 := args[3]
arg5 := args[4]
arg6 := args[5]
arg7 := args[6]
arg8 := args[7]
arg9 := args[8]
arg10 := args[9]
return fmt.Sprintf(s, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
// return fmt.Sprintf(s, args)
}
*/
func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) { func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
var end string var end string
// shortened version numbers // shortened version numbers
var mhort string = repo.GetMasterVersion() var mhort string = repo.GetMasterVersion()
var dhort string = repo.GetDevelVersion() var dhort string = repo.GetDevelVersion()
var uhort string = repo.GetUserVersion() var uver string = repo.GetUserVersion()
if uhort == "uerr" { if uver == "uerr" {
// blank these out // blank these out
uhort = "" uver = ""
} }
var lasttag string = repo.GetLastTag() var lasttag string = repo.GetLastTag()
var thort string = repo.GetTargetVersion() var thort string = repo.GetTargetVersion()
@ -252,15 +169,16 @@ func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
} }
if repo.IsDirty() { if repo.IsDirty() {
age = "#" age = ""
uver = "* " + uver
end += "(dirty) " end += "(dirty) "
} }
var args []string var args []string
if full { if full {
args = []string{cname, age, mhort, dhort, uhort, chort, lasttag, thort, rtype, gopath} args = []string{cname, age, uver, dhort, mhort, chort, lasttag, thort, rtype, gopath}
} else { } else {
args = []string{gopath, cname, age, mhort, dhort, uhort, chort, lasttag, thort, rtype} args = []string{gopath, cname, age, uver, dhort, mhort, chort, lasttag, thort, rtype}
} }
start := cobol.StandardTableSize10(sizes, args) start := cobol.StandardTableSize10(sizes, args)
@ -270,23 +188,6 @@ func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
} }
} }
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() { switch repo.GetState() {
case "PERFECT": case "PERFECT":
case "unchanged": case "unchanged":
@ -302,6 +203,23 @@ func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
end += "(" + repo.GetState() + ") " end += "(" + repo.GetState() + ") "
} }
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.GetNamespace())
if debname != filepath.Base(gopath) {
end += "(deb:" + debname + ") "
}
log.Info(cobol.TerminalChomp(start + " " + end)) log.Info(cobol.TerminalChomp(start + " " + end))
} }
@ -328,7 +246,7 @@ func (f *Forge) printForgedToTable(repo *gitpb.Repo, sizes []int) {
log.Info(cobol.TerminalChomp(start + " " + end)) log.Info(cobol.TerminalChomp(start + " " + end))
} }
func (psets *Patchsets) PrintTable() { func (psets *Sets) PrintTable() {
if psets == nil { if psets == nil {
return return
} }
@ -339,23 +257,57 @@ func (psets *Patchsets) PrintTable() {
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2} sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args))) log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
var countCONTENTS int /*
var countPARTS int var countCONTENTS int
for x, pset := range psets.GetPatchsets() { var countPARTS int
log.Info(pset.Uuid, pset.Name) for x, pset := range psets.GetSets() {
cId := log.Sprintf("%d", x) log.Info(pset.Patches.Uuid, pset.Patches.Name, pset.Patches.State)
countCONTENTS += 1 if pset.State == "DONE" {
for i, p := range pset.Patches.GetPatches() { // old patchset
continue
}
cId := log.Sprintf("%d", x)
countCONTENTS += 1
for i, p := range pset.Patches.GetPatches() {
var args []string
partId := log.Sprintf("%d", i)
_, fname := filepath.Split(p.GetFilename())
args = []string{p.CommitHash, p.NewHash, cId, partId, fname, p.GetNamespace(), "", "", "", "", ""}
start := cobol.StandardTableSize10(sizes, args)
log.Info(cobol.TerminalChomp(start))
countPARTS += 1
}
}
log.Infof("Total Contents (%d) Parts (%d)\n", countCONTENTS, countPARTS)
*/
}
func (patches *Patches) PrintTable() {
if patches == nil {
return
}
log.DaemonMode(true)
// print the header
args := []string{"commit hash", "new hash", "", "", "name", "Repo Namespace", "", "", "", "", ""}
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
/*
var countPARTS int
for x, p := range patches.GetPatches() {
var args []string var args []string
partId := log.Sprintf("%d", i) partId := log.Sprintf("%d", x)
_, fname := filepath.Split(p.GetFilename()) _, fname := filepath.Split(p.GetFilename())
args = []string{p.CommitHash, p.NewHash, cId, partId, fname, p.GetNamespace(), "", "", "", "", ""} args = []string{p.CommitHash, p.NewHash, partId, fname, p.GetNamespace(), "", "", "", "", ""}
start := cobol.StandardTableSize10(sizes, args) start := cobol.StandardTableSize10(sizes, args)
log.Info(cobol.TerminalChomp(start)) log.Info(cobol.TerminalChomp(start))
countPARTS += 1 countPARTS += 1
} }
} log.Infof("Total Patches (%d)\n", countPARTS)
log.Infof("Total Contents (%d) Parts (%d)\n", countCONTENTS, countPARTS) */
} }

249
init.go
View File

@ -5,178 +5,159 @@ package forgepb
import ( import (
"os" "os"
"os/user" "os/user"
"time" "path/filepath"
"go.wit.com/lib/config"
"go.wit.com/lib/fhelp" "go.wit.com/lib/fhelp"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
/* better syntax from gin /* better syntax from gin
Default returns an Engine instance with the Logger and Recovery middleware already attached. Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine { func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
engine := New() engine := New()
engine.Use(Logger(), Recovery()) engine.Use(Logger(), Recovery())
return engine.With(opts...) return engine.With(opts...)
} }
*/ */
func Init() *Forge { func Init() *Forge {
f := InitPB() f := new(Forge)
cfg := new(ForgeConfigs)
/* err := config.ConfigLoad(cfg, "forge", "forge")
f.Machine = new(zoopb.Machine) f.Config = cfg
if err := f.Machine.ConfigLoad(); err != nil { if err != nil {
log.Log(WARN, "zoopb.ConfigLoad() failed", err) // fhelp.DumpENV("finit:")
f.setenv()
if !fhelp.QuestionUser("This is your first time using forge, use these default values?") {
os.Exit(-1)
} }
*/ f.Config.InitDefaults()
f.Config.ConfigSave()
f.initFromConfig()
f.Config.DumpENV()
return f
}
if f.Config.Username == "" { if f.Config.Username == "" {
usr, _ := user.Current() usr, _ := user.Current()
f.Config.Username = usr.Username f.Config.Username = usr.Username
f.SetConfigSave(true) f.Config.ConfigSave()
} }
f.initFromConfig()
if f.Config.Xterm == "" { if f.Config.Mode == ForgeMode_MASTER {
f.Config.Xterm = "xterm" log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg") // fhelp.DumpENV("finit:")
f.Config.XtermArgv = append(f.Config.XtermArgv, "black") f.Config.DumpENV()
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
f.SetConfigSave(true)
} }
// f.Machine.InitWit()
if f.hasFullScan {
// duplicate time checking below. which one to keep?
if f.FullScanAge() > time.Minute {
log.Log(INFO, "forgepb.Scan() skipping scan. been run a minute ago", f.FullScanAge())
return f
}
}
now := time.Now()
start := f.Repos.Len()
f.ScanGoSrc()
f.FullScanRan()
end := f.Repos.Len()
if (end - start) == 0 {
log.Log(INFO, "forgepb.Scan() Scan did not find new git repositories. Total =", end)
if f.FullScanAge() > time.Minute {
f.rillUpdate(20, 10)
}
} else {
log.Log(INFO, "forgepb.Scan() Scan found", end-start, "new git repositories. Total =", end)
f.rillUpdate(20, 10)
}
if f.configSave {
// taking this out to debug Marshal() panic
f.ConfigSave()
f.configSave = false
}
log.Log(INFO, "update() check took", shell.FormatDuration(time.Since(now)))
return f return f
} }
func (f *Forge) InitPB() { func InitByAppname(argname string) *Forge {
f.setenv() cfg := new(ForgeConfigs)
err := config.ConfigLoad(cfg, argname, "forge")
if err != nil {
log.Info("forge has not been configured yet", cfg.Filename)
log.Info("go install go.wit.com/apps/forge@latest")
os.Exit(-1)
}
f := new(Forge)
f.Config = cfg
f.initFromConfig()
log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
return f
}
// load the ~/.config/forge/ config func (f *Forge) initFromConfig() {
f.Config = new(ForgeConfigs) if f.configENV() {
if err := f.Config.ConfigLoad(f.configDir); err != nil { log.Info("ENV changed config. todo: save config here")
log.Log(WARN, "forgepb.ConfigLoad() failed", err) f.Config.ConfigSave()
}
if f.Config.ReposPB != os.Getenv("FORGE_REPOSPB") {
// if different, use the ENV var
// this probably means that it gets saved as the default in the config
// we probably want that (?)
f.Config.ReposPB = os.Getenv("FORGE_REPOSPB")
}
if _, s := filepath.Split(f.Config.ReposPB); s != "repos.pb" {
fhelp.DumpENV("forge:")
f.Config.DumpENV()
log.Infof("ReposPB invalid filename '%s'\n", f.Config.ReposPB)
os.Exit(-1)
} }
f.Repos = gitpb.NewRepos() f.Repos = gitpb.NewRepos()
f.Repos.ConfigLoad() f.Repos.ConfigLoad(f.Config.ReposPB)
if f.Repos.HasFullScan {
f.hasFullScan = true // init the Patchsets
} f.Patchsets = NewSets()
// todo: play with these / determine good values based on user's machine // todo: play with these / determine good values based on user's machine
f.rillX = 10 if f.Config.RillX == 0 {
f.rillY = 20 f.Config.RillX = 10
} }
if f.Config.RillY == 0 {
func (f *Forge) InitMachine() { f.Config.RillY = 20
if f.Config.Username == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
} }
f.hostname, _ = os.Hostname()
// log.Info(hostname, err)
}
// only init's the protobuf. intended to not scan or change anything
func InitPB() *Forge {
f := new(Forge)
f.setenv()
f.InitPB()
return f
} }
func (f *Forge) SetConfigSave(b bool) { func (f *Forge) SetConfigSave(b bool) {
f.configSave = b config.SetChanged("forge", b)
} }
// saves the config if there have been changes // saves the config if there have been changes
func (f *Forge) Exit() { func (f *Forge) Exit() {
// log.Info("forge.configSave =", f.configSave) // log.Info("forge.configSave =", f.configSave)
if f.configSave { if f.Config.Mode == ForgeMode_MASTER {
f.ConfigSave() // fhelp.DumpENV("forge:")
// f.Config.DumpENV()
// todo: tell the user to switch to NORMAL mode
}
f.ConfigSave()
if f.Repos != nil {
if config.HasChanged("repos") {
if err := f.Repos.ConfigSave(f.Config.ReposPB); err != nil {
log.Info("forge.Repos.ConfigSave() error", err)
}
}
} }
// log.Info("forge.Exit() ok") // log.Info("forge.Exit() ok")
os.Exit(0) os.Exit(0)
} }
func RawInitPB() *Forge {
f := new(Forge)
f.RawInitPB()
return f
}
func (f *Forge) RawInitPB() {
f.setenv()
// load the ~/.config/forge/ config
f.Config = new(ForgeConfigs)
if err := f.Config.ConfigLoad(f.configDir); err != nil {
log.Log(WARN, "forgepb.ConfigLoad() failed", err)
}
f.Repos = gitpb.NewRepos()
f.Repos.ConfigLoad()
// todo: play with these / determine good values based on user's machine
f.rillX = 10
f.rillY = 20
}
// the first thing done is process any ENV settings // the first thing done is process any ENV settings
// try to NOT use the ENV settings anywhere but here // try to NOT use the ENV settings anywhere but here
// all initial ENV settings should be stored in the forge struct // all initial ENV settings should be stored in the forge struct
func (f *Forge) setenv() { func (f *Forge) setenv() {
f.once.Do(func() { f.once.Do(func() {
log.Info("doing setenv()")
if err := fhelp.ConfigureENV(); err != nil { if err := fhelp.ConfigureENV(); err != nil {
log.Info("forge ConfigureENV() failed", err) log.Info("forge ConfigureENV() failed", err)
os.Exit(-1) os.Exit(-1)
} }
f.configDir = os.Getenv("FORGE_CONFIG") if f.Config == nil {
f.goSrc = os.Getenv("FORGE_GOSRC") log.Info("forge.Config() was nil")
f.repoPB = os.Getenv("FORGE_REPOPB") os.Exit(-1)
f.forgeURL = os.Getenv("FORGE_URL") }
f.patchDir = os.Getenv("FORGE_PATCHDIR") // f.forgeURL = os.Getenv("FORGE_URL")
f.hostname = os.Getenv("HOSTNAME")
if os.Getenv("FORGE_GOWORK") == "true" { if os.Getenv("FORGE_GOWORK") == "true" {
f.goWork = true f.goWork = true
} }
f.Config.ReposPB = os.Getenv("FORGE_REPOSPB")
f.Config.ReposDir = os.Getenv("FORGE_REPOSDIR")
f.Config.PatchDir = os.Getenv("FORGE_PATCHDIR")
f.Config.ForgeURL = os.Getenv("FORGE_URL")
fhelp.DumpENV("setenv end()")
}) })
} }
func (f *Forge) GetForgeURL() string { func (f *Forge) GetForgeURL() string {
return f.forgeURL return f.Config.ForgeURL
} }
// set the URL for forge otherwise fallback to ENV or to forge.wit.com // set the URL for forge otherwise fallback to ENV or to forge.wit.com
@ -187,7 +168,47 @@ func (f *Forge) SetForgeURL(url string) {
if url == "" { if url == "" {
url = "https://forge.wit.com/" url = "https://forge.wit.com/"
} }
f.forgeURL = url f.Config.ForgeURL = url
os.Setenv("FORGE_URL", f.forgeURL) os.Setenv("FORGE_URL", f.Config.ForgeURL)
log.Info("Forge URL has been set to", f.forgeURL) log.Info("Forge URL has been set to", f.Config.ForgeURL)
}
// the first thing done is process any ENV settings
// try to NOT use the ENV settings anywhere but here
// all initial ENV settings should be stored in the forge struct
func (f *Forge) configENV() bool {
var changed bool
f.once.Do(func() {
if err := fhelp.ConfigureENV(); err != nil {
log.Info("forge ConfigureENV() failed", err)
os.Exit(-1)
}
if os.Getenv("FORGE_REPOPB") != "" && f.Config.ReposPB != os.Getenv("FORGE_REPOPB") {
log.Info("ENV: updating FORGE_REPOSPB from", f.Config.ReposPB, "to", os.Getenv("FORGE_REPOPB"))
f.Config.ReposPB = os.Getenv("FORGE_REPOPB")
changed = true
}
if os.Getenv("FORGE_GOSRC") != "" && f.Config.ReposDir != os.Getenv("FORGE_GOSRC") {
log.Info("ENV: updating FORGE_GOSRC from", f.Config.ReposDir, "to", os.Getenv("FORGE_GOSRC"))
f.Config.ReposDir = os.Getenv("FORGE_GOSRC")
changed = true
}
if os.Getenv("FORGE_PATCHDIR") != "" && f.Config.PatchDir != os.Getenv("FORGE_PATCHDIR") {
log.Info("ENV: updating FORGE_PATCHDIR from", f.Config.PatchDir, "to", os.Getenv("FORGE_PATCHDIRC"))
f.Config.PatchDir = os.Getenv("FORGE_PATCHDIR")
changed = true
}
f.Config.ForgeURL = os.Getenv("FORGE_URL")
f.hostname = os.Getenv("HOSTNAME")
if os.Getenv("FORGE_GOWORK") == "true" {
f.goWork = true
}
})
if changed {
// save config here
}
return changed
} }

69
patch.proto Normal file
View File

@ -0,0 +1,69 @@
// 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 namespace = 1; // the base repo git namespace
bytes data = 2; // the raw data of the whole patch
string gH = 3; // Commit Hash (%H)
string gT = 4; // Tree Hash (%T)
string gP = 5; // Parent Hashes (%P)
string gs = 6; // Subject (%s)
string gaI = 7; // Author Date, ISO 8601 format (%aI)
string gan = 8; // Author Name (%an)
string gae = 9; // Author Email (%ae)
string gcI = 10; // Committer Date, ISO 8601 format (%cI)
string gcn = 11; // Committer Name (%cn)
string gce = 12; // Committer Email (%ce)
string gN = 13; // Commit Notes (%N)
string gGG = 14; // GPG Signature, raw (%GG)
string gGS = 15; // GPG Signer Name (%GS)
string gGK = 16; // GPG Key ID (%GK)
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 `autogenpb:sort` `autogenpb:unique`
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?
string patchId = 27; // patchId `autogenpb:unique`
string treeHash = 28; // final tree Hash
}
// this is a "PATCH: [1/x]" series
message Patches { // `autogenpb:marshal` `autogenpb:gui:Patch` `autogenpb:http`
string uuid = 1; // `autogenpb:uuid:2679065e-c81d-4a00-aca4-03c158a834fb`
string version = 2; // `autogenpb:version:v2.0.0`
repeated Patch patches = 3;
string Error = 4; // when passing these around, if there is an error, store it here
}

View File

@ -1,91 +0,0 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package forgepb
import (
"go.wit.com/log"
)
// retrieves current patches from forge
func (f *Forge) GetPatches() error {
/*
defer func() {
if r := recover(); r != nil {
log.Warn("PANIC ecovered in loadUpstream()", r)
}
}()
*/
url := f.forgeURL + "GetPatchsets"
log.Info("GetPatchsets() url", url)
body, err := f.HttpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return 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 err
}
psets.PrintTable()
f.loadUpstreamPatchsets(psets)
return nil
}
func (f *Forge) loadUpstreamPatchsets(psets *Patchsets) {
/*
defer func() {
if r := recover(); r != nil {
log.Warn("PANIC ecovered in loadUpstream()", r)
}
}()
*/
var foundnew bool
all := psets.All()
for all.Scan() {
pset := all.Next()
found := f.Patchsets.FindByUuid(pset.Uuid)
author := log.Sprintf("Author: %s <%s>", found.GitAuthorName, found.GitAuthorEmail)
if found == nil {
log.Info("new patchset", pset.Name, pset.Uuid)
pset.State = "new"
foundnew = true
if pset == nil {
log.Warn("OH NO! pset == nil")
continue
}
if f.Patchsets == nil {
log.Warn("OH NO! f.Patchsets == nil")
continue
}
log.Warn("appending pset", pset.Uuid, pset.State, pset.Name)
f.Patchsets.Append(pset)
continue
}
log.Info("EXAMINE PSET", pset.Name, pset.Uuid, pset.Patches.Len())
for _, patch := range pset.Patches.Patches {
// log.Info("\tnew patch:", i, patch.CommitHash, patch.Namespace)
if f.findPatch(patch) {
// log.Info("\talready found!!!!!!!", pset.Uuid, patch.Namespace)
continue
}
if err := f.addRandomPatch(patch); err == nil {
log.Info("\tnew patch added:", patch.CommitHash, found.Name, found.Comment, author)
foundnew = true
} else {
log.Info("\tnew patch failed:", patch.CommitHash, found.Name, found.Comment, author, err)
}
}
pset.State = found.State
if pset.State == "" {
pset.State = "new"
}
}
if foundnew {
f.SavePatchsets()
}
}

View File

@ -1,32 +1,52 @@
package forgepb package forgepb
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"go.wit.com/lib/hostname"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/lib/protobuf/httppb"
"go.wit.com/log" "go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb" timestamppb "google.golang.org/protobuf/types/known/timestamppb"
) )
func newPatchset(name string) *Patchset { func (p *Patches) HttpPostVerbose(baseURL string, route string) (*Patches, *httppb.HttpRequest, error) {
pset := new(Patchset) p.PrintTable()
return p.HttpPost(baseURL, route)
}
func (p *Set) HttpPostVerbose(baseURL string, route string) (*Set, *httppb.HttpRequest, error) {
p.PrintTable()
return p.HttpPost(baseURL, route)
}
func (p *Sets) HttpPostVerbose(baseURL string, route string) (*Sets, *httppb.HttpRequest, error) {
p.PrintTable()
return p.HttpPost(baseURL, route)
}
func newPatchset(name string) *Set {
pset := new(Set)
pset.Name = name pset.Name = name
pset.Ctime = timestamppb.New(time.Now()) pset.Ctime = timestamppb.New(time.Now())
pset.Uuid = uuid.New().String() pset.Uuid = uuid.New().String()
pset.Hostname, _ = os.Hostname() pset.Hostname, _ = hostname.Get()
pset.Patches = NewPatches()
return pset return pset
} }
// creates a patchset // creates a patchset
// works from the user branches against the devel branches // works from the user branches against the devel branches
func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) { func (f *Forge) MakeDevelPatchSet(name string) (*Set, error) {
pset := newPatchset(name) pset := newPatchset(name)
if os.Getenv("GIT_AUTHOR_NAME") == "" { if os.Getenv("GIT_AUTHOR_NAME") == "" {
return nil, fmt.Errorf("GIT_AUTHOR_NAME not set") return nil, fmt.Errorf("GIT_AUTHOR_NAME not set")
@ -43,7 +63,7 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer os.RemoveAll(dir) // clean up // defer os.RemoveAll(dir) // clean up
pset.TmpDir = dir pset.TmpDir = dir
all := f.Repos.SortByFullPath() all := f.Repos.SortByFullPath()
@ -59,6 +79,10 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
continue continue
} }
if repo.ActualGetDevelHash() == repo.ActualGetUserHash() {
continue
}
// make a patchset from user to devel // make a patchset from user to devel
// TODO: verify branches are otherwise exact // TODO: verify branches are otherwise exact
pset.StartBranchName = repo.GetDevelBranchName() pset.StartBranchName = repo.GetDevelBranchName()
@ -72,41 +96,7 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
return pset, nil return pset, nil
} }
/* func (pset *Set) makePatchSetNew(repo *gitpb.Repo) error {
func (f *Forge) MakeMasterPatchSet() (*Patchset, error) {
pset := newPatchset("masterBranchPS")
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 startBranch := pset.StartBranchName
endBranch := pset.EndBranchName endBranch := pset.EndBranchName
repoDir := filepath.Join(pset.TmpDir, repo.GetGoPath()) repoDir := filepath.Join(pset.TmpDir, repo.GetGoPath())
@ -143,22 +133,23 @@ func (pset *Patchset) makePatchSetNew(repo *gitpb.Repo) error {
return errors.New(fmt.Sprintf("git returned %d", r.Exit)) return errors.New(fmt.Sprintf("git returned %d", r.Exit))
} }
if len(r.Stdout) == 0 { if len(r.Stdout) == 0 {
log.Infof("No patches in %s (%s,%s)\n", repo.FullPath, repo.ActualGetDevelHash(), repo.ActualGetUserHash())
// git created no files to add // git created no files to add
return nil return nil
} }
err = pset.addPatchFiles(repo) err = pset.addPatchFiles(repo, repoDir)
pset.Ctime = timestamppb.New(time.Now()) log.Infof("Added %d patches for %s len=%d\n", len(r.Stdout), repo.FullPath, pset.Patches.Len())
// pset.PrintTable()
return err return err
} }
// git show <original_commit_hash> | git patch-id
// git cat-file -p <commit_hash> | grep tree
// process each file in pDir/ // process each file in pDir/
func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error { func (p *Set) addPatchFiles(repo *gitpb.Repo, fullDir string) error {
psetDir := repo.GetGoPath()
tmpDir := p.TmpDir
// log.Info("ADD PATCH FILES ADDED DIR", tmpDir)
fullDir := filepath.Join(tmpDir, psetDir)
var baderr error var baderr error
// log.Info("ADD PATCH FILES ADDED DIR", fullDir)
filepath.Walk(fullDir, func(path string, info os.FileInfo, err error) error { filepath.Walk(fullDir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
// Handle possible errors, like permission issues // Handle possible errors, like permission issues
@ -170,10 +161,6 @@ func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
if info.IsDir() { if info.IsDir() {
return nil 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) data, err := os.ReadFile(path)
if err != nil { if err != nil {
log.Info("addPatchFile() failed", path) log.Info("addPatchFile() failed", path)
@ -183,15 +170,22 @@ func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
patch := new(Patch) patch := new(Patch)
patch.Filename, _ = filepath.Rel(p.TmpDir, path) patch.Filename, _ = filepath.Rel(p.TmpDir, path)
patch.Data = data patch.Data = data
patch.parseData() if err := patch.parseData(); err != nil {
patch.StartHash = repo.DevelHash() log.Info("parseData() failed", err)
return err
}
if err := findPatchId(repo, patch); err != nil {
log.Info("findPatchId() failed", err)
return err
}
patch.StartHash = repo.ActualDevelHash()
patch.NewHash = "na" patch.NewHash = "na"
patch.Namespace = repo.GetGoPath() patch.Namespace = repo.GetGoPath()
if p.Patches == nil { if p.Patches == nil {
log.Info("SHOULD NOT HAVE HAPPENED. p.Patches == nil")
p.Patches = new(Patches) p.Patches = new(Patches)
} }
p.Patches.Append(patch) p.Patches.Append(patch)
p.Patches.Uuid = uuid.New().String()
// log.Info("ADDED PATCH FILE", path) // log.Info("ADDED PATCH FILE", path)
return nil return nil
}) })
@ -201,7 +195,7 @@ func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
// looks at the git format-patch output // looks at the git format-patch output
// saves the commit Hash // saves the commit Hash
// saves the diff lines // saves the diff lines
func (p *Patch) parseData() string { func (p *Patch) parseData() error {
lines := strings.Split(string(p.Data), "\n") lines := strings.Split(string(p.Data), "\n")
for _, line := range lines { for _, line := range lines {
fields := strings.Fields(line) fields := strings.Fields(line)
@ -217,7 +211,7 @@ func (p *Patch) parseData() string {
p.Files = append(p.Files, line) p.Files = append(p.Files, line)
} }
} }
return "" return nil
} }
// just an example of how to walk only directories // just an example of how to walk only directories
@ -242,3 +236,59 @@ func onlyWalkDirs(pDir string) error {
}) })
return baderr return baderr
} }
// func runPipe() error {
func findPatchId(repo *gitpb.Repo, p *Patch) error {
if p.CommitHash == "" {
return log.Errorf("%s commit hash not found", p.Filename)
}
// 1. Create the command to get the diff for the commit.
// "git show" is the perfect tool for this.
cmdShow := exec.Command("git", "show", p.CommitHash)
cmdShow.Dir = repo.GetFullPath()
// 2. Create the command to calculate the patch-id from stdin.
cmdPipeID := exec.Command("git", "patch-id", "--stable")
cmdPipeID.Dir = repo.GetFullPath()
// 3. Connect the output of "git show" to the input of "git patch-id".
// This is the Go equivalent of the shell pipe `|`.
pipe, err := cmdShow.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to create pipe: %w", err)
}
cmdPipeID.Stdin = pipe
// 4. We need a buffer to capture the final output from git patch-id.
var output bytes.Buffer
cmdPipeID.Stdout = &output
// 5. Start the reading command (patch-id) first.
if err := cmdPipeID.Start(); err != nil {
return fmt.Errorf("failed to start git-patch-id: %w", err)
}
// 6. Run the writing command (show). This will block until it's done.
if err := cmdShow.Run(); err != nil {
return fmt.Errorf("failed to run git-show: %w", err)
}
// 7. Wait for the reading command to finish.
if err := cmdPipeID.Wait(); err != nil {
return fmt.Errorf("failed to wait for git-patch-id: %w", err)
}
fields := strings.Fields(output.String())
if len(fields) != 2 {
return fmt.Errorf("git-patch-id produced empty output")
}
if fields[1] != p.CommitHash {
return fmt.Errorf("patchid did not match %s != %v", p.CommitHash, fields)
}
// log.Infof("hash=%s patchid(%v) %s\n", p.CommitHash, fields, p.Filename)
p.PatchId = fields[0]
return nil
}

View File

@ -1,83 +0,0 @@
package forgepb
// functions to import and export the protobuf
// data to and from config files
import (
"errors"
"strings"
"time"
"go.wit.com/log"
)
// makes a new patches protobuf. These are all the patches on your machine.
func NewPatches() *Patches {
x := new(Patches)
x.Uuid = "2679065e-c81d-4a00-aca4-03c158a834fb"
x.Version = "v2.0.0 go.wit.com/lib/protobuf/forgepb"
return x
}
func (f *Forge) SendPatchSet(pset *Patchset) error {
var err error
data, err := pset.Marshal()
if err != nil {
log.Info("proto.Marshal() pset(len) error", len(data), err)
return err
}
now := time.Now()
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
cfgfile := "patchset/patchset." + timestamp + ".pb"
log.Info("proto.Marshal() pset(len)", len(data))
configWrite(cfgfile, data)
return errors.New("don't know how to send yet")
}
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) submitPatchset(pset *Patchset) error {
var url string
url = f.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
}
newpb := NewPatches()
if err := newpb.Unmarshal(body); err != nil {
cfcheck := string(body[0:100])
if strings.Contains(cfcheck, "<title>Just a moment...</title>") {
return log.Errorf("Cloudflare throttled this attempt to submit. TODO: fix this")
} else {
log.Infof("forged DID NOT SEND BACK PROTOBUF len(body)=%d %s (TODO: look for failure on cloudflare 'is human' check here)\n", len(body), body[0:100])
// log.Infof("TODO: try to identify data here len(body)=%d body[0:40]=%s\n", len(body), body[0:40])
// log.Info("BODY START:", body[0:10], string(body[0:10]))
// log.Info(string(body))
// if err := newpb.UnmarshalTEXT(body[2:]); err == nil {
// log.Info("wow, that did work. newpb.Len() =", newpb.Len())
// }
}
return err
}
log.Info("Total patches sent ok:", newpb.Len())
return nil
}

View File

@ -14,9 +14,9 @@ import (
) )
func (f *Forge) LoadPatchsets() error { func (f *Forge) LoadPatchsets() error {
f.Patchsets = NewPatchsets() f.Patchsets = NewSets()
filename := filepath.Join(f.patchDir, "all-patches.pb") filename := filepath.Join(f.Config.PatchDir, "all-patches.pb")
data, err := os.ReadFile(filename) data, err := os.ReadFile(filename)
if err != nil { if err != nil {
@ -28,12 +28,24 @@ func (f *Forge) LoadPatchsets() error {
log.Infof("LoadPatchsets() proto.Marshal() error %v\n", err) log.Infof("LoadPatchsets() proto.Marshal() error %v\n", err)
return err return err
} }
log.Infof("LoadPatchsets() found %d patches.\n", f.Patchsets.Len()) // log.Infof("LoadPatchsets() found %d patches.\n", f.Patchsets.Len())
return nil return nil
} }
func (f *Forge) InitPatchsets() error {
if err := f.LoadPatchsets(); err == nil {
return nil
} else {
log.Info("LoadPatchsets() failed", err)
}
// TODO: check if Unmarshal failed here
f.Patchsets = NewSets()
f.findAutoPatchset() // adds the default values
return f.SavePatchsets()
}
func (f *Forge) SavePatchsets() error { func (f *Forge) SavePatchsets() error {
filename := filepath.Join(f.patchDir, "all-patches.pb") filename := filepath.Join(f.Config.PatchDir, "all-patches.pb")
regfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) regfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil { if err != nil {
log.Info("SavePatchsets() filename open error:", filename, err) log.Info("SavePatchsets() filename open error:", filename, err)
@ -42,7 +54,7 @@ func (f *Forge) SavePatchsets() error {
} }
defer regfile.Close() defer regfile.Close()
newpb := proto.Clone(f.Patchsets).(*Patchsets) newpb := proto.Clone(f.Patchsets).(*Sets)
if newpb == nil { if newpb == nil {
for pset := range f.Patchsets.IterAll() { for pset := range f.Patchsets.IterAll() {
pset.ShowPatchsets() pset.ShowPatchsets()
@ -67,7 +79,7 @@ func cleanSubject(line string) string {
return strings.TrimSpace(cleaned) return strings.TrimSpace(cleaned)
} }
func (pb *Patchset) ShowPatchsets() error { func (pb *Set) ShowPatchsets() error {
author := "Author: " + pb.GitAuthorName author := "Author: " + pb.GitAuthorName
author += " <" + pb.GitAuthorEmail + ">" author += " <" + pb.GitAuthorEmail + ">"
log.Printf("%-16s %s %s %s\n", string(pb.Uuid)[0:8], pb.Name, pb.Comment, author) log.Printf("%-16s %s %s %s\n", string(pb.Uuid)[0:8], pb.Name, pb.Comment, author)
@ -75,17 +87,29 @@ func (pb *Patchset) ShowPatchsets() error {
comment := cleanSubject(patch.Comment) comment := cleanSubject(patch.Comment)
log.Printf("\t%-8s %-50s %-50s\n", string(patch.CommitHash)[0:8], patch.Namespace, comment) log.Printf("\t%-8s %-50s %-50s\n", string(patch.CommitHash)[0:8], patch.Namespace, comment)
} }
/* // for patch := range pb.IterAll() {
for patch := range pb.IterAll() { // comment := cleanSubject(patch.Comment)
comment := cleanSubject(patch.Comment) // log.Info("\tnew patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.Namespace, comment)
log.Info("\tnew patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.Namespace, comment) // }
}
*/
return nil return nil
} }
// adds a patch. returns true if patch is new
func (f *Forge) AddPatch(patch *Patch) bool {
if f.findPatch(patch) {
// log.Info("\talready found!!!!!!!", pset.Uuid, patch.Namespace)
return false
}
if f.AddNewPatch(patch) {
log.Info("\tnew patch added:", patch.CommitHash, patch.Gs, patch.Gae, patch.Gan)
return true
}
log.Info("\tnew patch failed:", patch.CommitHash, patch.Gs)
return false
}
// adds a patchset or just the patches // adds a patchset or just the patches
func (f *Forge) AddPatchset(pb *Patchset) bool { func (f *Forge) AddPatchset(pb *Set) bool {
var changed bool var changed bool
// if the name of the patchset is "forge auto commit" // if the name of the patchset is "forge auto commit"
// then just add all the patches // then just add all the patches
@ -99,11 +123,11 @@ func (f *Forge) AddPatchset(pb *Patchset) bool {
if f.findPatch(patch) { if f.findPatch(patch) {
// log.Info("\talready found!!!!!!!", pset.Uuid, patch.Namespace) // log.Info("\talready found!!!!!!!", pset.Uuid, patch.Namespace)
} else { } else {
if err := f.addRandomPatch(patch); err == nil { if f.AddNewPatch(patch) {
log.Info("\tnew patch added:", patch.CommitHash, pb.Name, pb.Comment, author) log.Info("\tnew patch added:", patch.CommitHash, pb.Name, pb.Comment, author)
changed = true changed = true
} else { } else {
log.Info("\tnew patch failed:", patch.CommitHash, pb.Name, pb.Comment, author, err) log.Info("\tnew patch failed:", patch.CommitHash, pb.Name, pb.Comment, author)
} }
} }
@ -122,52 +146,49 @@ func (f *Forge) AddPatchset(pb *Patchset) bool {
} }
} }
// Clone() this protobuf into me.forge.Patchsets f.Patchsets.Append(pb)
var newpb *Patchset
newpb = proto.Clone(pb).(*Patchset)
if newpb != nil {
f.Patchsets.Patchsets = append(f.Patchsets.Patchsets, newpb)
}
return true return true
} }
func (f *Forge) findAutoPatchset() *Patchset { func (f *Forge) findAutoPatchset() *Set {
for pset := range f.Patchsets.IterAll() { for pset := range f.Patchsets.IterAll() {
if pset.Name == "forge auto commit" { if pset.Name == "forge auto commit" {
return pset return pset
} }
} }
var fauto *Patchset var fauto *Set
log.Warn("findAutoPatchset() had to create 'forge auto commit'") log.Warn("findAutoPatchset() had to create 'forge auto commit'")
if fauto == nil { if fauto == nil {
fauto = new(Patchset) fauto = makeDefaultPatchset()
fauto.Name = "forge auto commit" f.Patchsets.Append(fauto)
fauto.Patches = NewPatches()
fauto.Uuid = uuid.New().String()
f.Patchsets.Patchsets = append(f.Patchsets.Patchsets, fauto)
} }
return fauto return fauto
} }
// adds submitted patches not specifically assigned to a patchset func makeDefaultPatchset() *Set {
// to the generic patchset called "forge auto commit" fauto := new(Set)
func (f *Forge) addRandomPatch(patch *Patch) error { fauto.Name = "forge auto commit"
fauto.Patches = NewPatches()
fauto.Uuid = uuid.New().String()
return fauto
}
// adds submitted patches not
// If the patch was actually new, return true
func (f *Forge) AddNewPatch(patch *Patch) bool {
// ignore patch if it's already here // ignore patch if it's already here
if f.findPatch(patch) { if f.findPatch(patch) {
log.Info("already found patch", patch.CommitHash, patch.Namespace) log.Info("already found patch", patch.CommitHash, patch.Namespace)
return nil return false
} }
fauto := f.findAutoPatchset() fauto := f.findAutoPatchset()
if fauto == nil { if fauto == nil {
return log.Errorf("no default place yet") // should have made the default patchset
return false
} }
newpb := proto.Clone(patch).(*Patch) fauto.Patches.Append(patch)
if newpb == nil { return true
return log.Errorf("proto.Clone returned nil")
}
fauto.Patches.Patches = append(fauto.Patches.Patches, newpb)
return nil
} }
// returns true if the patch already exists in the protobuf // returns true if the patch already exists in the protobuf
@ -189,5 +210,10 @@ func (f *Forge) findPatch(newpatch *Patch) bool {
func (f *Forge) IsPatchApplied(newpatch *Patch) (*gitpb.Repo, bool) { func (f *Forge) IsPatchApplied(newpatch *Patch) (*gitpb.Repo, bool) {
log.Info("todo: find namespace and repo for patch", newpatch.Filename) log.Info("todo: find namespace and repo for patch", newpatch.Filename)
if f.findPatch(newpatch) {
log.Info("\tfindPatch() patch was found")
} else {
log.Info("\tfindPatch() patch was not found")
}
return nil, false return nil, false
} }

11
patchset.new.go Normal file
View File

@ -0,0 +1,11 @@
package forgepb
/*
// makes a new patches protobuf. These are all the patches on your machine.
func NewPatches() *Patchs {
x := new(Patchs)
x.Uuid = "2679065e-c81d-4a00-aca4-03c158a834fb"
x.Version = "v2.0.0 go.wit.com/lib/protobuf/forgepb"
return x
}
*/

View File

@ -1,89 +0,0 @@
// 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 namespace = 1; // the base repo git namespace
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 `autogenpb:sort` `autogenpb:unique`
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?
}
// this is a "PATCH: [1/x]" series
message Patches { // `autogenpb:marshal` `autogenpb:gui:Patch`
string uuid = 1; // `autogenpb:uuid:2679065e-c81d-4a00-aca4-03c158a834fb`
string version = 2; // `autogenpb:version:v2.0.0`
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`
string hostname = 14; //
}
message Patchsets { // `autogenpb:marshal` `autogenpb:gui` `autogenpb:nomutex`
string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079`
string version = 2; // `autogenpb:version:v0.0.45`
repeated Patchset Patchsets = 3;
}

View File

@ -9,12 +9,13 @@ import (
"regexp" "regexp"
"strings" "strings"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func (f *Forge) NewGoRepo(gopath string, url string) (*gitpb.Repo, error) { func (f *Forge) NewGoRepo(gopath string, url string) (*gitpb.Repo, error) {
fullpath := filepath.Join(f.GetGoSrc(), gopath) fullpath := filepath.Join(f.Config.ReposDir, gopath)
test := f.Repos.FindByFullPath(fullpath) test := f.Repos.FindByFullPath(fullpath)
if test != nil { if test != nil {
return test, nil return test, nil
@ -35,7 +36,7 @@ func (f *Forge) NewGoRepo(gopath string, url string) (*gitpb.Repo, error) {
if f.Config.IsReadOnly(repo.GetGoPath()) { if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true repo.ReadOnly = true
} }
repo.Reload() repo.ReloadCheck()
return repo, nil return repo, nil
} }
@ -52,12 +53,13 @@ func (f *Forge) AddNamespaceDir(ns string, fullpath string) (*gitpb.Repo, error)
} }
f.VerifyBranchNames(repo) f.VerifyBranchNames(repo)
repo.Reload() repo.ReloadCheck()
// set the readonly flag based on the users' forge config // set the readonly flag based on the users' forge config
if f.Config.IsReadOnly(repo.GetGoPath()) { if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true repo.ReadOnly = true
} }
config.SetChanged("repos", true)
return repo, nil return repo, nil
} }
@ -162,6 +164,9 @@ func (f *Forge) VerifyBranchNames(repo *gitpb.Repo) {
if repo.GetUserBranchName() == "" { if repo.GetUserBranchName() == "" {
uname := f.configUserBranchName(repo) uname := f.configUserBranchName(repo)
if uname == "" {
log.Info("configUserBranchName() ERROR: failed with blank")
}
if repo.IsBranch(uname) { if repo.IsBranch(uname) {
repo.SetUserBranchName(uname) repo.SetUserBranchName(uname)
} else { } else {
@ -169,6 +174,9 @@ func (f *Forge) VerifyBranchNames(repo *gitpb.Repo) {
repo.SetUserBranchName(uname) repo.SetUserBranchName(uname)
} }
} }
if repo.GetUserBranchName() == "" || repo.GetDevelBranchName() == "" {
log.Infof("VerifyBranchNames() failed m=%s d=%s u=%s\n", repo.GetMasterBranchName(), repo.GetDevelBranchName(), repo.GetUserBranchName())
}
} }
// what name should be used for the user branch? // what name should be used for the user branch?
@ -184,6 +192,9 @@ func (f *Forge) configUserBranchName(repo *gitpb.Repo) string {
if uname != "" { if uname != "" {
return uname return uname
} }
if f.Config.Username == "" {
// something is wrong!
}
// use the os.Username // use the os.Username
uname = f.Config.Username uname = f.Config.Username
@ -214,7 +225,7 @@ func (f *Forge) AddFullPath(fulldir string) *gitpb.Repo {
} }
func (f *Forge) FindByGoPath(gopath string) *gitpb.Repo { func (f *Forge) FindByGoPath(gopath string) *gitpb.Repo {
fullpath := filepath.Join(f.GetGoSrc(), gopath) fullpath := filepath.Join(f.Config.ReposDir, gopath)
return f.Repos.FindByFullPath(fullpath) return f.Repos.FindByFullPath(fullpath)
} }
@ -241,6 +252,6 @@ func (f *Forge) FindAnyPath(dir string) *gitpb.Repo {
} }
func (f *Forge) DeleteByGoPath(gopath string) bool { func (f *Forge) DeleteByGoPath(gopath string) bool {
fullpath := filepath.Join(f.GetGoSrc(), gopath) fullpath := filepath.Join(f.Config.ReposDir, gopath)
return f.Repos.DeleteByFullPath(fullpath) return f.Repos.DeleteByFullPath(fullpath)
} }

37
rill.go
View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/destel/rill" "github.com/destel/rill"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
@ -33,7 +34,6 @@ func (f *Forge) rillUpdate(pool1 int, pool2 int) (int, error) {
// Concurrency = 10 // Concurrency = 10
err := rill.ForEach(rills, pool2, func(repo *gitpb.Repo) error { err := rill.ForEach(rills, pool2, func(repo *gitpb.Repo) error {
counter += 1 counter += 1
// log.Info("rill.ForEach() gopath=", repo.GetGoPath())
return f.updateRepo(repo) return f.updateRepo(repo)
}) })
@ -42,16 +42,14 @@ func (f *Forge) rillUpdate(pool1 int, pool2 int) (int, error) {
func (f *Forge) updateRepo(repo *gitpb.Repo) error { func (f *Forge) updateRepo(repo *gitpb.Repo) error {
if !repo.IsValidDir() { if !repo.IsValidDir() {
log.Printf("%10s %-50s gopath=%s\n", "git dir is missing\n", repo.FullPath, repo.GetGoPath()) log.Printf("%10s %-50s gopath=%s\n", "git dir is missing\n", repo.FullPath, repo.GetNamespace())
f.Repos.DeleteByFullPath(repo.FullPath) f.Repos.DeleteByFullPath(repo.FullPath)
f.configSave = true
return nil return nil
} }
if repo.DidRepoChange() { if repo.HasChanged() {
f.configSave = true
// log.Info("repo changed ", repo.FullPath, repo.StateChange) // log.Info("repo changed ", repo.FullPath, repo.StateChange)
if err := repo.Reload(); err != nil { if err := repo.ReloadCheck(); err != nil {
return err return err
} }
} else { } else {
@ -62,7 +60,6 @@ func (f *Forge) updateRepo(repo *gitpb.Repo) error {
} else { } else {
log.Info("readonly flag on repo is wrong", repo.GetGoPath()) log.Info("readonly flag on repo is wrong", repo.GetGoPath())
repo.ReadOnly = true repo.ReadOnly = true
f.configSave = true
} }
} }
return nil return nil
@ -79,7 +76,10 @@ func (f *Forge) RillReload() int {
var all []*gitpb.Repo var all []*gitpb.Repo
for repo := range f.Repos.IterAll() { for repo := range f.Repos.IterAll() {
if !repo.IsValidDir() { if !repo.IsValidDir() {
log.Printf("%s %-50s\n", "got an invalid repo in forgepb.RillReload()", repo.GetGoPath()) log.Printf("%s %-50s\n", "got an invalid repo in forgepb.RillReload()", repo.GetFullPath())
f.Repos.Delete(repo)
log.Info("reposSave = true")
config.SetChanged("repos", true)
continue continue
} }
all = append(all, repo) all = append(all, repo)
@ -95,11 +95,10 @@ func (f *Forge) RillReload() int {
}) })
rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error { rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error {
if !repo.DidRepoChange() { if !repo.HasChanged() {
return nil return nil
} }
f.configSave = true repo.ReloadCheck()
repo.Reload()
counter += 1 counter += 1
return nil return nil
}) })
@ -116,9 +115,9 @@ func (f *Forge) RillFuncError(rillf func(*gitpb.Repo) error) map[string]*RillSta
} }
func (f *Forge) ConfigRill(rillX int, rillY int) { func (f *Forge) ConfigRill(rillX int, rillY int) {
f.rillX = rillX f.Config.RillX = int32(rillX)
f.rillY = rillY f.Config.RillY = int32(rillY)
log.Infof("Setting rill values to %d,%d\n", f.rillX, f.rillY) log.Infof("Setting rill values to %d,%d\n", f.Config.RillX, f.Config.RillY)
} }
type RillStats struct { type RillStats struct {
@ -141,11 +140,15 @@ func (f *Forge) RillRepos(rillf func(*gitpb.Repo) error) map[string]*RillStats {
for repo := range f.Repos.IterAll() { for repo := range f.Repos.IterAll() {
if !repo.IsValidDir() { if !repo.IsValidDir() {
log.Printf("got an invalid repo in forgepb.RillRepos() %-50s\n", repo.GetGoPath()) log.Printf("got an invalid repo in forgepb.RillRepos() %-50s\n", repo.GetFullPath())
f.Repos.Delete(repo)
log.Info("reposSave = true")
config.SetChanged("repos", true)
continue continue
} }
all = append(all, repo) all = append(all, repo)
} }
// log.Info("Rill Repos len =", len(all))
// Convert a slice of user IDs into a channel // Convert a slice of user IDs into a channel
ids := rill.FromSlice(all, nil) ids := rill.FromSlice(all, nil)
@ -154,11 +157,11 @@ func (f *Forge) RillRepos(rillf func(*gitpb.Repo) error) map[string]*RillStats {
// Read users from the API. // Read users from the API.
// Concurrency = 20 // Concurrency = 20
dirs := rill.Map(ids, f.rillX, func(id *gitpb.Repo) (*gitpb.Repo, error) { dirs := rill.Map(ids, int(f.Config.RillX), func(id *gitpb.Repo) (*gitpb.Repo, error) {
return id, nil return id, nil
}) })
rill.ForEach(dirs, f.rillY, func(repo *gitpb.Repo) error { rill.ForEach(dirs, int(f.Config.RillY), func(repo *gitpb.Repo) error {
// todo: make this a goroutine to show stats to the user // todo: make this a goroutine to show stats to the user
rillMu.Lock() rillMu.Lock()
counter += 1 counter += 1

170
scanRepoDir.go Normal file
View File

@ -0,0 +1,170 @@
package forgepb
import (
"fmt"
"os"
"path/filepath"
"github.com/destel/rill"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func reloadCheck(repo *gitpb.Repo) error {
if err := repo.ReloadCheck(); err != nil {
log.Infof("%s reload() says %v\n", repo.FullPath, err)
return err
}
return nil
}
func (f *Forge) TestScan() error {
f.Repos = gitpb.NewRepos()
dirs, err := gitDirectoriesNew(f.Config.ReposDir)
if err != nil {
return err
}
for i, fullpath := range dirs {
repo, err := gitpb.NewRepo(fullpath)
if err != nil {
log.Info("ReAdd() error", fullpath, err)
}
log.Info(i, "worked", repo.FullPath)
repo = f.Repos.Append(repo)
f.VerifyBranchNames(repo)
if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true
}
repo.ReloadCheck()
if i > 5 {
break
}
}
return nil
}
func (f *Forge) checkNamespace(fullpath string) (*gitpb.Repo, error) {
if repo := f.Repos.FindByFullPath(fullpath); repo != nil {
return nil, nil
}
repo, err := gitpb.NewRepo(fullpath)
if err != nil {
log.Info(fullpath, err)
return nil, err
}
return repo, err
}
func (f *Forge) ScanRepoDir() error {
dirs, err := gitDirectoriesNew(f.Config.ReposDir)
if err != nil {
return err
}
stats := f.RillRepos(reloadCheck)
for _, stat := range stats {
if stat.Err == nil {
continue
}
config.SetChanged("repos", true)
}
newcount, err := f.rillScanDirsNew(dirs)
if err != nil {
log.Info("go src dir problem. exit for now?", err)
return err
}
if newcount != 0 {
log.Info("forge go src scan found", newcount, "repos")
config.SetChanged("repos", true)
}
return err
}
// rill is awesome. long live rill
// attempt scan with rill
func (f *Forge) rillScanDirsNew(fullpaths []string) (int, error) {
// Convert a slice of user IDs into a channel
ids := rill.FromSlice(fullpaths, nil)
// Read users from the API. // Concurrency = 20
dirs := rill.Map(ids, int(f.Config.RillX), func(id string) (*gitpb.Repo, error) {
return f.checkNamespace(id)
})
var counter int
// Activate users. // Concurrency = 10
err := rill.ForEach(dirs, int(f.Config.RillY), func(repo *gitpb.Repo) error {
if repo == nil {
return nil
}
repo = f.Repos.Append(repo)
f.VerifyBranchNames(repo)
if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true
}
repo.ReloadCheck()
counter += 1
return nil
})
return counter, err
}
// doesn't enter the directory any further when it finds a .git/
// not stupid like my old version
func gitDirectoriesNew(srcDir string) ([]string, error) {
var all []string
var trip bool
err := filepath.WalkDir(srcDir, func(path string, d os.DirEntry, err error) error {
if err != nil {
// Handle possible errors, like permission issues
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
return err
}
if d.IsDir() {
// log.Info("path is dir", path)
} else {
_, fname := filepath.Split(path)
switch fname {
case "repos.pb":
case "go.work":
case "go.work.last":
case "go.work.sum":
default:
// todo: figure out a way to do padding for init()
if trip == false {
log.Info("WARNING:")
}
log.Info("WARNING: you have an untracked file outside of any .git repository:", path)
trip = true
}
return nil
}
gitdir := filepath.Join(path, ".git")
_, err2 := os.Stat(gitdir)
if !os.IsNotExist(err2) {
all = append(all, path)
return filepath.SkipDir
}
return nil
})
//
// probably always leave this here forever
// this check, along with CheckDirty() makes sure you can safely delete ~/go/src or the go.work directory
// because everything is either checked in or deleted. An important thing to know!
if trip {
log.Info("WARNING:")
log.Info("WARNING: there isn't a way to disable this warning yet")
log.Info("WARNING: probably this is a good thing however. you don't want to leave files outside of git repos here")
log.Info("WARNING: so this warning should probably stay")
log.Info("WARNING:")
log.Info("WARNING: this also might mean you put these files here because you are actively working on them")
log.Info("WARNING: and you don't want to forget about them")
log.Info("WARNING:")
}
return all, err
}

32
set.proto Normal file
View File

@ -0,0 +1,32 @@
// 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
import "patch.proto"; // Import the well-known type for Timestamp
message Set { // `autogenpb:http`
Patches patches = 1;
string uuid = 2;
google.protobuf.Timestamp ctime = 3; // when the patches were submitted
string submitter = 4; // who submitted these // deprecate this
string name = 5; // "fixes for foo"
string gitAuthorName = 6;
string gitAuthorEmail = 7;
string hostname = 8;
string tmpDir = 9; // temp dir for 'git am' deprecate this
string startBranchName = 10; // deprecate this
string endBranchName = 11; // deprecate this
string startBranchHash = 12; // deprecate this
string endBranchHash = 13; // deprecate this
string comment = 14; // deprecate this
string state = 15; // deprecate this
}
message Sets { // `autogenpb:marshal` `autogenpb:gui` `autogenpb:nomutex` `autogenpb:http`
string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079`
string version = 2; // `autogenpb:version:v0.0.45`
repeated Set sets = 3;
}

89
set.table.go Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb
import (
"time"
"go.wit.com/lib/cobol"
"go.wit.com/log"
)
func (pset *Set) PrintTable() {
if pset == nil || pset.Patches == nil {
return
}
log.DaemonMode(true) // don't timestamp lines
tablePB := pset.Patches.makeStandardTable()
tablePB.MakeTable()
tablePB.PrintTable()
}
func (mt *PatchesTable) PrintTable() {
// log.Info("ShowTable() SENDING TO GUI")
mt.MakeTable()
cobol.PrintTable(mt.pb)
}
func (pb *Patches) makeStandardTable() *PatchesTable {
t := pb.NewTable("tagList")
t.NewUuid()
col := t.AddNamespace()
col.Width = 28
col = t.AddCommitHash()
col.Width = 8
col = t.AddPatchId()
col.Width = 8
col = t.AddNewHash()
col.Width = 8
col = t.AddTimeFunc("ctime", func(p *Patch) time.Time {
// todo
return p.Ctime.AsTime()
})
col.Width = 4
/*
col = t.AddTimeFunc("age", func(repo *gitpb.GitTag) time.Time {
// todo
return time.Now()
})
col.Width = 4
*/
// col = t.AddStringFunc("filename", func(p *Patch) string {
// _, base := filepath.Split(p.Filename)
// return base
// })
// col.Width = 24
col = t.AddComment()
col.Width = 80
return t
}
/*
func (pb *Sets) makeStandardTablePB() *SetsTable {
t := pb.NewTable("tagList")
t.NewUuid()
col := t.AddUuid()
col.Width = 12
col = t.AddTimeFunc("ctime", func(pset *Set) time.Time {
// todo
return pset.Ctime.AsTime()
})
col.Width = 4
col = t.AddComment()
col.Width = -1
return t
}
*/

View File

@ -2,7 +2,6 @@ package forgepb
import ( import (
sync "sync" sync "sync"
"time"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
) )
@ -10,46 +9,14 @@ import (
// maybe an interface someday? // maybe an interface someday?
type Forge struct { type Forge struct {
// one-time initialized data // one-time initialized data
once sync.Once once sync.Once
initErr error // init error, if any Config *ForgeConfigs // config repos for readonly, private, etc
goSrc string // the path to go/src Repos *gitpb.Repos // the repo protobufs
repoPB string // the path to the repos.pb cache file Patchsets *Sets // patches that are in progress
configDir string // normally ~/.config/forge hostname string // your hostname
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
Repos *gitpb.Repos // the repo protobufs
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
hostname string // your hostname
forgeURL string // URL to use to forge.wit.com
rillX int // used for Rill()
rillY int // used for Rill()
Patchsets *Patchsets // patches that are in progress
patchDir string // where patches are stored
}
func (f *Forge) GetGoSrc() string {
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)
}