diff options
Diffstat (limited to 'misc/dashboard/builder/vcs.go')
-rw-r--r-- | misc/dashboard/builder/vcs.go | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/misc/dashboard/builder/vcs.go b/misc/dashboard/builder/vcs.go new file mode 100644 index 000000000..63198a34b --- /dev/null +++ b/misc/dashboard/builder/vcs.go @@ -0,0 +1,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> +` |