Compare commits

...

201 Commits

Author SHA1 Message Date
Jeff Carr ab666ddbc3 rm old code 2025-09-16 23:25:22 -05:00
Jeff Carr 0b30cd36dc misc 2025-09-16 09:32:36 -05:00
Jeff Carr 2a47f1e547 cleaning up obscure git cases 2025-09-13 05:33:31 -05:00
Jeff Carr 719287c3bf changes to Reload() 2025-09-13 00:52:59 -05:00
Jeff Carr 6654dbb410 tag handling is still bad 2025-09-12 14:53:39 -05:00
Jeff Carr 62e5fc396c pass the full filename to ConfigSave() 2025-09-12 10:13:59 -05:00
Jeff Carr e793c89712 yep. switch to thins 2025-09-12 02:03:32 -05:00
Jeff Carr 9d968721d0 rm GOSRC 2025-09-11 23:10:49 -05:00
Jeff Carr 5a98058474 add arg for packaging 2025-09-11 15:04:06 -05:00
Jeff Carr 1d8380b9e7 pull out the namespace 2025-09-11 14:21:37 -05:00
Jeff Carr e395456c53 cleaning up .git/config parsing 2025-09-11 13:40:48 -05:00
Jeff Carr cb60421374 rm more readonly stuff finally 2025-09-11 06:41:22 -05:00
Jeff Carr c2e3108698 rm readonly checks finally 2025-09-11 06:39:34 -05:00
Jeff Carr bf59b9124a cleanup output. start not doing stuff like this anymore 2025-09-11 06:31:45 -05:00
Jeff Carr 33c585f4cf look for out of date remote branches 2025-09-11 05:18:19 -05:00
Jeff Carr 7d60a495ca notes for the future 2025-09-08 08:40:35 -05:00
Jeff Carr 1745f44aa0 file is autogen'd now 2025-09-08 04:03:43 -05:00
Jeff Carr f9c8a37491 http autogen testing 2025-09-08 03:38:03 -05:00
Jeff Carr 6a99b7b099 start using a standard http PB 2025-09-07 21:41:23 -05:00
Jeff Carr 97fe127345 fields for 'forge pull' 2025-09-07 12:06:42 -05:00
Jeff Carr da649abceb sometimes, just care about the sun 2025-09-06 21:51:09 -05:00
Jeff Carr 722ebeeef8 verbose merge 2025-09-05 13:24:00 -05:00
Jeff Carr 5f882d1388 more branch merge tracking 2025-09-05 01:59:10 -05:00
Jeff Carr 658a2ce1a2 make something to get the hashes betwen branches 2025-09-05 01:53:01 -05:00
Jeff Carr db05f8a8aa stuff 2025-09-04 21:50:09 -05:00
Jeff Carr 193f27ec30 add some more git .config fields 2025-09-04 21:35:05 -05:00
Jeff Carr e7c6156562 testing something to cut strings to a certain width 2025-09-04 18:47:48 -05:00
Jeff Carr 1e0f1e8e88 work on sending tables to stdout also 2025-09-04 18:34:39 -05:00
Jeff Carr a4a48d7480 minor 2025-09-03 19:08:17 -05:00
Jeff Carr 6b1e922aaa missing \n in Printf() 2025-08-31 16:25:20 -05:00
Jeff Carr 46d61345af go back to global mutex again. hmmm. 2025-08-31 13:28:40 -05:00
Jeff Carr 434f62a7e6 try mutex locks again 2025-08-31 12:25:13 -05:00
Jeff Carr f585edc192 minor syntax changes 2025-08-29 10:29:36 -05:00
Jeff Carr d47d25e3a6 quiet output 2025-08-21 12:30:46 -05:00
Jeff Carr 5460316daf minor 2025-08-20 12:59:28 -05:00
Gemini 72b5864000 refactor(gitpb): Modify deleteProtobufFile to remove file instead of exiting 2025-08-20 12:56:43 -05:00
Jeff Carr aef107af0d quiet older printf's to STDOUT 2025-08-20 11:45:16 -05:00
Jeff Carr f08a517bc4 something also simple 2025-08-20 00:01:29 -05:00
Jeff Carr 1ead02a6d3 experiement with ~/.cache/forge/ 2025-08-18 06:11:20 -05:00
Jeff Carr 68af638f9e something to make user branches 2025-07-21 22:25:27 -05:00
Jeff Carr 1c43978294 add debugging printf's 2025-07-08 15:48:59 -05:00
Jeff Carr d0616fae03 add namespace 2025-07-07 18:52:15 -05:00
Jeff Carr 33399f18e5 SendPB() 2025-07-04 09:42:57 -05:00
Jeff Carr e2ca78cfb3 send a repos PB 2025-07-02 12:51:36 -05:00
Jeff Carr 683522ee46 version v4: use 'namespace' as primary field 2025-06-30 07:45:30 -05:00
Jeff Carr d4f9878298 fixes old UTF8 tags in uboot 2025-06-29 02:49:31 -05:00
Jeff Carr fcc5a36ad0 try to fix defective "Git on Borg" repos 2025-04-04 06:03:42 -05:00
Jeff Carr 7297b63339 fixed name as a 'scanner' 2025-03-27 06:26:46 -05:00
Jeff Carr acc18b186f junk code 2025-03-23 04:47:38 -05:00
Jeff Carr 27820d88f2 discover branches 2025-03-22 21:38:18 -05:00
Jeff Carr 0bf8cc3d79 now using the awesome golang 1.24 'iter' 2025-03-19 06:40:32 -05:00
Jeff Carr 88d25c85c2 work related to autogenpb IterBy() functions 2025-03-18 15:15:19 -05:00
Jeff Carr dba126cfdf quiet debugging output 2025-03-11 12:00:53 -05:00
Jeff Carr 9ad1193746 rearrange code 2025-03-05 22:06:44 -06:00
Jeff Carr f58f7df9ac rm code now made by autogenpb 2025-03-05 21:41:58 -06:00
Jeff Carr c572bb2f04 start passing guipb.Widget 2025-03-05 20:14:33 -06:00
Jeff Carr 0c4bf8e9bb generic table protobuf buttons work 2025-03-05 19:39:30 -06:00
Jeff Carr b6b0cadde5 code moved to autogenpb 2025-03-05 13:33:16 -06:00
Jeff Carr b38cf5f0fa nil check 2025-03-05 02:59:11 -06:00
Jeff Carr cc51fd364d show uuid 2025-03-04 15:46:14 -06:00
Jeff Carr be1c2d0f11 attempt delete table 2025-03-04 14:32:54 -06:00
Jeff Carr 2d439a0468 work on a table Update() 2025-03-03 11:59:30 -06:00
Jeff Carr 5ec788fe17 wrong logic 2025-02-21 17:17:10 -06:00
Jeff Carr 587a3debeb rm old code 2025-02-21 15:38:18 -06:00
Jeff Carr 2204624369 for forge 2025-02-21 10:21:25 -06:00
Jeff Carr 27c8c38047 rm old code 2025-02-21 09:34:41 -06:00
Jeff Carr 3f1c8bf5c2 used for forge protobuf tables 2025-02-21 09:14:01 -06:00
Jeff Carr 135346af6a add autogenpb:gui 2025-02-20 09:39:42 -06:00
Jeff Carr 5d0f74360f finally use protobuf for this 2025-02-15 18:51:56 -06:00
Jeff Carr c9149bbb13 keep switching over to the protobuf 2025-02-15 17:07:49 -06:00
Jeff Carr 010791e828 actually use the damn information I already put in a protobuf 2025-02-15 05:34:12 -06:00
Jeff Carr 312c4742b6 needed for building deb packages 2025-02-14 20:43:31 -06:00
Jeff Carr e5a5454bc0 minor improvments 2025-02-14 18:39:59 -06:00
Jeff Carr e127f53bd4 check branch differences 2025-02-13 23:39:07 -06:00
Jeff Carr 9cc9b9bc87 detect more devel branch problems 2025-02-09 16:48:45 -06:00
Jeff Carr bb54c065ad check for the terrible situation of devel < master 2025-02-09 14:18:18 -06:00
Jeff Carr 11d1136797 oops 2025-02-08 06:33:30 -06:00
Jeff Carr 6f66340d82 general parsing routines for go-mod-clean 2025-02-07 09:49:08 -06:00
Jeff Carr 31702354f2 'deverr' was useful at the beginning when testing this code 2025-02-02 15:05:17 -06:00
Jeff Carr 7cacc395ef auto formatting with autogenpb 2025-02-01 12:01:42 -06:00
Jeff Carr d62954bb63 notes on git variables 2025-01-30 18:00:41 -06:00
Jeff Carr a07ad8bae8 attempt at merge to master 2025-01-30 11:58:27 -06:00
Jeff Carr 32a5530129 making safety checks 2025-01-30 11:50:35 -06:00
Jeff Carr d01cb1c9d7 put the checking logic here 2025-01-30 11:27:48 -06:00
Jeff Carr 387f69631b trying to fix user create branch 2025-01-30 02:24:47 -06:00
Jeff Carr 57e38ee8ce just name fixups 2025-01-30 01:48:17 -06:00
Jeff Carr 0bd0af4845 need to track times to throttle access 2025-01-30 01:15:41 -06:00
Jeff Carr 7ba0c49ee3 notsure 2025-01-29 20:43:14 -06:00
Jeff Carr b57144e6bf some useful helper functions 2025-01-29 20:01:07 -06:00
Jeff Carr 19fb3a29fb update old code. still not great 2025-01-29 16:19:11 -06:00
Jeff Carr 53d986bf59 ignore stuff 2025-01-29 12:23:55 -06:00
Jeff Carr 807a965602 rm old code 2025-01-29 12:22:03 -06:00
Jeff Carr 6b8ef6fc60 last commit for the day 2025-01-29 12:22:03 -06:00
Jeff Carr 829b6ba55f add IsBranchRemote() 2025-01-29 12:22:03 -06:00
Jeff Carr 336ab60e01 need better handling here 2025-01-20 01:40:47 -06:00
Jeff Carr 3b17710c1a more rill improvements 2025-01-19 16:08:06 -06:00
Jeff Carr 53acead41e might show branch age? 2025-01-19 10:49:08 -06:00
Jeff Carr 966e3d7858 recreate user branches 2025-01-19 08:48:43 -06:00
Jeff Carr d4cc68c07f no need to warn anymore on nonexistent user branch 2025-01-19 07:06:31 -06:00
Jeff Carr 90e20c4ff0 protobuf version checking works 2025-01-19 03:19:06 -06:00
Jeff Carr b0595d6a1d remove every mutex change since Marshal() was crashing 2025-01-19 02:37:58 -06:00
Jeff Carr 84c9fe2bfc die on protobuf mis-match 2025-01-19 00:36:48 -06:00
Jeff Carr 723ca5e829 add os.Stat() check for Reload() 2025-01-18 23:26:41 -06:00
Jeff Carr 93e4eae19d move away from maps in protobuf? 2025-01-18 15:49:13 -06:00
Jeff Carr 9094779044 jesus. totally forgot *.swp in most of my repos 2025-01-18 11:13:20 -06:00
Jeff Carr 0c30a9da2f parse .git/config 2025-01-18 10:34:58 -06:00
Jeff Carr 80f602c4d9 work on branch detection 2025-01-18 07:48:03 -06:00
Jeff Carr d014dbe3d4 early common things for branch handling 2025-01-18 03:59:44 -06:00
Jeff Carr 803f0cf98e add a way to track a tag 2025-01-17 13:21:03 -06:00
Jeff Carr 660255c8a3 add ExamineBranches() 2025-01-17 11:00:06 -06:00
Jeff Carr 0773f20d00 better output 2025-01-17 09:48:29 -06:00
Jeff Carr a477ea8f8f fix git pull output 2025-01-17 06:04:36 -06:00
Jeff Carr bd925757df more accurate repo Age() 2025-01-17 04:48:08 -06:00
Jeff Carr 8c06b25fc2 set the uuid and version 2025-01-17 02:51:46 -06:00
Jeff Carr ceb2d6d5c8 quiet expected errors 2025-01-13 08:56:49 -06:00
Jeff Carr 0b5caa03e5 tinkering with the mutex / Marshal() problem 2025-01-13 08:13:01 -06:00
Jeff Carr 25ab223c07 testing mutex locks and Marshal() function panic 2025-01-13 05:56:45 -06:00
Jeff Carr 208118ad5f do mutex entries cause Marshal() to fail? 2025-01-13 04:14:51 -06:00
Jeff Carr 4aa85a2f0f fixes for new autogenpb 2025-01-12 20:05:57 -06:00
Jeff Carr 7df10fba51 fix order 2025-01-11 06:08:48 -06:00
Jeff Carr de71f2692a fixes for new autogenpb 2025-01-10 04:23:13 -06:00
Jeff Carr 52bbf4c827 minor
Signed-off-by: Jeff Carr <jcarr@wit.com>
2025-01-09 20:44:45 -06:00
Jeff Carr 6c5a858125 minor 2025-01-08 10:11:49 -06:00
Jeff Carr 0ca1240c75 quiet output 2025-01-08 04:08:34 -06:00
Jeff Carr b0fca659c5 fix the dumb names 2025-01-08 03:13:09 -06:00
Jeff Carr ebda2ea222 keep working the problem 2025-01-08 02:38:50 -06:00
Jeff Carr d4a31b8e0b always set the State for now 2025-01-07 21:31:15 -06:00
Jeff Carr 3298e02c2a wow, even a name change is a BAD IDEA. NEVER DO IT 2025-01-07 21:23:21 -06:00
Jeff Carr f21c471032 add RunVerbose() 2025-01-07 18:45:19 -06:00
Jeff Carr 8c54c4a7d8 rm old code 2025-01-07 18:24:27 -06:00
Jeff Carr 366f4680ac add state. go generate autogenpb 2025-01-07 18:06:54 -06:00
Jeff Carr 676cb338a4 super dumb logic error here 2025-01-07 07:09:49 -06:00
Jeff Carr 4d093bc1e3 silence stuff 2025-01-07 05:58:49 -06:00
Jeff Carr 2fe035fbf0 migrate in some old code 2025-01-07 04:59:01 -06:00
Jeff Carr 016335b322 simple check for devel branch 2025-01-07 03:24:21 -06:00
Jeff Carr bbd34df599 duh. logic was backwards 2025-01-05 18:42:12 -06:00
Jeff Carr 754e60fffa try to verify the tag is greater than the past tags 2025-01-05 17:29:07 -06:00
Jeff Carr 80130ab563 more notes 2025-01-05 13:32:24 -06:00
Jeff Carr de75f95161 Merge branch 'jcarr' into devel 2025-01-05 12:14:36 -06:00
Jeff Carr 22b157f6d7 add merge things 2025-01-05 12:02:54 -06:00
Jeff Carr 6112d6e298 a simple get DevelHash() 2025-01-05 06:15:35 -06:00
Jeff Carr bab03875d2 a simple get DevelHash() 2025-01-05 03:22:04 -06:00
Jeff Carr 010a2c6b58 shortcut functions to see what branch is checked out 2024-12-30 04:41:13 -06:00
Jeff Carr 4446068e1a need to switch to the slices package 2024-12-27 14:40:27 -06:00
Jeff Carr 40830d3f38 weird. NewestAge() took _seconds_ to run in some cases 2024-12-27 04:55:57 -06:00
Jeff Carr 328fbe9fdd rm duplicate code 2024-12-18 23:06:27 -06:00
Jeff Carr 9935f45bd4 wasn't setting the dirty flag 2024-12-18 22:02:37 -06:00
Jeff Carr 36fdd99d15 this is annoying 2024-12-18 20:09:18 -06:00
Jeff Carr 406817d5b2 minor 2024-12-18 19:36:23 -06:00
Jeff Carr 22d2ae5cd7 is primitive fixes 2024-12-18 18:51:51 -06:00
Jeff Carr cbac3a5914 use os.Mkdir() 2024-12-18 18:00:27 -06:00
Jeff Carr a60188915d library for now 2024-12-17 23:27:17 -06:00
Jeff Carr af50986812 wasn't reloading versions 2024-12-17 23:01:13 -06:00
Jeff Carr 339393a88a keep trying to fix init() and update() 2024-12-17 21:15:19 -06:00
Jeff Carr bea54091d2 try to fix Current branch and version 2024-12-17 20:48:23 -06:00
Jeff Carr 10cc012a12 move Revert() here 2024-12-17 18:49:41 -06:00
Jeff Carr 233f7bca76 lots of changes to isolate exec 'git' 2024-12-17 06:37:14 -06:00
Jeff Carr 4bc95ad268 keep isolating use of os.Exec("git") 2024-12-17 01:15:31 -06:00
Jeff Carr c53da5a9a1 smarter and faster mtime logic 2024-12-17 00:00:49 -06:00
Jeff Carr a115ba144b work on supporting arbitrary paths 2024-12-16 03:04:39 -06:00
Jeff Carr cea5968b0f start trying to make smarter tracking of git changes 2024-12-16 00:21:08 -06:00
Jeff Carr 01b332ceb8 all of this code still sucks 2024-12-15 15:53:08 -06:00
Jeff Carr 5006d718fd tring to fix go-clone 2024-12-15 12:14:48 -06:00
Jeff Carr 5c1bebb16c output to help figure out what to do when this happens 2024-12-15 08:48:42 -06:00
Jeff Carr 5341a70557 shortcut to get the age of a repo 2024-12-14 11:28:34 -06:00
Jeff Carr 1e4b0c0d37 check for notes before deleting them 2024-12-13 17:31:40 -06:00
Jeff Carr 2574ec733a IsLocalBranch() 2024-12-13 16:18:25 -06:00
Jeff Carr d7a90a71ab optionally pass branch name 2024-12-13 13:52:09 -06:00
Jeff Carr adef980837 store autogenerated files in git notes 2024-12-13 12:57:02 -06:00
Jeff Carr c5f7570834 working on go-mod-clean
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-12-13 00:19:33 -06:00
Jeff Carr adddfad2b7 increment target version 2024-12-12 02:07:25 -06:00
Jeff Carr a0b6a435a7 TRUNC new file 2024-12-11 13:55:36 -06:00
Jeff Carr c1e3282864 add RunEcho() 2024-12-10 01:48:49 -06:00
Jeff Carr cb04f3585c debugging switching go/src dirs 2024-12-07 16:50:26 -06:00
Jeff Carr 945ae6329b early 'git pull' using rill 2024-12-06 01:50:28 -06:00
Jeff Carr 205e6f022b making config files more viable 2024-12-05 12:37:24 -06:00
Jeff Carr 77cd0ae36e always run goimports. misc other changes 2024-12-04 15:37:31 -06:00
Jeff Carr 4a64e3a7c2 fix logic 2024-12-03 22:36:06 -06:00
Jeff Carr 97a2f05cf2 rename 2024-12-03 18:03:54 -06:00
Jeff Carr 18905d7cf7 even with this, go mod tidy still DOWNLOADS STUPID OLD go.sum files 2024-12-03 13:25:00 -06:00
Jeff Carr aec1833fe6 might be right? 2024-12-03 03:19:22 -06:00
Jeff Carr 33880f9006 sure, why not. store a repo description 2024-12-03 01:59:04 -06:00
Jeff Carr 1d1d8e7eea lots of code hopefully better than before 2024-12-03 00:35:50 -06:00
Jeff Carr 7a90613c91 sort was reversed? 2024-12-02 08:45:42 -06:00
Jeff Carr 345343647a attempt SortByAge(). not sure if it works. 2024-12-02 07:01:37 -06:00
Jeff Carr 4b83d18db4 notsure 2024-12-02 05:15:39 -06:00
Jeff Carr 425cb042b9 cleanup old code 2024-12-01 22:37:11 -06:00
Jeff Carr cfb5a7adc0 remove old panic
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-12-01 22:27:13 -06:00
Jeff Carr 45fc9ea257 autogenpb now completely automatic 2024-12-01 22:23:38 -06:00
Jeff Carr 2ebdd32040 move example to wit-utils 2024-12-01 18:46:11 -06:00
Jeff Carr 390bbae435 stop being lazy. never make directories in go libraries 2024-12-01 17:57:07 -06:00
Jeff Carr 6b42d07949 avoid nil panic here 2024-12-01 16:40:41 -06:00
Jeff Carr 412658698a Repotype() working somewhat 2024-12-01 16:04:07 -06:00
Jeff Carr 82935ae6b2 attempt to detect new package dependancy versions 2024-12-01 15:10:55 -06:00
Jeff Carr fd3b806b4a add test app 2024-12-01 12:53:46 -06:00
Jeff Carr f24d8131c3 add IsDirectory() check 2024-12-01 11:44:20 -06:00
Jeff Carr 8e9ec30b29 save GoDeps at the time of publication to pkg.go.dev 2024-12-01 10:46:32 -06:00
Jeff Carr 455ea31d70 checkDirty() 2024-12-01 00:49:25 -06:00
Jeff Carr 8a6fb6d442 autogenpb 2024-11-30 15:07:57 -06:00
Jeff Carr 0faa5d01a0 autogenpb runs protoc finally 2024-11-30 13:48:31 -06:00
Jeff Carr c07d48c711 minor 2024-11-30 12:45:18 -06:00
44 changed files with 3667 additions and 627 deletions

