Compare commits

..

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

19 changed files with 569 additions and 509 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
@ -27,14 +27,8 @@ clean:
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
patchset.pb.go: patchset.proto
autogenpb --proto patchset.proto
protoc-test:
cd ~/go/src && protoc \

View File

@ -3,8 +3,6 @@
package forgepb
import (
"os"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/prep"
"go.wit.com/log"
@ -19,6 +17,7 @@ func (f *Forge) ConfigSave() error {
}
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())
@ -34,53 +33,3 @@ func (f *Forge) ConfigSave() error {
}
return err
}
// functions to import and export the protobuf
// data to and from config files
// write to ~/.config/forge/
func (cfg *ForgeConfigs) ConfigSave() error {
var header string
header += "\n"
header += "# the forge config file\n"
header += "# You can customize things like:\n"
header += "#\n"
header += "# * which repos you have write access to\n"
header += "# * custom branch names for 'master', 'devel' and 'user'\n"
header += "# * 'favorites' so you can remember which things you like\n"
header += "# * sometimes protobuf TEXT can fail so as a backup this also creates a .json file\n"
header += "#\n"
header += "\n"
return config.ConfigSaveWithHeader(cfg, header)
}
func (cfg *ForgeConfigs) DumpENV() {
log.Infof("CfgPB.Filename = %s\n", cfg.Filename)
log.Infof("CfgPB.ReposPB = %s\n", cfg.ReposPB)
log.Infof("CfgPB.ReposDir = %s\n", cfg.ReposDir)
log.Infof("CfgPB.PatchDir = %s\n", cfg.PatchDir)
log.Infof("CfgPB.ForgeURL = %s\n", cfg.ForgeURL)
if cfg.GoWork {
log.Infof("CfgPB.GoWork = %v\n", cfg.GoWork)
}
log.Infof("CfgPB.Mode = %s\n", cfg.Mode)
// log.Infof("CfgPB.Hostname=%s\n", cfg.Hostname)
if cfg.ReposPB != os.Getenv("FORGE_REPOSPB") {
log.Infof("CfgPB file problem: cfg.ReposPB=%s != FORGE_REPOSPB=%s\n", cfg.ReposPB, os.Getenv("FORGE_REPOSPB"))
}
}
/*
if f.Config.Xterm == "" {
f.Config.Xterm = "xterm"
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
f.SetConfigSave(true)
}
*/
func (cfg *ForgeConfigs) InitDefaults() {
}

56
forgeConfig.config.go Normal file
View File

@ -0,0 +1,56 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package forgepb
import (
"os"
"go.wit.com/lib/config"
"go.wit.com/log"
)
// functions to import and export the protobuf
// data to and from config files
// write to ~/.config/forge/
func (cfg *ForgeConfigs) ConfigSave() error {
var header string
header += "\n"
header += "# the forge config file\n"
header += "# You can customize things like:\n"
header += "#\n"
header += "# * which repos you have write access to\n"
header += "# * custom branch names for 'master', 'devel' and 'user'\n"
header += "# * 'favorites' so you can remember which things you like\n"
header += "#\n"
header += "\n"
return config.ConfigSaveWithHeader(cfg, header)
}
func (cfg *ForgeConfigs) DumpENV() {
if cfg.ReposPB != os.Getenv("FORGE_REPOSPB") {
log.Infof("RepoPB file problem: cfg.ReposPB=%s != FORGE_REPOSPB=%s\n", cfg.ReposPB, os.Getenv("FORGE_REPOSPB"))
}
}
// load the ~/.config/forge/ files
func (c *ForgeConfigs) ConfigLoad(fullpath string) error {
return nil
}
/*
if f.Config.Username == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
f.SetConfigSave(true)
}
if f.Config.Xterm == "" {
f.Config.Xterm = "xterm"
f.Config.XtermArgv = append(f.Config.XtermArgv, "-bg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "black")
f.Config.XtermArgv = append(f.Config.XtermArgv, "-fg")
f.Config.XtermArgv = append(f.Config.XtermArgv, "white")
f.SetConfigSave(true)
}
*/

