diff --git a/cmd/root.go b/cmd/root.go index d686d59f..f0ea4549 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -54,6 +54,10 @@ func init() { asyncHelp := "sets the number of applications to be processed in parallel\nthe default (0) is no limit" rootCmd.PersistentFlags().IntVarP(&asyncLevel, "async", "a", 0, asyncHelp) + smartModeBaseRevisionHelp := "base revision to compare against in Smart Mode\n" + + "if not provided, only local changes will be considered" + rootCmd.PersistentFlags().String("smart-mode.base-revision", "", smartModeBaseRevisionHelp) + if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { log.Fatal().Err(err).Msg("Unable to bind flags") } @@ -82,7 +86,7 @@ func detectTargetEnvsAndApps(cmd *cobra.Command, args []string) (err error) { // smart mode requires instantiation of globe object to get the list of environments // the globe object will not be used later in the process. It is only used to get the list of all environments and their apps. globeAllEnvsAndApps := myks.New(".") - targetEnvironments, targetApplications, err = globeAllEnvsAndApps.DetectChangedEnvsAndApps() + targetEnvironments, targetApplications, err = globeAllEnvsAndApps.DetectChangedEnvsAndApps(viper.GetString("smart-mode.base-revision")) if err != nil { log.Warn().Err(err).Msg("Unable to run Smart Mode. Rendering everything.") } diff --git a/internal/myks/git.go b/internal/myks/git.go deleted file mode 100644 index cb087cf5..00000000 --- a/internal/myks/git.go +++ /dev/null @@ -1,93 +0,0 @@ -package myks - -import ( - "fmt" - "os" - "regexp" - "strings" - - "github.com/rs/zerolog/log" -) - -type ChangedFiles map[string]string - -// getChangedFiles returns list of files changed sinc the revision, if specified, and since the last commit -func getChangedFiles(revision string) (ChangedFiles, error) { - logFn := func(name string, args []string) { - log.Debug().Msg(msgRunCmd("get diff for smart-mode", name, args)) - } - - files := ChangedFiles{} - if revision != "" { - result, err := runCmd("git", nil, []string{"diff", "--name-status", revision}, logFn) - if err != nil { - return nil, err - } - for path, status := range convertToChangedFiles(result.Stdout) { - files[path] = status - } - } - - result, err := runCmd("git", nil, []string{"status", "--porcelain"}, logFn) - if err != nil { - return nil, err - } - for path, status := range convertToChangedFiles(result.Stdout) { - files[path] = status - } - - return files, nil -} - -func convertToChangedFiles(changes string) ChangedFiles { - cfs := ChangedFiles{} - expr := regexp.MustCompile(`^([A-Z]\t|[A-Z? ]{2} )(.*)$`) - for _, str := range strings.Split(changes, "\n") { - matches := expr.FindStringSubmatch(str) - if len(matches) == 3 { - // 1: the first character of the status, after trimming spaces and tabs - // 2: the file path - cfs[matches[2]] = strings.Trim(matches[1], " \t")[:1] - } - } - return cfs -} - -// get head revision of main branch -func getMainBranchHeadRevision(mainBranch string) (string, error) { - logFn := func(name string, args []string) { - log.Debug().Msg(msgRunCmd("get main branch head revision for smart-mode", name, args)) - } - _, err := runCmd("git", nil, []string{"fetch", "origin", mainBranch}, logFn) - if err != nil { - return "", err - } - cmdResult, err := runCmd("git", nil, []string{"merge-base", "origin/" + mainBranch, "HEAD"}, logFn) - if err != nil { - return "", err - } - // git adds new line to output which messes up the result - headRevision := strings.TrimRight(cmdResult.Stdout, "\n") - return headRevision, nil -} - -// get head revision -func getCurrentBranchHeadRevision() (string, error) { - logFn := func(name string, args []string) { - log.Debug().Msg(msgRunCmd("get current head revision for smart-mode", name, args)) - } - cmdResult, err := runCmd("git", nil, []string{"rev-parse", "HEAD"}, logFn) - if err != nil { - return "", fmt.Errorf("failed to get current branch head revision: %v", err) - } - return strings.TrimRight(cmdResult.Stdout, "\n"), nil -} - -func getDiffRevision(mainBranch string) (string, error) { - if os.Getenv("CI") != "" { - log.Debug().Msg("Pipeline mode: comparing with HEAD revision on main") - return getMainBranchHeadRevision(mainBranch) - } - log.Debug().Msg("Local mode: comparing with HEAD revision on current branch") - return getCurrentBranchHeadRevision() -} diff --git a/internal/myks/globe.go b/internal/myks/globe.go index 2b81ac42..bedede48 100644 --- a/internal/myks/globe.go +++ b/internal/myks/globe.go @@ -23,6 +23,8 @@ type Globe struct { // Base directory for environments EnvironmentBaseDir string `default:"envs"` + // Main branch name + MainBranchName string `default:"main"` // Prefix for kubernetes namespaces NamespacePrefix string `default:""` // Application prototypes directory @@ -68,8 +70,6 @@ type Globe struct { YttPkgStepDirName string `default:"ytt-pkg"` // Ytt step directory name YttStepDirName string `default:"ytt"` - // Main branch name - MainBranchName string `default:"main"` /// User input diff --git a/internal/myks/smart_mode.go b/internal/myks/smart_mode.go index 23eff14b..22576524 100644 --- a/internal/myks/smart_mode.go +++ b/internal/myks/smart_mode.go @@ -40,7 +40,7 @@ func (g *Globe) getAppsExpr() string { return "^" + g.GitPathPrefix + "(" + g.EnvironmentBaseDir + "/.*?)/_apps/(.*?)/.*$" } -func (g *Globe) DetectChangedEnvsAndApps() ([]string, []string, error) { +func (g *Globe) DetectChangedEnvsAndApps(baseRevision string) ([]string, []string, error) { g.collectEnvironments(nil) err := process(0, g.environments, func(item interface{}) error { @@ -55,12 +55,7 @@ func (g *Globe) DetectChangedEnvsAndApps() ([]string, []string, error) { return nil, nil, err } - curRev, err := getDiffRevision(g.MainBranchName) - if err != nil { - log.Err(err).Msg(g.Msg("Failed to get current revision")) - return nil, nil, err - } - changedFiles, err := getChangedFiles(curRev) + changedFiles, err := getChangedFiles(baseRevision) if err != nil { log.Err(err).Msg(g.Msg("Failed to get diff")) return nil, nil, err diff --git a/internal/myks/smart_mode_git.go b/internal/myks/smart_mode_git.go new file mode 100644 index 00000000..9082cb45 --- /dev/null +++ b/internal/myks/smart_mode_git.go @@ -0,0 +1,52 @@ +package myks + +import ( + "regexp" + "strings" + + "github.com/rs/zerolog/log" +) + +type ChangedFiles map[string]string + +// getChangedFiles returns list of files changed since the baseRevision, if specified, and since the last commit +func getChangedFiles(baseRevision string) (ChangedFiles, error) { + logFn := func(name string, args []string) { + log.Debug().Msg(msgRunCmd("collect changed files for smart-mode", name, args)) + } + + files := ChangedFiles{} + if baseRevision != "" { + result, err := runCmd("git", nil, []string{"diff", "--name-status", baseRevision}, logFn) + if err != nil { + return nil, err + } + for path, status := range convertToChangedFiles(result.Stdout) { + files[path] = status + } + } + + result, err := runCmd("git", nil, []string{"status", "--porcelain"}, logFn) + if err != nil { + return nil, err + } + for path, status := range convertToChangedFiles(result.Stdout) { + files[path] = status + } + + return files, nil +} + +func convertToChangedFiles(changes string) ChangedFiles { + cfs := ChangedFiles{} + expr := regexp.MustCompile(`^([A-Z]\t|[A-Z? ]{2} )(.*)$`) + for _, str := range strings.Split(changes, "\n") { + matches := expr.FindStringSubmatch(str) + if len(matches) == 3 { + // 1: the first character of the status, after trimming spaces and tabs + // 2: the file path + cfs[matches[2]] = strings.Trim(matches[1], " \t")[:1] + } + } + return cfs +} diff --git a/internal/myks/git_test.go b/internal/myks/smart_mode_git_test.go similarity index 58% rename from internal/myks/git_test.go rename to internal/myks/smart_mode_git_test.go index 902e78e8..91bbed4f 100644 --- a/internal/myks/git_test.go +++ b/internal/myks/smart_mode_git_test.go @@ -28,53 +28,6 @@ func Test_getChangedFiles(t *testing.T) { } } -func Test_getMainBranchHeadRevision(t *testing.T) { - type args struct { - mainBranch string - } - tests := []struct { - name string - args args - wantErr bool - }{ - {"happy path", args{"main"}, false}, - {"sad path", args{"unknown-branch"}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getMainBranchHeadRevision(tt.args.mainBranch) - if (err != nil) != tt.wantErr { - t.Errorf("getMainBranchHeadRevision() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !tt.wantErr && got == "" { - t.Errorf("getMainBranchHeadRevision() must not be empty") - } - }) - } -} - -func Test_getCurrentBranchHeadRevision(t *testing.T) { - tests := []struct { - name string - wantErr bool - }{ - {"happy path", false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getCurrentBranchHeadRevision() - if (err != nil) != tt.wantErr { - t.Errorf("getCurrentBranchHeadRevision() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !tt.wantErr && got == "" { - t.Errorf("getCurrentBranchHeadRevision() must not be empty") - } - }) - } -} - func Test_convertToChangedFiles(t *testing.T) { type args struct { changes string