mirror of
https://github.com/Jguer/yay
synced 2024-10-05 23:59:13 +00:00
feat(vcs): package vcs
This commit is contained in:
parent
c1171d4146
commit
c5af6f8189
253
pkg/vcs/vcs.go
Normal file
253
pkg/vcs/vcs.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package vcs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
gosrc "github.com/Morganamilo/go-srcinfo"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/Jguer/yay/v10/pkg/settings/exe"
|
||||
"github.com/Jguer/yay/v10/pkg/text"
|
||||
)
|
||||
|
||||
// InfoStore is a collection of OriginInfoByURL by Package.
|
||||
// Containing a map of last commit SHAs of a repo
|
||||
type InfoStore struct {
|
||||
OriginsByPackage map[string]OriginInfoByURL
|
||||
FilePath string
|
||||
Runner exe.Runner
|
||||
CmdBuilder *exe.CmdBuilder
|
||||
}
|
||||
|
||||
// OriginInfoByURL stores the OriginInfo of each origin URL provided
|
||||
type OriginInfoByURL map[string]OriginInfo
|
||||
|
||||
// OriginInfo contains the last commit sha of a repo
|
||||
// Example:
|
||||
// "github.com/Jguer/yay.git": {
|
||||
// "protocols": [
|
||||
// "https"
|
||||
// ],
|
||||
// "branch": "next",
|
||||
// "sha": "c1171d41467c68ffd3c46748182a16366aaaf87b"
|
||||
// }
|
||||
type OriginInfo struct {
|
||||
Protocols []string `json:"protocols"`
|
||||
Branch string `json:"branch"`
|
||||
SHA string `json:"sha"`
|
||||
}
|
||||
|
||||
func NewInfoStore(filePath string, runner exe.Runner, cmdBuilder *exe.CmdBuilder) *InfoStore {
|
||||
infoStore := &InfoStore{
|
||||
CmdBuilder: cmdBuilder,
|
||||
FilePath: filePath,
|
||||
OriginsByPackage: map[string]OriginInfoByURL{},
|
||||
Runner: runner}
|
||||
|
||||
return infoStore
|
||||
}
|
||||
|
||||
// GetCommit parses HEAD commit from url and branch
|
||||
func (v *InfoStore) getCommit(url, branch string, protocols []string) string {
|
||||
if len(protocols) > 0 {
|
||||
protocol := protocols[len(protocols)-1]
|
||||
|
||||
cmd := v.CmdBuilder.BuildGitCmd("", "ls-remote", protocol+"://"+url, branch)
|
||||
stdout, _, err := v.Runner.Capture(cmd, 5)
|
||||
if err != nil {
|
||||
text.Warnln(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
split := strings.Fields(stdout)
|
||||
|
||||
if len(split) < 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
commit := split[0]
|
||||
return commit
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v *InfoStore) Update(pkgName string, sources []gosrc.ArchString, mux sync.Locker, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
info := make(OriginInfoByURL)
|
||||
checkSource := func(source gosrc.ArchString) {
|
||||
defer wg.Done()
|
||||
url, branch, protocols := parseSource(source.Value)
|
||||
if url == "" || branch == "" {
|
||||
return
|
||||
}
|
||||
|
||||
commit := v.getCommit(url, branch, protocols)
|
||||
if commit == "" {
|
||||
return
|
||||
}
|
||||
|
||||
mux.Lock()
|
||||
info[url] = OriginInfo{
|
||||
protocols,
|
||||
branch,
|
||||
commit,
|
||||
}
|
||||
|
||||
v.OriginsByPackage[pkgName] = info
|
||||
text.Warnln(gotext.Get("Found git repo: %s", text.Cyan(url)))
|
||||
|
||||
if err := v.Save(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
mux.Unlock()
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
wg.Add(1)
|
||||
go checkSource(source)
|
||||
}
|
||||
}
|
||||
|
||||
// parseSource returns the git url, default branch and protocols it supports
|
||||
func parseSource(source string) (url, branch string, protocols []string) {
|
||||
split := strings.Split(source, "::")
|
||||
source = split[len(split)-1]
|
||||
split = strings.SplitN(source, "://", 2)
|
||||
|
||||
if len(split) != 2 {
|
||||
return "", "", nil
|
||||
}
|
||||
protocols = strings.SplitN(split[0], "+", 2)
|
||||
|
||||
git := false
|
||||
for _, protocol := range protocols {
|
||||
if protocol == "git" {
|
||||
git = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
protocols = protocols[len(protocols)-1:]
|
||||
|
||||
if !git {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
split = strings.SplitN(split[1], "#", 2)
|
||||
if len(split) == 2 {
|
||||
secondSplit := strings.SplitN(split[1], "=", 2)
|
||||
if secondSplit[0] != "branch" {
|
||||
// source has #commit= or #tag= which makes them not vcs
|
||||
// packages because they reference a specific point
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
if len(secondSplit) == 2 {
|
||||
url = split[0]
|
||||
branch = secondSplit[1]
|
||||
}
|
||||
} else {
|
||||
url = split[0]
|
||||
branch = "HEAD"
|
||||
}
|
||||
|
||||
url = strings.Split(url, "?")[0]
|
||||
branch = strings.Split(branch, "?")[0]
|
||||
|
||||
return url, branch, protocols
|
||||
}
|
||||
|
||||
func (v *InfoStore) NeedsUpdate(infos OriginInfoByURL) bool {
|
||||
// used to signal we have gone through all sources and found nothing
|
||||
finished := make(chan struct{})
|
||||
alive := 0
|
||||
|
||||
// if we find an update we use this to exit early and return true
|
||||
hasUpdate := make(chan struct{})
|
||||
|
||||
checkHash := func(url string, info OriginInfo) {
|
||||
hash := v.getCommit(url, info.Branch, info.Protocols)
|
||||
if hash != "" && hash != info.SHA {
|
||||
hasUpdate <- struct{}{}
|
||||
} else {
|
||||
finished <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for url, info := range infos {
|
||||
alive++
|
||||
go checkHash(url, info)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-hasUpdate:
|
||||
return true
|
||||
case <-finished:
|
||||
alive--
|
||||
if alive == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *InfoStore) Save() error {
|
||||
marshalledinfo, err := json.MarshalIndent(v, "", "\t")
|
||||
if err != nil || string(marshalledinfo) == "null" {
|
||||
return err
|
||||
}
|
||||
in, err := os.OpenFile(v.FilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
_, err = in.Write(marshalledinfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = in.Sync()
|
||||
return err
|
||||
}
|
||||
|
||||
// RemovePackage removes package from VCS information
|
||||
func (v *InfoStore) RemovePackage(pkgs []string) {
|
||||
updated := false
|
||||
|
||||
for _, pkgName := range pkgs {
|
||||
if _, ok := v.OriginsByPackage[pkgName]; ok {
|
||||
delete(v.OriginsByPackage, pkgName)
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
if updated {
|
||||
if err := v.Save(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LoadStore reads a json file and populates a InfoStore structure
|
||||
func (v InfoStore) Load() error {
|
||||
vfile, err := os.Open(v.FilePath)
|
||||
if !os.IsNotExist(err) && err != nil {
|
||||
return fmt.Errorf("failed to open vcs file '%s': %s", v.FilePath, err)
|
||||
}
|
||||
|
||||
defer vfile.Close()
|
||||
if !os.IsNotExist(err) {
|
||||
decoder := json.NewDecoder(vfile)
|
||||
if err = decoder.Decode(&v.OriginsByPackage); err != nil {
|
||||
return fmt.Errorf("failed to read vcs '%s': %s", v.FilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
42
pkg/vcs/vcs_test.go
Normal file
42
pkg/vcs/vcs_test.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package vcs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParsing(t *testing.T) {
|
||||
type source struct {
|
||||
URL string
|
||||
Branch string
|
||||
Protocols []string
|
||||
}
|
||||
|
||||
urls := []string{
|
||||
"git+https://github.com/neovim/neovim.git",
|
||||
"git://github.com/jguer/yay.git#branch=master",
|
||||
"git://github.com/davidgiven/ack",
|
||||
"git://github.com/jguer/yay.git#tag=v3.440",
|
||||
"git://github.com/jguer/yay.git#commit=e5470c88c6e2f9e0f97deb4728659ffa70ef5d0c",
|
||||
"a+b+c+d+e+f://github.com/jguer/yay.git#branch=foo",
|
||||
}
|
||||
|
||||
sources := []source{
|
||||
{"github.com/neovim/neovim.git", "HEAD", []string{"https"}},
|
||||
{"github.com/jguer/yay.git", "master", []string{"git"}},
|
||||
{"github.com/davidgiven/ack", "HEAD", []string{"git"}},
|
||||
{"", "", nil},
|
||||
{"", "", nil},
|
||||
{"", "", nil},
|
||||
}
|
||||
|
||||
for n, url := range urls {
|
||||
url, branch, protocols := parseSource(url)
|
||||
compare := sources[n]
|
||||
|
||||
assert.Equal(t, compare.URL, url)
|
||||
assert.Equal(t, compare.Branch, branch)
|
||||
assert.Equal(t, compare.Protocols, protocols)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue