Compare commits
228 Commits
Author | SHA1 | Date |
---|---|---|
|
ab666ddbc3 | |
|
0b30cd36dc | |
|
2a47f1e547 | |
|
719287c3bf | |
|
6654dbb410 | |
|
62e5fc396c | |
|
e793c89712 | |
|
9d968721d0 | |
|
5a98058474 | |
|
1d8380b9e7 | |
|
e395456c53 | |
|
cb60421374 | |
|
c2e3108698 | |
|
bf59b9124a | |
|
33c585f4cf | |
|
7d60a495ca | |
|
1745f44aa0 | |
|
f9c8a37491 | |
|
6a99b7b099 | |
|
97fe127345 | |
|
da649abceb | |
|
722ebeeef8 | |
|
5f882d1388 | |
|
658a2ce1a2 | |
|
db05f8a8aa | |
|
193f27ec30 | |
|
e7c6156562 | |
|
1e0f1e8e88 | |
|
a4a48d7480 | |
|
6b1e922aaa | |
|
46d61345af | |
|
434f62a7e6 | |
|
f585edc192 | |
|
d47d25e3a6 | |
|
5460316daf | |
|
72b5864000 | |
|
aef107af0d | |
|
f08a517bc4 | |
|
1ead02a6d3 | |
|
68af638f9e | |
|
1c43978294 | |
|
d0616fae03 | |
|
33399f18e5 | |
|
e2ca78cfb3 | |
|
683522ee46 | |
|
d4f9878298 | |
|
fcc5a36ad0 | |
|
7297b63339 | |
|
acc18b186f | |
|
27820d88f2 | |
|
0bf8cc3d79 | |
|
88d25c85c2 | |
|
dba126cfdf | |
|
9ad1193746 | |
|
f58f7df9ac | |
|
c572bb2f04 | |
|
0c4bf8e9bb | |
|
b6b0cadde5 | |
|
b38cf5f0fa | |
|
cc51fd364d | |
|
be1c2d0f11 | |
|
2d439a0468 | |
|
5ec788fe17 | |
|
587a3debeb | |
|
2204624369 | |
|
27c8c38047 | |
|
3f1c8bf5c2 | |
|
135346af6a | |
|
5d0f74360f | |
|
c9149bbb13 | |
|
010791e828 | |
|
312c4742b6 | |
|
e5a5454bc0 | |
|
e127f53bd4 | |
|
9cc9b9bc87 | |
|
bb54c065ad | |
|
11d1136797 | |
|
6f66340d82 | |
|
31702354f2 | |
|
7cacc395ef | |
|
d62954bb63 | |
|
a07ad8bae8 | |
|
32a5530129 | |
|
d01cb1c9d7 | |
|
387f69631b | |
|
57e38ee8ce | |
|
0bd0af4845 | |
|
7ba0c49ee3 | |
|
b57144e6bf | |
|
19fb3a29fb | |
|
53d986bf59 | |
|
807a965602 | |
|
6b8ef6fc60 | |
|
829b6ba55f | |
|
336ab60e01 | |
|
3b17710c1a | |
|
53acead41e | |
|
966e3d7858 | |
|
d4cc68c07f | |
|
90e20c4ff0 | |
|
b0595d6a1d | |
|
84c9fe2bfc | |
|
723ca5e829 | |
|
93e4eae19d | |
|
9094779044 | |
|
0c30a9da2f | |
|
80f602c4d9 | |
|
d014dbe3d4 | |
|
803f0cf98e | |
|
660255c8a3 | |
|
0773f20d00 | |
|
a477ea8f8f | |
|
bd925757df | |
|
8c06b25fc2 | |
|
ceb2d6d5c8 | |
|
0b5caa03e5 | |
|
25ab223c07 | |
|
208118ad5f | |
|
4aa85a2f0f | |
|
7df10fba51 | |
|
de71f2692a | |
|
52bbf4c827 | |
|
6c5a858125 | |
|
0ca1240c75 | |
|
b0fca659c5 | |
|
ebda2ea222 | |
|
d4a31b8e0b | |
|
3298e02c2a | |
|
f21c471032 | |
|
8c54c4a7d8 | |
|
366f4680ac | |
|
676cb338a4 | |
|
4d093bc1e3 | |
|
2fe035fbf0 | |
|
016335b322 | |
|
bbd34df599 | |
|
754e60fffa | |
|
80130ab563 | |
|
de75f95161 | |
|
22b157f6d7 | |
|
6112d6e298 | |
|
bab03875d2 | |
|
010a2c6b58 | |
|
4446068e1a | |
|
40830d3f38 | |
|
328fbe9fdd | |
|
9935f45bd4 | |
|
36fdd99d15 | |
|
406817d5b2 | |
|
22d2ae5cd7 | |
|
cbac3a5914 | |
|
a60188915d | |
|
af50986812 | |
|
339393a88a | |
|
bea54091d2 | |
|
10cc012a12 | |
|
233f7bca76 | |
|
4bc95ad268 | |
|
c53da5a9a1 | |
|
a115ba144b | |
|
cea5968b0f | |
|
01b332ceb8 | |
|
5006d718fd | |
|
5c1bebb16c | |
|
5341a70557 | |
|
1e4b0c0d37 | |
|
2574ec733a | |
|
d7a90a71ab | |
|
adef980837 | |
|
c5f7570834 | |
|
adddfad2b7 | |
|
a0b6a435a7 | |
|
c1e3282864 | |
|
cb04f3585c | |
|
945ae6329b | |
|
205e6f022b | |
|
77cd0ae36e | |
|
4a64e3a7c2 | |
|
97a2f05cf2 | |
|
18905d7cf7 | |
|
aec1833fe6 | |
|
33880f9006 | |
|
1d1d8e7eea | |
|
7a90613c91 | |
|
345343647a | |
|
4b83d18db4 | |
|
425cb042b9 | |
|
cfb5a7adc0 | |
|
45fc9ea257 | |
|
2ebdd32040 | |
|
390bbae435 | |
|
6b42d07949 | |
|
412658698a | |
|
82935ae6b2 | |
|
fd3b806b4a | |
|
f24d8131c3 | |
|
8e9ec30b29 | |
|
455ea31d70 | |
|
8a6fb6d442 | |
|
0faa5d01a0 | |
|
c07d48c711 | |
|
3de739288b | |
|
5d28c6741a | |
|
1af05cc0ef | |
|
3e45924aa2 | |
|
e88341d808 | |
|
3d260a0219 | |
|
4a79cccffd | |
|
7615317ca7 | |
|
bdcaba32f8 | |
|
3359a44996 | |
|
c49d247ae8 | |
|
d283d2d297 | |
|
96d37b5941 | |
|
3b90d979d7 | |
|
02b9ed57b3 | |
|
5a9b953d33 | |
|
9129cfe885 | |
|
1321787a3b | |
|
ed3eacfe75 | |
|
8b987962ea | |
|
c9d99d6693 | |
|
c87e162518 | |
|
c51b8c96b1 | |
|
cedd7ea6f1 | |
|
6dd1bba42b | |
|
2e3e4f98d3 | |
|
44caec2e7f |
|
@ -1,5 +1,8 @@
|
|||
go.*
|
||||
*.swp
|
||||
*.patch
|
||||
|
||||
go.*
|
||||
*.pb.go
|
||||
|
||||
example/example
|
||||
scanGoSrc/scanGoSrc
|
||||
validate/validate
|
||||
|
|
69
Makefile
69
Makefile
|
@ -5,29 +5,64 @@
|
|||
# go install
|
||||
|
||||
|
||||
all: refs.pb.go vet
|
||||
all: gitTag.pb.go goDep.pb.go repo.pb.go goimports vet
|
||||
|
||||
vet: lint
|
||||
GO111MODULE=off go vet
|
||||
generate: clean
|
||||
go-mod-clean
|
||||
go generate
|
||||
|
||||
lint:
|
||||
# -buf lint refs.proto # todo: figure out where buf comes from again
|
||||
vet:
|
||||
@GO111MODULE=off go vet
|
||||
@echo this go library package builds okay
|
||||
|
||||
# autofixes your import headers in your golang files
|
||||
goimports:
|
||||
goimports -w *.go
|
||||
|
||||
redomod:
|
||||
rm -f go.*
|
||||
GO111MODULE= go mod init
|
||||
GO111MODULE= go mod tidy
|
||||
|
||||
# dump autogenerated files and potential patches
|
||||
clean:
|
||||
rm -f *.pb.go
|
||||
-rm -f go.*
|
||||
make -C example clean
|
||||
rm -f *.pb.go go.* *.patch
|
||||
go-mod-clean purge
|
||||
|
||||
refs.pb.go: refs.proto
|
||||
cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
|
||||
--go_opt=Mrefs.proto=go.wit.com/lib/protobuf/gitpb \
|
||||
refs.proto
|
||||
#refs.pb.go: refs.proto
|
||||
# cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
|
||||
# --go_opt=Mrefs.proto=go.wit.com/lib/protobuf/gitpb \
|
||||
# refs.proto
|
||||
|
||||
gitTag.pb.go: 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
|
||||
|
|
13
README.md
13
README.md
|
@ -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
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package gitpb
|
||||
|
||||
// functions that check the ages of files
|
||||
// and track if the repo needs to be re-scanned
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (repo *Repo) LastGitPull() (time.Time, error) {
|
||||
return repo.oldMtime(".git/FETCH_HEAD")
|
||||
}
|
||||
|
||||
func (repo *Repo) GoSumAge() (time.Duration, error) {
|
||||
var mtime time.Time
|
||||
var err error
|
||||
mtime, err = repo.oldMtime("go.sum")
|
||||
if err == nil {
|
||||
return time.Since(mtime), nil
|
||||
}
|
||||
mtime, err = repo.oldMtime("go.mod")
|
||||
if err == nil {
|
||||
return time.Since(mtime), nil
|
||||
}
|
||||
now := time.Now()
|
||||
return time.Since(now), errors.New(repo.GetGoPath() + " go.mod missing")
|
||||
}
|
||||
|
||||
func (repo *Repo) GitChanged() bool {
|
||||
fullfile := filepath.Join(repo.FullPath, ".git/FETCH_HEAD")
|
||||
lasttime, err := repo.LastGitPull()
|
||||
if err == nil {
|
||||
// if error, something is wrong, assume true
|
||||
log.Info("gitpb:", fullfile, "changed")
|
||||
return true
|
||||
}
|
||||
newtime := repo.Times.LastPull.AsTime()
|
||||
|
||||
if lasttime == newtime {
|
||||
return false
|
||||
}
|
||||
log.Info("gitpb:", fullfile, "changed")
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) GitPullAge() time.Duration {
|
||||
lastpull, err := repo.LastGitPull()
|
||||
if err == nil {
|
||||
// if error, something is wrong, assume true
|
||||
ltime := repo.Times.LastPull.AsTime()
|
||||
return time.Since(ltime)
|
||||
}
|
||||
|
||||
return time.Since(lastpull)
|
||||
}
|
||||
|
||||
func (repo *Repo) oldMtime(filename string) (time.Time, error) {
|
||||
pathf := filepath.Join(repo.FullPath, filename)
|
||||
statf, err := os.Stat(pathf)
|
||||
if err == nil {
|
||||
return statf.ModTime(), nil
|
||||
}
|
||||
log.Log(WARN, "Mtime() os.Stat() error", pathf, err)
|
||||
return time.Now(), err
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package gitpb
|
||||
|
||||
// go.wit.com/apps/autogenpb auto generates Sort() and Marshal() for protobuf files
|
||||
//go:generate autogenpb --proto gitTag.proto
|
||||
//go:generate autogenpb --proto goDep.proto
|
||||
//go:generate autogenpb --proto repo.proto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// files : a list of files to save ["go.mod", "go.sum"]
|
||||
// refname : could be "master" or "v0.1.5" or "a605119c2cc41"
|
||||
// del : true means empty out existing notes, otherwise append
|
||||
func (repo *Repo) AutogenSave(files []string, refname string, del bool) error {
|
||||
if del {
|
||||
cmd := []string{"git", "notes", "show", refname}
|
||||
if _, err := repo.RunQuiet(cmd); err != nil {
|
||||
// if there are not any notes, no need to remove them
|
||||
} else {
|
||||
cmd := []string{"git", "notes", "remove", refname}
|
||||
if _, err := repo.RunQuiet(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, fname := range files {
|
||||
autotag := "// `autogen:" + fname + "`"
|
||||
cmd := []string{"git", "notes", "append", "-m", autotag, refname}
|
||||
if _, err := repo.RunQuiet(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = []string{"git", "notes", "append", "-F", fname, refname}
|
||||
if _, err := repo.RunQuiet(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// a tag with a blank name indicates the end of the autogen file or files
|
||||
autotag := "// `autogen:`"
|
||||
cmd := []string{"git", "notes", "append", "-m", autotag, refname}
|
||||
if _, err := repo.RunQuiet(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// restores files from git metadata (notes)
|
||||
func (repo *Repo) AutogenRestore(refname string) error {
|
||||
var cmd []string
|
||||
if refname == "" {
|
||||
cmd = []string{"git", "notes", "show"}
|
||||
} else {
|
||||
cmd = []string{"git", "notes", "show", refname}
|
||||
}
|
||||
|
||||
result := repo.Run(cmd)
|
||||
if result.Exit != 0 {
|
||||
return errors.New(fmt.Sprint("git notes show returned ", result.Exit))
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if len(result.Stdout) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var newf *os.File
|
||||
var err error
|
||||
var body string
|
||||
for _, line := range result.Stdout {
|
||||
if strings.HasPrefix(line, "// `autogen:") {
|
||||
if newf != nil {
|
||||
fmt.Fprintln(newf, strings.TrimSpace(body))
|
||||
newf.Close()
|
||||
newf = nil
|
||||
body = ""
|
||||
}
|
||||
fbase := strings.TrimPrefix(line, "// `autogen:")
|
||||
fbase = strings.TrimSpace(fbase)
|
||||
fbase = strings.TrimSuffix(fbase, "`")
|
||||
// if line == // `autogen:` , then the filename is blank
|
||||
if fbase != "" {
|
||||
fname := filepath.Join(filepath.Join(repo.FullPath, fbase))
|
||||
newf, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
body += line + "\n"
|
||||
}
|
||||
if newf != nil {
|
||||
fmt.Fprintln(newf, strings.TrimSpace(body))
|
||||
newf.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (repo *Repo) CheckoutMaster() bool {
|
||||
bName := repo.GetMasterBranchName()
|
||||
if repo.checkoutBranch(bName) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (repo *Repo) CheckoutDevel() bool {
|
||||
bName := repo.GetDevelBranchName()
|
||||
if repo.checkoutBranch(bName) {
|
||||
repo.DevelBranchName = bName
|
||||
return true
|
||||
// switch ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (repo *Repo) CheckoutUser() error {
|
||||
bName := repo.GetUserBranchName()
|
||||
if bName == "uerr" {
|
||||
usr, _ := user.Current()
|
||||
repo.SetUserBranchName(usr.Username)
|
||||
bName = usr.Username
|
||||
log.Info("gitpb CheckoutUser() somehow got user 'uerr'")
|
||||
}
|
||||
|
||||
return repo.createUserBranch(bName)
|
||||
/*
|
||||
if err != nil {
|
||||
log.Info("attempting checkout user error", repo.GetGoPath(), bName, err)
|
||||
}
|
||||
return err
|
||||
*/
|
||||
}
|
||||
|
||||
func (repo *Repo) BranchExists(bName string) bool {
|
||||
// fixme after move to protobuf
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) checkoutBranch(bName string) bool {
|
||||
if !repo.BranchExists(bName) {
|
||||
return false
|
||||
}
|
||||
if bName == "" {
|
||||
return false
|
||||
}
|
||||
if repo.CheckDirty() {
|
||||
log.Log(INFO, repo.GetFullPath(), "is dirty")
|
||||
return false
|
||||
}
|
||||
cmd := []string{"git", "checkout", bName}
|
||||
r := repo.Run(cmd)
|
||||
if r.Error != nil {
|
||||
log.Log(INFO, "git checkout error:", r.Error)
|
||||
}
|
||||
|
||||
realname := repo.GetCurrentBranchName()
|
||||
realversion := repo.GetCurrentBranchVersion()
|
||||
log.Log(INFO, repo.GetFullPath(), "realname =", realname, "realversion =", realversion)
|
||||
|
||||
if realname != bName {
|
||||
log.Log(INFO, "git checkout failed", repo.GetFullPath(), bName, "!=", realname)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// actually creates a local user branch
|
||||
func (repo *Repo) createUserBranch(branch string) error {
|
||||
if branch == "" {
|
||||
// get the username here?
|
||||
return fmt.Errorf("gitpb createuserBranch() logic err. git branch name can not be blank")
|
||||
}
|
||||
if repo.IsDirty() {
|
||||
// never change repos on dirty branches
|
||||
return fmt.Errorf("repo is dirty")
|
||||
}
|
||||
|
||||
if repo.Exists(filepath.Join(".git/refs/heads", branch)) {
|
||||
var err error
|
||||
// there is already a local user branch
|
||||
cmd := []string{"git", "checkout", branch}
|
||||
if _, err = repo.RunVerboseOnError(cmd); err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Log(INFO, "git checkout error:", err)
|
||||
}
|
||||
|
||||
if repo.Exists(filepath.Join(".git/refs/remote/origin", branch)) {
|
||||
var err error
|
||||
// there is a remote user branch
|
||||
// todo: check other remotes
|
||||
cmd := []string{"git", "checkout", branch}
|
||||
if _, err = repo.RunVerboseOnError(cmd); err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Log(INFO, "git checkout error:", err)
|
||||
}
|
||||
|
||||
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
|
||||
repo.CheckoutDevel()
|
||||
}
|
||||
repo.ReloadCheck()
|
||||
|
||||
if repo.GetCurrentBranchName() != repo.GetDevelBranchName() {
|
||||
log.Info("create user branch will probably fail", repo.GetGoPath())
|
||||
// TODO: FIX THIS
|
||||
// return fmt.Errorf("repo must be on devel branch %s", repo.GetGoPath())
|
||||
}
|
||||
|
||||
// create the branch from devel
|
||||
cmd := []string{"git", "branch", branch}
|
||||
if _, err := repo.RunVerboseOnError(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = []string{"git", "checkout", branch}
|
||||
if _, err := repo.RunVerboseOnError(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package gitpb
|
||||
|
||||
func (repo *Repo) SetReadOnly(b bool) {
|
||||
repo.ReadOnly = b
|
||||
}
|
||||
|
||||
func (repo *Repo) SetTargetVersion(target string) {
|
||||
repo.TargetVersion = target
|
||||
}
|
||||
|
||||
func (repo *Repo) SetMasterBranchName(s string) {
|
||||
repo.MasterBranchName = s
|
||||
}
|
||||
|
||||
func (repo *Repo) GetGoPath() string {
|
||||
if repo.GoInfo == nil {
|
||||
return repo.Namespace
|
||||
}
|
||||
if repo.GoInfo.GoPath == "" {
|
||||
return repo.Namespace
|
||||
}
|
||||
return repo.GoInfo.GoPath
|
||||
}
|
||||
|
||||
func (repo *Repo) GetGoPrimitive() bool {
|
||||
if repo.GoInfo == nil {
|
||||
return false
|
||||
}
|
||||
return repo.GoInfo.GoPrimitive
|
||||
}
|
||||
|
||||
func (repo *Repo) SetGoPrimitive(b bool) {
|
||||
if repo.GoInfo == nil {
|
||||
repo.GoInfo = new(GoInfo)
|
||||
}
|
||||
repo.GoInfo.GoPrimitive = b
|
||||
}
|
||||
|
||||
func (repo *Repo) IsUserBranch() bool {
|
||||
if repo.GetCurrentBranchName() == repo.GetUserBranchName() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (repo *Repo) IsMasterBranch() bool {
|
||||
if repo.GetCurrentBranchName() == repo.GetMasterBranchName() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (repo *Repo) IsDevelBranch() bool {
|
||||
if repo.GetCurrentBranchName() == repo.GetDevelBranchName() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package gitpb
|
||||
|
||||
// functions to import and export the protobuf
|
||||
// data to and from config files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.wit.com/lib/protobuf/bugpb"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// write to ~/.config/forge/ unless ENV{FORGE_REPOSDIR} is set
|
||||
func (all *Repos) ConfigSave(fname string) error {
|
||||
if all == nil {
|
||||
log.Warn("gitpb repos == nil")
|
||||
return errors.New("gitpb.ConfigSave() repos == nil")
|
||||
}
|
||||
|
||||
if _, s := filepath.Split(fname); s != "repos.pb" {
|
||||
log.Infof("ConfigSave() filename '%s' invalid\n", fname)
|
||||
return log.Errorf("ConfigSave() filename '%s' invalid\n", fname)
|
||||
}
|
||||
|
||||
data, err := all.Marshal()
|
||||
if err != nil {
|
||||
log.Info("gitpb proto.Marshal() failed len", len(data), err)
|
||||
// often this is because strings have invalid UTF-8. This should probably be fixed in the protobuf code
|
||||
// this might be fixed in the create code, but it can't hurt to try this as a last ditch effort here
|
||||
log.Info("gitpb.ConfigSave() ATTEMPTING TO VALIDATE UTF-8 strings in the protobuf file")
|
||||
if err := all.tryValidate(); err != nil {
|
||||
log.Info("gitpb.ConfigSave() STILL FAILEd", err)
|
||||
return err
|
||||
} else {
|
||||
// re-attempt Marshal() here
|
||||
data, err = all.Marshal()
|
||||
if err == nil {
|
||||
// validate & sanitize strings worked
|
||||
configWrite(fname, data)
|
||||
return nil
|
||||
}
|
||||
log.Info("gitpb.ConfigSave() STILL FAILEd", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
configWrite(fname, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// todo: move this to Marshal() functions automatically in autogenpb?
|
||||
func (repo *Repo) ValidateUTF8() error {
|
||||
if _, err := repo.Marshal(); err == nil {
|
||||
// exit if Marshal() works
|
||||
return nil
|
||||
} else {
|
||||
// log.Printf("%s repo.Marshal() failed: %v\n", repo.GetFullPath(), err)
|
||||
}
|
||||
// you only need to do this if Marshal() fails
|
||||
err := bugpb.ValidateProtoUTF8(repo)
|
||||
if err != nil {
|
||||
// log.Printf("Protobuf UTF-8 validation failed: %v\n", err)
|
||||
}
|
||||
if err := bugpb.SanitizeProtoUTF8(repo); err != nil {
|
||||
log.Warn("gitpb.ValidateUTF8()( failed:", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (all *Repos) tryValidate() error {
|
||||
err := bugpb.ValidateProtoUTF8(all)
|
||||
if err != nil {
|
||||
log.Printf("Protobuf UTF-8 validation failed: %v\n", err)
|
||||
}
|
||||
if err := bugpb.SanitizeProtoUTF8(all); err != nil {
|
||||
log.Warn("Sanitation failed:", err)
|
||||
// log.Fatalf("Sanitization failed: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// load the repos.pb file. I shouldn't really matter if this
|
||||
// fails. the file should be autogenerated. This is used
|
||||
// locally just for speed
|
||||
func (all *Repos) ConfigLoadOld() error {
|
||||
if os.Getenv("FORGE_REPOSDIR") == "" {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
fullpath := filepath.Join(homeDir, ".config/forge")
|
||||
os.Setenv("FORGE_REPOSDIR", fullpath)
|
||||
}
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
cfgname := filepath.Join(os.Getenv("FORGE_REPOSDIR"), "repos.pb")
|
||||
if data, err = loadFile(cfgname); err != nil {
|
||||
// something went wrong loading the file
|
||||
// all.sampleConfig() // causes nil panic
|
||||
return err
|
||||
}
|
||||
// this means the forge.pb file exists and was read
|
||||
if len(data) == 0 {
|
||||
return errors.New("gitpb.ConfigLoad() repos.pb is empty")
|
||||
}
|
||||
err = all.Unmarshal(data)
|
||||
test := NewRepos()
|
||||
if test.Uuid != all.Uuid {
|
||||
log.Log(WARN, "uuids do not match", test.Uuid, all.Uuid)
|
||||
deleteProtobufFile(cfgname)
|
||||
}
|
||||
if test.Version != all.Version {
|
||||
log.Log(WARN, "versions do not match", test.Version, all.Version)
|
||||
deleteProtobufFile(cfgname)
|
||||
}
|
||||
log.Log(INFO, cfgname, "protobuf versions and uuid match", all.Uuid, all.Version)
|
||||
return err
|
||||
}
|
||||
|
||||
func (all *Repos) ConfigLoad(cfgname string) error {
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
if data, err = loadFile(cfgname); err != nil {
|
||||
// something went wrong loading the file
|
||||
// all.sampleConfig() // causes nil panic
|
||||
return err
|
||||
}
|
||||
// this means the forge.pb file exists and was read
|
||||
if len(data) == 0 {
|
||||
return errors.New("gitpb.ConfigLoad() repos.pb is empty")
|
||||
}
|
||||
err = all.Unmarshal(data)
|
||||
test := NewRepos()
|
||||
if test.Uuid != all.Uuid {
|
||||
log.Log(WARN, "uuids do not match", test.Uuid, all.Uuid)
|
||||
deleteProtobufFile(cfgname)
|
||||
}
|
||||
if test.Version != all.Version {
|
||||
log.Log(WARN, "versions do not match", test.Version, all.Version)
|
||||
deleteProtobufFile(cfgname)
|
||||
}
|
||||
log.Log(INFO, cfgname, "protobuf versions and uuid match", all.Uuid, all.Version)
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteProtobufFile(filename string) {
|
||||
log.Log(WARN, "The protobuf file format has changed for", filename)
|
||||
log.Log(WARN, "Deleting old file:", filename)
|
||||
log.Log(WARN, "This file will be recreated on the next run.")
|
||||
err := os.Remove(filename)
|
||||
if err != nil {
|
||||
log.Log(WARN, "failed to remove old protobuf file", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (all *Repos) sampleConfig() {
|
||||
newr := new(Repo)
|
||||
newr.FullPath = "/opt/forge/dummyentry"
|
||||
all.Append(newr)
|
||||
}
|
||||
|
||||
func loadFile(fullname string) ([]byte, error) {
|
||||
data, err := os.ReadFile(fullname)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// if file does not exist, just return nil. this
|
||||
// will cause ConfigLoad() to try the next config file like "forge.text"
|
||||
// because the user might want to edit the .config by hand
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
// log.Info("open config file :", err)
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func configWrite(fullname string, data []byte) error {
|
||||
log.Infof("%s your repos have changed state. cached state. (%d) bytes\n", fullname, len(data))
|
||||
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
defer cfgfile.Close()
|
||||
if err != nil {
|
||||
log.Warn("open config file :", err)
|
||||
return err
|
||||
}
|
||||
cfgfile.Write(data)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
package gitpb
|
||||
|
||||
// runs git, parses output
|
||||
// types faster than you can
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (repo *Repo) reloadVersions() {
|
||||
repo.setMasterVersion()
|
||||
repo.setDevelVersion()
|
||||
repo.setUserVersion()
|
||||
repo.setLastTag()
|
||||
repo.setCurrentBranchName()
|
||||
repo.setCurrentBranchVersion()
|
||||
}
|
||||
|
||||
func (repo *Repo) setMasterVersion() {
|
||||
bname := repo.GetMasterBranchName()
|
||||
v, err := repo.gitVersionByName(bname)
|
||||
/*
|
||||
count := repo.LenGitTags()
|
||||
log.Info(repo.GetGoPath(), "tag count", count)
|
||||
repo.UpdateGitTags()
|
||||
count = repo.LenGitTags()
|
||||
log.Info(repo.GetGoPath(), "tag count", count)
|
||||
*/
|
||||
|
||||
if err == nil {
|
||||
repo.MasterVersion = v
|
||||
} else {
|
||||
log.Log(WARN, "gitpb.GitMasterVersion() error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *Repo) setDevelVersion() {
|
||||
bname := repo.GetDevelBranchName()
|
||||
v, err := repo.gitVersionByName(bname)
|
||||
if err == nil {
|
||||
repo.DevelVersion = v
|
||||
} else {
|
||||
// log.Log(WARN, "gitpb.GitDevelVersion() error:", err)
|
||||
repo.DevelVersion = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *Repo) setUserVersion() {
|
||||
bname := repo.GetUserBranchName()
|
||||
if !repo.Exists(filepath.Join(".git/refs/heads", bname)) {
|
||||
// the user branch does not exist at this time
|
||||
repo.UserVersion = "uerr"
|
||||
return
|
||||
}
|
||||
v, err := repo.gitVersionByName(bname)
|
||||
if err == nil {
|
||||
repo.UserVersion = v
|
||||
} else {
|
||||
// log.Log(WARN, "gitpb.GitUserVersion() error:", err)
|
||||
repo.UserVersion = "uerr"
|
||||
}
|
||||
}
|
||||
|
||||
// this is used often. probably move everything to this
|
||||
// returns things like
|
||||
// v0.2.2
|
||||
// v0.22.39-1-g2141737
|
||||
// v0.23-dirty
|
||||
// mystuff
|
||||
func (repo *Repo) GetCurrentVersion() string {
|
||||
if repo == nil {
|
||||
return ""
|
||||
}
|
||||
bver := repo.GetCurrentBranchVersion()
|
||||
if repo.CheckDirty() {
|
||||
bver = bver + "-dirty"
|
||||
}
|
||||
return bver
|
||||
}
|
||||
|
||||
func (repo *Repo) gitDescribeByHash(hash string) (string, error) {
|
||||
if hash == "" {
|
||||
return "", errors.New("hash was blank")
|
||||
}
|
||||
r, err := repo.RunQuiet([]string{"git", "describe", "--tags", hash})
|
||||
out := strings.Join(r.Stdout, "\n")
|
||||
if err != nil {
|
||||
// log.Warn("not in a git repo or bad hash?", err, repo.GetGoPath())
|
||||
return "gitpb err", err
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// this should get the most recent tag
|
||||
func (repo *Repo) GetLastTagVersion() string {
|
||||
return repo.LastTag
|
||||
}
|
||||
|
||||
func (repo *Repo) DebianReleaseVersion() string {
|
||||
lasttag := repo.GetLastTagVersion()
|
||||
newv := trimNonNumericFromStart(lasttag)
|
||||
if newv == "" {
|
||||
newv = "0.0"
|
||||
if lasttag != "" {
|
||||
newv += "-" + lasttag
|
||||
}
|
||||
}
|
||||
return newv
|
||||
}
|
||||
|
||||
func (repo *Repo) DebianCurrentVersion() string {
|
||||
cbversion := repo.GetCurrentBranchVersion()
|
||||
|
||||
newv := trimNonNumericFromStart(cbversion)
|
||||
if newv == "" {
|
||||
newv = "0.0"
|
||||
}
|
||||
if repo.CheckDirty() {
|
||||
newv += "-dirty"
|
||||
}
|
||||
return newv
|
||||
}
|
||||
|
||||
func (repo *Repo) gitVersionByName(name string) (string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
if name == "" {
|
||||
// git will return the current tag
|
||||
cmd := []string{"git", "describe", "--tags"}
|
||||
r, err := repo.RunQuiet(cmd)
|
||||
output := strings.Join(r.Stdout, "\n")
|
||||
if err != nil {
|
||||
log.Log(WARN, repo.FullPath, "gitDescribeByName() ", output, err, cmd)
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
if !repo.IsBranch(name) {
|
||||
// branch does not exist
|
||||
return "", errors.New("gitDescribeByName() git fatal: Not a valid object name: " + name)
|
||||
}
|
||||
cmd := []string{"git", "describe", "--tags", name}
|
||||
result, err := repo.RunQuiet(cmd)
|
||||
output := strings.Join(result.Stdout, "\n")
|
||||
if err != nil {
|
||||
//log.Log(WARN, "cmd =", cmd)
|
||||
//log.Log(WARN, "err =", err)
|
||||
//log.Log(WARN, "output (might have worked with error?) =", output)
|
||||
//log.Log(WARN, "not in a git repo or bad tag?", repo.GetGoPath())
|
||||
return "", result.Error
|
||||
}
|
||||
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
|
||||
func trimNonNumericFromStart(s string) string {
|
||||
for i, r := range s {
|
||||
if unicode.IsDigit(r) {
|
||||
return s[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func normalizeVersion(s string) string {
|
||||
// reg, err := regexp.Compile("[^a-zA-Z0-9]+")
|
||||
parts := strings.Split(s, "-")
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
reg, err := regexp.Compile("[^0-9.]+")
|
||||
if err != nil {
|
||||
log.Log(WARN, "normalizeVersion() regexp.Compile() ERROR =", err)
|
||||
return parts[0]
|
||||
}
|
||||
clean := reg.ReplaceAllString(parts[0], "")
|
||||
log.Log(INFO, "normalizeVersion() s =", clean)
|
||||
return clean
|
||||
}
|
||||
|
||||
// golang doesn't seem to really support v0.1 and seems to want v0.1.0
|
||||
// TODO: confirm this. (as of Dec 2024, this appears to be the case -- jcarr )
|
||||
//
|
||||
// personally I hope GO stays with the vX.X.X version scheme. it's a good system.
|
||||
//
|
||||
// if the version is "57", convert it to v0.0.57 for GO
|
||||
func splitVersion(version string) (a, b, c string) {
|
||||
tmp := normalizeVersion(version)
|
||||
parts := strings.Split(tmp, ".")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return "", "", parts[0] // converts someone using version "57" to "v0.0.57"
|
||||
case 2:
|
||||
return parts[0], parts[1], "" // converts someone using version "1.2" to "v1.2.0"
|
||||
default:
|
||||
return parts[0], parts[1], parts[2]
|
||||
}
|
||||
}
|
||||
|
||||
func splitInts(ver string) (int, int, int) {
|
||||
major, minor, revision := splitVersion(ver)
|
||||
a, _ := strconv.Atoi(major)
|
||||
b, _ := strconv.Atoi(minor)
|
||||
c, _ := strconv.Atoi(revision)
|
||||
return a, b, c
|
||||
}
|
||||
|
||||
// changes the target minor. v0.1.3 becomes v0.2.0
|
||||
func (repo *Repo) IncrementTargetMinor() {
|
||||
lasttag := repo.GetLastTag()
|
||||
// var major, minor, revision string
|
||||
major, minor, revision := splitInts(lasttag)
|
||||
|
||||
minor += 1
|
||||
revision = 0
|
||||
|
||||
newa := strconv.Itoa(major)
|
||||
newb := strconv.Itoa(minor)
|
||||
newc := strconv.Itoa(revision)
|
||||
|
||||
repo.SetTargetVersion("v" + newa + "." + newb + "." + newc)
|
||||
}
|
||||
|
||||
// changes the target revision. v0.1.3 becomes v0.1.4
|
||||
func (repo *Repo) IncrementTargetRevision() {
|
||||
// first try just going from the last tag
|
||||
repo.incrementRevision(repo.GetLastTag())
|
||||
|
||||
if !isNewerVersion(repo.GetMasterVersion(), repo.GetTargetVersion()) {
|
||||
// log.Printf("tag error. master version() %s was higher than target version %s\n", repo.GetMasterVersion(), repo.GetTargetVersion())
|
||||
repo.incrementRevision(repo.GetMasterVersion())
|
||||
}
|
||||
/*
|
||||
if !isNewerVersion(repo.GetLastTag(), repo.GetTargetVersion()) {
|
||||
log.Printf("last tag versn() %s is higher than target version %s\n", repo.GetLastTag(), repo.GetTargetVersion())
|
||||
return false
|
||||
}
|
||||
if !isNewerVersion(repo.GetMasterVersion(), repo.GetTargetVersion()) {
|
||||
log.Printf("master version() %s is higher than target version %s\n", repo.GetMasterVersion(), repo.GetTargetVersion())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
*/
|
||||
}
|
||||
|
||||
func (repo *Repo) incrementRevision(lasttag string) {
|
||||
major, minor, revision := splitInts(lasttag)
|
||||
|
||||
revision += 1
|
||||
|
||||
newa := strconv.Itoa(major)
|
||||
newb := strconv.Itoa(minor)
|
||||
newc := strconv.Itoa(revision)
|
||||
|
||||
repo.SetTargetVersion("v" + newa + "." + newb + "." + newc)
|
||||
}
|
||||
|
||||
// makes sure the new target version to be released is greater
|
||||
// than the current master version
|
||||
// this is just a sanity check, but this can actually fail sometimes
|
||||
// if other things failed terribly in prior cases
|
||||
// gitpb v.3.1.4
|
||||
// A = major = 3
|
||||
// B = minor = 1
|
||||
// C = revision = 4
|
||||
func isNewerVersion(oldver, newver string) bool {
|
||||
olda, oldb, oldc := splitInts(oldver)
|
||||
newa, newb, newc := splitInts(newver)
|
||||
|
||||
if newa < olda {
|
||||
return false
|
||||
}
|
||||
if newb < oldb {
|
||||
return false
|
||||
}
|
||||
if newc <= oldc {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
/*
|
||||
// todo: probably switch to using slices. new things added in 1.23
|
||||
// https://pkg.go.dev/slices
|
||||
|
||||
func (all *GitTags) newSort() *GitTagScanner {
|
||||
slices.SortFunc(all.GitTags, func(a, b *GitTag) int {
|
||||
if n := strings.Compare(a.Name, b.Name); n != 0 {
|
||||
return n
|
||||
}
|
||||
// If names are equal, order by age
|
||||
return cmp.Compare(a.Age, b.Age)
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
// all this code below is junk and seamingly wrong
|
||||
|
||||
func (all *GitTags) GetAge(name string) time.Time {
|
||||
packs := all.selectAllGitTags()
|
||||
|
||||
var newest time.Time
|
||||
|
||||
for _, tag := range packs {
|
||||
// log.Info("\t\ttag", i, tag.Refname, tag.Authordate.AsTime())
|
||||
_, rname := filepath.Split(tag.Refname)
|
||||
if name == rname {
|
||||
// log.Info("\t\tfound tag", i, rbase, rname, tag.Authordate.AsTime())
|
||||
newest = tag.Authordate.AsTime()
|
||||
return newest
|
||||
}
|
||||
newest = tag.Authordate.AsTime()
|
||||
}
|
||||
|
||||
return newest
|
||||
}
|
||||
|
||||
func (all *GitTags) SortByAge() *GitTagScanner {
|
||||
packs := all.selectAllGitTags()
|
||||
|
||||
sort.Sort(GitTagAge(packs))
|
||||
|
||||
iterator := newGitTagScanner(packs)
|
||||
return iterator
|
||||
}
|
||||
|
||||
type GitTagAge []*GitTag
|
||||
|
||||
func (a GitTagAge) Len() int { return len(a) }
|
||||
|
||||
// sorts in ? order
|
||||
func (a GitTagAge) Less(i, j int) bool {
|
||||
if time.Since(a[i].Authordate.AsTime()) < time.Since(a[j].Authordate.AsTime()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (a GitTagAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// biased code that gives out newer tag dates
|
||||
// even if the code hasn't been patched
|
||||
func (repo *Repo) NewestAge() time.Duration {
|
||||
alltags := repo.Tags.selectAllGitTags()
|
||||
|
||||
var newest time.Time
|
||||
|
||||
for _, tag := range alltags {
|
||||
// check the actual age of the patch
|
||||
if newest.Before(tag.Authordate.AsTime()) {
|
||||
newest = tag.Authordate.AsTime()
|
||||
}
|
||||
// check the age of the commit
|
||||
if newest.Before(tag.Creatordate.AsTime()) {
|
||||
newest = tag.Creatordate.AsTime()
|
||||
}
|
||||
}
|
||||
|
||||
return time.Since(newest)
|
||||
}
|
||||
|
||||
func (repo *Repo) NewestTime() time.Time {
|
||||
alltags := repo.Tags.selectAllGitTags()
|
||||
|
||||
var newest time.Time
|
||||
|
||||
for _, tag := range alltags {
|
||||
// check the actual age of the patch
|
||||
if newest.Before(tag.Authordate.AsTime()) {
|
||||
newest = tag.Authordate.AsTime()
|
||||
}
|
||||
// check the age of the commit
|
||||
if newest.Before(tag.Creatordate.AsTime()) {
|
||||
newest = tag.Creatordate.AsTime()
|
||||
}
|
||||
}
|
||||
|
||||
return newest
|
||||
}
|
||||
|
||||
func (repo *Repo) NewestAgeVerbose() time.Duration {
|
||||
alltags := repo.Tags.selectAllGitTags()
|
||||
|
||||
var newest time.Time
|
||||
var cur time.Time
|
||||
|
||||
for i, tag := range alltags {
|
||||
cur = tag.Authordate.AsTime()
|
||||
rbase, rname := filepath.Split(tag.Refname)
|
||||
log.Info("\t\tfound tag", i, rbase, rname, tag.Authordate.AsTime(), tag.Creatordate.AsTime())
|
||||
if newest.Before(cur) {
|
||||
newest = cur
|
||||
}
|
||||
}
|
||||
|
||||
return time.Since(newest)
|
||||
}
|
||||
|
||||
// not really accurate. temprorary until git Config() parsing is better
|
||||
func (repo *Repo) BranchAge(branch string) time.Duration {
|
||||
alltags := repo.Tags.selectAllGitTags()
|
||||
|
||||
var newest time.Time
|
||||
var cur time.Time
|
||||
var auth time.Time
|
||||
|
||||
for _, tag := range alltags {
|
||||
cur = tag.Creatordate.AsTime()
|
||||
auth = tag.Authordate.AsTime()
|
||||
if branch == filepath.Base(tag.Refname) {
|
||||
// log.Info("\t\tfound tag", i, branch, tag.Authordate.AsTime(), tag.Creatordate.AsTime())
|
||||
if cur.Before(auth) {
|
||||
return time.Since(auth)
|
||||
}
|
||||
return time.Since(cur)
|
||||
}
|
||||
|
||||
// check both dates I guess
|
||||
if newest.Before(auth) {
|
||||
newest = auth
|
||||
}
|
||||
if newest.Before(cur) {
|
||||
newest = cur
|
||||
}
|
||||
}
|
||||
|
||||
return time.Since(newest)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package gitpb;
|
||||
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
message GitRemote { // `autogenpb:nomutex`
|
||||
string url = 1;
|
||||
string fetch = 2;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package gitpb
|
||||
|
||||
// this is becoming a standard format
|
||||
// todo: autogenerate this from the .proto file?
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (repo *Repo) DeleteGoDepByHash(hash string) {
|
||||
repo.GoDeps.DeleteByHash(hash)
|
||||
}
|
||||
|
||||
// enforces no duplicate package names
|
||||
func (repo *Repo) AppendGoDep(newP *GoDep) bool {
|
||||
return repo.GoDeps.AppendByGoPath(newP)
|
||||
}
|
||||
|
||||
// returns time.Duration since last scan of go.sum & go.mod
|
||||
func (repo *Repo) AgeGoDep() time.Duration {
|
||||
t := time.Since(repo.Times.LastGoDep.AsTime())
|
||||
return t
|
||||
}
|
||||
|
||||
// find a dependancy by the go path
|
||||
func (repo *Repo) FindGoDepByPath(gopath string) *GoDep {
|
||||
return repo.GoDeps.FindByGoPath(gopath)
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package gitpb
|
||||
|
||||
// does processing on the go.mod and go.sum files
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// reads and parses the go.sum file into a protobuf struct
|
||||
// this function isn't supposed to change anything, just parse the existing files
|
||||
func (repo *Repo) ParseGoSum() bool {
|
||||
// empty out what was there before
|
||||
repo.GoDeps = nil
|
||||
|
||||
// check of the repo is a primitive
|
||||
// that means, there is not a go.sum file
|
||||
// because the package is completely self contained!
|
||||
if err := repo.setPrimitive(); err != nil {
|
||||
// temporarily enabled this. this is really noisy
|
||||
// log.Info("gitpb.ParseGoSum()", err)
|
||||
return false
|
||||
}
|
||||
if repo.GetGoPrimitive() {
|
||||
// log.Info("This repo is primitive!")
|
||||
return true
|
||||
}
|
||||
tmp := filepath.Join(repo.FullPath, "go.sum")
|
||||
gosum, err := os.Open(tmp)
|
||||
defer gosum.Close()
|
||||
if err != nil {
|
||||
log.Info("gitpb.ParseGoSum() missing go.sum. Some error happened with go mod init & tidy", err)
|
||||
return false
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(gosum)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) == 3 {
|
||||
godep := strings.TrimSpace(parts[0])
|
||||
version := strings.TrimSpace(parts[1])
|
||||
if strings.HasSuffix(version, "/go.mod") {
|
||||
version = strings.TrimSuffix(version, "/go.mod")
|
||||
}
|
||||
new1 := GoDep{
|
||||
GoPath: godep,
|
||||
Version: version,
|
||||
}
|
||||
if repo.GoDeps == nil {
|
||||
repo.GoDeps = new(GoDeps)
|
||||
}
|
||||
repo.GoDeps.AppendByGoPath(&new1)
|
||||
} else {
|
||||
log.Info("gitpb.ParseGoSum() go.sum parse error invalid:", line)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
repo.GoDeps = nil
|
||||
log.Info("gitpb.ParseGoSum()", err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// attempt to parse go.* files in a directory
|
||||
func GoSumParseDir(moddir string) (*GoDeps, error) {
|
||||
isprim, err := computePrimitive(moddir)
|
||||
if err != nil {
|
||||
// "go mod init" failed
|
||||
return nil, err
|
||||
}
|
||||
if isprim {
|
||||
// might be a GO primitive. no go.sum file
|
||||
return nil, nil
|
||||
}
|
||||
// go.sum exists. parse the go.sum file
|
||||
return parseGoSum(moddir)
|
||||
}
|
||||
|
||||
// Detect a 'Primitive' package. Sets the isPrimitive flag
|
||||
// will return true if the repo is truly not dependent on _anything_ else
|
||||
// like spew or lib/widget
|
||||
// it assumes 'go mod init' and 'go mod tidy' ran without error
|
||||
func computePrimitive(moddir string) (bool, error) {
|
||||
// go mod init & go mod tidy ran without errors
|
||||
log.Log(INFO, "isPrimitiveGoMod()", moddir)
|
||||
gomod, err := os.Open(filepath.Join(moddir, "go.mod"))
|
||||
if err != nil {
|
||||
log.Log(INFO, "missing go.mod", moddir)
|
||||
return false, err
|
||||
}
|
||||
defer gomod.Close()
|
||||
|
||||
if shell.Exists(filepath.Join(moddir, "go.sum")) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(gomod)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
parts := strings.Fields(line)
|
||||
log.Log(INFO, " gomod:", parts)
|
||||
if len(parts) >= 1 {
|
||||
log.Log(INFO, " gomod: part[0] =", parts[0])
|
||||
if parts[0] == "require" {
|
||||
log.Log(INFO, " should return false here")
|
||||
return false, errors.New("go.mod file is not primitive")
|
||||
}
|
||||
/*
|
||||
if parts[0] == "go" {
|
||||
if parts[1] != "1.21" {
|
||||
log.Log(WARN, "go not set to 1.21 for", repo.GetGoPath())
|
||||
// return false, errors.New("go not set to 1.21 for " + repo.GetGoPath())
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// parse the go.sum file into a protobuf
|
||||
func parseGoSum(moddir string) (*GoDeps, error) {
|
||||
godeps := new(GoDeps)
|
||||
|
||||
tmp, err := os.Open(filepath.Join(moddir, "go.sum"))
|
||||
defer tmp.Close()
|
||||
if err != nil {
|
||||
log.Info("gitpb.ParseGoSum() missing go.sum. Some error happened with go mod init & tidy", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(tmp)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) == 3 {
|
||||
godep := strings.TrimSpace(parts[0])
|
||||
version := strings.TrimSpace(parts[1])
|
||||
if strings.HasSuffix(version, "/go.mod") {
|
||||
version = strings.TrimSuffix(version, "/go.mod")
|
||||
}
|
||||
new1 := GoDep{
|
||||
GoPath: godep,
|
||||
Version: version,
|
||||
}
|
||||
godeps.AppendByGoPath(&new1)
|
||||
} else {
|
||||
return nil, fmt.Errorf("gitpb.ParseGoSum() go.sum parse error invalid: %s", line)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
godeps = nil
|
||||
return nil, err
|
||||
}
|
||||
return godeps, nil
|
||||
}
|
||||
|
||||
func (repo *Repo) GoSumFromPkgDir() (*GoDeps, error) {
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rver := repo.GetLastTag()
|
||||
if rver == "" {
|
||||
return nil, errors.New("could not get master version")
|
||||
}
|
||||
goget := repo.GetGoPath() + "@" + rver
|
||||
moddir := filepath.Join(homedir, "go/pkg/mod", repo.GetGoPath()+"@"+rver)
|
||||
|
||||
if !shell.IsDir(moddir) {
|
||||
cmd := []string{"go", "get", goget}
|
||||
repo.RunVerboseOnError(cmd)
|
||||
}
|
||||
|
||||
if !shell.IsDir(moddir) {
|
||||
return nil, errors.New("missing go/pkg/mod. Run: go get " + goget)
|
||||
}
|
||||
return GoSumParseDir(moddir)
|
||||
}
|
||||
|
||||
func (repo *Repo) GoSumFromRepo() (*GoDeps, error) {
|
||||
return GoSumParseDir(repo.GetFullPath())
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
syntax = "proto3";
|
||||
|
||||
// store go dependancies
|
||||
|
||||
package gitpb;
|
||||
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
// global settings for autogenpb `autogenpb:mutex`
|
||||
|
||||
message GoDep { // `autogenpb:nomutex`
|
||||
string hash = 1; // `autogenpb:unique` `autogenpb:sort` // md5sum/hash value from the go.sum file
|
||||
google.protobuf.Timestamp ctime = 2; // get the go date from 'go list' ?
|
||||
string version = 3; // v1.2.2
|
||||
string goPath = 4; // `autogenpb:unique` `autogenpb:sort` // "go.wit.com/lib/foo"
|
||||
string goVersion = 5; // version of golang the developer used to make this package version
|
||||
}
|
||||
|
||||
message GoDeps { // `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`
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// does processing on the go.mod and go.sum files
|
||||
|
||||
// checks to see if the go.sum and go.mod files exist
|
||||
// also check for a match with the repo.pb GoPrimitive bool
|
||||
// todo: check mtime
|
||||
func (repo *Repo) ValidGoSum() error {
|
||||
if repo.ParseGoSum() {
|
||||
return nil
|
||||
}
|
||||
log.Info("ValidGoSum() deprecated")
|
||||
return fmt.Errorf("ParseGoSum() failed")
|
||||
}
|
||||
|
||||
func (repo *Repo) GoDepsLen() int {
|
||||
if repo.GoDeps == nil {
|
||||
return 0
|
||||
}
|
||||
return len(repo.GoDeps.GoDeps)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package gitpb
|
||||
|
||||
//
|
||||
// DOES NOT MODIFY FILES
|
||||
//
|
||||
// only reads in the go.mod file. doesn't change anything
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// deprecate use of IsPrimitive() to this function
|
||||
// this assumes go.mod and go.sum are in a releasable state
|
||||
func (repo *Repo) setPrimitive() error {
|
||||
_, err := repo.computePrimitive()
|
||||
return err
|
||||
}
|
||||
|
||||
// Detect a 'Primitive' package. Sets the isPrimitive flag
|
||||
// will return true if the repo is truly not dependent on _anything_ else
|
||||
// like spew or lib/widget
|
||||
// it assumes go mod ran init and tidy ran without error
|
||||
func (repo *Repo) computePrimitive() (bool, error) {
|
||||
// go mod init & go mod tidy ran without errors
|
||||
log.Log(INFO, "isPrimitiveGoMod()", repo.FullPath)
|
||||
tmp := filepath.Join(repo.FullPath, "go.mod")
|
||||
gomod, err := os.Open(tmp)
|
||||
if err != nil {
|
||||
log.Log(INFO, "missing go.mod", repo.FullPath)
|
||||
return false, err
|
||||
}
|
||||
defer gomod.Close()
|
||||
|
||||
if repo.Exists("go.sum") {
|
||||
repo.GoInfo.GoPrimitive = false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(gomod)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
|
||||
parts := strings.Fields(line)
|
||||
log.Log(INFO, " gomod:", parts)
|
||||
if len(parts) >= 1 {
|
||||
log.Log(INFO, " gomod: part[0] =", parts[0])
|
||||
if parts[0] == "require" {
|
||||
log.Log(INFO, " should return false here")
|
||||
return false, errors.New("go.mod file is not primitive")
|
||||
}
|
||||
/*
|
||||
if parts[0] == "go" {
|
||||
if parts[1] != "1.21" {
|
||||
log.Log(WARN, "go not set to 1.21 for", repo.GetGoPath())
|
||||
// return false, errors.New("go not set to 1.21 for " + repo.GetGoPath())
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
repo.GoInfo.GoPrimitive = true
|
||||
repo.GoDeps = nil
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
var NOW *log.LogFlag
|
||||
var INFO *log.LogFlag
|
||||
var WARN *log.LogFlag
|
||||
|
||||
func init() {
|
||||
full := "go.wit.com/lib/protobuf/gitpb"
|
||||
short := "gitpb"
|
||||
|
||||
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")
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package gitpb
|
||||
|
||||
// todo: autogen this
|
||||
// functions to import and export the protobuf
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// human readable JSON
|
||||
func (r *Refs) FormatJSON() string {
|
||||
return protojson.Format(r)
|
||||
}
|
||||
|
||||
// apparently this isn't supposed to be used?
|
||||
// https://protobuf.dev/reference/go/faq/#unstable-text
|
||||
// this is a shame because this is much nicer output than JSON Format()
|
||||
func (r *Refs) FormatTEXT() string {
|
||||
return prototext.Format(r)
|
||||
}
|
||||
|
||||
// marshal json
|
||||
func (r *Refs) MarshalJSON() ([]byte, error) {
|
||||
return protojson.Marshal(r)
|
||||
}
|
||||
|
||||
// unmarshal
|
||||
func (r *Refs) UnmarshalJSON(data []byte) error {
|
||||
return protojson.Unmarshal(data, r)
|
||||
}
|
||||
|
||||
// marshal to wire
|
||||
func (r *Refs) Marshal() ([]byte, error) {
|
||||
return proto.Marshal(r)
|
||||
}
|
||||
|
||||
// unmarshal from wire
|
||||
func (r *Refs) Unmarshal(data []byte) error {
|
||||
return proto.Unmarshal(data, r)
|
||||
}
|
19
refs.proto
19
refs.proto
|
@ -1,19 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package gitpb;
|
||||
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
message Ref {
|
||||
string hash = 1; // git objectname
|
||||
google.protobuf.Timestamp ctime = 2; // git creatordate
|
||||
string refName = 3; // git refname
|
||||
string author = 4; // git author
|
||||
string subject = 5; // git subject
|
||||
}
|
||||
|
||||
message Refs {
|
||||
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 Ref refs = 3;
|
||||
}
|
180
refs.sort.go
180
refs.sort.go
|
@ -1,180 +0,0 @@
|
|||
package gitpb
|
||||
|
||||
// this is becoming a standard format
|
||||
// todo: autogenerate this from the .proto file?
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
sync "sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// bad global lock until I figure out some other plan
|
||||
var refslock sync.RWMutex
|
||||
|
||||
type RefIterator struct {
|
||||
sync.RWMutex
|
||||
|
||||
packs []*Ref
|
||||
index int
|
||||
}
|
||||
|
||||
// NewRefIterator initializes a new iterator.
|
||||
func NewRefIterator(packs []*Ref) *RefIterator {
|
||||
return &RefIterator{packs: packs}
|
||||
}
|
||||
|
||||
// Scan moves to the next element and returns false if there are no more packs.
|
||||
func (it *RefIterator) Scan() bool {
|
||||
if it.index >= len(it.packs) {
|
||||
return false
|
||||
}
|
||||
it.index++
|
||||
return true
|
||||
}
|
||||
|
||||
// Ref returns the current repo.
|
||||
func (it *RefIterator) Ref() *Ref {
|
||||
if it.packs[it.index-1] == nil {
|
||||
for i, d := range it.packs {
|
||||
fmt.Println("i =", i, d)
|
||||
}
|
||||
fmt.Println("len =", len(it.packs))
|
||||
fmt.Println("repo == nil", it.index, it.index-1)
|
||||
os.Exit(-1)
|
||||
}
|
||||
return it.packs[it.index-1]
|
||||
}
|
||||
|
||||
// Use Scan() in a loop, similar to a while loop
|
||||
//
|
||||
// for iterator.Scan() {
|
||||
// d := iterator.Ref()
|
||||
// fmt.Println("Ref UUID:", d.Uuid)
|
||||
// }
|
||||
|
||||
func (r *Refs) All() *RefIterator {
|
||||
repoPointers := r.selectAllRefs()
|
||||
|
||||
iterator := NewRefIterator(repoPointers)
|
||||
return iterator
|
||||
}
|
||||
|
||||
func (r *Refs) SortByName() *RefIterator {
|
||||
packs := r.selectAllRefs()
|
||||
|
||||
sort.Sort(ByName(packs))
|
||||
|
||||
iterator := NewRefIterator(packs)
|
||||
return iterator
|
||||
}
|
||||
|
||||
// enforces no duplicate package names
|
||||
func (r *Refs) Append(newP *Ref) bool {
|
||||
refslock.Lock()
|
||||
defer refslock.Unlock()
|
||||
|
||||
for _, p := range r.Refs {
|
||||
if p.RefName == newP.RefName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
r.Refs = append(r.Refs, newP)
|
||||
return true
|
||||
}
|
||||
|
||||
// returns time.Duration since last Update()
|
||||
func (r *Ref) Age(newP *Ref) time.Duration {
|
||||
t := time.Since(r.Ctime.AsTime())
|
||||
return t
|
||||
}
|
||||
|
||||
// find a package by name
|
||||
func (r *Refs) FindByName(name string) *Ref {
|
||||
refslock.RLock()
|
||||
defer refslock.RUnlock()
|
||||
|
||||
for _, p := range r.Refs {
|
||||
if p.RefName == name {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Refs) Len() int {
|
||||
refslock.RLock()
|
||||
defer refslock.RUnlock()
|
||||
|
||||
return len(r.Refs)
|
||||
}
|
||||
|
||||
type ByName []*Ref
|
||||
|
||||
func (a ByName) Len() int { return len(a) }
|
||||
func (a ByName) Less(i, j int) bool { return a[i].RefName < a[j].RefName }
|
||||
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// safely returns a slice of pointers to the Ref protobufs
|
||||
func (r *Refs) selectAllRefs() []*Ref {
|
||||
refslock.RLock()
|
||||
defer refslock.RUnlock()
|
||||
|
||||
// Create a new slice to hold pointers to each Ref
|
||||
var allPacks []*Ref
|
||||
allPacks = make([]*Ref, len(r.Refs))
|
||||
for i, p := range r.Refs {
|
||||
allPacks[i] = p // Copy pointers for safe iteration
|
||||
}
|
||||
|
||||
return allPacks
|
||||
}
|
||||
|
||||
func (all *Refs) DeleteByHash(hash string) *Ref {
|
||||
refslock.Lock()
|
||||
defer refslock.Unlock()
|
||||
|
||||
for i, _ := range all.Refs {
|
||||
if all.Refs[i].Hash == hash {
|
||||
all.Refs[i] = all.Refs[len(all.Refs)-1]
|
||||
all.Refs = all.Refs[:len(all.Refs)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (r *Refs) UnmergedRefRepos() *RefRepoIterator {
|
||||
repoPointers := r.selectUnmergedRefRepos()
|
||||
|
||||
sort.Sort(ByName(repoPointers))
|
||||
|
||||
iterator := NewRefRepoIterator(repoPointers)
|
||||
|
||||
return iterator
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// this sort doesn't really work. I think it forgets to sort the last two
|
||||
// todo: sort this out. literally
|
||||
// SelectRefPointers safely returns a slice of pointers to Ref records.
|
||||
func (r *Refs) selectUnmergedRefs() []*RefRow {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
// Create a new slice to hold pointers to each Ref
|
||||
// repoPointers := make([]*Ref, len(c.E.Refs))
|
||||
var repoPointers []*RefRow
|
||||
for _, repo := range me.allrepos {
|
||||
repoPointers = append(repoPointers, repo) // Copy pointers for safe iteration
|
||||
}
|
||||
|
||||
return repoPointers
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,140 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/config"
|
||||
"go.wit.com/log"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// sets a flag that the repos have changed
|
||||
// used later by applications on exit to test if
|
||||
// the protobuf needs to be written to disk
|
||||
func reposChanged(b bool) {
|
||||
config.SetChanged("repos", true)
|
||||
}
|
||||
|
||||
// returns true based on os.Stat() only checks
|
||||
// seems to kinda work ok. goal is to avoid os.Exec() here for speed
|
||||
// this might be the 1 place where libgit2 would be a good idea
|
||||
func (repo *Repo) HasChanged() bool {
|
||||
return repo.DidRepoChange()
|
||||
}
|
||||
|
||||
// does a fast check with os.Stat()
|
||||
// if the mtimes changed, does a full repo.ReloadForce()
|
||||
func (repo *Repo) ReloadCheck() error {
|
||||
if !repo.DidRepoChange() {
|
||||
return nil
|
||||
}
|
||||
reposChanged(true)
|
||||
err := repo.ReloadForce()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return log.Errorf("gitpb.ReloadCheck() detected a change in the repo")
|
||||
}
|
||||
|
||||
// TODO: clean this up more, but it is working now more or less
|
||||
func (repo *Repo) ReloadForce() error {
|
||||
reposChanged(true)
|
||||
// sometimes, on new repos, if .git/HEAD does not exist
|
||||
// defective git daemons or badly configured repos, 'git clone' can fail
|
||||
// if so, 'git fetch origin' can repair the state
|
||||
if !repo.Exists(".git/HEAD") {
|
||||
cmd := []string{"git", "fetch", "origin"}
|
||||
repo.RunVerbose(cmd)
|
||||
cmd = []string{"git", "checkout", "main"} // todo: figure out main
|
||||
repo.RunVerbose(cmd)
|
||||
}
|
||||
// log.Info("in reload", repo.FullPath)
|
||||
repo.Tags = new(GitTags)
|
||||
repo.reloadGitTags()
|
||||
|
||||
repo.GoDeps = new(GoDeps)
|
||||
if repo.GoInfo == nil {
|
||||
repo.GoInfo = new(GoInfo)
|
||||
}
|
||||
repo.ParseGoSum() // also sets GoPrimitive
|
||||
repo.reloadVersions()
|
||||
repo.setRepoType()
|
||||
|
||||
// this is probably a good place & time to store these
|
||||
repo.reloadMtimes()
|
||||
|
||||
repo.CheckDirty()
|
||||
repo.setRepoState()
|
||||
|
||||
if repo.GitConfig == nil {
|
||||
if err := repo.updateGitConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName())
|
||||
repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName())
|
||||
|
||||
// LastUpdate should always be the newest time
|
||||
repo.Times.LastUpdate = timestamppb.New(time.Now())
|
||||
repo.ValidateUTF8()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repo) VerifyRemoteAndLocalBranches(bname string) bool {
|
||||
if !repo.IsBranchRemote(bname) {
|
||||
return true
|
||||
}
|
||||
lh := repo.GetLocalHash(bname)
|
||||
rh := repo.GetRemoteHash(bname)
|
||||
if lh == rh {
|
||||
// log.Info(r.FullPath, "local devel == remote devel", lh, rh)
|
||||
return true
|
||||
} else {
|
||||
log.Info(lh, rh, "local != remote", repo.FullPath, bname)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (repo *Repo) SetDevelBranchName(bname string) {
|
||||
repo.DevelBranchName = bname
|
||||
}
|
||||
|
||||
func (repo *Repo) SetUserBranchName(bname string) {
|
||||
repo.UserBranchName = bname
|
||||
}
|
||||
|
||||
// updates LastTag by age
|
||||
func (repo *Repo) setLastTag() {
|
||||
repo.LastTag = repo.FindLastTag()
|
||||
}
|
||||
|
||||
func (repo *Repo) setCurrentBranchName() {
|
||||
repo.CurrentBranchName = ""
|
||||
r, err := repo.RunQuiet([]string{"git", "branch", "--show-current"})
|
||||
output := strings.Join(r.Stdout, "\n")
|
||||
if err != nil {
|
||||
log.Log(WARN, "GetCurrentBranchName() not in a git repo?", err, repo.GetGoPath())
|
||||
log.Log(WARN, "GetCurrentBranchName() output might have worked anyway:", output)
|
||||
}
|
||||
repo.CurrentBranchName = strings.TrimSpace(output)
|
||||
}
|
||||
|
||||
// always spawns 'git' and always should spawn 'git'
|
||||
func (repo *Repo) setCurrentBranchVersion() {
|
||||
repo.CurrentBranchVersion = ""
|
||||
if repo == nil {
|
||||
log.Info("repo.GetCurrentBranchVersion() repo == nil")
|
||||
return
|
||||
}
|
||||
r, err := repo.RunQuiet([]string{"git", "describe", "--tags"})
|
||||
output := strings.Join(r.Stdout, "\n")
|
||||
if err != nil {
|
||||
// log.Log(WARN, "GetCurrentBranchVersion() not in a git repo?", err, repo.GetGoPath())
|
||||
// log.Log(WARN, "GetCurrentBranchVersion() output might have worked anyway:", output)
|
||||
repo.CurrentBranchVersion = "gitpb err"
|
||||
return
|
||||
}
|
||||
repo.CurrentBranchVersion = strings.TrimSpace(output)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package gitpb
|
||||
|
||||
// runs git, parses output
|
||||
// types faster than you can
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (repo *Repo) NoteChange(s string) {
|
||||
log.Warn("NoteChange()", s)
|
||||
}
|
||||
|
||||
// just return the current value
|
||||
func (repo *Repo) IsDirty() bool {
|
||||
return repo.Dirty
|
||||
}
|
||||
|
||||
// actually os.Exec('git')
|
||||
func (repo *Repo) CheckDirty() bool {
|
||||
cmd := []string{"git", "status", "--porcelain"}
|
||||
r := shell.PathRunLog(repo.FullPath, cmd, INFO)
|
||||
if r.Error != nil {
|
||||
log.Warn("CheckDirty() status cmd =", cmd)
|
||||
out := strings.Join(r.Stdout, "\n")
|
||||
log.Warn("CheckDirty() status out =", out)
|
||||
log.Warn("CheckDirty() status err =", r.Error)
|
||||
log.Error(r.Error, "CheckDirty() git status error")
|
||||
repo.NoteChange("git status is in error " + fmt.Sprint(r.Error))
|
||||
repo.Dirty = true
|
||||
repo.State = "dirty"
|
||||
return true
|
||||
}
|
||||
|
||||
// dirty if anything but go.mod and go.sum
|
||||
var bad bool = false
|
||||
for _, line := range r.Stdout {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) == 2 {
|
||||
switch parts[1] {
|
||||
case "go.mod":
|
||||
case "go.sum":
|
||||
default:
|
||||
bad = true
|
||||
}
|
||||
} else {
|
||||
bad = true
|
||||
}
|
||||
}
|
||||
repo.DirtyList = r.Stdout
|
||||
|
||||
pbnow := timestamppb.New(time.Now())
|
||||
repo.Times.LastDirty = pbnow
|
||||
repo.Dirty = bad
|
||||
if bad {
|
||||
repo.State = "dirty"
|
||||
}
|
||||
return bad
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
func (repo *Repo) isTracked(file string) (bool, error) {
|
||||
cmd := []string{"git", "ls-files", "--error-unmatch", file}
|
||||
result := repo.Run(cmd)
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
if result.Exit != 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (repo *Repo) isIgnored(file string) (bool, error) {
|
||||
cmd := []string{"git", "check-ignore", "-q", file}
|
||||
result := repo.Run(cmd)
|
||||
if result.Error != nil {
|
||||
return false, result.Error
|
||||
}
|
||||
if result.Exit == 0 {
|
||||
// exit with 0 means the file is ignored
|
||||
return true, nil
|
||||
}
|
||||
// non-zero exit means the file is not ignored
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// is it a good idea to run go-mod-clean in this repo?
|
||||
// for now, check if this repo should be ignored
|
||||
// TODO: go.mod and go.sum should be moved to git tag metadata
|
||||
func (repo *Repo) RepoIgnoresGoMod() error {
|
||||
repo.GoInfo.GitIgnoresGoSum = false
|
||||
file := "go.mod"
|
||||
if tracked, err := repo.isTracked(file); err != nil {
|
||||
msg := fmt.Sprintf("%s Error checking if %s tracked: %v\n", repo.GetGoPath(), file, err)
|
||||
log.Info("gitpb:", msg)
|
||||
return err
|
||||
} else {
|
||||
if tracked {
|
||||
msg := fmt.Sprintf("%s %s is tracked by Git.\n", repo.GetGoPath(), file)
|
||||
log.Info("gitpb:", msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
}
|
||||
|
||||
if ignored, err := repo.isIgnored(file); err != nil {
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s Error checking if ignored: %v\n", repo.GetGoPath(), err)
|
||||
log.Info("gitpb:", msg)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if ignored {
|
||||
// fmt.Printf("%s %s is ignored by Git.\n", repo.GetGoPath(), file)
|
||||
repo.GoInfo.GitIgnoresGoSum = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
msg := fmt.Sprintf("%s %s is neither tracked nor ignored by Git.\n", repo.GetGoPath(), file)
|
||||
// this means, if you make a go.mod file, it'll add it to the repo to be tracked
|
||||
// so you need to either add it to .gitignore (this is what should happen)
|
||||
// or accept you want an auto-generated file to put endless garbage in your git repo
|
||||
// this obviously exposes my opinion on this subject matter
|
||||
log.Info("gitpb:", msg)
|
||||
return errors.New(msg)
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
package gitpb
|
||||
|
||||
// An app to submit patches for the 30 GO GUI repos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func (repo *Repo) Mtime(fname string) *time.Time {
|
||||
var fileTime *time.Time
|
||||
tmp, err := repo.oldMtime(fname)
|
||||
fileTime = &tmp
|
||||
if err != nil {
|
||||
log.Info("MTime got err", err)
|
||||
return nil
|
||||
}
|
||||
return fileTime
|
||||
}
|
||||
|
||||
func (repo *Repo) changedDir() bool {
|
||||
fname := ".git"
|
||||
fileTime := repo.Mtime(fname)
|
||||
if fileTime == nil {
|
||||
// .git doesn't exist. something is wrong. rescan this repo
|
||||
return true
|
||||
}
|
||||
mtime := timestamppb.New(*fileTime)
|
||||
pbtime := repo.Times.MtimeDir
|
||||
if pbtime == nil { // this can happen?
|
||||
repo.Times.MtimeDir = mtime
|
||||
return true
|
||||
}
|
||||
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
|
||||
return false
|
||||
}
|
||||
dur := mtime.AsTime().Sub(pbtime.AsTime())
|
||||
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
|
||||
repo.Times.MtimeDir = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) didFileChange(fname string, pbtime *timestamppb.Timestamp) bool {
|
||||
fileTime := repo.Mtime(fname)
|
||||
if fileTime == nil {
|
||||
repo.StateChange = fmt.Sprintf("%s missing", fname)
|
||||
return true
|
||||
}
|
||||
mtime := timestamppb.New(*fileTime)
|
||||
if pbtime == nil {
|
||||
repo.StateChange = fmt.Sprintf("%s mtime never recorded", fname)
|
||||
return true
|
||||
}
|
||||
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
|
||||
// it's the same!
|
||||
return false
|
||||
}
|
||||
dur := mtime.AsTime().Sub(pbtime.AsTime())
|
||||
repo.StateChange = fmt.Sprintf("%s mtime changed %s", fname, shell.FormatDuration(dur))
|
||||
// need to reload from the filesystem
|
||||
return true
|
||||
}
|
||||
|
||||
// boo. I'm not good at golang. this should use reflect. I'm bad. my code is bad. boo this man. you're cool, I'm outta here
|
||||
// make this work right someday
|
||||
func (repo *Repo) updateMtime(fname string, pbname string) bool {
|
||||
fileTime := repo.Mtime(fname)
|
||||
if fileTime == nil {
|
||||
// .git/HEAD doesn't exist. something is wrong. rescan this repo
|
||||
return true
|
||||
}
|
||||
mtime := timestamppb.New(*fileTime)
|
||||
pbtime := repo.Times.MtimeHead
|
||||
if pbtime == nil { // this can happen?
|
||||
repo.Times.MtimeHead = mtime
|
||||
return true
|
||||
}
|
||||
switch pbname {
|
||||
case "MtimeHead":
|
||||
if pbtime == nil { // this can happen?
|
||||
repo.Times.MtimeHead = mtime
|
||||
return true
|
||||
}
|
||||
default:
|
||||
}
|
||||
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
|
||||
return false
|
||||
}
|
||||
dur := mtime.AsTime().Sub(pbtime.AsTime())
|
||||
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
|
||||
repo.Times.MtimeHead = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) changedHead() bool {
|
||||
fname := ".git/HEAD"
|
||||
fileTime := repo.Mtime(fname)
|
||||
if fileTime == nil {
|
||||
// .git/HEAD doesn't exist. something is wrong. rescan this repo
|
||||
return true
|
||||
}
|
||||
mtime := timestamppb.New(*fileTime)
|
||||
pbtime := repo.Times.MtimeHead
|
||||
if pbtime == nil { // this can happen?
|
||||
repo.Times.MtimeHead = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
|
||||
return false
|
||||
}
|
||||
dur := mtime.AsTime().Sub(pbtime.AsTime())
|
||||
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
|
||||
repo.Times.MtimeHead = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
// check the mtime of the .git/config file
|
||||
func (repo *Repo) changedConfig() bool {
|
||||
fname := ".git/config"
|
||||
fileTime := repo.Mtime(fname)
|
||||
if fileTime == nil {
|
||||
// .git/config doesn't exist. something is wrong!
|
||||
log.Info("gitpb .git/config is missing", repo.GetGoPath())
|
||||
return false
|
||||
}
|
||||
mtime := timestamppb.New(*fileTime)
|
||||
pbtime := repo.Times.MtimeConfig
|
||||
if pbtime == nil { // this can happen?
|
||||
repo.Times.MtimeConfig = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
|
||||
return false
|
||||
}
|
||||
dur := mtime.AsTime().Sub(pbtime.AsTime())
|
||||
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
|
||||
repo.Times.MtimeConfig = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) changedIndex() bool {
|
||||
fname := ".git/index"
|
||||
fileTime := repo.Mtime(fname)
|
||||
if fileTime == nil {
|
||||
// .git/index doesn't exist. something is wrong. rescan this repo
|
||||
return true
|
||||
}
|
||||
mtime := timestamppb.New(*fileTime)
|
||||
pbtime := repo.Times.MtimeIndex
|
||||
if pbtime == nil { // this can happen?
|
||||
repo.Times.MtimeIndex = mtime
|
||||
return true
|
||||
}
|
||||
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
|
||||
return false
|
||||
}
|
||||
dur := mtime.AsTime().Sub(pbtime.AsTime())
|
||||
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
|
||||
repo.Times.MtimeIndex = mtime
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) reloadMtimes() bool {
|
||||
var changed bool
|
||||
if repo.Times == nil {
|
||||
repo.Times = new(GitTimes)
|
||||
log.Info(repo.FullPath, "repo.Times were nil")
|
||||
}
|
||||
|
||||
if repo.changedHead() {
|
||||
changed = true
|
||||
}
|
||||
if repo.changedIndex() {
|
||||
changed = true
|
||||
}
|
||||
if repo.changedConfig() {
|
||||
changed = true
|
||||
}
|
||||
if repo.changedDir() {
|
||||
// changed = true
|
||||
}
|
||||
|
||||
return changed
|
||||
}
|
||||
|
||||
func (repo *Repo) DidRepoChange() bool {
|
||||
if repo.Times == nil {
|
||||
repo.Times = new(GitTimes)
|
||||
}
|
||||
if repo.didFileChange(".git/HEAD", repo.Times.MtimeHead) {
|
||||
return true
|
||||
}
|
||||
if repo.didFileChange(".git/index", repo.Times.MtimeIndex) {
|
||||
return true
|
||||
}
|
||||
if repo.didFileChange(".git/config", repo.Times.MtimeConfig) {
|
||||
return true
|
||||
}
|
||||
if repo.didFileChange(".git", repo.Times.MtimeDir) {
|
||||
// todo: do something with CheckDirty()
|
||||
// return true
|
||||
}
|
||||
if repo.Times.LastUpdate == nil {
|
||||
log.Info("repo.Reload() was never run")
|
||||
return true
|
||||
} else {
|
||||
if repo.Times.LastUpdate.Seconds < repo.Times.MtimeHead.Seconds {
|
||||
log.Info("SHOULD RUN Reload() here", repo.Times.MtimeHead.Seconds-repo.Times.LastUpdate.Seconds, "secs diff")
|
||||
return true
|
||||
}
|
||||
}
|
||||
// log.Info("DidRepoChange() is false", repo.FullPath)
|
||||
return false
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package gitpb
|
||||
|
||||
// does processing on the go.mod and go.sum files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// TODO: this needs to be redone in a smarter way
|
||||
// to identify which repos have things to build in them
|
||||
func (repo *Repo) GetRepoType() string {
|
||||
if repo == nil {
|
||||
return "nil"
|
||||
}
|
||||
if repo.GoInfo == nil {
|
||||
log.Warn("gitpb.RepoType() plugin was not set correctly")
|
||||
log.Warn("gitpb.RepoType() plugin was not set correctly")
|
||||
log.Warn("gitpb.RepoType() plugin was not set correctly")
|
||||
repo.GoInfo = new(GoInfo)
|
||||
repo.setRepoType()
|
||||
}
|
||||
|
||||
if repo.GoInfo.GoPlugin {
|
||||
return "plugin"
|
||||
}
|
||||
if repo.GoInfo.GoBinary {
|
||||
if repo.Exists(".plugin") {
|
||||
log.Warn("gitpb.RepoType() plugin was not set correctly")
|
||||
repo.GoInfo.GoPlugin = true
|
||||
return "plugin"
|
||||
}
|
||||
return "binary"
|
||||
}
|
||||
// binary should always take precidence over libraries that are protobuf's
|
||||
if repo.GoInfo.GoProtobuf {
|
||||
return "protobuf"
|
||||
}
|
||||
if repo.GoInfo.GoLibrary {
|
||||
return "library"
|
||||
}
|
||||
// todo: figure out what to do here. for now, binary is easiest
|
||||
return "library"
|
||||
}
|
||||
|
||||
func (repo *Repo) setRepoType() {
|
||||
if repo == nil {
|
||||
return
|
||||
}
|
||||
if repo.Exists(".plugin") {
|
||||
repo.GoInfo.GoPlugin = true
|
||||
}
|
||||
if ok, _, _ := repo.IsProtobuf(); ok {
|
||||
repo.GoInfo.GoProtobuf = true
|
||||
}
|
||||
switch repo.goListRepoType() {
|
||||
case "binary":
|
||||
repo.GoInfo.GoBinary = true
|
||||
return
|
||||
case "library":
|
||||
repo.GoInfo.GoLibrary = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (repo *Repo) goListRepoType() string {
|
||||
os.Setenv("GO111MODULE", "off")
|
||||
cmd := []string{"go", "list", "-f", "'{{if eq .Name \"main\"}}binary{{else}}library{{end}}'"}
|
||||
// cmd := []string{"go", "list", "-f", "'{{.Name}}'"} // probably use this. this just prints out the package name
|
||||
// cmd := []string{"go", "list", "-f", "'{{.ImportPath}}'"} // returns go.wit.com/lib/protobuf/gitpb
|
||||
|
||||
result, err := repo.RunQuiet(cmd)
|
||||
if err != nil {
|
||||
// log.Info("go list binary detect failed", err)
|
||||
return ""
|
||||
}
|
||||
output := strings.TrimSpace(strings.Join(result.Stdout, "\n"))
|
||||
output = strings.Trim(output, "'")
|
||||
return output
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// redo this. use go-git2 ?
|
||||
func (repo *Repo) allCommits() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// this is dumb
|
||||
func (repo *Repo) AllCommits() error {
|
||||
// tags := []string{"cd", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
|
||||
// format := strings.Join(tags, "_,,,_%")
|
||||
|
||||
cmd := []string{"git", "log", "--format=%cd"}
|
||||
result := shell.PathRunQuiet(repo.FullPath, cmd)
|
||||
if result.Error != nil {
|
||||
log.Warn("git for-each-ref error:", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
newest := strings.Join(result.Stdout, "\n")
|
||||
newest = strings.TrimSpace(newest)
|
||||
tmp := getGitDateStamp(newest)
|
||||
repo.Times.NewestCommit = timestamppb.New(tmp)
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
// reload the tags
|
||||
func (repo *Repo) reloadGitTags() error {
|
||||
// todo: look for changes in the tags?
|
||||
repo.Tags = new(GitTags)
|
||||
|
||||
tags := []string{"%(objectname)", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
|
||||
format := strings.Join(tags, "_,,,_")
|
||||
cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format", format}
|
||||
// log.Info("RUNNING:", strings.Join(cmd, " "))
|
||||
result := shell.PathRunQuiet(repo.FullPath, cmd)
|
||||
if result.Error != nil {
|
||||
log.Warn("git for-each-ref error:", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
lines := result.Stdout
|
||||
// reverse the git order
|
||||
slices.Reverse(lines)
|
||||
|
||||
var refname string
|
||||
var hash string
|
||||
var subject string
|
||||
var ctime *timestamppb.Timestamp
|
||||
var atime *timestamppb.Timestamp
|
||||
|
||||
for i, line := range lines {
|
||||
var parts []string
|
||||
parts = make([]string, 0)
|
||||
parts = strings.Split(line, "_,,,_")
|
||||
if len(parts) != 5 {
|
||||
log.Info("tag error:", i, parts)
|
||||
continue
|
||||
}
|
||||
hash = parts[0]
|
||||
if parts[1] != "" {
|
||||
tmp := getGitDateStamp(parts[1])
|
||||
ctime = timestamppb.New(tmp)
|
||||
}
|
||||
if parts[2] != "" {
|
||||
tmp := getGitDateStamp(parts[2])
|
||||
atime = timestamppb.New(tmp)
|
||||
}
|
||||
refname = parts[3]
|
||||
subject = parts[4]
|
||||
|
||||
newr := GitTag{
|
||||
Refname: refname,
|
||||
Hash: hash,
|
||||
Subject: subject,
|
||||
Creatordate: ctime,
|
||||
Authordate: atime,
|
||||
}
|
||||
|
||||
repo.Tags.Append(&newr)
|
||||
}
|
||||
|
||||
// GIT_COMMITTER_DATE="$(git log -1 --format=%cI)" \
|
||||
// GIT_AUTHOR_DATE="$(git log -1 --format=%aI)" \
|
||||
// git am --committer-date-is-author-date < patch.mbox
|
||||
|
||||
// good format for insuring the hashs are identical when using git am
|
||||
// git log -1 --format="%H %aI %cI %an %ae %cn %ce"
|
||||
// also set the repo.NewestCommit
|
||||
|
||||
cmd = []string{"git", "log", "-1", "--format=%cd"}
|
||||
result = shell.PathRunQuiet(repo.FullPath, cmd)
|
||||
if result.Error != nil {
|
||||
log.Warn("git for-each-ref error:", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
newest := strings.Join(result.Stdout, "\n")
|
||||
newest = strings.TrimSpace(newest)
|
||||
tmp := getGitDateStamp(newest)
|
||||
repo.Times.NewestCommit = timestamppb.New(tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// attempt to parse "2024-12-13 15:39:57 -0600"
|
||||
func parseGitDate(dateString string) time.Time {
|
||||
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
|
||||
const gitLayout = "2006-01-02 15:04:05 -0600"
|
||||
tagTime, err := time.Parse(gitLayout, dateString)
|
||||
if err != nil {
|
||||
const gitLayout2 = "2006-01-02 15:04:05 +0600"
|
||||
tagTime, err = time.Parse(gitLayout2, dateString)
|
||||
}
|
||||
if err != nil {
|
||||
log.Warn("GOT THIS IN PARSE AAA." + dateString + ".AAA")
|
||||
log.Warn(err)
|
||||
return time.Now()
|
||||
}
|
||||
return tagTime
|
||||
}
|
||||
|
||||
// attempt to parse strict ISO 8601 format // 2025-01-07T21:22:16-06:00
|
||||
func parseDateRFC3339(dateString string) time.Time {
|
||||
tagTime, err := time.Parse(time.RFC3339, dateString)
|
||||
if err != nil {
|
||||
log.Warn("GOT THIS IN PARSE AAA." + dateString + ".AAA")
|
||||
log.Warn(err)
|
||||
return time.Now()
|
||||
}
|
||||
return tagTime
|
||||
}
|
||||
|
||||
// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600"
|
||||
func getGitDateStamp(gitdefault string) time.Time {
|
||||
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
|
||||
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
|
||||
tagTime, err := time.Parse(gitLayout, gitdefault)
|
||||
if err != nil {
|
||||
log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA")
|
||||
log.Warn(err)
|
||||
return time.Now()
|
||||
}
|
||||
return tagTime
|
||||
}
|
||||
|
||||
func (tag *GitTag) GetAge() time.Duration {
|
||||
return time.Since(tag.GetAuthordate().AsTime())
|
||||
}
|
||||
|
||||
func (repo *Repo) NewestTag() *GitTag {
|
||||
loop := repo.Tags.SortByAge()
|
||||
for loop.Scan() {
|
||||
r := loop.Next()
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// this should just do is.Exists(".git/refs/heads/findname")
|
||||
// this is a simple check and doesn't work all the time
|
||||
func (repo *Repo) LocalTagExists(findname string) bool {
|
||||
fname := filepath.Join(".git/refs/heads", findname)
|
||||
if repo.Exists(fname) {
|
||||
return true
|
||||
}
|
||||
fname = filepath.Join(".git/refs/tags", findname)
|
||||
if repo.Exists(fname) {
|
||||
return true
|
||||
}
|
||||
/*
|
||||
loop := repo.Tags.SortByRefname()
|
||||
for loop.Scan() {
|
||||
ref := loop.Next()
|
||||
// log.Info(repo.GoPath, ref.Refname)
|
||||
if strings.HasPrefix(ref.Refname, "refs/remotes") {
|
||||
continue
|
||||
}
|
||||
tagname := filepath.Base(ref.Refname)
|
||||
// log.Info("tag:", path, tagname, "from", repo.GoPath)
|
||||
if tagname == findname {
|
||||
// log.Info("found tag:", path, tagname, "from", repo.GoPath)
|
||||
return true
|
||||
}
|
||||
}
|
||||
*/
|
||||
return false
|
||||
}
|
||||
|
||||
// returns true if 'taggy' is _ONLY_ a local tag
|
||||
// this means you can not do a git pull or git push on it
|
||||
func (repo *Repo) IsOnlyLocalTag(taggy string) bool {
|
||||
// first make sure the tag is actually even local
|
||||
if !repo.LocalTagExists(taggy) {
|
||||
// this means it's not even local now.
|
||||
return false
|
||||
}
|
||||
// okay, taggy exists, does it exist in a remote repo?
|
||||
loop := repo.Tags.SortByRefname()
|
||||
for loop.Scan() {
|
||||
ref := loop.Next()
|
||||
tagname := ref.Refname
|
||||
if strings.HasPrefix(tagname, "refs/remotes") {
|
||||
path, filename := filepath.Split(tagname)
|
||||
if filename == taggy {
|
||||
log.Log(INFO, "found tag:", path, filename, "from", repo.GetGoPath())
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// we couldn't find the local tag anywhere remote, so it's probably only local
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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
|
||||
//
|
||||
// this are made from protoc / proto-gen-go
|
||||
// these packages also use go.wit.com/apps/autogenpb
|
||||
//
|
||||
// errors() if a .proto file does not have an autogenerated .pb.go file
|
||||
func (repo *Repo) IsProtobuf() (bool, []string, error) {
|
||||
fullp, fullc, err := scanForProtobuf(repo.FullPath)
|
||||
protos := make(map[string]string)
|
||||
protoc := make(map[string]string)
|
||||
var anyfound bool = false
|
||||
for _, s := range fullp {
|
||||
filebase := filepath.Base(s)
|
||||
name := strings.TrimSuffix(filebase, ".proto")
|
||||
anyfound = true
|
||||
protos[name] = s
|
||||
}
|
||||
// make sure every .proto file has a .pb.go file
|
||||
for pname, _ := range protos {
|
||||
var found bool = false
|
||||
for _, s := range fullc {
|
||||
cfilebase := filepath.Base(s)
|
||||
cname := strings.TrimSuffix(cfilebase, ".pb.go")
|
||||
protoc[cname] = s
|
||||
if cname == pname {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
// log.Info("found ok")
|
||||
} else {
|
||||
// log.Info("gitpb: IsProtobuf() missing compiled proto file:", pname+".pb.go")
|
||||
err = errors.New("compiled file " + pname + ".pb.go missing")
|
||||
}
|
||||
}
|
||||
|
||||
// assume every .pb.go file is autogenerated
|
||||
var allc []string
|
||||
for _, testf := range fullc {
|
||||
if strings.HasSuffix(testf, ".pb.go") {
|
||||
basef := filepath.Base(testf)
|
||||
allc = append(allc, basef) // no, not the 3.5" floppy disks
|
||||
}
|
||||
}
|
||||
return anyfound, allc, err
|
||||
}
|
||||
|
||||
// look for any proto files. do not enter directories
|
||||
// note: good golang libraries are best done in a single directory
|
||||
func scanForProtobuf(srcDir string) ([]string, []string, error) {
|
||||
var protofiles []string
|
||||
var compiled []string
|
||||
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Log(WARN, "Error accessing path:", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// ignore the start dir
|
||||
if srcDir == path {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".proto") {
|
||||
//
|
||||
protofiles = append(protofiles, path)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".pb.go") {
|
||||
compiled = append(compiled, path)
|
||||
}
|
||||
|
||||
// don't go into any directories
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return protofiles, compiled, err
|
||||
}
|
||||
|
||||
func (repo *Repo) GetProtoFiles() ([]string, error) {
|
||||
var protofiles []string
|
||||
srcDir := repo.GetFullPath()
|
||||
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Log(WARN, "Error accessing path:", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// ignore the start dir
|
||||
if srcDir == path {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".proto") {
|
||||
//
|
||||
protofiles = append(protofiles, path)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return protofiles, err
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// scans in a new git repo. If it detects the repo is a golang project,
|
||||
// then it parses the go.mod/go.sum files
|
||||
// TODO: try adding python, rails, perl, rust, other language things?
|
||||
// I probably will never have time to try that, but I'd take patches for anyone
|
||||
// that might see this note and feel so inclined.
|
||||
// todo: use Repos.Lock() ?
|
||||
func (all *Repos) NewGoRepo(fullpath string, gopath string) (*Repo, error) {
|
||||
if gopath == "" {
|
||||
return nil, errors.New("blank gopath")
|
||||
}
|
||||
if r := all.FindByFullPath(fullpath); r != nil {
|
||||
log.Info("gitpb.NewGoPath() already has gopath", r.GetGoPath())
|
||||
log.Info("gitpb.NewGoPath() already has FullPath", r.FullPath)
|
||||
// already had this gopath
|
||||
return r, errors.New("gitpb.NewGoPath() duplicate gopath " + gopath)
|
||||
}
|
||||
|
||||
// add a new one here
|
||||
newr := Repo{
|
||||
FullPath: fullpath,
|
||||
Namespace: gopath,
|
||||
}
|
||||
newr.Times = new(GitTimes)
|
||||
|
||||
newr.GoInfo = new(GoInfo)
|
||||
newr.GoInfo.GoPath = gopath
|
||||
// everything happens in here
|
||||
newr.ReloadForce()
|
||||
|
||||
newr.ValidateUTF8()
|
||||
if all.AppendByFullPath(&newr) {
|
||||
// worked
|
||||
return &newr, nil
|
||||
} else {
|
||||
// this is dumb, probably never happens. todo: use Repos.Lock()
|
||||
if r := all.FindByFullPath(fullpath); r != nil {
|
||||
// already had this gopath
|
||||
return r, errors.New("gitpb.NewGoPath() AppendUnique() failed but Find() worked" + gopath)
|
||||
}
|
||||
}
|
||||
// todo: use Repos.Lock()
|
||||
return nil, errors.New("repo gitpb.NewGoPath() should never have gotten here " + gopath)
|
||||
}
|
||||
|
||||
// enforces GoPath is unique
|
||||
// TODO: deprecate this (?)
|
||||
// mutex's should finally work in the autogenpb protobuf code
|
||||
/*
|
||||
func (all *Repos) AppendByGoPathOld(newr *Repo) bool {
|
||||
// all.RLock()
|
||||
repoMu.RLock()
|
||||
|
||||
for _, r := range all.Repos {
|
||||
if r.GoInfo.GoPath == newr.GoInfo.GoPath {
|
||||
// all.RUnlock()
|
||||
repoMu.RUnlock()
|
||||
return false
|
||||
}
|
||||
}
|
||||
// all.RUnlock()
|
||||
repoMu.RUnlock()
|
||||
|
||||
all.Append(newr)
|
||||
return true
|
||||
}
|
||||
*/
|
||||
|
||||
func (all *Repos) NewRepo(fullpath string, namespace string) (*Repo, error) {
|
||||
if r := all.FindByFullPath(fullpath); r != nil {
|
||||
log.Info("gitpb.NewRepo() might already have namespace", r.GetNamespace())
|
||||
log.Info("gitpb.NewRepo() already has FullPath", r.FullPath)
|
||||
// already had this gopath
|
||||
return r, errors.New("gitpb.NewRepo() duplicate path " + fullpath)
|
||||
}
|
||||
|
||||
// add a new one here
|
||||
newr := Repo{
|
||||
FullPath: fullpath,
|
||||
Namespace: namespace,
|
||||
}
|
||||
newr.Times = new(GitTimes)
|
||||
|
||||
// everything happens in here
|
||||
newr.ReloadForce()
|
||||
|
||||
newr.ValidateUTF8()
|
||||
if all.AppendByFullPath(&newr) {
|
||||
// worked
|
||||
return &newr, nil
|
||||
}
|
||||
|
||||
// todo: use Repos.Lock()
|
||||
return nil, errors.New("gitpb.NewRepo() append failed " + fullpath)
|
||||
}
|
||||
|
||||
func 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
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package gitpb;
|
||||
|
||||
// stores information about git repos
|
||||
// If the project is in golang, also gets the go language dependacies
|
||||
|
||||
import "gitTag.proto";
|
||||
import "goDep.proto";
|
||||
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
|
||||
|
||||
// global settings for autogenpb `autogenpb:mutex`
|
||||
|
||||
// should it be done this way?
|
||||
|
||||
message GitTimes { // `autogenpb:nomutex`
|
||||
google.protobuf.Timestamp lastPull = 1; // last time a git pull was done
|
||||
google.protobuf.Timestamp lastUpdate = 2; // when was ReloadGit() last done
|
||||
google.protobuf.Timestamp lastDirty = 3; // last time CheckDirty() was run
|
||||
google.protobuf.Timestamp mtimeDir = 4; // mtime for ./git // maybe useful to track
|
||||
google.protobuf.Timestamp mtimeHead = 5; // mtime for ./git/HEAD // these two mtimes allow really fast checks to see if git has changed
|
||||
google.protobuf.Timestamp mtimeIndex = 6; // mtime for ./git/HEAD // probably always in sync with HEAD
|
||||
google.protobuf.Timestamp mtimeFetch = 7; // mtime for ./git/FETCH_HEAD // last time 'git fetch' or 'git pull' was run on current branch?
|
||||
google.protobuf.Timestamp lastGoDep = 8; // mtime for last go.sum scan
|
||||
google.protobuf.Timestamp newestCommit = 9; // when the newest commit was
|
||||
google.protobuf.Timestamp mtimeConfig = 10; // mtime for the .git/config file
|
||||
}
|
||||
|
||||
// this is probably better. think about moving to this instead
|
||||
message GoInfo { // `autogenpb:nomutex`
|
||||
string goPath = 1; // the logical path as used by golang: 'go.wit.com/apps/helloworld'
|
||||
string desc = 2; // what is this repo?
|
||||
bool goLibrary = 3; // is this a golang library?
|
||||
bool goBinary = 4; // is this a golang binary?
|
||||
bool goPrimitive = 5; // if this is a golang primitive (only has go.mod)
|
||||
bool goPlugin = 6; // is this a golang plugin?
|
||||
bool goProtobuf = 7; // autogen go files from .proto
|
||||
GoDeps goDeps = 8; // what is in the go.sum file
|
||||
GoDeps published = 9; // the last published go.mod/go.sum
|
||||
bytes goMod = 10; // the last go.mod file
|
||||
bytes goSum = 11; // the last go.sum file
|
||||
bool gitIgnoresGoSum = 12; // does .gitignore ignore go.mod & go.sum?
|
||||
}
|
||||
|
||||
message Repo { // `autogenpb:marshal` `autogenpb:nomutex`
|
||||
string namespace = 1; // `autogenpb:unique` `autogenpb:sort` // this repo is 'go.wit.com/lib/protobuf/gitpb'
|
||||
string fullPath = 2; // `autogenpb:unique` `autogenpb:sort` // the OS path to the .git directory: '/home/devel/golang.org/x/tools'
|
||||
string masterBranchName = 3; // git 'main' or 'master' branch name
|
||||
string develBranchName = 4; // whatever the git 'devel' branch name is
|
||||
string userBranchName = 5; // whatever your username branch is
|
||||
bool dirty = 6; // if git says things have been changed
|
||||
string URL = 7; // the URL
|
||||
GitTags tags = 8; // known tags
|
||||
GitTimes times = 9; // store all the mtime values here. these are temporary
|
||||
GoInfo goInfo = 10; // put all the go specifcs here
|
||||
GoDeps goDeps = 11; // what is in the go.sum file
|
||||
string currentBranchName = 12; // the branch currently checked out
|
||||
string currentBranchVersion = 13; // the branch currently checked out
|
||||
string lastTag = 14; // the oldest tag
|
||||
string targetVersion = 15; // useful during the package release process
|
||||
bool readOnly = 16; // tracks access to 'git push'
|
||||
string desc = 17; // what is this repo?
|
||||
string stateChange = 18; // used for debugging tool logic
|
||||
string masterVersion = 19; // just store this for now
|
||||
string develVersion = 20; //
|
||||
string userVersion = 21; //
|
||||
repeated string dirtyList = 22; // store the list from git status --porcelain
|
||||
string state = 23; // status or state. useful for building tooling
|
||||
GitTag currentTag = 24; // used to examine repo branches
|
||||
GitConfig gitConfig = 25; // protobuf of the current .git/config
|
||||
string MasterHash = 26; // hash of the current master branch
|
||||
string DevelHash = 27; // hash of the current devel branch
|
||||
map<string, string> control = 28; // control values. can be used to make packages (like .deb or .rpm)
|
||||
}
|
||||
|
||||
message Repos { // `autogenpb:marshal` `autogenpb:sort` `autogenpb:gui` `autogenpb:nomutex` `autogenpb:http`
|
||||
string uuid = 1; // `autogenpb:uuid:8daaeba1-fb1f-4762-ae6e-95a55d352673`
|
||||
string version = 2; // `autogenpb:version:v4`
|
||||
repeated Repo repos = 3; // `autogenpb:append` // generate AppendUnique() function for this
|
||||
bool hasFullScan = 4; // a full repo scan has been saved to disk
|
||||
google.protobuf.Timestamp fullScan = 5; // mtime of the last full scan saved to disk
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package gitpb
|
||||
|
||||
import "go.wit.com/log"
|
||||
|
||||
// reverts master to devel
|
||||
// used in the unwind process of making GUI releases
|
||||
func (repo *Repo) RevertMasterToDevel() bool {
|
||||
if repo.CheckDirty() {
|
||||
log.Info("sorry, it's still dirty")
|
||||
return false
|
||||
}
|
||||
|
||||
curName := repo.GetCurrentBranchName()
|
||||
dName := repo.GetDevelBranchName()
|
||||
mName := repo.GetMasterBranchName()
|
||||
if curName != mName {
|
||||
log.Info("repo is not working from main branch", curName, "!=", mName)
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("reset master to devel", curName, repo.GetGoPath())
|
||||
|
||||
var all [][]string
|
||||
all = append(all, []string{"git", "checkout", dName}) // switch to the devel branch
|
||||
all = append(all, []string{"git", "branch", "-D", mName})
|
||||
all = append(all, []string{"git", "branch", mName}) // make a master branch based on devel
|
||||
all = append(all, []string{"git", "checkout", mName})
|
||||
all = append(all, []string{"git", "push", "--set-upstream", "--force", "origin", mName})
|
||||
|
||||
// don't do anything with tags here
|
||||
// all = append(all, []string{"git", "tag", "--delete", release.version.String()})
|
||||
// all = append(all, []string{"git", "push", "--delete", "origin", release.version.String()})
|
||||
|
||||
if repo.RunAll(all) {
|
||||
log.Info("EVERYTHING OK. RERELEASED", repo.GetGoPath())
|
||||
repo.ReloadCheck()
|
||||
return true
|
||||
}
|
||||
|
||||
log.Info("SOMETHING FAILED")
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package gitpb
|
||||
|
||||
// runs git, parses output
|
||||
// types faster than you can
|
||||
|
||||
import (
|
||||
"errors"
|
||||
sync "sync"
|
||||
|
||||
"github.com/destel/rill"
|
||||
"github.com/go-cmd/cmd"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
var ErrorMissingGitConfig error = errors.New("missing .git/config")
|
||||
var ErrorGitPullOnLocal error = errors.New("git pull on local only branch")
|
||||
var ErrorGitPullOnDirty error = errors.New("cannot git pull on dirty repo")
|
||||
|
||||
func (repo *Repo) GitPull() (*cmd.Status, error) {
|
||||
/*
|
||||
currentName := repo.GetCurrentBranchName()
|
||||
if repo.IsOnlyLocalTag(currentName) {
|
||||
var result cmd.Status
|
||||
result.Exit = 21
|
||||
result.Error = ErrorGitPullOnLocal
|
||||
// log.Info("git pull skipped on local only branch", repo.GetGoPath())
|
||||
return result
|
||||
}
|
||||
*/
|
||||
var cmd []string
|
||||
cmd = append(cmd, "git", "pull")
|
||||
return nil, repo.RunVerbose(cmd)
|
||||
}
|
||||
|
||||
// rill is awesome. long live rill
|
||||
// attempt scan with rill
|
||||
func (all *Repos) RillGitPull(part1 int, part2 int) map[*Repo]*cmd.Status {
|
||||
var lock sync.Mutex
|
||||
var allerr map[*Repo]*cmd.Status
|
||||
allerr = make(map[*Repo]*cmd.Status)
|
||||
|
||||
// Convert a slice of user IDs into a channel
|
||||
ids := rill.FromSlice(all.Repos, nil)
|
||||
|
||||
var counter int
|
||||
// Read users from the API.
|
||||
// Concurrency = 20
|
||||
dirs := rill.Map(ids, part1, func(id *Repo) (*Repo, error) {
|
||||
return id, nil
|
||||
})
|
||||
|
||||
rill.ForEach(dirs, part2, func(repo *Repo) error {
|
||||
counter += 1
|
||||
if repo.CheckDirty() {
|
||||
// log.Info("git pull skipped on dirty repo", repo.GoPath)
|
||||
result := new(cmd.Status)
|
||||
result.Error = ErrorGitPullOnDirty
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
allerr[repo] = result
|
||||
} else {
|
||||
// todo: sort out what the hell is wrong with my code
|
||||
// something seems to be trampling things
|
||||
/*
|
||||
var dur time.Duration
|
||||
dur = time.Duration((1+rand.Intn(50))*20) * time.Millisecond
|
||||
time.Sleep(dur)
|
||||
*/
|
||||
var result *cmd.Status
|
||||
result, _ = repo.GitPull()
|
||||
log.Info("git pull", repo.GetGoPath())
|
||||
// log.Info(strings.Join(result.Stdout, "\n"))
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
allerr[repo] = result
|
||||
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// for r, err := range allerr {
|
||||
// log.Info("git pull error:", r.GoPath, err)
|
||||
// }
|
||||
|
||||
return allerr
|
||||
}
|
||||
|
||||
func (repo *Repo) GitPullRealtime() cmd.Status {
|
||||
currentName := repo.GetCurrentBranchName()
|
||||
if repo.IsOnlyLocalTag(currentName) {
|
||||
var result cmd.Status
|
||||
result.Exit = 21
|
||||
result.Error = ErrorGitPullOnLocal
|
||||
// log.Info("git pull skipped on local only branch", repo.GoPath)
|
||||
return result
|
||||
}
|
||||
var cmd []string
|
||||
cmd = append(cmd, "git", "pull")
|
||||
r := repo.RunRealtime(cmd)
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-cmd/cmd"
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
)
|
||||
|
||||
// execute something with the working directory
|
||||
// set to the FullPath
|
||||
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.Log(WARN, "cmd:", cmd)
|
||||
log.Log(WARN, "ouptput:", output)
|
||||
log.Log(WARN, "failed with error:", result.Error)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (repo *Repo) RunEcho(cmd []string) cmd.Status {
|
||||
result := shell.PathRunQuiet(repo.FullPath, cmd)
|
||||
log.Log(NOW, "cmd:", repo.FullPath, cmd)
|
||||
log.Warn(WARN, "cmd.Exit:", result.Exit, "cmd.Error:", result.Error)
|
||||
for _, line := range result.Stdout {
|
||||
log.Log(NOW, "STDOUT:", line)
|
||||
}
|
||||
for _, line := range result.Stderr {
|
||||
log.Log(NOW, "STDERR:", line)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (repo *Repo) RunRealtime(cmd []string) cmd.Status {
|
||||
return shell.PathRunRealtime(repo.GetFullPath(), cmd)
|
||||
}
|
||||
|
||||
func (repo *Repo) RunRealtimeVerbose(cmd []string) cmd.Status {
|
||||
log.Log(NOW, "EXEC: cd", repo.GetFullPath(), ";", cmd)
|
||||
return shell.PathRunRealtime(repo.GetFullPath(), cmd)
|
||||
}
|
||||
|
||||
func (repo *Repo) RunQuiet(cmd []string) (*cmd.Status, error) {
|
||||
result := shell.PathRunQuiet(repo.FullPath, cmd)
|
||||
if result.Error != nil {
|
||||
log.Warn(repo.GetGoPath(), cmd, "wow. golang is cool. an os.Error:", result.Error)
|
||||
return &result, result.Error
|
||||
}
|
||||
if result.Exit != 0 {
|
||||
// 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) RunStrict(cmd []string) (*cmd.Status, error) {
|
||||
result, err := repo.RunQuiet(cmd)
|
||||
if err != nil {
|
||||
log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath())
|
||||
return result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *Repo) Exists(filename string) bool {
|
||||
if repo == nil {
|
||||
return false
|
||||
}
|
||||
testf := filepath.Join(repo.FullPath, filename)
|
||||
_, err := os.Stat(testf)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) IsValidDir() bool {
|
||||
if repo == nil {
|
||||
return false
|
||||
}
|
||||
if !repo.IsGitDirectory() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) ReadFile(fname string) ([]byte, error) {
|
||||
fullname := filepath.Join(repo.FullPath, fname)
|
||||
return os.ReadFile(fullname)
|
||||
}
|
||||
|
||||
func (repo *Repo) IsGitDirectory() bool {
|
||||
gitdir := filepath.Join(repo.FullPath, ".git")
|
||||
info, err := os.Stat(gitdir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
func (repo *Repo) IsDirectory() bool {
|
||||
info, err := os.Stat(repo.FullPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
func (repo *Repo) RunAll(all [][]string) bool {
|
||||
for _, cmd := range all {
|
||||
log.Log(WARN, "doAll() RUNNING: cmd =", cmd)
|
||||
r := repo.Run(cmd)
|
||||
if r.Error != nil {
|
||||
log.Log(WARN, "doAll() err =", r.Error)
|
||||
log.Log(WARN, "doAll() out =", r.Stdout)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (repo *Repo) RunStrictAll(all [][]string) (*cmd.Status, error) {
|
||||
for _, cmd := range all {
|
||||
log.Log(WARN, "doAll() RUNNING: cmd =", cmd)
|
||||
if result, err := repo.RunStrict(cmd); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (repo *Repo) RunVerbose(cmd []string) error {
|
||||
// log.Info("EXEC Running:", repo.GetGoPath(), cmd)
|
||||
err := shell.PathExecVerbose(repo.GetFullPath(), cmd)
|
||||
if err != nil {
|
||||
log.Info("Error", cmd, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repo) RunVerboseOnError(cmd []string) (*cmd.Status, error) {
|
||||
r, err := repo.RunStrict(cmd)
|
||||
if err == nil {
|
||||
return r, err
|
||||
}
|
||||
log.Info("Run Error:", repo.GetGoPath(), cmd, err)
|
||||
for _, line := range r.Stdout {
|
||||
log.Info(line)
|
||||
}
|
||||
for _, line := range r.Stderr {
|
||||
log.Info(line)
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
// only safe to run len() on STDOUT
|
||||
// DO NOT TRY TO PARSE THIS EXCEPT HASH AS FIRST VALUE
|
||||
// Intended to be human readable
|
||||
func (repo *Repo) ConstructGitDiffLog(branch1, branch2 string) []string {
|
||||
var cmd []string
|
||||
cmd = append(cmd, "git")
|
||||
cmd = append(cmd, "log")
|
||||
cmd = append(cmd, "--format=\"%H %ae %as %s\"")
|
||||
cmd = append(cmd, branch1)
|
||||
cmd = append(cmd, "--not")
|
||||
cmd = append(cmd, branch2)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// count all objects only in branch1
|
||||
func (repo *Repo) CountDiffObjects(branch1, branch2 string) int {
|
||||
cmd := repo.ConstructGitDiffLog(branch1, branch2)
|
||||
r, err := repo.RunVerboseOnError(cmd)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
// log.Info("countDiffObjects()", cmd, len(r.Stdout), strings.Join(r.Stdout, " "))
|
||||
return len(r.Stdout)
|
||||
}
|
79
update.go
79
update.go
|
@ -1,79 +0,0 @@
|
|||
package gitpb
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.wit.com/lib/gui/shell"
|
||||
"go.wit.com/log"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// this is becoming a standard format
|
||||
// todo: autogenerate this from the .proto file?
|
||||
|
||||
// Update version and timestamp.
|
||||
// returns ok (ok == true if not found)
|
||||
func (r *Refs) Update(path string) error {
|
||||
// delete the old hash
|
||||
// r.DeleteByHash(hash)
|
||||
r.Refs = nil
|
||||
|
||||
tags := []string{"%(objectname)", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
|
||||
format := strings.Join(tags, "_,,,_")
|
||||
cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format", format}
|
||||
// log.Info("RUNNING:", strings.Join(cmd, " "))
|
||||
result := shell.PathRunQuiet("", cmd)
|
||||
if result.Error != nil {
|
||||
log.Warn("git for-each-ref error:", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
lines := result.Stdout
|
||||
// reverse the git order
|
||||
slices.Reverse(lines)
|
||||
|
||||
var refName string
|
||||
var hash string
|
||||
var subject string
|
||||
var ctime time.Time
|
||||
|
||||
for i, line := range lines {
|
||||
var parts []string
|
||||
parts = make([]string, 0)
|
||||
parts = strings.Split(line, "_,,,_")
|
||||
if len(parts) != 5 {
|
||||
log.Info("tag error:", i, parts)
|
||||
continue
|
||||
}
|
||||
refName = parts[3]
|
||||
hash = parts[0]
|
||||
|
||||
ctime = getGitDateStamp(parts[1])
|
||||
|
||||
subject = parts[4]
|
||||
}
|
||||
newr := Ref{
|
||||
Hash: hash,
|
||||
Subject: subject,
|
||||
RefName: refName,
|
||||
Ctime: timestamppb.New(ctime),
|
||||
}
|
||||
|
||||
r.Append(&newr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600"
|
||||
func getGitDateStamp(gitdefault string) time.Time {
|
||||
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
|
||||
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
|
||||
tagTime, err := time.Parse(gitLayout, gitdefault)
|
||||
if err != nil {
|
||||
log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA")
|
||||
log.Warn(err)
|
||||
return time.Now()
|
||||
}
|
||||
return tagTime
|
||||
}
|
Loading…
Reference in New Issue