Compare commits

..

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

48 changed files with 1203 additions and 4278 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

@ -1,52 +1,20 @@
VERSION = $(shell git describe --tags) VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d_%H%M) BUILDTIME = $(shell date +%Y.%m.%d)
# make build # go build using your git cloned repos (GO111MODULE=off) all: goimports vet install
# make install # go install using your git cloned repos (GO111MODULE=off) forge
# make gocui # try the ncurses gui plugin
# make andlabs # try the andlabs gui plugin (uses GTK)
default: install-verbose tag
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
build: goimports vet plugin verbose:
GO111MODULE=off go build \ GO111MODULE=off go build -v -x \
-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
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
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
go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
plugin:
rm -f resources/*.so
# -cp ../../toolkits/gocui/gocui.so resources/
GTK: clean install
forge --gui andlabs
GTK-verbose: clean install
forge --gui andlabs --gui-verbose
CUI: install
forge --gui gocui
CUI-verbose: install
forge --gui gocui --gui-verbose >/tmp/forge.log 2>&1
goimports: goimports:
reset reset
@ -54,33 +22,36 @@ goimports:
@# // to globally reset paths: @# // to globally reset paths:
@# // gofmt -w -r '"go.wit.com/gui/gadgets" -> "go.wit.com/lib/gadgets"' *.go @# // gofmt -w -r '"go.wit.com/gui/gadgets" -> "go.wit.com/lib/gadgets"' *.go
clean: gocui: install
-rm -f forge go.* forge --gui gocui
# -rm -f ~/go/src/repos.pb
go-mod-clean purge
identify-protobuf: redomod-all:
autogenpb --identify ~/go/src/repos.pb forge --do-RedoGoMod
devel: redomod-erase:
forge clean devel --force --verbose forge --do-RedoGoMod --do-erase
private: install
reset
forge --find-private
fix: install
reset
forge --fix
readonly: install
reset
forge --do-list --find-readonly
config: install
forge --config
scan: install
reset
forge --do-scan
pull: install pull: install
FORGE_URL="https://forge.grid.wit.com/" forge pull check forge --do-git-pull
# cloudflare blocks POST due to captcha checks / human detection? mine: install
# POST must be direct socket. probably for the best anyway forge --find-mine
submit:
FORGE_URL="https://forge.grid.wit.com/" forge patch submit "forge auto commit"
commit:
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

22
PATENTS
View File

@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
WIT.COM Inc. as part of the go.wit.com project.
WIT.COM Inc. hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation on go.wit.com, where such license applies only to those patent
claims, both currently owned or controlled by WIT.COM Inc. and acquired in
the future, licensable by WIT.COM Inc. that are necessarily infringed by this
implementation on go.wit.com. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation on go.wit.com or any code incorporated within this
implementation on go.wit.com constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation on go.wit.com
shall terminate as of the date such litigation is filed.

View File

@ -1,79 +0,0 @@
# forge
forge is a GUI front end for 'git' designed with the
intent of simplifying federated git development.
## Install:
* go install go.wit.com/apps/forge@latest
## Theory
* Software engineering is the art of making things work.
* Release engineering is the art of making things perfect.
## Rules
1) forge is only a GUI and wrapper around 'git'
2) forge _only_ os.Exec()'s git. Anything forge does
can be done on the command line using 'git' directly
3) forge's default behavior is to use 3 branches:
a) The git upstream master/main branch
b) A "devel" branch that is published
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
This can be used to maintain git repositories
expiremental work on federated git
* Scans directories looking for git repositories
* The default behavior is to use 3 branches. 'master or main', 'devel', '<username>'
* shows tags and dirty state
* uses a GUI or the console(console display needs work)
* always wrap around 'git' -- it basically just types 'git' commands really fast
## building from sources
```
# note as of Feb 2025. Forge has an option in forge to build itself.
# The instructions below are out of date, but provide the general idea.
# accurate instructions are in the forge code itself
go install go.wit.com/apps/go-clone@latest # this tool makes it easier to 'git clone' repos and recursively 'git clone' the dependancies
go install go.wit.com/apps/autogenpb@latest # this tool will generate the protobuf *pb.go files (also Marshal(), Sort(), etc.)
go-clone go.wit.com/apps/forge # this will 'git clone' about 20 repos into ~/go/src (or where your go.work file is)
cd go.wit.com/lib/protobuf/forgepb
make # autogenpb will make .pb.go, marshal.pb.go and sort.pb.go files
cd go.wit.com/lib/protobuf/gitpb
make # autogenpb will make .pb.go, marshal.pb.go and sort.pb.go files
cd go.wit.com/apps/forge
make # this runs GO111MODULE=off go build insuring that your using only your git sources
```
## Debian packages:
Instructions are on https://mirrors.wit.com/
## possible 'git bug' integration ideas:
```
git pull origin +refs/bugs/\*:refs/bugs/\*
git pull origin +refs/identities/\*:refs/identities/\*
# remove the caches
rm -rf .git/git-bug
# rebuild the cache with any command
git bug user
```

View File

@ -1,48 +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
/*
// saves the patches in ~/.config/forge/currentpatches/
func savePatchset(pset *forgepb.Patchset) error {
log.Info("savePatches() NAME", pset.Name)
log.Info("savePatches() COMMENT", pset.Comment)
log.Info("savePatches() GIT_AUTHOR_NAME", pset.GetGitAuthorName())
log.Info("savePatches() GIT_AUTHOR_EMAIL", pset.GetGitAuthorEmail())
log.Info("savePatches() Branch Name", pset.GetStartBranchName())
log.Info("savePatches() Start Hash", pset.GetStartBranchHash())
}
*/
/*
// From 18ee541f89be2e9f9a91c54873da87885e8ffdf5 Mon Sep 17 00:00:00 2001
// From: Jeff Carr <jcarr@wit.com>
// Date: Sun, 5 Jan 2025 01:18:47 -0600
// Subject: [PATCH] 'forge dirty' will find and list only dirty repos
// list patches in jcarr but not in devel
// git log --format="%H %Subject" jcarr --not devel
func countCurrentPatches(repo *gitpb.Repo) int {
cmd := []string{"git", "log", "--format=\"%H %s\"", "--no-merges", "jcarr", "--not", "devel"}
result := repo.Run(cmd)
return len(result.Stdout)
}
func doRegister(newurl string) error {
var url string
url = me.urlbase + "/register?url=" + newurl
body, err := me.forge.HttpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
test := strings.TrimSpace(string(body))
for _, line := range strings.Split(test, "\n") {
line = strings.TrimSpace(line)
log.Info("server returned:", line)
}
return nil
}
*/

216
argv.go
View File

