| // 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] |
| } |