Compare commits

..

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

38 changed files with 2488 additions and 1857 deletions

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
*.swp *.swp
*.pb
go.mod go.mod
go.sum go.sum
/resources/*.so /resources/*.so

View File

@ -6,27 +6,25 @@ 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: gocui
#forge
tag:
forge tag list
vet: vet:
@GO111MODULE=off go vet @GO111MODULE=off go vet
@echo this go binary package builds okay @echo this go binary package builds okay
verbose:
GO111MODULE=off go build -v -x \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
build: goimports vet plugin build: goimports vet plugin
GO111MODULE=off go build \ GO111MODULE=off go build \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install: goimports plugin install: goimports vet plugin
GO111MODULE=off go install \ GO111MODULE=off go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
cp -f ~/go/bin/forge ~/go/bin/last.forge # this is a hack so that go-deb can build a .deb file for forge # TODO: remove this cp -f ~/go/bin/forge ~/go/bin/last.forge # this is a hack so that go-deb can build a .deb file for forge
install-verbose: goimports vet plugin
GO111MODULE=off go install -v -x \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install-raw: goimports vet plugin install-raw: goimports vet plugin
go install \ go install \
@ -36,17 +34,12 @@ plugin:
rm -f resources/*.so rm -f resources/*.so
# -cp ../../toolkits/gocui/gocui.so resources/ # -cp ../../toolkits/gocui/gocui.so resources/
GTK: clean install andlabs: clean install
forge --gui andlabs forge --gui gocui --gui-verbose --gui-file ../../toolkits/andlabs/andlabs.so
GTK-verbose: clean install gocui: install
forge --gui andlabs --gui-verbose forge --gui gocui --gui-verbose --gui-file ../../toolkits/gocui/gocui.so >/tmp/forge.log 2>&1
# forge --gui gocui --gui-verbose --debugger
CUI: install
forge --gui gocui
CUI-verbose: install
forge --gui gocui --gui-verbose >/tmp/forge.log 2>&1
goimports: goimports:
reset reset
@ -66,21 +59,16 @@ devel:
forge clean devel --force --verbose forge clean devel --force --verbose
pull: install pull: install
# forge pull dirty
# FORGE_URL="https://forge.grid.wit.com/" forge pull dirty
# FORGE_URL="https://forge.grid.wit.com/" forge pull patches
FORGE_URL="https://forge.grid.wit.com/" forge pull check FORGE_URL="https://forge.grid.wit.com/" forge pull check
# forge pull patches
# cloudflare blocks POST due to captcha checks / human detection? # cloudflare blocks POST due to captcha checks / human detection?
# POST must be direct socket. probably for the best anyway # POST must be direct socket. probably for the best anyway
submit: submit:
FORGE_URL="https://forge.grid.wit.com/" forge patch submit "forge auto commit" FORGE_URL="https://forge.grid.wit.com/" forge patch --submit "forge auto commit"
commit: commit:
FORGE_URL="https://forge.grid.wit.com/" forge commit --all FORGE_URL="https://forge.grid.wit.com/" forge commit --all
check: install
FORGE_URL="https://forge.grid.wit.com/" forge patch check
doc:
echo "/*" > doc.go
forge -h >> doc.go
echo "*/" >> doc.go
echo "package main" >> doc.go

View File

@ -3,9 +3,7 @@
forge is a GUI front end for 'git' designed with the forge is a GUI front end for 'git' designed with the
intent of simplifying federated git development. intent of simplifying federated git development.
## Install: FORGE NEVER DOES ANYTHING BUT EXEC's 'git'
* go install go.wit.com/apps/forge@latest
## Theory ## Theory
@ -22,10 +20,6 @@ intent of simplifying federated git development.
b) A "devel" branch that is published b) A "devel" branch that is published
c) a "user" branch that can be local only to the developer c) a "user" branch that can be local only to the developer
## Development Goals
* have a GUI that also works on the command line
## Notes ## Notes
This can be used to maintain git repositories This can be used to maintain git repositories
@ -37,6 +31,14 @@ expiremental work on federated git
* uses a GUI or the console(console display needs work) * uses a GUI or the console(console display needs work)
* always wrap around 'git' -- it basically just types 'git' commands really fast * always wrap around 'git' -- it basically just types 'git' commands really fast
## Development Goals
* have a GUI that also works on the command line
## Install:
* go install go.wit.com/apps/forge@latest
## building from sources ## building from sources
``` ```

View File

@ -3,6 +3,16 @@
package main package main
import (
"os"
"path/filepath"
"strings"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
/* /*
// saves the patches in ~/.config/forge/currentpatches/ // saves the patches in ~/.config/forge/currentpatches/
func savePatchset(pset *forgepb.Patchset) error { func savePatchset(pset *forgepb.Patchset) error {
@ -12,10 +22,40 @@ 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 := findDirty()
if found.Len() == 0 {
return false
} else {
return true
}
}
// From 18ee541f89be2e9f9a91c54873da87885e8ffdf5 Mon Sep 17 00:00:00 2001 // From 18ee541f89be2e9f9a91c54873da87885e8ffdf5 Mon Sep 17 00:00:00 2001
// From: Jeff Carr <jcarr@wit.com> // From: Jeff Carr <jcarr@wit.com>
// Date: Sun, 5 Jan 2025 01:18:47 -0600 // Date: Sun, 5 Jan 2025 01:18:47 -0600
@ -29,6 +69,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
@ -39,10 +116,9 @@ func doRegister(newurl string) error {
} }
test := strings.TrimSpace(string(body)) test := strings.TrimSpace(string(body))
for _, line := range strings.Split(test, "\n") { for line := range strings.SplitSeq(test, "\n") {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
log.Info("server returned:", line) log.Info("server returned:", line)
} }
return nil return nil
} }
*/

109
argv.go
View File

@ -6,8 +6,6 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"go.wit.com/lib/gui/prep"
) )
/* /*
@ -17,20 +15,17 @@ import (
var argv args var argv args
type args struct { type args struct {
Help *EmptyCmd `arg:"subcommand:help" help:"New to forge? This is for you.'"`
Checkout *CheckoutCmd `arg:"subcommand:checkout" help:"switch branches using 'git checkout'"` Checkout *CheckoutCmd `arg:"subcommand:checkout" help:"switch branches using 'git checkout'"`
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 *DebugCmd `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"`
List *FindCmd `arg:"subcommand:list" help:"print a table of the current repos"` List *FindCmd `arg:"subcommand:list" help:"print a table of the current repos"`
Merge *MergeCmd `arg:"subcommand:merge" help:"merge branches"` Merge *MergeCmd `arg:"subcommand:merge" help:"merge branches"`
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"`
@ -38,16 +33,13 @@ type args struct {
BuildForge bool `arg:"--forge-rebuild" help:"download and rebuild forge"` BuildForge bool `arg:"--forge-rebuild" help:"download and rebuild forge"`
Force bool `arg:"--force" help:"try to strong arm things"` Force bool `arg:"--force" help:"try to strong arm things"`
Verbose bool `arg:"--verbose" help:"show more output"` Verbose bool `arg:"--verbose" help:"show more output"`
Bash bool `arg:"--bash" help:"generate bash completion"`
BashAuto []string `arg:"--auto-complete" help:"todo: move this to go-arg"`
} }
type EmptyCmd struct { type EmptyCmd struct {
} }
type NormalCmd struct {
On *EmptyCmd `arg:"subcommand:on" help:"turn normal mode on"`
Off *EmptyCmd `arg:"subcommand:off" help:"turn normal mode off"`
}
type CommitCmd struct { type CommitCmd struct {
Submit bool `arg:"--submit" default:"true" help:"submit the patches to forge"` Submit bool `arg:"--submit" default:"true" help:"submit the patches to forge"`
} }
@ -55,7 +47,12 @@ 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"`
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"` Repo string `arg:"--repo" help:"which repo to look at"`
} }
@ -64,28 +61,17 @@ type CleanDevelCmd struct {
} }
type PatchCmd struct { type PatchCmd struct {
Check *EmptyCmd `arg:"subcommand:check" help:"check the state of the patches"`
List *EmptyCmd `arg:"subcommand:list" help:"your downloaded patchsets"` List *EmptyCmd `arg:"subcommand:list" help:"your downloaded patchsets"`
Get *EmptyCmd `arg:"subcommand:get" help:"get the new patchsets"` Get *EmptyCmd `arg:"subcommand:get" help:"get the new patchsets"`
Show *EmptyCmd `arg:"subcommand:show" help:"your pending commits to your code"` Show *EmptyCmd `arg:"subcommand:show" help:"your pending commits to your code"`
Submit *SubmitCmd `arg:"subcommand:submit" help:"submit your commits"` Submit string `arg:"--submit" help:"submit your commits"`
Repos *SubmitCmd `arg:"subcommand:repos" help:"show repos with patches"`
}
type SubmitCmd struct {
Match string `arg:"positional"`
} }
type PullCmd struct { type PullCmd struct {
Check *EmptyCmd `arg:"subcommand:check" help:"check repo versions"` Check *EmptyCmd `arg:"subcommand:check" help:"check repo versions"`
Dirty *EmptyCmd `arg:"subcommand:dirty" help:"only check dirty repos"` Dirty *EmptyCmd `arg:"subcommand:dirty" help:"only check dirty repos"`
Patches *EmptyCmd `arg:"subcommand:patches" help:"only check repos with patches"` Patches *EmptyCmd `arg:"subcommand:patches" help:"only check repos with patches"`
} Sync *SyncCmd `arg:"subcommand:sync" help:"sync repos with upstream"`
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 {
@ -111,6 +97,10 @@ type ConfigCmd struct {
Register string `arg:"--register" help:"register your git URL (foo.com/mystuff) or (github.com/foo/bar)"` Register string `arg:"--register" help:"register your git URL (foo.com/mystuff) or (github.com/foo/bar)"`
} }
type DebugCmd struct {
Config *EmptyCmd `arg:"subcommand:config" help:"used to debug protobuf Marshal() if things go wrong"`
}
type CheckoutCmd struct { type CheckoutCmd struct {
User *FindCmd `arg:"subcommand:user" help:"git checkout user"` User *FindCmd `arg:"subcommand:user" help:"git checkout user"`
Devel *FindCmd `arg:"subcommand:devel" help:"git checkout devel"` Devel *FindCmd `arg:"subcommand:devel" help:"git checkout devel"`
@ -123,6 +113,11 @@ type MergeCmd struct {
Publish *EmptyCmd `arg:"subcommand:publish" help:"increment versions and publish master branch"` Publish *EmptyCmd `arg:"subcommand:publish" help:"increment versions and publish master branch"`
} }
type SyncCmd struct {
Clean *EmptyCmd `arg:"subcommand:clean" help:"sync everything to upstream master"`
User *EmptyCmd `arg:"subcommand:user" help:"sync everything to user"`
}
type DirtyCmd struct { type DirtyCmd struct {
} }
@ -149,57 +144,19 @@ forge -- a tool to manage lots of git repos. forge includes a GUI and TUI.
` `
} }
// handles shell autocomplete func (args) doBashHelpDebug() {
func DoAutoComplete(pb *prep.Auto) { fmt.Fprintln(os.Stderr, "")
switch pb.Cmd { fmt.Fprintln(os.Stderr, "hello world")
case "checkout": var more string
pb.Autocomplete2("devel master user") p0 := "0" + argv.BashAuto[0]
case "clean": p1 := "1" + argv.BashAuto[1]
pb.Autocomplete2("") p2 := "2" + argv.BashAuto[2]
case "commit": if len(argv.BashAuto[1]) >= 0 {
pb.Autocomplete2("--all") more = "more"
case "config":
fmt.Println("add fix list")
case "dirty":
fmt.Println("")
case "gui":
fmt.Println("")
case "--gui":
pb.Autocomplete2("andlabs gocui")
case "list":
pb.Autocomplete2("--mine --favorites --dirty")
case "merge":
pb.Autocomplete2("devel master --all")
case "normal":
pb.Autocomplete2("on off")
case "pull":
pb.Autocomplete2("--force check")
case "patch":
fmt.Println("check get list repos submit show")
case "tag":
fmt.Println("list --delete clean")
default:
if pb.Cmd == "" {
pb.Autocomplete2("help list checkout clean commit dirty fetch gui normal merge patch pull tag --gui")
} else { } else {
pb.Autocomplete2("list checkout clean commit dirty normal merge tag") more = "less"
} }
} p1a := fmt.Sprintf("1a.%s.%+v.\n", argv.BashAuto[1], len(argv.BashAuto[1]))
os.Exit(0) fmt.Fprintln(os.Stderr, "pull something else", argv.BashAuto, len(argv.BashAuto), p0, p1, p2, p1a, "end", more)
} fmt.Fprintln(os.Stderr, "")
func (args) Appname() string {
return ARGNAME
}
func ifBlank(arg string) bool {
if arg == "''" {
// if empty, the user has not typed something
return true
}
return false
}
func (a args) DoAutoComplete(autoArgv *prep.Auto) {
DoAutoComplete(autoArgv)
} }

123
argvAutoshell.go Normal file
View File

@ -0,0 +1,123 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"os"
)
/*
handles shell autocomplete
*/
// used for shell auto completion
// var ARGNAME string = "forge" // todo: get this from $0 ?
func deleteMatch() {
// f := forgedb.InitSimple()
fmt.Println("go.wit.com/lib/gui/repostatus todo: need to do this")
}
func (args) doBashAuto() {
argv.doBashHelp()
switch argv.BashAuto[0] {
case "checkout":
fmt.Println("user devel master ")
case "clean":
// 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("devel user")
case "commit":
fmt.Println("--all")
case "config":
fmt.Println("add fix list debug")
case "delete":
deleteMatch()
case "debug":
fmt.Println("config")
case "dirty":
fmt.Println("")
case "examine":
fmt.Println("fix")
case "list":
fmt.Println("--full")
case "merge":
fmt.Println("devel master")
case "pull":
fmt.Println("dirty clean list patches sync --force")
case "--find":
fmt.Println("foo bar")
case "patch":
fmt.Println("get list --submit show")
case "user":
fmt.Println("--force")
case "devel":
fmt.Println("--force")
case "sync":
fmt.Println("clean user --force")
case "master":
fmt.Println("")
case "verify":
fmt.Println("user devel master")
default:
if argv.BashAuto[0] == ARGNAME {
// list the subcommands here
fmt.Println("--bash list checkout commit config dirty debug fetch merge patch pull")
}
}
os.Exit(0)
}
// prints help to STDERR // TODO: move everything below this to go-args
func (args) doBashHelp() {
if argv.BashAuto[1] != "''" {
// if this is not blank, then the user has typed something
return
}
if argv.BashAuto[0] != ARGNAME {
// if this is not the name of the command, the user already started doing something
return
}
if argv.BashAuto[0] == ARGNAME {
me.pp.WriteHelp(os.Stderr)
return
}
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "hello world")
fmt.Fprintln(os.Stderr, "")
}
// complete -F forge --bash forge
func (args) doBash() {
fmt.Println("# add this in your bashrc:")
fmt.Println("")
fmt.Println("# todo: add this to go-arg as a 'hidden' go-arg option --bash")
fmt.Println("#")
fmt.Println("# todo: can this output work/parse with:")
fmt.Println("# complete -C `" + ARGNAME + " --bash` " + ARGNAME)
fmt.Println("")
fmt.Println("_" + ARGNAME + "_complete()")
fmt.Println("{")
fmt.Println(" # sets local to this func vars")
fmt.Println(" local cur prev all")
fmt.Println(" cur=${COMP_WORDS[COMP_CWORD]}")
fmt.Println(" prev=${COMP_WORDS[COMP_CWORD-1]}")
fmt.Println(" all=${COMP_WORDS[@]}")
fmt.Println("")
fmt.Println(" # this is where we generate the go-arg output")
fmt.Println(" GOARGS=$(" + ARGNAME + " --auto-complete $prev \\'$cur\\' $all)")
fmt.Println("")
fmt.Println(" # this compares the command line input from the user")
fmt.Println(" # to whatever strings we output")
fmt.Println(" COMPREPLY=( $(compgen -W \"$GOARGS\" -- $cur) ) # THIS WORKS")
fmt.Println(" return 0")
fmt.Println("}")
fmt.Println("complete -F _" + ARGNAME + "_complete " + ARGNAME)
fmt.Println("")
fmt.Println("# copy and paste the above into your bash shell should work")
os.Exit(0)
}

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

