Compare commits

..

No commits in common. "master" and "v0.0.103" have entirely different histories.

32 changed files with 1079 additions and 1845 deletions

View File

@ -5,7 +5,7 @@
# go install
all: forgeConfig.pb.go patch.pb.go set.pb.go goimports vet
all: forgeConfig.pb.go patchset.pb.go goimports vet
generate: clean
autogenpb --proto patchset.proto
@ -22,24 +22,10 @@ goimports:
clean:
rm -f *.pb.go *.patch
-rm -f go.*
go-mod-clean purge
go-mod-clean --purge
forgeConfig.pb.go: forgeConfig.proto
autogenpb --proto forgeConfig.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
patchset.pb.go: patchset.proto
autogenpb --proto patchset.proto

View File

@ -1,316 +0,0 @@
package forgepb
import (
"path/filepath"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
var ErrorNotAllReposOnMaster error = log.Errorf("not all repos on are on the master branch")
var ErrorNotAllReposOnDevel error = log.Errorf("not all repos on are on the devel branch")
var ErrorNotAllReposOnUser error = log.Errorf("not all repos on are on the user branch")
func (f *Forge) IsEverythingOnMaster() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
for repo := range f.Repos.IterAll() {
total += 1
if repo.GetMasterBranchName() == repo.GetCurrentBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
// log.Info(ErrorNotAllReposOnMaster)
return total, count, nope, ErrorNotAllReposOnMaster
}
return total, count, nope, nil
}
func (f *Forge) IsEverythingOnDevel() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
for repo := range f.Repos.IterAll() {
total += 1
if repo.GetDevelBranchName() == repo.GetCurrentBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
return total, count, nope, ErrorNotAllReposOnDevel
}
return total, count, nope, nil
}
func (f *Forge) IsEverythingOnUser() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
for repo := range f.Repos.IterAll() {
total += 1
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
return total, count, nope, ErrorNotAllReposOnUser
}
return total, count, nope, nil
}
// trys to figure out if there is still something to update
func (f *Forge) DoAllCheckoutMaster() error {
now := time.Now()
f.RillFuncError(rillCheckoutMaster)
count := f.RillReload()
if count != 0 {
config.SetChanged("repos", true)
}
total, count, nope, err := f.IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on master
found := new(gitpb.Repos)
all := f.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
found.Append(repo)
}
}
f.PrintHumanTable(found)
log.Printf("There are %d repos that are NOT on the master branch\n", found.Len())
return err
}
return nil
}
func rillCheckoutMaster(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
// 'giterr' means something is very wrong with this repo
if repo.GetMasterVersion() == "giterr" {
repo.CheckoutMaster()
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
return nil
}
repo.CheckoutMaster()
return nil
}
func rillCheckoutUser(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on devel branch. have to move them there first for now
// return repo.CheckoutDevel()
}
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
// repo is already on user branch
return nil
}
if err := repo.CheckoutUser(); err != nil {
log.Info(repo.GetFullPath(), err)
return err
}
return nil
}
// trys to figure out if there is still something to update
func (f *Forge) DoAllCheckoutUser(force bool) error {
now := time.Now()
if force {
log.Info("going to force create user branches")
if err := f.makeUserBranches(); err != nil {
return err
}
}
f.RillFuncError(rillCheckoutUser)
count := f.RillReload()
if count != 0 {
config.SetChanged("repos", true)
}
total, count, nope, err := f.IsEverythingOnUser()
log.Printf("User branch check. %d total repos. (%d ok) (%d not on user branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on user
found := new(gitpb.Repos)
all := f.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
found.Append(repo)
}
}
f.PrintHumanTable(found)
log.Printf("There are %d repos that are NOT on the user branch\n", found.Len())
return err
}
return nil
}
func (f *Forge) makeUserBranches() error {
all := f.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
branch := repo.GetUserBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
continue
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
continue
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
}
return nil
}
func testReload(repo *gitpb.Repo) error {
if !repo.HasChanged() {
return nil
}
repo.ReloadCheck()
return log.Errorf("repo changed")
}
func (f *Forge) DoAllCheckoutDevelNew(force bool) error {
f.makeDevelBranches()
// first run git checkout
stats := f.RillFuncError(rillCheckoutDevel)
for path, stat := range stats {
dur := stat.End.Sub(stat.Start)
if dur > 1*time.Second {
log.Infof("%s git checkout took a long time (%s)\n", path, shell.FormatDuration(dur))
}
if stat.Err == nil {
// there was no error
continue
}
// didn't change to devel
}
var counter int
// recreate the repo protobuf
stats = f.RillFuncError(testReload)
for path, stat := range stats {
dur := stat.End.Sub(stat.Start)
if dur > 1*time.Second {
log.Infof("%s # Reload took a long time (%s)\n", path, shell.FormatDuration(dur))
}
if stat.Err == nil {
// repo didn't reload
continue
}
// repo reloaded
counter += 1
}
log.Info("reloaded", counter, "repos")
config.SetChanged("repos", true)
return nil
}
// is every repo on the devel branch?
func (f *Forge) DoAllCheckoutDevel(force bool) error {
now := time.Now()
if force {
log.Info("going to force create devel branches")
f.makeDevelBranches()
}
log.Info("going to rill:")
f.RillFuncError(rillCheckoutDevel)
count := f.RillReload()
if count != 0 {
config.SetChanged("repos", true)
}
total, count, nope, err := f.IsEverythingOnDevel()
log.Printf("Devel branch check. %d total repos. (%d ok) (%d not on devel branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on user
found := new(gitpb.Repos)
all := f.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
found.Append(repo)
}
}
f.PrintHumanTable(found)
log.Printf("There are %d repos that are NOT on the devel branch\n", found.Len())
return err
}
return nil
}
func (f *Forge) makeDevelBranches() error {
all := f.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
branch := repo.GetDevelBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
continue
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
continue
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
}
return nil
}
func rillCheckoutDevel(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetDevelBranchName() {
// repo is already on devel branch
return nil
}
repo.CheckoutDevel()
return nil
}

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

View File

