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 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
// Package bzr offers an interface to manage branches of the Bazaar VCS.
package bzr
import (
"bytes"
"fmt"
"os"
"os/exec"
"path"
"strings"
)
// Branch represents a Bazaar branch.
type Branch struct {
location string
env []string
}
// New returns a new Branch for the Bazaar branch at location.
func New(location string) *Branch {
b := &Branch{location, cenv()}
if _, err := os.Stat(location); err == nil {
stdout, _, err := b.bzr("root")
if err == nil {
// Need to trim \r as well as \n for Windows compatibility
b.location = strings.TrimRight(string(stdout), "\r\n")
}
}
return b
}
// cenv returns a copy of the current process environment with LC_ALL=C.
func cenv() []string {
env := os.Environ()
for i, pair := range env {
if strings.HasPrefix(pair, "LC_ALL=") {
env[i] = "LC_ALL=C"
return env
}
}
return append(env, "LC_ALL=C")
}
// Location returns the location of branch b.
func (b *Branch) Location() string {
return b.location
}
// Join returns b's location with parts appended as path components.
// In other words, if b's location is "lp:foo", and parts is {"bar, baz"},
// Join returns "lp:foo/bar/baz".
func (b *Branch) Join(parts ...string) string {
return path.Join(append([]string{b.location}, parts...)...)
}
func (b *Branch) bzr(subcommand string, args ...string) (stdout, stderr []byte, err error) {
cmd := exec.Command("bzr", append([]string{subcommand}, args...)...)
if _, err := os.Stat(b.location); err == nil {
cmd.Dir = b.location
}
errbuf := &bytes.Buffer{}
cmd.Stderr = errbuf
cmd.Env = b.env
stdout, err = cmd.Output()
// Some commands fail with exit status 0 (e.g. bzr root). :-(
if err != nil || bytes.Contains(errbuf.Bytes(), []byte("ERROR")) {
var errmsg string
if err != nil {
errmsg = err.Error()
}
return nil, nil, fmt.Errorf(`error running "bzr %s": %s%s%s`, subcommand, stdout, errbuf.Bytes(), errmsg)
}
return stdout, errbuf.Bytes(), err
}
// Init intializes a new branch at b's location.
func (b *Branch) Init() error {
_, _, err := b.bzr("init", b.location)
return err
}
// Add adds to b the path resultant from calling b.Join(parts...).
func (b *Branch) Add(parts ...string) error {
_, _, err := b.bzr("add", b.Join(parts...))
return err
}
// Commit commits pending changes into b.
func (b *Branch) Commit(message string) error {
_, _, err := b.bzr("commit", "-q", "-m", message)
return err
}
// RevisionId returns the Bazaar revision id for the tip of b.
func (b *Branch) RevisionId() (string, error) {
stdout, stderr, err := b.bzr("revision-info", "-d", b.location)
if err != nil {
return "", err
}
pair := bytes.Fields(stdout)
if len(pair) != 2 {
return "", fmt.Errorf(`invalid output from "bzr revision-info": %s%s`, stdout, stderr)
}
id := string(pair[1])
if id == "null:" {
return "", fmt.Errorf("branch has no content")
}
return id, nil
}
// PushLocation returns the default push location for b.
func (b *Branch) PushLocation() (string, error) {
stdout, _, err := b.bzr("info", b.location)
if err != nil {
return "", err
}
if i := bytes.Index(stdout, []byte("push branch:")); i >= 0 {
return string(stdout[i+13 : i+bytes.IndexAny(stdout[i:], "\r\n")]), nil
}
return "", fmt.Errorf("no push branch location defined")
}
// PushAttr holds options for the Branch.Push method.
type PushAttr struct {
Location string // Location to push to. Use the default push location if empty.
Remember bool // Whether to remember the location being pushed to as the default.
}
// Push pushes any new revisions in b to attr.Location if that's
// provided, or to the default push location otherwise.
// See PushAttr for other options.
func (b *Branch) Push(attr *PushAttr) error {
var args []string
if attr != nil {
if attr.Remember {
args = append(args, "--remember")
}
if attr.Location != "" {
args = append(args, attr.Location)
}
}
_, _, err := b.bzr("push", args...)
return err
}
// CheckClean returns an error if 'bzr status' is not clean.
func (b *Branch) CheckClean() error {
stdout, _, err := b.bzr("status", b.location)
if err != nil {
return err
}
if bytes.Count(stdout, []byte{'\n'}) == 1 && bytes.Contains(stdout, []byte(`See "bzr shelve --list" for details.`)) {
return nil // Shelves are fine.
}
if len(stdout) > 0 {
return fmt.Errorf("branch is not clean (bzr status)")
}
return nil
}
|