@ -5,39 +5,286 @@ package main
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"time" "time"
"go.wit.com/lib/gui/shell" "go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
var ErrorNotAllReposOnMaster error = fmt.Errorf("not all repos on are on the master branch")
var ErrorNotAllReposOnDevel error = fmt.Errorf("not all repos on are on the devel branch")
var ErrorNotAllReposOnUser error = fmt.Errorf("not all repos on are on the user branch")
func IsEverythingOnMaster() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
for repo := range me.forge.Repos.IterAll() {
total += 1
if repo.GetMasterBranchName() == repo.GetCurrentBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
// log.Info(ErrorNotAllReposOnMaster)
return total, count, nope, ErrorNotAllReposOnMaster
}
return total, count, nope, nil
}
func IsEverythingOnDevel() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
for repo := range me.forge.Repos.IterAll() {
total += 1
if repo.GetDevelBranchName() == repo.GetCurrentBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
return total, count, nope, ErrorNotAllReposOnDevel
}
return total, count, nope, nil
}
func IsEverythingOnUser() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
for repo := range me.forge.Repos.IterAll() {
total += 1
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
return total, count, nope, ErrorNotAllReposOnUser
}
return total, count, nope, nil
}
func doGitReset() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
// log.Info("is readonly", repo.GetGoPath())
if repo.CheckDirty() {
log.Info("is readonly and dirty", repo.GetGoPath())
cmd := []string{"git", "reset", "--hard"}
repo.RunRealtime(cmd)
}
} else {
// log.Info("is not readonly", repo.GetGoPath())
}
}
}
func rillCheckoutUser(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on devel branch. have to move them there first for now
// return repo.CheckoutDevel()
}
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
// repo is already on user branch
return nil
}
if err := repo.CheckoutUser(); err != nil {
log.Info(repo.GetFullPath(), err)
return err
}
return nil
}
// trys to figure out if there is still something to update // trys to figure out if there is still something to update
func doAllCheckoutUser() error {
now := time.Now()
if argv.Force {
log.Info("going to force create user branches")
if err := makeUserBranches(); err != nil {
return err
}
}
me.forge.RillFuncError(rillCheckoutUser)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, err := IsEverythingOnUser()
log.Printf("User branch check. %d total repos. (%d ok) (%d not on user branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on user
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
found.AppendByGoPath(repo)
}
}
me.forge.PrintHumanTable(found)
log.Printf("There are %d repos that are NOT on the user branch\n", found.Len())
return err
}
return nil
}
func rillCheckoutDevel(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetDevelBranchName() {
// repo is already on devel branch
return nil
}
repo.CheckoutDevel()
return nil
}
// is every repo on the devel branch?
func doAllCheckoutDevel() error {
now := time.Now()
if argv.Force {
log.Info("going to force create devel branches")
makeDevelBranches()
}
log.Info("going to rill:")
me.forge.RillFuncError(rillCheckoutDevel)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, err := IsEverythingOnDevel()
log.Printf("Devel branch check. %d total repos. (%d ok) (%d not on devel branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on user
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
found.AppendByGoPath(repo)
}
}
me.forge.PrintHumanTable(found)
log.Printf("There are %d repos that are NOT on the devel branch\n", found.Len())
return err
}
return nil
}
func rillCheckoutMaster(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
// 'giterr' means something is very wrong with this repo
if repo.GetMasterVersion() == "giterr" {
repo.CheckoutMaster()
log.Info("master == giterr. BAD REPO", repo.GetFullPath())
log.Info("master == giterr. BAD REPO", repo.GetFullPath())
log.Info("master == giterr. BAD REPO", repo.GetFullPath())
cmd := []string{"git", "checkout", "main"} // todo: figure out main
repo.RunVerbose(cmd)
os.Exit(-1)
return nil
}
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on master
return nil
}
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
// skip other checks for readonly repos
repo.CheckoutMaster()
return nil
}
/*
if repo.GetUserVersion() != repo.GetDevelVersion() {
// don't switch branches if the user branch has uncommitted patches
return nil
}
if repo.GetDevelVersion() != repo.GetMasterVersion() {
// don't switch braches if the devel branch does not match master (needs merge)
return nil
}
*/
repo.CheckoutMaster()
return nil
}
// trys to figure out if there is still something to update
func doAllCheckoutMaster() error {
now := time.Now()
me.forge.RillFuncError(rillCheckoutMaster)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, err := IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on master
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
found.AppendByGoPath(repo)
}
}
me.forge.PrintHumanTable(found)
log.Printf("There are %d repos that are NOT on the master branch\n", found.Len())
return err
}
return nil
}
// trys to figure out if there is still something to update
// todo: redo this logic as it is terrible
func doCheckout() error { func doCheckout() error {
if argv.Checkout.User != nil { if argv.Checkout.User != nil {
start := time.Now() if err := doAllCheckoutUser(); err != nil {
err := me.forge.DoAllCheckoutUser(argv.Force)
dur := time.Since(start)
log.Printf("Checked out %d user braches in %s\n", me.forge.Repos.Len(), shell.FormatDuration(dur))
if err != nil {
badExit(err) badExit(err)
} }
okExit("") okExit("")
} }
if argv.Checkout.Devel != nil { if argv.Checkout.Devel != nil {
// setForgeMode(forgepb.ForgeMode_DEVEL) if err := doAllCheckoutDevel(); err != nil {
if err := me.forge.DoAllCheckoutDevelNew(argv.Force); err != nil {
badExit(err) badExit(err)
} }
okExit("") okExit("")
} }
if argv.Checkout.Master != nil { if argv.Checkout.Master != nil {
setForgeMode(forgepb.ForgeMode_MASTER) // disable "normal" mode if set if err := doAllCheckoutMaster(); err != nil {
if err := me.forge.DoAllCheckoutMaster(); err != nil {
badExit(err) badExit(err)
} }
okExit("") okExit("")
@ -45,3 +292,45 @@ func doCheckout() error {
badExit(fmt.Errorf("did not specify what branch to checkout")) badExit(fmt.Errorf("did not specify what branch to checkout"))
return nil return nil
} }
func makeDevelBranches() error {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
branch := repo.GetDevelBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
continue
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
continue
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
}
return nil
}
func makeUserBranches() error {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
branch := repo.GetUserBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
continue
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
continue
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
}
return nil
}

View File

@ -6,131 +6,61 @@ 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 { var ErrorReposHasLocalBranches error = fmt.Errorf("repo still has local branches")
if err := repo.ReloadCheck(); err != nil { var ErrorMergeBranch error = fmt.Errorf("trunk has things not in the branch")
log.Info("need to reload", repo.FullPath) var ErrorMergeTrunk error = fmt.Errorf("branch has things not in trunk")
}
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
// automatically deletes local devel and user branches
func doClean() error { func doClean() error {
setForgeMode(forgepb.ForgeMode_CLEAN) if argv.Clean.Pub != nil {
if err := doCleanPub(); err != nil {
if argv.Clean.Verify != nil { badExit(err)
stats := me.forge.RillRepos(checkRemoteBranches)
for path, stat := range stats {
if stat.Err == nil {
continue
} }
dur := stat.End.Sub(stat.Start) log.Info("finished attempt at cleaning devel branches")
if dur > time.Second { return nil
log.Infof("%s checkRemoteBranches() took a long time (%s) (err=%v)\n", path, shell.FormatDuration(dur), stat.Err)
} }
if argv.Clean.Devel != nil {
if err := doCleanDevel(); err != nil {
badExit(err)
}
log.Info("finished attempt at cleaning devel branches")
return nil
}
if argv.Clean.User != nil {
if err := doCleanUser(); err != nil {
log.Info(err)
okExit("")
}
return nil
} }
// 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 return nil
} }
// fix this to work, then delete all the other options for "forge clean' func doCleanUser() error {
if err := me.forge.DoAllCheckoutMaster(); err != nil { if _, count, _, err := IsEverythingOnMaster(); err != nil {
// badExit(err) if count == 0 {
log.Info("No repos are on the master branch")
return nil
} }
log.Info("Not all repos are on the master branch")
// return err
}
var anyerr error
all := me.forge.Repos.SortByFullPath() all := me.forge.Repos.SortByFullPath()
for all.Scan() { for all.Scan() {
repo := all.Next() repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { if err := doCleanUserRepo(repo); err != nil {
continue
}
if repo.IsDirty() {
continue
}
// when publishing, clean out the details of that if it's still there
if repo.GetTargetVersion() != "" {
repo.SetTargetVersion("")
configSave = true
}
// try to delete user
if err := doRepoCleanUser(repo); err != nil {
log.Info(repo.GetGoPath(), err) log.Info(repo.GetGoPath(), err)
} anyerr = err
// try to delete devel
doRepoCleanDevel(repo)
}
found := gitpb.NewRepos()
total := 0
// find all repos that aren't "clean"
all = me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
total += 1
// find repos not on master branch
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
found.AppendByFullPath(repo)
continue
}
// find dirty repos
if repo.IsDirty() {
found.AppendByFullPath(repo)
continue
}
// find repos that still have a local user branch
if repo.IsLocalBranch(repo.GetUserBranchName()) {
found.AppendByFullPath(repo)
continue
}
// find repos that still have a local devel branch
if repo.IsLocalBranch(repo.GetDevelBranchName()) {
found.AppendByFullPath(repo)
continue
} }
} }
return anyerr
// display all the repos that aren't clean to the user
log.Printf("\n") // padding for now
if argv.Verbose {
me.forge.PrintHumanTableDirty(found)
} else {
me.forge.PrintHumanTable(found)
}
log.Printf("\n") // padding for now
var hmm int
hmm = found.Len()
if hmm == 0 {
log.Printf("%d repos are not clean\n", hmm)
} else {
log.Info("All repos are clean", total, hmm)
}
return nil
} }
/* /*
@ -138,28 +68,39 @@ func doClean() error {
return repo.Exists(filepath.Join(".git/refs/heads", branch)) return repo.Exists(filepath.Join(".git/refs/heads", branch))
} }
*/ */
func doRepoCleanDevel(repo *gitpb.Repo) error {
func doCleanDevel() error {
var total int
var count int
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
total += 1
if !repo.IsLocalBranch(repo.GetDevelBranchName()) { if !repo.IsLocalBranch(repo.GetDevelBranchName()) {
// there is no local branch named 'devel' // there is no local branch named 'devel'
return nil continue
} }
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
return log.Errorf("%s not on master branch:", repo.GetFullPath()) log.Info("Repo not on master branch:", repo.GetGoPath())
continue
} }
if repo.IsDirty() { if repo.IsDirty() {
return log.Errorf("%s is dirty:", repo.GetFullPath()) log.Info("Repo is dirty:", repo.GetGoPath())
continue
} }
count += 1
if err := justDeleteTheDevelBranchAlready(repo); err != nil { if err := justDeleteTheDevelBranchAlready(repo); err != nil {
log.Info("justDeleteTheDevel() err", repo.GetGoPath(), err) log.Info("justDeleteTheDevel() err", repo.GetGoPath(), err)
configSave = true
return err
} }
}
log.Info("")
log.Printf("attempted cleaning %d devel branches of %d total branches\n", count, total)
return nil return nil
} }
// removes all local user branches // removes all local branches
func doRepoCleanUser(repo *gitpb.Repo) error { func doCleanUserRepo(repo *gitpb.Repo) error {
if repo.IsDirty() { if repo.IsDirty() {
return nil return nil
} }
@ -177,38 +118,33 @@ func doRepoCleanUser(repo *gitpb.Repo) error {
return nil return nil
} }
// will you loose work if you delete your user branch? log.Info("trying to delete", bruser, repo.GetUserVersion())
// if DevelBranchExists()
// then if UserBranchCommits exist in DevelBranch
// DeleteUserBranch is safe
if repo.IsLocalBranch(brdevel) {
b1 := repo.CountDiffObjects(bruser, "refs/heads/"+brdevel) // should be zero
if b1 == 0 {
// every user branch exists in devel. delete user branch
cmd := []string{"git", "branch", "-D", bruser}
// log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
}
brmaster := repo.GetMasterBranchName() b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero
// will you loose work if you delete your user branch?
// if master branch exists()
// then if all user commits exist in master
// delete user branch is safe
if repo.IsLocalBranch(brmaster) {
b1 := repo.CountDiffObjects(bruser, "refs/heads/"+brmaster) // should be zero
if b1 == 0 { if b1 == 0 {
cmd := []string{"git", "branch", "-D", bruser} cmd := []string{"git", "branch", "-D", bruser}
// log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd) log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd) err := repo.RunVerbose(cmd)
return err return err
} }
return fmt.Errorf("%s branch has things not in %s count=%d", bruser, brdevel, b1)
} }
return fmt.Errorf("%s branch has unique commits", bruser) // hack to cleanup release versioning info
func doCleanPub() error {
total := 0
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetTargetVersion() != "" {
repo.SetTargetVersion("")
configSave = true
total += 1
}
}
log.Printf("clearing %d total repos\n", total)
return nil
} }
// if you call this, there is no going back. no checks anymore. nothing // if you call this, there is no going back. no checks anymore. nothing
@ -229,8 +165,8 @@ func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
b1 := repo.CountDiffObjects(branch, remote) // should be zero b1 := repo.CountDiffObjects(branch, remote) // should be zero
if b1 == 0 { if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()} cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd) log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd) err := repo.RunVerbose(cmd)
return err return err
} }
cmd := []string{"git", "push"} cmd := []string{"git", "push"}
@ -241,32 +177,15 @@ func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
// remote doesn't exist, check against master // remote doesn't exist, check against master
master := repo.GetMasterBranchName() master := repo.GetMasterBranchName()
b1 := repo.CountDiffObjects(branch, "refs/heads/"+master) // should be zero b1 := repo.CountDiffObjects(branch, master) // should be zero
if b1 == 0 { if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()} cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd) log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd) err := repo.RunVerbose(cmd)
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
} }
func doGitReset() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
// log.Info("is readonly", repo.GetGoPath())
if repo.CheckDirty() {
log.Info("is readonly and dirty", repo.GetGoPath())
cmd := []string{"git", "reset", "--hard"}
repo.RunRealtime(cmd)
}
} else {
// log.Info("is not readonly", repo.GetGoPath())
}
}
}

