diff --git a/cmd/state-installer/cmd.go b/cmd/state-installer/cmd.go index 462444eff5..5dfc5bfc6f 100644 --- a/cmd/state-installer/cmd.go +++ b/cmd/state-installer/cmd.go @@ -123,6 +123,19 @@ func main() { logging.Debug("Original Args: %v", os.Args) logging.Debug("Processed Args: %v", processedArgs) + // Store sessionToken to config + for _, envVar := range []string{constants.OverrideSessionTokenEnvVarName, constants.SessionTokenEnvVarName} { + sessionToken, ok := os.LookupEnv(envVar) + if !ok { + continue + } + err := cfg.Set(anaConst.CfgSessionToken, sessionToken) + if err != nil { + multilog.Error("Unable to set session token: " + errs.JoinMessage(err)) + } + break + } + an = sync.New(anaConst.SrcStateInstaller, cfg, nil, out) an.Event(anaConst.CatInstallerFunnel, "start") diff --git a/cmd/state-installer/installer.go b/cmd/state-installer/installer.go index eccf65709b..480b64f7a8 100644 --- a/cmd/state-installer/installer.go +++ b/cmd/state-installer/installer.go @@ -9,7 +9,6 @@ import ( svcApp "github.com/ActiveState/cli/cmd/state-svc/app" svcAutostart "github.com/ActiveState/cli/cmd/state-svc/autostart" - anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/errs" @@ -30,10 +29,9 @@ import ( ) type Installer struct { - out output.Outputer - cfg *config.Instance - payloadPath string - sessionToken string + out output.Outputer + cfg *config.Instance + payloadPath string *Params } @@ -49,13 +47,6 @@ func NewInstaller(cfg *config.Instance, out output.Outputer, payloadPath string, } func (i *Installer) Install() (rerr error) { - // Store sessionToken to config - if i.sessionToken != "" && i.cfg.GetString(anaConst.CfgSessionToken) == "" { - if err := i.cfg.Set(anaConst.CfgSessionToken, i.sessionToken); err != nil { - return errs.Wrap(err, "Failed to set session token") - } - } - // Store update tag if i.updateTag != "" { if err := i.cfg.Set(updater.CfgUpdateTag, i.updateTag); err != nil { @@ -145,9 +136,6 @@ func (i *Installer) InstallPath() string { // sanitizeInput cleans up the input and inserts fallback values func (i *Installer) sanitizeInput() error { - if sessionToken, ok := os.LookupEnv(constants.SessionTokenEnvVarName); ok { - i.sessionToken = sessionToken - } if tag, ok := os.LookupEnv(constants.UpdateTagEnvVarName); ok { i.updateTag = tag } diff --git a/cmd/state-installer/test/integration/installer_int_test.go b/cmd/state-installer/test/integration/installer_int_test.go index e65da195e2..5b4547e49b 100644 --- a/cmd/state-installer/test/integration/installer_int_test.go +++ b/cmd/state-installer/test/integration/installer_int_test.go @@ -8,9 +8,7 @@ import ( "runtime" "strings" "testing" - "time" - "github.com/ActiveState/termtest" "github.com/stretchr/testify/suite" "github.com/ActiveState/cli/internal/condition" @@ -18,7 +16,6 @@ import ( "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/environment" "github.com/ActiveState/cli/internal/fileutils" - "github.com/ActiveState/cli/internal/httputil" "github.com/ActiveState/cli/internal/installation" "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/subshell" @@ -37,7 +34,7 @@ func (suite *InstallerIntegrationTestSuite) TestInstallFromLocalSource() { ts := e2e.New(suite.T(), false) defer ts.Close() - suite.SetupRCFile(ts) + ts.SetupRCFile() suite.T().Setenv(constants.HomeEnvVarName, ts.Dirs.HomeDir) dir, err := ioutil.TempDir("", "system*") @@ -193,66 +190,6 @@ func (suite *InstallerIntegrationTestSuite) TestInstallErrorTips() { "error tips should be displayed in shell created by installer") } -func (suite *InstallerIntegrationTestSuite) TestStateTrayRemoval() { - suite.OnlyRunForTags(tagsuite.Installer, tagsuite.Critical) - ts := e2e.New(suite.T(), false) - defer ts.Close() - - dir := installationDir(ts) - - // Install a release version that still has state-tray. - version := "0.35.0-SHAb78e2a4" - var cp *e2e.SpawnedCmd - if runtime.GOOS != "windows" { - oneLiner := fmt.Sprintf("sh <(curl -q https://platform.activestate.com/dl/cli/pdli01/install.sh) -f -n -t %s -v %s", dir, version) - cp = ts.SpawnCmdWithOpts( - "bash", e2e.OptArgs("-c", oneLiner), - e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.OverwriteDefaultSystemPathEnvVarName, dir)), - ) - } else { - b, err := httputil.GetDirect("https://platform.activestate.com/dl/cli/pdli01/install.ps1") - suite.Require().NoError(err) - - ps1File := filepath.Join(ts.Dirs.Work, "install.ps1") - suite.Require().NoError(fileutils.WriteFile(ps1File, b)) - - cp = ts.SpawnCmdWithOpts("powershell.exe", e2e.OptArgs(ps1File, "-f", "-n", "-t", dir, "-v", version), - e2e.OptAppendEnv("SHELL="), - e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.OverwriteDefaultSystemPathEnvVarName, dir)), - ) - } - cp.Expect("Installation Complete", termtest.OptExpectTimeout(5*time.Minute)) - - // Verify state-tray is there. - svcExec, err := installation.ServiceExecFromDir(dir) - suite.Require().NoError(err) - trayExec := strings.Replace(svcExec, constants.StateSvcCmd, "state-tray", 1) - suite.FileExists(trayExec) - updateDialogExec := strings.Replace(svcExec, constants.StateSvcCmd, "state-update-dialog", 1) - // suite.FileExists(updateDialogExec) // this is not actually installed... - - // Run the installer, which should remove state-tray and clean up after it. - cp = ts.SpawnCmdWithOpts( - suite.installerExe, - e2e.OptArgs("-f", "-n", "-t", dir), - e2e.OptAppendEnv(constants.UpdateBranchEnvVarName+"=release"), - e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.OverwriteDefaultSystemPathEnvVarName, dir)), - ) - cp.Expect("Installing", termtest.OptExpectTimeout(10*time.Second)) - cp.Expect("Done", termtest.OptExpectTimeout(30*time.Second)) - - // Verify state-tray is no longer there. - suite.NoFileExists(trayExec) - suite.NoFileExists(updateDialogExec) - - // Verify state can still be run and has a newly updated version. - stateExec, err := installation.StateExecFromDir(dir) - suite.Require().NoError(err) - cp = ts.SpawnCmdWithOpts(stateExec, e2e.OptArgs("--version")) - suite.Assert().NotContains(cp.Output(), version) - cp.ExpectExitCode(0) -} - func (suite *InstallerIntegrationTestSuite) TestInstallerOverwriteServiceApp() { suite.OnlyRunForTags(tagsuite.Installer) if runtime.GOOS != "darwin" { @@ -286,22 +223,6 @@ func (suite *InstallerIntegrationTestSuite) TestInstallerOverwriteServiceApp() { cp.ExpectExitCode(0) } -func (suite *InstallerIntegrationTestSuite) SetupRCFile(ts *e2e.Session) { - if runtime.GOOS == "windows" { - return - } - - cfg, err := config.New() - suite.Require().NoError(err) - - subshell := subshell.New(cfg) - rcFile, err := subshell.RcFile() - suite.Require().NoError(err) - - err = fileutils.CopyFile(rcFile, filepath.Join(ts.Dirs.HomeDir, filepath.Base(rcFile))) - suite.Require().NoError(err) -} - func (suite *InstallerIntegrationTestSuite) AssertConfig(ts *e2e.Session) { if runtime.GOOS != "windows" { // Test bashrc diff --git a/cmd/state/main.go b/cmd/state/main.go index 1d0dba8224..0ee1d8d14e 100644 --- a/cmd/state/main.go +++ b/cmd/state/main.go @@ -32,6 +32,7 @@ import ( "github.com/ActiveState/cli/internal/prompt" _ "github.com/ActiveState/cli/internal/prompt" // Sets up survey defaults "github.com/ActiveState/cli/internal/rollbar" + "github.com/ActiveState/cli/internal/runbits/legacy/projectmigration" "github.com/ActiveState/cli/internal/runbits/panics" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/internal/svcctl" @@ -211,6 +212,13 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out // Set up prompter prompter := prompt.New(isInteractive, an) + // This is an anti-pattern. DO NOT DO THIS! Normally we should be passing prompt and out as + // arguments everywhere it is needed. However, we need to support legacy projects with commitId in + // activestate.yaml, and whenever that commitId is needed, we need to prompt the user to migrate + // their project. This would result in a lot of boilerplate for a legacy feature, so we're + // working around it with package "globals". + projectmigration.Register(prompter, out) + // Set up conditional, which accesses a lot of primer data sshell := subshell.New(cfg) diff --git a/go.mod b/go.mod index bdbe6ab90d..9a561d963e 100644 --- a/go.mod +++ b/go.mod @@ -133,7 +133,7 @@ require ( github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/sergi/go-diff v1.3.1 + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 github.com/src-d/gcfg v1.4.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect diff --git a/go.sum b/go.sum index f0a67c7427..556af48072 100644 --- a/go.sum +++ b/go.sum @@ -941,6 +941,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w= github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y= github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= diff --git a/installers/install.ps1 b/installers/install.ps1 index 6fa7d6a63a..ca6efb24de 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -113,40 +113,54 @@ function error([string] $msg) Write-Host $msg -ForegroundColor Red } -if (!$script:VERSION) { +$version = $script:VERSION +if (!$version) { # If the user did not specify a version, formulate a query to fetch the JSON info of the latest # version, including where it is. $jsonURL = "$script:BASEINFOURL/?channel=$script:CHANNEL&platform=windows&source=install" -} elseif (!($script:VERSION | Select-String -Pattern "-SHA" -SimpleMatch)) { +} elseif (!($version | Select-String -Pattern "-SHA" -SimpleMatch)) { # If the user specified a partial version (i.e. no SHA), formulate a query to fetch the JSON # info of that version's latest SHA, including where it is. - $jsonURL = "$script:BASEINFOURL/?channel=$script:CHANNEL&platform=windows&source=install&target-version=$script:VERSION" + $versionNoSHA = $version + $version = "" + $jsonURL = "$script:BASEINFOURL/?channel=$script:CHANNEL&platform=windows&source=install&target-version=$versionNoSHA" +} else { + # If the user specified a full version with SHA, formulate a query to fetch the JSON info of + # that version. + $versionNoSHA = $version -replace "-SHA.*", "" + $jsonURL = "$script:BASEINFOURL/?channel=$script:CHANNEL&platform=windows&source=install&target-version=$versionNoSHA" } -if ($jsonURL) { - # If the user specified no version or a partial version we need to use the json URL to get the - # actual installer URL. - try { - $infoJson = ConvertFrom-Json -InputObject (download $jsonURL) - } catch [System.Exception] { - } - if (!$infoJson) { - if (!$script:VERSION) { +# Fetch version info. +try { + $infoJson = ConvertFrom-Json -InputObject (download $jsonURL) +} catch [System.Exception] { +} +if (!$infoJson) { + if (!$version) { Write-Error "Unable to retrieve the latest version number" - } else { + } else { Write-Error "Could not download a State Tool Installer for the given command line arguments" - } - Write-Error $_.Exception.Message - exit 1 } + Write-Error $_.Exception.Message + exit 1 +} + +# Extract checksum. +$checksum = $infoJson.Sha256 + +if (!$version) { + # If the user specified no version or a partial version we need to use the json URL to get the + # actual installer URL. $version = $infoJson.Version - $checksum = $infoJson.Sha256 $relUrl = $infoJson.Path } else { - # If the user specified a full version, strip the SHA to get the folder name of the installer - # URL. Then we can construct the installer URL. - $versionNoSHA = $script:VERSION -replace "-SHA.*", "" - $relUrl = "$script:CHANNEL/$versionNoSHA/windows-amd64/state-windows-amd64-$script:VERSION.zip" + # If the user specified a full version, construct the installer URL. + if ($version -ne $infoJson.Version) { + Write-Error "Unknown version: $version" + exit 1 + } + $relUrl = "$script:CHANNEL/$versionNoSHA/windows-amd64/state-windows-amd64-$version.zip" } # Fetch the requested or latest version. @@ -167,9 +181,9 @@ catch [System.Exception] exit 1 } -# Verify checksum if possible. +# Verify checksum. $hash = (Get-FileHash -Path $zipPath -Algorithm SHA256).Hash -if ($checksum -and $hash -ne $checksum) +if ($hash -ne $checksum) { Write-Warning "SHA256 sum did not match:" Write-Warning "Expected: $checksum" diff --git a/installers/install.sh b/installers/install.sh index 2afa290a9b..28aa820928 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -117,32 +117,41 @@ if [ -z "$VERSION" ]; then elif [ -z "`echo $VERSION | grep -o '\-SHA'`" ]; then # If the user specified a partial version (i.e. no SHA), formulate a query to fetch the JSON info # of that version's latest SHA, including where it is. - JSONURL="$BASE_INFO_URL?channel=$CHANNEL&source=install&platform=$OS&target-version=$VERSION" + VERSIONNOSHA="$VERSION" + VERSION="" + JSONURL="$BASE_INFO_URL?channel=$CHANNEL&source=install&platform=$OS&target-version=$VERSIONNOSHA" +else + # If the user specified a full version with SHA, formulate a query to fetch the JSON info of that + # version. + VERSIONNOSHA="`echo $VERSION | sed 's/-SHA.*$//'`" + JSONURL="$BASE_INFO_URL?channel=$CHANNEL&source=install&platform=$OS&target-version=$VERSIONNOSHA" +fi + +# Fetch version info. +$FETCH $INSTALLERTMPDIR/info.json $JSONURL || exit 1 +if [ ! -z "`grep -o Invalid $INSTALLERTMPDIR/info.json`" ]; then + error "Could not download a State Tool installer for the given command line arguments" + exit 1 fi -if [ ! -z "$JSONURL" ]; then +# Extract checksum. +SUM=`cat $INSTALLERTMPDIR/info.json | sed -ne 's/.*"sha256":[ \t]*"\([^"]*\)".*/\1/p'` + +if [ -z "$VERSION" ]; then # If the user specified no version or a partial version we need to use the json URL to get the # actual installer URL. - $FETCH $INSTALLERTMPDIR/info.json $JSONURL || exit 1 - if [ ! -z "`grep -o Invalid $INSTALLERTMPDIR/info.json`" ]; then - error "Could not download a State Tool installer for the given command line arguments" - exit 1 - fi - - # Parse info. VERSION=`cat $INSTALLERTMPDIR/info.json | sed -ne 's/.*"version":[ \t]*"\([^"]*\)".*/\1/p'` if [ -z "$VERSION" ]; then error "Unable to retrieve the latest version number" exit 1 fi - SUM=`cat $INSTALLERTMPDIR/info.json | sed -ne 's/.*"sha256":[ \t]*"\([^"]*\)".*/\1/p'` RELURL=`cat $INSTALLERTMPDIR/info.json | sed -ne 's/.*"path":[ \t]*"\([^"]*\)".*/\1/p'` - rm $INSTALLERTMPDIR/info.json - else - # If the user specified a full version, strip the SHA to get the folder name of the installer URL. - # Then we can construct the installer URL. - VERSIONNOSHA="`echo $VERSION | sed 's/-SHA.*$//'`" + # If the user specified a full version, construct the installer URL. + if [ "$VERSION" != "`cat $INSTALLERTMPDIR/info.json | sed -ne 's/.*"version":[ \t]*"\([^"]*\)".*/\1/p'`" ]; then + error "Unknown version: $VERSION" + exit 1 + fi RELURL="$CHANNEL/$VERSIONNOSHA/$OS-amd64/state-$OS-amd64-$VERSION$DOWNLOADEXT" fi @@ -162,8 +171,8 @@ if [ $? -ne 0 -o \( "`echo $FETCH | grep -o 'curl'`" = "curl" -a ! -z "`grep -o exit 1 fi -# Verify checksum if possible. -if [ ! -z "$SUM" -a "`$SHA256SUM -b $INSTALLERTMPDIR/$ARCHIVE | cut -d ' ' -f1`" != "$SUM" ]; then +# Verify checksum. +if [ "`$SHA256SUM -b $INSTALLERTMPDIR/$ARCHIVE | cut -d ' ' -f1`" != "$SUM" ]; then error "SHA256 sum did not match:" error "Expected: $SUM" error "Received: `$SHA256SUM -b $INSTALLERTMPDIR/$ARCHIVE | cut -d ' ' -f1`" diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 6313383ccc..bdcf611455 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -59,6 +59,13 @@ const DisableRuntime = "ACTIVESTATE_CLI_DISABLE_RUNTIME" // DisableUpdates is the env var used to disable automatic updates const DisableUpdates = "ACTIVESTATE_CLI_DISABLE_UPDATES" +// DisableLanguageTemplates is the env var used to disable templating for new activestate.yaml files +const DisableLanguageTemplates = "ACTIVESTATE_CLI_DISABLE_LANGUAGE_TEMPLATES" + +// DisableProjectMigrationPrompt is the env var used to disable the project migration prompt for legacy projects. +// This is set by default for integration tests for backward-compatibility with old integration tests. +const DisableProjectMigrationPrompt = "ACTIVESTATE_CLI_DISABLE_PROJECT_MIGRATION_PROMPT" + // UpdateBranchEnvVarName is the env var that is used to override which branch to pull the update from const UpdateBranchEnvVarName = "ACTIVESTATE_CLI_UPDATE_BRANCH" @@ -107,6 +114,9 @@ const ProfileEnvVarName = "ACTIVESTATE_PROFILE" // SessionTokenEnvVarName records the session token const SessionTokenEnvVarName = "ACTIVESTATE_SESSION_TOKEN" +// OverrideSessionTokenEnvVarName overrides SessionTokenEnvVarName for integration tests. +const OverrideSessionTokenEnvVarName = "ACTIVESTATE_OVERRIDE_SESSION_TOKEN" + // UpdateTagEnvVarName const UpdateTagEnvVarName = "ACTIVESTATE_UPDATE_TAG" diff --git a/internal/constraints/constraints.go b/internal/constraints/constraints.go index b6c42444be..fc454f90ee 100644 --- a/internal/constraints/constraints.go +++ b/internal/constraints/constraints.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/internal/rtutils/ptr" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/projectfile" "github.com/ActiveState/cli/pkg/sysinfo" @@ -81,6 +81,7 @@ type projectable interface { Path() string Dir() string URL() string + LegacyCommitID() string // for commitmediator.Get } func NewPrimeConditional(auth *authentication.Auth, pj projectable, subshellName string) *Conditional { @@ -98,8 +99,8 @@ func NewPrimeConditional(auth *authentication.Auth, pj projectable, subshellName pjName = pj.Name() pjNamespace = pj.NamespaceString() pjURL = pj.URL() - commitID, err := localcommit.Get(pj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(pj) + if err != nil { multilog.Error("Unable to get local commit: %v", errs.JoinMessage(err)) } pjCommit = commitID.String() diff --git a/internal/locale/locales/en-us.yaml b/internal/locale/locales/en-us.yaml index b95d39c06f..4fdc7a6ea1 100644 --- a/internal/locale/locales/en-us.yaml +++ b/internal/locale/locales/en-us.yaml @@ -2068,5 +2068,12 @@ err_local_commit_file: To view your projects commits run '[ACTIONABLE]state history[/RESET]'. Please avoid deleting or editing this file directly. +projectmigration_confirm: + other: | + State Tool has introduced a new project format to provide enhanced functionality. Your project is currently using the old format and will remain read-only until you migrate to the new project format. + + [ERROR]WARNING:[/RESET] After you migrate, older versions of the State Tool will not be able to use your project. + + Would you like to perform the migration now? err_searchingredient_toomany: other: Too many ingredients match the query '[ACTIONABLE]{{.V0}}[/RESET]', please try to be more specific. diff --git a/internal/runbits/buildscript/buildscript_test.go b/internal/runbits/buildscript/buildscript_test.go index 83daaf4b5e..ceb19353b2 100644 --- a/internal/runbits/buildscript/buildscript_test.go +++ b/internal/runbits/buildscript/buildscript_test.go @@ -2,8 +2,10 @@ package buildscript import ( "encoding/json" + "path/filepath" "testing" + "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/rtutils/ptr" "github.com/ActiveState/cli/pkg/platform/runtime/buildexpression" "github.com/ActiveState/cli/pkg/platform/runtime/buildscript" @@ -64,3 +66,69 @@ in: in: runtime`, result) } + +// TestRealWorld tests a real-world case where: +// - There is a Platform Python project with an initial commit. +// - There is a local project that just checks it out. +// - The Platform project adds requests@2.30.0 (an older version). +// - The local project adds requests (latest version). +// - The local project pulls from the Platform project, resulting in conflicting times and version +// requirements for requests. +func TestRealWorld(t *testing.T) { + script1, err := buildscript.NewScript(fileutils.ReadFileUnsafe(filepath.Join("testdata", "buildscript1.yaml"))) + require.NoError(t, err) + script2, err := buildscript.NewScript(fileutils.ReadFileUnsafe(filepath.Join("testdata", "buildscript2.yaml"))) + require.NoError(t, err) + result, err := generateDiff(script1, script2.Expr) + require.NoError(t, err) + assert.Equal(t, `let: + runtime = state_tool_artifacts_v1( + build_flags = [ + ], + camel_flags = [ + ], + src = "$sources" + ) + sources = solve( +<<<<<<< local + at_time = "2023-10-16T22:20:29.000000Z", +======= + at_time = "2023-08-01T16:20:11.985000Z", +>>>>>>> remote + platforms = [ + "78977bc8-0f32-519d-80f3-9043f059398c", + "7c998ec2-7491-4e75-be4d-8885800ef5f2", + "96b7e6f2-bebf-564c-bc1c-f04482398f38" + ], + requirements = [ + { + name = "python", + namespace = "language", + version_requirements = [ + { + comparator = "eq", + version = "3.10.11" + } + ] + }, + { + name = "requests", +<<<<<<< local + namespace = "language/python" +======= + namespace = "language/python", + version_requirements = [ + { + comparator = "eq", + version = "2.30.0" + } + ] +>>>>>>> remote + } + ], + solver_version = null + ) + +in: + runtime`, result) +} diff --git a/internal/runbits/buildscript/testdata/buildscript1.yaml b/internal/runbits/buildscript/testdata/buildscript1.yaml new file mode 100644 index 0000000000..7ae010f237 --- /dev/null +++ b/internal/runbits/buildscript/testdata/buildscript1.yaml @@ -0,0 +1,36 @@ +let: + runtime = state_tool_artifacts_v1( + build_flags = [ + ], + camel_flags = [ + ], + src = "$sources" + ) + sources = solve( + at_time = "2023-10-16T22:20:29.000000Z", + platforms = [ + "78977bc8-0f32-519d-80f3-9043f059398c", + "7c998ec2-7491-4e75-be4d-8885800ef5f2", + "96b7e6f2-bebf-564c-bc1c-f04482398f38" + ], + requirements = [ + { + name = "python", + namespace = "language", + version_requirements = [ + { + comparator = "eq", + version = "3.10.11" + } + ] + }, + { + name = "requests", + namespace = "language/python" + } + ], + solver_version = null + ) + +in: + runtime \ No newline at end of file diff --git a/internal/runbits/buildscript/testdata/buildscript2.yaml b/internal/runbits/buildscript/testdata/buildscript2.yaml new file mode 100644 index 0000000000..0196b1d734 --- /dev/null +++ b/internal/runbits/buildscript/testdata/buildscript2.yaml @@ -0,0 +1,42 @@ +let: + runtime = state_tool_artifacts_v1( + build_flags = [ + ], + camel_flags = [ + ], + src = "$sources" + ) + sources = solve( + at_time = "2023-08-01T16:20:11.985000Z", + platforms = [ + "78977bc8-0f32-519d-80f3-9043f059398c", + "7c998ec2-7491-4e75-be4d-8885800ef5f2", + "96b7e6f2-bebf-564c-bc1c-f04482398f38" + ], + requirements = [ + { + name = "python", + namespace = "language", + version_requirements = [ + { + comparator = "eq", + version = "3.10.11" + } + ] + }, + { + name = "requests", + namespace = "language/python", + version_requirements = [ + { + comparator = "eq", + version = "2.30.0" + } + ] + } + ], + solver_version = null + ) + +in: + runtime \ No newline at end of file diff --git a/internal/runbits/commitmediator/commitmediator.go b/internal/runbits/commitmediator/commitmediator.go new file mode 100644 index 0000000000..64bb3d2709 --- /dev/null +++ b/internal/runbits/commitmediator/commitmediator.go @@ -0,0 +1,33 @@ +package commitmediator + +import ( + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/runbits/legacy/projectmigration" + "github.com/ActiveState/cli/pkg/localcommit" + "github.com/go-openapi/strfmt" +) + +type projecter interface { + Dir() string + URL() string + Path() string + LegacyCommitID() string +} + +// Get returns the given project's commit ID in either the new format (commit file), or the old +// format (activestate.yaml). +// If you require the commit file, use localcommit.Get(). +func Get(proj projecter) (strfmt.UUID, error) { + if commitID, err := localcommit.Get(proj.Dir()); err == nil { + return commitID, nil + } else if localcommit.IsFileDoesNotExistError(err) { + if migrated, err := projectmigration.PromptAndMigrate(proj); err == nil && migrated { + return localcommit.Get(proj.Dir()) + } else if err != nil { + return "", errs.Wrap(err, "Could not prompt and/or migrate project") + } + return strfmt.UUID(proj.LegacyCommitID()), nil + } else { + return "", errs.Wrap(err, "Could not get local commit") + } +} diff --git a/internal/runbits/legacy/projectmigration/projectmigration.go b/internal/runbits/legacy/projectmigration/projectmigration.go new file mode 100644 index 0000000000..cd701aec22 --- /dev/null +++ b/internal/runbits/legacy/projectmigration/projectmigration.go @@ -0,0 +1,93 @@ +package projectmigration + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + + "github.com/ActiveState/cli/internal/constants" + "github.com/ActiveState/cli/internal/errs" + "github.com/ActiveState/cli/internal/fileutils" + "github.com/ActiveState/cli/internal/locale" + "github.com/ActiveState/cli/internal/multilog" + "github.com/ActiveState/cli/internal/output" + "github.com/ActiveState/cli/internal/prompt" + "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/pkg/projectfile" +) + +type projecter interface { + Dir() string + URL() string + Path() string + LegacyCommitID() string +} + +var prompter prompt.Prompter +var out output.Outputer +var declined bool + +// Register exists to avoid boilerplate in passing prompt and out to every caller of +// commitmediator.Get() for retrieving legacy commitId from activestate.yaml. +// This is an anti-pattern and is only used to make this legacy feature palatable. +func Register(prompter_ prompt.Prompter, out_ output.Outputer) { + prompter = prompter_ + out = out_ +} + +func PromptAndMigrate(proj projecter) (bool, error) { + if prompter == nil || out == nil { + return false, errs.New("projectmigration.Register() has not been called") + } + + if declined { + return false, nil + } + + if os.Getenv(constants.DisableProjectMigrationPrompt) == "true" { + return false, nil + } + + defaultChoice := false + if migrate, err := prompter.Confirm("", locale.T("projectmigration_confirm"), &defaultChoice); err == nil && !migrate { + if out.Config().Interactive { + out.Notice(locale.Tl("projectmigration_declined", "Migration declined for now")) + } + declined = true + return false, nil + } else if err != nil { + return false, errs.Wrap(err, "Could not confirm migration choice") + } + + if err := localcommit.Set(proj.Dir(), proj.LegacyCommitID()); err != nil { + return false, errs.Wrap(err, "Could not create local commit file") + } + + for dir := proj.Dir(); filepath.Dir(dir) != dir; dir = filepath.Dir(dir) { + if !fileutils.DirExists(filepath.Join(dir, ".git")) { + continue + } + err := localcommit.AddToGitIgnore(dir) + if err != nil { + if !errors.Is(err, fs.ErrPermission) { + multilog.Error("Unable to add local commit file to .gitignore: %v", err) + } + out.Notice(locale.T("notice_commit_id_gitignore")) + } + break + } + + pf := projectfile.NewProjectField() + if err := pf.LoadProject(proj.URL()); err != nil { + return false, errs.Wrap(err, "Could not load activestate.yaml") + } + pf.StripCommitID() + if err := pf.Save(proj.Path()); err != nil { + return false, errs.Wrap(err, "Could not save activestate.yaml") + } + + out.Notice(locale.Tl("projectmigration_success", "Your project was successfully migrated")) + + return true, nil +} diff --git a/internal/runners/activate/activate.go b/internal/runners/activate/activate.go index 97cd2abbe9..ae3dc37082 100644 --- a/internal/runners/activate/activate.go +++ b/internal/runners/activate/activate.go @@ -22,6 +22,7 @@ import ( "github.com/ActiveState/cli/internal/process" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits/activation" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/runbits/findproject" "github.com/ActiveState/cli/internal/runbits/runtime" "github.com/ActiveState/cli/internal/subshell" @@ -29,7 +30,6 @@ import ( "github.com/ActiveState/cli/pkg/cmdlets/checker" "github.com/ActiveState/cli/pkg/cmdlets/checkout" "github.com/ActiveState/cli/pkg/cmdlets/git" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/target" @@ -189,8 +189,8 @@ func (r *Activate) Run(params *ActivateParams) (rerr error) { } } - commitID, err := localcommit.Get(proj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(proj) + if err != nil { return errs.Wrap(err, "Unable to get local commit") } if commitID == "" { diff --git a/internal/runners/cve/cve.go b/internal/runners/cve/cve.go index da56304200..9c58520fe8 100644 --- a/internal/runners/cve/cve.go +++ b/internal/runners/cve/cve.go @@ -7,7 +7,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" medmodel "github.com/ActiveState/cli/pkg/platform/api/mediator/model" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" @@ -66,7 +66,7 @@ func (c *Cve) Run() error { ) } - commitID, err := localcommit.Get(c.proj.Dir()) + commitID, err := commitmediator.Get(c.proj) if err != nil { return errs.Wrap(err, "Could not get local commit") } diff --git a/internal/runners/cve/report.go b/internal/runners/cve/report.go index 40ecea55bd..8315c71238 100644 --- a/internal/runners/cve/report.go +++ b/internal/runners/cve/report.go @@ -9,7 +9,7 @@ import ( "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" medmodel "github.com/ActiveState/cli/pkg/platform/api/mediator/model" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" @@ -102,7 +102,7 @@ func (r *Report) fetchVulnerabilities(namespaceOverride project.Namespaced) (*me commitID = namespaceOverride.CommitID.String() } else { var err error - commitUUID, err := localcommit.Get(r.proj.Dir()) + commitUUID, err := commitmediator.Get(r.proj) if err != nil { return nil, errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/runners/export/recipe.go b/internal/runners/export/recipe.go index 4f99d181fc..34c32411a3 100644 --- a/internal/runners/export/recipe.go +++ b/internal/runners/export/recipe.go @@ -8,7 +8,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" "github.com/ActiveState/cli/pkg/sysinfo" @@ -83,8 +83,8 @@ func fetchRecipe(proj *project.Project, commitID strfmt.UUID, platform string) ( if commitID == "" { var err error - commitID, err = localcommit.Get(proj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err = commitmediator.Get(proj) + if err != nil { return "", errs.Wrap(err, "Unable to get local commit") } } diff --git a/internal/runners/hello/hello_example.go b/internal/runners/hello/hello_example.go index 72d1d79507..bd78721b4b 100644 --- a/internal/runners/hello/hello_example.go +++ b/internal/runners/hello/hello_example.go @@ -15,8 +15,8 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/runbits" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/runbits/rationalize" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" ) @@ -145,7 +145,7 @@ func currentCommitMessage(proj *project.Project) (string, error) { return "", errs.New("Cannot determine which project to use") } - commitId, err := localcommit.Get(proj.Dir()) + commitId, err := commitmediator.Get(proj) if err != nil { return "", errs.Wrap(err, "Cannot determine which commit to use") } diff --git a/internal/runners/history/history.go b/internal/runners/history/history.go index d5b15fd827..55f7d26e49 100644 --- a/internal/runners/history/history.go +++ b/internal/runners/history/history.go @@ -5,8 +5,8 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/cmdlets/commit" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" @@ -39,7 +39,7 @@ func (h *History) Run(params *HistoryParams) error { } h.out.Notice(locale.Tl("operating_message", "", h.project.NamespaceString(), h.project.Dir())) - localCommitID, err := localcommit.Get(h.project.Dir()) + localCommitID, err := commitmediator.Get(h.project) if err != nil { return errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/runners/initialize/init.go b/internal/runners/initialize/init.go index 44c206548c..caa393e997 100644 --- a/internal/runners/initialize/init.go +++ b/internal/runners/initialize/init.go @@ -17,6 +17,7 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/runbits" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" @@ -69,8 +70,8 @@ func inferLanguage(config projectfile.ConfigGetter) (string, string, bool) { if err != nil { return "", "", false } - commitID, err := localcommit.Get(defaultProj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(defaultProj) + if err != nil { multilog.Error("Unable to get local commit: %v", errs.JoinMessage(err)) return "", "", false } diff --git a/internal/runners/languages/languages.go b/internal/runners/languages/languages.go index 5abd10fa96..1381ede185 100644 --- a/internal/runners/languages/languages.go +++ b/internal/runners/languages/languages.go @@ -6,7 +6,7 @@ import ( "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" ) @@ -31,7 +31,7 @@ func (l *Languages) Run() error { return locale.NewInputError("err_no_project") } - commitID, err := localcommit.Get(l.project.Dir()) + commitID, err := commitmediator.Get(l.project) if err != nil { return errs.AddTips( locale.WrapError( diff --git a/internal/runners/packages/list.go b/internal/runners/packages/list.go index 115322aa91..f99ff25614 100644 --- a/internal/runners/packages/list.go +++ b/internal/runners/packages/list.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" ) @@ -109,8 +109,8 @@ func targetFromProjectFile(proj *project.Project) (*strfmt.UUID, error) { if proj == nil { return nil, locale.NewInputError("err_no_project") } - commit, err := localcommit.Get(proj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commit, err := commitmediator.Get(proj) + if err != nil { return nil, errs.Wrap(err, "Unable to get local commit") } if commit == "" { diff --git a/internal/runners/packages/search.go b/internal/runners/packages/search.go index a7f243b791..d1b96698ba 100644 --- a/internal/runners/packages/search.go +++ b/internal/runners/packages/search.go @@ -9,7 +9,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" ) @@ -78,8 +78,8 @@ func targetedLanguage(languageOpt string, proj *project.Project) (string, error) ) } - commitID, err := localcommit.Get(proj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(proj) + if err != nil { return "", errs.Wrap(err, "Unable to get local commit") } lang, err := model.LanguageByCommit(commitID) diff --git a/internal/runners/platforms/list.go b/internal/runners/platforms/list.go index 0de857b618..83311eb717 100644 --- a/internal/runners/platforms/list.go +++ b/internal/runners/platforms/list.go @@ -5,7 +5,7 @@ import ( "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" "github.com/go-openapi/strfmt" @@ -33,8 +33,8 @@ func (l *List) Run() error { return locale.NewInputError("err_no_project") } - commitID, err := localcommit.Get(l.proj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(l.proj) + if err != nil { return errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/runners/prepare/prepare.go b/internal/runners/prepare/prepare.go index a5e30e8574..f4050c4f74 100644 --- a/internal/runners/prepare/prepare.go +++ b/internal/runners/prepare/prepare.go @@ -20,8 +20,8 @@ import ( "github.com/ActiveState/cli/internal/osutils/autostart" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/subshell" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/model" rt "github.com/ActiveState/cli/pkg/platform/runtime" "github.com/ActiveState/cli/pkg/platform/runtime/target" @@ -74,7 +74,7 @@ func (r *Prepare) resetExecutors() error { return errs.Wrap(err, "Could not get project from its directory") } - commitID, err := localcommit.Get(proj.Dir()) + commitID, err := commitmediator.Get(proj) if err != nil { return errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/runners/shell/shell.go b/internal/runners/shell/shell.go index dfb4f6c9a2..15c0d8f540 100644 --- a/internal/runners/shell/shell.go +++ b/internal/runners/shell/shell.go @@ -11,11 +11,11 @@ import ( "github.com/ActiveState/cli/internal/process" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits/activation" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/runbits/findproject" "github.com/ActiveState/cli/internal/runbits/runtime" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/internal/virtualenvironment" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/setup" @@ -72,7 +72,7 @@ func (u *Shell) Run(params *Params) error { return locale.WrapError(err, "err_shell_cannot_load_project") } - commitID, err := localcommit.Get(proj.Dir()) + commitID, err := commitmediator.Get(proj) if err != nil { return errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/runners/show/show.go b/internal/runners/show/show.go index 40750905eb..faeda19abd 100644 --- a/internal/runners/show/show.go +++ b/internal/runners/show/show.go @@ -14,8 +14,8 @@ import ( "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/secrets" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/api/mono/mono_models" secretsapi "github.com/ActiveState/cli/pkg/platform/api/secrets" "github.com/ActiveState/cli/pkg/platform/authentication" @@ -193,7 +193,7 @@ func (s *Show) Run(params Params) error { return locale.WrapError(err, "err_show_scripts", "Could not parse scripts") } - commitID, err = localcommit.Get(s.project.Dir()) + commitID, err = commitmediator.Get(s.project) if err != nil { return errs.Wrap(err, "Unable to get local commit") } @@ -385,7 +385,7 @@ func commitsData(owner, project, branchName string, commitID strfmt.UUID, localP if err != nil { return "", locale.WrapError(err, "err_show_commits_behind", "Could not determine number of commits behind latest") } - localCommitID, err := localcommit.Get(localProject.Dir()) + localCommitID, err := commitmediator.Get(localProject) if err != nil { return "", errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/runners/update/update.go b/internal/runners/update/update.go index 12e1164dc9..599827e140 100644 --- a/internal/runners/update/update.go +++ b/internal/runners/update/update.go @@ -55,7 +55,8 @@ func New(prime primeable) *Update { func (u *Update) Run(params *Params) error { // Check for available update - upd, err := u.svc.CheckUpdate(context.Background(), params.Channel, "") + channel := fetchChannel(params.Channel, false) + upd, err := u.svc.CheckUpdate(context.Background(), channel, "") if err != nil { return errs.AddTips(locale.WrapError( err, "err_update_fetch", @@ -69,7 +70,7 @@ func (u *Update) Run(params *Params) error { if !update.ShouldInstall() { logging.Debug("No update found") u.out.Print(output.Prepare( - locale.Tr("update_none_found", params.Channel), + locale.Tr("update_none_found", channel), &struct{}{}, )) return nil @@ -79,10 +80,10 @@ func (u *Update) Run(params *Params) error { // Handle switching channels var installPath string - if params.Channel != "" && params.Channel != constants.BranchName { - installPath, err = installation.InstallPathForBranch(params.Channel) + if channel != constants.BranchName { + installPath, err = installation.InstallPathForBranch(channel) if err != nil { - return locale.WrapError(err, "err_update_install_path", "Could not get installation path for branch {{.V0}}", params.Channel) + return locale.WrapError(err, "err_update_install_path", "Could not get installation path for branch {{.V0}}", channel) } } @@ -100,7 +101,7 @@ func (u *Update) Run(params *Params) error { } message := "" - if params.Channel != constants.BranchName { + if channel != constants.BranchName { message = locale.Tl("update_switch_channel", "[NOTICE]Please start a new shell for the update to take effect.[/RESET]") } u.out.Print(output.Prepare( diff --git a/internal/runners/use/use.go b/internal/runners/use/use.go index fbf4a09199..7b42f8d641 100644 --- a/internal/runners/use/use.go +++ b/internal/runners/use/use.go @@ -12,13 +12,13 @@ import ( "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/prompt" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/runbits/findproject" "github.com/ActiveState/cli/internal/runbits/runtime" "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/cli/pkg/cmdlets/checker" "github.com/ActiveState/cli/pkg/cmdlets/checkout" "github.com/ActiveState/cli/pkg/cmdlets/git" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/platform/runtime/setup" @@ -77,7 +77,7 @@ func (u *Use) Run(params *Params) error { return locale.WrapInputError(err, "err_use_cannot_find_local_project", "Local project cannot be found.") } - commitID, err := localcommit.Get(proj.Dir()) + commitID, err := commitmediator.Get(proj) if err != nil { return errs.Wrap(err, "Unable to get local commit") } diff --git a/internal/testhelpers/e2e/session.go b/internal/testhelpers/e2e/session.go index 5ce4563cc1..2f25d5fd4f 100644 --- a/internal/testhelpers/e2e/session.go +++ b/internal/testhelpers/e2e/session.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/ActiveState/cli/internal/subshell" "github.com/ActiveState/termtest" "github.com/go-openapi/strfmt" "github.com/google/uuid" @@ -32,6 +33,8 @@ import ( "github.com/ActiveState/cli/internal/osutils/stacktrace" "github.com/ActiveState/cli/internal/rtutils/singlethread" "github.com/ActiveState/cli/internal/strutils" + "github.com/ActiveState/cli/internal/subshell/bash" + "github.com/ActiveState/cli/internal/subshell/sscommon" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" "github.com/ActiveState/cli/pkg/platform/api" "github.com/ActiveState/cli/pkg/platform/api/mono" @@ -177,6 +180,7 @@ func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session constants.ProjectEnvVarName + "=", constants.E2ETestEnvVarName + "=true", constants.DisableUpdates + "=true", + constants.DisableProjectMigrationPrompt + "=true", constants.OptinUnstableEnvVarName + "=true", constants.ServiceSockDir + "=" + dirs.SockRoot, constants.HomeEnvVarName + "=" + dirs.HomeDir, @@ -185,12 +189,35 @@ func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session if updatePath { // add bin path + // Remove release state tool installation from PATH in tests + // This is a workaround as our test sessions are not compeltely + // sandboxed. This should be addressed in: https://activestatef.atlassian.net/browse/DX-2285 oldPath, _ := os.LookupEnv("PATH") + installPath, err := installation.InstallPathForBranch("release") + require.NoError(t, err) + + binPath := filepath.Join(installPath, "bin") + oldPath = strings.Replace(oldPath, binPath+string(os.PathListSeparator), "", -1) newPath := fmt.Sprintf( "PATH=%s%s%s", dirs.Bin, string(os.PathListSeparator), oldPath, ) env = append(env, newPath) + t.Setenv("PATH", newPath) + + cfg, err := config.New() + require.NoError(t, err) + + // In order to ensure that the release state tool does not appear on the PATH + // when a new subshell is started we remove the installation entries from the + // rc file. This is added back later in the session's Close method. + // Again, this is a workaround to be addressed in: https://activestatef.atlassian.net/browse/DX-2285 + if runtime.GOOS != "windows" { + s := bash.SubShell{} + err = s.CleanUserEnv(cfg, sscommon.InstallID, false) + require.NoError(t, err) + } + t.Setenv(constants.HomeEnvVarName, dirs.HomeDir) } // add session environment variables @@ -204,6 +231,14 @@ func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session session.SvcExe = session.copyExeToBinDir(svcExe) session.ExecutorExe = session.copyExeToBinDir(execExe) + // Set up environment for test runs. This is separate + // from the environment for the session itself. + // Setting environment variables here allows helper + // functions access to them. + // This is a workaround as our test sessions are not compeltely + // sandboxed. This should be addressed in: https://activestatef.atlassian.net/browse/DX-2285 + t.Setenv(constants.HomeEnvVarName, dirs.HomeDir) + err = fileutils.Touch(filepath.Join(dirs.Base, installation.InstallDirMarker)) require.NoError(session.t, err) @@ -580,6 +615,24 @@ func (s *Session) Close() error { } } + // Add back the release state tool installation to the bash RC file. + // This was done on session creation to ensure that the release state tool + // does not appear on the PATH when a new subshell is started. This is a + // workaround to be addressed in: https://activestatef.atlassian.net/browse/DX-2285 + if runtime.GOOS != "windows" { + installPath, err := installation.InstallPathForBranch("release") + if err != nil { + s.t.Errorf("Could not get install path: %v", errs.JoinMessage(err)) + } + binDir := filepath.Join(installPath, "bin") + + ss := bash.SubShell{} + err = ss.WriteUserEnv(cfg, map[string]string{"PATH": binDir}, sscommon.InstallID, false) + if err != nil { + s.t.Errorf("Could not clean user env: %v", errs.JoinMessage(err)) + } + } + return nil } @@ -694,6 +747,33 @@ func (s *Session) DetectLogErrors() { } } +func (s *Session) SetupRCFile() { + if runtime.GOOS == "windows" { + return + } + + cfg, err := config.New() + require.NoError(s.t, err) + + s.SetupRCFileCustom(subshell.New(cfg)) +} + +func (s *Session) SetupRCFileCustom(subshell subshell.SubShell) { + if runtime.GOOS == "windows" { + return + } + + rcFile, err := subshell.RcFile() + require.NoError(s.t, err) + + if fileutils.TargetExists(filepath.Join(s.Dirs.HomeDir, filepath.Base(rcFile))) { + err = fileutils.CopyFile(rcFile, filepath.Join(s.Dirs.HomeDir, filepath.Base(rcFile))) + } else { + err = fileutils.Touch(rcFile) + } + require.NoError(s.t, err) +} + func RunningOnCI() bool { return condition.OnCI() } diff --git a/pkg/cmdlets/checker/checker.go b/pkg/cmdlets/checker/checker.go index d3501a920e..c43b546318 100644 --- a/pkg/cmdlets/checker/checker.go +++ b/pkg/cmdlets/checker/checker.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/profile" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/updater" "github.com/ActiveState/cli/pkg/platform/model" "github.com/ActiveState/cli/pkg/project" @@ -56,7 +56,7 @@ func CommitsBehind(p *project.Project) (int, error) { return 0, locale.NewError("err_latest_commit", "Latest commit ID is nil") } - commitID, err := localcommit.Get(p.Dir()) + commitID, err := commitmediator.Get(p) if err != nil { return 0, errs.Wrap(err, "Unable to get local commit") } diff --git a/pkg/cmdlets/git/test/integration/git_test.go b/pkg/cmdlets/git/test/integration/git_test.go index c8f2fb669c..ca691edaeb 100644 --- a/pkg/cmdlets/git/test/integration/git_test.go +++ b/pkg/cmdlets/git/test/integration/git_test.go @@ -19,7 +19,6 @@ import ( "github.com/ActiveState/cli/internal/testhelpers/outputhelper" gitlet "github.com/ActiveState/cli/pkg/cmdlets/git" "github.com/ActiveState/cli/pkg/project" - "github.com/ActiveState/cli/pkg/projectfile" ) type GitTestSuite struct { @@ -42,7 +41,7 @@ func (suite *GitTestSuite) BeforeTest(suiteName, testName string) { projectURL := fmt.Sprintf("https://%s/%s/%s", constants.PlatformURL, "test-owner", "test-project") - _, err = projectfile.TestOnlyCreateWithProjectURL(projectURL, suite.dir) + err = fileutils.WriteFile(filepath.Join(suite.dir, "activestate.yaml"), []byte("project: "+projectURL)) suite.NoError(err, "could not create a projectfile") err = fileutils.Touch(filepath.Join(suite.dir, "test-file")) diff --git a/pkg/platform/runtime/buildexpression/buildexpression.go b/pkg/platform/runtime/buildexpression/buildexpression.go index 318e46ee32..2c5de9e9e0 100644 --- a/pkg/platform/runtime/buildexpression/buildexpression.go +++ b/pkg/platform/runtime/buildexpression/buildexpression.go @@ -910,17 +910,7 @@ func (v *Value) MarshalJSON() ([]byte, error) { case v.Ap != nil: return json.Marshal(v.Ap) case v.List != nil: - // Buildexpression list order does not matter, so sorting is necessary for - // comparisons. Go's JSON marshaling is deterministic, so utilize that. - // This should not be necessary when PB-4607 is implemented. - list := make([]*Value, len(*v.List)) - copy(list, *v.List) - sort.SliceStable(list, func(i, j int) bool { - b1, err1 := json.Marshal(list[i]) - b2, err2 := json.Marshal(list[j]) - return err1 == nil && err2 == nil && string(b1) < string(b2) - }) - return json.Marshal(list) + return json.Marshal(v.List) case v.Str != nil: return json.Marshal(strings.Trim(*v.Str, `"`)) case v.Null != nil: diff --git a/pkg/platform/runtime/buildexpression/merge/merge.go b/pkg/platform/runtime/buildexpression/merge/merge.go index 9a71742937..176f402bb2 100644 --- a/pkg/platform/runtime/buildexpression/merge/merge.go +++ b/pkg/platform/runtime/buildexpression/merge/merge.go @@ -3,7 +3,6 @@ package merge import ( "encoding/json" "reflect" - "sort" "github.com/ActiveState/cli/internal/errs" "github.com/ActiveState/cli/internal/logging" @@ -71,7 +70,7 @@ func isAutoMergePossible(exprA *buildexpression.BuildExpression, exprB *buildexp logging.Debug("Checking for possibility of auto-merging build expressions") logging.Debug("JsonA: %v", jsonA) logging.Debug("JsonB: %v", jsonB) - return reflect.DeepEqual(jsonA, jsonB) // TODO: replace with DX-1939 solution + return reflect.DeepEqual(jsonA, jsonB) } // getComparableJson returns a comparable JSON map[string]interface{} structure for the given build @@ -98,8 +97,6 @@ func getComparableJson(expr *buildexpression.BuildExpression) (map[string]interf return nil, errs.New("'let' key is not a JSON object") } deleteKey(&letMap, "requirements") - // TODO: the following shouldn't be needed after DX-1939. - sortLists(&letMap) deleteKey(&letMap, "at_time") return m, nil @@ -121,21 +118,3 @@ func deleteKey(m *map[string]interface{}, key string) bool { } return false } - -// sortLists recursively iterates over the given JSON map looking for string lists, and sorts them. -// This is needed because isAutoMergePossible() does a reflect.DeepEqual(), but build expression -// list order does not matter. -// This will not be necessary after DX-1939 is implemented. -func sortLists(m *map[string]interface{}) { - for _, v := range *m { - if list, ok := v.([]interface{}); ok { - sort.SliceStable(list, func(i, j int) bool { - s1, ok1 := list[i].(string) - s2, ok2 := list[j].(string) - return ok1 && ok2 && s1 < s2 - }) - } else if m2, ok := v.(map[string]interface{}); ok { - sortLists(&m2) - } - } -} diff --git a/pkg/platform/runtime/buildexpression/merge/merge_test.go b/pkg/platform/runtime/buildexpression/merge/merge_test.go index 69233a21a5..bbf3d142e9 100644 --- a/pkg/platform/runtime/buildexpression/merge/merge_test.go +++ b/pkg/platform/runtime/buildexpression/merge/merge_test.go @@ -80,35 +80,6 @@ in: mergedScript, err := buildscript.NewScriptFromBuildExpression(mergedExpr) require.NoError(t, err) - // TODO: delete this block after DX-1939. Sorting requirements is needed until we have - // buildexpression hashes for comparing equality. - assert.Equal(t, - `let: - runtime = solve( - platforms = [ - "12345", - "67890" - ], - requirements = [ - { - name = "JSON", - namespace = "language/perl" - }, - { - name = "perl", - namespace = "language" - }, - { - name = "DateTime", - namespace = "language/perl" - } - ] - ) - -in: - runtime`, mergedScript.String()) - return - assert.Equal(t, `let: runtime = solve( @@ -168,14 +139,12 @@ in: exprA, err := buildexpression.New(bytes) require.NoError(t, err) - // Note the intentional swap of platform order. Buildexpression list order does not matter. - // isAutoMergePossible() should still return true, and the original platforms will be used. scriptB, err := buildscript.NewScript([]byte( `let: runtime = solve( platforms = [ - "67890", - "12345" + "12345", + "67890" ], requirements = [ { @@ -211,31 +180,6 @@ in: mergedScript, err := buildscript.NewScriptFromBuildExpression(mergedExpr) require.NoError(t, err) - // TODO: delete this block after DX-1939. Sorting requirements is needed until we have - // buildexpression hashes for comparing equality. - assert.Equal(t, - `let: - runtime = solve( - platforms = [ - "12345", - "67890" - ], - requirements = [ - { - name = "DateTime", - namespace = "language/perl" - }, - { - name = "perl", - namespace = "language" - } - ] - ) - -in: - runtime`, mergedScript.String()) - return - assert.Equal(t, `let: runtime = solve( @@ -321,15 +265,3 @@ func TestDeleteKey(t *testing.T) { _, exists := m["foo"].(map[string]interface{})["quux"] assert.False(t, exists, "did not delete quux") } - -func TestSortLists(t *testing.T) { - m := map[string]interface{}{ - "one": []interface{}{"foo", "bar", "baz"}, - "two": map[string]interface{}{ - "three": []interface{}{"foobar", "barfoo", "barbaz"}, - }, - } - sortLists(&m) - assert.Equal(t, []interface{}{"bar", "baz", "foo"}, m["one"]) - assert.Equal(t, []interface{}{"barbaz", "barfoo", "foobar"}, m["two"].(map[string]interface{})["three"]) -} diff --git a/pkg/platform/runtime/buildscript/buildscript.go b/pkg/platform/runtime/buildscript/buildscript.go index 42207ce514..de4b765fcd 100644 --- a/pkg/platform/runtime/buildscript/buildscript.go +++ b/pkg/platform/runtime/buildscript/buildscript.go @@ -98,8 +98,9 @@ func (s *Script) String() string { buf.WriteString("let:\n") for _, assignment := range s.Expr.Let.Assignments { buf.WriteString(indent(assignmentString(assignment))) + buf.WriteString("\n") } - buf.WriteString("\n\n") + buf.WriteString("\n") buf.WriteString("in:\n") switch { case s.Expr.Let.In.FuncCall != nil: diff --git a/pkg/platform/runtime/buildscript/buildscript_test.go b/pkg/platform/runtime/buildscript/buildscript_test.go index de99117a27..b705392934 100644 --- a/pkg/platform/runtime/buildscript/buildscript_test.go +++ b/pkg/platform/runtime/buildscript/buildscript_test.go @@ -386,14 +386,12 @@ func TestBuildExpression(t *testing.T) { script, err := NewScriptFromBuildExpression(expr) require.NoError(t, err) require.NotNil(t, script) - //newExpr := script.Expr + newExpr := script.Expr exprBytes, err := json.Marshal(expr) require.NoError(t, err) - //newExprBytes, err := json.Marshal(newExpr) - //require.NoError(t, err) - // TODO: re-enable this test in DX-1939. Buildexpression equality is implicitly tested - // elsewhere, so temporarily disabling this explicit test is okay. - //assert.Equal(t, string(exprBytes), string(newExprBytes)) + newExprBytes, err := json.Marshal(newExpr) + require.NoError(t, err) + assert.Equal(t, string(exprBytes), string(newExprBytes)) // Verify comparisons between buildscripts and buildexpressions is accurate. assert.True(t, script.EqualsBuildExpression(expr)) @@ -416,48 +414,3 @@ func TestBuildExpression(t *testing.T) { } assert.True(t, nullHandled, "JSON null not encountered") } - -func TestJsonListEquality(t *testing.T) { - // When comparing buildscripts to buildexpressions, the former is converted to the latter - // via JSON marshaling. Since buildexpression list order does not matter (in addition to - // key-value order not mattering), test for list equality. - // This should not be necessary after DX-1939. - - // Test that ["foo", "bar"] == ["bar", "foo"]. - v1 := &Value{List: &[]*Value{ - {Str: ptr.To(`"foo"`)}, - {Str: ptr.To(`"bar"`)}, - }} - v2 := &Value{List: &[]*Value{ - {Str: ptr.To(`"bar"`)}, - {Str: ptr.To(`"foo"`)}, - }} - - b1, err := json.Marshal(v1) - require.NoError(t, err) - b2, err := json.Marshal(v2) - require.NoError(t, err) - - assert.Equal(t, string(b2), string(b1)) - - // Test that [{"name": "foo"}, {"name": "bar"}] == [{"name": "bar"}, {"name": "foo"}]. - v1 = &Value{List: &[]*Value{ - {Object: &[]*Assignment{ - {"name", &Value{Str: ptr.To(`"foo"`)}}, - {"name", &Value{Str: ptr.To(`"bar"`)}}, - }}, - }} - v2 = &Value{List: &[]*Value{ - {Object: &[]*Assignment{ - {"name", &Value{Str: ptr.To(`"foo"`)}}, - {"name", &Value{Str: ptr.To(`"bar"`)}}, - }}, - }} - - b1, err = json.Marshal(v1) - require.NoError(t, err) - b2, err = json.Marshal(v2) - require.NoError(t, err) - - assert.Equal(t, string(b2), string(b1)) -} diff --git a/pkg/platform/runtime/buildscript/json.go b/pkg/platform/runtime/buildscript/json.go index da159a1d24..c82100113c 100644 --- a/pkg/platform/runtime/buildscript/json.go +++ b/pkg/platform/runtime/buildscript/json.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "sort" "strings" ) @@ -33,17 +32,7 @@ func (v *Value) MarshalJSON() ([]byte, error) { case v.FuncCall != nil: return json.Marshal(v.FuncCall) case v.List != nil: - // Buildexpression list order does not matter, so sorting is necessary for - // comparisons. Go's JSON marshaling is deterministic, so utilize that. - // This should not be necessary when DX-1939 is implemented. - list := make([]*Value, len(*v.List)) - copy(list, *v.List) - sort.SliceStable(list, func(i, j int) bool { - b1, err1 := json.Marshal(list[i]) - b2, err2 := json.Marshal(list[j]) - return err1 == nil && err2 == nil && string(b1) < string(b2) - }) - return json.Marshal(list) + return json.Marshal(v.List) case v.Str != nil: return json.Marshal(strings.Trim(*v.Str, `"`)) case v.Number != nil: diff --git a/pkg/platform/runtime/target/target.go b/pkg/platform/runtime/target/target.go index acbf3acdda..de5ee4d73b 100644 --- a/pkg/platform/runtime/target/target.go +++ b/pkg/platform/runtime/target/target.go @@ -11,7 +11,7 @@ import ( "github.com/ActiveState/cli/internal/installation/storage" "github.com/ActiveState/cli/internal/logging" "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/project" "github.com/go-openapi/strfmt" ) @@ -88,8 +88,8 @@ func (p *ProjectTarget) CommitUUID() strfmt.UUID { if p.customCommit != nil { return *p.customCommit } - commitID, err := localcommit.Get(p.Project.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(p.Project) + if err != nil { multilog.Error("Unable to get local commit: %v", errs.JoinMessage(err)) return "" } diff --git a/pkg/project/expander.go b/pkg/project/expander.go index e7fd89955b..b3dcca3f16 100644 --- a/pkg/project/expander.go +++ b/pkg/project/expander.go @@ -12,9 +12,9 @@ import ( "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/osutils" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/internal/rxutils" "github.com/ActiveState/cli/internal/scriptfile" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/projectfile" ) @@ -238,7 +238,7 @@ func ProjectExpander(_ string, name string, _ string, isFunction bool, ctx *Expa case "url": return project.URL(), nil case "commit": - commitID, err := localcommit.Get(project.Dir()) + commitID, err := commitmediator.Get(project) if err != nil { return "", errs.Wrap(err, "Unable to get local commit") } diff --git a/pkg/project/namespace.go b/pkg/project/namespace.go index 0b11f818e5..668e82b1f0 100644 --- a/pkg/project/namespace.go +++ b/pkg/project/namespace.go @@ -8,7 +8,7 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/locale" "github.com/ActiveState/cli/internal/multilog" - "github.com/ActiveState/cli/pkg/localcommit" + "github.com/ActiveState/cli/internal/runbits/commitmediator" "github.com/ActiveState/cli/pkg/projectfile" "github.com/go-openapi/strfmt" ) @@ -153,8 +153,8 @@ func NameSpaceForConfig(configFile string) *Namespaced { Project: prj.Name(), } - commitID, err := localcommit.Get(prj.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { + commitID, err := commitmediator.Get(prj) + if err != nil { multilog.Error("Unable to get local commit: %v", errs.JoinMessage(err)) } if commitID != "" { diff --git a/pkg/project/namespace_test.go b/pkg/project/namespace_test.go index 8b1f6e3b54..c622ea7190 100644 --- a/pkg/project/namespace_test.go +++ b/pkg/project/namespace_test.go @@ -41,13 +41,6 @@ func TestParseProjectNoOwner(t *testing.T) { assert.Equal(t, parsed.Project, "project") assert.Empty(t, parsed.CommitID) assert.True(t, parsed.AllowOmitOwner) - - parsed, err = ParseProjectNoOwner("project#a10-b11c12-d13e14-f15") - assert.NoError(t, err, "should be able to parse project part of namspace") - assert.Empty(t, parsed.Owner) - assert.Equal(t, parsed.Project, "project") - assert.Equal(t, *parsed.CommitID, strfmt.UUID("a10-b11c12-d13e14-f15")) - assert.True(t, parsed.AllowOmitOwner) } func TestParseNamespaceOrConfigfile(t *testing.T) { diff --git a/pkg/project/project.go b/pkg/project/project.go index 7fa85cf02a..d1ffc0a559 100644 --- a/pkg/project/project.go +++ b/pkg/project/project.go @@ -19,7 +19,6 @@ import ( "github.com/ActiveState/cli/internal/multilog" "github.com/ActiveState/cli/internal/osutils" "github.com/ActiveState/cli/internal/output" - "github.com/ActiveState/cli/pkg/localcommit" secretsapi "github.com/ActiveState/cli/pkg/platform/api/secrets" "github.com/ActiveState/cli/pkg/platform/authentication" "github.com/ActiveState/cli/pkg/projectfile" @@ -215,6 +214,11 @@ func (p *Project) ProjectDir() string { return p.Dir() } +// LegacyCommitID is for use by commitmediator.Get() ONLY. +func (p *Project) LegacyCommitID() string { + return p.projectfile.LegacyCommitID() +} + func (p *Project) IsHeadless() bool { match := projectfile.CommitURLRe.FindStringSubmatch(p.URL()) return len(match) > 1 @@ -242,11 +246,7 @@ func (p *Project) Cache() string { return p.projectfile.Cache } // Namespace returns project namespace func (p *Project) Namespace() *Namespaced { - commitID, err := localcommit.Get(p.Dir()) - if err != nil && !localcommit.IsFileDoesNotExistError(err) { - multilog.Error("Unable to get local commit: %v", errs.JoinMessage(err)) - } - return &Namespaced{p.projectfile.Owner(), p.projectfile.Name(), &commitID, false} + return &Namespaced{Owner: p.projectfile.Owner(), Project: p.projectfile.Name()} } // NamespaceString is a convenience function to make interfaces simpler diff --git a/pkg/projectfile/projectfield.go b/pkg/projectfile/projectfield.go index 471e63549c..f53555edb1 100644 --- a/pkg/projectfile/projectfield.go +++ b/pkg/projectfile/projectfield.go @@ -29,9 +29,6 @@ func (p *projectField) LoadProject(rawProjectValue string) error { } p.url = u - // Strip legacy commitID parameter. - p.unsetQuery("commitID") - return nil } @@ -47,6 +44,10 @@ func (p *projectField) SetBranch(branch string) { p.setQuery("branch", branch) } +func (p *projectField) StripCommitID() { + p.unsetQuery("commitID") // legacy +} + func (p *projectField) setPath(path string) { p.url.Path = path p.url.RawPath = p.url.EscapedPath() diff --git a/pkg/projectfile/projectfile.go b/pkg/projectfile/projectfile.go index 0c1be8d3e2..ffab8e795d 100644 --- a/pkg/projectfile/projectfile.go +++ b/pkg/projectfile/projectfile.go @@ -32,7 +32,6 @@ import ( "github.com/ActiveState/cli/internal/rtutils" "github.com/ActiveState/cli/internal/sliceutils" "github.com/ActiveState/cli/internal/strutils" - "github.com/ActiveState/cli/pkg/localcommit" "github.com/ActiveState/cli/pkg/sysinfo" "github.com/go-openapi/strfmt" "github.com/google/uuid" @@ -465,28 +464,6 @@ func (p *Project) Init() error { } p.parsedURL = parsedURL - if p.parsedURL.LegacyCommitID != "" { - // Migrate from commitID in activestate.yaml to .activestate/commit file. - // Writing to disk during Parse() feels wrong though. - projectDir := filepath.Dir(p.Path()) - if err := localcommit.Set(projectDir, p.parsedURL.LegacyCommitID); err != nil { - return errs.Wrap(err, "Could not create local commit file") - } - if fileutils.DirExists(filepath.Join(projectDir, ".git")) { - err := localcommit.AddToGitIgnore(projectDir) - if err != nil { - multilog.Error("Unable to add local commit file to .gitignore: %v", err) - } - } - pf := NewProjectField() - if err := pf.LoadProject(p.Project); err != nil { - return errs.Wrap(err, "Could not load activestate.yaml") - } - if err := pf.Save(p.path); err != nil { - return errs.Wrap(err, "Could not save activestate.yaml") - } - } - // Ensure branch name is set if p.parsedURL.Owner != "" && p.parsedURL.BranchName == "" { logging.Debug("Appending default branch as none is set") @@ -556,6 +533,11 @@ func detectDeprecations(dat []byte, configFilepath string) error { } } +// URL returns the project namespace's string URL from activestate.yaml. +func (p *Project) URL() string { + return p.Project +} + // Owner returns the project namespace's organization func (p *Project) Owner() string { return p.parsedURL.Owner @@ -576,6 +558,12 @@ func (p *Project) Path() string { return p.path } +// LegacyCommitID is for use by commitmediator.Get() ONLY. +// It returns a pre-migrated project's commit ID from activestate.yaml. +func (p *Project) LegacyCommitID() string { + return p.parsedURL.LegacyCommitID +} + // SetPath sets the path of the project file and should generally only be used by tests func (p *Project) SetPath(path string) { p.path = path @@ -923,14 +911,6 @@ type CreateParams struct { Cache string } -// TestOnlyCreateWithProjectURL a new activestate.yaml with default content -func TestOnlyCreateWithProjectURL(projectURL, path string) (*Project, error) { - return createCustom(&CreateParams{ - ProjectURL: projectURL, - Directory: path, - }, language.Python3) -} - // Create will create a new activestate.yaml with a projectURL for the given details func Create(params *CreateParams) (*Project, error) { lang := language.MakeByName(params.Language) @@ -983,8 +963,9 @@ func createCustom(params *CreateParams, lang language.Language) (*Project, error shell = "batch" } + languageDisabled := os.Getenv(constants.DisableLanguageTemplates) == "true" content := params.Content - if content == "" && lang != language.Unset && lang != language.Unknown { + if !languageDisabled && content == "" && lang != language.Unset && lang != language.Unknown { tplName := "activestate.yaml." + strings.TrimRight(lang.String(), "23") + ".tpl" template, err := assets.ReadFileBytes(tplName) if err != nil { diff --git a/pkg/projectfile/projectfile_test.go b/pkg/projectfile/projectfile_test.go index 64f80d2a88..6e1657145f 100644 --- a/pkg/projectfile/projectfile_test.go +++ b/pkg/projectfile/projectfile_test.go @@ -12,7 +12,7 @@ import ( "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/environment" "github.com/ActiveState/cli/internal/errs" - "github.com/ActiveState/cli/internal/fileutils" + "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/locale" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -314,17 +314,17 @@ func TestNewProjectfile(t *testing.T) { assert.NoError(t, err, "Should be no error when getting a temp directory") os.Chdir(dir) - pjFile, err := TestOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", dir) + pjFile, err := testOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", dir) assert.NoError(t, err, "There should be no error when loading from a path") assert.Equal(t, "activationMessage", pjFile.Scripts[0].Name) - _, err = TestOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", "") + _, err = testOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", "") assert.Error(t, err, "We don't accept blank paths") setCwd(t, "") dir, err = os.Getwd() assert.NoError(t, err, "Should be no error when getting the CWD") - _, err = TestOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", dir) + _, err = testOnlyCreateWithProjectURL("https://platform.activestate.com/xowner/xproject", dir) assert.Error(t, err, "Cannot create new project if existing as.yaml ...exists") } @@ -469,24 +469,10 @@ languages: } } -func TestMigrateCommitFromASY(t *testing.T) { - tempDir := fileutils.TempDirUnsafe() - defer os.RemoveAll(tempDir) - - commitID := "7BA74758-8665-4D3F-921C-757CD271A0C1" - asy := filepath.Join(tempDir, constants.ConfigFileName) - err := fileutils.WriteFile(asy, []byte("project: https://platform.activestate.com/Owner/Name?branch=main&commitID="+commitID)) - require.NoError(t, err) - - proj, err := Parse(asy) - require.NoError(t, err) - assert.Equal(t, "Owner", proj.Owner()) - assert.Equal(t, "Name", proj.Name()) - assert.Equal(t, "main", proj.BranchName()) - - commitIdFile := filepath.Join(tempDir, constants.ProjectConfigDirName, constants.CommitIdFileName) - require.FileExists(t, commitIdFile) - assert.Equal(t, commitID, string(fileutils.ReadFileUnsafe(commitIdFile))) - - assert.NotContains(t, string(fileutils.ReadFileUnsafe(asy)), commitID) +// testOnlyCreateWithProjectURL a new activestate.yaml with default content +func testOnlyCreateWithProjectURL(projectURL, path string) (*Project, error) { + return createCustom(&CreateParams{ + ProjectURL: projectURL, + Directory: path, + }, language.Python3) } diff --git a/test/integration/activate_int_test.go b/test/integration/activate_int_test.go index 6932fe6b11..f7d60bac60 100644 --- a/test/integration/activate_int_test.go +++ b/test/integration/activate_int_test.go @@ -280,7 +280,7 @@ func (suite *ActivateIntegrationTestSuite) activatePython(version string, extraE e2e.OptArgs("-c", "import sys; print(sys.copyright);"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("ActiveState Software Inc.") + cp.Expect("ActiveState Software Inc.", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) } @@ -466,6 +466,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivate_NamespaceWins() { c2 := ts.SpawnWithOpts( e2e.OptArgs("activate", "ActiveState-CLI/Python2"), // activate a different namespace e2e.OptWD(targetPath), + e2e.OptAppendEnv(constants.DisableLanguageTemplates+"=true"), ) c2.Expect("ActiveState-CLI/Python2") c2.Expect("Activated") @@ -527,7 +528,7 @@ func (suite *ActivateIntegrationTestSuite) TestActivate_FromCache() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.ExpectInput() + cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt) cp.SendLine("exit") cp.ExpectExitCode(0) suite.NotContains(cp.Output(), "Downloading") diff --git a/test/integration/analytics_int_test.go b/test/integration/analytics_int_test.go index 9f27c3339e..e0807e5dae 100644 --- a/test/integration/analytics_int_test.go +++ b/test/integration/analytics_int_test.go @@ -220,8 +220,7 @@ func (suite *AnalyticsIntegrationTestSuite) TestExecEvents() { commitID := "efcc851f-1451-4d0a-9dcb-074ac3f35f0a" // We want to do a clean test without an activate event, so we have to manually seed the yaml - url := fmt.Sprintf("https://platform.activestate.com/%s?branch=main&commitID=%s", namespace, commitID) - suite.Require().NoError(fileutils.WriteFile(filepath.Join(ts.Dirs.Work, "activestate.yaml"), []byte("project: "+url))) + ts.PrepareProject(namespace, commitID) heartbeatInterval := 1000 // in milliseconds sleepTime := time.Duration(heartbeatInterval) * time.Millisecond diff --git a/test/integration/bundle_int_test.go b/test/integration/bundle_int_test.go index 464fbbfa73..74b9551330 100644 --- a/test/integration/bundle_int_test.go +++ b/test/integration/bundle_int_test.go @@ -225,7 +225,7 @@ func (suite *BundleIntegrationTestSuite) TestJSON() { e2e.OptArgs("bundles", "install", "Testing", "--output", "json"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect(`"name":"Testing"`) + cp.Expect(`"name":"Testing"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) AssertValidJSON(suite.T(), cp) @@ -233,7 +233,7 @@ func (suite *BundleIntegrationTestSuite) TestJSON() { e2e.OptArgs("bundles", "uninstall", "Testing", "-o", "editor"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect(`"name":"Testing"`) + cp.Expect(`"name":"Testing"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) AssertValidJSON(suite.T(), cp) } diff --git a/test/integration/checkout_int_test.go b/test/integration/checkout_int_test.go index 6b239d5031..8a075b7798 100644 --- a/test/integration/checkout_int_test.go +++ b/test/integration/checkout_int_test.go @@ -202,7 +202,7 @@ func (suite *CheckoutIntegrationTestSuite) TestCheckoutCustomRTPath() { suite.Require().NoError(err) customRTPath = strings.ToLower(customRTPath) } - cp.Expect(customRTPath) + cp.Expect(customRTPath, e2e.RuntimeSourcingTimeoutOpt) } func (suite *CheckoutIntegrationTestSuite) TestCheckoutNotFound() { diff --git a/test/integration/deploy_int_test.go b/test/integration/deploy_int_test.go index d07b049879..e2505cb6aa 100644 --- a/test/integration/deploy_int_test.go +++ b/test/integration/deploy_int_test.go @@ -7,9 +7,7 @@ import ( "path/filepath" "runtime" "testing" - "time" - "github.com/ActiveState/termtest" "github.com/google/uuid" "github.com/stretchr/testify/suite" @@ -57,12 +55,12 @@ func (suite *DeployIntegrationTestSuite) deploy(ts *e2e.Session, prj string, tar ) } - cp.Expect("Installing", termtest.OptExpectTimeout(40*time.Second)) - cp.Expect("Configuring", termtest.OptExpectTimeout(40*time.Second)) + cp.Expect("Installing", e2e.RuntimeSourcingTimeoutOpt) + cp.Expect("Configuring") if runtime.GOOS != "windows" { - cp.Expect("Symlinking", termtest.OptExpectTimeout(30*time.Second)) + cp.Expect("Symlinking") } - cp.Expect("Deployment Information", termtest.OptExpectTimeout(60*time.Second)) + cp.Expect("Deployment Information") cp.Expect(targetID) // expect bin dir if runtime.GOOS == "windows" { cp.Expect("log out") @@ -158,7 +156,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployPython() { ts := e2e.New(suite.T(), false) defer ts.Close() - suite.SetupRCFile(ts) + ts.SetupRCFile() suite.T().Setenv("ACTIVESTATE_HOME", ts.Dirs.HomeDir) targetID, err := uuid.NewUUID() @@ -261,7 +259,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployConfigure() { ts := e2e.New(suite.T(), false) defer ts.Close() - suite.SetupRCFile(ts) + ts.SetupRCFile() suite.T().Setenv("ACTIVESTATE_HOME", ts.Dirs.HomeDir) targetID, err := uuid.NewUUID() @@ -291,7 +289,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployConfigure() { ) } - cp.Expect("Configuring shell", termtest.OptExpectTimeout(60*time.Second)) + cp.Expect("Configuring shell", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) suite.AssertConfig(ts, targetID.String()) @@ -300,7 +298,7 @@ func (suite *DeployIntegrationTestSuite) TestDeployConfigure() { e2e.OptArgs("deploy", "configure", "ActiveState-CLI/Python3", "--path", targetPath, "--user"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Configuring shell", termtest.OptExpectTimeout(60*time.Second)) + cp.Expect("Configuring shell", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) out, err := exec.Command("reg", "query", `HKCU\Environment`, "/v", "Path").Output() @@ -309,22 +307,6 @@ func (suite *DeployIntegrationTestSuite) TestDeployConfigure() { } } -func (suite *DeployIntegrationTestSuite) SetupRCFile(ts *e2e.Session) { - if runtime.GOOS == "windows" { - return - } - - cfg, err := config.New() - suite.Require().NoError(err) - - subshell := subshell.New(cfg) - rcFile, err := subshell.RcFile() - suite.Require().NoError(err) - - err = fileutils.CopyFile(rcFile, filepath.Join(ts.Dirs.HomeDir, filepath.Base(rcFile))) - suite.Require().NoError(err) -} - func (suite *DeployIntegrationTestSuite) AssertConfig(ts *e2e.Session, targetID string) { if runtime.GOOS != "windows" { // Test config file diff --git a/test/integration/exec_int_test.go b/test/integration/exec_int_test.go index 23c422ae4f..be1857cac9 100644 --- a/test/integration/exec_int_test.go +++ b/test/integration/exec_int_test.go @@ -183,7 +183,7 @@ func (suite *ExecIntegrationTestSuite) TestExecWithPath() { e2e.OptArgs("exec", "--path", pythonDir, "which", "python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Operating on project ActiveState-CLI/Python-3.9") + cp.Expect("Operating on project ActiveState-CLI/Python-3.9", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectRe(regexp.MustCompile("cache/[0-9A-Fa-f]+/usr/bin/python3").String()) cp.ExpectExitCode(0) diff --git a/test/integration/export_int_test.go b/test/integration/export_int_test.go index 2267ab8748..8f05f060bc 100644 --- a/test/integration/export_int_test.go +++ b/test/integration/export_int_test.go @@ -89,7 +89,7 @@ func (suite *ExportIntegrationTestSuite) TestExport_Env() { e2e.OptArgs("export", "env"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect(`PATH: `) + cp.Expect(`PATH: `, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) suite.Assert().NotContains(cp.Output(), "ACTIVESTATE_ACTIVATED") @@ -115,7 +115,7 @@ func (suite *ExportIntegrationTestSuite) TestJSON() { e2e.OptArgs("export", "env", "-o", "json"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.ExpectExitCode(0) + cp.ExpectExitCode(0, e2e.RuntimeSourcingTimeoutOpt) AssertValidJSON(suite.T(), cp) ts.LoginAsPersistentUser() diff --git a/test/integration/init_int_test.go b/test/integration/init_int_test.go index 474396279a..47b2a2dec9 100644 --- a/test/integration/init_int_test.go +++ b/test/integration/init_int_test.go @@ -117,7 +117,7 @@ func (suite *InitIntegrationTestSuite) TestInit_InferLanguageFromUse() { e2e.OptArgs("use", "Python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) pname := strutils.UUID() diff --git a/test/integration/install_scripts_int_test.go b/test/integration/install_scripts_int_test.go index 624bbbbcba..04314fb3cf 100644 --- a/test/integration/install_scripts_int_test.go +++ b/test/integration/install_scripts_int_test.go @@ -6,13 +6,12 @@ import ( "path/filepath" "runtime" "testing" - "time" - "github.com/ActiveState/termtest" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/thoas/go-funk" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/condition" "github.com/ActiveState/cli/internal/constants" "github.com/ActiveState/cli/internal/environment" @@ -67,8 +66,7 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { suite.Require().NoError(fileutils.WriteFile(script, b)) // Construct installer command to execute. - installDir := filepath.Join(ts.Dirs.Work, "install") - argsPlain := []string{script, "-t", installDir} + argsPlain := []string{script} if tt.Channel != "" { argsPlain = append(argsPlain, "-b", tt.Channel) } @@ -97,12 +95,14 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { "bash", e2e.OptArgs(argsWithActive...), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AppInstallDirOverrideEnvVarName, appInstallDir)), + e2e.OptAppendEnv(fmt.Sprintf("%s=FOO", constants.OverrideSessionTokenEnvVarName)), ) } else { cp = ts.SpawnCmdWithOpts("powershell.exe", e2e.OptArgs(argsWithActive...), e2e.OptAppendEnv("SHELL="), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), e2e.OptAppendEnv(fmt.Sprintf("%s=%s", constants.AppInstallDirOverrideEnvVarName, appInstallDir)), + e2e.OptAppendEnv(fmt.Sprintf("%s=FOO", constants.OverrideSessionTokenEnvVarName)), ) } @@ -110,7 +110,7 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { if tt.Activate != "" || tt.ActivateByCommand != "" { cp.Expect("Creating a Virtual Environment") - cp.Expect("Quick Start", termtest.OptExpectTimeout(time.Second*60)) + cp.Expect("Quick Start", e2e.RuntimeSourcingTimeoutOpt) // ensure that shell is functional cp.ExpectInput() @@ -118,6 +118,20 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { cp.Expect("ActiveState") } + // We get the default install path and use that to directly invoke + // the state tool. This is to avoid inadvertently using the state + // tool that is already on the PATH. + installPath, err := installation.InstallPathForBranch(constants.BranchName) + suite.NoError(err) + + binPath := filepath.Join(installPath, "bin") + + if runtime.GOOS != "windows" { + cp.SendLine("echo $PATH") + } else { + cp.SendLine("echo %PATH%") + } + cp.Expect(installPath) cp.SendLine("state --version") cp.Expect("Version " + constants.Version) cp.Expect("Branch " + constants.BranchName) @@ -126,12 +140,13 @@ func (suite *InstallScriptsIntegrationTestSuite) TestInstall() { cp.ExpectExitCode(0) - stateExec, err := installation.StateExecFromDir(installDir) + stateExec, err := installation.StateExecFromDir(ts.Dirs.HomeDir) suite.NoError(err) suite.FileExists(stateExec) - suite.assertBinDirContents(filepath.Join(installDir, "bin")) - suite.assertCorrectVersion(ts, installDir, tt.Version, tt.Channel) + suite.assertBinDirContents(binPath) + suite.assertCorrectVersion(ts, binPath, tt.Version, tt.Channel) + suite.assertAnalytics(ts) suite.DirExists(ts.Dirs.Config) // Verify that can install overtop @@ -217,7 +232,7 @@ func scriptPath(t *testing.T, targetDir string) string { func expectStateToolInstallation(cp *e2e.SpawnedCmd) { cp.Expect("Preparing Installer for State Tool Package Manager") - cp.Expect("Installation Complete", termtest.OptExpectTimeout(time.Minute)) + cp.Expect("Installation Complete", e2e.RuntimeSourcingTimeoutOpt) } // assertBinDirContents checks if given files are or are not in the bin directory @@ -260,6 +275,21 @@ func (suite *InstallScriptsIntegrationTestSuite) assertCorrectVersion(ts *e2e.Se } } +func (suite *InstallScriptsIntegrationTestSuite) assertAnalytics(ts *e2e.Session) { + // Verify analytics reported a non-empty sessionToken. + sessionTokenFound := false + events := parseAnalyticsEvents(suite, ts) + suite.Require().NotEmpty(events) + for _, event := range events { + if event.Category == anaConst.CatInstallerFunnel && event.Dimensions != nil { + suite.Assert().NotEmpty(*event.Dimensions.SessionToken) + sessionTokenFound = true + break + } + } + suite.Assert().True(sessionTokenFound, "sessionToken was not found in analytics") +} + func TestInstallScriptsIntegrationTestSuite(t *testing.T) { suite.Run(t, new(InstallScriptsIntegrationTestSuite)) } diff --git a/test/integration/package_int_test.go b/test/integration/package_int_test.go index d824fe0cde..7e5bb7d68c 100644 --- a/test/integration/package_int_test.go +++ b/test/integration/package_int_test.go @@ -487,7 +487,7 @@ func (suite *PackageIntegrationTestSuite) TestJSON() { e2e.OptArgs("uninstall", "Text-CSV", "-o", "json"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect(`{"name":"Text-CSV"`) + cp.Expect(`{"name":"Text-CSV"`, e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) AssertValidJSON(suite.T(), cp) } @@ -555,7 +555,9 @@ func (suite *PackageIntegrationTestSuite) TestInstall_InvalidVersion() { e2e.OptArgs("install", "pytest@999.9999.9999"), e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) - cp.Expect("Error occurred while trying to create a commit") + // User facing error from build planner + // We only assert the state tool curated part of the error as the underlying build planner error may change + cp.Expect("Could not plan build") cp.ExpectExitCode(1) } @@ -576,7 +578,9 @@ func (suite *PackageIntegrationTestSuite) TestUpdate_InvalidVersion() { e2e.OptArgs("install", "pytest@999.9999.9999"), // update e2e.OptAppendEnv(constants.DisableRuntime+"=false"), // We DO want to test the runtime part, just not for every step ) - cp.Expect("Error occurred while trying to create a commit") + // User facing error from build planner + // We only assert the state tool curated part of the error as the underlying build planner error may change + cp.Expect("Could not plan build") cp.ExpectExitCode(1) } diff --git a/test/integration/progress_int_test.go b/test/integration/progress_int_test.go index 7f772287bb..811b617c88 100644 --- a/test/integration/progress_int_test.go +++ b/test/integration/progress_int_test.go @@ -23,7 +23,7 @@ func (suite *ProgressIntegrationTestSuite) TestProgress() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) cp.Expect(locale.T("setup_runtime")) - cp.Expect("Checked out") + cp.Expect("Checked out", e2e.RuntimeSourcingTimeoutOpt) suite.Assert().NotContains(cp.Output(), "...") cp.ExpectExitCode(0) @@ -33,7 +33,7 @@ func (suite *ProgressIntegrationTestSuite) TestProgress() { ) cp.Expect(locale.T("setup_runtime")) cp.Expect("...") - cp.Expect("Checked out") + cp.Expect("Checked out", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) } diff --git a/test/integration/project_migration_int_test.go b/test/integration/project_migration_int_test.go new file mode 100644 index 0000000000..a39c648f7a --- /dev/null +++ b/test/integration/project_migration_int_test.go @@ -0,0 +1,86 @@ +package integration + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/ActiveState/cli/internal/constants" + "github.com/ActiveState/cli/internal/fileutils" + "github.com/ActiveState/cli/internal/testhelpers/e2e" + "github.com/ActiveState/cli/internal/testhelpers/tagsuite" + "github.com/stretchr/testify/suite" +) + +type ProjectMigrationIntegrationTestSuite struct { + tagsuite.Suite +} + +func (suite *ProjectMigrationIntegrationTestSuite) TestPromptMigration() { + suite.OnlyRunForTags(tagsuite.Critical) + ts := e2e.New(suite.T(), false) + defer ts.Close() + + commitID := "9090c128-e948-4388-8f7f-96e2c1e00d98" + ts.PrepareActiveStateYAML(`project: https://platform.activestate.com/ActiveState-CLI/test?commitID=` + commitID) + suite.Require().NoError(fileutils.Mkdir(filepath.Join(ts.Dirs.Work, ".git")), "could not mimic this being a git repo") + + // Verify the user is prompted to migrate an unmigrated project. + cp := ts.SpawnWithOpts( + e2e.OptArgs("packages"), + e2e.OptAppendEnv(constants.DisableProjectMigrationPrompt+"=false"), + ) + cp.Expect("migrate") + cp.Expect("? (y/N)") + cp.SendEnter() + cp.Expect("declined") + + // Verify that read-only actions still work for unmigrated projects. + cp.Expect("pylint") + cp.Expect("pytest") + cp.ExpectExitCode(0) + + // Verify activestate.yaml remains unchanged and a .activestate/commit was not created, nor was a + // .gitignore created. + bytes := fileutils.ReadFileUnsafe(filepath.Join(ts.Dirs.Work, constants.ConfigFileName)) + suite.Assert().Contains(string(bytes), commitID, "as.yaml was migrated and does not still contain commitID") + projectConfigDir := filepath.Join(ts.Dirs.Work, constants.ProjectConfigDirName) + suite.Assert().NoDirExists(projectConfigDir, ".activestate dir was created") + gitignoreFile := filepath.Join(ts.Dirs.Work, ".gitignore") + suite.Assert().NoFileExists(gitignoreFile, ".gitignore was created") + + // Verify that migration works. + cp = ts.SpawnWithOpts( + e2e.OptArgs("packages"), + e2e.OptAppendEnv(constants.DisableProjectMigrationPrompt+"=false"), + ) + cp.Expect("migrate") + cp.Expect("? (y/N)") + cp.SendLine("Y") + cp.Expect("success") + + cp.Expect("pylint") + cp.Expect("pytest") + cp.ExpectExitCode(0) + + // Verify .activestate/commit and .gitignore were created. + suite.Require().True(fileutils.DirExists(projectConfigDir), ",migration should have created "+projectConfigDir) + commitIDFile := filepath.Join(projectConfigDir, constants.CommitIdFileName) + suite.Assert().True(fileutils.FileExists(commitIDFile), "commit file not created") + suite.Assert().Contains(string(fileutils.ReadFileUnsafe(commitIDFile)), commitID, "migration did not populate .activestate/commit") + suite.Assert().True(fileutils.FileExists(gitignoreFile), "migration did not create .gitignore") + suite.Assert().Contains(string(fileutils.ReadFileUnsafe(gitignoreFile)), fmt.Sprintf("%s/%s", constants.ProjectConfigDirName, constants.CommitIdFileName), "commit file not added to .gitignore") + + // Verify no prompt for migrated project. + cp = ts.SpawnWithOpts( + e2e.OptArgs("packages"), + e2e.OptAppendEnv(constants.DisableProjectMigrationPrompt+"=false"), + ) + cp.Expect("pylint") + cp.Expect("pytest") + cp.ExpectExitCode(0) +} + +func TestProjectMigrationIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(ProjectMigrationIntegrationTestSuite)) +} diff --git a/test/integration/pull_int_test.go b/test/integration/pull_int_test.go index b4ff02e38d..2861d128c7 100644 --- a/test/integration/pull_int_test.go +++ b/test/integration/pull_int_test.go @@ -148,7 +148,7 @@ func (suite *PullIntegrationTestSuite) TestMergeBuildScript() { e2e.OptArgs("install", "requests"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Package added") + cp.Expect("Package added", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) proj, err := project.FromPath(ts.Dirs.Work) diff --git a/test/integration/push_int_test.go b/test/integration/push_int_test.go index c72f41b3d6..f683b8fb71 100644 --- a/test/integration/push_int_test.go +++ b/test/integration/push_int_test.go @@ -191,10 +191,10 @@ func (suite *PushIntegrationTestSuite) TestCarlisle() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false")) switch runtime.GOOS { case "darwin": - cp.ExpectRe("added|being built", termtest.OptExpectTimeout(60*time.Second)) // while cold storage is off + cp.ExpectRe("added|being built", e2e.RuntimeSourcingTimeoutOpt) // while cold storage is off cp.Wait() default: - cp.Expect("added", termtest.OptExpectTimeout(60*time.Second)) + cp.Expect("added", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) } diff --git a/test/integration/refresh_int_test.go b/test/integration/refresh_int_test.go index f4a75ffb78..1001b5baa8 100644 --- a/test/integration/refresh_int_test.go +++ b/test/integration/refresh_int_test.go @@ -25,7 +25,7 @@ func (suite *RefreshIntegrationTestSuite) TestRefresh() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) cp.Expect("Setting Up Runtime") - cp.Expect("Runtime updated") + cp.Expect("Runtime updated", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) cp = ts.SpawnWithOpts( @@ -41,14 +41,14 @@ func (suite *RefreshIntegrationTestSuite) TestRefresh() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) cp.Expect("Setting Up Runtime") - cp.Expect("Runtime updated") + cp.Expect("Runtime updated", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) cp = ts.SpawnWithOpts( e2e.OptArgs("exec", "--", "python3", "-c", "import requests"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.ExpectExitCode(0) + cp.ExpectExitCode(0, e2e.RuntimeSourcingTimeoutOpt) cp = ts.Spawn("refresh") suite.Assert().NotContains(cp.Output(), "Setting Up Runtime", "Unchanged runtime should not refresh") diff --git a/test/integration/revert_int_test.go b/test/integration/revert_int_test.go index f5ac49eef2..9a59e4e2d8 100644 --- a/test/integration/revert_int_test.go +++ b/test/integration/revert_int_test.go @@ -53,7 +53,7 @@ func (suite *RevertIntegrationTestSuite) TestRevert() { e2e.OptArgs("shell", "Revert"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.ExpectInput() + cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt) cp.SendLine("python3") cp.Expect("3.9.15") cp.SendLine("import urllib3") diff --git a/test/integration/runtime_int_test.go b/test/integration/runtime_int_test.go index 2ea4d22cbb..19261c24bc 100644 --- a/test/integration/runtime_int_test.go +++ b/test/integration/runtime_int_test.go @@ -89,7 +89,7 @@ func (suite *RuntimeIntegrationTestSuite) TestInterruptSetup() { e2e.OptArgs("checkout", "ActiveState-CLI/test-interrupt-small-python#863c45e2-3626-49b6-893c-c15e85a17241", "."), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Checked out project") + cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) targetDir := target.ProjectDirToTargetDir(ts.Dirs.Work, ts.Dirs.Cache) pythonExe := filepath.Join(setup.ExecDir(targetDir), "python3"+exeutils.Extension) diff --git a/test/integration/secrets_int_test.go b/test/integration/secrets_int_test.go index 219e342870..c8381736b7 100644 --- a/test/integration/secrets_int_test.go +++ b/test/integration/secrets_int_test.go @@ -71,7 +71,7 @@ func (suite *SecretsIntegrationTestSuite) TestSecret_Expand() { defer clearSecrets(ts, "project.test-secret", "user.test-secret") asyData := strings.TrimSpace(` -project: https://platform.activestate.com/ActiveState-CLI/secrets-test?commitID=c7f8f45d-39e2-4f22-bd2e-4182b914880f +project: https://platform.activestate.com/ActiveState-CLI/secrets-test scripts: - name: project-secret language: bash @@ -84,6 +84,7 @@ scripts: `) ts.PrepareActiveStateYAML(asyData) + ts.PrepareCommitIdFile("c7f8f45d-39e2-4f22-bd2e-4182b914880f") cp := ts.Spawn("secrets", "set", "project.project-secret", "project-value") cp.Expect("Operating on project") diff --git a/test/integration/shell_int_test.go b/test/integration/shell_int_test.go index 9f3f06d53c..306aa55863 100644 --- a/test/integration/shell_int_test.go +++ b/test/integration/shell_int_test.go @@ -190,7 +190,7 @@ func (suite *ShellIntegrationTestSuite) TestDefaultNoLongerExists() { e2e.OptArgs("use", "ActiveState-CLI/Python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) err := os.RemoveAll(filepath.Join(ts.Dirs.Work, "Python3")) @@ -228,7 +228,7 @@ func (suite *ShellIntegrationTestSuite) TestUseShellUpdates() { e2e.OptAppendEnv("SHELL=bash"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) // Ensure both bash and zsh RC files are updated @@ -258,24 +258,8 @@ func (suite *ShellIntegrationTestSuite) SetupRCFile(ts *e2e.Session) { return } - cfg, err := config.New() - suite.Require().NoError(err) - - subshell := subshell.New(cfg) - rcFile, err := subshell.RcFile() - suite.Require().NoError(err) - - err = fileutils.CopyFile(rcFile, filepath.Join(ts.Dirs.HomeDir, filepath.Base(rcFile))) - suite.Require().NoError(err) - - zsh := &zsh.SubShell{} - zshRcFile, err := zsh.RcFile() - suite.NoError(err) - err = fileutils.TouchFileUnlessExists(zshRcFile) - suite.NoError(err) - - err = fileutils.CopyFile(rcFile, filepath.Join(ts.Dirs.HomeDir, filepath.Base(zshRcFile))) - suite.Require().NoError(err) + ts.SetupRCFile() + ts.SetupRCFileCustom(&zsh.SubShell{}) } func (suite *ShellIntegrationTestSuite) TestRuby() { @@ -294,7 +278,7 @@ func (suite *ShellIntegrationTestSuite) TestRuby() { e2e.OptArgs("shell", "Ruby-3.2.2"), e2e.OptAppendEnv(constants.DisableRuntime+"=false"), ) - cp.Expect("Activated") + cp.Expect("Activated", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectInput() cp.SendLine("ruby -v") cp.Expect("3.2.2") diff --git a/test/integration/shells_int_test.go b/test/integration/shells_int_test.go index 1dff3545fc..2ac9cb1f0f 100644 --- a/test/integration/shells_int_test.go +++ b/test/integration/shells_int_test.go @@ -36,7 +36,7 @@ func (suite *ShellsIntegrationTestSuite) TestShells() { e2e.OptArgs("checkout", "ActiveState-CLI/small-python"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Checked out project") + cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) for _, shell := range shells { diff --git a/test/integration/show_int_test.go b/test/integration/show_int_test.go index 3ee2b5581d..c8ea19107c 100644 --- a/test/integration/show_int_test.go +++ b/test/integration/show_int_test.go @@ -28,7 +28,7 @@ func (suite *ShowIntegrationTestSuite) TestShow() { e2e.OptArgs("activate"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.ExpectInput() + cp.ExpectInput(e2e.RuntimeSourcingTimeoutOpt) cp = ts.Spawn("show") cp.Expect(`Name`) diff --git a/test/integration/uninstall_int_test.go b/test/integration/uninstall_int_test.go index 1c0436a9ce..79cfd1da75 100644 --- a/test/integration/uninstall_int_test.go +++ b/test/integration/uninstall_int_test.go @@ -11,7 +11,6 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/installation" "github.com/ActiveState/cli/internal/osutils" - "github.com/ActiveState/cli/internal/osutils/user" "github.com/ActiveState/cli/internal/testhelpers/e2e" "github.com/ActiveState/cli/internal/testhelpers/tagsuite" "github.com/stretchr/testify/suite" @@ -98,6 +97,7 @@ func (suite *UninstallIntegrationTestSuite) testUninstall(all bool) { suite.Fail("State service executable should not exist after uninstall") } + /* Disabled because we never configured anything in the first place: https://activestatef.atlassian.net/browse/DX-2296 if runtime.GOOS == "linux" { // When installed in a non-desktop environment (i.e. on a server), verify the user's ~/.profile was reverted. homeDir, err := user.HomeDir() @@ -105,6 +105,7 @@ func (suite *UninstallIntegrationTestSuite) testUninstall(all bool) { profile := filepath.Join(homeDir, ".profile") suite.NotContains(string(fileutils.ReadFileUnsafe(profile)), ts.SvcExe, "autostart should not be configured for Linux server environment anymore") } + */ if runtime.GOOS == "darwin" { if fileutils.DirExists(filepath.Join(ts.Dirs.Bin, "system")) { diff --git a/test/integration/use_int_test.go b/test/integration/use_int_test.go index ffa1a97d8d..6745178f3d 100644 --- a/test/integration/use_int_test.go +++ b/test/integration/use_int_test.go @@ -37,7 +37,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { e2e.OptArgs("use", "ActiveState-CLI/Python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) // Verify runtime works. @@ -57,7 +57,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { e2e.OptArgs("use", "ActiveState-CLI/Python-3.9"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) // Verify the new runtime works. @@ -70,7 +70,7 @@ func (suite *UseIntegrationTestSuite) TestUse() { e2e.OptArgs("use", "Python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) // Verify the first runtime is set up correctly and usable. @@ -102,7 +102,7 @@ func (suite *UseIntegrationTestSuite) TestUseCwd() { e2e.OptWD(pythonDir), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) emptyDir := filepath.Join(ts.Dirs.Work, "EmptyDir") @@ -121,7 +121,7 @@ func (suite *UseIntegrationTestSuite) TestReset() { ts := e2e.New(suite.T(), false) defer ts.Close() - suite.SetupRCFile(ts) + ts.SetupRCFile() suite.T().Setenv("ACTIVESTATE_HOME", ts.Dirs.HomeDir) cp := ts.SpawnWithOpts(e2e.OptArgs("checkout", "ActiveState-CLI/Python3")) @@ -133,7 +133,7 @@ func (suite *UseIntegrationTestSuite) TestReset() { e2e.OptArgs("use", "ActiveState-CLI/Python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) python3Exe := filepath.Join(ts.Dirs.DefaultBin, "python3"+osutils.ExeExt) @@ -188,7 +188,7 @@ func (suite *UseIntegrationTestSuite) TestShow() { e2e.OptArgs("use", "ActiveState-CLI/Python3"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) cp = ts.SpawnWithOpts( @@ -239,7 +239,7 @@ func (suite *UseIntegrationTestSuite) TestSetupNotice() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) cp.Expect("Setting Up Runtime") - cp.Expect("Checked out project") + cp.Expect("Checked out project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) suite.Require().NoError(os.RemoveAll(filepath.Join(ts.Dirs.Work, "Python3"))) // runtime marker still exists @@ -256,7 +256,7 @@ func (suite *UseIntegrationTestSuite) TestSetupNotice() { e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) cp.Expect("Setting Up Runtime") - cp.Expect("Switched to project") + cp.Expect("Switched to project", e2e.RuntimeSourcingTimeoutOpt) cp.ExpectExitCode(0) } @@ -274,7 +274,7 @@ func (suite *UseIntegrationTestSuite) TestJSON() { e2e.OptArgs("use", "-o", "json"), e2e.OptAppendEnv("ACTIVESTATE_CLI_DISABLE_RUNTIME=false"), ) - cp.Expect(`"namespace":`) + cp.Expect(`"namespace":`, e2e.RuntimeSourcingTimeoutOpt) cp.Expect(`"path":`) cp.Expect(`"executables":`) cp.ExpectExitCode(0) @@ -288,22 +288,6 @@ func (suite *UseIntegrationTestSuite) TestJSON() { AssertValidJSON(suite.T(), cp) } -func (suite *UseIntegrationTestSuite) SetupRCFile(ts *e2e.Session) { - if runtime.GOOS == "windows" { - return - } - - cfg, err := config.New() - suite.Require().NoError(err) - - subshell := subshell.New(cfg) - rcFile, err := subshell.RcFile() - suite.Require().NoError(err) - - err = fileutils.CopyFile(rcFile, filepath.Join(ts.Dirs.HomeDir, filepath.Base(rcFile))) - suite.Require().NoError(err) -} - func TestUseIntegrationTestSuite(t *testing.T) { suite.Run(t, new(UseIntegrationTestSuite)) } diff --git a/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go b/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go index 4f7b42488a..915d5090dd 100644 --- a/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go +++ b/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go @@ -34,8 +34,6 @@ const ( DiffInsert Operation = 1 // DiffEqual item represents an equal diff. DiffEqual Operation = 0 - //IndexSeparator is used to seperate the array indexes in an index string - IndexSeparator = "," ) // Diff represents one diff operation @@ -406,14 +404,11 @@ func (dmp *DiffMatchPatch) DiffLinesToRunes(text1, text2 string) ([]rune, []rune func (dmp *DiffMatchPatch) DiffCharsToLines(diffs []Diff, lineArray []string) []Diff { hydrated := make([]Diff, 0, len(diffs)) for _, aDiff := range diffs { - chars := strings.Split(aDiff.Text, IndexSeparator) - text := make([]string, len(chars)) + runes := []rune(aDiff.Text) + text := make([]string, len(runes)) - for i, r := range chars { - i1, err := strconv.Atoi(r) - if err == nil { - text[i] = lineArray[i1] - } + for i, r := range runes { + text[i] = lineArray[runeToInt(r)] } aDiff.Text = strings.Join(text, "") diff --git a/vendor/github.com/sergi/go-diff/diffmatchpatch/stringutil.go b/vendor/github.com/sergi/go-diff/diffmatchpatch/stringutil.go index 44c4359547..eb727bb594 100644 --- a/vendor/github.com/sergi/go-diff/diffmatchpatch/stringutil.go +++ b/vendor/github.com/sergi/go-diff/diffmatchpatch/stringutil.go @@ -9,11 +9,16 @@ package diffmatchpatch import ( - "strconv" + "fmt" "strings" "unicode/utf8" ) +const UNICODE_INVALID_RANGE_START = 0xD800 +const UNICODE_INVALID_RANGE_END = 0xDFFF +const UNICODE_INVALID_RANGE_DELTA = UNICODE_INVALID_RANGE_END - UNICODE_INVALID_RANGE_START + 1 +const UNICODE_RANGE_MAX = 0x10FFFF + // unescaper unescapes selected chars for compatibility with JavaScript's encodeURI. // In speed critical applications this could be dropped since the receiving application will certainly decode these fine. Note that this function is case-sensitive. Thus "%3F" would not be unescaped. But this is ok because it is only called with the output of HttpUtility.UrlEncode which returns lowercase hex. Example: "%3f" -> "?", "%24" -> "$", etc. var unescaper = strings.NewReplacer( @@ -93,14 +98,93 @@ func intArrayToString(ns []uint32) string { return "" } - indexSeparator := IndexSeparator[0] - - // Appr. 3 chars per num plus the comma. - b := []byte{} + b := []rune{} for _, n := range ns { - b = strconv.AppendInt(b, int64(n), 10) - b = append(b, indexSeparator) + b = append(b, intToRune(n)) } - b = b[:len(b)-1] return string(b) } + +// These constants define the number of bits representable +// in 1,2,3,4 byte utf8 sequences, respectively. +const ONE_BYTE_BITS = 7 +const TWO_BYTE_BITS = 11 +const THREE_BYTE_BITS = 16 +const FOUR_BYTE_BITS = 21 + +// Helper for getting a sequence of bits from an integer. +func getBits(i uint32, cnt byte, from byte) byte { + return byte((i >> from) & ((1 << cnt) - 1)) +} + +// Converts an integer in the range 0~1112060 into a rune. +// Based on the ranges table in https://en.wikipedia.org/wiki/UTF-8 +func intToRune(i uint32) rune { + if i < (1 << ONE_BYTE_BITS) { + return rune(i) + } + + if i < (1 << TWO_BYTE_BITS) { + r, size := utf8.DecodeRune([]byte{0b11000000 | getBits(i, 5, 6), 0b10000000 | getBits(i, 6, 0)}) + if size != 2 || r == utf8.RuneError { + panic(fmt.Sprintf("Error encoding an int %d with size 2, got rune %v and size %d", size, r, i)) + } + return r + } + + // Last -3 here needed because for some reason 3rd to last codepoint 65533 in this range + // was returning utf8.RuneError during encoding. + if i < ((1 << THREE_BYTE_BITS) - UNICODE_INVALID_RANGE_DELTA - 3) { + if i >= UNICODE_INVALID_RANGE_START { + i += UNICODE_INVALID_RANGE_DELTA + } + + r, size := utf8.DecodeRune([]byte{0b11100000 | getBits(i, 4, 12), 0b10000000 | getBits(i, 6, 6), 0b10000000 | getBits(i, 6, 0)}) + if size != 3 || r == utf8.RuneError { + panic(fmt.Sprintf("Error encoding an int %d with size 3, got rune %v and size %d", size, r, i)) + } + return r + } + + if i < (1<= UNICODE_INVALID_RANGE_END { + return result - UNICODE_INVALID_RANGE_DELTA + } + + return result + } + + if size == 4 { + result := uint32(bytes[0]&0b111)<<18 | uint32(bytes[1]&0b111111)<<12 | uint32(bytes[2]&0b111111)<<6 | uint32(bytes[3]&0b111111) + return result - UNICODE_INVALID_RANGE_DELTA - 3 + } + + panic(fmt.Sprintf("Unexpected state decoding rune=%v size=%d", r, size)) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3379a8cb43..a8bedbf9f0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -389,8 +389,8 @@ github.com/rivo/uniseg # github.com/rollbar/rollbar-go v1.1.0 ## explicit github.com/rollbar/rollbar-go -# github.com/sergi/go-diff v1.3.1 -## explicit; go 1.12 +# github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 +## explicit; go 1.13 github.com/sergi/go-diff/diffmatchpatch # github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 ## explicit diff --git a/version.txt b/version.txt index 4753a5b164..f0938929d6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.42.0-RC1 +0.42.0-RC1 \ No newline at end of file