Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

internal/config: migrate local config to .git/glab-cli #813

Merged
merged 1 commit into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ test/testdata-*
coverage*
vendor
log.txt.glab-cli
.git
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ Get a GitLab access token at <https://gitlab.com/-/profile/personal_access_token

## Configuration

`glab` follows the XDG Base Directory [Spec](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html): global configuration file is saved at `~/.config/glab-cli`. Local configuration file is saved at the root of the working git directory and automatically added to `.gitignore`.
`glab` follows the XDG Base Directory [Spec](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html): global configuration file is saved at `~/.config/glab-cli`. Local configuration file is saved at `.git/glab-cli` in the current working git directory.

**To set configuration globally**

Expand Down
31 changes: 31 additions & 0 deletions internal/config/config_migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ func MigrateOldConfig() error {
if err := migrateUserConfigs(".glab-cli/config", cfg, false); err != nil {
return err
}

// move local config from {currentDir}/.glab-cli/config to {currentDir}/.git/glab-cli
oldLocalCfgFile := OldLocalConfigFile()
if CheckFileExists(oldLocalCfgFile) {
log.Println("- Migrating local config dir from", oldLocalCfgFile, "to", LocalConfigFile())
newLocalPath := LocalConfigDir()
if !CheckPathExists(filepath.Join(newLocalPath...)) {
if err := os.MkdirAll(filepath.Join(newLocalPath...), os.ModePerm); err != nil {
return fmt.Errorf("failed to create new local config dir: %v", err)
}
}
err = copy(oldLocalCfgFile, LocalConfigFile())
if err != nil {
return err
}
// backup old local config file
err = BackupConfigFile(oldLocalCfgFile)
if err != nil {
return err
}
}
return nil
}

Expand Down Expand Up @@ -215,3 +236,13 @@ func writeConfig(cfg Config, key, value string, isGlobal bool) (nCfg Config, err
}
return
}

func copy(src string, dst string) error {
// Read all content of src to data
data, err := ioutil.ReadFile(src)
if err != nil {
return err
}
// Write data to dst
return WriteFile(dst, data, 0600)
}
2 changes: 1 addition & 1 deletion internal/config/config_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type HostConfig struct {
Host string
}

// This type implements a low-level get/set config that is backed by an in-memory tree of Yaml
// ConfigMap type implements a low-level get/set config that is backed by an in-memory tree of Yaml
// nodes. It allows us to interact with a yaml-based config programmatically, preserving any
// comments that were present when the yaml was parsed.
type ConfigMap struct {
Expand Down
37 changes: 0 additions & 37 deletions internal/config/file.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package config

import (
"bufio"
"os"
)

Expand All @@ -22,42 +21,6 @@ func CheckFileExists(filename string) bool {
return !info.IsDir()
}

// CheckFileHasLine : returns true if line exists in file, otherwise false (also for non-existant file).
func CheckFileHasLine(filePath, line string) bool {
f, err := os.Open(filePath)
if err != nil {
return false
}
defer f.Close()

fs := bufio.NewScanner(f)
fs.Split(bufio.ScanLines)

for fs.Scan() {
if fs.Text() == line {
return true
}
}

return false
}

// ReadAndAppend : appends string to file
func ReadAndAppend(file, text string) error {
// If the file doesn't exist, create it, or append to the file
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
if _, err := f.Write([]byte(text)); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}

// BackupConfigFile creates a backup of the provided config file
var BackupConfigFile = func(filename string) error {
return os.Rename(filename, filename+".bak")
Expand Down
102 changes: 0 additions & 102 deletions internal/config/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config
import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/alecthomas/assert"
Expand Down Expand Up @@ -67,104 +66,3 @@ func Test_BackupConfigFile(t *testing.T) {
assert.EqualError(t, err, "rename /Path/Not/Exist /Path/Not/Exist.bak: no such file or directory")
})
}

func Test_CheckFileHasLine(t *testing.T) {
t.Run("success", func(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Skipf("Unexpected error creeating temporary file for testing = %s", err)
}
fPath := file.Name()
defer os.Remove(fPath)

_, _ = file.WriteString("profclems/glab")

got := CheckFileHasLine(fPath, "profclems/glab")
assert.True(t, got)
})
t.Run("failed", func(t *testing.T) {
t.Run("no-line-present", func(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Skipf("Unexpected error creeating temporary file for testing = %s", err)
}
fPath := file.Name()
defer os.Remove(fPath)

_, _ = file.WriteString("profclems/glab")

got := CheckFileHasLine(fPath, "maxice8/glab")
assert.False(t, got)
})
t.Run("no-file-present", func(t *testing.T) {
got := CheckFileHasLine("/Path/Not/Exist", "profclems/glab")
assert.False(t, got)
})
})
}