View File

@ -6,15 +6,16 @@ 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"
) )
func doCommit() error { func doCommit() {
if argv.All { if argv.All {
found := me.forge.CheckDirty() log.Info("do a commit everywhere")
doCheckDirtyAndConfigSave()
found := findDirty()
var newpatches bool var newpatches bool
for repo := range found.IterAll() { for repo := range found.IterAll() {
log.Info("do a commit on repo", repo.GetGoPath()) log.Info("do a commit on repo", repo.GetGoPath())
@ -22,21 +23,25 @@ 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) // if there are enw patches, autocommit them
return doPatchSubmit() _, err := me.forge.SubmitDevelPatchSet("forge auto commit")
if err != nil {
badExit(err)
}
} }
okExit("") okExit("")
} }
repo := findCurrentPwdRepoOrDie() pwd, _ := os.Getwd()
repo := me.forge.Repos.FindByFullPath(pwd)
if !repo.CheckDirty() { if repo == nil {
okExit(log.Sprintf("this repo %s is not dirty.\n\n--all # commit all changes in all repos", repo.GetFullPath())) log.Info("todo: forge doesn't know how to work here yet")
} else { okExit("")
log.Info("repo is dirty", repo.GetFullPath())
} }
if repo.GetCurrentBranchName() != repo.GetUserBranchName() { if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
@ -63,8 +68,7 @@ func doCommit() error {
if err := shell.ExecCheck([]string{"git", "commit", "--all"}); err != nil { if err := shell.ExecCheck([]string{"git", "commit", "--all"}); err != nil {
badExit(err) badExit(err)
} }
log.Info("git commit ok. forge done")
return doPatchSubmit()
} }
func doCommitRepo(repo *gitpb.Repo) error { func doCommitRepo(repo *gitpb.Repo) error {

View File

@ -21,8 +21,6 @@ func doConfig() {
log.Info("todo") log.Info("todo")
okExit("") okExit("")
} }
/*
if argv.Config.Register != "" { if argv.Config.Register != "" {
if err := doRegister(argv.Config.Register); err == nil { if err := doRegister(argv.Config.Register); err == nil {
okExit("attempting to register " + argv.Config.Register) okExit("attempting to register " + argv.Config.Register)
@ -30,7 +28,6 @@ func doConfig() {
badExit(err) badExit(err)
} }
} }
*/
// try to add, then save config and exit // try to add, then save config and exit
if argv.Config.Add != nil { if argv.Config.Add != nil {
@ -54,8 +51,6 @@ func doConfig() {
os.Exit(0) os.Exit(0)
} }
log.Info("config.PathLock =", me.forge.Config.PathLock)
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
}

View File

@ -3,14 +3,81 @@
package main package main
import (
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doDirty() { func doDirty() {
found := me.forge.CheckDirty() doCheckDirtyAndConfigSave()
if found.Len() == 0 { if allerr := me.forge.RillRepos(checkNormalRepoState); len(allerr) != 0 {
return log.Info("Some repos are not in a 'normal' state. error count =", len(allerr))
for repo, err := range allerr {
log.Info("repo not normal", repo.GetFullPath(), err)
} }
}
found := findDirty()
if argv.Verbose { if argv.Verbose {
me.forge.PrintHumanTableDirty(found) me.forge.PrintHumanTableDirty(found)
} else { } else {
me.forge.PrintHumanTable(found) me.forge.PrintHumanTable(found)
} }
} }
// 99% of the time, the repos during development should be on your user branch.
// error out if it's not
// this checks to see if a devel and user branch exist
// this needs to run each time in case repos were added manually by the user
// this also verifies that
func checkNormalRepoState(repo *gitpb.Repo) error {
if err := repo.MakeLocalDevelBranch(); err != nil {
return err
}
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
return repo.CheckoutUser()
}
return nil
}
func straightCheckDirty() int {
var count int
// var total int
// now := time.Now()
for repo := range me.forge.Repos.IterAll() {
// total += 1
if repo.IsDirty() {
count += 1
}
}
// log.Printf("rill dirty check (%d dirty repos) (%d total repos) took:%s\n", count, total, shell.FormatDuration(time.Since(now)))
return count
}
func doCheckDirty(repo *gitpb.Repo) error {
repo.CheckDirty()
// reset these in here for now
if repo.GetTargetVersion() != "" {
repo.TargetVersion = ""
me.forge.SetConfigSave(true)
}
return nil
}
// recheck every repo's dirty state according to 'git'
func doCheckDirtyAndConfigSave() {
start := straightCheckDirty()
now := time.Now()
me.forge.RillFuncError(doCheckDirty)
end := straightCheckDirty()
log.Printf("dirty check (%d dirty repos) (%d total repos) took:%s\n", end, me.forge.Repos.Len(), shell.FormatDuration(time.Since(now)))
if start != end {
// todo: use internal forgepb configsave flag. should work?
me.forge.SetConfigSave(true)
me.forge.ConfigSave()
}
}

View File

@ -24,7 +24,7 @@ func doFind() *gitpb.Repos {
} }
if argv.List.Dirty { if argv.List.Dirty {
return me.forge.FindDirty() return findDirty()
} }
return findAll() return findAll()
@ -52,7 +52,7 @@ func (f *FindCmd) findRepos() *gitpb.Repos {
} }
if f.Dirty { if f.Dirty {
return me.forge.FindDirty() return findDirty()
} }
if f.User { if f.User {
@ -66,7 +66,7 @@ func findPrivate() *gitpb.Repos {
found := gitpb.NewRepos() found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsPrivate(repo.GetGoPath()) { if me.forge.Config.IsPrivate(repo.GetGoPath()) {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
} }
@ -81,7 +81,7 @@ func findMine() *gitpb.Repos {
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsWritable(repo.GetGoPath()) { if me.forge.Config.IsWritable(repo.GetGoPath()) {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
} }
return found return found
@ -95,7 +95,19 @@ func findFavorites() *gitpb.Repos {
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsFavorite(repo.GetGoPath()) { if me.forge.Config.IsFavorite(repo.GetGoPath()) {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
}
}
return found
}
// finds repos that git is reporting as dirty
func findDirty() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if repo.IsDirty() {
found.AppendByGoPath(repo)
} }
} }
return found return found
@ -104,7 +116,7 @@ func findFavorites() *gitpb.Repos {
func findAll() *gitpb.Repos { func findAll() *gitpb.Repos {
found := gitpb.NewRepos() found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
return found return found
} }
@ -113,7 +125,7 @@ func find50() *gitpb.Repos {
count := 0 count := 0
found := gitpb.NewRepos() found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
if count > 50 { if count > 50 {
return found return found
} }
@ -127,7 +139,7 @@ func findUser() *gitpb.Repos {
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
if repo.GetCurrentBranchName() == repo.GetUserBranchName() { if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
} }
return found return found
@ -140,7 +152,7 @@ func findPublishable() *gitpb.Repos {
if repo.GetTargetVersion() == "" { if repo.GetTargetVersion() == "" {
continue continue
} }
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
return found return found
} }
@ -151,12 +163,12 @@ func findReposWithPatches() *gitpb.Repos {
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
if repo.GetTargetVersion() != "" { if repo.GetTargetVersion() != "" {
// add everything that has a target version set // add everything that has a target version set
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
continue continue
} }
if repo.IsDirty() { if repo.IsDirty() {
// always add dirty branches // always add dirty branches
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
continue continue
} }
if repo.GetUserVersion() == "" || repo.GetUserVersion() == "uerr" { if repo.GetUserVersion() == "" || repo.GetUserVersion() == "uerr" {
@ -164,21 +176,7 @@ func findReposWithPatches() *gitpb.Repos {
continue continue
} }
if repo.GetUserVersion() != repo.GetDevelVersion() { if repo.GetUserVersion() != repo.GetDevelVersion() {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
continue
}
// show anything that differs between 'devel' & 'master' branches
if repo.GetDevelVersion() != repo.GetMasterVersion() {
// this repo.State code isn't great, but it got me here quickly
// I'll defend my code by saying it's faster for me if I do dumb things
// sometimes and fix them later. Probably some employee will have to
// fix this. if that is the case I owe you lunch. or stock options
if repo.State == "DEVEL behind MASTER" {
// log.Info("repo state", repo.FullPath, repo.State)
continue
}
found.AppendByFullPath(repo)
continue continue
} }
@ -187,9 +185,15 @@ func findReposWithPatches() *gitpb.Repos {
continue continue
} }
// show anything that differs between 'devel' & 'master' branches
if repo.GetDevelVersion() != repo.GetMasterVersion() {
found.AppendByGoPath(repo)
continue
}
// this is an old test to see if the current 'last tag' is accurate and should be removed // this is an old test to see if the current 'last tag' is accurate and should be removed
if repo.GetLastTag() != repo.GetMasterVersion() { if repo.GetLastTag() != repo.GetMasterVersion() {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
repo.FindLastTag() repo.FindLastTag()
continue continue
} }

243
doGui.go
View File

@ -18,39 +18,97 @@ import (
"go.wit.com/log" "go.wit.com/log"
) )
func doGui() { func debug() {
win := gadgets.NewGenericWindow("Forge: A federated git development tool by WIT.COM", "Current Settings") time.Sleep(2 * time.Second)
win.Custom = func() { for {
log.Warn("MAIN WINDOW CLOSE") now := time.Now()
okExit("")
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.AppendByGoPath(repo)
}
tmp := fmt.Sprintf("writable (%d)", found.Len())
me.repoWritableB.SetLabel(tmp)
}
doCheckDirtyAndConfigSave()
found := findDirty()
dirty := found.Len()
if me.repoDirtyB != nil {
tmp := fmt.Sprintf("dirty (%d)", dirty)
me.repoDirtyB.SetLabel(tmp)
}
if me.reposWinB != nil {
tmp := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len())
if dirty > 0 {
tmp = fmt.Sprintf("Repos (%d) (%d dirty)", me.forge.Repos.Len(), dirty)
}
me.reposWinB.SetLabel(tmp)
}
log.Printf("finished a forge scan here in (%s)\n", shell.FormatDuration(time.Since(now)))
time.Sleep(90 * time.Second)
}
}
func doGui() {
if me.forge.Config.GetDefaultGui() == "" {
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
me.myGui.Default()
mainWindow := gadgets.NewGenericWindow("Forge: (this kinda works sometimes)", "Current Settings")
mainWindow.Custom = func() {
log.Warn("MAIN WINDOW CLOSE")
now := time.Now()
count := me.forge.RillReload()
log.Info("Repo Reload count =", count)
if count != 0 {
me.forge.ConfigSave()
}
log.Printf("rill repos.Reload() took (%s)\n", shell.FormatDuration(time.Since(now)))
os.Exit(0)
}
drawWindow(mainWindow)
// sits here forever
debug()
}
func drawWindow(win *gadgets.GenericWindow) {
grid := win.Group.RawGrid() grid := win.Group.RawGrid()
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) grid.NewLabel("")
} else {
me.goSrcEdit = gadgets.NewBasicEntry(grid, "Working Directory")
me.goSrcEdit.SetText(me.forge.Config.ReposDir)
me.goSrcEdit.Custom = func() {
log.Info("updating text to", me.goSrcEdit.String())
}
}
lockpath := grid.NewCheckbox("Lock").SetChecked(me.forge.Config.PathLock)
lockpath.Custom = func() {
if lockpath.IsChecked() {
log.Info("lock working directory")
me.forge.Config.PathLock = true
} else {
log.Info("unlock working directory")
me.forge.Config.PathLock = false
}
me.forge.Config.ConfigSave()
okExit("you must restart forge after changing the Path Lock")
}
grid.NextRow() grid.NextRow()
me.goSrcPwd.SetText(me.forge.GetGoSrc())
// use ENV GIT_AUTHOR // use ENV GIT_AUTHOR
me.gitAuthor = gadgets.NewOneLiner(grid, "Git Author") me.gitAuthor = gadgets.NewOneLiner(grid, "Git Author")
grid.NextRow() grid.NextRow()
@ -65,8 +123,31 @@ func doGui() {
grid.NextRow() grid.NextRow()
// groupM := hbox.NewGroup("Windows")
// gridM := groupM.RawGrid()
// hbox := win.Stack.Box().Horizontal()
gridM := win.Stack.RawGrid() gridM := win.Stack.RawGrid()
var releaseWin *gadgets.GenericWindow
gridM.NewButton("Release Window", func() {
log.Info("todo: move releaser here")
log.Info("for now, run guireleaser")
if releaseWin != nil {
releaseWin.Toggle()
return
}
releaseWin = makeModeMasterWin()
})
var patches *stdPatchsetTableWin
gridM.NewButton("Patch Window", func() {
if patches != nil {
patches.Toggle()
return
}
patches = makePatchsetsWin()
})
var insertWin *gadgets.GenericWindow var insertWin *gadgets.GenericWindow
s := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len()) s := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len())
me.reposWinB = gridM.NewButton(s, func() { me.reposWinB = gridM.NewButton(s, func() {
@ -79,52 +160,55 @@ func doGui() {
insertWin = makeReposWinNew() insertWin = makeReposWinNew()
}) })
// var reposWin *gadgets.GenericWindow
var reposWin *stdReposTableWin
gridM.NewButton("Fix Repos", func() {
if reposWin != nil {
reposWin.Toggle()
return
}
reposWin = makeReposWin()
})
var patchesWin *stdPatchTableWin var patchesWin *stdPatchTableWin
var patchButton *gui.Node gridM.NewButton("Pending patches", func() {
patchButton = gridM.NewButton("Your patches", func() {
if patchesWin != nil { if patchesWin != nil {
patchesWin.Toggle() patchesWin.Toggle()
return return
} }
if !isPatchingSafe() { loadUpstreamPatchsets()
patchButton.SetLabel("not safe yet") if me.psets == nil {
log.Info("failed to download current patchsets")
return return
} }
// patchesWin = makePatchesWin(me.forge.Patchsets)
notdone := new(forgepb.Patches) notdone := new(forgepb.Patches)
all := me.forge.Patchsets.All() all := me.psets.All()
for all.Scan() { for all.Scan() {
pset := all.Next() pset := all.Next()
if pset.State == "DONE" { AddNotDonePatches(notdone, pset, false)
// skip old patchsets
continue
} }
AddAllPatches(notdone, pset, false)
// AddNotDonePatches(notdone, pset, false)
}
notdone.PrintTable()
for patch := range notdone.IterAll() {
comment := cleanSubject(patch.Comment)
log.Info("new patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.RepoNamespace, comment)
}
// savePatchsets()
patchesWin = makePatchesWin(notdone) patchesWin = makePatchesWin(notdone)
}) })
var pubWin *gadgets.GenericWindow gridM.NextRow()
gridM.NewButton("Publish", func() { var howtoWin *gadgets.GenericWindow
if pubWin != nil { gridM.NewButton("Tutorial", func() {
pubWin.Toggle() if howtoWin != nil {
howtoWin.Toggle()
return return
} }
pubWin = makePublishWindow() howtoWin = makeHowtoWin()
}) })
var oldWin *gadgets.GenericWindow gridM.NextRow()
gridM.NewButton("old", func() { gridM.NewLabel("")
if oldWin != nil {
oldWin.Toggle()
return
}
oldWin = makeOldStuff()
})
} }
// this is the magic that generates a window directly from the protocol buffer // this is the magic that generates a window directly from the protocol buffer
@ -132,13 +216,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()
@ -174,7 +258,7 @@ func findMergeToDevel() *gitpb.Repos {
for repo := range me.forge.Repos.IterByFullPath() { for repo := range me.forge.Repos.IterByFullPath() {
// this sees if user has patches for devel. If it does, add it to found // this sees if user has patches for devel. If it does, add it to found
if repo.CountDiffObjects(repo.GetUserBranchName(), repo.GetDevelBranchName()) > 0 { if repo.CountDiffObjects(repo.GetUserBranchName(), repo.GetDevelBranchName()) > 0 {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
} }
now := time.Now() now := time.Now()
@ -185,7 +269,7 @@ func findMergeToDevel() *gitpb.Repos {
// me.forge.PrintHumanTable(found) // me.forge.PrintHumanTable(found)
// check for merges from devel // check for merges from devel
total, count, nope, _ := me.forge.IsEverythingOnDevel() total, count, nope, _ := IsEverythingOnDevel()
log.Printf("devel branch check. %d total repos. (%d ok) (%d not on devel branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now))) log.Printf("devel branch check. %d total repos. (%d ok) (%d not on devel branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
return found return found
} }
@ -197,7 +281,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
} }
/* /*
@ -205,7 +289,7 @@ func findMergeToMaster() *gitpb.Repos {
continue continue
} }
if repo.GetMasterVersion() != repo.GetDevelVersion() { if repo.GetMasterVersion() != repo.GetDevelVersion() {
me.found.AppendByFullPath(repo) me.found.AppendByGoPath(repo)
continue continue
} }
*/ */
@ -215,12 +299,12 @@ 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
if repo.CountDiffObjects(repo.GetDevelBranchName(), repo.GetMasterBranchName()) > 0 { if repo.CountDiffObjects(repo.GetDevelBranchName(), repo.GetMasterBranchName()) > 0 {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
} }
now := time.Now() now := time.Now()
@ -231,7 +315,7 @@ func findMergeToMaster() *gitpb.Repos {
me.forge.PrintHumanTable(found) me.forge.PrintHumanTable(found)
// check for merges from devel // check for merges from devel
total, count, nope, _ := me.forge.IsEverythingOnMaster() total, count, nope, _ := IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now))) log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
return found return found
@ -247,7 +331,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 +382,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 {
@ -329,34 +413,3 @@ func mergeUserToDevel(doit bool) {
} }
me.forge.ConfigSave() me.forge.ConfigSave()
} }
// old things before they are removed, deprecated, fixed, etc
func makeOldStuff() *gadgets.GenericWindow {
oldWin := gadgets.NewGenericWindow("old code", "old code on it's way out")
grid := oldWin.Group.RawGrid()
// var reposWin *gadgets.GenericWindow
var reposWin *stdReposTableWin
grid.NewButton("Fix Repos", func() {
if reposWin != nil {
reposWin.Toggle()
return
}
reposWin = makeReposWin()
})
var howtoWin *gadgets.GenericWindow
grid.NewButton("Tutorial", func() {
if howtoWin != nil {
howtoWin.Toggle()
return
}
howtoWin = makeHowtoWin()
})
grid.NextRow()
grid.NewLabel("")
return oldWin
}

