blob: 21f4edda0e733c50b961a79b6e92c90a0979e932 [file] [log] [blame] [edit]
// Package cache provides an interface for interfacing with the Glide local cache
//
// Glide has a local cache of metadata and repositories similar to the GOPATH.
// To store the cache Glide creates a .glide directory with a cache subdirectory.
// This is usually in the users home directory unless there is no accessible
// home directory in which case the .glide directory is in the root of the
// repository.
//
// To get the cache location use the `cache.Location()` function. This will
// return the proper base location in your environment.
//
// Within the cache directory there are two subdirectories. They are the src
// and info directories. The src directory contains version control checkouts
// of the packages. The info direcory contains metadata. The metadata maps to
// the RepoInfo struct. Both stores are happed to keys.
//
// Using the `cache.Key()` function you can get a key for a repo. Pass in a
// location such as `https://github.com/foo/bar` or `git@example.com:foo.git`
// and a key will be returned that can be used for caching operations.
//
// Note, the caching is based on repo rather than package. This is important
// for a couple reasons.
//
// 1. Forks or package replacements are supported in Glide. Where a different
// repo maps to a package.
// 2. Permissions enable different access. For example `https://example.com/foo.git`
// and `git@example.com:foo.git` may have access to different branches or tags.
package cache
import (
"encoding/json"
"errors"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
"github.com/Masterminds/glide/msg"
gpath "github.com/Masterminds/glide/path"
)
// Enabled sets if the cache is globally enabled. Defaults to true.
var Enabled = true
// ErrCacheDisabled is returned with the cache is disabled.
var ErrCacheDisabled = errors.New("Cache disabled")
var isSetup bool
var setupMutex sync.Mutex
// Setup creates the cache location.
func Setup() {
setupMutex.Lock()
defer setupMutex.Unlock()
if isSetup {
return
}
msg.Debug("Setting up the cache directory")
pths := []string{
"cache",
filepath.Join("cache", "src"),
filepath.Join("cache", "info"),
}
for _, l := range pths {
err := os.MkdirAll(filepath.Join(gpath.Home(), l), 0755)
if err != nil {
msg.Die("Cache directory unavailable: %s", err)
}
}
isSetup = true
}
// SetupReset resets if setup has been completed. The next time setup is run
// it will attempt a full setup.
func SetupReset() {
isSetup = false
}
// Location returns the location of the cache.
func Location() string {
p := filepath.Join(gpath.Home(), "cache")
Setup()
return p
}
// scpSyntaxRe matches the SCP-like addresses used to access repos over SSH.
var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
// Key generates a cache key based on a url or scp string. The key is file
// system safe.
func Key(repo string) (string, error) {
var u *url.URL
var err error
var strip bool
if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
// Match SCP-like syntax and convert it to a URL.
// Eg, "git@github.com:user/repo" becomes
// "ssh://git@github.com/user/repo".
u = &url.URL{
Scheme: "ssh",
User: url.User(m[1]),
Host: m[2],
Path: "/" + m[3],
}
strip = true
} else {
u, err = url.Parse(repo)
if err != nil {
return "", err
}
}
if strip {
u.Scheme = ""
}
var key string
if u.Scheme != "" {
key = u.Scheme + "-"
}
if u.User != nil && u.User.Username() != "" {
key = key + u.User.Username() + "-"
}
key = key + u.Host
if u.Path != "" {
key = key + strings.Replace(u.Path, "/", "-", -1)
}
key = strings.Replace(key, ":", "-", -1)
return key, nil
}
// RepoInfo holds information about a repo.
type RepoInfo struct {
DefaultBranch string `json:"default-branch"`
LastUpdate string `json:"last-update"`
}
// SaveRepoData stores data about a repo in the Glide cache
func SaveRepoData(key string, data RepoInfo) error {
if !Enabled {
return ErrCacheDisabled
}
location := Location()
data.LastUpdate = time.Now().String()
d, err := json.Marshal(data)
if err != nil {
return err
}
pp := filepath.Join(location, "info")
err = os.MkdirAll(pp, 0755)
if err != nil {
return err
}
p := filepath.Join(pp, key+".json")
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(d)
return err
}
// RepoData retrieves cached information about a repo.
func RepoData(key string) (*RepoInfo, error) {
if !Enabled {
return &RepoInfo{}, ErrCacheDisabled
}
location := Location()
c := &RepoInfo{}
p := filepath.Join(location, "info", key+".json")
f, err := ioutil.ReadFile(p)
if err != nil {
return &RepoInfo{}, err
}
err = json.Unmarshal(f, c)
if err != nil {
return &RepoInfo{}, err
}
return c, nil
}
var lockSync sync.Mutex
var lockData = make(map[string]*sync.Mutex)
// Lock locks a particular key name
func Lock(name string) {
lockSync.Lock()
m, ok := lockData[name]
if !ok {
m = &sync.Mutex{}
lockData[name] = m
}
lockSync.Unlock()
msg.Debug("Locking %s", name)
m.Lock()
}
// Unlock unlocks a particular key name
func Unlock(name string) {
msg.Debug("Unlocking %s", name)
lockSync.Lock()
if m, ok := lockData[name]; ok {
m.Unlock()
}
lockSync.Unlock()
}