Compare commits

...

504 Commits

Author SHA1 Message Date
Jeff Carr e0520ca96d apply patches 2025-09-23 14:50:59 -05:00
Jeff Carr 6d7d74feb4 fixing against new .proto files 2025-09-23 14:21:58 -05:00
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
Jeff Carr 6012733385 individual 'git am' buttons 2025-03-12 09:37:19 -05:00
Jeff Carr 695da486d2 work on getting new patchset lists 2025-03-12 09:21:09 -05:00
Jeff Carr 6cf3648b94 better patch window 2025-03-12 08:17:23 -05:00
Jeff Carr 369c252a68 auto commit all patches 2025-03-12 07:58:15 -05:00
Jeff Carr 4fd06bc159 remove the 'mode' concept 2025-03-12 07:53:44 -05:00
Jeff Carr ee52199402 require all that other stuff 2025-03-11 19:44:36 -05:00
Jeff Carr 74a6d4bdfb resurrect code I deleted for 'git am' 2025-03-11 12:15:12 -05:00
Jeff Carr 422d853020 start tracking patchset state 2025-03-11 12:00:27 -05:00
Jeff Carr 95d4e03ca4 more patch cleanups 2025-03-10 23:25:27 -05:00
Jeff Carr 3870959747 fix patches stats 2025-03-10 17:24:06 -05:00
Jeff Carr 1b145d1f04 rm old code 2025-03-10 17:14:59 -05:00
Jeff Carr 391a988e5f working on more standard patch windows 2025-03-10 14:57:33 -05:00
Jeff Carr 18d9a7a099 trying to get patchset tables to work 2025-03-10 13:52:25 -05:00
Jeff Carr 2c5841f5ec better Patchsets view 2025-03-10 11:46:46 -05:00
Jeff Carr 0baeaa5525 cleanup noise 2025-03-10 09:44:23 -05:00
Jeff Carr 62b8c685f0 more standard protobuf window for patches 2025-03-10 09:11:31 -05:00
Jeff Carr 112c2d37d8 autogenpb 2025-03-05 21:40:20 -06:00
Jeff Carr 8f4f351885 works in all pb tables 2025-03-05 20:21:50 -06:00
Jeff Carr cad4ed3d29 generic protobuf table buttons actually work 2025-03-05 19:39:02 -06:00
Jeff Carr 52e08e6077 test out buttons in PB tables 2025-03-05 13:32:29 -06:00
Jeff Carr 31d2ac1259 early working protobuf table update example 2025-03-05 03:26:53 -06:00
Jeff Carr 7367335143 work on delete and full protobuf gui 2025-03-05 02:58:31 -06:00
Jeff Carr f01423a151 minor 2025-03-04 21:04:57 -06:00
Jeff Carr 0529b0dca0 docs 2025-03-04 20:29:23 -06:00
Jeff Carr aa229b8778 insert PB table seems to work 2025-03-04 15:45:52 -06:00
Jeff Carr 2098040d62 gui code cleanups 2025-03-04 14:32:17 -06:00
Jeff Carr aa04e8c122 rm old code notes 2025-03-04 13:34:56 -06:00
Jeff Carr 3b44a76cc4 stop git branch delete if no local branch 2025-03-04 06:35:53 -06:00
Jeff Carr cad5321516 general code and gui reorg 2025-03-04 04:29:14 -06:00
Jeff Carr 6cb34ae52c use gadgets.GenericWindow() 2025-03-04 04:12:14 -06:00
Jeff Carr eca95a62fc large single mutex might have fixed gocui crashes for now 2025-03-04 04:06:16 -06:00
Jeff Carr d35d0c16e7 mod button highlighting works 2025-03-04 00:27:15 -06:00
Jeff Carr 7364eb1ebf switch to GenericWindow() 2025-03-03 23:49:24 -06:00
Jeff Carr 2dc7e87e96 minor 2025-03-03 15:00:00 -06:00
Jeff Carr 99f80ecddb attempt to stub in table Update() 2025-03-03 11:59:21 -06:00
Jeff Carr 0eb355cb12 more proper init. also, Stdout mode 2025-03-03 03:42:58 -06:00
Jeff Carr 37c6d5a6b5 switch mode button logic 2025-03-02 17:44:02 -06:00
Jeff Carr 1ea717ce36 stub in configure button 2025-03-02 12:04:45 -06:00
Jeff Carr 10a1741bd9 mode windows 2025-03-02 08:13:43 -06:00
Jeff Carr 9ccc3d0470 work on forge 'mode' windows 2025-03-02 07:49:30 -06:00
Jeff Carr c5b6539c3b clearer gui. hopefully 2025-03-02 04:13:10 -06:00
Jeff Carr d953ae8db6 add a 'forge mode' concept 2025-03-02 03:03:29 -06:00
Jeff Carr 948c47b7ff start switching to generic GUI table code 2025-02-24 11:00:08 -06:00
Jeff Carr e3d786a79c minor notes 2025-02-23 23:15:17 -06:00
Jeff Carr 2d83d9ca79 switch to GenericWindow 2025-02-23 18:55:55 -06:00
Jeff Carr 2856d88b12 try merge to master also 2025-02-22 19:11:42 -06:00
Jeff Carr fd41059087 keep working on a merge button 2025-02-22 18:55:46 -06:00
Jeff Carr 642f53dcef start a merge button here 2025-02-22 18:46:00 -06:00
Jeff Carr 1d4817f6af command line option to rebuild 2025-02-22 09:42:49 -06:00
Jeff Carr ec11efc347 minor rearrange 2025-02-22 07:59:19 -06:00
Jeff Carr ba514f7827 better instructions 2025-02-22 06:52:40 -06:00
Jeff Carr 3d58bc1c32 rm old code 2025-02-22 04:54:06 -06:00
Jeff Carr 6eb37f63f2 rm old code 2025-02-22 04:52:34 -06:00
Jeff Carr 79178228b3 attempt to merge devel 2025-02-22 04:46:45 -06:00
Jeff Carr c1f7968f7c force reset with no checking. dangerous. 2025-02-22 03:54:56 -06:00
Jeff Carr 844bf0e294 good deal 2025-02-22 03:40:37 -06:00
Jeff Carr df0f0a21c3 more removals of "me.found" 2025-02-21 18:49:33 -06:00
Jeff Carr 1a255bdbf6 start deprecating me.found 2025-02-21 18:31:26 -06:00
Jeff Carr 66e65c7a00 cleanup some of the noise 2025-02-21 16:55:17 -06:00
Jeff Carr f8dd6bca59 correctly cleans remote user branches finally! 2025-02-21 16:35:21 -06:00
Jeff Carr ce938cc73b start converting over to using a direct pb grid 2025-02-21 16:10:33 -06:00
Jeff Carr efd0373c21 attempt to switch table to just a grid 2025-02-21 15:37:28 -06:00
Jeff Carr 1e4362b29e work on protobuf tables in andlabs 2025-02-21 13:57:02 -06:00
Jeff Carr fbf3e66de8 more totals. not quite correct yet though 2025-02-21 10:30:55 -06:00
Jeff Carr 9cc63ce391 show problem totals 2025-02-21 10:21:13 -06:00
Jeff Carr 76143a6474 remove old code 2025-02-21 09:33:45 -06:00
Jeff Carr 9ce889b404 testing dirty check 2025-02-21 09:17:41 -06:00
Jeff Carr b672943d0e make a problems window 2025-02-21 09:13:54 -06:00
Jeff Carr a5222d3894 update stats every 90 seconds 2025-02-21 08:20:44 -06:00
Jeff Carr 9db8405954 standard protobuf table changes 2025-02-21 06:05:55 -06:00
Jeff Carr a2a9fbb840 redo main window 2025-02-21 05:41:30 -06:00
Jeff Carr 815585d343 commit --all does all dirty repos 2025-02-20 09:39:00 -06:00
Jeff Carr 77da32b99a new autogen 2025-02-20 09:37:42 -06:00
Jeff Carr 5db0b57b31 simple table 2025-02-20 06:49:16 -06:00
Jeff Carr be3e625f15 "apply all" button for a patchset 2025-02-19 17:54:45 -06:00
Jeff Carr f735cb937f check if merge should be done 2025-02-18 09:39:30 -06:00
Jeff Carr 6c6815c727 found a weird defect 2025-02-15 18:55:35 -06:00
Jeff Carr 23c730cd85 seems like it can clean the repos okay now 2025-02-15 17:18:45 -06:00
Jeff Carr 620569a97c remote lots of cruft 2025-02-15 17:07:07 -06:00
Jeff Carr ec0fe277f6 send the right args already 2025-02-15 12:30:26 -06:00
Jeff Carr ae13593d05 totally stupid logic before 2025-02-15 05:33:13 -06:00
Jeff Carr f0922a73f3 save an old copy for go-deb 2025-02-14 20:40:44 -06:00
Jeff Carr b2597a337c duh 2025-02-14 19:17:56 -06:00
Jeff Carr 34cc35ad7b button to do it all 2025-02-14 19:15:39 -06:00
Jeff Carr 10f53c5f17 general work on 'Build()' 2025-02-14 18:40:55 -06:00
Jeff Carr 7d4bb336b3 more attempts at merge 2025-02-13 23:46:11 -06:00
Jeff Carr 007bf0df9c check branch differences 2025-02-13 23:38:22 -06:00
Jeff Carr 116dafc662 merge buttons 2025-02-13 23:20:24 -06:00
Jeff Carr f0572a6198 attempt at merge to devel button 2025-02-13 22:52:10 -06:00
Jeff Carr eee34ec9f0 debugging init() 2025-02-13 22:28:23 -06:00
Jeff Carr 43c98cb819 disable gocui disable() 2025-02-13 20:47:40 -06:00
Jeff Carr cb23b1f378 fix repos window 2025-02-13 20:27:24 -06:00
Jeff Carr 88f6e8519b stubbed in tables 2025-02-13 20:10:45 -06:00
Jeff Carr ce18af897a start thinking about making a Table() 2025-02-10 23:42:21 -06:00
Jeff Carr 493b2ec72d simple 'clean' bash autocomplete 2025-02-09 16:34:34 -06:00
Jeff Carr 9e64b36566 allows patches to be applied 2025-02-09 16:17:47 -06:00
Jeff Carr 396161f6c1 nil panic 2025-02-09 14:41:28 -06:00
Jeff Carr 0ed94d57e8 quiet output 2025-02-09 14:34:57 -06:00
Jeff Carr f72756b089 check for serious problem of devel < master 2025-02-09 14:18:37 -06:00
Jeff Carr b4cb43178b make a 'found repo' window 2025-02-09 08:28:28 -06:00
Jeff Carr d17c61ddf3 minor 2025-02-09 05:18:26 -06:00
Jeff Carr 074cf12864 rm old code 2025-02-09 03:17:56 -06:00
Jeff Carr 5171aca31f doCheckout() shared between GUI and command line 2025-02-09 02:22:22 -06:00
Jeff Carr d7d2e0ba1b cleaning stuff 2025-02-08 18:44:29 -06:00
Jeff Carr 1dd4a2d979 minor fixes 2025-02-07 19:10:37 -06:00
Jeff Carr 1d89d38bc3 save on dirty changes 2025-02-07 12:45:21 -06:00
Jeff Carr 574bce8006 trying to test full repo list in 'gocui' toolkit 2025-02-07 04:06:45 -06:00
Jeff Carr bf66727ab7 disable window while 'git checkout' is running 2025-02-02 16:26:18 -06:00
Jeff Carr 6aafc842ae playing with the patch windows 2025-02-02 16:17:48 -06:00
Jeff Carr f8e13c6cd8 minor 2025-02-02 15:09:04 -06:00
Jeff Carr 81c72468a1 code for a new user to start from scratch 2025-02-02 15:09:04 -06:00
Jeff Carr 260f4c67e7 save config on dirty doClean() exit 2025-02-01 19:43:19 -06:00
Jeff Carr d608c36c28 hmm. notsure 2025-02-01 12:28:35 -06:00
Jeff Carr da82eeff92 oops 2025-02-01 12:23:14 -06:00
Jeff Carr 47bb2ae6e0 add GPL 3.0 2025-02-01 11:57:52 -06:00
Jeff Carr ed686fc4d6 more improvements to viewing patchsets 2025-02-01 11:55:17 -06:00
Jeff Carr 7c4feea366 save files in ~/.config/forge by default 2025-01-31 13:47:45 -06:00
Jeff Carr 8329c71b08 save this in ~/.config/forge 2025-01-31 13:47:45 -06:00
Jeff Carr fb91818c5f save the patchsets file 2025-01-31 13:47:45 -06:00
Jeff Carr 5f0a271bf7 almost there on a first git am attempt 2025-01-30 19:47:48 -06:00
Jeff Carr 25fe17d59c apply mbox button 2025-01-30 19:19:05 -06:00
Jeff Carr bc15bd663e shows state and dims the buttons 2025-01-30 19:05:21 -06:00
Jeff Carr 282215e760 moved good/bad into the GUI 2025-01-30 19:00:57 -06:00
Jeff Carr b60e7aa98b marks patchsets as defective and remembers that 2025-01-30 18:46:35 -06:00
Jeff Carr a491579a6d open and save patchsets to a file 2025-01-30 18:31:10 -06:00
Jeff Carr 3db2e7ff6c rethink the patch options 2025-01-30 17:59:47 -06:00
Jeff Carr 4d4aad27e4 dump patchsets to the console 2025-01-30 15:18:46 -06:00
Jeff Carr f26e5e9980 dump old code 2025-01-30 13:34:38 -06:00
Jeff Carr cb922fa0fa devel worked, now trying merge to master 2025-01-30 11:50:02 -06:00
Jeff Carr a2c761f055 attempt GUI update on window Show() 2025-01-30 11:31:22 -06:00
Jeff Carr e1f37c79ce about to attempt a merge to devel again 2025-01-30 11:27:16 -06:00
Jeff Carr 026d95b9c2 maybe got it back to merging 2025-01-30 11:09:34 -06:00
Jeff Carr 0ff7e62c60 working back to merge working 2025-01-30 11:02:19 -06:00
Jeff Carr 7ea9732c8f old file gone 2025-01-30 10:28:04 -06:00
Jeff Carr 50a5695241 rm lots more old code 2025-01-30 10:25:59 -06:00
Jeff Carr 0b6419d1a5 try this. maybe 2025-01-30 10:10:02 -06:00
Jeff Carr bb865d4a49 disable buttons if things aren't correct 2025-01-30 10:04:51 -06:00
Jeff Carr 80df33888a done on this part of the GUI for now I think 2025-01-30 09:38:28 -06:00
Jeff Carr 7779d01854 removing "patch summary" struct 2025-01-30 09:16:39 -06:00
Jeff Carr 18caa7869b hammer please 2025-01-30 09:09:47 -06:00
Jeff Carr 965d6cb6a9 more hammering 2025-01-30 09:04:45 -06:00
Jeff Carr 7fb79a4736 hammering on the GUI 2025-01-30 08:59:41 -06:00
Jeff Carr 2fafc0bd13 more UI improvements 2025-01-30 08:54:13 -06:00
Jeff Carr 5d4850a93d more code and gui cleanups 2025-01-30 08:48:59 -06:00
Jeff Carr 441c1e7ad8 make a window of the current patches 2025-01-30 07:42:50 -06:00
Jeff Carr c815c8b403 lots more gui cleanup 2025-01-30 07:30:04 -06:00
Jeff Carr 76eb230ae8 more gui cleanups 2025-01-30 07:21:08 -06:00
Jeff Carr 33a6037982 remove lots of old code. breaks a lot too. 2025-01-30 07:12:47 -06:00
Jeff Carr 71d4cf3c2d rm old code 2025-01-30 06:26:08 -06:00
Jeff Carr e7d71c78e2 GetPatchsets work 2025-01-30 06:21:26 -06:00
Jeff Carr a76028dd23 wasn't saving 2025-01-30 03:06:33 -06:00
Jeff Carr 4539a85125 trying to fix the user branch create 2025-01-30 02:24:17 -06:00
Jeff Carr 5d2daf75c0 rename all these damn things from the past 2025-01-30 01:47:14 -06:00
Jeff Carr d493a55cfa debugging releaser 2025-01-29 19:59:55 -06:00
Jeff Carr bab84d7c90 show the new hash 2025-01-29 12:18:16 -06:00
Jeff Carr 152a25591d make a seperate window to maintain the patches 2025-01-29 12:18:16 -06:00
Jeff Carr 54b50299f3 slim down this window to keep it functional 2025-01-29 12:18:16 -06:00
Jeff Carr e548b0fb6d show git commit message. repo namespace 2025-01-29 12:18:16 -06:00
Jeff Carr f61891ca88 shows all the patches 2025-01-29 12:18:16 -06:00
Jeff Carr f2281a2102 start a 'view patch' window 2025-01-29 12:18:16 -06:00
Jeff Carr b047d70679 try verbose. doesn't work though 2025-01-29 12:18:16 -06:00
Jeff Carr 41203c36e8 need to move branch repair to the gui 2025-01-29 12:18:16 -06:00
Jeff Carr 24988d440c rethink doVerify() strategy. put in the GUI instead 2025-01-29 12:18:16 -06:00
Jeff Carr 345c1ee9b1 move submit patchset to forgepb 2025-01-29 12:18:16 -06:00
Jeff Carr bbf5f79acb simple shortcut 2025-01-29 12:18:16 -06:00
Jeff Carr 438a8812f6 smarter git pull 2025-01-29 12:18:16 -06:00
Jeff Carr eee88af0ce general usefulness 2025-01-29 12:18:16 -06:00
Jeff Carr bb7937bb79 print git checkout user error 2025-01-29 12:18:16 -06:00
Jeff Carr 6e7874faa6 switch to 'verify devel' 2025-01-29 12:18:16 -06:00
Jeff Carr ca92a4f4e4 gui patchset cleanups 2025-01-28 18:10:32 -06:00
Jeff Carr d16f4d4d32 more argv cleanups 2025-01-28 17:35:18 -06:00
Jeff Carr 51ec39d157 cleanup argv handling. go-arg saves the day 2025-01-28 17:03:56 -06:00
Jeff Carr 7402aaded7 fixes from cleaning an old set of repos 2025-01-28 14:02:39 -06:00
Jeff Carr dd7355571d add 'forge find patches' 2025-01-28 13:58:41 -06:00
Jeff Carr 91c28de514 make a patchset grid widget 2025-01-28 13:20:10 -06:00
Jeff Carr 3b6e84abdf minor 2025-01-28 11:35:47 -06:00
Jeff Carr eeb3306bd5 better logging 2025-01-20 07:58:13 -06:00
Jeff Carr 4721184398 attempt to embedded gocui plugin toolkit 2025-01-20 05:08:52 -06:00
Jeff Carr ef1a306352 verbose show _anything_ not exact 2025-01-20 04:11:53 -06:00
Jeff Carr 55d8abd2a3 clean devel now has an EXACT check against upstream and master 2025-01-20 04:07:16 -06:00
Jeff Carr 6386bc4826 minor 2025-01-20 03:30:58 -06:00
Jeff Carr 6d04764749 moved code to forgepb 2025-01-20 03:23:14 -06:00
Jeff Carr e470de205a add GetPatchsets() 2025-01-20 03:18:44 -06:00
Jeff Carr 1ba66e4e54 mv 2025-01-20 02:51:50 -06:00
Jeff Carr 0e6ef50ad2 gui is improved 2025-01-20 02:50:07 -06:00
Jeff Carr 6cee7c5420 gui testing and cleanups 2025-01-20 02:30:31 -06:00
Jeff Carr d9c4d18b16 patch window 2025-01-20 02:14:08 -06:00
Jeff Carr 25b2e50f6b git commit --all works 2025-01-20 01:47:27 -06:00
Jeff Carr ca51b9ba74 time checkout master 2025-01-20 01:39:59 -06:00
Jeff Carr eed26134fd good grief 2025-01-19 18:02:24 -06:00
Jeff Carr 16f0b731c5 wrong logic for checkout master 2025-01-19 16:11:13 -06:00
Jeff Carr 835c000aef check devel branches 2025-01-19 16:07:17 -06:00
Jeff Carr 4c38b052fd clear target versions in here for now 2025-01-19 11:51:35 -06:00
Jeff Carr 7285a7a8fb quiet testing output 2025-01-19 10:48:03 -06:00
Jeff Carr ae24f02bbb misc fix 2025-01-19 09:25:25 -06:00
Jeff Carr ab9f3bf62d recreate user branches after delete 2025-01-19 08:48:17 -06:00
Jeff Carr 62dd96ccec oops. checkout master 2025-01-19 07:26:01 -06:00
Jeff Carr 383edb2766 start ignoring missing user branch 2025-01-19 07:24:18 -06:00
Jeff Carr f0f11794ed delete user branches works 2025-01-19 07:05:45 -06:00
Jeff Carr 1e8bdc5c53 fix checkout user 2025-01-19 04:31:38 -06:00
Jeff Carr 193d041306 git checkout on readonly repos 2025-01-19 03:45:10 -06:00
Jeff Carr b9d4f88abd more checks on merge to master 2025-01-19 03:39:35 -06:00
Jeff Carr 69eee08045 git branch handling continues 2025-01-19 02:56:48 -06:00
Jeff Carr 6b2a61ff0e builds and runs ok 2025-01-19 02:37:04 -06:00
Jeff Carr d84d7d4687 more work on cleaning user branches 2025-01-19 00:35:58 -06:00
Jeff Carr 39f72d2034 better git checkout 2025-01-18 23:25:55 -06:00
Jeff Carr cbbd43f7b5 start a 'clean' argv option 2025-01-18 15:50:06 -06:00
Jeff Carr 4444e942ea rill dirty check 2025-01-18 11:10:53 -06:00
Jeff Carr 335531e0fb misc code cleanups 2025-01-18 10:34:13 -06:00
Jeff Carr 8fabd77b49 attempt again to shortcut 2025-01-18 07:57:34 -06:00
Jeff Carr aa18544794 save and exit at the end 2025-01-18 07:48:55 -06:00
Jeff Carr 973bcfd4eb button to quickly prep everything 2025-01-18 07:47:34 -06:00
Jeff Carr dcc51eb776 more tracking down of the master branch name 2025-01-18 05:48:21 -06:00
Jeff Carr 824c54d205 more branch debugging 2025-01-18 03:59:05 -06:00
Jeff Carr a04e1784d1 identify and fix out of sync user branches 2025-01-18 03:13:18 -06:00
Jeff Carr 2b10e228d2 fix commandline patch submit 2025-01-17 13:59:59 -06:00
Jeff Carr fc9c26cc8d working on branches 2025-01-17 13:20:15 -06:00
Jeff Carr 9acbb24284 start an 'examine' argv 2025-01-17 10:59:05 -06:00
Jeff Carr af84727178 show list patch error 2025-01-17 09:47:21 -06:00
Jeff Carr 4f282cc302 is it possible to make a username argv alias? 2025-01-17 05:16:03 -06:00
Jeff Carr e40251c7fd add a flag to show dirty files 2025-01-17 03:30:43 -06:00
Jeff Carr 3809663e6e things are safe to turn off merge for every repo 2025-01-13 08:55:06 -06:00
Jeff Carr f4bad31b0b stuff used to debug a hung mutex() lock 2025-01-13 08:11:09 -06:00
Jeff Carr ce6311893c standard names finally thanks to autogenpb 2025-01-13 04:14:06 -06:00
Jeff Carr 96c9588c6b fixes due to new autogen function names 2025-01-12 06:35:13 -06:00
Jeff Carr fcd25fa76a force less to pause 2025-01-11 09:11:30 -06:00
Jeff Carr c57bb42f20 os.Exec() is smarter now 2025-01-11 08:14:45 -06:00
Jeff Carr 4546d067dd add 'forge commit' so I don't commit on the wrong branch 2025-01-11 07:45:16 -06:00
Jeff Carr abdcd1fc62 updated to new protobuf file 2025-01-11 05:54:19 -06:00
Jeff Carr ae8fd94a2c output cleanups 2025-01-08 10:10:14 -06:00
Jeff Carr e27e1716d7 better filenames 2025-01-08 05:47:33 -06:00
Jeff Carr 9d97b94616 tracked down 'dirty' not showing up 2025-01-08 04:07:33 -06:00
Jeff Carr 8e2a557b25 this code probably isn't needed, but it does work 2025-01-08 02:40:06 -06:00
Jeff Carr 06ad922780 add some more useful buttons 2025-01-08 01:16:27 -06:00
Jeff Carr bcabf1b3b7 move COBOL like print table into forgepb 2025-01-08 00:51:53 -06:00
Jeff Carr d5143b94b8 show a single repo 2025-01-08 00:23:04 -06:00
Jeff Carr b27a1fccad allow delete for a single repo 2025-01-07 22:27:54 -06:00
Jeff Carr 5eb51f2832 attempt to make this work right 2025-01-07 21:31:31 -06:00
Jeff Carr 33c556f95d attempting to set pb repo state 2025-01-07 20:28:40 -06:00
Jeff Carr 51db8c4f0c maybe the interface works again? 2025-01-07 19:30:25 -06:00
Jeff Carr 9bc0e8a398 rm old code 2025-01-07 18:06:29 -06:00
Jeff Carr 1e8836a62a add bash autocomplete 2025-01-07 17:16:03 -06:00
Jeff Carr 745bafccc8 good deal 2025-01-07 06:51:58 -06:00
Jeff Carr e579b7ca26 old stuff gone 2025-01-07 06:46:39 -06:00
Jeff Carr 1966246169 minor gui stuff 2025-01-07 06:14:47 -06:00
Jeff Carr fb945a5295 trying to fix gui 2025-01-07 05:59:27 -06:00
Jeff Carr 5bfc643962 attempt to rescan the changes 2025-01-07 05:36:03 -06:00
Jeff Carr c9f948de9f builds and seems to work still 2025-01-07 04:51:42 -06:00
Jeff Carr 73c3969731 no longer merge to master here 2025-01-07 03:23:35 -06:00
Jeff Carr fd592e8222 more cleanups due to bash completion working. hell ya 2025-01-06 21:52:48 -06:00
Jeff Carr 7b1392bae9 more argv examples 2025-01-06 21:29:19 -06:00
Jeff Carr be98039d69 forgeConfig code merged here
Signed-off-by: Jeff Carr <jcarr@wit.com>
2025-01-06 21:02:04 -06:00
Jeff Carr 38c0ec7caa move config handling here 2025-01-06 20:57:52 -06:00
Jeff Carr 2ffc60a5d4 cleaner code 2025-01-06 19:16:00 -06:00
Jeff Carr 4d1d02cc1a simple UI changes 2025-01-06 19:03:35 -06:00
Jeff Carr cd7d256a80 even better bash 2025-01-06 18:14:33 -06:00
Jeff Carr 5434ab498b awesome bash completion work. thank good. what a timesaver 2025-01-06 17:53:20 -06:00
Jeff Carr ec8a3688eb more argv cleanups 2025-01-06 16:53:13 -06:00
Jeff Carr 362ea63b97 adding bash completion handling 2025-01-06 15:44:56 -06:00
Jeff Carr bc5994b84e skip merge if not on right branch 2025-01-05 12:18:53 -06:00
Jeff Carr ffd510e1d8 can't merge all if some repos are dirty 2025-01-05 12:13:32 -06:00
Jeff Carr 34fc90e4bf try merge 2025-01-05 12:02:19 -06:00
Jeff Carr 6ded56a13a more overall checking for branch changes 2025-01-05 06:14:06 -06:00
Jeff Carr 2c7e15ccfa check to make sure everything is on the devel branch 2025-01-05 05:48:02 -06:00
Jeff Carr 79ec3ce469 stop apply if repos are dirty 2025-01-05 04:54:05 -06:00
Jeff Carr 92ff05cf6f start adding logic to check the hashes 2025-01-05 03:21:26 -06:00
Jeff Carr 026e0cde90 button to dump last patch info 2025-01-05 02:01:15 -06:00
Jeff Carr 18ee541f89 'forge dirty' will find and list only dirty repos 2025-01-05 01:18:47 -06:00
Jeff Carr c25a7ea736 start working on 'git am' 2024-12-30 02:03:56 -06:00
Jeff Carr 4fc958fcc9 trust nothing. but this might work. notsure
it's hard to do distributed work when the tools are themselves
	being worked on. lots of crazy in these patches until
	they actually work. I'm switching between droplets, hypervisors,
	etc.
