Repository: justwatchcom/gopass (go)

Issue:
Password store path in the config file strips "~"
In my config file I have the path to the password store as:

```
root:
  path: gpgcli-gitcli-fs+file://~/.local/share/password-store
```

The 1.7.0 version stopped recognizing it as the "home" path and it strips it.

The exact error is: 

```
Error: failed to list store: lstat /.local/share/password-store: no such file or directory
```

Pull request description:
Expand tilde in store path
Fixes #801

Patch:
diff --git a/pkg/backend/storage.go b/pkg/backend/storage.go
index fbd6d731..36f425a8 100644
--- a/pkg/backend/storage.go
+++ b/pkg/backend/storage.go
@@ -2,6 +2,7 @@ package backend
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/blang/semver"
 )
@@ -24,6 +25,7 @@ func (s StorageBackend) String() string {
 
 // Storage is an storage backend
 type Storage interface {
+	fmt.Stringer
 	Get(ctx context.Context, name string) ([]byte, error)
 	Set(ctx context.Context, name string, value []byte) error
 	Delete(ctx context.Context, name string) error
diff --git a/pkg/backend/storage/fs/store.go b/pkg/backend/storage/fs/store.go
index b3567f0d..c0156708 100644
--- a/pkg/backend/storage/fs/store.go
+++ b/pkg/backend/storage/fs/store.go
@@ -2,6 +2,7 @@ package fs
 
 import (
 	"context"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -22,7 +23,7 @@ type Store struct {
 // New creates a new store
 func New(dir string) *Store {
 	return &Store{
-		path: dir,
+		path: filepath.Clean(dir),
 	}
 }
 
@@ -114,6 +115,11 @@ func (s *Store) Version() semver.Version {
 	return semver.Version{Minor: 1}
 }
 
+// String implements fmt.Stringer
+func (s *Store) String() string {
+	return fmt.Sprintf("fs(v0.1.0,path:%s)", s.path)
+}
+
 // Available will check if this backend is useable
 func (s *Store) Available(ctx context.Context) error {
 	return s.Set(ctx, ".test", []byte("test"))
diff --git a/pkg/backend/storage/kv/consul/store.go b/pkg/backend/storage/kv/consul/store.go
index 3632b202..6418dcca 100644
--- a/pkg/backend/storage/kv/consul/store.go
+++ b/pkg/backend/storage/kv/consul/store.go
@@ -2,6 +2,7 @@ package consul
 
 import (
 	"context"
+	"fmt"
 	"strings"
 
 	"github.com/justwatchcom/gopass/pkg/out"
@@ -131,6 +132,11 @@ func (s *Store) Version() semver.Version {
 	return semver.Version{Major: 1}
 }
 
+// String implements fmt.Stringer
+func (s *Store) String() string {
+	return fmt.Sprintf("consul(1.0.0,%s)", s.prefix)
+}
+
 // Available will check if this backend is useable
 func (s *Store) Available(ctx context.Context) error {
 	return s.Set(ctx, ".test", []byte("test"))
diff --git a/pkg/backend/storage/kv/inmem/store.go b/pkg/backend/storage/kv/inmem/store.go
index f8c35211..0ec09ff6 100644
--- a/pkg/backend/storage/kv/inmem/store.go
+++ b/pkg/backend/storage/kv/inmem/store.go
@@ -116,6 +116,11 @@ func (m *InMem) Version() semver.Version {
 	return semver.Version{Major: 1}
 }
 
+// String implement fmt.Stringer
+func (m *InMem) String() string {
+	return "inmem()"
+}
+
 // Available will check if this backend is useable
 func (m *InMem) Available(ctx context.Context) error {
 	if m.data == nil {
diff --git a/pkg/backend/url.go b/pkg/backend/url.go
index 1d39f775..53612ea1 100644
--- a/pkg/backend/url.go
+++ b/pkg/backend/url.go
@@ -4,7 +4,10 @@ import (
 	"fmt"
 	"net"
 	"net/url"
+	"path/filepath"
 	"strings"
+
+	homedir "github.com/mitchellh/go-homedir"
 )
 
 // URL is a parsed backend URL
@@ -56,6 +59,14 @@ func ParseURL(us string) (*URL, error) {
 		u.Username = nu.User.Username()
 		u.Password, _ = nu.User.Password()
 	}
+	if nu.Host == "~" {
+		hd, err := homedir.Dir()
+		if err != nil {
+			return u, err
+		}
+		u.Path = filepath.Join(hd, u.Path)
+		nu.Host = ""
+	}
 	u.Query = nu.Query()
 	if nu.Host != "" {
 		h, p, err := net.SplitHostPort(nu.Host)
diff --git a/pkg/store/sub/storage.go b/pkg/store/sub/storage.go
index fec95015..330953ef 100644
--- a/pkg/store/sub/storage.go
+++ b/pkg/store/sub/storage.go
@@ -17,8 +17,8 @@ import (
 func (s *Store) initStorageBackend(ctx context.Context) error {
 	switch s.url.Storage {
 	case backend.FS:
-		out.Debug(ctx, "Using Storage Backend: fs")
 		s.storage = fs.New(s.url.Path)
+		out.Debug(ctx, "Using Storage Backend: %s", s.storage.String())
 	case backend.InMem:
 		out.Debug(ctx, "Using Storage Backend: inmem")
 		s.storage = inmem.New()
diff --git a/pkg/store/sub/store.go b/pkg/store/sub/store.go
index dba6014a..1c595a1c 100644
--- a/pkg/store/sub/store.go
+++ b/pkg/store/sub/store.go
@@ -77,7 +77,7 @@ func New(ctx context.Context, sc recipientHashStorer, alias string, u *backend.U
 		return nil, errors.Wrapf(err, "failed to init crypto backend: %s", err)
 	}
 
-	out.Debug(ctx, "sub.New - initialized - storage: %p - rcs: %p - crypto: %p", s.storage, s.rcs, s.crypto)
+	out.Debug(ctx, "sub.New - initialized - storage: %s (%p) - rcs: %s (%p) - crypto: %s (%p)", s.storage.Name(), s.storage, s.rcs.Name(), s.rcs, s.crypto.Name(), s.crypto)
 	return s, nil
 }
 


Test patch:
diff --git a/pkg/backend/url_test.go b/pkg/backend/url_test.go
index 25f61ab6..a5b80046 100644
--- a/pkg/backend/url_test.go
+++ b/pkg/backend/url_test.go
@@ -1,9 +1,12 @@
 package backend
 
 import (
+	"path/filepath"
 	"testing"
 
+	homedir "github.com/mitchellh/go-homedir"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	yaml "gopkg.in/yaml.v2"
 )
 
@@ -68,12 +71,15 @@ func TestURLString(t *testing.T) {
 }
 
 func TestParseScheme(t *testing.T) {
+	hd, err := homedir.Dir()
+	require.NoError(t, err)
 	for _, tc := range []struct {
 		Name    string
 		URL     string
 		Crypto  CryptoBackend
 		RCS     RCSBackend
 		Storage StorageBackend
+		Path    string
 	}{
 		{
 			Name:    "legacy file path",
@@ -96,6 +102,14 @@ func TestParseScheme(t *testing.T) {
 			RCS:     Noop,
 			Storage: Consul,
 		},
+		{
+			Name:    "Homedir expansion",
+			URL:     "gpgcli-gitcli-fs+file://~/.local/share/password-store",
+			Crypto:  GPGCLI,
+			RCS:     GitCLI,
+			Storage: FS,
+			Path:    filepath.Join(hd, ".local", "share", "password-store"),
+		},
 		//{
 		//	URL:     "plain+vault-http://localhost:9600/foo/bar",
 		//	Crypto:  Plain,
@@ -109,6 +123,9 @@ func TestParseScheme(t *testing.T) {
 		assert.Equal(t, tc.Crypto, u.Crypto, tc.Name)
 		assert.Equal(t, tc.RCS, u.RCS, tc.Name)
 		assert.Equal(t, tc.Storage, u.Storage, tc.Name)
+		if tc.Path != "" {
+			assert.Equal(t, tc.Path, u.Path, tc.Name)
+		}
 	}
 }