Compare commits

...

144 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
38 changed files with 2133 additions and 543 deletions

4
.gitignore vendored
View File

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

View File

@ -5,7 +5,11 @@
# go install
all: goimports gitTag.pb.go goDep.pb.go repo.pb.go vet
all: gitTag.pb.go goDep.pb.go repo.pb.go goimports vet
generate: clean
go-mod-clean
go generate
vet:
@GO111MODULE=off go vet
@ -15,9 +19,10 @@ vet:
goimports:
goimports -w *.go
# dump autogenerated files and potential patches
clean:
rm -f *.pb.go
-rm -f go.*
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 \
@ -25,10 +30,39 @@ clean:
# refs.proto
gitTag.pb.go: gitTag.proto
autogenpb --proto gitTag.proto
autogenpb --proto gitTag.proto --mutex-name "john"
goDep.pb.go: goDep.proto
autogenpb --proto goDep.proto
repo.pb.go: repo.proto
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 \
--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

2
age.go
View File

@ -65,6 +65,6 @@ func (repo *Repo) oldMtime(filename string) (time.Time, error) {
if err == nil {
return statf.ModTime(), nil
}
log.Log(GITPBWARN, "Mtime() os.Stat() error", pathf, err)
log.Log(WARN, "Mtime() os.Stat() error", pathf, err)
return time.Now(), err
}

View File