2024-12-30 00:50:51 -06:00
Jeff Carr 45c32bc7fc add git author and email 2024-12-29 21:36:39 -06:00
Jeff Carr 16a6c8b11a attempt again 2024-12-28 19:36:03 -06:00
Jeff Carr d02ced3f2e starting to work. maybe 2024-12-28 03:00:42 -06:00
Jeff Carr a957c22f8b start work on an applyPatch() 2024-12-27 22:27:19 -06:00
Jeff Carr 8b3be0ab42 more argv improvements 2024-12-27 04:36:29 -06:00
Jeff Carr 9818e8d1ee more ideas for command line options 2024-12-27 03:39:53 -06:00
Jeff Carr bdaa40c51f try to push patchsets 2024-12-25 23:17:24 -06:00
Jeff Carr 4ec714a678 minor cleanups after argv changes 2024-12-24 04:58:27 -06:00
Jeff Carr 55e7e11a4c set a Name for the patchset 2024-12-24 03:35:19 -06:00
Jeff Carr 3505a66d84 more argv cleanups 2024-12-24 03:08:06 -06:00
Jeff Carr 778de10e87 deprecate go specific stuff 2024-12-24 03:00:55 -06:00
Jeff Carr 2f7c1807a1 more cleanups 2024-12-24 02:26:54 -06:00
Jeff Carr 2b087365f6 move things into 'find' and 'do' subcommands 2024-12-24 02:07:58 -06:00
Jeff Carr bd332301be use subcommands 2024-12-24 01:54:33 -06:00
Jeff Carr c5fcb2a6c0 start work on an apply patchset window 2024-12-23 11:15:16 -06:00
Jeff Carr b7a6001ba4 submit sends patches 2024-12-23 03:45:30 -06:00
Jeff Carr e68cb3e80f button for repo window 2024-12-23 03:27:29 -06:00
Jeff Carr cc55a5ad7a start working on the GUI again for this 2024-12-23 02:37:48 -06:00
Jeff Carr 30a4988f08 minor 2024-12-18 23:05:09 -06:00
Jeff Carr 9adb650b3e add --dirty 2024-12-18 22:02:07 -06:00
Jeff Carr e30ef08c11 use os.Mkdir 2024-12-18 17:58:02 -06:00
Jeff Carr 5ac4c26ded minor 2024-12-17 23:28:24 -06:00
48 changed files with 4192 additions and 1416 deletions