func Test_ReadAndAppend(t *testing.T) {
t.Run("success", func(t *testing.T) {
t.Run("write", func(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Skipf("Unexpected error creating temporary file for testing = %s", err)
}
fPath := file.Name()
defer os.Remove(fPath)

err = ReadAndAppend(fPath, "profclems/glab")
assert.NoError(t, err)
got := CheckFileHasLine(fPath, "profclems/glab")
assert.True(t, got)
})
t.Run("create-and-write", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Skipf("Unexpected error creating temporary directory for testing = %s", err)
}
defer os.RemoveAll(dir)

fPath := filepath.Join(dir, "file")

err = ReadAndAppend(fPath, "profclems/glab")
assert.NoError(t, err)
got := CheckFileHasLine(fPath, "profclems/glab")
assert.True(t, got)
})
t.Run("create-and-write-and-append", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Skipf("Unexpected error creating temporary directory for testing = %s", err)
}
defer os.RemoveAll(dir)

fPath := filepath.Join(dir, "file")

err = ReadAndAppend(fPath, "profclems/glab")
assert.NoError(t, err)
err = ReadAndAppend(fPath, "maxice8/glab")
assert.NoError(t, err)
})
t.Run("write-and-append", func(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Skipf("Unexpected error creating temporary file for testing = %s", err)
}
fPath := file.Name()
defer os.Remove(fPath)

err = ReadAndAppend(fPath, "profclems/glab")
assert.NoError(t, err)

err = ReadAndAppend(fPath, "maxice8/glab")
assert.NoError(t, err)
})
})
t.Run("failed", func(t *testing.T) {
t.Run("no-permissions", func(t *testing.T) {
err := ReadAndAppend("/no-perm", "profclems/glab")
assert.EqualError(t, err, "open /no-perm: permission denied")
})
})
}
18 changes: 9 additions & 9 deletions internal/config/local_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"path"
"path/filepath"

"gopkg.in/yaml.v3"
)
Expand All @@ -13,10 +14,12 @@ type LocalConfig struct {
Parent Config
}

const oldLocalConfigFile = ".glab-cli/config/config.yml"

// LocalConfigDir returns the local config path in map
// which must be joined for complete path
var LocalConfigDir = func() []string {
return []string{".glab-cli", "config"}
return []string{".git", "glab-cli"}
}

// LocalConfigFile returns the config file name with full path
Expand All @@ -25,6 +28,11 @@ var LocalConfigFile = func() string {
return path.Join(configFile...)
}

// OldLocalConfigFile returns the path to the old local config path.
func OldLocalConfigFile() string {
return filepath.Clean(oldLocalConfigFile)
}

func (a *LocalConfig) Get(key string) (string, bool) {
key = ConfigKeyEquivalence(key)
if a.Empty() {
Expand Down Expand Up @@ -72,14 +80,6 @@ func (a *LocalConfig) Write() error {
return fmt.Errorf("failed to write config: %w", err)
}

// Append local config dir if not already ignored in the .gitignore file
if !CheckFileHasLine(".gitignore", LocalConfigDir()[0]) {
err := ReadAndAppend(".gitignore", LocalConfigDir()[0]+"\n")
if err != nil {
return fmt.Errorf("failed to write file to .gitignore: %w", err)
}
}

return nil
}

Expand Down
11 changes: 9 additions & 2 deletions internal/config/local_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@ import (

func Test_LocalConfigDir(t *testing.T) {
got := LocalConfigDir()
assert.ElementsMatch(t, []string{".glab-cli", "config"}, got)
assert.ElementsMatch(t, []string{".git", "glab-cli"}, got)
}

func Test_LocalConfigFile(t *testing.T) {
t.Run("default", func(t *testing.T) {
expectedPath := filepath.Join(".glab-cli", "config", "config.yml")
expectedPath := filepath.Join(".git", "glab-cli", "config.yml")
got := LocalConfigFile()
assert.Equal(t, expectedPath, got)
})

t.Run("old config file", func(t *testing.T) {
expectedPath := filepath.Join(".glab-cli", "config", "config.yml")
got := OldLocalConfigFile()
assert.Equal(t, expectedPath, got)
})

t.Run("modified-LocalConfigDir()", func(t *testing.T) {
expectedPath := filepath.Join(".config", "glab-cli", "config.yml")

Expand Down