diff --git a/bug/bug_test.go b/bug/bug_test.go
index 05f6e617e9497e27e5d19425d305f2bf04457f5c..6363f4e92db5bf2da5cc6620fea38f8cace203c5 100644
--- a/bug/bug_test.go
+++ b/bug/bug_test.go
@@ -130,10 +130,10 @@ func TestBugRemove(t *testing.T) {
 	remoteB := repository.CreateGoGitTestRepo(true)
 	defer repository.CleanupTestRepos(repo, remoteA, remoteB)
 
-	err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath())
+	err := repo.AddRemote("remoteA", remoteA.GetLocalRemote())
 	require.NoError(t, err)
 
-	err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath())
+	err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
 	require.NoError(t, err)
 
 	// generate a bunch of bugs
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index 4fc88015c254b5653b9201b9f506ad42b5e93daf..7228a38337fd5c9ee02897537df38191c78bf5de 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -5,8 +5,6 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
-	"path"
-	"path/filepath"
 	"strconv"
 	"sync"
 
@@ -129,25 +127,18 @@ func (c *RepoCache) write() error {
 }
 
 func (c *RepoCache) lock() error {
-	lockPath := repoLockFilePath(c.repo)
-
 	err := repoIsAvailable(c.repo)
 	if err != nil {
 		return err
 	}
 
-	err = os.MkdirAll(filepath.Dir(lockPath), 0777)
-	if err != nil {
-		return err
-	}
-
-	f, err := os.Create(lockPath)
+	f, err := c.repo.LocalStorage().Create(lockfile)
 	if err != nil {
 		return err
 	}
 
 	pid := fmt.Sprintf("%d", os.Getpid())
-	_, err = f.WriteString(pid)
+	_, err = f.Write([]byte(pid))
 	if err != nil {
 		return err
 	}
@@ -166,8 +157,7 @@ func (c *RepoCache) Close() error {
 	c.bugs = make(map[entity.Id]*BugCache)
 	c.bugExcerpts = nil
 
-	lockPath := repoLockFilePath(c.repo)
-	return os.Remove(lockPath)
+	return c.repo.LocalStorage().Remove(lockfile)
 }
 
 func (c *RepoCache) buildCache() error {
@@ -211,17 +201,11 @@ func (c *RepoCache) buildCache() error {
 	return nil
 }
 
-func repoLockFilePath(repo repository.Repo) string {
-	return path.Join(repo.GetPath(), "git-bug", lockfile)
-}
-
 // repoIsAvailable check is the given repository is locked by a Cache.
 // Note: this is a smart function that will cleanup the lock file if the
 // corresponding process is not there anymore.
 // If no error is returned, the repo is free to edit.
-func repoIsAvailable(repo repository.Repo) error {
-	lockPath := repoLockFilePath(repo)
-
+func repoIsAvailable(repo repository.RepoStorage) error {
 	// Todo: this leave way for a racey access to the repo between the test
 	// if the file exist and the actual write. It's probably not a problem in
 	// practice because using a repository will be done from user interaction
@@ -233,8 +217,7 @@ func repoIsAvailable(repo repository.Repo) error {
 	// computer. Should add a configuration that prevent the cleaning of the
 	// lock file
 
-	f, err := os.Open(lockPath)
-
+	f, err := repo.LocalStorage().Open(lockfile)
 	if err != nil && !os.IsNotExist(err) {
 		return err
 	}
@@ -266,7 +249,7 @@ func repoIsAvailable(repo repository.Repo) error {
 			return err
 		}
 
-		err = os.Remove(lockPath)
+		err = repo.LocalStorage().Remove(lockfile)
 		if err != nil {
 			return err
 		}
diff --git a/cache/repo_cache_bug.go b/cache/repo_cache_bug.go
index 0c583e0d1858c50b61bb1e161ddc231992cb5dad..ab567dddda861e5395bdb4643621c0d11da94cbf 100644
--- a/cache/repo_cache_bug.go
+++ b/cache/repo_cache_bug.go
@@ -5,8 +5,6 @@ import (
 	"encoding/gob"
 	"errors"
 	"fmt"
-	"os"
-	"path"
 	"sort"
 	"time"
 
@@ -20,10 +18,6 @@ const bugCacheFile = "bug-cache"
 
 var errBugNotInCache = errors.New("bug missing from cache")
 
-func bugCacheFilePath(repo repository.Repo) string {
-	return path.Join(repo.GetPath(), "git-bug", bugCacheFile)
-}
-
 // bugUpdated is a callback to trigger when the excerpt of a bug changed,
 // that is each time a bug is updated
 func (c *RepoCache) bugUpdated(id entity.Id) error {
@@ -52,7 +46,7 @@ func (c *RepoCache) loadBugCache() error {
 	c.muBug.Lock()
 	defer c.muBug.Unlock()
 
-	f, err := os.Open(bugCacheFilePath(c.repo))
+	f, err := c.repo.LocalStorage().Open(bugCacheFile)
 	if err != nil {
 		return err
 	}
@@ -99,7 +93,7 @@ func (c *RepoCache) writeBugCache() error {
 		return err
 	}
 
-	f, err := os.Create(bugCacheFilePath(c.repo))
+	f, err := c.repo.LocalStorage().Create(bugCacheFile)
 	if err != nil {
 		return err
 	}
diff --git a/cache/repo_cache_common.go b/cache/repo_cache_common.go
index 95e2f7bbfc2e71cb22fcecfb42390a2d8d6a2f09..5dc19d22ed83082d8dd0445aacd7e06c9721b511 100644
--- a/cache/repo_cache_common.go
+++ b/cache/repo_cache_common.go
@@ -3,6 +3,7 @@ package cache
 import (
 	"fmt"
 
+	"github.com/go-git/go-billy/v5"
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/bug"
@@ -30,13 +31,19 @@ func (c *RepoCache) AnyConfig() repository.ConfigRead {
 	return c.repo.AnyConfig()
 }
 
+// Keyring give access to a user-wide storage for secrets
 func (c *RepoCache) Keyring() repository.Keyring {
 	return c.repo.Keyring()
 }
 
-// GetPath returns the path to the repo.
-func (c *RepoCache) GetPath() string {
-	return c.repo.GetPath()
+// GetUserName returns the name the the user has used to configure git
+func (c *RepoCache) GetUserName() (string, error) {
+	return c.repo.GetUserName()
+}
+
+// GetUserEmail returns the email address that the user has used to configure git.
+func (c *RepoCache) GetUserEmail() (string, error) {
+	return c.repo.GetUserEmail()
 }
 
 // GetCoreEditor returns the name of the editor that the user has used to configure git.
@@ -49,14 +56,9 @@ func (c *RepoCache) GetRemotes() (map[string]string, error) {
 	return c.repo.GetRemotes()
 }
 
-// GetUserName returns the name the the user has used to configure git
-func (c *RepoCache) GetUserName() (string, error) {
-	return c.repo.GetUserName()
-}
-
-// GetUserEmail returns the email address that the user has used to configure git.
-func (c *RepoCache) GetUserEmail() (string, error) {
-	return c.repo.GetUserEmail()
+// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug
+func (c *RepoCache) LocalStorage() billy.Filesystem {
+	return c.repo.LocalStorage()
 }
 
 // ReadData will attempt to read arbitrary data from the given hash
diff --git a/cache/repo_cache_identity.go b/cache/repo_cache_identity.go
index 8957d4aabf1fec55b66416705abdbe98749ca340..8df5b810b29c90546767f3dad0cc42be76345b40 100644
--- a/cache/repo_cache_identity.go
+++ b/cache/repo_cache_identity.go
@@ -4,20 +4,13 @@ import (
 	"bytes"
 	"encoding/gob"
 	"fmt"
-	"os"
-	"path"
 
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/identity"
-	"github.com/MichaelMure/git-bug/repository"
 )
 
 const identityCacheFile = "identity-cache"
 
-func identityCacheFilePath(repo repository.Repo) string {
-	return path.Join(repo.GetPath(), "git-bug", identityCacheFile)
-}
-
 // identityUpdated is a callback to trigger when the excerpt of an identity
 // changed, that is each time an identity is updated
 func (c *RepoCache) identityUpdated(id entity.Id) error {
@@ -41,7 +34,7 @@ func (c *RepoCache) loadIdentityCache() error {
 	c.muIdentity.Lock()
 	defer c.muIdentity.Unlock()
 
-	f, err := os.Open(identityCacheFilePath(c.repo))
+	f, err := c.repo.LocalStorage().Open(identityCacheFile)
 	if err != nil {
 		return err
 	}
@@ -88,7 +81,7 @@ func (c *RepoCache) writeIdentityCache() error {
 		return err
 	}
 
-	f, err := os.Create(identityCacheFilePath(c.repo))
+	f, err := c.repo.LocalStorage().Create(identityCacheFile)
 	if err != nil {
 		return err
 	}
diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go
index 0037c7bbaf619140005d4c4fda4149aa74757059..1c5c41d217787ae86cedaedb360026bb3e1ab3eb 100644
--- a/cache/repo_cache_test.go
+++ b/cache/repo_cache_test.go
@@ -167,10 +167,10 @@ func TestRemove(t *testing.T) {
 	remoteB := repository.CreateGoGitTestRepo(true)
 	defer repository.CleanupTestRepos(repo, remoteA, remoteB)
 
-	err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath())
+	err := repo.AddRemote("remoteA", remoteA.GetLocalRemote())
 	require.NoError(t, err)
 
-	err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath())
+	err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
 	require.NoError(t, err)
 
 	repoCache, err := NewRepoCache(repo)
diff --git a/commands/env.go b/commands/env.go
index 5658342d2efdd806b03feb0dbe000d270337b402..8d12c5bfa0847a314ac86811589de92ff4533b1a 100644
--- a/commands/env.go
+++ b/commands/env.go
@@ -54,7 +54,7 @@ func loadRepo(env *Env) func(*cobra.Command, []string) error {
 			return fmt.Errorf("unable to get the current working directory: %q", err)
 		}
 
-		env.repo, err = repository.NewGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader})
+		env.repo, err = repository.OpenGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader})
 		if err == repository.ErrNotARepo {
 			return fmt.Errorf("%s must be run from within a git repo", rootCommandName)
 		}
diff --git a/commands/select/select.go b/commands/select/select.go
index cf861fcc885ad5b9eb2a28d502af8b9976c98be5..3df743874a86cdfbddba80ec43619273c5fdc527 100644
--- a/commands/select/select.go
+++ b/commands/select/select.go
@@ -5,14 +5,12 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
-	"path"
 
 	"github.com/pkg/errors"
 
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/entity"
-	"github.com/MichaelMure/git-bug/repository"
 )
 
 const selectFile = "select"
@@ -71,14 +69,12 @@ func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string
 
 // Select will select a bug for future use
 func Select(repo *cache.RepoCache, id entity.Id) error {
-	selectPath := selectFilePath(repo)
-
-	f, err := os.OpenFile(selectPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+	f, err := repo.LocalStorage().OpenFile(selectFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
 	if err != nil {
 		return err
 	}
 
-	_, err = f.WriteString(id.String())
+	_, err = f.Write([]byte(id.String()))
 	if err != nil {
 		return err
 	}
@@ -88,15 +84,11 @@ func Select(repo *cache.RepoCache, id entity.Id) error {
 
 // Clear will clear the selected bug, if any
 func Clear(repo *cache.RepoCache) error {
-	selectPath := selectFilePath(repo)
-
-	return os.Remove(selectPath)
+	return repo.LocalStorage().Remove(selectFile)
 }
 
 func selected(repo *cache.RepoCache) (*cache.BugCache, error) {
-	selectPath := selectFilePath(repo)
-
-	f, err := os.Open(selectPath)
+	f, err := repo.LocalStorage().Open(selectFile)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return nil, nil
@@ -115,7 +107,7 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) {
 
 	id := entity.Id(buf)
 	if err := id.Validate(); err != nil {
-		err = os.Remove(selectPath)
+		err = repo.LocalStorage().Remove(selectFile)
 		if err != nil {
 			return nil, errors.Wrap(err, "error while removing invalid select file")
 		}
@@ -135,7 +127,3 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) {
 
 	return b, nil
 }
-
-func selectFilePath(repo repository.RepoCommon) string {
-	return path.Join(repo.GetPath(), "git-bug", selectFile)
-}
diff --git a/doc/gen_docs.go b/doc/gen_docs.go
index 8bb596fe4b5e037ae03923a811404dead8817b26..482abd8e95162ab83e40c91c3f4f59bf54dc9ba6 100644
--- a/doc/gen_docs.go
+++ b/doc/gen_docs.go
@@ -3,7 +3,6 @@ package main
 import (
 	"fmt"
 	"os"
-	"path"
 	"path/filepath"
 	"sync"
 	"time"
@@ -42,7 +41,7 @@ func main() {
 
 func genManPage(root *cobra.Command) error {
 	cwd, _ := os.Getwd()
-	dir := path.Join(cwd, "doc", "man")
+	dir := filepath.Join(cwd, "doc", "man")
 
 	// fixed date to avoid having to commit each month
 	date := time.Date(2019, 4, 1, 12, 0, 0, 0, time.UTC)
@@ -69,7 +68,7 @@ func genManPage(root *cobra.Command) error {
 
 func genMarkdown(root *cobra.Command) error {
 	cwd, _ := os.Getwd()
-	dir := path.Join(cwd, "doc", "md")
+	dir := filepath.Join(cwd, "doc", "md")
 
 	files, err := filepath.Glob(dir + "/*.md")
 	if err != nil {
diff --git a/input/input.go b/input/input.go
index ca787ceb516654fa4a2560b7051ad9246de44c62..ef76f53ee59b17f4d444bca31f7f4fdbf01d2141 100644
--- a/input/input.go
+++ b/input/input.go
@@ -11,9 +11,11 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strings"
 
 	"github.com/MichaelMure/git-bug/repository"
+	"github.com/go-git/go-billy/v5/util"
 	"github.com/pkg/errors"
 )
 
@@ -35,7 +37,7 @@ const bugTitleCommentTemplate = `%s%s
 // BugCreateEditorInput will open the default editor in the terminal with a
 // template for the user to fill. The file is then processed to extract title
 // and message.
-func BugCreateEditorInput(repo repository.RepoCommon, preTitle string, preMessage string) (string, string, error) {
+func BugCreateEditorInput(repo repository.RepoCommonStorage, preTitle string, preMessage string) (string, string, error) {
 	if preMessage != "" {
 		preMessage = "\n\n" + preMessage
 	}
@@ -101,10 +103,10 @@ const bugCommentTemplate = `%s
 
 // BugCommentEditorInput will open the default editor in the terminal with a
 // template for the user to fill. The file is then processed to extract a comment.
-func BugCommentEditorInput(repo repository.RepoCommon, preMessage string) (string, error) {
+func BugCommentEditorInput(repo repository.RepoCommonStorage, preMessage string) (string, error) {
 	template := fmt.Sprintf(bugCommentTemplate, preMessage)
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
 
+	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
 	if err != nil {
 		return "", err
 	}
@@ -152,10 +154,10 @@ const bugTitleTemplate = `%s
 
 // BugTitleEditorInput will open the default editor in the terminal with a
 // template for the user to fill. The file is then processed to extract a title.
-func BugTitleEditorInput(repo repository.RepoCommon, preTitle string) (string, error) {
+func BugTitleEditorInput(repo repository.RepoCommonStorage, preTitle string) (string, error) {
 	template := fmt.Sprintf(bugTitleTemplate, preTitle)
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
 
+	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
 	if err != nil {
 		return "", err
 	}
@@ -212,10 +214,10 @@ const queryTemplate = `%s
 
 // QueryEditorInput will open the default editor in the terminal with a
 // template for the user to fill. The file is then processed to extract a query.
-func QueryEditorInput(repo repository.RepoCommon, preQuery string) (string, error) {
+func QueryEditorInput(repo repository.RepoCommonStorage, preQuery string) (string, error) {
 	template := fmt.Sprintf(queryTemplate, preQuery)
-	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
 
+	raw, err := launchEditorWithTemplate(repo, messageFilename, template)
 	if err != nil {
 		return "", err
 	}
@@ -238,11 +240,8 @@ func QueryEditorInput(repo repository.RepoCommon, preQuery string) (string, erro
 
 // launchEditorWithTemplate will launch an editor as launchEditor do, but with a
 // provided template.
-func launchEditorWithTemplate(repo repository.RepoCommon, fileName string, template string) (string, error) {
-	path := fmt.Sprintf("%s/%s", repo.GetPath(), fileName)
-
-	err := ioutil.WriteFile(path, []byte(template), 0644)
-
+func launchEditorWithTemplate(repo repository.RepoCommonStorage, fileName string, template string) (string, error) {
+	err := util.WriteFile(repo.LocalStorage(), fileName, []byte(template), 0644)
 	if err != nil {
 		return "", err
 	}
@@ -254,20 +253,25 @@ func launchEditorWithTemplate(repo repository.RepoCommon, fileName string, templ
 // method blocks until the editor command has returned.
 //
 // The specified filename should be a temporary file and provided as a relative path
-// from the repo (e.g. "FILENAME" will be converted to "[<reporoot>/].git/FILENAME"). This file
+// from the repo (e.g. "FILENAME" will be converted to "[<reporoot>/].git/git-bug/FILENAME"). This file
 // will be deleted after the editor is closed and its contents have been read.
 //
 // This method returns the text that was read from the temporary file, or
 // an error if any step in the process failed.
-func launchEditor(repo repository.RepoCommon, fileName string) (string, error) {
-	path := fmt.Sprintf("%s/%s", repo.GetPath(), fileName)
-	defer os.Remove(path)
+func launchEditor(repo repository.RepoCommonStorage, fileName string) (string, error) {
+	defer repo.LocalStorage().Remove(fileName)
 
 	editor, err := repo.GetCoreEditor()
 	if err != nil {
 		return "", fmt.Errorf("Unable to detect default git editor: %v\n", err)
 	}
 
+	repo.LocalStorage().Root()
+
+	// bypass the interface but that's ok: we need that because we are communicating
+	// the absolute path to an external program
+	path := filepath.Join(repo.LocalStorage().Root(), fileName)
+
 	cmd, err := startInlineCommand(editor, path)
 	if err != nil {
 		// Running the editor directly did not work. This might mean that
diff --git a/misc/gen_completion.go b/misc/gen_completion.go
index af00fa645767af49b6f7978d5dc16302e0f9dd0e..c073e67e8a2536994f07f9ce7b832d0fd9d12b70 100644
--- a/misc/gen_completion.go
+++ b/misc/gen_completion.go
@@ -3,7 +3,7 @@ package main
 import (
 	"fmt"
 	"os"
-	"path"
+	"path/filepath"
 	"sync"
 
 	"github.com/spf13/cobra"
@@ -41,24 +41,24 @@ func main() {
 
 func genBash(root *cobra.Command) error {
 	cwd, _ := os.Getwd()
-	dir := path.Join(cwd, "misc", "bash_completion", "git-bug")
+	dir := filepath.Join(cwd, "misc", "bash_completion", "git-bug")
 	return root.GenBashCompletionFile(dir)
 }
 
 func genFish(root *cobra.Command) error {
 	cwd, _ := os.Getwd()
-	dir := path.Join(cwd, "misc", "fish_completion", "git-bug")
+	dir := filepath.Join(cwd, "misc", "fish_completion", "git-bug")
 	return root.GenFishCompletionFile(dir, true)
 }
 
 func genPowerShell(root *cobra.Command) error {
 	cwd, _ := os.Getwd()
-	filepath := path.Join(cwd, "misc", "powershell_completion", "git-bug")
-	return root.GenPowerShellCompletionFile(filepath)
+	path := filepath.Join(cwd, "misc", "powershell_completion", "git-bug")
+	return root.GenPowerShellCompletionFile(path)
 }
 
 func genZsh(root *cobra.Command) error {
 	cwd, _ := os.Getwd()
-	filepath := path.Join(cwd, "misc", "zsh_completion", "git-bug")
-	return root.GenZshCompletionFile(filepath)
+	path := filepath.Join(cwd, "misc", "zsh_completion", "git-bug")
+	return root.GenZshCompletionFile(path)
 }
diff --git a/misc/random_bugs/cmd/main.go b/misc/random_bugs/cmd/main.go
index 3127b4aaddcc50956a654aeb7de95c2c7465683a..010ae6d1bb4606455c11fb3197eb2448e5004bfd 100644
--- a/misc/random_bugs/cmd/main.go
+++ b/misc/random_bugs/cmd/main.go
@@ -20,7 +20,7 @@ func main() {
 		bug.ClockLoader,
 	}
 
-	repo, err := repository.NewGoGitRepo(dir, loaders)
+	repo, err := repository.OpenGoGitRepo(dir, loaders)
 	if err != nil {
 		panic(err)
 	}
diff --git a/repository/git.go b/repository/git.go
index 8c319285e0a2ad023548dd17d82a0a7129c402aa..e67e12f54735dba5fc8091a39fbe900a1f692c20 100644
--- a/repository/git.go
+++ b/repository/git.go
@@ -5,7 +5,6 @@ import (
 	"bytes"
 	"fmt"
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -379,9 +378,7 @@ func (repo *GitRepo) GetOrCreateClock(name string) (lamport.Clock, error) {
 	repo.clocksMutex.Lock()
 	defer repo.clocksMutex.Unlock()
 
-	p := path.Join(repo.path, clockPath, name+"-clock")
-
-	c, err = lamport.NewPersistedClock(p)
+	c, err = lamport.NewPersistedClock(repo.LocalStorage(), name+"-clock")
 	if err != nil {
 		return nil, err
 	}
@@ -398,9 +395,7 @@ func (repo *GitRepo) getClock(name string) (lamport.Clock, error) {
 		return c, nil
 	}
 
-	p := path.Join(repo.path, clockPath, name+"-clock")
-
-	c, err := lamport.LoadPersistedClock(p)
+	c, err := lamport.LoadPersistedClock(repo.LocalStorage(), name+"-clock")
 	if err == nil {
 		repo.clocks[name] = c
 		return c, nil
diff --git a/repository/gogit.go b/repository/gogit.go
index 874885dba2721d0260126442e099123ad4094437..6a695da39185c71ac4c23b335436cf1ac78faad5 100644
--- a/repository/gogit.go
+++ b/repository/gogit.go
@@ -6,20 +6,22 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
-	stdpath "path"
 	"path/filepath"
 	"sort"
 	"strings"
 	"sync"
 	"time"
 
+	"github.com/99designs/keyring"
 	"github.com/go-git/go-billy/v5"
+	"github.com/go-git/go-billy/v5/memfs"
 	"github.com/go-git/go-billy/v5/osfs"
 	gogit "github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/filemode"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/storage/memory"
 
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
@@ -28,16 +30,18 @@ var _ ClockedRepo = &GoGitRepo{}
 var _ TestedRepo = &GoGitRepo{}
 
 type GoGitRepo struct {
-	r    *gogit.Repository
-	path string
+	r        *gogit.Repository
+	path     string
+	isMemory bool
 
 	clocksMutex sync.Mutex
 	clocks      map[string]lamport.Clock
 
-	keyring Keyring
+	keyring      Keyring
+	localStorage billy.Filesystem
 }
 
-func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
+func OpenGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
 	path, err := detectGitPath(path)
 	if err != nil {
 		return nil, err
@@ -54,10 +58,12 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
 	}
 
 	repo := &GoGitRepo{
-		r:       r,
-		path:    path,
-		clocks:  make(map[string]lamport.Clock),
-		keyring: k,
+		r:            r,
+		path:         path,
+		isMemory:     false,
+		clocks:       make(map[string]lamport.Clock),
+		keyring:      k,
+		localStorage: osfs.New(filepath.Join(path, "git-bug")),
 	}
 
 	for _, loader := range clockLoaders {
@@ -79,6 +85,69 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
 	return repo, nil
 }
 
+// InitGoGitRepo create a new empty git repo at the given path
+func InitGoGitRepo(path string) (*GoGitRepo, error) {
+	r, err := gogit.PlainInit(path, false)
+	if err != nil {
+		return nil, err
+	}
+
+	k, err := defaultKeyring()
+	if err != nil {
+		return nil, err
+	}
+
+	return &GoGitRepo{
+		r:            r,
+		path:         filepath.Join(path, ".git"),
+		isMemory:     false,
+		clocks:       make(map[string]lamport.Clock),
+		keyring:      k,
+		localStorage: osfs.New(filepath.Join(path, ".git", "git-bug")),
+	}, nil
+}
+
+// InitBareGoGitRepo create a new --bare empty git repo at the given path
+func InitBareGoGitRepo(path string) (*GoGitRepo, error) {
+	r, err := gogit.PlainInit(path, true)
+	if err != nil {
+		return nil, err
+	}
+
+	k, err := defaultKeyring()
+	if err != nil {
+		return nil, err
+	}
+
+	return &GoGitRepo{
+		r:            r,
+		path:         path,
+		isMemory:     false,
+		clocks:       make(map[string]lamport.Clock),
+		keyring:      k,
+		localStorage: osfs.New(filepath.Join(path, "git-bug")),
+	}, nil
+}
+
+func InitMemoryGoGitRepo() (*GoGitRepo, error) {
+	r, err := gogit.Init(memory.NewStorage(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	k := keyring.NewArrayKeyring(nil)
+
+	repo := &GoGitRepo{
+		r:            r,
+		isMemory:     true,
+		clocks:       make(map[string]lamport.Clock),
+		keyring:      k,
+		localStorage: memfs.New(),
+	}
+
+	return repo, nil
+}
+
 func detectGitPath(path string) (string, error) {
 	// normalize the path
 	path, err := filepath.Abs(path)
@@ -87,12 +156,12 @@ func detectGitPath(path string) (string, error) {
 	}
 
 	for {
-		fi, err := os.Stat(stdpath.Join(path, ".git"))
+		fi, err := os.Stat(filepath.Join(path, ".git"))
 		if err == nil {
 			if !fi.IsDir() {
 				return "", fmt.Errorf(".git exist but is not a directory")
 			}
-			return stdpath.Join(path, ".git"), nil
+			return filepath.Join(path, ".git"), nil
 		}
 		if !os.IsNotExist(err) {
 			// unknown error
@@ -120,7 +189,7 @@ func isGitDir(path string) (bool, error) {
 	markers := []string{"HEAD", "objects", "refs"}
 
 	for _, marker := range markers {
-		_, err := os.Stat(stdpath.Join(path, marker))
+		_, err := os.Stat(filepath.Join(path, marker))
 		if err == nil {
 			continue
 		}
@@ -135,46 +204,6 @@ func isGitDir(path string) (bool, error) {
 	return true, nil
 }
 
-// InitGoGitRepo create a new empty git repo at the given path
-func InitGoGitRepo(path string) (*GoGitRepo, error) {
-	r, err := gogit.PlainInit(path, false)
-	if err != nil {
-		return nil, err
-	}
-
-	k, err := defaultKeyring()
-	if err != nil {
-		return nil, err
-	}
-
-	return &GoGitRepo{
-		r:       r,
-		path:    path + "/.git",
-		clocks:  make(map[string]lamport.Clock),
-		keyring: k,
-	}, nil
-}
-
-// InitBareGoGitRepo create a new --bare empty git repo at the given path
-func InitBareGoGitRepo(path string) (*GoGitRepo, error) {
-	r, err := gogit.PlainInit(path, true)
-	if err != nil {
-		return nil, err
-	}
-
-	k, err := defaultKeyring()
-	if err != nil {
-		return nil, err
-	}
-
-	return &GoGitRepo{
-		r:       r,
-		path:    path,
-		clocks:  make(map[string]lamport.Clock),
-		keyring: k,
-	}, nil
-}
-
 // LocalConfig give access to the repository scoped configuration
 func (repo *GoGitRepo) LocalConfig() Config {
 	return newGoGitLocalConfig(repo.r)
@@ -182,10 +211,7 @@ func (repo *GoGitRepo) LocalConfig() Config {
 
 // GlobalConfig give access to the global scoped configuration
 func (repo *GoGitRepo) GlobalConfig() Config {
-	// TODO: replace that with go-git native implementation once it's supported
-	// see: https://github.com/go-git/go-git
-	// see: https://github.com/src-d/go-git/issues/760
-	return newGoGitGlobalConfig(repo.r)
+	return newGoGitGlobalConfig()
 }
 
 // AnyConfig give access to a merged local/global configuration
@@ -270,7 +296,7 @@ func (repo *GoGitRepo) GetRemotes() (map[string]string, error) {
 
 // LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug
 func (repo *GoGitRepo) LocalStorage() billy.Filesystem {
-	return osfs.New(repo.path)
+	return repo.localStorage
 }
 
 // FetchRefs fetch git refs from a remote
@@ -614,9 +640,7 @@ func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) {
 	repo.clocksMutex.Lock()
 	defer repo.clocksMutex.Unlock()
 
-	p := stdpath.Join(repo.path, clockPath, name+"-clock")
-
-	c, err = lamport.NewPersistedClock(p)
+	c, err = lamport.NewPersistedClock(repo.localStorage, name+"-clock")
 	if err != nil {
 		return nil, err
 	}
@@ -633,9 +657,7 @@ func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) {
 		return c, nil
 	}
 
-	p := stdpath.Join(repo.path, clockPath, name+"-clock")
-
-	c, err := lamport.LoadPersistedClock(p)
+	c, err := lamport.LoadPersistedClock(repo.localStorage, name+"-clock")
 	if err == nil {
 		repo.clocks[name] = c
 		return c, nil
@@ -664,6 +686,7 @@ func (repo *GoGitRepo) GetLocalRemote() string {
 
 // EraseFromDisk delete this repository entirely from the disk
 func (repo *GoGitRepo) EraseFromDisk() error {
+
 	path := filepath.Clean(strings.TrimSuffix(repo.path, string(filepath.Separator)+".git"))
 
 	// fmt.Println("Cleaning repo:", path)
diff --git a/repository/gogit_config.go b/repository/gogit_config.go
index 2f9a4cc34c5357e81c25adad9e637e11ba23fb6b..ba61adcac651a9217e3c36dccc3a9747c1d68d2e 100644
--- a/repository/gogit_config.go
+++ b/repository/gogit_config.go
@@ -24,7 +24,11 @@ func newGoGitLocalConfig(repo *gogit.Repository) *goGitConfig {
 	}
 }
 
-func newGoGitGlobalConfig(repo *gogit.Repository) *goGitConfig {
+func newGoGitGlobalConfig() *goGitConfig {
+	// TODO: replace that with go-git native implementation once it's supported
+	// see: https://github.com/go-git/go-git
+	// see: https://github.com/src-d/go-git/issues/760
+
 	return &goGitConfig{
 		ConfigRead: &goGitConfigReader{getConfig: func() (*config.Config, error) {
 			return config.LoadConfig(config.GlobalScope)
diff --git a/repository/gogit_test.go b/repository/gogit_test.go
index a1f67664c053afde03439e3bdfbb1383a7c3a75f..ccaf74c158c0d0ca46279f8e8a3256581ba93b44 100644
--- a/repository/gogit_test.go
+++ b/repository/gogit_test.go
@@ -19,7 +19,7 @@ func TestNewGoGitRepo(t *testing.T) {
 
 	_, err = InitGoGitRepo(plainRoot)
 	require.NoError(t, err)
-	plainGitDir := path.Join(plainRoot, ".git")
+	plainGitDir := filepath.Join(plainRoot, ".git")
 
 	// Bare
 	bareRoot, err := ioutil.TempDir("", "")
@@ -52,7 +52,7 @@ func TestNewGoGitRepo(t *testing.T) {
 	}
 
 	for i, tc := range tests {
-		r, err := NewGoGitRepo(tc.inPath, nil)
+		r, err := OpenGoGitRepo(tc.inPath, nil)
 
 		if tc.err {
 			require.Error(t, err, i)
@@ -66,3 +66,17 @@ func TestNewGoGitRepo(t *testing.T) {
 func TestGoGitRepo(t *testing.T) {
 	RepoTest(t, CreateGoGitTestRepo, CleanupTestRepos)
 }
+
+// func TestGoGitMemoryRepo(t *testing.T) {
+// 	creator := func(bool) TestedRepo {
+// 		r, err := InitMemoryGoGitRepo()
+// 		if err != nil {
+// 			log.Fatal(err)
+// 		}
+// 		return r
+// 	}
+//
+// 	cleaner := func(repos ...Repo) {}
+//
+// 	RepoTest(t, creator, cleaner)
+// }
diff --git a/repository/keyring.go b/repository/keyring.go
index f690b0b31995d1decdb5a0a2ef06fe02d5a08821..4cb3c9ff57079b171db4a4ad5f4ceba5fd1d3ac3 100644
--- a/repository/keyring.go
+++ b/repository/keyring.go
@@ -2,7 +2,7 @@ package repository
 
 import (
 	"os"
-	"path"
+	"path/filepath"
 
 	"github.com/99designs/keyring"
 )
@@ -38,7 +38,7 @@ func defaultKeyring() (Keyring, error) {
 		ServiceName: "git-bug",
 
 		// Fallback encrypted file
-		FileDir: path.Join(ucd, "git-bug", "keyring"),
+		FileDir: filepath.Join(ucd, "git-bug", "keyring"),
 		// As we write the file in the user's config directory, this file should already be protected by the OS against
 		// other user's access. We actually don't terribly need to protect it further and a password prompt across all
 		// UI's would be a pain. Therefore we use here a constant password so the file will be unreadable by generic file
diff --git a/repository/repo.go b/repository/repo.go
index d34995f9b5cef741fec514bc005c81009f02ef9e..d8fe44e63a3772afb379cf2469cf447e87577ef7 100644
--- a/repository/repo.go
+++ b/repository/repo.go
@@ -25,6 +25,11 @@ type Repo interface {
 	RepoStorage
 }
 
+type RepoCommonStorage interface {
+	RepoCommon
+	RepoStorage
+}
+
 // ClockedRepo is a Repo that also has Lamport clocks
 type ClockedRepo interface {
 	Repo
diff --git a/util/lamport/persisted_clock.go b/util/lamport/persisted_clock.go
index e70b01efd3c92348cb454213e54a462bb7493bf9..b9246f73e0581cf2c647230223707785b757cdc1 100644
--- a/util/lamport/persisted_clock.go
+++ b/util/lamport/persisted_clock.go
@@ -5,30 +5,28 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"path/filepath"
+
+	"github.com/go-git/go-billy/v5"
+	"github.com/go-git/go-billy/v5/util"
 )
 
 var ErrClockNotExist = errors.New("clock doesn't exist")
 
 type PersistedClock struct {
 	*MemClock
+	root     billy.Filesystem
 	filePath string
 }
 
 // NewPersistedClock create a new persisted Lamport clock
-func NewPersistedClock(filePath string) (*PersistedClock, error) {
+func NewPersistedClock(root billy.Filesystem, filePath string) (*PersistedClock, error) {
 	clock := &PersistedClock{
 		MemClock: NewMemClock(),
+		root:     root,
 		filePath: filePath,
 	}
 
-	dir := filepath.Dir(filePath)
-	err := os.MkdirAll(dir, 0777)
-	if err != nil {
-		return nil, err
-	}
-
-	err = clock.Write()
+	err := clock.Write()
 	if err != nil {
 		return nil, err
 	}
@@ -37,8 +35,9 @@ func NewPersistedClock(filePath string) (*PersistedClock, error) {
 }
 
 // LoadPersistedClock load a persisted Lamport clock from a file
-func LoadPersistedClock(filePath string) (*PersistedClock, error) {
+func LoadPersistedClock(root billy.Filesystem, filePath string) (*PersistedClock, error) {
 	clock := &PersistedClock{
+		root:     root,
 		filePath: filePath,
 	}
 
@@ -71,13 +70,19 @@ func (pc *PersistedClock) Witness(time Time) error {
 }
 
 func (pc *PersistedClock) read() error {
-	content, err := ioutil.ReadFile(pc.filePath)
+	f, err := pc.root.Open(pc.filePath)
 	if os.IsNotExist(err) {
 		return ErrClockNotExist
 	}
 	if err != nil {
 		return err
 	}
+	defer f.Close()
+
+	content, err := ioutil.ReadAll(f)
+	if err != nil {
+		return err
+	}
 
 	var value uint64
 	n, err := fmt.Sscanf(string(content), "%d", &value)
@@ -96,5 +101,5 @@ func (pc *PersistedClock) read() error {
 
 func (pc *PersistedClock) Write() error {
 	data := []byte(fmt.Sprintf("%d", pc.counter))
-	return ioutil.WriteFile(pc.filePath, data, 0644)
+	return util.WriteFile(pc.root, pc.filePath, data, 0644)
 }
diff --git a/util/lamport/persisted_clock_test.go b/util/lamport/persisted_clock_test.go
index aacec3bfed458957b0e60e456a7265610f880f24..9ef690da3922f944b74db8eaff0aa48ef6adaf1b 100644
--- a/util/lamport/persisted_clock_test.go
+++ b/util/lamport/persisted_clock_test.go
@@ -1,18 +1,16 @@
 package lamport
 
 import (
-	"io/ioutil"
-	"path"
 	"testing"
 
+	"github.com/go-git/go-billy/v5/memfs"
 	"github.com/stretchr/testify/require"
 )
 
 func TestPersistedClock(t *testing.T) {
-	dir, err := ioutil.TempDir("", "")
-	require.NoError(t, err)
+	root := memfs.New()
 
-	c, err := NewPersistedClock(path.Join(dir, "test-clock"))
+	c, err := NewPersistedClock(root, "test-clock")
 	require.NoError(t, err)
 
 	testClock(t, c)