1
.gitignore vendored
View File

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

114
Makefile
View File

@ -1,24 +1,52 @@
VERSION = $(shell git describe --tags) VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d) BUILDTIME = $(shell date +%Y.%m.%d_%H%M)
all: # make build # go build using your git cloned repos (GO111MODULE=off)
make private # make install # go install using your git cloned repos (GO111MODULE=off)
@echo "make restart # remove the repos.pb file" # make gocui # try the ncurses gui plugin
@echo "make private # only the private ones" # make andlabs # try the andlabs gui plugin (uses GTK)
@echo "make mine # just show my repos"
@echo "make pull # run git pull on every repo" default: install-verbose tag
tag:
forge tag list
vet: vet:
@GO111MODULE=off go vet @GO111MODULE=off go vet
@echo this go binary package builds okay @echo this go binary package builds okay
verbose: build: goimports vet plugin
GO111MODULE=off go build -v -x \ GO111MODULE=off go build \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install: goimports vet install: goimports plugin
GO111MODULE=off go install \ GO111MODULE=off go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
cp -f ~/go/bin/forge ~/go/bin/last.forge # this is a hack so that go-deb can build a .deb file for forge # TODO: remove this
install-verbose: goimports vet plugin
GO111MODULE=off go install -v -x \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install-raw: goimports vet plugin
go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
plugin:
rm -f resources/*.so
# -cp ../../toolkits/gocui/gocui.so resources/
GTK: clean install
forge --gui andlabs
GTK-verbose: clean install
forge --gui andlabs --gui-verbose
CUI: install
forge --gui gocui
CUI-verbose: install
forge --gui gocui --gui-verbose >/tmp/forge.log 2>&1
goimports: goimports:
reset reset
@ -26,55 +54,33 @@ goimports:
@# // to globally reset paths: @# // to globally reset paths:
@# // gofmt -w -r '"go.wit.com/gui/gadgets" -> "go.wit.com/lib/gadgets"' *.go @# // gofmt -w -r '"go.wit.com/gui/gadgets" -> "go.wit.com/lib/gadgets"' *.go
gocui: install clean:
forge --gui gocui -rm -f forge go.*
# -rm -f ~/go/src/repos.pb
go-mod-clean purge
redomod-all: identify-protobuf:
forge --do-RedoGoMod autogenpb --identify ~/go/src/repos.pb
redomod-erase: devel:
forge --do-RedoGoMod --do-erase forge clean devel --force --verbose
private: install
forge --find-private
fix: install
forge --fix --find-all
list-all: install
forge --find-all
git-reset: install
forge --do-git-reset --find-all
readonly: install
forge --do-list --find-readonly
config: install
forge --config
scan: install
forge --do-scan
pull: install pull: install
forge --do-git-pull FORGE_URL="https://forge.grid.wit.com/" forge pull check
mine: install # cloudflare blocks POST due to captcha checks / human detection?
forge --find-mine # POST must be direct socket. probably for the best anyway
submit:
FORGE_URL="https://forge.grid.wit.com/" forge patch submit "forge auto commit"
gui: install commit:
forge --do-gui FORGE_URL="https://forge.grid.wit.com/" forge commit --all
patches: install check: install
forge --do-patches FORGE_URL="https://forge.grid.wit.com/" forge patch check
patches-localhost: install doc:
forge --do-patches --url "http://localhost:2233/" echo "/*" > doc.go
forge -h >> doc.go
patches-list: install echo "*/" >> doc.go
forge --list-patches --url "http://localhost:2233/" echo "package main" >> doc.go
restart:
reset
rm ~/go/src/repos.pb
make private

