Compare commits

..

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

25 changed files with 641 additions and 629 deletions

View File

@ -6,10 +6,8 @@ BUILDTIME = $(shell date +%Y.%m.%d_%H%M)
# make gocui # try the ncurses gui plugin # make gocui # try the ncurses gui plugin
# make andlabs # try the andlabs gui plugin (uses GTK) # make andlabs # try the andlabs gui plugin (uses GTK)
default: install-verbose tag default: install-verbose
forge patch list
tag:
forge tag list
vet: vet:
@GO111MODULE=off go vet @GO111MODULE=off go vet

View File

@ -12,6 +12,39 @@ func savePatchset(pset *forgepb.Patchset) error {
log.Info("savePatches() GIT_AUTHOR_EMAIL", pset.GetGitAuthorEmail()) log.Info("savePatches() GIT_AUTHOR_EMAIL", pset.GetGitAuthorEmail())
log.Info("savePatches() Branch Name", pset.GetStartBranchName()) log.Info("savePatches() Branch Name", pset.GetStartBranchName())
log.Info("savePatches() Start Hash", pset.GetStartBranchHash()) log.Info("savePatches() Start Hash", pset.GetStartBranchHash())
var count int
var bad int
var lasterr error
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
basedir := filepath.Join(os.Getenv("FORGE_CONFIG"), "currentpatches")
if fullname, err := savePatchFile(p, basedir); err != nil {
log.Info(fullname, "save failed", err)
bad += 1
lasterr = err
}
count += 1
}
log.Info("pset has", count, "total patches, ", bad, "bad save patches")
if bad == 0 {
return lasterr
}
return nil
}
*/
/*
// re-run git CheckDirty() on everything
func IsAnythingDirty() bool {
doCheckDirtyAndConfigSave()
found := me.forge.FindDirty()
if found.Len() == 0 {
return false
} else {
return true
}
} }
*/ */
@ -29,6 +62,43 @@ func countCurrentPatches(repo *gitpb.Repo) int {
return len(result.Stdout) return len(result.Stdout)
} }
func savePatchFile(p *forgepb.Patch, basedir string) (string, error) {
basepath, filename := filepath.Split(p.Filename)
fulldir := filepath.Join(basedir, basepath)
err := os.MkdirAll(fulldir, os.ModePerm)
if err != nil {
log.Info("applyPathces() MkdirAll failed for", fulldir)
log.Info("applyPathces() MkdirAll failed err", err)
return "", err
}
tmpname := filepath.Join(fulldir, filename)
log.Info("pset filename FILENAME IS REAL?", tmpname)
raw, _ := os.OpenFile(tmpname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
raw.Write(p.Data)
raw.Close()
return tmpname, nil
}
func readPatchFile(pbfile string) (*forgepb.Patchset, error) {
bytes, err := os.ReadFile(pbfile)
if err != nil {
log.Info("readfile error", pbfile, err)
return nil, err
}
return handleBytes(bytes)
}
func handleBytes(bytes []byte) (*forgepb.Patchset, error) {
var pset *forgepb.Patchset
pset = new(forgepb.Patchset)
err := pset.Unmarshal(bytes)
if err != nil {
log.Info("Unmarshal failed", err)
return nil, err
}
return pset, nil
}
func doRegister(newurl string) error { func doRegister(newurl string) error {
var url string var url string
url = me.urlbase + "/register?url=" + newurl url = me.urlbase + "/register?url=" + newurl

85
argv.go
View File

@ -6,8 +6,6 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"go.wit.com/lib/gui/prep"
) )
/* /*
@ -22,6 +20,7 @@ type args struct {
Clean *CleanCmd `arg:"subcommand:clean" help:"start over at the beginning"` Clean *CleanCmd `arg:"subcommand:clean" help:"start over at the beginning"`
Commit *CommitCmd `arg:"subcommand:commit" help:"'git commit' but errors out if on wrong branch"` Commit *CommitCmd `arg:"subcommand:commit" help:"'git commit' but errors out if on wrong branch"`
Config *ConfigCmd `arg:"subcommand:config" help:"show your .config/forge/ settings"` Config *ConfigCmd `arg:"subcommand:config" help:"show your .config/forge/ settings"`
Debug *EmptyCmd `arg:"subcommand:debug" help:"debug forge"`
Dirty *DirtyCmd `arg:"subcommand:dirty" help:"show dirty git repos"` Dirty *DirtyCmd `arg:"subcommand:dirty" help:"show dirty git repos"`
GitFetch *FindCmd `arg:"subcommand:fetch" help:"run 'git fetch master'"` GitFetch *FindCmd `arg:"subcommand:fetch" help:"run 'git fetch master'"`
Gui *EmptyCmd `arg:"subcommand:gui" help:"open the gui"` Gui *EmptyCmd `arg:"subcommand:gui" help:"open the gui"`
@ -30,7 +29,6 @@ type args struct {
Normal *NormalCmd `arg:"subcommand:normal" help:"set every repo to the default state for software development"` Normal *NormalCmd `arg:"subcommand:normal" help:"set every repo to the default state for software development"`
Patch *PatchCmd `arg:"subcommand:patch" help:"make patchsets"` Patch *PatchCmd `arg:"subcommand:patch" help:"make patchsets"`
Pull *PullCmd `arg:"subcommand:pull" help:"run 'git pull'"` Pull *PullCmd `arg:"subcommand:pull" help:"run 'git pull'"`
Tag *TagCmd `arg:"subcommand:tag" help:"manage git tags"`
URL string `arg:"--connect" help:"forge url"` URL string `arg:"--connect" help:"forge url"`
All bool `arg:"--all" help:"git commit --all"` All bool `arg:"--all" help:"git commit --all"`
Build string `arg:"--build" help:"build a repo"` Build string `arg:"--build" help:"build a repo"`
@ -55,8 +53,13 @@ type CommitCmd struct {
type testCmd string type testCmd string
type CleanCmd struct { type CleanCmd struct {
Verify *EmptyCmd `arg:"subcommand:verify" help:"rescan repo"` Delete *EmptyCmd `arg:"subcommand:delete" help:"rescan repo"`
Repo string `arg:"--repo" help:"which repo to look at"` Devel *CleanDevelCmd `arg:"subcommand:devel" help:"clean and verify the devel branches"`
Force *EmptyCmd `arg:"subcommand:force" help:"do destructive stuff"`
GitReset *EmptyCmd `arg:"subcommand:git-reset" help:"git reset --hard"`
Pub *EmptyCmd `arg:"subcommand:pub" help:"clean target version numbers"`
User *EmptyCmd `arg:"subcommand:user" help:"clean the user branches"`
Repo string `arg:"--repo" help:"which repo to look at"`
} }
type CleanDevelCmd struct { type CleanDevelCmd struct {
@ -82,12 +85,6 @@ type PullCmd struct {
Patches *EmptyCmd `arg:"subcommand:patches" help:"only check repos with patches"` Patches *EmptyCmd `arg:"subcommand:patches" help:"only check repos with patches"`
} }
type TagCmd struct {
List *EmptyCmd `arg:"subcommand:list" help:"list the tags"`
Clean *EmptyCmd `arg:"subcommand:clean" help:"clean out old and duplicate tags"`
Delete string `arg:"--delete" help:"delete a tag"`
}
type ConfigAddCmd struct { type ConfigAddCmd struct {
Path string `arg:"--path" help:"absolute path of the git repo"` Path string `arg:"--path" help:"absolute path of the git repo"`
GoPath string `arg:"--gopath" help:"GO path of the git repo"` GoPath string `arg:"--gopath" help:"GO path of the git repo"`
@ -149,49 +146,63 @@ forge -- a tool to manage lots of git repos. forge includes a GUI and TUI.
` `
} }
// handles shell autocomplete //
func DoAutoComplete(pb *prep.Auto) { // handles shell autocomplete
switch pb.Cmd { //
func DoAutoComplete(argv []string) {
// fmt.Fprintln(os.Stderr, "") // these are for debugging
// fmt.Fprintln(os.Stderr, "in autocomplete:", argv) // these are for debugging
switch argv[0] {
case "checkout": case "checkout":
pb.Autocomplete2("devel master user") fmt.Println("devel master user")
case "clean": case "clean":
pb.Autocomplete2("") // me.pp.WriteHelp(os.Stderr)
// me.pp.WriteUsageForSubcommand(os.Stderr, me.pp.SubcommandNames()...)
// me.pp.WriteHelpForSubcommand(os.Stderr, me.pp.SubcommandNames()...)
// me.pp.WriteHelpForSubcommand(os.Stderr, "clean")
fmt.Println("--force")
case "commit": case "commit":
pb.Autocomplete2("--all") fmt.Println("--all")
case "config": case "config":
fmt.Println("add fix list") fmt.Println("add fix list debug")
case "dirty": case "dirty":
fmt.Println("") fmt.Println("")
case "gui": case "gui":
fmt.Println("") if ifBlank(argv[1]) {
case "--gui": fmt.Fprintln(os.Stderr, "")
pb.Autocomplete2("andlabs gocui") fmt.Fprintln(os.Stderr, "CUI: terminal interface using 'gocui'")
fmt.Fprintln(os.Stderr, "GUI: linux and macos GUI using GTK")
} else {
fmt.Println("CUI GUI")
}
case "list": case "list":
pb.Autocomplete2("--mine --favorites --dirty") fmt.Println("--full")
case "merge": case "merge":
pb.Autocomplete2("devel master --all") fmt.Println("devel master")
case "normal": case "normal":
pb.Autocomplete2("on off") fmt.Println("on off")
case "pull": case "pull":
pb.Autocomplete2("--force check") fmt.Println("--force check")
case "patch": case "patch":
fmt.Println("check get list repos submit show") fmt.Println("check get list repos submit show")
case "tag": case "user":
fmt.Println("list --delete clean") fmt.Println("--force")
case "devel":
fmt.Println("--force")
case "master":
fmt.Println("")
case "verify":
fmt.Println("user devel master")
default: default:
if pb.Cmd == "" { if argv[0] == ARGNAME {
pb.Autocomplete2("help list checkout clean commit dirty fetch gui normal merge patch pull tag --gui") // list the subcommands here
} else { fmt.Println("help list checkout clean commit dirty debug fetch gui normal merge patch pull")
pb.Autocomplete2("list checkout clean commit dirty normal merge tag")
} }
} }
os.Exit(0) os.Exit(0)
} }
func (args) Appname() string {
return ARGNAME
}
func ifBlank(arg string) bool { func ifBlank(arg string) bool {
if arg == "''" { if arg == "''" {
// if empty, the user has not typed something // if empty, the user has not typed something
@ -200,6 +211,6 @@ func ifBlank(arg string) bool {
return false return false
} }
func (a args) DoAutoComplete(autoArgv *prep.Auto) { func (a args) DoAutoComplete(argv []string) {
DoAutoComplete(autoArgv) DoAutoComplete(argv)
} }

View File

@ -1,36 +0,0 @@
package main
// functions to import and export the protobuf
// data to and from config files
import (
"fmt"
"go.wit.com/lib/config"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func forgeConfigSave() error {
return me.forge.Config.ConfigSave()
}
func setForgeMode(fmode forgepb.ForgeMode) {
if me.forge.Config.Mode == fmode {
return
}
log.Info("changing mode", me.forge.Config.Mode, fmode)
me.forge.Config.Mode = fmode
config.SetChanged("forge", true)
me.forge.Config.ConfigSave()
}
func sampleConfig(all *forgepb.ForgeConfigs) {
new1 := new(forgepb.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

@ -1,88 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// An app to submit patches for the 30 GO GUI repos
import (
"fmt"
"time"
"go.wit.com/gui"
"go.wit.com/lib/debugger"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func init() {
if debugger.ArgDebug() {
log.Info("cmd line --debugger == true")
go func() {
log.Sleep(2)
debugger.DebugWindow()
}()
}
}
func debug() {
defer func() {
if r := recover(); r != nil {
gui.Crash(r, "forge debug()")
}
}()
time.Sleep(2 * time.Second)
for {
now := time.Now()
if me.repoAllB != nil {
tmp := fmt.Sprintf("All (%d)", me.forge.Repos.Len())
me.repoAllB.SetLabel(tmp)
}
if me.repoDevelMergeB != nil {
found := findMergeToDevel()
tmp := fmt.Sprintf("needs merge to devel (%d)", found.Len())
me.repoDevelMergeB.SetLabel(tmp)
}
if me.repoWritableB != nil {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
found.AppendByFullPath(repo)
}
tmp := fmt.Sprintf("writable (%d)", found.Len())
me.repoWritableB.SetLabel(tmp)
}
dirty := me.forge.CheckDirty()
if me.repoDirtyB != nil {
tmp := fmt.Sprintf("dirty (%d)", dirty.Len())
me.repoDirtyB.SetLabel(tmp)
}
if me.reposWinB != nil {
tmp := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len())
if dirty.Len() > 0 {
tmp = fmt.Sprintf("Repos (%d) (%d dirty)", me.forge.Repos.Len(), dirty.Len())
}
me.reposWinB.SetLabel(tmp)
}
// check for new patches
log.Info("should check for packages here")
// if err := me.forge.ListPatches(); err != nil {
// log.Info("List Patchsets Failed", err)
// }
log.Printf("finished a forge scan here in (%s)\n", shell.FormatDuration(time.Since(now)))
time.Sleep(90 * time.Second)
}
}

View File

@ -8,7 +8,6 @@ import (
"time" "time"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log" "go.wit.com/log"
) )
@ -27,7 +26,6 @@ func doCheckout() error {
} }
if argv.Checkout.Devel != nil { if argv.Checkout.Devel != nil {
// setForgeMode(forgepb.ForgeMode_DEVEL)
if err := me.forge.DoAllCheckoutDevelNew(argv.Force); err != nil { if err := me.forge.DoAllCheckoutDevelNew(argv.Force); err != nil {
badExit(err) badExit(err)
} }
@ -35,8 +33,6 @@ func doCheckout() error {
} }
if argv.Checkout.Master != nil { if argv.Checkout.Master != nil {
setForgeMode(forgepb.ForgeMode_MASTER) // disable "normal" mode if set
if err := me.forge.DoAllCheckoutMaster(); err != nil { if err := me.forge.DoAllCheckoutMaster(); err != nil {
badExit(err) badExit(err)
} }

View File

@ -6,50 +6,14 @@ package main
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func checkRemoteBranches(repo *gitpb.Repo) error {
if err := repo.ReloadCheck(); err != nil {
log.Info("need to reload", repo.FullPath)
}
if repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName()) {
} else {
return log.Errorf("remote devel is out of sync with local: todo: git pull or git fetch")
}
if repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName()) {
} else {
return log.Errorf("remote master is out of sync with local: todo: git pull or git fetch")
}
return nil
}
// reverts all repos back to the original master branches // reverts all repos back to the original master branches
// automatically deletes local devel and user branches // automatically deletes local devel and user branches
func doClean() error { func doClean() error {
setForgeMode(forgepb.ForgeMode_CLEAN)
if argv.Clean.Verify != nil {
stats := me.forge.RillRepos(checkRemoteBranches)
for path, stat := range stats {
if stat.Err == nil {
continue
}
dur := stat.End.Sub(stat.Start)
if dur > time.Second {
log.Infof("%s checkRemoteBranches() took a long time (%s) (err=%v)\n", path, shell.FormatDuration(dur), stat.Err)
}
}
// log.Infof("%-60s, %-60s %v %s\n", stat.Start, stat.End.String(), dur, path)
// log.Infof("%-30v %s %v\n", dur, path, stat.Err)
return nil
}
// fix this to work, then delete all the other options for "forge clean' // fix this to work, then delete all the other options for "forge clean'
if err := me.forge.DoAllCheckoutMaster(); err != nil { if err := me.forge.DoAllCheckoutMaster(); err != nil {
// badExit(err) // badExit(err)
@ -249,7 +213,7 @@ func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
return err return err
} }
cmd := []string{"git", "merge something somehow"} cmd := []string{"git", "merge something somehow"}
log.Info("devel local, remote and master branches are wrong", repo.GetGoPath(), cmd, b1) log.Info("DEVEL LOCAL NEEDS GIT MERGE TO MASTER", repo.GetGoPath(), cmd, b1)
// _, err := repo.RunVerbose(cmd) // _, err := repo.RunVerbose(cmd)
return nil return nil
} }

View File

@ -6,7 +6,6 @@ package main
import ( import (
"os" "os"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
@ -22,16 +21,22 @@ func doCommit() error {
badExit(err) badExit(err)
} }
newpatches = true newpatches = true
repo.CheckDirty() }
if !argv.Commit.Submit {
okExit("")
} }
if newpatches { if newpatches {
config.SetChanged("repos", true)
return doPatchSubmit() return doPatchSubmit()
} }
okExit("") okExit("")
} }
repo := findCurrentPwdRepoOrDie() pwd, _ := os.Getwd()
repo := me.forge.Repos.FindByFullPath(pwd)
if repo == nil {
log.Info("todo: forge doesn't know how to work here yet")
okExit("")
}
if !repo.CheckDirty() { if !repo.CheckDirty() {
okExit(log.Sprintf("this repo %s is not dirty.\n\n--all # commit all changes in all repos", repo.GetFullPath())) okExit(log.Sprintf("this repo %s is not dirty.\n\n--all # commit all changes in all repos", repo.GetFullPath()))

View File

@ -55,6 +55,7 @@ func doConfig() {
} }
log.Info("config.PathLock =", me.forge.Config.PathLock) log.Info("config.PathLock =", me.forge.Config.PathLock)
log.Info("config.GoSrc =", me.forge.Config.GoSrc)
me.forge.ConfigPrintTable() me.forge.ConfigPrintTable()
okExit("") okExit("")

168
doDebug.go Normal file
View File

@ -0,0 +1,168 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"bytes"
"fmt"
"io"
"log"
"reflect"
"unicode/utf8"
"go.wit.com/lib/protobuf/bugpb"
"go.wit.com/lib/protobuf/forgepb"
"golang.org/x/text/encoding/charmap"
"google.golang.org/protobuf/proto"
)
func doDebug() {
me.forge = forgepb.InitPB()
me.forge.ScanGoSrc()
if err := me.forge.ConfigSave(); err != nil {
if err := me.forge.Repos.ConfigSave(); err != nil {
err := ValidateProtoUTF8(me.forge.Repos)
if err != nil {
log.Printf("Protobuf UTF-8 validation failed: %v\n", err)
}
if err := bugpb.SanitizeProtoUTF8(me.forge.Repos); err != nil {
log.Fatalf("Sanitization failed: %v", err)
}
}
// badExit(err)
}
me.forge.SetConfigSave(true)
me.forge.Exit()
okExit("this never runs")
}
// ValidateProtoUTF8 checks all string fields in a proto.Message recursively.
func ValidateProtoUTF8(msg proto.Message) error {
return validateValue(reflect.ValueOf(msg), "")
}
func validateValue(val reflect.Value, path string) error {
if !val.IsValid() {
return nil
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return nil
}
return validateValue(val.Elem(), path)
}
switch val.Kind() {
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := val.Type().Field(i)
fieldPath := fmt.Sprintf("%s.%s", path, fieldType.Name)
if err := validateValue(field, fieldPath); err != nil {
return err
}
}
case reflect.String:
s := val.String()
if !utf8.ValidString(s) {
return fmt.Errorf("invalid UTF-8 string at %s: %q", path, s)
}
case reflect.Slice:
if val.Type().Elem().Kind() == reflect.Uint8 {
return nil // skip []byte
}
for i := 0; i < val.Len(); i++ {
if err := validateValue(val.Index(i), fmt.Sprintf("%s[%d]", path, i)); err != nil {
return err
}
}
case reflect.Map:
for _, key := range val.MapKeys() {
valItem := val.MapIndex(key)
if err := validateValue(valItem, fmt.Sprintf("%s[%v]", path, key)); err != nil {
return err
}
}
}
return nil
}
// SanitizeProtoUTF8 fixes all invalid UTF-8 strings in a proto.Message recursively.
func SanitizeProtoUTF8(msg proto.Message) error {
return sanitizeValue(reflect.ValueOf(msg), "")
}
func sanitizeValue(val reflect.Value, path string) error {
if !val.IsValid() {
return nil
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return nil
}
return sanitizeValue(val.Elem(), path)
}
switch val.Kind() {
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := val.Type().Field(i)
if !field.CanSet() {
continue
}
if err := sanitizeValue(field, fmt.Sprintf("%s.%s", path, fieldType.Name)); err != nil {
return err
}
}
case reflect.String:
s := val.String()
if !utf8.ValidString(s) {
utf8Str, err := latin1ToUTF8(s)
if err != nil {
return fmt.Errorf("failed to convert %s to UTF-8: %v", path, err)
}
val.SetString(utf8Str)
}
case reflect.Slice:
if val.Type().Elem().Kind() == reflect.Uint8 {
return nil // skip []byte
}
for i := 0; i < val.Len(); i++ {
if err := sanitizeValue(val.Index(i), fmt.Sprintf("%s[%d]", path, i)); err != nil {
return err
}
}
case reflect.Map:
for _, key := range val.MapKeys() {
valItem := val.MapIndex(key)
newItem := reflect.New(valItem.Type()).Elem()
newItem.Set(valItem)
if err := sanitizeValue(newItem, fmt.Sprintf("%s[%v]", path, key)); err != nil {
return err
}
val.SetMapIndex(key, newItem)
}
}
return nil
}
func latin1ToUTF8(input string) (string, error) {
reader := charmap.ISO8859_1.NewDecoder().Reader(bytes.NewReader([]byte(input)))
result, err := io.ReadAll(reader)
if err != nil {
return "", err
}
return string(result), nil
}

110
doGui.go
View File

@ -11,6 +11,7 @@ import (
"time" "time"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/gadgets" "go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
@ -18,20 +19,107 @@ import (
"go.wit.com/log" "go.wit.com/log"
) )
func debug() {
defer func() {
if r := recover(); r != nil {
gui.Crash(r, "forge debug()")
}
}()
time.Sleep(2 * time.Second)
for {
now := time.Now()
if me.repoAllB != nil {
tmp := fmt.Sprintf("All (%d)", me.forge.Repos.Len())
me.repoAllB.SetLabel(tmp)
}
if me.repoDevelMergeB != nil {
found := findMergeToDevel()
tmp := fmt.Sprintf("needs merge to devel (%d)", found.Len())
me.repoDevelMergeB.SetLabel(tmp)
}
if me.repoWritableB != nil {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
found.AppendByFullPath(repo)
}
tmp := fmt.Sprintf("writable (%d)", found.Len())
me.repoWritableB.SetLabel(tmp)
}
dirty := me.forge.CheckDirty()
if me.repoDirtyB != nil {
tmp := fmt.Sprintf("dirty (%d)", dirty.Len())
me.repoDirtyB.SetLabel(tmp)
}
if me.reposWinB != nil {
tmp := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len())
if dirty.Len() > 0 {
tmp = fmt.Sprintf("Repos (%d) (%d dirty)", me.forge.Repos.Len(), dirty.Len())
}
me.reposWinB.SetLabel(tmp)
}
// check for new patches
log.Info("should check for packages here")
// if err := me.forge.ListPatches(); err != nil {
// log.Info("List Patchsets Failed", err)
// }
log.Printf("finished a forge scan here in (%s)\n", shell.FormatDuration(time.Since(now)))
time.Sleep(90 * time.Second)
}
}
func doGui() { func doGui() {
win := gadgets.NewGenericWindow("Forge: A federated git development tool by WIT.COM", "Current Settings") if me.forge.Config.GetDefaultGui() == "" {
win.Custom = func() { me.forge.Config.DefaultGui = "gocui"
me.forge.ConfigSave()
}
me.myGui = gui.New()
me.myGui.InitEmbed(resources)
me.myGui.SetAppDefaultPlugin(me.forge.Config.DefaultGui) // sets the default GUI plugin to use
if pname, err := me.myGui.Default(); err != nil {
if !fhelp.BuildPlugin("gocui") {
log.Info("You can't run the forge GUI since the plugins did not build", pname)
okExit("")
} else {
if err := me.myGui.LoadToolkitNew("gocui"); err != nil {
log.Info("The plugins built, but still failed to load", pname)
badExit(err)
}
log.Info("The plugins built and loaded!", pname)
}
}
mainWindow := gadgets.NewGenericWindow("Forge: A federated git development tool by WIT.COM", "Current Settings")
mainWindow.Custom = func() {
log.Warn("MAIN WINDOW CLOSE") log.Warn("MAIN WINDOW CLOSE")
okExit("") okExit("")
} }
drawWindow(mainWindow)
// sits here forever
debug()
}
func drawWindow(win *gadgets.GenericWindow) {
grid := win.Group.RawGrid() grid := win.Group.RawGrid()
if me.forge.Config.GetPathLock() { if me.forge.Config.GetPathLock() {
me.goSrcPwd = gadgets.NewOneLiner(grid, "Working Directory") me.goSrcPwd = gadgets.NewOneLiner(grid, "Working Directory")
me.goSrcPwd.SetText(me.forge.Config.ReposDir) me.goSrcPwd.SetText(me.forge.GetGoSrc())
} else { } else {
me.goSrcEdit = gadgets.NewBasicEntry(grid, "Working Directory") me.goSrcEdit = gadgets.NewBasicEntry(grid, "Working Directory")
me.goSrcEdit.SetText(me.forge.Config.ReposDir) me.goSrcEdit.SetText(me.forge.GetGoSrc())
me.goSrcEdit.Custom = func() { me.goSrcEdit.Custom = func() {
log.Info("updating text to", me.goSrcEdit.String()) log.Info("updating text to", me.goSrcEdit.String())
} }
@ -132,13 +220,13 @@ func makeStandardReposGrid(pb *gitpb.Repos) *gitpb.ReposTable {
t := pb.NewTable("testDirty") t := pb.NewTable("testDirty")
t.NewUuid() t.NewUuid()
sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string { sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string {
return r.GetNamespace() return r.GetGoPath()
}) })
// t.Custom = func() { // t.Custom = func() {
// log.Info("close grid?") // log.Info("close grid?")
// } // }
sf.Custom = func(r *gitpb.Repo) { sf.Custom = func(r *gitpb.Repo) {
log.Info("do button click on", r.GetNamespace()) log.Info("do button click on", r.GetGoPath())
} }
t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time { t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time {
return repo.NewestTime() return repo.NewestTime()
@ -197,7 +285,7 @@ func findMergeToMaster() *gitpb.Repos {
for all.Scan() { for all.Scan() {
repo := all.Next() repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetNamespace()) { if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue continue
} }
/* /*
@ -215,7 +303,7 @@ func findMergeToMaster() *gitpb.Repos {
// everything is normal // everything is normal
} else { } else {
repo.State = "DEVEL < MASTER" repo.State = "DEVEL < MASTER"
log.Info("SERIOUS ERROR. DEVEL BRANCH IS BEHIND MASTER", repo.GetNamespace()) log.Info("SERIOUS ERROR. DEVEL BRANCH IS BEHIND MASTER", repo.GetGoPath())
} }
// this sees if devel has patches for master. If it does, add it to me.found // this sees if devel has patches for master. If it does, add it to me.found
@ -247,7 +335,7 @@ func mergeDevelToMaster(doit bool) {
all := found.SortByFullPath() all := found.SortByFullPath()
for all.Scan() { for all.Scan() {
repo := all.Next() repo := all.Next()
log.Info("repo:", repo.GetNamespace()) log.Info("repo:", repo.GetGoPath())
if result, err := repo.MergeToMaster(); err == nil { if result, err := repo.MergeToMaster(); err == nil {
log.Warn("THINGS SEEM OK", repo.GetFullPath()) log.Warn("THINGS SEEM OK", repo.GetFullPath())
for _, line := range result.Stdout { for _, line := range result.Stdout {
@ -298,10 +386,10 @@ func mergeUserToDevel(doit bool) {
b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero
if b1 == 0 { if b1 == 0 {
// log.Info("User is already merged into Devel", repo.GetNamespace(), cmd) // log.Info("User is already merged into Devel", repo.GetGoPath(), cmd)
return return
} }
log.Info("merging user into devel repo:", repo.GetNamespace()) log.Info("merging user into devel repo:", repo.GetGoPath())
if result, err := repo.MergeToDevel(); err == nil { if result, err := repo.MergeToDevel(); err == nil {
log.Warn("THINGS SEEM OK", repo.GetFullPath()) log.Warn("THINGS SEEM OK", repo.GetFullPath())
for _, line := range result.Stdout { for _, line := range result.Stdout {

View File

@ -4,61 +4,11 @@
package main package main
import ( import (
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func doMerge() error {
if argv.All == true {
start := time.Now()
repos, err := doMergeDevel()
dur := time.Since(start)
if err != nil {
badExit(err)
}
log.Printf("Merged %d devel branches in %s\n", repos.Len(), shell.FormatDuration(dur))
start = time.Now()
repos, err = doMergeMaster()
dur = time.Since(start)
if err != nil {
badExit(err)
}
log.Printf("Merged %d master branches in %s\n", repos.Len(), shell.FormatDuration(dur))
okExit("")
}
if argv.Merge.Devel != nil {
start := time.Now()
repos, err := doMergeDevel()
dur := time.Since(start)
if err != nil {
badExit(err)
}
log.Printf("Merged %d devel branches in %s\n", repos.Len(), shell.FormatDuration(dur))
okExit("")
}
if argv.Merge.Master != nil {
start := time.Now()
repos, err := doMergeMaster()
dur := time.Since(start)
if err != nil {
badExit(err)
}
log.Printf("Merged %d master branches in %s\n", repos.Len(), shell.FormatDuration(dur))
okExit("")
}
repo := findCurrentPwdRepoOrDie()
if err := repoMergeToDevel(repo); err != nil {
badRepoExit(repo, err)
}
return nil
}
func doMergeReport() *forgepb.Patches { func doMergeReport() *forgepb.Patches {
found := forgepb.NewPatches() found := forgepb.NewPatches()
for repo := range me.forge.Repos.IterAll() { for repo := range me.forge.Repos.IterAll() {
@ -81,14 +31,14 @@ func doMergeDevel() (*gitpb.Repos, error) {
found := findMergeToDevel() found := findMergeToDevel()
for repo := range found.IterAll() { for repo := range found.IterAll() {
if repo.CheckDirty() { if repo.CheckDirty() {
log.Info("repo is dirty", repo.GetFullPath()) log.Info("repo is dirty", repo.GetGoPath())
continue continue
} }
log.Infof("%s starting git merge\n", repo.FullPath) log.Infof("%s starting git merge\n", repo.FullPath)
if repo.CheckoutDevel() { if repo.CheckoutDevel() {
log.Info("checkout devel failed", repo.GetGoPath()) log.Info("checkout devel failed", repo.GetGoPath())
err = log.Errorf("checkout devel failed") err = log.Errorf("checkout devel failed")
badExit(err) break
} }
// hash differences when merging user into devel branch // hash differences when merging user into devel branch
out := repo.GetBranchDifferences(repo.GetDevelBranchName(), repo.GetUserBranchName()) out := repo.GetBranchDifferences(repo.GetDevelBranchName(), repo.GetUserBranchName())
@ -100,44 +50,17 @@ func doMergeDevel() (*gitpb.Repos, error) {
err = log.Errorf("merge from user failed") err = log.Errorf("merge from user failed")
// log.Info(strings.Join(r.Stdout, "\n")) // log.Info(strings.Join(r.Stdout, "\n"))
// log.Info(strings.Join(r.Stderr, "\n")) // log.Info(strings.Join(r.Stderr, "\n"))
badExit(err) break
} }
done.Append(repo) done.Append(repo)
config.SetChanged("repos", true)
} }
return done, err return done, err
} }
func repoMergeToDevel(repo *gitpb.Repo) error {
if repo.CheckDirty() {
return log.Errorf("can not merge. repo is dirty")
}
log.Infof("%s starting git merge\n", repo.FullPath)
if repo.CheckoutDevel() {
log.Info("checkout devel failed", repo.GetGoPath())
err := log.Errorf("checkout devel failed")
badExit(err)
}
// hash differences when merging user into devel branch
out := repo.GetBranchDifferences(repo.GetDevelBranchName(), repo.GetUserBranchName())
for i, hash := range out {
log.Info("MERGE HASH FROM USER TO DEVEL", i, hash)
}
if _, err := repo.MergeToDevel(); err != nil {
log.Info("merge from user failed", repo.GetGoPath(), err)
// err := log.Errorf("merge from user failed")
// log.Info(strings.Join(r.Stdout, "\n"))
// log.Info(strings.Join(r.Stderr, "\n"))
badExit(err)
}
config.SetChanged("repos", true)
return nil
}
func doMergeMaster() (*gitpb.Repos, error) { func doMergeMaster() (*gitpb.Repos, error) {
var err error var err error
setForgeMode(forgepb.ForgeMode_MASTER) me.forge.Config.Mode = forgepb.ForgeMode_MASTER // disable "normal" mode if set
configSave = true
done := gitpb.NewRepos() done := gitpb.NewRepos()
found := findMergeToMaster() found := findMergeToMaster()
for repo := range found.IterAll() { for repo := range found.IterAll() {
@ -150,7 +73,7 @@ func doMergeMaster() (*gitpb.Repos, error) {
if repo.CheckoutMaster() { if repo.CheckoutMaster() {
log.Info("checkout devel failed", repo.GetGoPath()) log.Info("checkout devel failed", repo.GetGoPath())
err = log.Errorf("checkout devel failed") err = log.Errorf("checkout devel failed")
badExit(err) break
} }
if _, err := repo.MergeToMaster(); err != nil { if _, err := repo.MergeToMaster(); err != nil {
@ -158,11 +81,10 @@ func doMergeMaster() (*gitpb.Repos, error) {
err = log.Errorf("merge from user failed") err = log.Errorf("merge from user failed")
// log.Info(strings.Join(r.Stdout, "\n")) // log.Info(strings.Join(r.Stdout, "\n"))
// log.Info(strings.Join(r.Stderr, "\n")) // log.Info(strings.Join(r.Stderr, "\n"))
badExit(err) break
} }
done.Append(repo) done.Append(repo)
config.SetChanged("repos", true)
} }
return done, err return done, err
} }

View File

@ -6,11 +6,8 @@ package main
// checks that repos are in a "normal" state // checks that repos are in a "normal" state
import ( import (
"path/filepath"
"strings"
"time" "time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
@ -38,7 +35,7 @@ func doNormal() bool {
log.Info("Some repos are not in a 'normal' state. error count =", count) log.Info("Some repos are not in a 'normal' state. error count =", count)
log.Info("TODO: list the repos here. forge patch repos?") log.Info("TODO: list the repos here. forge patch repos?")
dumpWorkRepos() dumpWorkRepos()
config.SetChanged("repos", true) configSave = true
return false return false
} }
return true return true
@ -50,63 +47,28 @@ func doNormal() bool {
// this needs to run each time in case repos were added manually by the user // this needs to run each time in case repos were added manually by the user
// this also verifies that // this also verifies that
func checkNormalRepoState(repo *gitpb.Repo) error { func checkNormalRepoState(repo *gitpb.Repo) error {
var err error
tmp := filepath.Join(me.forge.Config.ReposDir, repo.GetNamespace())
if tmp != repo.FullPath {
log.Infof("%s != %s\n", repo.FullPath, tmp)
if strings.HasPrefix(repo.FullPath, me.forge.Config.ReposDir) {
tmp = strings.TrimPrefix(repo.FullPath, me.forge.Config.ReposDir)
tmp = strings.Trim(tmp, "/")
repo.Namespace = tmp
err = log.Errorf("namespace set to filepath")
}
} else {
// log.Infof("%s == %s\n", repo.FullPath, tmp)
}
tmp = strings.Trim(repo.Namespace, "/")
if tmp != repo.Namespace {
err = log.Errorf("junk in ns %s", repo.Namespace)
repo.Namespace = tmp
}
if repo.GetMasterBranchName() == "" { if repo.GetMasterBranchName() == "" {
me.forge.VerifyBranchNames(repo) me.forge.VerifyBranchNames(repo)
configSave = true
log.Info("ABNORMAL: master branch name was blank in", repo.GetFullPath()) log.Info("ABNORMAL: master branch name was blank in", repo.GetFullPath())
} }
if repo.GetMasterBranchName() == "" { if repo.GetMasterBranchName() == "" {
me.forge.VerifyBranchNames(repo) return log.Errorf("master branch name blank")
err = log.Errorf("master branch name blank")
} }
if repo.GetDevelBranchName() == "" { if repo.GetDevelBranchName() == "" {
me.forge.VerifyBranchNames(repo) return log.Errorf("devel branch name blank")
err = log.Errorf("devel branch name blank")
} }
if repo.GetUserBranchName() == "" { if repo.GetUserBranchName() == "" {
me.forge.VerifyBranchNames(repo) return log.Errorf("user branch name blank")
err = log.Errorf("user branch name blank")
}
if repo.GetGoPath() == repo.GetNamespace() {
// log.Info(repo.FullPath, "gopath == namespace", repo.GetGoPath(), repo.GetNamespace())
} else {
log.Info(repo.FullPath, "gopath != namespace", repo.GetGoPath(), repo.GetNamespace())
} }
repo.MakeLocalDevelBranch() repo.MakeLocalDevelBranch()
repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName())
repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName())
if repo.GetCurrentBranchName() != repo.GetUserBranchName() { if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
log.Infof("changing to user(%s) branch: %s\n", repo.GetUserBranchName(), repo.FullPath) configSave = true
log.Info("changing to user branch", repo.FullPath)
repo.CheckoutUser() repo.CheckoutUser()
repo.ReloadCheck() repo.Reload()
err = log.Errorf("now on user branch") return log.Errorf("now on user branch")
} }
return nil
if me.forge.Config.IsReadOnly(repo.GetGoPath()) != repo.GetReadOnly() {
repo.ReadOnly = me.forge.Config.IsReadOnly(repo.GetGoPath())
log.Info("damnit", repo.FullPath)
err = log.Errorf("readonly bit wrong")
}
return err
} }

View File

@ -45,16 +45,7 @@ func doPatchSubmit() error {
if err != nil { if err != nil {
return err return err
} }
if pset.Patches == nil { _, _, err = pset.Patches.HttpPostVerbose(myServer(), "new")
log.Info("pset.Patches == nil")
return err
}
if pset.Patches.Len() == 0 {
log.Info("did not find any patches")
return nil
}
pset.PrintTable()
_, _, err = pset.HttpPost(myServer(), "new")
return err return err
} }
@ -73,7 +64,7 @@ func doPatch() error {
} }
if argv.Patch.Get != nil { if argv.Patch.Get != nil {
psets := forgepb.NewSets() psets := forgepb.NewPatchsets()
newpb, _, _ := psets.HttpPostVerbose(myServer(), "get") newpb, _, _ := psets.HttpPostVerbose(myServer(), "get")
newpb.PrintTable() newpb.PrintTable()
me.forge.Patchsets = newpb me.forge.Patchsets = newpb
@ -101,80 +92,66 @@ func doPatch() error {
if argv.Patch.List != nil { if argv.Patch.List != nil {
var changed bool var changed bool
newpatches := new(forgepb.Set) newpatches := forgepb.NewPatches()
newpatches.Patches = forgepb.NewPatches()
for pset := range me.forge.Patchsets.IterAll() { for pset := range me.forge.Patchsets.IterAll() {
pset.PrintTable() log.Info(pset.Uuid)
for patch := range pset.Patches.IterAll() { for patch := range pset.Patches.IterAll() {
changed = true if setNewCommitHash(patch) {
if patch.NewHash == "" || patch.NewHash == "na" { changed = true
if newpatches.Patches.AppendByPatchId(patch) { }
log.Info("patchId added here", patch.PatchId) if patch.NewHash == "na" {
} else { newpatches.Append(patch)
log.Info("patchId already here", patch.PatchId) log.Info("apply this patch?")
}
} else {
if err := setNewCommitHash(patch); err != nil {
log.Infof("%s bad check on patch failure %v\n", patch.Filename, err)
return err
}
log.Info("newhash set already here", patch.PatchId, patch.NewHash)
} }
} }
/*
for patch := range pset.Patches.IterAll() {
if repo, ok := me.forge.IsPatchApplied(patch); ok {
log.Info("\tfound patch in repo", repo.Namespace, patch.Filename)
} else {
log.Info("\tdid not find patch", patch.CommitHash, patch.NewHash, patch.Filename)
}
}
*/
} }
if changed { if changed {
if err := me.forge.SavePatchsets(); err != nil { if err := me.forge.SavePatchsets(); err != nil {
log.Warn("savePatchsets() failed", err) log.Warn("savePatchsets() failed", err)
} }
} }
log.Info("NEW PATCHES TABLE") me.forge.Patchsets.PrintTable()
newpatches.PrintTable() if newpatches.Len() != 0 {
for patch := range newpatches.Patches.IterAll() { for patch := range newpatches.IterAll() {
if err := setNewCommitHash(patch); err == nil { log.Info("new patch:", patch.CommitHash, patch.NewHash, patch.Filename)
log.Info("newhash set already here", patch.PatchId, patch.NewHash) repo := me.forge.FindByGoPath(patch.Namespace)
continue if repo == nil {
} log.Info("\tCould not find namespace:", patch.Namespace)
log.Infof("%s is new\n", patch.Filename) continue
repo := me.forge.FindByGoPath(patch.Namespace) }
if repo == nil { if fhelp.QuestionUser("apply this patch?") {
log.Info("\tCould not find namespace:", patch.Namespace) newhash, err := applyAndTrackPatch(repo, patch)
continue log.Info("apply results:", newhash, err)
}
if fhelp.QuestionUser("apply this patch?") {
newhash, err := applyAndTrackPatch(repo, patch)
log.Info("apply results:", newhash, err)
}
}
return nil
/*
if newpatches.Len() != 0 {
for patch := range newpatches.IterAll() {
log.Info("new patch:", patch.CommitHash, patch.NewHash, patch.Filename)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("\tCould not find namespace:", patch.Namespace)
continue
}
} }
return log.Errorf("patches need to be applied")
} }
return log.Errorf("patches need to be applied")
}
// return doPatchList() // return doPatchList()
applied := findApplied() applied := findApplied()
if applied == nil || applied.Len() == 0 { if applied == nil || applied.Len() == 0 {
log.Info("no patches have been appled to the devel branch yet") log.Info("no patches have been appled to the devel branch yet")
return nil
}
// for patch := range applied.IterAll() {
// log.Info("SEND APPLIED: newhash:", patch.NewHash, "commithash:", patch.CommitHash, "patch", patch.Namespace)
// }
newpb, _, err := applied.HttpPostVerbose(myServer(), "applied")
if err != nil {
return err
}
newpb.PrintTable()
return nil return nil
*/ }
// for patch := range applied.IterAll() {
// log.Info("SEND APPLIED: newhash:", patch.NewHash, "commithash:", patch.CommitHash, "patch", patch.Namespace)
// }
newpb, _, err := applied.HttpPostVerbose(myServer(), "applied")
if err != nil {
return err
}
newpb.PrintTable()
return nil
} }
// if nothing, show patches & dirty repos // if nothing, show patches & dirty repos
@ -207,7 +184,7 @@ func dumpWorkRepos() bool {
// returns bad if patches can not be applied // returns bad if patches can not be applied
// logic is not great here but it was a first pass // logic is not great here but it was a first pass
func dumpPatchset(pset *forgepb.Set) bool { func dumpPatchset(pset *forgepb.Patchset) bool {
// don't even bother to continue if we already know it's broken // don't even bother to continue if we already know it's broken
if pset.State == "BROKEN" { if pset.State == "BROKEN" {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS BAD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail()) log.Printf("Patchset Name: %-24s Author: %s <%s> IS BAD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())

125
doTag.go
View File

@ -1,125 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// checks that repos are in a "normal" state
import (
"os"
"path/filepath"
"time"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func FindRepoByFullPath(wd string) *gitpb.Repo {
for repo := range me.forge.Repos.IterAll() {
if repo.FullPath == wd {
return repo
}
}
return nil
}
func findCurrentPwdRepoOrDie() *gitpb.Repo {
wd, err := os.Getwd()
repo := FindRepoByFullPath(wd)
if repo == nil {
log.Info("Could not find repo:", wd)
badExit(err)
}
return repo
}
func doTag() error {
wd, _ := os.Getwd()
if argv.Tag.List != nil {
repo := findCurrentPwdRepoOrDie()
tagTablePB := makeTagTablePB(repo, repo.Tags)
// tbox := win.Bottom.Box().SetProgName("TBOX")
// t.SetParent(tbox)
tagTablePB.MakeTable()
tagTablePB.PrintTable()
log.Info("list tags here", repo.Namespace)
return nil
}
if argv.Tag.Delete != "" {
repo := FindRepoByFullPath(wd)
if repo == nil {
log.Info("Could not find repo:", wd)
return nil
}
// check if the git tag already exists somehow
/*
if !repo.LocalTagExists(testtag) {
log.Info("Tag", testtag, "does not exist")
return log.Errorf("%s TAG DOES NOT EXIST %s", repo.FullPath, testtag)
}
*/
testtag := argv.Tag.Delete
if !argv.Force {
if !fhelp.QuestionUser(log.Sprintf("delete tag '%s'?", testtag)) {
return nil
}
}
log.Info("Delete tag here", testtag)
// delete local and remote tag
repo.RunVerbose([]string{"git", "tag", "--delete", testtag})
repo.RunVerbose([]string{"git", "push", "--delete", "origin", testtag})
return nil
}
log.Info("do other tag stuff here")
return nil
}
func makeTagTablePB(repo *gitpb.Repo, pb *gitpb.GitTags) *gitpb.GitTagsTable {
t := pb.NewTable("tagList")
t.NewUuid()
col := t.AddHash()
col.Width = 12
col = t.AddStringFunc("bashash", func(tag *gitpb.GitTag) string {
_, base := filepath.Split(tag.Refname)
cmd, err := repo.RunStrict([]string{"git", "log", "-1", base, "--format=%H"})
if err != nil {
return "err"
}
if len(cmd.Stdout) == 0 {
return ""
}
return cmd.Stdout[0]
})
col.Width = 12
col = t.AddTimeFunc("ctime", func(tag *gitpb.GitTag) time.Time {
// todo
return tag.Creatordate.AsTime()
})
col.Width = 4
col = t.AddTimeFunc("age", func(repo *gitpb.GitTag) time.Time {
// todo
return time.Now()
})
col.Width = 4
col = t.AddStringFunc("Ref Name", func(r *gitpb.GitTag) string {
_, ref := filepath.Split(r.GetRefname())
return ref
})
col.Width = 16
col = t.AddSubject()
col.Width = -1
return t
}

19
doc.go
View File

@ -3,14 +3,20 @@ forge -- a tool to manage lots of git repos. forge includes a GUI and TUI.
forge only executes the 'git' command. Everything it does, you can run by hand with 'git'. forge only executes the 'git' command. Everything it does, you can run by hand with 'git'.
forge v0.22.138-6-gaea7f16 Built on 2025.09.03_1935
Usage: forge [--debugger] [--logger] [--no-gui] [--gui GUI] [--gui-file GUI-FILE] [--gui-build] [--gui-verbose] [--gui-check-plugin GUI-CHECK-PLUGIN] [--connect CONNECT] [--all] [--build BUILD] [--install INSTALL] [--forge-rebuild] [--force] [--verbose] [--bash] [--auto-complete AUTO-COMPLETE] <command> [<args>]
Options: Options:
--debugger open the debugger window --debugger open the debugger window
--logger open the log.* control window --logger open the log.* control window
--gui GUI select the plugin (andlabs,gocui,etc) --no-gui ignore all these gui problems
--gui GUI Use this gui toolkit [andlabs,gocui,nocui,stdin]
--gui-file GUI-FILE Use a specific plugin.so file
--gui-build attempt to build the GUI plugins
--gui-verbose enable all logging --gui-verbose enable all logging
--bash generate bash completion --gui-check-plugin GUI-CHECK-PLUGIN
--bash generate bash completion hack to verify GO plugins load
--connect CONNECT forge url --connect CONNECT forge url
--all git commit --all --all git commit --all
--build BUILD build a repo --build BUILD build a repo
@ -18,24 +24,25 @@ Options:
--forge-rebuild download and rebuild forge --forge-rebuild download and rebuild forge
--force try to strong arm things --force try to strong arm things
--verbose show more output --verbose show more output
--bash generate bash completion
--auto-complete AUTO-COMPLETE
todo: move this to go-arg
--help, -h display this help and exit --help, -h display this help and exit
--version display version and exit --version display version and exit
Commands: Commands:
help New to forge? This is for you.'
checkout switch branches using 'git checkout' checkout switch branches using 'git checkout'
clean start over at the beginning clean start over at the beginning
commit 'git commit' but errors out if on wrong branch commit 'git commit' but errors out if on wrong branch
config show your .config/forge/ settings config show your .config/forge/ settings
debug debug forge
dirty show dirty git repos dirty show dirty git repos
fetch run 'git fetch master' fetch run 'git fetch master'
gui open the gui
list print a table of the current repos list print a table of the current repos
merge merge branches merge merge branches
normal set every repo to the default state for software development normal set every repo to the default state for software development
patch make patchsets patch make patchsets
pull run 'git pull' pull run 'git pull'
tag manage git tags
*/ */
package main package main

View File

@ -23,11 +23,11 @@ func okExit(thing string) {
} }
func badExit(err error) { func badExit(err error) {
log.Info("forge failed: ", err, me.forge.Config.ReposDir) log.Info("forge failed: ", err, me.forge.GetGoSrc())
os.Exit(-1) os.Exit(-1)
} }
func badRepoExit(repo *gitpb.Repo, err error) { func badRepoExit(repo *gitpb.Repo, err error) {
log.Printf("%s FAILED: %v\n", repo.GetNamespace(), err) log.Printf("forge failed on %s with %v\n", repo.GetGoPath(), err)
os.Exit(-1) os.Exit(-1)
} }

View File

@ -12,33 +12,15 @@ import (
"strings" "strings"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func makeReposTablePB(pb *gitpb.Repos) *gitpb.ReposTable {
t := pb.NewTable("quickListRepos")
t.NewUuid()
sf := t.AddStringFunc("Namespace", func(r *gitpb.Repo) string {
return r.GetNamespace()
})
sf.Width = 16
userVer := t.AddStringFunc("user", func(repo *gitpb.Repo) string {
ver := repo.GetUserVersion()
return ver
})
userVer.Width = 4
return t
}
/* /*
type stdPatchsetTableWin struct { type stdPatchsetTableWin struct {
sync.Mutex sync.Mutex
win *gadgets.GenericWindow // the machines gui window win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget box *gui.Node // the machines gui parent box widget
TB *forgepb.SetsTable // the gui table buffer TB *forgepb.PatchsetsTable // the gui table buffer
update bool // if the window should be updated update bool // if the window should be updated
} }
@ -54,7 +36,7 @@ func (w *stdPatchsetTableWin) Toggle() {
*/ */
/* /*
etimef := func(e *forgepb.Set) string { etimef := func(e *forgepb.Patchset) string {
etime := e.Etime.AsTime() etime := e.Etime.AsTime()
s := etime.Format("2006/01/02 15:04") s := etime.Format("2006/01/02 15:04")
if strings.HasPrefix(s, "1970/") { if strings.HasPrefix(s, "1970/") {
@ -67,14 +49,14 @@ func (w *stdPatchsetTableWin) Toggle() {
*/ */
/* /*
ctimef := func(p *forgepb.Set) string { ctimef := func(p *forgepb.Patchset) string {
ctime := p.Ctime.AsTime() ctime := p.Ctime.AsTime()
return ctime.Format("2006/01/02 15:04") return ctime.Format("2006/01/02 15:04")
} }
} }
*/ */
func setPatchsetState(p *forgepb.Set) { func setPatchsetState(p *forgepb.Patchset) {
var bad bool var bad bool
var good bool var good bool
var done bool = true var done bool = true
@ -173,33 +155,53 @@ func findCommitBySubject(subject string) (string, error) {
} }
// returns true if PB changed // returns true if PB changed
func setNewCommitHash(patch *forgepb.Patch) error { func setNewCommitHash(patch *forgepb.Patch) bool {
// parts := strings.Fields(patch.Comment)
repo := me.forge.FindByGoPath(patch.Namespace) repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil { if repo == nil {
return log.Errorf("could not find repo %s", patch.Namespace) log.Info("could not find repo", patch.Namespace)
return false
} }
comment := cleanSubject(patch.Comment) comment := cleanSubject(patch.Comment)
if patch.NewHash == "" {
log.Info("init() new patch to 'na' ", patch.NewHash, "commithash:", patch.CommitHash, patch.Namespace, comment)
patch.NewHash = "na"
return true
}
os.Chdir(repo.GetFullPath()) os.Chdir(repo.GetFullPath())
newhash, err := findCommitBySubject(comment) newhash, err := findCommitBySubject(comment)
if err != nil { if err != nil {
return log.Errorf("patch: not found hash: %s %s %s %s %v", patch.CommitHash, patch.Namespace, comment, newhash, err) log.Info("patch: not found hash:", patch.CommitHash, patch.Namespace, comment, newhash, err)
return false
} }
if patch.NewHash == newhash {
patchId, err := repo.FindPatchId(newhash) // patch was already set
if err != nil { return false
return err }
if patch.NewHash != "na" {
log.Infof("patch: hash MISMATCH %s old=%s new=%s name=%s\n", patch.Namespace, patch.NewHash, newhash, comment)
return false
} }
patch.PatchId = patchId
patch.NewHash = newhash patch.NewHash = newhash
log.Info("patch: found hash:", patch.CommitHash, newhash, patch.Namespace, comment) log.Info("patch: found hash:", patch.CommitHash, newhash, patch.Namespace, comment)
return nil return true
} }
func AddAllPatches(notdone *forgepb.Patches, pset *forgepb.Set, full bool) { /*
func setNewCommitHashLoop(p *forgepb.Patchset) bool {
var done bool = true
for patch := range p.Patches.IterAll() {
setNewCommitHashLoop(patch)
}
return done
}
*/
func AddAllPatches(notdone *forgepb.Patches, pset *forgepb.Patchset, full bool) {
for patch := range pset.Patches.IterAll() { for patch := range pset.Patches.IterAll() {
comment := cleanSubject(patch.Comment) comment := cleanSubject(patch.Comment)
@ -213,7 +215,7 @@ func AddAllPatches(notdone *forgepb.Patches, pset *forgepb.Set, full bool) {
} }
} }
func AddNotDonePatches(notdone *forgepb.Patches, pset *forgepb.Set, full bool) { func AddNotDonePatches(notdone *forgepb.Patches, pset *forgepb.Patchset, full bool) {
for patch := range pset.Patches.IterAll() { for patch := range pset.Patches.IterAll() {
comment := cleanSubject(patch.Comment) comment := cleanSubject(patch.Comment)
@ -231,7 +233,7 @@ func AddNotDonePatches(notdone *forgepb.Patches, pset *forgepb.Set, full bool) {
continue continue
} }
if patch.NewHash != "" { if patch.NewHash != "na" {
log.Info("already applied patch", patch.Namespace, ": newhash:", patch.NewHash, "commithash:", patch.CommitHash, comment) log.Info("already applied patch", patch.Namespace, ": newhash:", patch.NewHash, "commithash:", patch.CommitHash, comment)
continue continue
} }
@ -281,7 +283,7 @@ func findExpired() *forgepb.Patches {
continue continue
} }
if patch.NewHash != "" { if patch.NewHash != "na" {
log.Info("already applied patch", patch.Namespace, ": newhash:", patch.NewHash, "commithash:", patch.CommitHash, comment) log.Info("already applied patch", patch.Namespace, ": newhash:", patch.NewHash, "commithash:", patch.CommitHash, comment)
found.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice found.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
continue continue

110
main.go
View File

@ -7,9 +7,15 @@ package main
import ( import (
"embed" "embed"
"fmt"
"os"
"strings" "strings"
"time"
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui"
"go.wit.com/lib/gui/prep" "go.wit.com/lib/gui/prep"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
@ -41,15 +47,41 @@ func getVersion(repo *gitpb.Repo, name string) string {
func main() { func main() {
me = new(mainType) me = new(mainType)
me.myGui = prep.Gui() // prepares the GUI package for go-args prep.Bash(ARGNAME, argv.DoAutoComplete) // this line should be: prep.Bash(argv)
me.auto = prep.Bash3(&argv) // this line should be: prep.Bash(&argv) me.myGui = prep.Gui() // prepares the GUI package for go-args
me.pp = arg.MustParse(&argv)
// me.auto = prep.Bash3(argv.DoAutoComplete, &argv) // this line should be: prep.Bash(&argv) /*
// arg.MustParse(&argv) // these three lines are becoming terrible syntax if argv.Bash {
// me.auto = prep.MustParse(&argv) // try to make this work? fhelp.DoBash(ARGNAME)
os.Exit(0)
}
if len(argv.BashAuto) != 0 {
argv.doBashAuto()
os.Exit(0)
}
*/
me.urlbase = argv.URL
if me.urlbase == "" {
me.urlbase = "https://go.wit.com/"
}
if os.Getenv("FORGE_URL") != "" {
me.urlbase = os.Getenv("FORGE_URL")
log.Info("got forge url", me.urlbase)
}
me.urlbase = strings.Trim(me.urlbase, "/") // track down why trailing '/' makes http POST not work
me.forge = forgepb.Init() // init forge.pb // internally debugging can be triggered here before Init()
me.forge.ScanRepoDir() // looks for new dirs, checks existing repos for changes if argv.Debug != nil {
doDebug()
okExit("")
}
if forgepb.FirstTimeUser() {
log.Info("You are running forge for the first time here")
}
// load the ~/.config/forge/ config
me.forge = forgepb.Init()
// initialize patches // initialize patches
doPatchInit() doPatchInit()
@ -71,6 +103,7 @@ func main() {
} }
if argv.Checkout != nil { if argv.Checkout != nil {
me.forge.Config.Mode = forgepb.ForgeMode_MASTER
if err := doCheckout(); err != nil { if err := doCheckout(); err != nil {
badExit(err) badExit(err)
} }
@ -92,6 +125,17 @@ func main() {
} }
if argv.Clean != nil { if argv.Clean != nil {
me.forge.Config.Mode = forgepb.ForgeMode_CLEAN
if argv.Clean.Repo != "" {
log.Info("only looking at repo:", argv.Clean.Repo)
okExit("")
}
if argv.Clean.GitReset != nil {
doGitReset()
okExit("reset")
}
if err := doClean(); err != nil { if err := doClean(); err != nil {
badExit(err) badExit(err)
} }
@ -109,18 +153,14 @@ func main() {
okExit("") okExit("")
} }
if argv.Tag != nil {
doTag()
okExit("")
}
if argv.Normal != nil { if argv.Normal != nil {
if argv.Normal.On != nil { if argv.Normal.On != nil {
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL { if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
log.Info("you are already in the normal state") log.Info("you are already in the normal state")
okExit("") okExit("")
} }
setForgeMode(forgepb.ForgeMode_NORMAL) me.forge.Config.Mode = forgepb.ForgeMode_NORMAL
me.forge.Config.ConfigSave()
log.Info("normal mode on") log.Info("normal mode on")
okExit("") okExit("")
} }
@ -130,7 +170,8 @@ func main() {
log.Info("you were aleady not in the normal state") log.Info("you were aleady not in the normal state")
okExit("") okExit("")
} }
setForgeMode(forgepb.ForgeMode_DEVEL) me.forge.Config.Mode = forgepb.ForgeMode_MASTER
me.forge.Config.ConfigSave()
log.Info("normal mode off") log.Info("normal mode off")
okExit("") okExit("")
} }
@ -139,7 +180,8 @@ func main() {
log.Infof("all %d repos are on your user branch. It is safe to write code now.\n", me.forge.Repos.Len()) log.Infof("all %d repos are on your user branch. It is safe to write code now.\n", me.forge.Repos.Len())
if me.forge.Config.Mode != forgepb.ForgeMode_NORMAL { if me.forge.Config.Mode != forgepb.ForgeMode_NORMAL {
log.Infof("Forge has set the mode to 'Normal'\n") log.Infof("Forge has set the mode to 'Normal'\n")
setForgeMode(forgepb.ForgeMode_NORMAL) me.forge.Config.Mode = forgepb.ForgeMode_NORMAL
me.forge.ConfigSave()
} }
okExit("") okExit("")
} }
@ -155,10 +197,28 @@ func main() {
} }
if argv.Merge != nil { if argv.Merge != nil {
if err := doMerge(); err != nil { if argv.Merge.Devel != nil {
badExit(err) start := time.Now()
repos, err := doMergeDevel()
dur := time.Since(start)
if err != nil {
badExit(err)
}
log.Printf("Merged %d devel branches in %s\n", repos.Len(), shell.FormatDuration(dur))
okExit("")
} }
okExit("")
if argv.Merge.Master != nil {
start := time.Now()
repos, err := doMergeMaster()
dur := time.Since(start)
if err != nil {
badExit(err)
}
log.Printf("Merged %d master branches in %s\n", repos.Len(), shell.FormatDuration(dur))
okExit("")
}
badExit(fmt.Errorf("You must choose which branch to merge to (devel or master)"))
} }
if argv.Pull != nil { if argv.Pull != nil {
@ -184,16 +244,22 @@ func main() {
okExit("") okExit("")
} }
// if the user doesn't want to open the GUI and
// nothing else was specified to be done,
// then just list the table to stdout
if gui.NoGui() {
found := doFind()
me.forge.PrintHumanTable(found)
okExit("")
}
// open the gui unless the user performed some other // open the gui unless the user performed some other
// basically, if you run just 'forge' it should open the GUI // basically, if you run just 'forge' it should open the GUI
if argv.Gui != nil { if argv.Gui != nil {
// if opening the GUI, always check git for dirty repos // if opening the GUI, always check git for dirty repos
me.forge.CheckDirty() me.forge.CheckDirty()
doGui()
me.myGui.Start() // loads the GUI toolkit
doGui() // start making our forge GUI
debug() // sits here forever
} }
// got to the end with nothing to do (?) // got to the end with nothing to do (?)
if dumpWorkRepos() { if dumpWorkRepos() {

View File

@ -4,9 +4,9 @@
package main package main
import ( import (
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/gadgets" "go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
) )
@ -27,10 +27,9 @@ func myServer() string {
// this app's variables // this app's variables
type mainType struct { type mainType struct {
// pp *arg.Parser // for parsing the command line args. Yay to alexflint! pp *arg.Parser // for parsing the command line args. Yay to alexf lint!
auto *prep.Auto // more experiments for bash handling
forge *forgepb.Forge // for holding the forge protobuf files forge *forgepb.Forge // for holding the forge protobuf files
myGui *prep.GuiPrep // for initializing the GUI toolkits myGui *gui.Node // the gui toolkit handle
foundPaths []string // stores gopaths to act on (when doing go-clone) foundPaths []string // stores gopaths to act on (when doing go-clone)
configSave bool // if the config file should be saved after finishing configSave bool // if the config file should be saved after finishing
urlbase string // base URL urlbase string // base URL

0
test
View File

View File

@ -41,10 +41,10 @@ func makeHowtoWin() *gadgets.GenericWindow {
grid.NewLabel("") // a stupid way to add padding grid.NewLabel("") // a stupid way to add padding
grid.NextRow() grid.NextRow()
// howtoWin.Group.NewLabel("Working dir: " + me.forge.Config.ReposDir) // howtoWin.Group.NewLabel("Working dir: " + me.forge.GetGoSrc())
grid = howtoWin.Group.RawGrid() grid = howtoWin.Group.RawGrid()
grid.NewButton("Download into "+me.forge.Config.ReposDir, func() { grid.NewButton("Download into "+me.forge.GetGoSrc(), func() {
howtoWin.Disable() howtoWin.Disable()
defer howtoWin.Enable() defer howtoWin.Enable()
downloadForge() downloadForge()

View File

@ -58,12 +58,34 @@ func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
grid.NewLabel(fmt.Sprintf("total repos")) grid.NewLabel(fmt.Sprintf("total repos"))
grid.NextRow() grid.NextRow()
grid.NewButton("Update", func() {
log.Info("TODO: doesn't update this window")
me.forge.GetPatches()
dwin.win.Custom()
// loadUpstreamPatchsets()
})
grid.NewButton("Apply All", func() { grid.NewButton("Apply All", func() {
var count int var count int
all := patches.SortByFilename() all := patches.SortByFilename()
for all.Scan() { for all.Scan() {
p := all.Next() p := all.Next()
applyPatchNew(p) applyPatchNew(p)
/*
rn := p.Namespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
log.Info("Could not figure out repo path", rn)
return
}
count += 1
if _, err := applyAndTrackPatch(repo, p); err != nil {
cmd := []string{"git", "am", "--abort"}
err := repo.RunVerbose(cmd)
log.Info("warn user of git am error", err)
return
}
*/
} }
log.Info("ALL PATCHES WORKED! count =", count) log.Info("ALL PATCHES WORKED! count =", count)
}) })
@ -117,9 +139,12 @@ func applyPatchLabel(p *forgepb.Patch) string {
// log.Info("Could not figure out repo path", rn) // log.Info("Could not figure out repo path", rn)
return "" return ""
} }
if p.NewHash == "" { if p.NewHash == "na" {
return "git am" return "git am"
} }
if p.NewHash == "" {
return "new"
}
return "done" return "done"
} }

View File

@ -99,7 +99,7 @@ func makeReposWin() *stdReposTableWin {
cmd = []string{"git", "branch", "--delete", "--remote", "origin/" + brname} cmd = []string{"git", "branch", "--delete", "--remote", "origin/" + brname}
log.Info(repo.GetGoPath(), cmd) log.Info(repo.GetGoPath(), cmd)
repo.RunVerbose(cmd) repo.RunVerbose(cmd)
repo.ReloadCheck() repo.Reload()
} }
me.forge.SetConfigSave(true) me.forge.SetConfigSave(true)
me.forge.ConfigSave() me.forge.ConfigSave()

View File

@ -21,8 +21,8 @@ type repoPatchWindow struct {
shelf *gui.Node // the first box in the stack, set as horizontal shelf *gui.Node // the first box in the stack, set as horizontal
grid *gui.Node // the list of available patches grid *gui.Node // the list of available patches
// summary *patchSummary // summary of current patches // summary *patchSummary // summary of current patches
setgrid *gui.Node // the list of each patchset setgrid *gui.Node // the list of each patchset
pset *forgepb.Set // the patchset in question pset *forgepb.Patchset // the patchset in question
} }
// todo: autogenerate these or make them standared 'gui' package functions // todo: autogenerate these or make them standared 'gui' package functions
@ -109,7 +109,7 @@ func makeRepoPatchWindow(repo *gitpb.Repo, fset []*forgepb.Patch) *repoPatchWind
return pw return pw
} }
func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Set) { func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Patchset) {
repomap := make(map[*gitpb.Repo][]*forgepb.Patch) repomap := make(map[*gitpb.Repo][]*forgepb.Patch)
repohash := make(map[*gitpb.Repo]string) repohash := make(map[*gitpb.Repo]string)