Compare commits

...

210 Commits

Author SHA1 Message Date
Jeff Carr 9c87e1a040 print the table of the patches 2025-09-23 09:36:05 -05:00
Jeff Carr bda5fb4fbe set PB tables are working 2025-09-23 09:02:00 -05:00
Jeff Carr 1e3f4a3b9f new .proto files 2025-09-23 07:43:30 -05:00
Jeff Carr e300719241 cleanup tag list 2025-09-22 23:02:59 -05:00
Jeff Carr a9c4b21b35 try doc again 2025-09-22 21:07:24 -05:00
Jeff Carr 37aebd9d73 fix merge options 2025-09-22 20:40:30 -05:00
Jeff Carr 5a54f9c0b2 good grief 2025-09-22 20:14:35 -05:00
Jeff Carr 31db2f96f6 more work on autocomplete 2025-09-22 19:21:41 -05:00
Jeff Carr 3a967eac13 cleanup mode handling 2025-09-22 19:03:00 -05:00
Jeff Carr 17a62eb8da show the user name 2025-09-22 09:29:46 -05:00
Jeff Carr ba2f156c3d unused field 2025-09-21 20:41:17 -05:00
Jeff Carr 3c922f1277 wow. really? this works? 2025-09-18 16:53:36 -05:00
Jeff Carr ac16ef7127 wow. this worked. thanks Alex Flint 2025-09-18 16:48:03 -05:00
Jeff Carr 2ce32a0f2f more autocomplete cleanups 2025-09-18 16:03:11 -05:00
Jeff Carr 4d4dcf31cb slowly working out autocomplete kinks 2025-09-18 15:24:20 -05:00
Jeff Carr 19479f312f still thinking this out 2025-09-18 08:54:24 -05:00
Jeff Carr 58ce9ca53c more debugging of line feeds in bash 2025-09-18 07:29:53 -05:00
Jeff Carr 62e8d457f1 better command line handling 2025-09-17 22:36:11 -05:00
Jeff Carr f936a17bc0 compiles 2025-09-17 22:01:48 -05:00
Jeff Carr 9bc6d030e5 something to debug if under 400ms 2025-09-17 20:54:52 -05:00
Jeff Carr b8252f5caa use an auto complete PB 2025-09-17 17:03:43 -05:00
Jeff Carr ce0fd10064 common argv handling 2025-09-17 01:08:30 -05:00
Jeff Carr ae2cbf1886 all are returning AnyCol 2025-09-16 23:13:00 -05:00
Jeff Carr 29f2084e25 --gui bash completion 2025-09-16 17:33:57 -05:00
Jeff Carr 02d34d3e55 misc 2025-09-16 09:32:17 -05:00
Jeff Carr e14f7b93d1 more work on 'any' 2025-09-15 05:10:29 -05:00
Jeff Carr 7fdd7075fd testing showing tag age 2025-09-15 03:51:22 -05:00
Jeff Carr 179c19147e working len(rows) 2025-09-14 11:06:13 -05:00
Jeff Carr 0aafe6bb86 make generic print table PB to STDOUT 2025-09-14 05:50:23 -05:00
Jeff Carr 03b8e58451 quiet output 2025-09-13 08:39:12 -05:00
Jeff Carr 5637809f5c ooops 2025-09-13 08:31:45 -05:00
Jeff Carr 86306aa887 stuff 2025-09-13 07:45:04 -05:00
Jeff Carr 30ee1fcdf7 more lame fixes 2025-09-13 07:19:28 -05:00
Jeff Carr 9cdfface3c moved to new dir scan 2025-09-13 07:09:13 -05:00
Jeff Carr c463ec70f0 new dir scanner 2025-09-13 06:26:44 -05:00
Jeff Carr f4d60d1fb9 work on a new repo Scan() function 2025-09-13 05:32:19 -05:00
Jeff Carr 72d728d4e7 don't do anything if it hasn't changed 2025-09-13 01:52:44 -05:00
Jeff Carr 20fe78266c better "ForgeMode" handling 2025-09-13 01:46:53 -05:00
Jeff Carr da0fc653ae checking if commit() triggers save() 2025-09-13 01:06:55 -05:00
Jeff Carr 405ddb6994 work on using repo.Reload() more smarter 2025-09-13 00:51:22 -05:00
Jeff Carr b5df8f2dad add delete tag 2025-09-12 14:52:05 -05:00
Jeff Carr 7c520aae88 add tag handling 2025-09-12 14:30:26 -05:00
Jeff Carr 96a8f66138 minor 2025-09-12 10:12:00 -05:00
Jeff Carr bd951e4817 stop using GoSrc() 2025-09-11 22:14:30 -05:00
Jeff Carr fa3e6c3cd5 cleaning out old file 2025-09-11 21:39:57 -05:00
Jeff Carr 101e9bd0d2 minor 2025-09-11 20:54:42 -05:00
Jeff Carr 40db2d84ef move into forgepb 2025-09-11 06:57:29 -05:00
Jeff Carr f44aef8926 more stuff 2025-09-11 06:37:46 -05:00
Jeff Carr 0785f0e97f minor 2025-09-11 06:32:50 -05:00
Jeff Carr 363460290d finally detecting state changes again 2025-09-11 06:31:24 -05:00
Jeff Carr 309fcffc86 code to double check remote branches are in sync 2025-09-11 05:39:32 -05:00
Jeff Carr 2471b1ea4c check these every time 2025-09-11 05:18:09 -05:00
Jeff Carr efb966b3f8 common forge.Init() 2025-09-11 04:42:24 -05:00
Jeff Carr b30ecc89fb minor 2025-09-11 02:45:31 -05:00
Jeff Carr 7ab21831a0 notsure. did things break somehow? 2025-09-11 02:31:45 -05:00
Jeff Carr 1b9e9c7518 using new config package 2025-09-11 02:19:47 -05:00
Jeff Carr 602e1fc4ae redo config handling 2025-09-11 01:53:14 -05:00
Jeff Carr 2b3dfe540c lint 2025-09-09 06:25:51 -05:00
Jeff Carr 14bd2c4b0e okay something 2025-09-09 05:07:36 -05:00
Jeff Carr 860290fc26 common gui code 2025-09-09 04:37:50 -05:00
Jeff Carr c0ec2f359a rm old code 2025-09-09 03:54:07 -05:00
Jeff Carr f6bad20818 rm old code 2025-09-09 03:16:20 -05:00
Jeff Carr a991a4c187 argv autocomplete is fun! 2025-09-09 02:04:35 -05:00
Jeff Carr ad2c2ff2ec old code nope 2025-09-08 23:14:15 -05:00
Jeff Carr 297355f27e minor code reorder 2025-09-08 22:45:51 -05:00
Jeff Carr e62d74f0e6 language change 2025-09-08 16:44:40 -05:00
Jeff Carr 6c6af8707a finally back to one file again 2025-09-08 14:57:46 -05:00
Jeff Carr ce813a3450 common code. add "gui" to open the gui 2025-09-08 13:48:06 -05:00
Jeff Carr 11bf5481c7 common patch submit code 2025-09-08 13:19:40 -05:00
Jeff Carr 66802a287f try to figure out what to do with these 2025-09-08 09:25:20 -05:00
Jeff Carr c3afc8c2a1 testing repo submit 2025-09-08 08:13:27 -05:00
Jeff Carr cd3ca5dc82 minor fixes 2025-09-08 05:24:54 -05:00
Jeff Carr 2ddba9924c always unset 'normal' on merge master 2025-09-08 04:46:14 -05:00
Jeff Carr d6a562849c changes for autogenpb http functions 2025-09-08 04:03:13 -05:00
Jeff Carr c55e807262 fixed pull 2025-09-08 00:06:55 -05:00
Jeff Carr 2c7d1de637 redo doPull() 2025-09-08 00:03:54 -05:00
Jeff Carr 99de9e31bc merge turns off 'normal' development mode 2025-09-07 22:58:42 -05:00
Jeff Carr 86eb446408 this can bypass the normal check 2025-09-07 22:55:32 -05:00
Jeff Carr b71471c61e more general work for patches 2025-09-07 22:49:23 -05:00
Jeff Carr ca3a70d623 move to new httppd package 2025-09-07 21:40:54 -05:00
Jeff Carr db758bbed2 work on "forge pull" 2025-09-07 12:05:36 -05:00
Jeff Carr 3df2601f27 don't do patches unless in 'normal' state 2025-09-06 22:50:20 -05:00
Jeff Carr 5c6bbcdbb2 cli user interface 2025-09-06 21:50:43 -05:00
Jeff Carr f6a79c3be8 apply patches working more smoothly 2025-09-06 20:08:28 -05:00
Jeff Carr a6c0edb89d minor fix 2025-09-06 19:26:49 -05:00
Jeff Carr d99eb81385 start checking the "normal" state every time 2025-09-06 19:21:00 -05:00
Jeff Carr 1087b39f9c start reporting applied patches 2025-09-06 17:14:46 -05:00
Jeff Carr 893c88bbf5 patches are starting to work 2025-09-06 16:05:15 -05:00
Jeff Carr e5a2bec217 cleanup after crazy "http://foo.com//blah" issue 2025-09-06 15:21:36 -05:00
Jeff Carr e713541b35 better output 2025-09-05 13:11:52 -05:00
Jeff Carr 7f831e90d2 more on patches 2025-09-05 13:02:00 -05:00
Jeff Carr e639f7d7b7 set patch NewHash 2025-09-05 12:53:05 -05:00
Jeff Carr 790c48e0d0 debugging merge 2025-09-05 02:00:42 -05:00
Jeff Carr 809dfe7fa3 more patch tracking 2025-09-05 01:59:00 -05:00
Jeff Carr 04028e6181 more stuff 2025-09-05 01:24:20 -05:00
Jeff Carr c5025d25b2 stuff 2025-09-04 22:48:32 -05:00
Jeff Carr e09849b1a2 try to report old patches 2025-09-04 22:37:55 -05:00
Jeff Carr 5dbfed7a31 detect patches that are applied already 2025-09-04 22:16:50 -05:00
Jeff Carr c9d732800a always rewrite the pb here 2025-09-04 21:49:54 -05:00
Jeff Carr 13aff0d5dc try to figure out why this fails 2025-09-04 21:34:39 -05:00
Jeff Carr 4063e03108 not sure 2025-09-04 20:39:25 -05:00
Jeff Carr f789b58389 continue to identify a new user 2025-09-04 20:18:40 -05:00
Jeff Carr 770e6752f1 add 'forge help' 2025-09-04 20:02:23 -05:00
Jeff Carr c381a8d179 trap potential panics 2025-09-04 18:57:31 -05:00
Jeff Carr 3a5e6ae51c testing Width 2025-09-04 18:47:37 -05:00
Jeff Carr 78e883a106 more work on patches 2025-09-04 18:32:01 -05:00
Jeff Carr 19ce7eef56 patch work 2025-09-04 15:10:28 -05:00
Jeff Carr 05f798dea3 better instructions 2025-09-04 14:43:56 -05:00
Jeff Carr 5b3764c1b7 cleaned up merge output to stdout 2025-09-04 10:40:26 -05:00
Jeff Carr 0517f01aa9 print times for how long merge & checkout take 2025-09-04 10:25:09 -05:00
Jeff Carr 7697ab186a add doCheckout() back 2025-09-04 09:58:36 -05:00
Jeff Carr d2010b859a finally some sensible merge() code 2025-09-04 09:20:19 -05:00
Jeff Carr 5fec66f97c lots more code cleanups 2025-09-04 09:15:32 -05:00
Jeff Carr 680069d4ca a breath of fresh air. finally can remove all the old code. 2025-09-04 08:28:09 -05:00
Jeff Carr b020604931 fix panic() 2025-09-04 08:04:25 -05:00
Jeff Carr 9a32a7fe0b rm junk 2025-09-04 00:13:17 -05:00
Jeff Carr 7081a04d58 stub in routines to find patches 2025-09-03 21:37:55 -05:00
Jeff Carr b847d9aa6c remove old code 2025-09-03 19:39:02 -05:00
Jeff Carr aea7f16891 dump more code cruft 2025-09-03 19:22:49 -05:00
Jeff Carr 6f4ec05ccb common code moved to forgepb 2025-09-03 19:06:08 -05:00
Jeff Carr e896cae995 fixing gocui plugin exit() 2025-09-03 17:31:22 -05:00
Jeff Carr f6803f07f1 rewrite core ENV configuration 2025-09-03 13:53:46 -05:00
Jeff Carr 30dcc8af9f working on patch handling 2025-09-03 05:50:18 -05:00
Jeff Carr 557a5853a4 duh. wrong args 2025-09-03 04:02:37 -05:00
Jeff Carr d4e65a581d go vet doesn't work on darwin (?) 2025-08-31 18:26:14 -05:00
Castor Regex c7a12cbdd3 fix(forge): replace strings.SplitSeq for broader compatibility 2025-08-31 18:06:56 -05:00
Jeff Carr 6d249abb44 standard rill() with stats 2025-08-31 15:54:19 -05:00
Jeff Carr f754a93493 cleanups for "forge normal" 2025-08-31 15:05:27 -05:00
Jeff Carr a84db9a788 rm old code 2025-08-31 14:15:38 -05:00
Jeff Carr 731a4d9da8 better output with 'forge normal' 2025-08-31 13:27:39 -05:00
Jeff Carr c4252d2103 maybe locks will work. maybe I can make a global repos.pb file? 2025-08-31 12:16:38 -05:00
Jeff Carr 9292eb18fa still need to work on this 2025-08-29 17:37:16 -05:00
Jeff Carr b70417565a fix branch names if they somehow end up blank 2025-08-29 10:28:58 -05:00
Jeff Carr 9bcf2d968c add "forge normal" to reset things to default development state 2025-08-28 10:11:31 -05:00
Jeff Carr a21c117e5b cleanup gui() 2025-08-28 04:26:22 -05:00
Jeff Carr 86c342b163 more stuff 2025-08-26 11:42:47 -05:00
Jeff Carr d8b615f5b9 minor 2025-08-22 01:44:10 -05:00
Jeff Carr d1573f83ab quiet 'forge patch' 2025-08-22 00:50:05 -05:00
Jeff Carr d9ebcefd2f quiet 'git delete' output except on error 2025-08-21 23:29:25 -05:00
Jeff Carr b83a5d491f rename to 'Namespace' 2025-08-21 22:43:28 -05:00
Jeff Carr eb9dea5dd0 almost there 2025-08-21 14:40:34 -05:00
Jeff Carr 43c6af4a8b common patch submit function 2025-08-21 14:28:23 -05:00
Jeff Carr e928cee038 printf error 2025-08-21 14:25:49 -05:00
Jeff Carr f17ccce780 attempt to fix URL 2025-08-21 14:23:01 -05:00
Jeff Carr 31c2a90f02 save the file 2025-08-21 12:30:26 -05:00
Jeff Carr 80dd77a6c8 more cleanups on doClean() 2025-08-20 11:44:32 -05:00
Jeff Carr 12a6696960 something simple 2025-08-20 00:01:23 -05:00
Jeff Carr cf83bbf6ac more cleanups on usefulness 2025-08-19 22:22:33 -05:00
Jeff Carr 47cd26fbab more "commit" tests 2025-08-19 22:20:40 -05:00
Jeff Carr 650ecc6aab formatting cleanup 2025-08-19 21:54:01 -05:00
Jeff Carr c0e82648db formatting fix 2025-08-19 21:52:35 -05:00
Jeff Carr 2b7257fd2d better formatting 2025-08-19 21:20:57 -05:00
Jeff Carr dad2e089c3 finally fixed? 2025-08-19 21:18:44 -05:00
Jeff Carr ffe615ef2a okay 2025-08-19 21:12:37 -05:00
Jeff Carr 456de36547 more tests 2025-08-19 21:10:30 -05:00
Jeff Carr 46c617a31a more small fixes 2025-08-19 21:08:45 -05:00
Jeff Carr ef8240da56 simple fix 2025-08-19 21:07:33 -05:00
Jeff Carr d1c9436e45 cleaning up default behavior 2025-08-19 21:04:57 -05:00
Jeff Carr 631544356a attempt git am --abort 2025-08-17 23:24:37 -05:00
Jeff Carr 212c706e14 shortcuts to work until cloudflare issue resolved 2025-08-17 22:59:53 -05:00
Jeff Carr b06aea8115 smarter checks for the start hash 2025-08-16 22:05:00 -05:00
Jeff Carr 2bd007d0c0 stub in something to check the versions 2025-08-16 18:58:52 -05:00
Jeff Carr a21d17dda9 check for "normal" repo states 2025-07-21 22:24:59 -05:00
Jeff Carr 5b5d262ce4 docs + fixes to Rill() 2025-07-21 21:05:25 -05:00
Jeff Carr 8c25ed19a3 doc reformat 2025-07-21 13:00:11 -05:00
Jeff Carr 2c27181bb5 better url 2025-07-09 23:24:32 -05:00
Jeff Carr 3efb520fe0 first draft 2025-07-08 17:17:05 -05:00
Jeff Carr 0dea6a2553 detect repos to update 2025-07-08 15:48:22 -05:00
Jeff Carr 807049f6c7 add 'forge merge master' 2025-07-07 23:07:12 -05:00
Jeff Carr 86e513d845 minor 2025-07-07 20:39:34 -05:00
Jeff Carr 5d57a8968d s/GitPull/Pull/ 2025-07-07 20:27:10 -05:00
Jeff Carr e2d33ea496 move argv "sync" under "pull" 2025-07-07 20:14:24 -05:00
Jeff Carr d3938adf63 deprecate the "forge mode" concept
unfortunately, this was a bad idea. boo
	I wasted lots of time on this. live and learn