79
README.md Normal file
View File

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

48
applyPatch.go Normal file
View File

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

218
argv.go
View File

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

3
build
View File

@ -3,3 +3,6 @@
# include forgeConfig # include forgeConfig
mkdir -p files/usr/bin/ mkdir -p files/usr/bin/
cp ../utils/wit-utils/forgeConfig/forgeConfig 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

198
cobol.go
View File

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

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 Package: forge
Maintainer: Jeff Carr <jcarr@wit.com> Maintainer: Jeff Carr <jcarr@wit.com>
Architecture: amd64 Architecture: amd64
Depends: Depends: go-gui-toolkits, autogenpb, go-mod-clean, go-clone
Recommends: go-clone Recommends: go-clone
Description: 'forge' an attempt at federated 'git' 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)
}
}

20
debugger.go Normal file
View File

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

47
doBuild.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"go.wit.com/log"
)
func doBuild() error {
v := []string{}
if argv.Verbose {
v = []string{"-v", "-x"}
}
gopath := argv.Build
repo := me.forge.FindByGoPath(gopath)
if repo == nil {
return fmt.Errorf("rep not found: %s", gopath)
}
if err := me.forge.Build(repo, v); err != nil {
log.Warn("Build failed:", repo.GetGoPath(), err)
return err
}
return nil
}
func doInstall() error {
v := []string{}
if argv.Verbose {
v = []string{"-v", "-x"}
}
gopath := argv.Install
repo := me.forge.FindByGoPath(gopath)
if repo == nil {
return fmt.Errorf("rep not found: %s", gopath)
}
if err := me.forge.Install(repo, v); err != nil {
log.Warn("Install failed", repo.GetGoPath(), err)
return err
}
return nil
}

47
doCheckout.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
// trys to figure out if there is still something to update
func doCheckout() error {
if argv.Checkout.User != nil {
start := time.Now()
err := me.forge.DoAllCheckoutUser(argv.Force)
dur := time.Since(start)
log.Printf("Checked out %d user braches in %s\n", me.forge.Repos.Len(), shell.FormatDuration(dur))
if err != nil {
badExit(err)
}
okExit("")
}
if argv.Checkout.Devel != nil {
// setForgeMode(forgepb.ForgeMode_DEVEL)
if err := me.forge.DoAllCheckoutDevelNew(argv.Force); err != nil {
badExit(err)
}
okExit("")
}
if argv.Checkout.Master != nil {
setForgeMode(forgepb.ForgeMode_MASTER) // disable "normal" mode if set
if err := me.forge.DoAllCheckoutMaster(); err != nil {
badExit(err)
}
okExit("")
}
badExit(fmt.Errorf("did not specify what branch to checkout"))
return nil
}

272
doClean.go Normal file
View File

