git-gamble

Install the tool

Read about the usage

Watch the demo

asciicast

Theory

Alternatively : you can watch the slides about the theory

TDD

TDD (Test Driven Development) is cool!

It makes sure we develop the right thing, step by step

Short version

flowchart TB
    start([Start])
    ==> red([Write only 1 failing test])
    ==> green([Make all tests pass])
    ==> refactor([Refactor])
    ==> finish([Finish])

    refactor -.->|Incomplete ?| red

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

Long version

flowchart TB
    subgraph red[Red]
        direction LR

        fix_layout(( )) ==> write_a_test

        write_a_test[Write only 1 failing test]
        ==> run_tests{{Run tests}}

        run_tests
        -.->|Pass\nWrite another test| write_a_test
    end

    start([Start]) === fix_layout

    run_tests ==>|Fail| green([Green])

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

    classDef fix_layout stroke:white,fill:transparent;
    class fix_layout fix_layout
flowchart TB
    subgraph green[Green]
        direction LR

        fix_layout(( )) ==> write_the_minimum_code

        write_the_minimum_code[Write the minimum code\nto make all tests pass]
        ==> run_tests{{Run tests}}

        run_tests
        -.->|Fail\nTry something else| write_the_minimum_code
    end

    red([Red]) === fix_layout

    run_tests ==>|Pass| refactor([Refactor])

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

    classDef fix_layout stroke:white,fill:transparent;
    class fix_layout fix_layout
flowchart TB
    subgraph refactor[Refactor]
        direction LR

        rewrite_code[Rewrite code\nwithout changing the behavior]
        ==> run_tests{{Run tests}}
        -.->|Pass\nAnother things to refactor ?| rewrite_code

        run_tests
        -->|Fail\nChange something else| rewrite_code
    end

    green([Green]) ==> rewrite_code

    run_tests ==>|Pass| finish([Finish])
    run_tests -.->|Pass\nAnother feature to add ?| red([Red])

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

    classDef fix_layout stroke:white,fill:transparent;
    class fix_layout fix_layout

TCR

TCR (test && commit || revert) is cool!

It encourages doing baby steps, reducing the waste when we are wrong

flowchart TB
    gamble{{Gambling on test results}}

    gamble ==>|Pass| commit(Commit)

    gamble -->|Fail| revert(Revert)
flowchart TB
    start([Start])
    ==> green([Change some code])
    ==> finish([Finish])

    green -.->|Incomplete ?| green

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

But it doesn't allow us to see the tests failing

So:

  • Maybe we test nothing (assert forgotten)

    def test_should_be_Buzz_given_5():
        input = 5
        actual = fizz_buzz(input)
        # Oops! Assert has been forgotten
    
  • Maybe we are testing something that is not the thing we should be testing

    it("should be Fizz given 3", () => {
      const input = 3;
      const actual = fizzBuzz(input);
      expect(input).toBe("Fizz");
      // Oops! Asserts on the input value instead of the actual value
    });
    

Detailed view

flowchart TB
    subgraph green[Green]
        direction TB

        start([Start])
        ==> change_code[Change some code]
        ==> run_tests{{"Run tests\n<code>test && commit || revert</code>"}}
        ==>|Pass| commit(Commit)
        ==> finish([Finish])

        commit
        -->|Another things to change ?| change_code

        run_tests
        -.->|Fail| revert(Revert)
        -.->|Try something else| change_code
    end

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

TCRDD

TCRDD = TCR + TDD

TCRDD blends the constraints of the two methods to benefit from their advantages

