Add gosrc

Move github.com/garyburd/gosrc to this repo.
diff --git a/gosrc/LICENSE b/gosrc/LICENSE
new file mode 100644
index 0000000..65d761b
--- /dev/null
+++ b/gosrc/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/gosrc/README.markdown b/gosrc/README.markdown
new file mode 100644
index 0000000..7dc2a8a
--- /dev/null
+++ b/gosrc/README.markdown
@@ -0,0 +1,9 @@
+Package gosrc fetches Go package source code from version control services.
+
+Contributions
+-------------
+Contributions to this project are welcome, though please send mail before
+starting work on anything major. Contributors retain their copyright, so we
+need you to fill out a short form before we can accept your contribution:
+https://developers.google.com/open-source/cla/individual
+
diff --git a/gosrc/bitbucket.go b/gosrc/bitbucket.go
new file mode 100644
index 0000000..b1da770
--- /dev/null
+++ b/gosrc/bitbucket.go
@@ -0,0 +1,102 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"net/http"
+	"path"
+	"regexp"
+)
+
+func init() {
+	addService(&service{
+		pattern: regexp.MustCompile(`^bitbucket\.org/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`),
+		prefix:  "bitbucket.org/",
+		get:     getBitbucketDir,
+	})
+}
+
+var bitbucketEtagRe = regexp.MustCompile(`^(hg|git)-`)
+
+func getBitbucketDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
+
+	c := &httpClient{client: client}
+
+	if m := bitbucketEtagRe.FindStringSubmatch(savedEtag); m != nil {
+		match["vcs"] = m[1]
+	} else {
+		var repo struct {
+			Scm string
+		}
+		if err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}", match), &repo); err != nil {
+			return nil, err
+		}
+		match["vcs"] = repo.Scm
+	}
+
+	tags := make(map[string]string)
+	for _, nodeType := range []string{"branches", "tags"} {
+		var nodes map[string]struct {
+			Node string
+		}
+		if err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}", match, nodeType), &nodes); err != nil {
+			return nil, err
+		}
+		for t, n := range nodes {
+			tags[t] = n.Node
+		}
+	}
+
+	var err error
+	match["tag"], match["commit"], err = bestTag(tags, defaultTags[match["vcs"]])
+	if err != nil {
+		return nil, err
+	}
+
+	etag := expand("{vcs}-{commit}", match)
+	if etag == savedEtag {
+		return nil, ErrNotModified
+	}
+
+	var contents struct {
+		Directories []string
+		Files       []struct {
+			Path string
+		}
+	}
+
+	if err := c.getJSON(expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/src/{tag}{dir}/", match), &contents); err != nil {
+		return nil, err
+	}
+
+	var files []*File
+	var dataURLs []string
+
+	for _, f := range contents.Files {
+		_, name := path.Split(f.Path)
+		if isDocFile(name) {
+			files = append(files, &File{Name: name, BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}/{0}", match, f.Path)})
+			dataURLs = append(dataURLs, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/raw/{tag}/{0}", match, f.Path))
+		}
+	}
+
+	if err := c.getFiles(dataURLs, files); err != nil {
+		return nil, err
+	}
+
+	return &Directory{
+		BrowseURL:      expand("https://bitbucket.org/{owner}/{repo}/src/{tag}{dir}", match),
+		Etag:           etag,
+		Files:          files,
+		LineFmt:        "%s#cl-%d",
+		ProjectName:    match["repo"],
+		ProjectRoot:    expand("bitbucket.org/{owner}/{repo}", match),
+		ProjectURL:     expand("https://bitbucket.org/{owner}/{repo}/", match),
+		Subdirectories: contents.Directories,
+		VCS:            match["vcs"],
+	}, nil
+}
diff --git a/gosrc/build.go b/gosrc/build.go
new file mode 100644
index 0000000..5ffc969
--- /dev/null
+++ b/gosrc/build.go
@@ -0,0 +1,62 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"bytes"
+	"go/build"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+	"time"
+)
+
+// Import returns details about the package in the directory.
+func (dir *Directory) Import(ctx *build.Context, mode build.ImportMode) (*build.Package, error) {
+	safeCopy := *ctx
+	ctx = &safeCopy
+	ctx.JoinPath = path.Join
+	ctx.IsAbsPath = path.IsAbs
+	ctx.SplitPathList = func(list string) []string { return strings.Split(list, ":") }
+	ctx.IsDir = func(path string) bool { return false }
+	ctx.HasSubdir = func(root, dir string) (rel string, ok bool) { return "", false }
+	ctx.ReadDir = dir.readDir
+	ctx.OpenFile = dir.openFile
+	return ctx.ImportDir(".", mode)
+}
+
+type fileInfo struct{ f *File }
+
+func (fi fileInfo) Name() string       { return fi.f.Name }
+func (fi fileInfo) Size() int64        { return int64(len(fi.f.Data)) }
+func (fi fileInfo) Mode() os.FileMode  { return 0 }
+func (fi fileInfo) ModTime() time.Time { return time.Time{} }
+func (fi fileInfo) IsDir() bool        { return false }
+func (fi fileInfo) Sys() interface{}   { return nil }
+
+func (dir *Directory) readDir(name string) ([]os.FileInfo, error) {
+	if name != "." {
+		return nil, os.ErrNotExist
+	}
+	fis := make([]os.FileInfo, len(dir.Files))
+	for i, f := range dir.Files {
+		fis[i] = fileInfo{f}
+	}
+	return fis, nil
+}
+
+func (dir *Directory) openFile(path string) (io.ReadCloser, error) {
+	name := strings.TrimPrefix(path, "./")
+	for _, f := range dir.Files {
+		if f.Name == name {
+			return ioutil.NopCloser(bytes.NewReader(f.Data)), nil
+		}
+	}
+	return nil, os.ErrNotExist
+}
diff --git a/gosrc/client.go b/gosrc/client.go
new file mode 100644
index 0000000..c025e4d
--- /dev/null
+++ b/gosrc/client.go
@@ -0,0 +1,124 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+)
+
+type httpClient struct {
+	errFn  func(*http.Response) error
+	header http.Header
+	client *http.Client
+}
+
+func (c *httpClient) err(resp *http.Response) error {
+	if resp.StatusCode == 404 {
+		return NotFoundError{"Resource not found: " + resp.Request.URL.String()}
+	}
+	if c.errFn != nil {
+		return c.errFn(resp)
+	}
+	return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: (%s)", resp.StatusCode, resp.Request.URL.String())}
+}
+
+func (c *httpClient) get(url string) (*http.Response, error) {
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	for k, vs := range c.header {
+		req.Header[k] = vs
+	}
+	resp, err := c.client.Do(req)
+	if err != nil {
+		return nil, &RemoteError{req.URL.Host, err}
+	}
+	return resp, err
+}
+
+func (c *httpClient) getBytes(url string) ([]byte, error) {
+	resp, err := c.get(url)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return nil, c.err(resp)
+	}
+	p, err := ioutil.ReadAll(resp.Body)
+	resp.Body.Close()
+	return p, err
+}
+
+func (c *httpClient) getReader(url string) (io.ReadCloser, error) {
+	resp, err := c.get(url)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return nil, c.err(resp)
+	}
+	return resp.Body, nil
+}
+
+func (c *httpClient) getJSON(url string, v interface{}) error {
+	resp, err := c.get(url)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return c.err(resp)
+	}
+	err = json.NewDecoder(resp.Body).Decode(v)
+	if _, ok := err.(*json.SyntaxError); ok {
+		err = NotFoundError{"JSON syntax error at " + url}
+	}
+	return err
+}
+
+func (c *httpClient) getFiles(urls []string, files []*File) error {
+	ch := make(chan error, len(files))
+	for i := range files {
+		go func(i int) {
+			resp, err := c.get(urls[i])
+			if err != nil {
+				ch <- err
+				return
+			}
+			defer resp.Body.Close()
+			if resp.StatusCode != 200 {
+				var err error
+				if c.errFn != nil {
+					err = c.errFn(resp)
+				} else {
+					err = &RemoteError{resp.Request.URL.Host, fmt.Errorf("get %s -> %d", urls[i], resp.StatusCode)}
+				}
+				ch <- err
+				return
+			}
+			files[i].Data, err = ioutil.ReadAll(resp.Body)
+			if err != nil {
+				ch <- &RemoteError{resp.Request.URL.Host, err}
+				return
+			}
+			ch <- nil
+		}(i)
+	}
+	for _ = range files {
+		if err := <-ch; err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/gosrc/github.go b/gosrc/github.go
new file mode 100644
index 0000000..347a608
--- /dev/null
+++ b/gosrc/github.go
@@ -0,0 +1,296 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strings"
+	"time"
+)
+
+func init() {
+	addService(&service{
+		pattern:         regexp.MustCompile(`^github\.com/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`),
+		prefix:          "github.com/",
+		get:             getGitHubDir,
+		getPresentation: getGitHubPresentation,
+		getProject:      getGitHubProject,
+	})
+
+	addService(&service{
+		pattern: regexp.MustCompile(`^gist\.github\.com/(?P<gist>[a-z0-9A-Z_.\-]+)\.git$`),
+		prefix:  "gist.github.com/",
+		get:     getGistDir,
+	})
+}
+
+var (
+	gitHubRawHeader     = http.Header{"Accept": {"application/vnd.github-blob.raw"}}
+	gitHubPreviewHeader = http.Header{"Accept": {"application/vnd.github.preview"}}
+)
+
+func gitHubError(resp *http.Response) error {
+	var e struct {
+		Message string `json:"message"`
+	}
+	if err := json.NewDecoder(resp.Body).Decode(&e); err == nil {
+		return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: %s (%s)", resp.StatusCode, e.Message, resp.Request.URL.String())}
+	}
+	return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: (%s)", resp.StatusCode, resp.Request.URL.String())}
+}
+
+func getGitHubDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
+
+	c := &httpClient{client: client, errFn: gitHubError}
+
+	var refs []*struct {
+		Object struct {
+			Type string
+			Sha  string
+			URL  string
+		}
+		Ref string
+		URL string
+	}
+
+	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/git/refs", match), &refs); err != nil {
+		return nil, err
+	}
+
+	tags := make(map[string]string)
+	for _, ref := range refs {
+		switch {
+		case strings.HasPrefix(ref.Ref, "refs/heads/"):
+			tags[ref.Ref[len("refs/heads/"):]] = ref.Object.Sha
+		case strings.HasPrefix(ref.Ref, "refs/tags/"):
+			tags[ref.Ref[len("refs/tags/"):]] = ref.Object.Sha
+		}
+	}
+
+	var commit string
+	var err error
+	match["tag"], commit, err = bestTag(tags, "master")
+	if err != nil {
+		return nil, err
+	}
+
+	if commit == savedEtag {
+		return nil, ErrNotModified
+	}
+
+	var contents []*struct {
+		Type    string
+		Name    string
+		GitURL  string `json:"git_url"`
+		HTMLURL string `json:"html_url"`
+	}
+
+	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}?ref={tag}", match), &contents); err != nil {
+		return nil, err
+	}
+
+	if len(contents) == 0 {
+		return nil, NotFoundError{"No files in directory."}
+	}
+
+	// Because Github API URLs are case-insensitive, we check that the owner
+	// and repo returned from Github matches the one that we are requesting.
+	if !strings.HasPrefix(contents[0].GitURL, expand("https://api.github.com/repos/{owner}/{repo}/", match)) {
+		return nil, NotFoundError{"Github import path has incorrect case."}
+	}
+
+	var files []*File
+	var dataURLs []string
+	var subdirs []string
+
+	for _, item := range contents {
+		switch {
+		case item.Type == "dir":
+			if isValidPathElement(item.Name) {
+				subdirs = append(subdirs, item.Name)
+			}
+		case isDocFile(item.Name):
+			files = append(files, &File{Name: item.Name, BrowseURL: item.HTMLURL})
+			dataURLs = append(dataURLs, item.GitURL)
+		}
+	}
+
+	c.header = gitHubRawHeader
+	if err := c.getFiles(dataURLs, files); err != nil {
+		return nil, err
+	}
+
+	browseURL := expand("https://github.com/{owner}/{repo}", match)
+	if match["dir"] != "" {
+		browseURL = expand("https://github.com/{owner}/{repo}/tree/{tag}{dir}", match)
+	}
+
+	return &Directory{
+		BrowseURL:      browseURL,
+		Etag:           commit,
+		Files:          files,
+		LineFmt:        "%s#L%d",
+		ProjectName:    match["repo"],
+		ProjectRoot:    expand("github.com/{owner}/{repo}", match),
+		ProjectURL:     expand("https://github.com/{owner}/{repo}", match),
+		Subdirectories: subdirs,
+		VCS:            "git",
+	}, nil
+}
+
+func getGitHubPresentation(client *http.Client, match map[string]string) (*Presentation, error) {
+	c := &httpClient{client: client, header: gitHubRawHeader}
+
+	p, err := c.getBytes(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/{file}", match))
+	if err != nil {
+		return nil, err
+	}
+
+	apiBase, err := url.Parse(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/", match))
+	if err != nil {
+		return nil, err
+	}
+	rawBase, err := url.Parse(expand("https://raw.github.com/{owner}/{repo}/master{dir}/", match))
+	if err != nil {
+		return nil, err
+	}
+
+	c.header = gitHubRawHeader
+
+	b := &presBuilder{
+		data:     p,
+		filename: match["file"],
+		fetch: func(fnames []string) ([]*File, error) {
+			var files []*File
+			var dataURLs []string
+			for _, fname := range fnames {
+				u, err := apiBase.Parse(fname)
+				if err != nil {
+					return nil, err
+				}
+				u.RawQuery = apiBase.RawQuery
+				files = append(files, &File{Name: fname})
+				dataURLs = append(dataURLs, u.String())
+			}
+			err := c.getFiles(dataURLs, files)
+			return files, err
+		},
+		resolveURL: func(fname string) string {
+			u, err := rawBase.Parse(fname)
+			if err != nil {
+				return "/notfound"
+			}
+			if strings.HasSuffix(fname, ".svg") {
+				u.Host = "rawgithub.com"
+			}
+			return u.String()
+		},
+	}
+
+	return b.build()
+}
+
+// GetGitHubUpdates returns the full names ("owner/repo") of recently pushed GitHub repositories.
+// by pushedAfter.
+func GetGitHubUpdates(client *http.Client, pushedAfter string) (maxPushedAt string, names []string, err error) {
+	c := httpClient{client: client, header: gitHubPreviewHeader}
+
+	if pushedAfter == "" {
+		pushedAfter = time.Now().Add(-24 * time.Hour).UTC().Format("2006-01-02T15:04:05Z")
+	}
+	u := "https://api.github.com/search/repositories?order=asc&sort=updated&q=fork:true+language:Go+pushed:>" + pushedAfter
+	var updates struct {
+		Items []struct {
+			FullName string `json:"full_name"`
+			PushedAt string `json:"pushed_at"`
+		}
+	}
+	err = c.getJSON(u, &updates)
+	if err != nil {
+		return pushedAfter, nil, err
+	}
+
+	maxPushedAt = pushedAfter
+	for _, item := range updates.Items {
+		names = append(names, item.FullName)
+		if item.PushedAt > maxPushedAt {
+			maxPushedAt = item.PushedAt
+		}
+	}
+	return maxPushedAt, names, nil
+}
+
+func getGitHubProject(client *http.Client, match map[string]string) (*Project, error) {
+	c := &httpClient{client: client, errFn: gitHubError}
+
+	var repo struct {
+		Description string
+	}
+
+	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}", match), &repo); err != nil {
+		return nil, err
+	}
+
+	return &Project{
+		Description: repo.Description,
+	}, nil
+}
+
+func getGistDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
+	c := &httpClient{client: client, errFn: gitHubError}
+
+	var gist struct {
+		Files map[string]struct {
+			Content string
+		}
+		HtmlUrl string `json:"html_url"`
+		History []struct {
+			Version string
+		}
+	}
+
+	if err := c.getJSON(expand("https://api.github.com/gists/{gist}", match), &gist); err != nil {
+		return nil, err
+	}
+
+	if len(gist.History) == 0 {
+		return nil, NotFoundError{"History not found."}
+	}
+	commit := gist.History[0].Version
+
+	if commit == savedEtag {
+		return nil, ErrNotModified
+	}
+
+	var files []*File
+
+	for name, file := range gist.Files {
+		if isDocFile(name) {
+			files = append(files, &File{
+				Name:      name,
+				Data:      []byte(file.Content),
+				BrowseURL: gist.HtmlUrl + "#file-" + strings.Replace(name, ".", "-", -1),
+			})
+		}
+	}
+
+	return &Directory{
+		BrowseURL:      gist.HtmlUrl,
+		Etag:           commit,
+		Files:          files,
+		LineFmt:        "%s-L%d",
+		ProjectName:    match["gist"],
+		ProjectRoot:    expand("gist.github.com/{gist}.git", match),
+		ProjectURL:     gist.HtmlUrl,
+		Subdirectories: nil,
+		VCS:            "git",
+	}, nil
+}
diff --git a/gosrc/google.go b/gosrc/google.go
new file mode 100644
index 0000000..8bd7cf3
--- /dev/null
+++ b/gosrc/google.go
@@ -0,0 +1,214 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"errors"
+	"net/http"
+	"net/url"
+	"regexp"
+	"strings"
+)
+
+func init() {
+	addService(&service{
+		pattern:         regexp.MustCompile(`^code\.google\.com/(?P<pr>[pr])/(?P<repo>[a-z0-9\-]+)(:?\.(?P<subrepo>[a-z0-9\-]+))?(?P<dir>/[a-z0-9A-Z_.\-/]+)?$`),
+		prefix:          "code.google.com/",
+		get:             getGoogleDir,
+		getPresentation: getGooglePresentation,
+	})
+}
+
+var (
+	googleRepoRe     = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)
+	googleRevisionRe = regexp.MustCompile(`<h2>(?:[^ ]+ - )?Revision *([^:]+):`)
+	googleEtagRe     = regexp.MustCompile(`^(hg|git|svn)-`)
+	googleFileRe     = regexp.MustCompile(`<li><a href="([^"]+)"`)
+)
+
+func getGoogleDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
+	c := &httpClient{client: client}
+
+	setupGoogleMatch(match)
+	if m := googleEtagRe.FindStringSubmatch(savedEtag); m != nil {
+		match["vcs"] = m[1]
+	} else if err := getGoogleVCS(c, match); err != nil {
+		return nil, err
+	}
+
+	// Scrape the repo browser to find the project revision and individual Go files.
+	p, err := c.getBytes(expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/", match))
+	if err != nil {
+		return nil, err
+	}
+
+	var etag string
+	m := googleRevisionRe.FindSubmatch(p)
+	if m == nil {
+		return nil, errors.New("Could not find revision for " + match["importPath"])
+	}
+	etag = expand("{vcs}-{0}", match, string(m[1]))
+	if etag == savedEtag {
+		return nil, ErrNotModified
+	}
+
+	var subdirs []string
+	var files []*File
+	var dataURLs []string
+	for _, m := range googleFileRe.FindAllSubmatch(p, -1) {
+		fname := string(m[1])
+		switch {
+		case strings.HasSuffix(fname, "/"):
+			fname = fname[:len(fname)-1]
+			if isValidPathElement(fname) {
+				subdirs = append(subdirs, fname)
+			}
+		case isDocFile(fname):
+			files = append(files, &File{Name: fname, BrowseURL: expand("http://code.google.com/{pr}/{repo}/source/browse{dir}/{0}{query}", match, fname)})
+			dataURLs = append(dataURLs, expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/{0}", match, fname))
+		}
+	}
+
+	if err := c.getFiles(dataURLs, files); err != nil {
+		return nil, err
+	}
+
+	var projectURL string
+	if match["subrepo"] == "" {
+		projectURL = expand("https://code.google.com/{pr}/{repo}/", match)
+	} else {
+		projectURL = expand("https://code.google.com/{pr}/{repo}/source/browse?repo={subrepo}", match)
+	}
+
+	return &Directory{
+		BrowseURL:   expand("http://code.google.com/{pr}/{repo}/source/browse{dir}/{query}", match),
+		Etag:        etag,
+		Files:       files,
+		LineFmt:     "%s#%d",
+		ProjectName: expand("{repo}{dot}{subrepo}", match),
+		ProjectRoot: expand("code.google.com/{pr}/{repo}{dot}{subrepo}", match),
+		ProjectURL:  projectURL,
+		VCS:         match["vcs"],
+	}, nil
+}
+
+func setupGoogleMatch(match map[string]string) {
+	if s := match["subrepo"]; s != "" {
+		match["dot"] = "."
+		match["query"] = "?repo=" + s
+	} else {
+		match["dot"] = ""
+		match["query"] = ""
+	}
+}
+
+func getGoogleVCS(c *httpClient, match map[string]string) error {
+	// Scrape the HTML project page to find the VCS.
+	p, err := c.getBytes(expand("http://code.google.com/{pr}/{repo}/source/checkout", match))
+	if err != nil {
+		return err
+	}
+	m := googleRepoRe.FindSubmatch(p)
+	if m == nil {
+		return NotFoundError{"Could not VCS on Google Code project page."}
+	}
+	match["vcs"] = string(m[1])
+	return nil
+}
+
+func getStandardDir(client *http.Client, importPath string, savedEtag string) (*Directory, error) {
+	c := &httpClient{client: client}
+
+	p, err := c.getBytes("http://go.googlecode.com/hg-history/release/src/pkg/" + importPath + "/")
+	if err != nil {
+		return nil, err
+	}
+
+	var etag string
+	m := googleRevisionRe.FindSubmatch(p)
+	if m == nil {
+		return nil, errors.New("Could not find revision for " + importPath)
+	}
+	etag = string(m[1])
+	if etag == savedEtag {
+		return nil, ErrNotModified
+	}
+
+	var files []*File
+	var dataURLs []string
+	for _, m := range googleFileRe.FindAllSubmatch(p, -1) {
+		fname := strings.Split(string(m[1]), "?")[0]
+		if isDocFile(fname) {
+			files = append(files, &File{Name: fname, BrowseURL: "http://code.google.com/p/go/source/browse/src/pkg/" + importPath + "/" + fname + "?name=release"})
+			dataURLs = append(dataURLs, "http://go.googlecode.com/hg-history/release/src/pkg/"+importPath+"/"+fname)
+		}
+	}
+
+	if err := c.getFiles(dataURLs, files); err != nil {
+		return nil, err
+	}
+
+	return &Directory{
+		BrowseURL:    "http://code.google.com/p/go/source/browse/src/pkg/" + importPath + "?name=release",
+		Etag:         etag,
+		Files:        files,
+		ImportPath:   importPath,
+		LineFmt:      "%s#%d",
+		ProjectName:  "Go",
+		ProjectRoot:  "",
+		ProjectURL:   "https://code.google.com/p/go/",
+		ResolvedPath: importPath,
+		VCS:          "hg",
+	}, nil
+}
+
+func getGooglePresentation(client *http.Client, match map[string]string) (*Presentation, error) {
+	c := &httpClient{client: client}
+
+	setupGoogleMatch(match)
+	if err := getGoogleVCS(c, match); err != nil {
+		return nil, err
+	}
+
+	rawBase, err := url.Parse(expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/", match))
+	if err != nil {
+		return nil, err
+	}
+
+	p, err := c.getBytes(expand("http://{subrepo}{dot}{repo}.googlecode.com/{vcs}{dir}/{file}", match))
+	if err != nil {
+		return nil, err
+	}
+
+	b := &presBuilder{
+		data:     p,
+		filename: match["file"],
+		fetch: func(fnames []string) ([]*File, error) {
+			var files []*File
+			var dataURLs []string
+			for _, fname := range fnames {
+				u, err := rawBase.Parse(fname)
+				if err != nil {
+					return nil, err
+				}
+				files = append(files, &File{Name: fname})
+				dataURLs = append(dataURLs, u.String())
+			}
+			err := c.getFiles(dataURLs, files)
+			return files, err
+		},
+		resolveURL: func(fname string) string {
+			u, err := rawBase.Parse(fname)
+			if err != nil {
+				return "/notfound"
+			}
+			return u.String()
+		},
+	}
+
+	return b.build()
+}
diff --git a/gosrc/gosrc.go b/gosrc/gosrc.go
new file mode 100644
index 0000000..c1f5aab
--- /dev/null
+++ b/gosrc/gosrc.go
@@ -0,0 +1,371 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package gosrc fetches Go package source code from version control services.
+package gosrc
+
+import (
+	"encoding/xml"
+	"errors"
+	"io"
+	"net/http"
+	"path"
+	"regexp"
+	"strings"
+)
+
+// File represents a file.
+type File struct {
+	// File name with no directory.
+	Name string
+
+	// Contents of the file.
+	Data []byte
+
+	// Location of file on version control service website.
+	BrowseURL string
+}
+
+// Directory describes a directory on a version control service.
+type Directory struct {
+	// The import path for this package.
+	ImportPath string
+
+	// Import path of package after resolving go-import meta tags, if any.
+	ResolvedPath string
+
+	// Import path prefix for all packages in the project.
+	ProjectRoot string
+
+	// Name of the project.
+	ProjectName string
+
+	// Project home page.
+	ProjectURL string
+
+	// Version control system: git, hg, bzr, ...
+	VCS string
+
+	// Cache validation tag. This tag is not necessarily an HTTP entity tag.
+	// The tag is "" if there is no meaningful cache validation for the VCS.
+	Etag string
+
+	// Files.
+	Files []*File
+
+	// Subdirectories, not guaranteed to contain Go code.
+	Subdirectories []string
+
+	// Location of directory on version control service website.
+	BrowseURL string
+
+	// Format specifier for link to source line. Example: "%s#L%d"
+	LineFmt string
+}
+
+// Project represents a repository.
+type Project struct {
+	Description string
+}
+
+// NotFoundError indicates that the directory or presentation was not found.
+type NotFoundError struct {
+	// Diagnostic message describing why the directory was not found.
+	Message string
+}
+
+func (e NotFoundError) Error() string {
+	return e.Message
+}
+
+// IsNotFound returns true if err is of type NotFoundError.
+func IsNotFound(err error) bool {
+	_, ok := err.(NotFoundError)
+	return ok
+}
+
+type RemoteError struct {
+	Host string
+	err  error
+}
+
+func (e *RemoteError) Error() string {
+	return e.err.Error()
+}
+
+// ErrNotModified indicates that the directory matches the specified etag.
+var ErrNotModified = errors.New("package not modified")
+
+var errNoMatch = errors.New("no match")
+
+// service represents a source code control service.
+type service struct {
+	pattern         *regexp.Regexp
+	prefix          string
+	get             func(*http.Client, map[string]string, string) (*Directory, error)
+	getPresentation func(*http.Client, map[string]string) (*Presentation, error)
+	getProject      func(*http.Client, map[string]string) (*Project, error)
+}
+
+var services []*service
+
+func addService(s *service) {
+	if s.prefix == "" {
+		services = append(services, s)
+	} else {
+		services = append([]*service{s}, services...)
+	}
+}
+
+func (s *service) match(importPath string) (map[string]string, error) {
+	if !strings.HasPrefix(importPath, s.prefix) {
+		return nil, nil
+	}
+	m := s.pattern.FindStringSubmatch(importPath)
+	if m == nil {
+		if s.prefix != "" {
+			return nil, NotFoundError{"Import path prefix matches known service, but regexp does not."}
+		}
+		return nil, nil
+	}
+	match := map[string]string{"importPath": importPath}
+	for i, n := range s.pattern.SubexpNames() {
+		if n != "" {
+			match[n] = m[i]
+		}
+	}
+	return match, nil
+}
+
+func attrValue(attrs []xml.Attr, name string) string {
+	for _, a := range attrs {
+		if strings.EqualFold(a.Name.Local, name) {
+			return a.Value
+		}
+	}
+	return ""
+}
+
+func fetchMeta(client *http.Client, importPath string) (map[string]string, error) {
+	uri := importPath
+	if !strings.Contains(uri, "/") {
+		// Add slash for root of domain.
+		uri = uri + "/"
+	}
+	uri = uri + "?go-get=1"
+
+	c := httpClient{client: client}
+	scheme := "https"
+	resp, err := c.get(scheme + "://" + uri)
+	if err != nil || resp.StatusCode != 200 {
+		if err == nil {
+			resp.Body.Close()
+		}
+		scheme = "http"
+		resp, err = c.get(scheme + "://" + uri)
+		if err != nil {
+			return nil, err
+		}
+	}
+	defer resp.Body.Close()
+	return parseMeta(scheme, importPath, resp.Body)
+}
+
+func parseMeta(scheme, importPath string, r io.Reader) (map[string]string, error) {
+	var match map[string]string
+
+	d := xml.NewDecoder(r)
+	d.Strict = false
+metaScan:
+	for {
+		t, tokenErr := d.Token()
+		if tokenErr != nil {
+			break metaScan
+		}
+		switch t := t.(type) {
+		case xml.EndElement:
+			if strings.EqualFold(t.Name.Local, "head") {
+				break metaScan
+			}
+		case xml.StartElement:
+			if strings.EqualFold(t.Name.Local, "body") {
+				break metaScan
+			}
+			if !strings.EqualFold(t.Name.Local, "meta") ||
+				attrValue(t.Attr, "name") != "go-import" {
+				continue metaScan
+			}
+			f := strings.Fields(attrValue(t.Attr, "content"))
+			if len(f) != 3 ||
+				!strings.HasPrefix(importPath, f[0]) ||
+				!(len(importPath) == len(f[0]) || importPath[len(f[0])] == '/') {
+				continue metaScan
+			}
+			if match != nil {
+				return nil, NotFoundError{"More than one <meta> found at " + scheme + "://" + importPath}
+			}
+
+			projectRoot, vcs, repo := f[0], f[1], f[2]
+
+			repo = strings.TrimSuffix(repo, "."+vcs)
+			i := strings.Index(repo, "://")
+			if i < 0 {
+				return nil, NotFoundError{"Bad repo URL in <meta>."}
+			}
+			proto := repo[:i]
+			repo = repo[i+len("://"):]
+
+			match = map[string]string{
+				// Used in getVCSDoc, same as vcsPattern matches.
+				"importPath": importPath,
+				"repo":       repo,
+				"vcs":        vcs,
+				"dir":        importPath[len(projectRoot):],
+
+				// Used in getVCSDoc
+				"scheme": proto,
+
+				// Used in getDynamic.
+				"projectRoot": projectRoot,
+				"projectName": path.Base(projectRoot),
+				"projectURL":  scheme + "://" + projectRoot,
+			}
+		}
+	}
+	if match == nil {
+		return nil, NotFoundError{"<meta> not found."}
+	}
+	return match, nil
+}
+
+var getVCSDirFn = func(client *http.Client, m map[string]string, etag string) (*Directory, error) {
+	return nil, errNoMatch
+}
+
+// getDynamic gets a directory from a service that is not statically known.
+func getDynamic(client *http.Client, importPath, etag string) (*Directory, error) {
+	match, err := fetchMeta(client, importPath)
+	if err != nil {
+		return nil, err
+	}
+
+	if match["projectRoot"] != importPath {
+		rootMatch, err := fetchMeta(client, match["projectRoot"])
+		if err != nil {
+			return nil, err
+		}
+		if rootMatch["projectRoot"] != match["projectRoot"] {
+			return nil, NotFoundError{"Project root mismatch."}
+		}
+	}
+
+	dir, err := getStatic(client, expand("{repo}{dir}", match), etag)
+	if err == errNoMatch {
+		dir, err = getVCSDirFn(client, match, etag)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	if dir != nil {
+		dir.ImportPath = importPath
+		dir.ProjectRoot = match["projectRoot"]
+		dir.ProjectName = match["projectName"]
+		dir.ProjectURL = match["projectURL"]
+		if dir.ResolvedPath == "" {
+			dir.ResolvedPath = dir.ImportPath
+		}
+	}
+
+	return dir, err
+}
+
+// getStatic gets a diretory from a statically known service. getStatic
+// returns errNoMatch if the import path is not recognized.
+func getStatic(client *http.Client, importPath, etag string) (*Directory, error) {
+	for _, s := range services {
+		if s.get == nil {
+			continue
+		}
+		match, err := s.match(importPath)
+		if err != nil {
+			return nil, err
+		}
+		if match != nil {
+			dir, err := s.get(client, match, etag)
+			if dir != nil {
+				dir.ImportPath = importPath
+				dir.ResolvedPath = importPath
+			}
+			return dir, err
+		}
+	}
+	return nil, errNoMatch
+}
+
+func Get(client *http.Client, importPath string, etag string) (dir *Directory, err error) {
+	switch {
+	case localPath != "":
+		dir, err = getLocal(importPath)
+	case IsGoRepoPath(importPath):
+		dir, err = getStandardDir(client, importPath, etag)
+	case IsValidRemotePath(importPath):
+		dir, err = getStatic(client, importPath, etag)
+		if err == errNoMatch {
+			dir, err = getDynamic(client, importPath, etag)
+		}
+	default:
+		err = errNoMatch
+	}
+
+	if err == errNoMatch {
+		err = NotFoundError{"Import path not valid:"}
+	}
+
+	return dir, err
+}
+
+// GetPresentation gets a presentation from the the given path.
+func GetPresentation(client *http.Client, importPath string) (*Presentation, error) {
+	ext := path.Ext(importPath)
+	if ext != ".slide" && ext != ".article" {
+		return nil, NotFoundError{"unknown file extension."}
+	}
+
+	importPath, file := path.Split(importPath)
+	importPath = strings.TrimSuffix(importPath, "/")
+	for _, s := range services {
+		if s.getPresentation == nil {
+			continue
+		}
+		match, err := s.match(importPath)
+		if err != nil {
+			return nil, err
+		}
+		if match != nil {
+			match["file"] = file
+			return s.getPresentation(client, match)
+		}
+	}
+	return nil, NotFoundError{"path does not match registered service"}
+}
+
+// GetProject gets information about a repository.
+func GetProject(client *http.Client, importPath string) (*Project, error) {
+	for _, s := range services {
+		if s.getProject == nil {
+			continue
+		}
+		match, err := s.match(importPath)
+		if err != nil {
+			return nil, err
+		}
+		if match != nil {
+			return s.getProject(client, match)
+		}
+	}
+	return nil, NotFoundError{"path does not match registered service"}
+}
diff --git a/gosrc/launchpad.go b/gosrc/launchpad.go
new file mode 100644
index 0000000..d63f00d
--- /dev/null
+++ b/gosrc/launchpad.go
@@ -0,0 +1,135 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"archive/tar"
+	"bytes"
+	"compress/gzip"
+	"crypto/md5"
+	"encoding/hex"
+	"io"
+	"net/http"
+	"path"
+	"regexp"
+	"sort"
+	"strings"
+)
+
+func init() {
+	addService(&service{
+		pattern: regexp.MustCompile(`^launchpad\.net/(?P<repo>(?P<project>[a-z0-9A-Z_.\-]+)(?P<series>/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]+)*$`),
+		prefix:  "launchpad.net/",
+		get:     getLaunchpadDir,
+	})
+}
+
+type byHash []byte
+
+func (p byHash) Len() int { return len(p) / md5.Size }
+func (p byHash) Less(i, j int) bool {
+	return -1 == bytes.Compare(p[i*md5.Size:(i+1)*md5.Size], p[j*md5.Size:(j+1)*md5.Size])
+}
+func (p byHash) Swap(i, j int) {
+	var temp [md5.Size]byte
+	copy(temp[:], p[i*md5.Size:])
+	copy(p[i*md5.Size:(i+1)*md5.Size], p[j*md5.Size:])
+	copy(p[j*md5.Size:], temp[:])
+}
+
+func getLaunchpadDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
+	c := &httpClient{client: client}
+
+	if match["project"] != "" && match["series"] != "" {
+		rc, err := c.getReader(expand("https://code.launchpad.net/{project}{series}/.bzr/branch-format", match))
+		switch {
+		case err == nil:
+			rc.Close()
+			// The structure of the import path is launchpad.net/{root}/{dir}.
+		case IsNotFound(err):
+			// The structure of the import path is is launchpad.net/{project}/{dir}.
+			match["repo"] = match["project"]
+			match["dir"] = expand("{series}{dir}", match)
+		default:
+			return nil, err
+		}
+	}
+
+	p, err := c.getBytes(expand("https://bazaar.launchpad.net/+branch/{repo}/tarball", match))
+	if err != nil {
+		return nil, err
+	}
+
+	gzr, err := gzip.NewReader(bytes.NewReader(p))
+	if err != nil {
+		return nil, err
+	}
+	defer gzr.Close()
+
+	tr := tar.NewReader(gzr)
+
+	var hash []byte
+	inTree := false
+	dirPrefix := expand("+branch/{repo}{dir}/", match)
+	var files []*File
+	for {
+		h, err := tr.Next()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+		d, f := path.Split(h.Name)
+		if !isDocFile(f) {
+			continue
+		}
+		b := make([]byte, h.Size)
+		if _, err := io.ReadFull(tr, b); err != nil {
+			return nil, err
+		}
+
+		m := md5.New()
+		m.Write(b)
+		hash = m.Sum(hash)
+
+		if !strings.HasPrefix(h.Name, dirPrefix) {
+			continue
+		}
+		inTree = true
+		if d == dirPrefix {
+			files = append(files, &File{
+				Name:      f,
+				BrowseURL: expand("http://bazaar.launchpad.net/+branch/{repo}/view/head:{dir}/{0}", match, f),
+				Data:      b})
+		}
+	}
+
+	if !inTree {
+		return nil, NotFoundError{"Directory tree does not contain Go files."}
+	}
+
+	sort.Sort(byHash(hash))
+	m := md5.New()
+	m.Write(hash)
+	hash = m.Sum(hash[:0])
+	etag := hex.EncodeToString(hash)
+	if etag == savedEtag {
+		return nil, ErrNotModified
+	}
+
+	return &Directory{
+		BrowseURL:   expand("http://bazaar.launchpad.net/+branch/{repo}/view/head:{dir}/", match),
+		Etag:        etag,
+		Files:       files,
+		LineFmt:     "%s#L%d",
+		ProjectName: match["repo"],
+		ProjectRoot: expand("launchpad.net/{repo}", match),
+		ProjectURL:  expand("https://launchpad.net/{repo}/", match),
+		VCS:         "bzr",
+	}, nil
+}
diff --git a/gosrc/local.go b/gosrc/local.go
new file mode 100644
index 0000000..d9ae236
--- /dev/null
+++ b/gosrc/local.go
@@ -0,0 +1,63 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"go/build"
+	"io/ioutil"
+	"path/filepath"
+	"strconv"
+	"time"
+)
+
+var localPath string
+
+// SetLocalDevMode sets the package to local development mode. In this mode,
+// the GOPATH specified by path is used to find directories instead of version
+// control services.
+func SetLocalDevMode(path string) {
+	localPath = path
+}
+
+func getLocal(importPath string) (*Directory, error) {
+	ctx := build.Default
+	if localPath != "" {
+		ctx.GOPATH = localPath
+	}
+	bpkg, err := ctx.Import(importPath, ".", build.FindOnly)
+	if err != nil {
+		return nil, err
+	}
+	dir := filepath.Join(bpkg.SrcRoot, filepath.FromSlash(importPath))
+	fis, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return nil, err
+	}
+	var modTime time.Time
+	var files []*File
+	for _, fi := range fis {
+		if fi.IsDir() || !isDocFile(fi.Name()) {
+			continue
+		}
+		if fi.ModTime().After(modTime) {
+			modTime = fi.ModTime()
+		}
+		b, err := ioutil.ReadFile(filepath.Join(dir, fi.Name()))
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, &File{
+			Name: fi.Name(),
+			Data: b,
+		})
+	}
+	return &Directory{
+		ImportPath: importPath,
+		Etag:       strconv.FormatInt(modTime.Unix(), 16),
+		Files:      files,
+	}, nil
+}
diff --git a/gosrc/path.go b/gosrc/path.go
new file mode 100644
index 0000000..2b14adf
--- /dev/null
+++ b/gosrc/path.go
@@ -0,0 +1,529 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"path"
+	"regexp"
+	"strings"
+)
+
+var standardPath = map[string]bool{
+	"builtin": true,
+
+	// go list -f '"{{.ImportPath}}": true,'  std
+	"archive/tar":         true,
+	"archive/zip":         true,
+	"bufio":               true,
+	"bytes":               true,
+	"compress/bzip2":      true,
+	"compress/flate":      true,
+	"compress/gzip":       true,
+	"compress/lzw":        true,
+	"compress/zlib":       true,
+	"container/heap":      true,
+	"container/list":      true,
+	"container/ring":      true,
+	"crypto":              true,
+	"crypto/aes":          true,
+	"crypto/cipher":       true,
+	"crypto/des":          true,
+	"crypto/dsa":          true,
+	"crypto/ecdsa":        true,
+	"crypto/elliptic":     true,
+	"crypto/hmac":         true,
+	"crypto/md5":          true,
+	"crypto/rand":         true,
+	"crypto/rc4":          true,
+	"crypto/rsa":          true,
+	"crypto/sha1":         true,
+	"crypto/sha256":       true,
+	"crypto/sha512":       true,
+	"crypto/subtle":       true,
+	"crypto/tls":          true,
+	"crypto/x509":         true,
+	"crypto/x509/pkix":    true,
+	"database/sql":        true,
+	"database/sql/driver": true,
+	"debug/dwarf":         true,
+	"debug/elf":           true,
+	"debug/gosym":         true,
+	"debug/macho":         true,
+	"debug/pe":            true,
+	"encoding":            true,
+	"encoding/ascii85":    true,
+	"encoding/asn1":       true,
+	"encoding/base32":     true,
+	"encoding/base64":     true,
+	"encoding/binary":     true,
+	"encoding/csv":        true,
+	"encoding/gob":        true,
+	"encoding/hex":        true,
+	"encoding/json":       true,
+	"encoding/pem":        true,
+	"encoding/xml":        true,
+	"errors":              true,
+	"expvar":              true,
+	"flag":                true,
+	"fmt":                 true,
+	"go/ast":              true,
+	"go/build":            true,
+	"go/doc":              true,
+	"go/format":           true,
+	"go/parser":           true,
+	"go/printer":          true,
+	"go/scanner":          true,
+	"go/token":            true,
+	"hash":                true,
+	"hash/adler32":        true,
+	"hash/crc32":          true,
+	"hash/crc64":          true,
+	"hash/fnv":            true,
+	"html":                true,
+	"html/template":       true,
+	"image":               true,
+	"image/color":         true,
+	"image/color/palette": true,
+	"image/draw":          true,
+	"image/gif":           true,
+	"image/jpeg":          true,
+	"image/png":           true,
+	"index/suffixarray":   true,
+	"io":                  true,
+	"io/ioutil":           true,
+	"log":                 true,
+	"log/syslog":          true,
+	"math":                true,
+	"math/big":            true,
+	"math/cmplx":          true,
+	"math/rand":           true,
+	"mime":                true,
+	"mime/multipart":      true,
+	"net":                 true,
+	"net/http":            true,
+	"net/http/cgi":        true,
+	"net/http/cookiejar":  true,
+	"net/http/fcgi":       true,
+	"net/http/httptest":   true,
+	"net/http/httputil":   true,
+	"net/http/pprof":      true,
+	"net/mail":            true,
+	"net/rpc":             true,
+	"net/rpc/jsonrpc":     true,
+	"net/smtp":            true,
+	"net/textproto":       true,
+	"net/url":             true,
+	"os":                  true,
+	"os/exec":             true,
+	"os/signal":           true,
+	"os/user":             true,
+	"path":                true,
+	"path/filepath":       true,
+	"reflect":             true,
+	"regexp":              true,
+	"regexp/syntax":       true,
+	"runtime":             true,
+	"runtime/cgo":         true,
+	"runtime/debug":       true,
+	"runtime/pprof":       true,
+	"runtime/race":        true,
+	"sort":                true,
+	"strconv":             true,
+	"strings":             true,
+	"sync":                true,
+	"sync/atomic":         true,
+	"syscall":             true,
+	"testing":             true,
+	"testing/iotest":      true,
+	"testing/quick":       true,
+	"text/scanner":        true,
+	"text/tabwriter":      true,
+	"text/template":       true,
+	"text/template/parse": true,
+	"time":                true,
+	"unicode":             true,
+	"unicode/utf16":       true,
+	"unicode/utf8":        true,
+	"unsafe":              true,
+}
+
+var validTLD = map[string]bool{
+	// curl http://data.iana.org/TLD/tlds-alpha-by-domain.txt | sed  -e '/#/ d' -e 's/.*/"&": true,/' | tr [:upper:] [:lower:]
+	".ac":                     true,
+	".ad":                     true,
+	".ae":                     true,
+	".aero":                   true,
+	".af":                     true,
+	".ag":                     true,
+	".ai":                     true,
+	".al":                     true,
+	".am":                     true,
+	".an":                     true,
+	".ao":                     true,
+	".aq":                     true,
+	".ar":                     true,
+	".arpa":                   true,
+	".as":                     true,
+	".asia":                   true,
+	".at":                     true,
+	".au":                     true,
+	".aw":                     true,
+	".ax":                     true,
+	".az":                     true,
+	".ba":                     true,
+	".bb":                     true,
+	".bd":                     true,
+	".be":                     true,
+	".bf":                     true,
+	".bg":                     true,
+	".bh":                     true,
+	".bi":                     true,
+	".biz":                    true,
+	".bj":                     true,
+	".bm":                     true,
+	".bn":                     true,
+	".bo":                     true,
+	".br":                     true,
+	".bs":                     true,
+	".bt":                     true,
+	".bv":                     true,
+	".bw":                     true,
+	".by":                     true,
+	".bz":                     true,
+	".ca":                     true,
+	".cat":                    true,
+	".cc":                     true,
+	".cd":                     true,
+	".cf":                     true,
+	".cg":                     true,
+	".ch":                     true,
+	".ci":                     true,
+	".ck":                     true,
+	".cl":                     true,
+	".cm":                     true,
+	".cn":                     true,
+	".co":                     true,
+	".com":                    true,
+	".coop":                   true,
+	".cr":                     true,
+	".cu":                     true,
+	".cv":                     true,
+	".cw":                     true,
+	".cx":                     true,
+	".cy":                     true,
+	".cz":                     true,
+	".de":                     true,
+	".dj":                     true,
+	".dk":                     true,
+	".dm":                     true,
+	".do":                     true,
+	".dz":                     true,
+	".ec":                     true,
+	".edu":                    true,
+	".ee":                     true,
+	".eg":                     true,
+	".er":                     true,
+	".es":                     true,
+	".et":                     true,
+	".eu":                     true,
+	".fi":                     true,
+	".fj":                     true,
+	".fk":                     true,
+	".fm":                     true,
+	".fo":                     true,
+	".fr":                     true,
+	".ga":                     true,
+	".gb":                     true,
+	".gd":                     true,
+	".ge":                     true,
+	".gf":                     true,
+	".gg":                     true,
+	".gh":                     true,
+	".gi":                     true,
+	".gl":                     true,
+	".gm":                     true,
+	".gn":                     true,
+	".gov":                    true,
+	".gp":                     true,
+	".gq":                     true,
+	".gr":                     true,
+	".gs":                     true,
+	".gt":                     true,
+	".gu":                     true,
+	".gw":                     true,
+	".gy":                     true,
+	".hk":                     true,
+	".hm":                     true,
+	".hn":                     true,
+	".hr":                     true,
+	".ht":                     true,
+	".hu":                     true,
+	".id":                     true,
+	".ie":                     true,
+	".il":                     true,
+	".im":                     true,
+	".in":                     true,
+	".info":                   true,
+	".int":                    true,
+	".io":                     true,
+	".iq":                     true,
+	".ir":                     true,
+	".is":                     true,
+	".it":                     true,
+	".je":                     true,
+	".jm":                     true,
+	".jo":                     true,
+	".jobs":                   true,
+	".jp":                     true,
+	".ke":                     true,
+	".kg":                     true,
+	".kh":                     true,
+	".ki":                     true,
+	".km":                     true,
+	".kn":                     true,
+	".kp":                     true,
+	".kr":                     true,
+	".kw":                     true,
+	".ky":                     true,
+	".kz":                     true,
+	".la":                     true,
+	".lb":                     true,
+	".lc":                     true,
+	".li":                     true,
+	".lk":                     true,
+	".lr":                     true,
+	".ls":                     true,
+	".lt":                     true,
+	".lu":                     true,
+	".lv":                     true,
+	".ly":                     true,
+	".ma":                     true,
+	".mc":                     true,
+	".md":                     true,
+	".me":                     true,
+	".mg":                     true,
+	".mh":                     true,
+	".mil":                    true,
+	".mk":                     true,
+	".ml":                     true,
+	".mm":                     true,
+	".mn":                     true,
+	".mo":                     true,
+	".mobi":                   true,
+	".mp":                     true,
+	".mq":                     true,
+	".mr":                     true,
+	".ms":                     true,
+	".mt":                     true,
+	".mu":                     true,
+	".museum":                 true,
+	".mv":                     true,
+	".mw":                     true,
+	".mx":                     true,
+	".my":                     true,
+	".mz":                     true,
+	".na":                     true,
+	".name":                   true,
+	".nc":                     true,
+	".ne":                     true,
+	".net":                    true,
+	".nf":                     true,
+	".ng":                     true,
+	".ni":                     true,
+	".nl":                     true,
+	".no":                     true,
+	".np":                     true,
+	".nr":                     true,
+	".nu":                     true,
+	".nz":                     true,
+	".om":                     true,
+	".org":                    true,
+	".pa":                     true,
+	".pe":                     true,
+	".pf":                     true,
+	".pg":                     true,
+	".ph":                     true,
+	".pk":                     true,
+	".pl":                     true,
+	".pm":                     true,
+	".pn":                     true,
+	".post":                   true,
+	".pr":                     true,
+	".pro":                    true,
+	".ps":                     true,
+	".pt":                     true,
+	".pw":                     true,
+	".py":                     true,
+	".qa":                     true,
+	".re":                     true,
+	".ro":                     true,
+	".rs":                     true,
+	".ru":                     true,
+	".rw":                     true,
+	".sa":                     true,
+	".sb":                     true,
+	".sc":                     true,
+	".sd":                     true,
+	".se":                     true,
+	".sg":                     true,
+	".sh":                     true,
+	".si":                     true,
+	".sj":                     true,
+	".sk":                     true,
+	".sl":                     true,
+	".sm":                     true,
+	".sn":                     true,
+	".so":                     true,
+	".sr":                     true,
+	".st":                     true,
+	".su":                     true,
+	".sv":                     true,
+	".sx":                     true,
+	".sy":                     true,
+	".sz":                     true,
+	".tc":                     true,
+	".td":                     true,
+	".tel":                    true,
+	".tf":                     true,
+	".tg":                     true,
+	".th":                     true,
+	".tj":                     true,
+	".tk":                     true,
+	".tl":                     true,
+	".tm":                     true,
+	".tn":                     true,
+	".to":                     true,
+	".tp":                     true,
+	".tr":                     true,
+	".travel":                 true,
+	".tt":                     true,
+	".tv":                     true,
+	".tw":                     true,
+	".tz":                     true,
+	".ua":                     true,
+	".ug":                     true,
+	".uk":                     true,
+	".us":                     true,
+	".uy":                     true,
+	".uz":                     true,
+	".va":                     true,
+	".vc":                     true,
+	".ve":                     true,
+	".vg":                     true,
+	".vi":                     true,
+	".vn":                     true,
+	".vu":                     true,
+	".wf":                     true,
+	".ws":                     true,
+	".xn--0zwm56d":            true,
+	".xn--11b5bs3a9aj6g":      true,
+	".xn--3e0b707e":           true,
+	".xn--45brj9c":            true,
+	".xn--80akhbyknj4f":       true,
+	".xn--80ao21a":            true,
+	".xn--90a3ac":             true,
+	".xn--9t4b11yi5a":         true,
+	".xn--clchc0ea0b2g2a9gcd": true,
+	".xn--deba0ad":            true,
+	".xn--fiqs8s":             true,
+	".xn--fiqz9s":             true,
+	".xn--fpcrj9c3d":          true,
+	".xn--fzc2c9e2c":          true,
+	".xn--g6w251d":            true,
+	".xn--gecrj9c":            true,
+	".xn--h2brj9c":            true,
+	".xn--hgbk6aj7f53bba":     true,
+	".xn--hlcj6aya9esc7a":     true,
+	".xn--j6w193g":            true,
+	".xn--jxalpdlp":           true,
+	".xn--kgbechtv":           true,
+	".xn--kprw13d":            true,
+	".xn--kpry57d":            true,
+	".xn--lgbbat1ad8j":        true,
+	".xn--mgb9awbf":           true,
+	".xn--mgbaam7a8h":         true,
+	".xn--mgbayh7gpa":         true,
+	".xn--mgbbh1a71e":         true,
+	".xn--mgbc0a9azcg":        true,
+	".xn--mgberp4a5d4ar":      true,
+	".xn--mgbx4cd0ab":         true,
+	".xn--o3cw4h":             true,
+	".xn--ogbpf8fl":           true,
+	".xn--p1ai":               true,
+	".xn--pgbs0dh":            true,
+	".xn--s9brj9c":            true,
+	".xn--wgbh1c":             true,
+	".xn--wgbl6a":             true,
+	".xn--xkc2al3hye2a":       true,
+	".xn--xkc2dl3a5ee0h":      true,
+	".xn--yfro4i67o":          true,
+	".xn--ygbi2ammx":          true,
+	".xn--zckzah":             true,
+	".xxx":                    true,
+	".ye":                     true,
+	".yt":                     true,
+	".za":                     true,
+	".zm":                     true,
+	".zw":                     true,
+}
+
+var validHost = regexp.MustCompile(`^[-a-z0-9]+(?:\.[-a-z0-9]+)+$`)
+var validPathElement = regexp.MustCompile(`^[-A-Za-z0-9~+][-A-Za-z0-9_.]*$`)
+
+func isValidPathElement(s string) bool {
+	return validPathElement.MatchString(s) && s != "testdata"
+}
+
+// IsValidRemotePath returns true if importPath is structurally valid for "go get".
+func IsValidRemotePath(importPath string) bool {
+
+	parts := strings.Split(importPath, "/")
+
+	if len(parts) <= 1 {
+		// Import path must contain at least one "/".
+		return false
+	}
+
+	if !validTLD[path.Ext(parts[0])] {
+		return false
+	}
+
+	if !validHost.MatchString(parts[0]) {
+		return false
+	}
+
+	for _, part := range parts[1:] {
+		if !isValidPathElement(part) {
+			return false
+		}
+	}
+
+	return true
+}
+
+var goRepoPath = map[string]bool{}
+
+func init() {
+	for p := range standardPath {
+		for {
+			goRepoPath[p] = true
+			i := strings.LastIndex(p, "/")
+			if i < 0 {
+				break
+			}
+			p = p[:i]
+		}
+	}
+}
+
+func IsGoRepoPath(importPath string) bool {
+	return goRepoPath[importPath]
+}
+
+func IsValidPath(importPath string) bool {
+	return importPath == "C" || standardPath[importPath] || IsValidRemotePath(importPath)
+}
diff --git a/gosrc/path_test.go b/gosrc/path_test.go
new file mode 100644
index 0000000..024c186
--- /dev/null
+++ b/gosrc/path_test.go
@@ -0,0 +1,46 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"testing"
+)
+
+var goodImportPaths = []string{
+	"github.com/user/repo",
+	"github.com/user/repo/src/pkg/compress/somethingelse",
+	"github.com/user/repo/src/compress/gzip",
+	"github.com/user/repo/src/pkg",
+	"camlistore.org/r/p/camlistore",
+	"example.com/foo.git",
+	"launchpad.net/~user/foo/trunk",
+	"launchpad.net/~user/+junk/version",
+}
+
+var badImportPaths = []string{
+	"foobar",
+	"foo.",
+	".bar",
+	"favicon.ico",
+	"exmpple.com",
+	"github.com/user/repo/testdata/x",
+	"github.com/user/repo/_ignore/x",
+	"github.com/user/repo/.ignore/x",
+}
+
+func TestIsValidRemotePath(t *testing.T) {
+	for _, importPath := range goodImportPaths {
+		if !IsValidRemotePath(importPath) {
+			t.Errorf("isBadImportPath(%q) -> true, want false", importPath)
+		}
+	}
+	for _, importPath := range badImportPaths {
+		if IsValidRemotePath(importPath) {
+			t.Errorf("isBadImportPath(%q) -> false, want true", importPath)
+		}
+	}
+}
diff --git a/gosrc/present.go b/gosrc/present.go
new file mode 100644
index 0000000..455804b
--- /dev/null
+++ b/gosrc/present.go
@@ -0,0 +1,73 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"regexp"
+	"time"
+)
+
+type Presentation struct {
+	Filename string
+	Files    map[string][]byte
+	Updated  time.Time
+}
+
+type presBuilder struct {
+	filename   string
+	data       []byte
+	resolveURL func(fname string) string
+	fetch      func(fnames []string) ([]*File, error)
+}
+
+var assetPat = regexp.MustCompile(`(?m)^\.(play|code|image|iframe|html)\s+(?:-\S+\s+)*(\S+)`)
+
+func (b *presBuilder) build() (*Presentation, error) {
+	var data []byte
+	var fnames []string
+	i := 0
+	for _, m := range assetPat.FindAllSubmatchIndex(b.data, -1) {
+		name := string(b.data[m[4]:m[5]])
+		switch string(b.data[m[2]:m[3]]) {
+		case "iframe", "image":
+			data = append(data, b.data[i:m[4]]...)
+			data = append(data, b.resolveURL(name)...)
+		case "html":
+			// TODO: sanitize and fix relative URLs in HTML.
+			data = append(data, "\nERROR: .html not supported\n"...)
+		case "play", "code":
+			data = append(data, b.data[i:m[5]]...)
+			found := false
+			for _, n := range fnames {
+				if n == name {
+					found = true
+					break
+				}
+			}
+			if !found {
+				fnames = append(fnames, name)
+			}
+		default:
+			data = append(data, "\nERROR: unknown command\n"...)
+		}
+		i = m[5]
+	}
+	data = append(data, b.data[i:]...)
+	files, err := b.fetch(fnames)
+	if err != nil {
+		return nil, err
+	}
+	pres := &Presentation{
+		Updated:  time.Now().UTC(),
+		Filename: b.filename,
+		Files:    map[string][]byte{b.filename: data},
+	}
+	for _, f := range files {
+		pres.Files[f.Name] = f.Data
+	}
+	return pres, nil
+}
diff --git a/gosrc/print.go b/gosrc/print.go
new file mode 100644
index 0000000..f78bb9c
--- /dev/null
+++ b/gosrc/print.go
@@ -0,0 +1,79 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// +build ignore
+
+// Command print fetches and prints package.
+//
+// Usage: go run print.go importPath
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net/http"
+	"strings"
+
+	"github.com/garyburd/gosrc"
+)
+
+var (
+	etag    = flag.String("etag", "", "Etag")
+	local   = flag.String("local", "", "Get package from local workspace.")
+	present = flag.Bool("present", false, "Get presentation.")
+)
+
+func main() {
+	flag.Parse()
+	gosrc.SetUserAgent("print")
+	if len(flag.Args()) != 1 {
+		log.Fatal("Usage: go run print.go importPath")
+	}
+	if *present {
+		printPresentation(flag.Args()[0])
+	} else {
+		printDir(flag.Args()[0])
+	}
+}
+
+func printDir(path string) {
+	if *local != "" {
+		gosrc.SetLocalDevMode(*local)
+	}
+	dir, err := gosrc.Get(http.DefaultClient, path, *etag)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Println("ImportPath    ", dir.ImportPath)
+	fmt.Println("ResovledPath  ", dir.ResolvedPath)
+	fmt.Println("ProjectRoot   ", dir.ProjectRoot)
+	fmt.Println("ProjectName   ", dir.ProjectName)
+	fmt.Println("ProjectURL    ", dir.ProjectURL)
+	fmt.Println("VCS           ", dir.VCS)
+	fmt.Println("Etag          ", dir.Etag)
+	fmt.Println("BrowseURL     ", dir.BrowseURL)
+	fmt.Println("Subdirectories", strings.Join(dir.Subdirectories, ", "))
+	fmt.Println("LineFmt       ", dir.LineFmt)
+	fmt.Println("Files:")
+	for _, file := range dir.Files {
+		fmt.Printf("%30s %5d %s\n", file.Name, len(file.Data), file.BrowseURL)
+	}
+}
+
+func printPresentation(path string) {
+	pres, err := gosrc.GetPresentation(http.DefaultClient, path)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("%s\n", pres.Files[pres.Filename])
+	for name, data := range pres.Files {
+		if name != pres.Filename {
+			fmt.Printf("---------- %s ----------\n%s\n", name, data)
+		}
+	}
+}
diff --git a/gosrc/util.go b/gosrc/util.go
new file mode 100644
index 0000000..f5670dd
--- /dev/null
+++ b/gosrc/util.go
@@ -0,0 +1,70 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+var defaultTags = map[string]string{"git": "master", "hg": "default"}
+
+func bestTag(tags map[string]string, defaultTag string) (string, string, error) {
+	if commit, ok := tags["go1"]; ok {
+		return "go1", commit, nil
+	}
+	if commit, ok := tags[defaultTag]; ok {
+		return defaultTag, commit, nil
+	}
+	return "", "", NotFoundError{"Tag or branch not found."}
+}
+
+// expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match.
+func expand(template string, match map[string]string, subs ...string) string {
+	var p []byte
+	var i int
+	for {
+		i = strings.Index(template, "{")
+		if i < 0 {
+			break
+		}
+		p = append(p, template[:i]...)
+		template = template[i+1:]
+		i = strings.Index(template, "}")
+		if s, ok := match[template[:i]]; ok {
+			p = append(p, s...)
+		} else {
+			j, _ := strconv.Atoi(template[:i])
+			p = append(p, subs[j]...)
+		}
+		template = template[i+1:]
+	}
+	p = append(p, template...)
+	return string(p)
+}
+
+var readmePat = regexp.MustCompile(`(?i)^readme(?:$|\.)`)
+
+// isDocFile returns true if a file with name n should be included in the
+// documentation.
+func isDocFile(n string) bool {
+	if strings.HasSuffix(n, ".go") && n[0] != '_' && n[0] != '.' {
+		return true
+	}
+	return readmePat.MatchString(n)
+}
+
+var linePat = regexp.MustCompile(`(?m)^//line .*$`)
+
+func OverwriteLineComments(p []byte) {
+	for _, m := range linePat.FindAllIndex(p, -1) {
+		for i := m[0] + 2; i < m[1]; i++ {
+			p[i] = ' '
+		}
+	}
+}
diff --git a/gosrc/util_test.go b/gosrc/util_test.go
new file mode 100644
index 0000000..2561e3a
--- /dev/null
+++ b/gosrc/util_test.go
@@ -0,0 +1,31 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package gosrc
+
+import (
+	"testing"
+)
+
+var lineCommentTests = []struct {
+	in, out string
+}{
+	{"", ""},
+	{"//line  1", "//       "},
+	{"//line x\n//line y", "//      \n//      "},
+	{"x\n//line ", "x\n//     "},
+}
+
+func TestOverwriteLineComments(t *testing.T) {
+	for _, tt := range lineCommentTests {
+		p := []byte(tt.in)
+		OverwriteLineComments(p)
+		s := string(p)
+		if s != tt.out {
+			t.Errorf("in=%q, actual=%q, expect=%q", tt.in, s, tt.out)
+		}
+	}
+}
diff --git a/gosrc/vcs.go b/gosrc/vcs.go
new file mode 100644
index 0000000..6d2d78a
--- /dev/null
+++ b/gosrc/vcs.go
@@ -0,0 +1,255 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// +build !appengine
+
+package gosrc
+
+import (
+	"bytes"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+func init() {
+	addService(&service{
+		pattern: regexp.MustCompile(`^(?P<repo>(?:[a-z0-9.\-]+\.)+[a-z0-9.\-]+(?::[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?P<vcs>bzr|git|hg|svn)(?P<dir>/[A-Za-z0-9_.\-/]*)?$`),
+		prefix:  "",
+		get:     getVCSDir,
+	})
+	getVCSDirFn = getVCSDir
+}
+
+// Store temporary data in this directory.
+var TempDir = filepath.Join(os.TempDir(), "gddo")
+
+type urlTemplates struct {
+	re         *regexp.Regexp
+	fileBrowse string
+	project    string
+	line       string
+}
+
+var vcsServices = []*urlTemplates{
+	{
+		regexp.MustCompile(`^git\.gitorious\.org/(?P<repo>[^/]+/[^/]+)$`),
+		"https://gitorious.org/{repo}/blobs/{tag}/{dir}{0}",
+		"https://gitorious.org/{repo}",
+		"%s#line%d",
+	},
+	{
+		regexp.MustCompile(`^git\.oschina\.net/(?P<repo>[^/]+/[^/]+)$`),
+		"http://git.oschina.net/{repo}/blob/{tag}/{dir}{0}",
+		"http://git.oschina.net/{repo}",
+		"%s#L%d",
+	},
+	{
+		regexp.MustCompile(`^(?P<r1>[^.]+)\.googlesource.com/(?P<r2>[^./]+)$`),
+		"https://{r1}.googlesource.com/{r2}/+/{tag}/{dir}{0}",
+		"https://{r1}.googlesource.com/{r2}/+/{tag}",
+		"",
+	},
+	{
+		regexp.MustCompile(`^gitcafe.com/(?P<repo>[^/]+/.[^/]+)$`),
+		"https://gitcafe.com/{repo}/tree/{tag}/{dir}{0}",
+		"https://gitcafe.com/{repo}",
+		"",
+	},
+}
+
+// lookupURLTemplate finds an expand() template, match map and line number
+// format for well known repositories.
+func lookupURLTemplate(repo, dir, tag string) (*urlTemplates, map[string]string) {
+	if strings.HasPrefix(dir, "/") {
+		dir = dir[1:] + "/"
+	}
+	for _, t := range vcsServices {
+		if m := t.re.FindStringSubmatch(repo); m != nil {
+			match := map[string]string{
+				"dir": dir,
+				"tag": tag,
+			}
+			for i, name := range t.re.SubexpNames() {
+				if name != "" {
+					match[name] = m[i]
+				}
+			}
+			return t, match
+		}
+	}
+	return &urlTemplates{}, nil
+}
+
+type vcsCmd struct {
+	schemes  []string
+	download func([]string, string, string) (string, string, error)
+}
+
+var vcsCmds = map[string]*vcsCmd{
+	"git": {
+		schemes:  []string{"http", "https", "git"},
+		download: downloadGit,
+	},
+}
+
+var lsremoteRe = regexp.MustCompile(`(?m)^([0-9a-f]{40})\s+refs/(?:tags|heads)/(.+)$`)
+
+func downloadGit(schemes []string, repo, savedEtag string) (string, string, error) {
+	var p []byte
+	var scheme string
+	for i := range schemes {
+		cmd := exec.Command("git", "ls-remote", "--heads", "--tags", schemes[i]+"://"+repo+".git")
+		log.Println(strings.Join(cmd.Args, " "))
+		var err error
+		p, err = cmd.Output()
+		if err == nil {
+			scheme = schemes[i]
+			break
+		}
+	}
+
+	if scheme == "" {
+		return "", "", NotFoundError{"VCS not found"}
+	}
+
+	tags := make(map[string]string)
+	for _, m := range lsremoteRe.FindAllSubmatch(p, -1) {
+		tags[string(m[2])] = string(m[1])
+	}
+
+	tag, commit, err := bestTag(tags, "master")
+	if err != nil {
+		return "", "", err
+	}
+
+	etag := scheme + "-" + commit
+
+	if etag == savedEtag {
+		return "", "", ErrNotModified
+	}
+
+	dir := path.Join(TempDir, repo+".git")
+	p, err = ioutil.ReadFile(path.Join(dir, ".git/HEAD"))
+	switch {
+	case err != nil:
+		if err := os.MkdirAll(dir, 0777); err != nil {
+			return "", "", err
+		}
+		cmd := exec.Command("git", "clone", scheme+"://"+repo+".git", dir)
+		log.Println(strings.Join(cmd.Args, " "))
+		if err := cmd.Run(); err != nil {
+			return "", "", err
+		}
+	case string(bytes.TrimRight(p, "\n")) == commit:
+		return tag, etag, nil
+	default:
+		cmd := exec.Command("git", "fetch")
+		log.Println(strings.Join(cmd.Args, " "))
+		cmd.Dir = dir
+		if err := cmd.Run(); err != nil {
+			return "", "", err
+		}
+	}
+
+	cmd := exec.Command("git", "checkout", "--detach", "--force", commit)
+	cmd.Dir = dir
+	if err := cmd.Run(); err != nil {
+		return "", "", err
+	}
+
+	return tag, etag, nil
+}
+
+func getVCSDir(client *http.Client, match map[string]string, etagSaved string) (*Directory, error) {
+	cmd := vcsCmds[match["vcs"]]
+	if cmd == nil {
+		return nil, NotFoundError{expand("VCS not supported: {vcs}", match)}
+	}
+
+	scheme := match["scheme"]
+	if scheme == "" {
+		i := strings.Index(etagSaved, "-")
+		if i > 0 {
+			scheme = etagSaved[:i]
+		}
+	}
+
+	schemes := cmd.schemes
+	if scheme != "" {
+		for i := range cmd.schemes {
+			if cmd.schemes[i] == scheme {
+				schemes = cmd.schemes[i : i+1]
+				break
+			}
+		}
+	}
+
+	// Download and checkout.
+
+	tag, etag, err := cmd.download(schemes, match["repo"], etagSaved)
+	if err != nil {
+		return nil, err
+	}
+
+	// Find source location.
+
+	template, urlMatch := lookupURLTemplate(match["repo"], match["dir"], tag)
+
+	// Slurp source files.
+
+	d := path.Join(TempDir, expand("{repo}.{vcs}", match), match["dir"])
+	f, err := os.Open(d)
+	if err != nil {
+		if os.IsNotExist(err) {
+			err = NotFoundError{err.Error()}
+		}
+		return nil, err
+	}
+	fis, err := f.Readdir(-1)
+	if err != nil {
+		return nil, err
+	}
+
+	var files []*File
+	var subdirs []string
+	for _, fi := range fis {
+		switch {
+		case fi.IsDir():
+			if isValidPathElement(fi.Name()) {
+				subdirs = append(subdirs, fi.Name())
+			}
+		case isDocFile(fi.Name()):
+			b, err := ioutil.ReadFile(path.Join(d, fi.Name()))
+			if err != nil {
+				return nil, err
+			}
+			files = append(files, &File{
+				Name:      fi.Name(),
+				BrowseURL: expand(template.fileBrowse, urlMatch, fi.Name()),
+				Data:      b,
+			})
+		}
+	}
+
+	return &Directory{
+		LineFmt:        template.line,
+		ProjectRoot:    expand("{repo}.{vcs}", match),
+		ProjectName:    path.Base(match["repo"]),
+		ProjectURL:     expand(template.project, urlMatch),
+		BrowseURL:      "",
+		Etag:           etag,
+		VCS:            match["vcs"],
+		Subdirectories: subdirs,
+		Files:          files,
+	}, nil
+}