@ -0,0 +1,272 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"path/filepath"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func checkRemoteBranches(repo *gitpb.Repo) error {
if err := repo.ReloadCheck(); err != nil {
log.Info("need to reload", repo.FullPath)
}
if repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName()) {
} else {
return log.Errorf("remote devel is out of sync with local: todo: git pull or git fetch")
}
if repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName()) {
} else {
return log.Errorf("remote master is out of sync with local: todo: git pull or git fetch")
}
return nil
}
// reverts all repos back to the original master branches
// automatically deletes local devel and user branches
func doClean() error {
setForgeMode(forgepb.ForgeMode_CLEAN)
if argv.Clean.Verify != nil {
stats := me.forge.RillRepos(checkRemoteBranches)
for path, stat := range stats {
if stat.Err == nil {
continue
}
dur := stat.End.Sub(stat.Start)
if dur > time.Second {
log.Infof("%s checkRemoteBranches() took a long time (%s) (err=%v)\n", path, shell.FormatDuration(dur), stat.Err)
}
}
// log.Infof("%-60s, %-60s %v %s\n", stat.Start, stat.End.String(), dur, path)
// log.Infof("%-30v %s %v\n", dur, path, stat.Err)
return nil
}
// fix this to work, then delete all the other options for "forge clean'
if err := me.forge.DoAllCheckoutMaster(); err != nil {
// badExit(err)
}
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
continue
}
if repo.IsDirty() {
continue
}
// when publishing, clean out the details of that if it's still there
if repo.GetTargetVersion() != "" {
repo.SetTargetVersion("")
configSave = true
}
// try to delete user
if err := doRepoCleanUser(repo); err != nil {
log.Info(repo.GetGoPath(), err)
}
// try to delete devel
doRepoCleanDevel(repo)
}
found := gitpb.NewRepos()
total := 0
// find all repos that aren't "clean"
all = me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
total += 1
// find repos not on master branch
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
found.AppendByFullPath(repo)
continue
}
// find dirty repos
if repo.IsDirty() {
found.AppendByFullPath(repo)
continue
}
// find repos that still have a local user branch
if repo.IsLocalBranch(repo.GetUserBranchName()) {
found.AppendByFullPath(repo)
continue
}
// find repos that still have a local devel branch
if repo.IsLocalBranch(repo.GetDevelBranchName()) {
found.AppendByFullPath(repo)
continue
}
}
// display all the repos that aren't clean to the user
log.Printf("\n") // padding for now
if argv.Verbose {
me.forge.PrintHumanTableDirty(found)
} else {
me.forge.PrintHumanTable(found)
}
log.Printf("\n") // padding for now
var hmm int
hmm = found.Len()
if hmm == 0 {
log.Printf("%d repos are not clean\n", hmm)
} else {
log.Info("All repos are clean", total, hmm)
}
return nil
}
/*
func doesLocalBranchExist(repo *gitpb.Repo, branch string) bool {
return repo.Exists(filepath.Join(".git/refs/heads", branch))
}
*/
func doRepoCleanDevel(repo *gitpb.Repo) error {
if !repo.IsLocalBranch(repo.GetDevelBranchName()) {
// there is no local branch named 'devel'
return nil
}
if repo.GetCurrentBranchName() != repo.GetMasterBranchName() {
return log.Errorf("%s not on master branch:", repo.GetFullPath())
}
if repo.IsDirty() {
return log.Errorf("%s is dirty:", repo.GetFullPath())
}
if err := justDeleteTheDevelBranchAlready(repo); err != nil {
log.Info("justDeleteTheDevel() err", repo.GetGoPath(), err)
configSave = true
return err
}
return nil
}
// removes all local user branches
func doRepoCleanUser(repo *gitpb.Repo) error {
if repo.IsDirty() {
return nil
}
bruser := repo.GetUserBranchName()
brdevel := repo.GetDevelBranchName()
if repo.IsBranchRemote(bruser) {
log.Info("forge is designed to always have local only user branches", bruser)
return fmt.Errorf("forge is designed to always have local only user branches")
}
if !repo.IsLocalBranch(bruser) {
// there is no local user branch
return nil
}
// will you loose work if you delete your user branch?
// if DevelBranchExists()
// then if UserBranchCommits exist in DevelBranch
// DeleteUserBranch is safe
if repo.IsLocalBranch(brdevel) {
b1 := repo.CountDiffObjects(bruser, "refs/heads/"+brdevel) // should be zero
if b1 == 0 {
// every user branch exists in devel. delete user branch
cmd := []string{"git", "branch", "-D", bruser}
// log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
}
brmaster := repo.GetMasterBranchName()
// will you loose work if you delete your user branch?
// if master branch exists()
// then if all user commits exist in master
// delete user branch is safe
if repo.IsLocalBranch(brmaster) {
b1 := repo.CountDiffObjects(bruser, "refs/heads/"+brmaster) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", bruser}
// log.Info("USER IS IN DEVEL", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
}
return fmt.Errorf("%s branch has unique commits", bruser)
}
// if you call this, there is no going back. no checks anymore. nothing
// it deletes the 'devel' branch. git branch -D "devel". END OF STORY
func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error {
branch := repo.GetDevelBranchName()
remote := filepath.Join("origin", branch)
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
if repo.IsDevelRemote() {
// just make sure the remote & local branches are the same
return repo.DeleteLocalDevelBranch()
}
}
// check against remote if it exists
if repo.IsDevelRemote() {
b1 := repo.CountDiffObjects(branch, remote) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
cmd := []string{"git", "push"}
log.Info("DEVEL LOCAL NEEDS GIT PUSH TO REMOTE", repo.GetGoPath(), cmd)
err := repo.RunVerbose(cmd)
return err
}
// remote doesn't exist, check against master
master := repo.GetMasterBranchName()
b1 := repo.CountDiffObjects(branch, "refs/heads/"+master) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
// log.Info("DEVEL IS IN REMOTE", repo.GetGoPath(), cmd)
_, err := repo.RunVerboseOnError(cmd)
return err
}
cmd := []string{"git", "merge something somehow"}
log.Info("devel local, remote and master branches are wrong", repo.GetGoPath(), cmd, b1)
// _, err := repo.RunVerbose(cmd)
return nil
}
func doGitReset() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsReadOnly(repo.GetGoPath()) {
// log.Info("is readonly", repo.GetGoPath())
if repo.CheckDirty() {
log.Info("is readonly and dirty", repo.GetGoPath())
cmd := []string{"git", "reset", "--hard"}
repo.RunRealtime(cmd)
}
} else {
// log.Info("is not readonly", repo.GetGoPath())
}
}
}

99
doCommit.go Normal file
View File

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

View File

@ -1,73 +0,0 @@
package main
import (
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
func doScan() {
me.forge.ScanGoSrc()
}
func doGitPull() {
allerr := me.found.RillGitPull(40, 5)
all := me.found.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)
}
}
func doGitReset() {
if !argv.DoGitReset {
return
}
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 checkoutBranches(repo *gitpb.Repo) error {
dname := repo.GetDevelBranchName()
if dname == "" {
if err := me.forge.MakeDevelBranch(repo); err != nil {
log.Info("verify() no devel branch name", repo.GetGoPath())
return err
}
configSave = true
}
if repo.GetUserBranchName() == "" {
if err := me.forge.MakeUserBranch(repo); err != nil {
log.Info("verify() no devel branch name", repo.GetGoPath())
return err
}
configSave = true
}
return nil
}

75
doConfig.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"os"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func doConfig() {
if argv.Config.Delete != "" {
me.forge.DeleteByGoPath(argv.Config.Delete)
me.forge.SetConfigSave(true)
okExit("")
}
if argv.Config.Fix != nil {
log.Info("todo")
okExit("")
}
/*
if argv.Config.Register != "" {
if err := doRegister(argv.Config.Register); err == nil {
okExit("attempting to register " + argv.Config.Register)
} else {
badExit(err)
}
}
*/
// try to add, then save config and exit
if argv.Config.Add != nil {
log.Info("going to add a new repo", argv.Config.Add.GoPath)
deleteGoPath(me.forge, argv.Config.Add.GoPath)
new1 := forgepb.ForgeConfig{
GoPath: argv.Config.Add.GoPath,
Writable: argv.Config.Add.Writable,
ReadOnly: argv.Config.Add.ReadOnly,
Private: argv.Config.Add.Private,
Directory: argv.Config.Add.Directory,
Favorite: argv.Config.Add.Favorite,
Interesting: argv.Config.Add.Interesting,
MasterBranchName: argv.Config.Add.Master,
DevelBranchName: argv.Config.Add.Devel,
UserBranchName: argv.Config.Add.User,
}
me.forge.Config.Append(&new1)
me.forge.ConfigSave()
os.Exit(0)
}
log.Info("config.PathLock =", me.forge.Config.PathLock)
me.forge.ConfigPrintTable()
okExit("")
}
func deleteGoPath(f *forgepb.Forge, gopath string) bool {
var deleted bool = false
for {
if f.Config.DeleteByGoPath(gopath) {
log.Info("deleted ok", gopath)
deleted = true
} else {
log.Info("did not delete", gopath)
break
}
}
return deleted
}

16
doDirty.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
func doDirty() {
found := me.forge.CheckDirty()
if found.Len() == 0 {
return
}
if argv.Verbose {
me.forge.PrintHumanTableDirty(found)
} else {
me.forge.PrintHumanTable(found)
}
}

198
doFind.go 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
}

362
doGui.go
View File

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

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
}

261
doPatch.go Normal file
View File