Therefore, TCRDD makes sure we develop the right thing, step by step, and we are encouraged to do so by baby steps, reducing the waste when we are wrong
flowchart TB
    red{{Write only 1 failing test}}
    green{{Make all tests pass}}
    refactor{{Refactor}}

    red ==>|Fail| red_commit(Commit) ==> green
    green ==>|Pass| green_commit(Commit) ==> refactor
    refactor ==>|Pass| refactor_commit(Commit)

    red -->|Pass| red_revert(Revert) --> red
    green -->|Fail| green_revert(Revert) --> green
    refactor -->|Fail| refactor_revert(Revert) --> refactor

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase
flowchart TB
    subgraph red[Red]
        direction LR

        fix_layout(( )) ==> write_a_test

        write_a_test[Write only 1 test]
        ==> gamble[/Gamble that the test fail\n<code>git gamble --red</code>/]
        ==> run_tests{{Actually run tests}}
        ==>|Fail| commit(Commit)

        run_tests
        -->|Pass| revert(Revert)
        -->|Write another test| write_a_test
    end

    start([Start]) ==> fix_layout

    commit ==> green([Green])

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

    classDef fix_layout stroke:white,fill:transparent;
    class fix_layout fix_layout
flowchart TB
    subgraph green[Green]
        direction LR

        fix_layout(( )) ==> write_the_minimum_code

        write_the_minimum_code[Write the minimum code]
        ==> gamble[/Gamble that the tests pass\n<code>git gamble --green</code>/]
        ==> run_tests{{Actually run tests}}
        ==>|Pass| commit(Commit)

        run_tests
        -->|Fail| revert(Revert)
        -->|Try something else| write_the_minimum_code
    end

    red([Red]) ==> fix_layout

    commit ==> refactor([Refactor])

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

    classDef fix_layout stroke:white,fill:transparent;
    class fix_layout fix_layout
flowchart TB
    subgraph refactor[Refactor]
        direction LR

        rewrite_code[Rewrite code\nwithout changing the behavior]
        ==> gamble[/Gamble that the tests pass\n<code>git gamble --refactor</code>/]
        ==> run_tests{{Actually run tests}}
        ==>|Pass| commit(Commit)
        -.->|Another things to refactor ?| rewrite_code

        run_tests
        -->|Fail| revert(Revert)
        -->|Change something else| rewrite_code
    end

    green([Green]) ==> rewrite_code

    commit ==> finish([Finish])
    commit -.->|Another feature to add ?| red([Red])

    classDef red_phase font-weight:bold,color:black,fill:coral;
    class red red_phase

    classDef green_phase font-weight:bold,color:black,fill:#1cba1c;
    class green green_phase

    classDef refactor_phase font-weight:bold,color:black,fill:#668cff;
    class refactor refactor_phase

    classDef fix_layout stroke:white,fill:transparent;
    class fix_layout fix_layout

git-gamble is a tool that helps to use the TCRDD method

Similar projects

  • tcrdd by Xavier Detant
    • He introduced me to :
  • TCR by Murex

Reinvent the wheel

Why reinvent the wheel?

The script of Xavier Detant already works well

Because i would like to learn Rust Β―\_(ツ)_/Β―

Installation

There is several methods to install depending on your operating system

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Methods

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Nix / NixOS

Install Nix

Use without installing

export NIX_CONFIG="extra-experimental-features = flakes nix-command"
nix run gitlab:pinage404/git-gamble -- --help

To install it at

DirEnv

To automate the setup of the environment it's recommanded to install DirEnv

Add in .envrc

use nix

Then run this command

direnv allow

Cachix

If you want to avoid rebuilding from scratch, you can use Cachix

cachix use git-gamble

Update

export NIX_CONFIG="extra-experimental-features = flakes nix-command"
nix flake update

AppImage

AppImage available on GitLab

Requirements

git-gamble doesn't repackage git, it uses the one installed on your system

You have to install git manually

Make sure it is available in your $PATH, you can check it with this command

git --help

Install latest main version

  1. Download the latest main version

    curl --location "https://gitlab.com/pinage404/git-gamble/-/jobs/artifacts/main/raw/target%2Fgit-gamble-v-x86_64.AppImage?job=package%20AppImage" --output git-gamble-x86_64.AppImage
    
  2. Make it executable

    chmod +x git-gamble-x86_64.AppImage
    
  3. Put it your $PATH

    # for example
    mkdir -p ~/.local/bin
    ln git-gamble-x86_64.AppImage ~/.local/bin/git-gamble
    export PATH+=":~/.local/bin"
    