@ -1,5 +1,10 @@
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"
@ -14,11 +19,11 @@ import (
func (repo *Repo) AutogenSave(files []string, refname string, del bool) error {
if del {
cmd := []string{"git", "notes", "show", refname}
if err := repo.StrictRun(cmd); err != nil {
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.StrictRun(cmd); err != nil {
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
}
@ -26,18 +31,18 @@ func (repo *Repo) AutogenSave(files []string, refname string, del bool) error {
for _, fname := range files {
autotag := "// `autogen:" + fname + "`"
cmd := []string{"git", "notes", "append", "-m", autotag, refname}
if err := repo.StrictRun(cmd); err != nil {
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
cmd = []string{"git", "notes", "append", "-F", fname, refname}
if err := repo.StrictRun(cmd); err != nil {
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.StrictRun(cmd); err != nil {
if _, err := repo.RunQuiet(cmd); err != nil {
return err
}
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)
}

View File

@ -1,6 +1,12 @@
package gitpb
import "go.wit.com/log"
import (
"fmt"
"os/user"
"path/filepath"
"go.wit.com/log"
)
func (repo *Repo) CheckoutMaster() bool {
bName := repo.GetMasterBranchName()
@ -13,20 +19,29 @@ func (repo *Repo) CheckoutMaster() bool {
func (repo *Repo) CheckoutDevel() bool {
bName := repo.GetDevelBranchName()
if repo.checkoutBranch(bName) {
repo.UserBranchName = bName
repo.DevelBranchName = bName
return true
// switch ok
}
return false
}
func (repo *Repo) CheckoutUser() bool {
func (repo *Repo) CheckoutUser() error {
bName := repo.GetUserBranchName()
if repo.checkoutBranch(bName) {
repo.UserBranchName = bName
return true
if bName == "uerr" {
usr, _ := user.Current()
repo.SetUserBranchName(usr.Username)
bName = usr.Username
log.Info("gitpb CheckoutUser() somehow got user 'uerr'")
}
return false
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 {
@ -42,22 +57,77 @@ func (repo *Repo) checkoutBranch(bName string) bool {
return false
}
if repo.CheckDirty() {
log.Log(GITPB, repo.GetFullPath(), "is dirty")
log.Log(INFO, repo.GetFullPath(), "is dirty")
return false
}
cmd := []string{"git", "checkout", bName}
r := repo.Run(cmd)
if r.Error != nil {
log.Log(GITPB, "git checkout error:", r.Error)
log.Log(INFO, "git checkout error:", r.Error)
}
realname := repo.GetCurrentBranchName()
realversion := repo.GetCurrentBranchVersion()
log.Log(GITPB, repo.GetFullPath(), "realname =", realname, "realversion =", realversion)
log.Log(INFO, repo.GetFullPath(), "realname =", realname, "realversion =", realversion)
if realname != bName {
log.Log(GITPB, "git checkout failed", repo.GetFullPath(), bName, "!=", realname)
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
}

View File

@ -14,7 +14,10 @@ func (repo *Repo) SetMasterBranchName(s string) {
func (repo *Repo) GetGoPath() string {
if repo.GoInfo == nil {
return ""
return repo.Namespace
}
if repo.GoInfo.GoPath == "" {
return repo.Namespace
}
return repo.GoInfo.GoPath
}
@ -32,3 +35,24 @@ func (repo *Repo) SetGoPrimitive(b bool) {
}
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
}

151
config.go
View File

@ -8,44 +8,93 @@ import (
"os"
"path/filepath"
"go.wit.com/lib/protobuf/bugpb"
"go.wit.com/log"
)
// write to ~/.config/forge/ unless ENV{FORGE_GOSRC} is set
func (all *Repos) ConfigSave() error {
if os.Getenv("FORGE_GOSRC") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_GOSRC", fullpath)
}
// write to ~/.config/forge/ unless ENV{FORGE_REPOSDIR} is set
func (all *Repos) ConfigSave(fname string) error {
if all == nil {
log.Warn("gitpb all == nil")
return errors.New("gitpb.ConfigSave() 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
}
log.Info("gitpb.ConfigSave() repos.Marshal() worked len", len(data))
configWrite("repos.pb", data)
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) ConfigLoad() error {
if os.Getenv("FORGE_GOSRC") == "" {
func (all *Repos) ConfigLoadOld() error {
if os.Getenv("FORGE_REPOSDIR") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_GOSRC", fullpath)
os.Setenv("FORGE_REPOSDIR", fullpath)
}
var data []byte
var err error
cfgname := filepath.Join(os.Getenv("FORGE_GOSRC"), "repos.pb")
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
@ -53,20 +102,63 @@ func (all *Repos) ConfigLoad() error {
}
// this means the forge.pb file exists and was read
if len(data) == 0 {
all.sampleConfig() // causes nil panic
return errors.New("gitpb.ConfigLoad() repos.pb is empty")
}
return all.Unmarshal(data)
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"
if all.Append(newr) {
log.Info("added", newr.GetGoPath(), "ok")
} else {
log.Info("added", newr.GetGoPath(), "failed")
}
all.Append(newr)
}
func loadFile(fullname string) ([]byte, error) {
@ -84,25 +176,14 @@ func loadFile(fullname string) ([]byte, error) {
return data, nil
}
func configWrite(filename string, data []byte) error {
fullname := filepath.Join(os.Getenv("FORGE_GOSRC"), filename)
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
}
if filename == "forge.text" {
// add header
cfgfile.Write([]byte("# this file is automatically re-generated from forge.pb, however,\n"))
cfgfile.Write([]byte("# if you want to edit it by hand, you can:\n"))
cfgfile.Write([]byte("# stop forge; remove forge.pb; edit forge.text; start forge\n"))
cfgfile.Write([]byte("# this will cause the default behavior to fallback to parsing this file for the config\n"))
cfgfile.Write([]byte("\n"))
cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n"))
cfgfile.Write([]byte("# git repos you have write access to. That is, where you can run 'git push'\n"))
}
cfgfile.Write(data)
return nil
}

View File

@ -37,8 +37,7 @@ func (repo *Repo) setMasterVersion() {
if err == nil {
repo.MasterVersion = v
} else {
log.Log(GITPBWARN, "gitpb.GitMasterVersion() error:", err)
repo.MasterVersion = "giterr"
log.Log(WARN, "gitpb.GitMasterVersion() error:", err)
}
}
@ -48,35 +47,27 @@ func (repo *Repo) setDevelVersion() {
if err == nil {
repo.DevelVersion = v
} else {
log.Log(GITPBWARN, "gitpb.GitDevelVersion() error:", err)
repo.DevelVersion = "deverr"
// 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(GITPBWARN, "gitpb.GitUserVersion() error:", err)
// log.Log(WARN, "gitpb.GitUserVersion() error:", err)
repo.UserVersion = "uerr"
}
}
/*
now tracked in repo.Reload()
func (repo *Repo) GetCurrentBranchName() string {
r := repo.RunQuiet([]string{"git", "branch", "--show-current"})
output := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Log(GITPBWARN, "GetCurrentBranchName() not in a git repo?", r.Error, repo.GetGoPath())
log.Log(GITPBWARN, "GetCurrentBranchName() output might have worked anyway:", output)
}
return strings.TrimSpace(output)
}
*/
// this is used often. probably move everything to this
// returns things like
// v0.2.2
@ -98,13 +89,13 @@ func (repo *Repo) gitDescribeByHash(hash string) (string, error) {
if hash == "" {
return "", errors.New("hash was blank")
}
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always", hash})
r, err := repo.RunQuiet([]string{"git", "describe", "--tags", hash})
out := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Warn("not in a git repo or bad hash?", r.Error, repo.GetGoPath())
return out, r.Error
if err != nil {
// log.Warn("not in a git repo or bad hash?", err, repo.GetGoPath())
return "gitpb err", err
}
return out, r.Error
return out, err
}
// this should get the most recent tag
@ -142,84 +133,33 @@ func (repo *Repo) gitVersionByName(name string) (string, error) {
if name == "" {
// git will return the current tag
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always"})
cmd := []string{"git", "describe", "--tags"}
r, err := repo.RunQuiet(cmd)
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.GetGoPath())
return "", r.Error
if err != nil {
log.Log(WARN, repo.FullPath, "gitDescribeByName() ", output, err, cmd)
return "", err
}
return strings.TrimSpace(output), nil
}
if !repo.IsBranch(name) {
// tag does not exist
log.Log(GITPBWARN, "LocalTagExists()", name, "did not exist")
// branch does 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)
cmd := []string{"git", "describe", "--tags", name}
result, err := 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.GetGoPath())
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
}
// 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.GetGoPath())
if filename == findname {
log.Log(GITPB, "gitpb.IsBranch() found tag:", path, filename, "from", repo.GetGoPath())
return true
}
}
log.Log(GITPB, "did not find tag:", findname, "in", repo.GetGoPath())
return false
}
// todo: redo this and above. both are messed up. ignore for now until things are stable
func (repo *Repo) IsLocalBranch(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/heads") {
continue
}
path, filename := filepath.Split(tagname)
log.Log(GITPB, "gitpb.IsBranch() tag:", path, filename, "from", repo.GetGoPath())
if filename == findname {
log.Log(GITPB, "gitpb.IsBranch() found tag:", path, filename, "from", repo.GetGoPath())
return true
}
}
log.Log(GITPB, "did not find tag:", findname, "in", repo.GetGoPath())
return false
}
func trimNonNumericFromStart(s string) string {
for i, r := range s {
if unicode.IsDigit(r) {
@ -237,64 +177,111 @@ func normalizeVersion(s string) string {
}
reg, err := regexp.Compile("[^0-9.]+")
if err != nil {
log.Log(GITPBWARN, "normalizeVersion() regexp.Compile() ERROR =", err)
log.Log(WARN, "normalizeVersion() regexp.Compile() ERROR =", err)
return parts[0]
}
clean := reg.ReplaceAllString(parts[0], "")
log.Log(GITPB, "normalizeVersion() s =", clean)
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
// 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], "", ""
return "", "", parts[0] // converts someone using version "57" to "v0.0.57"
case 2:
return parts[0], parts[1], ""
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 = splitVersion(lasttag)
// var major, minor, revision string
major, minor, revision := splitInts(lasttag)
olda, _ := strconv.Atoi(major)
oldb, _ := strconv.Atoi(minor)
oldc, _ := strconv.Atoi(revision)
minor += 1
revision = 0
oldb += 1
oldc = 0
newa := strconv.Itoa(olda)
newb := strconv.Itoa(oldb)
newc := strconv.Itoa(oldc)
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() {
lasttag := repo.GetLastTag()
var major, minor, revision string
major, minor, revision = splitVersion(lasttag)
// first try just going from the last tag
repo.incrementRevision(repo.GetLastTag())
olda, _ := strconv.Atoi(major)
oldb, _ := strconv.Atoi(minor)
oldc, _ := strconv.Atoi(revision)
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
*/
}
oldc += 1
func (repo *Repo) incrementRevision(lasttag string) {
major, minor, revision := splitInts(lasttag)
newa := strconv.Itoa(olda)
newb := strconv.Itoa(oldb)
newc := strconv.Itoa(oldc)
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
}

View File

@ -1,19 +1,55 @@
package gitpb
// runs git, parses output
// types faster than you can
import (
"path/filepath"
"sort"
"time"
"go.wit.com/log"
)
func (all *GitTags) SortByAge() *GitTagIterator {
packs := all.selectAllGitTag()
/*
// 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 := NewGitTagIterator(packs)
iterator := newGitTagScanner(packs)
return iterator
}
@ -30,14 +66,91 @@ func (a GitTagAge) Less(i, j int) bool {
}
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 {
return time.Since(repo.Times.NewestCommit.AsTime())
/*
all := repo.Tags.SortByAge()
for all.Scan() {
r := all.Next()
return time.Since(r.GetAuthordate().AsTime())
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()
}
return time.Since(time.Now())
*/
// 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,18 +4,37 @@ package gitpb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
// global settings for autogenpb `autogenpb:mutex`
message GitTag { // `autogenpb:marshal`
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 { // `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 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

@ -13,7 +13,7 @@ func (repo *Repo) DeleteGoDepByHash(hash string) {
// 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

View File

@ -4,15 +4,18 @@ package gitpb
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
// does not change anything
// 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
@ -21,7 +24,8 @@ func (repo *Repo) ParseGoSum() bool {
// that means, there is not a go.sum file
// because the package is completely self contained!
if err := repo.setPrimitive(); err != nil {
log.Info("gitpb.ParseGoSum()", err)
// temporarily enabled this. this is really noisy
// log.Info("gitpb.ParseGoSum()", err)
return false
}
if repo.GetGoPrimitive() {
@ -32,12 +36,11 @@ func (repo *Repo) ParseGoSum() bool {
gosum, err := os.Open(tmp)
defer gosum.Close()
if err != nil {
log.Info("gitpb.ParseGoSum()", err)
log.Info("gitpb.ParseGoSum() missing go.sum. Some error happened with go mod init & tidy", err)
return false
}
scanner := bufio.NewScanner(gosum)
log.Info("gosum:", tmp)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
@ -55,7 +58,7 @@ func (repo *Repo) ParseGoSum() bool {
if repo.GoDeps == nil {
repo.GoDeps = new(GoDeps)
}
repo.GoDeps.AppendUniqueGoPath(&new1)
repo.GoDeps.AppendByGoPath(&new1)
} else {
log.Info("gitpb.ParseGoSum() go.sum parse error invalid:", line)
return false
@ -70,22 +73,76 @@ func (repo *Repo) ParseGoSum() bool {
return true
}
/*
// reads and parses the go.sum file
// is identical to the one above, change that
func (repo *Repo) UpdatePublished() (bool, error) {
// empty out what was there before
repo.Published = nil
tmp := filepath.Join(repo.FullPath, "go.sum")
gosum, err := os.Open(tmp)
// attempt to parse go.* files in a directory
func GoSumParseDir(moddir string) (*GoDeps, error) {
isprim, err := computePrimitive(moddir)
if err != nil {
log.Warn("missing go.sum", repo.FullPath)
// "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 gosum.Close()
defer gomod.Close()
scanner := bufio.NewScanner(gosum)
log.Info("gosum:", tmp)
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())
@ -100,19 +157,42 @@ func (repo *Repo) UpdatePublished() (bool, error) {
GoPath: godep,
Version: version,
}
if repo.Published == nil {
repo.Published = new(GoDeps)
}
repo.Published.AppendUniqueGoPath(&new1)
godeps.AppendByGoPath(&new1)
} else {
return false, errors.New("go.sum parse error invalid: " + line)
return nil, fmt.Errorf("gitpb.ParseGoSum() go.sum parse error invalid: %s", line)
}
}
if err := scanner.Err(); err != nil {
repo.Published = nil
return false, err
godeps = nil
return nil, err
}
return true, nil
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

@ -8,16 +8,16 @@ import "google/protobuf/timestamp.proto"; // Import the well-known type for Time
// global settings for autogenpb `autogenpb:mutex`
message GoDep { // `autogenpb:marshal`
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
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 { // `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 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`
}

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
}

View File

@ -28,11 +28,11 @@ func (repo *Repo) setPrimitive() error {
// 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(GITPB, "isPrimitiveGoMod()", repo.FullPath)
log.Log(INFO, "isPrimitiveGoMod()", 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)
log.Log(INFO, "missing go.mod", repo.FullPath)
return false, err
}
defer gomod.Close()
@ -47,17 +47,17 @@ func (repo *Repo) computePrimitive() (bool, error) {
line := strings.TrimSpace(scanner.Text())
parts := strings.Fields(line)
log.Log(GITPB, " gomod:", parts)
log.Log(INFO, " gomod:", parts)
if len(parts) >= 1 {
log.Log(GITPB, " gomod: part[0] =", parts[0])
log.Log(INFO, " gomod: part[0] =", parts[0])
if parts[0] == "require" {
log.Log(GITPB, " should return false here")
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(GITPBWARN, "go not set to 1.21 for", repo.GetGoPath())
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())
}
}

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")
}

View File

@ -1,112 +0,0 @@
package gitpb
import (
"os"
"path/filepath"
"strings"
"go.wit.com/log"
)
type Patch struct {
GoPath string
Ref string
giturl string
comment string
}
// move all this to repolist and gowit repos
func (repo *Repo) GetPatches(oldname string, newname string) (int, []*Patch) {
var patchcount int
patches := make([]*Patch, 0, 0)
if oldname == newname {
return 0, nil
}
// log.Info("repo userv, develv", userv, develv)
gitcmd := []string{"git", "log", "--oneline", oldname + ".." + newname}
log.Info("Run:", gitcmd)
r := repo.Run(gitcmd)
if r.Error != nil {
log.Info("git failed ", repo.GetGoPath(), "err =", r.Error)
return 0, nil
}
// patches = strings.Split(output, "\n")
log.Info("Run:", r.Stdout)
for _, line := range r.Stdout {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.Split(line, " ")
newp := new(Patch)
newp.Ref = parts[0]
newp.comment = strings.Join(parts[1:], " ")
log.Info("Patch line:", line, repo.GetGoPath())
patchcount += 1
patches = append(patches, newp)
}
return patchcount, patches
}
func (repo *Repo) GetUserPatches() (int, []*Patch) {
usern := repo.GetUserBranchName()
develn := repo.GetDevelBranchName()
userv := repo.GetUserVersion()
develv := repo.GetDevelVersion()
if userv == develv {
return 0, nil
}
c, all := repo.GetPatches(develn, usern)
log.Info("GetPatches() guireleaser", develn, usern, "count =", c)
return c, all
}
func (repo *Repo) GetMasterPatches() (int, []*Patch) {
lasttag := repo.GetLastTag()
mastern := repo.GetMasterBranchName()
masterv := repo.GetMasterVersion()
if lasttag == masterv {
return 0, nil
}
c, all := repo.GetPatches(lasttag, mastern)
log.Info("GetPatches() guireleaser", lasttag, mastern, "count =", c)
return c, all
}
func (all *Repos) MakePatchset(setdir string) bool {
loop := all.SortByFullPath()
for loop.Scan() {
repo := loop.Next()
log.Info("repo", repo.GetGoPath())
userv := repo.GetUserVersion()
develv := repo.GetDevelVersion()
usern := repo.GetUserBranchName()
develn := repo.GetDevelBranchName()
if userv == develv {
// this repo is unchanged
continue
}
repodir := filepath.Join(setdir, repo.GetGoPath())
os.MkdirAll(repodir, os.ModeDir)
// git format-patch branch1..branch2
gitcmd := []string{"git", "format-patch", "-o", repodir, develn + ".." + usern}
log.Info("Run:", gitcmd)
r := repo.Run(gitcmd)
log.Info("output =", r.Stdout)
if r.Error == nil {
log.Info("patches made okay for:", repo.GetGoPath())
continue
}
log.Info("patches failed for:", repo.GetGoPath())
return false
}
return true
}

123
reload.go
View File

@ -2,12 +2,53 @@ package gitpb
import (
"strings"
"time"
"go.wit.com/lib/config"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// TODO: fix and clean this up. this is a work in progress
func (repo *Repo) Reload() error {
// 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()
@ -20,11 +61,42 @@ func (repo *Repo) Reload() error {
repo.reloadVersions()
repo.setRepoType()
// everything has been checked, now save the mtime's
repo.updateMtimes()
// 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
}
@ -33,39 +105,18 @@ func (repo *Repo) SetUserBranchName(bname string) {
repo.UserBranchName = bname
}
// updates LastTag // todo, get this from the protobuf
// updates LastTag by age
func (repo *Repo) setLastTag() {
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)
repo.LastTag = ""
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)
repo.LastTag = ""
return
}
repo.LastTag = result.Stdout[0]
repo.LastTag = repo.FindLastTag()
}
func (repo *Repo) setCurrentBranchName() {
repo.CurrentBranchName = ""
r := repo.RunQuiet([]string{"git", "branch", "--show-current"})
r, err := repo.RunQuiet([]string{"git", "branch", "--show-current"})
output := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Log(GITPBWARN, "GetCurrentBranchName() not in a git repo?", r.Error, repo.GetGoPath())
log.Log(GITPBWARN, "GetCurrentBranchName() output might have worked anyway:", output)
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)
}
@ -77,11 +128,13 @@ func (repo *Repo) setCurrentBranchVersion() {
log.Info("repo.GetCurrentBranchVersion() repo == nil")
return
}
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always"})
r, err := repo.RunQuiet([]string{"git", "describe", "--tags"})
output := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Log(GITPBWARN, "GetCurrentBranchVersion() not in a git repo?", r.Error, repo.GetGoPath())
log.Log(GITPBWARN, "GetCurrentBranchVersion() output might have worked anyway:", output)
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
}

View File

@ -25,7 +25,7 @@ func (repo *Repo) IsDirty() bool {
// actually os.Exec('git')
func (repo *Repo) CheckDirty() bool {
cmd := []string{"git", "status", "--porcelain"}
r := shell.PathRunLog(repo.FullPath, cmd, GITPB)
r := shell.PathRunLog(repo.FullPath, cmd, INFO)
if r.Error != nil {
log.Warn("CheckDirty() status cmd =", cmd)
out := strings.Join(r.Stdout, "\n")
@ -34,6 +34,7 @@ func (repo *Repo) CheckDirty() bool {
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
}
@ -52,10 +53,13 @@ func (repo *Repo) CheckDirty() bool {
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
}

View File

@ -59,7 +59,7 @@ func (repo *Repo) RepoIgnoresGoMod() error {
}
} else {
if ignored {
fmt.Printf("%s %s is ignored by Git.\n", repo.GetGoPath(), file)
// fmt.Printf("%s %s is ignored by Git.\n", repo.GetGoPath(), file)
repo.GoInfo.GitIgnoresGoSum = true
return nil
}

View File

@ -119,6 +119,31 @@ func (repo *Repo) changedHead() bool {
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)
@ -141,7 +166,7 @@ func (repo *Repo) changedIndex() bool {
return true
}
func (repo *Repo) updateMtimes() bool {
func (repo *Repo) reloadMtimes() bool {
var changed bool
if repo.Times == nil {
repo.Times = new(GitTimes)
@ -154,6 +179,9 @@ func (repo *Repo) updateMtimes() bool {
if repo.changedIndex() {
changed = true
}
if repo.changedConfig() {
changed = true
}
if repo.changedDir() {
// changed = true
}
@ -171,10 +199,22 @@ func (repo *Repo) DidRepoChange() bool {
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
}

View File

@ -9,6 +9,8 @@ import (
"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"
@ -24,9 +26,6 @@ func (repo *Repo) GetRepoType() string {
if repo.GoInfo.GoPlugin {
return "plugin"
}
if repo.GoInfo.GoProtobuf {
return "protobuf"
}
if repo.GoInfo.GoBinary {
if repo.Exists(".plugin") {
log.Warn("gitpb.RepoType() plugin was not set correctly")
@ -35,6 +34,10 @@ func (repo *Repo) GetRepoType() string {
}
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"
}
@ -48,11 +51,9 @@ func (repo *Repo) setRepoType() {
}
if repo.Exists(".plugin") {
repo.GoInfo.GoPlugin = true
return
}
if ok, _, _ := repo.IsProtobuf(); ok {
repo.GoInfo.GoProtobuf = true
return
}
switch repo.goListRepoType() {
case "binary":
@ -70,9 +71,9 @@ func (repo *Repo) goListRepoType() string {
// 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)
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"))

View File

@ -11,6 +11,31 @@ import (
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?
@ -67,7 +92,14 @@ func (repo *Repo) reloadGitTags() error {
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 {
@ -81,6 +113,34 @@ func (repo *Repo) reloadGitTags() error {
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")
@ -107,21 +167,33 @@ func (repo *Repo) NewestTag() *GitTag {
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 {
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.Split(ref.Refname)
// log.Info("tag:", path, tagname, "from", repo.GoPath)
if tagname == findname {
// log.Info("found tag:", path, tagname, "from", repo.GoPath)
return true
}
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
}
@ -141,7 +213,7 @@ func (repo *Repo) IsOnlyLocalTag(taggy string) bool {
if strings.HasPrefix(tagname, "refs/remotes") {
path, filename := filepath.Split(tagname)
if filename == taggy {
log.Log(GITPB, "found tag:", path, filename, "from", repo.GetGoPath())
log.Log(INFO, "found tag:", path, filename, "from", repo.GetGoPath())
return false
}
}

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
//
@ -64,7 +81,7 @@ func scanForProtobuf(srcDir string) ([]string, []string, error) {
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
}
@ -97,7 +114,7 @@ func (repo *Repo) GetProtoFiles() ([]string, error) {
srcDir := repo.GetFullPath()
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
}

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

@ -2,6 +2,8 @@ package gitpb
import (
"errors"
"net/url"
"path/filepath"
"go.wit.com/log"
)
@ -25,16 +27,18 @@ func (all *Repos) NewGoRepo(fullpath string, gopath string) (*Repo, error) {
// add a new one here
newr := Repo{
FullPath: fullpath,
FullPath: fullpath,
Namespace: gopath,
}
newr.Times = new(GitTimes)
newr.GoInfo = new(GoInfo)
newr.GoInfo.GoPath = gopath
// everything happens in here
newr.Reload()
newr.ReloadForce()
if all.AppendUniqueFullPath(&newr) {
newr.ValidateUTF8()
if all.AppendByFullPath(&newr) {
// worked
return &newr, nil
} else {
@ -49,16 +53,81 @@ func (all *Repos) NewGoRepo(fullpath string, gopath string) (*Repo, error) {
}
// enforces GoPath is unique
func (all *Repos) AppendUniqueGoPath(newr *Repo) bool {
all.Lock.RLock()
defer all.Lock.RUnlock()
// 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.Repos = append(all.Repos, newr)
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 NewRepo(fullpath string) (*Repo, error) {
// add a new one here
repo := Repo{
FullPath: fullpath,
}
repo.Times = new(GitTimes)
// 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

@ -11,60 +11,72 @@ import "google/protobuf/timestamp.proto"; // Import the well-known type for Time
// global settings for autogenpb `autogenpb:mutex`
message Repo { // `autogenpb:marshal`
string fullPath = 1; // `autogenpb:unique` // the actual 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; // reason for state change
string masterVersion = 19; // just store this for now
string develVersion = 20; //
string userVersion = 21; //
}
message Repos { // `autogenpb:marshal`
string uuid = 1; // `autogenpb:uuid:8daaeba1-fb1f-4762-ae6e-95a55d352673`
string version = 2; // maybe can be used for protobuf schema change violations
repeated Repo repos = 3;
}
// should it be done this way?
message GitTimes {
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
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
}
// this is probably better. think about moving to this instead
message GoInfo {
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 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
}

View File

@ -33,7 +33,7 @@ func (repo *Repo) RevertMasterToDevel() bool {
if repo.RunAll(all) {
log.Info("EVERYTHING OK. RERELEASED", repo.GetGoPath())
repo.Reload()
repo.ReloadCheck()
return true
}

58
rill.go
View File

@ -5,7 +5,6 @@ package gitpb
import (
"errors"
"strings"
sync "sync"
"github.com/destel/rill"
@ -17,46 +16,28 @@ 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 {
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
}
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")
r := repo.Run(cmd)
// output := strings.Join(r.Stdout, "\n")
/* notsure why I had this. I think from a long time ago
if r.Error != nil {
output = "git error_,,,_a_,,,_b_,,,c"
}
*/
/*
if r.Error == nil {
if r.Exit == 0 {
log.Log(GITPBWARN, "git pull ran ", repo.GetGoPath())
// log.Log(GITPBWARN, "git pull output", output)
return r
} else {
log.Log(GITPBWARN, "git pull error", repo.GetGoPath(), strings.Join(r.Stderr, "\n"))
return r
}
}
log.Log(GITPBWARN, "git pull error", repo.GetGoPath(), r.Error)
*/
return r
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 {
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)
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)
@ -72,7 +53,7 @@ func (all *Repos) RillGitPull(part1 int, part2 int) map[*Repo]cmd.Status {
counter += 1
if repo.CheckDirty() {
// log.Info("git pull skipped on dirty repo", repo.GoPath)
var result cmd.Status
result := new(cmd.Status)
result.Error = ErrorGitPullOnDirty
lock.Lock()
defer lock.Unlock()
@ -85,9 +66,10 @@ func (all *Repos) RillGitPull(part1 int, part2 int) map[*Repo]cmd.Status {
dur = time.Duration((1+rand.Intn(50))*20) * time.Millisecond
time.Sleep(dur)
*/
var result cmd.Status
result = repo.GitPull()
log.Info("git pull", strings.Join(result.Stdout, " "), repo.GetGoPath())
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

112
shell.go
View File

@ -18,27 +18,22 @@ 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 {
result := shell.PathRunQuiet(repo.FullPath, cmd)
return result
}
func (repo *Repo) RunEcho(cmd []string) cmd.Status {
result := shell.PathRunQuiet(repo.FullPath, cmd)
log.Info("cmd:", repo.FullPath, cmd)
log.Warn("cmd.Exit:", result.Exit, "cmd.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.Info("STDOUT:", line)
log.Log(NOW, "STDOUT:", line)
}
for _, line := range result.Stderr {
log.Info("STDERR:", line)
log.Log(NOW, "STDERR:", line)
}
return result
}
@ -47,22 +42,31 @@ func (repo *Repo) RunRealtime(cmd []string) cmd.Status {
return shell.PathRunRealtime(repo.GetFullPath(), cmd)
}
// error if result.Error or if result.Exit != 0
func (repo *Repo) RunStrict(cmd []string) error {
return repo.StrictRun(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) StrictRun(cmd []string) error {
result := repo.RunQuiet(cmd)
func (repo *Repo) RunQuiet(cmd []string) (*cmd.Status, error) {
result := shell.PathRunQuiet(repo.FullPath, cmd)
if result.Error != nil {
log.Warn(repo.GetGoPath(), cmd, "wow. golang is cool. an os.Error:", result.Error)
return result.Error
return &result, result.Error
}
if result.Exit != 0 {
log.Warn(cmd, "failed with", result.Exit)
return errors.New(fmt.Sprint(cmd, "failed with", result.Exit))
// log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath())
return &result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit))
}
return 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 {
@ -111,13 +115,73 @@ func (repo *Repo) IsDirectory() bool {
func (repo *Repo) RunAll(all [][]string) bool {
for _, cmd := range all {
log.Log(GITPBWARN, "doAll() RUNNING: cmd =", cmd)
log.Log(WARN, "doAll() RUNNING: cmd =", cmd)
r := repo.Run(cmd)
if r.Error != nil {
log.Log(GITPBWARN, "doAll() err =", r.Error)
log.Log(GITPBWARN, "doAll() out =", r.Stdout)
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)
}