From 70027a988029323b33ebd85adcb12594c19c506b Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Mon, 30 Dec 2024 16:42:45 -0500 Subject: [PATCH 1/3] Begin move to project/alias by unique ID --- cmd/alias/alias_add.go | 16 ++++++++++++---- cmd/project/project.go | 2 +- internal/cache/cache_aliases.go | 20 +++++++++++--------- internal/cache/projects.go | 7 ++++--- internal/cache/projects_alias.go | 6 ++++-- internal/remotes/projects/projects.go | 19 +++++++++++++++++-- internal/remotes/projects/projects_net.go | 6 ++---- 7 files changed, 51 insertions(+), 25 deletions(-) diff --git a/cmd/alias/alias_add.go b/cmd/alias/alias_add.go index 0d57196..47e4ea1 100644 --- a/cmd/alias/alias_add.go +++ b/cmd/alias/alias_add.go @@ -28,7 +28,15 @@ func runAddAliasCmd(cmd *cobra.Command, args []string) { // Check by flag if projectID := viper.GetInt(util.ViperAliasAddPID); projectID > 0 { utils.Logger().Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID)) - project = utils.Cache().GetProjectByID(projectID) + + projects := utils.Cache().GetProjectsByID(projectID) + if len(projects) > 0 { + project = projects[0] + } + if len(projects) > 1 { + utils.Logger(). + Warn(fmt.Sprintf("found %d remotes with same ID, using first remote", len(projects))) + } } // Check by arg @@ -45,10 +53,10 @@ func runAddAliasCmd(cmd *cobra.Command, args []string) { } } - AddNewAliases(cmd, project.ID) + AddNewAliases(cmd, project.GetID()) } -func AddNewAliases(cmd *cobra.Command, projectID int) { +func AddNewAliases(cmd *cobra.Command, projectID string) { u := util.MustFromCtx(cmd.Context()) project := u.Cache().GetProjectByID(projectID) if project == nil { @@ -65,7 +73,7 @@ func AddNewAliases(cmd *cobra.Command, projectID int) { if a == "" { continue } - if err := u.Cache().AddAlias(a, project.ID, project.Remote); err != nil { + if err := u.Cache().AddAlias(a, project); err != nil { u.Logger().Debug("Skipping alias add", u.Logger().Args( "error", err, "alias", a, diff --git a/cmd/project/project.go b/cmd/project/project.go index 59cc261..789aa2a 100644 --- a/cmd/project/project.go +++ b/cmd/project/project.go @@ -50,7 +50,7 @@ func getProject(cmd *cobra.Command, args []string) *projects.Project { if len(utils.Cache().GetProjectAliases(project)) == 0 { utils.Logger().Info("New project, set aliases or press enter for default") - alias.AddNewAliases(cmd, project.ID) + alias.AddNewAliases(cmd, project.GetID()) } return project diff --git a/internal/cache/cache_aliases.go b/internal/cache/cache_aliases.go index 89e4698..dd1fc1e 100644 --- a/internal/cache/cache_aliases.go +++ b/internal/cache/cache_aliases.go @@ -22,7 +22,7 @@ func (c *Cache) DeleteAlias(alias *ProjectAlias) { c.deleteAlias(alias) } -func (c *Cache) addAlias(alias string, projectID int, remote string) error { +func (c *Cache) addAlias(alias string, project *projects.Project) error { if c.GetAliasByName(alias) != nil { return errors.New("failed to add alias, already exists") } @@ -30,26 +30,27 @@ func (c *Cache) addAlias(alias string, projectID int, remote string) error { c.Aliases = append(c.Aliases, &ProjectAlias{ Alias: alias, - ProjectID: projectID, - Remote: remote, + ProjectID: project.ID, + ID: project.GetID(), + Remote: project.Remote, }) return nil } -func (c *Cache) AddAlias(alias string, projectID int, remote string) error { +func (c *Cache) AddAlias(alias string, project *projects.Project) error { c.lock.Lock() defer c.lock.Unlock() - return c.addAlias(alias, projectID, remote) + return c.addAlias(alias, project) } func (c *Cache) GetProjectsWithAliases() []*projects.Project { projectList := make([]*projects.Project, 0) - projectsFound := make([]int, 0) + projectsFound := make([]string, 0) for _, a := range c.Aliases { - if !slices.Contains(projectsFound, a.ProjectID) { + if !slices.Contains(projectsFound, a.ID) { projectList = append(projectList, c.GetProjectByAlias(a)) - projectsFound = append(projectsFound, a.ProjectID) + projectsFound = append(projectsFound, a.ID) } } return projectList @@ -68,12 +69,13 @@ func (c *Cache) setAliasRemotes() { } func (c *Cache) setAliasRemote(alias *ProjectAlias) { - project := c.GetProjectByID(alias.ProjectID) + project := c.GetProjectByID(alias.ID) if project != nil { alias.Remote = project.Remote c.log.Debug("Fixed missing alias remote", c.log.Args( "alias", alias.Alias, "projectID", alias.ProjectID, + "ID", alias.ID, "remote", alias.Remote, )) } diff --git a/internal/cache/projects.go b/internal/cache/projects.go index 16fed6d..272bf99 100644 --- a/internal/cache/projects.go +++ b/internal/cache/projects.go @@ -4,8 +4,9 @@ import ( "strings" "github.com/pterm/pterm" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "golang.org/x/exp/slices" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" ) func (c *Cache) ProjectString(p *projects.Project) string { @@ -60,9 +61,9 @@ func (c *Cache) GetProjectByRemoteAndId(remote string, id int) *projects.Project return nil } -func (c *Cache) GetProjectByID(id int) *projects.Project { +func (c *Cache) GetProjectByID(id string) *projects.Project { for _, p := range c.Projects { - if p.ID == id { + if p.GetID() == id { return p } } diff --git a/internal/cache/projects_alias.go b/internal/cache/projects_alias.go index 17906d2..0d76bf0 100644 --- a/internal/cache/projects_alias.go +++ b/internal/cache/projects_alias.go @@ -7,13 +7,15 @@ import ( "text/tabwriter" "github.com/pterm/pterm" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "golang.org/x/exp/slices" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" ) type ProjectAlias struct { Alias string ProjectID int + ID string Remote string } @@ -96,7 +98,7 @@ func (c *Cache) GetProjectByAlias(alias *ProjectAlias) *projects.Project { return nil } for _, p := range c.Projects { - if p.ID == alias.ProjectID && p.Remote == alias.Remote { + if p.GetID() == alias.ID { return p } } diff --git a/internal/remotes/projects/projects.go b/internal/remotes/projects/projects.go index 18a3133..a8847ea 100644 --- a/internal/remotes/projects/projects.go +++ b/internal/remotes/projects/projects.go @@ -1,6 +1,7 @@ package projects import ( + "crypto/sha1" "fmt" "strings" "time" @@ -35,8 +36,7 @@ type ProjectLanguage struct { } func NewProjectLanguages() *ProjectLanguages { - var pLangs ProjectLanguages - pLangs = make([]*ProjectLanguage, 0) + var pLangs ProjectLanguages = make([]*ProjectLanguage, 0) return &pLangs } @@ -44,6 +44,21 @@ func (pl *ProjectLanguages) AddLanguage(lang *ProjectLanguage) { *pl = append(*pl, lang) } +// Gets a unique ID using a short-sha of the http repo URL +// 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 + } + + shortSha := fmt.Sprintf("%x", sha1.Sum([]byte(shaText)))[:12] + return fmt.Sprintf("%s||%d", shortSha, p.ID) +} + func (p *Project) String() string { var projectString string if p != nil { diff --git a/internal/remotes/projects/projects_net.go b/internal/remotes/projects/projects_net.go index ddc0d6a..2b3942c 100644 --- a/internal/remotes/projects/projects_net.go +++ b/internal/remotes/projects/projects_net.go @@ -20,9 +20,7 @@ const ( GitProtoHTTP ) -var ( - ErrUnknownHost error = errors.New("No addresses found for host") -) +var ErrUnknownHost error = errors.New("no addresses found for host") func (p *Project) CheckHost(proto GitProto) error { switch proto { @@ -31,7 +29,7 @@ func (p *Project) CheckHost(proto GitProto) error { case GitProtoSSH: return p.checkSSHRemote() } - return errors.New("Unknown git protocol") + return errors.New("unknown git protocol") } func (p *Project) checkHTTPRemote() error { From 30d86d72ed2d465d3fe27386770d2a0968c9b6c4 Mon Sep 17 00:00:00 2001 From: Ryan D McGuire Date: Tue, 31 Dec 2024 17:45:45 -0500 Subject: [PATCH 2/3] Implement cache migrations --- cmd/alias/alias_list.go | 17 ++++++--- go.mod | 10 ++++-- go.sum | 17 ++++++++- internal/cache/cache.go | 8 +++++ internal/cache/cache_migrations.go | 57 ++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 internal/cache/cache_migrations.go diff --git a/cmd/alias/alias_list.go b/cmd/alias/alias_list.go index bc3dce5..18dc1eb 100644 --- a/cmd/alias/alias_list.go +++ b/cmd/alias/alias_list.go @@ -3,10 +3,11 @@ package alias import ( "fmt" - "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" "github.com/pterm/pterm" "github.com/spf13/cobra" "github.com/spf13/viper" + + "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util" ) // listCmd represents the list command @@ -21,10 +22,18 @@ var aliasListCmd = &cobra.Command{ func runListAliasCmd(cmd *cobra.Command, args []string) { remotes := viper.GetStringSlice(util.FlagRemote) - pterm.DefaultBox. + aliases := utils.Cache().AliasesByProjectString(remotes...) + + printer := pterm.DefaultBox. WithLeftPadding(5).WithRightPadding(5). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). - WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project"))). - Print("\n" + utils.Cache().AliasesByProjectString(remotes...)) + WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project"))) + + if len(aliases) < 1 { + printer.Print("\n" + "No Aliases Found") + } else { + printer.Print("\n" + aliases) + } + fmt.Print("\n\n") } diff --git a/go.mod b/go.mod index b1cc6e2..241b4e5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module gitea.libretechconsulting.com/rmcguire/git-project-manager go 1.23.4 require ( - github.com/go-git/go-git/v5 v5.12.0 + github.com/go-git/go-git/v5 v5.13.0 github.com/ktr0731/go-fuzzyfinder v0.8.0 github.com/lithammer/fuzzysearch v1.1.8 github.com/pterm/pterm v0.12.80 @@ -18,7 +18,11 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/mmcloughlin/avo v0.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/tools v0.28.0 // indirect ) require ( @@ -39,7 +43,7 @@ require ( github.com/gdamore/tcell/v2 v2.7.4 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.0 // indirect + github.com/go-git/go-billy/v5 v5.6.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-github/v58 v58.0.0 // direct github.com/google/go-querystring v1.1.0 // indirect @@ -58,7 +62,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nsf/termbox-go v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pjbgf/sha1cd v0.3.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect diff --git a/go.sum b/go.sum index 5549e80..8eb27bb 100644 --- a/go.sum +++ b/go.sum @@ -34,7 +34,6 @@ github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZ github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -48,6 +47,7 @@ github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454Wv github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -64,16 +64,21 @@ github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAY github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= +github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -139,6 +144,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= @@ -147,6 +154,8 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI= +github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -223,6 +232,8 @@ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/ golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -236,6 +247,8 @@ golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -277,6 +290,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 6aa51b9..152cdbd 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -14,6 +14,8 @@ import ( "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" ) +const cacheVersion = "v0.1.0" + type Cache struct { Projects []*projects.Project Aliases []*ProjectAlias @@ -27,6 +29,7 @@ type Cache struct { file string log *pterm.Logger path string + CacheVersion string } type CacheOpts struct { @@ -118,6 +121,11 @@ func (c *Cache) Read() error { d := yaml.NewDecoder(file) d.Decode(c) + // Perform migrations + if err := c.doMigrations(); err != nil { + c.log.Error("Failed to run cache migrations", c.log.Args("error", err)) + } + c.readFromFile = true return nil } diff --git a/internal/cache/cache_migrations.go b/internal/cache/cache_migrations.go new file mode 100644 index 0000000..1306c12 --- /dev/null +++ b/internal/cache/cache_migrations.go @@ -0,0 +1,57 @@ +package cache + +import ( + "errors" + "fmt" + + "golang.org/x/mod/semver" +) + +type migrationFunc func(c *Cache) error + +// Registry of migrations by version +var migrations = map[string]map[string]migrationFunc{ + "v0.1.0": { + "Make Aliases Unique": v010_aliases, + }, +} + +// Performs any required updates based on version +// 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 { + c.lock.Lock() + defer c.lock.Unlock() + return c.doMigrations() +} + +func (c *Cache) doMigrations() error { + var errs error + for version, migrationFuncs := range migrations { + if semver.Compare(c.CacheVersion, version) < 0 { + for name, migration := range migrationFuncs { + err := migration(c) + if err != nil { + errs = errors.Join( + errs, + fmt.Errorf("%s - %s: %w", version, name, err), + ) + } + } + + // We've reached a cache version, update the CacheVersion + // and write to disk + if errs == nil { + c.CacheVersion = version + c.write() + } + } + } + + return errs +} + +func v010_aliases(c *Cache) error { + return errors.New("unimplemented migration") +} From 2f0e1b0d462726445860116758023bc8a2c36ebd Mon Sep 17 00:00:00 2001 From: Ryan D McGuire Date: Tue, 31 Dec 2024 20:12:02 -0500 Subject: [PATCH 3/3] Implement cache migrations --- internal/cache/cache.go | 12 +++++++-- internal/cache/cache_migrations.go | 37 +++++++++++++++++++++------ internal/remotes/projects/projects.go | 29 +++++++++++++++------ 3 files changed, 60 insertions(+), 18 deletions(-) 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 {