@ -20,8 +20,7 @@ var cleanVerbose bool = false
func (f *Forge) CleanGoDepsCheckOk(check *gitpb.Repo) error {
var err error = nil
var fixes [][]string
log.Printf("%s repo go dependancy count: %d\n", check.GetGoPath(), check.GoDepsLen())
log.Printf("current repo %s go dependancy count: %d", check.GetGoPath(), check.GoDepsLen())
if check.GoDeps == nil {
return errors.New("check.GoDeps == nil")
}
@ -49,20 +48,20 @@ func (f *Forge) CleanGoDepsCheckOk(check *gitpb.Repo) error {
if f.Config.IsReadOnly(check.GetGoPath()) {
ends += "(ignoring read-only) "
if cleanVerbose {
log.Printf("%-48s ok error .%s. vs .%s. %s\n", depRepo.GetGoPath(),
log.Printf("%-48s ok error .%s. vs .%s. %s", depRepo.GetGoPath(),
depRepo.GetVersion(), found.GetMasterVersion(), ends)
}
} else {
if f.CheckOverride(depRepo.GetGoPath()) {
ends += "(override) "
if cleanVerbose {
log.Printf("%-48s ok error .%s. vs .%s. %s\n", depRepo.GetGoPath(),
log.Printf("%-48s ok error .%s. vs .%s. %s", depRepo.GetGoPath(),
depRepo.GetVersion(), found.GetMasterVersion(), ends)
// skip this gopath because it's probably broken forever
}
continue
} else {
log.Printf("%-48s error %10s vs %10s %s\n", depRepo.GetGoPath(),
log.Printf("%-48s error %10s vs %10s %s", depRepo.GetGoPath(),
depRepo.GetVersion(), found.GetMasterVersion(), ends)
errs := fmt.Sprintf("%s error %s vs %s %s", depRepo.GetGoPath(),
depRepo.GetVersion(), found.GetMasterVersion(), ends)

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.Config.ReposDir, gopath)
fullpath := filepath.Join(f.goSrc, gopath)
if pb := f.FindAnyPath(fullpath); pb != nil {
// repo already exists
return pb, nil
@ -101,12 +101,6 @@ func overridePath(gopath string) string {
if strings.HasPrefix(gopath, "github.com/go-gl/glfw") {
return "https://github.com/go-gl/glfw"
}
if strings.HasPrefix(gopath, "cloud.google.com/go") {
return "https://github.com/googleapis/google-cloud-go"
}
if strings.HasPrefix(gopath, "go.opentelemetry.io/contrib") {
return "https://github.com/open-telemetry/opentelemetry-go-contrib"
}
return ""
}
@ -156,7 +150,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.Config.ReposDir, gopath)
fullpath := filepath.Join(f.goSrc, gopath)
basedir, newdir := filepath.Split(fullpath)
// clone the URL directly
@ -179,10 +173,6 @@ func (f *Forge) urlClone(gopath, giturl string) (*gitpb.Repo, error) {
// for example:
// This will check go.wit.com for "go.wit.com/apps/go-clone"
// and return where the URL to do git clone should be
func FindGoImport(url string) (string, error) {
return findGoImport(url)
}
func findGoImport(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
@ -224,38 +214,3 @@ func findGoImport(url string) (string, error) {
return newurl, nil
}
// GetNamesapce removes http://, https://, and .git suffix from the given URL if present.
func GetNamespace(url string) string {
// Trim protocol prefix
if strings.HasPrefix(url, "http://") {
url = strings.TrimPrefix(url, "http://")
} else if strings.HasPrefix(url, "https://") {
url = strings.TrimPrefix(url, "https://")
}
// Trim trailing .git
url = strings.TrimSuffix(url, ".git")
return url
}
func (f *Forge) Clone(url string) (*gitpb.Repo, error) {
ns := GetNamespace(url)
if ns == url {
return nil, errors.New("todo: forgepb.Clone() fix url parsing")
}
//
// returns repo if namespace already exists
if repo := f.Repos.FindByNamespace(ns); repo != nil {
log.Info("FindByNamespace() worked = ", ns)
return repo, nil
}
if repo, _ := f.urlClone(ns, url); repo != nil {
return repo, nil
}
return nil, errors.New("todo: forgepb.Clone() url failed " + url)
}

207
config.go
View File

@ -2,85 +2,180 @@
package forgepb
import (
"os"
// 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
if f.Config == nil {
return log.Errorf("forge.Config == nil")
// backup the current config files
if e := backupConfig(); e != nil {
log.Info("forge.BackupConfig() error", e)
err = e
// continue here? notsure. could be bad either way
// out of disk space?
}
if 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 f.Config != nil {
if e := f.Config.ConfigSave(); e != nil {
log.Info("forge.Config.ConfigSave() error", e)
err = e
}
}
if f.Repos != nil {
if f.HasFullScan() {
f.Repos.HasFullScan = true
t := time.Now()
f.Repos.FullScan = timestamppb.New(t)
}
if e := f.Repos.ConfigSave(); e != nil {
log.Info("forge.Repos.ConfigSave() error", e)
err = e
}
}
return err
}
// functions to import and export the protobuf
// data to and from config files
// write to ~/.config/forge/ unless ENV{FORGE_CONFIG} is set
func (f *ForgeConfigs) ConfigSave() error {
data, err := f.Marshal()
if err != nil {
log.Info("proto.Marshal() failed len", len(data), err)
return err
}
// log.Info("forgepb.ConfigSave() proto.Marshal() worked len", len(data))
// 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)
s := f.FormatTEXT()
configWrite("forge.text", []byte(s))
s = f.FormatJSON()
configWrite("forge.json", []byte(s))
return 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)
// load the ~/.config/forge/ files
func (c *ForgeConfigs) ConfigLoad() error {
if os.Getenv("FORGE_CONFIG") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_CONFIG", fullpath)
}
// 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")
}
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 err := c.loadText(); err == nil {
return nil
}
// 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
}
}
}
}
/*
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)
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")
}
*/
func (cfg *ForgeConfigs) InitDefaults() {
// 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")
}
// attempt to marshal forge.text
if err := c.UnmarshalTEXT(data); err != nil {
return err
}
log.Log(INFO, "forge.ConfigLoad()", len(c.ForgeConfigs), "entries in ~/.config/forge")
return nil
}
func loadFile(filename string) ([]byte, error) {
fullname := filepath.Join(os.Getenv("FORGE_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
}

74
configBackup.go Normal file
View File

@ -0,0 +1,74 @@
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 backupConfig() error {
// make a new dir to backup the files
srcDir := filepath.Join(os.Getenv("FORGE_CONFIG"))
destDir := filepath.Join(os.Getenv("FORGE_CONFIG"), "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
}

15
configDefault.go Normal file
View File

@ -0,0 +1,15 @@
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

@ -18,8 +18,6 @@ package forgepb
import (
"path/filepath"
"strings"
"go.wit.com/lib/protobuf/gitpb"
)
/*
@ -118,17 +116,6 @@ func (fc *ForgeConfigs) DebName(gopath string) string {
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
//

View File

@ -1,99 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
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"
)
func (f *Forge) CheckDirtyQuiet() {
start := f.straightCheckDirty()
now := time.Now()
stats := f.RillRepos(doCheckDirty)
end := f.straightCheckDirty()
diff := end - start
var changed bool
for _, s := range stats {
if s.Err == nil {
} else {
config.SetChanged("repos", true)
changed = true
}
}
if changed {
log.Printf("dirty check (%d dirty repos) (%d total repos) (%d changed) took:%s\n", end, f.Repos.Len(), diff, shell.FormatDuration(time.Since(now)))
}
}
func (f *Forge) CheckDirty() *gitpb.Repos {
start := f.straightCheckDirty()
now := time.Now()
stats := f.RillRepos(doCheckDirty)
end := f.straightCheckDirty()
diff := end - start
log.Printf("dirty check (%d dirty repos) (%d total repos) (%d changed) took:%s\n", end, f.Repos.Len(), diff, shell.FormatDuration(time.Since(now)))
for i, s := range stats {
if s.Err == nil {
} else {
log.Info(i, s.Err)
config.SetChanged("repos", true)
}
}
return f.FindDirty()
}
func (f *Forge) straightCheckDirty() int {
var count int
for repo := range f.Repos.IterAll() {
if repo.IsDirty() {
count += 1
}
}
return count
}
func doCheckDirty(repo *gitpb.Repo) error {
// reset these in here for now
if repo.GetTargetVersion() != "" {
repo.TargetVersion = ""
}
if repo.IsDirty() {
if repo.CheckDirty() {
// nothing changed
} else {
log.Info("Repo changed to clean", repo.FullPath)
return log.Errorf("%s repo changed to clean", repo.FullPath)
}
} else {
if repo.CheckDirty() {
log.Info("Repo changed to dirty", repo.FullPath)
return log.Errorf("%s repo changed to dirty", repo.FullPath)
} else {
// nothing changed
}
}
return nil
}
func (f *Forge) FindDirty() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range f.Repos.IterByFullPath() {
if repo.IsDirty() {
found.AppendByFullPath(repo)
}
}
return found
}

View File

@ -72,39 +72,38 @@ func (f *Forge) FinalGoDepsCheckOk(check *gitpb.Repo, verbose bool) error {
return nil
}
// TODO: this is a dumb hack & needs to be updated to talk to forged
func (f *Forge) CheckOverride(gopath string) bool {
if gopath == "cloud.google.com/go" {
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
if gopath == "bou.ke/monkey" {
log.Info("CheckOverride() is bypassing", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
if gopath == "github.com/posener/complete/v2" {
log.Info("CheckOverride() is bypassing", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
if strings.HasPrefix(gopath, "github.com/go-gl") {
log.Info("CheckOverride() is bypassing", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
if strings.HasPrefix(gopath, "google.golang.org") {
log.Info("CheckOverride() is bypassing", gopath)
return true
}
if strings.HasPrefix(gopath, "cloud.google.com/go") {
log.Info("CheckOverride() is bypassing", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
if strings.HasPrefix(gopath, "go.opencensus.io") {
log.Info("CheckOverride() is bypassing", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
if strings.HasPrefix(gopath, "github.com/nicksnyder/go-i18n") {
log.Info("CheckOverride() is bypassing", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
// fuckit for now. just blacklist github.com
if strings.HasPrefix(gopath, "github.com/") {
log.Info("CheckOverride() is ignoring everything at github.com", gopath)
// log.Info("CheckOverride() is ignoring", gopath)
return true
}
return false

View File

@ -14,7 +14,6 @@ import "google/protobuf/timestamp.proto"; // Import the well-known type for Time
// package names sometimes must be different than the binary name
// for example 'zookeeper' is packaged as 'zookeeper-go'
// due to the prior apache foundation project. This happens and is ok!
message ForgeConfig { // `autogenpb:nomutex`
string goPath = 1; // `autogenpb:unique` `autogenpb:sort` // Examples: 'go.wit.com/apps/go-clone' or "~/mythings" or "/home/src/foo"
@ -35,15 +34,13 @@ message ForgeConfig { // `autogenpb:nom
google.protobuf.Timestamp verstamp = 12; // the git commit timestamp of the version
string goSrc = 13; // is ~/go/src unless a go.work file is found
string namespace = 14; // `autogenpb:unique` `autogenpb:sort`
}
// todo: fix autogenpb to look for enum
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`
@ -55,15 +52,6 @@ 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
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

View File

@ -84,10 +84,6 @@ type Origin struct {
// A Tags describes the available tags in a code repository.
func RunGoList(url string) (string, error) {
return runGoList(url)
}
func runGoList(url string) (string, error) {
ver, err := getLatestVersion(url)
if err != nil {

90
goSrcFind.go Normal file
View File

@ -0,0 +1,90 @@
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,115 +1,56 @@
package forgepb
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"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) ScanGoSrc() (bool, error) {
dirs, err := gitDirectoriesNew(f.goSrc)
if err != nil {
return false, err
}
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 {
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
}
config.SetChanged("repos", true)
gopaths = append(gopaths, gopath)
} else {
log.Log(WARN, "ScanGoSrc() bad:", dir)
return false, errors.New("forgepb.ScanGoSrc() bad dir: " + dir)
}
newcount, err := f.rillScanDirsNew(dirs)
}
newcount, err := f.rillScanDirs(gopaths)
if err != nil {
log.Info("go src dir problem. exit for now?", err)
return err
return false, err
}
if newcount != 0 {
log.Info("forge go src scan found", newcount, "repos")
config.SetChanged("repos", true)
f.SetConfigSave(true)
}
return err
return true, 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
func (f *Forge) ScanDir(dir string) *gitpb.Repo {
// repo, err := f.NewGoRepo(gopath, "")
repo, err := f.Repos.NewGoRepo(dir, "")
log.Info("need to implement ScanDir()", dir, err)
return repo
}
// doesn't enter the directory any further when it finds a .git/
@ -132,7 +73,6 @@ func gitDirectoriesNew(srcDir string) ([]string, error) {
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 {
@ -168,3 +108,69 @@ func gitDirectoriesNew(srcDir string) ([]string, error) {
}
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
}

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.Config.ReposDir, "go.work")
filename := filepath.Join(f.GetGoSrc(), "go.work")
workf, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
@ -32,10 +32,26 @@ 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

134
http.go
View File

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

View File

@ -61,7 +61,14 @@ func (f *Forge) HumanPrintRepo(check *gitpb.Repo) {
log.Info("git current name ==", check.GetCurrentBranchName())
// testNext(check)
// f.PrintHumanTable(check)
found := new(gitpb.Repos)
if !found.AppendByGoPath(check) {
log.Info("forgepb check. repo already existed", check.FullPath, check.GetGoPath())
} else {
log.Info("forgepb check. repo was new", check.FullPath, check.GetGoPath())
}
f.PrintHumanTable(found)
printTime("Last Pull", check.Times.LastPull.AsTime())
printTime("Last Dirty", check.Times.LastDirty.AsTime())

View File

@ -3,160 +3,186 @@
package forgepb
import (
"fmt"
"path/filepath"
"go.wit.com/lib/cobol"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// you can replace all of COBOL with this amount of GO
// ah yes, COBOL. what an ancient throwback. for those that know
// then you know exactly what is in this file. For those that don't, here it is:
// All this does is output human readable text formatted to be viewable on
// a console with a fixed with font. AKA: a typerwriter
// a console with a fixed with font. AKA: a typerwriter. Which is exactly
// what COBOL did in the 1970's (60s? notsure) And the 80s.
// So, you want to dump out stuff on the console. Let's see. Something like
/*
forge --favorites
go.wit.com/apps/myapp v0.2.0 (installed)
go.wit.com/lib/somethingfun v0.0.7 (not downloaded)
*/
// anyway, you get the idea. This is also called COBOL because it does on
// thing and truncates every line output to the columns you see with stty -a
// my monitor is huge, so it's not going to work at 80x24. 160x48 is better
// actually, I'd predict some of these will probably end up 240 wide
// long live good eyesight and 4K monitors!
func (f *Forge) PrintHumanTable(allr *gitpb.Repos) {
log.DaemonMode(true)
t := new(tally)
// print the header
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)))
var count int
// log.Info(standardStart5("gopath", "cur name", "master", "user", "repo type"))
log.Info(standardTable10("repopath", "cur br", "age", "master", "devel", "user", "curver", "lasttag", "next", "repo type"))
all := allr.SortByFullPath()
for all.Scan() {
repo := all.Next()
f.printRepoToTable(repo, sizes, false)
tallyBranchTotals(t, repo)
f.printRepoToTable(repo)
count += 1
}
log.Infof("Total repositories: %d (%d master) (%d devel) (%d user) (%d unknown)\n", t.total, t.master, t.devel, t.user, t.unknown)
}
func (f *Forge) PrintForgedTable(allr *gitpb.Repos) {
log.DaemonMode(true)
t := new(tally)
// print the header
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()
for all.Scan() {
repo := all.Next()
f.printForgedToTable(repo, sizes)
tallyBranchTotals(t, repo)
}
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) {
log.DaemonMode(true)
t := new(tally)
// print the header
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()
for all.Scan() {
repo := all.Next()
f.printRepoToTable(repo, sizes, true)
tallyBranchTotals(t, repo)
}
log.Infof("Total repositories: %d (%d user) (%d devel) (%d master) (%d unknown)\n", t.total, t.user, t.devel, t.master, t.unknown)
log.Info("Total git repositories:", count)
}
// also shows which files are dirty
func (f *Forge) PrintHumanTableDirty(allr *gitpb.Repos) {
log.DaemonMode(true)
t := new(tally)
// print the header
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)))
var count int
// log.Info(standardStart5("gopath", "cur name", "master", "user", "repo type"))
log.Info(standardTable10("repopath", "cur br", "age", "master", "devel", "user", "curver", "lasttag", "next", "repo type"))
// all := allr.SortByFullPath()
for repo := range allr.IterAll() {
f.printRepoToTable(repo, sizes, false)
f.printRepoToTable(repo)
if len(repo.DirtyList) != 0 {
for _, line := range repo.DirtyList {
log.Info(cobol.TerminalChomp("\t" + line))
log.Info("\t", line)
}
}
var mver string = repo.GetMasterVersion()
repo.Tags.GetAge(mver)
tallyBranchTotals(t, repo)
count += 1
}
log.Infof("Total repositories: %d (%d user) (%d devel) (%d master) (%d unknown)\n", t.total, t.user, t.devel, t.master, t.unknown)
log.Info("Total git repositories:", count)
}
// used to count which repos are on which branches (master/main, devel, user)
type tally struct {
total int
master int
devel int
user int
unknown int
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 tallyBranchTotals(t *tally, repo *gitpb.Repo) {
t.total += 1
func standardTable10(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 string) string {
len1 := 40
len2 := 12
len3 := 6
len4 := 12
len5 := 16
len6 := 16
len7 := 16
len8 := 12
len9 := 12
len10 := 8
var s string
if len(arg1) > len1 {
arg1 = arg1[:len1]
}
s = "%-" + fmt.Sprintf("%d", len1) + "s "
if len(arg2) > len2 {
arg2 = arg2[:len2]
}
s += "%-" + fmt.Sprintf("%d", len2) + "s "
if len(arg3) > len3 {
arg3 = arg3[:len3]
}
s += "%-" + fmt.Sprintf("%d", len3) + "s "
if len(arg4) > len4 {
arg4 = arg4[:len4]
}
s += "%-" + fmt.Sprintf("%d", len4) + "s "
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
t.master += 1
return
if len(arg5) > len5 {
arg5 = arg5[:len5]
}
if repo.GetCurrentBranchName() == repo.GetDevelBranchName() {
t.devel += 1
return
s += "%-" + fmt.Sprintf("%d", len5) + "s "
if len(arg6) > len6 {
arg6 = arg6[:len6]
}
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
t.user += 1
return
s += "%-" + fmt.Sprintf("%d", len6) + "s "
if len(arg7) > len7 {
arg7 = arg7[:len7]
}
log.Printf("unknown curr=%s user=%s devel=%s master=%s\n", repo.GetCurrentBranchName(), repo.GetUserBranchName(), repo.GetDevelBranchName(), repo.GetMasterBranchName())
t.unknown += 1
s += "%-" + fmt.Sprintf("%d", len7) + "s "
if len(arg8) > len8 {
arg8 = arg8[:len8]
}
s += "%-" + fmt.Sprintf("%d", len8) + "s "
if len(arg9) > len9 {
arg9 = arg9[:len9]
}
s += "%-" + fmt.Sprintf("%d", len9) + "s "
if len(arg10) > len10 {
arg10 = arg10[:len10]
}
s += "%-" + fmt.Sprintf("%d", len10) + "s "
return fmt.Sprintf(s, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
}
func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
func (f *Forge) printRepoToTable(repo *gitpb.Repo) {
var end string
// shortened version numbers
var mhort string = repo.GetMasterVersion()
var dhort string = repo.GetDevelVersion()
var uver string = repo.GetUserVersion()
if uver == "uerr" {
var uhort string = repo.GetUserVersion()
if uhort == "uerr" {
// blank these out
uver = ""
uhort = ""
}
var lasttag string = repo.GetLastTag()
var thort string = repo.GetTargetVersion()
var chort string = "" // repo.GetCurrentBranchVersion()
var chort string = repo.GetCurrentBranchVersion()
var cname string = repo.GetCurrentBranchName()
var gopath string = repo.GetNamespace()
if gopath == "" {
gopath = repo.GetFullPath()
}
var gopath string = repo.GetGoPath()
var rtype string = repo.GetRepoType()
switch rtype {
case "binary":
rtype = "GO bin"
case "library":
rtype = "GO lib"
case "protobuf":
rtype = "GO pb"
}
if f.IsPrivate(repo) {
rtype = "priv"
}
// ctime := repo.Tags.GetAge(mhort)
// age := shell.FormatDuration(time.Since(ctime))
@ -170,17 +196,10 @@ func (f *Forge) printRepoToTable(repo *gitpb.Repo, sizes []int, full bool) {
if repo.IsDirty() {
age = ""
uver = "* " + uver
end += "(dirty) "
}
var args []string
if full {
args = []string{cname, age, uver, dhort, mhort, chort, lasttag, thort, rtype, gopath}
} else {
args = []string{gopath, cname, age, uver, dhort, mhort, chort, lasttag, thort, rtype}
}
start := cobol.StandardTableSize10(sizes, args)
start := standardTable10(gopath, cname, age, mhort, dhort, uhort, chort, lasttag, thort, rtype)
if rtype == "protobuf" {
if repo.GoInfo.GoBinary {
@ -188,6 +207,23 @@ 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":
@ -203,111 +239,5 @@ 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))
}
func (f *Forge) printForgedToTable(repo *gitpb.Repo, sizes []int) {
var end string
// shortened version numbers
var mhort string = repo.GetMasterVersion()
var dhort string = repo.GetDevelVersion()
var lasttag string = repo.GetLastTag()
var cname string = repo.GetCurrentBranchName()
var ns string = repo.GetNamespace()
age := shell.FormatDuration(repo.BranchAge(cname))
var args []string
args = []string{ns, cname, age, mhort, dhort, lasttag, "", "", "", ""}
start := cobol.StandardTableSize10(sizes, args)
end += "todo"
log.Info(cobol.TerminalChomp(start + " " + end))
}
func (psets *Sets) PrintTable() {
if psets == nil {
return
}
log.DaemonMode(true)
// print the header
args := []string{"commit hash", "new hash", "", "", "name", "Repo Namespace", "", "", "", "", ""}
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
/*
var countCONTENTS int
var countPARTS int
for x, pset := range psets.GetSets() {
log.Info(pset.Patches.Uuid, pset.Patches.Name, pset.Patches.State)
if pset.State == "DONE" {
// old patchset
continue
}
cId := log.Sprintf("%d", x)
countCONTENTS += 1
for i, p := range pset.Patches.GetPatches() {
var args []string
partId := log.Sprintf("%d", i)
_, fname := filepath.Split(p.GetFilename())
args = []string{p.CommitHash, p.NewHash, cId, partId, fname, p.GetNamespace(), "", "", "", "", ""}
start := cobol.StandardTableSize10(sizes, args)
log.Info(cobol.TerminalChomp(start))
countPARTS += 1
}
}
log.Infof("Total Contents (%d) Parts (%d)\n", countCONTENTS, countPARTS)
*/
}
func (patches *Patches) PrintTable() {
if patches == nil {
return
}
log.DaemonMode(true)
// print the header
args := []string{"commit hash", "new hash", "", "", "name", "Repo Namespace", "", "", "", "", ""}
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
/*
var countPARTS int
for x, p := range patches.GetPatches() {
var args []string
partId := log.Sprintf("%d", x)
_, fname := filepath.Split(p.GetFilename())
args = []string{p.CommitHash, p.NewHash, partId, fname, p.GetNamespace(), "", "", "", "", ""}
start := cobol.StandardTableSize10(sizes, args)
log.Info(cobol.TerminalChomp(start))
countPARTS += 1
}
log.Infof("Total Patches (%d)\n", countPARTS)
*/
log.Info(start, end)
}

280
init.go
View File

@ -6,209 +6,171 @@ import (
"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/hostname"
"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...)
}
*/
// todo: use initOnce
// cache.go has Do()
// f.initOnce.Do(f.initWork)
func Init() *Forge {
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
f := InitPB()
/*
f.Machine = new(zoopb.Machine)
if err := f.Machine.ConfigLoad(); err != nil {
log.Log(WARN, "zoopb.ConfigLoad() failed", err)
}
*/
if f.Config.Username == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
f.Config.ConfigSave()
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()
if f.Config.Xterm == "" {
f.Config.Xterm = "xterm"
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
f.SetConfigSave(true)
}
// f.Machine.InitWit()
if f.hasFullScan {
// duplicate time checking below. which one to keep?
if f.FullScanAge() > time.Minute {
log.Log(INFO, "forgepb.Scan() skipping scan. been run a minute ago", f.FullScanAge())
return f
}
}
now := time.Now()
start := f.Repos.Len()
f.ScanGoSrc()
f.FullScanRan()
end := f.Repos.Len()
if (end - start) == 0 {
log.Log(INFO, "forgepb.Scan() Scan did not find new git repositories. Total =", end)
if f.FullScanAge() > time.Minute {
f.rillUpdate(20, 10)
}
} else {
log.Log(INFO, "forgepb.Scan() Scan found", end-start, "new git repositories. Total =", end)
f.rillUpdate(20, 10)
}
if f.configSave {
// taking this out to debug Marshal() panic
// os.Exit(-1)
f.ConfigSave()
f.configSave = false
}
log.Log(INFO, "update() check took", shell.FormatDuration(time.Since(now)))
return f
}
func 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)
}
func DetermineGoPath() *Forge {
f := new(Forge)
f.Config = cfg
f.initFromConfig()
log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
// TODO: rethink this but it works for now
gosrc := os.Getenv("FORGE_GOSRC")
if gosrc == "" {
goSrcDir, err := f.findGoSrc()
if err != nil {
log.Log(WARN, "forge init() findGoSrc()", err)
}
os.Setenv("FORGE_GOSRC", goSrcDir)
}
f.goSrc = os.Getenv("FORGE_GOSRC")
// also rethink this, but maybe this is the right thing to do
if os.Getenv("FORGE_CONFIG") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_CONFIG", fullpath)
}
f.configDir = os.Getenv("FORGE_CONFIG")
// check again for go.work // user could have a go.work file in ~/go/src
if f.goWorkExists() {
f.goWork = true
}
// print out the settings that will be used
log.Log(INFO, "forgepb.Init() FORGE_CONFIG", os.Getenv("FORGE_CONFIG"))
return f
}
func (f *Forge) initFromConfig() {
if f.configENV() {
log.Info("ENV changed config. todo: save config here")
f.Config.ConfigSave()
func (f *Forge) InitPB() {
// load the ~/.config/forge/ config
f.Config = new(ForgeConfigs)
if err := f.Config.ConfigLoad(); err != nil {
log.Log(WARN, "forgepb.ConfigLoad() failed", err)
}
if f.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)
if f.IsGoWork() {
log.Log(INFO, "forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = true)")
} else {
log.Log(INFO, "forgepb.Init() FORGE_GOSRC ", os.Getenv("FORGE_GOSRC"), "(go.work = false)")
}
f.Repos = gitpb.NewRepos()
f.Repos.ConfigLoad(f.Config.ReposPB)
// init the Patchsets
f.Patchsets = NewSets()
// todo: play with these / determine good values based on user's machine
if f.Config.RillX == 0 {
f.Config.RillX = 10
f.Repos.ConfigLoad()
if f.Repos.HasFullScan {
f.hasFullScan = true
}
if f.Config.RillY == 0 {
f.Config.RillY = 20
if os.Getenv("FORGE_URL") != "" {
forgeURL = os.Getenv("FORGE_URL")
log.Info("got forge url", forgeURL)
}
}
func (f *Forge) InitMachine() {
if f.Config.Username == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
}
f.hostname, _ = hostname.Get()
// log.Info(hostname, err)
}
// only init's the protobuf. intended to not scan or change anything
func InitPB() *Forge {
f := DetermineGoPath()
f.InitPB()
return f
}
func (f *Forge) SetConfigSave(b bool) {
config.SetChanged("forge", b)
f.configSave = b
}
// saves the config if there have been changes
func (f *Forge) Exit() {
// log.Info("forge.configSave =", f.configSave)
if f.Config.Mode == ForgeMode_MASTER {
// fhelp.DumpENV("forge:")
// f.Config.DumpENV()
// todo: tell the user to switch to NORMAL mode
}
if f.configSave {
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)
}
// 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)
}
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()")
})
}
func (f *Forge) GetForgeURL() string {
return f.Config.ForgeURL
}
// set the URL for forge otherwise fallback to ENV or to forge.wit.com
func (f *Forge) SetForgeURL(url string) {
if url == "" {
url = os.Getenv("FORGE_URL")
}
if url == "" {
url = "https://forge.wit.com/"
}
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
}

View File

@ -1,69 +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?
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
}

