diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 152cdbd..91c9885 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -122,8 +122,16 @@ func (c *Cache) Read() error { d.Decode(c) // Perform migrations - if err := c.doMigrations(); err != nil { - c.log.Error("Failed to run cache migrations", c.log.Args("error", err)) + err, migrated := c.doMigrations() + 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 diff --git a/internal/cache/cache_migrations.go b/internal/cache/cache_migrations.go index 1306c12..ba680b9 100644 --- a/internal/cache/cache_migrations.go +++ b/internal/cache/cache_migrations.go @@ -5,9 +5,13 @@ import ( "fmt" "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 var migrations = map[string]map[string]migrationFunc{ @@ -20,38 +24,55 @@ var migrations = map[string]map[string]migrationFunc{ // of cache read from disk. // Does not check to ensure migrations were successful, // only checks if a version has been achieved -func (c *Cache) DoMigrations() error { +func (c *Cache) DoMigrations() (error, int) { c.lock.Lock() defer c.lock.Unlock() return c.doMigrations() } -func (c *Cache) doMigrations() error { +func (c *Cache) doMigrations() (error, int) { var errs error + var migrated int for version, migrationFuncs := range migrations { + var funcMigrated int if semver.Compare(c.CacheVersion, version) < 0 { for name, migration := range migrationFuncs { - err := migration(c) + err, numMigrated := migration(c) if err != nil { errs = errors.Join( errs, fmt.Errorf("%s - %s: %w", version, name, err), ) } + funcMigrated += numMigrated } // We've reached a cache version, update the CacheVersion // and write to disk - if errs == nil { + if errs == nil && funcMigrated > 0 { c.CacheVersion = version c.write() } } + migrated += funcMigrated } - return errs + return errs, migrated } -func v010_aliases(c *Cache) error { - return errors.New("unimplemented migration") +func v010_aliases(c *Cache) (error, int) { + 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 } diff --git a/internal/remotes/projects/projects.go b/internal/remotes/projects/projects.go index a8847ea..724d52d 100644 --- a/internal/remotes/projects/projects.go +++ b/internal/remotes/projects/projects.go @@ -9,6 +9,8 @@ import ( "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 { ID int Description string @@ -44,19 +46,30 @@ func (pl *ProjectLanguages) AddLanguage(lang *ProjectLanguage) { *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. // Uses SSH URL and then Remote if previous is empty func (p *Project) GetID() string { - shaText := p.HTTPURLToRepo - if shaText == "" && p.SSHURLToRepo != "" { - shaText = p.SSHURLToRepo - } else if shaText == "" { - shaText = p.Remote + return fmt.Sprintf("%s||%d", p.GetRemoteSha(), p.ID) +} + +func MakeID(remote string, projectID int) string { + 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 fmt.Sprintf("%s||%d", shortSha, p.ID) + return GetRemoteSha(remote) +} + +func GetRemoteSha(remote string) string { + return fmt.Sprintf("%x", sha1.Sum([]byte(remote)))[:12] } func (p *Project) String() string {