From c752d408395387a57e6a335e335bc2c51bcb3802 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sun, 21 Sep 2025 20:49:32 -0500 Subject: make forge work for a new user --- config.common.go | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++ config.go | 58 +++++++++ forgeConfig.common.go | 337 -------------------------------------------------- forgeConfig.config.go | 56 --------- forgeConfig.proto | 2 +- init.go | 46 ++++--- 6 files changed, 427 insertions(+), 409 deletions(-) create mode 100644 config.common.go delete mode 100644 forgeConfig.common.go delete mode 100644 forgeConfig.config.go diff --git a/config.common.go b/config.common.go new file mode 100644 index 0000000..5a230a8 --- /dev/null +++ b/config.common.go @@ -0,0 +1,337 @@ +package forgepb + +/* + lookup settings for a particular *gitpb.Repo or gopath string + + user settings are configured in ~/.config/forge/forge.text + + // searchs by string + Configs.IsReadOnly(path) // user can't push commits + Configs.IsWritable(path) // the opposite, but maybe different so I put both here + + IsPrivate(repo) // repo can't be published to the pkg.go.dev system + DebName() // for 'zookeeper' returns 'zookeeper-go' + + This code is practical, not perfect +*/ + +import ( + "path/filepath" + "strings" + + "go.wit.com/lib/protobuf/gitpb" +) + +/* +func (all *ForgeConfigs) UpdateGoPath(name string, gopath string) bool { + oldr := all.DeleteByGoPath(name) + if oldr == nil { + // nothing to update + return false + } + + // update gopath and append it back to the list + oldr.GoPath = gopath + return all.Append(oldr) +} +*/ + +// returns true if gopath is readonly() +// will attempt to match IsWritable("foo") against anything ending in "foo" +func (fc *ForgeConfigs) IsReadOnly(gopath string) bool { + var match *ForgeConfig + + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == gopath { + // exact gopath match + if r.Writable { + return false + } + if r.ReadOnly { + return true + } + // private is assumed to be r/w unless above is specifically set + if r.Private { + return false + } + } + // if gopath == "foo" will return false if "go.wit.com/apps/foo" is Writable + base := filepath.Base(r.GoPath) + if base == gopath { + if r.Writable { + return false + } + } + // search for potential dir matches + if r.Directory { + // test the dir + if strings.HasPrefix(gopath, r.GoPath) { + match = r + } + } + } + + if match == nil { + // log.Info("did not match in IsReadOnly()", gopath) + return true + } + + // take the settings from the directory match + if match.Writable { + return false + } + if match.ReadOnly { + return true + } + // private is assumed to be r/w unless above is specifically set + if match.Private { + return false + } + + // always assume readonly + return true +} + +// returns the deb package name +// this let's you check a git tag version against a package .deb version +// allows gopath's to not need to match the .deb name +// this is important in lots of cases! It is normal and happens often enough. +func (fc *ForgeConfigs) DebName(gopath string) string { + // get "zookeeper" from "go.wit.com/apps/zookeeper" + normalBase := filepath.Base(gopath) + + all := fc.SortByGoPath() + for all.Scan() { + r := all.Next() + if r.GoPath == gopath { + // returns "zookeeper-go" for "go.wit.com/apps/zookeeper" + if r.DebName != "" { + // log.Info("FOUND DebName", r.DebName) + return r.DebName + } else { + return normalBase + } + } + } + return normalBase +} + +// a work in progress +func (f *Forge) IsPrivate(repo *gitpb.Repo) bool { + namespace := repo.GetNamespace() + if namespace == "" { + // assume true + return true + } + + return f.Config.IsPrivate(namespace) +} + +// is this a non-publishable repo? +// matches package names from apt +// +// IsPrivate("foo") will match anything in the config file ending in "foo" +// +// IsPrivate("go.foo.com/jcarr/foo") returns true if private +// IsPrivate("foo") also returns true if "go.bar.com/jcarr/foo" is private +func (fc *ForgeConfigs) IsPrivate(thing string) bool { + var match *ForgeConfig + + // sort by path means the simple 'match' logic + // here works in the sense the last directory match + // is the one that is used + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == thing { + // if private is set here, then ok, otherwise + // still check if a Directory match exists + if r.Private { + return true + } + } + base := filepath.Base(r.GoPath) + if base == thing { + if r.Private { + return true + } + } + // check to see if IsPrivate("foo") + // search for potential dir matches + if r.Directory { + // test the dir + if strings.HasPrefix(thing, r.GoPath) { + match = r + } + } + } + if match == nil { + // log.Info("did not match in IsPrivate()", thing) + return false + } + + // otherwise, assume not private + return match.Private +} + +// IsFavorite() -- fun option for the config +// file that lets you set things as favorites +// so you can just go-clone a bunch of common things +// on a new box or after you reset/delete your ~/go/src dir +func (fc *ForgeConfigs) IsFavorite(thing string) bool { + var match *ForgeConfig + + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == thing { + if r.Favorite { + return true + } + } + base := filepath.Base(r.GoPath) + if base == thing { + if r.Favorite { + return true + } + } + if r.Directory { + if strings.HasPrefix(thing, r.GoPath) { + match = r + } + } + } + if match == nil { + return false + } + + return match.Favorite +} + +// IsWritable() checks your .config/forge/ settings +// looks for an exact match, then +// looks for a directory match +func (fc *ForgeConfigs) IsWritable(thing string) bool { + var match *ForgeConfig + + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == thing { + if r.Writable { + return true + } + } + base := filepath.Base(r.GoPath) + if base == thing { + if r.Writable { + return true + } + } + if r.Directory { + if strings.HasPrefix(thing, r.GoPath) { + match = r + } + } + } + if match == nil { + return false + } + + return match.Writable +} + +// allows custom user branch names in the forge config +func (fc *ForgeConfigs) FindUserBranch(thing string) string { + var match *ForgeConfig + + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == thing { + if r.UserBranchName != "" { + return r.UserBranchName + } + } + base := filepath.Base(r.GoPath) + if base == thing { + if r.UserBranchName != "" { + return r.UserBranchName + } + } + if r.Directory { + if strings.HasPrefix(thing, r.GoPath) { + match = r + } + } + } + if match == nil { + return "" + } + + return match.UserBranchName +} + +// allows custom devel branch names in the forge config +func (fc *ForgeConfigs) FindDevelBranch(thing string) string { + var match *ForgeConfig + + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == thing { + if r.DevelBranchName != "" { + return r.DevelBranchName + } + } + base := filepath.Base(r.GoPath) + if base == thing { + if r.DevelBranchName != "" { + return r.DevelBranchName + } + } + if r.Directory { + if strings.HasPrefix(thing, r.GoPath) { + match = r + } + } + } + if match == nil { + return "" + } + + return match.DevelBranchName +} + +// allows custom devel branch names in the forge config +func (fc *ForgeConfigs) FindMasterBranch(thing string) string { + var match *ForgeConfig + + all := fc.SortByGoPath() // get the list of repos + for all.Scan() { + r := all.Next() + if r.GoPath == thing { + if r.MasterBranchName != "" { + return r.MasterBranchName + } + } + base := filepath.Base(r.GoPath) + if base == thing { + if r.MasterBranchName != "" { + return r.MasterBranchName + } + } + if r.Directory { + if strings.HasPrefix(thing, r.GoPath) { + match = r + } + } + } + if match == nil { + return "" + } + + return match.MasterBranchName +} diff --git a/config.go b/config.go index d5d80c8..8442933 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,8 @@ package forgepb import ( + "os" + "go.wit.com/lib/config" "go.wit.com/lib/gui/prep" "go.wit.com/log" @@ -32,3 +34,59 @@ func (f *Forge) ConfigSave() error { } return err } + +// functions to import and export the protobuf +// data to and from config files + +// write to ~/.config/forge/ +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) +} + +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 cfg.ReposPB != os.Getenv("FORGE_REPOSPB") { + log.Infof("CfgPB file problem: cfg.ReposPB=%s != FORGE_REPOSPB=%s\n", cfg.ReposPB, os.Getenv("FORGE_REPOSPB")) + } +} + +/* + if f.Config.Username == "" { + usr, _ := user.Current() + f.Config.Username = usr.Username + f.SetConfigSave(true) + } + + if f.Config.Xterm == "" { + f.Config.Xterm = "xterm" + f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg") + f.Config.XtermArgv = append(f.Config.XtermArgv, "black") + f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg") + f.Config.XtermArgv = append(f.Config.XtermArgv, "white") + f.SetConfigSave(true) + } +*/ + +func (cfg *ForgeConfigs) InitDefaults() { +} diff --git a/forgeConfig.common.go b/forgeConfig.common.go deleted file mode 100644 index 5a230a8..0000000 --- a/forgeConfig.common.go +++ /dev/null @@ -1,337 +0,0 @@ -package forgepb - -/* - lookup settings for a particular *gitpb.Repo or gopath string - - user settings are configured in ~/.config/forge/forge.text - - // searchs by string - Configs.IsReadOnly(path) // user can't push commits - Configs.IsWritable(path) // the opposite, but maybe different so I put both here - - IsPrivate(repo) // repo can't be published to the pkg.go.dev system - DebName() // for 'zookeeper' returns 'zookeeper-go' - - This code is practical, not perfect -*/ - -import ( - "path/filepath" - "strings" - - "go.wit.com/lib/protobuf/gitpb" -) - -/* -func (all *ForgeConfigs) UpdateGoPath(name string, gopath string) bool { - oldr := all.DeleteByGoPath(name) - if oldr == nil { - // nothing to update - return false - } - - // update gopath and append it back to the list - oldr.GoPath = gopath - return all.Append(oldr) -} -*/ - -// returns true if gopath is readonly() -// will attempt to match IsWritable("foo") against anything ending in "foo" -func (fc *ForgeConfigs) IsReadOnly(gopath string) bool { - var match *ForgeConfig - - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == gopath { - // exact gopath match - if r.Writable { - return false - } - if r.ReadOnly { - return true - } - // private is assumed to be r/w unless above is specifically set - if r.Private { - return false - } - } - // if gopath == "foo" will return false if "go.wit.com/apps/foo" is Writable - base := filepath.Base(r.GoPath) - if base == gopath { - if r.Writable { - return false - } - } - // search for potential dir matches - if r.Directory { - // test the dir - if strings.HasPrefix(gopath, r.GoPath) { - match = r - } - } - } - - if match == nil { - // log.Info("did not match in IsReadOnly()", gopath) - return true - } - - // take the settings from the directory match - if match.Writable { - return false - } - if match.ReadOnly { - return true - } - // private is assumed to be r/w unless above is specifically set - if match.Private { - return false - } - - // always assume readonly - return true -} - -// returns the deb package name -// this let's you check a git tag version against a package .deb version -// allows gopath's to not need to match the .deb name -// this is important in lots of cases! It is normal and happens often enough. -func (fc *ForgeConfigs) DebName(gopath string) string { - // get "zookeeper" from "go.wit.com/apps/zookeeper" - normalBase := filepath.Base(gopath) - - all := fc.SortByGoPath() - for all.Scan() { - r := all.Next() - if r.GoPath == gopath { - // returns "zookeeper-go" for "go.wit.com/apps/zookeeper" - if r.DebName != "" { - // log.Info("FOUND DebName", r.DebName) - return r.DebName - } else { - return normalBase - } - } - } - return normalBase -} - -// a work in progress -func (f *Forge) IsPrivate(repo *gitpb.Repo) bool { - namespace := repo.GetNamespace() - if namespace == "" { - // assume true - return true - } - - return f.Config.IsPrivate(namespace) -} - -// is this a non-publishable repo? -// matches package names from apt -// -// IsPrivate("foo") will match anything in the config file ending in "foo" -// -// IsPrivate("go.foo.com/jcarr/foo") returns true if private -// IsPrivate("foo") also returns true if "go.bar.com/jcarr/foo" is private -func (fc *ForgeConfigs) IsPrivate(thing string) bool { - var match *ForgeConfig - - // sort by path means the simple 'match' logic - // here works in the sense the last directory match - // is the one that is used - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == thing { - // if private is set here, then ok, otherwise - // still check if a Directory match exists - if r.Private { - return true - } - } - base := filepath.Base(r.GoPath) - if base == thing { - if r.Private { - return true - } - } - // check to see if IsPrivate("foo") - // search for potential dir matches - if r.Directory { - // test the dir - if strings.HasPrefix(thing, r.GoPath) { - match = r - } - } - } - if match == nil { - // log.Info("did not match in IsPrivate()", thing) - return false - } - - // otherwise, assume not private - return match.Private -} - -// IsFavorite() -- fun option for the config -// file that lets you set things as favorites -// so you can just go-clone a bunch of common things -// on a new box or after you reset/delete your ~/go/src dir -func (fc *ForgeConfigs) IsFavorite(thing string) bool { - var match *ForgeConfig - - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == thing { - if r.Favorite { - return true - } - } - base := filepath.Base(r.GoPath) - if base == thing { - if r.Favorite { - return true - } - } - if r.Directory { - if strings.HasPrefix(thing, r.GoPath) { - match = r - } - } - } - if match == nil { - return false - } - - return match.Favorite -} - -// IsWritable() checks your .config/forge/ settings -// looks for an exact match, then -// looks for a directory match -func (fc *ForgeConfigs) IsWritable(thing string) bool { - var match *ForgeConfig - - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == thing { - if r.Writable { - return true - } - } - base := filepath.Base(r.GoPath) - if base == thing { - if r.Writable { - return true - } - } - if r.Directory { - if strings.HasPrefix(thing, r.GoPath) { - match = r - } - } - } - if match == nil { - return false - } - - return match.Writable -} - -// allows custom user branch names in the forge config -func (fc *ForgeConfigs) FindUserBranch(thing string) string { - var match *ForgeConfig - - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == thing { - if r.UserBranchName != "" { - return r.UserBranchName - } - } - base := filepath.Base(r.GoPath) - if base == thing { - if r.UserBranchName != "" { - return r.UserBranchName - } - } - if r.Directory { - if strings.HasPrefix(thing, r.GoPath) { - match = r - } - } - } - if match == nil { - return "" - } - - return match.UserBranchName -} - -// allows custom devel branch names in the forge config -func (fc *ForgeConfigs) FindDevelBranch(thing string) string { - var match *ForgeConfig - - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == thing { - if r.DevelBranchName != "" { - return r.DevelBranchName - } - } - base := filepath.Base(r.GoPath) - if base == thing { - if r.DevelBranchName != "" { - return r.DevelBranchName - } - } - if r.Directory { - if strings.HasPrefix(thing, r.GoPath) { - match = r - } - } - } - if match == nil { - return "" - } - - return match.DevelBranchName -} - -// allows custom devel branch names in the forge config -func (fc *ForgeConfigs) FindMasterBranch(thing string) string { - var match *ForgeConfig - - all := fc.SortByGoPath() // get the list of repos - for all.Scan() { - r := all.Next() - if r.GoPath == thing { - if r.MasterBranchName != "" { - return r.MasterBranchName - } - } - base := filepath.Base(r.GoPath) - if base == thing { - if r.MasterBranchName != "" { - return r.MasterBranchName - } - } - if r.Directory { - if strings.HasPrefix(thing, r.GoPath) { - match = r - } - } - } - if match == nil { - return "" - } - - return match.MasterBranchName -} diff --git a/forgeConfig.config.go b/forgeConfig.config.go deleted file mode 100644 index b5080a6..0000000 --- a/forgeConfig.config.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2025 WIT.COM Inc Licensed GPL 3.0 - -package forgepb - -import ( - "os" - - "go.wit.com/lib/config" - "go.wit.com/log" -) - -// functions to import and export the protobuf -// data to and from config files - -// write to ~/.config/forge/ -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 += "#\n" - header += "\n" - return config.ConfigSaveWithHeader(cfg, header) -} - -func (cfg *ForgeConfigs) DumpENV() { - if cfg.ReposPB != os.Getenv("FORGE_REPOSPB") { - log.Infof("RepoPB file problem: cfg.ReposPB=%s != FORGE_REPOSPB=%s\n", cfg.ReposPB, os.Getenv("FORGE_REPOSPB")) - } -} - -// load the ~/.config/forge/ files -func (c *ForgeConfigs) ConfigLoad(fullpath string) error { - return nil -} - -/* - if f.Config.Username == "" { - usr, _ := user.Current() - f.Config.Username = usr.Username - f.SetConfigSave(true) - } - - if f.Config.Xterm == "" { - f.Config.Xterm = "xterm" - f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg") - f.Config.XtermArgv = append(f.Config.XtermArgv, "black") - f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg") - f.Config.XtermArgv = append(f.Config.XtermArgv, "white") - f.SetConfigSave(true) - } -*/ diff --git a/forgeConfig.proto b/forgeConfig.proto index 14ef1ef..ec081d0 100644 --- a/forgeConfig.proto +++ b/forgeConfig.proto @@ -55,7 +55,7 @@ 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 diff --git a/init.go b/init.go index 6ade4ab..9243e9d 100644 --- a/init.go +++ b/init.go @@ -24,17 +24,26 @@ func Default(opts ...OptionFunc) *Engine { */ func Init() *Forge { + f := new(Forge) cfg := new(ForgeConfigs) err := config.ConfigLoad(cfg, "forge", "forge") + f.Config = cfg if err != nil { - log.Info("forge has not been configured yet filename =", cfg.Filename) - log.Info("go install go.wit.com/apps/forge@latest") - os.Exit(-1) + // 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 } - f := initFromConfig(cfg) + 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:") + // fhelp.DumpENV("finit:") f.Config.DumpENV() } return f @@ -48,14 +57,14 @@ func InitByAppname(argname string) *Forge { log.Info("go install go.wit.com/apps/forge@latest") os.Exit(-1) } - f := initFromConfig(cfg) + f := new(Forge) + f.Config = cfg + f.initFromConfig() log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len()) return f } -func initFromConfig(cfg *ForgeConfigs) *Forge { - f := new(Forge) - f.Config = cfg +func (f *Forge) initFromConfig() { if f.configENV() { log.Info("ENV changed config. todo: save config here") f.Config.ConfigSave() @@ -80,9 +89,12 @@ func initFromConfig(cfg *ForgeConfigs) *Forge { f.Patchsets = NewPatchsets() // todo: play with these / determine good values based on user's machine - f.Config.RillX = 10 - f.Config.RillY = 20 - return f + if f.Config.RillX == 0 { + f.Config.RillX = 10 + } + if f.Config.RillY == 0 { + f.Config.RillY = 20 + } } func (f *Forge) SetConfigSave(b bool) { @@ -93,8 +105,9 @@ func (f *Forge) SetConfigSave(b bool) { func (f *Forge) Exit() { // log.Info("forge.configSave =", f.configSave) if f.Config.Mode == ForgeMode_MASTER { - fhelp.DumpENV("forge:") - f.Config.DumpENV() + // fhelp.DumpENV("forge:") + // f.Config.DumpENV() + // todo: tell the user to switch to NORMAL mode } f.ConfigSave() @@ -114,6 +127,7 @@ func (f *Forge) Exit() { // 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) @@ -128,9 +142,11 @@ func (f *Forge) setenv() { f.goWork = true } - f.Config.ReposPB = os.Getenv("FORGE_REPOPB") + 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()") }) } -- cgit v1.2.3