From 55402a2b46b9839ad57afb2e4257ab5d97eb6ffe Mon Sep 17 00:00:00 2001 From: Andrew Gerrand Date: Mon, 15 Dec 2014 16:55:31 +1100 Subject: [PATCH] godoc/redirect: support loading hg->git change map from a file This isn't exposed through the godoc binary, as it will only be used by the Google-specific deployment of godoc. Change-Id: Id5808f3adcb7eb36a7ccd6e4960ce3f01179fe51 Reviewed-on: https://go-review.googlesource.com/1567 Reviewed-by: Brad Fitzpatrick --- godoc/redirect/hash.go | 138 +++++++++++++++++++++++++++++++++++++ godoc/redirect/redirect.go | 44 ++++++++++-- 2 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 godoc/redirect/hash.go diff --git a/godoc/redirect/hash.go b/godoc/redirect/hash.go new file mode 100644 index 0000000000..d5a1e3eb67 --- /dev/null +++ b/godoc/redirect/hash.go @@ -0,0 +1,138 @@ +// Copyright 2014 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. + +// This file provides a compact encoding of +// a map of Mercurial hashes to Git hashes. + +package redirect + +import ( + "encoding/binary" + "fmt" + "io" + "os" + "sort" + "strconv" + "strings" +) + +// hashMap is a map of Mercurial hashes to Git hashes. +type hashMap struct { + file *os.File + entries int +} + +// newHashMap takes a file handle that contains a map of Mercurial to Git +// hashes. The file should be a sequence of pairs of little-endian encoded +// uint32s, representing a hgHash and a gitHash respectively. +// The sequence must be sorted by hgHash. +// The file must remain open for as long as the returned hashMap is used. +func newHashMap(f *os.File) (*hashMap, error) { + fi, err := f.Stat() + if err != nil { + return nil, err + } + return &hashMap{file: f, entries: int(fi.Size() / 8)}, nil +} + +// Lookup finds an hgHash in the map that matches the given prefix, and returns +// its corresponding gitHash. The prefix must be at least 8 characters long. +func (m *hashMap) Lookup(s string) gitHash { + if m == nil { + return 0 + } + hg, err := hgHashFromString(s) + if err != nil { + return 0 + } + var git gitHash + b := make([]byte, 8) + sort.Search(m.entries, func(i int) bool { + n, err := m.file.ReadAt(b, int64(i*8)) + if err != nil { + panic(err) + } + if n != 8 { + panic(io.ErrUnexpectedEOF) + } + v := hgHash(binary.LittleEndian.Uint32(b[:4])) + if v == hg { + git = gitHash(binary.LittleEndian.Uint32(b[4:])) + } + return v >= hg + }) + return git +} + +// hgHash represents the lower (leftmost) 32 bits of a Mercurial hash. +type hgHash uint32 + +func (h hgHash) String() string { + return intToHash(int64(h)) +} + +func hgHashFromString(s string) (hgHash, error) { + if len(s) < 8 { + return 0, fmt.Errorf("string too small: len(s) = %d", len(s)) + } + hash := s[:8] + i, err := strconv.ParseInt(hash, 16, 64) + if err != nil { + return 0, err + } + return hgHash(i), nil +} + +// gitHash represents the leftmost 28 bits of a Git hash in its upper 28 bits, +// and it encodes hash's repository in the lower 4 bits. +type gitHash uint32 + +func (h gitHash) Hash() string { + return intToHash(int64(h))[:7] +} + +func (h gitHash) Repo() string { + return repo(h & 0xF).String() +} + +func intToHash(i int64) string { + s := strconv.FormatInt(i, 16) + if len(s) < 8 { + s = strings.Repeat("0", 8-len(s)) + s + } + return s +} + +// repo represents a Go Git repository. +type repo byte + +const ( + repoGo repo = iota + repoBlog + repoCrypto + repoExp + repoImage + repoMobile + repoNet + repoSys + repoTalks + repoText + repoTools +) + +func (r repo) String() string { + return map[repo]string{ + repoGo: "go", + repoBlog: "blog", + repoCrypto: "crypto", + repoExp: "exp", + repoImage: "image", + repoMobile: "mobile", + repoNet: "net", + repoSys: "sys", + repoTalks: "talks", + repoText: "text", + repoTools: "tools", + }[r] +} diff --git a/godoc/redirect/redirect.go b/godoc/redirect/redirect.go index 7ce1698a0c..b7608c881c 100644 --- a/godoc/redirect/redirect.go +++ b/godoc/redirect/redirect.go @@ -8,7 +8,9 @@ package redirect // import "golang.org/x/tools/godoc/redirect" import ( + "fmt" "net/http" + "os" "regexp" "strconv" "strings" @@ -33,6 +35,7 @@ func Register(mux *http.ServeMux) { // NB: /src/pkg (sans trailing slash) is the index of packages. mux.HandleFunc("/src/pkg/", srcPkgHandler) mux.HandleFunc("/cl/", clHandler) + mux.HandleFunc("/change/", changeHandler) } func handlePathRedirects(mux *http.ServeMux, redirects map[string]string, prefix string) { @@ -128,11 +131,6 @@ var redirects = map[string]string{ } var prefixHelpers = map[string]string{ - // TODO(adg): add redirects from known hg hashes to the git equivalents - // and switch this to point to "https://go.googlesource.com/go/+/". - // (We can only change this once we know all the new git hashes.) - "change": "https://code.google.com/p/go/source/detail?r=", - "issue": "https://github.com/golang/go/issues/", "play": "http://play.golang.org/", "talks": "http://talks.golang.org/", @@ -193,3 +191,39 @@ func clHandler(w http.ResponseWriter, r *http.Request) { } http.Redirect(w, r, target, http.StatusFound) } + +var changeMap *hashMap + +// LoadChangeMap loads the specified map of Mercurial to Git revisions, +// which is used by the /change/ handler to intelligently map old hg +// revisions to their new git equivalents. +// It should be called before calling Register. +// The file should remain open as long as the process is running. +// See the implementation of this package for details. +func LoadChangeMap(filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + m, err := newHashMap(f) + if err != nil { + return err + } + changeMap = m + return nil +} + +func changeHandler(w http.ResponseWriter, r *http.Request) { + const prefix = "/change/" + if p := r.URL.Path; p == prefix { + // redirect /prefix/ to /prefix + http.Redirect(w, r, p[:len(p)-1], http.StatusFound) + return + } + hash := r.URL.Path[len(prefix):] + target := "https://go.googlesource.com/go/+/" + hash + if git := changeMap.Lookup(hash); git > 0 { + target = fmt.Sprintf("https://go.googlesource.com/%v/+/%v", git.Repo(), git.Hash()) + } + http.Redirect(w, r, target, http.StatusFound) +}