| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- // Package vcs provides the ability to work with varying version control systems
- // (VCS), also known as source control systems (SCM) though the same interface.
- //
- // This package includes a function that attempts to detect the repo type from
- // the remote URL and return the proper type. For example,
- //
- // remote := "https://github.com/Masterminds/vcs"
- // local, _ := ioutil.TempDir("", "go-vcs")
- // repo, err := NewRepo(remote, local)
- //
- // In this case repo will be a GitRepo instance. NewRepo can detect the VCS for
- // numerous popular VCS and from the URL. For example, a URL ending in .git
- // that's not from one of the popular VCS will be detected as a Git repo and
- // the correct type will be returned.
- //
- // If you know the repository type and would like to create an instance of a
- // specific type you can use one of constructors for a type. They are NewGitRepo,
- // NewSvnRepo, NewBzrRepo, and NewHgRepo. The definition and usage is the same
- // as NewRepo.
- //
- // Once you have an object implementing the Repo interface the operations are
- // the same no matter which VCS you're using. There are some caveats. For
- // example, each VCS has its own version formats that need to be respected and
- // checkout out branches, if a branch is being worked with, is different in
- // each VCS.
- package vcs
- import (
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "regexp"
- "strings"
- "time"
- )
- // Logger is where you can provide a logger, implementing the log.Logger interface,
- // where verbose output from each VCS will be written. The default logger does
- // not log data. To log data supply your own logger or change the output location
- // of the provided logger.
- var Logger *log.Logger
- func init() {
- // Initialize the logger to one that does not actually log anywhere. This is
- // to be overridden by the package user by setting vcs.Logger to a different
- // logger.
- Logger = log.New(ioutil.Discard, "go-vcs", log.LstdFlags)
- }
- const longForm = "2006-01-02 15:04:05 -0700"
- // Type describes the type of VCS
- type Type string
- // VCS types
- const (
- NoVCS Type = ""
- Git Type = "git"
- Svn Type = "svn"
- Bzr Type = "bzr"
- Hg Type = "hg"
- )
- // Repo provides an interface to work with repositories using different source
- // control systems such as Git, Bzr, Mercurial, and SVN. For implementations
- // of this interface see BzrRepo, GitRepo, HgRepo, and SvnRepo.
- type Repo interface {
- // Vcs retrieves the underlying VCS being implemented.
- Vcs() Type
- // Remote retrieves the remote location for a repo.
- Remote() string
- // LocalPath retrieves the local file system location for a repo.
- LocalPath() string
- // Get is used to perform an initial clone/checkout of a repository.
- Get() error
- // Initializes a new repository locally.
- Init() error
- // Update performs an update to an existing checkout of a repository.
- Update() error
- // UpdateVersion sets the version of a package of a repository.
- UpdateVersion(string) error
- // Version retrieves the current version.
- Version() (string, error)
- // Current retrieves the current version-ish. This is different from the
- // Version method. The output could be a branch name if on the tip of a
- // branch (git), a tag if on a tag, a revision if on a specific revision
- // that's not the tip of the branch. The values here vary based on the VCS.
- Current() (string, error)
- // Date retrieves the date on the latest commit.
- Date() (time.Time, error)
- // CheckLocal verifies the local location is of the correct VCS type
- CheckLocal() bool
- // Branches returns a list of available branches on the repository.
- Branches() ([]string, error)
- // Tags returns a list of available tags on the repository.
- Tags() ([]string, error)
- // IsReference returns if a string is a reference. A reference can be a
- // commit id, branch, or tag.
- IsReference(string) bool
- // IsDirty returns if the checkout has been modified from the checked
- // out reference.
- IsDirty() bool
- // CommitInfo retrieves metadata about a commit.
- CommitInfo(string) (*CommitInfo, error)
- // TagsFromCommit retrieves tags from a commit id.
- TagsFromCommit(string) ([]string, error)
- // Ping returns if remote location is accessible.
- Ping() bool
- // RunFromDir executes a command from repo's directory.
- RunFromDir(cmd string, args ...string) ([]byte, error)
- // CmdFromDir creates a new command that will be executed from repo's
- // directory.
- CmdFromDir(cmd string, args ...string) *exec.Cmd
- // ExportDir exports the current revision to the passed in directory.
- ExportDir(string) error
- }
- // NewRepo returns a Repo based on trying to detect the source control from the
- // remote and local locations. The appropriate implementation will be returned
- // or an ErrCannotDetectVCS if the VCS type cannot be detected.
- // Note, this function may make calls to the Internet to determind help determine
- // the VCS.
- func NewRepo(remote, local string) (Repo, error) {
- vtype, remote, err := detectVcsFromRemote(remote)
- // From the remote URL the VCS could not be detected. See if the local
- // repo contains enough information to figure out the VCS. The reason the
- // local repo is not checked first is because of the potential for VCS type
- // switches which will be detected in each of the type builders.
- if err == ErrCannotDetectVCS {
- vtype, err = DetectVcsFromFS(local)
- }
- if err != nil {
- return nil, err
- }
- switch vtype {
- case Git:
- return NewGitRepo(remote, local)
- case Svn:
- return NewSvnRepo(remote, local)
- case Hg:
- return NewHgRepo(remote, local)
- case Bzr:
- return NewBzrRepo(remote, local)
- }
- // Should never fall through to here but just in case.
- return nil, ErrCannotDetectVCS
- }
- // CommitInfo contains metadata about a commit.
- type CommitInfo struct {
- // The commit id
- Commit string
- // Who authored the commit
- Author string
- // Date of the commit
- Date time.Time
- // Commit message
- Message string
- }
- type base struct {
- remote, local string
- Logger *log.Logger
- }
- func (b *base) log(v interface{}) {
- b.Logger.Printf("%s", v)
- }
- // Remote retrieves the remote location for a repo.
- func (b *base) Remote() string {
- return b.remote
- }
- // LocalPath retrieves the local file system location for a repo.
- func (b *base) LocalPath() string {
- return b.local
- }
- func (b *base) setRemote(remote string) {
- b.remote = remote
- }
- func (b *base) setLocalPath(local string) {
- b.local = local
- }
- func (b base) run(cmd string, args ...string) ([]byte, error) {
- out, err := exec.Command(cmd, args...).CombinedOutput()
- b.log(out)
- if err != nil {
- err = fmt.Errorf("%s: %s", out, err)
- }
- return out, err
- }
- func (b *base) CmdFromDir(cmd string, args ...string) *exec.Cmd {
- c := exec.Command(cmd, args...)
- c.Dir = b.local
- c.Env = envForDir(c.Dir)
- return c
- }
- func (b *base) RunFromDir(cmd string, args ...string) ([]byte, error) {
- c := b.CmdFromDir(cmd, args...)
- out, err := c.CombinedOutput()
- return out, err
- }
- func (b *base) referenceList(c, r string) []string {
- var out []string
- re := regexp.MustCompile(r)
- for _, m := range re.FindAllStringSubmatch(c, -1) {
- out = append(out, m[1])
- }
- return out
- }
- func envForDir(dir string) []string {
- env := os.Environ()
- return mergeEnvLists([]string{"PWD=" + dir}, env)
- }
- func mergeEnvLists(in, out []string) []string {
- NextVar:
- for _, inkv := range in {
- k := strings.SplitAfterN(inkv, "=", 2)[0]
- for i, outkv := range out {
- if strings.HasPrefix(outkv, k) {
- out[i] = inkv
- continue NextVar
- }
- }
- out = append(out, inkv)
- }
- return out
- }
- func depInstalled(name string) bool {
- if _, err := exec.LookPath(name); err != nil {
- return false
- }
- return true
- }
|