This commit is contained in:
2023-12-08 16:52:26 -05:00
parent 424e572fe0
commit f17ce69ef8
23 changed files with 648 additions and 104 deletions

View File

@ -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 {

View File

@ -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

View File

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

View 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
View 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
}

View File

@ -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
}