2025-07-07 19:58:34 -05:00
Jeff Carr 08b7f2406c more on 'forge pull' 2025-07-07 17:34:10 -05:00
Jeff Carr 041be81af1 start a "httpPB" scheme 2025-07-02 12:51:00 -05:00
Jeff Carr 4fc9d038ca working out testing 2025-07-01 19:03:55 -05:00
Jeff Carr 1ea9bdf841 deprecate old stuff 2025-07-01 18:54:41 -05:00
Jeff Carr 1d50f9eb69 rename 2025-07-01 18:37:11 -05:00
Jeff Carr d962ff8db0 deprecate me.found 2025-07-01 18:36:48 -05:00
Jeff Carr d2a0aa3098 more doFind() cleanups 2025-07-01 18:23:38 -05:00
Jeff Carr 47b7222445 todo: redo patch handling against forge.wit.com 2025-06-30 07:44:07 -05:00
Jeff Carr 2616d0d8b4 add a way to debug protobuf Marshal() errors 2025-06-29 02:48:47 -05:00
Jeff Carr 0219d69bfb make forge dir 2025-06-26 17:58:07 -05:00
Jeff Carr 391d47318b fix "patchable" repos button 2025-05-31 21:53:14 -05:00
Jeff Carr 1282c17e81 rearrange main window 2025-05-31 16:03:00 -05:00
Jeff Carr 9f367cb39b drop old code. rearrange buttons 2025-05-31 14:38:55 -05:00
Jeff Carr 2f8da5a8be move buttons around 2025-05-31 12:49:19 -05:00
Jeff Carr 912c5a9bb9 code rearrange 2025-05-31 11:35:49 -05:00
Jeff Carr 715b63b1c8 rename 2025-05-31 11:35:04 -05:00
Jeff Carr ad5d8dbb87 redo repos window 2025-05-31 11:31:11 -05:00
Jeff Carr 8a24141803 fix for --purge 2025-05-29 19:21:27 -05:00
Jeff Carr 1c733adfce minor 2025-05-23 03:27:43 -05:00
Jeff Carr 1defa8d582 use the local build by default 2025-04-30 14:40:05 -05:00
Jeff Carr f061bf9730 add commit --submit=false option 2025-04-29 17:49:05 -05:00
Jeff Carr 6d62858d69 code re-arrange 2025-04-29 17:18:02 -05:00
Jeff Carr cc8800bf60 merge this into a single window 2025-04-24 19:28:04 -05:00
Jeff Carr ef6da4aa17 use RillRepo() 2025-04-20 21:45:03 -05:00
Jeff Carr 52c9fece43 add 'forge sync' 2025-04-20 20:41:24 -05:00
Jeff Carr 06cf0f7d84 fix defective google "Git on Borg" repos 2025-04-04 06:07:56 -05:00
Jeff Carr 99e30376f0 button to show all patches 2025-03-25 13:17:00 -05:00
Jeff Carr aed4c92713 window with only unapplied patches 2025-03-23 09:30:34 -05:00
Jeff Carr edc362f4b9 rename 2025-03-23 08:51:06 -05:00
Jeff Carr 033e81bb22 window to show unapplied patches 2025-03-23 08:47:34 -05:00
Jeff Carr 682e06590f lookup the new hash for applied patches 2025-03-23 04:47:21 -05:00
Jeff Carr dd2f7ed01d list branches 2025-03-22 21:37:45 -05:00
Jeff Carr 205b1fe1ed add hostname 2025-03-22 08:30:06 -05:00
Jeff Carr fd9cefdb76 switch some things to Iter()
The Iter() syntax is nice and simple. In some
	cases, the Scan() / Next() syntax is more readable
	and maybe better for new programmers to understand. notsure
2025-03-19 07:05:16 -05:00
Jeff Carr 9aa086e7c4 golang 1.24 'iter' 2025-03-19 06:40:02 -05:00
Jeff Carr 4b2ac683b7 start using IterBy() 2025-03-18 15:13:55 -05:00
Jeff Carr 96aa1f09ea wrong debian control file syntax 2025-03-12 10:18:09 -05:00
41 changed files with 2589 additions and 2515 deletions

1
.gitignore vendored
View File

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

View File

