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