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
git-gamble
is a tool that helps to develop the right thing ๐, baby step by baby step ๐ถ๐ฆถgit-gamble
uses the TCRDD methodgit-gamble
works with all languagesgit-gamble
is agit
addon
Watch the demo
Community
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
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
- test && commit || revert
- TDD is dead, long live TCR?
- Xavier's TCRDD first commit
- git-gamble's first commit
- Murex's TCR first commit
Similar projects
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
There is several methods to install depending on your operating system:
Installation method | Linux | macOS | Windows |
---|---|---|---|
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
Nix is a package manager available for Linux, macOS and Windows (through WSL2)
Requirements
-
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
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
-
Make sure it is available in your
$PATH
, you can check it with this commandgit --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
-
Go to the latest version of
git-gamble-debian
-
Download the latest version
git-gamble_2.11.0_amd64.deb
-
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
Requirements
-
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
Requirements
Chocolatey
-
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
-
Make sure it is available in your
$PATH
, you can check it with this commandgit --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
Requirements
Cargo
-
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
-
Make sure it is available in your
$PATH
, you can check it with this commandgit --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
Requirements
git-gamble
doesn't repackage git
, it uses the one installed on your system
-
Make sure it is available in your
$PATH
, you can check it with this commandgit --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
- Download the binary on the release page
- Rename it to
git-gamble
- 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
-
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 :
-
Write a failing test in your codebase, then :
git gamble --red # or git gamble --fail
-
Write the minimum code to make tests pass, then :
git gamble --green # or git gamble --pass
-
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
-
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
-
Write the minimum code to make tests pass, then :
git gamble --green -- $YOUR_TEST_COMMAND
-
Refactor your code, then :
git gamble --refactor -- $YOUR_TEST_COMMAND
Demo
For more detailed example, this the demo
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
~/.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
orfail pass
)- Suggest to take a break
- Good gamble (
pass pass
orfail 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>
ispass
orfail
<ACTUAL>
ispass
orfail
Custom hooks of git-gamble
are like any other client-side git hooks :
-
a hook is a file (
pre-gamble
orpost-gamble
) -
a hook must be in the
$GIT_DIR/hooks/
folder- or in the folder configured by
git config core.hooksPath
- or in the folder configured by
-
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
post-gamble.real_time_collaboration.sample.sh
is specially adapted to near real-time collaborationpost-gamble.assistant.sample.sh
is a simple assistant that displays tips based on the result of the gamble- If you are using
git-time-keeper
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
- Open the Command Palette (F1 or CTRL + Shift + P)
- Select
Tasks: Run Task
- 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 ?
- Does it commit failing tests ?
- What is the default commit message ?
- Feel stuck ?
- What languages are supported ?
When to use it ?
- You want to :
- You follow the Test F.I.R.S.T. Principles
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
- ran very slowly
- 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 messagegit 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 :
-
Take a break โธ๏ธ
-
Slow down โณ
-
Rollback the failing commit โช
Keep a branch if needed
git branch keep_it_for_maybe_later
Then actually rollback
git reset --hard @~
-
Refactor to "make the change easy" ๐ง ๐ก
for each desired change, make the change easy (warning: this may be hard), then make the easy change
-
Try to do smaller steps ๐ถ๐ฆถ
-
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 ?
- If yes, please add a star on GitLab
- If no, please open an issue to give your feedbacks
Contributing
Any contributions (feedback, bug report, merge request ...) are welcome
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
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
Development Setup
Gitpod
You can contribute with just your web browser, directly from Gitpod
Nix
The easiest way to get everything needed is to use Nix
-
Install Nix
-
Install DirEnv
-
Get the source
git clone https://gitlab.com/pinage404/git-gamble
-
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
-
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:
-
Install Rustup
-
Start a new terminal to load the new configuration
-
Clone and navigate to git-gamble
git clone https://gitlab.com/pinage404/git-gamble.git cd git-gamble
-
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
-
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
Repos | Workspaces | Binaries | |
---|---|---|---|
Maintainer | |||
easy to set up | Good, easiest, just git clone | Bad, need a little of work | Neutral, Good if every tools support it |
easy to maintain | Bad, need several maitainance | Good, that's what workspaces are for | Neutral, easy but risk of confusion between what belongs to which tool |
easy to distribute | Bad, need to resetup external platforms | Neutral if every tools support it | Neutral, Good if every tools support it |
avoid duplication | Bad | Neutral, can have a shared Crate | Good |
User | |||
easy to install | Bad, need several installations | Neutral | Neutral, Good if every tools support it |
easy to use | Neutral | Neutral | Neutral |
understand tools are independent | Good | Neutral | Neutral |
easy to use tools together | Bad | Neutral | Neutral |
Total | Good 2 Neutral 1 Bad 5 = -3 | Good 1 Neutral 6 Bad 1 = 0 | Good 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
- Using external services
- Using GitLab only
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
- Migration to use Cynerd's cache
- Others migrations have been included in the comparison table above
- Container image size
-
Most images are smaller
Documentation builder Size Dockerfile 344 Mio Nix 28 Mio -
Rust related images are bigger
Rust test coverage Size Dockerfile 940 MB Nix before optimization 1.94 GB Nix after optimization 1 1.19 GB
-
It seems that there is no more easy stuff to do
Pros and Cons of the Options
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 ๐คท
- There is no call to action for 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 ๐คท
- There are machines that will compute stuff
- 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
cachix/install-nix-action
- Seems to run the official Nix installer
DeterminateSystems/nix-installer-action
&DeterminateSystems/magic-nix-cache-action
- Rely on big TypeScript program that does stuffs ๐คท
GitHub is not Open Source โ
Garnix
Pricing have a free tier (1 500 minutes/month) that may be enougth ๐ถ
- Need scaring authorization on GitHub : Act on your behalf ๐คจ
- Need to move to GitHub โ
Cachix
Already used in the pipeline ๐
- Pricing have a free tier (5Go per project)
- 2.7Go are already used, it may be not enough ๐
- Cachix's client is open source but don't found the server โ
- Simple Nix instance ? ๐คท
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
-
Vonfry's gitlab-ci-nix example
- By GitLab CI itself, download previous cache, if any โฌ๏ธ
- Import derivations to Nix Store from cache, if any โฌ ๏ธ
- Do stuff โ๏ธ
- Export all derivations from Nix Store to cache โก๏ธ
- By GitLab CI itself, upload cache โฌ๏ธ
-
Cynerd's gitlab-ci-nix example
- By GitLab CI itself, download previous cache, if any โฌ๏ธ
- Set substituters : local cache, Cachix, S3, SSH, if any โฌ ๏ธ
- Save current derivations
- Do stuff โ๏ธ
- List new derivations by comparing current derivations with the saved ones
- Export new derivations from Nix Store to cache โก๏ธ
- Upload new derivations to Cachix, S3, SSH, if any โฌ๏ธ
- By GitLab CI itself, upload cache โฌ๏ธ
-
TECHNOFAB's nix-gitlab-ci template
Declare pipeline using Nix instead of YAML
Seems easy to test locally
- declare the pipeline using Nix
- generate the
.gitlab-ci.yaml
- trigger a pipeline from the generated
.gitlab-ci.yaml
The generated pipeline works likely Cynerd's version
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 ?
- Should an extra step with
๐ | Vonfry's version | Cynerd's version | TECHNOFAB's version |
---|---|---|---|
Complexity | Simpler โ | More complex ๐ | Even more complex ๐ต |
Cache | Naive โ | Optimized โ | Optimized โ |
Reusability | Just an example โ | Made for โ | Made for โ |
Reproductibility | Not 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 โ
- Download a base image
- Install Nix inside GitLab's job
- Install packages using Nix
Seems to be heavy
Build image using Nix
- Interesting blog post explaining how to build image using Nix inside GitLab CI
- NixOS official documentation about
dockerTools
in the too long all in one page heavy manual- Content of the official documentation about
dockerTools
in a lighter page
- Content of the official documentation about
dockerTools.buildImage
dockerTools.buildImage
- Produce a container image with one big layer
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
- Produce a container image with several ordered layers
- Each layers are stored in the Nix Store
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
- Produce a container image with several ordered layers
- Layers are NOT stored in the Nix Store
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
buildImage | buildLayeredImage | streamLayeredImage | nix2container | |
---|---|---|---|---|
Nix Store | GZipped Image | GZipped Image | Script + JSON | JSON |
Layers | 1 | Several | Several | 1 |
Image size | 1.9 GB | 1.7 GB | 1.7 GB | 1.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
- Erica's presentation who gaves motivation to deep dive to find a solution
- Real world example to build container image from Nix
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
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
supportgit 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
- branch based developement
- 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 ?
- re-use
- stash instead of revert ?
- shell completion
- in the package ?
- Cargo
- Chocolatey
- for
git gamble
not onlygit-gamble
- in the package ?
- 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
- OS distribution
- Linux
- RPM
cargo-rpm
nix bundle
- SnapCraft ?
- FlatPak ?
fpm
: tool that help to generate to several packages- Open Build Service seems to be a service that build for every Linux targets
- it can be use
- thougth REST
- badly documented
- with
ocs
a CLI who seems to be a VCS like SVN (commit = commit + push)- who use the REST API under the hood
- thougth REST
- it can be use
- RPM
- Mac OS X
- experiment GitLab Runner on Mac OS X
- run tests
- precompile
- experiment GitLab Runner on Mac OS X
- try to distribute OS less binaries
- Container Image (Docker) ?
- Awesome Rust Deployment
- Awesome Rust Platform Specific
- Linux
- permanent URL to download latest version
- symlinks to URL containing the version name ?
- using GitLab Pages ?
- versioned Homebrew Formula
- Use Cargo-Release to bump version
- how to update sha256 in the versioned file ?
Technical improvement opportunities
- licence check
- cargo license
- cargo deny
- smaller binary
- cargo bloat
- cargo deps
- cargo machete
- refactor to iterate over
Shell
enum
's values instead of having an hard-coded array