Repository: remotemobprogramming/mob (go)

Issue:
Did we intend to deprecate older Go versions and rename the executable to `mob.sh`?
While touch-testing #121 today it took me several confusing minutes to figure out why none of the new features were showing up in `mob`, and after a lot of thrashing around I finally realized my `mob` executable wasn't rebuilding when I ran `go build`.  I finally found this in go.mod, last modified in 878661c5:

```
module mob.sh

go 1.16
```

That causes `go build` to build an executable named `mob.sh`, which will cause these issues:

- an `.sh` suffix implies a shell script -- my eyes didn't spot it as a Go executable
- the name change from `mob` to `mob.sh` breaks a buncha local code that assumes the executable is named `mob`
- the new name disagrees with examples in the README

The upgrade from version 1.15 to 1.16 is also puzzling me -- the code builds and the tests pass in our org's 1.14.2 environment, though I admit I may be missing something, and I didn't try this in a fresh Go 1.14.2 install with an empty local package cache 'cause I'm busy today digging into #121.  Did we bump that version number because of requirements that need 1.16 features?

Pull request:
go.mod: fix executable name, lower expected version to 1.14
- closes #151

Patch:
diff --git a/README.md b/README.md
index f2b87c5..4d9f6a5 100644
--- a/README.md
+++ b/README.md
@@ -60,13 +60,7 @@ On windows via [Scoop](https://scoop.sh/):
 scoop install mob
 ``` 
 
-On [Nix](http://nixos.org) through the [mob.nix](./mob.nix) expression like this `mob = callPackage ./mob.nix {};`. To install and configure espeak-ng for text-to-spech support, pass `withSpeech = true;`.
-
-On Arch Linux via yay:
-
-```bash
-yay -S mobsh-bin
-```
+On [Nix](http://nixos.org) through the [mob.nix](./mob.nix) expression like this `mob = callPackage ./mob.nix {};`.
 
 ## How to use
 
@@ -226,7 +220,7 @@ MOB_NEXT_STAY=true mob next
 cd $PROJECT_ROOT
 
 git version # >= 2.17
-go version # >= 1.16
+go version # >= 1.14
 
 go build # builds 'mob'
 
@@ -239,20 +233,6 @@ go test -run TestDetermineBranches # runs the single test named 'TestDetermineBr
 go test -coverprofile=cover.out && go tool cover -html=cover.out
 ```
 
-### Run the Tests from your IDE (GoLand or IntelliJ IDEA)
-
-When you try to run *mob_test.go* in your IDE you may get the following errors:
-```
-./mob_test.go:17:2: undefined: configuration
-./mob_test.go:17:18: undefined: getDefaultConfiguration
-... and so on ...
-```
-
-This is because the **Run Configuration** uses **Test Kind: File** as default and therefor does not find the source code within *mob.go*.
-Change the **Test Kind** to **Directory**, and try again.
-The tests should work now.
-
-
 ## Design Concepts
 
 - **mob** is a thin wrapper around git.
diff --git a/go.mod b/go.mod
index c82f153..617ff1c 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
-module mob.sh
+module github.com/remotemobprogramming/mob
 
-go 1.16
+go 1.14
diff --git a/mob.go b/mob.go
index 5973f6c..a7118c1 100644
--- a/mob.go
+++ b/mob.go
@@ -280,17 +280,17 @@ func execute(command string, parameter []string) {
 			startTimer(configuration.MobTimer)
 		}
 
-		status(configuration)
+		status()
 	case "n", "next":
 		next(configuration)
 	case "d", "done":
-		done(configuration)
+		done()
 	case "reset":
-		reset(configuration)
+		reset()
 	case "config":
 		config(configuration)
 	case "status":
-		status(configuration)
+		status()
 	case "t", "timer":
 		if len(parameter) > 0 {
 			timer := parameter[0]
@@ -478,7 +478,7 @@ func moo() {
 	}
 }
 
-func reset(configuration Configuration) {
+func reset() {
 	git("fetch", configuration.RemoteName)
 
 	currentBaseBranch, currentWipBranch := determineBranches(gitCurrentBranch(), gitBranches(), configuration)
@@ -487,7 +487,7 @@ func reset(configuration Configuration) {
 	if hasLocalBranch(currentWipBranch) {
 		git("branch", "--delete", "--force", currentWipBranch)
 	}
-	if hasRemoteBranch(currentWipBranch, configuration) {
+	if hasRemoteBranch(currentWipBranch) {
 		git("push", "--no-verify", configuration.RemoteName, "--delete", currentWipBranch)
 	}
 	sayInfo("Branches " + currentWipBranch + " and " + configuration.RemoteName + "/" + currentWipBranch + " deleted")
@@ -521,7 +521,7 @@ func start(configuration Configuration) {
 		return
 	}
 
-	if !hasRemoteBranch(currentBaseBranch, configuration) {
+	if !hasRemoteBranch(currentBaseBranch) {
 		sayError("Remote branch " + configuration.RemoteName + "/" + currentBaseBranch + " is missing")
 		sayTodo("To set the upstream branch, use", "git push "+configuration.RemoteName+" "+currentBaseBranch+" --set-upstream")
 		return
@@ -531,7 +531,7 @@ func start(configuration Configuration) {
 		git("merge", "FETCH_HEAD", "--ff-only")
 	}
 
-	if hasRemoteBranch(currentWipBranch, configuration) {
+	if hasRemoteBranch(currentWipBranch) {
 		startJoinMobSession()
 	} else {
 		startNewMobSession()
@@ -645,7 +645,7 @@ func getCachedChanges() string {
 	return strings.TrimSpace(silentgit("diff", "--cached", "--stat"))
 }
 
-func done(configuration Configuration) {
+func done() {
 	if !isMobProgramming(configuration) {
 		sayError("you aren't mob programming")
 		sayTodo("to start mob programming, use", "mob start")
@@ -656,7 +656,7 @@ func done(configuration Configuration) {
 
 	currentBaseBranch, currentWipBranch := determineBranches(gitCurrentBranch(), gitBranches(), configuration)
 
-	if hasRemoteBranch(currentWipBranch, configuration) {
+	if hasRemoteBranch(currentWipBranch) {
 		if !isNothingToCommit() {
 			git("add", "--all")
 			git("commit", "--message", configuration.WipCommitMessage, "--no-verify")
@@ -665,7 +665,7 @@ func done(configuration Configuration) {
 
 		git("checkout", currentBaseBranch)
 		git("merge", configuration.RemoteName+"/"+currentBaseBranch, "--ff-only")
-		mergeFailed := gitignorefailure("merge", squashOrNoCommit(configuration), "--ff", currentWipBranch)
+		mergeFailed := gitignorefailure("merge", squashOrNoCommit(), "--ff", currentWipBranch)
 		if mergeFailed != nil {
 			return
 		}
@@ -682,7 +682,7 @@ func done(configuration Configuration) {
 	}
 }
 
-func squashOrNoCommit(configuration Configuration) string {
+func squashOrNoCommit() string {
 	if configuration.MobDoneSquash {
 		return "--squash"
 	} else {
@@ -690,7 +690,7 @@ func squashOrNoCommit(configuration Configuration) string {
 	}
 }
 
-func status(configuration Configuration) {
+func status() {
 	if isMobProgramming(configuration) {
 		sayInfo("you are mob programming")
 
@@ -756,7 +756,7 @@ func hasLocalBranch(localBranch string) bool {
 	return false
 }
 
-func hasRemoteBranch(branch string, configuration Configuration) bool {
+func hasRemoteBranch(branch string) bool {
 	remoteBranches := gitRemoteBranches()
 	remoteBranch := configuration.RemoteName + "/" + branch
 	debugInfo("Remote Branches: " + strings.Join(remoteBranches, "\n"))
diff --git a/mob.nix b/mob.nix
index 67e6d05..03a73f3 100644
--- a/mob.nix
+++ b/mob.nix
@@ -1,4 +1,4 @@
-{withSpeech ? false, makeWrapper, espeak-ng, buildGoPackage, fetchFromGitHub, lib, ...}:
+{buildGoPackage, fetchFromGitHub, lib, ...}:
 buildGoPackage rec {
     pname = "mob.sh";
     version = "1.3.0";
@@ -11,12 +11,6 @@ buildGoPackage rec {
       sha256 = "04x6cl2r4ja41cmy82p5apyavmdvak6jsclzf2l7islf0pmsnddv";
     };
 
-    buildInputs =
-      if withSpeech then
-        [ espeak-ng makeWrapper ]
-      else
-        [];
-
     goPackagePath = "github.com/${owner}/${repo}/";
 
     subPackages = [ "." ];
@@ -26,9 +20,4 @@ buildGoPackage rec {
       homepage = "https://mob.sh";
       license = lib.licenses.mit;
     };
-
-    preFixup = if withSpeech then ''
-wrapProgram "$out/bin/mob" --set MOB_VOICE_COMMAND "${espeak-ng.out}/bin/espeak"
-'' else "";
-
 }


Test patch:
diff --git a/create-testbed b/create-testbed
index d5ecc47..c8d2165 100755
--- a/create-testbed
+++ b/create-testbed
@@ -1,49 +1,33 @@
 #!/bin/bash
 set -e
 
-createrepo() {
-    dir=$1
-    name=$(basename $dir)
-    echo ""
-    echo "Cloning remote to ${dir}"
-    rm -rf "${dir}"
-    mkdir -p "${dir}"
-    (cd "${dir}" && git clone --origin origin "file://${remotedir}" .)
-    (cd "${dir}" && git config --local user.name ${name})
-    (cd "${dir}" && git config --local user.email ${name}@example.com)
-}
-
-tmpdir="/tmp/mob" # XXX this should be more unique
+echo "Creating test assets..."
+tmpdir="/tmp/mob"
 localdir="${tmpdir}/local"
 remotedir="${tmpdir}/remote"
 notgit="${tmpdir}/notgit"
-
-# create base dir
 echo ""
-echo "Creating temporary test assets in ${tmpdir}"
-mkdir -p ${tmpdir}
+echo "Root of temporary test assets: ${tmpdir}"
+echo "Path to central repository ('remote'): ${remotedir}"
+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}"
 
-# create remote repo
 echo ""
-echo "Creating remote repository ${remotedir}"
-rm -rf "${remotedir}"
-mkdir -p "${remotedir}"
+echo "Creating test repositories..."
 (cd "${remotedir}" && git --bare init)
+(cd "${localdir}" && git clone --origin origin "file://${remotedir}" . && git config --local user.name "Test User 1" && git config --local user.email "test@test.test" && echo "test" >test.txt && git add . && git commit -m "initial import" && git push --set-upstream --all origin)
 
-# create first local repo
-createrepo "${localdir}"
-
-# populate an initial import
-echo "Populating and pushing"
-(cd "${localdir}" && echo "test" >test.txt && git add . && git commit -m "initial import" && git push --set-upstream --all origin)
-
-# create more local repos
-for name in localother alice bob
-do
-    createrepo "${tmpdir}/${name}"
-done
-
-# create a non-git area
-mkdir -p "${localdir}" "${notgit}"
+# optional
+echo ""
+echo "Creating a second mob participant's repository..."
+localotherdir="${tmpdir}/localother"
+echo "Path to second clone of central repository ('localotherdir'): ${localotherdir}"
+rm -rf "${localotherdir}"
+mkdir -p "${localotherdir}"
+(cd "${localotherdir}" && git clone --origin origin "file://${remotedir}" . && git config --local user.name "Test User 2" && git config --local user.email "test2@test.test")
 
 echo "Done."
diff --git a/mob_test.go b/mob_test.go
index 44c16ec..036771e 100644
--- a/mob_test.go
+++ b/mob_test.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"errors"
 	fmt "fmt"
 	"io/ioutil"
 	"os"
@@ -12,6 +11,7 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+	"errors"
 )
 
 func TestParseArgs(t *testing.T) {
@@ -221,7 +221,7 @@ func TestVersion(t *testing.T) {
 func TestStatusNotMobProgramming(t *testing.T) {
 	output := setup(t)
 
-	status(configuration)
+	status()
 
 	assertOutputContains(t, output, "you aren't mob programming")
 }
@@ -269,7 +269,7 @@ func TestRequireCommitMessage(t *testing.T) {
 func TestDoneNotMobProgramming(t *testing.T) {
 	output := setup(t)
 
-	done(configuration)
+	done()
 
 	assertOutputContains(t, output, "you aren't mob programming")
 }
@@ -278,7 +278,7 @@ func TestStatusMobProgramming(t *testing.T) {
 	output := setup(t)
 	start(configuration)
 
-	status(configuration)
+	status()
 
 	assertOutputContains(t, output, "you are mob programming")
 }
@@ -294,7 +294,7 @@ func TestStatusWithMoreThan5LinesOfLog(t *testing.T) {
 	}
 
 	output := captureOutput()
-	status(configuration)
+	status()
 	assertOutputContains(t, output, "This mob branch contains 6 commits.")
 }
 
@@ -393,7 +393,7 @@ func TestStartNextWithBranch(t *testing.T) {
 	assertOnBranch(t, "master")
 
 	configuration.WipBranchQualifier = "green"
-	reset(configuration)
+	reset()
 	assertNoMobSessionBranches(t, "mob/master-green")
 }
 
@@ -445,7 +445,7 @@ func TestStartNextWithBranchContainingHyphen(t *testing.T) {
 func TestReset(t *testing.T) {
 	setup(t)
 
-	reset(configuration)
+	reset()
 
 	assertOnBranch(t, "master")
 	assertNoMobSessionBranches(t, "mob-session")
@@ -458,7 +458,7 @@ func TestResetCommit(t *testing.T) {
 	next(configuration)
 	assertMobSessionBranches(t, "mob-session")
 
-	reset(configuration)
+	reset()
 
 	assertOnBranch(t, "master")
 	assertNoMobSessionBranches(t, "mob-session")
@@ -539,55 +539,12 @@ func TestStartDoneWithMobDoneSquashTrue(t *testing.T) {
 	start(configuration)
 	assertOnBranch(t, "mob-session")
 
-	done(configuration)
+	done()
 
 	assertOnBranch(t, "master")
 	assertNoMobSessionBranches(t, "mob-session")
 }
 
-func TestRunOutput(t *testing.T) {
-	setup(t)
-
-	setWorkingDir("/tmp/mob/local")
-	start(configuration)
-	createFile(t, "file1.txt", "asdf")
-	output := run(t, "cat", "/tmp/mob/local/file1.txt")
-	assertOutputContains(t, output, "asdf")
-}
-
-func TestTestbed(t *testing.T) {
-	setup(t)
-
-	setWorkingDir("/tmp/mob/local")
-	start(configuration)
-	createFile(t, "file1.txt", "asdf")
-	next(configuration)
-
-	setWorkingDir("/tmp/mob/localother")
-	start(configuration)
-	createFile(t, "file2.txt", "asdf")
-	next(configuration)
-
-	setWorkingDir("/tmp/mob/alice")
-	start(configuration)
-	createFile(t, "file3.txt", "owqe")
-	next(configuration)
-
-	setWorkingDir("/tmp/mob/bob")
-	start(configuration)
-	createFile(t, "file4.txt", "zcvx")
-	next(configuration)
-
-	setWorkingDir("/tmp/mob/local")
-	start(configuration)
-
-	output := silentgit("log", "--pretty=format:'%ae'")
-	assertOutputContains(t, &output, "local")
-	assertOutputContains(t, &output, "localother")
-	assertOutputContains(t, &output, "alice")
-	assertOutputContains(t, &output, "bob")
-}
-
 func TestStartDoneWithMobDoneSquashFalse(t *testing.T) {
 	setup(t)
 	configuration.MobDoneSquash = false
@@ -595,7 +552,7 @@ func TestStartDoneWithMobDoneSquashFalse(t *testing.T) {
 	start(configuration)
 	assertOnBranch(t, "mob-session")
 
-	done(configuration)
+	done()
 
 	assertOnBranch(t, "master")
 	assertNoMobSessionBranches(t, "mob-session")
@@ -613,7 +570,7 @@ func TestStartDonePublishingOneManualCommit(t *testing.T) {
 	createFileAndCommitIt(t, "example.txt", "content", "[manual-commit-1] publish this commit to master")
 	assertCommits(t, 2)
 
-	done(configuration) // without squash (configuration)
+	done() // without squash (configuration)
 
 	assertOnBranch(t, "master")
 	assertCommitsOnBranch(t, 2, "master")
@@ -634,7 +591,7 @@ func TestStartDoneSquashTheOneManualCommit(t *testing.T) {
 	createFileAndCommitIt(t, "example.txt", "content", "[manual-commit-1] publish this commit to master")
 	assertCommits(t, 2)
 
-	done(configuration)
+	done()
 
 	// MAYBE assertUnstagedChanges()
 	assertOnBranch(t, "master")
@@ -651,7 +608,7 @@ func TestStartDoneFeatureBranch(t *testing.T) {
 	start(configuration)
 	assertOnBranch(t, "mob/feature1")
 
-	done(configuration)
+	done()
 
 	assertOnBranch(t, "feature1")
 	assertNoMobSessionBranches(t, "mob-session")
@@ -786,7 +743,7 @@ func TestConflictingMobSessions(t *testing.T) {
 
 	setWorkingDir("/tmp/mob/local")
 	start(configuration)
-	done(configuration)
+	done()
 	git("commit", "-m", "\"finished mob session\"")
 
 	setWorkingDir("/tmp/mob/local")
@@ -813,7 +770,7 @@ func TestConflictingMobSessionsNextStay(t *testing.T) {
 
 	setWorkingDir("/tmp/mob/local")
 	start(configuration)
-	done(configuration)
+	done()
 	git("commit", "-m", "\"finished mob session\"")
 
 	setWorkingDir("/tmp/mob/localother")
@@ -834,7 +791,7 @@ func TestDoneMergeConflict(t *testing.T) {
 
 	setWorkingDir("/tmp/mob/local")
 	start(configuration)
-	done(configuration)
+	done()
 	assertOutputContains(t, output, "Automatic merge failed; fix conflicts and then commit the result.")
 }
 
@@ -852,7 +809,7 @@ func TestDoneMerge(t *testing.T) {
 
 	setWorkingDir("/tmp/mob/local")
 	start(configuration)
-	done(configuration)
+	done()
 	assertOutputContains(t, output, "   git commit")
 }
 
@@ -894,7 +851,7 @@ func captureOutput() *string {
 	return &messages
 }
 
-func run(t *testing.T, name string, args ...string) *string {
+func run(t *testing.T, name string, args ...string) {
 	commandString, output, err := runCommand(name, args...)
 	if err != nil {
 		fmt.Println(commandString)
@@ -902,7 +859,6 @@ func run(t *testing.T, name string, args ...string) *string {
 		fmt.Println(err.Error())
 		t.Error("command " + commandString + " failed")
 	}
-	return &output
 }
 
 func createTestbed(t *testing.T) {
@@ -980,7 +936,7 @@ func assertOutputNotContains(t *testing.T, output *string, notContains string) {
 }
 
 func assertMobSessionBranches(t *testing.T, branch string) {
-	if !hasRemoteBranch(branch, configuration) {
+	if !hasRemoteBranch(branch) {
 		failWithFailure(t, configuration.RemoteName+"/"+branch, "none")
 	}
 	if !hasLocalBranch(branch) {
@@ -989,7 +945,7 @@ func assertMobSessionBranches(t *testing.T, branch string) {
 }
 
 func assertNoMobSessionBranches(t *testing.T, branch string) {
-	if hasRemoteBranch(branch, configuration) {
+	if hasRemoteBranch(branch) {
 		failWithFailure(t, "none", configuration.RemoteName+"/"+branch)
 	}
 	if hasLocalBranch(branch) {