Install latest released version

  1. Download the latest released version

    curl --location "https://gitlab.com/api/v4/projects/pinage404%2Fgit-gamble/packages/generic/git-gamble-AppImage/2.7.0/git-gamble-v2.7.0-x86_64.AppImage" --output git-gamble-v2.7.0-x86_64.AppImage
    
  2. Make it executable

    chmod +x git-gamble-v2.7.0-x86_64.AppImage
    
  3. Put it your $PATH

    # for example
    mkdir -p ~/.local/bin
    ln git-gamble-v2.7.0-x86_64.AppImage ~/.local/bin/git-gamble
    export PATH+=":~/.local/bin"
    

Update

There is no update mechanism

Uninstall

Just remove the binary from your $PATH

Debian

Debian available on GitLab

Requirements

git-gamble doesn't repackage git, it uses the one installed on your system

You have to install git manually

Make sure it is available in your $PATH, you can check it with this command

git --help

Install

  1. Go to the package registry page

  2. Go to the latest version of git-gamble-debian

  3. Download the latest version git-gamble_2.7.0_amd64.deb

  4. Install package

    As root

    dpkg --install git-gamble*.deb
    

This is not really convenient but a better way will come when GitLab Debian Package Manager MVC will be available

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Update

There is no update mechanism

Uninstall

As root

dpkg --remove git-gamble

Mac OS X / Homebrew

Homebrew available on GitLab

Install Homebrew

brew tap pinage404/git-gamble https://gitlab.com/pinage404/git-gamble.git
brew install --HEAD git-gamble

Upgrade

git-gamble has not yet been packaged by Homebrew

To upgrade git-gamble, run this command

brew reinstall git-gamble

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Uninstall

brew uninstall git-gamble
brew untap pinage404/git-gamble

Windows / Chocolatey

Chocolatey available on GitLab

Requirements

git-gamble doesn't repackage git, it uses the one installed on your system

You have to install git manually

Make sure it is available in your $PATH, you can check it with this command

git --help

Install

Install Chocolatey

  1. Go to the package registry page

  2. Go to the latest version of git-gamble.portable

  3. Download the latest version git-gamble.portable.2.7.0.nupkg

  4. Install package

    Follow GitLab's documentation and Chocolatey's documentation

    choco install ./git-gamble.portable.2.7.0.nupkg
    

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Update

choco upgrade git-gamble

Uninstall

choco uninstall git-gamble

Cargo

Crate available on Crates.io

Requirements

git-gamble doesn't repackage git, it uses the one installed on your system

You have to install git manually

Make sure it is available in your $PATH, you can check it with this command

git --help

Install

Install Cargo

cargo install git-gamble

Add ~/.cargo/bin to your $PATH

Fish:

set --export --append PATH ~/.cargo/bin

Bash / ZSH:

export PATH=$PATH:~/.cargo/bin

Update

cargo update --package git-gamble

Uninstall

cargo uninstall git-gamble

Download the binary

Release

Requirements

git-gamble doesn't repackage git, it uses the one installed on your system

You have to install git manually

Make sure it is available in your $PATH, you can check it with this command

git --help

Install

Only for Linux and Windows x86_64

  1. Download the binary on the release page
  2. Put it in your $PATH

Update

There is no update mechanism

Uninstall

Just remove the binary from your $PATH

Check the installation

Check if all have been well settled

git gamble -version

If it has been well settled , it should output this :

git-gamble 2.7.0

If it has been badly settled , it should output this :

git : 'gamble' is not a git command. See 'git --help'.

Usage

How to use ?

To see all available flags and options

git-gamble --help # dash between `git` and `gamble` is only needed for --help

There are two ways to run your tests using git gamble :

In both cases, the test command must exit with a 0 status when there are 0 failed tests, anything else is considered as a failure

Environment variable

Setting an environment variable and run only the git command command

  1. Start by setting a environment variable with the test command :

    The example below is for running your tests for a Rust project

    export GAMBLE_TEST_COMMAND="cargo test"
    
  2. Write a failing test in your codebase, then :

    git gamble --red
    # or
    git gamble --fail
    
  3. Write the minimum code to make tests pass, then :

    git gamble --green
    # or
    git gamble --pass
    
  4. Refactor your code, then :

    git gamble --refactor
    # or
    git gamble --pass
    