@ -1,205 +1,47 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import (
"fmt"
"os"
"go.wit.com/lib/gui/prep"
)
/* /*
this parses the command line arguements using alex flint's go-arg this parses the command line arguements
*/ */
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.'"` Config bool `arg:"--config" help:"work from your .config/forge/ configuration"`
Checkout *CheckoutCmd `arg:"subcommand:checkout" help:"switch branches using 'git checkout'"` FindAll bool `arg:"--find-all" help:"select every repo"`
Clean *CleanCmd `arg:"subcommand:clean" help:"start over at the beginning"` FindReadOnly bool `arg:"--find-readonly" help:"include read-only repos"`
Commit *CommitCmd `arg:"subcommand:commit" help:"'git commit' but errors out if on wrong branch"` FindMine bool `arg:"--find-mine" help:"download private and writeable repos"`
Config *ConfigCmd `arg:"subcommand:config" help:"show your .config/forge/ settings"` FindFavorites bool `arg:"--find-favorites" help:"download repos marked as favorites"`
Dirty *DirtyCmd `arg:"subcommand:dirty" help:"show dirty git repos"` FindPrivate bool `arg:"--find-private" help:"list private repos in .config/forge/"`
GitFetch *FindCmd `arg:"subcommand:fetch" help:"run 'git fetch master'"` DoList bool `arg:"--do-list" help:"list found repos"`
Gui *EmptyCmd `arg:"subcommand:gui" help:"open the gui"` DoScan bool `arg:"--do-scan" help:"rescan your repos"`
List *FindCmd `arg:"subcommand:list" help:"print a table of the current repos"` DoClone bool `arg:"--do-clone" help:"go-clone things you are missing"`
Merge *MergeCmd `arg:"subcommand:merge" help:"merge branches"` DoForce bool `arg:"--do-force" help:"force redo go-clone"`
Normal *NormalCmd `arg:"subcommand:normal" help:"set every repo to the default state for software development"` DoGitPull bool `arg:"--do-git-pull" help:"run 'git pull' on all your repos"`
Patch *PatchCmd `arg:"subcommand:patch" help:"make patchsets"` DoBuild bool `arg:"--do-build" default:"true" help:"also try to build it"`
Pull *PullCmd `arg:"subcommand:pull" help:"run 'git pull'"` DoInstall bool `arg:"--do-install" help:"try to install every binary package"`
Tag *TagCmd `arg:"subcommand:tag" help:"manage git tags"` DoRedoGoMod bool `arg:"--do-RedoGoMod" help:"remake all the go.sum and go.mod files"`
URL string `arg:"--connect" help:"forge url"` DoErase bool `arg:"--do-EraseGoMod" help:"erase the go.mod and go.sum files"`
All bool `arg:"--all" help:"git commit --all"` DoGui bool `arg:"--do-gui" help:"test the gui"`
Build string `arg:"--build" help:"build a repo"` DryRun bool `arg:"--dry-run" help:"show what would be run"`
Install string `arg:"--install" help:"install a repo"` Fix bool `arg:"--fix" help:"fix config, save config & exit"`
BuildForge bool `arg:"--forge-rebuild" help:"download and rebuild forge"`
Force bool `arg:"--force" help:"try to strong arm things"`
Verbose bool `arg:"--verbose" help:"show more output"`
}
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 {
Submit bool `arg:"--submit" default:"true" help:"submit the patches to forge"`
}
type testCmd string
type CleanCmd struct {
Verify *EmptyCmd `arg:"subcommand:verify" help:"rescan repo"`
Repo string `arg:"--repo" help:"which repo to look at"`
}
type CleanDevelCmd struct {
Force bool `arg:"--force" help:"try to strong arm things"`
}
type PatchCmd struct {
Check *EmptyCmd `arg:"subcommand:check" help:"check the state of the patches"`
List *EmptyCmd `arg:"subcommand:list" help:"your downloaded patchsets"`
Get *EmptyCmd `arg:"subcommand:get" help:"get the new patchsets"`
Show *EmptyCmd `arg:"subcommand:show" help:"your pending commits to your code"`
Submit *SubmitCmd `arg:"subcommand: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 {
Check *EmptyCmd `arg:"subcommand:check" help:"check repo versions"`
Dirty *EmptyCmd `arg:"subcommand:dirty" help:"only check dirty repos"`
Patches *EmptyCmd `arg:"subcommand:patches" help:"only check repos with patches"`
}
type TagCmd struct {
List *EmptyCmd `arg:"subcommand:list" help:"list the tags"`
Clean *EmptyCmd `arg:"subcommand:clean" help:"clean out old and duplicate tags"`
Delete string `arg:"--delete" help:"delete a tag"`
}
type ConfigAddCmd struct {
Path string `arg:"--path" help:"absolute path of the git repo"`
GoPath string `arg:"--gopath" help:"GO path of the git repo"`
Directory bool `arg:"--directory" default:"false" help:"repo is a directory to match against"`
ReadOnly bool `arg:"--readonly" default:"false" help:"repo is readonly"`
Writable bool `arg:"--writable" default:"false" help:"repo is writable"`
Favorite bool `arg:"--favorite" default:"false" help:"forge will always go-clone or git clone this"`
Private bool `arg:"--private" default:"false" help:"repo can not be published"`
Interesting bool `arg:"--interesting" default:"false" help:"something you decided was cool"`
DebName string `arg:"--debname" help:"the name of the debian package (or rpm, etc)"`
Master string `arg:"--master" help:"the git 'master' or 'main' branch name"`
Devel string `arg:"--devel" help:"the git devel branch name"`
User string `arg:"--user" help:"the git user branch name"`
}
type ConfigCmd struct {
Add *ConfigAddCmd `arg:"subcommand:add" help:"add a config setting"`
Fix *EmptyCmd `arg:"subcommand:fix" help:"fix .config/forge/ and/or repos.pb protobuf file"`
List *EmptyCmd `arg:"subcommand:list" help:"list your config settings"`
Delete string `arg:"--delete" help:"delete this repo"`
Register string `arg:"--register" help:"register your git URL (foo.com/mystuff) or (github.com/foo/bar)"`
}
type CheckoutCmd struct {
User *FindCmd `arg:"subcommand:user" help:"git checkout user"`
Devel *FindCmd `arg:"subcommand:devel" help:"git checkout devel"`
Master *FindCmd `arg:"subcommand:master" help:"git checkout master"`
}
type MergeCmd struct {
Devel *FindCmd `arg:"subcommand:devel" help:"merge user to devel"`
Master *FindCmd `arg:"subcommand:master" help:"merge devel to master"`
Publish *EmptyCmd `arg:"subcommand:publish" help:"increment versions and publish master branch"`
}
type DirtyCmd struct {
}
type FindCmd struct {
All bool `arg:"--all" help:"select every repo (the default)"`
Mine bool `arg:"--mine" help:"your repos as defined in the forge config"`
Favorites bool `arg:"--favorites" help:"your repos configured as favorites"`
Private bool `arg:"--private" help:"your private repos from your .config/forge/"`
Dirty bool `arg:"--dirty" help:"only use dirty git repos"`
User bool `arg:"--user" help:"show repos on the user branch"`
Full bool `arg:"--full" help:"show full repo names"`
// ReadOnly bool `arg:"--readonly" help:"include read-only repos"`
} }
func (args) Version() string { func (args) Version() string {
return ARGNAME + " " + VERSION + " Built on " + BUILDTIME return "forge " + VERSION + " Built on " + BUILDTIME
} }
func (a args) Description() string { func (a args) Description() string {
return ` return `
forge -- a tool to manage lots of git repos. forge includes a GUI and TUI. forge -- in the spirit of things like sourceforge
forge only executes the 'git' command. Everything it does, you can run by hand with 'git'. Examples:
forge --config # shows your forge config (~/.config/forge/)
forge --mine # find your private and writable repos
forge --favorites # find configured as favorites
forge --git-pull # run 'git pull' in every repo
forge --build --dry-run # build every binary package (but just show what would run)
forge --mine --clone # clone every package you have in your config file
` `
} }
// handles shell autocomplete
func DoAutoComplete(pb *prep.Auto) {
switch pb.Cmd {
case "checkout":
pb.Autocomplete2("devel master user")
case "clean":
pb.Autocomplete2("")
case "commit":
pb.Autocomplete2("--all")
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 {
pb.Autocomplete2("list checkout clean commit dirty normal merge tag")
}
}
os.Exit(0)
}
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)
}

8
build
View File

@ -1,8 +0,0 @@
#!/bin/bash -x
# include forgeConfig
mkdir -p files/usr/bin/
cp ../utils/wit-utils/forgeConfig/forgeConfig files/usr/bin/
mkdir -p files/usr/share/bash-completion/completions/
forge --bash > files/usr/share/bash-completion/completions/forge

161
cobol.go Normal file
View File

@ -0,0 +1,161 @@
package main
import (
"fmt"
"os"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// ah yes, COBOL. what a throwback. for those that know
// then you know exactly what is in this file. For those that don't, here it is:
// All this does is output human readable text formatted to be viewable on
// a console with a fixed with font. AKA: a typerwriter. Which is exactly
// what COBOL did in the 1970's (60s? notsure) And the 80s.
// So, you want to dump out stuff on the console. Let's see. Something like
/*
forge --favorites
go.wit.com/apps/myapp v0.2.0 (installed)
go.wit.com/lib/somethingfun v0.0.7 (not downloaded)
*/
// anyway, you get the idea. This is also called COBOL because it does on
// thing and truncates every line output to the columns you see with stty -a
// my monitor is huge, so it's not going to work at 80x24. 160x48 is better
// actually, I'd predict some of these will probably end up 240 wide
// long live good eyesight and 4K monitors!
func doCobol() {
log.DaemonMode(true)
log.Info(standardStart5("gopath", "cur name", "master", "user", "repo type"))
repos := me.found.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
verifyPrint(repo)
}
}
func standardStart5(arg1, arg2, arg3, arg4, arg5 string) string {
len1 := 40
len2 := 12
len3 := 12
len4 := 16
len5 := 8
var s string
if len(arg1) > len1 {
arg1 = arg1[:len1]
}
s = "%-" + fmt.Sprintf("%d", len1) + "s "
if len(arg2) > len2 {
arg2 = arg2[:len2]
}
s += "%-" + fmt.Sprintf("%d", len2) + "s "
if len(arg3) > len3 {
arg3 = arg3[:len3]
}
s += "%-" + fmt.Sprintf("%d", len3) + "s "
if len(arg4) > len4 {
arg4 = arg4[:len4]
}
s += "%-" + fmt.Sprintf("%d", len4) + "s "
if len(arg5) > len5 {
arg5 = arg5[:len5]
}
s += "%-" + fmt.Sprintf("%d", len5) + "s"
return fmt.Sprintf(s, arg1, arg2, arg3, arg4, arg5)
}
func verifyPrint(repo *gitpb.Repo) {
var end string
if repo.CheckDirty() {
end += "(dirty) "
}
s := make(map[string]string)
if !verify(repo, s) {
log.Info("going to delete", repo.GoPath)
if argv.Fix {
me.forge.Repos.DeleteByGoPath(repo.GetGoPath())
me.forge.Repos.ConfigSave()
} else {
log.Info("need argv --real to delete", repo.GoPath)
os.Exit(0)
}
}
if me.forge.Config.IsReadOnly(repo.GoPath) {
if repo.ReadOnly {
} else {
log.Info("readonly flag on repo is wrong", repo.GoPath)
}
}
var mhort string = s["mver"]
var uhort string = s["uver"]
var cname string = s["cname"]
// start := fmt.Sprintf("%-40s %-12s %-12s %-12s %-8s", s["gopath"], cname, mhort, uhort, s["rtype"])
start := standardStart5(s["gopath"], cname, mhort, uhort, s["rtype"])
if me.forge.Config.IsReadOnly(repo.GoPath) {
end += "(readonly) "
}
log.Info(start, end)
}
func verify(repo *gitpb.Repo, s map[string]string) bool {
if !repo.IsValid() {
// return false
}
s["gopath"] = repo.GetGoPath()
s["rtype"] = repo.RepoType()
s["mname"] = repo.GetMasterBranchName()
if s["mname"] == "" {
log.Info("verify() no master branch name", repo.GoPath)
s["mver"] = repo.GetMasterVersion()
return false
}
// only verify the master branch name with read-only repos
if me.forge.Config.IsReadOnly(repo.GoPath) {
s["mver"] = repo.GetMasterVersion()
return true
}
s["dname"] = repo.GetDevelBranchName()
if s["dname"] == "" {
log.Info("verify() no devel branch name", repo.GoPath)
return false
}
s["uname"] = repo.GetUserBranchName()
if s["uname"] == "" {
log.Info("verify() no user branch name", repo.GoPath)
return false
}
s["cname"] = repo.GetCurrentBranchName()
s["mver"] = repo.GetMasterVersion()
if s["mver"] == "" {
log.Info("verify() no master branch name", repo.GoPath, repo.GetMasterBranchName())
return false
}
s["dver"] = repo.GetDevelVersion()
if s["dver"] == "" {
log.Info("verify() no devel branch name", repo.GoPath, repo.GetDevelBranchName())
return false
}
s["uver"] = repo.GetUserVersion()
if s["uver"] == "" {
log.Info("verify() no user branch name", repo.GoPath, repo.GetUserBranchName())
return false
}
s["cver"] = repo.GetCurrentBranchVersion()
s["url"] = repo.URL
return true
}

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,8 +0,0 @@
Source: forge
Build-Depends: golang, protoc-gen-go, autogenpb, go-mod-clean
Package: forge
Maintainer: Jeff Carr <jcarr@wit.com>
Architecture: amd64
Depends: go-gui-toolkits, autogenpb, go-mod-clean, go-clone
Recommends: go-clone
Description: 'forge' an attempt at federated 'git'

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

@ -1,20 +0,0 @@
package main
/*
enables GUI options and the debugger in your application
*/
import (
"go.wit.com/lib/debugger"
"go.wit.com/log"
)
func init() {
if debugger.ArgDebug() {
log.Info("cmd line --debugger == true")
go func() {
log.Sleep(2)
debugger.DebugWindow()
}()
}
}

View File

@ -1,47 +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 (
"fmt"
"go.wit.com/log"
)
func doBuild() error {
v := []string{}
if argv.Verbose {
v = []string{"-v", "-x"}
}
gopath := argv.Build
repo := me.forge.FindByGoPath(gopath)
if repo == nil {
return fmt.Errorf("rep not found: %s", gopath)
}
if err := me.forge.Build(repo, v); err != nil {
log.Warn("Build failed:", repo.GetGoPath(), err)
return err
}
return nil
}
func doInstall() error {
v := []string{}
if argv.Verbose {
v = []string{"-v", "-x"}
}
gopath := argv.Install
repo := me.forge.FindByGoPath(gopath)
if repo == nil {
return fmt.Errorf("rep not found: %s", gopath)
}
if err := me.forge.Install(repo, v); err != nil {
log.Warn("Install failed", repo.GetGoPath(), err)
return err
}
return nil
}

View File

@ -1,47 +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 (
"fmt"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
// trys to figure out if there is still something to update
func doCheckout() error {
if argv.Checkout.User != nil {
start := time.Now()
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)
}
okExit("")
}
if argv.Checkout.Devel != nil {
// setForgeMode(forgepb.ForgeMode_DEVEL)
if err := me.forge.DoAllCheckoutDevelNew(argv.Force); err != nil {
badExit(err)
}
okExit("")
}
if argv.Checkout.Master != nil {
setForgeMode(forgepb.ForgeMode_MASTER) // disable "normal" mode if set
if err := me.forge.DoAllCheckoutMaster(); err != nil {
badExit(err)
}
okExit("")
}
badExit(fmt.Errorf("did not specify what branch to checkout"))
return nil
}

View File

@ -1,272 +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 (
"fmt"
"path/filepath"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func checkRemoteBranches(repo *gitpb.Repo) error {
if err := repo.ReloadCheck(); err != nil {
log.Info("need to reload", repo.FullPath)
}
if repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName()) {
} else {
return log.Errorf("remote devel is out of sync with local: todo: git pull or git fetch")
}
if repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName()) {
} else {
return log.Errorf("remote master is out of sync with local: todo: git pull or git fetch")
}
return nil
}
// reverts all repos back to the original master branches
// automatically deletes local devel and user branches
func doClean() error {
setForgeMode(forgepb.ForgeMode_CLEAN)
if argv.Clean.Verify != nil {
stats := me.forge.RillRepos(checkRemoteBranches)
for path, stat := range stats {
if stat.Err == nil {
continue
}
dur := stat.End.Sub(stat.Start)
if dur > time.Second {
log.Infof("%s checkRemoteBranches() took a long time (%s) (err=%v)\n", path, shell.FormatDuration(dur), stat.Err)
}
}
// log.Infof("%-60s, %-60s %v %s\n", stat.Start, stat.End.String(), dur, path)
// log.Infof("%-30v %s %v\n", dur, path, stat.Err)
return nil
}
// fix this to work, then delete all the other options for "forge clean'
if err := me.forge.DoAllCheckoutMaster(); err != nil {
// badExit(err)
}
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
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)
}
// 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
}
}
// 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
}
/*
func doesLocalBranchExist(repo *gitpb.Repo, branch string) bool {
return repo.Exists(filepath.Join(".git/refs/heads", branch))
}
*/
func doRepoCleanDevel(repo *gitpb.Repo) error {
if !repo.IsLocalBranch(repo.GetDevelBranchName()) {
// there is no local branch named 'devel'
return nil
}
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
return log.Errorf("%s not on master branch:", repo.GetFullPath())
}
if repo.IsDirty() {
return log.Errorf("%s is dirty:", repo.GetFullPath())
}
if err := justDeleteTheDevelBranchAlready(repo); err != nil {
log.Info("justDeleteTheDevel() err", repo.GetGoPath(), err)
configSave = true
return err
}
return nil
}
// removes all local user branches
func doRepoCleanUser(repo *gitpb.Repo) error {
if repo.IsDirty() {
return nil
}
bruser := repo.GetUserBranchName()
brdevel := repo.GetDevelBranchName()
if repo.IsBranchRemote(bruser) {
log.Info("forge is designed to always have local only user branches", bruser)
return fmt.Errorf("forge is designed to always have local only user branches")
}
if !repo.IsLocalBranch(bruser) {
// there is no local user branch
return nil
}
// will you loose work if you delete your user branch?
// 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()
// 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 {
cmd := []string{"git", "branch", "-D", bruser}
// log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
}
return fmt.Errorf("%s branch has unique commits", bruser)
}
// if you call this, there is no going back. no checks anymore. nothing
// it deletes the 'devel' branch. git branch -D "devel". END OF STORY
func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
branch := repo.GetDevelBranchName()
remote := filepath.Join("origin", branch)
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
if repo.IsDevelRemote() {
// just make sure the remote & local branches are the same
return repo.DeleteLocalDevelBranch()
}
}
// check against remote if it exists
if repo.IsDevelRemote() {
b1 := repo.CountDiffObjects(branch, remote) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
cmd := []string{"git", "push"}
log.Info("DEVEL LOCAL NEEDS GIT PUSH TO REMOTE", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
// remote doesn't exist, check against master
master := repo.GetMasterBranchName()
b1 := repo.CountDiffObjects(branch, "refs/heads/"+master) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
cmd := []string{"git", "merge something somehow"}
log.Info("devel local, remote and master branches are wrong", repo.GetGoPath(), cmd, b1)
// _, err := repo.RunVerbose(cmd)
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

@ -1,99 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"os"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doCommit() error {
if argv.All {
found := me.forge.CheckDirty()
var newpatches bool
for repo := range found.IterAll() {
log.Info("do a commit on repo", repo.GetGoPath())
if err := doCommitRepo(repo); err != nil {
badExit(err)
}
newpatches = true
repo.CheckDirty()
}
if newpatches {
config.SetChanged("repos", true)
return doPatchSubmit()
}
okExit("")
}
repo := findCurrentPwdRepoOrDie()
if !repo.CheckDirty() {
okExit(log.Sprintf("this repo %s is not dirty.\n\n--all # commit all changes in all repos", repo.GetFullPath()))
} else {
log.Info("repo is dirty", repo.GetFullPath())
}
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
found := new(gitpb.Repos)
found.Append(repo)
me.forge.PrintHumanTable(found)
log.Info("")
log.Info("wrong branch. Can not commit on", repo.GetCurrentBranchName())
log.Info("")
okExit("")
}
os.Setenv("LESS", "-XR")
if err := shell.Exec([]string{"git", "diff"}); err != nil {
badExit(err)
}
if argv.All {
if err := shell.ExecCheck([]string{"git", "add", "--all"}); err != nil {
badExit(err)
}
}
if err := shell.ExecCheck([]string{"git", "commit", "--all"}); err != nil {
badExit(err)
}
return doPatchSubmit()
}
func doCommitRepo(repo *gitpb.Repo) error {
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
found := new(gitpb.Repos)
found.Append(repo)
me.forge.PrintHumanTable(found)
log.Info("")
log.Info("wrong branch. Can not commit on", repo.GetCurrentBranchName())
log.Info("")
return nil
}
os.Chdir(repo.GetFullPath())
os.Setenv("LESS", "-XR")
if err := shell.Exec([]string{"git", "diff"}); err != nil {
return err
}
if argv.All {
if err := shell.ExecCheck([]string{"git", "add", "--all"}); err != nil {
return err
}
}
if err := shell.ExecCheck([]string{"git", "commit", "--all"}); err != nil {
shell.ExecCheck([]string{"git", "reset"})
return err
}
log.Info("git commit ok. forge done")
return nil
}

49
doCommon.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doScan() {
me.forge.ScanGoSrc()
}
func doGitPull() {
allerr := me.found.RillGitPull(40, 5)
all := me.found.SortByGoPath()
for all.Scan() {
repo := all.Next()
result := allerr[repo]
if result.Error == gitpb.ErrorGitPullOnDirty {
log.Info("skip git pull. repo is dirty", repo.GoPath)
continue
}
if result.Error == gitpb.ErrorGitPullOnLocal {
log.Info("skip git pull. local branch ", repo.GoPath)
continue
}
if result.Exit == 0 {
continue
}
log.Info("git pull error:", repo.GoPath, result.Error)
log.Info("git pull error:", repo.GoPath, result.Stdout)
}
}
func doFix() {
all := me.found.SortByGoPath()
for all.Scan() {
repo := all.Next()
if !repo.IsValid() {
log.Printf("%10s %-50s", "old?\n", repo.GetGoPath())
continue
}
log.Printf("running on: %-50s\n", repo.GetGoPath())
cmd := []string{"ls"}
repo.Run(cmd)
}
}

View File

@ -1,75 +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 (
"os"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func doConfig() {
if argv.Config.Delete != "" {
me.forge.DeleteByGoPath(argv.Config.Delete)
me.forge.SetConfigSave(true)
okExit("")
}
if argv.Config.Fix != nil {
log.Info("todo")
okExit("")
}
/*
if argv.Config.Register != "" {
if err := doRegister(argv.Config.Register); err == nil {
okExit("attempting to register " + argv.Config.Register)
} else {
badExit(err)
}
}
*/
// try to add, then save config and exit
if argv.Config.Add != nil {
log.Info("going to add a new repo", argv.Config.Add.GoPath)
deleteGoPath(me.forge, argv.Config.Add.GoPath)
new1 := forgepb.ForgeConfig{
GoPath: argv.Config.Add.GoPath,
Writable: argv.Config.Add.Writable,
ReadOnly: argv.Config.Add.ReadOnly,
Private: argv.Config.Add.Private,
Directory: argv.Config.Add.Directory,
Favorite: argv.Config.Add.Favorite,
Interesting: argv.Config.Add.Interesting,
MasterBranchName: argv.Config.Add.Master,
DevelBranchName: argv.Config.Add.Devel,
UserBranchName: argv.Config.Add.User,
}
me.forge.Config.Append(&new1)
me.forge.ConfigSave()
os.Exit(0)
}
log.Info("config.PathLock =", me.forge.Config.PathLock)
me.forge.ConfigPrintTable()
okExit("")
}
func deleteGoPath(f *forgepb.Forge, gopath string) bool {
var deleted bool = false
for {
if f.Config.DeleteByGoPath(gopath) {
log.Info("deleted ok", gopath)
deleted = true
} else {
log.Info("did not delete", gopath)
break
}
}
return deleted
}

41
doDelete.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func Delete(repo *gitpb.Repo, s map[string]string) bool {
if repo.Published == nil {
log.Info("published is nil", repo.Published)
} else {
log.Info("published len", repo.Published.Len())
}
// add a new one here
newr := gitpb.Repo{
FullPath: repo.FullPath,
GoPath: repo.GoPath,
URL: repo.URL,
Tags: repo.Tags,
LastPull: repo.LastPull,
MasterBranchName: repo.MasterBranchName,
DevelBranchName: repo.DevelBranchName,
UserBranchName: repo.UserBranchName,
GoLibrary: repo.GoLibrary,
GoBinary: repo.GoBinary,
GoPrimitive: repo.GoPrimitive,
GoPlugin: repo.GoPlugin,
GoDeps: repo.GoDeps,
LastGoDep: repo.LastGoDep,
Dirty: repo.Dirty,
Published: repo.Published,
TargetVersion: repo.TargetVersion,
ReadOnly: repo.ReadOnly,
GoProtobuf: repo.GoProtobuf,
}
if argv.Fix {
me.forge.Repos.AppendUniqueGoPath(&newr)
}
return true
}

View File

@ -1,16 +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
func doDirty() {
found := me.forge.CheckDirty()
if found.Len() == 0 {
return
}
if argv.Verbose {
me.forge.PrintHumanTableDirty(found)
} else {
me.forge.PrintHumanTable(found)
}
}

198
doFind.go
View File

@ -1,198 +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 (
"go.wit.com/lib/protobuf/gitpb"
)
// this populates a slice of protobuf records representing each git repo
// var found []*gitpb.Repo
//
// so, it makes a subset of repos that are then used performing actions on
//
// by default, it adds every repo
func doFind() *gitpb.Repos {
if argv.List == nil {
return findAll()
}
if argv.List.Mine {
return findMine()
}
if argv.List.Dirty {
return me.forge.FindDirty()
}
return findAll()
}
func (f *FindCmd) findRepos() *gitpb.Repos {
if f == nil {
return findMine()
}
if f.All {
return findAll()
}
if f.Private {
return findPrivate()
}
if f.Mine {
return findMine()
}
if f.Favorites {
return findFavorites()
}
if f.Dirty {
return me.forge.FindDirty()
}
if f.User {
return findUser()
}
return findAll()
}
func findPrivate() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsPrivate(repo.GetGoPath()) {
found.AppendByFullPath(repo)
}
}
return found
}
// finds repos that are writable
func findMine() *gitpb.Repos {
found := gitpb.NewRepos()
// log.Printf("get mine %s\n", me.forge.GetGoSrc())
for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsWritable(repo.GetGoPath()) {
found.AppendByFullPath(repo)
}
}
return found
}
// finds repos the user has marked as favorites in the forge .config
func findFavorites() *gitpb.Repos {
found := gitpb.NewRepos()
// log.Printf("get favorites %s\n", me.forge.GetGoSrc())
for repo := range me.forge.Repos.IterByFullPath() {
if me.forge.Config.IsFavorite(repo.GetGoPath()) {
found.AppendByFullPath(repo)
}
}
return found
}
func findAll() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
found.AppendByFullPath(repo)
}
return found
}
func find50() *gitpb.Repos {
count := 0
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
found.AppendByFullPath(repo)
if count > 50 {
return found
}
count += 1
}
return found
}
func findUser() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
found.AppendByFullPath(repo)
}
}
return found
}
func findPublishable() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if repo.GetTargetVersion() == "" {
continue
}
found.AppendByFullPath(repo)
}
return found
}
func findReposWithPatches() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
if repo.GetTargetVersion() != "" {
// add everything that has a target version set
found.AppendByFullPath(repo)
continue
}
if repo.IsDirty() {
// always add dirty branches
found.AppendByFullPath(repo)
continue
}
if repo.GetUserVersion() == "" || repo.GetUserVersion() == "uerr" {
// skip anything without a user branch
continue
}
if repo.GetUserVersion() != repo.GetDevelVersion() {
found.AppendByFullPath(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
}
// ignore read-only repos for checks below here
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
// this is an old test to see if the current 'last tag' is accurate and should be removed
if repo.GetLastTag() != repo.GetMasterVersion() {
found.AppendByFullPath(repo)
repo.FindLastTag()
continue
}
}
return found
}

362
doGui.go
View File

@ -1,362 +1,40 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
// An app to submit patches for the 30 GO GUI repos // An app to submit patches for the 30 GO GUI repos
import ( import (
"fmt"
"os"
"time"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/gadgets" "go.wit.com/lib/gadgets"
"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"
) )
func doGui() { func doGui() {
win := gadgets.NewGenericWindow("Forge: A federated git development tool by WIT.COM", "Current Settings") me.myGui = gui.New()
win.Custom = func() { me.myGui.Default()
log.Warn("MAIN WINDOW CLOSE")
okExit("")
}
grid := win.Group.RawGrid() me.mainWindow = gadgets.RawBasicWindow("submit & test patchsets")
if me.forge.Config.GetPathLock() { me.mainWindow.Make()
me.goSrcPwd = gadgets.NewOneLiner(grid, "Working Directory") me.mainWindow.Show()
me.goSrcPwd.SetText(me.forge.Config.ReposDir) me.mainbox = me.mainWindow.Box()
} 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() // disable the interface while everything is scanned
me.Disable()
// use ENV GIT_AUTHOR vbox2 := me.mainbox.NewVerticalBox("BOX2")
me.gitAuthor = gadgets.NewOneLiner(grid, "Git Author") globalBuildOptions(vbox2)
grid.NextRow() me.summary = submitPatchesBox(vbox2)
if os.Getenv("GIT_AUTHOR_NAME") == "" { me.repos = makeRepoView()
me.gitAuthor.SetText("ENV GIT_AUTHOR_NAME is unset")
} else {
author := os.Getenv("GIT_AUTHOR_NAME")
author += " <" + os.Getenv("GIT_AUTHOR_EMAIL") + ">"
me.gitAuthor.SetText(author)
}
grid.NextRow() // processing is done. update the repo summary box
me.summary.Update()
gridM := win.Stack.RawGrid() me.Enable()
var insertWin *gadgets.GenericWindow // intermittently scans the status indefinitly
s := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len()) me.repos.View.Watchdog(func() {
me.reposWinB = gridM.NewButton(s, func() { log.Info("In main()")
// if the window exists, just toggle it open or closed // processing is done. update the repo summary box
if insertWin != nil { me.summary.Update()
insertWin.Toggle()
return
}
insertWin = makeReposWinNew()
})
var patchesWin *stdPatchTableWin
var patchButton *gui.Node
patchButton = gridM.NewButton("Your patches", func() {
if patchesWin != nil {
patchesWin.Toggle()
return
}
if !isPatchingSafe() {
patchButton.SetLabel("not safe yet")
return
}
// patchesWin = makePatchesWin(me.forge.Patchsets)
notdone := new(forgepb.Patches)
all := me.forge.Patchsets.All()
for all.Scan() {
pset := all.Next()
if pset.State == "DONE" {
// skip old patchsets
continue
}
AddAllPatches(notdone, pset, false)
// AddNotDonePatches(notdone, pset, false)
}
notdone.PrintTable()
patchesWin = makePatchesWin(notdone)
})
var pubWin *gadgets.GenericWindow
gridM.NewButton("Publish", func() {
if pubWin != nil {
pubWin.Toggle()
return
}
pubWin = makePublishWindow()
})
var oldWin *gadgets.GenericWindow
gridM.NewButton("old", func() {
if oldWin != nil {
oldWin.Toggle()
return
}
oldWin = makeOldStuff()
}) })
} }
// this is the magic that generates a window directly from the protocol buffer
func makeStandardReposGrid(pb *gitpb.Repos) *gitpb.ReposTable {
t := pb.NewTable("testDirty")
t.NewUuid()
sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string {
return r.GetNamespace()
})
// t.Custom = func() {
// log.Info("close grid?")
// }
sf.Custom = func(r *gitpb.Repo) {
log.Info("do button click on", r.GetNamespace())
}
t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time {
return repo.NewestTime()
})
t.AddMasterVersion()
t.AddDevelVersion()
t.AddUserVersion()
t.AddCurrentBranchName()
t.AddState()
return t
}
// this is the magic that generates a window directly from the protocol buffer
func makeStandardReposWindow(title string, pb *gitpb.Repos) (*gitpb.ReposTable, *gui.Node) {
win := gadgets.RawBasicWindow(title)
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)
t := makeStandardReposGrid(pb)
t.SetParent(box)
t.ShowTable()
return t, box
}
func findMergeToDevel() *gitpb.Repos {
found := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
// this sees if user has patches for devel. If it does, add it to found
if repo.CountDiffObjects(repo.GetUserBranchName(), repo.GetDevelBranchName()) > 0 {
found.AppendByFullPath(repo)
}
}
now := time.Now()
if found.Len() == 0 {
log.Info("nothing to merge with devel")
return found
}
// me.forge.PrintHumanTable(found)
// check for merges from devel
total, count, nope, _ := me.forge.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)))
return found
}
func findMergeToMaster() *gitpb.Repos {
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetNamespace()) {
continue
}
/*
if repo.IsDirty() {
continue
}
if repo.GetMasterVersion() != repo.GetDevelVersion() {
me.found.AppendByFullPath(repo)
continue
}
*/
// this sees if devel is behind master. IT SHOULD NOT BE
if repo.CountDiffObjects(repo.GetMasterBranchName(), repo.GetDevelBranchName()) == 0 {
// everything is normal
} else {
repo.State = "DEVEL < MASTER"
log.Info("SERIOUS ERROR. DEVEL BRANCH IS BEHIND MASTER", repo.GetNamespace())
}
// this sees if devel has patches for master. If it does, add it to me.found
if repo.CountDiffObjects(repo.GetDevelBranchName(), repo.GetMasterBranchName()) > 0 {
found.AppendByFullPath(repo)
}
}
now := time.Now()
if found.Len() == 0 {
log.Info("nothing to merge with master")
return found
}
me.forge.PrintHumanTable(found)
// check for merges from devel
total, count, nope, _ := me.forge.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)))
return found
}
func mergeDevelToMaster(doit bool) {
found := findMergeToMaster()
if !doit {
return
}
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
log.Info("repo:", repo.GetNamespace())
if result, err := repo.MergeToMaster(); err == nil {
log.Warn("THINGS SEEM OK", repo.GetFullPath())
for _, line := range result.Stdout {
log.Warn("stdout:", line)
}
for _, line := range result.Stderr {
log.Warn("stderr:", line)
}
} else {
log.Warn("THINGS FAILED ", repo.GetFullPath())
log.Warn("err", err)
if result == nil {
break
}
for _, line := range result.Stdout {
log.Warn("stdout:", line)
}
for _, line := range result.Stderr {
log.Warn("stderr:", line)
}
log.Warn("THINGS FAILED ", repo.GetFullPath())
break
}
me.forge.SetConfigSave(true)
// view.Update()
}
me.forge.ConfigSave()
}
func mergeUserToDevel(doit bool) {
found := findMergeToDevel()
if !doit {
return
}
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
bruser := repo.GetUserBranchName()
brdevel := repo.GetDevelBranchName()
if repo.GetUserVersion() == "uerr" {
// no user branch
return
}
log.Info("trying", bruser, repo.GetUserVersion())
b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero
if b1 == 0 {
// log.Info("User is already merged into Devel", repo.GetNamespace(), cmd)
return
}
log.Info("merging user into devel repo:", repo.GetNamespace())
if result, err := repo.MergeToDevel(); err == nil {
log.Warn("THINGS SEEM OK", repo.GetFullPath())
for _, line := range result.Stdout {
log.Warn("stdout:", line)
}
for _, line := range result.Stderr {
log.Warn("stderr:", line)
}
} else {
log.Warn("THINGS FAILED ", repo.GetFullPath())
log.Warn("err", err)
if result == nil {
break
}
for _, line := range result.Stdout {
log.Warn("stdout:", line)
}
for _, line := range result.Stderr {
log.Warn("stderr:", line)
}
break
}
me.forge.SetConfigSave(true)
// view.Update()
}
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

@ -1,261 +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 (
"path/filepath"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func doPatchInit() {
if me.forge.Patchsets != nil {
if me.forge.Patchsets.Len() == 0 {
// 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 {
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
}
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 {
psets := forgepb.NewSets()
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 {
var changed bool
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
// repos that have patches ready in them
found := findReposWithPatches()
if found.Len() == 0 {
log.Info("you currently have no repos with patches")
return false
} else {
me.forge.PrintHumanTable(found)
}
return true
}
// returns bad if patches can not be applied
// logic is not great here but it was a first pass
func dumpPatchset(pset *forgepb.Set) bool {
// don't even bother to continue if we already know it's broken
if pset.State == "BROKEN" {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS BAD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())
return false
} else {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS GOOD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())
}
var count int
var bad int
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
if IsValidPatch(p) {
// ok
} else {
pset.State = "BROKEN"
bad += 1
}
count += 1
}
log.Info("pset has", count, "total patches, ", bad, "bad patches")
if bad == 0 {
return true
}
return false
}
func IsValidPatch(p *forgepb.Patch) bool {
basepath, filename := filepath.Split(p.Filename)
repo := me.forge.FindByGoPath(basepath)
if argv.Verbose {
log.Info("start:", p.StartHash, "end:", p.CommitHash, "file:", basepath, filename, "devel version", repo.GetDevelVersion())
}
if repo == nil {
log.Info("can not apply patch! repo not found", basepath, filename)
return false
}
if repo.ActualDevelHash() != p.StartHash {
log.Info("can not apply patch! devel hash mismatch", basepath, filename)
return false
}
if repo.ActualDevelHash() == p.StartHash {
log.Info("local devel hash:", repo.ActualDevelHash(), "matches patch hash", p.StartHash, "and can be applied")
}
log.Info("start:", p.StartHash, "end:", p.CommitHash, "file:", basepath, filename, "devel version", repo.GetDevelVersion())
for _, line := range p.Files {
log.Info("\t", line)
}
return true
}

111
doPull.go
View File

@ -1,111 +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/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"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 {
if repo.IsDirty() {
// never do dirty repos
return nil
}
t, _ := repo.LastGitPull()
if time.Since(t) < time.Minute*10 && !argv.Force {
if argv.Verbose {
log.Info(repo.GetFullPath(), "git pulled too recently", shell.FormatDuration(time.Since(t)))
}
return nil
}
cur := repo.GetCurrentBranchName()
if !repo.IsBranchRemote(cur) {
if argv.Verbose {
log.Info(repo.GetFullPath(), "branch is not remote", cur)
}
return nil
}
var cmd []string
cmd = append(cmd, "git", "pull")
err := repo.RunVerbose(cmd)
if err != nil {
log.Info(repo.GetFullPath(), "git pull err:", err)
}
return nil
}
/*
// git fetch origin master:master
func rillFetchMaster(repo *gitpb.Repo) error {
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
// only fetch when branch is on the user branch
return nil
}
branch := repo.GetMasterBranchName()
cmd := []string{"git", "fetch", "origin", branch + ":" + branch}
err := repo.RunVerbose(cmd)
return err
}
func doGitFetch() {
me.forge.RillFuncError(rillFetchMaster)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
}
*/

35
doRedoGoMod.go Normal file
View File

@ -0,0 +1,35 @@
package main
// An app to submit patches for the 30 GO GUI repos
import (
"os"
"go.wit.com/log"
)
func doRedoGoMod() {
me.forge.RillRedoGoMod()
os.Exit(0)
repos := me.forge.Repos.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
if !repo.IsValid() {
log.Printf("%10s %-50s", "old?", repo.GetGoPath())
continue
}
log.Printf("running on: %-50s", repo.GetGoPath())
repo.RedoGoMod()
}
}
func doEraseGoMod() {
/*
var cmds [][]string
cmds = append(cmds, []string{"rm", "-f", "go.mod", "go.sum"})
errs := me.forge.RillCmds(me.packs, cmds)
foreach x, y := range errs {
log.Info("EraseGoMod() error", x.GoPath, y)
}
*/
}

View File

@ -1,79 +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
// trys to figure out if there is still something to update
/*
func doSync() error {
if argv.Pull.Sync.Clean != nil {
return doSyncClean()
}
if argv.Pull.Sync.User != nil {
return doSyncUser()
}
return fmt.Errorf("nothing to do")
}
func doSyncClean() error {
log.Printf("todo: fix this?")
return nil
}
func doSyncUser() error {
me.forge.ConfigRill(10, 20)
if allerr := me.forge.RillRepos(syncDevelBranch); len(allerr) != 0 {
log.Info("RillFunc() failed", allerr)
return fmt.Errorf("Rill doSyncUser() error count = %d", len(allerr))
} else {
log.Info("Rill syncDevelBranch() ok count =", len(allerr))
}
argv.Force = true
if err := doAllCheckoutUser(); err != nil {
return err
}
return nil
}
func syncDevelBranch(repo *gitpb.Repo) error {
branch := repo.GetDevelBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
return nil
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
err := repo.RunVerbose(cmd)
return err
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
err := repo.RunVerbose(cmd)
return err
}
func syncDevelBranches() 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
}
*/

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
}

41
doc.go
View File

@ -1,41 +0,0 @@
/*
forge -- a tool to manage lots of git repos. forge includes a GUI and TUI.
forge only executes the 'git' command. Everything it does, you can run by hand with 'git'.
Options:
--debugger open the debugger window
--logger open the log.* control window
--gui GUI select the plugin (andlabs,gocui,etc)
--gui-verbose enable all logging
--bash generate bash completion
--bash generate bash completion
--connect CONNECT forge url
--all git commit --all
--build BUILD build a repo
--install INSTALL install a repo
--forge-rebuild download and rebuild forge
--force try to strong arm things
--verbose show more output
--help, -h display this help and exit
--version display version and exit
Commands:
help New to forge? This is for you.'
checkout switch branches using 'git checkout'
clean start over at the beginning
commit 'git commit' but errors out if on wrong branch
config show your .config/forge/ settings
dirty show dirty git repos
fetch run 'git fetch master'
gui open the gui
list print a table of the current repos
merge merge branches
normal set every repo to the default state for software development
patch make patchsets
pull run 'git pull'
tag manage git tags
*/
package main

33
exit.go
View File

@ -1,33 +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 (
"os"
"go.wit.com/gui"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func okExit(thing string) {
gui.UnloadToolkits()
if configSave {
me.forge.SetConfigSave(configSave)
}
if thing != "" {
log.Info("forge exit:", thing, "ok")
}
me.forge.Exit()
}
func badExit(err error) {
log.Info("forge failed: ", err, me.forge.Config.ReposDir)
os.Exit(-1)
}
func badRepoExit(repo *gitpb.Repo, err error) {
log.Printf("%s FAILED: %v\n", repo.GetNamespace(), err)
os.Exit(-1)
}

55
findConfig.go Normal file
View File

@ -0,0 +1,55 @@
package main
import "go.wit.com/log"
// retuns true if nothing was done
func findConfig() bool {
var done bool = false
if argv.FindMine {
findConfigMine()
done = true
}
if argv.FindAll {
findConfigAll()
done = true
}
if !done {
findConfigAll()
done = true
}
return done
}
// finds config repos that are writable
func findConfigMine() {
loop := me.forge.Config.SortByGoPath()
for loop.Scan() {
r := loop.Next()
gopath := r.GoPath
if r.GetDirectory() {
continue
}
if me.forge.Config.IsWritable(gopath) {
log.Info("mine:", gopath)
me.foundPaths = append(me.foundPaths, gopath)
continue
}
}
}
// get everything in your config
func findConfigAll() {
loop := me.forge.Config.SortByGoPath()
for loop.Scan() {
r := loop.Next()
gopath := r.GoPath
if r.GetDirectory() {
continue
}
if me.forge.Config.IsWritable(gopath) {
log.Info("mine:", gopath)
me.foundPaths = append(me.foundPaths, gopath)
continue
}
}
}

91
findRepos.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"go.wit.com/log"
)
func findRepos() bool {
var done bool = false
if argv.FindAll {
findAll()
done = true
}
if argv.FindPrivate {
findPrivate()
done = true
}
if argv.FindMine {
findMine()
done = true
}
if argv.FindFavorites {
findFavorites()
done = true
}
// this is the 'default' behavior when no command line arguments are given
// if no argv was set, select repos marked as 'mine'
if !done {
findMine()
done = true
}
return done
}
func findPrivate() {
repos := me.forge.Repos.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
if me.forge.Config.IsPrivate(repo.GoPath) {
me.found.AppendUniqueGoPath(repo)
}
}
}
// finds repos that are writable
func findMine() {
// log.Printf("get mine %s\n", me.forge.GetGoSrc())
repos := me.forge.Repos.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
if me.forge.Config.IsWritable(repo.GoPath) {
me.found.AppendUniqueGoPath(repo)
}
}
}
// finds repos that are writable
func findFavorites() {
// log.Printf("get favorites %s\n", me.forge.GetGoSrc())
repos := me.forge.Repos.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
if me.forge.Config.IsFavorite(repo.GoPath) {
me.found.AppendUniqueGoPath(repo)
}
}
}
func findAll() {
var configsave bool
repos := me.forge.Repos.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
if me.forge.Config.IsReadOnly(repo.GoPath) && !argv.FindReadOnly {
if repo.ReadOnly {
continue
}
log.Info("todo: ConfigSave() readonly flag on repo is wrong", repo.GoPath)
repo.ReadOnly = true
configsave = true
continue
}
me.found.AppendUniqueGoPath(repo)
}
if configsave {
log.Info("should ConfigSave here")
me.forge.Repos.ConfigSave()
}
}

91
globalBuildOptions.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"os"
"os/user"
"path/filepath"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
)
func doesExist(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// only errors on bad errors
func quickCmd(fullpath string, cmd []string) bool {
if me.autoDryRun.Checked() {
log.Warn("RUN --dry-run", fullpath, cmd)
return false
} else {
log.Warn("RUN:", fullpath, cmd)
}
result := shell.PathRun(fullpath, cmd)
if result.Error != nil {
log.Warn("quickCmd() cmd =", cmd)
log.Warn("quickCmd() err =", result.Error)
log.Warn("quickCmd() b =", result.Exit)
log.Warn("quickCmd() output =", result.Stdout)
return false
} else if result.Exit != 0 {
log.Warn("quickCmd() b =", result.Exit)
log.Warn("quickCmd() output =", result.Stdout)
return true
}
log.Warn("quickCmd() output = ", result.Stdout)
return true
}
func globalBuildOptions(vbox *gui.Node) {
group1 := vbox.NewGroup("Global Build Options")
grid := group1.NewGrid("buildOptions", 0, 0)
// me.autoWorkingPwd = gadgets.NewOneLiner(grid, "working directory (pwd)")
me.userHomePwd = gadgets.NewOneLiner(grid, "user home")
grid.NextRow()
me.goSrcPwd = gadgets.NewOneLiner(grid, "go src home")
grid.NextRow()
usr, _ := user.Current()
homeDir, err := os.UserHomeDir()
if err != nil {
log.Warn("Error getting home directory:", err)
homeDir = "/home/autotypist"
}
me.userHomePwd.SetText(homeDir)
srcDir := filepath.Join(homeDir, "go/src")
me.goSrcPwd.SetText(srcDir)
// select the branch you want to test, build and develop against
// this lets you select your user branch, but, when you are happy
// you can merge everything into the devel branch and make sure it actually
// works. Then, when that is good, merge and version everything in master
me.setBranchB = grid.NewButton("set current branch to:", func() {
targetName := me.newBranch.String()
log.Warn("setting all branches to", targetName)
loop := me.repos.View.ReposSortByName()
for loop.Scan() {
repo := loop.Repo()
repo.Status.CheckoutBranch(targetName)
repo.Scan()
}
})
me.newBranch = grid.NewCombobox()
me.newBranch.AddText("master")
me.newBranch.AddText("devel")
me.newBranch.AddText(usr.Username)
me.newBranch.SetText(usr.Username)
// checking this will automatically make the branches off of devel
me.autoCreateBranches = grid.NewCheckbox("create if missing").SetChecked(true)
grid.NextRow()
}

62
globalDisplayOptions.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"go.wit.com/gui"
"go.wit.com/lib/gui/repolist"
"go.wit.com/log"
)
func hideFunction(r *repolist.RepoRow) {
/*
if r.Status.IsDirty() {
r.Show()
return
}
if me.autoHideReadOnly.Checked() {
if r.Status.ReadOnly() {
r.Hide()
return
}
}
if me.autoHidePerfect.Checked() {
if r.IsPerfect() {
r.Hide()
return
}
}
r.Show()
*/
}
func globalDisplayOptions(vbox *gui.Node) {
group1 := vbox.NewGroup("Global Display Options")
group1.NewButton("Show Repository Window", func() {
// globalDisplaySetRepoState()
if me.repos.Hidden() {
me.repos.Show()
} else {
me.repos.Hide()
}
})
me.autoHideReadOnly = group1.NewCheckbox("Hide read-only repos").SetChecked(true)
me.autoHideReadOnly.Custom = func() {
me.repos.View.RegisterHideFunction(hideFunction)
me.repos.View.ScanRepositories()
}
me.autoHidePerfect = group1.NewCheckbox("Hide Perfectly clean repos").SetChecked(true)
me.autoHidePerfect.Custom = func() {
me.repos.View.RegisterHideFunction(hideFunction)
me.repos.View.ScanRepositories()
}
scanbox := group1.Box().Horizontal()
me.autoScanReposCB = scanbox.NewCheckbox("auto scan").SetChecked(true)
scanbox.NewButton("scan now", func() {
log.Info("re-scanning repos now")
i, s := me.repos.View.ScanRepositories()
log.Info("re-scanning repos done", i, s)
})
}

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
}

254
main.go
View File

@ -1,15 +1,11 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
// An app to submit patches for the 30 GO GUI repos // An app to submit patches for the 30 GO GUI repos
import ( import (
"embed" "os"
"strings"
"go.wit.com/lib/gui/prep" "go.wit.com/dev/alexflint/arg"
"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"
@ -19,212 +15,62 @@ import (
var VERSION string var VERSION string
var BUILDTIME string var BUILDTIME string
// this optionally can store the GUI plugins
//
//go:embed resources/*
var resources embed.FS
// used for shell auto completion
var ARGNAME string = "forge"
// using this for now. triggers config save
var configSave bool
func getVersion(repo *gitpb.Repo, name string) string {
cmd := []string{"git", "describe", "--tags", "--always", name}
result, _ := repo.RunQuiet(cmd)
output := strings.Join(result.Stdout, "\n")
log.Info("cmd =", cmd, output)
return strings.TrimSpace(output)
}
func main() { func main() {
me = new(mainType) me = new(mainType)
me.myGui = prep.Gui() // prepares the GUI package for go-args me.pp = arg.MustParse(&argv)
me.auto = prep.Bash3(&argv) // this line should be: prep.Bash(&argv)
// me.auto = prep.Bash3(argv.DoAutoComplete, &argv) // this line should be: prep.Bash(&argv) // load the ~/.config/forge/ config
// arg.MustParse(&argv) // these three lines are becoming terrible syntax me.forge = forgepb.Init()
// me.auto = prep.MustParse(&argv) // try to make this work? me.found = new(gitpb.Repos)
me.forge = forgepb.Init() // init forge.pb
me.forge.ScanRepoDir() // looks for new dirs, checks existing repos for changes
// initialize patches
doPatchInit()
// 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 {
doConfig() if findConfig() {
okExit("") me.forge.ConfigPrintTable()
} os.Exit(0)
if argv.Commit != nil {
doCommit()
okExit("")
}
if argv.BuildForge {
buildForge()
okExit("")
}
if argv.Checkout != nil {
if err := doCheckout(); err != nil {
badExit(err)
} }
okExit("")
}
if argv.Build != "" {
if err := doBuild(); err != nil {
badExit(err)
}
okExit("")
}
if argv.Install != "" {
if err := doInstall(); err != nil {
badExit(err)
}
okExit("")
}
if argv.Clean != nil {
if err := doClean(); err != nil {
badExit(err)
}
me.forge.ConfigSave()
okExit("")
}
if argv.Help != nil {
doHelp()
okExit("")
}
if argv.Dirty != nil {
doDirty()
okExit("")
}
if argv.Tag != nil {
doTag()
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 err := doMerge(); err != nil {
badExit(err)
}
okExit("")
}
if argv.Pull != nil {
doPull()
okExit("")
}
if argv.List != nil {
found := argv.List.findRepos()
// print out the repos
if argv.List.Full {
me.forge.PrintHumanTableFull(found)
} else {
me.forge.PrintHumanTable(found)
}
okExit("")
}
if argv.Patch != nil {
if err := doPatch(); err != nil {
badExit(err)
}
okExit("")
}
// open the gui unless the user performed some other
// 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
me.forge.CheckDirty()
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 { } else {
// every repo is in a really clean state. no extra files anywhere findRepos()
// 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 log.Info("found", me.found.Len(), "repos. found", len(me.foundPaths), "paths from .config/forge")
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() { // now, do something to all of them (or just print out a table of them)
log.Info("TODO: ?") var done bool = false
okExit("") if argv.DoScan {
doScan()
done = true
}
if argv.DoRedoGoMod {
doRedoGoMod()
done = true
}
if argv.DoGitPull {
doGitPull()
done = true
}
if argv.DoList {
// print out the repos
doCobol()
done = true
}
if argv.Fix {
// print out the repos
doFix()
done = true
}
// do the gui at the very end
if argv.DoGui {
doGui()
os.Exit(0)
}
if !done {
// if nothing was selected, print out a table of them on STDOUT
doCobol()
}
} }

202
repoview.go Normal file
View File

@ -0,0 +1,202 @@
package main
import (
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/repolist"
"go.wit.com/log"
"go.wit.com/gui"
)
type repoWindow struct {
win *gadgets.BasicWindow
box *gui.Node
// the top box of the repolist window
topbox *gui.Node
View *repolist.RepoList
}
func (r *repoWindow) Hidden() bool {
return r.win.Hidden()
}
func (r *repoWindow) Show() {
r.win.Show()
}
func (r *repoWindow) Hide() {
r.win.Hide()
}
func (r *repoWindow) Disable() {
r.box.Disable()
}
func (r *repoWindow) Enable() {
r.box.Enable()
}
// you can only have one of these
func makeRepoView() *repoWindow {
if me.repos != nil {
return me.repos
}
r := new(repoWindow)
r.win = gadgets.RawBasicWindow("All git repositories in ~/go/src/")
r.win.Make()
r.box = r.win.Box().NewBox("bw vbox", false)
// me.reposwin.Draw()
r.win.Custom = func() {
log.Warn("GOT HERE: main() gadgets.NewBasicWindow() close")
log.Warn("Should I do something special here?")
}
r.topbox = r.repoMenu()
r.View = repolist.InitBox(me.forge, r.box)
r.View.Enable()
r.View.ScanRepositories()
/*
r.View = repolist.AutotypistView(r.box)
showncount := r.View.MirrorShownCount()
r.topbox.Append(showncount)
duration := r.View.MirrorScanDuration()
r.topbox.Append(duration)
*/
return r
}
func (r *repoWindow) repoMenu() *gui.Node {
// reposbox.SetExpand(false)
group1 := r.box.NewGroup("Run on all repos:")
hbox := group1.Box()
// hbox.Horizontal()
hbox.Vertical()
box2 := hbox.Box().Vertical()
/*
box2.NewButton("merge all user to devel", func() {
r.Disable()
if !r.mergeAllUserToDevel() {
return
}
r.Enable()
})
box2.NewButton("merge all devel to main", func() {
r.Disable()
if !r.mergeAllDevelToMain() {
return
}
r.Enable()
})
*/
box2.NewButton("merge it all", func() {
r.Disable()
if !r.mergeAllUserToDevel() {
return
}
if !r.mergeAllDevelToMain() {
return
}
r.Enable()
})
box2.NewButton("show apps", func() {
loop := me.repos.View.ReposSortByName()
for loop.Scan() {
repo := loop.Repo()
rtype := repo.Status.RepoType()
switch rtype {
case "'binary'":
// log.Info(repo.Status.Path(), "compile here. Show()")
repo.Show()
case "'library'":
// log.Info(repo.Status.Path(), "library here. Hide()")
repo.Hide()
default:
log.Info(repo.Status.Path(), "unknown type", rtype)
// repo.Hide()
}
}
})
box2.NewButton("scan now", func() {
log.Info("re-scanning now")
i, s := me.repos.View.ScanRepositories()
log.Info("re-scanning done", i, "repos in", s)
})
return box2
}
func (r *repoWindow) mergeAllDevelToMain() bool {
log.Info("merge all here")
loop := me.repos.View.ReposSortByName()
for loop.Scan() {
repo := loop.Repo()
if repo.ReadOnly() {
log.Info("skipping readonly", repo.Name(), repo.State())
continue
}
if repo.State() != "merge to main" {
log.Info("skipping. not merge to main", repo.Name(), repo.State())
continue
}
if repo.CheckDirty() {
log.Info("skipping dirty", repo.Name(), repo.State())
continue
}
log.Info("repo:", repo.Name(), repo.State())
repo.NewScan()
if repo.Status.MergeDevelToMaster() {
log.Warn("THINGS SEEM OK fullAutomation() returned true.")
} else {
log.Warn("last repo:", repo.Name())
log.Warn("THINGS FAILED fullAutomation() returned false")
return false
}
repo.NewScan()
}
log.Warn("EVERYTHING WORKED")
return true
}
func (r *repoWindow) mergeAllUserToDevel() bool {
log.Info("merge all here")
loop := me.repos.View.ReposSortByName()
for loop.Scan() {
repo := loop.Repo()
if repo.ReadOnly() {
log.Info("skipping readonly", repo.Name(), repo.State())
continue
}
if repo.State() != "merge to devel" {
log.Info("skipping. not merge to devel", repo.Name(), repo.State())
continue
}
if repo.CheckDirty() {
log.Info("skipping dirty", repo.Name(), repo.State())
continue
}
log.Info("repo:", repo.Name(), repo.State())
repo.NewScan()
if repo.Status.MergeUserToDevel() {
log.Warn("THINGS SEEM OK fullAutomation() returned true.")
} else {
log.Warn("last repo:", repo.Status.Path())
log.Warn("THINGS FAILED fullAutomation() returned false")
return false
}
repo.NewScan()
}
log.Warn("EVERYTHING WORKED")
return true
}

View File

@ -1,22 +0,0 @@
# this file is intended to be used to customize settings on what
# git repos you have write access to. That is, where you can run 'git push'
#
# add entries to this using 'forge config'
ForgeConfigs: {
goPath: "go.wit.com"
writable: true
directory: true
}
repos: {
goPath: "go.wit.com/apps/zookeeper"
debName: "zookeeper-go"
}
xterm: "xterm"
xtermArgv: "-bg"
xtermArgv: "black"
xtermArgv: "-fg"
xtermArgv: "white"
xtermArgv: "-geometry"
xtermArgv: "140x32"
xtermArgv: "-e"

View File

@ -1,13 +1,11 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
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"
"go.wit.com/lib/protobuf/gitpb"
) )
var me *mainType var me *mainType
@ -20,32 +18,53 @@ 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
found *gitpb.Repos // stores the list of repos to process things on
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
// our view of the repositories
repos *repoWindow
mainWindow *gadgets.BasicWindow mainWindow *gadgets.BasicWindow
mainbox *gui.Node // the main box. enable/disable this
autoDryRun *gui.Node // checkbox for --dry-run
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
// these hold the branches that the user can switch all the repositories to them // the main box. enable/disable this
reposWinB *gui.Node // button that opens the repos window mainbox *gui.Node
repoAllB *gui.Node // "all" repos button
repoDirtyB *gui.Node // "dirty" repos button // the window from the /lib/gui/gowit package
repoDevelMergeB *gui.Node // "merge to devel" repos button lw *gadgets.BasicWindow
repoWritableB *gui.Node // "what repos are writable" repos button
// #### Sorting options for the repolist
autoHidePerfect *gui.Node
autoHideReadOnly *gui.Node
// checkbox for --dry-run
autoDryRun *gui.Node
// checkbox to enable intermittent scanning
// if checked, it will check all your repos for changes
autoScanReposCB *gui.Node
// what is being used as your home dir
userHomePwd *gadgets.OneLiner
// what is being used as ~/go/src
goSrcPwd *gadgets.OneLiner
// displays a summary of all the repos
// has total dirty, total read-only
// total patches, etc
summary *patchSummary
// when switch to user or devel branches, autocreate them
autoCreateBranches *gui.Node
// these hold the branches that the user can switch all
// the repositories to them
newBranch *gui.Node
setBranchB *gui.Node
} }

240
subitPatches.go Normal file
View File

@ -0,0 +1,240 @@
package main
import (
"path/filepath"
"strconv"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/repolist"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
)
/*
type patch struct {
ref string
giturl string
comment string
rs *repostatus.RepoStatus
}
*/
type patchSummary struct {
grid *gui.Node
updateB *gui.Node
docsB *gui.Node
gitPushB *gui.Node
gitPullB *gui.Node
checkB *gui.Node
// stats
totalOL *gadgets.OneLiner
totalGoOL *gadgets.OneLiner
dirtyOL *gadgets.OneLiner
readonlyOL *gadgets.OneLiner
totalPatchesOL *gadgets.OneLiner
totalUserRepos *gui.Node
totalDevelRepos *gui.Node
totalMasterRepos *gui.Node
totalUserPatches *gui.Node
totalDevelPatches *gui.Node
totalMasterPatches *gui.Node
// patch set generation
unknownOL *gadgets.BasicEntry
unknownSubmitB *gui.Node
reason *gadgets.BasicEntry
submitB *gui.Node
allp []*repolist.Patch
}
func submitPatchesBox(box *gui.Node) *patchSummary {
s := new(patchSummary)
group1 := box.NewGroup("Submit Patches Summary")
s.grid = group1.RawGrid()
s.grid.NewButton("Update Patch Counts", func() {
var repocount, patchcount int
// broken after move to forge protobuf
loop := me.forge.Repos.SortByGoPath()
for loop.Scan() {
repo := loop.Next()
if repo.GetReadOnly() {
continue
}
i, _ := repo.GetMasterPatches()
patchcount += i
if i > 0 {
repocount += 1
}
}
s.totalMasterPatches.SetText(strconv.Itoa(patchcount) + " patches")
s.totalMasterRepos.SetText(strconv.Itoa(repocount) + " go repos")
repocount = 0
patchcount = 0
// broken after move to forge protobuf
loop = me.forge.Repos.SortByGoPath()
for loop.Scan() {
repo := loop.Next()
if repo.GetReadOnly() {
continue
}
i, _ := repo.GetUserPatches()
patchcount += i
if i > 0 {
repocount += 1
}
}
s.totalUserPatches.SetText(strconv.Itoa(patchcount) + " patches")
s.totalUserRepos.SetText(strconv.Itoa(repocount) + " go repos")
})
/* this used to be the way and should probably be revisited
s.grid.NewButton("Make Patches", func() {
for i, p := range s.allp {
log.Info(i, p.Ref, p.RS.String())
}
})
*/
s.grid.NextRow()
s.totalOL = gadgets.NewOneLiner(s.grid, "Total")
s.grid.NextRow()
s.totalGoOL = gadgets.NewOneLiner(s.grid, "Total GO")
s.grid.NextRow()
s.dirtyOL = gadgets.NewOneLiner(s.grid, "dirty")
s.grid.NextRow()
s.readonlyOL = gadgets.NewOneLiner(s.grid, "read-only")
s.grid.NextRow()
// s.grid = group1.RawGrid()
s.grid.NewLabel("")
s.grid.NewLabel("")
s.grid.NewLabel("user to devel")
s.grid.NewLabel("devel to master")
s.grid.NewLabel("master to last tag")
s.grid.NextRow()
s.grid.NewLabel("total modified")
s.grid.NewLabel("")
s.totalUserRepos = s.grid.NewLabel("x go repos")
s.totalDevelRepos = s.grid.NewLabel("")
s.totalMasterRepos = s.grid.NewLabel("x go repos")
s.grid.NextRow()
s.totalPatchesOL = gadgets.NewOneLiner(s.grid, "total commits")
s.totalUserPatches = s.grid.NewLabel("x patches")
s.totalDevelPatches = s.grid.NewLabel("")
s.totalMasterPatches = s.grid.NewLabel("x patches")
s.grid.NextRow()
s.grid.NewLabel("")
s.grid.NewLabel("")
s.grid.NewButton("merge from user", func() {
log.Info("this should make a patchset of your patches")
})
s.grid.NewButton("merge from devel", func() {
log.Info("this probably should not exist")
})
s.grid.NextRow()
group1 = box.NewGroup("Create GUI Patch Set")
s.grid = group1.RawGrid()
s.reason = gadgets.NewBasicEntry(s.grid, "patch name:")
s.reason.Custom = func() {
if s.reason.String() != "" {
s.submitB.Enable()
} else {
s.submitB.Disable()
}
}
s.submitB = s.grid.NewButton("Create Patch Set", func() {
dirname := "submit-patchset.quilt"
patchdir := filepath.Join(me.userHomePwd.String(), dirname)
if shell.Exists(patchdir) {
log.Info("patchset dir already exists", patchdir)
shell.PathRun(me.userHomePwd.String(), []string{"rm", "-rf", dirname})
}
shell.Mkdir(patchdir)
if !shell.Exists(patchdir) {
log.Info("something went wrong making", patchdir)
return
}
me.repos.View.MakePatchset(patchdir)
})
s.submitB = s.grid.NewButton("Submit quilt", func() {
log.Info("do a submit here")
})
// disable these until there are not dirty repos
s.reason.Disable()
s.submitB.Disable()
s.grid.NextRow()
s.unknownOL.Disable()
s.unknownSubmitB.Disable()
s.grid.NextRow()
return s
}
// does not run any commands
func (s *patchSummary) Update() {
var total, totalgo, dirty, readonly int
var userT, develT, masterT int
// var userP, develP, masterP int
// broken after move to forge protobuf
loop := me.forge.Repos.SortByGoPath()
for loop.Scan() {
repo := loop.Next()
total += 1
if repo.IsDirty() {
dirty += 1
}
if repo.GetReadOnly() {
readonly += 1
// don't count these in any further stats
continue
}
// compute which GUI repos are out of sync with master
userV := repo.GetUserVersion()
develV := repo.GetDevelVersion()
masterV := repo.GetMasterVersion()
lastV := repo.GetLastTagVersion()
if userV != develV {
userT += 1
}
if develV != masterV {
log.Info("develV != masterV", develV, masterV, repo.GetGoPath())
develT += 1
}
if masterV != lastV {
masterT += 1
}
}
s.totalOL.SetText(strconv.Itoa(total) + " repos")
s.totalGoOL.SetText(strconv.Itoa(totalgo) + " repos")
s.dirtyOL.SetText(strconv.Itoa(dirty) + " repos")
s.readonlyOL.SetText(strconv.Itoa(readonly) + " repos")
s.totalUserRepos.SetText(strconv.Itoa(userT) + " repos")
s.totalDevelRepos.SetText(strconv.Itoa(develT) + " repos")
s.totalMasterRepos.SetText(strconv.Itoa(masterT) + " repos")
if dirty == 0 {
s.reason.Enable()
s.submitB.Enable()
s.unknownOL.Enable()
s.unknownSubmitB.Enable()
} else {
s.reason.Disable()
s.submitB.Enable()
s.unknownOL.Enable()
s.unknownSubmitB.Enable()
}
}

0
test
View File

View File

@ -1,118 +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 (
"os"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
)
func makeHowtoWin() *gadgets.GenericWindow {
howtoWin := gadgets.NewGenericWindow("Howto", "forge -- a GUI tool for git repostories")
tmp := `A good way to see how forge works is to download forge
This will 'git clone' a few things (~50 repos):
`
howtoWin.Group.NewLabel(tmp)
grid := howtoWin.Group.RawGrid()
grid.NewLabel("forge")
grid.NewLabel("the sources for forge")
grid.NextRow()
grid.NewLabel("autogenpb")
grid.NewLabel("generates needed code for working with the protobuf files")
grid.NextRow()
grid.NewLabel("go-clone")
grid.NewLabel("recursively 'git clone' dependencies based on go.mod")
grid.NextRow()
grid.NewLabel("the GUI")
grid.NewLabel("GO plugins for libcurses and GTK")
grid.NextRow()
grid.NewLabel("") // a stupid way to add padding
grid.NextRow()
// howtoWin.Group.NewLabel("Working dir: " + me.forge.Config.ReposDir)
grid = howtoWin.Group.RawGrid()
grid.NewButton("Download into "+me.forge.Config.ReposDir, func() {
howtoWin.Disable()
defer howtoWin.Enable()
downloadForge()
})
grid.NewButton("Build forge & GUI GO plugins", func() {
howtoWin.Disable()
defer howtoWin.Enable()
buildForge()
})
return howtoWin
}
func downloadForge() {
log.Info("download here")
if path, err := fhelp.CheckCmd("go-clone"); err != nil {
log.Info("go-clone missing", path, err)
cmd := []string{"go", "install", "go.wit.com/apps/go-clone@latest"}
shell.RunRealtime(cmd)
}
if _, err := fhelp.CheckCmd("autogenpb"); err != nil {
cmd := []string{"go", "install", "go.wit.com/apps/autogenpb@latest"}
shell.RunRealtime(cmd)
}
if _, err := fhelp.CheckCmd("go-mod-clean"); err != nil {
cmd := []string{"go", "install", "go.wit.com/apps/go-mod-clean@latest"}
shell.RunRealtime(cmd)
}
if path, err := fhelp.CheckCmd("go-clone"); err != nil {
log.Info("can't prep build. you probably need ~/go/bin in your PATH", path, err)
return
}
var basecmd []string
var cmd []string
if me.forge.IsGoWork() {
log.Info("Using go.work directory")
basecmd = []string{"go-clone", "--work", "--recursive"}
} else {
basecmd = []string{"go-clone", "--recursive"}
}
// log.Info("Running:", cmd)
// shell.RunRealtime(cmd)
cmd = append(basecmd, "go.wit.com/apps/go-mod-clean")
log.Info("Running:", cmd)
shell.RunRealtime(cmd)
os.Exit(-1)
cmd = append(basecmd, "go.wit.com/apps/forge")
log.Info("Running:", cmd)
shell.RunRealtime(cmd)
cmd = append(basecmd, "go.wit.com/toolkits/gocui")
log.Info("Running:", cmd)
shell.RunRealtime(cmd)
cmd = append(basecmd, "go.wit.com/toolkits/andlabs")
log.Info("Running:", cmd)
shell.RunRealtime(cmd)
// should, after go-clone, be able to go build
/*
repo := me.forge.FindByGoPath("go.wit.com/lib/proto/forgepb")
if repo != nil {
repo.RunVerbose([]string{"autogenpb"})
}
*/
}
func buildForge() {
log.Info("buildForge() not done yet")
}

View File

@ -1,217 +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 (
"fmt"
"os"
"path/filepath"
"sync"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
type stdPatchTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget
TB *forgepb.PatchesTable // the gui table buffer
update bool // if the window should be updated
}
func (w *stdPatchTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
dwin := new(stdPatchTableWin)
dwin.win = gadgets.NewGenericWindow("current patches", "patching options")
dwin.win.Custom = func() {
log.Info("test delete window here")
dwin.win.Hide()
// dwin = nil
}
grid := dwin.win.Group.RawGrid()
grid.NewLabel(fmt.Sprintf("%d", patches.Len()))
grid.NewLabel(fmt.Sprintf("total patches"))
grid.NextRow()
repomap := make(map[string]int)
all := patches.All()
for all.Scan() {
patch := all.Next()
repomap[patch.Namespace] += 1
}
grid.NewLabel(fmt.Sprintf("%d", len(repomap)))
grid.NewLabel(fmt.Sprintf("total repos"))
grid.NextRow()
grid.NewButton("Apply All", func() {
var count int
all := patches.SortByFilename()
for all.Scan() {
p := all.Next()
applyPatchNew(p)
}
log.Info("ALL PATCHES WORKED! count =", count)
})
// make a box at the bottom of the window for the protobuf table
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
if patches != nil {
dwin.doPatchesTable(patches)
}
return dwin
}
func applyPatchNew(p *forgepb.Patch) error {
rn := p.Namespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
log.Info("Could not figure out repo path", rn)
return log.Errorf("%s namespace?\n", rn)
}
if _, err := applyAndTrackPatch(repo, p); err != nil {
cmd := []string{"git", "am", "--abort"}
err := repo.RunVerbose(cmd)
log.Info("warn user of git am error", err)
return err
}
return nil
}
func (dwin *stdPatchTableWin) doPatchesTable(currentPatches *forgepb.Patches) {
dwin.Lock()
defer dwin.Unlock()
if dwin.TB != nil {
dwin.TB.Delete()
dwin.TB = nil
}
// display the protobuf
dwin.TB = AddPatchesPB(dwin.box, currentPatches)
f := func(p *forgepb.Patch) {
log.Info("do something with patch", p.Filename)
}
dwin.TB.Custom(f)
}
// used by the PB table
func applyPatchLabel(p *forgepb.Patch) string {
rn := p.Namespace
if repo := me.forge.FindByGoPath(rn); repo == nil {
// log.Info("Could not figure out repo path", rn)
return ""
}
if p.NewHash == "" {
return "git am"
}
return "done"
}
func applyPatchClick(p *forgepb.Patch) {
if err := applyPatchNew(p); err != nil {
log.Info("git am failed on file", p.Filename, "with error", err)
return
}
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.AddNamespace()
// t.AddFilename()
t.AddStringFunc("file", func(p *forgepb.Patch) string {
_, fname := filepath.Split(p.Filename)
return fname
})
t.AddCommitHash()
t.ShowTable()
return t
}
func applyPatch(repo *gitpb.Repo, filename string) error {
cmd := []string{"git", "am", filename}
err := repo.RunVerbose(cmd)
return err
}
func savePatch(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()
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
}

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

@ -1,393 +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"
"os"
"time"
"go.wit.com/lib/debugger"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func makeReposWin() *stdReposTableWin {
rwin := new(stdReposTableWin)
win := gadgets.NewGenericWindow("find errors and try to fix them", "types of errors of some sort or another")
rwin.win = win
grid := win.Group.RawGrid()
// win.Top.NewGroup("misc (works in progress)")
// grid = win.Top.RawGrid()
grid = win.Group.RawGrid()
var found *gitpb.Repos
var txt string
found = develBehindMasterProblem()
txt = fmt.Sprintf("devel is behind master (%d)", found.Len())
grid.NewButton(txt, func() {
win := gadgets.RawBasicWindow("devel branches that are out of sync with master")
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)
found := develBehindMasterProblem()
group := box.NewGroup("test buttons")
hbox := group.Box().Horizontal()
hbox.NewButton("git merge master devel", func() {
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
mname := repo.GetMasterBranchName()
dname := repo.GetDevelBranchName()
if dname != repo.GetCurrentBranchName() {
log.Info("Repo is not on the devel branch", repo.GetGoPath())
}
cmd := []string{"git", "merge", mname}
log.Info(repo.GetGoPath(), cmd)
repo.RunVerbose(cmd)
}
})
hbox.NewButton("test", func() {
})
t := makeDevelBehindMaster(found)
t.SetParent(box)
t.ShowTable()
})
found = remoteUserBranchProblem()
txt = fmt.Sprintf("user branch is remote (%d)", found.Len())
grid.NewButton(txt, func() {
win := gadgets.RawBasicWindow("repos that seem to have remote 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)
found := remoteUserBranchProblem()
group := box.NewGroup("test buttons")
hbox := group.Box().Horizontal()
hbox.NewButton("git branch delete", func() {
win.Disable()
defer win.Enable()
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
brname := repo.GetUserBranchName()
// git push origin --delete jcarr
os.Setenv("GIT_TERMINAL_PROMPT", "0")
cmd := []string{"git", "push", "origin", "--delete", brname}
log.Info("You may want to run:", repo.GetGoPath(), cmd)
repo.RunVerbose(cmd)
os.Unsetenv("GIT_TERMINAL_PROMPT")
// git branch --delete --remote origin/jcarr
cmd = []string{"git", "branch", "--delete", "--remote", "origin/" + brname}
log.Info(repo.GetGoPath(), cmd)
repo.RunVerbose(cmd)
repo.ReloadCheck()
}
me.forge.SetConfigSave(true)
me.forge.ConfigSave()
})
t := makeStandardReposGrid(found)
t.SetParent(box)
t.ShowTable()
})
rwin.boxTB = win.Bottom.Box()
grid.NextRow()
found = develRemoteProblem()
txt = fmt.Sprintf("remote devel != local devel (%d)", found.Len())
grid.NewButton(txt, func() {
found := develRemoteProblem()
makeStandardReposWindow(txt, found)
})
found = masterRemoteProblem()
txt = fmt.Sprintf("remote master != local master (%d)", found.Len())
grid.NewButton(txt, func() {
found := masterRemoteProblem()
makeStandardReposWindow(txt, found)
})
grid.NextRow()
makeHackModeWindow(rwin)
return rwin
}
// table of devel errors behind master
func makeDevelBehindMaster(pb *gitpb.Repos) *gitpb.ReposTable {
t := pb.NewTable("testDirty")
t.NewUuid()
sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string {
return r.GetGoPath()
})
sf.Custom = func(r *gitpb.Repo) {
log.Info("merge master into devel here", r.GetGoPath())
}
t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time {
return repo.NewestTime()
})
t.AddMasterVersion()
t.AddDevelVersion()
t.AddState()
return t
}
// default window for active running droplets
func (rwin *stdReposTableWin) doReposTable(pb *gitpb.Repos) {
rwin.Lock()
defer rwin.Unlock()
if rwin.TB != nil {
rwin.TB.Delete()
rwin.TB = nil
}
rwin.pb = pb
t := makeStandardReposGrid(pb)
t.SetParent(rwin.boxTB)
t.ShowTable()
rwin.TB = t
}
func makeHackModeWindow(stdwin *stdReposTableWin) {
group := stdwin.win.Top.NewGroup("This is a work in progress")
grid := group.RawGrid()
grid.NewButton("git pull", func() {
log.Info("todo: run git pull on each repo")
})
me.repoDevelMergeB = grid.NewButton("merge", func() {
found := findMergeToDevel()
_, box := makeStandardReposWindow("repos to merge from user to devel", found)
hbox := box.Box().Horizontal()
hbox.NewButton("merge all", func() {
stdwin.win.Disable()
defer stdwin.win.Enable()
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
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())
return
}
if _, err := repo.MergeToDevel(); err != nil {
log.Info("merge from user failed", repo.GetGoPath(), err)
// log.Info(strings.Join(r.Stdout, "\n"))
// log.Info(strings.Join(r.Stderr, "\n"))
return
}
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
}
}
})
})
grid.NextRow()
group2 := stdwin.win.Top.NewGroup("Merge")
grid = group2.RawGrid()
/*
grid.NewButton("merge to devel", func() {
stdwin.win.Disable()
defer stdwin.win.Enable()
mergeUserToDevel(true)
})
grid.NewButton("merge to master", func() {
stdwin.win.Disable()
defer stdwin.win.Enable()
mergeDevelToMaster(true)
})
grid.NewButton("merge all", func() {
stdwin.win.Disable()
defer stdwin.win.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)
})
*/
grid.NewButton("show dirty repos on win.Bottom", func() {
log.Info("try to show dirty repos on bottom")
found := me.forge.FindDirty()
stdwin.doReposTable(found)
})
group3 := stdwin.win.Top.NewGroup("work in progress")
grid = group3.RawGrid()
grid.NewButton("forge ConfigSave()", func() {
me.forge.ConfigSave()
})
grid.NewButton("debugger()", func() {
debugger.DebugWindow()
})
}
func develBehindMasterProblem() *gitpb.Repos {
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetDevelVersion() == repo.GetMasterVersion() {
continue
}
found.AppendByFullPath(repo)
}
return found
}
func remoteUserBranchProblem() *gitpb.Repos {
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
username := repo.GetUserBranchName()
if repo.IsBranchRemote(username) {
found.AppendByFullPath(repo)
}
}
return found
}
func develRemoteProblem() *gitpb.Repos {
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
brname := repo.GetDevelBranchName()
if !repo.IsBranchRemote(brname) {
// log.Info("repo does not have remote devel branch", repo.GetGoPath())
continue
}
lhash := repo.GetLocalHash(brname)
rhash := repo.GetRemoteHash(brname)
// log.Info(lhash, rhash, repo.GetGoPath())
if lhash == "" || rhash == "" {
// something is wrong if either of these are blank
found.AppendByFullPath(repo)
continue
}
if lhash == rhash {
continue
}
found.AppendByFullPath(repo)
}
return found
}
func masterRemoteProblem() *gitpb.Repos {
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
brname := repo.GetMasterBranchName()
if !repo.IsBranchRemote(brname) {
// log.Info("repo does not have remote devel branch", repo.GetGoPath())
continue
}
lhash := repo.GetLocalHash(brname)
rhash := repo.GetRemoteHash(brname)
// log.Info(lhash, rhash, repo.GetGoPath())
if lhash == "" || rhash == "" {
// something is wrong if either of these are blank
found.AppendByFullPath(repo)
continue
}
if lhash == rhash {
continue
}
found.AppendByFullPath(repo)
}
return found
}
func makeWritableWindow(pb *gitpb.Repos) (*gadgets.GenericWindow, *gitpb.ReposTable) {
win := gadgets.NewGenericWindow("Repos You have write access to", "Configure")
t := pb.NewTable("testForgeRepos")
t.NewUuid()
grid := win.Group.RawGrid()
grid.NewButton("git pull", func() {
log.Info("todo: run git pull on each repo")
})
/*
grid.NewButton("do repos.ReScan()", func() {
t.Update()
})
*/
tbox := win.Bottom.Box()
t.SetParent(tbox)
sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string {
return r.GetGoPath()
})
sf.Custom = func(r *gitpb.Repo) {
log.Info("do button click on", r.GetGoPath())
}
t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time {
return repo.NewestTime()
})
t.AddMasterVersion()
t.AddDevelVersion()
t.AddUserVersion()
t.AddCurrentBranchName()
t.AddState()
t.ShowTable()
return win, t
}

View File

@ -1,185 +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 (
"sync"
"time"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
type stdReposTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
boxTB *gui.Node // the machines gui parent box widget
TB *gitpb.ReposTable // the gui table buffer
pb *gitpb.Repos // the current repos protobuf
update bool // if the window should be updated
}
func (w *stdReposTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
func makeWindowForPB() *gadgets.GenericWindow {
win := gadgets.NewGenericWindow("Forge Repos Protobuf View", "Display Git Repositories")
return win
}
func makeReposWinNew() *gadgets.GenericWindow {
insertWin := makeWindowForPB()
insertWin.Win.Custom = func() {
log.Info("test delete window here")
}
grid := insertWin.Group.RawGrid()
var t *gitpb.ReposTable
grid.NewButton("dirty", func() {
if t != nil {
t.Delete()
t = nil
}
found := me.forge.FindDirty()
// display the protobuf
t = addWindowPB(insertWin, found)
f := func(repo *gitpb.Repo) {
log.Info("got to ReposTable.Custom() id =", repo.GetGoPath(), repo.GetCurrentVersion())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
grid.NewButton("to publish", func() {
if t != nil {
t.Delete()
t = nil
}
found := findReposWithPatches()
me.forge.PrintHumanTable(found)
// make the window for the first time
t = addWindowPB(insertWin, found)
f := func(repo *gitpb.Repo) {
log.Info("got to ReposTable.Custom() id =", repo.GetGoPath(), repo.GetCurrentVersion())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
grid.NewButton("favorites", func() {
if t != nil {
t.Delete()
t = nil
}
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if !me.forge.Config.IsFavorite(repo.GetGoPath()) {
continue
}
found.AppendByFullPath(repo)
}
// make the window for the first time
t = addWindowPB(insertWin, found)
f := func(repo *gitpb.Repo) {
log.Info("got to ReposTable.Custom() id =", repo.GetGoPath(), repo.GetCurrentVersion())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
grid.NewButton("writeable", func() {
if t != nil {
t.Delete()
t = nil
}
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
found.AppendByFullPath(repo)
}
// make the window for the first time
t = addWindowPB(insertWin, found)
f := func(repo *gitpb.Repo) {
log.Info("got to ReposTable.Custom() id =", repo.GetGoPath(), repo.GetCurrentVersion())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
grid.NewButton("all", func() {
if t != nil {
t.Delete()
t = nil
}
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
found.AppendByFullPath(repo)
}
// display the protobuf
t = addWindowPB(insertWin, found)
f := func(repo *gitpb.Repo) {
log.Info("got to ReposTable.Custom() id =", repo.GetGoPath(), repo.GetCurrentVersion())
}
t.Custom(f)
log.Info("table has uuid", t.GetUuid())
})
return insertWin
}
func addWindowPB(win *gadgets.GenericWindow, pb *gitpb.Repos) *gitpb.ReposTable {
t := pb.NewTable("testForgeRepos")
t.NewUuid()
tbox := win.Bottom.Box().SetProgName("TBOX")
t.SetParent(tbox)
sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string {
return r.GetGoPath()
})
sf.Custom = func(r *gitpb.Repo) {
log.Info("do button click on", r.GetGoPath())
}
t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time {
return repo.NewestTime()
})
t.AddMasterVersion()
// hmm := t.AddMasterVersion()
// hmm.SetTitle("Master")
t.AddDevelVersion()
t.AddUserVersion()
t.AddCurrentBranchName()
t.AddState()
f := func(repo *gitpb.Repo) string {
log.Info("repo =", repo.GetGoPath(), repo.GetCurrentVersion())
return repo.GetGoPath()
}
t.AddButtonFunc("cur version", f)
t.ShowTable()
return t
}

View File

@ -1,147 +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 (
"sync"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
"go.wit.com/gui"
)
type repoPatchWindow struct {
once sync.Once // only init() the window once
win *gadgets.BasicWindow // the patches window
stack *gui.Node // the top box set as vertical
shelf *gui.Node // the first box in the stack, set as horizontal
grid *gui.Node // the list of available patches
// summary *patchSummary // summary of current patches
setgrid *gui.Node // the list of each patchset
pset *forgepb.Set // the patchset in question
}
// todo: autogenerate these or make them standared 'gui' package functions
// make this an go interface somehow
// is the window hidden right now?
func (w *repoPatchWindow) Hidden() bool {
return w.win.Hidden()
}
// switches between the window being visable or hidden on the desktop
func (w *repoPatchWindow) Toggle() {
if w.Hidden() {
w.Show()
} else {
w.Hide()
}
}
// hides the window completely
func (w *repoPatchWindow) Show() {
w.win.Show()
}
func (w *repoPatchWindow) Hide() {
w.win.Hide()
}
// should be the first box/widget in the window
// greys out the window to the user
func (w *repoPatchWindow) Disable() {
w.stack.Disable()
}
func (w *repoPatchWindow) Enable() {
w.stack.Enable()
}
// you can only have one of these
func makeRepoPatchWindow(repo *gitpb.Repo, fset []*forgepb.Patch) *repoPatchWindow {
pw := new(repoPatchWindow)
// sync.Once()
pw.win = gadgets.RawBasicWindow("Patcheset for " + repo.GetGoPath())
pw.win.Make()
pw.stack = pw.win.Box().NewBox("bw vbox", false)
// me.reposwin.Draw()
pw.win.Custom = func() {
// sets the hidden flag to false so Toggle() works
pw.win.Hide()
}
grid := pw.stack.NewGrid("", 0, 0)
grid.NewLabel(repo.GetGoPath())
grid.NewLabel(repo.GetCurrentBranchName())
g := pw.stack.NewGroup("PatchSet List")
// make a grid and a header
filegrid := g.NewGrid("", 0, 0)
filegrid.NewLabel("patch name")
filegrid.NewLabel("Applied in current branch?")
filegrid.NewLabel("original hash")
filegrid.NewLabel("new hash")
filegrid.NewButton("git squash", func() {
})
filegrid.NewButton("git am", func() {
})
filegrid.NextRow()
for _, p := range fset {
filegrid.NewLabel(p.Comment)
filegrid.NewLabel("yes?")
filegrid.NewLabel(p.CommitHash)
filegrid.NewLabel(p.NewHash)
filegrid.NewCheckbox("").SetChecked(true)
filegrid.NewCheckbox("").SetChecked(true)
filegrid.NextRow()
}
// add the patches to the grid
// pw.addPatchset(filegrid, pset)
return pw
}
func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Set) {
repomap := make(map[*gitpb.Repo][]*forgepb.Patch)
repohash := make(map[*gitpb.Repo]string)
// sort patches by repo namespace
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
s := p.Namespace
repo := me.forge.FindByGoPath(s)
if repo == nil {
log.Info("COULD NOT FIND", s)
continue
}
repomap[repo] = append(repomap[repo], p)
repohash[repo] = p.StartHash
}
for repo, patches := range repomap {
log.Info(repo.GetGoPath())
grid.NewLabel(repo.GetGoPath())
for i, p := range patches {
log.Info(i, p.Filename)
grid.NewLabel(p.Comment)
grid.NewLabel("in current branch?")
break
}
hash := repohash[repo]
grid.NewLabel(hash)
grid.NewButton("View", func() {
log.Info("todo: show patches for repo", repo.GetGoPath())
})
grid.NextRow()
}
}