View File

@ -55,7 +55,7 @@ message ForgeConfigs { // `autogenpb:mar
repeated string xtermArgv = 6; // the argv line for xterm
string defaultGui = 7; // default GUI plugin to use
ForgeMode mode = 8; // what "mode" forge is in
bool goWork = 9; // true if there is a go.work file
string goSrc = 9; // is ~/go/src unless a go.work file is found
bool pathLock = 10; // the path is locked
string ReposPB = 11; // where the repos.pb is
string ReposDir = 12; // where the repos are

86
goSrcFind.go Normal file
View File

@ -0,0 +1,86 @@
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
func (f *Forge) GetHome() string {
return f.Config.ReposDir
}
/*
// 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.Config.ReposDir, "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")
}
*/

206
goSrcScan.go Normal file
View File

@ -0,0 +1,206 @@
package forgepb
import (
"fmt"
"os"
"path/filepath"
"go.wit.com/log"
)
/*
func (f *Forge) ScanGoSrc() (bool, error) {
dirs, err := gitDirectoriesNew(f.Config.ReposDir)
if err != nil {
return false, err
}
stats := f.RillRepos(reloadCheck)
for _, stat := range stats {
if stat.Err == nil {
continue
}
config.SetChanged("repos", true)
}
var gopaths []string
for _, dir := range dirs {
if strings.HasPrefix(dir, f.Config.ReposDir) {
gopath := strings.TrimPrefix(dir, f.Config.ReposDir)
gopath = strings.Trim(gopath, "/")
if r := f.FindByGoPath(gopath); r != nil {
// log.Info("already have", gopath)
continue
}
gopaths = append(gopaths, gopath)
} else {
log.Log(WARN, "ScanGoSrc() bad:", dir)
return false, errors.New("forgepb.ScanGoSrc() bad dir: " + dir)
}
}
newcount, err := f.rillScanDirs(gopaths)
if err != nil {
log.Info("go src dir problem. exit for now?", err)
return false, err
}
if newcount != 0 {
log.Info("forge go src scan found", newcount, "repos")
config.SetChanged("repos", true)
}
return true, err
}
// returns a repo protobuf for a directory if the directory is a git repo
func (f *Forge) ScanDir(dir string) *gitpb.Repo {
repo, err := f.Repos.NewRepo(dir, "")
if err != nil {
log.Info("ScanDir() error", dir, err)
return nil
}
return repo
}
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.Config.ReposDir, gopath)
log.Info("forge creating protobuf for", fullpath)
repo, err := f.NewGoRepo(gopath, "")
if err != nil {
log.Info("\tprotobuf error", gopath, err)
}
return repo, err
}
// deletes the repo from the protobuf (pray your mutex locks are working)
// re-scans the repo
// returns the new repo
func (f *Forge) ReAdd(repo *gitpb.Repo) (*gitpb.Repo, error) {
if repo == nil {
return nil, log.Errorf("can't delete repo == nil")
}
fullpath := repo.GetFullPath()
ns := repo.GetNamespace()
if !f.Repos.Delete(repo) {
return nil, log.Errorf("delete of repo failed")
}
repo, err := f.AddNamespaceDir(ns, fullpath)
if err != nil {
log.Info("ReAdd() error", fullpath, err)
return nil, err
}
return repo, err
}
*/
// doesn't enter the directory any further when it finds a .git/
// not stupid like my old version
func gitDirectoriesNew(srcDir string) ([]string, error) {
var all []string
var trip bool
err := filepath.WalkDir(srcDir, func(path string, d os.DirEntry, err error) error {
if err != nil {
// Handle possible errors, like permission issues
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
return err
}
if d.IsDir() {
// log.Info("path is dir", path)
} else {
_, fname := filepath.Split(path)
switch fname {
case "repos.pb":
case "go.work":
case "go.work.last":
case "go.work.sum":
default:
// todo: figure out a way to do padding for init()
if trip == false {
log.Info("WARNING:")
}
log.Info("WARNING: you have an untracked file outside of any .git repository:", path)
trip = true
}
return nil
}
gitdir := filepath.Join(path, ".git")
_, err2 := os.Stat(gitdir)
if !os.IsNotExist(err2) {
all = append(all, path)
return filepath.SkipDir
}
return nil
})
//
// probably always leave this here forever
// this check, along with CheckDirty() makes sure you can safely delete ~/go/src or the go.work directory
// because everything is either checked in or deleted. An important thing to know!
if trip {
log.Info("WARNING:")
log.Info("WARNING: there isn't a way to disable this warning yet")
log.Info("WARNING: probably this is a good thing however. you don't want to leave files outside of git repos here")
log.Info("WARNING: so this warning should probably stay")
log.Info("WARNING:")
log.Info("WARNING: this also might mean you put these files here because you are actively working on them")
log.Info("WARNING: and you don't want to forget about them")
log.Info("WARNING:")
}
return all, err
}

View File

@ -246,7 +246,7 @@ func (f *Forge) printForgedToTable(repo *gitpb.Repo, sizes []int) {
log.Info(cobol.TerminalChomp(start + " " + end))
}
func (psets *Sets) PrintTable() {
func (psets *Patchsets) PrintTable() {
if psets == nil {
return
}
@ -257,31 +257,29 @@ func (psets *Sets) PrintTable() {
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
/*
var countCONTENTS int
var countPARTS int
for x, pset := range psets.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)
var countCONTENTS int
var countPARTS int
for x, pset := range psets.GetPatchsets() {
log.Info(pset.Uuid, pset.Name, pset.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(), "", "", "", "", ""}
_, 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)
*/
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() {
@ -295,19 +293,17 @@ func (patches *Patches) PrintTable() {
sizes := []int{12, 12, 3, 3, 40, 80, 2, 2, 2, 2}
log.Info(cobol.TerminalChomp(cobol.StandardTableSize10(sizes, args)))
/*
var countPARTS int
for x, p := range patches.GetPatches() {
var args []string
partId := log.Sprintf("%d", x)
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(), "", "", "", "", ""}
_, 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)
*/
start := cobol.StandardTableSize10(sizes, args)
log.Info(cobol.TerminalChomp(start))
countPARTS += 1
}
log.Infof("Total Patches (%d)\n", countPARTS)
}

58
init.go
View File

@ -4,7 +4,6 @@ package forgepb
import (
"os"
"os/user"
"path/filepath"
"go.wit.com/lib/config"
@ -25,31 +24,17 @@ func Default(opts ...OptionFunc) *Engine {
*/
func Init() *Forge {
f := new(Forge)
cfg := new(ForgeConfigs)
err := config.ConfigLoad(cfg, "forge", "forge")
f.Config = cfg
if err != nil {
// 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
log.Info("forge has not been configured yet filename =", cfg.Filename)
log.Info("go install go.wit.com/apps/forge@latest")
os.Exit(-1)
}
if f.Config.Username == "" {
usr, _ := user.Current()
f.Config.Username = usr.Username
f.Config.ConfigSave()
}
f.initFromConfig()
if f.Config.Mode == ForgeMode_MASTER {
f := initFromConfig(cfg)
if f.Config.Mode != ForgeMode_NORMAL {
log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
// fhelp.DumpENV("finit:")
fhelp.DumpENV("finit:")
f.Config.DumpENV()
}
return f
@ -63,14 +48,14 @@ func InitByAppname(argname string) *Forge {
log.Info("go install go.wit.com/apps/forge@latest")
os.Exit(-1)
}
f := new(Forge)
f.Config = cfg
f.initFromConfig()
f := initFromConfig(cfg)
log.Printf("forge.Init() %s len()=%d\n", f.Config.Filename, f.Repos.Len())
return f
}
func (f *Forge) initFromConfig() {
func initFromConfig(cfg *ForgeConfigs) *Forge {
f := new(Forge)
f.Config = cfg
if f.configENV() {
log.Info("ENV changed config. todo: save config here")
f.Config.ConfigSave()
@ -92,15 +77,12 @@ func (f *Forge) initFromConfig() {
f.Repos.ConfigLoad(f.Config.ReposPB)
// init the Patchsets
f.Patchsets = NewSets()
f.Patchsets = NewPatchsets()
// todo: play with these / determine good values based on user's machine
if f.Config.RillX == 0 {
f.Config.RillX = 10
}
if f.Config.RillY == 0 {
f.Config.RillY = 20
}
f.Config.RillX = 10
f.Config.RillY = 20
return f
}
func (f *Forge) SetConfigSave(b bool) {
@ -110,10 +92,9 @@ func (f *Forge) SetConfigSave(b bool) {
// 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.Config.Mode != ForgeMode_NORMAL {
fhelp.DumpENV("forge:")
f.Config.DumpENV()
}
f.ConfigSave()
@ -133,7 +114,6 @@ func (f *Forge) Exit() {
// all initial ENV settings should be stored in the forge struct
func (f *Forge) setenv() {
f.once.Do(func() {
log.Info("doing setenv()")
if err := fhelp.ConfigureENV(); err != nil {
log.Info("forge ConfigureENV() failed", err)
os.Exit(-1)
@ -148,11 +128,9 @@ func (f *Forge) setenv() {
f.goWork = true
}
f.Config.ReposPB = os.Getenv("FORGE_REPOSPB")
f.Config.ReposDir = os.Getenv("FORGE_REPOSDIR")
f.Config.ReposPB = os.Getenv("FORGE_REPOPB")
f.Config.PatchDir = os.Getenv("FORGE_PATCHDIR")
f.Config.ForgeURL = os.Getenv("FORGE_URL")
fhelp.DumpENV("setenv end()")
})
}

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
}

View File

@ -1,17 +1,14 @@
package forgepb
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"go.wit.com/lib/hostname"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/lib/protobuf/httppb"
"go.wit.com/log"
@ -23,30 +20,24 @@ func (p *Patches) HttpPostVerbose(baseURL string, route string) (*Patches, *http
return p.HttpPost(baseURL, route)
}
func (p *Set) HttpPostVerbose(baseURL string, route string) (*Set, *httppb.HttpRequest, error) {
func (p *Patchsets) HttpPostVerbose(baseURL string, route string) (*Patchsets, *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()
pset.Hostname, _ = os.Hostname()
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 +54,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 +70,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 +83,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 +154,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 +181,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 +194,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.parseData()
patch.StartHash = repo.ActualDevelHash()
patch.NewHash = "na"
patch.Namespace = repo.GetGoPath()
if p.Patches == nil {
log.Info("SHOULD NOT HAVE HAPPENED. p.Patches == nil")
p.Patches = new(Patches)
}
p.Patches.Append(patch)
p.Patches.Uuid = uuid.New().String()
// log.Info("ADDED PATCH FILE", path)
return nil
})
@ -195,7 +212,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 +228,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 +253,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
}

View File

@ -14,7 +14,7 @@ import (
)
func (f *Forge) LoadPatchsets() error {
f.Patchsets = NewSets()
f.Patchsets = NewPatchsets()
filename := filepath.Join(f.Config.PatchDir, "all-patches.pb")
@ -39,7 +39,7 @@ func (f *Forge) InitPatchsets() error {
log.Info("LoadPatchsets() failed", err)
}
// TODO: check if Unmarshal failed here
f.Patchsets = NewSets()
f.Patchsets = NewPatchsets()
f.findAutoPatchset() // adds the default values
return f.SavePatchsets()
}
@ -54,7 +54,7 @@ func (f *Forge) SavePatchsets() error {
}
defer regfile.Close()
newpb := proto.Clone(f.Patchsets).(*Sets)
newpb := proto.Clone(f.Patchsets).(*Patchsets)
if newpb == nil {
for pset := range f.Patchsets.IterAll() {
pset.ShowPatchsets()
@ -79,7 +79,7 @@ func cleanSubject(line string) string {
return strings.TrimSpace(cleaned)
}
func (pb *Set) ShowPatchsets() error {
func (pb *Patchset) 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)
@ -109,7 +109,7 @@ func (f *Forge) AddPatch(patch *Patch) bool {
}
// adds a patchset or just the patches
func (f *Forge) AddPatchset(pb *Set) bool {
func (f *Forge) AddPatchset(pb *Patchset) bool {
var changed bool
// if the name of the patchset is "forge auto commit"
// then just add all the patches
@ -146,18 +146,23 @@ func (f *Forge) AddPatchset(pb *Set) bool {
}
}
f.Patchsets.Append(pb)
// Clone() this protobuf into me.forge.Patchsets
var newpb *Patchset
newpb = proto.Clone(pb).(*Patchset)
if newpb != nil {
f.Patchsets.Patchsets = append(f.Patchsets.Patchsets, newpb)
}
return true
}
func (f *Forge) findAutoPatchset() *Set {
func (f *Forge) findAutoPatchset() *Patchset {
for pset := range f.Patchsets.IterAll() {
if pset.Name == "forge auto commit" {
return pset
}
}
var fauto *Set
var fauto *Patchset
log.Warn("findAutoPatchset() had to create 'forge auto commit'")
if fauto == nil {
fauto = makeDefaultPatchset()
@ -166,8 +171,8 @@ func (f *Forge) findAutoPatchset() *Set {
return fauto
}
func makeDefaultPatchset() *Set {
fauto := new(Set)
func makeDefaultPatchset() *Patchset {
fauto := new(Patchset)
fauto.Name = "forge auto commit"
fauto.Patches = NewPatches()
fauto.Uuid = uuid.New().String()

View File

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

89
patchset.proto Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
syntax = "proto3";
package forgepb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
// Forge doesn't need this kind of specificity
// but this is what the patch files contain and how git sees them
// message Blob {
// string hunkLine = 1;
// bytes data = 2;
// }
//
// message File {
// string filename = 1;
// string hashLine = 2;
// repeated Blob Blobs = 3;
// }
//
// message Patch {
// repeated File Files = 1;
// string repoNamespace = 2;
// string gH = 3;
// string gaI = 4;
// string gcI = 5;
// }
// git log -1 --format="%H %aI %cI %an %ae %cn %ce"
message Patch {
string namespace = 1; // the base repo git namespace
bytes data = 2; // the raw data of the whole patch
string gH = 3; // Commit Hash (%H)
string gT = 4; // Tree Hash (%T)
string gP = 5; // Parent Hashes (%P)
string gs = 6; // Subject (%s)
string gaI = 7; // Author Date, ISO 8601 format (%aI)
string gan = 8; // Author Name (%an)
string gae = 9; // Author Email (%ae)
string gcI = 10; // Committer Date, ISO 8601 format (%cI)
string gcn = 11; // Committer Name (%cn)
string gce = 12; // Committer Email (%ce)
string gN = 13; // Commit Notes (%N)
string gGG = 14; // GPG Signature, raw (%GG)
string gGS = 15; // GPG Signer Name (%GS)
string gGK = 16; // GPG Key ID (%GK)
string newHash = 17; // new hash
string state = 18; // the 'state' of the patch
string filename = 19; // `autogenpb:unique` `autogenpb:sort`
string startHash = 20; // the start commit hash
string commitHash = 21; // the git commit hash of this patch `autogenpb:sort` `autogenpb:unique`
string comment = 22; // the git commit message (in patch form)
repeated string Files = 23; // the filenames this patch changes
google.protobuf.Timestamp ctime = 24; // create time of the patch
bool applied = 25; // have you applied this patch?
bool upstream = 26; // has this patch been applied upstream?
}
// this is a "PATCH: [1/x]" series
message Patches { // `autogenpb:marshal` `autogenpb:gui:Patch` `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 = 5; // when passing these around, if there is an error, store it here
}
message Patchset { // `autogenpb:marshal`
Patches patches = 1; //
string name = 2; // `autogenpb:sort`
string comment = 3; //
string gitAuthorName = 4; // `autogenpb:sort`
string gitAuthorEmail = 5; //
google.protobuf.Timestamp ctime = 6; // create time of the patchset
string tmpDir = 7; // temp dir
string startBranchName = 8; //
string endBranchName = 9; //
string startBranchHash = 10; //
string endBranchHash = 11; //
string state = 12; // the state of the patch
string uuid = 13; // `autogenpb:sort` `autogenpb:unique`
string hostname = 14; //
}
message Patchsets { // `autogenpb:marshal` `autogenpb:gui` `autogenpb:nomutex` `autogenpb:http`
string uuid = 1; // `autogenpb:uuid:be926ad9-f07f-484c-adf2-d96eeabf3079`
string version = 2; // `autogenpb:version:v0.0.45`
repeated Patchset Patchsets = 3;
}

View File

@ -164,9 +164,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 +171,7 @@ 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())
}
// log.Info("VerifyBranchNames", repo.GetMasterBranchName(), repo.GetDevelBranchName(), repo.GetUserBranchName())
}
// what name should be used for the user branch?
@ -192,9 +187,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

View File

@ -1,10 +1,6 @@
package forgepb
import (
"fmt"
"os"
"path/filepath"
"github.com/destel/rill"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/gitpb"
@ -62,6 +58,7 @@ func (f *Forge) ScanRepoDir() error {
return err
}
log.Info("doing reload()")
stats := f.RillRepos(reloadCheck)
for _, stat := range stats {
if stat.Err == nil {
@ -111,60 +108,3 @@ func (f *Forge) rillScanDirsNew(fullpaths []string) (int, error) {
return counter, err
}
// doesn't enter the directory any further when it finds a .git/
// not stupid like my old version
func gitDirectoriesNew(srcDir string) ([]string, error) {
var all []string
var trip bool
err := filepath.WalkDir(srcDir, func(path string, d os.DirEntry, err error) error {
if err != nil {
// Handle possible errors, like permission issues
fmt.Fprintf(os.Stderr, "error accessing path %q: %v\n", path, err)
return err
}
if d.IsDir() {
// log.Info("path is dir", path)
} else {
_, fname := filepath.Split(path)
switch fname {
case "repos.pb":
case "go.work":
case "go.work.last":
case "go.work.sum":
default:
// todo: figure out a way to do padding for init()
if trip == false {
log.Info("WARNING:")
}
log.Info("WARNING: you have an untracked file outside of any .git repository:", path)
trip = true
}
return nil
}
gitdir := filepath.Join(path, ".git")
_, err2 := os.Stat(gitdir)
if !os.IsNotExist(err2) {
all = append(all, path)
return filepath.SkipDir
}
return nil
})
//
// probably always leave this here forever
// this check, along with CheckDirty() makes sure you can safely delete ~/go/src or the go.work directory
// because everything is either checked in or deleted. An important thing to know!
if trip {
log.Info("WARNING:")
log.Info("WARNING: there isn't a way to disable this warning yet")
log.Info("WARNING: probably this is a good thing however. you don't want to leave files outside of git repos here")
log.Info("WARNING: so this warning should probably stay")
log.Info("WARNING:")
log.Info("WARNING: this also might mean you put these files here because you are actively working on them")
log.Info("WARNING: and you don't want to forget about them")
log.Info("WARNING:")
}
return all, err
}

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

@ -12,7 +12,7 @@ type Forge struct {
once sync.Once
Config *ForgeConfigs // config repos for readonly, private, etc
Repos *gitpb.Repos // the repo protobufs
Patchsets *Sets // patches that are in progress
Patchsets *Patchsets // patches that are in progress
hostname string // your hostname
goWork bool // means the user is currently using a go.work file
}