1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/xml"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
)
// Repo represents a mercurial repository.
type Repo struct {
Path string
sync.Mutex
}
// RemoteRepo constructs a *Repo representing a remote repository.
func RemoteRepo(url string) *Repo {
return &Repo{
Path: url,
}
}
// Clone clones the current Repo to a new destination
// returning a new *Repo if successful.
func (r *Repo) Clone(path, rev string) (*Repo, error) {
r.Lock()
defer r.Unlock()
if err := run(*cmdTimeout, nil, *buildroot, r.hgCmd("clone", "-r", rev, r.Path, path)...); err != nil {
return nil, err
}
return &Repo{
Path: path,
}, nil
}
// UpdateTo updates the working copy of this Repo to the
// supplied revision.
func (r *Repo) UpdateTo(hash string) error {
r.Lock()
defer r.Unlock()
return run(*cmdTimeout, nil, r.Path, r.hgCmd("update", hash)...)
}
// Exists reports whether this Repo represents a valid Mecurial repository.
func (r *Repo) Exists() bool {
fi, err := os.Stat(filepath.Join(r.Path, ".hg"))
if err != nil {
return false
}
return fi.IsDir()
}
// Pull pulls changes from the default path, that is, the path
// this Repo was cloned from.
func (r *Repo) Pull() error {
r.Lock()
defer r.Unlock()
return run(*cmdTimeout, nil, r.Path, r.hgCmd("pull")...)
}
// Log returns the changelog for this repository.
func (r *Repo) Log() ([]HgLog, error) {
if err := r.Pull(); err != nil {
return nil, err
}
const N = 50 // how many revisions to grab
r.Lock()
defer r.Unlock()
data, _, err := runLog(*cmdTimeout, nil, r.Path, r.hgCmd("log",
"--encoding=utf-8",
"--limit="+strconv.Itoa(N),
"--template="+xmlLogTemplate)...,
)
if err != nil {
return nil, err
}
var logStruct struct {
Log []HgLog
}
err = xml.Unmarshal([]byte("<Top>"+data+"</Top>"), &logStruct)
if err != nil {
log.Printf("unmarshal hg log: %v", err)
return nil, err
}
return logStruct.Log, nil
}
// FullHash returns the full hash for the given Mercurial revision.
func (r *Repo) FullHash(rev string) (string, error) {
r.Lock()
defer r.Unlock()
s, _, err := runLog(*cmdTimeout, nil, r.Path,
r.hgCmd("log",
"--encoding=utf-8",
"--rev="+rev,
"--limit=1",
"--template={node}")...,
)
if err != nil {
return "", nil
}
s = strings.TrimSpace(s)
if s == "" {
return "", fmt.Errorf("cannot find revision")
}
if len(s) != 40 {
return "", fmt.Errorf("hg returned invalid hash " + s)
}
return s, nil
}
func (r *Repo) hgCmd(args ...string) []string {
return append([]string{"hg", "--config", "extensions.codereview=!"}, args...)
}
// HgLog represents a single Mercurial revision.
type HgLog struct {
Hash string
Author string
Date string
Desc string
Parent string
// Internal metadata
added bool
}
// xmlLogTemplate is a template to pass to Mercurial to make
// hg log print the log in valid XML for parsing with xml.Unmarshal.
const xmlLogTemplate = `
<Log>
<Hash>{node|escape}</Hash>
<Parent>{parent|escape}</Parent>
<Author>{author|escape}</Author>
<Date>{date|rfc3339date}</Date>
<Desc>{desc|escape}</Desc>
</Log>
`
|