View File

@ -1,168 +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
import (
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"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 {
found := forgepb.NewPatches()
for repo := range me.forge.Repos.IterAll() {
if repo.GetDevelVersion() == repo.GetMasterVersion() {
continue
}
tmp := log.Sprintf("%s..%s", repo.GetMasterVersion(), repo.GetDevelVersion())
r, err := repo.RunStrict([]string{"git", "log", "--pretty=format:%H", tmp})
_ = err
for i, line := range r.Stdout {
log.Info(i, line, repo.FullPath)
}
}
return found
}
func doMergeDevel() (*gitpb.Repos, error) {
var err error
done := gitpb.NewRepos()
found := findMergeToDevel()
for repo := range found.IterAll() {
if repo.CheckDirty() {
log.Info("repo is dirty", repo.GetFullPath())
continue
}
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)
}
done.Append(repo)
config.SetChanged("repos", true)
}
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) {
var err error
setForgeMode(forgepb.ForgeMode_MASTER)
done := gitpb.NewRepos()
found := findMergeToMaster()
for repo := range found.IterAll() {
if repo.CheckDirty() {
log.Info("repo is dirty", repo.GetGoPath())
continue
}
log.Info("Starting merge on", repo.GetGoPath())
if repo.CheckoutMaster() {
log.Info("checkout devel failed", repo.GetGoPath())
err = log.Errorf("checkout devel failed")
badExit(err)
}
if _, err := repo.MergeToMaster(); 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)
}
done.Append(repo)
config.SetChanged("repos", true)
}
return done, err
}

View File

@ -1,112 +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 (
"path/filepath"
"strings"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doNormal() bool {
me.forge.CheckDirtyQuiet()
var count int
stats := me.forge.RillRepos(checkNormalRepoState)
for path, stat := range stats {
dur := stat.End.Sub(stat.Start)
if dur > 10*time.Second {
log.Infof("%s checkNormalRepoState() took a long time (%s)\n", path, shell.FormatDuration(dur))
}
if stat.Err == nil {
continue
}
// log.Infof("%-60s, %-60s %v %s\n", stat.Start, stat.End.String(), dur, path)
// log.Infof("%-30v %s %v\n", dur, path, stat.Err)
// log.Info("got path", path, stat.Err)
count += 1
}
if count > 0 {
log.Info("Some repos are not in a 'normal' state. error count =", count)
log.Info("TODO: list the repos here. forge patch repos?")
dumpWorkRepos()
config.SetChanged("repos", true)
return false
}
return true
}
// 99% of the time, the repos during development should be on your user branch.
// error out if it's not
// this checks to see if a devel and user branch exist
// this needs to run each time in case repos were added manually by the user
// this also verifies that
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() == "" {
me.forge.VerifyBranchNames(repo)
log.Info("ABNORMAL: master branch name was blank in", repo.GetFullPath())
}
if repo.GetMasterBranchName() == "" {
me.forge.VerifyBranchNames(repo)
err = log.Errorf("master branch name blank")
}
if repo.GetDevelBranchName() == "" {
me.forge.VerifyBranchNames(repo)
err = log.Errorf("devel branch name blank")
}
if repo.GetUserBranchName() == "" {
me.forge.VerifyBranchNames(repo)
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.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName())
repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName())
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
log.Infof("changing to user(%s) branch: %s\n", repo.GetUserBranchName(), repo.FullPath)
repo.CheckoutUser()
repo.ReloadCheck()
err = log.Errorf("now on user branch")
}
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

@ -4,210 +4,100 @@
package main package main
import ( import (
"fmt"
"os"
"path/filepath" "path/filepath"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log" "go.wit.com/log"
) )
func doPatchInit() { func doPatch() error {
if me.forge.Patchsets != nil { if argv.Patch.Submit != "" {
if me.forge.Patchsets.Len() == 0 { _, err := me.forge.SubmitDevelPatchSet(argv.Patch.Submit)
// log.Info("IGNORE: patches are empty")
} else {
log.Info("IGNORE: patches already initalized len =", me.forge.Patchsets.Len())
}
}
if err := me.forge.LoadPatchsets(); err != nil {
log.Info("patches failed to open", err)
if err := me.forge.SavePatchsets(); err != nil {
log.Warn("savePatchsets() failed", err)
}
}
}
func isPatchingSafe() bool {
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
return true
}
log.Info("This patch command is not safe to run now")
log.Info("you must reset the state of your git repositories. Run:")
log.Info("")
log.Info("forge normal")
log.Info("")
return false
}
// submit's current working patches
func doPatchSubmit() error {
pset, err := me.forge.MakeDevelPatchSet("testing")
if err != nil { if err != nil {
return err return err
} }
if pset.Patches == nil {
log.Info("pset.Patches == nil")
return err
}
if pset.Patches.Len() == 0 {
log.Info("did not find any patches")
return nil return nil
} }
pset.PrintTable()
_, _, err = pset.HttpPost(myServer(), "new")
return err
}
func doPatch() error {
if argv.Patch.Repos != nil {
dumpWorkRepos()
return nil
}
if argv.Patch.Submit != nil {
return doPatchSubmit()
}
if !isPatchingSafe() {
return log.Errorf("not safe to work on patches")
}
if argv.Patch.Get != nil { if argv.Patch.Get != nil {
psets := forgepb.NewSets() return doPatchGet()
newpb, _, _ := psets.HttpPostVerbose(myServer(), "get")
newpb.PrintTable()
me.forge.Patchsets = newpb
me.forge.SavePatchsets()
return nil
}
if argv.Patch.Check != nil {
/*
old := findExpired()
// old.PrintTable()
for p := range old.IterAll() {
log.Info("patch", p.Filename, p.Namespace)
}
newpb, err := old.HttpPostVerbose(myServer(), "check")
if err != nil {
return err
}
newpb.PrintTable()
*/
log.Info("do something here to find patches merged to devel")
doMergeReport()
return nil
} }
if argv.Patch.List != nil { if argv.Patch.List != nil {
var changed bool return doPatchList()
newpatches := new(forgepb.Set)
newpatches.Patches = forgepb.NewPatches()
for pset := range me.forge.Patchsets.IterAll() {
pset.PrintTable()
for patch := range pset.Patches.IterAll() {
changed = true
if patch.NewHash == "" || patch.NewHash == "na" {
if newpatches.Patches.AppendByPatchId(patch) {
log.Info("patchId added here", patch.PatchId)
} else {
log.Info("patchId already here", patch.PatchId)
} }
} 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)
}
}
}
if changed {
if err := me.forge.SavePatchsets(); err != nil {
log.Warn("savePatchsets() failed", err)
}
}
log.Info("NEW PATCHES TABLE")
newpatches.PrintTable()
for patch := range newpatches.Patches.IterAll() {
if err := setNewCommitHash(patch); err == nil {
log.Info("newhash set already here", patch.PatchId, patch.NewHash)
continue
}
log.Infof("%s is new\n", patch.Filename)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("\tCould not find namespace:", patch.Namespace)
continue
}
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 doPatchList()
applied := findApplied()
if applied == nil || applied.Len() == 0 {
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
*/
}
// if nothing, show patches & dirty repos
me.forge.Patchsets.PrintTable()
dumpWorkRepos()
return nil
}
// Shows repos that are:
// - git dirty repos
// - repos with 'user' branch patches not in 'devel' branch
// - repos with awaiting master branch verions
//
// return true if any are found
func dumpWorkRepos() bool {
// always run dirty first
me.forge.CheckDirtyQuiet()
// if no option is given to patch, list out the // if no option is given to patch, list out the
// repos that have patches ready in them // repos that have patches ready in them
found := findReposWithPatches() found := findReposWithPatches()
if found.Len() == 0 { if found.Len() == 0 {
log.Info("you currently have no repos with patches") log.Info("you currently have no patches in your user branches")
return false return nil
} else {
me.forge.PrintHumanTable(found)
} }
return true me.forge.PrintHumanTable(found)
return nil
}
func doPatchList() error {
openPatchsets()
if me.psets == nil {
return fmt.Errorf("Open Patchsets failed")
}
log.Info("got psets len", len(me.psets.Patchsets))
all := me.psets.SortByName()
for all.Scan() {
pset := all.Next()
// log.Info("pset name =", pset.Name)
dumpPatchset(pset)
}
return nil
}
func savePatchsets() error {
if me.psets == nil {
return fmt.Errorf("savePatchesets() can't save nil")
}
log.Info("savePatchsets() len =", me.psets.Len())
data, err := me.psets.Marshal()
if err != nil {
log.Info("protobuf.Marshal() failed:", err)
return err
}
fullpath := filepath.Join(me.forge.GetConfigDir(), "patchsets.pb")
var pfile *os.File
pfile, err = os.OpenFile(fullpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Info("Patchsets save failed:", err, fullpath)
return err
}
pfile.Write(data)
pfile.Close()
return nil
}
func openPatchsets() {
fullpath := filepath.Join(me.forge.GetConfigDir(), "patchsets.pb")
data, err := os.ReadFile(fullpath)
if err != nil {
log.Info("Patchsets open failed:", err, fullpath)
return
}
psets := new(forgepb.Patchsets)
err = psets.Unmarshal(data)
if err != nil {
log.Info("Unmarshal patchsets failed", err)
return
}
me.psets = psets
} }
// 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())
@ -216,6 +106,13 @@ func dumpPatchset(pset *forgepb.Set) bool {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS GOOD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail()) log.Printf("Patchset Name: %-24s Author: %s <%s> IS GOOD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())
} }
/*
log.Info("applyPatches() State", pset.State)
log.Info("applyPatches() COMMENT", pset.Comment)
log.Info("applyPatches() Branch Name", pset.GetStartBranchName())
log.Info("applyPatches() Start Hash", pset.GetStartBranchHash())
*/
var count int var count int
var bad int var bad int
all := pset.Patches.SortByFilename() all := pset.Patches.SortByFilename()
@ -246,12 +143,12 @@ func IsValidPatch(p *forgepb.Patch) bool {
log.Info("can not apply patch! repo not found", basepath, filename) log.Info("can not apply patch! repo not found", basepath, filename)
return false return false
} }
if repo.ActualDevelHash() != p.StartHash { if repo.DevelHash() != p.StartHash {
log.Info("can not apply patch! devel hash mismatch", basepath, filename) log.Info("can not apply patch! devel hash mismatch", basepath, filename)
return false return false
} }
if repo.ActualDevelHash() == p.StartHash { if repo.DevelHash() == p.StartHash {
log.Info("local devel hash:", repo.ActualDevelHash(), "matches patch hash", p.StartHash, "and can be applied") log.Info("local devel hash:", repo.DevelHash(), "matches patch hash", p.StartHash, "and can be applied")
} }
log.Info("start:", p.StartHash, "end:", p.CommitHash, "file:", basepath, filename, "devel version", repo.GetDevelVersion()) log.Info("start:", p.StartHash, "end:", p.CommitHash, "file:", basepath, filename, "devel version", repo.GetDevelVersion())
for _, line := range p.Files { for _, line := range p.Files {
@ -259,3 +156,24 @@ func IsValidPatch(p *forgepb.Patch) bool {
} }
return true return true
} }
func doPatchGet() error {
psets, err := me.forge.GetPatchesets()
if err != nil {
log.Info("Get Patchsets failed", err)
return err
}
log.Info("Got Patchsets ok", psets.Uuid)
log.Info("got psets len", len(psets.Patchsets))
all := psets.SortByName()
for all.Scan() {
pset := all.Next()
// log.Info("pset name =", pset.Name)
dumpPatchset(pset)
}
log.Info("FIXME: can't save these yet. must merge with on disk psets here")
// savePatchsets()
return nil
}

