Compare commits

..

No commits in common. "master" and "v0.0.13" have entirely different histories.

46 changed files with 827 additions and 3455 deletions

4
.gitignore vendored
View File

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

View File

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

View File

@ -5,11 +5,10 @@
# go install
all: gitTag.pb.go goDep.pb.go repo.pb.go goimports vet
all: gitTag.pb.go goDep.pb.go repo.pb.go vet
generate: clean
go-mod-clean
go generate
test:
make -C scanGoSrc/
vet:
@GO111MODULE=off go vet
@ -18,11 +17,18 @@ vet:
# autofixes your import headers in your golang files
goimports:
goimports -w *.go
make -C scanGoSrc/ goimports
redomod:
rm -f go.*
GO111MODULE= go mod init
GO111MODULE= go mod tidy
go mod edit -go=1.20
# dump autogenerated files and potential patches
clean:
rm -f *.pb.go go.* *.patch
go-mod-clean purge
rm -f *.pb.go
-rm -f go.*
make -C scanGoSrc clean
#refs.pb.go: refs.proto
# cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \
@ -30,39 +36,10 @@ clean:
# refs.proto
gitTag.pb.go: gitTag.proto
autogenpb --proto gitTag.proto --mutex-name "john"
autogenpb --proto gitTag.proto --mutex
goDep.pb.go: goDep.proto
autogenpb --proto goDep.proto
autogenpb --proto goDep.proto --mutex
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
autogenpb --proto repo.proto --mutex

View File

@ -1,12 +1 @@
# 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
protobuf definition files for git repositories

70
age.go
View File

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

View File

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

View File

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

View File