@ -0,0 +1,261 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"path/filepath"
"go.wit.com/lib/fhelp"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func doPatchInit() {
if me.forge.Patchsets != nil {
if me.forge.Patchsets.Len() == 0 {
// log.Info("IGNORE: patches are empty")
} else {
log.Info("IGNORE: patches already initalized len =", me.forge.Patchsets.Len())
}
}
if err := me.forge.LoadPatchsets(); err != nil {
log.Info("patches failed to open", err)
if err := me.forge.SavePatchsets(); err != nil {
log.Warn("savePatchsets() failed", err)
}
}
}
func isPatchingSafe() bool {
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
return true
}
log.Info("This patch command is not safe to run now")
log.Info("you must reset the state of your git repositories. Run:")
log.Info("")
log.Info("forge normal")
log.Info("")
return false
}
// submit's current working patches
func doPatchSubmit() error {
pset, err := me.forge.MakeDevelPatchSet("testing")
if err != nil {
return err
}
if pset.Patches == nil {
log.Info("pset.Patches == nil")
return err
}
if pset.Patches.Len() == 0 {
log.Info("did not find any patches")
return nil
}
pset.PrintTable()
_, _, err = pset.HttpPost(myServer(), "new")
return err
}
func doPatch() error {
if argv.Patch.Repos != nil {
dumpWorkRepos()
return nil
}
if argv.Patch.Submit != nil {
return doPatchSubmit()
}
if !isPatchingSafe() {
return log.Errorf("not safe to work on patches")
}
if argv.Patch.Get != nil {
psets := forgepb.NewSets()
newpb, _, _ := psets.HttpPostVerbose(myServer(), "get")
newpb.PrintTable()
me.forge.Patchsets = newpb
me.forge.SavePatchsets()
return nil
}
if argv.Patch.Check != nil {
/*
old := findExpired()
// old.PrintTable()
for p := range old.IterAll() {
log.Info("patch", p.Filename, p.Namespace)
}
newpb, err := old.HttpPostVerbose(myServer(), "check")
if err != nil {
return err
}
newpb.PrintTable()
*/
log.Info("do something here to find patches merged to devel")
doMergeReport()
return nil
}
if argv.Patch.List != nil {
var changed bool
newpatches := new(forgepb.Set)
newpatches.Patches = forgepb.NewPatches()
for pset := range me.forge.Patchsets.IterAll() {
pset.PrintTable()
for patch := range pset.Patches.IterAll() {
changed = true
if patch.NewHash == "" || patch.NewHash == "na" {
if newpatches.Patches.AppendByPatchId(patch) {
log.Info("patchId added here", patch.PatchId)
} else {
log.Info("patchId already here", patch.PatchId)
}
} else {
if err := setNewCommitHash(patch); err != nil {
log.Infof("%s bad check on patch failure %v\n", patch.Filename, err)
return err
}
log.Info("newhash set already here", patch.PatchId, patch.NewHash)
}
}
}
if changed {
if err := me.forge.SavePatchsets(); err != nil {
log.Warn("savePatchsets() failed", err)
}
}
log.Info("NEW PATCHES TABLE")
newpatches.PrintTable()
for patch := range newpatches.Patches.IterAll() {
if err := setNewCommitHash(patch); err == nil {
log.Info("newhash set already here", patch.PatchId, patch.NewHash)
continue
}
log.Infof("%s is new\n", patch.Filename)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("\tCould not find namespace:", patch.Namespace)
continue
}
if fhelp.QuestionUser("apply this patch?") {
newhash, err := applyAndTrackPatch(repo, patch)
log.Info("apply results:", newhash, err)
}
}
return nil
/*
if newpatches.Len() != 0 {
for patch := range newpatches.IterAll() {
log.Info("new patch:", patch.CommitHash, patch.NewHash, patch.Filename)
repo := me.forge.FindByGoPath(patch.Namespace)
if repo == nil {
log.Info("\tCould not find namespace:", patch.Namespace)
continue
}
}
return log.Errorf("patches need to be applied")
}
// return doPatchList()
applied := findApplied()
if applied == nil || applied.Len() == 0 {
log.Info("no patches have been appled to the devel branch yet")
return nil
}
// for patch := range applied.IterAll() {
// log.Info("SEND APPLIED: newhash:", patch.NewHash, "commithash:", patch.CommitHash, "patch", patch.Namespace)
// }
newpb, _, err := applied.HttpPostVerbose(myServer(), "applied")
if err != nil {
return err
}
newpb.PrintTable()
return nil
*/
}
// if nothing, show patches & dirty repos
me.forge.Patchsets.PrintTable()
dumpWorkRepos()
return nil
}
// Shows repos that are:
// - git dirty repos
// - repos with 'user' branch patches not in 'devel' branch
// - repos with awaiting master branch verions
//
// return true if any are found
func dumpWorkRepos() bool {
// always run dirty first
me.forge.CheckDirtyQuiet()
// if no option is given to patch, list out the
// repos that have patches ready in them
found := findReposWithPatches()
if found.Len() == 0 {
log.Info("you currently have no repos with patches")
return false
} else {
me.forge.PrintHumanTable(found)
}
return true
}
// returns bad if patches can not be applied
// logic is not great here but it was a first pass
func dumpPatchset(pset *forgepb.Set) bool {
// don't even bother to continue if we already know it's broken
if pset.State == "BROKEN" {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS BAD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())
return false
} else {
log.Printf("Patchset Name: %-24s Author: %s <%s> IS GOOD\n", pset.Name, pset.GetGitAuthorName(), pset.GetGitAuthorEmail())
}
var count int
var bad int
all := pset.Patches.SortByFilename()
for all.Scan() {
p := all.Next()
if IsValidPatch(p) {
// ok
} else {
pset.State = "BROKEN"
bad += 1
}
count += 1
}
log.Info("pset has", count, "total patches, ", bad, "bad patches")
if bad == 0 {
return true
}
return false
}
func IsValidPatch(p *forgepb.Patch) bool {
basepath, filename := filepath.Split(p.Filename)
repo := me.forge.FindByGoPath(basepath)
if argv.Verbose {
log.Info("start:", p.StartHash, "end:", p.CommitHash, "file:", basepath, filename, "devel version", repo.GetDevelVersion())
}
if repo == nil {
log.Info("can not apply patch! repo not found", basepath, filename)
return false
}
if repo.ActualDevelHash() != p.StartHash {
log.Info("can not apply patch! devel hash mismatch", basepath, filename)
return false
}
if repo.ActualDevelHash() == p.StartHash {
log.Info("local devel hash:", repo.ActualDevelHash(), "matches patch hash", p.StartHash, "and can be applied")
}
log.Info("start:", p.StartHash, "end:", p.CommitHash, "file:", basepath, filename, "devel version", repo.GetDevelVersion())
for _, line := range p.Files {
log.Info("\t", line)
}
return true
}

111
doPull.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// is every repo on the devel branch?
func doPull() error {
if argv.Pull.Check != nil {
// stats := me.forge.RillFuncError(rillPull)
log.Info("TODO: actually git pull here? this is a bad idea. stopping.")
submit := gitpb.NewRepos()
for repo := range me.forge.Repos.IterByFullPath() {
newrepo := new(gitpb.Repo)
newrepo.MasterHash = repo.MasterHash
newrepo.DevelHash = repo.DevelHash
newrepo.Namespace = repo.Namespace
newrepo.URL = repo.URL
submit.Append(newrepo)
}
updatepb, regPB, err := submit.HttpPost(myServer(), "check")
if regPB == nil || err != nil {
log.Info("regPB==nil or err:", err)
return nil
}
log.Infof("pull check %s pb.Len()=%d client.Len()=%d server.Len()=%d err=%v\n", regPB.URL, updatepb.Len(), regPB.ClientDataLen, regPB.ServerDataLen, err)
return nil
}
// below this, you must not be in 'normal' mode
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
log.Info("you must check out the devel or master branches")
return nil
}
if argv.Force == true {
now := time.Now()
stats := me.forge.RillFuncError(rillPull)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
total, count, nope, _ := me.forge.IsEverythingOnMaster()
log.Printf("Master branch check. %d total repos. (%d git pulled) (%d not on master branch) (%s) git pull total=FIXME%d\n", total, count, nope, shell.FormatDuration(time.Since(now)), len(stats))
return nil
}
log.Info("do a pull check here?")
return nil
}
func rillPull(repo *gitpb.Repo) error {
if repo.IsDirty() {
// never do dirty repos
return nil
}
t, _ := repo.LastGitPull()
if time.Since(t) < time.Minute*10 && !argv.Force {
if argv.Verbose {
log.Info(repo.GetFullPath(), "git pulled too recently", shell.FormatDuration(time.Since(t)))
}
return nil
}
cur := repo.GetCurrentBranchName()
if !repo.IsBranchRemote(cur) {
if argv.Verbose {
log.Info(repo.GetFullPath(), "branch is not remote", cur)
}
return nil
}
var cmd []string
cmd = append(cmd, "git", "pull")
err := repo.RunVerbose(cmd)
if err != nil {
log.Info(repo.GetFullPath(), "git pull err:", err)
}
return nil
}
/*
// git fetch origin master:master
func rillFetchMaster(repo *gitpb.Repo) error {
if repo.GetCurrentBranchName() != repo.GetUserBranchName() {
// only fetch when branch is on the user branch
return nil
}
branch := repo.GetMasterBranchName()
cmd := []string{"git", "fetch", "origin", branch + ":" + branch}
err := repo.RunVerbose(cmd)
return err
}
func doGitFetch() {
me.forge.RillFuncError(rillFetchMaster)
count := me.forge.RillReload()
if count != 0 {
me.forge.ConfigSave()
}
}
*/

View File

@ -1,28 +0,0 @@
package main
// An app to submit patches for the 30 GO GUI repos
func doRedoGoMod() {
// me.forge.RillRedoGoMod()
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if err := repo.ValidGoSum(); err == nil {
continue
}
if err := repo.RunStrict([]string{"go-mod-clean"}); err != nil {
badExit(err)
}
}
}
func doEraseGoMod() {
/*
var cmds [][]string
cmds = append(cmds, []string{"rm", "-f", "go.mod", "go.sum"})
errs := me.forge.RillCmds(me.packs, cmds)
foreach x, y := range errs {
log.Info("EraseGoMod() error", x.GoPath, y)
}
*/
}

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

18
exit.go
View File