238
doPull.go
View File

@ -4,61 +4,14 @@
package main package main
import ( import (
"fmt"
"time" "time"
"go.wit.com/lib/gui/shell" "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"
) )
// is every repo on the devel branch?
func doPull() error {
if argv.Pull.Check != nil {
// stats := me.forge.RillFuncError(rillPull)
log.Info("TODO: actually git pull here? this is a bad idea. stopping.")
submit := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
newrepo := new(gitpb.Repo)
newrepo.MasterHash = repo.MasterHash
newrepo.DevelHash = repo.DevelHash
newrepo.Namespace = repo.Namespace
newrepo.URL = repo.URL
submit.Append(newrepo)
}
updatepb, regPB, err := submit.HttpPost(myServer(), "check")
if regPB == nil || err != nil {
log.Info("regPB==nil or err:", err)
return nil
}
log.Infof("pull check %s pb.Len()=%d client.Len()=%d server.Len()=%d err=%v\n", regPB.URL, updatepb.Len(), regPB.ClientDataLen, regPB.ServerDataLen, err)
return nil
}
// below this, you must not be in 'normal' mode
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
log.Info("you must check out the devel or master branches")
return nil
}
if argv.Force == true {
now := time.Now()
stats := me.forge.RillFuncError(rillPull)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, _ := me.forge.IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d git pulled) (%d not on master branch) (%s) git pull total=FIXME%d\n", total, count, nope, shell.FormatDuration(time.Since(now)), len(stats))
return nil
}
log.Info("do a pull check here?")
return nil
}
func rillPull(repo *gitpb.Repo) error { func rillPull(repo *gitpb.Repo) error {
if repo.IsDirty() { if repo.IsDirty() {
// never do dirty repos // never do dirty repos
@ -88,7 +41,130 @@ func rillPull(repo *gitpb.Repo) error {
return nil return nil
} }
// is every repo on the devel branch?
func doGitPullNew() error {
if argv.Pull == nil {
return fmt.Errorf("not really 'fetch pull'")
}
if argv.Force {
now := time.Now()
pullcount := me.forge.RillFuncError(rillPull)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, _ := IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d git pulled) (%d not on master branch) (%s) git pull total=%d\n", total, count, nope, shell.FormatDuration(time.Since(now)), pullcount)
return nil
}
check := gitpb.NewRepos()
if argv.Pull.Dirty != nil {
check = findDirty()
}
if argv.Pull.Patches != nil {
check = findReposWithPatches()
}
if argv.Pull.Check != nil {
// TODO: never wrote this yet
// update, err := me.forge.CheckVersions()
// return err
return nil
}
if check.Len() == 0 {
// check = doFind()
check = findAll()
// check = find50()
// check = findMine()
}
me.forge.PrintHumanTableFull(check)
if argv.Pull.Dirty != nil {
log.Info("dirty count =", check.Len())
return nil
}
found, err := me.forge.LookupPB(check)
if err != nil {
log.Info("LookupPB() failed", err, "len(check)=", check.Len())
return err
}
// me.forge.PrintHumanTableFull(found)
// check to see if the repos need to be updated
update := gitpb.NewRepos()
if found.Len() == 0 {
return nil
}
log.Info("found.Len() ==", found.Len())
for repo := range found.IterAll() {
masterb := repo.GetMasterBranchName()
if masterb == "" {
log.Info(repo.GetNamespace(), "master branch blank")
continue
}
a := repo.GetLocalHash(masterb)
ns := repo.GetNamespace()
repo2 := me.forge.Repos.FindByNamespace(ns)
if repo2 == nil {
log.Info("repo namespace does not exist", a, repo.Namespace)
continue
}
b := repo2.GetLocalHash(repo2.GetMasterBranchName())
if b == a {
continue
}
log.Info(a, "!=", b, repo.Namespace)
update.AppendByNamespace(repo2)
}
if update.Len() == 0 {
// nothing to update
return nil
}
if _, err := me.forge.UpdatePB(update); err != nil {
log.Info("UpdatePB() failed", err, "len(check)=", update.Len())
return err
}
return nil
}
/* /*
func doGitPull() {
allerr := me.found.RillGitPull(40, 5)
all := me.found.SortByFullPath()
for all.Scan() {
repo := all.Next()
result := allerr[repo]
if result.Error == gitpb.ErrorGitPullOnDirty {
log.Info("skip git pull. repo is dirty", repo.GetGoPath())
continue
}
if result.Error == gitpb.ErrorGitPullOnLocal {
log.Info("skip git pull. local branch ", repo.GetGoPath())
continue
}
if result.Exit == 0 {
continue
}
log.Info("git pull error:", repo.GetGoPath(), result.Error)
log.Info("git pull error:", repo.GetGoPath(), result.Stdout)
}
}
*/
// git fetch origin master:master // git fetch origin master:master
func rillFetchMaster(repo *gitpb.Repo) error { func rillFetchMaster(repo *gitpb.Repo) error {
if repo.GetCurrentBranchName() != repo.GetUserBranchName() { if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
@ -108,4 +184,70 @@ func doGitFetch() {
me.forge.ConfigSave() me.forge.ConfigSave()
} }
} }
func doMergeDevel() (*gitpb.Repos, error) {
var err error
done := gitpb.NewRepos()
found := findMergeToDevel()
for repo := range found.IterAll() {
if repo.CheckDirty() {
log.Info("repo is dirty", repo.GetGoPath())
continue
}
log.Info("Starting merge on", repo.GetGoPath())
if repo.CheckoutDevel() {
log.Info("checkout devel failed", repo.GetGoPath())
err = fmt.Errorf("checkout devel failed")
break
}
if _, err := repo.MergeToDevel(); err != nil {
log.Info("merge from user failed", repo.GetGoPath(), err)
err = fmt.Errorf("merge from user failed")
// log.Info(strings.Join(r.Stdout, "\n"))
// log.Info(strings.Join(r.Stderr, "\n"))
break
}
done.Append(repo)
/*
if repo.CheckoutMaster() {
log.Info("checkout master failed", repo.GetGoPath())
return
}
if _, err := repo.MergeToMaster(); err != nil {
log.Info("merge from devel failed", repo.GetGoPath(), err)
return
}
*/ */
}
return done, err
}
func doMergeMaster() (*gitpb.Repos, error) {
var err error
done := gitpb.NewRepos()
found := findMergeToMaster()
for repo := range found.IterAll() {
if repo.CheckDirty() {
log.Info("repo is dirty", repo.GetGoPath())
continue
}
log.Info("Starting merge on", repo.GetGoPath())
if repo.CheckoutMaster() {
log.Info("checkout devel failed", repo.GetGoPath())
err = fmt.Errorf("checkout devel failed")
break
}
if _, err := repo.MergeToMaster(); err != nil {
log.Info("merge from user failed", repo.GetGoPath(), err)
err = fmt.Errorf("merge from user failed")
// log.Info(strings.Join(r.Stdout, "\n"))
// log.Info(strings.Join(r.Stderr, "\n"))
break
}
done.Append(repo)
}
return done, err
}

View File

@ -3,9 +3,18 @@
package main package main
import (
"fmt"
"path/filepath"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// trys to figure out if there is still something to update // trys to figure out if there is still something to update
/*
func doSync() error { func doSync() error {
if argv.Pull.Sync.Clean != nil { if argv.Pull.Sync.Clean != nil {
return doSyncClean() return doSyncClean()
@ -18,7 +27,36 @@ func doSync() error {
} }
func doSyncClean() error { func doSyncClean() error {
log.Printf("todo: fix this?") if err := doAllCheckoutMaster(); err != nil {
return err
}
if _, _, _, err := IsEverythingOnMaster(); err != nil {
log.Info("Not all repos are on the master branch")
return err
}
// force everything
argv.Force = true
if err := doCleanUser(); err != nil {
return err
}
if err := doCleanDevel(); err != nil {
return err
}
now := time.Now()
pullcount := me.forge.RillFuncError(rillPull)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, _ := IsEverythingOnMaster()
log.Printf("doSyncClean() ok. %d total repos. (%d git pulled) (%d not on master branch) (%s) git pull total=%d\n", total, count, nope, shell.FormatDuration(time.Since(now)), pullcount)
return nil return nil
} }
@ -76,4 +114,3 @@ func syncDevelBranches() error {
} }
return nil return nil
} }
*/

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
}

25
doc.go
View File

@ -1,16 +1,23 @@
/* /*
forge -- a tool to manage lots of git repos. forge includes a GUI and TUI. forge -- a tool to git repos at go.wit.com
forge only executes the 'git' command. Everything it does, you can run by hand with 'git'. but you can probably use it for other things
A video demonstration of the terminal UI is available at:
https://mirrors.wit.com/guidemo/forge-gocui-demo.webm
forge v0.22.124-2-g8c25ed1 Built on 2025.07.21_1348
Usage: forge [--debugger] [--logger] [--no-gui] [--gui GUI] [--gui-file GUI-FILE] [--gui-test GUI-TEST] [--gui-verbose] [--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-test GUI-TEST test a specific plugin.so will load
--gui-verbose enable all logging --gui-verbose enable all logging
--bash generate bash completion
--bash generate bash completion
--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 +25,24 @@ 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
patch make patchsets patch make patchsets
pull run 'git pull' pull run 'git pull'
tag manage git tags
*/ */
package main package main

13
exit.go
View File

@ -6,28 +6,27 @@ package main
import ( import (
"os" "os"
"go.wit.com/gui"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func okExit(thing string) { func okExit(thing string) {
gui.UnloadToolkits()
if configSave {
me.forge.SetConfigSave(configSave)
}
if thing != "" { if thing != "" {
log.Info("forge exit:", thing, "ok") log.Info("forge exit:", thing, "ok")
} }
if configSave {
me.forge.SetConfigSave(configSave)
}
// log.Info("Finished go-clean on", check.GetGoPath(), "ok")
me.forge.Exit() me.forge.Exit()
} }
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

@ -1,340 +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
import (
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"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 {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget
TB *forgepb.SetsTable // the gui table buffer
update bool // if the window should be updated
}
func (w *stdPatchsetTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
*/
/*
etimef := func(e *forgepb.Set) string {
etime := e.Etime.AsTime()
s := etime.Format("2006/01/02 15:04")
if strings.HasPrefix(s, "1970/") {
// just show a blank if it's not set
return ""
}
return s
}
t.AddStringFunc("etime", etimef)
*/
/*
ctimef := func(p *forgepb.Set) string {
ctime := p.Ctime.AsTime()
return ctime.Format("2006/01/02 15:04")
}
}
*/
func setPatchsetState(p *forgepb.Set) {
var bad bool
var good bool
var done bool = true
all := p.Patches.All()
for all.Scan() {
patch := all.Next()
// log.Info("patch:", patch.StartHash, patch.CommitHash, patch.Namespace, patch.Filename)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("could not find repo", patch.Namespace)
bad = true
continue
}
if _, err := repo.GetHashName(patch.CommitHash); err == nil {
// this patch has been applied
patch.Applied = true
done = true
continue
}
if name, err := repo.GetHashName(patch.StartHash); err == nil {
// it might be possible to apply this patch
log.Info("patch may be good:", patch.Namespace, name, patch.CommitHash, patch.Filename)
good = true
} else {
// probably screwed up git trees
log.Info("patch with unknown origin:", patch.Namespace, name, err, patch.CommitHash, patch.Filename)
bad = true
}
}
if bad {
p.State = "BAD"
return
}
if good {
p.State = "TRY"
return
}
if done {
p.State = "DONE"
return
}
}
func cleanSubject(line string) string {
// Regular expression to remove "Subject:" and "[PATCH...]" patterns
re := regexp.MustCompile(`(?i)^Subject:\s*(\[\s*PATCH[^\]]*\]\s*)?`)
cleaned := re.ReplaceAllString(line, "")
return strings.TrimSpace(cleaned)
}
// jcarr@framebook:~/go/src/go.wit.com/lib/protobuf/forgepb$ git branch --contains 4a27e7702b9b975b066ec9d2ee7ac932d86552e3
// * jcarr
// jcarr@framebook:~/go/src/go.wit.com/lib/protobuf/forgepb$ git merge-base --is-ancestor "4a27e7702b9b975b066ec9d2ee7ac932d86552e3" "devel" ; echo $?
// 1
// jcarr@framebook:~/go/src/go.wit.com/lib/protobuf/forgepb$ git merge-base --is-ancestor "4a27e7702b9b975b066ec9d2ee7ac932d86552e3" "jcarr" ; echo $?
// 0
func findCommitByHash(hash string, subject string) (string, error) {
cmd := exec.Command("git", "log", "--pretty=format:%H %s")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
lines := strings.Split(out.String(), "\n")
for _, line := range lines {
if strings.Contains(strings.ToLower(line), strings.ToLower(subject)) {
return strings.Fields(line)[0], nil // return the commit hash
}
if strings.Fields(line)[0] == hash {
return "", fmt.Errorf("start commit found: %s", hash)
}
}
return "", fmt.Errorf("no commit found for subject: %s", subject)
}
func findCommitBySubject(subject string) (string, error) {
cmd := exec.Command("git", "log", "--pretty=format:%H %s", "--grep="+subject, "-i")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
lines := strings.Split(out.String(), "\n")
for _, line := range lines {
if strings.Contains(strings.ToLower(line), strings.ToLower(subject)) {
return strings.Fields(line)[0], nil // return the commit hash
}
}
return "", fmt.Errorf("no commit found for subject: %s", subject)
}
// returns true if PB changed
func setNewCommitHash(patch *forgepb.Patch) error {
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
return log.Errorf("could not find repo %s", patch.Namespace)
}
comment := cleanSubject(patch.Comment)
os.Chdir(repo.GetFullPath())
newhash, err := findCommitBySubject(comment)
if err != nil {
return log.Errorf("patch: not found hash: %s %s %s %s %v", patch.CommitHash, patch.Namespace, comment, newhash, err)
}
patchId, err := repo.FindPatchId(newhash)
if err != nil {
return err
}
patch.PatchId = patchId
patch.NewHash = newhash
log.Info("patch: found hash:", patch.CommitHash, newhash, patch.Namespace, comment)
return nil
}
func AddAllPatches(notdone *forgepb.Patches, pset *forgepb.Set, full bool) {
for patch := range pset.Patches.IterAll() {
comment := cleanSubject(patch.Comment)
if found := notdone.FindByCommitHash(patch.CommitHash); found != nil {
log.Info("duplicate commit hash", patch.Namespace, "patch:", patch.NewHash, "commithash:", patch.CommitHash, comment)
// continue
}
// log.Info("adding patch:", patch.Namespace, patch.CommitHash, comment, newhash)
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
}
}
func AddNotDonePatches(notdone *forgepb.Patches, pset *forgepb.Set, full bool) {
for patch := range pset.Patches.IterAll() {
comment := cleanSubject(patch.Comment)
if found := notdone.FindByCommitHash(patch.CommitHash); found != nil {
log.Info("duplicate notdone", patch.Namespace, "patch:", patch.NewHash, "commithash:", patch.CommitHash, comment)
continue
}
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("could not find repo", patch.Namespace)
if full {
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
}
continue
}
if patch.NewHash != "" {
log.Info("already applied patch", patch.Namespace, ": newhash:", patch.NewHash, "commithash:", patch.CommitHash, comment)
continue
}
os.Chdir(repo.GetFullPath())
newhash, err := findCommitByHash(patch.StartHash, comment)
if err != nil {
// this patch has not been applied yet
log.Info("patch: not found hash:", patch.Namespace, patch.CommitHash, comment, err)
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
continue
}
newhash, err = findCommitBySubject(comment)
if err == nil {
patch.NewHash = newhash
log.Info("patch: found hash:", patch.Namespace, "commit patch", patch.CommitHash, "new hash", newhash, "start hash", patch.StartHash, comment)
continue
}
// this patch has not been applied yet
log.Info("patch: not found hash:", patch.Namespace, patch.CommitHash, comment, newhash, err)
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
}
}
func findExpired() *forgepb.Patches {
var pset *forgepb.Patches
for found := range me.forge.Patchsets.IterAll() {
if found.Name == "forge auto commit" {
pset = found.Patches
}
}
if pset == nil {
log.Info("failed to find 'forge auto commit'")
return forgepb.NewPatches()
}
found := forgepb.NewPatches()
for patch := range pset.IterAll() {
comment := cleanSubject(patch.Comment)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("could not find repo", patch.Namespace)
continue
}
if patch.NewHash != "" {
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
continue
}
os.Chdir(repo.GetFullPath())
_, err := findCommitByHash(patch.StartHash, comment)
if err == nil {
log.Info("found 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
continue
}
/*
newhash, err = findCommitBySubject(comment)
if err == nil {
patch.NewHash = newhash
log.Info("patch: found hash:", patch.Namespace, "commit patch", patch.CommitHash, "new hash", newhash, "start hash", patch.StartHash, comment)
found.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
continue
}
*/
}
return found
}
func findApplied() *forgepb.Patches {
var pset *forgepb.Patches
for found := range me.forge.Patchsets.IterAll() {
if found.Name == "forge auto commit" {
pset = found.Patches
}
}
if pset == nil {
log.Info("failed to find 'forge auto commit'")
return pset
}
found := forgepb.NewPatches()
for patch := range pset.IterAll() {
cmd := []string{"git", "merge-base", "--is-ancestor", patch.NewHash, "devel"}
repo := me.forge.Repos.FindByNamespace(patch.Namespace)
_, err := repo.RunStrict(cmd)
if err != nil {
// log.Info("NOT APPLIED", patch.Namespace, result, err)
// log.Info("NOT APPLIED newhash:", patch.NewHash, "commithash:", patch.CommitHash, "patch", patch.Namespace)
} else {
// log.Info("APPLIED newhash:", patch.NewHash, "commithash:", patch.CommitHash, "patch", patch.Namespace)
found.Append(patch)
}
}
return found
}

177
main.go
View File

@ -7,9 +7,12 @@ package main
import ( import (
"embed" "embed"
"fmt"
"os"
"strings" "strings"
"go.wit.com/lib/gui/prep" "go.wit.com/dev/alexflint/arg"
"go.wit.com/gui"
"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,18 +44,35 @@ 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 gui.InitArg()
me.auto = prep.Bash3(&argv) // this line should be: prep.Bash(&argv) me.pp = arg.MustParse(&argv)
// me.auto = prep.Bash3(argv.DoAutoComplete, &argv) // this line should be: prep.Bash(&argv) if argv.Bash {
// arg.MustParse(&argv) // these three lines are becoming terrible syntax argv.doBash()
// me.auto = prep.MustParse(&argv) // try to make this work? 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("")
}
// initialize patches // load the ~/.config/forge/ config
doPatchInit() me.forge = forgepb.Init()
// first find the repos or gopaths to operate on // first find the repos or gopaths to operate on
if argv.Config != nil { if argv.Config != nil {
@ -92,6 +112,16 @@ func main() {
} }
if argv.Clean != nil { if argv.Clean != nil {
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)
} }
@ -99,70 +129,42 @@ func main() {
okExit("") okExit("")
} }
if argv.Help != nil {
doHelp()
okExit("")
}
if argv.Dirty != nil { if argv.Dirty != nil {
doDirty() doDirty()
okExit("") okExit("")
} }
if argv.Tag != nil { if argv.GitFetch != nil {
doTag() doGitFetch()
okExit("") okExit("")
} }
if argv.Normal != nil {
if argv.Normal.On != nil {
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
log.Info("you are already in the normal state")
okExit("")
}
setForgeMode(forgepb.ForgeMode_NORMAL)
log.Info("normal mode on")
okExit("")
}
if argv.Normal.Off != nil {
if me.forge.Config.Mode != forgepb.ForgeMode_NORMAL {
log.Info("you were aleady not in the normal state")
okExit("")
}
setForgeMode(forgepb.ForgeMode_DEVEL)
log.Info("normal mode off")
okExit("")
}
if doNormal() {
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 {
log.Infof("Forge has set the mode to 'Normal'\n")
setForgeMode(forgepb.ForgeMode_NORMAL)
}
okExit("")
}
okExit("")
}
// if you are in "normal" mode, always run normal every time to catch accidental errors
// for example, if you accidentally changed branches from your user branch
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
if doNormal() {
log.Infof("all your %d repos are in a normal stete for development\n", me.forge.Repos.Len())
}
}
if argv.Merge != nil { if argv.Merge != nil {
if err := doMerge(); err != nil { if argv.Merge.Devel != nil {
if _, err := doMergeDevel(); err != nil {
badExit(err)
}
okExit("devel merge ok")
}
if argv.Merge.Master != nil {
if _, err := doMergeMaster(); err != nil {
badExit(err)
}
okExit("master merge ok")
}
badExit(fmt.Errorf("merge what?"))
}
if argv.Pull != nil {
if argv.Pull.Sync != nil {
if err := doSync(); err != nil {
badExit(err) badExit(err)
} }
okExit("") okExit("")
} }
if argv.Pull != nil { doGitPullNew()
doPull()
okExit("") okExit("")
} }
@ -181,50 +183,29 @@ func main() {
if err := doPatch(); err != nil { if err := doPatch(); err != nil {
badExit(err) badExit(err)
} }
okExit("patch list")
}
// todo: redo this logic using forgepb
if configSave {
me.forge.ConfigSave()
configSave = false
}
// 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("") 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 opening the GUI, always check git for dirty repos // if opening the GUI, always check git for dirty repos
me.forge.CheckDirty() doCheckDirtyAndConfigSave()
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 (?)
if dumpWorkRepos() {
// found some repos at least
} else {
// every repo is in a really clean state. no extra files anywhere
// no dirty repos, no repos that need to be published
// nothing different between user and master branch version. not common
log.Info("All of your git repositories appear to be in perfect shape")
}
okExit("")
}
// keep this small
func doHelp() {
log.Info("")
log.Info("forge -h : to see the available options")
log.Info("forge --bash : will create a bash autocomplete file")
log.Info("forge : with no arguements, forge tries to load a GO GUI plugin")
log.Info(" : there are two GUI plugins. terminal & GTK")
log.Info("")
log.Info("forge list : shows a table of all your repos")
log.Info("forge checkout : checks out all your repos to the same branch")
log.Info(" : the default is your user branch")
log.Info("forge clean : reverts all repos to the master branch")
log.Info("forge dirty : show all repos git reports as dirty")
log.Info("")
okExit("")
}
func doHelpPatches() {
log.Info("TODO: ?")
okExit("") okExit("")
} }

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"
) )
@ -20,17 +20,12 @@ func (b *mainType) Enable() {
b.mainbox.Enable() b.mainbox.Enable()
} }
// returns the server to connect to
func myServer() string {
return me.forge.GetForgeURL()
}
// 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
psets *forgepb.Patchsets // the locally stored on disk patchsets
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
@ -39,7 +34,6 @@ type mainType struct {
mainbox *gui.Node // the main box. enable/disable this mainbox *gui.Node // the main box. enable/disable this
autoDryRun *gui.Node // checkbox for --dry-run autoDryRun *gui.Node // checkbox for --dry-run
goSrcPwd *gadgets.OneLiner // what is being used as primary directory for your work goSrcPwd *gadgets.OneLiner // what is being used as primary directory for your work
goSrcEdit *gadgets.BasicEntry // what is being used as primary directory for your work
gitAuthor *gadgets.OneLiner // ENV GIT_AUTHOR NAME and EMAIL gitAuthor *gadgets.OneLiner // ENV GIT_AUTHOR NAME and EMAIL
// these hold the branches that the user can switch all the repositories to them // these hold the branches that the user can switch all the repositories to them

0
test
View File

164
windowForgePatchsets.go Normal file
View File

@ -0,0 +1,164 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// this is the "main" patch window. The first one
// then you can dig down and examine the patchsets and the files
import (
"fmt"
"strconv"
"go.wit.com/lib/gadgets"
"go.wit.com/log"
"go.wit.com/gui"
)
type patchesWindow struct {
win *gadgets.BasicWindow // the patches window
stack *gui.Node // the top box set as vertical
grid *gui.Node // the list of available patches
reason *gadgets.BasicEntry // the name of the patchset
submitB *gui.Node // the submit patchet button
psetgrid *gui.Node // the list of each patchset
totalOL *gadgets.OneLiner
dirtyOL *gadgets.OneLiner
readonlyOL *gadgets.OneLiner
rw *gadgets.OneLiner
// checkB *gui.Node
}
func (r *patchesWindow) Hidden() bool {
return r.win.Hidden()
}
func (r *patchesWindow) Toggle() {
if r.Hidden() {
r.Show()
} else {
r.Hide()
}
}
func (r *patchesWindow) Show() {
r.win.Show()
}
func (r *patchesWindow) Hide() {
r.win.Hide()
}
// you can only have one of these
func (r *patchesWindow) initWindow() {
r.win = gadgets.RawBasicWindow("Forge Patchesets")
r.win.Make()
r.stack = r.win.Box().NewBox("bw vbox", false)
// me.reposwin.Draw()
r.win.Custom = func() {
log.Warn("Patchset Window close. setting hidden=true")
// sets the hidden flag to false so Toggle() works
r.win.Hide()
}
r.grid = r.stack.RawGrid()
r.submitPatchesBox()
// update the stats about the repos and patches
r.Update()
}
func (r *patchesWindow) submitPatchesBox() {
// s := new(patchSummary)
group1 := r.stack.NewGroup("Repo Summary")
grid := group1.RawGrid()
// make the header table for repo stats
r.totalOL = gadgets.NewOneLiner(grid, "Total")
grid.NextRow()
r.dirtyOL = gadgets.NewOneLiner(grid, "dirty")
grid.NextRow()
r.readonlyOL = gadgets.NewOneLiner(grid, "read-only")
grid.NextRow()
r.rw = gadgets.NewOneLiner(grid, "r/w")
grid.NextRow()
// now, make the 'widget group' and the buttons at the bottom of the window
group1 = r.stack.NewGroup("Patchset Create")
grid = group1.RawGrid()
grid.NewButton("show current patches", func() {
r.Update()
pset, err := me.forge.MakeDevelPatchSet("current patches")
if err != nil {
log.Info("patchset creation failed", err)
return
}
if pset == nil {
log.Info("you have no current patches")
return
}
/*
win := makePatchWindow(pset)
win.Show()
*/
})
r.reason = gadgets.NewBasicEntry(grid, "Patchset name:")
r.reason.Custom = func() {
if r.reason.String() != "" {
log.Info("Forge: enable submit")
r.submitB.Enable()
} else {
log.Info("Forge: disable submit")
r.submitB.Disable()
}
}
r.submitB = grid.NewButton("Submit", func() {
if r.submitB.IsEnabled() {
log.Info("submit button is enabled")
} else {
log.Info("submit button is disabled. BAD GUI TOOLKIT ERROR")
return
}
// pset, err := me.forge.SubmitDevelPatchSet(r.reason.String())
// if err != nil {
// log.Info(err)
// return
// }
// r.addPatchsetNew(pset)
})
// disables the submit button until the user enters a name
r.submitB.Disable()
grid.NextRow()
}
// will update this from the current state of the protobuf
func (r *patchesWindow) Update() {
var total, dirty, readonly, rw int
// figure out the totals
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
total += 1
if repo.IsDirty() {
dirty += 1
}
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
readonly += 1
} else {
rw += 1
}
}
// send the values to the GUI toolkit
r.totalOL.SetText(strconv.Itoa(total) + " repos")
r.dirtyOL.SetText(strconv.Itoa(dirty) + " repos")
r.readonlyOL.SetText(strconv.Itoa(readonly) + " repos")
r.rw.SetText(fmt.Sprintf("%d repos", rw))
}

