Compare commits

...

60 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
30 changed files with 892 additions and 1482 deletions

View File

@ -5,7 +5,7 @@
# 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
autogenpb --proto patchset.proto
@ -27,5 +27,19 @@ clean:
forgeConfig.pb.go: forgeConfig.proto
autogenpb --proto forgeConfig.proto
patchset.pb.go: patchset.proto
autogenpb --proto patchset.proto
# patchset.pb.go: 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
import (
"os"
"path/filepath"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
@ -81,7 +81,7 @@ func (f *Forge) DoAllCheckoutMaster() error {
f.RillFuncError(rillCheckoutMaster)
count := f.RillReload()
if count != 0 {
f.ConfigSave()
config.SetChanged("repos", true)
}
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
if repo.GetMasterVersion() == "giterr" {
repo.CheckoutMaster()
log.Info("master == giterr. BAD REPO", repo.GetFullPath())
log.Info("master == giterr. BAD REPO", repo.GetFullPath())
log.Info("master == giterr. BAD REPO", repo.GetFullPath())
cmd := []string{"git", "checkout", "main"} // todo: figure out main
repo.RunVerbose(cmd)
os.Exit(-1)
return nil
log.Info(repo.GetFullPath(), "master == giterr. BAD REPO")
log.Info(repo.GetFullPath(), "master == giterr. BAD REPO git describe --tags master --always")
log.Info(repo.GetFullPath(), "master == giterr. BAD REPO. todo: figure this out in rillCheckoutMaster()")
// cmd := []string{"git", "checkout", "main"} // todo: figure out main
// repo.RunVerbose(cmd)
return log.Errorf("master version can not be determined")
}
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on master
@ -161,7 +160,7 @@ func (f *Forge) DoAllCheckoutUser(force bool) error {
f.RillFuncError(rillCheckoutUser)
count := f.RillReload()
if count != 0 {
f.ConfigSave()
config.SetChanged("repos", true)
}
total, count, nope, err := f.IsEverythingOnUser()
@ -205,10 +204,10 @@ func (f *Forge) makeUserBranches() error {
}
func testReload(repo *gitpb.Repo) error {
if !repo.DidRepoChange() {
if !repo.HasChanged() {
return nil
}
repo.Reload()
repo.ReloadCheck()
return log.Errorf("repo changed")
}
@ -245,7 +244,7 @@ func (f *Forge) DoAllCheckoutDevelNew(force bool) error {
counter += 1
}
log.Info("reloaded", counter, "repos")
f.configSave = true
config.SetChanged("repos", true)
return nil
}
@ -260,7 +259,7 @@ func (f *Forge) DoAllCheckoutDevel(force bool) error {
f.RillFuncError(rillCheckoutDevel)
count := f.RillReload()
if count != 0 {
f.ConfigSave()
config.SetChanged("repos", true)
}
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")
if testenv == "off" {
log.Info("GO111MODULE=off", "f.goWork =", f.IsGoWork(), "f.gosrc =", f.GetGoSrc())
log.Info("GO111MODULE=off", "f.goWork =", f.IsGoWork())
} 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:", cmd)
@ -202,10 +202,10 @@ func (f *Forge) runAutogenpb(repo *gitpb.Repo) error {
return nil
}
// sortcut to find
// used by guireleaser for now
func (f *Forge) FindWorkingDirRepo() *gitpb.Repo {
pwd, _ := os.Getwd()
basedir := strings.TrimPrefix(pwd, f.GetGoSrc())
basedir := strings.TrimPrefix(pwd, f.Config.ReposDir)
basedir = strings.Trim(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
// 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 {
// repo already exists
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) {
var err error
fullpath := filepath.Join(f.goSrc, gopath)
fullpath := filepath.Join(f.Config.ReposDir, gopath)
basedir, newdir := filepath.Split(fullpath)
// clone the URL directly

198
config.go
View File

@ -2,175 +2,85 @@
package forgepb
// functions to import and export the protobuf
// data to and from config files
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/prep"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// returns err1 || err2
func (f *Forge) ConfigSave() error {
var err error
// backup the current config files
if e := f.backupConfig(); e != nil {
log.Info("forge.BackupConfig() error", e)
err = e
// continue here? notsure. could be bad either way
// out of disk space?
if f.Config == nil {
return log.Errorf("forge.Config == nil")
}
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 {
log.Info("forge.Config.ConfigSave() error", e)
err = e
}
}
if f.Repos != nil {
if f.HasFullScan() {
f.Repos.HasFullScan = true
t := time.Now()
f.Repos.FullScan = timestamppb.New(t)
}
if e := f.Repos.ConfigSave(); e != nil {
log.Info("forge.Repos.ConfigSave() error", e)
err = e
}
}
return err
}
// functions to import and export the protobuf
// data to and from config files
// write to ~/.config/forge/
func (f *ForgeConfigs) ConfigSave() error {
data, err := f.Marshal()
if err != nil {
log.Info("proto.Marshal() failed len", len(data), err)
return err
}
// log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data))
s := f.FormatTEXT()
configWrite("forge.text", []byte(s))
s = f.FormatJSON()
configWrite("forge.json", []byte(s))
return nil
func (cfg *ForgeConfigs) ConfigSave() error {
var header string
header += "\n"
header += "# the forge config file\n"
header += "# You can customize things like:\n"
header += "#\n"
header += "# * which repos you have write access to\n"
header += "# * custom branch names for 'master', 'devel' and 'user'\n"
header += "# * 'favorites' so you can remember which things you like\n"
header += "# * sometimes protobuf TEXT can fail so as a backup this also creates a .json file\n"
header += "#\n"
header += "\n"
return config.ConfigSaveWithHeader(cfg, header)
}
// load the ~/.config/forge/ files
func (c *ForgeConfigs) ConfigLoad(fullpath string) error {
// var data []byte
// var err error
if c == nil {
// can't safely do c = new(ForgeConfig) if c is in a struct from the caller. notsure why
// TODO: recheck this. it might work now? It's probably still a bad idea(?)
return errors.New("It's not safe to run ConfigLoad() on a nil")
func (cfg *ForgeConfigs) DumpENV() {
log.Infof("CfgPB.Filename = %s\n", cfg.Filename)
log.Infof("CfgPB.ReposPB = %s\n", cfg.ReposPB)
log.Infof("CfgPB.ReposDir = %s\n", cfg.ReposDir)
log.Infof("CfgPB.PatchDir = %s\n", cfg.PatchDir)
log.Infof("CfgPB.ForgeURL = %s\n", cfg.ForgeURL)
if cfg.GoWork {
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 {
return nil
if cfg.ReposPB != os.Getenv("FORGE_REPOSPB") {
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
data, err := loadFile("forge.text")
if err != nil {
return err
}
if data == nil {
return fmt.Errorf("forge.text data was nil")
}
if len(data) == 0 {
return fmt.Errorf("forge.text was empty")
/*
if f.Config.Xterm == "" {
f.Config.Xterm = "xterm"
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
f.SetConfigSave(true)
}
*/
// attempt to marshal forge.text
if err := c.UnmarshalTEXT(data); err != nil {
return err
}
log.Log(INFO, "forge.loadText()", 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
func (cfg *ForgeConfigs) InitDefaults() {
}

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,6 +6,7 @@ package forgepb
import (
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
@ -22,8 +23,7 @@ func (f *Forge) CheckDirtyQuiet() {
for _, s := range stats {
if s.Err == nil {
} else {
// log.Info(i, s.Err)
f.SetConfigSave(true)
config.SetChanged("repos", true)
changed = true
}
}
@ -45,7 +45,7 @@ func (f *Forge) CheckDirty() *gitpb.Repos {
if s.Err == nil {
} else {
log.Info(i, s.Err)
f.SetConfigSave(true)
config.SetChanged("repos", true)
}
}
@ -73,13 +73,12 @@ func doCheckDirty(repo *gitpb.Repo) error {
// nothing changed
} else {
log.Info("Repo changed to clean", repo.FullPath)
// f.SetConfigSave(true)
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)
// f.SetConfigSave(true)
} else {
// nothing changed
}

View File

@ -42,6 +42,8 @@ enum ForgeMode {
MASTER = 0; // "release mode"
DEVEL = 1; // "patch 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`
@ -53,9 +55,17 @@ message ForgeConfigs { // `autogenpb:mar
repeated string xtermArgv = 6; // the argv line for xterm
string defaultGui = 7; // default GUI plugin to use
ForgeMode mode = 8; // what "mode" forge is in
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
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
// then dump the uuid and version from any arbitrary .pb file
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,202 +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
}
f.configSave = true
return repo, err
}

View File

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

22
http.go
View File

@ -17,7 +17,7 @@ import (
func (f *Forge) HttpPost(base string, route string, data []byte) ([]byte, error) {
// Fix using url.JoinPath (Best Practice)
baseURL, _ := url.Parse(f.forgeURL) // "http://forge.grid.wit.com:2520")
baseURL, _ := url.Parse(f.Config.ForgeURL) // "http://forge.grid.wit.com:2520")
finalURL := baseURL.JoinPath(route) // Correctly produces ...:2520/patches
var err error
@ -52,7 +52,7 @@ func rawHttpPost(req *http.Request) ([]byte, 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() {
if repo.Namespace == "" {
@ -63,6 +63,7 @@ func (f *Forge) LookupPBorig(check *gitpb.Repos) (*gitpb.Repos, error) {
return check.SubmitReposPB(url)
}
/*
func (f *Forge) LookupPB(check *gitpb.Repos) (*gitpb.Repos, error) {
url := f.forgeURL + "lookup"
@ -102,7 +103,9 @@ func (f *Forge) UpdatePB(check *gitpb.Repos) (*gitpb.Repos, error) {
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 {
@ -117,18 +120,19 @@ func (pb *Patches) AddHttpToPB(r *http.Request) error {
}
}
pb.HttpRequest = &Patches_HttpRequest{
pb.HttpRequest = &httppb.HttpRequest{
Method: r.Method,
Url: r.URL.String(),
URL: r.URL.String(),
Proto: r.Proto,
Headers: headers,
RemoteAddr: getClientIP(r),
IP: getClientIP(r),
Host: r.Host,
Hostname: r.Header.Get("hostname"),
}
pb.HttpRequest.Route = cleanURL(r.URL.Path)
// pb.HttpRequest.Route = cleanURL(r.URL.Path)
return nil
}
*/
func getIpSimple(r *http.Request) string {
host, _, err := net.SplitHostPort(r.RemoteAddr)
@ -155,9 +159,3 @@ func getClientIP(r *http.Request) string {
}
return host
}
// remove '?' part and trailing '/'
func cleanURL(url string) string {
url = "/" + strings.Trim(url, "/")
return url
}

View File

@ -11,29 +11,8 @@ import (
"go.wit.com/log"
)
// you can replace all of COBOL with this amount of GO
// ah yes, COBOL. what an ancient throwback. for those that know
// then you know exactly what is in this file. For those that don't, here it is:
// All this does is output human readable text formatted to be viewable on
// a console with a fixed with font. AKA: a typerwriter. Which is exactly
// what COBOL did in the 1970's (60s? notsure) And the 80s.
// So, you want to dump out stuff on the console. Let's see. Something like
/*
forge --favorites
go.wit.com/apps/myapp v0.2.0 (installed)
go.wit.com/lib/somethingfun v0.0.7 (not downloaded)
*/
// anyway, you get the idea. This is also called COBOL because it does on
// thing and truncates every line output to the columns you see with stty -a
// my monitor is huge, so it's not going to work at 80x24. 160x48 is better
// actually, I'd predict some of these will probably end up 240 wide
// long live good eyesight and 4K monitors!
// a console with a fixed with font. AKA: a typerwriter
func (f *Forge) PrintHumanTable(allr *gitpb.Repos) {
log.DaemonMode(true)
@ -41,8 +20,8 @@ func (f *Forge) PrintHumanTable(allr *gitpb.Repos) {
t := new(tally)
// print the header
args := []string{"namespace", "cur br", "age", "master", "devel", "user", "", "lasttag", "next", "repo type"}
sizes := []int{40, 9, 6, 16, 16, 16, 1, 12, 12, 8}
args := []string{"Namespace", "branch", "age", "user", "devel", "master", "", "lasttag", "next", "repo type"}
sizes := []int{35, 9, 4, 13, 13, 13, 1, 12, 12, 8}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
all := allr.SortByFullPath()
@ -60,8 +39,8 @@ func (f *Forge) PrintForgedTable(allr *gitpb.Repos) {
t := new(tally)
// print the header
args := []string{"namespace", "cur br", "age", "master", "devel", "last tag", "", "", "", ""}
sizes := []int{40, 9, 6, 12, 16, 16, 16, 12, 12, 8}
args := []string{"Namespace", "branch", "age", "master", "devel", "last tag", "", "", "", ""}
sizes := []int{35, 9, 4, 13, 13, 13, 13, 12, 12, 8}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
all := allr.SortByFullPath()
@ -70,7 +49,7 @@ func (f *Forge) PrintForgedTable(allr *gitpb.Repos) {
f.printForgedToTable(repo, sizes)
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) {
@ -79,8 +58,8 @@ func (f *Forge) PrintHumanTableFull(allr *gitpb.Repos) {
t := new(tally)
// print the header
args := []string{"cur br", "age", "master", "devel", "user", "", "lasttag", "next", "repo type", "namespace"}
sizes := []int{9, 6, 16, 16, 16, 1, 12, 12, 8, 0}
args := []string{"branch", "age", "user", "devel", "master", "", "lasttag", "next", "repo type", "Namespace"}
sizes := []int{9, 4, 13, 13, 13, 1, 12, 12, 8, 0}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
all := allr.SortByFullPath()
@ -89,7 +68,7 @@ func (f *Forge) PrintHumanTableFull(allr *gitpb.Repos) {
f.printRepoToTable(repo, sizes, true)
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
@ -99,8 +78,8 @@ func (f *Forge) PrintHumanTableDirty(allr *gitpb.Repos) {
t := new(tally)
// print the header
args := []string{"namespace", "cur br", "age", "master", "devel", "user", "", "lasttag", "next", "repo type"}
sizes := []int{40, 9, 6, 16, 16, 16, 1, 12, 12, 8}
args := []string{"Namespace", "branch", "age", "user", "devel", "master", "", "lasttag", "next", "repo type"}
sizes := []int{35, 9, 4, 13, 13, 13, 1, 12, 12, 8}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
for repo := range allr.IterAll() {
@ -115,7 +94,7 @@ func (f *Forge) PrintHumanTableDirty(allr *gitpb.Repos) {
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)
@ -142,82 +121,20 @@ func tallyBranchTotals(t *tally, repo *gitpb.Repo) {
t.user += 1
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
}
/*
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) {
var end string
// shortened version numbers
var mhort string = repo.GetMasterVersion()
var dhort string = repo.GetDevelVersion()
var uhort string = repo.GetUserVersion()
if uhort == "uerr" {
var uver string = repo.GetUserVersion()
if uver == "uerr" {
// blank these out
uhort = ""
uver = ""
}
var lasttag string = repo.GetLastTag()
var thort string = repo.GetTargetVersion()
@ -252,15 +169,16 @@ func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
}
if repo.IsDirty() {
age = "#"
age = ""
uver = "* " + uver
end += "(dirty) "
}
var args []string
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 {
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)
@ -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() {
case "PERFECT":
case "unchanged":
@ -302,6 +203,23 @@ func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
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))
}
@ -328,7 +246,7 @@ func (f *Forge) printForgedToTable(repo *gitpb.Repo, sizes []int) {
log.Info(cobol.TerminalChomp(start + " " + end))
}
func (psets *Patchsets) PrintTable() {
func (psets *Sets) PrintTable() {
if psets == nil {
return
}
@ -339,10 +257,11 @@ func (psets *Patchsets) PrintTable() {
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
/*
var countCONTENTS int
var countPARTS int
for x, pset := range psets.GetPatchsets() {
log.Info(pset.Uuid, pset.Name, pset.State)
for x, pset := range psets.GetSets() {
log.Info(pset.Patches.Uuid, pset.Patches.Name, pset.Patches.State)
if pset.State == "DONE" {
// old patchset
continue
@ -362,6 +281,7 @@ func (psets *Patchsets) PrintTable() {
}
}
log.Infof("Total Contents (%d) Parts (%d)\n", countCONTENTS, countPARTS)
*/
}
func (patches *Patches) PrintTable() {
@ -375,6 +295,7 @@ func (patches *Patches) PrintTable() {
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
@ -388,4 +309,5 @@ func (patches *Patches) PrintTable() {
countPARTS += 1
}
log.Infof("Total Patches (%d)\n", countPARTS)
*/
}

266
init.go
View File

@ -3,213 +3,161 @@
package forgepb
import (
"errors"
"os"
"os/user"
"path/filepath"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
/* better syntax from gin
Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}
*/
func Init() *Forge {
f := InitPB()
/*
f.Machine = new(zoopb.Machine)
if err := f.Machine.ConfigLoad(); err != nil {
log.Log(WARN, "zoopb.ConfigLoad() failed", err)
f := new(Forge)
cfg := new(ForgeConfigs)
err := config.ConfigLoad(cfg, "forge", "forge")
f.Config = cfg
if err != nil {
// 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 == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
f.SetConfigSave(true)
f.Config.ConfigSave()
}
if f.Config.Xterm == "" {
f.Config.Xterm = "xterm"
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
f.SetConfigSave(true)
f.initFromConfig()
if f.Config.Mode == ForgeMode_MASTER {
log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
// fhelp.DumpENV("finit:")
f.Config.DumpENV()
}
// 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
}
func FirstTimeUser() bool {
if checkenv() {
return false
func InitByAppname(argname string) *Forge {
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)
}
// setup the env
f := new(Forge)
f.setenv()
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), "forge.text")
_, err := os.ReadFile(fullname)
if errors.Is(err, os.ErrNotExist) {
return true
}
return false
f.Config = cfg
f.initFromConfig()
log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
return f
}
func (f *Forge) InitPB() {
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)
func (f *Forge) initFromConfig() {
if f.configENV() {
log.Info("ENV changed config. todo: save config here")
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.ConfigLoad()
if f.Repos.HasFullScan {
f.hasFullScan = true
}
f.Repos.ConfigLoad(f.Config.ReposPB)
// init the Patchsets
f.Patchsets = NewPatchsets()
f.Patchsets = NewSets()
// todo: play with these / determine good values based on user's machine
f.rillX = 10
f.rillY = 20
}
func (f *Forge) InitMachine() {
if f.Config.Username == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
if f.Config.RillX == 0 {
f.Config.RillX = 10
}
if f.Config.RillY == 0 {
f.Config.RillY = 20
}
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) {
f.configSave = b
config.SetChanged("forge", b)
}
// saves the config if there have been changes
func (f *Forge) Exit() {
// log.Info("forge.configSave =", f.configSave)
if f.configSave {
if f.Config.Mode == ForgeMode_MASTER {
// 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")
os.Exit(0)
}
func RawInitPB() *Forge {
f := new(Forge)
f.RawInitPB()
return f
}
func (f *Forge) RawInitPB() {
f.InitPB()
}
// 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) setenv() {
f.once.Do(func() {
log.Info("doing setenv()")
if err := fhelp.ConfigureENV(); err != nil {
log.Info("forge ConfigureENV() failed", err)
os.Exit(-1)
}
f.configDir = os.Getenv("FORGE_CONFIG")
f.goSrc = os.Getenv("FORGE_GOSRC")
f.repoPB = os.Getenv("FORGE_REPOPB")
f.forgeURL = os.Getenv("FORGE_URL")
f.patchDir = os.Getenv("FORGE_PATCHDIR")
if f.Config == nil {
log.Info("forge.Config() was nil")
os.Exit(-1)
}
// f.forgeURL = os.Getenv("FORGE_URL")
f.hostname = os.Getenv("HOSTNAME")
if os.Getenv("FORGE_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()")
})
}
// if the env vars are set, this is probably not a new user
func checkenv() bool {
if os.Getenv("FORGE_CONFIG") != "" {
return true
}
if os.Getenv("FORGE_GOSRC") != "" {
return true
}
if os.Getenv("FORGE_REPOPB") != "" {
return true
}
if os.Getenv("FORGE_URL") != "" {
return true
}
if os.Getenv("FORGE_PATCHDIR") != "" {
return true
}
if os.Getenv("FORGE_VERBOSE") != "" {
return true
}
return false
}
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
@ -220,7 +168,47 @@ func (f *Forge) SetForgeURL(url string) {
if url == "" {
url = "https://forge.wit.com/"
}
f.forgeURL = url
os.Setenv("FORGE_URL", f.forgeURL)
log.Info("Forge URL has been set to", f.forgeURL)
f.Config.ForgeURL = url
os.Setenv("FORGE_URL", f.Config.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,84 +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 {
body, err := f.HttpPost("junk", "GetPatchsets", 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) expireAllPatchsets() {
all := f.Patchsets.All()
for all.Scan() {
pset := all.Next()
pset.State = "DONE"
}
}
func (f *Forge) loadUpstreamPatchsets(psets *Patchsets) {
var foundnew bool
all := psets.All()
for all.Scan() {
pset := all.Next()
found := f.Patchsets.FindByUuid(pset.Uuid)
if found == nil {
f.expireAllPatchsets()
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
}
author := log.Sprintf("Author: %s <%s>", found.GitAuthorName, found.GitAuthorEmail)
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
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"go.wit.com/lib/hostname"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/lib/protobuf/httppb"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
func newPatchset(name string) *Patchset {
pset := new(Patchset)
func (p *Patches) HttpPostVerbose(baseURL string, route string) (*Patches, *httppb.HttpRequest, error) {
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.Ctime = timestamppb.New(time.Now())
pset.Uuid = uuid.New().String()
pset.Hostname, _ = os.Hostname()
pset.Hostname, _ = hostname.Get()
pset.Patches = NewPatches()
return pset
}
// creates a patchset
// 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)
if os.Getenv("GIT_AUTHOR_NAME") == "" {
return nil, fmt.Errorf("GIT_AUTHOR_NAME not set")
@ -43,7 +63,7 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
if err != nil {
return nil, err
}
defer os.RemoveAll(dir) // clean up
// defer os.RemoveAll(dir) // clean up
pset.TmpDir = dir
all := f.Repos.SortByFullPath()
@ -59,6 +79,10 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
continue
}
if repo.ActualGetDevelHash() == repo.ActualGetUserHash() {
continue
}
// make a patchset from user to devel
// TODO: verify branches are otherwise exact
pset.StartBranchName = repo.GetDevelBranchName()
@ -72,41 +96,7 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
return pset, nil
}
/*
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 {
func (pset *Set) makePatchSetNew(repo *gitpb.Repo) error {
startBranch := pset.StartBranchName
endBranch := pset.EndBranchName
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))
}
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
return nil
}
err = pset.addPatchFiles(repo)
pset.Ctime = timestamppb.New(time.Now())
err = pset.addPatchFiles(repo, repoDir)
log.Infof("Added %d patches for %s len=%d\n", len(r.Stdout), repo.FullPath, pset.Patches.Len())
// pset.PrintTable()
return err
}
// git show <original_commit_hash> | git patch-id
// git cat-file -p <commit_hash> | grep tree
// process each file in pDir/
func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
psetDir := repo.GetGoPath()
tmpDir := p.TmpDir
// log.Info("ADD PATCH FILES ADDED DIR", tmpDir)
fullDir := filepath.Join(tmpDir, psetDir)
func (p *Set) addPatchFiles(repo *gitpb.Repo, fullDir string) error {
var baderr error
// log.Info("ADD PATCH FILES ADDED DIR", fullDir)
filepath.Walk(fullDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
// Handle possible errors, like permission issues
@ -170,10 +161,6 @@ func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
if info.IsDir() {
return nil
}
// log.Info("IS THIS A FULL PATH ?", path)
// log.Info("trim this from path ?", fullDir)
// log.Info("trim this from path ?", psetDir)
// log.Info("trim this from path ?", tmpDir)
data, err := os.ReadFile(path)
if err != nil {
log.Info("addPatchFile() failed", path)
@ -183,15 +170,22 @@ func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
patch := new(Patch)
patch.Filename, _ = filepath.Rel(p.TmpDir, path)
patch.Data = data
patch.parseData()
patch.StartHash = repo.DevelHash()
if err := patch.parseData(); err != nil {
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.Namespace = repo.GetGoPath()
if p.Patches == nil {
log.Info("SHOULD NOT HAVE HAPPENED. p.Patches == nil")
p.Patches = new(Patches)
}
p.Patches.Append(patch)
p.Patches.Uuid = uuid.New().String()
// log.Info("ADDED PATCH FILE", path)
return nil
})
@ -201,7 +195,7 @@ func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
// looks at the git format-patch output
// saves the commit Hash
// saves the diff lines
func (p *Patch) parseData() string {
func (p *Patch) parseData() error {
lines := strings.Split(string(p.Data), "\n")
for _, line := range lines {
fields := strings.Fields(line)
@ -217,7 +211,7 @@ func (p *Patch) parseData() string {
p.Files = append(p.Files, line)
}
}
return ""
return nil
}
// just an example of how to walk only directories
@ -242,3 +236,59 @@ func onlyWalkDirs(pDir string) error {
})
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,138 +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"
)
func (f *Forge) SendPatches(what string, p *Patches) (*Patches, error) {
route := "/patches/" + what
data, err := p.Marshal()
if err != nil {
return nil, err
}
log.Infof("pb: len=%d, err=%v\n", len(data), err)
newdata, err := f.HttpPost("rm this", route, data)
if err != nil {
return nil, err
}
if newdata == nil {
return nil, err
}
if len(newdata) == 0 {
return nil, err
}
log.Info("TODO: Unmarshal() to patches", len(newdata))
return nil, err
}
// 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 {
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("rm this", "patchset", msg)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
log.Info("HTTP: proto.Marshal() sent", len(msg), "ok and got back", len(body))
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
}
func (f *Forge) SubmitPatchesNew(pset *Patches, urlpath string) (*Patches, error) {
msg, err := pset.Marshal()
if err != nil {
log.Info("proto.Marshal() failed:", err)
return nil, err
}
log.Info("proto.Marshal() msg len", len(msg))
body, err := f.HttpPost("rm this", urlpath, msg)
if err != nil {
log.Info("httpPost() failed:", err)
return nil, err
}
log.Info("HTTP: proto.Marshal() sent", len(msg), "ok and got back", len(body))
newpb := NewPatches()
if err := newpb.Unmarshal(body); err != nil {
cfcheck := string(body[0:100])
if strings.Contains(cfcheck, "<title>Just a moment...</title>") {
return nil, 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 nil, err
}
log.Info("Total patches sent ok:", newpb.Len())
return newpb, nil
}

View File

@ -14,9 +14,9 @@ import (
)
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)
if err != nil {
@ -28,7 +28,7 @@ func (f *Forge) LoadPatchsets() error {
log.Infof("LoadPatchsets() proto.Marshal() error %v\n", 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
}
@ -39,13 +39,13 @@ func (f *Forge) InitPatchsets() error {
log.Info("LoadPatchsets() failed", err)
}
// TODO: check if Unmarshal failed here
f.Patchsets = NewPatchsets()
f.Patchsets = NewSets()
f.findAutoPatchset() // adds the default values
return f.SavePatchsets()
}
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)
if err != nil {
log.Info("SavePatchsets() filename open error:", filename, err)
@ -54,7 +54,7 @@ func (f *Forge) SavePatchsets() error {
}
defer regfile.Close()
newpb := proto.Clone(f.Patchsets).(*Patchsets)
newpb := proto.Clone(f.Patchsets).(*Sets)
if newpb == nil {
for pset := range f.Patchsets.IterAll() {
pset.ShowPatchsets()
@ -79,7 +79,7 @@ func cleanSubject(line string) string {
return strings.TrimSpace(cleaned)
}
func (pb *Patchset) ShowPatchsets() error {
func (pb *Set) ShowPatchsets() error {
author := "Author: " + pb.GitAuthorName
author += " <" + pb.GitAuthorEmail + ">"
log.Printf("%-16s %s %s %s\n", string(pb.Uuid)[0:8], pb.Name, pb.Comment, author)
@ -87,17 +87,29 @@ func (pb *Patchset) ShowPatchsets() error {
comment := cleanSubject(patch.Comment)
log.Printf("\t%-8s %-50s %-50s\n", string(patch.CommitHash)[0:8], patch.Namespace, comment)
}
/*
for patch := range pb.IterAll() {
comment := cleanSubject(patch.Comment)
log.Info("\tnew patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.Namespace, comment)
}
*/
// for patch := range pb.IterAll() {
// comment := cleanSubject(patch.Comment)
// log.Info("\tnew patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.Namespace, comment)
// }
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
func (f *Forge) AddPatchset(pb *Patchset) bool {
func (f *Forge) AddPatchset(pb *Set) bool {
var changed bool
// if the name of the patchset is "forge auto commit"
// then just add all the patches
@ -111,11 +123,11 @@ func (f *Forge) AddPatchset(pb *Patchset) bool {
if f.findPatch(patch) {
// log.Info("\talready found!!!!!!!", pset.Uuid, patch.Namespace)
} else {
if err := f.addRandomPatch(patch); err == nil {
if f.AddNewPatch(patch) {
log.Info("\tnew patch added:", patch.CommitHash, pb.Name, pb.Comment, author)
changed = true
} 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)
}
}
@ -134,52 +146,49 @@ func (f *Forge) AddPatchset(pb *Patchset) bool {
}
}
// Clone() this protobuf into me.forge.Patchsets
var newpb *Patchset
newpb = proto.Clone(pb).(*Patchset)
if newpb != nil {
f.Patchsets.Patchsets = append(f.Patchsets.Patchsets, newpb)
}
f.Patchsets.Append(pb)
return true
}
func (f *Forge) findAutoPatchset() *Patchset {
func (f *Forge) findAutoPatchset() *Set {
for pset := range f.Patchsets.IterAll() {
if pset.Name == "forge auto commit" {
return pset
}
}
var fauto *Patchset
var fauto *Set
log.Warn("findAutoPatchset() had to create 'forge auto commit'")
if fauto == nil {
fauto = new(Patchset)
fauto.Name = "forge auto commit"
fauto.Patches = NewPatches()
fauto.Uuid = uuid.New().String()
f.Patchsets.Patchsets = append(f.Patchsets.Patchsets, fauto)
fauto = makeDefaultPatchset()
f.Patchsets.Append(fauto)
}
return fauto
}
// adds submitted patches not specifically assigned to a patchset
// to the generic patchset called "forge auto commit"
func (f *Forge) addRandomPatch(patch *Patch) error {
func makeDefaultPatchset() *Set {
fauto := new(Set)
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
if f.findPatch(patch) {
log.Info("already found patch", patch.CommitHash, patch.Namespace)
return nil
return false
}
fauto := f.findAutoPatchset()
if fauto == nil {
return log.Errorf("no default place yet")
// should have made the default patchset
return false
}
newpb := proto.Clone(patch).(*Patch)
if newpb == nil {
return log.Errorf("proto.Clone returned nil")
}
fauto.Patches.Patches = append(fauto.Patches.Patches, newpb)
return nil
fauto.Patches.Append(patch)
return true
}
// returns true if the patch already exists in the protobuf

View File

@ -1,119 +0,0 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package forgepb
import (
"bytes"
"net/http"
"net/url"
"os"
"os/user"
"go.wit.com/log"
)
func (p *Patches) HttpPostVerbose(baseURL string, route string) (*Patches, error) {
p.PrintTable()
return p.HttpPost(baseURL, route)
}
func (p *Patches) HttpPost(baseURL string, route string) (*Patches, error) {
// if you ever have "http://www.wit.com//" GO will regect the server recieving it.
// Even though the linux kernel gets the network payload
// also it never gives you an error about that, it just goes away invisably inside GO
tmpURL, _ := url.Parse(baseURL) // "http://forge.grid.wit.com:2520")
finalURL := tmpURL.JoinPath("/patches/", route) // Correctly produces ...:2520/patches
data, _ := p.Marshal()
var err error
var req *http.Request
log.Info("patches PB HttpPost", finalURL.String())
req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data))
if req == nil {
return nil, err
}
usr, _ := user.Current()
req.Header.Set("author", usr.Username)
hostname, _ := os.Hostname()
req.Header.Set("hostname", hostname)
newdata, err := rawHttpPost(req)
newpb := NewPatches()
err = newpb.Unmarshal(newdata)
log.Infof("patchset PB HttpPost %s sent len(%d) got len(%d)\n", finalURL.String(), p.Len(), newpb.Len())
return newpb, err
}
func (p *Patchset) HttpPost(baseURL string, route string) (*Patchset, error) {
// if you ever have "http://www.wit.com//" GO will regect the server recieving it.
// Even though the linux kernel gets the network payload
// also it never gives you an error about that, it just goes away invisably inside GO
tmpURL, _ := url.Parse(baseURL) // "http://forge.grid.wit.com:2520")
finalURL := tmpURL.JoinPath("/patchset/", route) // Correctly produces ...:2520/patches
data, _ := p.Marshal()
var err error
var req *http.Request
log.Info("patchset PB HttpPost", finalURL.String())
req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data))
if req == nil {
return nil, err
}
usr, _ := user.Current()
req.Header.Set("author", usr.Username)
hostname, _ := os.Hostname()
req.Header.Set("hostname", hostname)
newdata, err := rawHttpPost(req)
newpb := new(Patchset)
err = newpb.Unmarshal(newdata)
log.Infof("patchset PB HttpPost %s sent (%d)bytes got (%d)bytes\n", finalURL.String(), len(data), len(newdata))
return newpb, err
}
func (p *Patchsets) HttpPostVerbose(baseURL string, route string) (*Patchsets, error) {
p.PrintTable()
return p.HttpPost(baseURL, route)
}
func (p *Patchsets) HttpPost(baseURL string, route string) (*Patchsets, error) {
// if you ever have "http://www.wit.com//" GO will regect the server recieving it.
// Even though the linux kernel gets the network payload
// also it never gives you an error about that, it just goes away invisably inside GO
tmpURL, _ := url.Parse(baseURL) // "http://forge.grid.wit.com:2520")
finalURL := tmpURL.JoinPath("/patchsets/", route) // Correctly produces ...:2520/patches
data, _ := p.Marshal()
var err error
var req *http.Request
log.Info("patchsets PB HttpPost", finalURL.String())
req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data))
if req == nil {
return nil, err
}
usr, _ := user.Current()
req.Header.Set("author", usr.Username)
hostname, _ := os.Hostname()
req.Header.Set("hostname", hostname)
newdata, err := rawHttpPost(req)
newpb := NewPatchsets()
err = newpb.Unmarshal(newdata)
log.Infof("patchset PB HttpPost %s sent len(%d) got len(%d)\n", finalURL.String(), p.Len(), newpb.Len())
return newpb, err
}

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,102 +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; // 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?
}
// this is a "PATCH: [1/x]" series
message Patches { // `autogenpb:marshal` `autogenpb:gui:Patch`
message HttpRequest { // HttpRequest represents the essential fields of an incoming HTTP request.
string method = 1; // The request method, e.g., "GET", "POST".
string url = 2; // The full URL of the request, including scheme, host, path, and query string.
string route = 3; // just the route: "/add/" or "/find/"
string proto = 4; // The protocol version, e.g., "HTTP/1.1", "HTTP/2.0".
map<string, string> headers = 5; // The map of request headers. Header names are case-insensitive,
string remoteAddr = 6; // The remote IP address of the client, after resolving proxies.
string host = 7; // The host on which the URL is sought (www.wit.com)
string hostname = 8; // The hostname of the client if passed from the client (mylaptop.fun.me)
bytes body = 9; // The request body as raw bytes.
string namespace = 10; // When the body is a pb (always!). This is the pb namespace ("go.wit.com/lib/protobuf/virtpb")
}
string uuid = 1; // `autogenpb:uuid:2679065e-c81d-4a00-aca4-03c158a834fb`
string version = 2; // `autogenpb:version:v2.0.0`
repeated Patch Patches = 3;
HttpRequest httpRequest = 4; // who connected // rename httpRequest? This might make sense in our case
string Error = 5; // when passing these around, if there is an error, store it here
}
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"
"strings"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
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)
if 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()) {
repo.ReadOnly = true
}
repo.Reload()
repo.ReloadCheck()
return repo, nil
}
@ -52,13 +53,13 @@ func (f *Forge) AddNamespaceDir(ns string, fullpath string) (*gitpb.Repo, error)
}
f.VerifyBranchNames(repo)
repo.Reload()
repo.ReloadCheck()
// set the readonly flag based on the users' forge config
if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true
}
f.configSave = true
config.SetChanged("repos", true)
return repo, nil
}
@ -163,6 +164,9 @@ func (f *Forge) VerifyBranchNames(repo *gitpb.Repo) {
if repo.GetUserBranchName() == "" {
uname := f.configUserBranchName(repo)
if uname == "" {
log.Info("configUserBranchName() ERROR: failed with blank")
}
if repo.IsBranch(uname) {
repo.SetUserBranchName(uname)
} else {
@ -170,6 +174,9 @@ func (f *Forge) VerifyBranchNames(repo *gitpb.Repo) {
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?
@ -185,6 +192,9 @@ func (f *Forge) configUserBranchName(repo *gitpb.Repo) string {
if uname != "" {
return uname
}
if f.Config.Username == "" {
// something is wrong!
}
// use the os.Username
uname = f.Config.Username
@ -215,7 +225,7 @@ func (f *Forge) AddFullPath(fulldir 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)
}
@ -242,6 +252,6 @@ func (f *Forge) FindAnyPath(dir string) *gitpb.Repo {
}
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)
}

37
rill.go
View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/destel/rill"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
@ -33,7 +34,6 @@ func (f *Forge) rillUpdate(pool1 int, pool2 int) (int, error) {
// Concurrency = 10
err := rill.ForEach(rills, pool2, func(repo *gitpb.Repo) error {
counter += 1
// log.Info("rill.ForEach() gopath=", repo.GetGoPath())
return f.updateRepo(repo)
})
@ -42,16 +42,14 @@ func (f *Forge) rillUpdate(pool1 int, pool2 int) (int, error) {
func (f *Forge) updateRepo(repo *gitpb.Repo) error {
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.configSave = true
return nil
}
if repo.DidRepoChange() {
f.configSave = true
if repo.HasChanged() {
// log.Info("repo changed ", repo.FullPath, repo.StateChange)
if err := repo.Reload(); err != nil {
if err := repo.ReloadCheck(); err != nil {
return err
}
} else {
@ -62,7 +60,6 @@ func (f *Forge) updateRepo(repo *gitpb.Repo) error {
} else {
log.Info("readonly flag on repo is wrong", repo.GetGoPath())
repo.ReadOnly = true
f.configSave = true
}
}
return nil
@ -79,7 +76,10 @@ func (f *Forge) RillReload() int {
var all []*gitpb.Repo
for repo := range f.Repos.IterAll() {
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
}
all = append(all, repo)
@ -95,11 +95,10 @@ func (f *Forge) RillReload() int {
})
rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error {
if !repo.DidRepoChange() {
if !repo.HasChanged() {
return nil
}
f.configSave = true
repo.Reload()
repo.ReloadCheck()
counter += 1
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) {
f.rillX = rillX
f.rillY = rillY
log.Infof("Setting rill values to %d,%d\n", f.rillX, f.rillY)
f.Config.RillX = int32(rillX)
f.Config.RillY = int32(rillY)
log.Infof("Setting rill values to %d,%d\n", f.Config.RillX, f.Config.RillY)
}
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() {
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
}
all = append(all, repo)
}
// log.Info("Rill Repos len =", len(all))
// Convert a slice of user IDs into a channel
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.
// 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
})
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
rillMu.Lock()
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 (
sync "sync"
"time"
"go.wit.com/lib/protobuf/gitpb"
)
@ -11,45 +10,13 @@ import (
type Forge struct {
// one-time initialized data
once sync.Once
initErr error // init error, if any
goSrc string // the path to go/src
repoPB string // the path to the repos.pb cache file
configDir string // normally ~/.config/forge
goWork bool // means the user is currently using a go.work file
Config *ForgeConfigs // config repos for readonly, private, etc
Repos *gitpb.Repos // 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
Patchsets *Sets // patches that are in progress
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
goWork bool // means the user is currently using a go.work file
}
func (f *Forge) IsGoWork() bool {
return f.goWork
}
func (f *Forge) HasFullScan() bool {
return f.Repos.HasFullScan
}
func (f *Forge) FullScanRan() {
f.fullscan = time.Now()
}
func (f *Forge) FullScanAge() time.Duration {
fs := f.Repos.FullScan.AsTime()
return time.Since(fs)
}