mirror of
https://github.com/golang/go
synced 2024-11-19 00:24:41 -07:00
b96847cb92
Currently performance builders crash with: hg log: unmarshal Mercurial log: XML syntax error on line 4991: illegal character code U+001B R=adg CC=golang-codereviews https://golang.org/cl/110060046
211 lines
4.7 KiB
Go
211 lines
4.7 KiB
Go
// 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 (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"code.google.com/p/go.tools/go/vcs"
|
|
)
|
|
|
|
// Repo represents a mercurial repository.
|
|
type Repo struct {
|
|
Path string
|
|
Master *vcs.RepoRoot
|
|
sync.Mutex
|
|
}
|
|
|
|
// RemoteRepo constructs a *Repo representing a remote repository.
|
|
func RemoteRepo(url, path string) (*Repo, error) {
|
|
rr, err := vcs.RepoRootForImportPath(url, *verbose)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Repo{
|
|
Path: path,
|
|
Master: rr,
|
|
}, nil
|
|
}
|
|
|
|
// 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()
|
|
|
|
err := timeout(*cmdTimeout, func() error {
|
|
downloadPath := r.Path
|
|
if !r.Exists() {
|
|
downloadPath = r.Master.Repo
|
|
}
|
|
|
|
err := r.Master.VCS.CreateAtRev(path, downloadPath, rev)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return r.Master.VCS.TagSync(path, "")
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Repo{
|
|
Path: path,
|
|
Master: r.Master,
|
|
}, nil
|
|
}
|
|
|
|
// Export exports the current Repo at revision rev to a new destination.
|
|
func (r *Repo) Export(path, rev string) error {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
downloadPath := r.Path
|
|
if !r.Exists() {
|
|
_, err := r.Clone(path, rev)
|
|
return err
|
|
}
|
|
|
|
args := []string{r.Master.VCS.Cmd, "archive", "-t", "files", "-r", rev, path}
|
|
if err := run(*cmdTimeout, nil, downloadPath, args...); err != nil {
|
|
return fmt.Errorf("executing %s: %v", strings.Join(args, " "), err)
|
|
}
|
|
return 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 timeout(*cmdTimeout, func() error {
|
|
return r.Master.VCS.TagSync(r.Path, hash)
|
|
})
|
|
}
|
|
|
|
// Exists reports whether this Repo represents a valid Mecurial repository.
|
|
func (r *Repo) Exists() bool {
|
|
fi, err := os.Stat(filepath.Join(r.Path, "."+r.Master.VCS.Cmd))
|
|
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 timeout(*cmdTimeout, func() error {
|
|
return r.Master.VCS.Download(r.Path)
|
|
})
|
|
}
|
|
|
|
// Log returns the changelog for this repository.
|
|
func (r *Repo) Log() ([]HgLog, error) {
|
|
if err := r.Pull(); err != nil {
|
|
return nil, err
|
|
}
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
var logStruct struct {
|
|
Log []HgLog
|
|
}
|
|
err := timeout(*cmdTimeout, func() error {
|
|
data, err := r.Master.VCS.Log(r.Path, xmlLogTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We have a commit with description that contains 0x1b byte.
|
|
// Mercurial does not escape it, but xml.Unmarshal does not accept it.
|
|
data = bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1)
|
|
|
|
err = xml.Unmarshal([]byte("<Top>"+string(data)+"</Top>"), &logStruct)
|
|
if err != nil {
|
|
return fmt.Errorf("unmarshal %s log: %v", r.Master.VCS, err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i, log := range logStruct.Log {
|
|
// Let's pretend there can be only one parent.
|
|
if log.Parent != "" && strings.Contains(log.Parent, " ") {
|
|
logStruct.Log[i].Parent = strings.Split(log.Parent, " ")[0]
|
|
}
|
|
}
|
|
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()
|
|
|
|
var hash string
|
|
err := timeout(*cmdTimeout, func() error {
|
|
data, err := r.Master.VCS.LogAtRev(r.Path, rev, "{node}")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s := strings.TrimSpace(string(data))
|
|
if s == "" {
|
|
return fmt.Errorf("cannot find revision")
|
|
}
|
|
if len(s) != 40 {
|
|
return fmt.Errorf("%s returned invalid hash: %s", r.Master.VCS, s)
|
|
}
|
|
hash = s
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return hash, nil
|
|
}
|
|
|
|
// HgLog represents a single Mercurial revision.
|
|
type HgLog struct {
|
|
Hash string
|
|
Author string
|
|
Date string
|
|
Desc string
|
|
Parent string
|
|
Branch string
|
|
Files string
|
|
|
|
// Internal metadata
|
|
added bool
|
|
bench bool // needs to be benchmarked?
|
|
}
|
|
|
|
// xmlLogTemplate is a template to pass to Mercurial to make
|
|
// hg log print the log in valid XML for parsing with xml.Unmarshal.
|
|
// Can not escape branches and files, because it crashes python with:
|
|
// AttributeError: 'NoneType' object has no attribute 'replace'
|
|
const xmlLogTemplate = `
|
|
<Log>
|
|
<Hash>{node|escape}</Hash>
|
|
<Parent>{parents}</Parent>
|
|
<Author>{author|escape}</Author>
|
|
<Date>{date|rfc3339date}</Date>
|
|
<Desc>{desc|escape}</Desc>
|
|
<Branch>{branches}</Branch>
|
|
<Files>{files}</Files>
|
|
</Log>
|
|
`
|