diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..0cffcb348ff9cec9cd41492a9e5c5a41d86ce96e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+config.json
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9e899db3f251d8bc0f04c6f484218b259df75df8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,49 @@
+rb-gateway
+==========
+A service for managing your repositories.
+
+Install
+-------
+Instructions to install rb-gateway in the `$GOPATH/src/` directory from the master branch:
+
+    $ go get -d github.com/reviewboard/rb-gateway
+    $ cd github.com/reviewboard/rb-gateway
+    $ mv sample_config.json config.json
+    $ go get
+    $ go install
+
+    Modify config.json to point to your repositories.
+
+To start the server on localhost:8888:
+
+    go run *.go
+
+Dependencies
+------------
+This project depends on [git2go](https://github.com/libgit2/git2go/).
+
+rb-gateway will autofetch dependencies with `go get`.
+
+License
+-------
+The MIT License (MIT)
+
+Copyright (c) 2015 Review Board
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/auth.go b/auth.go
new file mode 100644
index 0000000000000000000000000000000000000000..c71774390adf6df2dcddf2cd1ee7d3608414aa77
--- /dev/null
+++ b/auth.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+	"encoding/base64"
+	"errors"
+	"net/http"
+	"strings"
+)
+
+type handler func(w http.ResponseWriter, r *http.Request)
+
+// BasicAuth relays all routes that require authentication checking.
+// A PRIVATE-TOKEN value is expected to be in the request header, corresponding
+// to the encrypted username, password combination. If the PRIVATE-TOKEN is
+// valid, this function will pass the request to the appropriate handler,
+// otherwise it will raise an appropriate HTTP error.
+func BasicAuth(pass handler) handler {
+	return func(w http.ResponseWriter, r *http.Request) {
+		privateToken := r.Header.Get("PRIVATE-TOKEN")
+
+		if privateToken == "" {
+			http.Error(w, "Bad private token", http.StatusBadRequest)
+			return
+		}
+
+		payload, _ := base64.StdEncoding.DecodeString(privateToken)
+		pair := strings.SplitN(string(payload), ":", 2)
+
+		if len(pair) != 2 || !validate(pair[0], pair[1]) {
+			http.Error(w, "Authorization failed", http.StatusUnauthorized)
+			return
+		}
+
+		pass(w, r)
+	}
+}
+
+// CreateSession creates a session from the Basic Authentication information
+// expected in the request. The session is constructed using the encrypted
+// username, password combination.
+// It returns a session and an error if Basic Authentication fails.
+func CreateSession(r *http.Request) (Session, error) {
+	authHeader := r.Header["Authorization"]
+	if authHeader == nil {
+		return Session{}, errors.New("Bad authorization syntax")
+	}
+
+	auth := strings.SplitN(authHeader[0], " ", 2)
+
+	if len(auth) != 2 || auth[0] != "Basic" {
+		return Session{}, errors.New("Bad authorization syntax")
+	}
+
+	payload, _ := base64.StdEncoding.DecodeString(auth[1])
+	pair := strings.SplitN(string(payload), ":", 2)
+
+	if len(pair) != 2 || !validate(pair[0], pair[1]) {
+		return Session{}, errors.New("Authorization failed")
+	}
+
+	return Session{string(auth[1])}, nil
+}
+
+// validate against the single user stored in the config file.
+// It returns true if the username and password matches what is in config.json.
+func validate(username, password string) bool {
+	if username == GetUsername() && password == GetPassword() {
+		return true
+	}
+	return false
+}
diff --git a/git_repository.go b/git_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c3a6058c8d115e608b5d783349dff7440ff8441
--- /dev/null
+++ b/git_repository.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+	"gopkg.in/libgit2/git2go.v22"
+)
+
+// GitRepository extends RepositoryInfo and is meant to represent a Git
+// Repository.
+type GitRepository struct {
+	RepositoryInfo
+}
+
+// GetName is a Repository implementation that returns the name of the
+// GitRepository.
+func (repo *GitRepository) GetName() string {
+	return repo.Name
+}
+
+// GetPath is a Repository implementation that returns the path of the
+// GitRepository.
+func (repo *GitRepository) GetPath() string {
+	return repo.Path
+}
+
+// GetFile is a Repository implementation that returns the contents of a file
+// in the GitRepository based on the file revision sha. On success, it returns
+// the file contents in a byte array. On failure, the error will be returned.
+func (repo *GitRepository) GetFile(id string) ([]byte, error) {
+	gitRepo, err := git.OpenRepository(repo.Path)
+	if err != nil {
+		return nil, err
+	}
+
+	oid, err := git.NewOid(id)
+	if err != nil {
+		return nil, err
+	}
+
+	blob, err := gitRepo.LookupBlob(oid)
+	if err != nil {
+		return nil, err
+	}
+
+	return blob.Contents(), nil
+}
+
+// GetFileByCommit is a Repository implementation that returns the contents of
+// a file in the GitRepository based on a commit sha and the file path. On
+// success, it returns the file contents in a byte array. On failure, the error
+// will be returned.
+func (repo *GitRepository) GetFileByCommit(commit,
+	filepath string) ([]byte, error) {
+	gitRepo, err := git.OpenRepository(repo.Path)
+	if err != nil {
+		return nil, err
+	}
+
+	oid, err := git.NewOid(commit)
+	if err != nil {
+		return nil, err
+	}
+
+	c, err := gitRepo.LookupCommit(oid)
+	if err != nil {
+		return nil, err
+	}
+
+	tree, err := c.Tree()
+	if err != nil {
+		return nil, err
+	}
+
+	file, err := tree.EntryByPath(filepath)
+	if err != nil {
+		return nil, err
+	}
+
+	blob, err := gitRepo.LookupBlob(file.Id)
+	if err != nil {
+		return nil, err
+	}
+
+	return blob.Contents(), nil
+}
+
+// FileExists is a Repository implementation that returns whether a file exists
+// in the GitRepository based on the file revision sha. It returns true if the
+// file exists, false otherwise. On failure, the error will also be returned.
+func (repo *GitRepository) FileExists(id string) (bool, error) {
+	gitRepo, err := git.OpenRepository(repo.Path)
+	if err != nil {
+		return false, err
+	}
+
+	oid, err := git.NewOid(id)
+	if err != nil {
+		return false, err
+	}
+
+	if _, err := gitRepo.Lookup(oid); err != nil {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+// FileExistsByCommit is a Repository implementation that returns whether a
+// file exists in the GitRepository based on a commit sha and the file path.
+// It returns true if the file exists, false otherwise. On failure, the error
+// will also be returned.
+func (repo *GitRepository) FileExistsByCommit(commit,
+	filepath string) (bool, error) {
+	gitRepo, err := git.OpenRepository(repo.Path)
+	if err != nil {
+		return false, err
+	}
+
+	oid, err := git.NewOid(commit)
+	if err != nil {
+		return false, err
+	}
+
+	c, err := gitRepo.LookupCommit(oid)
+	if err != nil {
+		return false, err
+	}
+
+	tree, err := c.Tree()
+	if err != nil {
+		return false, err
+	}
+
+	file, err := tree.EntryByPath(filepath)
+	if err != nil {
+		return false, err
+	}
+
+	if _, err := gitRepo.Lookup(file.Id); err != nil {
+		return false, nil
+	}
+
+	return true, nil
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..7210f895a5dfd572f821cd6db4c5e3163e451086
--- /dev/null
+++ b/main.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+	"log"
+	"net/http"
+	"strconv"
+)
+
+type logHTTPHandler struct {
+	handler http.Handler
+}
+
+type loggedResponse struct {
+	http.ResponseWriter
+	status  int
+	content []byte
+}
+
+func (l *loggedResponse) writeHeader(status int) {
+	l.status = status
+	l.ResponseWriter.WriteHeader(status)
+}
+
+func (l *loggedResponse) write(content []byte) (int, error) {
+	l.content = content
+	return l.ResponseWriter.Write(content)
+}
+
+// ServeHTTP intercepts the default http.Handler implementation in order to
+// handle HTTP request and response logging. It provides a default response
+// containing a 200 OK status, and an empty byte array as the content, if not
+// specified in the Responsewriter.
+//
+// It logs the request status, method, and URL, and the response status and
+// content length.
+func (h *logHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	lw := &loggedResponse{ResponseWriter: w, status: 200, content: []byte{}}
+	h.handler.ServeHTTP(lw, r)
+	log.Printf("%s %s %s status:%d content-length:%d",
+		r.RemoteAddr, r.Method, r.URL, lw.status, len(lw.content))
+}
+
+func main() {
+	LoadConfig()
+
+	Route()
+
+	log.Println("Starting rb-gateway server at port", GetPort())
+	log.Println("Quit the server with CONTROL-C.")
+	err := http.ListenAndServe(":"+strconv.Itoa(GetPort()),
+		&logHTTPHandler{http.DefaultServeMux})
+	if err != nil {
+		log.Fatal("ListenAndServe: ", err)
+	}
+}
diff --git a/repository.go b/repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1b661fc3c4b529ae954dcefc4b38df9a1d1652f
--- /dev/null
+++ b/repository.go
@@ -0,0 +1,38 @@
+package main
+
+// RepositoryInfo is a generic representation of a repository, containing
+// a name and a path to the repository.
+type RepositoryInfo struct {
+	Name string
+	Path string
+}
+
+// Repository is an interface that contains functions to perform actions on
+// repositories, such as getting the file contents or checking if a file
+// exists.
+type Repository interface {
+	// GetName returns the name of the repository.
+	GetName() string
+
+	// GetPath returns the path of the repository.
+	GetPath() string
+
+	// GetFile takes a file ID and returns the file contents as a byte array.
+	// If an error occurs, it will also be returned.
+	GetFile(id string) ([]byte, error)
+
+	// GetFileByCommit takes a commit and a file path pair, and returns the
+	// file contents as a byte array. If an error occurs, it will also be
+	// returned.
+	GetFileByCommit(commit, filepath string) ([]byte, error)
+
+	// FileExists takes a file ID and returns true if the file is found in the
+	// repository; false otherwise. If an error occurs, it will also be
+	// returned.
+	FileExists(id string) (bool, error)
+
+	// FileExistsByCommit takes a commit and file path pair, and returns true
+	// if the file is found in the repository; false otherwise. If an error
+	// occurs, it will also be returned.
+	FileExistsByCommit(commit, filepath string) (bool, error)
+}
diff --git a/routes.go b/routes.go
new file mode 100644
index 0000000000000000000000000000000000000000..60b23d7d413822241ef9403acd384e888c26f888
--- /dev/null
+++ b/routes.go
@@ -0,0 +1,139 @@
+package main
+
+import (
+	"encoding/json"
+	"net/http"
+	"strings"
+)
+
+const (
+	paramRepository = "repo"   // repo where the file is located
+	paramId         = "id"     // a sha in Git, nodeid in Mercurial, etc.
+	paramCommit     = "commit" // a commit
+	paramPath       = "path"   // the file name
+)
+
+// getFile gets the contents of a file.
+//
+// If the request method is GET, this sends a response containing the file
+// blob, if it exists. Otherwise, it will send a 404 Not Found. If the request
+// method is HEAD, this sends a 200 OK response if the file exists. Otherwise,
+// it will send a 404 Not Found.
+//
+// Files can be retrieved either by providing a id, or a commit and file path
+// pair in the query parameters.
+//
+// ID URL: /file?repo=<repo>&id=<id>
+// Commit and file path URL: /file?repo=<repo>&commit=<commit>&path=<path>
+func getFile(w http.ResponseWriter, r *http.Request) {
+	repoName := r.URL.Query().Get(paramRepository)
+	id := r.URL.Query().Get(paramId)
+	commit := r.URL.Query().Get(paramCommit)
+	path := r.URL.Query().Get(paramPath)
+
+	if len(repoName) != 0 &&
+		(len(id) != 0 || (len(commit) != 0 && len(path) != 0)) {
+		repo := GetRepository(repoName)
+		if repo == nil {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+
+		switch r.Method {
+		case "GET":
+			blob, err := getFileBlob(id, commit, path, repo)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusBadRequest)
+			} else {
+				w.Header().Set("Content-Type", "application/octet-stream")
+				w.Write(blob)
+			}
+		case "HEAD":
+			exists, err := getFileExists(id, commit, path, repo)
+			if exists {
+				w.WriteHeader(http.StatusOK)
+			} else {
+				if err != nil {
+					http.Error(w, err.Error(), http.StatusBadRequest)
+				} else {
+					w.WriteHeader(http.StatusNotFound)
+				}
+			}
+		default:
+			w.WriteHeader(http.StatusMethodNotAllowed)
+		}
+	} else {
+		http.Error(w, "Repository or ID not specified", http.StatusBadRequest)
+	}
+}
+
+func getFileBlob(id, commit, path string, repo Repository) ([]byte, error) {
+	if len(id) != 0 {
+		return repo.GetFile(id)
+	}
+
+	return repo.GetFileByCommit(commit, path)
+}
+
+func getFileExists(id, commit, path string, repo Repository) (bool, error) {
+	if len(id) != 0 {
+		return repo.FileExists(id)
+	}
+
+	return repo.FileExistsByCommit(commit, path)
+}
+
+// getPath retrieves the path of a valid repository.
+//
+// URL: /path?repo=<repo>
+func getPath(w http.ResponseWriter, r *http.Request) {
+	repoName := r.URL.Query().Get(paramRepository)
+
+	if len(repoName) != 0 {
+		repo := GetRepository(strings.Split(repoName, "/")[0])
+		if repo == nil {
+			http.Error(w, "Repository not found", http.StatusBadRequest)
+		}
+
+		if r.Method == "GET" {
+			w.Header().Set("Content-Type", "text/plain")
+			w.Write([]byte(repo.GetPath() + "/info/refs"))
+		} else {
+			w.WriteHeader(http.StatusMethodNotAllowed)
+		}
+	} else {
+		http.Error(w, "Repository not specified", http.StatusBadRequest)
+	}
+}
+
+// getSession uses Basic Authentication to return a private session token based
+// on the authentication information provided in the request header.
+//
+// This responds with a JSON of the Session, in the following format:
+// { "private-token":<token> }
+//
+// URL: /path/session
+func getSession(w http.ResponseWriter, r *http.Request) {
+	session, err := CreateSession(r)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusUnauthorized)
+		return
+	}
+
+	json, err := json.Marshal(session)
+	if err != nil {
+		http.Error(w, "Session marshalling error",
+			http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(json)
+}
+
+// Route handles all the URL routing.
+func Route() {
+	http.HandleFunc("/file", BasicAuth(getFile))
+	http.HandleFunc("/path", BasicAuth(getPath))
+	http.HandleFunc("/session", getSession)
+}
diff --git a/sample_config.json b/sample_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..99ea3aaeffcb1cc5ba6c0a6303779b280b6e01fb
--- /dev/null
+++ b/sample_config.json
@@ -0,0 +1,9 @@
+{
+    "port": 8888,
+    "username": "username",
+    "password": "password",
+    "repositories": [
+        {"name": "repo1", "path": "/path/to/repo1.git", "scm": "git"},
+        {"name": "repo2", "path": "/path/to/repo2.hg", "scm": "hg"}
+    ]
+}
\ No newline at end of file
diff --git a/session.go b/session.go
new file mode 100644
index 0000000000000000000000000000000000000000..b66fb0826e65883faecded8fa878a95fdf948207
--- /dev/null
+++ b/session.go
@@ -0,0 +1,8 @@
+package main
+
+// Represents a Session. Currently only holds the private_token, used for
+// authentication. This can be extended in the future to store more session
+// information such as time created, user email, etc.
+type Session struct {
+	PrivateToken string `json:"private_token"`
+}
diff --git a/util.go b/util.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc7c8af65ee7851b8b86fc94898e15e34dcbd9e2
--- /dev/null
+++ b/util.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"log"
+)
+
+var repos map[string]Repository = make(map[string]Repository)
+var port int
+var username string
+var password string
+
+// LoadConfig loads the configuration file, config.json.
+// The configuration file is a json file containing repositories with their
+// corresponding file paths, the port number to run the server on, and the
+// authentication information for the admin user. Repository names specified
+// in config.json must be unique; they do not need to be the actual repository
+// name.
+func LoadConfig() {
+	const CONF_PATH = "config.json"
+
+	type Repo struct {
+		Name string
+		Path string
+		Scm  string
+	}
+
+	type Configuration struct {
+		Port         int
+		Username     string
+		Password     string
+		Repositories []Repo
+	}
+
+	var conf Configuration
+
+	content, err := ioutil.ReadFile(CONF_PATH)
+	if err != nil {
+		log.Fatal("ReadFile: ", err,
+			"\nDid you set up ", CONF_PATH, " correctly?")
+	}
+
+	if json.Unmarshal(content, &conf) != nil {
+		log.Fatal("Unmarshal: ", err,
+			"\nDid you set up ", CONF_PATH, " correctly?")
+	}
+
+	port = conf.Port
+	username = conf.Username
+	password = conf.Password
+
+	for _, r := range conf.Repositories {
+		switch r.Scm {
+		case "git":
+			repos[r.Name] = &GitRepository{RepositoryInfo{r.Name, r.Path}}
+		default:
+			log.Println("Repository SCM type is not defined: ", r.Scm)
+		}
+	}
+}
+
+// GetRepository returns the repository based on the name specified in
+// config.json.
+func GetRepository(name string) Repository {
+	return repos[name]
+}
+
+// GetPort returns the port where rb-gateway is running.
+func GetPort() int {
+	return port
+}
+
+// GetUsername returns the admin username.
+func GetUsername() string {
+	return username
+}
+
+// GetPassword returns the admin password.
+func GetPassword() string {
+	return password
+}