@ -6,11 +6,9 @@ package gitpb
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) {
@ -25,7 +23,7 @@ func (repo *Repo) IsDirty() bool {
// actually os.Exec('git')
func (repo *Repo) CheckDirty() bool {
cmd := []string{"git", "status", "--porcelain"}
r := shell.PathRunLog(repo.FullPath, cmd, INFO)
r := shell.PathRunLog(repo.FullPath, cmd, GITPB)
if r.Error != nil {
log.Warn("CheckDirty() status cmd =", cmd)
out := strings.Join(r.Stdout, "\n")
@ -34,32 +32,14 @@ func (repo *Repo) CheckDirty() bool {
log.Error(r.Error, "CheckDirty() git status error")
repo.NoteChange("git status is in error " + fmt.Sprint(r.Error))
repo.Dirty = true
repo.State = "dirty"
return true
}
// 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
}
if len(r.Stdout) == 0 {
repo.Dirty = false
return false
}
repo.DirtyList = r.Stdout
repo.Dirty = true
return true
pbnow := timestamppb.New(time.Now())
repo.Times.LastDirty = pbnow
repo.Dirty = bad
if bad {
repo.State = "dirty"
}
return bad
}

View File

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

View File

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

176
config.go
View File

@ -8,160 +8,63 @@ import (
"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")
// write to ~/.config/forge/ unless ENV{FORGE_HOME} is set
func (all *Repos) ConfigSave() error {
if os.Getenv("FORGE_HOME") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_HOME", fullpath)
}
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)
if all == nil {
log.Warn("gitpb all == nil")
panic("why is this nil?")
}
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)
log.Info("gitpb.ConfigSave() repos.Marshal() worked len", len(data))
configWrite("repos.pb", 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") == "" {
// load the ~/.config/forge/ files
func (all *Repos) ConfigLoad() error {
if os.Getenv("FORGE_HOME") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_REPOSDIR", fullpath)
os.Setenv("FORGE_HOME", fullpath)
}
var data []byte
var err error
cfgname := filepath.Join(os.Getenv("FORGE_REPOSDIR"), "repos.pb")
if data, err = loadFile(cfgname); err != nil {
if data, err = loadFile("repos.pb"); 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")
if data != nil {
// this means the forge.pb file exists and was read
if len(data) == 0 {
// todo: error out if the file is empty?
// try forge.text & forge.json?
}
if err = all.Unmarshal(data); err != nil {
log.Warn("broken forge.pb config file")
return err
}
log.Info("found", len(all.Repos), "repos in ~/.config/forge/repos.pb")
return nil
}
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
return nil
}
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) {
func loadFile(filename string) ([]byte, error) {
fullname := filepath.Join(os.Getenv("FORGE_HOME"), filename)
data, err := os.ReadFile(fullname)
if errors.Is(err, os.ErrNotExist) {
// if file does not exist, just return nil. this
@ -176,14 +79,25 @@ func loadFile(fullname string) ([]byte, error) {
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)
func configWrite(filename string, data []byte) error {
fullname := filepath.Join(os.Getenv("FORGE_HOME"), filename)
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE, 0666)
defer cfgfile.Close()
if err != nil {
log.Warn("open config file :", err)
return err
}
if filename == "forge.text" {
// add header
cfgfile.Write([]byte("# this file is automatically re-generated from forge.pb, however,\n"))
cfgfile.Write([]byte("# if you want to edit it by hand, you can:\n"))
cfgfile.Write([]byte("# stop forge; remove forge.pb; edit forge.text; start forge\n"))
cfgfile.Write([]byte("# this will cause the default behavior to fallback to parsing this file for the config\n"))
cfgfile.Write([]byte("\n"))
cfgfile.Write([]byte("# this file is intended to be used to customize settings on what\n"))
cfgfile.Write([]byte("# git repos you have write access to. That is, where you can run 'git push'\n"))
}
cfgfile.Write(data)
return nil
}

View File

@ -6,101 +6,120 @@ package gitpb
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) GetLastTag() string {
cmd := []string{"git", "rev-list", "--tags", "--max-count=1"}
result := repo.RunQuiet(cmd)
// log.Info("getLastTagVersion()", result.Stdout)
if len(result.Stdout) != 1 {
log.Log(GITPBWARN, "git LastTag() error:", result.Stdout)
return ""
}
hash := result.Stdout[0]
cmd = []string{"git", "describe", "--tags", "--always", hash}
result = repo.RunQuiet(cmd)
if len(result.Stdout) != 1 {
log.Log(GITPBWARN, "git LastTag() error:", result.Stdout)
return ""
}
return result.Stdout[0]
}
func (repo *Repo) setMasterVersion() {
func (repo *Repo) GitMasterVersion() string {
bname := repo.GetMasterBranchName()
v, err := repo.gitVersionByName(bname)
/*
count := repo.LenGitTags()
log.Info(repo.GetGoPath(), "tag count", count)
log.Info(repo.GoPath, "tag count", count)
repo.UpdateGitTags()
count = repo.LenGitTags()
log.Info(repo.GetGoPath(), "tag count", count)
log.Info(repo.GoPath, "tag count", count)
*/
if err == nil {
repo.MasterVersion = v
return v
} else {
log.Log(WARN, "gitpb.GitMasterVersion() error:", err)
log.Log(GITPBWARN, "gitpb.GitMasterVersion() error:", err)
return ""
}
}
func (repo *Repo) setDevelVersion() {
func (repo *Repo) GitDevelVersion() string {
bname := repo.GetDevelBranchName()
v, err := repo.gitVersionByName(bname)
if err == nil {
repo.DevelVersion = v
return v
} else {
// log.Log(WARN, "gitpb.GitDevelVersion() error:", err)
repo.DevelVersion = ""
log.Log(GITPBWARN, "gitpb.GitDevelVersion() error:", err)
return ""
}
}
func (repo *Repo) setUserVersion() {
func (repo *Repo) GitUserVersion() string {
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
if bname != "jcarr" {
panic("not jcarr bname =" + bname)
}
v, err := repo.gitVersionByName(bname)
if err == nil {
repo.UserVersion = v
return 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 {
log.Log(GITPBWARN, "gitpb.GitUserVersion() error:", err)
return ""
}
bver := repo.GetCurrentBranchVersion()
if repo.CheckDirty() {
bver = bver + "-dirty"
}
func (repo *Repo) GetCurrentBranchName() string {
r := repo.RunQuiet([]string{"git", "branch", "--show-current"})
output := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Log(GITPBWARN, "GetCurrentBranchName() not in a git repo?", r.Error, repo.GoPath)
log.Log(GITPBWARN, "GetCurrentBranchName() output might have worked anyway:", output)
}
return bver
return strings.TrimSpace(output)
}
func (repo *Repo) GetCurrentBranchVersion() string {
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always"})
output := strings.Join(r.Stdout, "\n")
if r.Error != nil {
log.Log(GITPBWARN, "GetCurrentBranchVersion() not in a git repo?", r.Error, repo.GoPath)
log.Log(GITPBWARN, "GetCurrentBranchVersion() output might have worked anyway:", output)
}
return strings.TrimSpace(output)
}
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})
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always", 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
if r.Error != nil {
log.Warn("not in a git repo or bad hash?", r.Error, repo.GoPath)
return out, r.Error
}
return out, err
return out, r.Error
}
// this should get the most recent tag
func (repo *Repo) GetLastTagVersion() string {
return repo.LastTag
r := repo.RunQuiet([]string{"git", "rev-list", "--tags", "--max-count=1"})
hash := strings.Join(r.Stdout, "\n")
hash = strings.TrimSpace(hash)
log.Log(GITPB, "getLastTagVersion()", hash)
name, _ := repo.gitDescribeByHash(hash)
return name
}
func (repo *Repo) DebianReleaseVersion() string {
@ -133,33 +152,62 @@ func (repo *Repo) gitVersionByName(name string) (string, error) {
if name == "" {
// git will return the current tag
cmd := []string{"git", "describe", "--tags"}
r, err := repo.RunQuiet(cmd)
r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always"})
output := strings.Join(r.Stdout, "\n")
if err != nil {
log.Log(WARN, repo.FullPath, "gitDescribeByName() ", output, err, cmd)
return "", err
if r.Error != nil {
log.Log(GITPBWARN, "gitDescribeByName() output might have worked anyway:", output)
log.Log(GITPBWARN, "gitDescribeByName() not in a git repo?", r.Error, repo.GoPath)
return "", r.Error
}
return strings.TrimSpace(output), nil
}
if !repo.IsBranch(name) {
// branch does not exist
// tag does not exist
log.Log(GITPBWARN, "LocalTagExists()", name, "did not exist")
return "", errors.New("gitDescribeByName() git fatal: Not a valid object name: " + name)
}
cmd := []string{"git", "describe", "--tags", name}
result, err := repo.RunQuiet(cmd)
cmd := []string{"git", "describe", "--tags", "--always", name}
result := 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())
if result.Error != nil {
log.Log(GITPBWARN, "cmd =", cmd)
log.Log(GITPBWARN, "err =", result.Error)
log.Log(GITPBWARN, "output (might have worked with error?) =", output)
log.Log(GITPBWARN, "not in a git repo or bad tag?", repo.GoPath)
return "", result.Error
}
return strings.TrimSpace(output), nil
}
// find a branch name
// will find "master" or "devel"
// will also find "v0.1.1"
// or will find "patches-from-foo"
// will return *any* match on any git branch because it doesn't
// matter much here yet
// eventually this will be worked out by forge in some future code that hasn't been made yet
func (repo *Repo) IsBranch(findname string) bool {
loop := repo.Tags.All()
for loop.Scan() {
t := loop.Next()
// log.Info("LocalTagExists() tag:", t.Refname)
tagname := t.Refname
if strings.HasPrefix(tagname, "refs/remotes") {
continue
}
path, filename := filepath.Split(tagname)
log.Log(GITPB, "gitpb.IsBranch() tag:", path, filename, "from", repo.GoPath)
if filename == findname {
log.Log(GITPB, "gitpb.IsBranch() found tag:", path, filename, "from", repo.GoPath)
return true
}
}
log.Log(GITPB, "did not find tag:", findname, "in", repo.GoPath)
return false
}
func trimNonNumericFromStart(s string) string {
for i, r := range s {
if unicode.IsDigit(r) {
@ -168,120 +216,3 @@ func trimNonNumericFromStart(s string) string {
}
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
}

View File

@ -1,156 +0,0 @@
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)
}

View File

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

View File

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

View File

@ -4,37 +4,16 @@ 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 GitTag { // `autogenpb:marshal`
string refname = 1; // `autogenpb:unique` // tag name. treated as unique
google.protobuf.Timestamp creatordate = 2; // git creatordate
google.protobuf.Timestamp authordate = 3; // git author date
string hash = 4; // `autogenpb:unique` // git hash
string subject = 5; // git tag subject
}
message 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;
message GitTags { // `autogenpb:marshal`
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations
repeated GitTag gitTags = 3;
}

82
gitTag.update.go Normal file
View File

@ -0,0 +1,82 @@
package gitpb
import (
"slices"
"strings"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/log"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
// Update repo.Refs from .git/
func (repo *Repo) UpdateGitTags() error {
// 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)
}
return nil
}
// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600"
func getGitDateStamp(gitdefault string) time.Time {
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
tagTime, err := time.Parse(gitLayout, gitdefault)
if err != nil {
log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA")
log.Warn(err)
return time.Now()
}
return tagTime
}

View File

@ -13,12 +13,12 @@ func (repo *Repo) DeleteGoDepByHash(hash string) {
// enforces no duplicate package names
func (repo *Repo) AppendGoDep(newP *GoDep) bool {
return repo.GoDeps.AppendByGoPath(newP)
return repo.GoDeps.AppendUniqueGoPath(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())
t := time.Since(repo.LastGoDep.AsTime())
return t
}

View File

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

View File

@ -6,18 +6,16 @@ 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 GoDep { // `autogenpb:marshal`
string hash = 1; // `autogenpb:unique` // md5sum/hash value from the go.sum file
google.protobuf.Timestamp ctime = 2; // get the go date from 'go list' ?
string version = 3; // v1.2.2
string goPath = 4; // `autogenpb:unique` // "go.wit.com/lib/foo"
string goVersion = 5; // version of golang the developer used to make this package version
}
message 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`
message GoDeps { // `autogenpb:marshal`
string uuid = 1; // I guess why not just have this on each file
string version = 2; // maybe can be used for protobuf schema change violations
repeated GoDep goDeps = 3;
}

View File

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

81
http.go
View File

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

View File

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

10
log.go
View File

@ -4,15 +4,13 @@ import (
"go.wit.com/log"
)
var NOW *log.LogFlag
var INFO *log.LogFlag
var WARN *log.LogFlag
var GITPB *log.LogFlag
var GITPBWARN *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")
GITPB = log.NewFlag("GITPB", false, full, short, "general gitpb things")
GITPBWARN = log.NewFlag("GITPBWARN", true, full, short, "gitpb warnings")
}

140
reload.go
View File

@ -1,140 +0,0 @@
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)
}

View File

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

View File

@ -1,74 +0,0 @@
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)
}

View File

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

View File

@ -1,215 +0,0 @@
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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,11 @@
package gitpb
import (
"bufio"
"errors"
"net/url"
"os"
"path/filepath"
"strings"
"go.wit.com/log"
)
@ -13,121 +15,83 @@ import (
// TODO: try adding python, rails, perl, rust, other language things?
// I probably will never have time to try that, but I'd take patches for anyone
// that might see this note and feel so inclined.
// 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)
func (all *Repos) NewGoPath(basepath string, gopath string) (*Repo, error) {
if r := all.FindByGoPath(gopath); r != nil {
// already had this gopath
return r, errors.New("gitpb.NewGoPath() duplicate gopath " + gopath)
return r, nil
}
// if .git doesn't exist, error out here
gitpath := filepath.Join(basepath, gopath, ".git")
_, err := os.Stat(gitpath)
if err != nil {
return nil, err
}
// add a new one here
newr := Repo{
FullPath: fullpath,
Namespace: gopath,
FullPath: filepath.Join(basepath, gopath),
GoPath: gopath,
}
newr.Times = new(GitTimes)
newr.Tags = new(GitTags)
// newr.UpdateGit()
newr.UpdateGitTags()
newr.GoDeps = new(GoDeps)
// newr.RedoGoMod()
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)
}
switch newr.RepoType() {
case "library":
newr.GoLibrary = true
case "binary":
newr.GoBinary = true
}
// todo: use Repos.Lock()
return nil, errors.New("repo gitpb.NewGoPath() should never have gotten here " + gopath)
all.AppendUniqueGoPath(&newr)
return &newr, nil
}
// 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()
// Detect a 'Primative' package. Sets the isPrimative flag
// will return true if the repo is truly not dependent on _anything_ else
// like spew or lib/widget
// it assumes go mod ran init and tidy ran without error
func (repo *Repo) isPrimativeGoMod() (bool, error) {
// go mod init & go mod tidy ran without errors
log.Log(GITPB, "isPrimativeGoMod()", repo.FullPath)
tmp := filepath.Join(repo.FullPath, "go.mod")
gomod, err := os.Open(tmp)
if err != nil {
log.Log(GITPB, "missing go.mod", repo.FullPath)
repo.GoDeps = nil
return false, err
}
defer gomod.Close()
scanner := bufio.NewScanner(gomod)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Split(line, " ")
log.Log(GITPB, " gomod:", parts)
if len(parts) >= 1 {
log.Log(GITPB, " gomod: part[0] =", parts[0])
if parts[0] == "require" {
log.Log(GITPB, " should return false here")
return false, errors.New("go.mod file is not primative")
}
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)
return true, nil
}
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
func (repo *Repo) SetMasterBranchName(bname string) {
repo.MasterBranchName = bname
}
func (repo *Repo) SetDevelBranchName(bname string) {
repo.DevelBranchName = bname
}
func (repo *Repo) SetUserBranchName(bname string) {
repo.UserBranchName = bname
}

View File

@ -9,74 +9,26 @@ 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
message Repo { // `autogenpb:marshal`
string fullPath = 1; // the actual path to the .git directory: '/home/devel/golang.org/x/tools'
google.protobuf.Timestamp lastPull = 2; // last time a git pull was done
string masterBranchName = 3; // git 'main' or 'master' branch name
string develBranchName = 4; // whatever the git 'devel' branch name is
string userBranchName = 5; // whatever your username branch is
GitTags tags = 6; // known tags
string goPath = 7; // `autogenpb:unique` // the logical path as used by golang: 'go.wit.com/apps/helloworld'
bool goLibrary = 8; // is this a golang library?
bool goBinary = 9; // is this a golang binary?
bool goPrimitive = 10; // if this is a golang primitive (only has go.mod)
bool goPlugin = 11; // is this a golang plugin?
GoDeps goDeps = 12; // what is in the go.sum file
google.protobuf.Timestamp lastGoDep = 13; // last time go.sum was processed
bool dirty = 14; // if git says things have been changed
GoDeps published = 15; // the last published go.mod/go.sum
}
// 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
message Repos { // `autogenpb:marshal`
string uuid = 1; // `autogenpb:uuid:8daaeba1-fb1f-4762-ae6e-95a55d352673`
string version = 2; // maybe can be used for protobuf schema change violations
repeated Repo repos = 3;
}

View File

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

View File

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

101
rill.go
View File

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

23
scanGoSrc/Makefile Normal file
View File

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

50
scanGoSrc/argv.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"os"
"go.wit.com/dev/alexflint/arg"
)
var argv args
type args struct {
ConfigDir string `arg:"env:FORGE_HOME" help:"defaults to ~/.config/forge/"`
List bool `arg:"--list" default:"false" help:"list repos in your config"`
Add bool `arg:"--add" default:"false" help:"add a new repo"`
Delete bool `arg:"--delete" default:"false" help:"delete a repo"`
Update bool `arg:"--update" default:"false" help:"update a repo"`
GoPath string `arg:"--gopath" help:"gopath of the repo"`
Directory bool `arg:"--directory" default:"false" help:"repo is a directory to match against"`
ReadOnly bool `arg:"--readonly" default:"false" help:"repo is readonly"`
Writable bool `arg:"--writable" default:"false" help:"repo is writable"`
Favorite bool `arg:"--favorite" default:"false" help:"forge will always go-clone or git clone this"`
Private bool `arg:"--private" default:"false" help:"repo can not be published"`
Interesting bool `arg:"--interesting" default:"false" help:"something you decided was cool"`
}
func (a args) Description() string {
return `
forgeConfig -- add entries to your config files
This is just example protobuf code to test forgepb is working
but it could be used to automagically create a config file too.
If you need to change your config file, just edit the forge.text or forge.json
files then remove the forge.pb and ConfigLoad() will attempt to load those files instead
`
}
func (args) Version() string {
return "virtigo " + VERSION
}
func init() {
var pp *arg.Parser
pp = arg.MustParse(&argv)
if pp == nil {
pp.WriteHelp(os.Stdout)
os.Exit(0)
}
}

54
scanGoSrc/main.go Normal file
View File

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

163
shell.go
View File

@ -1,8 +1,6 @@
package gitpb
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
@ -18,60 +16,42 @@ 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)
log.Warn("cmd:", cmd)
log.Warn("ouptput:", output)
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) {
func (repo *Repo) RunQuiet(cmd []string) cmd.Status {
result := shell.PathRunQuiet(repo.FullPath, cmd)
output := strings.Join(result.Stdout, "\n")
if result.Error != nil {
log.Warn(repo.GetGoPath(), cmd, "wow. golang is cool. an os.Error:", result.Error)
return &result, result.Error
log.Warn("cmd:", cmd)
log.Warn("ouptput:", output)
log.Warn("failed with error:", result.Error)
}
return result
}
// for now, even check cmd.Exit
func (repo *Repo) strictRun(cmd []string) (bool, error) {
result := repo.RunQuiet(cmd)
if result.Error != nil {
log.Warn("go mod init failed err:", result.Error)
return false, 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))
log.Warn("go mod init exit =", result.Exit)
return false, result.Error
}
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
return true, nil
}
func (repo *Repo) Exists(filename string) bool {
if repo == nil {
return false
log.Warn("repo == nil for Exists()")
panic(-1)
}
testf := filepath.Join(repo.FullPath, filename)
_, err := os.Stat(testf)
@ -81,30 +61,6 @@ func (repo *Repo) Exists(filename string) bool {
return true
}
func (repo *Repo) IsValidDir() bool {
if repo == nil {
return false
}
if !repo.IsGitDirectory() {
return false
}
return true
}
func (repo *Repo) ReadFile(fname string) ([]byte, error) {
fullname := filepath.Join(repo.FullPath, fname)
return os.ReadFile(fullname)
}
func (repo *Repo) IsGitDirectory() bool {
gitdir := filepath.Join(repo.FullPath, ".git")
info, err := os.Stat(gitdir)
if err != nil {
return false
}
return info.IsDir()
}
func (repo *Repo) IsDirectory() bool {
info, err := os.Stat(repo.FullPath)
if err != nil {
@ -112,76 +68,3 @@ func (repo *Repo) IsDirectory() bool {
}
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)
}

20
validate/Makefile Normal file
View File

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

41
validate/argv.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"os"
"go.wit.com/dev/alexflint/arg"
)
var argv args
type args struct {
List bool `arg:"--list" default:"false" help:"list repos in your config"`
SaveConfig bool `arg:"--save" default:"false" help:"save your config file at the end"`
Interesting bool `arg:"--interesting" default:"false" help:"something you decided was cool"`
}
func (a args) Description() string {
return `
forgeConfig -- add entries to your config files
This is just example protobuf code to test forgepb is working
but it could be used to automagically create a config file too.
If you need to change your config file, just edit the forge.text or forge.json
files then remove the forge.pb and ConfigLoad() will attempt to load those files instead
`
}
func (args) Version() string {
return "virtigo " + VERSION
}
func init() {
var pp *arg.Parser
pp = arg.MustParse(&argv)
if pp == nil {
pp.WriteHelp(os.Stdout)
os.Exit(0)
}
}

50
validate/main.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"os"
"go.wit.com/dev/alexflint/arg"
"go.wit.com/gui"
"go.wit.com/lib/gui/repolist"
"go.wit.com/lib/protobuf/forgepb"
"go.wit.com/lib/protobuf/gitpb"
"go.wit.com/log"
)
// sent via ldflags
var VERSION string
var pp *arg.Parser
var forge *forgepb.Forge
var myGui *gui.Node
var rv *repolist.RepoList
var argvRepo *gitpb.Repo
func main() {
pp = arg.MustParse(&argv)
// load the ~/.config/forge/ config
forge = forgepb.Init()
// forge.ConfigPrintTable()
os.Setenv("REPO_WORK_PATH", forge.GetGoSrc())
myGui = gui.New()
myGui.Default()
repos := forge.Repos.SortByGoPath()
for repos.Scan() {
repo := repos.Next()
forge.VerifyBranchNames(repo)
fullpath := repo.GetFullPath()
mName := repo.GetMasterBranchName()
dName := repo.GetDevelBranchName()
uName := repo.GetUserBranchName()
log.Printf("repo: %-60s %-8s %-8s %-8s\n", fullpath, mName, dName, uName)
}
if argv.SaveConfig {
forge.Repos.ConfigSave()
}
os.Exit(0)
}