Skip to content

Commit

Permalink
♻️ refactored and added configuration
Browse files Browse the repository at this point in the history
moved things around in order to have a better organisation
moved from dep to go mod
changed the way git is used from shell commands to go-git
enabled configuration through git config in the section 'mergo'
  • Loading branch information
JulesFaucherre committed Nov 25, 2020
1 parent c8a7602 commit 811c986
Show file tree
Hide file tree
Showing 35 changed files with 1,246 additions and 1,257 deletions.
33 changes: 19 additions & 14 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
image: golang:latest

variables:
PROJECT_NAME: mergo
REPO_NAME: gitlab.com/jfaucherre/mergo
PROJECT_NAME: mergo

before_script:
- mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
- ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME
- cd $GOPATH/src/$REPO_NAME
- go get -u github.com/golang/dep/cmd/dep
- dep ensure

stages:
- dep
- test
- build

install:
stage: dep
script:
- go mod download
- go mod vendor

format:
stage: test
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
- go test -race $(go list ./... | grep -v /vendor/)
stage: test
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
- go test -race $(go list ./... | grep -v /vendor/)

compile:
stage: build
script:
- go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/$PROJECT_NAME
artifacts:
paths:
- $PROJECT_NAME
stage: build
script:
- go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/$PROJECT_NAME
artifacts:
paths:
- $PROJECT_NAME
33 changes: 0 additions & 33 deletions Gopkg.lock

This file was deleted.

38 changes: 0 additions & 38 deletions Gopkg.toml

This file was deleted.

62 changes: 40 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,80 @@

### Presentation

Have you ever been in the situation where you code in a group project, pushed your work on your remote branch and then comes the moment where you need to create a pull request on your git client in order for your code to be be present on the main branch.
This tool comes from my need not to leave my terminal and that, although there is the hub client, you can not create a pull request for any git host you'd like from your terminal
So this binary creates pull request from terminal, it has been thought so that it doesn't have your specific host, you can add it easily
Its behavior is trying to be as close as possible to the git commands behavior and its default values try to follow the github pull request defaults

