diff --git a/.gitignore b/.gitignore index 6cce53e..4bfdc3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ go.* *.pb.go -example/example +scanGoSrc/scanGoSrc diff --git a/Makefile b/Makefile index 362df41..905df97 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ all: refs.pb.go godep.pb.go repo.pb.go vet + make -C scanGoSrc/ vet: lint GO111MODULE=off go vet diff --git a/godep.redoGoMod.go b/godep.redoGoMod.go new file mode 100644 index 0000000..2ce7676 --- /dev/null +++ b/godep.redoGoMod.go @@ -0,0 +1,117 @@ +package gitpb + +// does processing on the go.mod and go.sum files + +import ( + "bufio" + "errors" + "os" + "path/filepath" + "strings" + + "go.wit.com/log" +) + +// poor name perhaps. It's because in most of these +// repos you can also type "make redomod" to do the same thing +// since it's a Makefile task that is also useful to be able to run +// from the command line +func (repo *Repo) MakeRedomod() (bool, error) { + // unset the go development ENV var to generate release files + os.Unsetenv("GO111MODULE") + if ok, err := repo.strictRun([]string{"rm", "-f", "go.mod", "go.sum"}); !ok { + log.Warn("rm go.mod go.sum failed", err) + return ok, err + } + if ok, err := repo.strictRun([]string{"go", "mod", "init", repo.GoPath}); !ok { + log.Warn("go mod init failed", err) + return ok, err + } + if ok, err := repo.strictRun([]string{"go", "mod", "tidy"}); !ok { + log.Warn("go mod tidy failed", err) + return ok, err + } + 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") +} + +// 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 + } + 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, + } + repo.AppendGoDep(&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 +} diff --git a/log.go b/log.go new file mode 100644 index 0000000..df58886 --- /dev/null +++ b/log.go @@ -0,0 +1,16 @@ +package gitpb + +import ( + "go.wit.com/log" +) + +var GITPB *log.LogFlag +var GITPBWARN *log.LogFlag + +func init() { + full := "go.wit.com/lib/protobuf/gitpb" + short := "gitpb" + + GITPB = log.NewFlag("GITPB", false, full, short, "general gitpb things") + GITPBWARN = log.NewFlag("GITPBWARN", true, full, short, "gitpb warnings") +} diff --git a/repo.new.go b/repo.new.go index c7f000f..bab2b25 100644 --- a/repo.new.go +++ b/repo.new.go @@ -1,7 +1,13 @@ package gitpb import ( + "bufio" + "errors" + "os" "path/filepath" + "strings" + + "go.wit.com/log" ) // scans in a new git repo. If it detects the repo is a golang project, @@ -9,11 +15,19 @@ import ( // TODO: try adding python, rails, perl, rust, other language things? // I probably will never have time to try that, but I'd take patches for anyone // that might see this note and feel so inclined. -func (all *Repos) NewGoPath(basepath string, gopath string) *Repo { +func (all *Repos) NewGoPath(basepath string, gopath string) (*Repo, error) { if r := all.FindByGoPath(gopath); r != nil { // already had this gopath - return r + 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: filepath.Join(basepath, gopath), @@ -22,5 +36,39 @@ func (all *Repos) NewGoPath(basepath string, gopath string) *Repo { newr.UpdateGit() all.add(&newr) - return &newr + return &newr, nil +} + +// Detect a 'Primative' package. Sets the isPrimative flag +// will return true if the repo is truly not dependent on _anything_ else +// like spew or lib/widget +// it assumes go mod ran init and tidy ran without error +func (repo *Repo) isPrimativeGoMod() (bool, error) { + // go mod init & go mod tidy ran without errors + log.Log(GITPB, "isPrimativeGoMod()", repo.FullPath) + tmp := filepath.Join(repo.FullPath, "go.mod") + gomod, err := os.Open(tmp) + if err != nil { + log.Log(GITPB, "missing go.mod", repo.FullPath) + repo.GoDeps = nil + return false, err + } + defer gomod.Close() + + scanner := bufio.NewScanner(gomod) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + parts := strings.Split(line, " ") + log.Log(GITPB, " gomod:", parts) + if len(parts) >= 1 { + log.Log(GITPB, " gomod: part[0] =", parts[0]) + if parts[0] == "require" { + log.Log(GITPB, " should return false here") + return false, errors.New("go.mod file is not primative") + } + + } + } + return true, nil } diff --git a/repo.proto b/repo.proto index 03fbd6e..029bad2 100644 --- a/repo.proto +++ b/repo.proto @@ -11,12 +11,15 @@ import "google/protobuf/timestamp.proto"; // Import the well-known type for Time message Repo { string fullPath = 1; // the actual path to the .git directory: '/home/devel/golang.org/x/tools' - string goPath = 2; // the logical path as used by golang: 'go.wit.com/apps/helloworld' - bool library = 3; // if this is a golang library - repeated Ref refs = 4; - repeated GoDep GoDeps = 5; - google.protobuf.Timestamp lastPull = 6; // last time a git pull was done + repeated Ref refs = 2; + google.protobuf.Timestamp lastPull = 3; // last time a git pull was done + + // things specific to golang projects + string goPath = 4; // the logical path as used by golang: 'go.wit.com/apps/helloworld' + repeated GoDep GoDeps = 6; google.protobuf.Timestamp lastGoDep = 7; // last time go.sum was processed + bool goLibrary = 8; // if this is a golang library + bool goPrimitive = 9; // if this is a golang primitive } message Repos { diff --git a/scanGoSrc/Makefile b/scanGoSrc/Makefile new file mode 100644 index 0000000..bcd01e3 --- /dev/null +++ b/scanGoSrc/Makefile @@ -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 diff --git a/scanGoSrc/argv.go b/scanGoSrc/argv.go new file mode 100644 index 0000000..5e01d4f --- /dev/null +++ b/scanGoSrc/argv.go @@ -0,0 +1,50 @@ +package main + +import ( + "os" + + "github.com/alexflint/go-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) + } +} diff --git a/scanGoSrc/main.go b/scanGoSrc/main.go new file mode 100644 index 0000000..ff24c9c --- /dev/null +++ b/scanGoSrc/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "os" + + "go.wit.com/lib/protobuf/forgepb" + "go.wit.com/lib/protobuf/gitpb" + "go.wit.com/log" +) + +// sent via ldflags +var VERSION string + +func main() { + var config forgepb.ForgeConfigs + if err := config.ConfigLoad(); err != nil { + log.Warn("forgepb.ConfigLoad() failed", err) + os.Exit(-1) + } + + config.PrintTable() + + 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) + } 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) + } + + /* + 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) +} diff --git a/shell.go b/shell.go new file mode 100644 index 0000000..de82f84 --- /dev/null +++ b/shell.go @@ -0,0 +1,51 @@ +package gitpb + +import ( + "os" + "path/filepath" + "strings" + + "github.com/go-cmd/cmd" + "go.wit.com/lib/gui/shell" + "go.wit.com/log" +) + +// execute something with the working directory +// set to the FullPath +func (repo *Repo) Run(cmd []string) cmd.Status { + result := shell.PathRun(repo.FullPath, cmd) + output := strings.Join(result.Stdout, "\n") + if result.Error != nil { + log.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.Run(cmd) + if result.Error != nil { + log.Warn("go mod init failed err:", result.Error) + return false, result.Error + } + if result.Exit != 0 { + log.Warn("go mod init exit =", result.Exit) + return false, result.Error + } + return true, nil +} + +func (repo *Repo) Exists(filename string) bool { + if repo == nil { + log.Warn("repo == nil for Exists()") + panic(-1) + } + testf := filepath.Join(repo.FullPath, filename) + _, err := os.Stat(testf) + if err != nil { + return false + } + return true +}