34
patchset.Get.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package forgepb
import (
"go.wit.com/log"
)
var forgeURL string = "https://go.wit.com/"
func (f *Forge) GetPatchesets() (*Patchsets, error) {
url := forgeURL + "GetPatchsets"
log.Info("GetPatchsets() url", url)
body, err := f.HttpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return nil, err
}
log.Info("GetPatchets() len(body)", len(body))
var psets *Patchsets
psets = new(Patchsets)
err = psets.Unmarshal(body)
if err != nil {
log.Info("Unmarshal failed", err)
return nil, err
}
/*
filename := filepath.Join("/tmp", pbfile)
f, _ := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
f.Write(body)
f.Close()
*/
return psets, nil
}

View File

@ -1,11 +1,9 @@
package forgepb
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
@ -13,40 +11,23 @@ import (
"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 (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)
func newPatchset(name string) *Patchset {
pset := new(Patchset)
pset.Name = name
pset.Ctime = timestamppb.New(time.Now())
pset.Uuid = uuid.New().String()
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) (*Set, error) {
func (f *Forge) MakeDevelPatchSet(name string) (*Patchset, error) {
pset := newPatchset(name)
if os.Getenv("GIT_AUTHOR_NAME") == "" {
return nil, fmt.Errorf("GIT_AUTHOR_NAME not set")
@ -63,7 +44,7 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Set, 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()
@ -79,10 +60,6 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Set, 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()
@ -96,7 +73,41 @@ func (f *Forge) MakeDevelPatchSet(name string) (*Set, error) {
return pset, nil
}
func (pset *Set) makePatchSetNew(repo *gitpb.Repo) error {
/*
func (f *Forge) MakeMasterPatchSet() (*Patchset, error) {
pset := newPatchset("masterBranchPS")
dir, err := os.MkdirTemp("", "forge")
if err != nil {
return nil, err
}
defer os.RemoveAll(dir) // clean up
pset.TmpDir = dir
all := f.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
startb := repo.GetMasterBranchName()
endb := repo.GetUserBranchName()
if startb == "" {
continue
}
if endb == "" {
continue
}
// log.Info("repo", repo.GetGoPath(), startb, "..", endb)
pset.StartBranchName = startb
pset.EndBranchName = endb
err := pset.makePatchSetNew(repo)
if err != nil {
return nil, err
}
}
return pset, nil
}
*/
func (pset *Patchset) makePatchSetNew(repo *gitpb.Repo) error {
startBranch := pset.StartBranchName
endBranch := pset.EndBranchName
repoDir := filepath.Join(pset.TmpDir, repo.GetGoPath())
@ -133,23 +144,22 @@ func (pset *Set) 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, repoDir)
log.Infof("Added %d patches for %s len=%d\n", len(r.Stdout), repo.FullPath, pset.Patches.Len())
// pset.PrintTable()
err = pset.addPatchFiles(repo)
pset.Ctime = timestamppb.New(time.Now())
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 *Set) addPatchFiles(repo *gitpb.Repo, fullDir string) error {
func (p *Patchset) addPatchFiles(repo *gitpb.Repo) error {
psetDir := repo.GetGoPath()
tmpDir := p.TmpDir
// log.Info("ADD PATCH FILES ADDED DIR", tmpDir)
fullDir := filepath.Join(tmpDir, psetDir)
var baderr error
// 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
@ -161,6 +171,10 @@ func (p *Set) addPatchFiles(repo *gitpb.Repo, fullDir string) 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)
@ -170,22 +184,15 @@ func (p *Set) addPatchFiles(repo *gitpb.Repo, fullDir string) error {
patch := new(Patch)
patch.Filename, _ = filepath.Rel(p.TmpDir, path)
patch.Data = data
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.parseData()
patch.StartHash = repo.DevelHash()
patch.NewHash = "na"
patch.Namespace = repo.GetGoPath()
patch.RepoNamespace = 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
})
@ -195,7 +202,7 @@ func (p *Set) addPatchFiles(repo *gitpb.Repo, fullDir string) error {
// looks at the git format-patch output
// saves the commit Hash
// saves the diff lines
func (p *Patch) parseData() error {
func (p *Patch) parseData() string {
lines := strings.Split(string(p.Data), "\n")
for _, line := range lines {
fields := strings.Fields(line)
@ -211,7 +218,7 @@ func (p *Patch) parseData() error {
p.Files = append(p.Files, line)
}
}
return nil
return ""
}
// just an example of how to walk only directories
@ -236,59 +243,3 @@ 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
}

65
patchset.Send.go Normal file
View File

@ -0,0 +1,65 @@
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) SendPatchSet(pset *Patchset) error {
var err error
data, err := pset.Marshal()
if err != nil {
log.Info("proto.Marshal() pset(len) error", len(data), err)
return err
}
now := time.Now()
timestamp := now.Format("2006.01.02.150405") // bummer. other date doesn't work?
cfgfile := "patchset/patchset." + timestamp + ".pb"
log.Info("proto.Marshal() pset(len)", len(data))
configWrite(cfgfile, data)
return errors.New("don't know how to send yet")
}
func (f *Forge) SubmitDevelPatchSet(name string) (*Patchset, error) {
pset, err := f.MakeDevelPatchSet(name)
if err != nil {
return nil, err
}
if err := f.submitPatchset(pset); err != nil {
return nil, err
}
return pset, nil
}
func (f *Forge) submitPatchset(pset *Patchset) error {
var url string
url = forgeURL + "patchset"
msg, err := pset.Marshal()
if err != nil {
log.Info("proto.Marshal() failed:", err)
return err
}
log.Info("proto.Marshal() msg len", len(msg))
body, err := f.HttpPost(url, msg)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
test := strings.TrimSpace(string(body))
lines := strings.Split(test, "\n")
count := 0
for _, line := range lines {
log.Info("got back:", line)
count += 1
}
log.Info("Total patches:", count)
return nil
}

View File

@ -1,219 +0,0 @@
package forgepb
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/google/uuid"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
"google.golang.org/protobuf/proto"
)
func (f *Forge) LoadPatchsets() error {
f.Patchsets = NewSets()
filename := filepath.Join(f.Config.PatchDir, "all-patches.pb")
data, err := os.ReadFile(filename)
if err != nil {
return err
}
err = f.Patchsets.Unmarshal(data)
if err != nil {
log.Infof("LoadPatchsets() proto.Marshal() error %v\n", err)
return err
}
// log.Infof("LoadPatchsets() found %d patches.\n", f.Patchsets.Len())
return nil
}
func (f *Forge) InitPatchsets() error {
if err := f.LoadPatchsets(); err == nil {
return nil
} else {
log.Info("LoadPatchsets() failed", err)
}
// TODO: check if Unmarshal failed here
f.Patchsets = NewSets()
f.findAutoPatchset() // adds the default values
return f.SavePatchsets()
}
func (f *Forge) SavePatchsets() error {
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)
// fmt.Fprintln(w, "filename open error:", filename, err)
return err
}
defer regfile.Close()
newpb := proto.Clone(f.Patchsets).(*Sets)
if newpb == nil {
for pset := range f.Patchsets.IterAll() {
pset.ShowPatchsets()
}
return log.Errorf("SavePatchsets() Clone failed!")
}
data, err := newpb.Marshal()
if err != nil {
log.Infof("SavePatchset() proto.Marshal() error %v\n", err)
return err
}
log.Infof("SavePatchset() worked (%d) bytes on %d patches\n", len(data), f.Patchsets.Len())
regfile.Write(data)
return nil
}
func cleanSubject(line string) string {
// Regular expression to remove "Subject:" and "[PATCH...]" patterns
re := regexp.MustCompile(`(?i)^Subject:\s*(\[\s*PATCH[^\]]*\]\s*)?`)
cleaned := re.ReplaceAllString(line, "")
return strings.TrimSpace(cleaned)
}
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)
for _, patch := range pb.Patches.Patches {
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)
// }
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 *Set) bool {
var changed bool
// if the name of the patchset is "forge auto commit"
// then just add all the patches
if pb.Name == "forge auto commit" {
author := "Author: " + pb.GitAuthorName
author += " <" + pb.GitAuthorEmail + ">"
fmt.Println(pb.Name, pb.Comment, author)
for _, patch := range pb.Patches.Patches {
// log.Info("\tnew patch:", i, patch.CommitHash, patch.Namespace)
if f.findPatch(patch) {
// log.Info("\talready found!!!!!!!", pset.Uuid, patch.Namespace)
} else {
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)
}
}
}
return changed
}
// if you got here, this patchset was submitted with a name
// Has this patchset already been submitted?
for pset := range f.Patchsets.IterAll() {
if pset.Uuid == pb.Uuid {
log.Info("ALREADY ADDED", pset.Uuid, pset.Name)
return false
} else {
}
}
f.Patchsets.Append(pb)
return true
}
func (f *Forge) findAutoPatchset() *Set {
for pset := range f.Patchsets.IterAll() {
if pset.Name == "forge auto commit" {
return pset
}
}
var fauto *Set
log.Warn("findAutoPatchset() had to create 'forge auto commit'")
if fauto == nil {
fauto = makeDefaultPatchset()
f.Patchsets.Append(fauto)
}
return fauto
}
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 false
}
fauto := f.findAutoPatchset()
if fauto == nil {
// should have made the default patchset
return false
}
fauto.Patches.Append(patch)
return true
}
// returns true if the patch already exists in the protobuf
func (f *Forge) findPatch(newpatch *Patch) bool {
// log.Info("\tlook for patch:", newpatch.CommitHash, newpatch.Namespace)
for pset := range f.Patchsets.IterAll() {
for _, patch := range pset.Patches.Patches {
if patch.CommitHash == newpatch.CommitHash {
// log.Info("\tfound pset!!!!!!", pset.Uuid, patch.Namespace)
return true
}
}
}
return false
}
func (f *Forge) IsPatchApplied(newpatch *Patch) (*gitpb.Repo, bool) {
log.Info("todo: find namespace and repo for patch", newpatch.Filename)
if f.findPatch(newpatch) {
log.Info("\tfindPatch() patch was found")
} else {
log.Info("\tfindPatch() patch was not found")
}
return nil, false
}