Repeating the test command

Typing the git gamble command with your test command repetitively

  1. Write a failing test in your codebase, then :

    git gamble --red -- $YOUR_TEST_COMMAND
    
  2. Write the minimum code to make tests pass, then :

    git gamble --green -- $YOUR_TEST_COMMAND
    
  3. Refactor your code, then :

    git gamble --refactor -- $YOUR_TEST_COMMAND
    

Demo

For more detailed example, this the demo

asciicast

When to use it ?

When NOT to use it ?

  • You don't really want to try this methodology
    • You will eventually cheat and lose the benefits while keeping some of the drawbacks
  • You don't know what you want to do
  • Your tests :
    • ran very slowly
    • are flaky
    • are not self-validating
  • You want to use double-loop TDD
    • unless you don't run tests on bigger loops when you run smaller loops

Demo

git-gamble's demo to develop using TCRDD by doing baby steps

on a simple program

git-gamble works with all languages and tools and editors

for this example, i use python with pytest and nano

export GAMBLE_TEST_COMMAND='pytest --quiet'

note : for a simpler demo, test code and production code are in the same file

Alternatively : you can also watch the slides about the demo


First TDD loop ➰

Write a program that says Hello world

First, πŸ”΄ Red phase : write a test that fails for the good reason

Alternative text
nano test_hello.py
def hello():
    pass

def test_say_hello_world():
    assert hello() == 'Hello world'

Then, gamble that the tests fail

git gamble --red

βœ”οΈ Committed

Second, 🟒 Green phase : write the minimum code to pass the tests

Let's fake it

Alternative text
nano test_hello.py
def hello():
    return 'Hello word'

Then, gamble that the tests pass

git gamble --green

❌ Reverted

Oh no !

I made a typo

 def hello():
-    return 'Hello word'
+    return 'Hello world'

Try again

Let's fake it without typo

Alternative text
nano test_hello.py
def hello():
    return 'Hello world'

Gamble again that the tests pass

git gamble --green

βœ”οΈ Committed

Yeah !

Third, πŸ”€ Refactor phase : Nothing to refactor yet


Then πŸ” Repeat ➰

Second TDD loop ➿

Write a program that says Hello to the given name when a name is given

πŸ”΄ Red phase

Alternative text
nano test_hello.py
def test_say_hello_name_given_a_name():
    assert hello('Jon') == 'Hello Jon'
git gamble --red

βœ”οΈ Committed

🟒 Green phase

Add a simple condition to handle both cases

Alternative text
nano test_hello.py
def hello(arg=None):
    if arg:
        return f'Hello {arg}'
    return 'Hello world'
git gamble --green

βœ”οΈ Committed

πŸ”€ Refactor loop ➰

πŸ”€ Refactor phase

It can be simplified

Alternative text
nano test_hello.py
def hello(arg='world'):
    return f'Hello {arg}'
git gamble --refactor

βœ”οΈ Committed

Still πŸ”€ Refactor phase : i have something else to refactor ➿

Better naming

Alternative text
nano test_hello.py
def hello(name='world'):
    return f'Hello {name}'
git gamble --refactor

βœ”οΈ Committed


And so on... ➿

πŸ” Repeat until you have tested all the rules, are satisfied and enougth confident

Command Line Interface

git-gamble --help

Blend TCR (`test && commit || revert`) + TDD (Test Driven Development) to make sure to develop
the right thing 😌, baby step by baby step πŸ‘ΆπŸ¦Ά

Usage: git-gamble [OPTIONS] <--pass|--fail> -- <TEST_COMMAND>...
       git-gamble [OPTIONS] <COMMAND>

Commands:
  generate-shell-completions  
  help                        Print this message or the help of the given subcommand(s)

Arguments:
  <TEST_COMMAND>...  The command to execute to know the result [env: GAMBLE_TEST_COMMAND=]

