Skip to content

Commit

Permalink
feat: add basic CLI + git helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
moul committed Apr 23, 2021
1 parent 9b2afda commit f5bd0bf
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 7 deletions.
45 changes: 45 additions & 0 deletions git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"fmt"
"os/exec"
"path/filepath"
"strings"

"moul.io/u"
)

func gitFindRootDir(path string) string {
for {
if u.DirExists(filepath.Join(path, ".git")) {
return path
}
parent := filepath.Dir(path)
if parent == path {
break
}
path = parent
}
return ""
}

func gitGetMainBranch(path string) (string, error) {
// equivalent of: git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'

cmd := exec.Command("git", "symbolic-ref", "refs/remotes/origin/HEAD")
stdout, _, err := u.ExecStandaloneOutputs(cmd)
if err != nil {
return "", fmt.Errorf("failed to execute command: %q: %w", cmd, err)
}

if len(stdout) == 0 {
return "", fmt.Errorf("invalid command output for %q", cmd)
}

branch := strings.TrimSpace(strings.TrimPrefix(string(stdout), "refs/remotes/origin/"))
if branch == "" || strings.ContainsRune(branch, '/') {
return "", fmt.Errorf("invalid branch: %q", branch)
}

return branch, nil
}
5 changes: 4 additions & 1 deletion go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

120 changes: 114 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"go.uber.org/zap"
"moul.io/motd"
"moul.io/srand"
"moul.io/u"
"moul.io/zapconfig"
)

Expand All @@ -21,15 +22,31 @@ func main() {
}
}

type Opts struct {
Path string
}

var (
rootFs = flag.NewFlagSet("<root>", flag.ExitOnError)
rootFs = flag.NewFlagSet("<root>", flag.ExitOnError)
doctorFs = flag.NewFlagSet("doctor", flag.ExitOnError)
maintenanceFs = flag.NewFlagSet("maintenance", flag.ExitOnError)
versionFs = flag.NewFlagSet("version", flag.ExitOnError)
opts Opts

logger *zap.Logger
)

func setupRootFlags(fs *flag.FlagSet) {
fs.StringVar(&opts.Path, "p", ".", "project's path")
}