@ -1,21 +1,33 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (
"os" "os"
"go.wit.com/gui"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
) )
func okExit(thing string) { func okExit(thing string) {
log.Info(thing, "ok") gui.UnloadToolkits()
if configSave { if configSave {
me.forge.SetConfigSave(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() me.forge.Exit()
} }
func badExit(err error) { 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("%s FAILED: %v\n", repo.GetNamespace(), err)
os.Exit(-1) os.Exit(-1)
} }

View File

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

View File

@ -1,92 +0,0 @@
package main
import (
"go.wit.com/log"
)
func findRepos() bool {
var done bool = false
if argv.FindAll {
findAll()
done = true
}
if argv.FindPrivate {
findPrivate()
done = true
}
if argv.FindMine {
findMine()
done = true
}
if argv.FindFavorites {
findFavorites()
done = true
}
// this is the 'default' behavior when no command line arguments are given
// if no argv was set, select repos marked as 'mine'
if !done {
findMine()
done = true
}
return done
}
func findPrivate() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
if me.forge.Config.IsPrivate(repo.GetGoPath()) {
me.found.AppendUniqueGoPath(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.AppendUniqueGoPath(repo)
}
}
}
// finds repos that are writable
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.AppendUniqueGoPath(repo)
}
}
}
func findAll() {
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
me.found.AppendUniqueGoPath(repo)
if me.forge.Config.IsReadOnly(repo.GetGoPath()) && !argv.FindReadOnly {
if repo.ReadOnly {
continue
}
log.Info("todo: ConfigSave() readonly flag on repo is wrong", repo.GetGoPath())
repo.ReadOnly = true
configSave = true
continue
}
}
/*
if configsave {
log.Info("should ConfigSave here")
me.forge.Repos.ConfigSave()
}
*/
}

View File

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

View File

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

340
helperPatches.go Normal file
View File

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

245
main.go
View File