100
windowFound.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// shows a window of the 'found' repos
import (
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
"go.wit.com/gui"
)
type foundWindow struct {
win *gadgets.BasicWindow // the patches window
stack *gui.Node // the top box set as vertical
grid *gui.Node // the list of available patches
reason *gadgets.BasicEntry // the name of the patchset
submitB *gui.Node // the submit patchet button
psetgrid *gui.Node // the list of each patchset
totalOL *gadgets.OneLiner
dirtyOL *gadgets.OneLiner
readonlyOL *gadgets.OneLiner
rw *gadgets.OneLiner
found *gitpb.Repos
}
func (r *foundWindow) Hidden() bool {
return r.win.Hidden()
}
func (r *foundWindow) Toggle() {
if r.Hidden() {
r.Show()
} else {
r.Hide()
}
}
func (r *foundWindow) Show() {
r.win.Show()
}
func (r *foundWindow) Hide() {
r.win.Hide()
}
// you can only have one of these
func (r *foundWindow) initWindow() {
r.win = gadgets.RawBasicWindow("Found Repos")
r.win.Make()
r.stack = r.win.Box().NewBox("bw vbox", false)
// me.reposwin.Draw()
r.win.Custom = func() {
log.Warn("Found Window close. setting hidden=true")
// sets the hidden flag to false so Toggle() works
r.win.Hide()
}
group1 := r.stack.NewGroup("Repo Summary")
group1.NewButton("dirty", func() {
log.Info("find dirty here")
found := findDirty()
me.forge.PrintHumanTable(found)
})
group1.NewButton("all", func() {
log.Info("find all here")
found := findAll()
me.forge.PrintHumanTable(found)
})
r.grid = r.stack.RawGrid()
group1.NewButton("show", func() {
r.listRepos()
})
}
func (r *foundWindow) listRepos() {
for repo := range r.found.IterAll() {
r.addRepo(repo)
}
}
func (r *foundWindow) addRepo(repo *gitpb.Repo) {
r.grid.NewButton("View", func() {
})
r.grid.NewLabel(repo.GetGoPath())
r.grid.NewLabel(repo.GetMasterVersion())
r.grid.NewLabel(repo.GetDevelVersion())
r.grid.NewLabel(repo.GetUserVersion())
r.grid.NewLabel(repo.GetCurrentBranchName())
r.grid.NextRow()
}
// will update this from the current state of the protobuf
func (r *foundWindow) Update() {
}

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()

