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  }