Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-11-07 08:03:04
Commit08dff9acdd4ef4de7329c7c47778449035488d55
Parent945fd713f4c81b269b0bb9854f14e362a60aa2af

Implement live backup functionality

Diffstat

M .gitignore | 1 +
M .vscode/launch.json | 3 ++-
A .vscode/tasks.json | 11 +++++++++++
M src/goit/admin.go | 2 +-
M src/goit/git.go | 4 ++--
M src/goit/goit.go | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
M src/goit/index.go | 2 +-
M src/goit/repo.go | 20 ++++++++++----------
M src/goit/user.go | 14 +++++++-------
M src/main.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M src/repo/commit.go | 10 +++++-----
M src/repo/edit.go | 8 ++++----
M src/repo/file.go | 8 ++++----
M src/repo/log.go | 8 ++++----
M src/repo/raw.go | 2 +-
M src/repo/refs.go | 8 ++++----
M src/repo/tree.go | 15 ++++++++-------

17 files changed, 331 insertions, 59 deletions

diff --git a/.gitignore b/.gitignore
index 964e84e..f43f734 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 /bin/
 
 !/.vscode/launch.json
+!/.vscode/tasks.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d743663..17d6e25 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,10 +5,11 @@
 			"name": "Debug",
 			"type": "go",
 			"request": "launch",
-			"program": "${workspaceFolder}/main.go",
+			"program": "${workspaceFolder}/src/main.go",
 			"cwd": "${workspaceFolder}/bin",
 			"mode": "debug",
 			"args": ["--debug"],
+			"postDebugTask": "clean",
 		},
 	],
 }
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..99110fd
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,11 @@
+{
+	"version": "2.0.0",
+	"tasks": [
+		{
+			"label": "clean",
+			"type": "shell",
+			"command": "rm /run/user/$(id -u)/goit-8080.sock",
+			"presentation": { "reveal": "silent" }
+		},
+	],
+}
diff --git a/src/goit/admin.go b/src/goit/admin.go
index 1dd9f85..9804e38 100644
--- a/src/goit/admin.go
+++ b/src/goit/admin.go
@@ -229,7 +229,7 @@ func HandleAdminRepos(w http.ResponseWriter, r *http.Request) {
 			log.Println("[/admin/repos]", err.Error())
 		}
 
-		size, err := util.DirSize(RepoPath(d.Name))
+		size, err := util.DirSize(RepoPath(d.Name, true))
 		if err != nil {
 			log.Println("[/admin/repos]", err.Error())
 		}
