Implement cache migrations

This commit is contained in:
Ryan McGuire 2024-12-31 20:12:02 -05:00
parent 30d86d72ed
commit 2f0e1b0d46
3 changed files with 60 additions and 18 deletions

View File

@ -122,8 +122,16 @@ func (c *Cache) Read() error {
d.Decode(c) d.Decode(c)
// Perform migrations // Perform migrations
if err := c.doMigrations(); err != nil { err, migrated := c.doMigrations()
c.log.Error("Failed to run cache migrations", c.log.Args("error", err)) if err != nil {
c.log.Error("Failed to run cache migrations",
c.log.Args(
"migrated", migrated,
"error", err,
))
} else if migrated > 0 {
c.log.Info("Migrations run successfully", c.log.Args(
"migrated", migrated))
} }
c.readFromFile = true c.readFromFile = true

View File

@ -5,9 +5,13 @@ import (
"fmt" "fmt"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
type migrationFunc func(c *Cache) error // Migrations funcs should return errors along with
// number of records updated
type migrationFunc func(c *Cache) (error, int)
// Registry of migrations by version // Registry of migrations by version
var migrations = map[string]map[string]migrationFunc{ var migrations = map[string]map[string]migrationFunc{
@ -20,38 +24,55 @@ var migrations = map[string]map[string]migrationFunc{
// of cache read from disk. // of cache read from disk.
// Does not check to ensure migrations were successful, // Does not check to ensure migrations were successful,
// only checks if a version has been achieved // only checks if a version has been achieved
func (c *Cache) DoMigrations() error { func (c *Cache) DoMigrations() (error, int) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
return c.doMigrations() return c.doMigrations()
} }
func (c *Cache) doMigrations() error { func (c *Cache) doMigrations() (error, int) {
var errs error var errs error
var migrated int
for version, migrationFuncs := range migrations { for version, migrationFuncs := range migrations {
var funcMigrated int
if semver.Compare(c.CacheVersion, version) < 0 { if semver.Compare(c.CacheVersion, version) < 0 {
for name, migration := range migrationFuncs { for name, migration := range migrationFuncs {
err := migration(c) err, numMigrated := migration(c)
if err != nil { if err != nil {
errs = errors.Join( errs = errors.Join(
errs, errs,
fmt.Errorf("%s - %s: %w", version, name, err), fmt.Errorf("%s - %s: %w", version, name, err),
) )
} }
funcMigrated += numMigrated
} }
// We've reached a cache version, update the CacheVersion // We've reached a cache version, update the CacheVersion
// and write to disk // and write to disk
if errs == nil { if errs == nil && funcMigrated > 0 {
c.CacheVersion = version c.CacheVersion = version
c.write() c.write()
} }
} }
migrated += funcMigrated
} }
return errs return errs, migrated
} }
func v010_aliases(c *Cache) error { func v010_aliases(c *Cache) (error, int) {
return errors.New("unimplemented migration") var aliasesMigrated int
var errs error
for i, a := range c.Aliases {
if a.ID == "" {
if a.Remote == "" {
errs = errors.Join(errs,
fmt.Errorf("alias %s [id:%d] has no remote", a.Alias, a.ProjectID))
continue
}
c.Aliases[i].ID = projects.MakeID(a.Remote, a.ProjectID)
aliasesMigrated++
}
}
return errs, aliasesMigrated
} }

View File

@ -9,6 +9,8 @@ import (
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
) )
// Git project metadata
// Do not use Project.ID directly (remotes may conflict), use Project.GetID()
type Project struct { type Project struct {
ID int ID int
Description string Description string
@ -44,19 +46,30 @@ func (pl *ProjectLanguages) AddLanguage(lang *ProjectLanguage) {
*pl = append(*pl, lang) *pl = append(*pl, lang)
} }
// Gets a unique ID using a short-sha of the http repo URL // Gets a unique ID using a short-sha of the http repo
// along with the numerical ID of the project. // along with the numerical ID of the project.
// Uses SSH URL and then Remote if previous is empty // Uses SSH URL and then Remote if previous is empty
func (p *Project) GetID() string { func (p *Project) GetID() string {
shaText := p.HTTPURLToRepo return fmt.Sprintf("%s||%d", p.GetRemoteSha(), p.ID)
if shaText == "" && p.SSHURLToRepo != "" { }
shaText = p.SSHURLToRepo
} else if shaText == "" { func MakeID(remote string, projectID int) string {
shaText = p.Remote return fmt.Sprintf("%s||%d", GetRemoteSha(remote), projectID)
}
func (p *Project) GetRemoteSha() string {
remote := p.Remote
if remote == "" && p.HTTPURLToRepo != "" {
remote = p.HTTPURLToRepo
} else if remote == "" && p.WebURL != "" {
remote = p.WebURL
} }
shortSha := fmt.Sprintf("%x", sha1.Sum([]byte(shaText)))[:12] return GetRemoteSha(remote)
return fmt.Sprintf("%s||%d", shortSha, p.ID) }
func GetRemoteSha(remote string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(remote)))[:12]
} }
func (p *Project) String() string { func (p *Project) String() string {