You have to leave your terminal to go on a web interface and then click several button just to create a "github like pull request".
The purpose of mergo is to create pull requests without having to leave my beloved terminal.
### Install
```bash
go get -u gitlab.com/jfaucherre/mergo
```
### Usage
```bash
mergo --help
Usage:
mergo [OPTIONS]

Application Options:
-d, --head= The head branch you want to merge into the base
-b, --base= The base branch you want to merge into (default: master)
--host= The git host you use, ie github, gitlab, etc.
--remote= The remote to use (default: origin)
--repository= The name of the repository on which you want to make the pull request
--owner= The owner of the repository
-v, --verbose Add logs, you can have more logs by calling it more times
-d, --head= The head branch you want to merge into the base (default: the actual checked out branch)
-b, --base= The base branch you want to merge into (default: master)
-m, --message= The pull request message (default: If you have only one commit, it takes this commit's message)
-f, --force Force the pull request, doesn't ask you if you have unstaged changes or things like that
-c, --clipboard Copy the URLs of your merge requests to your clipboard
--remote= The remote to use (default: origin)
-r, --remote-url= The remote URLs to use. Note that this overwrite the "remote" option
-e, --force-edition Force the edition of the message event it already have a value
--delete-creds= Use this option when you want to delete the credentials of an host

Help Options:
-h, --help Show this help message
-h, --help Show this help message
```
```bash
$> mergo -d dev -b staging --host=gitlab --owner=jfaucherre --repository=mergo
Enter the pull request's title:
My awesome pull request
<Your git configured editor will then open for you to write your pull request's content>
# placed in a repository on a branch you want to pull into master
mergo -m "My first pull request with mergo !"
```
If you don't give the repository informations, mergo will take the informations from the git repository you're in
### Configuration
You can configure the default behavior of the binary through the git config by writing to the `mergo` section
For example suppose the default branch you want to merge into is not master but staging you can write
```bash
$> mergo
Enter the pull request's title:
My awesome pull request
<Your git configured editor will then open for you to write your pull request's content>
git config add mergo.base staging
```
After that all the pull requests you are going to create with mergo are going to be merged into staging and not master
All the configurable values are:
* head
* base
* force
* clipboard
* remote
* remote-urls
* force-edition
* verbose
Note that if you want to configure verbose for a certain level (from 1 to 5) you must give an array of boolean and not an int, because of how [the argument parsing lib](https://github.com/jessevdk/go-flags#example) works:
```bash
# set log level to 3
git config add mergo.verbose true,true,true
```
### Personal config
As I like to keep all things in one place I have run the following command
In order to use it as any other git command I made an alias of it in git like that
```bash
git config --global alias.pr '!mergo'
```
So that I can then run
```bash
git pr
```
just after I have pushed code on my repository
### Support
Mergo actually support:
- github
- gitlab
Do not hesitate to propose pull request to support more git client
Do not hesitate to propose pull request to support more git hosts
111 changes: 57 additions & 54 deletions tools/configs.go → credentials/credentials.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// The package tools provides utilities for mergo
// The package credentials provides encryption for mergo
// The main utility you will find is to save, get and delete user's credentials
// You should use these functions when managing user's credentials since they
// come with their way of handling encryption
package tools
package credentials

import (
"crypto/aes"
Expand All @@ -19,73 +19,90 @@ import (
const keySize = 32

var (
configDir = path.Join(os.Getenv("HOME"), ".config", "mergo")
keyPath = path.Join(configDir, ".key")
configDir string
key []byte

ErrNoHostConfig = errors.New("no config for given host")
)

// GetHostConfig returns the config stored for the host 'host'
func GetHostConfig(host string) ([]byte, error) {
p := path.Join(configDir, host)
s, err := os.Stat(p)
// the init function ensures the config directory is present and creates it if
// it does not exist
func init() {
config, err := os.UserConfigDir()
if err != nil {
panic(err)
}

// ensure the configuration directory exists
configDir = path.Join(config, "mergo")
s, err := os.Stat(configDir)

if os.IsNotExist(err) {
err = os.MkdirAll(configDir, 0755)
}
if err != nil {
return nil, err
panic(err)
}
if s.IsDir() {
return nil, fmt.Errorf("%s must not be a directory", p)
if s != nil && !s.IsDir() {
panic(fmt.Errorf("%s must be a directory", configDir))
}

content, err := ioutil.ReadFile(p)
// create or load key
keyPath := path.Join(configDir, ".key")
_, err = os.Stat(keyPath)
if os.IsNotExist(err) {
key, err = createEncryptionKey(keyPath)
} else if err == nil {
key, err = ioutil.ReadFile(keyPath)
}
if err != nil {
panic(err)
}
}

// GetHostConfig returns the credentials stored for the host 'host'
func GetHostConfig(host string) ([]byte, error) {
p := path.Join(configDir, host)
_, err := os.Stat(p)

if os.IsNotExist(err) {
return nil, ErrNoHostConfig
}
if err != nil {
return nil, err
}

key, err := getEncryptionKey()
content, err := ioutil.ReadFile(p)
if err != nil {
return nil, err
}

return decrypt(content, key)
return decrypt(content)
}

// WriteHostConfig writes the config 'content' for the host 'host'
// WriteHostConfig writes the config 'content' as the credentials for the host
// 'host'
func WriteHostConfig(host string, content []byte) error {
s, err := os.Stat(configDir)

if err != nil && !os.IsNotExist(err) {
return err
}
if !os.IsNotExist(err) && !s.IsDir() {
return fmt.Errorf("%s must be a directory", configDir)
}
if os.IsNotExist(err) {
if err = os.MkdirAll(configDir, 0755); err != nil {
return err
}
}

p := path.Join(configDir, host)

key, err := getEncryptionKey()
if err != nil {
return err
}

encrypted, err := encrypt(content, key)
encrypted, err := encrypt(content)
if err != nil {
return err
}

return ioutil.WriteFile(p, encrypted, 0644)
}

// DeleteHostConfig deletes the config for host 'host'
// DeletedHostConfig takes a host name and deletes its credentials
func DeleteHostConfig(host string) error {
return os.Remove(path.Join(configDir, host))
err := os.Remove(path.Join(configDir, host))
if os.IsNotExist(err) {
return ErrNoHostConfig
}
return err
}

func encrypt(plaintext []byte, key []byte) ([]byte, error) {
func encrypt(plaintext []byte) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
Expand All @@ -104,7 +121,7 @@ func encrypt(plaintext []byte, key []byte) ([]byte, error) {
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

func decrypt(ciphertext []byte, key []byte) ([]byte, error) {
func decrypt(ciphertext []byte) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
Expand All @@ -124,21 +141,7 @@ func decrypt(ciphertext []byte, key []byte) ([]byte, error) {
return gcm.Open(nil, nonce, ciphertext, nil)
}

func getEncryptionKey() ([]byte, error) {
if key == nil {
_, err := os.Stat(keyPath)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
if os.IsNotExist(err) {
return createEncryptionKey()
}
return ioutil.ReadFile(keyPath)
}
return key, nil
}

func createEncryptionKey() ([]byte, error) {
func createEncryptionKey(keyPath string) ([]byte, error) {
key = make([]byte, keySize, keySize)
_, err := rand.Read(key)

Expand Down
Loading

0 comments on commit 811c986

Please sign in to comment.