| // 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() |
| } |