Options:
  -g, --pass                               Gamble that tests should pass [aliases: green, refactor]
  -r, --fail                               Gamble that tests should fail [aliases: red]
  -n, --dry-run                            Do not make any changes
      --no-verify                          Do not run git hooks
  -C, --repository-path <REPOSITORY_PATH>  Repository path [default: .]
  -m, --message <MESSAGE>                  Commit's message [default: ]
  -e, --edit                               Open editor to edit commit's message
      --fixup <FIXUP>                      Fixes up commit
      --squash <SQUASH>                    Construct a commit message for use with `rebase --autosquash`
  -h, --help                               Print help
  -V, --version                            Print version

Any contributions (feedback, bug report, merge request ...) are welcome
https://pinage404.gitlab.io/git-gamble/contributing/index.html

Shell completions

To manually generate shell completions you can use this command

git gamble generate-shell-completions --help

Usage: git-gamble generate-shell-completions [SHELL]

Arguments:
  [SHELL]
          Put generated file here :
          * Fish https://fishshell.com/docs/current/completions.html#where-to-put-completions
          * Others shells ; Don't know, MR are welcome
          
          [possible values: bash, elvish, fish, powershell, zsh]

Options:
  -h, --help
          Print help (see a summary with '-h')

Usage examples

There is several examples with several languages in the nix-sandboxes repository

Refactoring session

Install watchexec, then :

watchexec -- git gamble --refactor

This is just TCR (without TDD)

Alias

Some examples of git alias

If you use Fish Shell, it works very well with the Oh My Fish plugin enlarge_your_git_alias

Hooks

git-gamble provides his own custom hooks :

  • pre-gamble <GAMBLED>
    • pre-gamble hook is executed with one argument <GAMBLED>
  • post-gamble <GAMBLED> <ACTUAL>
    • post-gamble hook is executed with two arguments <GAMBLED> and <ACTUAL>

Where :

  • <GAMBLED> is pass or fail
  • <ACTUAL> is pass or fail