@ -1,11 +1,15 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
// An app to submit patches for the 30 GO GUI repos // An app to submit patches for the 30 GO GUI repos
import ( import (
"embed"
"strings" "strings"
"go.wit.com/dev/alexflint/arg" "go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb" "go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log" "go.wit.com/log"
@ -15,12 +19,20 @@ import (
var VERSION string var VERSION string
var BUILDTIME string var BUILDTIME string
// this optionally can store the GUI plugins
//
//go:embed resources/*
var resources embed.FS
// used for shell auto completion
var ARGNAME string = "forge"
// using this for now. triggers config save // using this for now. triggers config save
var configSave bool var configSave bool
func getVersion(repo *gitpb.Repo, name string) string { func getVersion(repo *gitpb.Repo, name string) string {
cmd := []string{"git", "describe", "--tags", "--always", name} cmd := []string{"git", "describe", "--tags", "--always", name}
result := repo.RunQuiet(cmd) result, _ := repo.RunQuiet(cmd)
output := strings.Join(result.Stdout, "\n") output := strings.Join(result.Stdout, "\n")
log.Info("cmd =", cmd, output) log.Info("cmd =", cmd, output)
@ -29,97 +41,190 @@ func getVersion(repo *gitpb.Repo, name string) string {
func main() { func main() {
me = new(mainType) me = new(mainType)
me.pp = arg.MustParse(&argv) me.myGui = prep.Gui() // prepares the GUI package for go-args
me.urlbase = argv.URL me.auto = prep.Bash3(&argv) // this line should be: prep.Bash(&argv)
me.urlbase = strings.Trim(me.urlbase, "/") // track down why trailing '/' makes http POST not work
// load the ~/.config/forge/ config // me.auto = prep.Bash3(argv.DoAutoComplete, &argv) // this line should be: prep.Bash(&argv)
me.forge = forgepb.Init() // arg.MustParse(&argv) // these three lines are becoming terrible syntax
me.found = new(gitpb.Repos) // me.auto = prep.MustParse(&argv) // try to make this work?
if configSave { me.forge = forgepb.Init() // init forge.pb
me.forge.ConfigSave() me.forge.ScanRepoDir() // looks for new dirs, checks existing repos for changes
configSave = false
}
if argv.Delete != "" { // initialize patches
me.forge.DeleteByGoPath(argv.Delete) doPatchInit()
me.forge.SetConfigSave(true)
okExit("")
}
/*
// var count int
all := me.forge.Repos.SortByFullPath()
for all.Scan() {
repo := all.Next()
verifyPrint(repo)
}
*/
if argv.Fix {
okExit("")
}
// first find the repos or gopaths to operate on // first find the repos or gopaths to operate on
if argv.Config { if argv.Config != nil {
if findConfig() { doConfig()
me.forge.ConfigPrintTable()
okExit("") okExit("")
} }
} else {
findRepos()
}
// okExit("")
log.Info("found", me.found.Len(), "repos. found", len(me.foundPaths), "paths from .config/forge") if argv.Commit != nil {
doCommit()
// now, do something to all of them (or just print out a table of them) okExit("")
var done bool = false
if argv.DoScan {
doScan()
done = true
} }
if argv.DoRedoGoMod { if argv.BuildForge {
doRedoGoMod() buildForge()
done = true okExit("")
} }
if argv.DoGitPull { if argv.Checkout != nil {
doGitPull() if err := doCheckout(); err != nil {
done = true badExit(err)
}
okExit("")
} }
if argv.DoGitReset { if argv.Build != "" {
doGitReset() if err := doBuild(); err != nil {
done = true badExit(err)
}
okExit("")
} }
if argv.DoList { if argv.Install != "" {
if err := doInstall(); err != nil {
badExit(err)
}
okExit("")
}
if argv.Clean != nil {
if err := doClean(); err != nil {
badExit(err)
}
me.forge.ConfigSave()
okExit("")
}
if argv.Help != nil {
doHelp()
okExit("")
}
if argv.Dirty != nil {
doDirty()
okExit("")
}
if argv.Tag != nil {
doTag()
okExit("")
}
if argv.Normal != nil {
if argv.Normal.On != nil {
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
log.Info("you are already in the normal state")
okExit("")
}
setForgeMode(forgepb.ForgeMode_NORMAL)
log.Info("normal mode on")
okExit("")
}
if argv.Normal.Off != nil {
if me.forge.Config.Mode != forgepb.ForgeMode_NORMAL {
log.Info("you were aleady not in the normal state")
okExit("")
}
setForgeMode(forgepb.ForgeMode_DEVEL)
log.Info("normal mode off")
okExit("")
}
if doNormal() {
log.Infof("all %d repos are on your user branch. It is safe to write code now.\n", me.forge.Repos.Len())
if me.forge.Config.Mode != forgepb.ForgeMode_NORMAL {
log.Infof("Forge has set the mode to 'Normal'\n")
setForgeMode(forgepb.ForgeMode_NORMAL)
}
okExit("")
}
okExit("")
}
// if you are in "normal" mode, always run normal every time to catch accidental errors
// for example, if you accidentally changed branches from your user branch
if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL {
if doNormal() {
log.Infof("all your %d repos are in a normal stete for development\n", me.forge.Repos.Len())
}
}
if argv.Merge != nil {
if err := doMerge(); err != nil {
badExit(err)
}
okExit("")
}
if argv.Pull != nil {
doPull()
okExit("")
}
if argv.List != nil {
found := argv.List.findRepos()
// print out the repos // print out the repos
doCobol() if argv.List.Full {
done = true me.forge.PrintHumanTableFull(found)
} else {
me.forge.PrintHumanTable(found)
}
okExit("")
} }
if argv.DoPatchSet { if argv.Patch != nil {
sendDevelDiff() if err := doPatch(); err != nil {
// sendMasterDiff() badExit(err)
okExit("patches") }
okExit("")
} }
if argv.ListPatchSet { // open the gui unless the user performed some other
listPatches() // basically, if you run just 'forge' it should open the GUI
okExit("patches")
}
// do the gui at the very end if argv.Gui != nil {
if argv.DoGui { // if opening the GUI, always check git for dirty repos
doGui() me.forge.CheckDirty()
me.myGui.Start() // loads the GUI toolkit
doGui() // start making our forge GUI
debug() // sits here forever
} }
if !done { // got to the end with nothing to do (?)
// if nothing was selected, print out a table of them on STDOUT if dumpWorkRepos() {
doCobol() // 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("") 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("")
}

50
post.go
View File

@ -1,50 +0,0 @@
package main
import (
"bytes"
"io/ioutil"
"net/http"
"os"
"os/user"
"go.wit.com/log"
)
func httpPost(url string, data []byte) ([]byte, error) {
var err error
var req *http.Request
// data := []byte("some junk")
// url := "https://go.wit.com/register/"
req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
log.Info("httpPost() with len", len(data), "url", url)
usr, _ := user.Current()
req.Header.Set("author", usr.Username)
hostname, _ := os.Hostname()
req.Header.Set("hostname", hostname)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Error(err)
return []byte("client.Do(req) error"), err
}
defer resp.Body.Close()
log.Info("httpPost() with len", len(data))
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Error(err)
return body, err
}
// test := strings.TrimSpace(string(body))
// log.Info("go.wit.com returned body:", test)
// if test == "OK" {
// return body, nil
// }
return body, nil
}

View File

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

22
resources/forge.text Normal file
View File

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

102
send.go
View File

@ -1,102 +0,0 @@
// Copyright 2024 WIT.COM Inc Licensed GPL 3.0
package main
import (
"strings"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/log"
)
func sendPatches(pset *forgepb.Patchs) error {
var url string
url = me.urlbase + "/patchset"
msg, err := pset.Marshal()
if err != nil {
log.Info("proto.Marshal() failed:", err)
return err
}
log.Info("proto.Marshal() msg len", len(msg))
body, err := httpPost(url, msg)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
test := strings.TrimSpace(string(body))
// log.Info("virtigo returned body:", test)
for _, line := range strings.Split(test, "\n") {
log.Info("got back:", line)
}
return nil
}
func listPatches() error {
var url string
url = me.urlbase + "/patchsetlist"
body, err := httpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
var last string
test := strings.TrimSpace(string(body))
for _, line := range strings.Split(test, "\n") {
log.Info("patchset:", line)
last = strings.TrimSpace(line)
}
getPatch(last)
return nil
}
func getPatch(pbfile string) error {
url := me.urlbase + "/patchsetget?filename=" + pbfile
log.Info("getPatch() url", url)
body, err := httpPost(url, nil)
if err != nil {
log.Info("httpPost() failed:", err)
return err
}
log.Info("getPatch() len(body)", len(body))
return nil
}
func sendDevelDiff() {
pset, err := me.forge.MakeDevelPatchSet()
if err != nil {
badExit(err)
}
if err := sendPatches(pset); err != nil {
badExit(err)
}
/*
all := pset.SortByFilename()
for all.Scan() {
p := all.Next()
log.Info("read in patch:", p.Filename)
}
err = me.forge.SendPatchSet(pset)
if err != nil {
badExit(err)
}
*/
}
func sendMasterDiff() {
pset, err := me.forge.MakeMasterPatchSet()
if err != nil {
badExit(err)
}
all := pset.SortByFilename()
for all.Scan() {
p := all.Next()
log.Info("read in patch:", p.Filename)
}
err = me.forge.SendPatchSet(pset)
if err != nil {
badExit(err)
}
}

View File

@ -1,11 +1,13 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main package main
import ( import (
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/gadgets" "go.wit.com/lib/gadgets"
"go.wit.com/lib/gui/prep"
"go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
) )
var me *mainType var me *mainType
@ -18,54 +20,32 @@ func (b *mainType) Enable() {
b.mainbox.Enable() b.mainbox.Enable()
} }
// returns the server to connect to
func myServer() string {
return me.forge.GetForgeURL()
}
// this app's variables // this app's variables
type mainType struct { type mainType struct {
pp *arg.Parser // for parsing the command line args. Yay to alexf lint! // 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 forge *forgepb.Forge // for holding the forge protobuf files
myGui *gui.Node // the gui toolkit handle myGui *prep.GuiPrep // for initializing the GUI toolkits
found *gitpb.Repos // stores the list of repos to process things on
foundPaths []string // stores gopaths to act on (when doing go-clone) foundPaths []string // stores gopaths to act on (when doing go-clone)
configSave bool // if the config file should be saved after finishing configSave bool // if the config file should be saved after finishing
urlbase string // base URL urlbase string // base URL
// our view of the repositories
repos *repoWindow
mainWindow *gadgets.BasicWindow mainWindow *gadgets.BasicWindow
mainbox *gui.Node // the main box. enable/disable this
autoDryRun *gui.Node // checkbox for --dry-run
goSrcPwd *gadgets.OneLiner // what is being used as primary directory for your work
goSrcEdit *gadgets.BasicEntry // what is being used as primary directory for your work
gitAuthor *gadgets.OneLiner // ENV GIT_AUTHOR NAME and EMAIL
// the main box. enable/disable this // these hold the branches that the user can switch all the repositories to them
mainbox *gui.Node reposWinB *gui.Node // button that opens the repos window
repoAllB *gui.Node // "all" repos button
// the window from the /lib/gui/gowit package repoDirtyB *gui.Node // "dirty" repos button
lw *gadgets.BasicWindow repoDevelMergeB *gui.Node // "merge to devel" repos button
repoWritableB *gui.Node // "what repos are writable" repos button
// #### Sorting options for the repolist
autoHidePerfect *gui.Node
autoHideReadOnly *gui.Node
// checkbox for --dry-run
autoDryRun *gui.Node
// checkbox to enable intermittent scanning
// if checked, it will check all your repos for changes
autoScanReposCB *gui.Node
// what is being used as your home dir
userHomePwd *gadgets.OneLiner
// what is being used as ~/go/src
goSrcPwd *gadgets.OneLiner
// displays a summary of all the repos
// has total dirty, total read-only
// total patches, etc
summary *patchSummary
// when switch to user or devel branches, autocreate them
autoCreateBranches *gui.Node
// these hold the branches that the user can switch all
// the repositories to them
newBranch *gui.Node
setBranchB *gui.Node
} }

View File

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

0
test Normal file
View File

118
windowHowto.go Normal file
View File

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

217
windowPatches.go Normal file
View File

@ -0,0 +1,217 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
"go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
type stdPatchTableWin struct {
sync.Mutex
win *gadgets.GenericWindow // the machines gui window
box *gui.Node // the machines gui parent box widget
TB *forgepb.PatchesTable // the gui table buffer
update bool // if the window should be updated
}
func (w *stdPatchTableWin) Toggle() {
if w == nil {
return
}
if w.win == nil {
return
}
w.win.Toggle()
}
func makePatchesWin(patches *forgepb.Patches) *stdPatchTableWin {
dwin := new(stdPatchTableWin)
dwin.win = gadgets.NewGenericWindow("current patches", "patching options")
dwin.win.Custom = func() {
log.Info("test delete window here")
dwin.win.Hide()
// dwin = nil
}
grid := dwin.win.Group.RawGrid()
grid.NewLabel(fmt.Sprintf("%d", patches.Len()))
grid.NewLabel(fmt.Sprintf("total patches"))
grid.NextRow()
repomap := make(map[string]int)
all := patches.All()
for all.Scan() {
patch := all.Next()
repomap[patch.Namespace] += 1
}
grid.NewLabel(fmt.Sprintf("%d", len(repomap)))
grid.NewLabel(fmt.Sprintf("total repos"))
grid.NextRow()
grid.NewButton("Apply All", func() {
var count int
all := patches.SortByFilename()
for all.Scan() {
p := all.Next()
applyPatchNew(p)
}
log.Info("ALL PATCHES WORKED! count =", count)
})
// make a box at the bottom of the window for the protobuf table
dwin.box = dwin.win.Bottom.Box().SetProgName("TBOX")
if patches != nil {
dwin.doPatchesTable(patches)
}
return dwin
}
func applyPatchNew(p *forgepb.Patch) error {
rn := p.Namespace
repo := me.forge.FindByGoPath(rn)
if repo == nil {
log.Info("Could not figure out repo path", rn)
return log.Errorf("%s namespace?\n", rn)
}
if _, err := applyAndTrackPatch(repo, p); err != nil {
cmd := []string{"git", "am", "--abort"}
err := repo.RunVerbose(cmd)
log.Info("warn user of git am error", err)
return err
}
return nil
}
func (dwin *stdPatchTableWin) doPatchesTable(currentPatches *forgepb.Patches) {
dwin.Lock()
defer dwin.Unlock()
if dwin.TB != nil {
dwin.TB.Delete()
dwin.TB = nil
}
// display the protobuf
dwin.TB = AddPatchesPB(dwin.box, currentPatches)
f := func(p *forgepb.Patch) {
log.Info("do something with patch", p.Filename)
}
dwin.TB.Custom(f)
}
// used by the PB table
func applyPatchLabel(p *forgepb.Patch) string {
rn := p.Namespace
if repo := me.forge.FindByGoPath(rn); repo == nil {
// log.Info("Could not figure out repo path", rn)
return ""
}
if p.NewHash == "" {
return "git am"
}
return "done"
}
func applyPatchClick(p *forgepb.Patch) {
if err := applyPatchNew(p); err != nil {
log.Info("git am failed on file", p.Filename, "with error", err)
return
}
log.Info("ran: git am", p.Filename, "ok")
}
// define what rows to have in the protobuf table
func AddPatchesPB(tbox *gui.Node, pb *forgepb.Patches) *forgepb.PatchesTable {
t := pb.NewTable("PatchesPB")
t.NewUuid()
t.SetParent(tbox)
gitam := t.AddButtonFunc("apply", applyPatchLabel)
gitam.Custom = applyPatchClick
t.AddCommitHash()
t.AddNamespace()
// t.AddFilename()
t.AddStringFunc("file", func(p *forgepb.Patch) string {
_, fname := filepath.Split(p.Filename)
return fname
})
t.AddCommitHash()
t.ShowTable()
return t
}
func applyPatch(repo *gitpb.Repo, filename string) error {
cmd := []string{"git", "am", filename}
err := repo.RunVerbose(cmd)
return err
}
func savePatch(p *forgepb.Patch) (string, error) {
_, filen := filepath.Split(p.Filename)
tmpname := filepath.Join("/tmp", filen)
log.Info("saving as", tmpname, p.Filename)
raw, err := os.OpenFile(tmpname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return "", err
}
raw.Write(p.Data)
raw.Close()
return tmpname, nil
}
func applyAndTrackPatch(repo *gitpb.Repo, p *forgepb.Patch) (string, error) {
_, filen := filepath.Split(p.Filename)
tmpname := filepath.Join("/tmp", filen)
log.Info("saving as", tmpname, p.Filename)
raw, err := os.OpenFile(tmpname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return "", err
}
raw.Write(p.Data)
raw.Close()
cmd := []string{"git", "am", tmpname}
err = repo.RunVerbose(cmd)
if err != nil {
log.Info("git am failed. run 'git am --abort' here")
return "", log.Errorf("git am failed")
}
log.Info("Try to find hash value now")
p.NewHash = "fixme applyAndTrack"
if setNewHash(p, p.NewHash) {
log.Info("setting NewHash worked", p.NewHash)
}
me.forge.SavePatchsets()
return p.NewHash, log.Errorf("did not lookup new hash")
}
func setNewHash(p *forgepb.Patch, hash string) bool {
for pset := range me.forge.Patchsets.IterAll() {
for patch := range pset.Patches.IterAll() {
if patch.CommitHash == hash {
patch.NewHash = hash
log.Info("found patch in repo")
me.forge.SavePatchsets()
return true
}
}
}
return false
}

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
}

393
windowReposFix.go Normal file
View File

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

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
}

147
windowViewRepoPatches.go Normal file
View File

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