diff --git a/cmd/foreach/foreach.go b/cmd/foreach/foreach.go index fe7dd20..8e0bd45 100644 --- a/cmd/foreach/foreach.go +++ b/cmd/foreach/foreach.go @@ -26,6 +26,8 @@ import ( "github.com/skyscanner/turbolift/internal/colors" "github.com/skyscanner/turbolift/internal/executor" "github.com/skyscanner/turbolift/internal/logging" + + "github.com/alessio/shellescape" ) var exec executor.Executor = executor.NewRealExecutor() @@ -34,6 +36,14 @@ var ( repoFile string = "repos.txt" ) +func formatArguments(arguments []string) string { + quotedArgs := make([]string, len(arguments)) + for i, arg := range arguments { + quotedArgs[i] = shellescape.Quote(arg) + } + return strings.Join(quotedArgs, " ") +} + func NewForeachCmd() *cobra.Command { cmd := &cobra.Command{ Use: "foreach [--repos REPOFILE] -- COMMAND [ARGUMENT...]", @@ -64,13 +74,15 @@ func run(c *cobra.Command, args []string) { } readCampaignActivity.EndWithSuccess() - command := strings.Join(args, " ") + // We shell escape these to avoid ambiguity in our logs, and give + // the user something they could copy and paste. + prettyArgs := formatArguments(args) var doneCount, skippedCount, errorCount int for _, repo := range dir.Repos { repoDirPath := path.Join("work", repo.OrgName, repo.RepoName) // i.e. work/org/repo - execActivity := logger.StartActivity("Executing %s in %s", command, repoDirPath) + execActivity := logger.StartActivity("Executing %s in %s", prettyArgs, repoDirPath) // skip if the working copy does not exist if _, err = os.Stat(repoDirPath); os.IsNotExist(err) { diff --git a/go.mod b/go.mod index 0c462d6..66fd088 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/skyscanner/turbolift go 1.16 require ( + github.com/alessio/shellescape v1.4.2 // indirect github.com/briandowns/spinner v1.15.0 github.com/fatih/color v1.12.0 github.com/manifoldco/promptui v0.9.0 diff --git a/go.sum b/go.sum index 167c699..0009688 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=