TCR Variants (test && commit || revert)

In his post, Kent Beck suggests ‘test && commit || revert.’ This a simplification. This post explores, which variants available to this and how you can implement TCR.

Note: TCR is new to you? Check out this post, which provides background, context and an example.

The Original

The schema of the original version is ‘test && commit || revert.’ As an answer to the question regarding the particular commands, Kent suggested:

$ ./test && git commit -am working || git reset — hard

This initial approximation has some flaws you will recognize as soon as you implement something: As soon as your project does not compile it reverts and as soon as the current test fails, the test gets deleted.

In Pseudocode:

if(test().success)
commit()
else
revert()

BTCR

The first alternative solves the compilation issue.

$ ./buildIt && (./test && git commit -am working || git reset — hard)

This version tries to build the software first. If it fails, nothing is executed — no commit, no revert. Compilation-Issue solved.

There are some details, that are interesting. Gradle, for example, has tested in its build-process. Therefore you do a ‘-x text’ in your build command. You also don’t lose performance (at least with Gradle), because the tests use the built artifacts — no extra work is done.

In Pseudo-Code:

if(build().failed)
return
if(test().success)
commit()
else
revert()

The Relaxed

The second issue of the original suggestion is that your unit-tests get deleted all the time. The idea is, not to reset the ‘test’ folder but only the ‘src’ folder. Gets ‘reset’ command is not capable of doing this. Therefore we use ‘checkout’:

# git checkout HEAD — src/main/
$ ./buildIt && (./test && git commit -am working || git checkout HEAD — src/main/)

It is worth, to put this in its script, called revert (much more beautiful to read).

In Pseudo-Code:

if(build().failed)
return
if(test().success)
commit()
else
revert('src/main')

The Gentle

It can become annoying to rewrite your production code, if you have to rewrite your reverted production-code again and again, just because of a tiny mistake. To introduce kind of a “Copy&Paste” memory, we can use the built-in tools for Git (‘stash’).

## git stash drop 0 2&>/dev/null; git add -A && git stash push
$ ./buildIt && (git stash drop 0 2&>/dev/null; git add -A && git stash push)

After TCR kicked in and reverted our code, we can still type

$ git stash apply

to get our wrong code back. You can argue, that this hurts the idea of TCR, but sometimes you want to do this version.

The Split

It is a good idea, to split the commands in different scripts. It becomes a joy to maintain them (mainly to try different revert strategies)

$ cat ./test
./scripts/buildIt && (./scripts/runTests && ./scripts/commit || ./scripts/revert)
$ tree script
scripts/
├── buildIt
├── commit
├── revert
└── runTests
$ cat scripts/buildIt
./gradlew build -x test
$ cat scripts/commit
git commit -am working
$ cat scripts/revert 
# git reset --hard
git checkout HEAD -- src/main/
$ cat scripts/runTests
./gradlew test

The buddy — Continous TCR

“How low can you go?” Triggering TCR manually remembers to TDD. But if we want to shorten our feedback-cycle further, we can do more. This approach goes in the direction of Google Docs (what is our holy grail):

while true
do
./tcr
done
$ cat tcr
./buildIt && (./test && git commit -am working || git checkout HEAD — src/main/)

So, why is this called “The buddy”? You have to try it. When you code it is like someone is sitting beside you and as soon he spots an error, he brings you back in a green state. Replace in the example in this post of Fibonacci the plus with a minus. A ghost hand changes it back immediately. With the buddy, you will feel the real spirit of TCR.

Pseudo-Code:

while(true) {
tcr()
}
function tcr() {
if(build().failed)
return
if(test().success)
commit()
else
revert()
}

The watch buddy

Alejandro Marcu pointed out, that the infinity-loop is a waste of resources. ‘The watch buddy’ brings an optimization as it introduces a file-system-watch (e.g. ionotify in Linux):

while true
do
inotifywait -r -e modify .
./tcr
done
$ cat tcr
./buildIt && (./test && git commit -am working || git checkout HEAD — src/main/)

ionotifywait -r srcblocks until there is a change in the src directory. The rest is the same as in ‘The buddy’. The Pseudo-code looks familiar:

while(true) {
block_until_change_in_directory('src')
tcr()
}
function tcr() {
if(build().failed)
return
if(test().success)
commit()
else
revert()
}

You can watch Alejandro implementing an example with ‘The watch buddy’ on YouTube.

The Collaborator

As explained more here, TCR consists of mainly two parts:

  1. ‘test && commit’: This can be used together with push/pull to synchronize with your colleagues. It becomes a collaboration tool.
  2. ‘test && revert’: This changes the way you code and supports the ideas of TDD much better as TDD (commit is here included as well, but only as an implementation detail).

In the collaborator, we add and run another script together with another variant (Kent Beck proposed this as part of “Limbo on the Cheap”:

while true
do
git pull --rebase
git push origin master
done

This script makes synchronization transparent — like Google Docs. As soon we run ‘./tcr.’ (whatever variant you choose), the second Git command pushes it, and on every workplace of your colleagues, the first command synchronizes their code.

In Pseudocode:

async {
while(true) {
Git.pull('rebase')
Git.push('origin', 'master')
}
}
./tcr

Local Buddy, Remote Team

An especially powerful variant is “The Collaborator” and “The Buddy” together.

do
git pull --rebase
git push origin master
done
## Open new Tab
while true
do
./tcr
done

I prefer “The Relaxed” with “The Split” as the implementation of ‘./tcr.’

In Pseudo-Code:

async {
while(true) {
Git.pull('rebase')
Git.push('origin', 'master')
}
}
function tcr() { ... }
async {
while(true) {
tcr()
}
}

The Storyteller

I’ve created a separate article as long as I am not sure, that this actually works. But feel to give it a look.

tl;dr Instead of just resetting the code, ‘The storyteller’ communicates back in a ‘human’ way so that it feels like a pair works with me remotely on the project.

Conclusion

Try all of them, play around. You will probably find a better version. Just make sure, you let me know.