func run(args []string) error {
rand.Seed(srand.Fast())

setupRootFlags(rootFs)
setupRootFlags(doctorFs)
setupRootFlags(maintenanceFs)

// init logger
{
var err error
Expand All @@ -42,9 +59,11 @@ func run(args []string) error {
root := &ffcli.Command{
FlagSet: rootFs,
Subcommands: []*ffcli.Command{
{Name: "doctor", Exec: doDoctor},
{Name: "maintenance", Exec: doMaintenance},
{Name: "version", Exec: doVersion},
{Name: "doctor", Exec: doDoctor, FlagSet: doctorFs},
{Name: "maintenance", Exec: doMaintenance, FlagSet: maintenanceFs},
{Name: "version", Exec: doVersion, FlagSet: versionFs},
// copyTemplate
// postTemplateClone
},
}

Expand All @@ -56,12 +75,101 @@ func run(args []string) error {
}

func doDoctor(ctx context.Context, args []string) error {
logger.Info("DOCTOR")
project, err := projectFromPath(opts.Path)
if err != nil {
return fmt.Errorf("invalid project: %w", err)
}
logger.Debug("doDoctor", zap.Any("opts", opts), zap.Any("project", project))
fmt.Println("## Project")
fmt.Println(u.PrettyJSON(project))
return nil
}

func doMaintenance(ctx context.Context, args []string) error {
logger.Info("MAINTENANCE")
project, err := projectFromPath(opts.Path)
if err != nil {
return fmt.Errorf("invalid project: %w", err)
}
logger.Debug("doMaintenance", zap.Any("opts", opts), zap.Any("project", project))

// - repoman.yml ->
// - template -> moul/golang-repo-template
// - exclude: - README.md
// - no-main / lib-only
// - auto update from template

// COMMANDS = hubsync checkoutmaster maintenance prlist
// REPOS ?= $(wildcard */)
// OPTS ?= ;
// REPOMAN ?= ~/go/src/moul.io/repoman
//
// .PHONY: $(COMMANDS)
// $(COMMANDS):
// @for repo in $(REPOS); do ( set -e; \
// echo "cd $$repo && make -s -f $(REPOMAN)/Makefile _do.$@ $(OPTS)"; \
// cd $$repo && make -s -f $(REPOMAN)/Makefile _do.$@ $(OPTS) \
// ); done
//
// _do.checkoutmaster: _do.hubsync
// git checkout master
//
// _do.hubsync:
// hub sync
//
// _do.prlist:
// @hub pr list -f "- %pC%>(8)%i%Creset %U - %t% l%n"
//
// _do.maintenance: _do.checkoutmaster
// # renovate.json
// mkdir -p .github
// git mv renovate.json .github/renovate.json || true
// git rm -f renovate.json || true
// cp ~/go/src/moul.io/golang-repo-template/.github/renovate.json .github/ || true
// git add .github/renovate.json || true
// git add renovate.json || true
//
// # dependabot
// cp ~/go/src/moul.io/golang-repo-template/.github/dependabot.yml .github/ || true
// git add .github/dependabot.yml || true
//
// # rules.mk
// if [ -f rules.mk ]; then cp ~/go/src/moul.io/rules.mk/rules.mk .; fi || true
//
// # authors
// if [ -f rules.mk ]; then make generate.authors; git add AUTHORS; fi || true
//
// # copyright
// set -xe; \
// for prefix in "©" "Copyright" "Copyright (c)"; do \
// for file in README.md LICENSE-APACHE LICENSE-MIT LICENSE COPYRIGHT; do \
// if [ -f "$$file" ]; then \
// sed -i "s/$$prefix 2014 /$$prefix 2014-2021 /" $$file; \
// sed -i "s/$$prefix 2015 /$$prefix 2015-2021 /" $$file; \
// sed -i "s/$$prefix 2016 /$$prefix 2016-2021 /" $$file; \
// sed -i "s/$$prefix 2017 /$$prefix 2017-2021 /" $$file; \
// sed -i "s/$$prefix 2018 /$$prefix 2018-2021 /" $$file; \
// sed -i "s/$$prefix 2019 /$$prefix 2019-2021 /" $$file; \
// sed -i "s/$$prefix 2020 /$$prefix 2020-2021 /" $$file; \
// sed -i "s/$$prefix \([0-9][0-9][0-9][0-9]\)-20[0-9][0-9] /$$prefix \1-2021 /" $$file; \
// sed -i "s/$$prefix 2021-2021/$$prefix 2021 /" $$file; \
// fi; \
// done; \
// done
//
// # golangci-lint fix
// sed -i "s/version: v1.26/version: v1.38/" .github/workflows/*.yml || true
// sed -i "s/version: v1.27/version: v1.38/" .github/workflows/*.yml || true
// sed -i "s/version: v1.28/version: v1.38/" .github/workflows/*.yml || true
//
// # apply changes
// git diff
// git diff --cached
// git branch -D dev/moul/maintenance || true
// git checkout -b dev/moul/maintenance
// git status
// git commit -s -a -m "chore: repo maintenance 🤖" -m "more details: https://github.com/moul/repoman"
// git push -u origin dev/moul/maintenance -f
// hub pull-request -m "chore: repo maintenance 🤖" -m "more details: https://github.com/moul/repoman" || $(MAKE) -f $(REPOMAN)/Makefile _do.prlist

return nil
}
Expand Down
48 changes: 48 additions & 0 deletions project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"fmt"
"path/filepath"

"go.uber.org/zap"
"moul.io/u"
)

type project struct {
Path string
Git struct {
Root string
MainBranch string
CurrentBranch string
OriginRemote string
}
}

func (p *project) checkoutMainBranch() error {
return nil
}

func projectFromPath(path string) (*project, error) {
abs, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("incorrect path: %q: %w", path, err)
}
path = abs

if !u.DirExists(path) {
return nil, fmt.Errorf("path is not a directory: %q", path)
}

project := &project{Path: path}
project.Git.Root = gitFindRootDir(path)
if project.Git.Root != "" {
project.Git.MainBranch, err = gitGetMainBranch(project.Git.Root)
if err != nil {
return nil, fmt.Errorf("cannot guess main branch: %w", err)
}
} else {
logger.Warn("project not withing a git directory", zap.String("path", path))
}

return project, nil
}

0 comments on commit f5bd0bf

Please sign in to comment.