~rogpeppe/juju-core/azure

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
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, 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 {
			b.location = strings.TrimRight(string(stdout), "\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
}