Repository: remotemobprogramming/mob (go)

Issue:
Add better message when issuing a mob command outside a git repository

Pull request description:
Show friendlier error message when run outside of git repo
Use git rev-parse to detect if mob is run from inside of
a git repo. Inform the user they may not be where they
thought they were.

Message reads: 
> 🤦🏿 It doesn't appear that you're running `mob` inside of a git repo ...
>       Check that you're in the right place.

![ezgif com-gif-maker](https://user-images.githubusercontent.com/86355/109856230-40f07000-7c27-11eb-88d0-94beb4608a49.gif)


Closes #114


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a95cef..96ae757 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 # 1.4.0
 - The list of commits included in a mob branch is now truncated to a maximum of 5 entries to prevent the need for scrolling up in order to see the latest included changes.
+- Show more informative error message when `mob <cmd>` is run outside of a git repository.
 
 # 1.3.0
 - The default `MOB_COMMIT_MESSAGE` now includes `[ci skip]` and `[skip ci]` so that all the typical CI systems (including Azure DevOps) will skip this commit.
diff --git a/mob.go b/mob.go
index 7e2ff72..c9e3850 100644
--- a/mob.go
+++ b/mob.go
@@ -856,9 +856,7 @@ func silentgit(args ...string) string {
 	commandString, output, err := runCommand("git", args...)
 
 	if err != nil {
-		sayError(commandString)
-		sayError(output)
-		sayError(err.Error())
+		sayGitError(commandString, output, err)
 		exit(1)
 	}
 	return output
@@ -868,15 +866,35 @@ func git(args ...string) {
 	commandString, output, err := runCommand("git", args...)
 
 	if err != nil {
-		sayError(commandString)
-		sayError(output)
-		sayError(err.Error())
+		sayGitError(commandString, output, err)
 		exit(1)
 	} else {
 		sayIndented(commandString)
 	}
 }
 
+func sayGitError(commandString string, output string, err error) {
+	if !isGit() {
+		path, err := os.Getwd();
+		if err == nil {
+			cwdMsg := fmt.Sprintf("The current working directory, %s, is not a git repository.", path)
+		
+			sayWithPrefix("mob expects the current working directory to be a git repository.", "🤦🏿 ")
+			sayIndented(cwdMsg);
+			say(" ")
+		}
+		
+	}
+	sayError(commandString)
+	sayError(output)
+	sayError(err.Error())
+}
+
+func isGit() bool {
+	_, _, err := runCommand("git", "rev-parse")
+	return err == nil
+}
+
 func gitignorefailure(args ...string) error {
 	commandString, output, err := runCommand("git", args...)
 


Test patch:
diff --git a/create-testbed b/create-testbed
index 6db3091..c8d2165 100755
--- a/create-testbed
+++ b/create-testbed
@@ -5,6 +5,7 @@ echo "Creating test assets..."
 tmpdir="/tmp/mob"
 localdir="${tmpdir}/local"
 remotedir="${tmpdir}/remote"
+notgit="${tmpdir}/notgit"
 echo ""
 echo "Root of temporary test assets: ${tmpdir}"
 echo "Path to central repository ('remote'): ${remotedir}"
@@ -13,6 +14,7 @@ echo "Path to clone of central repository ('local'): ${localdir}"
 mkdir -p "${localdir}" "${remotedir}"
 rm -rf "${localdir}" "${remotedir}"
 mkdir -p "${localdir}" "${remotedir}"
+mkdir -p "${localdir}" "${notgit}"
 
 echo ""
 echo "Creating test repositories..."
diff --git a/mob_test.go b/mob_test.go
index 31287c1..036771e 100644
--- a/mob_test.go
+++ b/mob_test.go
@@ -11,6 +11,7 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+	"errors"
 )
 
 func TestParseArgs(t *testing.T) {
@@ -812,6 +813,23 @@ func TestDoneMerge(t *testing.T) {
 	assertOutputContains(t, output, "   git commit")
 }
 
+func TestIsGitIdentifiesGitRepo(t *testing.T) {
+	setup(t)
+	equals(t, true, isGit())
+}
+
+func TestIsGitIdentifiesOutsideOfGitRepo(t *testing.T) {
+	setWorkingDir("/tmp/git/notgit")
+	equals(t, false, isGit())
+}
+
+func TestNotAGitRepoMessage(t *testing.T) {
+	setWorkingDir("/tmp/git/notgit")
+	output := captureOutput()
+	sayGitError("TEST", "TEST", errors.New("TEST"))
+	assertOutputContains(t, output, "mob expects the current working directory to be a git repository.")
+}
+
 func setup(t *testing.T) *string {
 	configuration = getDefaultConfiguration()
 	configuration.MobNextStay = false