5
.gitignore vendored
View File

@ -1,5 +1,8 @@
go.*
*.swp
*.patch
go.*
*.pb.go
scanGoSrc/scanGoSrc
validate/validate

3
.protobuf Normal file
View File

@ -0,0 +1,3 @@
gitTag.proto
goDep.proto
repo.proto

View File

@ -5,30 +5,24 @@
# go install
all: gitTag.pb.go goDep.pb.go repo.pb.go
make -C scanGoSrc/
all: gitTag.pb.go goDep.pb.go repo.pb.go goimports vet
vet: lint
GO111MODULE=off go vet
generate: clean
go-mod-clean
go generate
lint:
# -buf lint refs.proto # todo: figure out where buf comes from again
vet:
@GO111MODULE=off go vet
@echo this go library package builds okay
# autofixes your import headers in your golang files
goimports:
goimports -w *.go
make -C scanGoSrc/ goimports
redomod:
rm -f go.*
GO111MODULE= go mod init
GO111MODULE= go mod tidy
go mod edit -go=1.20
# dump autogenerated files and potential patches
clean:
rm -f *.pb.go
-rm -f go.*
make -C scanGoSrc clean
rm -f *.pb.go go.* *.patch
go-mod-clean purge
#refs.pb.go: refs.proto
# cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
@ -36,22 +30,39 @@ clean:
# refs.proto
gitTag.pb.go: gitTag.proto
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
--go_opt=MgitTag.proto=go.wit.com/lib/protobuf/gitpb \
gitTag.proto
autogenpb --proto gitTag.proto --sort "ByName,Refname" --sort "ByHash,Hash" --no-marshal
autogenpb --proto gitTag.proto --mutex-name "john"
goDep.pb.go: goDep.proto
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
--go_opt=MgoDep.proto=go.wit.com/lib/protobuf/gitpb \
goDep.proto
autogenpb --proto goDep.proto --sort "ByPath,GoPath" --sort "ByHash,Hash" --no-marshal
autogenpb --proto goDep.proto
repo.pb.go: repo.proto
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
--go_opt=Mrefs.proto=go.wit.com/lib/protobuf/gitpb \
autogenpb --proto repo.proto
protoc-bad:
cd ~/go/src && protoc \
--proto_path=. \
--proto_path=go.wit.com/lib/protobuf/gitpb \
--go_out=. \
--go_opt=Mgo.wit.com/lib/protobuf/gitpb/repo.proto=go.wit.com/lib/protobuf/gitpb \
--go_opt=MgitTag.proto=go.wit.com/lib/protobuf/gitpb \
--go_opt=MgoDep.proto=go.wit.com/lib/protobuf/gitpb \
go.wit.com/lib/protobuf/gitpb/repo.proto
protoc-good:
cd ~/go/src && protoc \
--proto_path=. \
--go_out=go.wit.com/lib/protobuf/gitpb \
--go_opt=Mrepo.proto=go.wit.com/lib/protobuf/gitpb \
--go_opt=MgitTag.proto=go.wit.com/lib/protobuf/gitpb \
repo.proto
autogenpb --proto repo.proto --sort "ByPath,GoPath"
--go_opt=MgoDep.proto=go.wit.com/lib/protobuf/gitpb \
go.wit.com/lib/protobuf/gitpb/repo.proto
protoc-todo-move-to-this:
# I think I should seperate these dirs. ONLY ONE .proto FILE PER DIRECTORY
# - httppb.HttpRequest httpRequest = 4; // correct syntax
protoc \
--proto_path=. \
--go_out=. \
--go_opt=Mgo.wit.com/lib/protobuf/forgepb/patchset.proto=go.wit.com/lib/protobuf/forgepb \
--go_opt=Mgo.wit.com/lib/protobuf/httppb/httpRequest.proto=go.wit.com/lib/protobuf/httppb \
go.wit.com/lib/protobuf/forgepb/patchset.proto

View File

@ -1 +1,12 @@
protobuf definition files for git repositories
# protobuf definition files for git repositories
# requires autogenpb & go-mod-clean
* go-clone go.wit.com/apps/autogenpb
* you might also need: go-clone go.wit.com/apps/utils/go-mod-clean
# Notes/TODO
* all autogen files are stored as git metadata this includes the go.mod and go.sum files
* this is done to keep the git commit log as clean as possible
* metadata can be manipulated using go-mod-clean

70
age.go Normal file
View File

@ -0,0 +1,70 @@
package gitpb
// functions that check the ages of files
// and track if the repo needs to be re-scanned
import (
"errors"
"os"
"path/filepath"
"time"
"go.wit.com/log"
)
func (repo *Repo) LastGitPull() (time.Time, error) {
return repo.oldMtime(".git/FETCH_HEAD")
}
func (repo *Repo) GoSumAge() (time.Duration, error) {
var mtime time.Time
var err error
mtime, err = repo.oldMtime("go.sum")
if err == nil {
return time.Since(mtime), nil
}
mtime, err = repo.oldMtime("go.mod")
if err == nil {
return time.Since(mtime), nil
}
now := time.Now()
return time.Since(now), errors.New(repo.GetGoPath() + " go.mod missing")
}
func (repo *Repo) GitChanged() bool {
fullfile := filepath.Join(repo.FullPath, ".git/FETCH_HEAD")
lasttime, err := repo.LastGitPull()
if err == nil {
// if error, something is wrong, assume true
log.Info("gitpb:", fullfile, "changed")
return true
}
newtime := repo.Times.LastPull.AsTime()
if lasttime == newtime {
return false
}
log.Info("gitpb:", fullfile, "changed")
return true
}
func (repo *Repo) GitPullAge() time.Duration {
lastpull, err := repo.LastGitPull()
if err == nil {
// if error, something is wrong, assume true
ltime := repo.Times.LastPull.AsTime()
return time.Since(ltime)
}
return time.Since(lastpull)
}
func (repo *Repo) oldMtime(filename string) (time.Time, error) {
pathf := filepath.Join(repo.FullPath, filename)
statf, err := os.Stat(pathf)
if err == nil {
return statf.ModTime(), nil
}
log.Log(WARN, "Mtime() os.Stat() error", pathf, err)
return time.Now(), err
}

102
autogen.go Normal file
View File

@ -0,0 +1,102 @@
package gitpb
// go.wit.com/apps/autogenpb auto generates Sort() and Marshal() for protobuf files
//go:generate autogenpb --proto gitTag.proto
//go:generate autogenpb --proto goDep.proto
//go:generate autogenpb --proto repo.proto
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// files : a list of files to save ["go.mod", "go.sum"]
// refname : could be "master" or "v0.1.5" or "a605119c2cc41"
// del : true means empty out existing notes, otherwise append
func (repo *Repo) AutogenSave(files []string, refname string, del bool) error {
if del {
cmd := []string{"git", "notes", "show", refname}
if _, err := repo.RunQuiet(cmd); err != nil {
// if there are not any notes, no need to remove them
} else {
cmd := []string{"git", "notes", "remove", refname}
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
}
}
for _, fname := range files {
autotag := "// `autogen:" + fname + "`"
cmd := []string{"git", "notes", "append", "-m", autotag, refname}
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
cmd = []string{"git", "notes", "append", "-F", fname, refname}
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
}
// a tag with a blank name indicates the end of the autogen file or files
autotag := "// `autogen:`"
cmd := []string{"git", "notes", "append", "-m", autotag, refname}
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
return nil
}
// restores files from git metadata (notes)
func (repo *Repo) AutogenRestore(refname string) error {
var cmd []string
if refname == "" {
cmd = []string{"git", "notes", "show"}
} else {
cmd = []string{"git", "notes", "show", refname}
}
result := repo.Run(cmd)
if result.Exit != 0 {
return errors.New(fmt.Sprint("git notes show returned ", result.Exit))
}
if result.Error != nil {
return result.Error
}
if len(result.Stdout) == 0 {
return nil
}
var newf *os.File
var err error
var body string
for _, line := range result.Stdout {
if strings.HasPrefix(line, "// `autogen:") {
if newf != nil {
fmt.Fprintln(newf, strings.TrimSpace(body))
newf.Close()
newf = nil
body = ""
}
fbase := strings.TrimPrefix(line, "// `autogen:")
fbase = strings.TrimSpace(fbase)
fbase = strings.TrimSuffix(fbase, "`")
// if line == // `autogen:` , then the filename is blank
if fbase != "" {
fname := filepath.Join(filepath.Join(repo.FullPath, fbase))
newf, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
}
continue
}
body += line + "\n"
}
if newf != nil {
fmt.Fprintln(newf, strings.TrimSpace(body))
newf.Close()
}
return nil
}

123
branches.go Normal file
View File

@ -0,0 +1,123 @@
package gitpb
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/go-cmd/cmd"
)
// returns true if 'git pull' will work
func (repo *Repo) ExistsUserBranchRemote() bool {
branchname := repo.GetUserBranchName()
if repo.IsBranchRemote(branchname) {
return true
}
return false
}
func readRefHash(filename string) string {
data, _ := os.ReadFile(filename)
return string(data)
}
func (repo *Repo) GetLocalBranches() []string {
return ListFiles(filepath.Join(repo.GetFullPath(), "/.git/refs/heads"))
}
func (repo *Repo) GetRemoteBranches() []string {
remotes := ListFiles(filepath.Join(repo.GetFullPath(), "/.git/refs/remotes"))
return remotes
}
// git describe --tags e548b0fb6d0d14cdfb693850d592419f247dc2b1
// v0.22.61-15-gbab84d7
func (repo *Repo) GetHashName(h string) (string, error) {
h = strings.TrimSpace(h)
// log.Info("GetHashName() is looking for", repo.GetGoPath(), h)
cmd := []string{"git", "describe", "--tags", h}
r, err := repo.RunQuiet(cmd)
if err != nil {
return "", err
}
if len(r.Stdout) == 0 {
return "", errors.New("git describe was empty")
}
return r.Stdout[0], nil
}
// lookup a hash from a tag with 'git rev-list'
func (repo *Repo) GetTagHash(t string) string {
// git rev-list -n 1 v0.0.66
cmd := []string{"git", "rev-list", "-n", "1", t}
result, _ := repo.RunStrict(cmd)
// log.Info("getLastTagVersion()", result.Stdout)
if len(result.Stdout) == 0 {
// log.Log(WARN, "no gitpb.LastTag() repo is broken. ignore this.", repo.GetGoPath())
return ""
}
return result.Stdout[0]
}
// lookup a hash from a tag with 'git rev-list'
func (repo *Repo) GetBranchDifferences(to string, from string) []string {
// git rev-list -n 1 v0.0.66
cmd := []string{"git", "rev-list", to + "..." + from}
result, _ := repo.RunStrict(cmd)
// log.Info("getLastTagVersion()", result.Stdout)
// tmp := strings.TrimSpace(strings.Join(result.Stdout, "\n"))
// return shell.SplitNewLines(tmp)
return result.Stdout
}
// deletes the devel local branch if it is a subset of the remote devel branch
func (repo *Repo) DeleteLocalDevelBranch() error {
branch := repo.GetDevelBranchName()
remote := filepath.Join("origin", branch)
if !repo.IsDevelRemote() {
return fmt.Errorf("no remote branch")
}
b1 := repo.CountDiffObjects(branch, remote) // should be zero
if b1 == 0 {
cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()}
_, err := repo.RunVerboseOnError(cmd)
return err
} else {
return fmt.Errorf("local branch has patches not in remote")
}
}
// makes a local branch based off of the master branch
// (unless a remote devel branch exists. then it uses that)
func (repo *Repo) MakeLocalDevelBranch() (*cmd.Status, error) {
branch := repo.GetDevelBranchName()
if branch == "" {
// hard coded default
branch = "devel"
}
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
// local devel branch already exists
return nil, nil
}
if repo.Exists(filepath.Join(".git/refs/remotes/origin", branch)) {
// remote devel branch exists, but local does not
cmd := []string{"git", "checkout", branch}
return repo.RunVerboseOnError(cmd)
}
master := repo.GetMasterBranchName()
cmd := []string{"git", "branch", branch, master}
repo.RunVerboseOnError(cmd)
cmd = []string{"git", "checkout", branch}
return repo.RunVerboseOnError(cmd)
}

133
checkout.go Normal file
View File

@ -0,0 +1,133 @@
package gitpb
import (
"fmt"
"os/user"
"path/filepath"
"go.wit.com/log"
)
func (repo *Repo) CheckoutMaster() bool {
bName := repo.GetMasterBranchName()
if repo.checkoutBranch(bName) {
return true
}
return false
}
func (repo *Repo) CheckoutDevel() bool {
bName := repo.GetDevelBranchName()
if repo.checkoutBranch(bName) {
repo.DevelBranchName = bName
return true
// switch ok
}
return false
}
func (repo *Repo) CheckoutUser() error {
bName := repo.GetUserBranchName()
if bName == "uerr" {
usr, _ := user.Current()
repo.SetUserBranchName(usr.Username)
bName = usr.Username
log.Info("gitpb CheckoutUser() somehow got user 'uerr'")
}
return repo.createUserBranch(bName)
/*
if err != nil {
log.Info("attempting checkout user error", repo.GetGoPath(), bName, err)
}
return err
*/
}
func (repo *Repo) BranchExists(bName string) bool {
// fixme after move to protobuf
return true
}
func (repo *Repo) checkoutBranch(bName string) bool {
if !repo.BranchExists(bName) {
return false
}
if bName == "" {
return false
}
if repo.CheckDirty() {
log.Log(INFO, repo.GetFullPath(), "is dirty")
return false
}
cmd := []string{"git", "checkout", bName}
r := repo.Run(cmd)
if r.Error != nil {
log.Log(INFO, "git checkout error:", r.Error)
}
realname := repo.GetCurrentBranchName()
realversion := repo.GetCurrentBranchVersion()
log.Log(INFO, repo.GetFullPath(), "realname =", realname, "realversion =", realversion)
if realname != bName {
log.Log(INFO, "git checkout failed", repo.GetFullPath(), bName, "!=", realname)
return false
}
return true
}
// actually creates a local user branch
func (repo *Repo) createUserBranch(branch string) error {
if branch == "" {
// get the username here?
return fmt.Errorf("gitpb createuserBranch() logic err. git branch name can not be blank")
}
if repo.IsDirty() {
// never change repos on dirty branches
return fmt.Errorf("repo is dirty")
}
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
var err error
// there is already a local user branch
cmd := []string{"git", "checkout", branch}
if _, err = repo.RunVerboseOnError(cmd); err == nil {
return nil
}
log.Log(INFO, "git checkout error:", err)
}
if repo.Exists(filepath.Join(".git/refs/remote/origin", branch)) {
var err error
// there is a remote user branch
// todo: check other remotes
cmd := []string{"git", "checkout", branch}
if _, err = repo.RunVerboseOnError(cmd); err == nil {
return nil
}
log.Log(INFO, "git checkout error:", err)
}
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
repo.CheckoutDevel()
}
repo.ReloadCheck()
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
log.Info("create user branch will probably fail", repo.GetGoPath())
// TODO: FIX THIS
// return fmt.Errorf("repo must be on devel branch %s", repo.GetGoPath())
}
// create the branch from devel
cmd := []string{"git", "branch", branch}
if _, err := repo.RunVerboseOnError(cmd); err != nil {
return err
}
cmd = []string{"git", "checkout", branch}
if _, err := repo.RunVerboseOnError(cmd); err != nil {
return err
}
return nil
}

58
common.go Normal file
View File