148
windowModeMaster.go Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// An app to submit patches for the 30 GO GUI repos
func makeModeMasterWin() *gadgets.GenericWindow {
win := gadgets.NewGenericWindow("Release", "tools")
grid := win.Group.RawGrid()
checkout := grid.NewButton("git checkout master", func() {
win.Disable()
defer win.Enable()
})
gitpull := grid.NewButton("git pull", func() {
win.Disable()
defer win.Enable()
})
grid.NextRow()
cleanUser := grid.NewButton("Clean user branches", func() {
win.Disable()
defer win.Enable()
if err := doCleanUser(); err != nil {
log.Info("Clean user branches failed", err)
}
})
cleanDevel := grid.NewButton("Clean devel branches", func() {
win.Disable()
defer win.Enable()
if err := doCleanDevel(); err != nil {
log.Info("Clean devel branches failed", err)
}
})
grid.NextRow()
f := func() {
total, count, nope, err := IsEverythingOnMaster()
if nope == 0 {
checkout.Disable()
gitpull.Enable()
} else {
log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) err=%v\n", total, count, nope, err)
checkout.Enable()
}
var localuser bool // are there still local user branches
var localdevel bool // are there still local devel branches
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.IsLocalBranch(repo.GetUserBranchName()) {
localuser = true
}
if repo.IsLocalBranch(repo.GetDevelBranchName()) {
localdevel = true
}
}
if localuser {
cleanUser.Enable()
} else {
cleanUser.Disable()
}
if localdevel {
cleanDevel.Enable()
} else {
cleanDevel.Disable()
}
}
grid.NewButton("check repo state", func() {
win.Disable()
defer win.Enable()
f()
})
grid.NewButton("reset user branches (?)", func() {
resetUserBranchesWindow()
})
return win
}
func resetUserBranchesWindow() {
found := gitpb.NewRepos()
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
uname := repo.GetUserBranchName()
dname := repo.GetDevelBranchName()
if repo.GetCurrentBranchName() == uname {
log.Info("Repo is on the user branch. Can't delete it.", repo.GetGoPath())
continue
}
b1 := repo.CountDiffObjects(uname, dname)
b2 := repo.CountDiffObjects(dname, uname)
log.Info("user vs devel count", b1, b2)
if b1 == 0 && b2 == 0 {
cmd := []string{"git", "branch", "-D", uname}
log.Info(repo.GetGoPath(), cmd)
repo.RunVerbose(cmd)
repo.Reload()
continue
}
found.Append(repo)
}
win := gadgets.RawBasicWindow("reset user branches")
win.Make()
win.Show()
win.Custom = func() {
// sets the hidden flag to false so Toggle() works
win.Hide()
}
box := win.Box().NewBox("bw vbox", false)
group := box.NewGroup("test buttons")
hbox := group.Box().Horizontal()
hbox.NewButton("force delete user branch", func() {
win.Disable()
defer win.Enable()
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
brname := repo.GetUserBranchName()
cmd := []string{"git", "branch", "-D", brname}
log.Info(repo.GetGoPath(), cmd)
repo.RunVerbose(cmd)
repo.Reload()
}
me.forge.SetConfigSave(true)
me.forge.ConfigSave()
})
t := makeStandardReposGrid(found)
t.SetParent(box)
t.ShowTable()
}

View File

