feat(cli): add git push option with message

This commit is contained in:
DataHearth 2022-02-28 23:41:21 +01:00
parent 759604ebda
commit 71526ec28d
4 changed files with 137 additions and 48 deletions

View File

@ -2,13 +2,17 @@ storage:
# Where will be the repository folder located ? [DEFAULT: MacOS($TMPDIR/config-mapper) | Linux(/tmp/config-mapper)]
location: /path/to/folder
git:
# * by default, if ssh dict is set with its keys filled, I'll try to clone with SSH
# username used for commit author
name: USERNAME
# email used for commit author
email: EMAIL
repository: git@github.com:DataHearth/my-config.git
basic-auth:
username: USERNAME
# * NOTE: if you're having trouble with error "authentication required", you should maybe use a token access
# * In some cases, it's due to 2FA authentication enabled on the git hosting provided
password: TOKEN
# * by default, if ssh dict is set with its keys filled, I'll try to clone with SSH
ssh:
# path can be relative and can contain environment variables
private-key: /path/to/private/key

View File

@ -4,6 +4,8 @@ import (
"fmt"
"log"
"os"
"strconv"
"time"
mapper "github.com/datahearth/config-mapper/internal"
"github.com/pterm/pterm"
@ -38,7 +40,7 @@ var initCmd = &cobra.Command{
logger.Println("initializing config-mapper folder from configuration...")
if _, err := mapper.OpenGitRepo(config.Storage.Git, config.Storage.Location); err != nil {
if _, err := mapper.NewRepository(config.Storage.Git, config.Storage.Path); err != nil {
errLogger.Printf(pterm.Red(fmt.Sprintf("failed to initialize folder: %v\n", err)))
os.Exit(1)
}
@ -59,7 +61,7 @@ var loadCmd = &cobra.Command{
os.Exit(1)
}
el := mapper.NewElement([]mapper.ItemLocation{}, config.Storage.Location)
el := mapper.NewElement([]mapper.ItemLocation{}, config.Storage.Path)
if !viper.GetBool("load-disable-files") {
el.AddItems(config.Files)
@ -88,13 +90,17 @@ var saveCmd = &cobra.Command{
saved location based on your configuration file`,
Run: func(cmd *cobra.Command, args []string) {
var config mapper.Configuration
if err := viper.Unmarshal(&config); err != nil {
errLogger.Printf(pterm.Red(fmt.Sprintf("failed to decode configuration: %v\n", err)))
os.Exit(1)
}
el := mapper.NewElement([]mapper.ItemLocation{}, config.Storage.Location)
repo, err := mapper.NewRepository(config.Storage.Git, config.Storage.Path)
if err != nil {
errLogger.Printf(pterm.Red(fmt.Sprintf("failed to open repository at %s: %v\n", config.Storage.Path, err)))
os.Exit(1)
}
el := mapper.NewElement([]mapper.ItemLocation{}, config.Storage.Path)
if !viper.GetBool("save-disable-files") {
el.AddItems(config.Files)
@ -114,6 +120,18 @@ var saveCmd = &cobra.Command{
os.Exit(1)
}
}
if viper.GetBool("push") {
s, _ := pterm.DefaultSpinner.WithShowTimer(true).WithRemoveWhenDone(false).Start("Pushing changes to remote repository")
if err := repo.PushChanges(viper.GetString("message")); err != nil {
errLogger.Printf(pterm.Red(fmt.Sprintf("failed to push changes to repository: %v\n", err)))
os.Exit(1)
}
s.Stop()
s.Success("Changes pushed")
}
},
}
@ -136,9 +154,13 @@ func init() {
saveCmd.PersistentFlags().Bool("disable-files", false, "files will be ignored")
saveCmd.PersistentFlags().Bool("disable-folders", false, "folders will be ignored")
saveCmd.PersistentFlags().Bool("disable-pkgs", false, "package managers will be ignored")
saveCmd.Flags().BoolP("push", "p", false, "new configurations will be committed and pushed")
saveCmd.Flags().StringP("message", "m", strconv.FormatInt(time.Now().Unix(), 10), "combined with --push to set a commit message")
viper.BindPFlag("save-disable-files", saveCmd.PersistentFlags().Lookup("disable-files"))
viper.BindPFlag("save-disable-folders", saveCmd.PersistentFlags().Lookup("disable-folders"))
viper.BindPFlag("save-disable-pkgs", saveCmd.PersistentFlags().Lookup("disable-pkgs"))
viper.BindPFlag("push", saveCmd.Flags().Lookup("push"))
viper.BindPFlag("message", saveCmd.Flags().Lookup("message"))
}
func Execute() {

View File

@ -23,14 +23,16 @@ type ItemLocation struct {
}
type Storage struct {
Location string `mapstructure:"location" yaml:"location"`
Git Git `mapstructure:"git" yaml:"git"`
Path string `mapstructure:"location" yaml:"location"`
Git Git `mapstructure:"git" yaml:"git"`
}
type Git struct {
Repository string `mapstructure:"repository" yaml:"repository"`
BasicAuth BasicAuth `mapstructure:"basic-auth" yaml:"basic-auth"`
SSH Ssh `mapstructure:"ssh" yaml:"ssh"`
URL string `mapstructure:"repository" yaml:"repository"`
Name string `mapstructure:"name" yaml:"name"`
Email string `mapstructure:"email" yaml:"email"`
BasicAuth BasicAuth `mapstructure:"basic-auth" yaml:"basic-auth"`
SSH Ssh `mapstructure:"ssh" yaml:"ssh"`
}
type BasicAuth struct {

View File

@ -3,8 +3,10 @@ package mapper
import (
"errors"
"os"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
@ -15,56 +17,115 @@ var (
ErrInvalidEnv = errors.New("found invalid environment variable in path")
)
func OpenGitRepo(c Git, l string) (*git.Repository, error) {
s, err := os.Stat(l)
if err != nil {
if os.IsNotExist(err) {
var auth transport.AuthMethod
type RepositoryActions interface {
PushChanges(msg string) error
openRepository() error
}
if c.SSH.Passphrase != "" && c.SSH.PrivateKey != "" {
privateKey, err := absolutePath(c.SSH.PrivateKey)
if err != nil {
return nil, err
}
type Repository struct {
auth transport.AuthMethod
repository *git.Repository
repoPath string
author author
url string
}
if _, err := os.Stat(privateKey); err != nil {
return nil, err
}
type author struct {
name string
email string
}
auth, err = ssh.NewPublicKeysFromFile("git", privateKey, c.SSH.Passphrase)
if err != nil {
return nil, err
}
} else {
auth = &http.BasicAuth{
Username: c.BasicAuth.Username,
Password: c.BasicAuth.Password,
}
}
func NewRepository(config Git, repoPath string) (RepositoryActions, error) {
var auth transport.AuthMethod
repo, err := git.PlainClone(l, false, &git.CloneOptions{
URL: c.Repository,
Progress: os.Stdout,
Auth: auth,
})
if err != nil {
return nil, err
}
return repo, nil
if config.SSH.Passphrase != "" && config.SSH.PrivateKey != "" {
privateKey, err := absolutePath(config.SSH.PrivateKey)
if err != nil {
return nil, err
}
return nil, err
if _, err := os.Stat(privateKey); err != nil {
return nil, err
}
auth, err = ssh.NewPublicKeysFromFile("git", privateKey, config.SSH.Passphrase)
if err != nil {
return nil, err
}
} else {
auth = &http.BasicAuth{
Username: config.BasicAuth.Username,
Password: config.BasicAuth.Password,
}
}
if !s.IsDir() {
return nil, ErrDirIsFile
repo := &Repository{
auth: auth,
repository: nil,
repoPath: repoPath,
author: author{
name: config.Name,
email: config.Email,
},
}
repo, err := git.PlainOpen(l)
if err != nil {
if err := repo.openRepository(); err != nil {
return nil, err
}
return repo, nil
}
func (r *Repository) openRepository() error {
s, err := os.Stat(r.repoPath)
if err != nil {
if os.IsNotExist(err) {
repo, err := git.PlainClone(r.repoPath, false, &git.CloneOptions{
URL: r.url,
Progress: os.Stdout,
Auth: r.auth,
})
if err != nil {
return err
}
r.repository = repo
return nil
}
return err
}
if !s.IsDir() {
return ErrDirIsFile
}
repo, err := git.PlainOpen(r.repoPath)
if err != nil {
return err
}
r.repository = repo
return nil
}
func (r *Repository) PushChanges(msg string) error {
w, err := r.repository.Worktree()
if err != nil {
return err
}
if _, err := w.Add("."); err != nil {
return err
}
w.Commit(msg, &git.CommitOptions{
Author: &object.Signature{
Name: r.author.name,
Email: r.author.email,
When: time.Now(),
},
})
return r.repository.Push(&git.PushOptions{})
}