@ -0,0 +1,58 @@
package gitpb
func (repo *Repo) SetReadOnly(b bool) {
repo.ReadOnly = b
}
func (repo *Repo) SetTargetVersion(target string) {
repo.TargetVersion = target
}
func (repo *Repo) SetMasterBranchName(s string) {
repo.MasterBranchName = s
}
func (repo *Repo) GetGoPath() string {
if repo.GoInfo == nil {
return repo.Namespace
}
if repo.GoInfo.GoPath == "" {
return repo.Namespace
}
return repo.GoInfo.GoPath
}
func (repo *Repo) GetGoPrimitive() bool {
if repo.GoInfo == nil {
return false
}
return repo.GoInfo.GoPrimitive
}
func (repo *Repo) SetGoPrimitive(b bool) {
if repo.GoInfo == nil {
repo.GoInfo = new(GoInfo)
}
repo.GoInfo.GoPrimitive = b
}
func (repo *Repo) IsUserBranch() bool {
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
return true
}
return false
}
func (repo *Repo) IsMasterBranch() bool {
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
return true
}
return false
}
func (repo *Repo) IsDevelBranch() bool {
if repo.GetCurrentBranchName() == repo.GetDevelBranchName() {
return true
}
return false
}

189
config.go Normal file
View File

@ -0,0 +1,189 @@
package gitpb
// functions to import and export the protobuf
// data to and from config files
import (
"errors"
"os"
"path/filepath"
"go.wit.com/lib/protobuf/bugpb"
"go.wit.com/log"
)
// write to ~/.config/forge/ unless ENV{FORGE_REPOSDIR} is set
func (all *Repos) ConfigSave(fname string) error {
if all == nil {
log.Warn("gitpb repos == nil")
return errors.New("gitpb.ConfigSave() repos == nil")
}
if _, s := filepath.Split(fname); s != "repos.pb" {
log.Infof("ConfigSave() filename '%s' invalid\n", fname)
return log.Errorf("ConfigSave() filename '%s' invalid\n", fname)
}
data, err := all.Marshal()
if err != nil {
log.Info("gitpb proto.Marshal() failed len", len(data), err)
// often this is because strings have invalid UTF-8. This should probably be fixed in the protobuf code
// this might be fixed in the create code, but it can't hurt to try this as a last ditch effort here
log.Info("gitpb.ConfigSave() ATTEMPTING TO VALIDATE UTF-8 strings in the protobuf file")
if err := all.tryValidate(); err != nil {
log.Info("gitpb.ConfigSave() STILL FAILEd", err)
return err
} else {
// re-attempt Marshal() here
data, err = all.Marshal()
if err == nil {
// validate & sanitize strings worked
configWrite(fname, data)
return nil
}
log.Info("gitpb.ConfigSave() STILL FAILEd", err)
}
return err
}
configWrite(fname, data)
return nil
}
// todo: move this to Marshal() functions automatically in autogenpb?
func (repo *Repo) ValidateUTF8() error {
if _, err := repo.Marshal(); err == nil {
// exit if Marshal() works
return nil
} else {
// log.Printf("%s repo.Marshal() failed: %v\n", repo.GetFullPath(), err)
}
// you only need to do this if Marshal() fails
err := bugpb.ValidateProtoUTF8(repo)
if err != nil {
// log.Printf("Protobuf UTF-8 validation failed: %v\n", err)
}
if err := bugpb.SanitizeProtoUTF8(repo); err != nil {
log.Warn("gitpb.ValidateUTF8()( failed:", err)
return err
}
return nil
}
func (all *Repos) tryValidate() error {
err := bugpb.ValidateProtoUTF8(all)
if err != nil {
log.Printf("Protobuf UTF-8 validation failed: %v\n", err)
}
if err := bugpb.SanitizeProtoUTF8(all); err != nil {
log.Warn("Sanitation failed:", err)
// log.Fatalf("Sanitization failed: %v", err)
return err
}
return nil
}
// load the repos.pb file. I shouldn't really matter if this
// fails. the file should be autogenerated. This is used
// locally just for speed
func (all *Repos) ConfigLoadOld() error {
if os.Getenv("FORGE_REPOSDIR") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_REPOSDIR", fullpath)
}
var data []byte
var err error
cfgname := filepath.Join(os.Getenv("FORGE_REPOSDIR"), "repos.pb")
if data, err = loadFile(cfgname); err != nil {
// something went wrong loading the file
// all.sampleConfig() // causes nil panic
return err
}
// this means the forge.pb file exists and was read
if len(data) == 0 {
return errors.New("gitpb.ConfigLoad() repos.pb is empty")
}
err = all.Unmarshal(data)
test := NewRepos()
if test.Uuid != all.Uuid {
log.Log(WARN, "uuids do not match", test.Uuid, all.Uuid)
deleteProtobufFile(cfgname)
}
if test.Version != all.Version {
log.Log(WARN, "versions do not match", test.Version, all.Version)
deleteProtobufFile(cfgname)
}
log.Log(INFO, cfgname, "protobuf versions and uuid match", all.Uuid, all.Version)
return err
}
func (all *Repos) ConfigLoad(cfgname string) error {
var data []byte
var err error
if data, err = loadFile(cfgname); err != nil {
// something went wrong loading the file
// all.sampleConfig() // causes nil panic
return err
}
// this means the forge.pb file exists and was read
if len(data) == 0 {
return errors.New("gitpb.ConfigLoad() repos.pb is empty")
}
err = all.Unmarshal(data)
test := NewRepos()
if test.Uuid != all.Uuid {
log.Log(WARN, "uuids do not match", test.Uuid, all.Uuid)
deleteProtobufFile(cfgname)
}
if test.Version != all.Version {
log.Log(WARN, "versions do not match", test.Version, all.Version)
deleteProtobufFile(cfgname)
}
log.Log(INFO, cfgname, "protobuf versions and uuid match", all.Uuid, all.Version)
return err
}
func deleteProtobufFile(filename string) {
log.Log(WARN, "The protobuf file format has changed for", filename)
log.Log(WARN, "Deleting old file:", filename)
log.Log(WARN, "This file will be recreated on the next run.")
err := os.Remove(filename)
if err != nil {
log.Log(WARN, "failed to remove old protobuf file", "err", err)
}
}
func (all *Repos) sampleConfig() {
newr := new(Repo)
newr.FullPath = "/opt/forge/dummyentry"
all.Append(newr)
}
func loadFile(fullname string) ([]byte, error) {
data, err := os.ReadFile(fullname)
if errors.Is(err, os.ErrNotExist) {
// if file does not exist, just return nil. this
// will cause ConfigLoad() to try the next config file like "forge.text"
// because the user might want to edit the .config by hand
return nil, nil
}
if err != nil {
// log.Info("open config file :", err)
return nil, err
}
return data, nil
}
func configWrite(fullname string, data []byte) error {
log.Infof("%s your repos have changed state. cached state. (%d) bytes\n", fullname, len(data))
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close()
if err != nil {
log.Warn("open config file :", err)
return err
}
cfgfile.Write(data)
return nil
}

287
currentVersions.go Normal file
View File

@ -0,0 +1,287 @@
package gitpb
// runs git, parses output
// types faster than you can
import (
"errors"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode"
"go.wit.com/log"
)
func (repo *Repo) reloadVersions() {
repo.setMasterVersion()
repo.setDevelVersion()
repo.setUserVersion()
repo.setLastTag()
repo.setCurrentBranchName()
repo.setCurrentBranchVersion()
}
func (repo *Repo) setMasterVersion() {
bname := repo.GetMasterBranchName()
v, err := repo.gitVersionByName(bname)
/*
count := repo.LenGitTags()
log.Info(repo.GetGoPath(), "tag count", count)
repo.UpdateGitTags()
count = repo.LenGitTags()
log.Info(repo.GetGoPath(), "tag count", count)
*/
if err == nil {
repo.MasterVersion = v
} else {
log.Log(WARN, "gitpb.GitMasterVersion() error:", err)
}
}
func (repo *Repo) setDevelVersion() {
bname := repo.GetDevelBranchName()
v, err := repo.gitVersionByName(bname)
if err == nil {
repo.DevelVersion = v
} else {
// log.Log(WARN, "gitpb.GitDevelVersion() error:", err)
repo.DevelVersion = ""
}
}
func (repo *Repo) setUserVersion() {
bname := repo.GetUserBranchName()
if !repo.Exists(filepath.Join(".git/refs/heads", bname)) {
// the user branch does not exist at this time
repo.UserVersion = "uerr"
return
}
v, err := repo.gitVersionByName(bname)
if err == nil {
repo.UserVersion = v
} else {
// log.Log(WARN, "gitpb.GitUserVersion() error:", err)
repo.UserVersion = "uerr"
}
}
// this is used often. probably move everything to this
// returns things like
// v0.2.2
// v0.22.39-1-g2141737
// v0.23-dirty
// mystuff
func (repo *Repo) GetCurrentVersion() string {
if repo == nil {
return ""
}
bver := repo.GetCurrentBranchVersion()
if repo.CheckDirty() {
bver = bver + "-dirty"
}
return bver
}
func (repo *Repo) gitDescribeByHash(hash string) (string, error) {
if hash == "" {
return "", errors.New("hash was blank")
}
r, err := repo.RunQuiet([]string{"git", "describe", "--tags", hash})
out := strings.Join(r.Stdout, "\n")
if err != nil {
// log.Warn("not in a git repo or bad hash?", err, repo.GetGoPath())
return "gitpb err", err
}
return out, err
}
// this should get the most recent tag
func (repo *Repo) GetLastTagVersion() string {
return repo.LastTag
}
func (repo *Repo) DebianReleaseVersion() string {
lasttag := repo.GetLastTagVersion()
newv := trimNonNumericFromStart(lasttag)
if newv == "" {
newv = "0.0"
if lasttag != "" {
newv += "-" + lasttag
}
}
return newv
}
func (repo *Repo) DebianCurrentVersion() string {
cbversion := repo.GetCurrentBranchVersion()
newv := trimNonNumericFromStart(cbversion)
if newv == "" {
newv = "0.0"
}
if repo.CheckDirty() {
newv += "-dirty"
}
return newv
}
func (repo *Repo) gitVersionByName(name string) (string, error) {
name = strings.TrimSpace(name)
if name == "" {
// git will return the current tag
cmd := []string{"git", "describe", "--tags"}
r, err := repo.RunQuiet(cmd)
output := strings.Join(r.Stdout, "\n")
if err != nil {
log.Log(WARN, repo.FullPath, "gitDescribeByName() ", output, err, cmd)
return "", err
}
return strings.TrimSpace(output), nil
}
if !repo.IsBranch(name) {
// branch does not exist
return "", errors.New("gitDescribeByName() git fatal: Not a valid object name: " + name)
}
cmd := []string{"git", "describe", "--tags", name}
result, err := repo.RunQuiet(cmd)
output := strings.Join(result.Stdout, "\n")
if err != nil {
//log.Log(WARN, "cmd =", cmd)
//log.Log(WARN, "err =", err)
//log.Log(WARN, "output (might have worked with error?) =", output)
//log.Log(WARN, "not in a git repo or bad tag?", repo.GetGoPath())
return "", result.Error
}
return strings.TrimSpace(output), nil
}
func trimNonNumericFromStart(s string) string {
for i, r := range s {
if unicode.IsDigit(r) {
return s[i:]
}
}
return ""
}
func normalizeVersion(s string) string {
// reg, err := regexp.Compile("[^a-zA-Z0-9]+")
parts := strings.Split(s, "-")
if len(parts) == 0 {
return ""
}
reg, err := regexp.Compile("[^0-9.]+")
if err != nil {
log.Log(WARN, "normalizeVersion() regexp.Compile() ERROR =", err)
return parts[0]
}
clean := reg.ReplaceAllString(parts[0], "")
log.Log(INFO, "normalizeVersion() s =", clean)
return clean
}
// golang doesn't seem to really support v0.1 and seems to want v0.1.0
// TODO: confirm this. (as of Dec 2024, this appears to be the case -- jcarr )
//
// personally I hope GO stays with the vX.X.X version scheme. it's a good system.
//
// if the version is "57", convert it to v0.0.57 for GO
func splitVersion(version string) (a, b, c string) {
tmp := normalizeVersion(version)
parts := strings.Split(tmp, ".")
switch len(parts) {
case 1:
return "", "", parts[0] // converts someone using version "57" to "v0.0.57"
case 2:
return parts[0], parts[1], "" // converts someone using version "1.2" to "v1.2.0"
default:
return parts[0], parts[1], parts[2]
}
}
func splitInts(ver string) (int, int, int) {
major, minor, revision := splitVersion(ver)
a, _ := strconv.Atoi(major)
b, _ := strconv.Atoi(minor)
c, _ := strconv.Atoi(revision)
return a, b, c
}
// changes the target minor. v0.1.3 becomes v0.2.0
func (repo *Repo) IncrementTargetMinor() {
lasttag := repo.GetLastTag()
// var major, minor, revision string
major, minor, revision := splitInts(lasttag)
minor += 1
revision = 0
newa := strconv.Itoa(major)
newb := strconv.Itoa(minor)
newc := strconv.Itoa(revision)
repo.SetTargetVersion("v" + newa + "." + newb + "." + newc)
}
// changes the target revision. v0.1.3 becomes v0.1.4
func (repo *Repo) IncrementTargetRevision() {
// first try just going from the last tag
repo.incrementRevision(repo.GetLastTag())
if !isNewerVersion(repo.GetMasterVersion(), repo.GetTargetVersion()) {
// log.Printf("tag error. master version() %s was higher than target version %s\n", repo.GetMasterVersion(), repo.GetTargetVersion())
repo.incrementRevision(repo.GetMasterVersion())
}
/*
if !isNewerVersion(repo.GetLastTag(), repo.GetTargetVersion()) {
log.Printf("last tag versn() %s is higher than target version %s\n", repo.GetLastTag(), repo.GetTargetVersion())
return false
}
if !isNewerVersion(repo.GetMasterVersion(), repo.GetTargetVersion()) {
log.Printf("master version() %s is higher than target version %s\n", repo.GetMasterVersion(), repo.GetTargetVersion())
return false
}
return true
*/
}
func (repo *Repo) incrementRevision(lasttag string) {
major, minor, revision := splitInts(lasttag)
revision += 1
newa := strconv.Itoa(major)
newb := strconv.Itoa(minor)
newc := strconv.Itoa(revision)
repo.SetTargetVersion("v" + newa + "." + newb + "." + newc)
}
// makes sure the new target version to be released is greater
// than the current master version
// this is just a sanity check, but this can actually fail sometimes
// if other things failed terribly in prior cases
// gitpb v.3.1.4
// A = major = 3
// B = minor = 1
// C = revision = 4
func isNewerVersion(oldver, newver string) bool {
olda, oldb, oldc := splitInts(oldver)
newa, newb, newc := splitInts(newver)
if newa < olda {
return false
}
if newb < oldb {
return false
}
if newc <= oldc {
return false
}
return true
}

156
gitTag.byAge.go Normal file
View File

