[go: up one dir, main page]

git-gamble
Craft Your Code,
Step by Step

Do all tests pass ?

def hello():
    return 'Hello word'

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

Are you ready to gamble ?

You did :

git-gamble --pass

Your gamble was good :

Committed

You did :

git-gamble --fail

But there was a typo :

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

Your gamble was wrong :

Reverted

git-gamble

Watch the demo

Go to the demo

Community

Loved by community

Starred on GitLab

Built by community

GitLab Contributors

Ready to transform your development process ?

Get started with git-gamble !

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 Write 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 to make all tests pass]
        ==> run_tests{{Run tests}}

        run_tests
        -.->|Fail Try 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 without changing the behavior]
        ==> run_tests{{Run tests}}
        -.->|Pass Another things to refactor ?| rewrite_code

        run_tests
        -->|Fail Change something else| rewrite_code
    end

    green([Green]) ==> rewrite_code

    run_tests ==>|Pass| finish([Finish])
    run_tests -.->|Pass 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

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 <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 ~~~ red_commit
    green ~~~ green_commit
    refactor ~~~ refactor_commit

    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

    red_revert ~~~ green
    green_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 <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 <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 without changing the behavior]
        ==> gamble[/Gamble that the tests pass <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

History of TCRDD

timeline
    title History of TCRDD
    2018
        : TCR                  `test && commit || revert`       Article                  by Kent Beck 2018-09-28
        : TCRDD          _TDD is dead, long live TCR?_ Article                  by Xavier Detant 2018-12-03
    2019
        : Xavier's TCRDD First commit 2019-08-26
        : git-gamble        First commit 2019-12-07
    2021 : Murex's TCR  First commit 2021-06-16

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

The package is available on these repositories, and can be installed as usual if you use one of them

Packaging status

There is several methods to install depending on your operating system:

Installation methodLinuxmacOSWindows
Nixโœ…โœ…โœ…
Debianโœ…โŒโŒ
Homebrewโœ…โœ…โŒ
ChocolateyโŒโŒโœ…
Cargoโœ…โœ…โœ…
Download the binaryโœ…โŒโœ…

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Install using Nix

Available on nixpkgs

Packaged for Nixpkgs

Nix is a package manager available for Linux, macOS and Windows (through WSL2)

Requirements

  1. Install Nix

  2. Check the installation with this command

    nix --version
    

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

    nix (Nix) 2.22.3
    

    Else if it has been badly settled, it should output something like this :

    nix: command not found
    

Install from Nixpkgs

The package is available on nixpkgs

nix-shell --packages git-gamble

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.11.0

Else if it has been badly settled , it should output this :

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

Install from source

Note: the latest version, not yet released, is also available

Use without installing

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

To install it at project level with flake.nix, see example

export NIX_CONFIG="extra-experimental-features = flakes nix-command"
nix flake init --template gitlab:pinage404/git-gamble

DirEnv

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

Then run this command

direnv allow

Cachix

To avoid rebuilding from scratch, Cachix can be used

cachix use git-gamble

Update

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

Install on Debian

Debian available on GitLab

It should work on any Debian based distributions : Linux Mint, Ubuntu...

Requirements

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

  1. Install git manually

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

    git --version
    

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

    git version 2.45.2
    

    Else if it has been badly settled, it should output something like this :

    git: command not found
    

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.11.0_amd64.deb

  4. Install package

    As root

    dpkg --install git-gamble*.deb
    

Note: the latest version, not yet released, is also available

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

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.11.0

Else if it has been badly settled , it should output this :

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

Update

There is no update mechanism

Uninstall

As root

dpkg --remove git-gamble

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Install using Homebrew on macOS

Homebrew available on GitLab

Requirements

  1. Install Homebrew

  2. Check the installation with this command

    brew --version
    

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

    Homebrew 4.3.14
    Homebrew/homebrew-core (git revision 7a574d89134; last commit 2024-08-07)
    

    Else if it has been badly settled, it should output something like this :

    brew: command not found
    

Install

Run these commands

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

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.11.0

Else if it has been badly settled , it should output this :

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

Upgrade

git-gamble has not yet been packaged by Homebrew

To upgrade git-gamble, run this command

brew reinstall git-gamble

Uninstall

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

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Homebrew requires repositories to have at least 75 stars and 30 forks

So, please add a star on GitLab and create a fork for any contribution

Install using Chocolatey on Windows

Chocolatey available on GitLab

Requirements

Chocolatey

  1. Install Chocolatey

  2. Check the installation with this command

    choco --version
    

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

    2.3.0
    

    Else if it has been badly settled, it should output something like this :

    choco: command not found
    

Git

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

  1. Install git manually

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

    git --version
    

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

    git version 2.45.2
    

    Else if it has been badly settled, it should output something like this :

    git: command not found
    

Install

Note: If you installed Chocolatey from an administrative shell, make sure to launch all choco commands from an administrative shell as well.

Check the version of Chocolatey installed using this command

choco --version
  • Chocolatey v2.x

    choco source add --name=git-gamble --source="https://gitlab.com/api/v4/projects/15761766/packages/nuget/index.json"
    choco install git-gamble.portable
    
  • Chocolatey v1.x

    Use the Nuget v2 source :

    choco source add --name=git-gamble --source="https://gitlab.com/api/v4/projects/15761766/packages/nuget/v2"
    choco install git-gamble.portable --version=2.11.0
    

    The Gitlab registry doesn't seem to fully support package discovery on the v2 source, so the version needs to be specified explicitly. This issue might address this.

Note: the latest version, not yet released, is also available

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.11.0

Else if it has been badly settled , it should output this :

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

Update

choco upgrade git-gamble.portable

With Chocolatey v1.x and/or the Nuget v2 source :

choco upgrade git-gamble.portable --version=2.11.0

Uninstall

choco uninstall git-gamble.portable

You might also want to remove the source :

choco source remove --name=git-gamble

Improving installation

Installation is currently not really convenient, so contributions are welcome

Feel free to add package to your favorite package repository

Install using Cargo

Crate available on Crates.io

Requirements

Cargo

  1. Install Cargo

  2. Check the installation with this command

    choco --version
    

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

    cargo 1.80.0 (376290515 2024-07-16)
    

    Else if it has been badly settled, it should output something like this :

    cargo: command not found
    

Git

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

  1. Install git manually

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

    git --version
    

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

    git version 2.45.2
    

    Else if it has been badly settled, it should output something like this :

    git: command not found
    

Install

Run the following command

cargo install git-gamble

Add ~/.cargo/bin to your $PATH

Fish:

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

Bash / ZSH:

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

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.11.0

Else if it has been badly settled , it should output this :

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

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

  1. Install git manually

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

    git --version
    

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

    git version 2.45.2
    

    Else if it has been badly settled, it should output something like this :

    git: command not found
    

Install

Only for Linux and Windows x86_64

  1. Download the binary on the release page
  2. Rename it to git-gamble
  3. Put it in your $PATH

Note: the latest version, not yet released, is also available

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.11.0

Else if it has been badly settled , it should output this :

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

Update

There is no update mechanism

Uninstall

Just remove the binary from your $PATH

Usage

git-gamble works with all languages and tools and editors

How to use ?

To see all available flags and options

git gamble --help

Dash - between git and gamble may be only needed for --help

git-gamble --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 gamble 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"
    

    Use the appropriate command for your shell :

  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
    

DirEnv

To avoid re-exporting manually the variable at each new terminal, it's recommanded to install DirEnv

Then add in a .envrc file

export GAMBLE_TEST_COMMAND="cargo test"

Then run this command

direnv allow

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
    

    The example below is for running your tests for a NodeJS project that use pnpm

    git gamble --red -- pnpm test
    
  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

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 editor ; this example uses 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 TDD (Test Driven Development) + TCR (`test && commit || revert`) 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://git-gamble.is-cool.dev/contributing/index.html

Shell completions

To manually generate shell completions, use this command :

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

Arguments:
  [SHELL]
          Put generated file here :
          * Bash : add it to your bash profile in ~/.bashrc
          * Fish : see https://fishshell.com/docs/current/completions.html#where-to-put-completions
          * Powershell : see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles
          * 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

In each language folder, there is a .envrc file, which contains the variable GAMBLE_TEST_COMMAND, that mainly refers to mask commands that are described in the sibling file maskfile.md

(Links above links to the Rust sandbox just for example)

These are only examples, the tool should work with any languages and tools

Refactoring session

Install watchexec, then :

watchexec -- git gamble --refactor

This is just TCR (without TDD)

Git Aliases

Some examples of git alias

~/.gitconfig or .git/config

[alias]
    # git-gamble aliases
    fail = gamble --fail
    pass = gamble --pass
    faile = gamble --fail --edit
    passe = gamble --pass --edit

    # git-gamble TDD's aliases
    red = gamble --red
    green = gamble --green
    refactor = gamble --refactor
    rede = gamble --red --edit

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

Hooks

git-gamble provides his own custom git hooks to enhance the development workflow

These hooks can be used to perform specific actions before and after gambling

Before Gambling (pre-gamble)

The pre-gamble hook is executed before the actual gambling process begins

Sometimes, you want to have certain things imperatively done before gambling on the tests

What if something irrelevant was just missing and that forgetting this was not worth starting again ?

Examples :

  • Formatting
  • Linting

After Gambling (post-gamble)

The post-gamble hook is triggered after the gambling result is known

Sometimes, you want to do actions after the gamble only in some situations

Examples :

  • Wrong gamble (pass fail or fail pass)
    • Suggest to take a break
  • Good gamble (pass pass or fail fail)
    • Congratulate
  • Failing tests (fail fail)
    • Remember to not push on the main branch
  • Passing tests (pass pass)
    • Suggest to push
    • Push automatically

Hook Execution and Configuration

Custom hooks are called with arguments :

  • 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 git hooks :

  • a hook is a file (pre-gamble or post-gamble)

  • 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

    • to make it executable, execute the following command

       chmod +x .git/hooks/*-gamble
      
  • will not be executed if any of these options are used :

    • --no-verify
    • --dry-run

Examples

Check the hooks' folder for examples of use

Hooks Execution Lifecycle

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

flowchart LR
    subgraph git-gamble's hooks' lifecyle
        direction TB
        git-gamble([git-gamble --pass OR --fail])
        --> pre-gamble[pre-gamble pass OR fail]:::gitGambleHookStyle
        --> git_add([git add --all]):::gitGambleInternalStyle
        --> GAMBLE_TEST_COMMAND([exec $GAMBLE_TEST_COMMAND]):::gitGambleInternalStyle
        --> gamble{Gamble result ?}:::gitGambleInternalStyle

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

            git_commit([git commit]):::gitGambleInternalStyle
            --> pre-commit:::gitHookStyle
            --> prepare-commit-msg[prepare-commit-msg $GIT_DIR/COMMIT_EDITMSG message]:::gitHookStyle
            --> commit-msg[commit-msg $GIT_DIR/COMMIT_EDITMSG]:::gitHookStyle
            --> post-commit:::gitHookStyle
            post-commit --> rewritten?
            rewritten?{{"Last commit rewritten ? When gambling fail after another gamble fail"}}:::gitGambleInternalStyle
            rewritten? -->|Yes| post-rewrite[post-rewrite amend]:::gitHookStyle --> post-gamble-success
            rewritten? -->|No| post-gamble-success
            post-gamble-success[post-gamble pass OR fail success]:::gitGambleHookStyle
        end

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

            git_reset([git reset --hard]):::gitGambleInternalStyle
            --> post-gamble-error[post-gamble pass OR fail error]:::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 first argument second argument]:::gitHookStyle
            git-gamble_hook_with_argument[git-gamble's hook first argument second 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

To run git-gamble from VSCode

Setup

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",
		},
	]
}

Remove direnv exec . if you don't use DirEnv yet

Execute

Using command palette

  1. Open the Command Palette (F1 or CTRL + Shift + P)
  2. Select Tasks: Run Task
  3. Select the task (e.g. Gamble refactor)

Using shortcuts

You can bind keyboard shortcuts

For example

[
    {
        "key": "ctrl+g ctrl+f",
        "command": "workbench.action.tasks.runTask",
        "args": "Gamble fail"
    },
    {
        "key": "ctrl+g ctrl+p",
        "command": "workbench.action.tasks.runTask",
        "args": "Gamble pass"
    },
]

Using buttons in the sidebar

Task Explorer extension can be installed to trigger using click

Frequently Asked Questions

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 achieve
  • Your tests :
    • ran very slowly
      • maybe a subset of the tests can be run to accelerate
    • 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

Does it commit failing tests ?

TL;DR history will contains commits with only passing tests (with a nominal usage)

Long story : Yes and no

Yes, it does a commit at every steps, including when tests are failing

And no, because it will amend the previous commit when :

  • gambling several times that the test fails
  • gambling that the pass after a gamble that the test fails

Commits with failing tests are temporary and should not ends up in the history

Example

Assuming every gambles were the good one, so everything is committed at each command

Doing the following commands :

git gamble --red      --message "first iteration red"
git gamble --red      --message "first iteration red edited" # improve the test
git gamble --green    --message "first iteration green"
git gamble --red      --message "second iteration red"
git gamble --green    --message "second iteration green"
git gamble --refactor --message "second iteration refactor"

Will end with this git history :

gitGraph TB:
    commit id:"initial commit"
    commit id:"first iteration green"
    commit id:"second iteration green"
    commit id:"second iteration refactor"

The full history will look like this :

---
config:
    theme: 'base'
---
gitGraph TB:
    commit id:"initial commit"
    branch GC_1
    commit id:"first iteration red" type:REVERSE
    switch main
    branch GC_2
    commit id:"first iteration red edited" type:REVERSE
    switch main
    commit id:"first iteration green"
    branch GC_3
    commit id:"second iteration red" type:REVERSE
    switch main
    commit id:"second iteration green"
    commit id:"second iteration refactor"

Note : the GC_* branches are just a representation of dangling commits that will eventually be deleted by the garbage collector

The garbage collector is an automatic mecanism in git, nothing special have to be done

What is the default commit message ?

By default, the commit message is empty

Does git allow empty commit message ?

Yes, not by default but given --allow-empty-message

This is an uncommon use case

Why having an empty commit message ?

The TCRDD methodology encourages to do baby steps

When doing so, the commit are so small that almost every times, the only possible message is describing what has been changed which a kind of repetition with the commit's content

How to avoid empty commit message ?

Give an optional message when gambling

When gambling :

  • add a message with the option --message (or -m) followed by the message

    git gamble --red --message "This is a great message"
    
  • open the default editor to write a message with the option --edit

    git gamble --red --edit
    

It may be a good idea to add a message when enabling a feature flag

Rearrange commits after

Add the end of iterations, the history can be rewritten using git rebase, commits can even be squashed

Feel stuck ?

What if the new test is too complex to pass with just one gamble ?

So it's often a good idea to :

  1. Take a break โธ๏ธ

  2. Slow down โณ

  3. Rollback the failing commit โช

    Keep a branch if needed

    git branch keep_it_for_maybe_later
    

    Then actually rollback

    git reset --hard @~
    
  4. Refactor to "make the change easy" ๐Ÿง ๐Ÿ’ก

    for each desired change, make the change easy (warning: this may be hard), then make the easy change

    -- Kent Beck 2012

  5. Try to do smaller steps ๐Ÿ‘ถ๐Ÿฆถ

  6. If needed, try to reapply the failing commit โฉ

    git cherry-pick keep_it_for_maybe_later --no-commit && git gamble --red
    

    It may be in conflict depending on the changes made in previous steps โš ๏ธ

What languages are supported ?

git-gamble should work with all languages

It just need a test command that must :

  • exit with a 0 status when there are 0 failed tests
  • anything else is considered as a failure

It is independant from tools and editors

It could be integrated with tools and editors

To see real examples, read the usage examples

Give Love โค๏ธ or Give Feedbacks ๐Ÿ—ฃ๏ธ

Do you like this project ?

Contributing

License ISC

Contributor Covenant

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

GitLab Contributors Open an issue Open a merge request Forks

No question is too simple

Respect the code of conduct

Follow Keep a Changelog

Add to a package repository

The package is available on these repositories, and can be installed as usual if you use one of them

Packaging status

If the package is missing or not up to date in the repository that you prefer to use you can add it to the X package repository where the X package repository is e.g. 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 Gitlab Pipeline Status AppVeyor for Homebrew status built with nix

Development Setup

Gitpod

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

Open in Gitpod

Nix

The easiest way to get everything needed is to use Nix

This project is built with nix

  1. Install Nix

  2. Install DirEnv

  3. Get the source

    git clone https://gitlab.com/pinage404/git-gamble
    
  4. Switch to the directory

    cd ./git-gamble
    

    Note : if you only want to edit the documentation or slides, you can directly change the working directory to it, this will avoid downloading dependencies that you don't need yet in the next step

    cd ./docs
    # or
    # cd ./slides
    
  5. Let DirEnv automagically set up the environment by executing the following command in the project directory

    This will setup all required dependencies

    direnv allow
    

Cachix

To avoid rebuilding from scratch, Cachix can be used

cachix use git-gamble

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

Please reconsider using Nix because of the following advantages:

  • Ensure that the exact same versions will be installed, the versions that are known to work
  • Ensure the packages are installed in a way that works
  • All packages are installed, not only the rust toolchain, allowing you to work on any part

If you still want to avoid Nix, then follow these instructions:

  1. Install Rustup

  2. Start a new terminal to load the new configuration

  3. Clone and navigate to git-gamble

    git clone https://gitlab.com/pinage404/git-gamble.git
    cd git-gamble
    
  4. Install project-specific Rust toolchain and build the project

    Running a build for the first time will install the toolchain from rust-toolchain.toml:

    cargo build
    
  5. Configure the environment

    DirEnv is the recommended tool to setup the environment. If you don't want to use it, then install and configure everything that should normally be installed by .envrc

Next, read the other files in the development section.

Commands

To list all available commands execute :

mask help

What is mask ?

Most of the people use make to create users-friendly alias of complexes commands : a task runner

The make syntax is complicated because make is not only a task runner but a whole build system (and because make is old)


mask is an awesome task runner

mask is made to execute complexes commands using the markdown syntax

So even if the people don't understand that they could executes them using the mask command, they have a readable documentation of all the available commands because markdown could be rendered in a pretty readable format

Available commands

The list below is a copy of maskfile.md

test

set -o errexit -o nounset -o pipefail -o errtrace

$MASK test shell
$MASK test rust

test shell

set -o errexit -o nounset -o pipefail -o errtrace

./tests/test_scripts.sh
./script/tests/test_generate_completion.sh

test rust

cargo test --features with_log

test rust with_coverage

cargo tarpaulin --engine Llvm  --locked --out Xml --features with_log

test rust debug (FILTERS)

export RUST_LOG="trace"
export RUST_TRACE="full"
# export RUST_BACKTRACE="full"
export RUST_TEST_NOCAPTURE="display stdout and stderr"

cargo test \
    --features with_log \
    -- \
    "$FILTERS"

test watch

bacon test --features with_log

test mutants

cargo mutants

lint

set -o errexit -o nounset -o pipefail -o errtrace

cargo clippy --all-targets --all-features
cargo check --all-targets --all-features
if command -v masklint >/dev/null; then
    masklint run
fi

format

set -o errexit -o nounset -o pipefail -o errtrace

if command -v nix >/dev/null; then
    nix fmt ./**.nix
fi
cargo fix --all-features --allow-dirty --allow-staged
cargo fmt

docs

docs serve

pushd ./docs/
direnv exec . \
    mask docs serve

slides

slides serve (SLIDE_FILE)

pushd ./slides/
direnv exec . \
    mask slides serve $SLIDE_FILE

update

set -o errexit -o nounset -o pipefail -o errtrace

nix flake update
direnv exec . \
    cargo update
pushd ./slides
direnv exec . \
    corepack use pnpm@latest
direnv exec . \
    pnpm update --latest

This folder has been setup from the nix-sandboxes's template

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 the session, just run the following command :

watchexec -- 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

They use the Markdown Architectural Decision Records's template

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 or 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 or 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

Pipeline single source of truth

Context and Problem Statement

Same tools, different versions

There is no single source of truth between local development and the pipeline

Tools are defined in different files

The versions of the tools used are not the same, depending on the package manager used to install the tools

Big container images

Debian-based container images are big

Past attempts have been failures to make working build with Alpine based container, because Alpine use Musl instead of GLibC

Example

The image created from ci/rust_with_git.Dockerfile

FROM rust:1.80-slim-bookworm

RUN apt-get --quiet=2 update \
    && apt-get --quiet=2 install --no-install-recommends \
    git \
    && apt-get --quiet=2 clean \
    && rm --force --recursive /var/lib/apt/lists/*

Using the following commands

docker build -f ./ci/rust_with_git.Dockerfile ./ci/
dive sha256:443b8185bfb389687697e4193b569feb40a3e0b5a2b023562f8da279516f5d83
  • Total Image size: 854MB
  • Potential wasted space: 4.7 MB
  • Image efficiency score: 99 %

Bad update process

Update is done manually in each Dockerfile, in the FROM ; which is not convenient

Decision Drivers

  • Open source
  • Price : must be free or very low
  • High reproducibility
  • Ability to use cache (Nix's cache or GitLab's cache)
  • Lighter
  • Faster
  • Use of standard tools

Considered Options

Decision Outcome

Replace image building with Kaniko in the pipeline with image building with Nix

Try using GitLab's Cache or Cachix using Cynerd's gitlab-ci-nix example (the tool or the idea made by the tool) to save Nix Store.

Consequences

  • Good, single source of truth for the versions used (which ends up to flake.lock)

  • Good, update process is already handled automatically

    Just execute

    nix flake update
    
  • Good, use open source tools

    • Maybe not the Cachix server which will maybe used
  • Good, free

  • Good, high reproducibility

  • Good, cache may be used

  • Bad, the images are twice bigger

    • It could probably be optimized
  • Bad, expect to be slower, because of the image size

    • On self hosted runner, it will use Docker's cache
  • Good, don't use a niche tool

Confirmation

Pros and Cons of the Options

NixCI

NixCI

  • Source not found
    • Not open source ? โŒ
  • Only one dev behind it ?
    • What about the maintainability ? โš ๏ธ
  • The page talks about GitLab integration
    • There is no call to action for GitLab
      • Don't know how to use it with GitLab ๐Ÿคท
  • Is it SaaS or Self Hosted ?
    • There are machines that will compute stuff
      • Who pays ? Pricing is unclear ๐Ÿ’ธ
    • The page talks about self-hostable
      • Don't found the documentation ๐Ÿคท
  • Need scaring authorization on GitHub : Act on your behalf ๐Ÿคจ

The page looks more like a roadmap than the documentation of actual functionalities

Doesn't look trustworthy

GitHub Actions

Need to move to GitHub

GitHub is not Open Source โŒ

Garnix

Garnix

Pricing have a free tier (1 500 minutes/month) that may be enougth ๐Ÿ’ถ

Cachix

Cachix

Already used in the pipeline ๐Ÿ‘

Jetify Cache

Jetify Cache

  • Pricing seems to start from $5/month + $0.60 Go/month ๐Ÿ’ฐ
  • Don't found the sources โŒ

Export Nix Store to GitLab CI's Cache

As the time pass, the cache will grow, it will slow the pipeline ๐Ÿ“ˆ

  • Every cache paths will be downloaded and reuploaded at each pipeline, not only the ones that are actually needed for the current job
  • How to do clean unecessary paths ? ๐Ÿšฎ
    • Should an extra step with nix-gc be added ?
    • Without erasing everything periodically ?
๐Ÿ†šVonfry's versionCynerd's versionTECHNOFAB's version
ComplexitySimpler โœ…More complex ๐Ÿ‘ŽEven more complex ๐Ÿ˜ต
CacheNaive โŒOptimized โœ…Optimized โœ…
ReusabilityJust an example โŒMade for โœ…Made for โœ…
ReproductibilityNot versionned ๐Ÿ‘ŽNot versionned ๐Ÿ‘ŽHave releases โœ…

Simple direnv allow

Use container from nixos/nix and run the following command inside

direnv allow

Pro : simple โœ…

Const : don't use cache, so download everything from scratch at each jobs โŒ

Install Nix inside GitLab's job

Won't use GitLab's cache โŒ

  1. Download a base image
  2. Install Nix inside GitLab's job
  3. Install packages using Nix

Seems to be heavy

Build image using Nix

dockerTools.buildImage

dockerTools.buildImage Proof of concept
{
  lib,
  pkgs,
}:

let
  rustupToolchainFile = lib.snowfall.fs.get-file "rust-toolchain.toml";
  rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile rustupToolchainFile;
in
pkgs.dockerTools.buildImage {
  name = "build-image";
  copyToRoot = [
    rust-toolchain
    pkgs.dockerTools.binSh
  ];
}
nix build '.#build-image'
dive --source docker-archive <(gunzip -c ./result)
  • Total Image size: 1.9 GB
  • Potential wasted space: 0 B
  • Image efficiency score: 100 %

dockerTools.buildLayeredImage

dockerTools.buildLayeredImage Proof of concept
{
  lib,
  pkgs,
}:

let
  rustupToolchainFile = lib.snowfall.fs.get-file "rust-toolchain.toml";
  rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile rustupToolchainFile;
in
pkgs.dockerTools.buildLayeredImage {
  name = "build-layered-image";
  contents = [
    rust-toolchain
    pkgs.dockerTools.binSh
  ];
}
nix build '.#build-layered-image'
dive --source docker-archive <(gunzip -c ./result)
  • Total Image size: 1.7 GB
  • Potential wasted space: 0 B
  • Image efficiency score: 100 %
dockerTools.buildLayeredImage Proof of concept limiting max layers

Same when trying to limit max layers

{
  lib,
  pkgs,
}:

let
  rustupToolchainFile = lib.snowfall.fs.get-file "rust-toolchain.toml";
  rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile rustupToolchainFile;
in
pkgs.dockerTools.buildLayeredImage {
  name = "build-layered-image-limit-layers";
  contents = [
    rust-toolchain
    pkgs.dockerTools.binSh
  ];
  maxLayers = 2;
}
nix build '.#build-layered-image-limit-layers'
dive --source docker-archive <(gunzip -c ./result)
  • Total Image size: 1.7 GB
  • Potential wasted space: 0 B
  • Image efficiency score: 100 %
dockerTools.buildLayeredImage Proof of concept without components

Same when trying to remove components to remove the rust's source

{
  lib,
  pkgs,
}:

let
  rustupToolchainFile = lib.snowfall.fs.get-file "rust-toolchain.toml";
  rustupToolchainFileContent = lib.trivial.importTOML rustupToolchainFile;
  rustupToolchain = rustupToolchainFileContent.toolchain;

  rustupToolchainCleaned = lib.snowfall.attrs.merge-deep [
    rustupToolchain
    { components = [ ]; }
  ];

  rust-toolchain = pkgs.rust-bin.fromRustupToolchain rustupToolchainCleaned;
in
pkgs.dockerTools.buildLayeredImage {
  name = "build-layered-image-no-rust-src";
  contents = [
    rust-toolchain
    pkgs.dockerTools.binSh
  ];
}
nix build '.#build-layered-image-no-rust-src'
dive --source docker-archive <(gunzip -c ./result)
  • Total Image size: 1.7 GB
  • Potential wasted space: 0 B
  • Image efficiency score: 100 %

dockerTools.streamLayeredImage

dockerTools.streamLayeredImage Proof of concept
{
  lib,
  pkgs,
}:

let
  rustupToolchainFile = lib.snowfall.fs.get-file "rust-toolchain.toml";
  rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile rustupToolchainFile;
in
pkgs.dockerTools.streamLayeredImage {
  name = "stream-layered-image";
  contents = [
    rust-toolchain
    pkgs.dockerTools.binSh
  ];
}
nix build '.#stream-layered-image'
./result > stream-layered-image.tar
dive docker-archive://./stream-layered-image.tar
  • Total Image size: 1.7 GB
  • Potential wasted space: 0 B
  • Image efficiency score: 100 %

nix2container

nix2container generate the json that describe the container where the layers are derivation linked to the Nix store

The article that explain what does it solve

nix2container Proof of concept
{
  lib,
  pkgs,
}:

let
  rustupToolchainFile = lib.snowfall.fs.get-file "rust-toolchain.toml";
  rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile rustupToolchainFile;
in
pkgs.nix2containerPkgs.nix2container.buildImage {
  name = "nix2container";
  copyToRoot = pkgs.buildEnv {
    name = "root";
    paths = [
      rust-toolchain
      pkgs.dockerTools.binSh
    ];
    pathsToLink = [ "/bin" ];
  };
}
nix run '.#nix2container.copyToDockerDaemon'
dive nix2container:cvlrpzfkvq2y0q99aa8csni1r6znl3bg
  • Total Image size: 1.7 GB
  • Potential wasted space: 0 B
  • Image efficiency score: 100 %

Comparison

buildImagebuildLayeredImagestreamLayeredImagenix2container
Nix StoreGZipped ImageGZipped ImageScript + JSONJSON
Layers1SeveralSeveral1
Image size1.9 GB1.7 GB1.7 GB1.7GB

Rust's source code, Linux's headers, locales, documentation and manual pages are included

It could probably be removed with more configuration

More Information

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

GitLab stars

git-time-keeper is a tool that helps to take baby steps ๐Ÿ‘ถ๐Ÿฆถ

git-time-keeper can be used as a companion tool to git-gamble or can be used separately

Setup

With git-gamble

# git config --local core.hooksPath ./hooks # if you want to versionate hooks
HOOKS_PATH=$(git rev-parse --git-path hooks)
mkdir -p "$HOOKS_PATH"
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

# git config --local core.hooksPath ./hooks # if you want to versionate hooks
HOOKS_PATH=$(git rev-parse --git-path hooks)
mkdir -p "$HOOKS_PATH"
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

Editor auto save

In order to avoid conflict while saving between the content stored on the disk and the content in the editor, it is recommanded to enable autosave (at least for the current project)

VSCode

For VSCode, you can set files.autoSave to afterDelay

You can run this command to do it for you

SETTINGS="./.vscode/settings.json"
if [ ! -f "$SETTINGS" ]; then
    echo '{
    "files.autoSave": "afterDelay",
}' >"$SETTINGS"
elif grep 'files.autoSave' "$SETTINGS" >/dev/null; then
    sed -Ei 's@"files.autoSave": "[a-zA-Z]+"@"files.autoSave": "afterDelay"@' "$SETTINGS"
else
    sed -i 's@^{@{\n    "files.autoSave": "afterDelay",@' "$SETTINGS"
fi

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 may be only needed for --help

git-time-keeper --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 / macOS) : need sh and kill

Slides why

You can watch the slides about why baby steps are important

Backlog

  • 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
      • Chocolatey
    • for git gamble not only git-gamble
  • 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/llogiq/mutagen

Distribution / publishing backlog

Technical improvement opportunities