@ -6,25 +6,27 @@ BUILDTIME = $(shell date +%Y.%m.%d_%H%M)
# make gocui # try the ncurses gui plugin
# make andlabs # try the andlabs gui plugin (uses GTK)
default: gocui
#forge
default: install-verbose tag
tag:
forge tag list
vet:
@GO111MODULE=off go vet
@echo this go binary package builds okay
verbose:
GO111MODULE=off go build -v -x \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
build: goimports vet plugin
GO111MODULE=off go build \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install: goimports vet plugin
install: goimports plugin
GO111MODULE=off go install \
-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
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 \
@ -34,12 +36,17 @@ plugin:
rm -f resources/*.so
# -cp ../../toolkits/gocui/gocui.so resources/
andlabs: clean install
GTK: clean install
forge --gui andlabs
gocui: install
forge --gui gocui --gui-verbose >/tmp/forge.log 2>&1
# forge --gui gocui --gui-verbose --debugger
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:
reset
@ -50,10 +57,30 @@ goimports:
clean:
-rm -f forge go.*
# -rm -f ~/go/src/repos.pb
go-mod-clean --purge
go-mod-clean purge
identify-protobuf:
autogenpb --identify ~/go/src/repos.pb
devel:
forge clean devel --force --verbose
pull: install
FORGE_URL="https://forge.grid.wit.com/" forge pull check
# cloudflare blocks POST due to captcha checks / human detection?
# POST must be direct socket. probably for the best anyway
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

View File

@ -1,10 +1,31 @@
# forge
## Design Goals
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
@ -16,14 +37,6 @@ expiremental work on federated git
* uses a GUI or the console(console display needs work)
* always wrap around 'git' -- it basically just types 'git' commands really fast
## Development Goals
* have a GUI that also works on the command line
## Install:
* go install go.wit.com/apps/forge@latest
## building from sources
```
@ -49,3 +62,18 @@ make # this runs GO111MODULE=off go build insuring that y
## 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

@ -3,17 +3,6 @@
package main
import (
"os"
"path/filepath"
"strings"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
/*
// saves the patches in ~/.config/forge/currentpatches/
func savePatchset(pset *forgepb.Patchset) error {
@ -23,42 +12,10 @@ func savePatchset(pset *forgepb.Patchset) error {
log.Info("savePatches() GIT_AUTHOR_EMAIL", pset.GetGitAuthorEmail())
log.Info("savePatches() Branch Name", pset.GetStartBranchName())
log.Info("savePatches() Start Hash", pset.GetStartBranchHash())
var count int
var bad int
var lasterr error
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
basedir := filepath.Join(os.Getenv("FORGE_CONFIG"), "currentpatches")
if fullname, err := savePatchFile(p, basedir); err != nil {
log.Info(fullname, "save failed", err)
bad += 1
lasterr = err
}
count += 1
}
log.Info("pset has", count, "total patches, ", bad, "bad save patches")
if bad == 0 {
return lasterr
}
return nil
}
*/
// re-run git CheckDirty() on everything
func IsAnythingDirty() bool {
me.found = new(gitpb.Repos)
findAll() // select all the repos
doCheckDirtyAndConfigSave()
found := findDirty()
if found.Len() == 0 {
return false
} else {
return true
}
}
/*
// From 18ee541f89be2e9f9a91c54873da87885e8ffdf5 Mon Sep 17 00:00:00 2001
// From: Jeff Carr <jcarr@wit.com>
// Date: Sun, 5 Jan 2025 01:18:47 -0600
@ -72,84 +29,6 @@ func countCurrentPatches(repo *gitpb.Repo) int {
return len(result.Stdout)
}
func applyPatchsetOLD(pset *forgepb.Patchset) error {
var everythingworked bool = true
tmpdir, err := os.MkdirTemp("", "forge")
if err != nil {
return err
}
// log.Info("got to applyPatches() pset", pset)
log.Info("applyPatches() NAME", pset.Name)
log.Info("applyPatches() COMMENT", pset.Comment)
log.Info("applyPatches() GIT_AUTHOR_NAME", pset.GetGitAuthorName())
log.Info("applyPatches() GIT_AUTHOR_EMAIL", pset.GetGitAuthorEmail())
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
basedir := me.forge.GetGoSrc()
if fullname, err := savePatchFile(p, basedir); err != nil {
log.Info(fullname, "save failed", err)
continue
} else {
basedir, filename := filepath.Split(fullname)
cmd := []string{"git", "am", filename}
log.Info("Should run: at", basedir, ":", cmd)
log.Info(basedir, filename)
result := shell.PathRun(basedir, cmd)
for _, line := range result.Stdout {
log.Warn("stdout:", line)
}
for _, line := range result.Stderr {
log.Warn("stderr:", line)
}
}
everythingworked = false
}
if everythingworked {
os.RemoveAll(tmpdir) // clean up
}
log.Info("THIS IS THE END MY FRIEND")
return nil
}
func savePatchFile(p *forgepb.Patch, basedir string) (string, error) {
basepath, filename := filepath.Split(p.Filename)
fulldir := filepath.Join(basedir, basepath)
err := os.MkdirAll(fulldir, os.ModePerm)
if err != nil {
log.Info("applyPathces() MkdirAll failed for", fulldir)
log.Info("applyPathces() MkdirAll failed err", err)
return "", err
}
tmpname := filepath.Join(fulldir, filename)
log.Info("pset filename FILENAME IS REAL?", tmpname)
raw, _ := os.OpenFile(tmpname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
raw.Write(p.Data)
raw.Close()
return tmpname, nil
}
func readPatchFile(pbfile string) (*forgepb.Patchset, error) {
bytes, err := os.ReadFile(pbfile)
if err != nil {
log.Info("readfile error", pbfile, err)
return nil, err
}
return handleBytes(bytes)
}
func handleBytes(bytes []byte) (*forgepb.Patchset, error) {
var pset *forgepb.Patchset
pset = new(forgepb.Patchset)
err := pset.Unmarshal(bytes)
if err != nil {
log.Info("Unmarshal failed", err)
return nil, err
}
return pset, nil
}
func doRegister(newurl string) error {
var url string
url = me.urlbase + "/register?url=" + newurl
@ -166,3 +45,4 @@ func doRegister(newurl string) error {
}
return nil
}
*/

139
argv.go
View File

@ -6,6 +6,8 @@ package main
import (
"fmt"
"os"
"go.wit.com/lib/gui/prep"
)
/*
@ -15,15 +17,20 @@ import (
var argv args
type args struct {
Help *EmptyCmd `arg:"subcommand:help" help:"New to forge? This is for you.'"`
Checkout *CheckoutCmd `arg:"subcommand:checkout" help:"switch branches using 'git checkout'"`
Clean *CleanCmd `arg:"subcommand:clean" help:"start over at the beginning"`
Commit *EmptyCmd `arg:"subcommand:commit" help:"'git commit' but errors out if on wrong branch"`
Commit *CommitCmd `arg:"subcommand:commit" help:"'git commit' but errors out if on wrong branch"`
Config *ConfigCmd `arg:"subcommand:config" help:"show your .config/forge/ settings"`
Dirty *DirtyCmd `arg:"subcommand:dirty" help:"show repos git says are dirty"`
Dirty *DirtyCmd `arg:"subcommand:dirty" help:"show dirty git repos"`
GitFetch *FindCmd `arg:"subcommand:fetch" help:"run 'git fetch master'"`
Gui *EmptyCmd `arg:"subcommand:gui" help:"open the gui"`
List *FindCmd `arg:"subcommand:list" help:"print a table of the current repos"`
Merge *MergeCmd `arg:"subcommand:merge" help:"merge branches"`
Normal *NormalCmd `arg:"subcommand:normal" help:"set every repo to the default state for software development"`
Patch *PatchCmd `arg:"subcommand:patch" help:"make patchsets"`
GitPull *FindCmd `arg:"subcommand:pull" help:"run 'git pull'"`
Pull *PullCmd `arg:"subcommand:pull" help:"run 'git pull'"`
Tag *TagCmd `arg:"subcommand:tag" help:"manage git tags"`
URL string `arg:"--connect" help:"forge url"`
All bool `arg:"--all" help:"git commit --all"`
Build string `arg:"--build" help:"build a repo"`
@ -31,24 +38,25 @@ type args struct {
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"`
Bash bool `arg:"--bash" help:"generate bash completion"`
BashAuto []string `arg:"--auto-complete" help:"todo: move this to go-arg"`
// Show string `arg:"--show" help:"show a repo"`
}
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 {
Delete *EmptyCmd `arg:"subcommand:delete" help:"rescan repo"`
Devel *CleanDevelCmd `arg:"subcommand:devel" help:"clean and verify the devel branches"`
Force *EmptyCmd `arg:"subcommand:force" help:"do destructive stuff"`
GitReset *EmptyCmd `arg:"subcommand:git-reset" help:"git reset --hard"`
Pub *EmptyCmd `arg:"subcommand:pub" help:"clean target version numbers"`
User *EmptyCmd `arg:"subcommand:user" help:"clean the user branches"`
Repo string `arg:"--repo" help:"which repo to look at"`
Verify *EmptyCmd `arg:"subcommand:verify" help:"rescan repo"`
Repo string `arg:"--repo" help:"which repo to look at"`
}
type CleanDevelCmd struct {
@ -56,10 +64,28 @@ type CleanDevelCmd struct {
}
type PatchCmd struct {
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 string `arg:"--submit" help:"submit your commits"`
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 {
@ -91,6 +117,12 @@ type CheckoutCmd struct {
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 {
}
@ -101,6 +133,7 @@ type FindCmd struct {
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"`
}
@ -110,25 +143,63 @@ func (args) Version() string {
func (a args) Description() string {
return `
forge -- a tool to git repos at go.wit.com
forge -- a tool to manage lots of git repos. forge includes a GUI and TUI.
but you can probably use it for other things
`
forge only executes the 'git' command. Everything it does, you can run by hand with 'git'.
`
}
func (args) doBashHelpDebug() {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "hello world")
var more string
p0 := "0" + argv.BashAuto[0]
p1 := "1" + argv.BashAuto[1]
p2 := "2" + argv.BashAuto[2]
if len(argv.BashAuto[1]) >= 0 {
more = "more"
} else {
more = "less"
// 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")
}
}
p1a := fmt.Sprintf("1a.%s.%+v.\n", argv.BashAuto[1], len(argv.BashAuto[1]))
fmt.Fprintln(os.Stderr, "pull something else", argv.BashAuto, len(argv.BashAuto), p0, p1, p2, p1a, "end", more)
fmt.Fprintln(os.Stderr, "")
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)
}

View File

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

3
build
View File

@ -3,3 +3,6 @@
# 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

36
config.go Normal file
View File

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

@ -3,6 +3,6 @@ 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
Depends: go-gui-toolkits, autogenpb, go-mod-clean, go-clone
Recommends: go-clone
Description: 'forge' an attempt at federated 'git'

88
debug.go Normal file
View File

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

View File

@ -5,354 +5,43 @@ 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"
)
var ErrorNotAllReposOnMaster error = fmt.Errorf("not all repos on are on the master branch")
var ErrorNotAllReposOnDevel error = fmt.Errorf("not all repos on are on the devel branch")
var ErrorNotAllReposOnUser error = fmt.Errorf("not all repos on are on the user branch")
func IsEverythingOnMaster() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
all := me.forge.Repos.All()
for all.Scan() {
repo := all.Next()
total += 1
if repo.GetMasterBranchName() == repo.GetCurrentBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
// log.Info(ErrorNotAllReposOnMaster)
return total, count, nope, ErrorNotAllReposOnMaster
}
return total, count, nope, nil
}
func IsEverythingOnDevel() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
all := me.forge.Repos.All()
for all.Scan() {
repo := all.Next()
total += 1
if repo.GetDevelBranchName() == repo.GetCurrentBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
return total, count, nope, ErrorNotAllReposOnDevel
}
return total, count, nope, nil
}
func IsEverythingOnUser() (int, int, int, error) {
var total int
var count int
var nope int
// first make sure every repo is on the master branch
all := me.forge.Repos.All()
for all.Scan() {
repo := all.Next()
total += 1
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
count += 1
} else {
nope += 1
}
}
if total != count {
return total, count, nope, ErrorNotAllReposOnUser
}
return total, count, nope, nil
}
func doGitReset() {
all := me.found.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
// log.Info("is readonly", repo.GetGoPath())
if repo.CheckDirty() {
log.Info("is readonly and dirty", repo.GetGoPath())
cmd := []string{"git", "reset", "--hard"}
repo.RunRealtime(cmd)
}
} else {
// log.Info("is not readonly", repo.GetGoPath())
}
}
}
func rillCheckoutUser(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on devel branch. have to move them there first for now
// return repo.CheckoutDevel()
}
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
// repo is already on user branch
return nil
}
if err := repo.CheckoutUser(); err != nil {
log.Info(repo.GetFullPath(), err)
return err
}
return nil
}
// trys to figure out if there is still something to update
func doAllCheckoutUser() error {
now := time.Now()
me.forge.RillFuncError(rillCheckoutUser)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, err := IsEverythingOnUser()
log.Printf("User branch check. %d total repos. (%d ok) (%d not on user branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on user
me.found = new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
me.found.AppendByGoPath(repo)
}
}
me.forge.PrintHumanTable(me.found)
log.Printf("There are %d repos that are NOT on the user branch\n", me.found.Len())
return err
}
return nil
}
func rillCheckoutDevel(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetDevelBranchName() {
// repo is already on devel branch
return nil
}
repo.CheckoutDevel()
return nil
}
// is every repo on the devel branch?
func doAllCheckoutDevel() error {
now := time.Now()
log.Info("going to rill:")
me.forge.RillFuncError(rillCheckoutDevel)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, err := IsEverythingOnDevel()
log.Printf("Devel branch check. %d total repos. (%d ok) (%d not on devel branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on user
me.found = new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
me.found.AppendByGoPath(repo)
}
}
me.forge.PrintHumanTable(me.found)
log.Printf("There are %d repos that are NOT on the devel branch\n", me.found.Len())
return err
}
return nil
}
func rillCheckoutMaster(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
// repo is already on master
return nil
}
if repo.GetUserVersion() == "uerr" {
repo.CheckoutMaster()
return nil
}
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
// skip other checks for readonly repos
repo.CheckoutMaster()
return nil
}
/*
if repo.GetUserVersion() != repo.GetDevelVersion() {
// don't switch branches if the user branch has uncommitted patches
return nil
}
if repo.GetDevelVersion() != repo.GetMasterVersion() {
// don't switch braches if the devel branch does not match master (needs merge)
return nil
}
*/
repo.CheckoutMaster()
return nil
}
// trys to figure out if there is still something to update
func doAllCheckoutMaster() error {
now := time.Now()
me.forge.RillFuncError(rillCheckoutMaster)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, err := IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now)))
if err != nil {
// display all repos not on master
me.found = new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
me.found.AppendByGoPath(repo)
}
}
me.forge.PrintHumanTable(me.found)
log.Printf("There are %d repos that are NOT on the master branch\n", me.found.Len())
return err
}
return nil
}
// shared this with the GUI and the command line?
func doCheckoutShared() error {
if me.argvCheckoutUser {
// log.Info("Starting git checkout user")
if argv.Force {
log.Info("going to force create user branches")
if err := makeUserBranches(); err != nil {
return err
}
}
// this uses rill and is super fast
doAllCheckoutUser()
return nil
}
if me.argvCheckoutDevel {
// log.Info("Starting git checkout devel")
if argv.Force {
log.Info("going to force create devel branches")
makeDevelBranches()
}
// this uses rill and is super fast
doAllCheckoutDevel()
return nil
}
if me.argvCheckoutMaster {
log.Info("Starting git checkout master")
doAllCheckoutMaster()
return nil
}
log.Info("Forge didn't know what branches to checkout")
return nil
}
// trys to figure out if there is still something to update
// todo: redo this logic as it is terrible
func doCheckout() error {
if argv.Checkout.User != nil {
me.argvCheckoutUser = true
me.forge.Config.Mode = forgepb.ForgeMode_USER
me.forge.Config.ConfigSave()
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 {
me.argvCheckoutDevel = true
me.forge.Config.Mode = forgepb.ForgeMode_DEVEL
me.forge.Config.ConfigSave()
// setForgeMode(forgepb.ForgeMode_DEVEL)
if err := me.forge.DoAllCheckoutDevelNew(argv.Force); err != nil {
badExit(err)
}
okExit("")
}
if argv.Checkout.Master != nil {
me.argvCheckoutMaster = true
me.forge.Config.Mode = forgepb.ForgeMode_MASTER
me.forge.Config.ConfigSave()
}
if err := doCheckoutShared(); err != nil {
badExit(err)
}
okExit("")
return nil
}
setForgeMode(forgepb.ForgeMode_MASTER) // disable "normal" mode if set
func makeDevelBranches() error {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
branch := repo.GetDevelBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
continue
if err := me.forge.DoAllCheckoutMaster(); err != nil {
badExit(err)
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
continue
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
}
return nil
}
func makeUserBranches() error {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
branch := repo.GetUserBranchName()
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
continue
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
cmd := []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
continue
}
cmd := []string{"git", "branch", branch}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", branch}
repo.RunVerbose(cmd)
okExit("")
}
badExit(fmt.Errorf("did not specify what branch to checkout"))
return nil
}