Custom hooks of git-gamble are like any other client-side hooks :

  • a hook is a file
  • a hook must be in the $GIT_DIR/hooks/ folder
    • or in the folder configured by git config core.hooksPath
  • a hook must be executable (chmod +x .git/hooks/*-gamble)
  • will not be executed if any of these options are used:
    • --no-verify
    • --dry-run

Check the hooks' folder for examples of use

The following diagram shows when custom hooks are executed in relation to normal git hooks

flowchart LR
    subgraph git-gamble's hooks' lifecyle
        direction TB
        git-gamble([git-gamble\n--pass OR --fail])
        --> pre-gamble[pre-gamble\npass OR fail]:::gitGambleHookStyle
        --> GAMBLE_TEST_COMMAND([exec $GAMBLE_TEST_COMMAND]):::gitGambleInternalStyle
        --> gamble{Result ?}:::gitGambleInternalStyle

        gamble -->|Success| Success
        subgraph Success
            direction TB

            git_add([git add --all]):::gitGambleInternalStyle
            --> git_commit([git commit]):::gitGambleInternalStyle
            --> pre-commit:::gitHookStyle
            --> prepare-commit-msg[prepare-commit-msg\n$GIT_DIR/COMMIT_EDITMSG\nmessage]:::gitHookStyle
            --> commit-msg[commit-msg\n$GIT_DIR/COMMIT_EDITMSG]:::gitHookStyle
            --> post-commit:::gitHookStyle
            post-commit --> rewritten?
            rewritten?{{"Last commit rewritten ?\nWhen gambling fail\nafter another gamble fail"}}:::gitGambleInternalStyle
            rewritten? -->|Yes| post-rewrite[post-rewrite\namend]:::gitHookStyle --> post-gamble-success
            rewritten? -->|No| post-gamble-success
            post-gamble-success[post-gamble\npass OR fail\nsuccess]:::gitGambleHookStyle
        end

        gamble -->|Error| Error
        subgraph Error
            direction TB

            git_reset([git reset --hard]):::gitGambleInternalStyle
            --> post-gamble-error[post-gamble\npass OR fail\nerror]:::gitGambleHookStyle
        end
    end

    subgraph Legend
        direction TB

        subgraph Legend_[" "]
            direction LR

            command([Command executed by user])
            git-gamble_command([Command executed by git-gamble]):::gitGambleInternalStyle
            condition{Condition ?}:::gitGambleInternalStyle
        end

        subgraph Hooks
            direction LR

            hook[git's hook]:::gitHookStyle
            hook_with_argument[git's hook\nfirst argument\nsecond argument]:::gitHookStyle
            git-gamble_hook_with_argument[git-gamble's hook\nfirst argument\nsecond argument]:::gitGambleHookStyle
        end
    end

    classDef gitHookStyle fill:#f05133,color:black,stroke:black;
    classDef gitGambleHookStyle fill:#5a3730,color:white,stroke:white;
    classDef gitGambleInternalStyle fill:#411d16,color:white,stroke:white;

VSCode

If you want to run git-gamble from VSCode

Add this in your .vscode/tasks.json

{
	// See https://go.microsoft.com/fwlink/?LinkId=733558
	// for the documentation about the tasks.json format
	"version": "2.0.0",
	"tasks": [
		{
			"label": "Gamble fail",
			"type": "shell",
			"command": "direnv exec . git gamble --fail",
			"group": "test",
		},
		{
			"label": "Gamble pass",
			"type": "shell",
			"command": "direnv exec . git gamble --pass",
			"group": "test",
		},
		{
			"label": "Gamble red",
			"type": "shell",
			"command": "direnv exec . git gamble --red",
			"group": "test",
		},
		{
			"label": "Gamble green",
			"type": "shell",
			"command": "direnv exec . git gamble --green",
			"group": "test",
		},
		{
			"label": "Gamble refactor",
			"type": "shell",
			"command": "direnv exec . git gamble --refactor",
			"group": "test",
		},
	]
}

Give Love ❀️ nor Give Feedbacks πŸ—£οΈ

Do you like this project ?

Contributing

License ISC

Contributor Covenant

Any contributions (feedback, bug report, merge request ...) are welcome

Open an issue Open a merge request

Respect the code of conduct

Follow Keep a Changelog

Add to a package repository

Adding package to the X package repository where the X package repository is e.g. Nixpgks, Debian, Homebrew, Chocolatey ...

Feel free to do it, we don't plan to do it at the moment, it's too long to learn and understand how each of them works

If you do it, please file an issue or open an MR to update the documentation

Development

The following sections will help you to contribute with a merge request

dependency status coverage report CodeScene Code Health pipeline status AppVeyor for Homebrew status built with nix

The coverage report is low (coverage report with low percent) because there are mainly end-to-end tests that are not taken into account

Development Setup

Gitpod

You can contribute with just your web browser, directly from Gitpod

Open in Gitpod

Nix

The easiest way to get everything you need is to use Nix

This project is built with nix

  1. Install Nix

  2. Install DirEnv

  3. Let direnv automagically set up the environment by executing the following command in the project directory

    direnv allow
    

Troubleshooting

If you get an error like this one when entering in the folder

direnv: loading ~/Project/git-gamble/.envrc
direnv: using nix
direnv: using cached derivation
direnv: eval /home/pinage404/Project/git-gamble/.direnv/cache-.336020.2128d0aa28e
direnv: loading ./script/setup_variables.sh
   Compiling git-gamble v2.4.0-alpha.0 (/home/pinage404/Project/git-gamble)
direnv: ([/nix/store/19arfqh2anf3cxzy8zsiqp08xv6iq6nl-direnv-2.29.0/bin/direnv export fish]) is taking a while to execute. Use CTRL-C to give up.
error: linker `cc` not found
  |
  = note: No such file or directory (os error 2)

error: could not compile `git-gamble` due to previous error

Just run the following command to fix it

direnv reload

Help wanted to permanently fix this

Manual

Commands

Available commands

Or just execute

mask help

Debug

There are some logs in the programs, pretty_env_logger is used to display them

There are 5 levels of logging (from the lightest to the most verbose) :

  • error
  • warn
  • info
  • debug
  • trace

The git-gamble logs are "hidden" behind the with_log feature

The option --features with_log (or --all-features) must be added to each cargo command for which you want to see logs (e.g.) :

cargo build --all-features
cargo run --all-features
cargo test --all-features

Then, to display logs, add this environment variable :

export RUST_LOG="git_gamble=debug"

To display really everything :

export RUST_LOG="trace"

There are other possibilities for logging in the env_logger documentation

You can also uncomment variables in the setup script

There is also a command that executes a test with all the outputs displayed

mask test rust debug <the test to filter>

Exemple

mask test rust debug commit_when_tests_fail

Check that the code is properly tested

To verify that the code is actually tested by the tests, run the following command :

mask test mutants

Refactoring session

At the beginning of your session, just run the following command :

cargo watch -- git gamble --refactor

This is just TCR (without TDD)

Deployment

Just run cargo release (in a wide terminal, in order to have --help not truncated in the usage section)

cargo release

Architectural Decisions Records

The following pages records architectural decisions

Binaries organisation

Story

We (a colleague [referred to as "he" in the following text] and pinage404) used git-gamble at work

He was not used to using TDD nor TCR

The fear of losing code led to an anti-pattern : before gambling, he took a lot of time to read the code carefully, compile it and execute it in his head, which slowed down the group

This strong methodology should lead you to let yourself be carried along by the tools

He recommended limiting the duration of iterations


git-gamble has been used by several groups and pinage404 has seen this anti-pattern several times

Context and Problem Statement

To solve the problem of iteration duration, another tool has been created since 2022-07-11, but it is neither documented nor easily distributed

The first version of git-time-keeper was written in Bash, and works most of the time, in order to have a more stable experience, it will be rewritten in Rust

git-gamble and git-time-keeper are tools that work independently and can be used together for an optimal experience

How to create, maintain and distribute several tools ?

Decision Drivers

  • from the maintainer's point of view
    • easy to set up
    • easy to maintain
    • easy to distribute
    • avoid duplication of configuration and utilities
  • from the user's point of view
    • easy to install
    • easy to use
    • easy to understand that each tool can be used separately
    • easy to use tools together for an optimal experience

Considered Options

  • Repos : several independent repositories
  • Workspaces : 1 repository with several Cargo workspaces
  • Binaries : 1 repository with 1 crate containing several binaries

Decision Outcome

Chosen option: "Binaries", because this solution seems to have the fewest downsides, see the table below

Pros and Cons of the Options

ReposWorkspacesBinaries
Maintainer
easy to set upGood, easiest, just git cloneBad, need a little of workNeutral, Good if every tools support it
easy to maintainBad, need several maitainanceGood, that's what workspaces are forNeutral, easy but risk of confusion between what belongs to which tool
easy to distributeBad, need to resetup external platformsNeutral if every tools support itNeutral, Good if every tools support it
avoid duplicationBadNeutral, can have a shared CrateGood
User
easy to installBad, need several installationsNeutralNeutral, Good if every tools support it
easy to useNeutralNeutralNeutral
understand tools are independentGoodNeutralNeutral
easy to use tools togetherBadNeutralNeutral
TotalGood 2 Neutral 1 Bad 5 = -3Good 1 Neutral 6 Bad 1 = 0Good 1 Neutral 7 Bad 0 = +1

Not yet published - Work In Progress

The following pages document work in progress that has not yet been published

All or part of it is subject to change without notice

git-time-keeper

pipeline status coverage report AppVeyor for Homebrew status built with nix

Contributor Covenant GitLab stars

git-time-keeper is a tool that helps to take baby steps πŸ‘ΆπŸ¦Ά

Setup

With git-gamble

HOOKS_PATH=$(git rev-parse --git-path hooks)
echo "git-time-keeper 'stop'" >>"$HOOKS_PATH/pre-gamble"
echo "git-time-keeper 'start' 'git-gamble --pass'" >>"$HOOKS_PATH/post-gamble"
chmod +x "$HOOKS_PATH"/{pre,post}-gamble

With git

Without git-gamble

HOOKS_PATH=$(git rev-parse --git-path hooks)
echo "git-time-keeper 'stop'" >>"$HOOKS_PATH/pre-commit"
echo "git-time-keeper 'start'" >>"$HOOKS_PATH/post-commit"
chmod +x "$HOOKS_PATH"/{pre,post}-commit

Set iteration duration

export TIME_KEEPER_MAXIMUM_ITERATION_DURATION=$((3 * 60)) # 3 minutes

How to use ?

When being on time

sequenceDiagram
    actor Dev
    participant git
    participant hooks
    participant git-time-keeper

    Dev->>git: git commit
    git->>hooks: git hook run pre-commit
    hooks-xgit-time-keeper: git time-keeper stop
    git->>hooks: git hook run post-commit
    hooks-)+git-time-keeper: git time-keeper start

    Dev->>git: git commit
    git->>hooks: git hook run pre-commit
    hooks-)git-time-keeper: git time-keeper stop
    git-time-keeper-->-hooks: timer stopped
    git->>hooks: git hook run post-commit
    hooks-xgit-time-keeper: git time-keeper start

When the countdown is missed

sequenceDiagram
    actor Dev
    participant git
    participant hooks
    participant git-time-keeper

    Dev->>git: git commit
    git->>hooks: git hook run pre-commit
    hooks-xgit-time-keeper: git time-keeper stop
    git->>hooks: git hook run post-commit
    hooks-)+git-time-keeper: git time-keeper start
    git-time-keeper-)git-time-keeper: time limit passed
    git-time-keeper-)-git: git restore .

To see all available flags and options

git-time-keeper --help # dash between `git` and `time-keeper` is only needed for --help

Usage

git-time-keeper --help

Usage: git-time-keeper [OPTIONS]

Options:
  -h, --help                               Print help
  -V, --version                            Print version

Any contributions (feedback, bug report, merge request ...) are welcome
https://gitlab.com/pinage404/git-gamble

Possible states

stateDiagram
    state "Timer is running" as running
    state "git restore ." as clean

    [*] --> running : start
    running --> [*] : stop
    running --> clean : time limit passed
    clean --> [*]

    running --> running : start
    [*] --> [*] : stop

Limitation

Unix compatible only (Linux / Mac OS X) : need sh and kill

Remaining questions

do it need a kind of loop ?

with a recursive program ?

git time-keeper
    git time-keeper start
        sleep
        exec test
        git timer-keeper start ?
    git time-keeper stop
git time-keeper start
    git time-keeper daemon
        while true
            sleep
            exec test
    git time-keeper stop
        kill l'autre ?

Slides why

You can watch the slides about why baby steps are important

Backlog

  • when revert -> git clean #3
  • git workspace support
    • git update-ref should contain an unique identifier to the workspace
      • branche name ?
      • folder path ?
  • gamble hooks
    • branch based developement
      • git commit --fixup
      • git rebase --autosquash
    • optional hook to revert if not gambled in a delay
      • git-time-keeper
        • document
        • package
        • distribute
  • like git, flags & options & arguments should be retrieved from CLI or environment variable or config's file
    • re-use git config to store in file ?
    • repository level config using direnv and environment variable ?
  • stash instead of revert ?
  • shell completion
    • in the package ?
      • Cargo
      • AppImage
      • Chocolatey
    • for git gamble not only git-gamble
  • https://docs.gitlab.com/ee/ci/runners/saas/macos_saas_runner.html
  • https://gitlab.com/gitlab-org/security-products/analyzers
  • https://gitlab.com/gitlab-org/security-products/ci-templates
  • https://medium.com/@tdeniffel/tcr-variants-test-commit-revert-bf6bd84b17d3
  • https://svgfilters.com/
  • https://github.com/sourcefrog/cargo-mutants
  • https://github.com/llogiq/mutagen

Distribution / publishing backlog

CheckList

  • package in local
  • package in CI
  • upload
  • make public available
  • complete how to install
  • manual test
  • update backlog

Technical improvement opportunities