@ -0,0 +1,156 @@
package gitpb
import (
"path/filepath"
"sort"
"time"
"go.wit.com/log"
)
/*
// todo: probably switch to using slices. new things added in 1.23
// https://pkg.go.dev/slices
func (all *GitTags) newSort() *GitTagScanner {
slices.SortFunc(all.GitTags, func(a, b *GitTag) int {
if n := strings.Compare(a.Name, b.Name); n != 0 {
return n
}
// If names are equal, order by age
return cmp.Compare(a.Age, b.Age)
})
}
*/
// all this code below is junk and seamingly wrong
func (all *GitTags) GetAge(name string) time.Time {
packs := all.selectAllGitTags()
var newest time.Time
for _, tag := range packs {
// log.Info("\t\ttag", i, tag.Refname, tag.Authordate.AsTime())
_, rname := filepath.Split(tag.Refname)
if name == rname {
// log.Info("\t\tfound tag", i, rbase, rname, tag.Authordate.AsTime())
newest = tag.Authordate.AsTime()
return newest
}
newest = tag.Authordate.AsTime()
}
return newest
}
func (all *GitTags) SortByAge() *GitTagScanner {
packs := all.selectAllGitTags()
sort.Sort(GitTagAge(packs))
iterator := newGitTagScanner(packs)
return iterator
}
type GitTagAge []*GitTag
func (a GitTagAge) Len() int { return len(a) }
// sorts in ? order
func (a GitTagAge) Less(i, j int) bool {
if time.Since(a[i].Authordate.AsTime()) < time.Since(a[j].Authordate.AsTime()) {
return true
}
return false
}
func (a GitTagAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// biased code that gives out newer tag dates
// even if the code hasn't been patched
func (repo *Repo) NewestAge() time.Duration {
alltags := repo.Tags.selectAllGitTags()
var newest time.Time
for _, tag := range alltags {
// check the actual age of the patch
if newest.Before(tag.Authordate.AsTime()) {
newest = tag.Authordate.AsTime()
}
// check the age of the commit
if newest.Before(tag.Creatordate.AsTime()) {
newest = tag.Creatordate.AsTime()
}
}
return time.Since(newest)
}
func (repo *Repo) NewestTime() time.Time {
alltags := repo.Tags.selectAllGitTags()
var newest time.Time
for _, tag := range alltags {
// check the actual age of the patch
if newest.Before(tag.Authordate.AsTime()) {
newest = tag.Authordate.AsTime()
}
// check the age of the commit
if newest.Before(tag.Creatordate.AsTime()) {
newest = tag.Creatordate.AsTime()
}
}
return newest
}
func (repo *Repo) NewestAgeVerbose() time.Duration {
alltags := repo.Tags.selectAllGitTags()
var newest time.Time
var cur time.Time
for i, tag := range alltags {
cur = tag.Authordate.AsTime()
rbase, rname := filepath.Split(tag.Refname)
log.Info("\t\tfound tag", i, rbase, rname, tag.Authordate.AsTime(), tag.Creatordate.AsTime())
if newest.Before(cur) {
newest = cur
}
}
return time.Since(newest)
}
// not really accurate. temprorary until git Config() parsing is better
func (repo *Repo) BranchAge(branch string) time.Duration {
alltags := repo.Tags.selectAllGitTags()
var newest time.Time
var cur time.Time
var auth time.Time
for _, tag := range alltags {
cur = tag.Creatordate.AsTime()
auth = tag.Authordate.AsTime()
if branch == filepath.Base(tag.Refname) {
// log.Info("\t\tfound tag", i, branch, tag.Authordate.AsTime(), tag.Creatordate.AsTime())
if cur.Before(auth) {
return time.Since(auth)
}
return time.Since(cur)
}
// check both dates I guess
if newest.Before(auth) {
newest = auth
}
if newest.Before(cur) {
newest = cur
}
}
return time.Since(newest)
}

141
gitTag.common.go Normal file
View File

@ -0,0 +1,141 @@
package gitpb
import (
"path/filepath"
"strings"
"go.wit.com/log"
)
func (repo *Repo) ActualDevelHash() string {
brname := repo.GetDevelBranchName()
refname := "refs/heads/" + brname
for tag := range repo.Tags.IterAll() {
// log.Info("repo tag", tag.GetHash(), tag.GetRefname())
if tag.GetRefname() == refname {
return tag.GetHash()
}
}
return ""
}
func (repo *Repo) GetLocalHash(brname string) string {
refname := "refs/heads/" + brname
for tag := range repo.Tags.IterAll() {
// log.Info("repo tag", tag.GetHash(), tag.GetRefname())
if tag.GetRefname() == refname {
return strings.TrimSpace(tag.GetHash())
}
}
return ""
}
func (repo *Repo) GetRemoteHash(brname string) string {
refname := "refs/remotes/origin/" + brname
for tag := range repo.Tags.IterAll() {
// log.Info("repo tag", tag.GetHash(), tag.GetRefname())
if tag.GetRefname() == refname {
return strings.TrimSpace(tag.GetHash())
}
}
return ""
}
// this is the correct way. uses 'git show-ref'
func (repo *Repo) IsBranchRemote(brname string) bool {
if repo.Tags == nil {
return false
}
brname = "refs/remotes/origin/" + brname
ref := repo.Tags.FindByRefname(brname)
if ref == nil {
// log.Info("did not found refname!!!!!!!!", brname)
return false
}
// log.Info("found refname!!!!!!!!")
return true
}
// this is the correct way. uses 'git show-ref'
func (repo *Repo) IsDevelRemote() bool {
if repo.Tags == nil {
return false
}
devname := repo.GetDevelBranchName()
refname := "refs/remotes/origin/" + devname
ref := repo.Tags.FindByRefname(refname)
if ref == nil {
// log.Info("did not found refname!!!!!!!!", refname)
return false
}
// log.Info("found refname!!!!!!!!")
return true
}
// find a branch namm
// will find "master" or "devel"
// will also find "v0.1.1"
// or will find "patches-from-foo"
// will return *any* match on any git branch because it doesn't
// matter much here yet
// eventually this will be worked out by forge in some future code that hasn't been made yet
func (repo *Repo) IsBranch(findname string) bool {
for t := range repo.Tags.IterAll() {
tagname := t.Refname
if strings.HasPrefix(tagname, "refs/remotes") {
continue
}
path, filename := filepath.Split(tagname)
log.Log(INFO, "gitpb.IsBranch() tag:", path, filename, "from", repo.GetGoPath())
if filename == findname {
log.Log(INFO, "gitpb.IsBranch() found tag:", path, filename, "from", repo.GetGoPath())
return true
}
}
log.Log(INFO, "did not find tag:", findname, "in", repo.GetGoPath())
return false
}
func (repo *Repo) IsLocalBranch(findname string) bool {
for t := range repo.Tags.IterAll() {
if !strings.HasPrefix(t.Refname, "refs/heads") {
continue
}
path, filename := filepath.Split(t.Refname)
log.Log(INFO, "gitpb.IsBranch() tag:", path, filename, "from", repo.GetGoPath())
if filename == findname {
log.Log(INFO, "gitpb.IsBranch() found tag:", path, filename, "from", repo.GetGoPath())
return true
}
}
log.Log(INFO, "did not find tag:", findname, "in", repo.GetGoPath())
return false
}
// finds the newest tag. used for deciding if master needs to be published
func (repo *Repo) FindLastTag() string {
var newest *GitTag
for tag := range repo.Tags.IterAll() {
if !strings.HasPrefix(tag.GetRefname(), "refs/tags/") {
continue
}
if newest == nil {
newest = tag
continue
}
cur := newest.Creatordate.AsTime()
if cur.Before(tag.Creatordate.AsTime()) {
newest = tag
}
// newtag := strings.TrimPrefix(tag.GetRefname(), "refs/tags/")
// log.Info("repo tag", tag.GetHash(), tag.Creatordate.AsTime(), tag.GetRefname(), newtag)
}
if newest == nil {
return ""
}
// log.Info("repo newest tag", newest.GetHash(), newest.Creatordate.AsTime(), newest.GetRefname())
newtag := strings.TrimPrefix(newest.GetRefname(), "refs/tags/")
return newtag
}

21
gitTag.printTable.go Normal file
View File

@ -0,0 +1,21 @@
// Code generated by go.wit.com/apps/autogenpb DO NOT EDIT.
// This file was autogenerated with autogenpb v0.5.1 2025-09-12_15:24:32_UTC
// go install go.wit.com/apps/autogenpb@latest
//
// define which structs (messages) you want to use in the .proto file
// Then sort.pb.go and marshal.pb.go files are autogenerated
//
// autogenpb uses it and has an example .proto file with instructions
//
package gitpb
import (
"go.wit.com/lib/cobol"
)
func (mt *GitTagsTable) PrintTable() {
// log.Info("ShowTable() SENDING TO GUI")
mt.MakeTable()
cobol.PrintTable(mt.pb)
}

View File

@ -4,16 +4,37 @@ package gitpb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message GitTag {
string refname = 1; // `autogenpb:unique` // tag name. treated as unique
google.protobuf.Timestamp creatordate = 2; // git creatordate
google.protobuf.Timestamp authordate = 3; // git author date
string hash = 4; // `autogenpb:unique` // git hash
string subject = 5; // git tag subject
message GitRemote { // `autogenpb:nomutex`
string url = 1;
string fetch = 2;
}
message GitTags {
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations
repeated GitTag gitTags = 3;
message GitBranch { // `autogenpb:nomutex`
string remote = 1; // the name of the remote repo
string merge = 2; // the merge path from the config file
string name = 3; // the branch name from the config file
}
message GitConfig { // `autogenpb:nomutex`
map<string, string> core = 1; // map[origin] = "https:/git.wit.org/gui/gadgets"
map<string, GitRemote> remotes = 2; // map[origin] = "https:/git.wit.org/gui/gadgets"
map<string, GitBranch> branches = 3; // map[guimaster] = origin guimaster
map<string, string> submodules = 4;
map<string, string> hashes = 5;
map<string, string> versions = 6;
repeated GitBranch local = 7; // move this this and away from the map<> variables
}
message GitTag { // `autogenpb:nomutex`
string refname = 1; // `autogenpb:unique` `autogenpb:sort` // tag name. treated as unique
google.protobuf.Timestamp creatordate = 2; // git creatordate
google.protobuf.Timestamp authordate = 3; // git author date
string hash = 4; // `autogenpb:unique` // git hash
string subject = 5; // git tag subject
}
message GitTags { // `autogenpb:marshal` `autogenpb:nomutex` `autogenpb:gui`
string uuid = 1; // `autogenpb:uuid:ffdff813-0316-4372-9e82-4c1c7d202526`
string version = 2; // `autogenpb:version:v0.0.47`
repeated GitTag gitTags = 3;
}

View File

@ -1,137 +0,0 @@
package gitpb
// runs git, parses output
// types faster than you can
import (
"errors"
"path/filepath"
"strings"
"go.wit.com/log"
)
func (repo *Repo) GetLastTag() string {
cmd := []string{"git", "rev-list", "--tags", "--max-count=1"}
result := repo.RunQuiet(cmd)
// log.Info("getLastTagVersion()", result.Stdout)
if len(result.Stdout) != 1 {
log.Log(GITPBWARN, "git LastTag() error:", result.Stdout)
return ""
}
hash := result.Stdout[0]
cmd = []string{"git", "describe", "--tags", "--always", hash}
result = repo.RunQuiet(cmd)
if len(result.Stdout) != 1 {
log.Log(GITPBWARN, "git LastTag() error:", result.Stdout)
return ""
}
return result.Stdout[0]
}
func (repo *Repo) GitMasterVersion() string {
bname := repo.GetMasterBranchName()
v, err := repo.gitVersionByName(bname)
/*
count := repo.LenGitTags()
log.Info(repo.GoPath, "tag count", count)
repo.UpdateGitTags()
count = repo.LenGitTags()
log.Info(repo.GoPath, "tag count", count)
*/
if err == nil {
return v
} else {
log.Log(GITPBWARN, "gitpb.GitMasterVersion() error:", err)
return ""
}
}
func (repo *Repo) GitDevelVersion() string {
bname := repo.GetDevelBranchName()
v, err := repo.gitVersionByName(bname)
if err == nil {
return v
} else {
log.Log(GITPBWARN, "gitpb.GitDevelVersion() error:", err)
return ""
}
}
func (repo *Repo) GitUserVersion() string {
bname := repo.GetUserBranchName()
v, err := repo.gitVersionByName(bname)
if err == nil {
return v
} else {
log.Log(GITPBWARN, "gitpb.GitUserVersion() error:", err)
return ""
}
}
func (repo *Repo) gitVersionByName(name string) (string, error) {
name = strings.TrimSpace(name)
if name == "" {
// git will return the current tag
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always"})
output := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Log(GITPBWARN, "gitDescribeByName() output might have worked anyway:", output)
log.Log(GITPBWARN, "gitDescribeByName() not in a git repo?", r.Error, repo.GoPath)
return "", r.Error
}
return strings.TrimSpace(output), nil
}
if !repo.IsBranch(name) {
// tag does not exist
log.Log(GITPBWARN, "LocalTagExists()", name, "did not exist")
return "", errors.New("gitDescribeByName() git fatal: Not a valid object name: " + name)
}
cmd := []string{"git", "describe", "--tags", "--always", name}
result := repo.RunQuiet(cmd)
output := strings.Join(result.Stdout, "\n")
if result.Error != nil {
log.Log(GITPBWARN, "cmd =", cmd)
log.Log(GITPBWARN, "err =", result.Error)
log.Log(GITPBWARN, "output (might have worked with error?) =", output)
log.Log(GITPBWARN, "not in a git repo or bad tag?", repo.GoPath)
return "", result.Error
}
return strings.TrimSpace(output), nil
}
// find a branch name
// will find "master" or "devel"
// will also find "v0.1.1"
// or will find "patches-from-foo"
// will return *any* match on any git branch because it doesn't
// matter much here yet
// eventually this will be worked out by forge in some future code that hasn't been made yet
func (repo *Repo) IsBranch(findname string) bool {
loop := repo.Tags.All()
for loop.Scan() {
t := loop.Next()
// log.Info("LocalTagExists() tag:", t.Refname)
tagname := t.Refname
if strings.HasPrefix(tagname, "refs/remotes") {
continue
}
path, filename := filepath.Split(tagname)
log.Log(GITPB, "gitpb.IsBranch() tag:", path, filename, "from", repo.GoPath)
if filename == findname {
log.Log(GITPB, "gitpb.IsBranch() found tag:", path, filename, "from", repo.GoPath)
return true
}
}
log.Log(GITPB, "did not find tag:", findname, "in", repo.GoPath)
return false
}

View File

@ -1,83 +0,0 @@
package gitpb
import (
"slices"
"strings"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// Update repo.Refs from .git/
func (repo *Repo) UpdateGitTags() error {
// delete the old hash
// r.DeleteByHash(hash)
repo.Tags = new(GitTags)
tags := []string{"%(objectname)", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
format := strings.Join(tags, "_,,,_")
cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format", format}
// log.Info("RUNNING:", strings.Join(cmd, " "))
result := shell.PathRunQuiet(repo.FullPath, cmd)
if result.Error != nil {
log.Warn("git for-each-ref error:", result.Error)
return result.Error
}
lines := result.Stdout
// reverse the git order
slices.Reverse(lines)
var refname string
var hash string
var subject string
var ctime *timestamppb.Timestamp
var atime *timestamppb.Timestamp
for i, line := range lines {
var parts []string
parts = make([]string, 0)
parts = strings.Split(line, "_,,,_")
if len(parts) != 5 {
log.Info("tag error:", i, parts)
continue
}
hash = parts[0]
if parts[1] != "" {
tmp := getGitDateStamp(parts[1])
ctime = timestamppb.New(tmp)
}
if parts[2] != "" {
tmp := getGitDateStamp(parts[2])
atime = timestamppb.New(tmp)
}
refname = parts[3]
subject = parts[4]
newr := GitTag{
Refname: refname,
Hash: hash,
Subject: subject,
Creatordate: ctime,
Authordate: atime,
}
repo.Tags.Append(&newr)
}
return nil
}
// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600"
func getGitDateStamp(gitdefault string) time.Time {
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
tagTime, err := time.Parse(gitLayout, gitdefault)
if err != nil {
log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA")
log.Warn(err)
return time.Now()
}
return tagTime
}

View File

@ -7,18 +7,18 @@ import (
"time"
)
func (repo *Repo) DeleteGoDepByHash(hash string) *GoDep {
return repo.GoDeps.DeleteByHash(hash)
func (repo *Repo) DeleteGoDepByHash(hash string) {
repo.GoDeps.DeleteByHash(hash)
}
// enforces no duplicate package names
func (repo *Repo) AppendGoDep(newP *GoDep) bool {
return repo.GoDeps.AppendUniqueGoPath(newP)
return repo.GoDeps.AppendByGoPath(newP)
}
// returns time.Duration since last scan of go.sum & go.mod
func (repo *Repo) AgeGoDep() time.Duration {
t := time.Since(repo.LastGoDep.AsTime())
t := time.Since(repo.Times.LastGoDep.AsTime())
return t
}

198
goDep.parseGoSum.go Normal file
View File

@ -0,0 +1,198 @@
package gitpb
// does processing on the go.mod and go.sum files
import (
"bufio"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
)
// reads and parses the go.sum file into a protobuf struct
// this function isn't supposed to change anything, just parse the existing files
func (repo *Repo) ParseGoSum() bool {
// empty out what was there before
repo.GoDeps = nil
// check of the repo is a primitive
// that means, there is not a go.sum file
// because the package is completely self contained!
if err := repo.setPrimitive(); err != nil {
// temporarily enabled this. this is really noisy
// log.Info("gitpb.ParseGoSum()", err)
return false
}
if repo.GetGoPrimitive() {
// log.Info("This repo is primitive!")
return true
}
tmp := filepath.Join(repo.FullPath, "go.sum")
gosum, err := os.Open(tmp)
defer gosum.Close()
if err != nil {
log.Info("gitpb.ParseGoSum() missing go.sum. Some error happened with go mod init & tidy", err)
return false
}
scanner := bufio.NewScanner(gosum)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Split(line, " ")
if len(parts) == 3 {
godep := strings.TrimSpace(parts[0])
version := strings.TrimSpace(parts[1])
if strings.HasSuffix(version, "/go.mod") {
version = strings.TrimSuffix(version, "/go.mod")
}
new1 := GoDep{
GoPath: godep,
Version: version,
}
if repo.GoDeps == nil {
repo.GoDeps = new(GoDeps)
}
repo.GoDeps.AppendByGoPath(&new1)
} else {
log.Info("gitpb.ParseGoSum() go.sum parse error invalid:", line)
return false
}
}
if err := scanner.Err(); err != nil {
repo.GoDeps = nil
log.Info("gitpb.ParseGoSum()", err)
return false
}
return true
}
// attempt to parse go.* files in a directory
func GoSumParseDir(moddir string) (*GoDeps, error) {
isprim, err := computePrimitive(moddir)
if err != nil {
// "go mod init" failed
return nil, err
}
if isprim {
// might be a GO primitive. no go.sum file
return nil, nil
}
// go.sum exists. parse the go.sum file
return parseGoSum(moddir)
}
// Detect a 'Primitive' package. Sets the isPrimitive flag
// will return true if the repo is truly not dependent on _anything_ else
// like spew or lib/widget
// it assumes 'go mod init' and 'go mod tidy' ran without error
func computePrimitive(moddir string) (bool, error) {
// go mod init & go mod tidy ran without errors
log.Log(INFO, "isPrimitiveGoMod()", moddir)
gomod, err := os.Open(filepath.Join(moddir, "go.mod"))
if err != nil {
log.Log(INFO, "missing go.mod", moddir)
return false, err
}
defer gomod.Close()
if shell.Exists(filepath.Join(moddir, "go.sum")) {
return false, nil
}
scanner := bufio.NewScanner(gomod)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Fields(line)
log.Log(INFO, " gomod:", parts)
if len(parts) >= 1 {
log.Log(INFO, " gomod: part[0] =", parts[0])
if parts[0] == "require" {
log.Log(INFO, " should return false here")
return false, errors.New("go.mod file is not primitive")
}
/*
if parts[0] == "go" {
if parts[1] != "1.21" {
log.Log(WARN, "go not set to 1.21 for", repo.GetGoPath())
// return false, errors.New("go not set to 1.21 for " + repo.GetGoPath())
}
}
*/
}
}
return true, nil
}
// parse the go.sum file into a protobuf
func parseGoSum(moddir string) (*GoDeps, error) {
godeps := new(GoDeps)
tmp, err := os.Open(filepath.Join(moddir, "go.sum"))
defer tmp.Close()
if err != nil {
log.Info("gitpb.ParseGoSum() missing go.sum. Some error happened with go mod init & tidy", err)
return nil, err
}
scanner := bufio.NewScanner(tmp)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Split(line, " ")
if len(parts) == 3 {
godep := strings.TrimSpace(parts[0])
version := strings.TrimSpace(parts[1])
if strings.HasSuffix(version, "/go.mod") {
version = strings.TrimSuffix(version, "/go.mod")
}
new1 := GoDep{
GoPath: godep,
Version: version,
}
godeps.AppendByGoPath(&new1)
} else {
return nil, fmt.Errorf("gitpb.ParseGoSum() go.sum parse error invalid: %s", line)
}
}
if err := scanner.Err(); err != nil {
godeps = nil
return nil, err
}
return godeps, nil
}
func (repo *Repo) GoSumFromPkgDir() (*GoDeps, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
rver := repo.GetLastTag()
if rver == "" {
return nil, errors.New("could not get master version")
}
goget := repo.GetGoPath() + "@" + rver
moddir := filepath.Join(homedir, "go/pkg/mod", repo.GetGoPath()+"@"+rver)
if !shell.IsDir(moddir) {
cmd := []string{"go", "get", goget}
repo.RunVerboseOnError(cmd)
}
if !shell.IsDir(moddir) {
return nil, errors.New("missing go/pkg/mod. Run: go get " + goget)
}
return GoSumParseDir(moddir)
}
func (repo *Repo) GoSumFromRepo() (*GoDeps, error) {
return GoSumParseDir(repo.GetFullPath())
}

View File

@ -6,16 +6,18 @@ package gitpb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message GoDep {
string hash = 1; // `autogenpb:unique` // md5sum/hash value from the go.sum file
google.protobuf.Timestamp ctime = 2; // get the go date from 'go list' ?
string version = 3; // v1.2.2
string goPath = 4; // `autogenpb:unique` // "go.wit.com/lib/foo"
string goVersion = 5; // version of golang the developer used to make this package version
// global settings for autogenpb `autogenpb:mutex`
message GoDep { // `autogenpb:nomutex`
string hash = 1; // `autogenpb:unique` `autogenpb:sort` // md5sum/hash value from the go.sum file
google.protobuf.Timestamp ctime = 2; // get the go date from 'go list' ?
string version = 3; // v1.2.2
string goPath = 4; // `autogenpb:unique` `autogenpb:sort` // "go.wit.com/lib/foo"
string goVersion = 5; // version of golang the developer used to make this package version
}
message GoDeps {
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations
repeated GoDep goDeps = 3;
message GoDeps { // `autogenpb:nomutex`
string uuid = 1; // `autogenpb:uuid:7de62c09-b335-4d80-902d-08552c501b7c`
string version = 2; // `autogenpb:version:v0.0.51`
repeated GoDep goDeps = 3; // `autogenpb:unique` `autogenpb:sort`
}

View File

@ -1,140 +1,27 @@
package gitpb
// does processing on the go.mod and go.sum files
import (
"bufio"
"errors"
"os"
"path/filepath"
"strings"
"fmt"
"go.wit.com/log"
)
// poor name perhaps. It's because in most of these
// repos you can also type "make redomod" to do the same thing
// since it's a Makefile task that is also useful to be able to run
// from the command line
func (repo *Repo) RedoGoMod() (bool, error) {
// unset the go development ENV var to generate release files
os.Unsetenv("GO111MODULE")
if ok, err := repo.strictRun([]string{"rm", "-f", "go.mod", "go.sum"}); !ok {
log.Warn("rm go.mod go.sum failed", err)
return ok, err
}
if ok, err := repo.strictRun([]string{"go", "mod", "init", repo.GoPath}); !ok {
log.Warn("go mod init failed", err)
return ok, err
}
if ok, err := repo.strictRun([]string{"go", "mod", "tidy"}); !ok {
log.Warn("go mod tidy failed", err)
return ok, err
}
// most things should build with golang after 1.20
if ok, err := repo.strictRun([]string{"go", "mod", "edit", "-go=1.20"}); !ok {
log.Warn("go mod edit failed", err)
return ok, err
}
// log.Info("MakeRedomod() worked", repo.GoPath)
// does processing on the go.mod and go.sum files
if repo.Exists("go.sum") {
// return the attempt to parse go.mod & go.sum
return repo.parseGoSum()
// checks to see if the go.sum and go.mod files exist
// also check for a match with the repo.pb GoPrimitive bool
// todo: check mtime
func (repo *Repo) ValidGoSum() error {
if repo.ParseGoSum() {
return nil
}
repo.GoDeps = nil
repo.GoPrimitive = false
ok, err := repo.isPrimativeGoMod()
if err != nil {
// this means this repo does not depend on any other package
log.Info("PRIMATIVE repo error:", repo.GoPath, "err =", err)
return false, err
}
if ok {
// this means the repo is primitive so there is no go.sum
repo.GoPrimitive = true
return true, nil
}
// this should never happen
return false, errors.New("MakeRedomod() logic failed")
log.Info("ValidGoSum() deprecated")
return fmt.Errorf("ParseGoSum() failed")
}
// reads and parses the go.sum file
func (repo *Repo) parseGoSum() (bool, error) {
// empty out what was there before
repo.GoDeps = nil
tmp := filepath.Join(repo.FullPath, "go.sum")
gosum, err := os.Open(tmp)
if err != nil {
log.Warn("missing go.sum", repo.FullPath)
return false, err
func (repo *Repo) GoDepsLen() int {
if repo.GoDeps == nil {
return 0
}
defer gosum.Close()
scanner := bufio.NewScanner(gosum)
log.Info("gosum:", tmp)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Split(line, " ")
if len(parts) == 3 {
godep := strings.TrimSpace(parts[0])
version := strings.TrimSpace(parts[1])
if strings.HasSuffix(version, "/go.mod") {
version = strings.TrimSuffix(version, "/go.mod")
}
new1 := GoDep{
GoPath: godep,
Version: version,
}
if repo.GoDeps == nil {
repo.GoDeps = new(GoDeps)
}
repo.GoDeps.AppendUniqueGoPath(&new1)
/*
found := repo.FindGoDepByPath(godep)
if found == nil {
currentversion, ok := deps[godep]
if ok {
// only use the first value found in the file?
// this shouldn't have been possible. this function should
// only be called from MakeRedomod()
// todo: make go things a seperate package so this function
// isn't exported?
if version != currentversion {
log.Warn("\tgo.sum ", godep, "had both", version, currentversion)
}
} else {
deps[godep] = version
log.Info("\t", godep, "=", version)
}
*/
} else {
// I've never seen this happen yet
panic(errors.New("go.sum invalid: " + line))
// return false, errors.New("go.sum invalid: " + line)
}
}
if err := scanner.Err(); err != nil {
repo.GoDeps = nil
return false, err
}
return true, nil
}
func (repo *Repo) RepoType() string {
os.Setenv("GO111MODULE", "off")
cmd := []string{"go", "list", "-f", "'{{if eq .Name \"main\"}}binary{{else}}library{{end}}'"}
// cmd := []string{"go", "list", "-f", "'{{.Name}}'"} // probably use this. this just prints out the package name
// cmd := []string{"go", "list", "-f", "'{{.ImportPath}}'"} // returns go.wit.com/lib/protobuf/gitpb
result := repo.RunQuiet(cmd)
if result.Error != nil {
log.Warn("go list binary detect failed", result.Error)
return ""
}
output := strings.TrimSpace(strings.Join(result.Stdout, "\n"))
output = strings.Trim(output, "'")
return output
return len(repo.GoDeps.GoDeps)
}

81
http.go Normal file
View File

@ -0,0 +1,81 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package gitpb
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os/user"
"go.wit.com/log"
)
func httpPost(url string, data []byte) ([]byte, error) {
var err error
var req *http.Request
req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
log.Info("httpPost() with len", len(data), "url", url)
if err != nil {
log.Error(err)
return nil, err
}
usr, _ := user.Current()
req.Header.Set("author", usr.Username)
req.Header.Set("hostname", "fixme: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
}
log.Info("gitpb.httpPost() worked", url)
return body, nil
}
func (r *Repos) SubmitReposPB(url string) (*Repos, error) {
msg, err := r.Marshal()
if err != nil {
log.Info("Marshal() failed:", err)
return nil, err
}
log.Info("proto.Marshal() msg len", len(msg))
body, err := httpPost(url, msg)
if err != nil {
log.Info("httpPost() failed:", err)
return nil, err
}
log.Info("httpPost() worked with url:", url, "len(body) =", len(body))
if err := r.Unmarshal(body); err != nil {
log.Info("SubmitReposPB() Unmarshal() failed:", err)
log.Printf("%s\n", body)
return nil, err
}
return r, nil
}
func (r *Repos) SendPB(w http.ResponseWriter) error {
data, err := r.Marshal()
if err != nil {
log.Info("Marshal() failed:", err)
return err
}
log.Info("SendPB() Marshal() len(data)", len(data))
fmt.Fprintf(w, "%s", data)
return nil
}

70
isPrimitive.go Normal file
View File

@ -0,0 +1,70 @@
package gitpb
//
// DOES NOT MODIFY FILES
//
// only reads in the go.mod file. doesn't change anything
import (
"bufio"
"errors"
"os"
"path/filepath"
"strings"
"go.wit.com/log"
)
// deprecate use of IsPrimitive() to this function
// this assumes go.mod and go.sum are in a releasable state
func (repo *Repo) setPrimitive() error {
_, err := repo.computePrimitive()
return err
}
// Detect a 'Primitive' package. Sets the isPrimitive flag
// will return true if the repo is truly not dependent on _anything_ else
// like spew or lib/widget
// it assumes go mod ran init and tidy ran without error
func (repo *Repo) computePrimitive() (bool, error) {
// go mod init & go mod tidy ran without errors
log.Log(INFO, "isPrimitiveGoMod()", repo.FullPath)
tmp := filepath.Join(repo.FullPath, "go.mod")
gomod, err := os.Open(tmp)
if err != nil {
log.Log(INFO, "missing go.mod", repo.FullPath)
return false, err
}
defer gomod.Close()
if repo.Exists("go.sum") {
repo.GoInfo.GoPrimitive = false
return false, nil
}
scanner := bufio.NewScanner(gomod)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Fields(line)
log.Log(INFO, " gomod:", parts)
if len(parts) >= 1 {
log.Log(INFO, " gomod: part[0] =", parts[0])
if parts[0] == "require" {
log.Log(INFO, " should return false here")
return false, errors.New("go.mod file is not primitive")
}
/*
if parts[0] == "go" {
if parts[1] != "1.21" {
log.Log(WARN, "go not set to 1.21 for", repo.GetGoPath())
// return false, errors.New("go not set to 1.21 for " + repo.GetGoPath())
}
}
*/
}
}
repo.GoInfo.GoPrimitive = true
repo.GoDeps = nil
return true, nil
}

10
log.go
View File

@ -4,13 +4,15 @@ import (
"go.wit.com/log"
)
var GITPB *log.LogFlag
var GITPBWARN *log.LogFlag
var NOW *log.LogFlag
var INFO *log.LogFlag
var WARN *log.LogFlag
func init() {
full := "go.wit.com/lib/protobuf/gitpb"
short := "gitpb"
GITPB = log.NewFlag("GITPB", false, full, short, "general gitpb things")
GITPBWARN = log.NewFlag("GITPBWARN", true, full, short, "gitpb warnings")
NOW = log.NewFlag("NOW", true, full, short, "stuff that's supposed to print")
INFO = log.NewFlag("INFO", false, full, short, "general gitpb things")
WARN = log.NewFlag("WARN", true, full, short, "gitpb warnings")
}

140
reload.go Normal file
View File

@ -0,0 +1,140 @@
package gitpb
import (
"strings"
"time"
"go.wit.com/lib/config"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// sets a flag that the repos have changed
// used later by applications on exit to test if
// the protobuf needs to be written to disk
func reposChanged(b bool) {
config.SetChanged("repos", true)
}
// returns true based on os.Stat() only checks
// seems to kinda work ok. goal is to avoid os.Exec() here for speed
// this might be the 1 place where libgit2 would be a good idea
func (repo *Repo) HasChanged() bool {
return repo.DidRepoChange()
}
// does a fast check with os.Stat()
// if the mtimes changed, does a full repo.ReloadForce()
func (repo *Repo) ReloadCheck() error {
if !repo.DidRepoChange() {
return nil
}
reposChanged(true)
err := repo.ReloadForce()
if err != nil {
return err
}
return log.Errorf("gitpb.ReloadCheck() detected a change in the repo")
}
// TODO: clean this up more, but it is working now more or less
func (repo *Repo) ReloadForce() error {
reposChanged(true)
// sometimes, on new repos, if .git/HEAD does not exist
// defective git daemons or badly configured repos, 'git clone' can fail
// if so, 'git fetch origin' can repair the state
if !repo.Exists(".git/HEAD") {
cmd := []string{"git", "fetch", "origin"}
repo.RunVerbose(cmd)
cmd = []string{"git", "checkout", "main"} // todo: figure out main
repo.RunVerbose(cmd)
}
// log.Info("in reload", repo.FullPath)
repo.Tags = new(GitTags)
repo.reloadGitTags()
repo.GoDeps = new(GoDeps)
if repo.GoInfo == nil {
repo.GoInfo = new(GoInfo)
}
repo.ParseGoSum() // also sets GoPrimitive
repo.reloadVersions()
repo.setRepoType()
// this is probably a good place & time to store these
repo.reloadMtimes()
repo.CheckDirty()
repo.setRepoState()
if repo.GitConfig == nil {
if err := repo.updateGitConfig(); err != nil {
return err
}
}
repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName())
repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName())
// LastUpdate should always be the newest time
repo.Times.LastUpdate = timestamppb.New(time.Now())
repo.ValidateUTF8()
return nil
}
func (repo *Repo) VerifyRemoteAndLocalBranches(bname string) bool {
if !repo.IsBranchRemote(bname) {
return true
}
lh := repo.GetLocalHash(bname)
rh := repo.GetRemoteHash(bname)
if lh == rh {
// log.Info(r.FullPath, "local devel == remote devel", lh, rh)
return true
} else {
log.Info(lh, rh, "local != remote", repo.FullPath, bname)
}
return false
}
func (repo *Repo) SetDevelBranchName(bname string) {
repo.DevelBranchName = bname
}
func (repo *Repo) SetUserBranchName(bname string) {
repo.UserBranchName = bname
}
// updates LastTag by age
func (repo *Repo) setLastTag() {
repo.LastTag = repo.FindLastTag()
}
func (repo *Repo) setCurrentBranchName() {
repo.CurrentBranchName = ""
r, err := repo.RunQuiet([]string{"git", "branch", "--show-current"})
output := strings.Join(r.Stdout, "\n")
if err != nil {
log.Log(WARN, "GetCurrentBranchName() not in a git repo?", err, repo.GetGoPath())
log.Log(WARN, "GetCurrentBranchName() output might have worked anyway:", output)
}
repo.CurrentBranchName = strings.TrimSpace(output)
}
// always spawns 'git' and always should spawn 'git'
func (repo *Repo) setCurrentBranchVersion() {
repo.CurrentBranchVersion = ""
if repo == nil {
log.Info("repo.GetCurrentBranchVersion() repo == nil")
return
}
r, err := repo.RunQuiet([]string{"git", "describe", "--tags"})
output := strings.Join(r.Stdout, "\n")
if err != nil {
// log.Log(WARN, "GetCurrentBranchVersion() not in a git repo?", err, repo.GetGoPath())
// log.Log(WARN, "GetCurrentBranchVersion() output might have worked anyway:", output)
repo.CurrentBranchVersion = "gitpb err"
return
}
repo.CurrentBranchVersion = strings.TrimSpace(output)
}

185
reloadBranches.go Normal file
View File

@ -0,0 +1,185 @@
package gitpb
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"unicode/utf8"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// TODO: make this report the error somewhere
// This is supposed to check all the branches to make sure
// they are the same. that was originally what this was for
// now I think it's jsut probably dumb old code that doesn't
// need to be here
// actually, this is to attempt to verify absolutely everything
// is pushed upstream before doing a rm -rf ~/go/src
// TODO: revisit this code in the autotypist later
func (repo *Repo) CheckBranches() bool {
var hashCheck string
var perfect bool = true
all := repo.GetBranches()
path := filepath.Join(repo.FullPath, ".git/refs/")
for _, b := range all {
parts := strings.Split(b, "/")
rdir := "heads"
if len(parts) == 2 {
rdir = "remotes"
}
fullfile := filepath.Join(path, rdir, b)
// check if the ref name is "HEAD". if so, skip
runeCount := utf8.RuneCountInString(fullfile)
// Convert the string to a slice of runes
runes := []rune(fullfile)
// Slice the last 4 runes
lastFour := runes[runeCount-4:]
if string(lastFour) == "HEAD" {
// assume HEAD is always a valid branch
// log.Info("skip HEAD. always valid branch name", fullfile)
continue
}
content, _ := ioutil.ReadFile(fullfile)
hash := strings.TrimSpace(string(content))
if hashCheck == "" {
hashCheck = hash
}
var cmd []string
cmd = append(cmd, "git", "show", "-s", "--format=%ci", hash)
r := shell.PathRunLog(repo.GetFullPath(), cmd, INFO)
if r.Error != nil {
log.Log(WARN, "CheckBranches() git show error:", r.Error)
}
// git show -s --format=%ci <hash> will give you the time
// log.Log(REPO, fullfile)
if hash == hashCheck {
// log.Info("notsure why this git show is here", hash)
} else {
// log.Printf("UNKNOWN BRANCH %-50s %s %s %s\n", repo.GetFullPath(), r.Stdout, cmd, b)
perfect = false
}
}
return perfect
}
func (repo *Repo) GetBranches() []string {
var all []string
var heads []string
var remotes []string
heads = ListFiles(filepath.Join(repo.GetFullPath(), "/.git/refs/heads"))
remotes = ListFiles(filepath.Join(repo.GetFullPath(), "/.git/refs/remotes"))
all = heads
all = append(all, remotes...)
// for _, branch := range all {
// log.Info("getBranches()", branch)
// }
return all
}
// goes in one directory so it gets remote branch names
// old code. todo: modernize it
func ListFiles(directory string) []string {
var files []string
fileInfo, err := os.ReadDir(directory)
if err != nil {
log.Error(err)
return nil
}
for _, file := range fileInfo {
if file.IsDir() {
dirname := file.Name()
newdir, _ := os.ReadDir(directory + "/" + dirname)
for _, file := range newdir {
if !file.IsDir() {
files = append(files, dirname+"/"+file.Name())
}
}
} else {
files = append(files, file.Name())
}
}
return files
}
// forge doesn't want a remote user branch
// this will make sure the user branch is only local
func (repo *Repo) checkUserBranch() error {
ubn := repo.GetUserBranchName()
log.Info("user branch name:", ubn, repo.GetGoPath())
if repo.GitConfig == nil {
return fmt.Errorf("GitConfig == nil")
}
for _, l := range repo.GitConfig.Local {
log.Info("local branch name:", l.Name)
}
return nil
}
func (repo *Repo) ExamineBranches() *GitTag {
var hashCheck string
all := repo.GetBranches()
path := filepath.Join(repo.FullPath, ".git/refs/")
for _, b := range all {
parts := strings.Split(b, "/")
rdir := "heads"
if len(parts) == 2 {
rdir = "remotes"
}
fullfile := filepath.Join(path, rdir, b)
// check if the ref name is "HEAD". if so, skip
runeCount := utf8.RuneCountInString(fullfile)
// Convert the string to a slice of runes
runes := []rune(fullfile)
// Slice the last 4 runes
lastFour := runes[runeCount-4:]
if string(lastFour) == "HEAD" {
// assume HEAD is always a valid branch
// log.Info("skip HEAD. always valid branch name", fullfile)
continue
}
content, _ := ioutil.ReadFile(fullfile)
hash := strings.TrimSpace(string(content))
if hashCheck == "" {
hashCheck = hash
}
var cmd []string
cmd = append(cmd, "git", "show", "-s", "--format=%cI", hash)
r := shell.PathRunLog(repo.GetFullPath(), cmd, INFO)
if r.Error != nil {
log.Log(WARN, "CheckBranches() git show error:", r.Error)
}
// git show -s --format=%ci <hash> will give you the time
// log.Log(REPO, fullfile)
if hash == hashCheck {
// log.Info("notsure why this git show is here", hash)
} else {
// log.Printf("UNKNOWN BRANCH %-50s %s %s %s\n", repo.GetFullPath(), r.Stdout, cmd, b)
tag := new(GitTag)
tag.Refname = b
tag.Hash = hash
if len(r.Stdout) > 0 {
tagtime := parseDateRFC3339(r.Stdout[0])
tag.Creatordate = timestamppb.New(tagtime)
}
return tag
}
}
return nil
}

65
reloadCheckDirty.go Normal file
View File

@ -0,0 +1,65 @@
package gitpb
// runs git, parses output
// types faster than you can
import (
"fmt"
"strings"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
"google.golang.org/protobuf/types/known/timestamppb"
)
func (repo *Repo) NoteChange(s string) {
log.Warn("NoteChange()", s)
}
// just return the current value
func (repo *Repo) IsDirty() bool {
return repo.Dirty
}
// actually os.Exec('git')
func (repo *Repo) CheckDirty() bool {
cmd := []string{"git", "status", "--porcelain"}
r := shell.PathRunLog(repo.FullPath, cmd, INFO)
if r.Error != nil {
log.Warn("CheckDirty() status cmd =", cmd)
out := strings.Join(r.Stdout, "\n")
log.Warn("CheckDirty() status out =", out)
log.Warn("CheckDirty() status err =", r.Error)
log.Error(r.Error, "CheckDirty() git status error")
repo.NoteChange("git status is in error " + fmt.Sprint(r.Error))
repo.Dirty = true
repo.State = "dirty"
return true
}
// dirty if anything but go.mod and go.sum
var bad bool = false
for _, line := range r.Stdout {
parts := strings.Fields(line)
if len(parts) == 2 {
switch parts[1] {
case "go.mod":
case "go.sum":
default:
bad = true
}
} else {
bad = true
}
}
repo.DirtyList = r.Stdout
pbnow := timestamppb.New(time.Now())
repo.Times.LastDirty = pbnow
repo.Dirty = bad
if bad {
repo.State = "dirty"
}
return bad
}

74
reloadIsTracked.go Normal file
View File

@ -0,0 +1,74 @@
package gitpb
import (
"errors"
"fmt"
"go.wit.com/log"
)
func (repo *Repo) isTracked(file string) (bool, error) {
cmd := []string{"git", "ls-files", "--error-unmatch", file}
result := repo.Run(cmd)
if result.Error != nil {
return false, result.Error
}
if result.Exit != 0 {
return false, nil
}
return true, nil
}
func (repo *Repo) isIgnored(file string) (bool, error) {
cmd := []string{"git", "check-ignore", "-q", file}
result := repo.Run(cmd)
if result.Error != nil {
return false, result.Error
}
if result.Exit == 0 {
// exit with 0 means the file is ignored
return true, nil
}
// non-zero exit means the file is not ignored
return false, nil
}
// is it a good idea to run go-mod-clean in this repo?
// for now, check if this repo should be ignored
// TODO: go.mod and go.sum should be moved to git tag metadata
func (repo *Repo) RepoIgnoresGoMod() error {
repo.GoInfo.GitIgnoresGoSum = false
file := "go.mod"
if tracked, err := repo.isTracked(file); err != nil {
msg := fmt.Sprintf("%s Error checking if %s tracked: %v\n", repo.GetGoPath(), file, err)
log.Info("gitpb:", msg)
return err
} else {
if tracked {
msg := fmt.Sprintf("%s %s is tracked by Git.\n", repo.GetGoPath(), file)
log.Info("gitpb:", msg)
return errors.New(msg)
}
}
if ignored, err := repo.isIgnored(file); err != nil {
if err != nil {
msg := fmt.Sprintf("%s Error checking if ignored: %v\n", repo.GetGoPath(), err)
log.Info("gitpb:", msg)
return err
}
} else {
if ignored {
// fmt.Printf("%s %s is ignored by Git.\n", repo.GetGoPath(), file)
repo.GoInfo.GitIgnoresGoSum = true
return nil
}
}
msg := fmt.Sprintf("%s %s is neither tracked nor ignored by Git.\n", repo.GetGoPath(), file)
// this means, if you make a go.mod file, it'll add it to the repo to be tracked
// so you need to either add it to .gitignore (this is what should happen)
// or accept you want an auto-generated file to put endless garbage in your git repo
// this obviously exposes my opinion on this subject matter
log.Info("gitpb:", msg)
return errors.New(msg)
}

220
reloadMtime.go Normal file
View File

@ -0,0 +1,220 @@
package gitpb
// An app to submit patches for the 30 GO GUI repos
import (
"fmt"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
"google.golang.org/protobuf/types/known/timestamppb"
)
func (repo *Repo) Mtime(fname string) *time.Time {
var fileTime *time.Time
tmp, err := repo.oldMtime(fname)
fileTime = &tmp
if err != nil {
log.Info("MTime got err", err)
return nil
}
return fileTime
}
func (repo *Repo) changedDir() bool {
fname := ".git"
fileTime := repo.Mtime(fname)
if fileTime == nil {
// .git doesn't exist. something is wrong. rescan this repo
return true
}
mtime := timestamppb.New(*fileTime)
pbtime := repo.Times.MtimeDir
if pbtime == nil { // this can happen?
repo.Times.MtimeDir = mtime
return true
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
repo.Times.MtimeDir = mtime
return true
}
func (repo *Repo) didFileChange(fname string, pbtime *timestamppb.Timestamp) bool {
fileTime := repo.Mtime(fname)
if fileTime == nil {
repo.StateChange = fmt.Sprintf("%s missing", fname)
return true
}
mtime := timestamppb.New(*fileTime)
if pbtime == nil {
repo.StateChange = fmt.Sprintf("%s mtime never recorded", fname)
return true
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
// it's the same!
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s mtime changed %s", fname, shell.FormatDuration(dur))
// need to reload from the filesystem
return true
}
// boo. I'm not good at golang. this should use reflect. I'm bad. my code is bad. boo this man. you're cool, I'm outta here
// make this work right someday
func (repo *Repo) updateMtime(fname string, pbname string) bool {
fileTime := repo.Mtime(fname)
if fileTime == nil {
// .git/HEAD doesn't exist. something is wrong. rescan this repo
return true
}
mtime := timestamppb.New(*fileTime)
pbtime := repo.Times.MtimeHead
if pbtime == nil { // this can happen?
repo.Times.MtimeHead = mtime
return true
}
switch pbname {
case "MtimeHead":
if pbtime == nil { // this can happen?
repo.Times.MtimeHead = mtime
return true
}
default:
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
repo.Times.MtimeHead = mtime
return true
}
func (repo *Repo) changedHead() bool {
fname := ".git/HEAD"
fileTime := repo.Mtime(fname)
if fileTime == nil {
// .git/HEAD doesn't exist. something is wrong. rescan this repo
return true
}
mtime := timestamppb.New(*fileTime)
pbtime := repo.Times.MtimeHead
if pbtime == nil { // this can happen?
repo.Times.MtimeHead = mtime
return true
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
repo.Times.MtimeHead = mtime
return true
}
// check the mtime of the .git/config file
func (repo *Repo) changedConfig() bool {
fname := ".git/config"
fileTime := repo.Mtime(fname)
if fileTime == nil {
// .git/config doesn't exist. something is wrong!
log.Info("gitpb .git/config is missing", repo.GetGoPath())
return false
}
mtime := timestamppb.New(*fileTime)
pbtime := repo.Times.MtimeConfig
if pbtime == nil { // this can happen?
repo.Times.MtimeConfig = mtime
return true
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
repo.Times.MtimeConfig = mtime
return true
}
func (repo *Repo) changedIndex() bool {
fname := ".git/index"
fileTime := repo.Mtime(fname)
if fileTime == nil {
// .git/index doesn't exist. something is wrong. rescan this repo
return true
}
mtime := timestamppb.New(*fileTime)
pbtime := repo.Times.MtimeIndex
if pbtime == nil { // this can happen?
repo.Times.MtimeIndex = mtime
return true
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
repo.Times.MtimeIndex = mtime
return true
}
func (repo *Repo) reloadMtimes() bool {
var changed bool
if repo.Times == nil {
repo.Times = new(GitTimes)
log.Info(repo.FullPath, "repo.Times were nil")
}
if repo.changedHead() {
changed = true
}
if repo.changedIndex() {
changed = true
}
if repo.changedConfig() {
changed = true
}
if repo.changedDir() {
// changed = true
}
return changed
}
func (repo *Repo) DidRepoChange() bool {
if repo.Times == nil {
repo.Times = new(GitTimes)
}
if repo.didFileChange(".git/HEAD", repo.Times.MtimeHead) {
return true
}
if repo.didFileChange(".git/index", repo.Times.MtimeIndex) {
return true
}
if repo.didFileChange(".git/config", repo.Times.MtimeConfig) {
return true
}
if repo.didFileChange(".git", repo.Times.MtimeDir) {
// todo: do something with CheckDirty()
// return true
}
if repo.Times.LastUpdate == nil {
log.Info("repo.Reload() was never run")
return true
} else {
if repo.Times.LastUpdate.Seconds < repo.Times.MtimeHead.Seconds {
log.Info("SHOULD RUN Reload() here", repo.Times.MtimeHead.Seconds-repo.Times.LastUpdate.Seconds, "secs diff")
return true
}
}
// log.Info("DidRepoChange() is false", repo.FullPath)
return false
}

215
reloadParseGitConfig.go Normal file
View File

@ -0,0 +1,215 @@
package gitpb
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"go.wit.com/log"
)
// does processing on the go.mod and go.sum files
func (repo *Repo) updateGitConfig() error {
if repo == nil {
return fmt.Errorf("gitpb.updateGitConfig() repo == nil")
}
if repo.GitConfig == nil {
repo.GitConfig = new(GitConfig)
}
repo.GitConfig.Core = make(map[string]string)
repo.GitConfig.Remotes = make(map[string]*GitRemote)
repo.GitConfig.Branches = make(map[string]*GitBranch)
repo.GitConfig.Submodules = make(map[string]string)
repo.GitConfig.Versions = make(map[string]string)
repo.GitConfig.Hashes = make(map[string]string)
url, err := repo.readGitConfig()
if repo.URL != "" {
log.Info("gitpb: url already set", url, repo.URL)
}
if url == "" {
log.Info(repo.FullPath, "url was blank. warn user this repo is only on the local disk")
} else {
repo.URL = url
}
return err
}
// readGitConfig reads and parses the .git/config file
func (repo *Repo) readGitConfig() (string, error) {
var foundURL string
filename := filepath.Join(repo.GetFullPath(), ".git/config")
file, err := os.Open(filename)
defer file.Close()
if err != nil {
return "", err
}
var currentSection string = ""
var currentName string = ""
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Skip empty lines and comments
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
continue
}
// Check for section headers
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
line = strings.Trim(line, "[]")
parts := strings.Split(line, " ")
currentSection = parts[0]
if len(parts) == 2 {
line = strings.Trim(line, "[]")
currentName = strings.Trim(parts[1], "\"")
}
continue
}
partsNew := strings.SplitN(line, "=", 2)
if len(partsNew) != 2 {
log.Log(WARN, "error on config section:", currentSection, "line:", line)
}
key := strings.TrimSpace(partsNew[0])
key = strings.TrimSuffix(key, "\"")
value := strings.TrimSpace(partsNew[1])
value = strings.TrimSuffix(value, "\"")
switch currentSection {
case "core":
repo.GitConfig.Core[key] = value
case "gui":
// don't really need gui stuff right now
case "pull":
// don't store git config pull settings here
// git config probably has 'rebase = false'
case "remote":
test, ok := repo.GitConfig.Remotes[currentName]
if !ok {
test = new(GitRemote)
repo.GitConfig.Remotes[currentName] = test
}
log.Log(INFO, "switch currentSection", currentSection, currentName)
switch key {
case "url":
if foundURL == "" {
foundURL = value
} else {
if foundURL != value {
log.Info("TODO: gitpb: handle multiple remotes in the parser", foundURL, value)
}
}
if test.Url == value {
continue
}
if test.Url == "" {
test.Url = value
continue
}
log.Log(INFO, "error url mismatch", test.Url, value)
case "fetch":
if test.Fetch == value {
continue
}
if test.Fetch == "" {
test.Fetch = value
continue
}
log.Log(INFO, "error fetch mismatch", test.Fetch, value)
default:
log.Log(INFO, "unknown remote:", line)
}
case "branch":
test, ok := repo.GitConfig.Branches[currentName]
if !ok {
test = new(GitBranch)
repo.GitConfig.Branches[currentName] = test
repo.processBranch(currentName)
}
switch key {
case "remote":
repo.GitConfig.Branches[currentName].Remote = value
case "merge":
repo.GitConfig.Branches[currentName].Merge = value
default:
log.Log(INFO, "error unknown remote:", currentSection, currentName, key, value)
log.Log(INFO, "unknown branch:", line)
}
case "submodule":
// test, ok := rs.gitConfig.submodules[currentName]
switch key {
case "active":
// probably 'true' or 'false'
case "url":
repo.GitConfig.Submodules[currentName] = value
default:
log.Log(WARN, "unknown submodule line:", line)
}
case "diff":
// test, ok := rs.gitConfig.submodules[currentName]
switch key {
case "renameLimit":
log.Log(INFO, "name:", line)
default:
log.Log(WARN, "unknown name line:", filename, line)
}
case "user":
// test, ok := rs.gitConfig.submodules[currentName]
switch key {
case "name":
log.Log(INFO, "name:", line)
case "email":
log.Log(INFO, "email:", line)
default:
log.Log(WARN, "unknown name line:", filename, line)
}
case "git-bug":
log.Log(WARN, "repo uses git-bug!", filename)
default:
log.Log(WARN, filename, "unknown line:", line)
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return foundURL, nil
}
func (repo *Repo) processBranch(branch string) {
log.Log(INFO, " ", branch)
hash, ok := repo.GitConfig.Hashes[branch]
filename := filepath.Join(repo.GetFullPath() + "/.git/refs/heads/" + branch)
log.Log(INFO, " hash: need to open", filename)
data, err := ioutil.ReadFile(filename)
if err != nil {
log.Log(WARN, "hash: read failed", filename)
return
}
newhash := strings.TrimSpace(string(data))
log.Log(INFO, " hash:", newhash)
repo.GitConfig.Hashes[branch] = newhash
if ok {
if hash != newhash {
log.Log(WARN, "hash changed", hash)
}
}
name, _ := repo.gitDescribeByHash(newhash)
repo.GitConfig.Versions[newhash] = name
log.Log(INFO, " hash: version", name)
}

83
reloadRepoState.go Normal file
View File

@ -0,0 +1,83 @@
package gitpb
// does processing on the go.mod and go.sum files
import (
"go.wit.com/log"
)
func (repo *Repo) setRepoState() {
if repo == nil {
return
}
if repo.IsDirty() {
repo.State = "dirty"
return
}
if repo.GetUserVersion() == "uerr" {
// user has not made user branches
} else {
if repo.GetUserVersion() != repo.GetDevelVersion() {
repo.State = "merge to devel"
return
}
}
if repo.GetDevelVersion() != repo.GetMasterVersion() {
if !repo.IsLocalBranch(repo.GetDevelBranchName()) {
// the remote devel branch exists but is not checked out
repo.State = "no devel branch"
return
}
b1 := repo.CountDiffObjects(repo.GetMasterBranchName(), repo.GetDevelBranchName())
if b1 == 0 {
repo.State = "merge to main"
// log.Info("master vs devel count is normal b1 == 0", b1)
} else {
repo.State = "DEVEL behind MASTER"
// log.Info("master vs devel count b1 != 0", b1)
log.Infof("%s devel branch is behind master branch (missing %d commits)\n", repo.GetGoPath(), b1)
}
return
}
// if IsGoTagVersionGreater(oldtag string, newtag string) bool {
if !IsGoTagVersionGreater(repo.GetLastTag(), repo.GetMasterVersion()) {
repo.State = "last tag greater error"
return
}
if repo.GetLastTag() != repo.GetMasterVersion() {
repo.State = "ready to release"
return
}
if repo.CheckBranches() {
repo.State = "PERFECT"
return
}
repo.State = "unknown branches"
}
// returns true if old="v0.2.4" and new="v0.3.3"
// returns true if equal
// todo: make all of this smarter someday
func IsGoTagVersionGreater(oldtag string, newtag string) bool {
olda, oldb, oldc := splitInts(oldtag)
newa, newb, newc := splitInts(newtag)
if newa < olda {
return false
}
if newb < oldb {
return false
}
if newc < oldc {
return false
}
return true
}
// returns true for "v0.2.4" and false for "v0.2.43-asdfj"
// actually returns false for anything not perfectly versioned
func IsGoTagPublished(oldtag string, newtag string) bool {
// todo
return true
}

82
reloadRepoType.go Normal file
View File

@ -0,0 +1,82 @@
package gitpb
// does processing on the go.mod and go.sum files
import (
"os"
"strings"
"go.wit.com/log"
)
// TODO: this needs to be redone in a smarter way
// to identify which repos have things to build in them
func (repo *Repo) GetRepoType() string {
if repo == nil {
return "nil"
}
if repo.GoInfo == nil {
log.Warn("gitpb.RepoType() plugin was not set correctly")
log.Warn("gitpb.RepoType() plugin was not set correctly")
log.Warn("gitpb.RepoType() plugin was not set correctly")
repo.GoInfo = new(GoInfo)
repo.setRepoType()
}
if repo.GoInfo.GoPlugin {
return "plugin"
}
if repo.GoInfo.GoBinary {
if repo.Exists(".plugin") {
log.Warn("gitpb.RepoType() plugin was not set correctly")
repo.GoInfo.GoPlugin = true
return "plugin"
}
return "binary"
}
// binary should always take precidence over libraries that are protobuf's
if repo.GoInfo.GoProtobuf {
return "protobuf"
}
if repo.GoInfo.GoLibrary {
return "library"
}
// todo: figure out what to do here. for now, binary is easiest
return "library"
}
func (repo *Repo) setRepoType() {
if repo == nil {
return
}
if repo.Exists(".plugin") {
repo.GoInfo.GoPlugin = true
}
if ok, _, _ := repo.IsProtobuf(); ok {
repo.GoInfo.GoProtobuf = true
}
switch repo.goListRepoType() {
case "binary":
repo.GoInfo.GoBinary = true
return
case "library":
repo.GoInfo.GoLibrary = true
return
}
}
func (repo *Repo) goListRepoType() string {
os.Setenv("GO111MODULE", "off")
cmd := []string{"go", "list", "-f", "'{{if eq .Name \"main\"}}binary{{else}}library{{end}}'"}
// cmd := []string{"go", "list", "-f", "'{{.Name}}'"} // probably use this. this just prints out the package name
// cmd := []string{"go", "list", "-f", "'{{.ImportPath}}'"} // returns go.wit.com/lib/protobuf/gitpb
result, err := repo.RunQuiet(cmd)
if err != nil {
// log.Info("go list binary detect failed", err)
return ""
}
output := strings.TrimSpace(strings.Join(result.Stdout, "\n"))
output = strings.Trim(output, "'")
return output
}

223
reloadTags.go Normal file
View File

@ -0,0 +1,223 @@
package gitpb
import (
"path/filepath"
"slices"
"strings"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// redo this. use go-git2 ?
func (repo *Repo) allCommits() error {
return nil
}
/*
// this is dumb
func (repo *Repo) AllCommits() error {
// tags := []string{"cd", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
// format := strings.Join(tags, "_,,,_%")
cmd := []string{"git", "log", "--format=%cd"}
result := shell.PathRunQuiet(repo.FullPath, cmd)
if result.Error != nil {
log.Warn("git for-each-ref error:", result.Error)
return result.Error
}
newest := strings.Join(result.Stdout, "\n")
newest = strings.TrimSpace(newest)
tmp := getGitDateStamp(newest)
repo.Times.NewestCommit = timestamppb.New(tmp)
return nil
}
*/
// reload the tags
func (repo *Repo) reloadGitTags() error {
// todo: look for changes in the tags?
repo.Tags = new(GitTags)
tags := []string{"%(objectname)", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
format := strings.Join(tags, "_,,,_")
cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format", format}
// log.Info("RUNNING:", strings.Join(cmd, " "))
result := shell.PathRunQuiet(repo.FullPath, cmd)
if result.Error != nil {
log.Warn("git for-each-ref error:", result.Error)
return result.Error
}
lines := result.Stdout
// reverse the git order
slices.Reverse(lines)
var refname string
var hash string
var subject string
var ctime *timestamppb.Timestamp
var atime *timestamppb.Timestamp
for i, line := range lines {
var parts []string
parts = make([]string, 0)
parts = strings.Split(line, "_,,,_")
if len(parts) != 5 {
log.Info("tag error:", i, parts)
continue
}
hash = parts[0]
if parts[1] != "" {
tmp := getGitDateStamp(parts[1])
ctime = timestamppb.New(tmp)
}
if parts[2] != "" {
tmp := getGitDateStamp(parts[2])
atime = timestamppb.New(tmp)
}
refname = parts[3]
subject = parts[4]
newr := GitTag{
Refname: refname,
Hash: hash,
Subject: subject,
Creatordate: ctime,
Authordate: atime,
}
repo.Tags.Append(&newr)
}
// GIT_COMMITTER_DATE="$(git log -1 --format=%cI)" \
// GIT_AUTHOR_DATE="$(git log -1 --format=%aI)" \
// git am --committer-date-is-author-date < patch.mbox
// good format for insuring the hashs are identical when using git am
// git log -1 --format="%H %aI %cI %an %ae %cn %ce"
// also set the repo.NewestCommit
cmd = []string{"git", "log", "-1", "--format=%cd"}
result = shell.PathRunQuiet(repo.FullPath, cmd)
if result.Error != nil {
log.Warn("git for-each-ref error:", result.Error)
return result.Error
}
newest := strings.Join(result.Stdout, "\n")
newest = strings.TrimSpace(newest)
tmp := getGitDateStamp(newest)
repo.Times.NewestCommit = timestamppb.New(tmp)
return nil
}
// attempt to parse "2024-12-13 15:39:57 -0600"
func parseGitDate(dateString string) time.Time {
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
const gitLayout = "2006-01-02 15:04:05 -0600"
tagTime, err := time.Parse(gitLayout, dateString)
if err != nil {
const gitLayout2 = "2006-01-02 15:04:05 +0600"
tagTime, err = time.Parse(gitLayout2, dateString)
}
if err != nil {
log.Warn("GOT THIS IN PARSE AAA." + dateString + ".AAA")
log.Warn(err)
return time.Now()
}
return tagTime
}
// attempt to parse strict ISO 8601 format // 2025-01-07T21:22:16-06:00
func parseDateRFC3339(dateString string) time.Time {
tagTime, err := time.Parse(time.RFC3339, dateString)
if err != nil {
log.Warn("GOT THIS IN PARSE AAA." + dateString + ".AAA")
log.Warn(err)
return time.Now()
}
return tagTime
}
// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600"
func getGitDateStamp(gitdefault string) time.Time {
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
tagTime, err := time.Parse(gitLayout, gitdefault)
if err != nil {
log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA")
log.Warn(err)
return time.Now()
}
return tagTime
}
func (tag *GitTag) GetAge() time.Duration {
return time.Since(tag.GetAuthordate().AsTime())
}
func (repo *Repo) NewestTag() *GitTag {
loop := repo.Tags.SortByAge()
for loop.Scan() {
r := loop.Next()
return r
}
return nil
}
// this should just do is.Exists(".git/refs/heads/findname")
// this is a simple check and doesn't work all the time
func (repo *Repo) LocalTagExists(findname string) bool {
fname := filepath.Join(".git/refs/heads", findname)
if repo.Exists(fname) {
return true
}
fname = filepath.Join(".git/refs/tags", findname)
if repo.Exists(fname) {
return true
}
/*
loop := repo.Tags.SortByRefname()
for loop.Scan() {
ref := loop.Next()
// log.Info(repo.GoPath, ref.Refname)
if strings.HasPrefix(ref.Refname, "refs/remotes") {
continue
}
tagname := filepath.Base(ref.Refname)
// log.Info("tag:", path, tagname, "from", repo.GoPath)
if tagname == findname {
// log.Info("found tag:", path, tagname, "from", repo.GoPath)
return true
}
}
*/
return false
}
// returns true if 'taggy' is _ONLY_ a local tag
// this means you can not do a git pull or git push on it
func (repo *Repo) IsOnlyLocalTag(taggy string) bool {
// first make sure the tag is actually even local
if !repo.LocalTagExists(taggy) {
// this means it's not even local now.
return false
}
// okay, taggy exists, does it exist in a remote repo?
loop := repo.Tags.SortByRefname()
for loop.Scan() {
ref := loop.Next()
tagname := ref.Refname
if strings.HasPrefix(tagname, "refs/remotes") {
path, filename := filepath.Split(tagname)
if filename == taggy {
log.Log(INFO, "found tag:", path, filename, "from", repo.GetGoPath())
return false
}
}
}
// we couldn't find the local tag anywhere remote, so it's probably only local
return true
}

View File

@ -9,6 +9,23 @@ import (
"go.wit.com/log"
)
// does this repo build a binary?
func (r *Repo) IsBinary() bool {
if r.GoInfo != nil {
return r.GoInfo.GetGoBinary()
}
// todo: detect C & other language binary packages
return false
}
// does this repo build a binary?
func (r *Repo) IsGoPlugin() bool {
if r.GoInfo != nil {
return r.GoInfo.GetGoPlugin()
}
return false
}
// This returns the list of autogenerated protobuf files
// it assumes any file *.pb.go is autogenerated
//
@ -41,7 +58,7 @@ func (repo *Repo) IsProtobuf() (bool, []string, error) {
if found {
// log.Info("found ok")
} else {
log.Info("missing compiled proto file:", pname+"pb.go")
// log.Info("gitpb: IsProtobuf() missing compiled proto file:", pname+".pb.go")
err = errors.New("compiled file " + pname + ".pb.go missing")
}
}
@ -57,15 +74,22 @@ func (repo *Repo) IsProtobuf() (bool, []string, error) {
return anyfound, allc, err
}
// look for any proto files. do not enter directories
// note: good golang libraries are best done in a single directory
func scanForProtobuf(srcDir string) ([]string, []string, error) {
var protofiles []string
var compiled []string
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Log(GITPBWARN, "Error accessing path:", path, err)
log.Log(WARN, "Error accessing path:", path, err)
return err
}
// ignore the start dir
if srcDir == path {
return nil
}
if strings.HasSuffix(path, ".proto") {
//
protofiles = append(protofiles, path)
@ -75,8 +99,40 @@ func scanForProtobuf(srcDir string) ([]string, []string, error) {
compiled = append(compiled, path)
}
// don't go into any directories
if info.IsDir() {
return filepath.SkipDir
}
return nil
})
return protofiles, compiled, err
}
func (repo *Repo) GetProtoFiles() ([]string, error) {
var protofiles []string
srcDir := repo.GetFullPath()
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Log(WARN, "Error accessing path:", path, err)
return err
}
// ignore the start dir
if srcDir == path {
return nil
}
if strings.HasSuffix(path, ".proto") {
//
protofiles = append(protofiles, path)
}
if info.IsDir() {
return filepath.SkipDir
}
return nil
})
return protofiles, err
}

19
repo.helpers.go Normal file
View File

@ -0,0 +1,19 @@
// Code generated by go.wit.com/apps/autogenpb DO NOT EDIT.
// This file was autogenerated with autogenpb v0.0.78 2025-08-31_15:49:04_UTC
// go install go.wit.com/apps/autogenpb@latest
//
// define which structs (messages) you want to use in the .proto file
// Then sort.pb.go and marshal.pb.go files are autogenerated
//
// autogenpb uses it and has an example .proto file with instructions
//
package gitpb
func (t *ReposTable) MyMasterBranch() *RepoAnyFunc {
sf := t.AddStringFunc("master", func(m *Repo) string {
return m.MasterBranchName
})
sf.Width = 30
return sf
}

92
repo.merge.go Normal file
View File

@ -0,0 +1,92 @@
package gitpb
import (
"fmt"
"github.com/go-cmd/cmd"
"go.wit.com/log"
)
func (r *Repo) MergeToDevel() (*cmd.Status, error) {
r.ReloadCheck()
if r.GetCurrentBranchName() != r.GetDevelBranchName() {
return nil, fmt.Errorf("repo not on devel branch")
}
if r.CheckDirty() {
return nil, fmt.Errorf("repo is dirty")
}
devel := r.GetDevelBranchName()
user := r.GetUserBranchName()
log.Info(r.FullPath, "MergeToDevel() merging from", user, "into", devel)
cmd := []string{"git", "merge", user}
result := r.RunRealtimeVerbose(cmd)
if result.Error != nil {
log.Log(WARN, "MergeToDevel() failed", r.GetFullPath())
return nil, result.Error
}
if !r.IsBranchRemote(devel) {
r.ReloadCheck() // rescan the repo
// devel branch is not remote. do not try 'git push'
return nil, nil
}
// it seems like we have write access. lets find out!
cmd = []string{"git", "push"}
result = r.RunRealtimeVerbose(cmd)
if result.Error != nil {
log.Log(WARN, "GitPushToDevel() failed", r.GetFullPath())
return nil, result.Error
}
r.ReloadCheck() // rescan the repo
return nil, nil
}
func (r *Repo) MergeToMaster() (*cmd.Status, error) {
r.ReloadCheck()
if r.GetCurrentBranchName() != r.GetMasterBranchName() {
return nil, fmt.Errorf("repo not on master branch")
}
/*
if r.GetReadOnly() {
r.ReloadCheck() // rescan the repo
// master branch is read only. you can not git push
lh := r.GetLocalHash("devel")
rh := r.GetRemoteHash("devel")
if lh == rh {
// log.Info(r.FullPath, "local devel == remote devel", lh, rh)
} else {
log.Info(r.FullPath, "local devel != remote devel", lh, rh)
}
log.Info("can't merge to master on read only() repos. trying anyway")
// return nil, fmt.Errorf("can't merge to master on read only() repos")
}
*/
if r.CheckDirty() {
return nil, fmt.Errorf("repo is dirty")
}
master := r.GetMasterBranchName()
devel := r.GetDevelBranchName()
log.Info("MergeToMaster() merging from", devel, "into", master)
cmd := []string{"git", "merge", devel}
result := r.RunRealtimeVerbose(cmd)
if result.Error != nil {
log.Log(WARN, "MergeToMaster() failed", r.GetFullPath())
return nil, result.Error
}
// it seems like we have write access. lets find out!
cmd = []string{"git", "push"}
result = r.RunRealtimeVerbose(cmd)
if result.Error != nil {
log.Log(WARN, "GitPushToMaster() failed", r.GetFullPath())
return nil, result.Error
}
r.ReloadCheck() // rescan the repo
return nil, nil
}

View File

@ -1,11 +1,9 @@
package gitpb
import (
"bufio"
"errors"
"os"
"net/url"
"path/filepath"
"strings"
"go.wit.com/log"
)
@ -15,73 +13,121 @@ import (
// TODO: try adding python, rails, perl, rust, other language things?
// I probably will never have time to try that, but I'd take patches for anyone
// that might see this note and feel so inclined.
func (all *Repos) NewGoPath(basepath string, gopath string) (*Repo, error) {
if r := all.FindByGoPath(gopath); r != nil {
// already had this gopath
return r, nil
// todo: use Repos.Lock() ?
func (all *Repos) NewGoRepo(fullpath string, gopath string) (*Repo, error) {
if gopath == "" {
return nil, errors.New("blank gopath")
}
// if .git doesn't exist, error out here
gitpath := filepath.Join(basepath, gopath, ".git")
_, err := os.Stat(gitpath)
if err != nil {
return nil, err
if r := all.FindByFullPath(fullpath); r != nil {
log.Info("gitpb.NewGoPath() already has gopath", r.GetGoPath())
log.Info("gitpb.NewGoPath() already has FullPath", r.FullPath)
// already had this gopath
return r, errors.New("gitpb.NewGoPath() duplicate gopath " + gopath)
}
// add a new one here
newr := Repo{
FullPath: filepath.Join(basepath, gopath),
GoPath: gopath,
FullPath: fullpath,
Namespace: gopath,
}
// newr.UpdateGit()
newr.UpdateGitTags()
newr.Times = new(GitTimes)
all.AppendUniqueGoPath(&newr)
return &newr, nil
}
// Detect a 'Primative' package. Sets the isPrimative flag
// will return true if the repo is truly not dependent on _anything_ else
// like spew or lib/widget
// it assumes go mod ran init and tidy ran without error
func (repo *Repo) isPrimativeGoMod() (bool, error) {
// go mod init & go mod tidy ran without errors
log.Log(GITPB, "isPrimativeGoMod()", repo.FullPath)
tmp := filepath.Join(repo.FullPath, "go.mod")
gomod, err := os.Open(tmp)
if err != nil {
log.Log(GITPB, "missing go.mod", repo.FullPath)
repo.GoDeps = nil
return false, err
}
defer gomod.Close()
scanner := bufio.NewScanner(gomod)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Split(line, " ")
log.Log(GITPB, " gomod:", parts)
if len(parts) >= 1 {
log.Log(GITPB, " gomod: part[0] =", parts[0])
if parts[0] == "require" {
log.Log(GITPB, " should return false here")
return false, errors.New("go.mod file is not primative")
}
newr.GoInfo = new(GoInfo)
newr.GoInfo.GoPath = gopath
// everything happens in here
newr.ReloadForce()
newr.ValidateUTF8()
if all.AppendByFullPath(&newr) {
// worked
return &newr, nil
} else {
// this is dumb, probably never happens. todo: use Repos.Lock()
if r := all.FindByFullPath(fullpath); r != nil {
// already had this gopath
return r, errors.New("gitpb.NewGoPath() AppendUnique() failed but Find() worked" + gopath)
}
}
return true, nil
// todo: use Repos.Lock()
return nil, errors.New("repo gitpb.NewGoPath() should never have gotten here " + gopath)
}
func (repo *Repo) SetMasterBranchName(bname string) {
repo.MasterBranchName = bname
// enforces GoPath is unique
// TODO: deprecate this (?)
// mutex's should finally work in the autogenpb protobuf code
/*
func (all *Repos) AppendByGoPathOld(newr *Repo) bool {
// all.RLock()
repoMu.RLock()
for _, r := range all.Repos {
if r.GoInfo.GoPath == newr.GoInfo.GoPath {
// all.RUnlock()
repoMu.RUnlock()
return false
}
}
// all.RUnlock()
repoMu.RUnlock()
all.Append(newr)
return true
}
*/
func (all *Repos) NewRepo(fullpath string, namespace string) (*Repo, error) {
if r := all.FindByFullPath(fullpath); r != nil {
log.Info("gitpb.NewRepo() might already have namespace", r.GetNamespace())
log.Info("gitpb.NewRepo() already has FullPath", r.FullPath)
// already had this gopath
return r, errors.New("gitpb.NewRepo() duplicate path " + fullpath)
}
// add a new one here
newr := Repo{
FullPath: fullpath,
Namespace: namespace,
}
newr.Times = new(GitTimes)
// everything happens in here
newr.ReloadForce()
newr.ValidateUTF8()
if all.AppendByFullPath(&newr) {
// worked
return &newr, nil
}
// todo: use Repos.Lock()
return nil, errors.New("gitpb.NewRepo() append failed " + fullpath)
}
func (repo *Repo) SetDevelBranchName(bname string) {
repo.DevelBranchName = bname
}
func NewRepo(fullpath string) (*Repo, error) {
// add a new one here
repo := Repo{
FullPath: fullpath,
}
repo.Times = new(GitTimes)
func (repo *Repo) SetUserBranchName(bname string) {
repo.UserBranchName = bname
// everything happens in here
repo.ReloadForce()
repo.ValidateUTF8()
if repo.Namespace == "" {
giturl := repo.GetURL()
if giturl == "" {
log.Info(repo.FullPath, "Namespace & URL are both blank. Warn the user of a local repo.")
return &repo, nil
}
// log.Info("GET Namespace from URL", giturl)
tmpURL, err := url.Parse(giturl)
if err != nil {
log.Info(repo.FullPath, "URL parse failed", giturl, err)
return &repo, nil
}
// log.Info(repo.FullPath, "namespace might be:", tmpURL.Hostname(), tmpURL.Path)
repo.Namespace = filepath.Join(tmpURL.Hostname(), tmpURL.Path)
// log.Info(repo.FullPath, "Namesapce =", repo.Namespace)
}
return &repo, nil
}

View File

@ -9,24 +9,74 @@ import "gitTag.proto";
import "goDep.proto";
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message Repo { // `autogenpb:marshal`
string fullPath = 1; // the actual path to the .git directory: '/home/devel/golang.org/x/tools'
google.protobuf.Timestamp lastPull = 2; // last time a git pull was done
string masterBranchName = 3; // git 'main' or 'master' branch name
string develBranchName = 4; // whatever the git 'devel' branch name is
string userBranchName = 5; // whatever your username branch is
GitTags tags = 6;
// global settings for autogenpb `autogenpb:mutex`
// things specific to golang projects
string goPath = 7; // `autogenpb:unique` // the logical path as used by golang: 'go.wit.com/apps/helloworld'
bool goLibrary = 8; // if this is a golang library
bool goPrimitive = 9; // if this is a golang primitive
GoDeps goDeps = 10;
google.protobuf.Timestamp lastGoDep = 11; // last time go.sum was processed
// should it be done this way?
message GitTimes { // `autogenpb:nomutex`
google.protobuf.Timestamp lastPull = 1; // last time a git pull was done
google.protobuf.Timestamp lastUpdate = 2; // when was ReloadGit() last done
google.protobuf.Timestamp lastDirty = 3; // last time CheckDirty() was run
google.protobuf.Timestamp mtimeDir = 4; // mtime for ./git // maybe useful to track
google.protobuf.Timestamp mtimeHead = 5; // mtime for ./git/HEAD // these two mtimes allow really fast checks to see if git has changed
google.protobuf.Timestamp mtimeIndex = 6; // mtime for ./git/HEAD // probably always in sync with HEAD
google.protobuf.Timestamp mtimeFetch = 7; // mtime for ./git/FETCH_HEAD // last time 'git fetch' or 'git pull' was run on current branch?
google.protobuf.Timestamp lastGoDep = 8; // mtime for last go.sum scan
google.protobuf.Timestamp newestCommit = 9; // when the newest commit was
google.protobuf.Timestamp mtimeConfig = 10; // mtime for the .git/config file
}
message Repos { // `autogenpb:marshal`
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations
repeated Repo repos = 3;
// this is probably better. think about moving to this instead
message GoInfo { // `autogenpb:nomutex`
string goPath = 1; // the logical path as used by golang: 'go.wit.com/apps/helloworld'
string desc = 2; // what is this repo?
bool goLibrary = 3; // is this a golang library?
bool goBinary = 4; // is this a golang binary?
bool goPrimitive = 5; // if this is a golang primitive (only has go.mod)
bool goPlugin = 6; // is this a golang plugin?
bool goProtobuf = 7; // autogen go files from .proto
GoDeps goDeps = 8; // what is in the go.sum file
GoDeps published = 9; // the last published go.mod/go.sum
bytes goMod = 10; // the last go.mod file
bytes goSum = 11; // the last go.sum file
bool gitIgnoresGoSum = 12; // does .gitignore ignore go.mod & go.sum?
}
message Repo { // `autogenpb:marshal` `autogenpb:nomutex`
string namespace = 1; // `autogenpb:unique` `autogenpb:sort` // this repo is 'go.wit.com/lib/protobuf/gitpb'
string fullPath = 2; // `autogenpb:unique` `autogenpb:sort` // the OS path to the .git directory: '/home/devel/golang.org/x/tools'
string masterBranchName = 3; // git 'main' or 'master' branch name
string develBranchName = 4; // whatever the git 'devel' branch name is
string userBranchName = 5; // whatever your username branch is
bool dirty = 6; // if git says things have been changed
string URL = 7; // the URL
GitTags tags = 8; // known tags
GitTimes times = 9; // store all the mtime values here. these are temporary
GoInfo goInfo = 10; // put all the go specifcs here
GoDeps goDeps = 11; // what is in the go.sum file
string currentBranchName = 12; // the branch currently checked out
string currentBranchVersion = 13; // the branch currently checked out
string lastTag = 14; // the oldest tag
string targetVersion = 15; // useful during the package release process
bool readOnly = 16; // tracks access to 'git push'
string desc = 17; // what is this repo?
string stateChange = 18; // used for debugging tool logic
string masterVersion = 19; // just store this for now
string develVersion = 20; //
string userVersion = 21; //
repeated string dirtyList = 22; // store the list from git status --porcelain
string state = 23; // status or state. useful for building tooling
GitTag currentTag = 24; // used to examine repo branches
GitConfig gitConfig = 25; // protobuf of the current .git/config
string MasterHash = 26; // hash of the current master branch
string DevelHash = 27; // hash of the current devel branch
map<string, string> control = 28; // control values. can be used to make packages (like .deb or .rpm)
}
message Repos { // `autogenpb:marshal` `autogenpb:sort` `autogenpb:gui` `autogenpb:nomutex` `autogenpb:http`
string uuid = 1; // `autogenpb:uuid:8daaeba1-fb1f-4762-ae6e-95a55d352673`
string version = 2; // `autogenpb:version:v4`
repeated Repo repos = 3; // `autogenpb:append` // generate AppendUnique() function for this
bool hasFullScan = 4; // a full repo scan has been saved to disk
google.protobuf.Timestamp fullScan = 5; // mtime of the last full scan saved to disk
}

42
revert.go Normal file
View File

@ -0,0 +1,42 @@
package gitpb
import "go.wit.com/log"
// reverts master to devel
// used in the unwind process of making GUI releases
func (repo *Repo) RevertMasterToDevel() bool {
if repo.CheckDirty() {
log.Info("sorry, it's still dirty")
return false
}
curName := repo.GetCurrentBranchName()
dName := repo.GetDevelBranchName()
mName := repo.GetMasterBranchName()
if curName != mName {
log.Info("repo is not working from main branch", curName, "!=", mName)
return false
}
log.Info("reset master to devel", curName, repo.GetGoPath())
var all [][]string
all = append(all, []string{"git", "checkout", dName}) // switch to the devel branch
all = append(all, []string{"git", "branch", "-D", mName})
all = append(all, []string{"git", "branch", mName}) // make a master branch based on devel
all = append(all, []string{"git", "checkout", mName})
all = append(all, []string{"git", "push", "--set-upstream", "--force", "origin", mName})
// don't do anything with tags here
// all = append(all, []string{"git", "tag", "--delete", release.version.String()})
// all = append(all, []string{"git", "push", "--delete", "origin", release.version.String()})
if repo.RunAll(all) {
log.Info("EVERYTHING OK. RERELEASED", repo.GetGoPath())
repo.ReloadCheck()
return true
}
log.Info("SOMETHING FAILED")
return false
}

101
rill.go Normal file
View File

@ -0,0 +1,101 @@
package gitpb
// runs git, parses output
// types faster than you can
import (
"errors"
sync "sync"
"github.com/destel/rill"
"github.com/go-cmd/cmd"
"go.wit.com/log"
)
var ErrorMissingGitConfig error = errors.New("missing .git/config")
var ErrorGitPullOnLocal error = errors.New("git pull on local only branch")
var ErrorGitPullOnDirty error = errors.New("cannot git pull on dirty repo")
func (repo *Repo) GitPull() (*cmd.Status, error) {
/*
currentName := repo.GetCurrentBranchName()
if repo.IsOnlyLocalTag(currentName) {
var result cmd.Status
result.Exit = 21
result.Error = ErrorGitPullOnLocal
// log.Info("git pull skipped on local only branch", repo.GetGoPath())
return result
}
*/
var cmd []string
cmd = append(cmd, "git", "pull")
return nil, repo.RunVerbose(cmd)
}
// rill is awesome. long live rill
// attempt scan with rill
func (all *Repos) RillGitPull(part1 int, part2 int) map[*Repo]*cmd.Status {
var lock sync.Mutex
var allerr map[*Repo]*cmd.Status
allerr = make(map[*Repo]*cmd.Status)
// Convert a slice of user IDs into a channel
ids := rill.FromSlice(all.Repos, nil)
var counter int
// Read users from the API.
// Concurrency = 20
dirs := rill.Map(ids, part1, func(id *Repo) (*Repo, error) {
return id, nil
})
rill.ForEach(dirs, part2, func(repo *Repo) error {
counter += 1
if repo.CheckDirty() {
// log.Info("git pull skipped on dirty repo", repo.GoPath)
result := new(cmd.Status)
result.Error = ErrorGitPullOnDirty
lock.Lock()
defer lock.Unlock()
allerr[repo] = result
} else {
// todo: sort out what the hell is wrong with my code
// something seems to be trampling things
/*
var dur time.Duration
dur = time.Duration((1+rand.Intn(50))*20) * time.Millisecond
time.Sleep(dur)
*/
var result *cmd.Status
result, _ = repo.GitPull()
log.Info("git pull", repo.GetGoPath())
// log.Info(strings.Join(result.Stdout, "\n"))
lock.Lock()
defer lock.Unlock()
allerr[repo] = result
}
return nil
})
// for r, err := range allerr {
// log.Info("git pull error:", r.GoPath, err)
// }
return allerr
}
func (repo *Repo) GitPullRealtime() cmd.Status {
currentName := repo.GetCurrentBranchName()
if repo.IsOnlyLocalTag(currentName) {
var result cmd.Status
result.Exit = 21
result.Error = ErrorGitPullOnLocal
// log.Info("git pull skipped on local only branch", repo.GoPath)
return result
}
var cmd []string
cmd = append(cmd, "git", "pull")
r := repo.RunRealtime(cmd)
return r
}

View File

@ -1,23 +0,0 @@
VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d)
build:
GO111MODULE=off go build \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
FORGE_HOME=/tmp/forge ./scanGoSrc
install:
GO111MODULE=off go install \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
goimports:
goimports -w *.go
prep:
go get -v -t -u
run:
go run *.go
clean:
-rm -f scanGoSrc

View File

@ -1,50 +0,0 @@
package main
import (
"os"
"go.wit.com/dev/alexflint/arg"
)
var argv args
type args struct {
ConfigDir string `arg:"env:FORGE_HOME" help:"defaults to ~/.config/forge/"`
List bool `arg:"--list" default:"false" help:"list repos in your config"`
Add bool `arg:"--add" default:"false" help:"add a new repo"`
Delete bool `arg:"--delete" default:"false" help:"delete a repo"`
Update bool `arg:"--update" default:"false" help:"update a repo"`
GoPath string `arg:"--gopath" help:"gopath of the 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"`
}
func (a args) Description() string {
return `
forgeConfig -- add entries to your config files
This is just example protobuf code to test forgepb is working
but it could be used to automagically create a config file too.
If you need to change your config file, just edit the forge.text or forge.json
files then remove the forge.pb and ConfigLoad() will attempt to load those files instead
`
}
func (args) Version() string {
return "virtigo " + VERSION
}
func init() {
var pp *arg.Parser
pp = arg.MustParse(&argv)
if pp == nil {
pp.WriteHelp(os.Stdout)
os.Exit(0)
}
}

View File

@ -1,54 +0,0 @@
package main
import (
"os"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// sent via ldflags
var VERSION string
func main() {
var repos *gitpb.Repos
repos = new(gitpb.Repos)
newr, err := repos.NewGoPath("/home/jcarr/go/src", "go.wit.com/apps/wit-package")
if err != nil {
log.Info("init failed", err)
panic("crapnuts")
} else {
log.Info("init worked for", newr.GoPath)
}
newr, err = repos.NewGoPath("/home/jcarr/go/src", "go.wit.com/apps/notathing")
if err != nil {
log.Info("init failed correctly:", err)
} else {
log.Info("init should have failed for", newr.GoPath)
panic("crapnuts")
}
/*
log.Info(forgepb.RepoHeader())
loop := repos.SortByPath() // get the list of repos
for loop.Scan() {
r := loop.Repo()
log.Info("repo:", r.GoPath)
}
*/
/*
log.Info("going to add a new repo", argv.GoPath)
new1 := forgepb.Repo{
GoPath: argv.GoPath,
Writable: argv.Writable,
ReadOnly: argv.ReadOnly,
Private: argv.Private,
Directory: argv.Directory,
Favorite: argv.Favorite,
Interesting: argv.Interesting,
}
*/
os.Exit(0)
}

163
shell.go
View File

@ -1,6 +1,8 @@
package gitpb
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
@ -16,42 +18,60 @@ func (repo *Repo) Run(cmd []string) cmd.Status {
result := shell.PathRun(repo.FullPath, cmd)
output := strings.Join(result.Stdout, "\n")
if result.Error != nil {
log.Warn("cmd:", cmd)
log.Warn("ouptput:", output)
log.Warn("failed with error:", result.Error)
log.Log(WARN, "cmd:", cmd)
log.Log(WARN, "ouptput:", output)
log.Log(WARN, "failed with error:", result.Error)
}
return result
}
func (repo *Repo) RunQuiet(cmd []string) cmd.Status {
func (repo *Repo) RunEcho(cmd []string) cmd.Status {
result := shell.PathRunQuiet(repo.FullPath, cmd)
output := strings.Join(result.Stdout, "\n")
if result.Error != nil {
log.Warn("cmd:", cmd)
log.Warn("ouptput:", output)
log.Warn("failed with error:", result.Error)
log.Log(NOW, "cmd:", repo.FullPath, cmd)
log.Warn(WARN, "cmd.Exit:", result.Exit, "cmd.Error:", result.Error)
for _, line := range result.Stdout {
log.Log(NOW, "STDOUT:", line)
}
for _, line := range result.Stderr {
log.Log(NOW, "STDERR:", line)
}
return result
}
// for now, even check cmd.Exit
func (repo *Repo) strictRun(cmd []string) (bool, error) {
result := repo.RunQuiet(cmd)
func (repo *Repo) RunRealtime(cmd []string) cmd.Status {
return shell.PathRunRealtime(repo.GetFullPath(), cmd)
}
func (repo *Repo) RunRealtimeVerbose(cmd []string) cmd.Status {
log.Log(NOW, "EXEC: cd", repo.GetFullPath(), ";", cmd)
return shell.PathRunRealtime(repo.GetFullPath(), cmd)
}
func (repo *Repo) RunQuiet(cmd []string) (*cmd.Status, error) {
result := shell.PathRunQuiet(repo.FullPath, cmd)
if result.Error != nil {
log.Warn("go mod init failed err:", result.Error)
return false, result.Error
log.Warn(repo.GetGoPath(), cmd, "wow. golang is cool. an os.Error:", result.Error)
return &result, result.Error
}
if result.Exit != 0 {
log.Warn("go mod init exit =", result.Exit)
return false, result.Error
// log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath())
return &result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit))
}
return true, nil
return &result, nil
}
func (repo *Repo) RunStrict(cmd []string) (*cmd.Status, error) {
result, err := repo.RunQuiet(cmd)
if err != nil {
log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath())
return result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit))
}
return result, nil
}
func (repo *Repo) Exists(filename string) bool {
if repo == nil {
log.Warn("repo == nil for Exists()")
panic(-1)
return false
}
testf := filepath.Join(repo.FullPath, filename)
_, err := os.Stat(testf)
@ -60,3 +80,108 @@ func (repo *Repo) Exists(filename string) bool {
}
return true
}
func (repo *Repo) IsValidDir() bool {
if repo == nil {
return false
}
if !repo.IsGitDirectory() {
return false
}
return true
}
func (repo *Repo) ReadFile(fname string) ([]byte, error) {
fullname := filepath.Join(repo.FullPath, fname)
return os.ReadFile(fullname)
}
func (repo *Repo) IsGitDirectory() bool {
gitdir := filepath.Join(repo.FullPath, ".git")
info, err := os.Stat(gitdir)
if err != nil {
return false
}
return info.IsDir()
}
func (repo *Repo) IsDirectory() bool {
info, err := os.Stat(repo.FullPath)
if err != nil {
return false
}
return info.IsDir()
}
func (repo *Repo) RunAll(all [][]string) bool {
for _, cmd := range all {
log.Log(WARN, "doAll() RUNNING: cmd =", cmd)
r := repo.Run(cmd)
if r.Error != nil {
log.Log(WARN, "doAll() err =", r.Error)
log.Log(WARN, "doAll() out =", r.Stdout)
return false
}
}
return true
}
func (repo *Repo) RunStrictAll(all [][]string) (*cmd.Status, error) {
for _, cmd := range all {
log.Log(WARN, "doAll() RUNNING: cmd =", cmd)
if result, err := repo.RunStrict(cmd); err != nil {
return result, err
}
}
return nil, nil
}
func (repo *Repo) RunVerbose(cmd []string) error {
// log.Info("EXEC Running:", repo.GetGoPath(), cmd)
err := shell.PathExecVerbose(repo.GetFullPath(), cmd)
if err != nil {
log.Info("Error", cmd, err)
return err
}
return nil
}
func (repo *Repo) RunVerboseOnError(cmd []string) (*cmd.Status, error) {
r, err := repo.RunStrict(cmd)
if err == nil {
return r, err
}
log.Info("Run Error:", repo.GetGoPath(), cmd, err)
for _, line := range r.Stdout {
log.Info(line)
}
for _, line := range r.Stderr {
log.Info(line)
}
return r, err
}
// only safe to run len() on STDOUT
// DO NOT TRY TO PARSE THIS EXCEPT HASH AS FIRST VALUE
// Intended to be human readable
func (repo *Repo) ConstructGitDiffLog(branch1, branch2 string) []string {
var cmd []string
cmd = append(cmd, "git")
cmd = append(cmd, "log")
cmd = append(cmd, "--format=\"%H %ae %as %s\"")
cmd = append(cmd, branch1)
cmd = append(cmd, "--not")
cmd = append(cmd, branch2)
return cmd
}
// count all objects only in branch1
func (repo *Repo) CountDiffObjects(branch1, branch2 string) int {
cmd := repo.ConstructGitDiffLog(branch1, branch2)
r, err := repo.RunVerboseOnError(cmd)
if err != nil {
return -1
}
// log.Info("countDiffObjects()", cmd, len(r.Stdout), strings.Join(r.Stdout, " "))
return len(r.Stdout)
}