@ -39,7 +39,6 @@ func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
dwin.win = gadgets.NewGenericWindow("current patches", "patching options") dwin.win = gadgets.NewGenericWindow("current patches", "patching options")
dwin.win.Custom = func() { dwin.win.Custom = func() {
log.Info("test delete window here") log.Info("test delete window here")
dwin.win.Hide()
// dwin = nil // dwin = nil
} }
grid := dwin.win.Group.RawGrid() grid := dwin.win.Group.RawGrid()
@ -52,18 +51,54 @@ func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
all := patches.All() all := patches.All()
for all.Scan() { for all.Scan() {
patch := all.Next() patch := all.Next()
repomap[patch.Namespace] += 1 repomap[patch.RepoNamespace] += 1
} }
grid.NewLabel(fmt.Sprintf("%d", len(repomap))) grid.NewLabel(fmt.Sprintf("%d", len(repomap)))
grid.NewLabel(fmt.Sprintf("total repos")) grid.NewLabel(fmt.Sprintf("total repos"))
grid.NextRow() grid.NextRow()
grid.NewButton("show all", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
notdone := new(forgepb.Patches)
all := me.psets.All()
for all.Scan() {
pset := all.Next()
AddNotDonePatches(notdone, pset, true)
}
for patch := range notdone.IterAll() {
comment := cleanSubject(patch.Comment)
log.Info("new patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.RepoNamespace, comment)
}
dwin.doPatchesTable(notdone)
})
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) rn := p.RepoNamespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
log.Info("Could not figure out repo path", rn)
return
}
filename, err := savePatch(p)
if err != nil {
log.Info("savePatch() failed", err)
return
}
count += 1
if err := applyPatch(repo, filename); 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)
}) })
@ -79,16 +114,20 @@ func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
} }
func applyPatchNew(p *forgepb.Patch) error { func applyPatchNew(p *forgepb.Patch) error {
rn := p.Namespace rn := p.RepoNamespace
repo := me.forge.FindByGoPath(rn) repo := me.forge.FindByGoPath(rn)
if repo == nil { if repo == nil {
log.Info("Could not figure out repo path", rn) return fmt.Errorf("Could not figure out repo path %s", rn)
return log.Errorf("%s namespace?\n", rn)
} }
if _, err := applyAndTrackPatch(repo, p); err != nil { filename, err := savePatch(p)
if err != nil {
log.Info("savePatch() failed", err)
return err
}
if err := applyPatch(repo, filename); err != nil {
log.Info("warn user of git am error", err)
cmd := []string{"git", "am", "--abort"} cmd := []string{"git", "am", "--abort"}
err := repo.RunVerbose(cmd) err := repo.RunVerbose(cmd)
log.Info("warn user of git am error", err)
return err return err
} }
return nil return nil
@ -110,20 +149,21 @@ func (dwin *stdPatchTableWin) doPatchesTable(currentPatches *forgepb.Patches) {
dwin.TB.Custom(f) dwin.TB.Custom(f)
} }
// used by the PB table // define what rows to have in the protobuf table
func applyPatchLabel(p *forgepb.Patch) string { func AddPatchesPB(tbox *gui.Node, pb *forgepb.Patches) *forgepb.PatchesTable {
rn := p.Namespace t := pb.NewTable("PatchesPB")
t.NewUuid()
t.SetParent(tbox)
gitam := t.AddButtonFunc("apply", func(p *forgepb.Patch) string {
rn := p.RepoNamespace
if repo := me.forge.FindByGoPath(rn); repo == nil { if repo := me.forge.FindByGoPath(rn); repo == nil {
// log.Info("Could not figure out repo path", rn) // log.Info("Could not figure out repo path", rn)
return "" return ""
} }
if p.NewHash == "" {
return "git am" return "git am"
} })
return "done" gitam.Custom = func(p *forgepb.Patch) {
}
func applyPatchClick(p *forgepb.Patch) {
if err := applyPatchNew(p); err != nil { if err := applyPatchNew(p); err != nil {
log.Info("git am failed on file", p.Filename, "with error", err) log.Info("git am failed on file", p.Filename, "with error", err)
return return
@ -131,17 +171,8 @@ func applyPatchClick(p *forgepb.Patch) {
log.Info("ran: git am", p.Filename, "ok") log.Info("ran: git am", p.Filename, "ok")
} }
// define what rows to have in the protobuf table
func AddPatchesPB(tbox *gui.Node, pb *forgepb.Patches) *forgepb.PatchesTable {
t := pb.NewTable("PatchesPB")
t.NewUuid()
t.SetParent(tbox)
gitam := t.AddButtonFunc("apply", applyPatchLabel)
gitam.Custom = applyPatchClick
t.AddCommitHash() t.AddCommitHash()
t.AddNamespace() t.AddRepoNamespace()
// t.AddFilename() // t.AddFilename()
t.AddStringFunc("file", func(p *forgepb.Patch) string { t.AddStringFunc("file", func(p *forgepb.Patch) string {
_, fname := filepath.Split(p.Filename) _, fname := filepath.Split(p.Filename)
@ -172,46 +203,3 @@ func savePatch(p *forgepb.Patch) (string, error) {
return tmpname, nil return tmpname, nil
} }
func applyAndTrackPatch(repo *gitpb.Repo, p *forgepb.Patch) (string, error) {
_, filen := filepath.Split(p.Filename)
tmpname := filepath.Join("/tmp", filen)
log.Info("saving as", tmpname, p.Filename)
raw, err := os.OpenFile(tmpname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return "", err
}
raw.Write(p.Data)
raw.Close()
cmd := []string{"git", "am", tmpname}
err = repo.RunVerbose(cmd)
if err != nil {
log.Info("git am failed. run 'git am --abort' here")
return "", log.Errorf("git am failed")
}
log.Info("Try to find hash value now")
p.NewHash = "fixme applyAndTrack"
if setNewHash(p, p.NewHash) {
log.Info("setting NewHash worked", p.NewHash)
}
me.forge.SavePatchsets()
return p.NewHash, log.Errorf("did not lookup new hash")
}
func setNewHash(p *forgepb.Patch, hash string) bool {
for pset := range me.forge.Patchsets.IterAll() {
for patch := range pset.Patches.IterAll() {
if patch.CommitHash == hash {
patch.NewHash = hash
log.Info("found patch in repo")
me.forge.SavePatchsets()
return true
}
}
}
return false
}

422
windowPatchsets.go Normal file
View File

@ -0,0 +1,422 @@
// 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"
"os"
"os/exec"
"regexp"
"strings"
"sync"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
type stdPatchsetTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget
TB *forgepb.PatchsetsTable // the gui table buffer
update bool // if the window should be updated
}
func (w *stdPatchsetTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
func loadUpstreamPatchsets() {
psets, err := me.forge.GetPatchesets()
if err != nil {
log.Info("Get Patchsets failed", err)
return
}
var foundnew bool
all := psets.All()
for all.Scan() {
pset := all.Next()
found := me.psets.FindByUuid(pset.Uuid)
if found == nil {
log.Info("new patchset", pset.Name, pset.Uuid)
pset.State = "new"
foundnew = true
} else {
log.Info("patchset already on disk", found.Name, found.State)
pset.State = found.State
if pset.State == "" {
pset.State = "new"
}
}
}
if foundnew {
log.Info("should save these here")
me.psets = psets
savePatchsets()
}
}
func makePatchsetsWin() *stdPatchsetTableWin {
dwin := new(stdPatchsetTableWin)
dwin.win = gadgets.NewGenericWindow("forge current patchsets", "patchset options")
dwin.win.Custom = func() {
log.Info("test delete window here")
}
grid := dwin.win.Group.RawGrid()
grid.NewButton("ondisk", func() {
openPatchsets()
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
dwin.doPatchsetsTable(me.psets)
})
grid.NewButton("upstream", func() {
loadUpstreamPatchsets()
dwin.doPatchsetsTable(me.psets)
})
grid.NewButton("save", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
savePatchsets()
})
grid.NewButton("set patchset state", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
all := me.psets.All()
for all.Scan() {
pset := all.Next()
if pset.State == "" {
log.Info("What is up with?", pset.Name)
setPatchsetState(pset)
} else {
log.Info("patchset already had state", pset.Name, pset.State)
}
}
savePatchsets()
})
grid.NewButton("find commit hashes", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
all := me.psets.All()
for all.Scan() {
pset := all.Next()
if pset.State != "new" {
log.Info("patchset already had state", pset.Name, pset.State)
continue
}
if setNewCommitHash(pset) {
// everything in this patchset is applied
pset.State = "APPLIED"
}
}
savePatchsets()
})
grid.NewButton("show pending patches", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
notdone := new(forgepb.Patches)
all := me.psets.All()
for all.Scan() {
pset := all.Next()
AddNotDonePatches(notdone, pset, false)
}
for patch := range notdone.IterAll() {
comment := cleanSubject(patch.Comment)
log.Info("new patch:", patch.NewHash, "commithash:", patch.CommitHash, patch.RepoNamespace, comment)
}
// savePatchsets()
makePatchesWin(notdone)
})
// make a box at the bottom of the window for the protobuf table
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
// load and show the current patch sets
openPatchsets()
if me.psets == nil {
log.Info("Open Patchsets failed")
return dwin
}
dwin.doPatchsetsTable(me.psets)
return dwin
}
func (dwin *stdPatchsetTableWin) doPatchsetsTable(currentPatchsets *forgepb.Patchsets) {
dwin.Lock()
defer dwin.Unlock()
if dwin.TB != nil {
dwin.TB.Delete()
dwin.TB = nil
}
// display the protobuf
dwin.TB = AddPatchsetsPB(dwin.box, currentPatchsets)
f := func(pset *forgepb.Patchset) {
log.Info("Triggered. do something here", pset.Name)
/*
win := makePatchWindow(pset)
win.Show()
*/
}
dwin.TB.Custom(f)
}
func AddPatchsetsPB(tbox *gui.Node, pb *forgepb.Patchsets) *forgepb.PatchsetsTable {
t := pb.NewTable("PatchsetsPB")
t.NewUuid()
t.SetParent(tbox)
t.AddStringFunc("#", func(p *forgepb.Patchset) string {
return fmt.Sprintf("%d", p.Patches.Len())
})
vp := t.AddButtonFunc("View Patchset", func(p *forgepb.Patchset) string {
return p.Name
})
vp.Custom = func(pset *forgepb.Patchset) {
log.Info("show patches here", pset.Name)
makePatchesWin(pset.Patches)
// patchwin := makePatchesWin()
// patchwin.doPatchesTable(pset.Patches)
/*
win := makePatchWindow(pset)
win.Show()
*/
}
t.AddComment()
t.AddState()
t.AddHostname()
ctimef := func(p *forgepb.Patchset) string {
ctime := p.Ctime.AsTime()
return ctime.Format("2006/01/02 15:04")
}
t.AddStringFunc("ctime", ctimef)
/*
etimef := func(e *forgepb.Patchset) string {
etime := e.Etime.AsTime()
s := etime.Format("2006/01/02 15:04")
if strings.HasPrefix(s, "1970/") {
// just show a blank if it's not set
return ""
}
return s
}
t.AddStringFunc("etime", etimef)
*/
t.AddStringFunc("Author", func(p *forgepb.Patchset) string {
return fmt.Sprintf("%s <%s>", p.GitAuthorName, p.GitAuthorEmail)
})
t.AddUuid()
newCommit := t.AddButtonFunc("new hash", func(p *forgepb.Patchset) string {
return "find"
})
newCommit.Custom = func(pset *forgepb.Patchset) {
log.Info("find new commits here", pset.Name)
// makePatchesWin(pset.Patches)
setNewCommitHash(pset)
}
t.ShowTable()
return t
}
func setPatchsetState(p *forgepb.Patchset) {
var bad bool
var good bool
var done bool = true
all := p.Patches.All()
for all.Scan() {
patch := all.Next()
// log.Info("patch:", patch.StartHash, patch.CommitHash, patch.RepoNamespace, patch.Filename)
repo := me.forge.FindByGoPath(patch.RepoNamespace)
if repo == nil {
log.Info("could not find repo", patch.RepoNamespace)
bad = true
continue
}
if _, err := repo.GetHashName(patch.CommitHash); err == nil {
// this patch has been applied
patch.Applied = true
done = true
continue
}
if name, err := repo.GetHashName(patch.StartHash); err == nil {
// it might be possible to apply this patch
log.Info("patch may be good:", patch.RepoNamespace, name, patch.CommitHash, patch.Filename)
good = true
} else {
// probably screwed up git trees
log.Info("patch with unknown origin:", patch.RepoNamespace, name, err, patch.CommitHash, patch.Filename)
bad = true
}
}
if bad {
p.State = "BAD"
return
}
if good {
p.State = "TRY"
return
}
if done {
p.State = "DONE"
return
}
}
func cleanSubject(line string) string {
// Regular expression to remove "Subject:" and "[PATCH...]" patterns
re := regexp.MustCompile(`(?i)^Subject:\s*(\[\s*PATCH[^\]]*\]\s*)?`)
cleaned := re.ReplaceAllString(line, "")
return strings.TrimSpace(cleaned)
}
func findCommitByHash(hash string, subject string) (string, error) {
cmd := exec.Command("git", "log", "--pretty=format:%H %s")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
lines := strings.Split(out.String(), "\n")
for _, line := range lines {
if strings.Contains(strings.ToLower(line), strings.ToLower(subject)) {
return strings.Fields(line)[0], nil // return the commit hash
}
if strings.Fields(line)[0] == hash {
return "", fmt.Errorf("start commit found: %s", hash)
}
}
return "", fmt.Errorf("no commit found for subject: %s", subject)
}
func findCommitBySubject(subject string) (string, error) {
cmd := exec.Command("git", "log", "--pretty=format:%H %s", "--grep="+subject, "-i")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
lines := strings.Split(out.String(), "\n")
for _, line := range lines {
if strings.Contains(strings.ToLower(line), strings.ToLower(subject)) {
return strings.Fields(line)[0], nil // return the commit hash
}
}
return "", fmt.Errorf("no commit found for subject: %s", subject)
}
func setNewCommitHash(p *forgepb.Patchset) bool {
var done bool = true
for patch := range p.Patches.IterAll() {
// parts := strings.Fields(patch.Comment)
repo := me.forge.FindByGoPath(patch.RepoNamespace)
if repo == nil {
log.Info("could not find repo", patch.RepoNamespace)
continue
}
comment := cleanSubject(patch.Comment)
if patch.NewHash != "na" {
log.Info("patch: newhash:", patch.NewHash, "commithash:", patch.CommitHash, patch.RepoNamespace, comment)
continue
}
done = false
os.Chdir(repo.GetFullPath())
newhash, err := findCommitBySubject(comment)
if err != nil {
log.Info("patch: not found hash:", patch.CommitHash, patch.RepoNamespace, comment, newhash, err)
continue
}
patch.NewHash = newhash
log.Info("patch: found hash:", patch.CommitHash, newhash, patch.RepoNamespace, comment)
}
return done
}
func AddNotDonePatches(notdone *forgepb.Patches, pset *forgepb.Patchset, full bool) {
for patch := range pset.Patches.IterAll() {
comment := cleanSubject(patch.Comment)
if found := notdone.FindByCommitHash(patch.CommitHash); found != nil {
log.Info("duplicate notdone", patch.RepoNamespace, "patch:", patch.NewHash, "commithash:", patch.CommitHash, comment)
continue
}
repo := me.forge.FindByGoPath(patch.RepoNamespace)
if repo == nil {
log.Info("could not find repo", patch.RepoNamespace)
if full {
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
}
continue
}
if patch.NewHash != "na" {
log.Info("already applied patch", patch.RepoNamespace, ": newhash:", patch.NewHash, "commithash:", patch.CommitHash, comment)
continue
}
os.Chdir(repo.GetFullPath())
newhash, err := findCommitByHash(patch.StartHash, comment)
if err != nil {
// this patch has not been applied yet
log.Info("patch: not found hash:", patch.RepoNamespace, patch.CommitHash, comment, err)
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
continue
}
newhash, err = findCommitBySubject(comment)
if err == nil {
patch.NewHash = newhash
log.Info("patch: found hash:", patch.RepoNamespace, "commit patch", patch.CommitHash, "new hash", newhash, "start hash", patch.StartHash, comment)
continue
}
// this patch has not been applied yet
log.Info("patch: not found hash:", patch.RepoNamespace, patch.CommitHash, comment, newhash, err)
notdone.AppendByCommitHash(patch) // double check to ensure the commit hash isn't added twice
}
}

View File

@ -1,42 +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 (
"go.wit.com/lib/gadgets"
)
// Publish Window
func makePublishWindow() *gadgets.GenericWindow {
pubWin := gadgets.NewGenericWindow("publish code", "tasks for merging, versioning and publishing code")
grid := pubWin.Group.RawGrid()
grid.NewButton("merge all patches to master", func() {
/*
pubWin.Disable()
defer pubWin.Enable()
if err := doAllCheckoutDevel(); err != nil {
log.Info("checkout error:", err)
} else {
log.Info("checkout was ok")
}
mergeUserToDevel(true)
if err := doAllCheckoutMaster(); err != nil {
log.Info("checkout error:", err)
} else {
log.Info("checkout was ok")
}
mergeDevelToMaster(true)
*/
})
return pubWin
}

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()
@ -218,7 +218,6 @@ func makeHackModeWindow(stdwin *stdReposTableWin) {
group2 := stdwin.win.Top.NewGroup("Merge") group2 := stdwin.win.Top.NewGroup("Merge")
grid = group2.RawGrid() grid = group2.RawGrid()
/*
grid.NewButton("merge to devel", func() { grid.NewButton("merge to devel", func() {
stdwin.win.Disable() stdwin.win.Disable()
defer stdwin.win.Enable() defer stdwin.win.Enable()
@ -253,11 +252,10 @@ func makeHackModeWindow(stdwin *stdReposTableWin) {
mergeDevelToMaster(true) mergeDevelToMaster(true)
}) })
*/
grid.NewButton("show dirty repos on win.Bottom", func() { grid.NewButton("show dirty repos on win.Bottom", func() {
log.Info("try to show dirty repos on bottom") log.Info("try to show dirty repos on bottom")
found := me.forge.FindDirty() found := findDirty()
stdwin.doReposTable(found) stdwin.doReposTable(found)
}) })
@ -281,7 +279,7 @@ func develBehindMasterProblem() *gitpb.Repos {
if repo.GetDevelVersion() == repo.GetMasterVersion() { if repo.GetDevelVersion() == repo.GetMasterVersion() {
continue continue
} }
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
return found return found
@ -294,7 +292,7 @@ func remoteUserBranchProblem() *gitpb.Repos {
repo := all.Next() repo := all.Next()
username := repo.GetUserBranchName() username := repo.GetUserBranchName()
if repo.IsBranchRemote(username) { if repo.IsBranchRemote(username) {
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
} }
@ -316,13 +314,13 @@ func develRemoteProblem() *gitpb.Repos {
// log.Info(lhash, rhash, repo.GetGoPath()) // log.Info(lhash, rhash, repo.GetGoPath())
if lhash == "" || rhash == "" { if lhash == "" || rhash == "" {
// something is wrong if either of these are blank // something is wrong if either of these are blank
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
continue continue
} }
if lhash == rhash { if lhash == rhash {
continue continue
} }
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
return found return found
@ -343,13 +341,13 @@ func masterRemoteProblem() *gitpb.Repos {
// log.Info(lhash, rhash, repo.GetGoPath()) // log.Info(lhash, rhash, repo.GetGoPath())
if lhash == "" || rhash == "" { if lhash == "" || rhash == "" {
// something is wrong if either of these are blank // something is wrong if either of these are blank
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
continue continue
} }
if lhash == rhash { if lhash == rhash {
continue continue
} }
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
return found return found

View File

@ -53,7 +53,7 @@ func makeReposWinNew() *gadgets.GenericWindow {
t.Delete() t.Delete()
t = nil t = nil
} }
found := me.forge.FindDirty() found := findDirty()
// display the protobuf // display the protobuf
t = addWindowPB(insertWin, found) t = addWindowPB(insertWin, found)
@ -93,7 +93,7 @@ func makeReposWinNew() *gadgets.GenericWindow {
if !me.forge.Config.IsFavorite(repo.GetGoPath()) { if !me.forge.Config.IsFavorite(repo.GetGoPath()) {
continue continue
} }
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
// make the window for the first time // make the window for the first time
@ -117,7 +117,7 @@ func makeReposWinNew() *gadgets.GenericWindow {
if me.forge.Config.IsReadOnly(repo.GetGoPath()) { if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue continue
} }
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
// make the window for the first time // make the window for the first time
@ -138,7 +138,7 @@ func makeReposWinNew() *gadgets.GenericWindow {
all := me.forge.Repos.SortByFullPath() all := me.forge.Repos.SortByFullPath()
for all.Scan() { for all.Scan() {
repo := all.Next() repo := all.Next()
found.AppendByFullPath(repo) found.AppendByGoPath(repo)
} }
// display the protobuf // display the protobuf

View File

@ -22,7 +22,7 @@ type repoPatchWindow struct {
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)
@ -117,7 +117,7 @@ func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Set) {
all := pset.Patches.SortByFilename() all := pset.Patches.SortByFilename()
for all.Scan() { for all.Scan() {
p := all.Next() p := all.Next()
s := p.Namespace s := p.RepoNamespace
repo := me.forge.FindByGoPath(s) repo := me.forge.FindByGoPath(s)
if repo == nil { if repo == nil {
log.Info("COULD NOT FIND", s) log.Info("COULD NOT FIND", s)