Feature/dependency pinning and update automation (#5451)

* * handle local flag
* add managerfactory handling for local flag
* add shortName handling for local flag
* add dot git file handling for local flag
* add tests

* fix normal listing

* add ParseGitRepository function, add viper, add testing for utils

* add latest tag logic, add auto pinning and auto fetching

* makke gorepomod list works with --local

* make pinning works with local flag, enable auto update on fork and non-fork repo

* fix: refactor to pass linter

* refactor code and fix comments

* edit README

* refactor code to pass linting

* refactor code

* refactor code and enable patch branch label

* ru add license

* fbackward compatibility for unpin
This commit is contained in:
Kurnianto Trilaksono
2024-01-17 05:34:56 +08:00
committed by GitHub
parent f3fedac429
commit ab519fdc13
18 changed files with 1189 additions and 88 deletions

View File

@@ -5,9 +5,12 @@ package repo
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
v "github.com/spf13/viper"
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/git"
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/utils"
@@ -48,39 +51,70 @@ func (dg *DotGitData) AbsPath() string {
//
// ~/gopath/src/sigs.k8s.io/kustomize
// ~/gopath/src/github.com/monopole/gorepomod
func NewDotGitDataFromPath(path string) (*DotGitData, error) {
func NewDotGitDataFromPath(path string, localFlag bool) (*DotGitData, error) {
if !utils.DirExists(filepath.Join(path, dotGitFileName)) {
return nil, fmt.Errorf(
"%q doesn't have a %q file", path, dotGitFileName)
}
// This is an attempt to figure out where the user has cloned
// their repos. In the old days, it was an import path under
// $GOPATH/src. If we cannot guess it, we may need to ask for it,
// or maybe proceed without knowing it.
index := strings.Index(path, srcHint)
if index < 0 {
return nil, fmt.Errorf(
"path %q doesn't contain %q", path, srcHint)
// If local flag is supplied, use local git naming instead of production (sigs.k8s.io)
if localFlag {
localPrefix, err := getLocalPrefix(path)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
v.Set("LocalGitPrefix", localPrefix)
wd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("error extracting git repository %w", err)
}
pathSlice := strings.Split(wd, "/")
return &DotGitData{
srcPath: strings.Join(pathSlice[:len(pathSlice)-1], "/"),
repoPath: pathSlice[len(pathSlice)-1],
}, nil
} else {
// This is an attempt to figure out where the user has cloned
// their repos. In the old days, it was an import path under
// $GOPATH/src. If we cannot guess it, we may need to ask for it,
// or maybe proceed without knowing it.
index := strings.Index(path, srcHint)
if index < 0 {
return nil, fmt.Errorf(
"path %q doesn't contain %q", path, srcHint)
}
return &DotGitData{
srcPath: path[:index+len(srcHint)-1],
repoPath: path[index+len(srcHint):],
}, nil
}
return &DotGitData{
srcPath: path[:index+len(srcHint)-1],
repoPath: path[index+len(srcHint):],
}, nil
}
// It's a factory factory.
func (dg *DotGitData) NewRepoFactory(
exclusions []string) (*ManagerFactory, error) {
exclusions []string, localFlag bool) (*ManagerFactory, error) {
modules, err := loadProtoModules(dg.AbsPath(), exclusions)
if err != nil {
return nil, err
}
err = dg.checkModules(modules)
if err != nil {
return nil, err
}
runner := git.NewQuiet(dg.AbsPath(), true)
if localFlag {
err = dg.checkModulesLocal(modules)
if err != nil {
return nil, err
}
} else {
err = dg.checkModules(modules)
if err != nil {
return nil, err
}
}
runner := git.NewQuiet(dg.AbsPath(), true, localFlag)
remoteName, err := runner.DetermineRemoteToUse()
if err != nil {
return nil, err
@@ -136,3 +170,35 @@ func (dg *DotGitData) checkModules(modules []*protoModule) error {
}
return nil
}
func (dg *DotGitData) checkModulesLocal(modules []*protoModule) error {
for _, pm := range modules {
file := filepath.Join(pm.PathToGoMod(), goModFile)
_, err := os.Stat(file)
if err != nil {
return fmt.Errorf(
"cannot find go.mod file in %q", file)
}
pm.ShortNameWithLocalFlag(dg.RepoPath())
}
return nil
}
// Extract local prefix from origin url information retrieved from .git
func getLocalPrefix(dgAbsPath string) (string, error) {
_, err := os.Stat(dgAbsPath + "/.git")
if err != nil {
return "", fmt.Errorf(".git directory does not exist in path %s", dgAbsPath)
}
out, err := exec.Command("git", "config", "--get", "remote.origin.url").Output()
if err != nil {
return "", fmt.Errorf("failed extracting git information: %w", err)
}
localPrefix := utils.ParseGitRepositoryPath(string(out))
if len(localPrefix) == 0 {
_ = fmt.Errorf("parsed git repository path is empty: %w", err)
}
return localPrefix, nil
}

View File

@@ -3,7 +3,10 @@
package repo
import "testing"
import (
"testing"
)
func TestLoadTags(t *testing.T) {
func TestLoadRepoManager(t *testing.T) {
t.Skip()
}

View File

@@ -80,6 +80,16 @@ func (mgr *Manager) hasUnPinnedDeps(m misc.LaModule) string {
}
func (mgr *Manager) List() error {
// Auto-update local tags
gr := git.NewQuiet(mgr.AbsPath(), false, false)
for _, module := range mgr.modules {
releaseBranch := fmt.Sprintf("release-%s", module.ShortName())
_, err := gr.GetLatestTag(releaseBranch)
if err != nil {
return fmt.Errorf("failed getting latest tags for %s", module)
}
}
fmt.Printf(" src path: %s\n", mgr.dg.SrcPath())
fmt.Printf(" repo path: %s\n", mgr.RepoPath())
fmt.Printf(" remote: %s\n", mgr.remoteName)
@@ -113,8 +123,8 @@ func determineBranchAndTag(
string(m.ShortName()) + "/" + v.String()
}
func (mgr *Manager) Debug(_ misc.LaModule, doIt bool) error {
gr := git.NewLoud(mgr.AbsPath(), doIt)
func (mgr *Manager) Debug(_ misc.LaModule, doIt bool, localFlag bool) error {
gr := git.NewLoud(mgr.AbsPath(), doIt, localFlag)
return gr.Debug(mgr.remoteName)
}
@@ -122,10 +132,8 @@ func (mgr *Manager) Debug(_ misc.LaModule, doIt bool) error {
//
// * All development happens in the branch named "master".
// * Each minor release gets its own branch.
//
func (mgr *Manager) Release(
target misc.LaModule, bump semver.SvBump, doIt bool) error {
target misc.LaModule, bump semver.SvBump, doIt bool, localFlag bool) error {
if reps := target.GetDisallowedReplacements(
mgr.allowedReplacements); len(reps) > 0 {
return fmt.Errorf(
@@ -145,7 +153,7 @@ func (mgr *Manager) Release(
newVersion, target.VersionRemote())
}
gr := git.NewLoud(mgr.AbsPath(), doIt)
gr := git.NewLoud(mgr.AbsPath(), doIt, localFlag)
relBranch, relTag := determineBranchAndTag(target, newVersion)
@@ -189,14 +197,14 @@ func (mgr *Manager) Release(
return nil
}
func (mgr *Manager) UnRelease(target misc.LaModule, doIt bool) error {
func (mgr *Manager) UnRelease(target misc.LaModule, doIt bool, localFlag bool) error {
fmt.Printf(
"Unreleasing %s/%s\n",
target.ShortName(), target.VersionRemote())
_, tag := determineBranchAndTag(target, target.VersionRemote())
gr := git.NewLoud(mgr.AbsPath(), doIt)
gr := git.NewLoud(mgr.AbsPath(), doIt, localFlag)
if err := gr.DeleteTagFromRemote(mgr.remoteName, tag); err != nil {
return err

View File

@@ -37,3 +37,23 @@ func (mf *ManagerFactory) NewRepoManager(allowedReplacements []string) *Manager
result.allowedReplacements = allowedReplacements
return result
}
func (mf *ManagerFactory) NewRepoManagerWithLocalFlag(allowedReplacements []string) *Manager {
result := &Manager{
dg: mf.dg,
remoteName: mf.remoteName,
}
var modules misc.LesModules
for _, pm := range mf.modules {
shortName := pm.ShortNameWithLocalFlag(mf.dg.RepoPath())
modules = append(
modules,
mod.New(
result, shortName, pm.mf,
mf.versionMapLocal.Latest(shortName),
mf.versionMapRemote.Latest(shortName)))
}
result.modules = modules
result.allowedReplacements = allowedReplacements
return result
}

View File

@@ -8,7 +8,9 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
v "github.com/spf13/viper"
"golang.org/x/mod/modfile"
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/misc"
"sigs.k8s.io/kustomize/cmd/gorepomod/internal/utils"
@@ -28,6 +30,13 @@ func (pm *protoModule) FullPath() string {
return pm.mf.Module.Mod.Path
}
func (pm *protoModule) FullLocalPath() string {
var localPrefix string = v.GetString("LocalGitPrefix")
var pathToModule string = pm.mf.Module.Mod.Path
pathSlice := strings.Split(pathToModule, "/")
return localPrefix + "/" + strings.Join(pathSlice[1:], "/")
}
func (pm *protoModule) PathToGoMod() string {
return pm.pathToGoMod
}
@@ -47,6 +56,19 @@ func (pm *protoModule) ShortName(
return misc.ModuleShortName(stripped)
}
func (pm *protoModule) ShortNameWithLocalFlag(
repoImportPath string) misc.ModuleShortName {
fp := pm.FullLocalPath()
if fp == repoImportPath {
return misc.ModuleAtTop
}
p := fp[len(repoImportPath)+2:]
stripped := trailingVersionPattern.ReplaceAllString(p, "")
pathSlice := strings.Split(stripped, "/")
return misc.ModuleShortName(strings.Join(pathSlice[3:], "/"))
}
func loadProtoModules(
repoRoot string, exclusions []string) (result []*protoModule, err error) {
var paths []string