diff --git a/src/goit/git.go b/src/goit/git.go
index 0d729d2..1c5197d 100644
--- a/src/goit/git.go
+++ b/src/goit/git.go
@@ -38,7 +38,7 @@ func HandleInfoRefs(w http.ResponseWriter, r *http.Request) {
 	c := NewGitCommand(strings.TrimPrefix(service, "git-"), "--stateless-rpc", "--advertise-refs", ".")
 	c.AddEnv(os.Environ()...)
 	c.AddEnv("GIT_PROTOCOL=version=2")
-	c.Dir = RepoPath(repo.Name)
+	c.Dir = RepoPath(repo.Name, true)
 
 	refs, _, err := c.Run(nil, nil)
 	if err != nil {
@@ -165,7 +165,7 @@ func gitHttpRpc(w http.ResponseWriter, r *http.Request, service string, repo *Re
 
 	c := NewGitCommand(strings.TrimPrefix(service, "git-"), "--stateless-rpc", ".")
 	c.AddEnv(os.Environ()...)
-	c.Dir = RepoPath(repo.Name)
+	c.Dir = RepoPath(repo.Name, true)
 
 	if p := r.Header.Get("Git-Protocol"); p == "version=2" {
 		c.AddEnv("GIT_PROTOCOL=version=2")
diff --git a/src/goit/goit.go b/src/goit/goit.go
index 1ea91b3..6ae4c24 100644
--- a/src/goit/goit.go
+++ b/src/goit/goit.go
@@ -5,15 +5,23 @@
 package goit
 
 import (
+	"archive/zip"
 	"database/sql"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
+	"io/fs"
 	"log"
 	"os"
-	"path"
+	"path/filepath"
+	"strings"
+	"time"
 
+	"github.com/Jamozed/Goit/src/util"
 	"github.com/adrg/xdg"
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing/transport"
 	_ "github.com/mattn/go-sqlite3"
 )
 
@@ -27,7 +35,7 @@ type Config struct {
 }
 
 var Conf = Config{
-	DataPath:   path.Join(xdg.DataHome, "goit"),
+	DataPath:   filepath.Join(xdg.DataHome, "goit"),
 	HttpAddr:   "",
 	HttpPort:   "8080",
 	GitPath:    "git",
@@ -56,13 +64,13 @@ func Goit(conf string) (err error) {
 		return fmt.Errorf("[Config] %w", err)
 	}
 
-	if dat, err := os.ReadFile(path.Join(Conf.DataPath, "favicon.png")); err != nil {
+	if dat, err := os.ReadFile(filepath.Join(Conf.DataPath, "favicon.png")); err != nil {
 		log.Println("[Favicon]", err.Error())
 	} else {
 		Favicon = dat
 	}
 
-	if db, err = sql.Open("sqlite3", path.Join(Conf.DataPath, "goit.db")); err != nil {
+	if db, err = sql.Open("sqlite3", filepath.Join(Conf.DataPath, "goit.db")); err != nil {
 		return fmt.Errorf("[Database] %w", err)
 	}
 
@@ -114,7 +122,7 @@ func Goit(conf string) (err error) {
 }
 
 func ConfPath() string {
-	if p, err := xdg.SearchConfigFile(path.Join("goit", "goit.json")); err != nil {
+	if p, err := xdg.SearchConfigFile(filepath.Join("goit", "goit.json")); err != nil {
 		log.Println("[Config]", err.Error())
 		return ""
 	} else {
@@ -122,6 +130,150 @@ func ConfPath() string {
 	}
 }
 
-func RepoPath(name string) string {
-	return path.Join(Conf.DataPath, "repos", name+".git")
+func RepoPath(name string, abs bool) string {
+	return util.If(abs, filepath.Join(Conf.DataPath, "repos", name+".git"), filepath.Join(name+".git"))
+}
+
+func Backup() error {
+	data := struct {
+		Users []User `json:"users"`
+		Repos []Repo `json:"repos"`
+	}{}
+
+	bdir := filepath.Join(Conf.DataPath, "backup")
+	if err := os.MkdirAll(bdir, 0o777); err != nil {
+		return err
+	}
+
+	/* Dump users */
+	rows, err := db.Query("SELECT id, name, name_full, pass, pass_algo, salt, is_admin FROM users")
+	if err != nil {
+		return err
+	}
+
+	for rows.Next() {
+		u := User{}
+		if err := rows.Scan(&u.Id, &u.Name, &u.FullName, &u.Pass, &u.PassAlgo, &u.Salt, &u.IsAdmin); err != nil {
+			return err
+		}
+
+		data.Users = append(data.Users, u)
+	}
+	rows.Close()
+
+	/* Dump repositories */
+	rows, err = db.Query("SELECT id, owner_id, name, description, is_private FROM repos")
+	if err != nil {
+		return err
+	}
+
+	for rows.Next() {
+		r := Repo{}
+		if err := rows.Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.IsPrivate); err != nil {
+			return err
+		}
+
+		data.Repos = append(data.Repos, r)
+	}
+	rows.Close()
+
+	/* Open an output ZIP file */
+	ts := "goit_" + time.Now().UTC().Format("20060102T150405Z")
+
+	zf, err := os.Create(filepath.Join(bdir, ts+".zip"))
+	if err != nil {
+		return err
+	}
+	defer zf.Close()
+
+	zw := zip.NewWriter(zf)
+	defer zw.Close()
+
+	/* Copy repositories to ZIP */
+	td, err := os.MkdirTemp(os.TempDir(), "goit-")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(td)
+
+	for _, r := range data.Repos {
+		cd := filepath.Join(td, RepoPath(r.Name, false))
+
+		gr, err := git.PlainClone(cd, true, &git.CloneOptions{
+			URL: RepoPath(r.Name, true), Mirror: true,
+		})
+		if err != nil {
+			if errors.Is(err, transport.ErrRepositoryNotFound) {
+				continue
+			}
+
+			if errors.Is(err, transport.ErrEmptyRemoteRepository) {
+				continue
+			}
+
+			return err
+		}
+
+		if err := gr.DeleteRemote("origin"); err != nil {
+			return fmt.Errorf("%s %w", cd, err)
+		}
+
+		/* Walk duplicated repository and add it to the ZIP */
+		if err = filepath.WalkDir(cd, func(path string, d fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+
+			info, err := d.Info()
+			if err != nil {
+				return err
+			}
+
+			head, err := zip.FileInfoHeader(info)
+			if err != nil {
+				return err
+			}
+
+			head.Name = filepath.Join(ts, strings.TrimPrefix(path, Conf.DataPath))
+
+			if d.IsDir() {
+				head.Name += "/"
+			} else {
+				head.Method = zip.Store
+			}
+
+			w, err := zw.CreateHeader(head)
+			if err != nil {
+				return err
+			}
+
+			if !d.IsDir() {
+				fi, err := os.Open(path)
+				if err != nil {
+					return err
+				}
+
+				if _, err := io.Copy(w, fi); err != nil {
+					return err
+				}
+			}
+
+			return nil
+		}); err != nil {
+			return err
+		}
+
+		os.RemoveAll(cd)
+	}
+
+	/* Write database as JSON to ZIP */
+	if b, err := json.MarshalIndent(data, "", "\t"); err != nil {
+		return err
+	} else if w, err := zw.Create(filepath.Join(ts, "goit.json")); err != nil {
+		return err
+	} else if _, err := w.Write(b); err != nil {
+		return err
+	}
+
+	return nil
 }
diff --git a/src/goit/index.go b/src/goit/index.go
index e487e27..c7f7b1a 100644
--- a/src/goit/index.go
+++ b/src/goit/index.go
@@ -58,7 +58,7 @@ func HandleIndex(w http.ResponseWriter, r *http.Request) {
 			}
 
 			var lastCommit string
-			if gr, err := git.PlainOpen(RepoPath(repo.Name)); err != nil {
+			if gr, err := git.PlainOpen(RepoPath(repo.Name, true)); err != nil {
 				log.Println("[/]", err.Error())
 			} else if ref, err := gr.Head(); err != nil {
 				if !errors.Is(err, plumbing.ErrReferenceNotFound) {
diff --git a/src/goit/repo.go b/src/goit/repo.go
index 15457ff..32c95a0 100644
--- a/src/goit/repo.go
+++ b/src/goit/repo.go
@@ -15,11 +15,11 @@ import (
 )
 
 type Repo struct {
-	Id          int64
-	OwnerId     int64
-	Name        string
-	Description string
-	IsPrivate   bool
+	Id          int64  `json:"id"`
+	OwnerId     int64  `json:"owner_id"`
+	Name        string `json:"name"`
+	Description string `json:"description"`
+	IsPrivate   bool   `json:"is_private"`
 }
 
 func GetRepo(rid int64) (*Repo, error) {
@@ -69,13 +69,13 @@ func CreateRepo(repo Repo) error {
 		return err
 	}
 
-	if _, err := git.PlainInit(RepoPath(repo.Name), true); err != nil {
+	if _, err := git.PlainInit(RepoPath(repo.Name, true), true); err != nil {
 		tx.Rollback()
 		return err
 	}
 
 	if err := tx.Commit(); err != nil {
-		os.RemoveAll(RepoPath(repo.Name))
+		os.RemoveAll(RepoPath(repo.Name, true))
 		return err
 	}
 
@@ -88,7 +88,7 @@ func DelRepo(rid int64) error {
 		return err
 	}
 
-	if err := os.RemoveAll(RepoPath(repo.Name)); err != nil {
+	if err := os.RemoveAll(RepoPath(repo.Name, true)); err != nil {
 		return err
 	}
 
@@ -133,14 +133,14 @@ func UpdateRepo(rid int64, repo Repo) error {
 	}
 
 	if repo.Name != old.Name {
-		if err := os.Rename(RepoPath(old.Name), RepoPath(repo.Name)); err != nil {
+		if err := os.Rename(RepoPath(old.Name, true), RepoPath(repo.Name, true)); err != nil {
 			tx.Rollback()
 			return err
 		}
 	}
 
 	if err := tx.Commit(); err != nil {
-		os.Rename(RepoPath(repo.Name), RepoPath(old.Name))
+		os.Rename(RepoPath(repo.Name, true), RepoPath(old.Name, true))
 		log.Println("[repo/update]", "error while renaming, check repo \""+old.Name+"\"/\""+repo.Name+"\"")
 		return err
 	}
diff --git a/src/goit/user.go b/src/goit/user.go
index 9c280b4..cbf110c 100644
--- a/src/goit/user.go
+++ b/src/goit/user.go
@@ -13,13 +13,13 @@ import (
 )
 
 type User struct {
-	Id       int64
-	Name     string
-	FullName string
-	Pass     []byte
-	PassAlgo string
-	Salt     []byte
-	IsAdmin  bool
+	Id       int64  `json:"id"`
+	Name     string `json:"name"`
+	FullName string `json:"name_full"`
+	Pass     []byte `json:"pass"`
+	PassAlgo string `json:"pass_algo"`
+	Salt     []byte `json:"salt"`
+	IsAdmin  bool   `json:"is_admin"`
 }
 
 func HandleUserLogout(w http.ResponseWriter, r *http.Request) {
diff --git a/src/main.go b/src/main.go
index 75c4ac7..aa662d7 100644
--- a/src/main.go
+++ b/src/main.go
@@ -5,25 +5,73 @@
 package main
 
 import (
+	"errors"
 	"flag"
+	"fmt"
 	"log"
+	"net"
 	"net/http"
+	"os"
+	"os/signal"
+	"path/filepath"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/Jamozed/Goit/res"
 	"github.com/Jamozed/Goit/src/goit"
 	"github.com/Jamozed/Goit/src/repo"
 	"github.com/Jamozed/Goit/src/user"
+	"github.com/Jamozed/Goit/src/util"
+	"github.com/adrg/xdg"
 	"github.com/gorilla/mux"
 )
 
 func main() {
+	var backup bool
+
+	flag.BoolVar(&backup, "backup", false, "Perform a backup")
 	flag.BoolVar(&goit.Debug, "debug", false, "Enable debug logging")
 	flag.Parse()
 
+	if backup /* IPC client */ {
+		c, err := net.Dial("unix", filepath.Join(xdg.RuntimeDir, "goit-"+goit.Conf.HttpPort+".sock"))
+		if err != nil {
+			log.Fatalln(err.Error())
+		}
+
+		_, err = c.Write([]byte{0xBA})
+		if err != nil {
+			log.Fatalln(err.Error())
+		}
+
+		buf := make([]byte, 512)
+		n, err := c.Read(buf)
+		if err != nil {
+			log.Fatalln(err.Error())
+		}
+
+		fmt.Println(string(buf[1:n]))
+		c.Close()
+
+		os.Exit(util.If(buf[0] == 0x01, -1, 0))
+	}
+
 	log.Println("Starting Goit", res.Version)
 
+	/* Listen for and handle SIGINT */
+	stop := make(chan struct{})
+	wait := &sync.WaitGroup{}
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt)
+
+	go func() {
+		<-c
+		close(stop)
+		wait.Wait()
+		os.Exit(0)
+	}()
+
 	if err := goit.Goit(goit.ConfPath()); err != nil {
 		log.Fatalln(err.Error())
 	}
@@ -71,8 +119,23 @@ func main() {
 		}
 	}()
 
+	/* Listen for IPC */
+	ipc, err := net.Listen("unix", filepath.Join(xdg.RuntimeDir, "goit-"+goit.Conf.HttpPort+".sock"))
+	if err != nil {
+		log.Fatalln("[sock]", err.Error())
+	}
+
+	go func() {
+		defer ipc.Close()
+		<-stop
+	}()
+
+	wait.Add(1)
+	go handleIpc(stop, wait, ipc)
+
+	/* Listen for HTTP on the specified port */
 	if err := http.ListenAndServe(goit.Conf.HttpAddr+":"+goit.Conf.HttpPort, logHttp(h)); err != nil {
-		log.Fatalln("[HTTP]", err)
+		log.Fatalln("[HTTP]", err.Error())
 	}
 }
 
@@ -105,3 +168,46 @@ func handleFavicon(w http.ResponseWriter, r *http.Request) {
 func redirectDotGit(w http.ResponseWriter, r *http.Request) {
 	http.Redirect(w, r, strings.TrimSuffix(r.URL.Path, ".git"), http.StatusMovedPermanently)
 }
+
+/* Handle IPC messages. */
+func handleIpc(stop chan struct{}, wait *sync.WaitGroup, ipc net.Listener) {
+	defer wait.Done()
+
+	for {
+		select {
+		case <-stop:
+			return
+		default:
+			c, err := ipc.Accept()
+			if err != nil {
+				if !errors.Is(err, net.ErrClosed) {
+					log.Println("[ipc]", err.Error())
+				}
+				continue
+			}
+
+			c.SetReadDeadline(time.Now().Add(1 * time.Second))
+
+			buf := make([]byte, 1)
+			if _, err := c.Read(buf); err != nil {
+				log.Println("[ipc]", err.Error())
+				continue
+			}
+
+			if buf[0] == 0xBA {
+				log.Println("[backup] Starting")
+				if err := goit.Backup(); err != nil {
+					c.Write(append([]byte{0x01}, []byte(err.Error())...))
+					log.Println("[backup]", err.Error())
+				} else {
+					c.Write(append([]byte{0x00}, []byte("SUCCESS")...))
+					log.Println("[backup] Success")
+				}
+			} else {
+				c.Write(append([]byte{0x01}, []byte("ILLEGAL")...))
+			}
+
+			c.Close()
+		}
+	}
+}
diff --git a/src/repo/commit.go b/src/repo/commit.go
index 914ca3c..3ce03b5 100644
--- a/src/repo/commit.go
+++ b/src/repo/commit.go
@@ -6,7 +6,7 @@ import (
 	"html/template"
 	"log"
 	"net/http"
-	"path"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
@@ -52,7 +52,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) {
 		Editable: (auth && repo.OwnerId == uid),
 	}
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/commit]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
@@ -68,10 +68,10 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) {
 		}
 	} else {
 		if readme, _ := findReadme(gr, ref); readme != "" {
-			data.Readme = path.Join("/", repo.Name, "file", readme)
+			data.Readme = filepath.Join("/", repo.Name, "file", readme)
 		}
 		if licence, _ := findLicence(gr, ref); licence != "" {
-			data.Licence = path.Join("/", repo.Name, "file", licence)
+			data.Licence = filepath.Join("/", repo.Name, "file", licence)
 		}
 	}
 
@@ -140,7 +140,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) {
 	}
 
 	c := goit.NewGitCommand("diff", "--color=always", "-p", phash, commit.Hash.String())
-	c.Dir = goit.RepoPath(repo.Name)
+	c.Dir = goit.RepoPath(repo.Name, true)
 	out, _, err := c.Run(nil, nil)
 	if err != nil {
 		log.Println("[/repo/commit]", err.Error())
diff --git a/src/repo/edit.go b/src/repo/edit.go
index dd92c3b..2afa940 100644
--- a/src/repo/edit.go
+++ b/src/repo/edit.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"log"
 	"net/http"
-	"path"
+	"path/filepath"
 	"slices"
 
 	"github.com/Jamozed/Goit/src/goit"
@@ -66,7 +66,7 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 	data.Edit.Description = repo.Description
 	data.Edit.IsPrivate = repo.IsPrivate
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/edit]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
@@ -82,10 +82,10 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 
 	if ref != nil {
 		if readme, _ := findReadme(gr, ref); readme != "" {
-			data.Readme = path.Join("/", repo.Name, "file", readme)
+			data.Readme = filepath.Join("/", repo.Name, "file", readme)
 		}
 		if licence, _ := findLicence(gr, ref); licence != "" {
-			data.Licence = path.Join("/", repo.Name, "file", licence)
+			data.Licence = filepath.Join("/", repo.Name, "file", licence)
 		}
 	}
 
diff --git a/src/repo/file.go b/src/repo/file.go
index 18898b1..334a7a7 100644
--- a/src/repo/file.go
+++ b/src/repo/file.go
@@ -5,7 +5,7 @@ import (
 	"io"
 	"log"
 	"net/http"
-	"path"
+	"path/filepath"
 	"strings"
 
 	"github.com/Jamozed/Goit/src/goit"
@@ -47,7 +47,7 @@ func HandleFile(w http.ResponseWriter, r *http.Request) {
 		Editable: (auth && repo.OwnerId == uid),
 	}
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/file]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
@@ -65,10 +65,10 @@ func HandleFile(w http.ResponseWriter, r *http.Request) {
 	}
 
 	if readme, _ := findReadme(gr, ref); readme != "" {
-		data.Readme = path.Join("/", repo.Name, "file", readme)
+		data.Readme = filepath.Join("/", repo.Name, "file", readme)
 	}
 	if licence, _ := findLicence(gr, ref); licence != "" {
-		data.Licence = path.Join("/", repo.Name, "file", licence)
+		data.Licence = filepath.Join("/", repo.Name, "file", licence)
 	}
 
 	commit, err := gr.CommitObject(ref.Hash())
diff --git a/src/repo/log.go b/src/repo/log.go
index 001441e..39a5932 100644
--- a/src/repo/log.go
+++ b/src/repo/log.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"log"
 	"net/http"
-	"path"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -51,7 +51,7 @@ func HandleLog(w http.ResponseWriter, r *http.Request) {
 		Editable: (auth && repo.OwnerId == uid),
 	}
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/log]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
@@ -68,10 +68,10 @@ func HandleLog(w http.ResponseWriter, r *http.Request) {
 	}
 
 	if readme, _ := findReadme(gr, ref); readme != "" {
-		data.Readme = path.Join("/", repo.Name, "file", readme)
+		data.Readme = filepath.Join("/", repo.Name, "file", readme)
 	}
 	if licence, _ := findLicence(gr, ref); licence != "" {
-		data.Licence = path.Join("/", repo.Name, "file", licence)
+		data.Licence = filepath.Join("/", repo.Name, "file", licence)
 	}
 
 	if iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}); err != nil {
diff --git a/src/repo/raw.go b/src/repo/raw.go
index ca189a2..d0b5430 100644
--- a/src/repo/raw.go
+++ b/src/repo/raw.go
@@ -32,7 +32,7 @@ func HandleRaw(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/file]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
diff --git a/src/repo/refs.go b/src/repo/refs.go
index f5416d0..a42e938 100644
--- a/src/repo/refs.go
+++ b/src/repo/refs.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"log"
 	"net/http"
-	"path"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -39,7 +39,7 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) {
 		Editable: (auth && repo.OwnerId == uid),
 	}
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/refs]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
@@ -55,10 +55,10 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) {
 		}
 	} else {
 		if readme, _ := findReadme(gr, ref); readme != "" {
-			data.Readme = path.Join("/", repo.Name, "file", readme)
+			data.Readme = filepath.Join("/", repo.Name, "file", readme)
 		}
 		if licence, _ := findLicence(gr, ref); licence != "" {
-			data.Licence = path.Join("/", repo.Name, "file", licence)
+			data.Licence = filepath.Join("/", repo.Name, "file", licence)
 		}
 	}
 
diff --git a/src/repo/tree.go b/src/repo/tree.go
index 06a5c98..ee6e003 100644
--- a/src/repo/tree.go
+++ b/src/repo/tree.go
@@ -5,6 +5,7 @@ import (
 	"log"
 	"net/http"
 	"path"
+	"path/filepath"
 	"sort"
 	"strings"
 
@@ -45,7 +46,7 @@ func HandleTree(w http.ResponseWriter, r *http.Request) {
 		Editable: (auth && repo.OwnerId == uid),
 	}
 
-	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
 	if err != nil {
 		log.Println("[/repo/tree]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
@@ -60,10 +61,10 @@ func HandleTree(w http.ResponseWriter, r *http.Request) {
 		}
 	} else {
 		if readme, _ := findReadme(gr, ref); readme != "" {
-			data.Readme = path.Join("/", repo.Name, "file", readme)
+			data.Readme = filepath.Join("/", repo.Name, "file", readme)
 		}
 		if licence, _ := findLicence(gr, ref); licence != "" {
-			data.Licence = path.Join("/", repo.Name, "file", licence)
+			data.Licence = filepath.Join("/", repo.Name, "file", licence)
 		}
 
 		commit, err := gr.CommitObject(ref.Hash())
@@ -82,7 +83,7 @@ func HandleTree(w http.ResponseWriter, r *http.Request) {
 
 		if treepath != "" {
 			data.Files = append(data.Files, row{
-				Mode: "d---------", Name: "..", Path: path.Join("tree", path.Dir(treepath)),
+				Mode: "d---------", Name: "..", Path: filepath.Join("tree", path.Dir(treepath)),
 			})
 
 			tree, err = tree.Tree(treepath)
@@ -115,8 +116,8 @@ func HandleTree(w http.ResponseWriter, r *http.Request) {
 					return
 				}
 
-				fpath = path.Join("file", treepath, v.Name)
-				rpath = path.Join(treepath, v.Name)
+				fpath = filepath.Join("file", treepath, v.Name)
+				rpath = filepath.Join(treepath, v.Name)
 				size = humanize.IBytes(uint64(file.Size))
 			} else {
 				var dirSize uint64
@@ -137,7 +138,7 @@ func HandleTree(w http.ResponseWriter, r *http.Request) {
 					return
 				}
 
-				fpath = path.Join("tree", treepath, v.Name)
+				fpath = filepath.Join("tree", treepath, v.Name)
 				size = humanize.IBytes(dirSize)
 			}