Use delayed variable expansion for error in cmd

Batch expands variables when it is read not executed, which can lead to
unexpected behavior in if and for statements in cmd. To read more about
it check https://devblogs.microsoft.com/oldnewthing/20060823-00/ &
http://batcheero.blogspot.com/2007/06/how-to-enabledelayedexpansion.html

In !1203 we
introduced the follow code
https://gitlab.com/gitlab-org/gitlab-runner/blob/a9b92ea558289218606269073e2c537b017fa66a/shells/abstract.go#L166-171
which leads to the following batch file:

```
"git" "remote" "add" "origin" "http://gitlab-ci-token:xxx@192.168.1.79:3000/root/ci-scratch-pad.git"
IF %errorlevel% EQU 0 (
  echo Created fresh repository.
) ELSE (
  "git" "remote" "set-url" "origin" "http://gitlab-ci-token:xxx@192.168.1.79:3000/root/ci-scratch-pad.git"
  IF %errorlevel% NEQ 0 exit /b %errorlevel%

  del /f /q ".git\index.lock" 2>NUL 1>NUL
  del /f /q ".git\shallow.lock" 2>NUL 1>NUL
  del /f /q ".git\HEAD.lock" 2>NUL 1>NUL
  del /f /q ".git\hooks\post-checkout" 2>NUL 1>NUL
  "git" "clean" "-ffdx"
  IF %errorlevel% NEQ 0 exit /b %errorlevel%

  "git" "diff" "--no-ext-diff" "--quiet" "--exit-code" 2>NUL 1>NUL
  IF %errorlevel% EQU 0 (
    echo Clean repository
  ) ELSE (
    "git" "reset" "--hard"
    IF %errorlevel% NEQ 0 exit /b %errorlevel%

  )
)
```

Now, %errorlevel% would always be the value of the first command since
the variable is expended everywhere. Using !errorlevel! instead would
allow us to check the exit status of the commands inside of the else
condition. We are already setting `ENABLEDELAYEDEXPANSION` in
https://gitlab.com/gitlab-org/gitlab-runner/blob/a9b92ea558289218606269073e2c537b017fa66a/shells/cmd.go#L226

closes #4080
parent 83d4b48d
......@@ -779,6 +779,27 @@ func TestBuildWithGitSSLAndStrategyFetch(t *testing.T) {
})
}
func TestBuildWithUntrackedDirFromPreviousBuild(t *testing.T) {
shellstest.OnEachShell(t, func(t *testing.T, shell string) {
successfulBuild, err := common.GetRemoteSuccessfulBuild()
assert.NoError(t, err)
build, cleanup := newBuild(t, successfulBuild, shell)
defer cleanup()
build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"})
out, err := runBuildReturningOutput(t, build)
assert.NoError(t, err)
assert.Contains(t, out, "Created fresh repository")
err = os.MkdirAll(fmt.Sprintf("%s/.test", build.FullProjectDir()), 0644)
require.NoError(t, err)
out, err = runBuildReturningOutput(t, build)
assert.NoError(t, err)
assert.Contains(t, out, "Removing .test/")
})
}
func TestInteractiveTerminal(t *testing.T) {
cases := []struct {
app string
......
......@@ -80,7 +80,7 @@ func (b *CmdWriter) Unindent() {
}
func (b *CmdWriter) checkErrorLevel() {
b.Line("IF %errorlevel% NEQ 0 exit /b %errorlevel%")
b.Line("IF !errorlevel! NEQ 0 exit /b !errorlevel!")
b.Line("")
}
......@@ -134,14 +134,14 @@ func (b *CmdWriter) IfFile(path string) {
func (b *CmdWriter) IfCmd(cmd string, arguments ...string) {
cmdline := b.buildCommand(cmd, arguments...)
b.Line(fmt.Sprintf("%s 2>NUL 1>NUL", cmdline))
b.Line("IF %errorlevel% EQU 0 (")
b.Line("IF !errorlevel! EQU 0 (")
b.Indent()
}
func (b *CmdWriter) IfCmdWithOutput(cmd string, arguments ...string) {
cmdline := b.buildCommand(cmd, arguments...)
b.Line(fmt.Sprintf("%s", cmdline))
b.Line("IF %errorlevel% EQU 0 (")
b.Line("IF !errorlevel! EQU 0 (")
b.Indent()
}
......
......@@ -50,7 +50,7 @@ func TestCMD_CDShellEscapes(t *testing.T) {
} {
writer := &CmdWriter{}
writer.Cd(tc.in)
expected := fmt.Sprintf("cd /D \"%s\"\r\nIF %%errorlevel%% NEQ 0 exit /b %%errorlevel%%\r\n\r\n", tc.out)
expected := fmt.Sprintf("cd /D \"%s\"\r\nIF !errorlevel! NEQ 0 exit /b !errorlevel!\r\n\r\n", tc.out)
assert.Equal(t, expected, writer.String(), "case %d", i)
}
}
......@@ -59,12 +59,12 @@ func TestCMD_CommandShellEscapes(t *testing.T) {
writer := &CmdWriter{}
writer.Command("foo", "x&(y)")
assert.Equal(t, "\"foo\" \"x^&(y)\"\r\nIF %errorlevel% NEQ 0 exit /b %errorlevel%\r\n\r\n", writer.String())
assert.Equal(t, "\"foo\" \"x^&(y)\"\r\nIF !errorlevel! NEQ 0 exit /b !errorlevel!\r\n\r\n", writer.String())
}
func TestCMD_IfCmdShellEscapes(t *testing.T) {
writer := &CmdWriter{}
writer.IfCmd("foo", "x&(y)")
assert.Equal(t, "\"foo\" \"x^&(y)\" 2>NUL 1>NUL\r\nIF %errorlevel% EQU 0 (\r\n", writer.String())
assert.Equal(t, "\"foo\" \"x^&(y)\" 2>NUL 1>NUL\r\nIF !errorlevel! EQU 0 (\r\n", writer.String())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment