Repository: peak/s5cmd (go)

Issue:
`s5cmd` should exit 1 if given an unknown command
If I pass in an unknown command, `s5cmd` prints usage and exits 0. I would expect this to be an error.

Pull request:
command/app: exit with code 1 if given command is not found
Fixes #295

```
s5cmd git:correct-exit-code Δ s5cmd naber
ERROR "naber": command not found

s5cmd git:correct-exit-code Δ echo $?
1
```

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96937a3..5eb39ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
 
 ## not released yet
 
+#### Improvements
+
+- If retryable errors are received during command execution, users now can see what's happening under the hood. ([#261](https://github.com/peak/s5cmd/pull/261))
+- Update documentation about the AWS_PROFILE environment variable. ([#275](https://github.com/peak/s5cmd/pull/275)) [@davebiffuk](https://github.com/davebiffuk)
+
+#### Bugfixes
+
+- Fixed a bug where write-bit was required to upload a file. ([#258](https://github.com/peak/s5cmd/issues/258))
+- Fixed a bug where object could not be found if S3 key contains certain special characters. ([#279](https://github.com/peak/s5cmd/issues/279)) [@khacminh](https://github.com/khacminh)
+- `s5cmd` exits with code `1` if given command is not found. It was `0` before. ([#295](https://github.com/peak/s5cmd/issues/295))
+
 
 ## v1.2.1 - 3 Dec 2020
 
diff --git a/command/app.go b/command/app.go
index 6608c67..1b90bb0 100644
--- a/command/app.go
+++ b/command/app.go
@@ -87,6 +87,17 @@ var app = &cli.App{
 
 		return nil
 	},
+	CommandNotFound: func(c *cli.Context, command string) {
+		msg := log.ErrorMessage{
+			Command: command,
+			Err:     "command not found",
+		}
+		log.Error(msg)
+
+		// After callback is not called if app exists with cli.Exit.
+		parallel.Close()
+		log.Close()
+	},
 	Action: func(c *cli.Context) error {
 		if c.Bool("install-completion") {
 			if cmpinstall.IsInstalled(appName) {
@@ -96,6 +107,12 @@ var app = &cli.App{
 			return cmpinstall.Install(appName)
 		}
 
+		args := c.Args()
+		if args.Present() {
+			cli.ShowCommandHelp(c, args.First())
+			return cli.Exit("", 1)
+		}
+
 		return cli.ShowAppHelp(c)
 	},
 	After: func(c *cli.Context) error {
diff --git a/command/mv.go b/command/mv.go
index 4d8e169..a4051df 100644
--- a/command/mv.go
+++ b/command/mv.go
@@ -40,11 +40,7 @@ var moveCommand = &cli.Command{
 	Flags:              copyCommandFlags, // move and copy commands share the same flags
 	CustomHelpTemplate: moveHelpTemplate,
 	Before: func(c *cli.Context) error {
-		err := copyCommand.Before(c)
-		if err != nil {
-			printError(givenCommand(c), c.Command.Name, err)
-		}
-		return err
+		return copyCommand.Before(c)
 	},
 	Action: func(c *cli.Context) (err error) {
 		defer stat.Collect(c.Command.FullName(), &err)()


Test patch:
diff --git a/e2e/app_test.go b/e2e/app_test.go
index 39fc919..140bbf9 100644
--- a/e2e/app_test.go
+++ b/e2e/app_test.go
@@ -76,10 +76,24 @@ func TestAppDashStat(t *testing.T) {
 	cmd := s5cmd("--stat", "ls")
 	result := icmd.RunCmd(cmd)
 
-	result.Assert(t, icmd.Expected{ExitCode: 0})
+	result.Assert(t, icmd.Success)
 
 	out := result.Stdout()
 
 	tsv := fmt.Sprintf("%s\t%s\t%s\t%s\t", "Operation", "Total", "Error", "Success")
 	assert.Assert(t, strings.Contains(out, tsv))
 }
+
+func TestAppUnknownCommand(t *testing.T) {
+	_, s5cmd, cleanup := setup(t)
+	defer cleanup()
+
+	cmd := s5cmd("unknown-command")
+	result := icmd.RunCmd(cmd)
+
+	result.Assert(t, icmd.Expected{ExitCode: 1})
+
+	assertLines(t, result.Stderr(), map[int]compareFunc{
+		0: equals(`ERROR "unknown-command": command not found`),
+	})
+}
diff --git a/e2e/ls_test.go b/e2e/ls_test.go
index e94e497..374c1fc 100644
--- a/e2e/ls_test.go
+++ b/e2e/ls_test.go
@@ -151,7 +151,7 @@ func TestListS3ObjectsWithDashS(t *testing.T) {
 	createBucket(t, s3client, bucket)
 	putFile(t, s3client, bucket, "testfile1.txt", "this is a file content")
 
-	cmd := s5cmd("ls -s", "s3://"+bucket+"/testfile1.txt")
+	cmd := s5cmd("ls", "-s", "s3://"+bucket+"/testfile1.txt")
 	result := icmd.RunCmd(cmd)
 
 	result.Assert(t, icmd.Success)