View File

@ -6,101 +6,160 @@ 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"
)
var ErrorReposHasLocalBranches error = fmt.Errorf("repo still has local branches")
var ErrorMergeBranch error = fmt.Errorf("trunk has things not in the branch")
var ErrorMergeTrunk error = fmt.Errorf("branch has things not in trunk")
func doClean() error {
if argv.Clean.Pub != nil {
if err := doCleanPub(); err != nil {
badExit(err)
}
log.Info("finished attempt at cleaning devel branches")
return nil
func checkRemoteBranches(repo *gitpb.Repo) error {
if err := repo.ReloadCheck(); err != nil {
log.Info("need to reload", repo.FullPath)
}
if argv.Clean.Devel != nil {
if err := doCleanDevel(); err != nil {
badExit(err)
}
log.Info("finished attempt at cleaning devel branches")
return nil
if repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName()) {
} else {
return log.Errorf("remote devel is out of sync with local: todo: git pull or git fetch")
}
if argv.Clean.User != nil {
if err := doCleanUser(); err != nil {
log.Info(err)
okExit("")
}
return nil
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
}
func doCleanUser() error {
if _, count, _, err := IsEverythingOnMaster(); err != nil {
if count == 0 {
log.Info("No repos are on the master branch")
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.Info("Not all repos are on the master branch")
// return 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
}
var anyerr error
// 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 err := doCleanUserRepo(repo); err != nil {
log.Info(repo.GetGoPath(), err)
anyerr = err
}
}
return anyerr
}
/*
func doesLocalBranchExist(repo *gitpb.Repo, branch string) bool {
return repo.Exists(filepath.Join(".git/refs/heads", branch))
}
*/
func doCleanDevel() error {
var total int
var count int
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
total += 1
if !repo.IsLocalBranch(repo.GetDevelBranchName()) {
// there is no local branch named 'devel'
continue
}
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
log.Info("Repo not on master branch:", repo.GetGoPath())
continue
}
if repo.IsDirty() {
log.Info("Repo is dirty:", repo.GetGoPath())
continue
}
count += 1
if err := justDeleteTheDevelBranchAlready(repo); err != nil {
log.Info("justDeleteTheDevel() err", repo.GetGoPath(), err)
// 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
}
}
log.Info("")
log.Printf("attempted cleaning %d devel branches of %d total branches\n", count, total)
// 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
}
// removes all local branches
func doCleanUserRepo(repo *gitpb.Repo) error {
/*
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
}
@ -118,33 +177,38 @@ func doCleanUserRepo(repo *gitpb.Repo) error {
return nil
}
log.Info("trying to delete", bruser, repo.GetUserVersion())
b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", bruser}
log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
return fmt.Errorf("%s branch has things not in %s count=%d", bruser, brdevel, b1)
}
// hack to cleanup release versioning info
func doCleanPub() error {
total := 0
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetTargetVersion() != "" {
repo.SetTargetVersion("")
configSave = true
total += 1
// 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
}
}
log.Printf("clearing %d total repos\n", total)
return nil
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
@ -165,8 +229,8 @@ func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
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.RunVerbose(cmd)
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
cmd := []string{"git", "push"}
@ -177,15 +241,32 @@ func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
// remote doesn't exist, check against master
master := repo.GetMasterBranchName()
b1 := repo.CountDiffObjects(branch, master) // should be zero
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.RunVerbose(cmd)
// 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 NEEDS GIT MERGE TO MASTER", repo.GetGoPath(), cmd, b1)
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

@ -6,46 +6,43 @@ 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() {
func doCommit() error {
if argv.All {
log.Info("do a commit everywhere")
doCheckDirtyAndConfigSave()
found := findDirty()
all := found.All()
found := me.forge.CheckDirty()
var newpatches bool
for all.Scan() {
repo := all.Next()
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 {
// if there are enw patches, autocommit them
_, err := me.forge.SubmitDevelPatchSet("forge auto commit")
if err != nil {
badExit(err)
}
config.SetChanged("repos", true)
return doPatchSubmit()
}
okExit("")
}
pwd, _ := os.Getwd()
repo := me.forge.Repos.FindByFullPath(pwd)
if repo == nil {
log.Info("todo: forge doesn't know how to work here yet")
okExit("")
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() {
me.found.Append(repo)
me.forge.PrintHumanTable(me.found)
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("")
@ -66,13 +63,15 @@ func doCommit() {
if err := shell.ExecCheck([]string{"git", "commit", "--all"}); err != nil {
badExit(err)
}
log.Info("git commit ok. forge done")
return doPatchSubmit()
}
func doCommitRepo(repo *gitpb.Repo) error {
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
me.found.Append(repo)
me.forge.PrintHumanTable(me.found)
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("")
@ -92,6 +91,7 @@ func doCommitRepo(repo *gitpb.Repo) error {
}
if err := shell.ExecCheck([]string{"git", "commit", "--all"}); err != nil {
shell.ExecCheck([]string{"git", "reset"})
return err
}
log.Info("git commit ok. forge done")

View File

@ -21,13 +21,16 @@ func doConfig() {
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)
/*
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 {
@ -51,6 +54,8 @@ func doConfig() {
os.Exit(0)
}
log.Info("config.PathLock =", me.forge.Config.PathLock)
me.forge.ConfigPrintTable()
okExit("")
}

View File

@ -3,62 +3,14 @@
package main
import (
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doDirty() {
findAll() // select all the repos
doCheckDirtyAndConfigSave()
found := findDirty()
found := me.forge.CheckDirty()
if found.Len() == 0 {
return
}
if argv.Verbose {
me.forge.PrintHumanTableDirty(found)
} else {
me.forge.PrintHumanTable(found)
}
}
func straightCheckDirty() int {
var count int
// var total int
// now := time.Now()
all := me.found.All()
for all.Scan() {
repo := all.Next()
// total += 1
if repo.IsDirty() {
count += 1
}
}
// log.Printf("rill dirty check (%d dirty repos) (%d total repos) took:%s\n", count, total, shell.FormatDuration(time.Since(now)))
return count
}
func doCheckDirty(repo *gitpb.Repo) error {
repo.CheckDirty()
// reset these in here for now
if repo.GetTargetVersion() != "" {
repo.TargetVersion = ""
me.forge.SetConfigSave(true)
}
return nil
}
func doCheckDirtyAndConfigSave() {
start := straightCheckDirty()
now := time.Now()
me.forge.RillFuncError(doCheckDirty)
end := straightCheckDirty()
log.Printf("dirty check (%d dirty repos) (%d total repos) took:%s\n", end, me.found.Len(), shell.FormatDuration(time.Since(now)))
if start != end {
// todo: use internal forgepb configsave flag. should work?
me.forge.SetConfigSave(true)
me.forge.ConfigSave()
}
}

198
doFind.go Normal file
View File

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

404
doGui.go
View File

@ -18,105 +18,38 @@ import (
"go.wit.com/log"
)
func 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()
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
found.AppendByGoPath(repo)
}
tmp := fmt.Sprintf("writable (%d)", found.Len())
me.repoWritableB.SetLabel(tmp)
}
doCheckDirtyAndConfigSave()
found := findDirty()
dirty := found.Len()
if me.repoDirtyB != nil {
tmp := fmt.Sprintf("dirty (%d)", dirty)
me.repoDirtyB.SetLabel(tmp)
}
if me.reposWinB != nil {
tmp := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len())
if dirty > 0 {
tmp = fmt.Sprintf("Repos (%d) (%d dirty)", me.forge.Repos.Len(), dirty)
}
me.reposWinB.SetLabel(tmp)
}
log.Printf("finished a forge scan here in (%s)\n", shell.FormatDuration(time.Since(now)))
time.Sleep(90 * time.Second)
}
}
func doGui() {
if me.forge.Config.GetDefaultGui() == "" {
me.forge.Config.DefaultGui = "gocui"
me.forge.ConfigSave()
}
me.myGui = gui.New()
me.myGui.InitEmbed(resources)
me.myGui.SetAppDefaultPlugin(me.forge.Config.DefaultGui) // sets the default GUI plugin to use
me.myGui.Default()
mainWindow := gadgets.NewGenericWindow("Forge: (this kinda works sometimes)", "Current Settings")
mainWindow.Custom = func() {
win := gadgets.NewGenericWindow("Forge: A federated git development tool by WIT.COM", "Current Settings")
win.Custom = func() {
log.Warn("MAIN WINDOW CLOSE")
now := time.Now()
count := me.forge.RillReload()
log.Info("Repo Reload count =", count)
if count != 0 {
me.forge.ConfigSave()
}
log.Printf("rill repos.Reload() took (%s)\n", shell.FormatDuration(time.Since(now)))
os.Exit(0)
okExit("")
}
drawWindow(mainWindow)
// sits here forever
debug()
}
func drawWindow(win *gadgets.GenericWindow) {
grid := win.Group.RawGrid()
me.goSrcPwd = gadgets.NewOneLiner(grid, "repo src home")
grid.NewLabel("")
var howtoWin *gadgets.GenericWindow
me.demoB = grid.NewButton("Howto", func() {
if howtoWin != nil {
howtoWin.Toggle()
return
if me.forge.Config.GetPathLock() {
me.goSrcPwd = gadgets.NewOneLiner(grid, "Working Directory")
me.goSrcPwd.SetText(me.forge.Config.ReposDir)
} 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())
}
howtoWin = makeHowtoWin()
})
grid.NextRow()
}
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")
}
me.goSrcPwd.SetText(me.forge.GetGoSrc())
grid.NextRow()
// use ENV GIT_AUTHOR
me.gitAuthor = gadgets.NewOneLiner(grid, "Git Author")
@ -130,200 +63,82 @@ func drawWindow(win *gadgets.GenericWindow) {
me.gitAuthor.SetText(author)
}
group1 := win.Stack.NewGroup("Forge Mode")
grid = group1.RawGrid()
me.forgeMode = grid.NewLabel("")
me.forgeMode.SetText(me.forge.GetMode())
me.newBranch = grid.NewDropdown()
me.newBranch.AddText("master")
me.newBranch.AddText("devel")
me.newBranch.AddText(me.forge.Config.GetUsername())
me.newBranch.Custom = func() {
me.setBranchB.Disable()
// toggle global values shared by the command line and the gui for doCheckout()
switch me.newBranch.String() {
case "master":
if me.forge.Config.Mode != forgepb.ForgeMode_MASTER {
me.setBranchB.Enable()
}
case "devel":
if me.forge.Config.Mode != forgepb.ForgeMode_DEVEL {
me.setBranchB.Enable()
}
default:
if me.forge.Config.Mode != forgepb.ForgeMode_USER {
me.setBranchB.Enable()
}
}
}
// 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("Switch mode", func() {
win.Disable()
defer win.Enable()
switch me.newBranch.String() {
case "master":
forgeSwitchMode(forgepb.ForgeMode_MASTER)
case "devel":
forgeSwitchMode(forgepb.ForgeMode_DEVEL)
default:
forgeSwitchMode(forgepb.ForgeMode_USER)
}
me.setBranchB.Disable()
})
me.setBranchB.Disable()
grid.NextRow()
groupM := win.Stack.NewGroup("Mode Windows")
gridM := groupM.RawGrid()
gridM := win.Stack.RawGrid()
var releaseWin *gadgets.GenericWindow
gridM.NewButton("Release Window", func() {
log.Info("todo: move releaser here")
log.Info("for now, run guireleaser")
if releaseWin != nil {
releaseWin.Toggle()
var insertWin *gadgets.GenericWindow
s := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len())
me.reposWinB = gridM.NewButton(s, func() {
// if the window exists, just toggle it open or closed
if insertWin != nil {
insertWin.Toggle()
return
}
releaseWin = makeModeMasterWin()
insertWin = makeReposWinNew()
})
/*
// the Devel / Patch mode window
var patchWin *patchesWindow
gridM.NewButton("Patch Window", func() {
if patchWin != nil {
patchWin.Toggle()
return
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
}
patchWin = new(patchesWindow)
patchWin.initWindow()
patchWin.Show()
})
*/
var patches *stdPatchsetTableWin
gridM.NewButton("Patch Window", func() {
if patches != nil {
patches.Toggle()
return
AddAllPatches(notdone, pset, false)
// AddNotDonePatches(notdone, pset, false)
}
patches = makePatchsetsWin()
notdone.PrintTable()
patchesWin = makePatchesWin(notdone)
})
// the user mode "hack Window"
var hackWin *gadgets.GenericWindow
gridM.NewButton("Hack Window", func() {
if hackWin != nil {
hackWin.Toggle()
var pubWin *gadgets.GenericWindow
gridM.NewButton("Publish", func() {
if pubWin != nil {
pubWin.Toggle()
return
}
hackWin = makeHackModeWindow()
pubWin = makePublishWindow()
})
var reposWin *gadgets.GenericWindow
me.reposWinB = gridM.NewButton("Repos", func() {
if reposWin != nil {
reposWin.Toggle()
var oldWin *gadgets.GenericWindow
gridM.NewButton("old", func() {
if oldWin != nil {
oldWin.Toggle()
return
}
reposWin = makeReposWin()
oldWin = makeOldStuff()
})
// set the initial button state based on the last
// forge mode the user saved in the config file
switch me.forge.Config.Mode {
case forgepb.ForgeMode_MASTER:
me.newBranch.SetText("master")
case forgepb.ForgeMode_DEVEL:
me.newBranch.SetText("devel")
case forgepb.ForgeMode_USER:
me.newBranch.SetText(me.forge.Config.GetUsername())
default:
me.newBranch.SetText(me.forge.Config.GetUsername())
}
forgeSwitchMode(me.forge.Config.Mode)
}
// verify the GUI button disable/enable settings
func forgeVerifyGuiState() {
me.forgeMode.SetText(me.forge.GetMode())
me.argvCheckoutUser = false
me.argvCheckoutDevel = false
me.argvCheckoutMaster = false
switch me.forge.Config.Mode {
case forgepb.ForgeMode_MASTER:
me.argvCheckoutMaster = true
me.newBranch.SetText("master")
case forgepb.ForgeMode_DEVEL:
me.argvCheckoutDevel = true
me.newBranch.SetText("devel")
case forgepb.ForgeMode_USER:
me.newBranch.SetText(me.forge.Config.GetUsername())
me.argvCheckoutUser = true
default:
me.newBranch.SetText(me.forge.Config.GetUsername())
me.argvCheckoutUser = true
}
}
// sets the text in the labels in the window
// and hides and shows the buttons
func forgeSwitchMode(newMode forgepb.ForgeMode) {
if newMode == me.forge.Config.Mode {
log.Info("you are already on", newMode.String())
forgeVerifyGuiState() // doing this here initializes the button state
return
}
me.forge.Config.Mode = newMode
forgeVerifyGuiState() // update the button states
me.forge.Config.ConfigSave()
}
/*
func doDisableUserW() {
me.argvCheckoutUser = false
var count int
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if !repo.IsLocalBranch(repo.GetUserBranchName()) {
// log.Info("repo doesn't have user branch", repo.GetGoPath())
continue
}
count += 1
}
if count > 0 {
s := fmt.Sprintf("git delete %d user branches", count)
return
}
}
*/
// 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.GetGoPath()
return r.GetNamespace()
})
// t.Custom = func() {
// log.Info("close grid?")
// }
sf.Custom = func(r *gitpb.Repo) {
log.Info("do button click on", r.GetGoPath())
log.Info("do button click on", r.GetNamespace())
}
t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time {
return repo.NewestTime()
@ -356,13 +171,10 @@ func makeStandardReposWindow(title string, pb *gitpb.Repos) (*gitpb.ReposTable,
func findMergeToDevel() *gitpb.Repos {
found := gitpb.NewRepos()
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
// this sees if user has patches for devel. If it does, add it to me.found
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.AppendByGoPath(repo)
found.AppendByFullPath(repo)
}
}
now := time.Now()
@ -373,17 +185,19 @@ func findMergeToDevel() *gitpb.Repos {
// me.forge.PrintHumanTable(found)
// check for merges from devel
total, count, nope, _ := IsEverythingOnDevel()
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() {
me.found = new(gitpb.Repos)
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.GetGoPath()) {
if me.forge.Config.IsReadOnly(repo.GetNamespace()) {
continue
}
/*
@ -391,7 +205,7 @@ func findMergeToMaster() {
continue
}
if repo.GetMasterVersion() != repo.GetDevelVersion() {
me.found.AppendByGoPath(repo)
me.found.AppendByFullPath(repo)
continue
}
*/
@ -401,38 +215,39 @@ func findMergeToMaster() {
// everything is normal
} else {
repo.State = "DEVEL < MASTER"
log.Info("SERIOUS ERROR. DEVEL BRANCH IS BEHIND MASTER", repo.GetGoPath())
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 {
me.found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
}
}
now := time.Now()
if me.found.Len() == 0 {
if found.Len() == 0 {
log.Info("nothing to merge with master")
return
return found
}
me.forge.PrintHumanTable(me.found)
me.forge.PrintHumanTable(found)
// check for merges from devel
total, count, nope, _ := IsEverythingOnMaster()
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) {
findMergeToMaster()
found := findMergeToMaster()
if !doit {
return
}
all := me.found.SortByFullPath()
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
log.Info("repo:", repo.GetGoPath())
log.Info("repo:", repo.GetNamespace())
if result, err := repo.MergeToMaster(); err == nil {
log.Warn("THINGS SEEM OK", repo.GetFullPath())
for _, line := range result.Stdout {
@ -483,10 +298,10 @@ func mergeUserToDevel(doit bool) {
b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero
if b1 == 0 {
// log.Info("User is already merged into Devel", repo.GetGoPath(), cmd)
// log.Info("User is already merged into Devel", repo.GetNamespace(), cmd)
return
}
log.Info("merging user into devel repo:", repo.GetGoPath())
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 {
@ -514,3 +329,34 @@ func mergeUserToDevel(doit bool) {
}
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
}

168
doMerge.go Normal file
View File

@ -0,0 +1,168 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"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
}

112
doNormal.go Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// checks that repos are in a "normal" state
import (
"path/filepath"
"strings"
"time"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doNormal() bool {
me.forge.CheckDirtyQuiet()
var count int
stats := me.forge.RillRepos(checkNormalRepoState)
for path, stat := range stats {
dur := stat.End.Sub(stat.Start)
if dur > 10*time.Second {
log.Infof("%s checkNormalRepoState() took a long time (%s)\n", path, shell.FormatDuration(dur))
}
if stat.Err == nil {
continue
}
// log.Infof("%-60s, %-60s %v %s\n", stat.Start, stat.End.String(), dur, path)
// log.Infof("%-30v %s %v\n", dur, path, stat.Err)
// log.Info("got path", path, stat.Err)
count += 1
}
if count > 0 {
log.Info("Some repos are not in a 'normal' state. error count =", count)
log.Info("TODO: list the repos here. forge patch repos?")
dumpWorkRepos()
config.SetChanged("repos", true)
return false
}
return true
}
// 99% of the time, the repos during development should be on your user branch.
// error out if it's not
// this checks to see if a devel and user branch exist
// this needs to run each time in case repos were added manually by the user
// this also verifies that
func checkNormalRepoState(repo *gitpb.Repo) error {
var err error
tmp := filepath.Join(me.forge.Config.ReposDir, repo.GetNamespace())
if tmp != repo.FullPath {
log.Infof("%s != %s\n", repo.FullPath, tmp)
if strings.HasPrefix(repo.FullPath, me.forge.Config.ReposDir) {
tmp = strings.TrimPrefix(repo.FullPath, me.forge.Config.ReposDir)
tmp = strings.Trim(tmp, "/")
repo.Namespace = tmp
err = log.Errorf("namespace set to filepath")
}
} else {
// log.Infof("%s == %s\n", repo.FullPath, tmp)
}
tmp = strings.Trim(repo.Namespace, "/")
if tmp != repo.Namespace {
err = log.Errorf("junk in ns %s", repo.Namespace)
repo.Namespace = tmp
}
if repo.GetMasterBranchName() == "" {
me.forge.VerifyBranchNames(repo)
log.Info("ABNORMAL: master branch name was blank in", repo.GetFullPath())
}
if repo.GetMasterBranchName() == "" {
me.forge.VerifyBranchNames(repo)
err = log.Errorf("master branch name blank")
}
if repo.GetDevelBranchName() == "" {
me.forge.VerifyBranchNames(repo)
err = log.Errorf("devel branch name blank")
}
if repo.GetUserBranchName() == "" {
me.forge.VerifyBranchNames(repo)
err = log.Errorf("user branch name blank")
}
if repo.GetGoPath() == repo.GetNamespace() {
// log.Info(repo.FullPath, "gopath == namespace", repo.GetGoPath(), repo.GetNamespace())
} else {
log.Info(repo.FullPath, "gopath != namespace", repo.GetGoPath(), repo.GetNamespace())
}
repo.MakeLocalDevelBranch()
repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName())
repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName())
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
log.Infof("changing to user(%s) branch: %s\n", repo.GetUserBranchName(), repo.FullPath)
repo.CheckoutUser()
repo.ReloadCheck()
err = log.Errorf("now on user branch")
}
if me.forge.Config.IsReadOnly(repo.GetGoPath()) != repo.GetReadOnly() {
repo.ReadOnly = me.forge.Config.IsReadOnly(repo.GetGoPath())
log.Info("damnit", repo.FullPath)
err = log.Errorf("readonly bit wrong")
}
return err
}

View File

@ -4,100 +4,196 @@
package main
import (
"fmt"
"os"
"path/filepath"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func doPatch() error {
if argv.Patch.Submit != "" {
_, err := me.forge.SubmitDevelPatchSet(argv.Patch.Submit)
if err != nil {
return err
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 {
return doPatchGet()
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 {
return doPatchList()
var changed bool
newpatches := forgepb.NewPatches()
for pset := range me.forge.Patchsets.IterAll() {
log.Info(pset.Uuid)
for patch := range pset.Patches.IterAll() {
if setNewCommitHash(patch) {
changed = true
}
if patch.NewHash == "na" {
newpatches.Append(patch)
log.Info("apply this patch?")
}
}
/*
for patch := range pset.Patches.IterAll() {
if repo, ok := me.forge.IsPatchApplied(patch); ok {
log.Info("\tfound patch in repo", repo.Namespace, patch.Filename)
} else {
log.Info("\tdid not find patch", patch.CommitHash, patch.NewHash, patch.Filename)
}
}
*/
}
if changed {
if err := me.forge.SavePatchsets(); err != nil {
log.Warn("savePatchsets() failed", err)
}
}
me.forge.Patchsets.PrintTable()
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
}
if fhelp.QuestionUser("apply this patch?") {
newhash, err := applyAndTrackPatch(repo, patch)
log.Info("apply results:", newhash, err)
}
}
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
findReposWithPatches()
if me.found.Len() == 0 {
log.Info("you currently have no patches in your user branches")
return nil
found := findReposWithPatches()
if found.Len() == 0 {
log.Info("you currently have no repos with patches")
return false
} else {
me.forge.PrintHumanTable(found)
}
me.forge.PrintHumanTable(me.found)
return nil
}
func doPatchList() error {
openPatchsets()
if me.psets == nil {
return fmt.Errorf("Open Patchsets failed")
}
log.Info("got psets len", len(me.psets.Patchsets))
all := me.psets.SortByName()
for all.Scan() {
pset := all.Next()
// log.Info("pset name =", pset.Name)
dumpPatchset(pset)
}
return nil
}
func savePatchsets() error {
if me.psets == nil {
return fmt.Errorf("savePatchesets() can't save nil")
}
log.Info("savePatchsets() len =", me.psets.Len())
data, err := me.psets.Marshal()
if err != nil {
log.Info("protobuf.Marshal() failed:", err)
return err
}
fullpath := filepath.Join(me.forge.GetConfigDir(), "patchsets.pb")
var pfile *os.File
pfile, err = os.OpenFile(fullpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Info("Patchsets save failed:", err, fullpath)
return err
}
pfile.Write(data)
pfile.Close()
return nil
}
func openPatchsets() {
fullpath := filepath.Join(me.forge.GetConfigDir(), "patchsets.pb")
data, err := os.ReadFile(fullpath)
if err != nil {
log.Info("Patchsets open failed:", err, fullpath)
return
}
psets := new(forgepb.Patchsets)
err = psets.Unmarshal(data)
if err != nil {
log.Info("Unmarshal patchsets failed", err)
return
}
me.psets = psets
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.Patchset) bool {
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())
@ -106,13 +202,6 @@ func dumpPatchset(pset *forgepb.Patchset) bool {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS GOOD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())
}
/*
log.Info("applyPatches() State", pset.State)
log.Info("applyPatches() COMMENT", pset.Comment)
log.Info("applyPatches() Branch Name", pset.GetStartBranchName())
log.Info("applyPatches() Start Hash", pset.GetStartBranchHash())
*/
var count int
var bad int
all := pset.Patches.SortByFilename()
@ -143,12 +232,12 @@ func IsValidPatch(p *forgepb.Patch) bool {
log.Info("can not apply patch! repo not found", basepath, filename)
return false
}
if repo.DevelHash() != p.StartHash {
if repo.ActualDevelHash() != p.StartHash {
log.Info("can not apply patch! devel hash mismatch", basepath, filename)
return false
}
if repo.DevelHash() == p.StartHash {
log.Info("local devel hash:", repo.DevelHash(), "matches patch hash", p.StartHash, "and can be applied")
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 {
@ -156,24 +245,3 @@ func IsValidPatch(p *forgepb.Patch) bool {
}
return true
}
func doPatchGet() error {
psets, err := me.forge.GetPatchesets()
if err != nil {
log.Info("Get Patchsets failed", err)
return err
}
log.Info("Got Patchsets ok", psets.Uuid)
log.Info("got psets len", len(psets.Patchsets))
all := psets.SortByName()
for all.Scan() {
pset := all.Next()
// log.Info("pset name =", pset.Name)
dumpPatchset(pset)
}
log.Info("FIXME: can't save these yet. must merge with on disk psets here")
// savePatchsets()
return nil
}

View File

@ -7,10 +7,58 @@ 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
@ -40,47 +88,7 @@ func rillPull(repo *gitpb.Repo) error {
return nil
}
// is every repo on the devel branch?
func doGitPullNew() {
now := time.Now()
pullcount := me.forge.RillFuncError(rillPull)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, _ := IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d git pulled) (%d not on master branch) (%s) git pull total=%d\n", total, count, nope, shell.FormatDuration(time.Since(now)), pullcount)
}
/*
func doGitPull() {
allerr := me.found.RillGitPull(40, 5)
all := me.found.SortByFullPath()
for all.Scan() {
repo := all.Next()
result := allerr[repo]
if result.Error == gitpb.ErrorGitPullOnDirty {
log.Info("skip git pull. repo is dirty", repo.GetGoPath())
continue
}
if result.Error == gitpb.ErrorGitPullOnLocal {
log.Info("skip git pull. local branch ", repo.GetGoPath())
continue
}
if result.Exit == 0 {
continue
}
log.Info("git pull error:", repo.GetGoPath(), result.Error)
log.Info("git pull error:", repo.GetGoPath(), result.Stdout)
}
}
*/
// git fetch origin master:master
func rillFetchMaster(repo *gitpb.Repo) error {
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
@ -100,3 +108,4 @@ func doGitFetch() {
me.forge.ConfigSave()
}
}
*/

79
doSync.go Normal file
View File

@ -0,0 +1,79 @@
// 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 Normal file
View File

@ -0,0 +1,125 @@
// 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 Normal file
View File

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

13
exit.go
View File

@ -6,27 +6,28 @@ package main
import (
"os"
"go.wit.com/gui"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func okExit(thing string) {
if thing != "" {
log.Info("forge exit:", thing, "ok")
}
gui.UnloadToolkits()
if configSave {
me.forge.SetConfigSave(configSave)
}
// log.Info("Finished go-clean on", check.GetGoPath(), "ok")
if thing != "" {
log.Info("forge exit:", thing, "ok")
}
me.forge.Exit()
}
func badExit(err error) {
log.Info("forge failed: ", err, me.forge.GetGoSrc())
log.Info("forge failed: ", err, me.forge.Config.ReposDir)
os.Exit(-1)
}
func badRepoExit(repo *gitpb.Repo, err error) {
log.Printf("forge failed on %s with %v\n", repo.GetGoPath(), err)
log.Printf("%s FAILED: %v\n", repo.GetNamespace(), err)
os.Exit(-1)
}

167
find.go
View File

@ -1,167 +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 me.found []*gitpb.Repo
//
// so, it makes a subset of repos that are then used performing actions on
//
// by default, it adds every repo
func (f *FindCmd) findRepos() *gitpb.Repos {
if f == nil {
findMine()
return me.found
}
if f.All {
findAll()
return me.found
}
if f.Private {
findPrivate()
return me.found
}
if f.Mine {
findMine()
return me.found
}
if f.Favorites {
findFavorites()
return me.found
}
if f.Dirty {
return findDirty()
}
if f.User {
findUser()
return me.found
}
findAll()
return me.found
}
func findPrivate() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsPrivate(repo.GetGoPath()) {
me.found.AppendByGoPath(repo)
}
}
}
// finds repos that are writable
func findMine() {
// log.Printf("get mine %s\n", me.forge.GetGoSrc())
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsWritable(repo.GetGoPath()) {
me.found.AppendByGoPath(repo)
}
}
}
// finds repos the user has marked as favorites in the forge .config
func findFavorites() {
// log.Printf("get favorites %s\n", me.forge.GetGoSrc())
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsFavorite(repo.GetGoPath()) {
me.found.AppendByGoPath(repo)
}
}
}
// finds repos that git is reporting as dirty
func findDirty() *gitpb.Repos {
found := gitpb.NewRepos()
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
var repo *gitpb.Repo
repo = all.Next()
if repo.IsDirty() {
found.AppendByGoPath(repo)
}
}
return found
}
func findAll() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
me.found.AppendByGoPath(repo)
}
}
func findUser() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
me.found.AppendByGoPath(repo)
}
}
}
func findPublishable() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetTargetVersion() == "" {
continue
}
me.found.AppendByGoPath(repo)
}
}
func findReposWithPatches() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetTargetVersion() != "" {
// add everything that has a target version set
me.found.AppendByGoPath(repo)
continue
}
if repo.IsDirty() {
// always add dirty branches
me.found.AppendByGoPath(repo)
continue
}
if repo.GetUserVersion() == "" || repo.GetUserVersion() == "uerr" {
// skip anything without a user branch
continue
}
if repo.GetUserVersion() != repo.GetDevelVersion() {
me.found.AppendByGoPath(repo)
continue
}
// this is an old test to see if the current 'last tag' is accurate and should be removed
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
if repo.GetLastTag() != repo.GetMasterVersion() {
me.found.AppendByGoPath(repo)
repo.FindLastTag()
continue
}
}
}