View File

@ -1,11 +0,0 @@
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
}
*/

87
patchset.proto Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
syntax = "proto3";
package forgepb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
// Forge doesn't need this kind of specificity
// but this is what the patch files contain and how git sees them
// message Blob {
// string hunkLine = 1;
// bytes data = 2;
// }
//
// message File {
// string filename = 1;
// string hashLine = 2;
// repeated Blob Blobs = 3;
// }
//
// message Patch {
// repeated File Files = 1;
// string repoNamespace = 2;
// string gH = 3;
// string gaI = 4;
// string gcI = 5;
// }
// git log -1 --format="%H %aI %cI %an %ae %cn %ce"
message Patch {
string repoNamespace = 1; // the base repo git URL
bytes data = 2; // the raw data of the whole patch
string gH = 3; // after some deliberation, I think I'll just try variable names
string gT = 4;
string gP = 5;
string gs = 6;
string gaI = 7; // that exactly match what git uses.
string gan = 8;
string gae = 9;
string gcI = 10;
string gcn = 11;
string gce = 12;
string gN = 13;
string gGG = 14;
string gGS = 15;
string gGK = 16;
string newHash = 17; // new hash
string state = 18; // the 'state' of the patch
string filename = 19; // `autogenpb:unique` `autogenpb:sort`
string startHash = 20; // the start commit hash
string commitHash = 21; // the git commit hash of this patch `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?
}
message Patches { // this is a "PATCH: [1/x]" series `autogenpb:gui:Patch`
string uuid = 1; // `autogenpb:uuid:be926ad9-1111-484c-adf2-d96eeabf3079`
string version = 2; // `autogenpb:version:v0.0.45`
repeated Patch Patches = 3;
}
message Patchset { // `autogenpb:marshal`
Patches patches = 1; //
string name = 2; // `autogenpb:sort`
string comment = 3; //
string gitAuthorName = 4; // `autogenpb:sort`
string gitAuthorEmail = 5; //
google.protobuf.Timestamp ctime = 6; // create time of the patchset
string tmpDir = 7; // temp dir
string startBranchName = 8; //
string endBranchName = 9; //
string startBranchHash = 10; //
string endBranchHash = 11; //
string state = 12; // the state of the patch
string uuid = 13; // `autogenpb:sort` `autogenpb:unique`
string hostname = 14; //
}
message Patchsets { // `autogenpb:marshal` `autogenpb:gui`
string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079`
string version = 2; // `autogenpb:version:v0.0.45`
repeated Patchset Patchsets = 3;
}

View File

@ -9,24 +9,19 @@ 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.Config.ReposDir, gopath)
fullpath := filepath.Join(f.GetGoSrc(), gopath)
test := f.Repos.FindByFullPath(fullpath)
if test != nil {
return test, nil
}
if gopath == "" {
log.Info("TODO: gopath was blank. I'm not sure what is happening here, but this needs to determine the repo Namespace. all calls to NewGoRepo() need to be deprecated. Someone will have to do this when I have funding for WIT. If you are the one looking at this, track me down and I will help do this correctly.")
return nil, log.Errorf("gopath was blank")
}
repo, err := f.Repos.NewGoRepo(fullpath, gopath)
if err != nil {
log.Info("WARNING. NEW FAILED", fullpath, err)
log.Info("WARNING. NEW FAILED", fullpath)
return nil, err
}
// slices.Reverse(f.Repos.Repos)
@ -36,30 +31,7 @@ func (f *Forge) NewGoRepo(gopath string, url string) (*gitpb.Repo, error) {
if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true
}
repo.ReloadCheck()
return repo, nil
}
// used by the forge daemon
func (f *Forge) AddNamespaceDir(ns string, fullpath string) (*gitpb.Repo, error) {
test := f.Repos.FindByNamespace(ns)
if test != nil {
return test, fmt.Errorf("already have namespace")
}
repo, err := f.Repos.NewGoRepo(fullpath, ns)
if err != nil {
log.Info("WARNING. NEW FAILED", fullpath)
return nil, err
}
f.VerifyBranchNames(repo)
repo.ReloadCheck()
// set the readonly flag based on the users' forge config
if f.Config.IsReadOnly(repo.GetGoPath()) {
repo.ReadOnly = true
}
config.SetChanged("repos", true)
repo.Reload()
return repo, nil
}
@ -131,6 +103,13 @@ func (f *Forge) findMasterBranch(repo *gitpb.Repo) {
// TODO: figure out the name from git
repo.SetMasterBranchName("master")
/* no longer checkout on Init()
if repo.CheckoutMaster() {
} else {
cmd := []string{"git", "branch", "master"}
repo.Run(cmd)
}
*/
}
// figure out what the name of the git devel branch is
@ -139,6 +118,13 @@ func (f *Forge) findDevelBranch(repo *gitpb.Repo) {
// check the forge config first
if bname := f.Config.FindDevelBranch(repo.GetGoPath()); bname != "" {
repo.SetDevelBranchName(bname)
/* no longer checkout on Init()
if repo.CheckoutDevel() {
} else {
cmd := []string{"git", "branch", bname}
repo.Run(cmd)
}
*/
return
}
@ -148,6 +134,13 @@ func (f *Forge) findDevelBranch(repo *gitpb.Repo) {
}
repo.SetDevelBranchName("devel")
/* no longer checkout on Init()
if repo.CheckoutDevel() {
} else {
cmd := []string{"git", "branch", "devel"}
repo.Run(cmd)
}
*/
}
// this is still in flux
@ -164,9 +157,6 @@ 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 {
@ -174,9 +164,6 @@ 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?
@ -192,9 +179,6 @@ 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
@ -225,7 +209,7 @@ func (f *Forge) AddFullPath(fulldir string) *gitpb.Repo {
}
func (f *Forge) FindByGoPath(gopath string) *gitpb.Repo {
fullpath := filepath.Join(f.Config.ReposDir, gopath)
fullpath := filepath.Join(f.GetGoSrc(), gopath)
return f.Repos.FindByFullPath(fullpath)
}
@ -252,6 +236,6 @@ func (f *Forge) FindAnyPath(dir string) *gitpb.Repo {
}
func (f *Forge) DeleteByGoPath(gopath string) bool {
fullpath := filepath.Join(f.Config.ReposDir, gopath)
fullpath := filepath.Join(f.GetGoSrc(), gopath)
return f.Repos.DeleteByFullPath(fullpath)
}

113
rill.go
View File

@ -2,10 +2,8 @@ package forgepb
import (
"sync"
"time"
"github.com/destel/rill"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
@ -34,6 +32,7 @@ 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,14 +41,16 @@ 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.GetNamespace())
log.Printf("%10s %-50s gopath=%s\n", "git dir is missing:", repo.FullPath, repo.GetGoPath())
f.Repos.DeleteByFullPath(repo.FullPath)
f.configSave = true
return nil
}
if repo.HasChanged() {
// log.Info("repo changed ", repo.FullPath, repo.StateChange)
if err := repo.ReloadCheck(); err != nil {
if repo.DidRepoChange() {
f.configSave = true
log.Info("repo changed ", repo.FullPath, repo.StateChange)
if err := repo.Reload(); err != nil {
return err
}
} else {
@ -60,13 +61,14 @@ 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
}
var RillX int = 10
var RillY int = 20
var RillY int = 10
// x is the size of the queued up pool (shouldn't matter here for this I think)
// y is how many simultanous functions will run
@ -76,10 +78,7 @@ 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.GetFullPath())
f.Repos.Delete(repo)
log.Info("reposSave = true")
config.SetChanged("repos", true)
log.Printf("%s %-50s", "got an invalid repo in forgepb.RillFuncError()", repo.GetGoPath())
continue
}
all = append(all, repo)
@ -95,10 +94,11 @@ func (f *Forge) RillReload() int {
})
rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error {
if !repo.HasChanged() {
if !repo.DidRepoChange() {
return nil
}
repo.ReloadCheck()
f.configSave = true
repo.Reload()
counter += 1
return nil
})
@ -110,107 +110,42 @@ func (f *Forge) RillReload() int {
// y is how many simultanous functions will run
// todo: tune and compute x,y by # of CPUs and disk io
// todo: store x,y in forge config ? (or compute them. notsure)
func (f *Forge) RillFuncError(rillf func(*gitpb.Repo) error) map[string]*RillStats {
return f.RillRepos(rillf)
}
func (f *Forge) ConfigRill(rillX int, rillY int) {
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 {
Err error
Start time.Time
End time.Time
}
var rillMu sync.Mutex
// x is the size of the queued up pool (shouldn't matter here for this I think)
// y is how many simultanous functions will run
// todo: tune and compute x,y by # of CPUs and disk io
// todo: store x,y in forge config ? (or compute them. notsure)
func (f *Forge) RillRepos(rillf func(*gitpb.Repo) error) map[string]*RillStats {
func (f *Forge) RillFuncError(rillf func(*gitpb.Repo) error) int {
var all []*gitpb.Repo
var stats map[string]*RillStats
stats = make(map[string]*RillStats)
for repo := range f.Repos.IterAll() {
if !repo.IsValidDir() {
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)
log.Printf("%s %-50s", "got an invalid repo in forgepb.RillFuncError()", repo.GetGoPath())
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)
var counter int
var watch int = 10
var meMu sync.Mutex
// Read users from the API.
// Concurrency = 20
dirs := rill.Map(ids, int(f.Config.RillX), func(id *gitpb.Repo) (*gitpb.Repo, error) {
dirs := rill.Map(ids, RillX, func(id *gitpb.Repo) (*gitpb.Repo, error) {
return id, nil
})
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()
err := rill.ForEach(dirs, RillY, func(repo *gitpb.Repo) error {
meMu.Lock()
counter += 1
if counter > watch {
// log.Info("Processed", watch, "repos") // this doesn't work
watch += 50
}
rillMu.Unlock()
rillSetStartTime(stats, repo.GetFullPath())
if err := rillf(repo); err != nil {
rillSetError(stats, repo.GetFullPath(), err)
}
rillSetEndTime(stats, repo.GetFullPath())
return nil
meMu.Unlock()
return rillf(repo)
})
return stats
if err != nil {
log.Info("rill.ForEach() error:", err)
}
func rillSetError(stats map[string]*RillStats, fullpath string, err error) {
rillMu.Lock()
defer rillMu.Unlock()
if s, ok := stats[fullpath]; ok {
s.Err = err
return
}
log.Info("WHAT THE FUCK STATS ERROR", fullpath)
}
func rillSetStartTime(stats map[string]*RillStats, fullpath string) {
rillMu.Lock()
defer rillMu.Unlock()
if s, ok := stats[fullpath]; ok {
s.Start = time.Now()
return
}
var s *RillStats
s = new(RillStats)
s.Start = time.Now()
stats[fullpath] = s
}
func rillSetEndTime(stats map[string]*RillStats, fullpath string) {
rillMu.Lock()
defer rillMu.Unlock()
if s, ok := stats[fullpath]; ok {
s.End = time.Now()
return
}
log.Info("WHAT THE FUCK STATS END TIME", fullpath)
return counter
}

View File

@ -1,32 +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
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;
}

View File

@ -1,89 +0,0 @@
// 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,6 +2,7 @@ package forgepb
import (
sync "sync"
"time"
"go.wit.com/lib/protobuf/gitpb"
)
@ -9,14 +10,41 @@ import (
// maybe an interface someday?
type Forge struct {
// one-time initialized data
once sync.Once
initOnce sync.Once
initErr error // init error, if any
goSrc string // the path to go/src
configDir string // normally ~/.config/forge
goWork bool // means the user is currently using a go.work file
Config *ForgeConfigs // config repos for readonly, private, etc
Repos *gitpb.Repos // the repo protobufs
Patchsets *Sets // patches that are in progress
configSave bool // if you need to save the config because things changed
hasFullScan bool // track last scan so it can be throttled
fullscan time.Time // time of the last scan so it can be throttled
hostname string // your hostname
goWork bool // means the user is currently using a go.work file
// Machine *zoopb.Machine // things for virtigo to track vm's
}
func (f *Forge) GetGoSrc() string {
return f.goSrc
}
func (f *Forge) GetConfigDir() string {
return f.configDir
}
func (f *Forge) IsGoWork() bool {
return f.goWork
}
func (f *Forge) HasFullScan() bool {
return f.Repos.HasFullScan
}
func (f *Forge) FullScanRan() {
f.fullscan = time.Now()
}
func (f *Forge) FullScanAge() time.Duration {
fs := f.Repos.FullScan.AsTime()
return time.Since(fs)
}