wip
This commit is contained in:
@ -8,6 +8,9 @@ type Config struct {
|
||||
LogLevel string `yaml:"logLevel" json:"logLevel" enum:"info,warn,debug,error"`
|
||||
ProjectPath string `yaml:"projectPath" json:"projectPath"`
|
||||
Cache cacheConfig `yaml:"cache" json:"cache"`
|
||||
Dump struct {
|
||||
Full bool
|
||||
}
|
||||
}
|
||||
|
||||
type cacheConfig struct {
|
||||
|
@ -2,12 +2,13 @@ package gitlab
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
const defProjectsPerPage = 100
|
||||
const defProjectsPerPage = 30
|
||||
|
||||
type Client struct {
|
||||
Ctx context.Context
|
||||
@ -15,12 +16,11 @@ type Client struct {
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID int
|
||||
Description string
|
||||
SSHURLToRepo string
|
||||
HTTPURLToRepo string
|
||||
WebURL string
|
||||
// Owner User
|
||||
ID int
|
||||
Description string
|
||||
SSHURLToRepo string
|
||||
HTTPURLToRepo string
|
||||
WebURL string
|
||||
Name string
|
||||
NameWithNamespace string
|
||||
Path string
|
||||
@ -51,6 +51,10 @@ type Progress struct {
|
||||
TotalProjects int
|
||||
}
|
||||
|
||||
func (p *Project) String() string {
|
||||
return fmt.Sprintf("%s (%s)", p.Path, p.PathWithNamespace)
|
||||
}
|
||||
|
||||
// Given there may be thousands of projects, this will return
|
||||
// channels that stream progress info and then finally the full
|
||||
// list of projects on separate channels
|
||||
@ -77,6 +81,7 @@ func (c *Client) streamProjects(pi *ProgressInfo) {
|
||||
Page: 1,
|
||||
},
|
||||
Archived: new(bool),
|
||||
Owned: gitlab.Ptr[bool](true),
|
||||
}
|
||||
|
||||
var numProjects int
|
||||
|
@ -2,8 +2,8 @@ package projects
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -48,7 +48,7 @@ func (c *Cache) Load() error {
|
||||
|
||||
// Saves the current state of the cache to disk
|
||||
func (c *Cache) write() {
|
||||
file, err := os.OpenFile(c.file, os.O_RDWR, fs.ModeAppend)
|
||||
file, err := os.OpenFile(c.file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
c.log.Error("Failed to write cache to disk", c.log.Args("error", err))
|
||||
}
|
||||
@ -66,7 +66,7 @@ func (c *Cache) Write() {
|
||||
}
|
||||
|
||||
// Loads and unmarshals the project cache from disk.
|
||||
func (c *Cache) Read() {
|
||||
func (c *Cache) Read() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.log.Debug("Reading project cache from disk", c.log.Args("file", c.file))
|
||||
@ -74,18 +74,14 @@ func (c *Cache) Read() {
|
||||
file, err := os.Open(c.file)
|
||||
if err != nil {
|
||||
c.log.Error("Failed to read project cache", c.log.Args("error", err))
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
d := yaml.NewDecoder(file)
|
||||
d.Decode(c)
|
||||
|
||||
if time.Since(c.Updated) > c.ttl {
|
||||
c.refresh()
|
||||
}
|
||||
|
||||
c.readFromFile = true
|
||||
c.log.Debug(c.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resets projects cache and also optionally clears
|
||||
@ -98,7 +94,6 @@ func (c *Cache) clear(clearAliases bool) {
|
||||
c.Aliases = make([]*ProjectAlias, 0)
|
||||
}
|
||||
c.setUpdated()
|
||||
c.log.Debug(c.String())
|
||||
}
|
||||
func (c *Cache) Clear(clearAliases bool) {
|
||||
c.lock.Lock()
|
||||
@ -127,6 +122,24 @@ func (c *Cache) String() string {
|
||||
len(c.Aliases))
|
||||
}
|
||||
|
||||
func (c *Cache) DumpString() string {
|
||||
str := strings.Builder{}
|
||||
str.WriteString(c.String() + "\n\nProjects:\n")
|
||||
for _, project := range c.Projects {
|
||||
str.WriteString(" - " + pterm.FgLightBlue.Sprint(project.Name) + " (")
|
||||
str.WriteString(project.PathWithNamespace + ")\n")
|
||||
aliases := c.GetProjectAliases(project)
|
||||
if len(aliases) > 0 {
|
||||
str.WriteString(pterm.FgLightGreen.Sprint(" aliases:"))
|
||||
for _, a := range aliases {
|
||||
str.WriteString(" [" + pterm.FgCyan.Sprint(a.Alias) + "]")
|
||||
}
|
||||
str.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
func (c *Cache) setUpdated() {
|
||||
c.Updated = time.Now()
|
||||
}
|
||||
|
23
internal/projects/cache_aliases.go
Normal file
23
internal/projects/cache_aliases.go
Normal file
@ -0,0 +1,23 @@
|
||||
package projects
|
||||
|
||||
import "errors"
|
||||
|
||||
func (c *Cache) addAlias(alias string, projectID int) error {
|
||||
if c.GetAliasByName(alias) != nil {
|
||||
return errors.New("Failed to add alias, already exists")
|
||||
}
|
||||
|
||||
c.Aliases = append(c.Aliases,
|
||||
&ProjectAlias{
|
||||
Alias: alias,
|
||||
ProjectID: projectID,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) AddAlias(alias string, projectID int) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
return c.addAlias(alias, projectID)
|
||||
}
|
37
internal/projects/fuzz.go
Normal file
37
internal/projects/fuzz.go
Normal file
@ -0,0 +1,37 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
||||
// Performs a fuzzy find on the input string, returning the closest
|
||||
// matched based on its Levenshtein distance, along with an integer
|
||||
// indicating number of matches found
|
||||
func (c *Cache) FuzzyFindAlias(name string) []*ProjectAlias {
|
||||
ranks := fuzzy.RankFindFold(name, c.AliasStrings())
|
||||
if ranks.Len() == 1 {
|
||||
c.log.Debug("Fuzzy found alias result",
|
||||
c.log.Args(
|
||||
"searchTerm", ranks[0].Source,
|
||||
"foundAlias", ranks[0].Target,
|
||||
"levenshteinDistance", ranks[0].Distance,
|
||||
))
|
||||
} else if ranks.Len() > 1 {
|
||||
found := make([]string, ranks.Len())
|
||||
for i, r := range ranks {
|
||||
found[i] = r.Target
|
||||
}
|
||||
c.log.Warn("Fuzzy found multiple aliases, try being more specific",
|
||||
c.log.Args("foundAliases", strings.Join(found, ",")))
|
||||
}
|
||||
var aliases []*ProjectAlias
|
||||
if ranks.Len() > 0 {
|
||||
aliases = make([]*ProjectAlias, ranks.Len())
|
||||
for i, r := range ranks {
|
||||
aliases[i] = c.GetAliasByName(r.Target)
|
||||
}
|
||||
}
|
||||
return aliases
|
||||
}
|
@ -2,6 +2,7 @@ package projects
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab"
|
||||
@ -9,8 +10,93 @@ import (
|
||||
|
||||
type ProjectAlias struct {
|
||||
Alias string
|
||||
ProjectID string
|
||||
Project *gitlab.Project
|
||||
ProjectID int
|
||||
}
|
||||
|
||||
func ProjectAliasesString(aliases []*ProjectAlias) string {
|
||||
var str string
|
||||
for _, a := range aliases {
|
||||
str += "[" + pterm.LightCyan(a.Alias) + "] "
|
||||
}
|
||||
return strings.Trim(str, " ")
|
||||
}
|
||||
|
||||
func (c *Cache) ProjectString(p *gitlab.Project) string {
|
||||
info := strings.Builder{}
|
||||
|
||||
info.WriteString(pterm.LightBlue(p.Name))
|
||||
info.WriteRune('\n')
|
||||
if p.Description != "" {
|
||||
info.WriteString(p.Description)
|
||||
info.WriteRune('\n')
|
||||
}
|
||||
|
||||
info.WriteString("\nPath: " + pterm.LightGreen(p.PathWithNamespace))
|
||||
info.WriteString("\nProjectID: " + pterm.LightGreen(p.ID))
|
||||
info.WriteString("\nURL: " + pterm.LightGreen(p.WebURL))
|
||||
info.WriteString("\nLastActivity: " + pterm.LightMagenta(p.LastActivityAt.String()))
|
||||
info.WriteString("\nAliases: ")
|
||||
|
||||
aliases := c.GetProjectAliases(p)
|
||||
info.WriteString(ProjectAliasesString(aliases))
|
||||
|
||||
return info.String()
|
||||
}
|
||||
|
||||
func (c *Cache) ProjectStrings() []string {
|
||||
projects := make([]string, len(c.Projects))
|
||||
for i, p := range c.Projects {
|
||||
projects[i] = p.NameWithNamespace
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
func (c *Cache) AliasStrings() []string {
|
||||
aliases := make([]string, len(c.Aliases))
|
||||
for i, a := range c.Aliases {
|
||||
aliases[i] = a.Alias
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
|
||||
func (c *Cache) GetAliasByName(name string) *ProjectAlias {
|
||||
for _, a := range c.Aliases {
|
||||
if name == a.Alias {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) GetProjectByID(id int) *gitlab.Project {
|
||||
for _, p := range c.Projects {
|
||||
if p.ID == id {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) GetProjectByAlias(alias *ProjectAlias) *gitlab.Project {
|
||||
if alias == nil {
|
||||
return nil
|
||||
}
|
||||
for _, p := range c.Projects {
|
||||
if p.ID == alias.ProjectID {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) GetProjectAliases(project *gitlab.Project) []*ProjectAlias {
|
||||
aliases := make([]*ProjectAlias, 0)
|
||||
for _, alias := range c.Aliases {
|
||||
if alias.ProjectID == project.ID {
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
|
||||
func (c *Cache) LoadProjects() {
|
||||
@ -46,6 +132,7 @@ func (c *Cache) LoadProjects() {
|
||||
c.log.Warn("LoadProjects cancelled", c.log.Args("reason", c.gitlab.Ctx.Err()))
|
||||
return
|
||||
case <-progressInfo.DoneChan:
|
||||
pBar.Add(pBar.Total - curProjects)
|
||||
c.log.Info("Project load complete")
|
||||
return
|
||||
}
|
||||
|
Reference in New Issue
Block a user