├── BUGS ├── AUTHORS ├── cmd └── hgo │ ├── pprof.go │ ├── cmdbranches.go │ ├── datacache.go │ ├── cmdtags.go │ ├── cmdmanifest.go │ ├── cmdrevlog.go │ ├── hgo.go │ ├── cmdcat.go │ ├── cmdlog.go │ ├── main.go │ └── cmdarchive.go ├── README.md ├── revlog ├── README ├── node.go ├── lookup.go ├── filebuilder.go ├── patch │ └── patch.go └── revlog.go ├── requires.go ├── LICENSE ├── helpers_test.go ├── tags.go ├── branches.go ├── changelog └── changelog.go ├── hg.go ├── store ├── casefolding.go └── store.go └── hgo_test.go /BUGS: -------------------------------------------------------------------------------- 1 | * Packages, esp. revlog, need documentation. 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michael Teichgräber 2 | -------------------------------------------------------------------------------- /cmd/hgo/pprof.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "runtime/pprof" 8 | ) 9 | 10 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 11 | var memprofile = flag.String("memprofile", "", "write memory profile to file") 12 | 13 | func startPProf() { 14 | if *cpuprofile != "" { 15 | f, err := os.Create(*cpuprofile) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | pprof.StartCPUProfile(f) 20 | } 21 | } 22 | 23 | func stopPProf() { 24 | if *cpuprofile != "" { 25 | pprof.StopCPUProfile() 26 | } 27 | if *memprofile != "" { 28 | f, err := os.Create(*memprofile) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | pprof.Lookup("heap").WriteTo(f, 0) 33 | f.Close() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hgo is a collection of Go packages providing read-access to 2 | local Mercurial repositories. Only a subset of Mercurial's 3 | functionality is supported. It is possible to access revisions 4 | of files and to read changelogs, manifests, and tags. 5 | 6 | Hgo supports the following repository features: 7 | 8 | * revlogv1 9 | * store 10 | * fncache (no support for hash encoded names, though) 11 | * dotencode 12 | 13 | The Go packages have been implemented from scratch, based 14 | on information found in Mercurial's wiki. 15 | 16 | The project should be considered unstable. The BUGS file lists 17 | known issues yet to be addressed. 18 | 19 | cmd/hgo contains an example program that implements a few 20 | commands similar to a subset of Mercurial's hg. 21 | -------------------------------------------------------------------------------- /cmd/hgo/cmdbranches.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | var cmdBranches = &Command{ 9 | UsageLine: "branches [-v]", 10 | Short: "list repository branches", 11 | Long: ``, 12 | } 13 | 14 | func init() { 15 | addStdFlags(cmdBranches) 16 | cmdBranches.Run = runBranches 17 | } 18 | 19 | func runBranches(cmd *Command, w io.Writer, args []string) { 20 | openRepository(args) 21 | 22 | st := repo.NewStore() 23 | clIndex, err := st.OpenChangeLog() 24 | if err != nil { 25 | fatalf("%s", err) 26 | } 27 | 28 | for r := clIndex.Tip(); r.FileRev() != -1; r = r.Prev() { 29 | id := r.Id().Node() 30 | s := branchHeads.ById[id] 31 | for i := range s { 32 | t := s[len(s)-i-1] 33 | fmt.Fprintf(w, "%-26s %5d:%s\n", t, r.FileRev(), id[:12]) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cmd/hgo/datacache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type dataCache []dataEnt 4 | 5 | type dataEnt struct { 6 | rev int 7 | data []byte 8 | } 9 | 10 | var cacheStats int 11 | 12 | func (c dataCache) Get(rev int) []byte { 13 | for i := range c { 14 | if c[i].rev == rev { 15 | return c[i].data 16 | } 17 | } 18 | return nil 19 | } 20 | 21 | func (pc *dataCache) Store(rev int, data []byte) { 22 | var p, maxp *dataEnt 23 | var maxrev int 24 | 25 | c := *pc 26 | n := len(c) 27 | 28 | for i := range c { 29 | p = &c[i] 30 | if p.rev == rev { 31 | return 32 | } 33 | if p.rev > maxrev { 34 | maxrev = p.rev 35 | maxp = p 36 | } 37 | } 38 | 39 | e := dataEnt{rev, data} 40 | 41 | if n < cap(c) { 42 | c = c[:n+1] 43 | c[n] = e 44 | *pc = c 45 | } else { 46 | *maxp = e 47 | } 48 | 49 | } 50 | 51 | var dc = make(dataCache, 0, 4) 52 | -------------------------------------------------------------------------------- /revlog/README: -------------------------------------------------------------------------------- 1 | Revlog NG == version 1 (since Mercurial 0.9) 2 | 3 | revision index: file extension .i 4 | 5 | Two storage types: 6 | 7 | - each revision record is followed by patch data 8 | (next revision record starts after that data) 9 | 10 | This is for rather small files. 11 | 12 | - patch data corresponding to revisions are stored externally, 13 | in a file with same base name (stem), but extension `.d' 14 | 15 | Index record format is described at http://mercurial.selenic.com/wiki/RevlogNG 16 | 17 | A data chunk starts either with 18 | 19 | u uncompressed, skip 1 byte (the `u') 20 | 21 | 0 uncompressed, dont skip 22 | 23 | or 24 | x zlib compressed 25 | 26 | A data chunk consists of a collection of hunks, each starting 27 | with three 4-byte values: start, end, length, followed by 28 | the data. 29 | 30 | 31 | /home/micha/ib/wmipf.de/home/ib/mercurial/mercurial/revlog.py -------------------------------------------------------------------------------- /cmd/hgo/cmdtags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | var cmdTags = &Command{ 9 | UsageLine: "tags [-v]", 10 | Short: "list repository tags", 11 | Long: ``, 12 | } 13 | 14 | func init() { 15 | addStdFlags(cmdTags) 16 | cmdTags.Run = runTags 17 | } 18 | 19 | func runTags(cmd *Command, w io.Writer, args []string) { 20 | openRepository(args) 21 | 22 | st := repo.NewStore() 23 | clIndex, err := st.OpenChangeLog() 24 | if err != nil { 25 | fatalf("%s", err) 26 | } 27 | 28 | if globalTags != allTags { 29 | globalTags.Add("tip", clIndex.Tip().Id().Node()) 30 | } 31 | allTags.Add("tip", clIndex.Tip().Id().Node()) 32 | 33 | for r := clIndex.Tip(); r.FileRev() != -1; r = r.Prev() { 34 | id := r.Id().Node() 35 | s := allTags.ById[id] 36 | for i := range s { 37 | t := s[len(s)-i-1] 38 | local := "" 39 | if verbose && globalTags.IdByName[t] == "" { 40 | local = " local" 41 | } 42 | fmt.Fprintf(w, "%-30s %5d:%s%s\n", t, r.FileRev(), id[:12], local) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /requires.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package hgo 6 | 7 | import ( 8 | "bufio" 9 | "fmt" 10 | "io" 11 | "strings" 12 | ) 13 | 14 | var needFlags = map[string]bool{ 15 | "revlogv1": true, 16 | "store": true, 17 | } 18 | var knownFlags = map[string]bool{ 19 | "revlogv1": true, 20 | "store": true, 21 | "fncache": true, 22 | "dotencode": true, 23 | } 24 | 25 | func parseRequires(r io.Reader) (m map[string]bool, err error) { 26 | m = make(map[string]bool, 8) 27 | f := bufio.NewReader(r) 28 | for { 29 | s, err1 := f.ReadString('\n') 30 | if err1 != nil { 31 | break 32 | } 33 | s = strings.TrimSpace(s) 34 | if !knownFlags[s] { 35 | err = fmt.Errorf(".hg/requires: unknown requirement: %s", s) 36 | return 37 | } 38 | m[s] = true 39 | } 40 | for k := range needFlags { 41 | if !m[k] { 42 | err = fmt.Errorf(".hg/requires: requirement `%s' not satisfied", k) 43 | return 44 | } 45 | } 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /revlog/node.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package revlog 6 | 7 | import ( 8 | "bytes" 9 | "crypto/sha1" 10 | "encoding/hex" 11 | "hash" 12 | ) 13 | 14 | // http://mercurial.selenic.com/wiki/Nodeid 15 | // http://mercurial.selenic.com/wiki/ChangeSetID 16 | 17 | type NodeId []byte 18 | 19 | func (i NodeId) String() string { 20 | return hex.EncodeToString(i[:6]) 21 | } 22 | func (i NodeId) Node() string { 23 | return hex.EncodeToString(i) 24 | } 25 | 26 | func (i NodeId) Eq(i2 NodeId) bool { 27 | return bytes.Equal(i, i2) 28 | } 29 | 30 | func NewId(hash string) (id NodeId, err error) { 31 | buf, err := hex.DecodeString(hash) 32 | if err == nil { 33 | id = NodeId(buf) 34 | } 35 | return 36 | } 37 | 38 | type NodeIdImpl interface { 39 | NewHash() hash.Hash 40 | NewNodeId([]byte) NodeId 41 | } 42 | 43 | type v1nodeid byte 44 | 45 | func (id v1nodeid) NewNodeId(b []byte) NodeId { 46 | if len(b) > 20 { 47 | b = b[:20] 48 | } 49 | return b 50 | } 51 | 52 | func (id v1nodeid) NewHash() hash.Hash { 53 | return sha1.New() 54 | } 55 | 56 | func sortedPair(i1, i2 NodeId) []NodeId { 57 | switch { 58 | case i1 == nil && i2 == nil: 59 | case i1 == nil: 60 | i1 = make(NodeId, len(i2)) 61 | case i2 == nil: 62 | i2 = make(NodeId, len(i1)) 63 | } 64 | if bytes.Compare(i1, i2) > 0 { 65 | return []NodeId{i2, i1} 66 | } 67 | return []NodeId{i1, i2} 68 | } 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The hgo Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * The names of the authors may not be used to endorse or promote 14 | products derived from this software without specific prior written 15 | permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /cmd/hgo/cmdmanifest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | 11 | "github.com/beyang/hgo/changelog" 12 | "github.com/beyang/hgo/revlog" 13 | "github.com/beyang/hgo/store" 14 | ) 15 | 16 | var cmdManifest = &Command{ 17 | UsageLine: "manifest [-R dir] [-r rev]", 18 | Short: "show the project's manifest", 19 | Long: ``, 20 | } 21 | 22 | func init() { 23 | addStdFlags(cmdManifest) 24 | addRevFlag(cmdManifest) 25 | cmdManifest.Run = runManifest 26 | } 27 | 28 | func runManifest(cmd *Command, w io.Writer, args []string) { 29 | openRepository(args) 30 | rs := getRevisionSpec() 31 | b := revlog.NewFileBuilder() 32 | c, err := getChangeset(rs, b) 33 | if err != nil { 34 | fatalf("%s", err) 35 | } 36 | mm, err := getManifest(int(c.Linkrev), c.ManifestNode, b) 37 | if err != nil { 38 | fatalf("%s", err) 39 | } 40 | for i := range mm { 41 | fmt.Fprintln(w, mm[i].FileName) 42 | } 43 | } 44 | 45 | func getChangeset(rs revlog.RevisionSpec, b *revlog.FileBuilder) (c *changelog.Entry, err error) { 46 | st := repo.NewStore() 47 | clIndex, err := st.OpenChangeLog() 48 | if err != nil { 49 | return 50 | } 51 | r, err := rs.Lookup(clIndex) 52 | if err != nil { 53 | return 54 | } 55 | c, err = changelog.BuildEntry(r, b) 56 | if err == nil { 57 | c.Rec = r 58 | } 59 | return 60 | } 61 | 62 | func getManifest(linkrev int, id revlog.NodeId, b *revlog.FileBuilder) (m store.Manifest, err error) { 63 | st := repo.NewStore() 64 | mlog, err := st.OpenManifests() 65 | if err != nil { 66 | return 67 | } 68 | 69 | r, err := mlog.LookupRevision(linkrev, id) 70 | if err != nil { 71 | return 72 | } 73 | 74 | m, err = store.BuildManifest(r, b) 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /helpers_test.go: -------------------------------------------------------------------------------- 1 | package hgo_test 2 | 3 | import ( 4 | "flag" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | var ( 13 | keepTmpDirs = flag.Bool("test.keeptmp", false, 14 | "don't remove temporary dirs after use") 15 | 16 | // tmpDirs is used by makeTmpDir and removeTmpDirs to record and clean up 17 | // temporary directories used during testing. 18 | tmpDirs []string 19 | ) 20 | 21 | // Convert time to OS X compatible `touch -t` time 22 | func appleTime(t string) string { 23 | ti, _ := time.Parse(time.RFC3339, t) 24 | return ti.Local().Format("200601021504.05") 25 | } 26 | 27 | func createRepo(t testing.TB, commands []string) string { 28 | dir := makeTmpDir(t) 29 | 30 | for _, command := range commands { 31 | cmd := exec.Command("bash", "-c", command) 32 | cmd.Dir = dir 33 | out, err := cmd.CombinedOutput() 34 | if err != nil { 35 | t.Fatalf("Command %q failed. Output was:\n\n%s", command, out) 36 | } 37 | } 38 | 39 | return dir 40 | } 41 | 42 | // removeTmpDirs removes all temporary directories created by makeTmpDir 43 | // (unless the -test.keeptmp flag is true, in which case they are retained). 44 | func removeTmpDirs(t testing.TB) { 45 | if *keepTmpDirs { 46 | return 47 | } 48 | for _, dir := range tmpDirs { 49 | err := os.RemoveAll(dir) 50 | if err != nil { 51 | t.Fatalf("tearDown: RemoveAll(%q) failed: %s", dir, err) 52 | } 53 | } 54 | tmpDirs = nil 55 | } 56 | 57 | // makeTmpDir creates a temporary directory and returns its path. The 58 | // directory is added to the list of directories to be removed when the 59 | // currently running test ends (assuming the test calls removeTmpDirs() after 60 | // execution). 61 | func makeTmpDir(t testing.TB) string { 62 | dir, err := ioutil.TempDir("", "hgo-") 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | if *keepTmpDirs { 68 | t.Logf("Using temp dir %s.", dir) 69 | } 70 | 71 | tmpDirs = append(tmpDirs, dir) 72 | return dir 73 | } 74 | -------------------------------------------------------------------------------- /cmd/hgo/cmdrevlog.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | 8 | "github.com/beyang/hgo/revlog" 9 | "github.com/beyang/hgo/revlog/patch" 10 | ) 11 | 12 | var cmdRevlog = &Command{ 13 | UsageLine: "revlog [-r filerev] [-build] file.i", 14 | Short: "dump a revlog index, or the contents of a revision specified by -r", 15 | Long: ``, 16 | } 17 | 18 | var revlogR = cmdRevlog.Flag.Int("r", -1, "file revision") 19 | var revlogBuild = cmdRevlog.Flag.Bool("build", false, "build the file, don't show the incremental data") 20 | 21 | func init() { 22 | cmdRevlog.Run = runRevlog 23 | } 24 | 25 | func runRevlog(cmd *Command, w io.Writer, args []string) { 26 | if len(args) == 0 { 27 | fatalf("missing argument: revlog index file") 28 | } 29 | index, err := revlog.Open(storeName(args[0])) 30 | if err != nil { 31 | fatalf("%s", err) 32 | } 33 | 34 | if *revlogR == -1 { 35 | index.Dump(w) 36 | return 37 | } 38 | 39 | r, err := revlog.FileRevSpec(*revlogR).Lookup(index) 40 | if err != nil { 41 | fatalf("%s", err) 42 | } 43 | if !*revlogBuild { 44 | dh := &dataHelper{} 45 | d, err := r.GetData(dh) 46 | if dh.file != nil { 47 | dh.file.Close() 48 | } 49 | if err != nil { 50 | fatalf("%s", err) 51 | } 52 | if r.IsBase() { 53 | w.Write(d) 54 | } else { 55 | hunks, err := patch.Parse(d) 56 | if err != nil { 57 | fatalf("%s", err) 58 | } 59 | for _, h := range hunks { 60 | h.Dump(w) 61 | } 62 | } 63 | } else { 64 | fb := revlog.NewFileBuilder() 65 | err = fb.BuildWrite(w, r) 66 | if err != nil { 67 | fatalf("%s", err) 68 | } 69 | } 70 | } 71 | 72 | type storeName string 73 | 74 | func (s storeName) Index() string { 75 | return string(s) 76 | } 77 | func (s storeName) Data() string { 78 | return string(s[:len(s)-2] + ".d") 79 | } 80 | 81 | type dataHelper struct { 82 | file revlog.DataReadCloser 83 | tmp *bytes.Buffer 84 | } 85 | 86 | func (dh *dataHelper) Open(fileName string) (file revlog.DataReadCloser, err error) { 87 | if dh.file != nil { 88 | file = dh.file 89 | return 90 | } 91 | file, err = os.Open(fileName) 92 | if err == nil { 93 | dh.file = file 94 | } 95 | return 96 | } 97 | 98 | func (dh *dataHelper) TmpBuffer() *bytes.Buffer { 99 | return dh.tmp 100 | } 101 | -------------------------------------------------------------------------------- /tags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package hgo 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | "io/ioutil" 11 | "sort" 12 | "strings" 13 | 14 | "github.com/beyang/hgo/revlog" 15 | ) 16 | 17 | // Tags contains a mapping from tag names to changeset IDs, 18 | // and a mapping from changesetIDs to slices of tag names. 19 | type Tags struct { 20 | IdByName map[string]string 21 | ById map[string][]string 22 | } 23 | 24 | func newTags() *Tags { 25 | return &Tags{ 26 | ById: map[string][]string{}, 27 | IdByName: map[string]string{}, 28 | } 29 | } 30 | 31 | func (dest *Tags) copy(src *Tags) { 32 | for k, v := range src.ById { 33 | dest.ById[k] = v 34 | } 35 | for k, v := range src.IdByName { 36 | dest.IdByName[k] = v 37 | } 38 | } 39 | 40 | // Parse tags, and return one Tags structure containing only 41 | // global tags, another one containing both global and local tags. 42 | func (r *Repository) Tags() (tGlobal, tAll *Tags) { 43 | tGlobal, tAll = newTags(), newTags() 44 | 45 | st := r.NewStore() 46 | index, err := st.OpenRevlog(".hgtags") 47 | if err == nil { 48 | fb := revlog.NewFileBuilder() 49 | if err := fb.BuildWrite(nil, index.Tip()); err == nil { 50 | tAll.parseFile(bytes.NewReader(fb.Bytes())) 51 | } 52 | } 53 | 54 | f, err := r.open(".hg/localtags") 55 | if err == nil { 56 | tGlobal.copy(tAll) 57 | err = tAll.parseFile(f) 58 | f.Close() 59 | } else { 60 | tGlobal = tAll 61 | } 62 | return 63 | } 64 | 65 | func (t *Tags) parseFile(r io.Reader) (err error) { 66 | buf, err := ioutil.ReadAll(r) 67 | if err != nil { 68 | return 69 | } 70 | 71 | lines := strings.Split(string(buf), "\n") 72 | m := make(map[string]string, len(lines)) 73 | 74 | for _, line := range lines { 75 | tag := strings.SplitN(strings.TrimSpace(line), " ", 2) 76 | if len(tag) != 2 { 77 | continue 78 | } 79 | m[tag[1]] = tag[0] 80 | } 81 | // unify 82 | for name, id := range m { 83 | t.ById[id] = append(t.ById[id], name) 84 | t.IdByName[name] = id 85 | } 86 | return 87 | } 88 | 89 | // Associate a new tag with a changeset ID. 90 | func (t *Tags) Add(name, id string) { 91 | t.ById[id] = append(t.ById[id], name) 92 | t.IdByName[name] = id 93 | } 94 | 95 | // For each changeset ID within the ById member, sort the tag names 96 | // associated with it in increasing order. This function should be called 97 | // if one or more tags have been inserted using Add. 98 | func (t *Tags) Sort() { 99 | for _, v := range t.ById { 100 | switch len(v) { 101 | case 1: 102 | case 2: 103 | if v[0] > v[1] { 104 | v[0], v[1] = v[1], v[0] 105 | } 106 | default: 107 | sort.Strings(v) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /cmd/hgo/hgo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // An example program that aims to imitate a subset of Mercurial's sub-commands. 6 | package main 7 | 8 | import ( 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/beyang/hgo" 13 | "github.com/beyang/hgo/revlog" 14 | ) 15 | 16 | var ( 17 | verbose bool 18 | repoRoot string 19 | revSpec string 20 | 21 | repo *hgo.Repository 22 | globalTags, allTags *hgo.Tags 23 | branchHeads *hgo.BranchHeads 24 | ) 25 | 26 | func addStdFlags(cmd *Command) { 27 | cmd.Flag.BoolVar(&verbose, "v", false, "verbose output") 28 | cmd.Flag.StringVar(&repoRoot, "R", "", "The root directory of a project, containing the .hgo subdirectory") 29 | } 30 | 31 | func addRevFlag(cmd *Command) { 32 | cmd.Flag.StringVar(&revSpec, "r", "", "a specific revision") 33 | } 34 | 35 | func openRepository(args []string) { 36 | if repoRoot == "" { 37 | arg0 := "." 38 | if len(args) > 0 { 39 | arg0 = args[0] 40 | } 41 | r, err := hgo.FindProjectRoot(arg0) 42 | if err != nil { 43 | fatalf("%s", err) 44 | } 45 | repoRoot = r 46 | } 47 | r, err := hgo.OpenRepository(repoRoot) 48 | if err != nil { 49 | fatalf("%s", err) 50 | } 51 | repo = r 52 | 53 | globalTags, allTags = repo.Tags() 54 | globalTags.Sort() 55 | allTags.Sort() 56 | 57 | branchHeads, err = repo.BranchHeads() 58 | if err != nil { 59 | fatalf("%s", err) 60 | } 61 | 62 | return 63 | } 64 | 65 | func getRevisionSpec() revlog.RevisionSpec { 66 | return parseRevisionSpec(revSpec, "tip") 67 | } 68 | 69 | func getRevisionRangeSpec() (first, last revlog.RevisionSpec) { 70 | s := revSpec 71 | if s == "" { 72 | s = "tip:0" 73 | } 74 | 75 | f := strings.SplitN(s, ":", 2) 76 | switch len(f) { 77 | case 1: 78 | first = parseRevisionSpec(f[0], "null") 79 | case 2: 80 | first = parseRevisionSpec(f[0], "0") 81 | last = parseRevisionSpec(f[1], "tip") 82 | default: 83 | fatalf("too many fields in revision spec") 84 | } 85 | return 86 | } 87 | 88 | func parseRevisionSpec(s, dflt string) revlog.RevisionSpec { 89 | if s == "" { 90 | s = dflt 91 | } 92 | if s == "tip" { 93 | return revlog.TipRevSpec{} 94 | } 95 | if s == "null" { 96 | return revlog.NullRevSpec{} 97 | } 98 | if id, ok := allTags.IdByName[s]; ok { 99 | s = id 100 | } else if id, ok := branchHeads.IdByName[s]; ok { 101 | s = id 102 | } else if i, err := strconv.Atoi(s); err == nil { 103 | return revlog.FileRevSpec(i) 104 | } 105 | 106 | return revlog.NodeIdRevSpec(s) 107 | } 108 | 109 | func getRecord(i *revlog.Index, rs revlog.RevisionSpec) (r *revlog.Rec) { 110 | r, err := rs.Lookup(i) 111 | if err != nil { 112 | fatalf("%s", err) 113 | } 114 | return 115 | } 116 | 117 | func getFileArg(args []string) (fileName string) { 118 | if len(args) == 0 { 119 | return 120 | } 121 | fileName, err := repo.RelFileName(args[0]) 122 | if err != nil { 123 | fatalf("%s", err) 124 | } 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /branches.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package hgo 6 | 7 | import ( 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | // BranchHeads contains a mapping from branch names to head changeset IDs, and a 16 | // mapping from head changeset IDs to slices of branch names. 17 | type BranchHeads struct { 18 | IdByName map[string]string 19 | ById map[string][]string 20 | } 21 | 22 | func newBranchHeads() *BranchHeads { 23 | return &BranchHeads{ 24 | ById: map[string][]string{}, 25 | IdByName: map[string]string{}, 26 | } 27 | } 28 | 29 | func (dest *BranchHeads) copy(src *BranchHeads) { 30 | for k, v := range src.ById { 31 | dest.ById[k] = v 32 | } 33 | for k, v := range src.IdByName { 34 | dest.IdByName[k] = v 35 | } 36 | } 37 | 38 | // BranchHeads parses and returns the repository's branch heads. 39 | func (r *Repository) BranchHeads() (*BranchHeads, error) { 40 | bh := newBranchHeads() 41 | 42 | names := []string{ 43 | // Original Mercurial branchheads 44 | "branchheads-served", "branchheads-base", "branchheads", 45 | 46 | // branchheads -> branch2 as of 47 | // http://selenic.com/pipermail/mercurial-devel/2013-November/054749.html 48 | "branch2-served", "branch2-base", "branch2", 49 | } 50 | for _, name := range names { 51 | f, err := r.open(".hg/cache/" + name) 52 | if os.IsNotExist(err) { 53 | continue 54 | } 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer f.Close() 59 | 60 | err = bh.parseFile(f) 61 | if err != nil { 62 | return nil, err 63 | } 64 | } 65 | 66 | return bh, nil 67 | } 68 | 69 | func (bh *BranchHeads) parseFile(r io.Reader) error { 70 | buf, err := ioutil.ReadAll(r) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | lines := strings.Split(string(buf), "\n") 76 | m := make(map[string]string, len(lines)-1) 77 | 78 | for i, line := range lines { 79 | if i == 0 { 80 | // first line is current numbered, don't include 81 | continue 82 | } 83 | branch := strings.SplitN(strings.TrimSpace(line), " ", 3) 84 | if len(branch) != 2 && len(branch) != 3 { 85 | continue 86 | } 87 | m[branch[len(branch)-1]] = branch[0] 88 | } 89 | // unify 90 | for name, id := range m { 91 | bh.ById[id] = append(bh.ById[id], name) 92 | bh.IdByName[name] = id 93 | } 94 | return nil 95 | } 96 | 97 | // Associate a new branch with a changeset ID. 98 | func (bh *BranchHeads) Add(name, id string) { 99 | bh.ById[id] = append(bh.ById[id], name) 100 | bh.IdByName[name] = id 101 | } 102 | 103 | // For each changeset ID within the ById member, sort the branch names 104 | // associated with it in increasing order. This function should be called 105 | // if one or more branches have been inserted using Add. 106 | func (bh *BranchHeads) Sort() { 107 | for _, v := range bh.ById { 108 | switch len(v) { 109 | case 1: 110 | case 2: 111 | if v[0] > v[1] { 112 | v[0], v[1] = v[1], v[0] 113 | } 114 | default: 115 | sort.Strings(v) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /changelog/changelog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package changelog provides read access to the changelog. 6 | package changelog 7 | 8 | import ( 9 | "errors" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/beyang/hgo/revlog" 15 | ) 16 | 17 | // http://mercurial.selenic.com/wiki/ChangelogEncodingPlan 18 | // http://mercurial.selenic.com/wiki/ChangeSet 19 | 20 | type Entry struct { 21 | Summary string 22 | Comment string 23 | Files []string 24 | Committer string 25 | Date time.Time 26 | ManifestNode revlog.NodeId 27 | Branch string 28 | Tags []string 29 | *revlog.Rec 30 | Id string 31 | } 32 | 33 | type LatestTag struct { 34 | Names []string 35 | Distance int 36 | } 37 | 38 | func (e *Entry) LatestTag(tags map[string][]string) (lt *LatestTag) { 39 | lt = new(LatestTag) 40 | lt.Names = []string{"null"} 41 | 42 | d := 0 43 | for r := e.Rec; r.FileRev() != -1; d++ { 44 | if t, ok := tags[r.Id().Node()]; ok { 45 | lt.Names = t 46 | break 47 | } 48 | r = r.Prev() 49 | } 50 | lt.Distance = d 51 | return 52 | } 53 | 54 | func BuildEntry(r *revlog.Rec, fb *revlog.FileBuilder) (e *Entry, err error) { 55 | err = fb.BuildWrite(nil, r) 56 | if err != nil { 57 | return 58 | } 59 | e, err = parseEntryData(fb.Bytes()) 60 | if err == nil { 61 | e.Rec = r 62 | e.Id = r.Id().Node() 63 | } 64 | return 65 | } 66 | 67 | func parseEntryData(data []byte) (result *Entry, err error) { 68 | var c Entry 69 | 70 | // separate comments from other data 71 | s := string(data) 72 | if i := strings.Index(s, "\n\n"); i != -1 { 73 | c.Comment = strings.TrimSpace(s[i+2:]) 74 | c.Summary = strings.TrimSpace(strings.Split(c.Comment, "\n")[0]) 75 | s = s[:i] 76 | } 77 | 78 | // split data into lines 79 | f := strings.Split(s, "\n") 80 | if len(f) < 3 { 81 | err = ErrCorrupted 82 | return 83 | } 84 | 85 | c.ManifestNode, err = revlog.NewId(f[0]) 86 | if err != nil { 87 | return 88 | } 89 | 90 | c.Committer = f[1] 91 | 92 | // f[2] contains date/timezone information, as well 93 | // as probably branch and source information 94 | tf := strings.SplitN(f[2], " ", 3) 95 | if len(tf) < 2 { 96 | err = ErrCorrupted 97 | return 98 | } 99 | 100 | c.Files = f[3:] 101 | 102 | // parse date/timezone 103 | us, err := strconv.ParseInt(tf[0], 10, 64) 104 | if err != nil { 105 | return 106 | } 107 | offset, err := strconv.Atoi(tf[1]) 108 | if err != nil { 109 | return 110 | } 111 | c.Date = time.Unix(us, 0).In(time.FixedZone("", -offset)) 112 | 113 | if len(tf) == 3 { 114 | c.Branch = parseMetaSection(tf[2], "\000")["branch"] 115 | } 116 | 117 | result = &c 118 | return 119 | } 120 | 121 | func parseMetaSection(text string, sep string) (m map[string]string) { 122 | m = make(map[string]string, 8) 123 | for _, s := range strings.Split(text, sep) { 124 | f := strings.Split(s, ":") 125 | if len(f) == 2 { 126 | m[f[0]] = strings.TrimSpace(f[1]) 127 | } 128 | } 129 | return 130 | } 131 | 132 | var ErrCorrupted = errors.New("changelog corrupted") 133 | -------------------------------------------------------------------------------- /hg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package hgo provides read access to Mercurial repositories. 6 | package hgo 7 | 8 | import ( 9 | "errors" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/beyang/hgo/store" 15 | ) 16 | 17 | // http://mercurial.selenic.com/wiki/FileFormats 18 | 19 | // Find the root of a project, given the name of a file or directory 20 | // anywhere within the project's directory tree. 21 | func FindProjectRoot(orig string) (root string, err error) { 22 | isRel := !filepath.IsAbs(orig) 23 | 24 | origAbs, err := filepath.Abs(orig) 25 | if err != nil { 26 | return 27 | } 28 | path := origAbs 29 | 30 | // if path is not a directory, skip the last part; (ignore an error, 31 | // because it is not important whether `path' exists at this place) 32 | if fi, err := os.Stat(path); err == nil && !fi.IsDir() { 33 | path = filepath.Dir(path) 34 | } 35 | 36 | for { 37 | if fi, err1 := os.Stat(filepath.Join(path, ".hg")); err1 == nil && fi.IsDir() { 38 | break 39 | } 40 | 41 | old := path 42 | path = filepath.Clean(filepath.Join(path, "..")) 43 | if path == old { 44 | err = errors.New("no repository found") 45 | return 46 | } 47 | } 48 | 49 | // found 50 | root = path 51 | if isRel { 52 | // if the original path was a relative one, try to make 53 | // `root' relative to the current working directory again 54 | if wd, err1 := os.Getwd(); err1 == nil { 55 | if r, err1 := filepath.Rel(wd, path); err1 == nil { 56 | root = r 57 | } 58 | } 59 | } 60 | return 61 | } 62 | 63 | type Repository struct { 64 | root string 65 | requires map[string]bool 66 | } 67 | 68 | // Open a repository located at the given project root directory, 69 | // i.e. a directory that contains a subdirectory named ‘.hg’. 70 | func OpenRepository(root string) (r *Repository, err error) { 71 | var t Repository 72 | t.root = root 73 | f, err := t.open(".hg/requires") 74 | if err != nil { 75 | return 76 | } 77 | t.requires, err = parseRequires(f) 78 | f.Close() 79 | if err != nil { 80 | return 81 | } 82 | r = &t 83 | return 84 | } 85 | 86 | // For a given absolute or relative file name, compute a name relative 87 | // to the repository's root. 88 | func (r *Repository) RelFileName(name string) (rel string, err error) { 89 | absName, err := filepath.Abs(name) 90 | if err != nil { 91 | return 92 | } 93 | absRoot, err := filepath.Abs(r.root) 94 | if err != nil { 95 | return 96 | } 97 | rel, err = filepath.Rel(absRoot, absName) 98 | if err != nil { 99 | return 100 | } 101 | rel = filepath.ToSlash(filepath.Clean(rel)) 102 | if rel == ".." || strings.HasPrefix(rel, "../") { 103 | rel = filepath.ToSlash(name) 104 | } 105 | return 106 | } 107 | 108 | // NewStore returns a new Store instance that provides 109 | // access to the repository's changelog, manifests, and filelogs. 110 | func (r *Repository) NewStore() *store.Store { 111 | return store.New(r.root, r.requires) 112 | } 113 | 114 | func (r *Repository) open(name string) (*os.File, error) { 115 | return os.Open(filepath.Join(r.root, filepath.FromSlash(name))) 116 | } 117 | -------------------------------------------------------------------------------- /store/casefolding.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package store 6 | 7 | import ( 8 | "strings" 9 | ) 10 | 11 | // http://mercurial.selenic.com/wiki/CaseFoldingPlan 12 | // http://mercurial.selenic.com/wiki/fncacheRepoFormat 13 | // http://mercurial.selenic.com/wiki/fncache2RepoFormat 14 | 15 | type filenameEncoder struct { 16 | buf []byte 17 | fncache bool 18 | dotencode bool 19 | } 20 | 21 | func newFilenameEncoder(requires map[string]bool) *filenameEncoder { 22 | return &filenameEncoder{ 23 | dotencode: requires["dotencode"], 24 | fncache: requires["fncache"], 25 | } 26 | } 27 | 28 | const hexdigits = "0123456789abcdef" 29 | 30 | func byteToHex(hex []byte, b byte) []byte { 31 | return hex 32 | } 33 | 34 | func (e *filenameEncoder) Encode(orig string) (indexName, dataName string) { 35 | var modified, segMod bool 36 | b := e.buf 37 | 38 | indexName = orig 39 | dataName = orig 40 | for i, seg := range strings.Split(orig, "/") { 41 | if i > 0 { 42 | b = append(b, '/') 43 | } 44 | if b, segMod = e.encodeSegment(b, seg, false); segMod { 45 | modified = true 46 | } 47 | } 48 | 49 | e.buf = b[:0] 50 | if e.fncache && len(b) > 120 { 51 | indexName = "dh/__BUG__hash_encoded_names_not_supported" 52 | dataName = indexName 53 | } else if modified { 54 | indexName = string(b) 55 | dataName = indexName 56 | } 57 | return 58 | } 59 | 60 | func (e *filenameEncoder) encodeSegment(b []byte, seg string, hashPreEncode bool) (result []byte, modified bool) { 61 | n := len(seg) 62 | switch { 63 | case !e.fncache: 64 | case n > 2 && strings.HasSuffix(seg, ".d"): 65 | // In various repositories, like golang's, it can be seen that ".d" gets 66 | // translated into ".d.hg". There seems to be no documentation in 67 | // Mercurial's wiki about that; for the nonce we encode .d for 68 | // the fncache option. 69 | seg += ".hg" 70 | modified = true 71 | n += 3 72 | case n >= 3: 73 | // check for some names reserved on MS-Windows 74 | nres := 3 75 | switch seg[:nres] { 76 | case "com", "lpt": 77 | nres = 4 78 | if n <= 3 || seg[3] < '1' || seg[3] > '9' { 79 | break 80 | } 81 | fallthrough 82 | case "con", "prn", "aux", "nul": 83 | if n > nres && seg[nres] != '.' { 84 | break 85 | } 86 | b = append(b, seg[0], seg[1], '~', hexdigits[seg[2]>>4], hexdigits[seg[2]&0xf]) 87 | result = append(b, seg[3:]...) 88 | modified = true 89 | return 90 | } 91 | } 92 | 93 | for i := 0; i < n; i++ { 94 | c := seg[i] 95 | switch { 96 | case c < ' ' || c >= '~': 97 | goto hex 98 | 99 | case i == 0 && e.dotencode && (c == ' ' || c == '.'): 100 | goto hex 101 | 102 | case c >= 'A' && c <= 'Z': 103 | if !hashPreEncode { 104 | b = append(b, '_') 105 | } 106 | c = c - 'A' + 'a' 107 | modified = true 108 | 109 | default: 110 | switch c { 111 | case '_': 112 | b = append(b, c) 113 | modified = true 114 | 115 | case '\\', ':', '*', '?', '"', '<', '>', '|': 116 | goto hex 117 | } 118 | } 119 | b = append(b, c) 120 | continue 121 | hex: 122 | modified = true 123 | b = append(b, '~', hexdigits[c>>4], hexdigits[c&0xf]) 124 | } 125 | result = b 126 | return 127 | } 128 | -------------------------------------------------------------------------------- /store/store.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package store provides access to Mercurial's ‘store’ repository format. 6 | package store 7 | 8 | import ( 9 | "errors" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/beyang/hgo/revlog" 14 | ) 15 | 16 | type Store struct { 17 | root string 18 | fe *filenameEncoder 19 | } 20 | 21 | func New(repoRoot string, requires map[string]bool) (s *Store) { 22 | s = new(Store) 23 | s.root = filepath.Join(repoRoot, ".hg", "store") 24 | s.fe = newFilenameEncoder(requires) 25 | return 26 | } 27 | 28 | func (s *Store) OpenRevlog(fileName string) (*revlog.Index, error) { 29 | i, d := s.fe.Encode("data/" + fileName) 30 | return revlog.Open(&encodedName{s.root, i, d}) 31 | } 32 | 33 | type Manifests struct { 34 | *revlog.Index 35 | } 36 | 37 | func (s *Store) OpenManifests() (m *Manifests, err error) { 38 | const name = "00manifest" 39 | 40 | c, err := revlog.Open(&encodedName{s.root, name, name}) 41 | if err == nil { 42 | m = &Manifests{c} 43 | } 44 | return 45 | } 46 | 47 | type Manifest []ManifestEnt 48 | 49 | func (m *Manifests) LookupRevision(linkrev int, wantId revlog.NodeId) (r *revlog.Rec, err error) { 50 | r, err = revlog.FileRevSpec(linkrev).Lookup(m.Index) 51 | if err != nil { 52 | r = m.Tip() 53 | err = nil 54 | } 55 | for int(r.Linkrev) > linkrev { 56 | r = r.Prev() 57 | } 58 | if !wantId.Eq(r.Id()) { 59 | err = errors.New("manifest node id does not match changelog entry") 60 | } 61 | return 62 | } 63 | 64 | func BuildManifest(r *revlog.Rec, fb *revlog.FileBuilder) (m Manifest, err error) { 65 | err = fb.BuildWrite(nil, r) 66 | if err != nil { 67 | return 68 | } 69 | 70 | m, err = ParseManifestData(fb.Bytes()) 71 | return 72 | } 73 | 74 | // Create a map with filename keys from a list of manifest entries. 75 | func (list Manifest) Map() (m map[string]*ManifestEnt) { 76 | m = make(map[string]*ManifestEnt, len(list)) 77 | for i, e := range list { 78 | m[e.FileName] = &list[i] 79 | } 80 | return 81 | } 82 | 83 | type ManifestEnt struct { 84 | FileName string 85 | hash string 86 | } 87 | 88 | func (e *ManifestEnt) value() (hash, opt string) { 89 | hash = e.hash 90 | if n := len(hash); n%2 == 1 { 91 | n-- 92 | opt = hash[n:] 93 | hash = hash[:n] 94 | } 95 | return 96 | } 97 | 98 | func (e *ManifestEnt) IsLink() bool { 99 | _, o := e.value() 100 | return o == "l" 101 | } 102 | 103 | func (e *ManifestEnt) IsExecutable() bool { 104 | _, o := e.value() 105 | return o == "x" 106 | } 107 | func (e *ManifestEnt) Id() (revlog.NodeId, error) { 108 | hash, _ := e.value() 109 | return revlog.NewId(hash) 110 | } 111 | 112 | func ParseManifestData(data []byte) (m Manifest, err error) { 113 | for _, line := range strings.Split(string(data), "\n") { 114 | f := strings.SplitN(line, "\000", 2) 115 | if len(f) != 2 { 116 | continue 117 | } 118 | m = append(m, ManifestEnt{f[0], f[1]}) 119 | } 120 | return 121 | } 122 | 123 | func (s *Store) OpenChangeLog() (*revlog.Index, error) { 124 | const name = "00changelog" 125 | return revlog.Open(&encodedName{s.root, name, name}) 126 | } 127 | 128 | type encodedName struct { 129 | root string 130 | indexPart string 131 | dataPart string 132 | } 133 | 134 | func (e *encodedName) Index() string { 135 | s := filepath.FromSlash(e.indexPart) 136 | return filepath.Join(e.root, s+".i") 137 | } 138 | 139 | func (e *encodedName) Data() string { 140 | s := filepath.FromSlash(e.dataPart) 141 | return filepath.Join(e.root, s+".d") 142 | } 143 | -------------------------------------------------------------------------------- /cmd/hgo/cmdcat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/beyang/hgo/changelog" 13 | "github.com/beyang/hgo/revlog" 14 | "github.com/beyang/hgo/store" 15 | ) 16 | 17 | var cmdCat = &Command{ 18 | UsageLine: "cat [-R dir] [-r rev] [file]", 19 | Short: "write the current or given revision of a file to stdout", 20 | Long: ``, 21 | } 22 | 23 | func init() { 24 | addStdFlags(cmdCat) 25 | addRevFlag(cmdCat) 26 | cmdCat.Run = runCat 27 | } 28 | 29 | func findPresentByNodeId(ent *store.ManifestEnt, rlist []*revlog.Rec) (index int, err error) { 30 | wantId, err := ent.Id() 31 | if err != nil { 32 | return 33 | } 34 | 35 | for i, r := range rlist { 36 | if wantId.Eq(r.Id()) { 37 | index = i 38 | return 39 | } 40 | } 41 | 42 | err = fmt.Errorf("internal error: none of the given records matches node id %v", wantId) 43 | return 44 | } 45 | 46 | func runCat(cmd *Command, w io.Writer, args []string) { 47 | openRepository(args) 48 | rs := getRevisionSpec() 49 | fileArg := getFileArg(args) 50 | st := repo.NewStore() 51 | 52 | fileLog, err := st.OpenRevlog(fileArg) 53 | if err != nil { 54 | fatalf("%s", err) 55 | } 56 | 57 | ra := repoAccess{ 58 | fb: revlog.NewFileBuilder(), 59 | st: st, 60 | } 61 | localId, ok := rs.(revlog.FileRevSpec) 62 | if !ok { 63 | localId, err = ra.localChangesetId(rs) 64 | if err != nil { 65 | return 66 | } 67 | } 68 | 69 | link := revlog.NewLinkRevSpec(int(localId)) 70 | link.FindPresent = func(rlist []*revlog.Rec) (index int, err error) { 71 | if len(rlist) > 1 { 72 | // Does link.Rev refer to a changelog revision that is a 73 | // descendant of one of the revisions in rlist? 74 | for i, r := range rlist { 75 | cr, err1 := ra.clRec(revlog.FileRevSpec(r.Linkrev)) 76 | if err1 != nil { 77 | err = err1 78 | return 79 | } 80 | if cr.IsDescendant(link.Rev) { 81 | index = i 82 | goto found 83 | } 84 | } 85 | err = fmt.Errorf("internal error: none of the given records is an ancestor of rev %v", link.Rev) 86 | return 87 | 88 | found: 89 | if !rlist[index].IsLeaf() { 90 | return 91 | } 92 | } 93 | 94 | // Check for the file's existence using the manifest. 95 | ent, err := ra.manifestEntry(link.Rev, fileArg) 96 | if err == nil { 97 | index, err = findPresentByNodeId(ent, rlist) 98 | } 99 | return 100 | } 101 | r, err := link.Lookup(fileLog) 102 | if err != nil { 103 | fatalf("%s", err) 104 | } 105 | 106 | fb := revlog.NewFileBuilder() 107 | err = fb.BuildWrite(w, r) 108 | if err != nil { 109 | fatalf("%s", err) 110 | } 111 | } 112 | 113 | type repoAccess struct { 114 | fb *revlog.FileBuilder 115 | st *store.Store 116 | changelog *revlog.Index 117 | } 118 | 119 | func (ra *repoAccess) manifestEntry(chgId int, fileName string) (me *store.ManifestEnt, err error) { 120 | r, err := ra.clRec(revlog.FileRevSpec(chgId)) 121 | if err != nil { 122 | return 123 | } 124 | c, err := changelog.BuildEntry(r, ra.fb) 125 | if err != nil { 126 | return 127 | } 128 | m, err := getManifest(int(c.Linkrev), c.ManifestNode, ra.fb) 129 | if err != nil { 130 | return 131 | } 132 | me = m.Map()[fileName] 133 | if me == nil { 134 | err = errors.New("file does not exist in given revision") 135 | } 136 | return 137 | } 138 | 139 | func (ra *repoAccess) localChangesetId(rs revlog.RevisionSpec) (chgId revlog.FileRevSpec, err error) { 140 | r, err := ra.clRec(rs) 141 | if err == nil { 142 | chgId = revlog.FileRevSpec(r.FileRev()) 143 | } 144 | return 145 | } 146 | 147 | func (ra *repoAccess) clRec(rs revlog.RevisionSpec) (r *revlog.Rec, err error) { 148 | if ra.changelog == nil { 149 | log, err1 := ra.st.OpenChangeLog() 150 | if err1 != nil { 151 | err = err1 152 | return 153 | } 154 | ra.changelog = log 155 | } 156 | r, err = rs.Lookup(ra.changelog) 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /revlog/lookup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package revlog 6 | 7 | import "errors" 8 | 9 | type RevisionSpec interface { 10 | Lookup(*Index) (*Rec, error) 11 | } 12 | 13 | type FileRevSpec int 14 | 15 | func (n FileRevSpec) Lookup(i *Index) (r *Rec, err error) { 16 | if n < 0 { 17 | n += FileRevSpec(len(i.index)) 18 | } 19 | if n < 0 || int(n) >= len(i.index) { 20 | err = ErrRevisionNotFound 21 | } else { 22 | r = i.Record(int(n)) 23 | } 24 | return 25 | } 26 | 27 | // A LinkRevSpec can be used to find a file revision 28 | // that was present at a certain changelog revision, by 29 | // examining the filelog records' linkrev values. 30 | // The behaviour of the Lookup method can be configured 31 | // by setting FindPresent to a user implemented function. 32 | type LinkRevSpec struct { 33 | Rev int 34 | 35 | // FindPresent should examine maybeAncestors' Linkrev values to 36 | // find a changelog record that is an ancestor of Rev. It also has to 37 | // make sure that the file actually existed in the revision specified 38 | // by Rev. 39 | // If FindPresent is nil (the default), Lookup will -- in case of multiple 40 | // matching branches -- return the last visited record, or a Null record 41 | // if no revision matches at all. 42 | FindPresent func(maybeAncestors []*Rec) (index int, err error) 43 | } 44 | 45 | func NewLinkRevSpec(rev int) *LinkRevSpec { 46 | return &LinkRevSpec{Rev: rev} 47 | } 48 | 49 | func (l LinkRevSpec) Lookup(i *Index) (match *Rec, err error) { 50 | // While doing the range loop, vr keeps track of the 51 | // last visited records of all branches. 52 | var vr []*Rec 53 | branch := 0 54 | 55 | for j := range i.index { 56 | if li := int(i.index[j].Linkrev); li == int(l.Rev) { 57 | // exact match 58 | match = i.Record(j) 59 | return 60 | } else if li > int(l.Rev) { 61 | break 62 | } 63 | 64 | r := i.Record(j) 65 | if vr == nil { 66 | vr = append(vr, r) 67 | continue 68 | } 69 | 70 | // If Parent2 points to one of the last visited 71 | // records of all visited branches, store the 72 | // entries index into p2branch. 73 | p2branch := -1 74 | if r.Parent2Present() { 75 | p := r.Parent2().FileRev() 76 | for k, r := range vr { 77 | if r == nil { 78 | continue 79 | } 80 | if r.FileRev() == p { 81 | p2branch = k 82 | } 83 | } 84 | } 85 | 86 | // If the parent of the current record is not a member of the 87 | // last visited branch, look if either parent or parent2 is one of 88 | // the other last visited records. Else, create a new branch. 89 | if p := r.Parent().FileRev(); vr[branch].FileRev() != p { 90 | for k, r := range vr { 91 | if r == nil { 92 | continue 93 | } 94 | if r.FileRev() == p { 95 | branch = k 96 | goto found 97 | } 98 | } 99 | if p2branch != -1 { 100 | branch = p2branch 101 | } else { 102 | branch = len(vr) 103 | vr = append(vr, r) 104 | } 105 | found: 106 | } 107 | vr[branch] = r 108 | if p2branch != -1 && p2branch != branch { 109 | vr[p2branch] = nil 110 | } 111 | } 112 | 113 | // Sort out nil entries. 114 | w := 0 115 | numNilsBeforeBranch := 0 116 | for i := range vr { 117 | if vr[i] != nil { 118 | vr[w] = vr[i] 119 | w++ 120 | } else if i < branch { 121 | numNilsBeforeBranch++ 122 | } 123 | } 124 | vr = vr[:w] 125 | branch -= numNilsBeforeBranch 126 | 127 | switch len(vr) { 128 | case 0: 129 | if l.FindPresent != nil { 130 | match = nil 131 | err = ErrRevisionNotFound 132 | return 133 | } 134 | match = i.Null() 135 | default: 136 | if l.FindPresent != nil { 137 | // make sure the most recent updated entry comes first 138 | if branch != 0 { 139 | vr[0], vr[branch] = vr[branch], vr[0] 140 | } 141 | branch, err = l.FindPresent(vr) 142 | if err == nil { 143 | match = vr[branch] 144 | } 145 | return 146 | } 147 | fallthrough 148 | 149 | case 1: 150 | match = vr[branch] 151 | if match.IsLeaf() { 152 | if l.FindPresent != nil { 153 | _, err = l.FindPresent([]*Rec{match}) 154 | } 155 | } 156 | } 157 | 158 | return 159 | } 160 | 161 | type NodeIdRevSpec string 162 | 163 | func (hash NodeIdRevSpec) Lookup(rv *Index) (r *Rec, err error) { 164 | var i = -1 165 | var found bool 166 | 167 | wantid, err := NewId(string(hash)) 168 | if err != nil { 169 | return 170 | } 171 | for j := range rv.index { 172 | nodeid := rv.NewNodeId(rv.index[j].NodeId[:]) 173 | if len(wantid) <= len(nodeid) { 174 | if wantid.Eq(nodeid[:len(wantid)]) { 175 | if found { 176 | err = ErrRevisionAmbiguous 177 | } 178 | found = true 179 | i = j 180 | } 181 | } 182 | } 183 | if i == -1 { 184 | err = ErrRevNotFound 185 | } else { 186 | r = rv.Record(i) 187 | } 188 | return 189 | } 190 | 191 | type TipRevSpec struct{} 192 | 193 | func (TipRevSpec) String() string { 194 | return "tip" 195 | } 196 | 197 | func (TipRevSpec) Lookup(i *Index) (r *Rec, err error) { 198 | if n := len(i.index); n == 0 { 199 | err = ErrRevisionNotFound 200 | } else { 201 | r = i.Record(n - 1) 202 | } 203 | return 204 | } 205 | 206 | type NullRevSpec struct{} 207 | 208 | func (NullRevSpec) String() string { 209 | return "null" 210 | } 211 | 212 | func (NullRevSpec) Lookup(i *Index) (r *Rec, err error) { 213 | r = &i.null 214 | return 215 | } 216 | 217 | var ErrRevisionNotFound = errors.New("hg/revlog: revision not found") 218 | var ErrRevisionAmbiguous = errors.New("hg/revlog: ambiguous revision spec") 219 | -------------------------------------------------------------------------------- /cmd/hgo/cmdlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "io" 9 | "strings" 10 | "text/template" 11 | 12 | "github.com/beyang/hgo/changelog" 13 | "github.com/beyang/hgo/revlog" 14 | ) 15 | 16 | var cmdLog = &Command{ 17 | UsageLine: "log [-R dir] [-r rev] [-l n] [-v] [file]", 18 | Short: "list changeset information", 19 | Long: ``, 20 | } 21 | 22 | func init() { 23 | addStdFlags(cmdLog) 24 | addRevFlag(cmdLog) 25 | cmdLog.Run = runLog 26 | } 27 | 28 | var logL = cmdLog.Flag.Int("l", 0, "list at most n changesets") 29 | 30 | const logTemplate = `{{range .}}changeset: {{.FileRev}}:{{.Id|short}} 31 | {{with .Branch}}branch: {{.}} 32 | {{end}}{{range index (tags) .Id}}tag: {{.}} 33 | {{end}}{{if .Parent1NotPrevious}}parent: {{.Parent.FileRev}}:{{.Parent.Id}} 34 | {{end}}{{if .Parent2Present}}parent: {{.Parent2.FileRev}}:{{.Parent2.Id}} 35 | {{end}}user: {{.Committer}} 36 | date: {{.Date.Format "Mon Jan 02 15:04:05 2006 -0700"}} 37 | {{if verbose}}{{with .Files}}files: {{range $_, $i := .}} {{.}}{{end}} 38 | {{end}}description: 39 | {{.Comment}} 40 | 41 | {{else}}summary: {{.Summary}} 42 | {{end}} 43 | {{end}}` 44 | 45 | func runLog(cmd *Command, w io.Writer, args []string) { 46 | openRepository(args) 47 | rsFrom, rsTo := getRevisionRangeSpec() 48 | fileArg := getFileArg(args) 49 | 50 | st := repo.NewStore() 51 | clIndex, err := st.OpenChangeLog() 52 | if err != nil { 53 | fatalf("%s", err) 54 | } 55 | 56 | allTags.Add("tip", clIndex.Tip().Id().Node()) 57 | 58 | rFrom := getRecord(clIndex, rsFrom) 59 | rTo := rFrom 60 | if rsTo != nil { 61 | rTo = getRecord(clIndex, rsTo) 62 | } 63 | var logIndex *revlog.Index 64 | wantFile := "" 65 | wantDir := "" 66 | if fileArg != "" && rTo != rFrom { 67 | if fileIndex, err1 := st.OpenRevlog(fileArg); err1 != nil { 68 | wantDir = fileArg + "/" 69 | } else { 70 | if rFrom.FileRev() > rTo.FileRev() { 71 | rTo, rFrom = mapLinkrevToFilerevRange(fileIndex, rTo, rFrom) 72 | } else { 73 | rFrom, rTo = mapLinkrevToFilerevRange(fileIndex, rFrom, rTo) 74 | } 75 | if rFrom == nil && rTo == nil { 76 | // range is empty 77 | return 78 | } 79 | logIndex = clIndex 80 | } 81 | } else { 82 | wantFile = fileArg 83 | } 84 | 85 | err = printChangelog(w, rFrom, rTo, logIndex, allTags.ById, wantDir, wantFile) 86 | if err != nil { 87 | fatalf("%s", err) 88 | } 89 | } 90 | 91 | func printChangelog(w io.Writer, rFrom, rTo *revlog.Rec, logIndex *revlog.Index, tags map[string][]string, wantDirPrefix, wantFile string) (err error) { 92 | var clr *revlog.Rec 93 | 94 | match := func([]string) bool { return true } 95 | if wantFile != "" { 96 | match = func(files []string) (ok bool) { 97 | for _, f := range files { 98 | if f == wantFile { 99 | ok = true 100 | return 101 | } 102 | } 103 | return 104 | } 105 | } else if wantDirPrefix != "" { 106 | match = func(files []string) (ok bool) { 107 | for _, f := range files { 108 | if strings.HasPrefix(f, wantDirPrefix) { 109 | ok = true 110 | return 111 | } 112 | } 113 | return 114 | } 115 | } 116 | 117 | fb := revlog.NewFileBuilder() 118 | fb.SetDataCache(&dc) 119 | fb.KeepDataOpen() 120 | defer fb.CloseData() 121 | t, err := setupLogTemplate(logTemplate, tags) 122 | if err != nil { 123 | return 124 | } 125 | ch := make(chan *changelog.Entry, 16) 126 | errch := make(chan error, 0) 127 | go func() { 128 | errch <- t.Execute(w, ch) 129 | }() 130 | r := rFrom 131 | target := rTo.FileRev() 132 | var next func() 133 | if rFrom.FileRev() > target { 134 | next = func() { r = r.Prev() } 135 | } else { 136 | next = func() { r = r.Next() } 137 | } 138 | i := 0 139 | for { 140 | if logIndex == nil { 141 | clr = r 142 | } else { 143 | clr, err = revlog.FileRevSpec(r.Linkrev).Lookup(logIndex) 144 | if err != nil { 145 | return 146 | } 147 | } 148 | c, err1 := changelog.BuildEntry(clr, fb) 149 | if err1 != nil { 150 | err = err1 151 | return 152 | } 153 | c.Rec = clr 154 | 155 | if match(c.Files) { 156 | select { 157 | case ch <- c: 158 | i++ 159 | case err = <-errch: 160 | return 161 | } 162 | } 163 | if r.FileRev() == target { 164 | break 165 | } 166 | next() 167 | if r.FileRev() == -1 { 168 | break 169 | } 170 | if *logL != 0 && i == *logL { 171 | break 172 | } 173 | } 174 | close(ch) 175 | err = <-errch 176 | return 177 | } 178 | 179 | func setupLogTemplate(tpl string, tags map[string][]string) (*template.Template, error) { 180 | t := template.New("logentry") 181 | t.Funcs(template.FuncMap{ 182 | "verbose": func() bool { 183 | return verbose 184 | }, 185 | "short": func(s string) string { 186 | if len(s) > 12 { 187 | return s[:12] 188 | } 189 | return s 190 | }, 191 | "tags": func() map[string][]string { 192 | return tags 193 | }, 194 | }) 195 | return t.Parse(tpl) 196 | } 197 | 198 | func mapLinkrevToFilerevRange(i *revlog.Index, rLo, rHi *revlog.Rec) (r1, r2 *revlog.Rec) { 199 | r1 = getRecord(i, revlog.NewLinkRevSpec(int(rLo.Linkrev))) 200 | r2 = getRecord(i, revlog.NewLinkRevSpec(int(rHi.Linkrev))) 201 | if r1.Linkrev < rLo.Linkrev { 202 | if r1.Linkrev == r2.Linkrev { 203 | goto emptyRange 204 | } 205 | r1 = r1.Next() 206 | } 207 | if r1.Linkrev == r2.Linkrev && r2.Linkrev < rHi.Linkrev { 208 | goto emptyRange 209 | } 210 | return 211 | 212 | emptyRange: 213 | r1 = nil 214 | r2 = nil 215 | return 216 | } 217 | -------------------------------------------------------------------------------- /cmd/hgo/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bufio" 9 | "flag" 10 | "fmt" 11 | "io" 12 | "log" 13 | "os" 14 | "strings" 15 | "sync" 16 | "text/template" 17 | "unicode" 18 | "unicode/utf8" 19 | ) 20 | 21 | // A Command is an implementation of a go command 22 | // like go build or go fix. 23 | type Command struct { 24 | // Run runs the command. 25 | // The args are the arguments after the command name. 26 | Run func(cmd *Command, w io.Writer, args []string) 27 | 28 | // UsageLine is the one-line usage message. 29 | // The first word in the line is taken to be the command name. 30 | UsageLine string 31 | 32 | // Short is the short description shown in the 'go help' output. 33 | Short string 34 | 35 | // Long is the long message shown in the 'go help ' output. 36 | Long string 37 | 38 | // Flag is a set of flags specific to this command. 39 | Flag flag.FlagSet 40 | 41 | // CustomFlags indicates that the command will do its own 42 | // flag parsing. 43 | CustomFlags bool 44 | } 45 | 46 | // Name returns the command's name: the first word in the usage line. 47 | func (c *Command) Name() string { 48 | name := c.UsageLine 49 | i := strings.Index(name, " ") 50 | if i >= 0 { 51 | name = name[:i] 52 | } 53 | return name 54 | } 55 | 56 | func (c *Command) Usage() { 57 | fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) 58 | fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) 59 | os.Exit(2) 60 | } 61 | 62 | // Runnable reports whether the command can be run; otherwise 63 | // it is a documentation pseudo-command such as importpath. 64 | func (c *Command) Runnable() bool { 65 | return c.Run != nil 66 | } 67 | 68 | // Commands lists the available commands and help topics. 69 | // The order here is the order in which they are printed by 'go help'. 70 | var commands = []*Command{ 71 | cmdCat, 72 | cmdLog, 73 | cmdManifest, 74 | cmdArchive, 75 | cmdRevlog, 76 | cmdTags, 77 | cmdBranches, 78 | } 79 | 80 | var exitStatus = 0 81 | var exitMu sync.Mutex 82 | 83 | func setExitStatus(n int) { 84 | exitMu.Lock() 85 | if exitStatus < n { 86 | exitStatus = n 87 | } 88 | exitMu.Unlock() 89 | } 90 | 91 | func main() { 92 | flag.Usage = usage 93 | flag.Parse() 94 | log.SetFlags(0) 95 | 96 | args := flag.Args() 97 | if len(args) < 1 { 98 | usage() 99 | } 100 | 101 | if args[0] == "help" { 102 | help(args[1:]) 103 | return 104 | } 105 | 106 | for _, cmd := range commands { 107 | if cmd.Name() == args[0] && cmd.Run != nil { 108 | cmd.Flag.Usage = func() { cmd.Usage() } 109 | if cmd.CustomFlags { 110 | args = args[1:] 111 | } else { 112 | cmd.Flag.Parse(args[1:]) 113 | args = cmd.Flag.Args() 114 | } 115 | w := bufio.NewWriter(os.Stdout) 116 | startPProf() 117 | cmd.Run(cmd, w, args) 118 | stopPProf() 119 | w.Flush() 120 | exit() 121 | return 122 | } 123 | } 124 | 125 | fmt.Fprintf(os.Stderr, "hgo: unknown subcommand %q\nRun 'hgo help' for usage.\n", args[0]) 126 | setExitStatus(2) 127 | exit() 128 | } 129 | 130 | var usageTemplate = `Hgo is an example program that implements some of Mercurials commands. 131 | 132 | Usage: 133 | 134 | hgo command [arguments] 135 | 136 | The commands are: 137 | {{range .}}{{if .Runnable}} 138 | {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 139 | 140 | Use "hgo help [command]" for more information about a command. 141 | 142 | Additional help topics: 143 | {{range .}}{{if not .Runnable}} 144 | {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 145 | 146 | Use "hgo help [topic]" for more information about that topic. 147 | 148 | ` 149 | 150 | var helpTemplate = `{{if .Runnable}}usage: hgo {{.UsageLine}} 151 | 152 | {{end}}{{.Long | trim}} 153 | ` 154 | 155 | // tmpl executes the given template text on data, writing the result to w. 156 | func tmpl(w io.Writer, text string, data interface{}) { 157 | t := template.New("top") 158 | t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) 159 | template.Must(t.Parse(text)) 160 | if err := t.Execute(w, data); err != nil { 161 | panic(err) 162 | } 163 | } 164 | 165 | func capitalize(s string) string { 166 | if s == "" { 167 | return s 168 | } 169 | r, n := utf8.DecodeRuneInString(s) 170 | return string(unicode.ToTitle(r)) + s[n:] 171 | } 172 | 173 | func printUsage(w io.Writer) { 174 | tmpl(w, usageTemplate, commands) 175 | } 176 | 177 | func usage() { 178 | printUsage(os.Stderr) 179 | os.Exit(2) 180 | } 181 | 182 | // help implements the 'help' command. 183 | func help(args []string) { 184 | if len(args) == 0 { 185 | printUsage(os.Stdout) 186 | // not exit 2: succeeded at 'go help'. 187 | return 188 | } 189 | if len(args) != 1 { 190 | fmt.Fprintf(os.Stderr, "usage: hgo help command\n\nToo many arguments given.\n") 191 | os.Exit(2) // failed at 'hgo help' 192 | } 193 | 194 | arg := args[0] 195 | 196 | for _, cmd := range commands { 197 | if cmd.Name() == arg { 198 | tmpl(os.Stdout, helpTemplate, cmd) 199 | // not exit 2: succeeded at 'go help cmd'. 200 | return 201 | } 202 | } 203 | 204 | fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'go help'.\n", arg) 205 | os.Exit(2) // failed at 'go help cmd' 206 | } 207 | 208 | var atexitFuncs []func() 209 | 210 | func atexit(f func()) { 211 | atexitFuncs = append(atexitFuncs, f) 212 | } 213 | 214 | func exit() { 215 | for _, f := range atexitFuncs { 216 | f() 217 | } 218 | os.Exit(exitStatus) 219 | } 220 | 221 | func fatalf(format string, args ...interface{}) { 222 | errorf(format, args...) 223 | exit() 224 | } 225 | 226 | func errorf(format string, args ...interface{}) { 227 | log.Printf(format, args...) 228 | setExitStatus(1) 229 | } 230 | 231 | var logf = log.Printf 232 | 233 | func exitIfErrors() { 234 | if exitStatus != 0 { 235 | exit() 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /hgo_test.go: -------------------------------------------------------------------------------- 1 | package hgo_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/beyang/hgo" 7 | hg_changelog "github.com/beyang/hgo/changelog" 8 | hg_revlog "github.com/beyang/hgo/revlog" 9 | hg_store "github.com/beyang/hgo/store" 10 | ) 11 | 12 | var ( 13 | touchTime = appleTime("2006-01-02T15:04:05Z") 14 | commands = []string{ 15 | "hg init", 16 | "touch --date=2006-01-02T15:04:05Z f || touch -t " + touchTime + " f", 17 | "hg add f", 18 | "hg commit -m foo --date '2006-12-06 13:18:29 UTC' --user 'a '", 19 | // Some versions of Mercurial don't create .hg/cache until another command 20 | // is ran that uses branches. Ran into this on Mercurial 2.0.2. 21 | "hg branches >/dev/null", 22 | } 23 | revision = "e8e11ff1be92a7be71b9b5cdb4cc674b7dc9facf" 24 | ) 25 | 26 | func TestOpenRepository(t *testing.T) { 27 | defer removeTmpDirs(t) 28 | 29 | dir := createRepo(t, commands) 30 | 31 | _, err := hgo.OpenRepository(dir) 32 | if err != nil { 33 | t.Fatal("Unable to open repository.") 34 | } 35 | } 36 | 37 | func TestTags(t *testing.T) { 38 | defer removeTmpDirs(t) 39 | 40 | dir := createRepo(t, commands) 41 | 42 | repo, err := hgo.OpenRepository(dir) 43 | if err != nil { 44 | t.Fatal("Unable to open repository.") 45 | } 46 | 47 | globalTags, allTags := repo.Tags() 48 | if globalTags == nil { 49 | t.Fatal("Unable to get global tags") 50 | } 51 | if allTags == nil { 52 | t.Fatal("Unable to get all tags") 53 | } 54 | } 55 | 56 | func TestBranchHeads(t *testing.T) { 57 | defer removeTmpDirs(t) 58 | 59 | dir := createRepo(t, commands) 60 | 61 | repo, err := hgo.OpenRepository(dir) 62 | if err != nil { 63 | t.Fatal("Unable to open repository.") 64 | } 65 | 66 | _, err = repo.BranchHeads() 67 | if err != nil { 68 | t.Errorf("Unable to get branch heads: %s", err) 69 | } 70 | } 71 | 72 | func TestOpenChangeLog(t *testing.T) { 73 | defer removeTmpDirs(t) 74 | 75 | dir := createRepo(t, commands) 76 | 77 | repo, err := hgo.OpenRepository(dir) 78 | if err != nil { 79 | t.Fatal("Unable to open repository.") 80 | } 81 | 82 | s := repo.NewStore() 83 | if s == nil { 84 | t.Fatal("Unable to create new store") 85 | } 86 | 87 | _, err = s.OpenChangeLog() 88 | if err != nil { 89 | t.Fatalf("Unable to open change log: %s", err) 90 | } 91 | } 92 | 93 | func TestNewStore(t *testing.T) { 94 | defer removeTmpDirs(t) 95 | 96 | dir := createRepo(t, commands) 97 | 98 | repo, err := hgo.OpenRepository(dir) 99 | if err != nil { 100 | t.Fatal("Unable to open repository.") 101 | } 102 | 103 | s := repo.NewStore() 104 | if s == nil { 105 | t.Fatal("Unable to create new store") 106 | } 107 | } 108 | 109 | func TestStoreOpenRevlog(t *testing.T) { 110 | defer removeTmpDirs(t) 111 | 112 | dir := createRepo(t, commands) 113 | 114 | repo, err := hgo.OpenRepository(dir) 115 | if err != nil { 116 | t.Fatal("Unable to open repository.") 117 | } 118 | 119 | s := repo.NewStore() 120 | if s == nil { 121 | t.Fatal("Unable to create new store") 122 | } 123 | 124 | _, err = s.OpenRevlog("f") 125 | if err != nil { 126 | t.Fatalf("Unable to open revlog: %s", err) 127 | } 128 | } 129 | 130 | func TestAddTag(t *testing.T) { 131 | defer removeTmpDirs(t) 132 | 133 | dir := createRepo(t, commands) 134 | 135 | repo, err := hgo.OpenRepository(dir) 136 | if err != nil { 137 | t.Fatal("Unable to open repository.") 138 | } 139 | 140 | s := repo.NewStore() 141 | if s == nil { 142 | t.Fatal("Unable to create new store") 143 | } 144 | 145 | cl, err := s.OpenChangeLog() 146 | if err != nil { 147 | t.Fatalf("Unable to open change log: %s", err) 148 | } 149 | 150 | globalTags, allTags := repo.Tags() 151 | if globalTags == nil { 152 | t.Fatal("Unable to get global tags") 153 | } 154 | if allTags == nil { 155 | t.Fatal("Unable to get all tags") 156 | } 157 | 158 | globalTags.Sort() 159 | allTags.Sort() 160 | allTags.Add("tip", cl.Tip().Id().Node()) 161 | } 162 | 163 | func TestLookup(t *testing.T) { 164 | defer removeTmpDirs(t) 165 | 166 | dir := createRepo(t, commands) 167 | 168 | repo, err := hgo.OpenRepository(dir) 169 | if err != nil { 170 | t.Fatal("Unable to open repository.") 171 | } 172 | 173 | s := repo.NewStore() 174 | if s == nil { 175 | t.Fatal("Unable to create new store") 176 | } 177 | 178 | cl, err := s.OpenChangeLog() 179 | if err != nil { 180 | t.Fatalf("Unable to open change log: %s", err) 181 | } 182 | 183 | _, err = hg_revlog.NodeIdRevSpec(revision).Lookup(cl) 184 | if err != nil { 185 | t.Errorf("Unable to get revision spec: %s", err) 186 | } 187 | } 188 | 189 | func TestBuildEntry(t *testing.T) { 190 | defer removeTmpDirs(t) 191 | 192 | dir := createRepo(t, commands) 193 | 194 | repo, err := hgo.OpenRepository(dir) 195 | if err != nil { 196 | t.Fatal("Unable to open repository.") 197 | } 198 | 199 | s := repo.NewStore() 200 | if s == nil { 201 | t.Fatal("Unable to create new store") 202 | } 203 | 204 | cl, err := s.OpenChangeLog() 205 | if err != nil { 206 | t.Fatalf("Unable to open change log: %s", err) 207 | } 208 | 209 | rec, err := hg_revlog.NodeIdRevSpec(revision).Lookup(cl) 210 | if err != nil { 211 | t.Errorf("Unable to get revision spec: %s", err) 212 | } 213 | 214 | fb := hg_revlog.NewFileBuilder() 215 | _, err = hg_changelog.BuildEntry(rec, fb) 216 | if err != nil { 217 | t.Errorf("Unable to build entry: %s", err) 218 | } 219 | } 220 | 221 | func TestBuildManifest(t *testing.T) { 222 | defer removeTmpDirs(t) 223 | 224 | dir := createRepo(t, commands) 225 | 226 | repo, err := hgo.OpenRepository(dir) 227 | if err != nil { 228 | t.Fatal("Unable to open repository.") 229 | } 230 | 231 | s := repo.NewStore() 232 | if s == nil { 233 | t.Fatal("Unable to create new store") 234 | } 235 | 236 | cl, err := s.OpenChangeLog() 237 | if err != nil { 238 | t.Fatalf("Unable to open change log: %s", err) 239 | } 240 | 241 | rec, err := hg_revlog.NodeIdRevSpec(revision).Lookup(cl) 242 | if err != nil { 243 | t.Errorf("Unable to get revision spec: %s", err) 244 | } 245 | 246 | fb := hg_revlog.NewFileBuilder() 247 | ce, err := hg_changelog.BuildEntry(rec, fb) 248 | if err != nil { 249 | t.Errorf("Unable to build entry: %s", err) 250 | } 251 | 252 | mlog, err := s.OpenManifests() 253 | if err != nil { 254 | t.Errorf("Unable to open manifest: %s", err) 255 | } 256 | 257 | rec2, err := mlog.LookupRevision(int(ce.Linkrev), ce.ManifestNode) 258 | if err != nil { 259 | t.Errorf("Unable to lookup revision: %s", err) 260 | } 261 | 262 | _, err = hg_store.BuildManifest(rec2, fb) 263 | if err != nil { 264 | t.Errorf("Unable to build manifest: %s", err) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /revlog/filebuilder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package revlog 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "os" 12 | 13 | "github.com/beyang/hgo/revlog/patch" 14 | ) 15 | 16 | func (fb *FileBuilder) swap() { 17 | fb.w, fb.w1 = fb.w1, fb.w 18 | } 19 | 20 | type DataCache interface { 21 | Get(int) []byte 22 | Store(int, []byte) 23 | } 24 | 25 | type noCache struct{} 26 | 27 | func (noCache) Get(int) (data []byte) { return } 28 | func (noCache) Store(int, []byte) {} 29 | 30 | type FileBuilder struct { 31 | w, w1 *patch.Joiner 32 | 33 | dataCache DataCache 34 | data dataHelper 35 | fileBuf bytes.Buffer 36 | metaBuf bytes.Buffer 37 | } 38 | 39 | func NewFileBuilder() (p *FileBuilder) { 40 | p = new(FileBuilder) 41 | p.w = patch.NewJoiner(600) 42 | p.w1 = patch.NewJoiner(600) 43 | p.data.tmp = bytes.NewBuffer(make([]byte, 0, 128)) 44 | return 45 | } 46 | 47 | type dataHelper struct { 48 | file DataReadCloser 49 | tmp *bytes.Buffer 50 | keepOpen bool 51 | } 52 | 53 | func (dh *dataHelper) Open(fileName string) (file DataReadCloser, err error) { 54 | if dh.file != nil { 55 | file = dh.file 56 | return 57 | } 58 | file, err = os.Open(fileName) 59 | if err == nil { 60 | dh.file = file 61 | } 62 | return 63 | } 64 | 65 | func (dh *dataHelper) TmpBuffer() *bytes.Buffer { 66 | return dh.tmp 67 | } 68 | 69 | func (p *FileBuilder) SetDataCache(dc DataCache) { 70 | p.dataCache = dc 71 | } 72 | func (p *FileBuilder) Bytes() []byte { 73 | return p.fileBuf.Bytes() 74 | } 75 | 76 | func (p *FileBuilder) KeepDataOpen() { 77 | p.data.keepOpen = true 78 | } 79 | func (p *FileBuilder) CloseData() (err error) { 80 | if p.data.file != nil { 81 | err = p.data.file.Close() 82 | p.data.file = nil 83 | } 84 | return 85 | } 86 | 87 | func (p *FileBuilder) PreparePatch(r *Rec) (f *FilePatch, err error) { 88 | var prevPatch []patch.Hunk 89 | rsav := r 90 | dc := p.dataCache 91 | if dc == nil { 92 | dc = noCache{} 93 | } 94 | 95 | if !p.data.keepOpen { 96 | defer p.CloseData() 97 | } 98 | 99 | for { 100 | d := dc.Get(r.i) 101 | if d == nil { 102 | d, err = r.GetData(&p.data) 103 | if err != nil { 104 | err = fmt.Errorf("rev %d: get data: %v", r.i, err) 105 | return 106 | } 107 | dc.Store(r.i, d) 108 | } 109 | if r.IsBase() { 110 | f = new(FilePatch) 111 | if rsav.IsStartOfBranch() { 112 | if r == rsav { 113 | // The normal case, rsav is a base revision, the 114 | // complete meta header is at the top of the data 115 | f.MetaData = scanMetaData(d) 116 | f.MetaSkip = len(f.MetaData) 117 | } else if len(prevPatch) > 0 { 118 | baseMeta := scanMetaData(d) 119 | skipFirst := false 120 | 121 | prevPatch[0].Adjust(func(begin, end int, data []byte) []byte { 122 | if n := len(baseMeta); n > 0 && begin >= 2 && end <= n-2 { 123 | // A rare case: There is a meta header at the top of the 124 | // data of the base revision (already parsed into tmp), but 125 | // there's also the first patch that modifies some content of the 126 | // meta header. (For a more robust solution the patches 127 | // following the first one would need to be checked too 128 | // whether they are located within the original meta header.) 129 | // An example is: 130 | // hgo revlog $GOROOT/.hg/store/data/test/fixedbugs/bug136.go.i 131 | // file revision 1 132 | b := &p.metaBuf 133 | b.Reset() 134 | b.Write(baseMeta[:begin]) 135 | b.Write(data) 136 | b.Write(baseMeta[end:]) 137 | f.MetaSkip = n 138 | f.MetaData = b.Bytes() 139 | skipFirst = true 140 | return data[:0] 141 | } 142 | 143 | // Another rare case, rsav is an incremental revision, the 144 | // meta header is at the top of the first hunk. 145 | // Example: hgo revlog -r 1 $PLAN9/.hg/store/data/src/cmd/dd.c.i 146 | if begin > 0 { 147 | return data 148 | } 149 | f.MetaData = scanMetaData(data) 150 | return data[len(f.MetaData):] 151 | }) 152 | 153 | if skipFirst { 154 | prevPatch = prevPatch[1:] 155 | } 156 | } 157 | } 158 | f.baseData = d 159 | f.patch = prevPatch 160 | f.rev = rsav 161 | f.fb = p 162 | return 163 | } 164 | hunks, err1 := patch.Parse(d) 165 | if err1 != nil { 166 | err = err1 167 | return 168 | } 169 | if prevPatch == nil { 170 | prevPatch = hunks 171 | } else { 172 | prevPatch = p.w.JoinPatches(hunks, prevPatch) 173 | p.swap() 174 | } 175 | r = r.Prev() 176 | } 177 | } 178 | 179 | func scanMetaData(d []byte) (meta []byte) { 180 | if len(d) <= 4 { 181 | return 182 | } 183 | if d[0] != '\001' || d[1] != '\n' { 184 | return 185 | } 186 | if i := bytes.Index(d[2:], []byte{'\001', '\n'}); i != -1 { 187 | meta = d[:i+4] 188 | } 189 | return 190 | } 191 | 192 | func (p *FileBuilder) BuildWrite(w io.Writer, r *Rec) (err error) { 193 | fp, err := p.PreparePatch(r) 194 | if err == nil { 195 | err = fp.Apply(w) 196 | } 197 | return 198 | } 199 | func (p *FileBuilder) Build(r *Rec) (file []byte, err error) { 200 | fp, err := p.PreparePatch(r) 201 | if err == nil { 202 | err = fp.Apply(nil) 203 | if err == nil { 204 | file = p.Bytes() 205 | } 206 | } 207 | return 208 | } 209 | 210 | type FilePatch struct { 211 | fb *FileBuilder 212 | rev *Rec 213 | baseData []byte 214 | patch []patch.Hunk 215 | 216 | MetaData []byte 217 | MetaSkip int 218 | } 219 | 220 | func (p *FilePatch) Apply(w io.Writer) (err error) { 221 | if w == nil { 222 | p.fb.fileBuf.Reset() 223 | w = &p.fb.fileBuf 224 | } 225 | 226 | r := p.rev 227 | 228 | h := r.Index.NewHash() 229 | for _, id := range sortedPair(r.Parent().Id(), r.Parent2().Id()) { 230 | h.Write([]byte(id)) 231 | } 232 | 233 | orig := p.baseData 234 | skip := 0 235 | nAdjust := 0 236 | if len(p.MetaData) > 0 { 237 | h.Write(p.MetaData) 238 | skip = p.MetaSkip 239 | nAdjust = len(p.MetaData) - skip 240 | 241 | } 242 | n, err := patch.Apply(io.MultiWriter(h, w), orig, skip, p.patch) 243 | if err != nil { 244 | return 245 | } 246 | n += nAdjust 247 | 248 | if n != int(r.FileLength) { 249 | err = fmt.Errorf("revlog: length of computed file differs from the expected value: %d != %d", n, r.FileLength) 250 | } else { 251 | fileId := NodeId(h.Sum(nil)) 252 | if !fileId.Eq(r.Id()) { 253 | err = fmt.Errorf("revlog: hash mismatch: internal error or corrupted data") 254 | } 255 | } 256 | return 257 | } 258 | -------------------------------------------------------------------------------- /cmd/hgo/cmdarchive.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "archive/tar" 9 | "archive/zip" 10 | "bytes" 11 | "compress/gzip" 12 | "io" 13 | "os" 14 | "strings" 15 | "time" 16 | 17 | "github.com/beyang/hgo/changelog" 18 | "github.com/beyang/hgo/revlog" 19 | "github.com/beyang/hgo/store" 20 | ) 21 | 22 | var cmdArchive = &Command{ 23 | UsageLine: "archive [-R dir] [-r rev] [-t type] [dest]", 24 | Short: "extract all files of the project at a certain revision", 25 | Long: ``, 26 | } 27 | 28 | var archiveType = cmdArchive.Flag.String("t", "files", "type of archive to be created") 29 | 30 | const archTemplate = `repo: {{(.Index.Record 0).Id.Node}} 31 | node: {{.Id}} 32 | branch: {{if .Branch}}{{.Branch}}{{else}}default{{end}} 33 | {{with .LatestTag (tags)}}{{range .Names}}latesttag: {{.}} 34 | {{end}}latesttagdistance: {{.Distance}} 35 | {{end}}` 36 | 37 | func init() { 38 | addStdFlags(cmdArchive) 39 | addRevFlag(cmdArchive) 40 | cmdArchive.Run = runArchive 41 | } 42 | 43 | type archiver interface { 44 | createFile(name string, mode int, sz int64, mTime time.Time) (io.Writer, error) 45 | symlink(name, target string, mTime time.Time) error 46 | Close() error 47 | } 48 | 49 | var amap = map[string]func(dest string) (archiver, string, error){ 50 | "tar": newTarArchiver, 51 | "tgz": newTarGzipArchiver, 52 | "zip": newZipArchiver, 53 | } 54 | 55 | func runArchive(cmd *Command, w io.Writer, args []string) { 56 | openRepository(args) 57 | rs := getRevisionSpec() 58 | b := revlog.NewFileBuilder() 59 | c, err := getChangeset(rs, b) 60 | if err != nil { 61 | fatalf("%s", err) 62 | } 63 | 64 | var ent *store.ManifestEnt 65 | link := revlog.NewLinkRevSpec(int(c.Linkrev)) 66 | link.FindPresent = func(rlist []*revlog.Rec) (index int, err error) { 67 | index, err = findPresentByNodeId(ent, rlist) 68 | return 69 | } 70 | 71 | mm, err := getManifest(link.Rev, c.ManifestNode, b) 72 | if err != nil { 73 | fatalf("%s", err) 74 | } 75 | st := repo.NewStore() 76 | 77 | fileArg := getFileArg(args) 78 | 79 | newArchive, ok := amap[*archiveType] 80 | if !ok { 81 | fatalf("unknown archive type: %s", *archiveType) 82 | } 83 | a, pathPfx, err := newArchive(fileArg) 84 | if err != nil { 85 | fatalf("%s", err) 86 | } 87 | 88 | err = createArchivalTxt(a, pathPfx, c) 89 | if err != nil { 90 | fatalf("%s", err) 91 | } 92 | pathPfx += "/" 93 | 94 | for i := range mm { 95 | ent = &mm[i] 96 | 97 | f, err := st.OpenRevlog(ent.FileName) 98 | if err != nil { 99 | fatalf("%s", err) 100 | } 101 | r, err := link.Lookup(f) 102 | if err != nil { 103 | fatalf("%s", err) 104 | } 105 | 106 | name := pathPfx + ent.FileName 107 | if ent.IsLink() { 108 | buf, err := b.Build(r) 109 | if err != nil { 110 | fatalf("%s", err) 111 | } 112 | err = a.symlink(name, string(buf), c.Date) 113 | if err != nil { 114 | fatalf("%s", err) 115 | } 116 | } else { 117 | var mode int 118 | if ent.IsExecutable() { 119 | mode = 0755 120 | } else { 121 | mode = 0644 122 | } 123 | p, err := b.PreparePatch(r) 124 | if err != nil { 125 | fatalf("%s", err) 126 | } 127 | 128 | w, err := a.createFile(name, mode, int64(r.FileLength)-int64(len(p.MetaData)), c.Date) 129 | if err != nil { 130 | fatalf("%s", err) 131 | } 132 | err = p.Apply(w) 133 | if err != nil { 134 | fatalf("%s", err) 135 | } 136 | } 137 | } 138 | err = a.Close() 139 | if err != nil { 140 | fatalf("%s", err) 141 | } 142 | } 143 | 144 | func createArchivalTxt(a archiver, pathPrefix string, c *changelog.Entry) (err error) { 145 | t, err := setupLogTemplate(archTemplate, globalTags.ById) 146 | if err != nil { 147 | return 148 | } 149 | var b bytes.Buffer 150 | err = t.Execute(&b, c) 151 | if err != nil { 152 | return 153 | } 154 | w, err := a.createFile(pathPrefix+"/.hg_archival.txt", 0644, int64(b.Len()), c.Date) 155 | if err != nil { 156 | return 157 | } 158 | _, err = b.WriteTo(w) 159 | return 160 | } 161 | 162 | type tarArchiver struct { 163 | *tar.Writer 164 | file io.WriteCloser 165 | h tar.Header 166 | } 167 | 168 | func newTarArchiver(dest string) (a archiver, pathPfx string, err error) { 169 | f, err := os.Create(dest) 170 | if err != nil { 171 | return 172 | } 173 | a = &tarArchiver{Writer: tar.NewWriter(f), file: f} 174 | pathPfx = stripExt(dest, ".tar") 175 | return 176 | } 177 | 178 | func (a *tarArchiver) createFile(name string, mode int, sz int64, mTime time.Time) (w io.Writer, err error) { 179 | hdr := a.initHeader(name, mode, mTime) 180 | hdr.Typeflag = tar.TypeReg 181 | hdr.Size = sz 182 | w = a.Writer 183 | err = a.WriteHeader(hdr) 184 | return 185 | } 186 | 187 | func (a *tarArchiver) symlink(name, target string, mTime time.Time) (err error) { 188 | hdr := a.initHeader(name, 0777, mTime) 189 | hdr.Typeflag = tar.TypeSymlink 190 | hdr.Linkname = target 191 | err = a.WriteHeader(hdr) 192 | return 193 | } 194 | 195 | func (a *tarArchiver) initHeader(name string, mode int, mTime time.Time) (hdr *tar.Header) { 196 | a.h = tar.Header{ /*Uname: "root", Gname: "root"*/ } 197 | hdr = &a.h 198 | hdr.Name = name 199 | hdr.ModTime = mTime 200 | hdr.Mode = int64(mode) 201 | return 202 | } 203 | 204 | func (a *tarArchiver) close() (err error) { 205 | err = a.Writer.Close() 206 | if err == nil { 207 | err = a.file.Close() 208 | } 209 | return 210 | } 211 | 212 | type tarGzipArchiver struct { 213 | tarArchiver 214 | zf io.Closer 215 | } 216 | 217 | func newTarGzipArchiver(dest string) (a archiver, pathPfx string, err error) { 218 | f, err := os.Create(dest) 219 | if err != nil { 220 | return 221 | } 222 | zf := gzip.NewWriter(f) 223 | a = &tarGzipArchiver{tarArchiver: tarArchiver{Writer: tar.NewWriter(zf), file: f}, zf: zf} 224 | pathPfx = stripExt(dest, ".tar.gz") 225 | pathPfx = stripExt(pathPfx, ".tgz") 226 | return 227 | } 228 | 229 | func (a *tarGzipArchiver) close() (err error) { 230 | err = a.Writer.Close() 231 | if err == nil { 232 | err = a.zf.Close() 233 | if err == nil { 234 | err = a.file.Close() 235 | } 236 | } 237 | return 238 | } 239 | 240 | type zipArchiver struct { 241 | *zip.Writer 242 | file io.WriteCloser 243 | h zip.FileHeader 244 | } 245 | 246 | func newZipArchiver(dest string) (a archiver, pathPfx string, err error) { 247 | f, err := os.Create(dest) 248 | if err != nil { 249 | return 250 | } 251 | a = &zipArchiver{Writer: zip.NewWriter(f), file: f} 252 | pathPfx = stripExt(dest, ".zip") 253 | return 254 | } 255 | 256 | func (a *zipArchiver) createFile(name string, mode int, sz int64, mTime time.Time) (w io.Writer, err error) { 257 | hdr := a.initHeader(name, os.FileMode(mode), mTime) 258 | hdr.UncompressedSize64 = uint64(sz) 259 | w, err = a.CreateHeader(hdr) 260 | return 261 | } 262 | 263 | func (a *zipArchiver) symlink(name, target string, mTime time.Time) (err error) { 264 | hdr := a.initHeader(name, 0777|os.ModeSymlink, mTime) 265 | w, err := a.CreateHeader(hdr) 266 | if err != nil { 267 | return 268 | } 269 | w.Write([]byte(target)) 270 | return 271 | } 272 | 273 | func (a *zipArchiver) initHeader(name string, mode os.FileMode, mTime time.Time) (hdr *zip.FileHeader) { 274 | hdr = new(zip.FileHeader) 275 | hdr.Name = name 276 | hdr.SetModTime(mTime) 277 | hdr.SetMode(os.FileMode(mode)) 278 | hdr.Method = zip.Deflate 279 | return 280 | } 281 | 282 | func (a *zipArchiver) close() (err error) { 283 | err = a.Writer.Close() 284 | if err == nil { 285 | err = a.file.Close() 286 | } 287 | return 288 | } 289 | 290 | func stripExt(fileName, ext string) (baseName string) { 291 | if strings.HasSuffix(fileName, ext) { 292 | baseName = fileName[:len(fileName)-len(ext)] 293 | } else { 294 | baseName = fileName 295 | } 296 | return 297 | } 298 | -------------------------------------------------------------------------------- /revlog/patch/patch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The patch package provides support for calculating and applying revlog patches 6 | package patch 7 | 8 | import ( 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | "io" 13 | ) 14 | 15 | var ( 16 | byteOrder = binary.BigEndian 17 | ) 18 | 19 | // A hunk describes how a certain region of original data 20 | // is to be modified. 21 | type Hunk struct { 22 | begin int 23 | end int 24 | data []byte 25 | } 26 | 27 | // Dump prints the information stored in a Hunk for debug purposes. 28 | func (h *Hunk) Dump(w io.Writer) { 29 | Δ := len(h.data) - (h.end - h.begin) 30 | fmt.Fprintf(w, "@@ %d..%d %+d @@\n", h.begin, h.end, Δ) 31 | w.Write(h.data) 32 | fmt.Fprint(w, "\n") 33 | } 34 | 35 | func (h *Hunk) Adjust(f func(begin, end int, data []byte) []byte) { 36 | h.data = f(h.begin, h.end, h.data) 37 | } 38 | 39 | // Parse converts a patch from its binary representation into a slice of Hunks. 40 | func Parse(data []byte) (hunks []Hunk, err error) { 41 | var h Hunk 42 | prevEnd := -1 43 | 44 | for len(data) > 0 { 45 | if len(data) < 3*4 { 46 | err = errors.New("corrupt hunk: less bytes than needed for hunk header") 47 | return 48 | } 49 | h.begin = int(byteOrder.Uint32(data[0:])) 50 | h.end = int(byteOrder.Uint32(data[4:])) 51 | 52 | if h.begin <= prevEnd { 53 | err = errors.New("corrupt hunk: overlapps with previous") 54 | return 55 | } 56 | if h.end < h.begin { 57 | err = errors.New("corrupt hunk: end position before beginning") 58 | return 59 | } 60 | prevEnd = h.end 61 | n := int(byteOrder.Uint32(data[8:])) 62 | data = data[12:] 63 | if len(data) < n { 64 | err = errors.New("patch: short read") 65 | return 66 | } 67 | h.data = data[:n] 68 | data = data[n:] 69 | hunks = append(hunks, h) 70 | } 71 | return 72 | } 73 | 74 | // A Joiner is a patch buffer that is the target of usually many 75 | // calls to its method JoinPatches. 76 | type Joiner struct { 77 | w []Hunk 78 | } 79 | 80 | // Creates a new Joiner. To avoid reallocations of the patch buffer 81 | // as the patch grows, an initial size may be stated. 82 | func NewJoiner(preAlloc int) *Joiner { 83 | return &Joiner{w: make([]Hunk, 0, preAlloc)} 84 | } 85 | 86 | func (j *Joiner) reset() { 87 | j.w = j.w[:0] 88 | } 89 | 90 | // Append a single hunk to the resulting patch, probably 91 | // merging it with the previous hunk. 92 | func (j *Joiner) emit(h Hunk) { 93 | if len(h.data) == 0 && h.begin == h.end { 94 | return 95 | } 96 | 97 | if n := len(j.w); n > 0 { 98 | p := &j.w[n-1] 99 | if p.end == h.begin { 100 | if len(h.data) == 0 { 101 | p.end = h.end 102 | return 103 | } 104 | if len(p.data) == 0 { 105 | p.data = h.data 106 | p.end = h.end 107 | return 108 | } 109 | } 110 | } 111 | 112 | j.w = append(j.w, h) 113 | } 114 | 115 | // Append all remaining hunks in h to the Joiner's patch. 116 | func (j *Joiner) emitTail(h []Hunk, roff0, roff int) { 117 | adjust := roff0 != 0 || roff != 0 118 | for i := range h { 119 | if adjust { 120 | h[i].begin -= roff0 121 | h[i].end -= roff 122 | roff0 = roff 123 | } 124 | } 125 | j.w = append(j.w, h...) 126 | } 127 | 128 | // With ‘left’ and ‘right’ being patches of two adjacent revlog revisions, 129 | // JoinPatches propagates hunk by hunk from the right to the left side, 130 | // probably intersecting hunks one or more times. The resulting patch will be 131 | // stored into the Joiner. Both right and left hunks may be altered. 132 | func (j *Joiner) JoinPatches(left, right []Hunk) []Hunk { 133 | var roff, roff0 int 134 | 135 | // Loop over the hunks on the left side, and sort, 136 | // one after another, hunks from the right into the output 137 | // stream, probably splitting or overlapping left side hunks. 138 | 139 | j.reset() 140 | 141 | for i, lh := range left { 142 | again: 143 | if len(right) == 0 { 144 | // no hunk remains on the right #1 145 | j.emit(lh) 146 | j.emitTail(left[i+1:], 0, 0) 147 | break 148 | } 149 | rh := right[0] 150 | ld := lh.end - lh.begin 151 | 152 | // the number of bytes lh will add 153 | Δlh := len(lh.data) - ld 154 | 155 | // translate lh's coordinates to the right side 156 | begin := lh.begin + roff 157 | end := begin + len(lh.data) 158 | 159 | Δfrontvis := rh.begin - begin 160 | switch { 161 | case begin >= rh.end: 162 | // rh comes before lh #2 163 | rh.begin -= roff0 164 | rh.end -= roff 165 | j.emit(rh) 166 | patchStats.inserted++ 167 | goto nextrh 168 | 169 | case end <= rh.begin: 170 | // lh comes before rh #3 171 | roff += Δlh 172 | roff0 = roff 173 | j.emit(lh) 174 | 175 | case Δfrontvis > 0: 176 | // lh starts before rh #4 177 | 178 | if end > rh.end { 179 | // rh is embedded in lh #5 180 | 181 | // emit front hunk 182 | h := lh 183 | h.data = h.data[:Δfrontvis] 184 | j.emit(h) 185 | 186 | roff += rh.end - begin - (ld) 187 | 188 | // trim lh 189 | lh.begin = lh.end 190 | lh.data = lh.data[rh.end-begin:] 191 | 192 | // emit rh 193 | rh.begin = lh.end 194 | rh.end = lh.end 195 | j.emit(rh) 196 | 197 | roff0 = roff 198 | patchStats.over.mid++ 199 | goto nextrh 200 | 201 | } else { 202 | // rh covers the end of the current lh #6 203 | 204 | // trim lh 205 | lh.end = lh.begin 206 | lh.data = lh.data[:Δfrontvis] 207 | j.emit(lh) 208 | roff0 = roff + (rh.begin - begin) 209 | roff += Δlh 210 | patchStats.over.bottom++ 211 | } 212 | case end > rh.end: 213 | // rh covers the beginning of current lh #7 214 | 215 | // trim lh 216 | lh.data = lh.data[rh.end-begin:] 217 | 218 | // emit rh 219 | rh.begin -= roff0 220 | roff += rh.end - begin - ld 221 | roff0 = roff 222 | rh.end = lh.end 223 | j.emit(rh) 224 | 225 | lh.begin = lh.end 226 | patchStats.over.top++ 227 | goto nextrh 228 | 229 | default: 230 | // rh covers lh fully #8 231 | roff += Δlh 232 | patchStats.over.full++ 233 | 234 | } 235 | continue 236 | nextrh: 237 | right = right[1:] 238 | roff0 = roff 239 | goto again 240 | } 241 | 242 | if len(right) > 0 { 243 | j.emitTail(right, roff0, roff) 244 | patchStats.inserted += len(right) 245 | } 246 | return j.w 247 | } 248 | 249 | var patchStats struct { 250 | inserted int 251 | over struct { 252 | top, mid, bottom, full int 253 | } 254 | } 255 | 256 | // Apply a patch to a slice of original bytes, writing the output to an io.Writer. 257 | // The number of bytes written, i.e. the size of the resulting file, is returned. 258 | func Apply(w io.Writer, orig []byte, pos int, patch []Hunk) (n int, err error) { 259 | var nw int 260 | 261 | n = pos 262 | for _, h := range patch { 263 | pos, nw, err = h.apply(w, orig, pos) 264 | n += nw 265 | if err != nil { 266 | return 267 | } 268 | } 269 | if len(orig) < pos { 270 | err = &posError{"current position is behind the end of the original file", pos, 0} 271 | return 272 | } 273 | nw, err = w.Write(orig[pos:]) 274 | if err != nil { 275 | return 276 | } 277 | n += nw 278 | return 279 | } 280 | 281 | func (h *Hunk) apply(w io.Writer, orig []byte, pos int) (newPos, n int, err error) { 282 | var nw int 283 | 284 | switch { 285 | case len(orig) < h.begin: 286 | err = &posError{"hunk starts after the end of the original file", pos, h.begin} 287 | 288 | case pos < 0 || len(orig) < pos: 289 | err = &posError{"current position is not within the boundaries of the original file", pos, 0} 290 | 291 | case h.begin < 0: 292 | err = &posError{"negative hunk start position", pos, h.begin} 293 | 294 | case h.begin < pos: 295 | err = &posError{"hunk starts before current position", pos, h.begin} 296 | 297 | default: 298 | nw, err = w.Write(orig[pos:h.begin]) 299 | if err != nil { 300 | break 301 | } 302 | n += nw 303 | nw, err = w.Write(h.data) 304 | if err != nil { 305 | break 306 | } 307 | n += nw 308 | newPos = h.end 309 | } 310 | 311 | return 312 | } 313 | 314 | type posError struct { 315 | message string 316 | origPos int 317 | hunkBegin int 318 | } 319 | 320 | func (e *posError) Error() string { 321 | return "patch: corrupt data or internal error: " + e.message 322 | } 323 | -------------------------------------------------------------------------------- /revlog/revlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The hgo Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package revlog provides read access to RevlogNG files. 6 | package revlog 7 | 8 | // http://mercurial.selenic.com/wiki/Repository 9 | // http://mercurial.selenic.com/wiki/RevlogNG 10 | 11 | import ( 12 | "bufio" 13 | "bytes" 14 | "compress/zlib" 15 | "encoding/binary" 16 | "errors" 17 | "fmt" 18 | "io" 19 | "os" 20 | "strconv" 21 | ) 22 | 23 | var ( 24 | byteOrder = binary.BigEndian 25 | ) 26 | 27 | const ( 28 | knownFlagsMask uint16 = 1 29 | inlineData uint16 = 1 30 | versionPos uint32 = 16 31 | ) 32 | 33 | type Name interface { 34 | Index() string 35 | Data() string 36 | } 37 | 38 | type record struct { 39 | OffsetHi uint16 40 | OffsetLo uint32 41 | Flags uint16 42 | DataLength uint32 43 | FileLength uint32 44 | Baserev rev 45 | Linkrev rev 46 | Parent1 rev 47 | Parent2 rev 48 | NodeId NodeId 49 | } 50 | 51 | type rev uint32 52 | 53 | func (r *rev) parse(buf []byte) { 54 | *r = rev(byteOrder.Uint32(buf)) 55 | } 56 | 57 | func (r rev) isNull() bool { 58 | return r == ^rev(0) 59 | } 60 | 61 | func (r rev) String() string { 62 | if r.isNull() { 63 | return "null" 64 | } 65 | return strconv.FormatUint(uint64(r), 10) 66 | } 67 | 68 | func (r *record) offset() int64 { 69 | return int64(r.OffsetHi)<<32 | int64(r.OffsetLo) 70 | } 71 | 72 | func (r *record) decode(buf []byte, cur rev, rPrev *record) (err error) { 73 | if cur > 0 { 74 | r.OffsetHi = byteOrder.Uint16(buf[0:]) 75 | r.OffsetLo = byteOrder.Uint32(buf[2:]) 76 | } 77 | 78 | r.Flags = byteOrder.Uint16(buf[6:]) 79 | 80 | r.DataLength = byteOrder.Uint32(buf[8:]) 81 | r.FileLength = byteOrder.Uint32(buf[12:]) 82 | 83 | r.Baserev.parse(buf[16:]) 84 | r.Linkrev.parse(buf[20:]) 85 | r.Parent1.parse(buf[24:]) 86 | r.Parent2.parse(buf[28:]) 87 | 88 | id := make([]byte, 32) 89 | copy(id, buf[32:]) 90 | r.NodeId = id 91 | 92 | // sanity check: 93 | switch { 94 | case cur == 0 && r.Baserev != 0: 95 | err = errors.New("first record not a base record") 96 | return 97 | case cur < r.Baserev: 98 | case !r.Parent1.isNull() && cur <= r.Parent1: 99 | case !r.Parent2.isNull() && cur <= r.Parent2: 100 | case rPrev != nil && r.offset() < rPrev.offset(): 101 | default: 102 | return 103 | } 104 | err = errors.New("revlog: corrupted record") 105 | return 106 | } 107 | 108 | func parseV1Flags(buf []byte) (flags uint16, err error) { 109 | offsetHi := byteOrder.Uint16(buf[0:]) 110 | offsetLo := byteOrder.Uint32(buf[2:]) 111 | flags = offsetHi 112 | if flags & ^knownFlagsMask != 0 { 113 | err = errors.New("unknown flags") 114 | return 115 | } 116 | if version := offsetLo >> versionPos; version != 1 { 117 | err = errors.New("unknown version") 118 | } 119 | return 120 | } 121 | 122 | type Rec struct { 123 | i int 124 | *record 125 | Index *Index 126 | } 127 | 128 | func (r *Rec) IsBase() bool { 129 | return r.i == int(r.Baserev) 130 | } 131 | func (r *Rec) BaseRev() int { 132 | return int(r.Baserev) 133 | } 134 | func (r *Rec) Prev() *Rec { 135 | if r.i == 0 { 136 | return &r.Index.null 137 | } 138 | return r.Index.Record(r.i - 1) 139 | } 140 | func (r *Rec) Next() *Rec { 141 | return r.Index.Record(r.i + 1) 142 | } 143 | func (r *Rec) Parent() *Rec { 144 | return r.parent(r.Parent1) 145 | } 146 | func (r *Rec) Parent2() *Rec { 147 | return r.parent(r.record.Parent2) 148 | } 149 | 150 | func (r *Rec) Parent1NotPrevious() bool { 151 | p := r.record.Parent1 152 | return r.Parent2Present() || int(p) != r.i-1 && !p.isNull() 153 | } 154 | 155 | func (r *Rec) Parent2Present() bool { 156 | p := r.record.Parent2 157 | return !p.isNull() 158 | } 159 | 160 | func (r *Rec) IsLeaf() (yes bool) { 161 | tail := r.Index.index[r.i+1:] 162 | for i := range tail { 163 | switch rev(r.i) { 164 | case tail[i].Parent1, tail[i].Parent2: 165 | return 166 | } 167 | } 168 | yes = true 169 | return 170 | } 171 | 172 | func (r *Rec) IsStartOfBranch() bool { 173 | return r.record.Parent1.isNull() && r.record.Parent2.isNull() 174 | } 175 | 176 | func (r *Rec) FileRev() int { 177 | return r.i 178 | } 179 | func (r *Rec) parent(idx rev) *Rec { 180 | if r == nil || idx.isNull() { 181 | return &r.Index.null 182 | } 183 | return r.Index.Record(int(idx)) 184 | } 185 | 186 | func (r *Rec) Id() NodeId { 187 | return r.Index.NewNodeId(r.record.NodeId[:]) 188 | } 189 | 190 | type DataReadCloser interface { 191 | io.ReaderAt 192 | io.Closer 193 | } 194 | 195 | type Index struct { 196 | name Name 197 | version int 198 | 199 | flags struct { 200 | inlineData bool 201 | } 202 | index []record 203 | data []byte 204 | v1nodeid 205 | null Rec 206 | } 207 | 208 | func (i *Index) Tip() *Rec { 209 | return i.Record(len(i.index) - 1) 210 | } 211 | 212 | func (i *Index) Null() *Rec { 213 | return &i.null 214 | } 215 | func Open(name Name) (rlog *Index, err error) { 216 | var ( 217 | data []byte 218 | idata uint32 219 | inline bool 220 | r, rPrev *record 221 | ) 222 | 223 | f, err := os.Open(name.Index()) 224 | if err != nil { 225 | return 226 | } 227 | defer f.Close() 228 | 229 | fi, err := f.Stat() 230 | if err != nil { 231 | return 232 | } 233 | 234 | index := make([]record, fi.Size()/64) 235 | i := 0 236 | br := bufio.NewReader(f) 237 | buf := make([]byte, 64) 238 | for ; ; i++ { 239 | if _, err1 := io.ReadFull(br, buf); err1 != nil { 240 | if err1 == io.EOF { 241 | break 242 | } 243 | err = err1 244 | return 245 | } 246 | if i == len(index) { 247 | old := index 248 | index = make([]record, 2*len(index)) 249 | copy(index, old) 250 | } 251 | if i == 0 { 252 | flags, err1 := parseV1Flags(buf) 253 | if err1 != nil { 254 | err = err1 255 | return 256 | } 257 | inline = flags&inlineData != 0 258 | } 259 | r = &index[i] 260 | if err = r.decode(buf, rev(i), rPrev); err != nil { 261 | return 262 | } 263 | rPrev = r 264 | 265 | if inline { 266 | data = append(data, make([]byte, r.DataLength)...) 267 | if _, err = io.ReadFull(br, data[idata:]); err != nil { 268 | return 269 | } 270 | idata += r.DataLength 271 | } 272 | } 273 | 274 | rlog = new(Index) 275 | rlog.data = data 276 | rlog.index = index[:i] 277 | rlog.name = name 278 | rlog.flags.inlineData = inline 279 | rlog.null = Rec{ 280 | i: -1, 281 | Index: rlog, 282 | record: &record{NodeId: make([]byte, 32)}, 283 | } 284 | return 285 | } 286 | 287 | func (rv *Index) Record(i int) *Rec { 288 | return &Rec{i, &rv.index[i], rv} 289 | } 290 | 291 | var ErrRevNotFound = errors.New("revision not found") 292 | 293 | func (rv *Index) Dump(w io.Writer) { 294 | for i, _ := range rv.index { 295 | fmt.Fprintf(w, "%d:\t%v\n", i, rv.index[i]) 296 | } 297 | } 298 | 299 | type DataHelper interface { 300 | Open(string) (DataReadCloser, error) 301 | TmpBuffer() *bytes.Buffer 302 | } 303 | 304 | func (r *Rec) GetData(dh DataHelper) (data []byte, err error) { 305 | if r.DataLength == 0 { 306 | return 307 | } 308 | 309 | defer func() { 310 | if r := recover(); r != nil { 311 | err = r.(error) 312 | } 313 | }() 314 | 315 | var dataType byte 316 | rv := r.Index 317 | if rv.flags.inlineData { 318 | o := int(r.offset()) 319 | data = rv.data[o : o+int(r.DataLength)] 320 | dataType = data[0] 321 | } else { 322 | f, err1 := dh.Open(rv.name.Data()) 323 | if err1 != nil { 324 | err = err1 325 | return 326 | } 327 | o := r.offset() 328 | sr := io.NewSectionReader(f, o, int64(r.DataLength)) 329 | b := []byte{0} 330 | if n, err1 := sr.ReadAt(b, 0); n != 1 { 331 | err = err1 332 | return 333 | } 334 | dataType = b[0] 335 | if dataType == 'x' { 336 | buf := dh.TmpBuffer() 337 | buf.Reset() 338 | if _, err = buf.ReadFrom(sr); err != nil { 339 | return 340 | } 341 | data = buf.Bytes() 342 | } else { 343 | data = make([]byte, r.DataLength) 344 | if _, err = io.ReadFull(sr, data); err != nil { 345 | data = nil 346 | return 347 | } 348 | } 349 | } 350 | switch dataType { 351 | default: 352 | err = errors.New("unknown data type") 353 | case 'u': 354 | data = data[1:] 355 | case 'x': 356 | zr, err1 := zlib.NewReader(bytes.NewReader(data)) 357 | data = nil 358 | if err1 != nil { 359 | err = err1 360 | return 361 | } 362 | if !r.IsBase() { 363 | data = make([]byte, r.DataLength*2) 364 | n := 0 365 | for { 366 | nr, err1 := io.ReadFull(zr, data[n:]) 367 | n += nr 368 | if err1 != nil { 369 | if err1 == io.ErrUnexpectedEOF || err1 == io.EOF { 370 | data = data[:n] 371 | break 372 | } 373 | err = err1 374 | return 375 | } else { 376 | data = append(data, make([]byte, cap(data))...) 377 | } 378 | } 379 | } else { 380 | data = make([]byte, r.FileLength) 381 | _, err = io.ReadFull(zr, data) 382 | } 383 | zr.Close() 384 | case 0: 385 | } 386 | return 387 | } 388 | 389 | // IsDescendant follows all branches that originate in r 390 | // until it passes record rev2. If that record is found to 391 | // be on one of these branches, it is a descendant of r. 392 | func (r *Rec) IsDescendant(rev2 int) (is bool) { 393 | // A region refers to a number of adjacent records in the revlog 394 | // that all have r as an ancestor. 395 | type region struct{ from, to int } 396 | var regions []region 397 | var cur region 398 | 399 | insideRegion := true 400 | cur.from = r.FileRev() 401 | index := r.Index.index 402 | 403 | if rev2 >= len(index) { 404 | return 405 | } 406 | 407 | L: 408 | for i := cur.from + 1; i <= rev2; i++ { 409 | // If parent1 or parent2 are found to point into 410 | // one of the regions, i is the index of a descendant 411 | // record. 412 | p1, p2 := index[i].Parent1, index[i].Parent2 413 | 414 | if insideRegion { 415 | if !p1.isNull() && int(p1) >= cur.from { 416 | continue 417 | } 418 | if !p2.isNull() && int(p2) >= cur.from { 419 | continue 420 | } 421 | } 422 | 423 | for _, r := range regions { 424 | switch { 425 | case !p1.isNull() && r.from <= int(p1) && int(p1) <= r.to: 426 | fallthrough 427 | case !p2.isNull() && r.from <= int(p2) && int(p2) <= r.to: 428 | if !insideRegion { 429 | cur.from = i 430 | insideRegion = true 431 | } 432 | continue L 433 | } 434 | } 435 | if insideRegion { 436 | insideRegion = false 437 | cur.to = i - 1 438 | regions = append(regions, cur) 439 | } 440 | } 441 | is = insideRegion 442 | return 443 | } 444 | --------------------------------------------------------------------------------