// 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(""+data+""), &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 = ` {node|escape} {parent|escape} {author|escape} {date|rfc3339date} {desc|escape} `