first commit
docs: readme fix: upgrade with commit id feat: log to logrus upgrade: lormatter feat: strong search go module version fix: github repo no version fix: ignore google.golang.org upgrade: github api first commit
This commit is contained in:
commit
72c9cc2f15
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.idea
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
## gomod
|
||||
|
||||
> go.mod file manager
|
||||
|
||||
### install
|
||||
|
||||
```shell
|
||||
go install gitter.top/apps/gomod/...@latest
|
||||
```
|
||||
|
||||
### usage
|
||||
|
||||
- [ ] `gomod` show go.mod available updates
|
||||
- [x] `gomod u` upgrade go.mod
|
||||
- [ ] `gomod a` analyzed project dependencies
|
76
git-ls-remote.go
Normal file
76
git-ls-remote.go
Normal file
@ -0,0 +1,76 @@
|
||||
package gomod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
lsRemote _lsRemote
|
||||
|
||||
tagRegexp = regexp.MustCompile(`refs/tags/(.*)`)
|
||||
headRegexp = regexp.MustCompile(`refs/heads/(master|main)`)
|
||||
)
|
||||
|
||||
type _lsRemote struct {
|
||||
url string
|
||||
output string
|
||||
}
|
||||
|
||||
func (lr *_lsRemote) setUrl(url string) *_lsRemote {
|
||||
url = strings.ReplaceAll(url, "https://", "")
|
||||
url = strings.ReplaceAll(url, "http://", "")
|
||||
if v := strings.Split(url, "/"); len(v) > 3 {
|
||||
url = strings.Join(v[:3], "/")
|
||||
}
|
||||
if !strings.HasPrefix(url, "https") {
|
||||
url = "https://" + url
|
||||
}
|
||||
lr.url = url
|
||||
return lr
|
||||
}
|
||||
|
||||
func (lr *_lsRemote) command() error {
|
||||
cmd := exec.Command("git", "ls-remote", "--heads", "--tags", "--sort=v:refname", lr.url)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithField("cmd", cmd.String()).Errorf("run command failed: %v", err)
|
||||
return err
|
||||
}
|
||||
lr.output = string(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *_lsRemote) tagOrCommitID() (string, error) {
|
||||
if err := lr.command(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
lr.output = strings.Trim(lr.output, "\n ")
|
||||
commits := strings.Split(lr.output, "\n")
|
||||
if len(commits) == 0 {
|
||||
return "", fmt.Errorf("get commit log empty")
|
||||
}
|
||||
commit := commits[len(commits)-1]
|
||||
|
||||
if strings.Contains(commit, "refs/heads/") {
|
||||
matches := headRegexp.FindStringSubmatch(commit)
|
||||
if len(matches) != 2 {
|
||||
return "", fmt.Errorf("%s no match rule: %s", commit, headRegexp.String())
|
||||
}
|
||||
return strings.Trim(strings.ReplaceAll(commit, matches[0], ""), " \n\t\r"), nil
|
||||
}
|
||||
|
||||
if strings.Contains(commit, "refs/tags/") {
|
||||
matches := tagRegexp.FindStringSubmatch(commit)
|
||||
if len(matches) != 2 {
|
||||
return "", fmt.Errorf("%s no match rule: %s", commit, tagRegexp.String())
|
||||
}
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%s no match heads and tags rule", commit)
|
||||
}
|
17
git-ls-remote_test.go
Normal file
17
git-ls-remote_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package gomod
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNormalUrlGetTag(t *testing.T) {
|
||||
output, err := lsRemote.setUrl("https://gitter.top/coco/bootstrap").tagOrCommitID()
|
||||
assert.NoError(t, err)
|
||||
t.Log(output)
|
||||
|
||||
output, err = lsRemote.setUrl("https://github.com/spf13/cobra").tagOrCommitID()
|
||||
assert.NoError(t, err)
|
||||
t.Log(output)
|
||||
}
|
23
go.mod
Normal file
23
go.mod
Normal file
@ -0,0 +1,23 @@
|
||||
module gitter.top/apps/gomod
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/google/go-github/v64 v64.0.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gitter.top/common/lormatter v0.0.1
|
||||
golang.org/x/mod v0.20.0
|
||||
golang.org/x/net v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
40
go.sum
Normal file
40
go.sum
Normal file
@ -0,0 +1,40 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-github/v64 v64.0.0 h1:4G61sozmY3eiPAjjoOHponXDBONm+utovTKbyUb2Qdg=
|
||||
github.com/google/go-github/v64 v64.0.0/go.mod h1:xB3vqMQNdHzilXBiO2I+M7iEFtHf+DP/omBOv6tQzVo=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gitter.top/common/lormatter v0.0.1 h1:RwNmDsgXl6gjU1KBgaVdDgG+dYDLjhM76TQu/C6bArY=
|
||||
gitter.top/common/lormatter v0.0.1/go.mod h1:P3v4TVOF52RRh41UgcABmDWyK8ZRo6ekbG/uln8PH+w=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
222
gomod.go
Normal file
222
gomod.go
Normal file
@ -0,0 +1,222 @@
|
||||
package gomod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v64/github"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultVersion = "latest"
|
||||
)
|
||||
|
||||
var (
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
)
|
||||
|
||||
func goGet(u, v string) error {
|
||||
versionUrl := u + "@" + v
|
||||
cmd := exec.Command("go", "get", "-u", versionUrl)
|
||||
if _, err := cmd.CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type upgrade interface {
|
||||
upgrade() error
|
||||
}
|
||||
|
||||
type githubRepo struct {
|
||||
client *github.Client
|
||||
url string
|
||||
owner string
|
||||
repo string
|
||||
semV string
|
||||
}
|
||||
|
||||
func newGithubRepo(url string) *githubRepo {
|
||||
return &githubRepo{
|
||||
client: github.NewClient(&http.Client{
|
||||
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
|
||||
Timeout: time.Second * 3,
|
||||
}),
|
||||
url: url,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *githubRepo) parse() error {
|
||||
ss := strings.Split(g.url, "/")
|
||||
if len(ss) < 3 {
|
||||
return fmt.Errorf("invalid github url: %s", g.url)
|
||||
}
|
||||
g.owner = ss[1]
|
||||
g.repo = ss[2]
|
||||
if len(ss) == 4 {
|
||||
g.semV = ss[3]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *githubRepo) getVersion() string {
|
||||
ctx := context.Background()
|
||||
release, _, err := g.client.Repositories.GetLatestRelease(ctx, g.owner, g.repo)
|
||||
if err == nil {
|
||||
return *release.TagName
|
||||
}
|
||||
// get latest commit id
|
||||
commits, _, err := g.client.Repositories.ListCommits(ctx, g.owner, g.repo, &github.CommitsListOptions{
|
||||
ListOptions: github.ListOptions{
|
||||
Page: 0,
|
||||
PerPage: 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return defaultVersion
|
||||
}
|
||||
if len(commits) == 0 {
|
||||
return defaultVersion
|
||||
}
|
||||
return (*commits[0].SHA)[:8]
|
||||
}
|
||||
|
||||
func (g *githubRepo) upgrade() error {
|
||||
if err := g.parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
v := g.getVersion()
|
||||
if err := goGet(g.url, v); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.WithField("url", g.url+"@"+v).Infof("upgrade success")
|
||||
return nil
|
||||
}
|
||||
|
||||
type repo struct {
|
||||
dependency string
|
||||
originRepo string
|
||||
}
|
||||
|
||||
func newRepo(dep string) *repo {
|
||||
return &repo{
|
||||
dependency: dep,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repo) extraGoImportMetadata(reader io.Reader) (string, error) {
|
||||
tokenizer := html.NewTokenizer(reader)
|
||||
for {
|
||||
tt := tokenizer.Next()
|
||||
switch tt {
|
||||
case html.ErrorToken:
|
||||
err := tokenizer.Err()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return "", errors.New("go-import meta attr not found")
|
||||
}
|
||||
return "", errors.New("read dependency metadata failed: " + err.Error())
|
||||
case html.StartTagToken, html.SelfClosingTagToken:
|
||||
t := tokenizer.Token()
|
||||
if t.Data != "meta" {
|
||||
continue
|
||||
}
|
||||
for i, attribute := range t.Attr {
|
||||
if attribute.Key == "name" && attribute.Val == "go-import" && i+1 < len(t.Attr) {
|
||||
return t.Attr[i+1].Val, nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repo) upgrade() error {
|
||||
u := "https://" + r.dependency + "?go-get=1"
|
||||
request, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
metadata, err := r.extraGoImportMetadata(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ss := strings.Split(metadata, " ")
|
||||
if len(ss) != 3 {
|
||||
return errors.New("go-import metadata invalid: " + metadata)
|
||||
}
|
||||
r.originRepo = ss[2]
|
||||
|
||||
v, err := lsRemote.setUrl(r.originRepo).tagOrCommitID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := goGet(r.dependency, v); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.WithField("url", r.dependency+"@"+v).Infof("upgrade success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ModUpgrade(upgradeIndirect bool) {
|
||||
modFileData, err := os.ReadFile("go.mod")
|
||||
if err != nil {
|
||||
logrus.Errorf("read go.mod file failed: %v", err)
|
||||
return
|
||||
}
|
||||
modFile, err := modfile.Parse("go.mod", modFileData, nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("parse go.mod file failed: %v", err)
|
||||
return
|
||||
}
|
||||
for _, mod := range modFile.Require {
|
||||
if mod.Indirect && !upgradeIndirect {
|
||||
continue
|
||||
}
|
||||
var repo upgrade
|
||||
if strings.Contains(mod.Mod.Path, "github.com") {
|
||||
repo = newGithubRepo(mod.Mod.Path)
|
||||
} else {
|
||||
repo = newRepo(mod.Mod.Path)
|
||||
}
|
||||
if err := repo.upgrade(); err != nil {
|
||||
logrus.WithField("url", mod.Mod.Path).Errorf("upgrade failed: %v. (starting fallback)", err)
|
||||
fallback(mod.Mod.Path)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tidy()
|
||||
}
|
||||
|
||||
func tidy() {
|
||||
cmd := exec.Command("go", "mod", "tidy")
|
||||
_ = cmd.Run()
|
||||
}
|
||||
|
||||
func fallback(repo string) {
|
||||
if err := goGet(repo, defaultVersion); err != nil {
|
||||
logrus.WithField("url", repo+"@"+defaultVersion).Errorf("upgrade failed: %v", err)
|
||||
return
|
||||
}
|
||||
logrus.WithField("url", repo+"@"+defaultVersion).Infof("upgrade success")
|
||||
}
|
71
gomod/main.go
Normal file
71
gomod/main.go
Normal file
@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"gitter.top/common/lormatter"
|
||||
|
||||
"gitter.top/apps/gomod"
|
||||
)
|
||||
|
||||
var (
|
||||
upgradeIndirect bool
|
||||
mod *cobra.Command
|
||||
)
|
||||
|
||||
func init() {
|
||||
formatter := &lormatter.Formatter{ShowField: true}
|
||||
logrus.SetFormatter(formatter)
|
||||
logrus.SetReportCaller(true)
|
||||
|
||||
mod = &cobra.Command{
|
||||
Use: "gomod",
|
||||
Short: "go mod manager",
|
||||
Example: "gomod",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
_, err := os.Stat("go.mod")
|
||||
if os.IsNotExist(err) {
|
||||
logrus.Fatalf("go.mod file not found on this directory")
|
||||
} else if err != nil {
|
||||
logrus.Fatalf("check go.mod file failed: %v", err)
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
},
|
||||
}
|
||||
mod.AddCommand(upgrade())
|
||||
mod.AddCommand(analyzed())
|
||||
}
|
||||
|
||||
func upgrade() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "update project dependencies to latest",
|
||||
Aliases: []string{"u"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
gomod.ModUpgrade(upgradeIndirect)
|
||||
},
|
||||
}
|
||||
cmd.Flags().BoolVarP(&upgradeIndirect, "indirect", "i", false, "upgrade indirect dependency")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func analyzed() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "analyzed",
|
||||
Short: "analyzed project dependencies",
|
||||
Aliases: []string{"a"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := mod.Execute(); err != nil {
|
||||
logrus.Errorf("execute command failed: %v", err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user