launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/bzr/bzr.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package bzr offers an interface to manage branches of the Bazaar VCS. 5 package bzr 6 7 import ( 8 "bytes" 9 "fmt" 10 "os" 11 "os/exec" 12 "path" 13 "strings" 14 ) 15 16 // Branch represents a Bazaar branch. 17 type Branch struct { 18 location string 19 env []string 20 } 21 22 // New returns a new Branch for the Bazaar branch at location. 23 func New(location string) *Branch { 24 b := &Branch{location, cenv()} 25 if _, err := os.Stat(location); err == nil { 26 stdout, _, err := b.bzr("root") 27 if err == nil { 28 b.location = strings.TrimRight(string(stdout), "\n") 29 } 30 } 31 return b 32 } 33 34 // cenv returns a copy of the current process environment with LC_ALL=C. 35 func cenv() []string { 36 env := os.Environ() 37 for i, pair := range env { 38 if strings.HasPrefix(pair, "LC_ALL=") { 39 env[i] = "LC_ALL=C" 40 return env 41 } 42 } 43 return append(env, "LC_ALL=C") 44 } 45 46 // Location returns the location of branch b. 47 func (b *Branch) Location() string { 48 return b.location 49 } 50 51 // Join returns b's location with parts appended as path components. 52 // In other words, if b's location is "lp:foo", and parts is {"bar, baz"}, 53 // Join returns "lp:foo/bar/baz". 54 func (b *Branch) Join(parts ...string) string { 55 return path.Join(append([]string{b.location}, parts...)...) 56 } 57 58 func (b *Branch) bzr(subcommand string, args ...string) (stdout, stderr []byte, err error) { 59 cmd := exec.Command("bzr", append([]string{subcommand}, args...)...) 60 if _, err := os.Stat(b.location); err == nil { 61 cmd.Dir = b.location 62 } 63 errbuf := &bytes.Buffer{} 64 cmd.Stderr = errbuf 65 cmd.Env = b.env 66 stdout, err = cmd.Output() 67 // Some commands fail with exit status 0 (e.g. bzr root). :-( 68 if err != nil || bytes.Contains(errbuf.Bytes(), []byte("ERROR")) { 69 var errmsg string 70 if err != nil { 71 errmsg = err.Error() 72 } 73 return nil, nil, fmt.Errorf(`error running "bzr %s": %s%s%s`, subcommand, stdout, errbuf.Bytes(), errmsg) 74 } 75 return stdout, errbuf.Bytes(), err 76 } 77 78 // Init intializes a new branch at b's location. 79 func (b *Branch) Init() error { 80 _, _, err := b.bzr("init", b.location) 81 return err 82 } 83 84 // Add adds to b the path resultant from calling b.Join(parts...). 85 func (b *Branch) Add(parts ...string) error { 86 _, _, err := b.bzr("add", b.Join(parts...)) 87 return err 88 } 89 90 // Commit commits pending changes into b. 91 func (b *Branch) Commit(message string) error { 92 _, _, err := b.bzr("commit", "-q", "-m", message) 93 return err 94 } 95 96 // RevisionId returns the Bazaar revision id for the tip of b. 97 func (b *Branch) RevisionId() (string, error) { 98 stdout, stderr, err := b.bzr("revision-info", "-d", b.location) 99 if err != nil { 100 return "", err 101 } 102 pair := bytes.Fields(stdout) 103 if len(pair) != 2 { 104 return "", fmt.Errorf(`invalid output from "bzr revision-info": %s%s`, stdout, stderr) 105 } 106 id := string(pair[1]) 107 if id == "null:" { 108 return "", fmt.Errorf("branch has no content") 109 } 110 return id, nil 111 } 112 113 // PushLocation returns the default push location for b. 114 func (b *Branch) PushLocation() (string, error) { 115 stdout, _, err := b.bzr("info", b.location) 116 if err != nil { 117 return "", err 118 } 119 if i := bytes.Index(stdout, []byte("push branch:")); i >= 0 { 120 return string(stdout[i+13 : i+bytes.IndexAny(stdout[i:], "\r\n")]), nil 121 } 122 return "", fmt.Errorf("no push branch location defined") 123 } 124 125 // PushAttr holds options for the Branch.Push method. 126 type PushAttr struct { 127 Location string // Location to push to. Use the default push location if empty. 128 Remember bool // Whether to remember the location being pushed to as the default. 129 } 130 131 // Push pushes any new revisions in b to attr.Location if that's 132 // provided, or to the default push location otherwise. 133 // See PushAttr for other options. 134 func (b *Branch) Push(attr *PushAttr) error { 135 var args []string 136 if attr != nil { 137 if attr.Remember { 138 args = append(args, "--remember") 139 } 140 if attr.Location != "" { 141 args = append(args, attr.Location) 142 } 143 } 144 _, _, err := b.bzr("push", args...) 145 return err 146 } 147 148 // CheckClean returns an error if 'bzr status' is not clean. 149 func (b *Branch) CheckClean() error { 150 stdout, _, err := b.bzr("status", b.location) 151 if err != nil { 152 return err 153 } 154 if bytes.Count(stdout, []byte{'\n'}) == 1 && bytes.Contains(stdout, []byte(`See "bzr shelve --list" for details.`)) { 155 return nil // Shelves are fine. 156 } 157 if len(stdout) > 0 { 158 return fmt.Errorf("branch is not clean (bzr status)") 159 } 160 return nil 161 }