360
helperPatches.go Normal file
View File

@ -0,0 +1,360 @@
// 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) bool {
// parts := strings.Fields(patch.Comment)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("could not find repo", patch.Namespace)
return false
}
comment := cleanSubject(patch.Comment)
if patch.NewHash == "" {
log.Info("init() new patch to 'na' ", patch.NewHash, "commithash:", patch.CommitHash, patch.Namespace, comment)
patch.NewHash = "na"
return true
}
os.Chdir(repo.GetFullPath())
newhash, err := findCommitBySubject(comment)
if err != nil {
log.Info("patch: not found hash:", patch.CommitHash, patch.Namespace, comment, newhash, err)
return false
}
if patch.NewHash == newhash {
// patch was already set
return false
}
if patch.NewHash != "na" {
log.Infof("patch: hash MISMATCH %s old=%s new=%s name=%s\n", patch.Namespace, patch.NewHash, newhash, comment)
return false
}
patch.NewHash = newhash
log.Info("patch: found hash:", patch.CommitHash, newhash, patch.Namespace, comment)
return true
}
/*
func setNewCommitHashLoop(p *forgepb.Set) bool {
var done bool = true
for patch := range p.Patches.IterAll() {
setNewCommitHashLoop(patch)
}
return done
}
*/
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 != "na" {
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 != "na" {
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
}

170
main.go
View File

@ -7,11 +7,9 @@ package main
import (
"embed"
"os"
"strings"
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui"
"go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
@ -43,30 +41,18 @@ func getVersion(repo *gitpb.Repo, name string) string {
func main() {
me = new(mainType)
gui.InitArg()
me.pp = arg.MustParse(&argv)
me.myGui = prep.Gui() // prepares the GUI package for go-args
me.auto = prep.Bash3(&argv) // this line should be: prep.Bash(&argv)
if argv.Bash {
argv.doBash()
os.Exit(0)
}
if len(argv.BashAuto) != 0 {
argv.doBashAuto()
os.Exit(0)
}
me.urlbase = argv.URL
if me.urlbase == "" {
me.urlbase = "https://go.wit.com/"
}
if os.Getenv("FORGE_URL") != "" {
me.urlbase = os.Getenv("FORGE_URL")
log.Info("got forge url", me.urlbase)
}
me.urlbase = strings.Trim(me.urlbase, "/") // track down why trailing '/' makes http POST not work
// me.auto = prep.Bash3(argv.DoAutoComplete, &argv) // this line should be: prep.Bash(&argv)
// arg.MustParse(&argv) // these three lines are becoming terrible syntax
// me.auto = prep.MustParse(&argv) // try to make this work?
// load the ~/.config/forge/ config
me.forge = forgepb.Init()
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
if argv.Config != nil {
@ -106,17 +92,6 @@ func main() {
}
if argv.Clean != nil {
if argv.Clean.Repo != "" {
log.Info("only looking at repo:", argv.Clean.Repo)
okExit("")
}
if argv.Clean.GitReset != nil {
findAll() // select all the repos
doGitReset()
okExit("reset")
}
if err := doClean(); err != nil {
badExit(err)
}
@ -124,25 +99,81 @@ func main() {
okExit("")
}
if argv.Help != nil {
doHelp()
okExit("")
}
if argv.Dirty != nil {
doDirty()
okExit("")
}
if argv.GitFetch != nil {
doGitFetch()
if argv.Tag != nil {
doTag()
okExit("")
}
if argv.GitPull != nil {
doGitPullNew()
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
me.forge.PrintHumanTable(found)
if argv.List.Full {
me.forge.PrintHumanTableFull(found)
} else {
me.forge.PrintHumanTable(found)
}
okExit("")
}
@ -150,29 +181,50 @@ func main() {
if err := doPatch(); err != nil {
badExit(err)
}
okExit("patch list")
}
// todo: redo this logic using forgepb
if configSave {
me.forge.ConfigSave()
configSave = false
}
// if the user doesn't want to open the GUI and
// nothing else was specified to be done,
// then just list the table to stdout
if gui.NoGui() {
me.forge.PrintHumanTable(me.found)
okExit("")
}
// open the gui unless the user performed some other
// basically, if you run just 'forge' it should open the GUI
// if opening the GUI, always check git for dirty repos
findAll() // select all the repos
doCheckDirtyAndConfigSave()
doGui()
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 {
// every repo is in a really clean state. no extra files anywhere
// no dirty repos, no repos that need to be published
// nothing different between user and master branch version. not common
log.Info("All of your git repositories appear to be in perfect shape")
}
okExit("")
}
// keep this small
func doHelp() {
log.Info("")
log.Info("forge -h : to see the available options")
log.Info("forge --bash : will create a bash autocomplete file")
log.Info("forge : with no arguements, forge tries to load a GO GUI plugin")
log.Info(" : there are two GUI plugins. terminal & GTK")
log.Info("")
log.Info("forge list : shows a table of all your repos")
log.Info("forge checkout : checks out all your repos to the same branch")
log.Info(" : the default is your user branch")
log.Info("forge clean : reverts all repos to the master branch")
log.Info("forge dirty : show all repos git reports as dirty")
log.Info("")
okExit("")
}
func doHelpPatches() {
log.Info("TODO: ?")
okExit("")
}

View File

@ -4,11 +4,10 @@
package main
import (
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
)
var me *mainType
@ -21,58 +20,32 @@ func (b *mainType) Enable() {
b.mainbox.Enable()
}
// returns the server to connect to
func myServer() string {
return me.forge.GetForgeURL()
}
// this app's variables
type mainType struct {
pp *arg.Parser // for parsing the command line args. Yay to alexf lint!
forge *forgepb.Forge // for holding the forge protobuf files
myGui *gui.Node // the gui toolkit handle
found *gitpb.Repos // stores the list of repos to process things on
psets *forgepb.Patchsets // the locally stored on disk patchsets
foundPaths []string // stores gopaths to act on (when doing go-clone)
configSave bool // if the config file should be saved after finishing
urlbase string // base URL
// our view of the repositories
// patchWin *patchesWindow
// pp *arg.Parser // for parsing the command line args. Yay to alexflint!
auto *prep.Auto // more experiments for bash handling
forge *forgepb.Forge // for holding the forge protobuf files
myGui *prep.GuiPrep // for initializing the GUI toolkits
foundPaths []string // stores gopaths to act on (when doing go-clone)
configSave bool // if the config file should be saved after finishing
urlbase string // base URL
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
// the main box. enable/disable this
mainbox *gui.Node
// the window from the /lib/gui/gowit package
// lw *gadgets.BasicWindow
// #### 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
goSrcPwd *gadgets.OneLiner // what is being used as primary directory for your work
gitAuthor *gadgets.OneLiner // ENV GIT_AUTHOR NAME and EMAIL
forgeMode *gui.Node // is the user in 'master', 'devel' or 'user' branches
// these hold the branches that the user can switch all
// the repositories to them
newBranch *gui.Node // deprecate?
setBranchB *gui.Node // deprecate?
// these hold the branches that the user can switch all the repositories to them
reposWinB *gui.Node // button that opens the repos window
repoAllB *gui.Node // "all" repos button
repoDirtyB *gui.Node // "dirty" repos button
repoDevelMergeB *gui.Node // "merge to devel" repos button
repoWritableB *gui.Node // "what repos are writable" repos button
demoB *gui.Node // opens the demo
// modeReleaseW *gui.Node // opens the release window
// modePatchW *gui.Node // opens the patch window
// modeUserW *gui.Node // opens the user/hack window
argvCheckoutUser bool // shared between the GUI and the command line tools
argvCheckoutDevel bool // shared between the GUI and the command line tools
argvCheckoutMaster bool // shared between the GUI and the command line tools
}

0
test Normal file
View File

View File

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

View File

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

View File

@ -1,115 +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/debugger"
"go.wit.com/lib/gadgets"
"go.wit.com/log"
)
func makeHackModeWindow() *gadgets.GenericWindow {
win := gadgets.NewGenericWindow("git user branch / Hack Mode Window", "This is a work in progress")
grid := win.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() {
win.Disable()
defer 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 := win.Stack.NewGroup("Merge")
grid = group2.RawGrid()
grid.NewButton("merge to devel", func() {
win.Disable()
defer win.Enable()
mergeUserToDevel(true)
})
grid.NewButton("merge to master", func() {
win.Disable()
defer win.Enable()
mergeDevelToMaster(true)
})
grid.NewButton("merge all", func() {
win.Disable()
defer win.Enable()
me.argvCheckoutUser = false
me.argvCheckoutDevel = true
me.argvCheckoutMaster = false
if err := doCheckoutShared(); err != nil {
log.Info("checkout error:", err)
} else {
log.Info("checkout was ok")
}
mergeUserToDevel(true)
me.argvCheckoutUser = false
me.argvCheckoutDevel = false
me.argvCheckoutMaster = true
if err := doCheckoutShared(); err != nil {
log.Info("checkout error:", err)
} else {
log.Info("checkout was ok")
}
mergeDevelToMaster(true)
})
group3 := win.Stack.NewGroup("work in progress")
grid = group3.RawGrid()
grid.NewButton("forge ConfigSave()", func() {
me.forge.ConfigSave()
})
grid.NewButton("debugger()", func() {
debugger.DebugWindow()
})
return win
}

View File

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

View File

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

View File

@ -1,241 +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"
"sync"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
type stdPatchsetTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget
TB *forgepb.PatchsetsTable // the gui table buffer
update bool // if the window should be updated
}
func (w *stdPatchsetTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
func makePatchsetsWin() *stdPatchsetTableWin {
dwin := new(stdPatchsetTableWin)
dwin.win = gadgets.NewGenericWindow("forge current patchsets", "patchset options")
dwin.win.Custom = func() {
log.Info("test delete window here")
}
grid := dwin.win.Group.RawGrid()
grid.NewButton("ondisk", func() {
openPatchsets()
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
dwin.doPatchsetsTable(me.psets)
})
grid.NewButton("upstream", func() {
psets, err := me.forge.GetPatchesets()
if err != nil {
log.Info("Get Patchsets failed", err)
return
}
var foundnew bool
all := psets.All()
for all.Scan() {
pset := all.Next()
found := me.psets.FindByUuid(pset.Uuid)
if found == nil {
log.Info("new patchset", pset.Name, pset.Uuid)
pset.State = "new"
foundnew = true
} else {
log.Info("patchset already on disk", found.Name, found.State)
pset.State = found.State
if pset.State == "" {
pset.State = "new"
}
}
}
dwin.doPatchsetsTable(psets)
if foundnew {
log.Info("should save these here")
me.psets = psets
savePatchsets()
}
})
grid.NewButton("save", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
savePatchsets()
})
grid.NewButton("analyse and save patchsets", func() {
if me.psets == nil {
log.Info("No Patchsets loaded")
return
}
all := me.psets.All()
for all.Scan() {
pset := all.Next()
if pset.State == "" {
log.Info("What is up with?", pset.Name)
setPatchsetState(pset)
} else {
log.Info("patchset already had state", pset.Name, pset.State)
}
}
savePatchsets()
})
// make a box at the bottom of the window for the protobuf table
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
// load and show the current patch sets
openPatchsets()
if me.psets == nil {
log.Info("Open Patchsets failed")
return dwin
}
dwin.doPatchsetsTable(me.psets)
return dwin
}
func (dwin *stdPatchsetTableWin) doPatchsetsTable(currentPatchsets *forgepb.Patchsets) {
dwin.Lock()
defer dwin.Unlock()
if dwin.TB != nil {
dwin.TB.Delete()
dwin.TB = nil
}
// display the protobuf
dwin.TB = AddPatchsetsPB(dwin.box, currentPatchsets)
f := func(pset *forgepb.Patchset) {
log.Info("Triggered. do something here", pset.Name)
/*
win := makePatchWindow(pset)
win.Show()
*/
}
dwin.TB.Custom(f)
}
func AddPatchsetsPB(tbox *gui.Node, pb *forgepb.Patchsets) *forgepb.PatchsetsTable {
t := pb.NewTable("PatchsetsPB")
t.NewUuid()
t.SetParent(tbox)
t.AddStringFunc("#", func(p *forgepb.Patchset) string {
return fmt.Sprintf("%d", p.Patches.Len())
})
vp := t.AddButtonFunc("View Patchset", func(p *forgepb.Patchset) string {
return p.Name
})
vp.Custom = func(pset *forgepb.Patchset) {
log.Info("show patches here", pset.Name)
makePatchesWin(pset.Patches)
// patchwin := makePatchesWin()
// patchwin.doPatchesTable(pset.Patches)
/*
win := makePatchWindow(pset)
win.Show()
*/
}
t.AddComment()
t.AddState()
ctimef := func(p *forgepb.Patchset) string {
ctime := p.Ctime.AsTime()
return ctime.Format("2006/01/02 15:04")
}
t.AddStringFunc("ctime", ctimef)
/*
etimef := func(e *forgepb.Patchset) string {
etime := e.Etime.AsTime()
s := etime.Format("2006/01/02 15:04")
if strings.HasPrefix(s, "1970/") {
// just show a blank if it's not set
return ""
}
return s
}
t.AddStringFunc("etime", etimef)
*/
t.AddStringFunc("Author", func(p *forgepb.Patchset) string {
return fmt.Sprintf("%s <%s>", p.GitAuthorName, p.GitAuthorEmail)
})
t.AddUuid()
t.ShowTable()
return t
}
func setPatchsetState(p *forgepb.Patchset) {
var bad bool
var good bool
var done bool = true
all := p.Patches.All()
for all.Scan() {
patch := all.Next()
// log.Info("patch:", patch.StartHash, patch.CommitHash, patch.RepoNamespace, patch.Filename)
repo := me.forge.FindByGoPath(patch.RepoNamespace)
if repo == nil {
log.Info("couldn't find repo", patch.RepoNamespace)
bad = true
continue
}
if _, err := repo.GetHashName(patch.CommitHash); err == nil {
// this patch has been applied
patch.Applied = true
done = true
continue
}
if name, err := repo.GetHashName(patch.StartHash); err == nil {
// it might be possible to apply this patch
log.Info("patch may be good:", patch.RepoNamespace, name, patch.CommitHash, patch.Filename)
good = true
} else {
// probably screwed up git trees
log.Info("patch with unknown origin:", patch.RepoNamespace, name, err, patch.CommitHash, patch.Filename)
bad = true
}
}
if bad {
p.State = "BAD"
return
}
if good {
p.State = "TRY"
return
}
if done {
p.State = "DONE"
return
}
}

View File

@ -39,6 +39,7 @@ func makePatchesWin(patches *forgepb.Patches) *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()
@ -51,35 +52,18 @@ func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
all := patches.All()
for all.Scan() {
patch := all.Next()
repomap[patch.RepoNamespace] += 1
repomap[patch.Namespace] += 1
}
grid.NewLabel(fmt.Sprintf("%d", len(repomap)))
grid.NewLabel(fmt.Sprintf("total repos"))
grid.NextRow()
grid.NewButton("reload", func() {
})
grid.NewButton("Apply All", func() {
var count int
all := patches.SortByFilename()
for all.Scan() {
p := all.Next()
rn := p.RepoNamespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
log.Info("Could not figure out repo path", rn)
return
}
filename, err := savePatch(p)
if err != nil {
log.Info("savePatch() failed", err)
return
}
count += 1
if err := applyPatch(repo, filename); err != nil {
log.Info("warn user of git am error", err)
return
}
applyPatchNew(p)
}
log.Info("ALL PATCHES WORKED! count =", count)
})
@ -95,17 +79,15 @@ func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
}
func applyPatchNew(p *forgepb.Patch) error {
rn := p.RepoNamespace
rn := p.Namespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
return fmt.Errorf("Could not figure out repo path %s", rn)
log.Info("Could not figure out repo path", rn)
return log.Errorf("%s namespace?\n", rn)
}
filename, err := savePatch(p)
if err != nil {
log.Info("savePatch() failed", err)
return err
}
if err := applyPatch(repo, filename); err != nil {
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
}
@ -128,25 +110,41 @@ func (dwin *stdPatchTableWin) doPatchesTable(currentPatches *forgepb.Patches) {
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 == "na" {
return "git am"
}
if p.NewHash == "" {
return "new"
}
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", func(p *forgepb.Patch) string {
return "git am"
})
gitam.Custom = func(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")
}
gitam := t.AddButtonFunc("apply", applyPatchLabel)
gitam.Custom = applyPatchClick
t.AddCommitHash()
t.AddRepoNamespace()
t.AddNamespace()
// t.AddFilename()
t.AddStringFunc("file", func(p *forgepb.Patch) string {
_, fname := filepath.Split(p.Filename)
@ -177,3 +175,46 @@ func savePatch(p *forgepb.Patch) (string, error) {
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
}

42
windowPublish.go Normal file
View File

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

@ -10,164 +10,22 @@ import (
"os"
"time"
"go.wit.com/lib/debugger"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func makeReposWin() *gadgets.GenericWindow {
win := gadgets.NewGenericWindow("git repos", "All about git repos")
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()
me.repoDirtyB = grid.NewButton("dirty", func() {
doCheckDirtyAndConfigSave()
found := findDirty()
tb, box := makeStandardReposWindow("dirty repos", found)
hbox := box.Box().Horizontal()
hbox.NewButton("commit all", func() {
all := found.SortByFullPath()
for all.Scan() {
repo := all.Next()
log.Info("do commit here on", repo.GetGoPath())
}
log.Info("TODO: fix this")
log.Info("run 'forge commit --all'")
})
hbox.NewButton("update table", func() {
me.forge.PrintHumanTable(found)
found2 := findDirty()
me.forge.PrintHumanTable(found2)
// tb.Update()
// tb.UpdateTable(found2)
})
hbox.NewButton("delete table", func() {
tb.Delete()
})
})
// win.Top.NewGroup("misc (works in progress)")
var writeWin *gadgets.GenericWindow
me.repoWritableB = grid.NewButton("writable", func() {
// if the window exists, just toggle it open or closed
if writeWin != nil {
writeWin.Toggle()
return
}
// make the window for the first time
found := new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
continue
}
found.AppendByGoPath(repo)
}
writeWin, _ = makeWritableWindow(found)
writeWin.Win.Custom = func() {
log.Info("closing window. could do somethine here")
}
})
me.repoAllB = grid.NewButton("All", func() {
me.found = new(gitpb.Repos)
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
me.found.AppendByGoPath(repo)
}
makeStandardReposWindow("All repos", me.found)
})
var insertWin *gadgets.GenericWindow
me.repoWritableB = grid.NewButton("insert test", func() {
// if the window exists, just toggle it open or closed
if insertWin != nil {
insertWin.Toggle()
return
}
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 := 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("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.AppendByGoPath(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())
})
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.AppendByGoPath(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("Configure", func() {
log.Info("add a forge config window here")
})
win.Stack.NewGroup("misc (works in progress)")
grid = win.Stack.RawGrid()
// grid = win.Top.RawGrid()
grid = win.Group.RawGrid()
var found *gitpb.Repos
var txt string
@ -203,7 +61,7 @@ func makeReposWin() *gadgets.GenericWindow {
hbox.NewButton("test", func() {
})
t := makeStandardReposGrid(found)
t := makeDevelBehindMaster(found)
t.SetParent(box)
t.ShowTable()
})
@ -241,7 +99,7 @@ func makeReposWin() *gadgets.GenericWindow {
cmd = []string{"git", "branch", "--delete", "--remote", "origin/" + brname}
log.Info(repo.GetGoPath(), cmd)
repo.RunVerbose(cmd)
repo.Reload()
repo.ReloadCheck()
}
me.forge.SetConfigSave(true)
me.forge.ConfigSave()
@ -252,9 +110,8 @@ func makeReposWin() *gadgets.GenericWindow {
t.ShowTable()
})
grid.NewButton("unknown branches", func() {
log.Info("unknown branches not done yet")
})
rwin.boxTB = win.Bottom.Box()
grid.NextRow()
found = develRemoteProblem()
@ -272,7 +129,148 @@ func makeReposWin() *gadgets.GenericWindow {
})
grid.NextRow()
return win
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 {
@ -283,7 +281,7 @@ func develBehindMasterProblem() *gitpb.Repos {
if repo.GetDevelVersion() == repo.GetMasterVersion() {
continue
}
found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
}
return found
@ -296,7 +294,7 @@ func remoteUserBranchProblem() *gitpb.Repos {
repo := all.Next()
username := repo.GetUserBranchName()
if repo.IsBranchRemote(username) {
found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
}
}
@ -318,13 +316,13 @@ func develRemoteProblem() *gitpb.Repos {
// log.Info(lhash, rhash, repo.GetGoPath())
if lhash == "" || rhash == "" {
// something is wrong if either of these are blank
found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
continue
}
if lhash == rhash {
continue
}
found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
}
return found
@ -345,13 +343,13 @@ func masterRemoteProblem() *gitpb.Repos {
// log.Info(lhash, rhash, repo.GetGoPath())
if lhash == "" || rhash == "" {
// something is wrong if either of these are blank
found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
continue
}
if lhash == rhash {
continue
}
found.AppendByGoPath(repo)
found.AppendByFullPath(repo)
}
return found
@ -367,9 +365,11 @@ func makeWritableWindow(pb *gitpb.Repos) (*gadgets.GenericWindow, *gitpb.ReposTa
log.Info("todo: run git pull on each repo")
})
grid.NewButton("do repos.ReScan()", func() {
t.Update()
})
/*
grid.NewButton("do repos.ReScan()", func() {
t.Update()
})
*/
tbox := win.Bottom.Box()
t.SetParent(tbox)
@ -391,38 +391,3 @@ func makeWritableWindow(pb *gitpb.Repos) (*gadgets.GenericWindow, *gitpb.ReposTa
t.ShowTable()
return win, t
}
func makeWindowForPB() *gadgets.GenericWindow {
win := gadgets.NewGenericWindow("Raw PB View", "Configure")
return win
}
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()
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
}

185
windowReposNew.go Normal file
View File

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

@ -21,8 +21,8 @@ type repoPatchWindow struct {
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.Patchset // the patchset in question
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
@ -109,7 +109,7 @@ func makeRepoPatchWindow(repo *gitpb.Repo, fset []*forgepb.Patch) *repoPatchWind
return pw
}
func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Patchset) {
func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Set) {
repomap := make(map[*gitpb.Repo][]*forgepb.Patch)
repohash := make(map[*gitpb.Repo]string)
@ -117,7 +117,7 @@ func (r *repoPatchWindow) addPatchset(grid *gui.Node, pset *forgepb.Patchset) {
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
s := p.RepoNamespace
s := p.Namespace
repo := me.forge.FindByGoPath(s)
if repo == nil {
log.Info("COULD NOT FIND", s)