1
// Package bzr offers an interface to manage branches of the Bazaar VCS.
13
// Branch represents a Bazaar branch.
19
// New returns a new Branch for the Bazaar branch at location.
20
func New(location string) *Branch {
21
b := &Branch{location, cenv()}
22
if _, err := os.Stat(location); err == nil {
23
stdout, _, err := b.bzr("root")
25
b.location = strings.TrimRight(string(stdout), "\n")
31
// cenv returns a copy of the current process environment with LC_ALL=C.
32
func cenv() []string {
34
for i, pair := range env {
35
if strings.HasPrefix(pair, "LC_ALL=") {
40
return append(env, "LC_ALL=C")
43
// Location returns the location of branch b.
44
func (b *Branch) Location() string {
48
// Join returns b's location with parts appended as path components.
49
// In other words, if b's location is "lp:foo", and parts is {"bar, baz"},
50
// Join returns "lp:foo/bar/baz".
51
func (b *Branch) Join(parts ...string) string {
52
return path.Join(append([]string{b.location}, parts...)...)
55
func (b *Branch) bzr(subcommand string, args ...string) (stdout, stderr []byte, err error) {
56
cmd := exec.Command("bzr", append([]string{subcommand}, args...)...)
57
if _, err := os.Stat(b.location); err == nil {
60
errbuf := &bytes.Buffer{}
63
stdout, err = cmd.Output()
64
// Some commands fail with exit status 0 (e.g. bzr root). :-(
65
if err != nil || bytes.Contains(errbuf.Bytes(), []byte("ERROR")) {
70
return nil, nil, fmt.Errorf(`error running "bzr %s": %s%s%s`, subcommand, stdout, errbuf.Bytes(), errmsg)
72
return stdout, errbuf.Bytes(), err
75
// Init intializes a new branch at b's location.
76
func (b *Branch) Init() error {
77
_, _, err := b.bzr("init", b.location)
81
// Add adds to b the path resultant from calling b.Join(parts...).
82
func (b *Branch) Add(parts ...string) error {
83
_, _, err := b.bzr("add", b.Join(parts...))
87
// Commit commits pending changes into b.
88
func (b *Branch) Commit(message string) error {
89
_, _, err := b.bzr("commit", "-q", "-m", message)
93
// RevisionId returns the Bazaar revision id for the tip of b.
94
func (b *Branch) RevisionId() (string, error) {
95
stdout, stderr, err := b.bzr("revision-info", "-d", b.location)
99
pair := bytes.Fields(stdout)
101
return "", fmt.Errorf(`invalid output from "bzr revision-info": %s%s`, stdout, stderr)
103
id := string(pair[1])
105
return "", fmt.Errorf("branch has no content")
110
// PushLocation returns the default push location for b.
111
func (b *Branch) PushLocation() (string, error) {
112
stdout, _, err := b.bzr("info", b.location)
116
if i := bytes.Index(stdout, []byte("push branch:")); i >= 0 {
117
return string(stdout[i+13 : i+bytes.IndexAny(stdout[i:], "\r\n")]), nil
119
return "", fmt.Errorf("no push branch location defined")
122
// PushAttr holds options for the Branch.Push method.
123
type PushAttr struct {
124
Location string // Location to push to. Use the default push location if empty.
125
Remember bool // Whether to remember the location being pushed to as the default.
128
// Push pushes any new revisions in b to attr.Location if that's
129
// provided, or to the default push location otherwise.
130
// See PushAttr for other options.
131
func (b *Branch) Push(attr *PushAttr) error {
135
args = append(args, "--remember")
137
if attr.Location != "" {
138
args = append(args, attr.Location)
141
_, _, err := b.bzr("push", args...)
145
// CheckClean returns an error if 'bzr status' is not clean.
146
func (b *Branch) CheckClean() error {
147
stdout, _, err := b.bzr("status", b.location)
151
if bytes.Count(stdout, []byte{'\n'}) == 1 && bytes.Contains(stdout, []byte(`See "bzr shelve --list" for details.`)) {
152
return nil // Shelves are fine.
155
return fmt.Errorf("branch is not clean (bzr status)")