├── levels ├── bisect │ ├── sequence │ └── bisect ├── workflows │ ├── sequence │ ├── gitignore │ └── pr ├── merge │ ├── sequence │ ├── merge-abort │ ├── conflict │ └── merge ├── remotes │ ├── sequence │ ├── problems │ └── friend ├── changing-the-past │ ├── sequence │ ├── reorder │ └── rebase ├── files │ ├── sequence │ ├── files-delete │ └── files-add ├── sandbox │ ├── sequence │ ├── empty │ ├── remote │ └── three-commits ├── index │ ├── sequence │ ├── rm │ ├── checkout │ ├── new │ ├── change │ ├── add │ ├── reset │ ├── compare │ └── steps ├── intro │ ├── sequence │ ├── init │ ├── cli │ ├── risky │ ├── commit │ ├── copies │ ├── who-are-you │ └── remote ├── tags │ ├── sequence │ ├── remove-tag │ ├── add-tag-later │ ├── add-tag │ └── remote-tag ├── stash │ ├── sequence │ ├── stash │ ├── stash-pop │ ├── stash-branch │ ├── stash-clear │ └── stash-merge ├── branches │ ├── sequence │ ├── checkout-commit │ ├── branch-create │ ├── grow │ ├── branch-remove │ ├── fork │ └── reorder ├── shit-happens │ ├── sequence │ ├── restore-a-file │ ├── restore-a-file-from-the-past │ ├── reflog │ ├── bad-commit │ └── pushed-something-broken ├── sequence ├── unused │ ├── init │ ├── clone │ ├── who-are-you │ ├── commit │ ├── remotes-delete │ ├── commit-a │ ├── index-mv │ ├── steps │ ├── split │ ├── files-move │ ├── checkout │ ├── remotes-add │ ├── restore │ ├── fetch │ └── pull-push └── low-level │ ├── sequence │ ├── symref-create │ ├── index-remove │ ├── basics │ ├── tree-create │ ├── blob-remove │ ├── puzzle-precious-blob │ ├── welcome │ ├── index-update │ ├── commit-parents │ ├── puzzle-apocalypse │ ├── commit-create │ ├── ref-remove │ ├── commit-rhombus │ ├── ref-move │ ├── blob-create │ ├── tree-read │ ├── puzzle-trees-all-the-way-down │ ├── index-add │ ├── ref-create │ ├── tree-nested │ └── symref-no-deref ├── .gitignore ├── nodes ├── pop.wav ├── pop.wav.import ├── blob.svg.import ├── head.svg.import ├── ref.svg.import ├── tree.svg.import ├── head1.svg.import ├── head2.svg.import ├── head3.svg.import ├── commit.svg.import ├── document.svg.import ├── blob.svg └── commit.svg ├── scenes ├── no_git.gd ├── cursor.gd ├── music_button.gd ├── level_repo.gd ├── tcp_server.tscn ├── tcp_server_shell.tscn ├── survey.gd ├── shell_command.gd ├── music_button.tscn ├── notification.gd ├── game.tscn ├── title.gd ├── input_dialog.tscn ├── cli_badge.gd ├── player.tscn ├── sandbox.gd ├── levels.gd ├── tcp_server.gd ├── text_editor.gd ├── chapter.gd ├── drop_area.gd ├── tcp_server_shell.gd ├── text_editor.tscn ├── cards.tscn ├── sandbox.tscn ├── arrow.gd ├── drop_area.tscn ├── arrow.tscn ├── cli_badge.tscn ├── card_particles.tscn ├── notification.tscn ├── level_select.gd ├── better_shell.gd └── title.tscn ├── sounds ├── poof.wav ├── buzzer.wav ├── success.wav ├── swish.wav ├── swoosh.wav ├── typewriter_ding.wav ├── poof.wav.import ├── swish.wav.import ├── buzzer.wav.import ├── swoosh.wav.import ├── success.wav.import └── typewriter_ding.wav.import ├── fonts ├── cabin-bold.ttf ├── cabin-regular.ttf ├── iosevka-regular.ttf ├── big.tres ├── default.tres ├── small.tres └── monospace.tres ├── images ├── oh-my-git.png ├── new.svg.import ├── ref.svg.import ├── file.svg.import ├── head.svg.import ├── commit.svg.import ├── remote.svg.import ├── string.svg.import ├── removed.svg.import ├── checkout.svg.import ├── conflict.svg.import ├── modified.svg.import ├── cli-badge.svg.import ├── oh-my-git.png.import └── untracked.svg.import ├── music ├── gigantic-greasy-giraffe.ogg └── gigantic-greasy-giraffe.ogg.import ├── default_bus_layout.tres ├── scripts ├── input_dialog.gd ├── gitconfig ├── better-reverse-shell ├── hint ├── fake-editor └── net-test ├── styles ├── alert_button_hover.tres ├── terminal_input.tres └── alert_button.tres ├── cards ├── rm.svg.import ├── add.svg.import ├── init.svg.import ├── pull.svg.import ├── push.svg.import ├── show.svg.import ├── clone.svg.import ├── fetch.svg.import ├── merge.svg.import ├── reset.svg.import ├── branch.svg.import ├── commit.svg.import ├── rebase.svg.import ├── reflog.svg.import ├── revert.svg.import ├── checkout.svg.import ├── commit-a.svg.import ├── file-copy.svg.import ├── file-new.svg.import ├── bisect-bad.svg.import ├── reset-file.svg.import ├── reset-hard.svg.import ├── bisect-good.svg.import ├── cherry-pick.svg.import ├── commit-auto.svg.import ├── file-delete.svg.import ├── file-rename.svg.import ├── merge-abort.svg.import ├── bisect-start.svg.import ├── branch-delete.svg.import ├── checkout-file.svg.import ├── checkout-from.svg.import ├── rebase-continue.svg.import └── rebase-interactive.svg.import ├── resources └── ref.svg.import ├── Makefile ├── LICENSE.md └── .github └── workflows └── build.yml /levels/bisect/sequence: -------------------------------------------------------------------------------- 1 | bisect 2 | -------------------------------------------------------------------------------- /levels/workflows/sequence: -------------------------------------------------------------------------------- 1 | pr 2 | -------------------------------------------------------------------------------- /levels/merge/sequence: -------------------------------------------------------------------------------- 1 | merge 2 | conflict 3 | -------------------------------------------------------------------------------- /levels/remotes/sequence: -------------------------------------------------------------------------------- 1 | friend 2 | problems 3 | -------------------------------------------------------------------------------- /levels/changing-the-past/sequence: -------------------------------------------------------------------------------- 1 | rebase 2 | reorder 3 | -------------------------------------------------------------------------------- /levels/files/sequence: -------------------------------------------------------------------------------- 1 | files-delete 2 | files-add 3 | 4 | -------------------------------------------------------------------------------- /levels/sandbox/sequence: -------------------------------------------------------------------------------- 1 | empty 2 | remote 3 | three-commits 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.import/ 2 | /cache/ 3 | /dependencies/ 4 | /build/ 5 | -------------------------------------------------------------------------------- /levels/index/sequence: -------------------------------------------------------------------------------- 1 | compare 2 | new 3 | change 4 | reset 5 | steps 6 | -------------------------------------------------------------------------------- /nodes/pop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/nodes/pop.wav -------------------------------------------------------------------------------- /levels/intro/sequence: -------------------------------------------------------------------------------- 1 | risky 2 | copies 3 | init 4 | cli 5 | commit 6 | remote 7 | -------------------------------------------------------------------------------- /levels/tags/sequence: -------------------------------------------------------------------------------- 1 | add-tag 2 | remove-tag 3 | add-tag-later 4 | remote-tag 5 | -------------------------------------------------------------------------------- /scenes/no_git.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | func quit(): 4 | get_tree().quit() 5 | -------------------------------------------------------------------------------- /sounds/poof.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/sounds/poof.wav -------------------------------------------------------------------------------- /sounds/buzzer.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/sounds/buzzer.wav -------------------------------------------------------------------------------- /sounds/success.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/sounds/success.wav -------------------------------------------------------------------------------- /sounds/swish.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/sounds/swish.wav -------------------------------------------------------------------------------- /sounds/swoosh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/sounds/swoosh.wav -------------------------------------------------------------------------------- /fonts/cabin-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/fonts/cabin-bold.ttf -------------------------------------------------------------------------------- /images/oh-my-git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/images/oh-my-git.png -------------------------------------------------------------------------------- /fonts/cabin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/fonts/cabin-regular.ttf -------------------------------------------------------------------------------- /levels/stash/sequence: -------------------------------------------------------------------------------- 1 | stash 2 | stash-pop 3 | stash-clear 4 | stash-branch 5 | stash-merge 6 | -------------------------------------------------------------------------------- /fonts/iosevka-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/fonts/iosevka-regular.ttf -------------------------------------------------------------------------------- /sounds/typewriter_ding.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/sounds/typewriter_ding.wav -------------------------------------------------------------------------------- /levels/branches/sequence: -------------------------------------------------------------------------------- 1 | checkout-commit 2 | fork 3 | branch-create 4 | grow 5 | branch-remove 6 | reorder 7 | -------------------------------------------------------------------------------- /music/gigantic-greasy-giraffe.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0rp30/oh-my-git/HEAD/music/gigantic-greasy-giraffe.ogg -------------------------------------------------------------------------------- /scenes/cursor.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | func _process(_delta): 4 | global_position = get_global_mouse_position() 5 | -------------------------------------------------------------------------------- /levels/shit-happens/sequence: -------------------------------------------------------------------------------- 1 | restore-a-file 2 | restore-a-file-from-the-past 3 | bad-commit 4 | pushed-something-broken 5 | reflog 6 | -------------------------------------------------------------------------------- /levels/sandbox/empty: -------------------------------------------------------------------------------- 1 | title = Empty sandbox 2 | 3 | [description] 4 | 5 | This is an empty sandbox you can play around in. 6 | 7 | [setup] 8 | -------------------------------------------------------------------------------- /scenes/music_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | 4 | func _ready(): 5 | pass 6 | 7 | 8 | func toggle_music(): 9 | game.toggle_music() 10 | -------------------------------------------------------------------------------- /scenes/level_repo.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name LevelRepo 3 | 4 | var slug 5 | var path 6 | var setup_commands = "" 7 | var action_commands = "" 8 | var win_conditions = {} 9 | -------------------------------------------------------------------------------- /levels/sequence: -------------------------------------------------------------------------------- 1 | intro 2 | files 3 | branches 4 | merge 5 | index 6 | remotes 7 | changing-the-past 8 | shit-happens 9 | workflows 10 | bisect 11 | stash 12 | tags 13 | sandbox 14 | -------------------------------------------------------------------------------- /levels/unused/init: -------------------------------------------------------------------------------- 1 | title = Welcome! 2 | cards = init 3 | 4 | [description] 5 | 6 | 7 | [setup] 8 | 9 | rm -rf .git 10 | 11 | [win] 12 | 13 | # Again, initialize your time machine! 14 | test -d .git 15 | -------------------------------------------------------------------------------- /scenes/tcp_server.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://scenes/tcp_server.gd" type="Script" id=1] 4 | 5 | [node name="TCPServer" type="Node"] 6 | script = ExtResource( 1 ) 7 | port = 6666 8 | -------------------------------------------------------------------------------- /scenes/tcp_server_shell.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://scenes/tcp_server_shell.gd" type="Script" id=1] 4 | 5 | [node name="TCPServerShell" type="Node"] 6 | script = ExtResource( 1 ) 7 | -------------------------------------------------------------------------------- /default_bus_layout.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AudioBusLayout" format=2] 2 | 3 | [resource] 4 | bus/1/name = "SFX" 5 | bus/1/solo = false 6 | bus/1/mute = false 7 | bus/1/bypass_fx = false 8 | bus/1/volume_db = 0.0 9 | bus/1/send = "Master" 10 | -------------------------------------------------------------------------------- /fonts/big.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://fonts/cabin-bold.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 45 7 | use_mipmaps = true 8 | use_filter = true 9 | font_data = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /fonts/default.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://fonts/cabin-regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 23 7 | use_mipmaps = true 8 | use_filter = true 9 | font_data = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /fonts/small.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://fonts/cabin-regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 15 7 | use_mipmaps = true 8 | use_filter = true 9 | font_data = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /scripts/input_dialog.gd: -------------------------------------------------------------------------------- 1 | extends WindowDialog 2 | 3 | signal entered(text) 4 | 5 | func _text_entered(text): 6 | emit_signal("entered", text) 7 | queue_free() 8 | 9 | func _notification(what): 10 | if what == Popup.NOTIFICATION_POST_POPUP: 11 | $LineEdit.grab_focus() 12 | -------------------------------------------------------------------------------- /styles/alert_button_hover.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.631373, 0.0745098, 0.0745098, 1 ) 5 | corner_radius_top_left = 4 6 | corner_radius_top_right = 4 7 | corner_radius_bottom_right = 4 8 | corner_radius_bottom_left = 4 9 | -------------------------------------------------------------------------------- /fonts/monospace.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://fonts/iosevka-regular.ttf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 21 7 | use_mipmaps = true 8 | use_filter = true 9 | font_data = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /scenes/survey.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | func _ready(): 4 | get_tree().set_auto_accept_quit(true) 5 | 6 | func quit(): 7 | get_tree().quit() 8 | 9 | func levels(): 10 | get_tree().change_scene("res://scenes/level_select.tscn") 11 | 12 | 13 | func on_survey_pressed(): 14 | game.open_survey() 15 | -------------------------------------------------------------------------------- /levels/low-level/sequence: -------------------------------------------------------------------------------- 1 | welcome 2 | basics 3 | blob-create 4 | blob-remove 5 | index-add 6 | index-remove 7 | index-update 8 | tree-create 9 | tree-read 10 | tree-nested 11 | commit-create 12 | commit-parents 13 | commit-rhombus 14 | ref-create 15 | ref-move 16 | ref-remove 17 | symref-create 18 | symref-no-deref 19 | -------------------------------------------------------------------------------- /scenes/shell_command.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name ShellCommand 3 | 4 | signal done 5 | 6 | var command 7 | var output 8 | var exit_code 9 | var crash_on_fail = true 10 | var thread 11 | 12 | func _unused(): 13 | # This is just to suppress a warning about the signal never being emitted. 14 | emit_signal("done") 15 | -------------------------------------------------------------------------------- /styles/terminal_input.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | content_margin_left = 10.0 5 | content_margin_right = 10.0 6 | content_margin_top = 10.0 7 | content_margin_bottom = 5.0 8 | bg_color = Color( 0, 0, 0, 1 ) 9 | border_width_top = 2 10 | border_color = Color( 0.498039, 0.498039, 0.498039, 1 ) 11 | -------------------------------------------------------------------------------- /scripts/gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | email = you@time.agency 3 | name = You 4 | [core] 5 | editor = ~/fake-editor 6 | [init] 7 | templateDir = "" 8 | defaultBranch = main 9 | [log] 10 | decorate = true 11 | [advice] 12 | detachedHead = false 13 | [receive] 14 | denyCurrentBranch = ignore 15 | [commit] 16 | gpgsign = false 17 | [pull] 18 | rebase = false -------------------------------------------------------------------------------- /levels/unused/clone: -------------------------------------------------------------------------------- 1 | title = Cloning a repo 2 | cards = clone commit-auto pull push 3 | 4 | [description] 5 | 6 | Get your friend's repo using clone, change something, push it back. 7 | 8 | [setup] 9 | 10 | rm -rf .git 11 | 12 | [setup friend] 13 | 14 | echo hi > file 15 | git add . 16 | git commit -m "Initial commit" 17 | 18 | [win friend] 19 | 20 | test "$(git show main:file)" != hi 21 | -------------------------------------------------------------------------------- /styles/alert_button.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | content_margin_left = 10.0 5 | content_margin_right = 10.0 6 | content_margin_top = 5.0 7 | content_margin_bottom = 5.0 8 | bg_color = Color( 0.717647, 0.160784, 0.160784, 1 ) 9 | corner_radius_top_left = 4 10 | corner_radius_top_right = 4 11 | corner_radius_bottom_right = 4 12 | corner_radius_bottom_left = 4 13 | -------------------------------------------------------------------------------- /scripts/better-reverse-shell: -------------------------------------------------------------------------------- 1 | #!/usr/env/perl 2 | 3 | use Socket; 4 | 5 | $ip="127.0.0.1"; 6 | $port=6666; 7 | 8 | socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp")); 9 | 10 | if(connect(S,sockaddr_in($port,inet_aton($ip)))){ 11 | open(STDIN,">&S"); 12 | open(STDOUT,">&S"); 13 | open(STDERR,">&S"); 14 | exec("python -c 'import pty; pty.spawn([\"/bin/bash\", \"--noprofile\", \"--norc\"])'") 15 | }; 16 | -------------------------------------------------------------------------------- /levels/workflows/gitignore: -------------------------------------------------------------------------------- 1 | title = Ignoring files 2 | 3 | [description] 4 | 5 | That chicken is running around a lot, and changing often. We don't want to have it in our commits. 6 | 7 | Add it to the file .gitignore, and try using `git add .`! 8 | 9 | [setup] 10 | 11 | touch .gitignore 12 | echo important > important 13 | git add important 14 | git commit -m "Initial commit" 15 | 16 | [actions] 17 | 18 | echo "$RANDOM" > chicken 19 | -------------------------------------------------------------------------------- /music/gigantic-greasy-giraffe.ogg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="ogg_vorbis" 4 | type="AudioStreamOGGVorbis" 5 | path="res://.import/gigantic-greasy-giraffe.ogg-f6c5f1ef0e57d5d768517fa39e6be36c.oggstr" 6 | 7 | [deps] 8 | 9 | source_file="res://music/gigantic-greasy-giraffe.ogg" 10 | dest_files=[ "res://.import/gigantic-greasy-giraffe.ogg-f6c5f1ef0e57d5d768517fa39e6be36c.oggstr" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /nodes/pop.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/pop.wav-c45fbac324d41566fc00fa2a6560f011.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://nodes/pop.wav" 10 | dest_files=[ "res://.import/pop.wav-c45fbac324d41566fc00fa2a6560f011.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /sounds/poof.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/poof.wav-db49a4251469e51fe6b35ddc6fb05dcc.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://sounds/poof.wav" 10 | dest_files=[ "res://.import/poof.wav-db49a4251469e51fe6b35ddc6fb05dcc.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /scenes/music_button.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://scenes/music_button.gd" type="Script" id=1] 4 | 5 | [node name="Button3" type="Button"] 6 | margin_left = 241.005 7 | margin_top = 58.856 8 | margin_right = 387.005 9 | margin_bottom = 97.856 10 | focus_mode = 0 11 | enabled_focus_mode = 0 12 | text = "Toggle music" 13 | script = ExtResource( 1 ) 14 | __meta__ = { 15 | "_edit_use_anchors_": false 16 | } 17 | [connection signal="pressed" from="." to="." method="toggle_music"] 18 | -------------------------------------------------------------------------------- /scripts/hint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use IO::Socket; 4 | 5 | $socket = IO::Socket::INET->new(PeerAddr => "127.0.0.1", 6 | PeerPort => 1235, 7 | Proto => "tcp", 8 | Type => SOCK_STREAM); 9 | 10 | my $message = join " ", @ARGV; 11 | 12 | # Send the length of the first argument as four bytes. 13 | $socket->send(pack("L", length($message))); 14 | # Send the first argument as a string. 15 | $socket->send($message); 16 | -------------------------------------------------------------------------------- /sounds/swish.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/swish.wav-e61d890ee6364cfac7a66a257b308699.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://sounds/swish.wav" 10 | dest_files=[ "res://.import/swish.wav-e61d890ee6364cfac7a66a257b308699.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /sounds/buzzer.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/buzzer.wav-2dd9ed8c0fc10c99ff5279a9bd90ea75.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://sounds/buzzer.wav" 10 | dest_files=[ "res://.import/buzzer.wav-2dd9ed8c0fc10c99ff5279a9bd90ea75.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /sounds/swoosh.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/swoosh.wav-d94298cb8d05e0380c25da11f976f097.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://sounds/swoosh.wav" 10 | dest_files=[ "res://.import/swoosh.wav-d94298cb8d05e0380c25da11f976f097.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /sounds/success.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/success.wav-51d483ad4ceec15770f55296ad79faf0.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://sounds/success.wav" 10 | dest_files=[ "res://.import/success.wav-51d483ad4ceec15770f55296ad79faf0.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /levels/sandbox/remote: -------------------------------------------------------------------------------- 1 | title = Sandbox with a remote 2 | cards = checkout commit-auto pull fetch push 3 | 4 | [description] 5 | 6 | Here's a sandbox with a remote! Try pulling, fetching, or pushing! 7 | 8 | How can you push tags and branches on a remote? How can you delete them again? 9 | 10 | [setup yours] 11 | 12 | echo "Line 1" > essay 13 | git add . 14 | git commit -m "Initial commit" 15 | 16 | git push -u friend main 17 | 18 | [setup friend] 19 | 20 | git checkout main 21 | echo "Line 2" >> essay 22 | git commit -am "Another line" 23 | -------------------------------------------------------------------------------- /scenes/notification.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var text setget _set_text 4 | var button_texts = ["Got it!", "Interesting!", "Very useful!", "Cool!", "Nice!", "Thanks!", "Whatever...", "Okay!", "Yay!", "Awesome!"] 5 | 6 | func _ready(): 7 | button_texts.shuffle() 8 | $Panel/CenterContainer/OK.text = button_texts[0] 9 | 10 | #func _gui_input(event): 11 | # if event is InputEventMouseButton: 12 | # queue_free() 13 | 14 | func _set_text(new_text): 15 | text = new_text 16 | $Panel/Label.text = new_text 17 | 18 | 19 | func confirm(): 20 | queue_free() 21 | -------------------------------------------------------------------------------- /sounds/typewriter_ding.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/typewriter_ding.wav-261baf709f530d1d0a04e5c5a6efc553.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://sounds/typewriter_ding.wav" 10 | dest_files=[ "res://.import/typewriter_ding.wav-261baf709f530d1d0a04e5c5a6efc553.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /levels/low-level/symref-create: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Instead of pointing directly to objects, refs can also point to other refs! 4 | 5 | When that happens, they are called "symbolic refs". You can create or update a symbolic ref using 6 | 7 | git symbolic-ref 8 | 9 | Create a symbolic ref called "refs/rainbow"! 10 | 11 | [setup] 12 | 13 | [setup goal] 14 | 15 | BLOB=$(git hash-object -w --stdin) 16 | git update-ref refs/double "$BLOB" 17 | git symbolic-ref refs/rainbow refs/double 18 | 19 | [win] 20 | 21 | git symbolic-ref refs/rainbow 22 | -------------------------------------------------------------------------------- /levels/intro/init: -------------------------------------------------------------------------------- 1 | title = Enter the time machine 2 | cards = init 3 | 4 | [description] 5 | 6 | You've been accepted to time travel school! Yay! It's your first day! Your teacher explains: 7 | 8 | "To do anything with a time machine, you first need to initialize it!" 9 | 10 | Drag that blue card up to play it! 11 | 12 | [setup] 13 | 14 | rm -rf .git 15 | 16 | [win] 17 | 18 | # Initialize the time machine! 19 | test -d .git 20 | 21 | [congrats] 22 | 23 | Nice! See that little owl squirrel that appeared? It will be your companion, and always show you where you are in time! 24 | -------------------------------------------------------------------------------- /levels/shit-happens/restore-a-file: -------------------------------------------------------------------------------- 1 | title = Restore a deleted file 2 | cards = checkout 3 | 4 | [description] 5 | 6 | Oops - you deleted the "essay" file, which you worked on all night! 7 | 8 | Luckily, Git is here to help! You can use `git checkout` to restore the file! 9 | 10 | [setup] 11 | 12 | echo important > essay 13 | git add . 14 | git commit -m "Initial commit" 15 | echo "important content" > essay 16 | git commit -am "Improve essay" 17 | rm essay 18 | 19 | [win] 20 | 21 | # Restore the essay to contain "important content" 22 | test "$(cat essay)" = "important content" 23 | -------------------------------------------------------------------------------- /scenes/game.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://scenes/game.gd" type="Script" id=1] 4 | [ext_resource path="res://music/gigantic-greasy-giraffe.ogg" type="AudioStream" id=2] 5 | [ext_resource path="res://scenes/tcp_server_shell.tscn" type="PackedScene" id=3] 6 | 7 | [node name="Node" type="Node"] 8 | script = ExtResource( 1 ) 9 | 10 | [node name="Music" type="AudioStreamPlayer" parent="."] 11 | stream = ExtResource( 2 ) 12 | volume_db = -15.0 13 | autoplay = true 14 | 15 | [node name="ShellServer" parent="." instance=ExtResource( 3 )] 16 | port = 6666 17 | -------------------------------------------------------------------------------- /levels/unused/who-are-you: -------------------------------------------------------------------------------- 1 | title = Nice to meet you! 2 | cards = config-name config-email 3 | 4 | [description] 5 | 6 | Introduce yourself using 7 | 8 | git config --global user.name Firstname 9 | git config --global user.email "your@mail.com" 10 | 11 | [setup] 12 | 13 | [actions] 14 | 15 | test "$(git config user.name)" != "You" && hint "Hey $(git config user.name), nice to meet you!" 16 | 17 | [win] 18 | 19 | # Have a name configured. 20 | test "$(git config user.name)" != "You" 21 | 22 | # Have an email address configured. 23 | test "$(git config user.email)" != "you@time.agency" 24 | -------------------------------------------------------------------------------- /levels/unused/commit: -------------------------------------------------------------------------------- 1 | title = Make a commit \o/ 2 | cards = add reset checkout commit 3 | 4 | [description] 5 | 6 | For practice, make a commit where all files contain an "x"! 7 | 8 | [setup] 9 | 10 | echo a > a 11 | echo x > b 12 | echo x > c 13 | git add . 14 | git commit -m "Initial commit" 15 | echo x > a 16 | echo b > b 17 | git add b 18 | echo c > c 19 | 20 | [win] 21 | 22 | # File a contains "x" in the last main commit. 23 | test "$(git show main:a)" = x 24 | # File b contains "x" in the last main commit. 25 | test "$(git show main:b)" = x 26 | # File c contains "x" in the last main commit. 27 | test "$(git show main:c)" = x 28 | -------------------------------------------------------------------------------- /scenes/title.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | func _ready(): 4 | if !OS.has_feature("standalone") and !game.skipped_title: 5 | game.skipped_title = true 6 | get_tree().change_scene("res://scenes/level_select.tscn") 7 | 8 | func quit(): 9 | get_tree().quit() 10 | 11 | func levels(): 12 | get_tree().change_scene("res://scenes/level_select.tscn") 13 | 14 | 15 | func on_survey_pressed(): 16 | game.open_survey() 17 | 18 | 19 | func sandbox(): 20 | game.current_chapter = levels.chapters.size() - 1 21 | game.current_level = levels.chapters[game.current_chapter].levels.size() -1 22 | get_tree().change_scene("res://scenes/main.tscn") 23 | -------------------------------------------------------------------------------- /levels/low-level/index-remove: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | To remove an entry from the index, use a command like this: 4 | 5 | git update-index --force-remove 6 | 7 | Remove all entries from the index! 8 | 9 | [setup] 10 | 11 | echo "file 1" > file1 12 | echo "file 2" > file2 13 | echo "file 3" > file3 14 | git add . 15 | 16 | [setup goal] 17 | 18 | echo "file 1" > file1 19 | echo "file 2" > file2 20 | echo "file 3" > file3 21 | git add . 22 | 23 | git update-index --force-remove file1 24 | git update-index --force-remove file2 25 | git update-index --force-remove file3 26 | 27 | [win] 28 | 29 | test "$(git ls-files | wc -l)" -eq 0 30 | -------------------------------------------------------------------------------- /levels/unused/remotes-delete: -------------------------------------------------------------------------------- 1 | title = Deleting and renaming a remote 2 | cards = checkout 3 | 4 | [description] 5 | 6 | Here, you already have two remotes configured! You can list them using `git remote`. 7 | 8 | [setup] 9 | 10 | git remote rename friend frend 11 | 12 | [setup friend] 13 | 14 | [setup enemy] 15 | 16 | [win] 17 | 18 | # Rename the remote with the typo (using `git remote rename [old name] [new name]`) 19 | git remote | grep friend 20 | # The remote with the typo is gone. 21 | ! grep 'frend' <(git remote) 22 | # Delete the remote you don't want to keep (using `git remote remove [remote]`) 23 | ! grep 'enemy' <(git remote) 24 | -------------------------------------------------------------------------------- /levels/index/rm: -------------------------------------------------------------------------------- 1 | title = Delete a file in the next commit 2 | cards = add reset-file checkout-file rm file-delete commit 3 | 4 | [description] 5 | 6 | If you want to remove a file in the next commit, you can use `git rm`! This will both delete the file locally, and in the index. 7 | 8 | If a file is modified, you'll need to reset these changes first/reset the files. 9 | 10 | [setup] 11 | 12 | echo a > a 13 | echo x > b 14 | echo x > c 15 | git add . 16 | git commit -m "Initial commit" 17 | echo x > a 18 | echo b > b 19 | git add b 20 | 21 | [win] 22 | 23 | # Make a commit where all files are deleted ¯\_(^_^)_/¯ 24 | test "$(git ls-tree main | wc -l)" -eq 0 25 | -------------------------------------------------------------------------------- /levels/index/checkout: -------------------------------------------------------------------------------- 1 | title = Checking out files from the index 2 | cards = add reset-file checkout-file commit 3 | 4 | [description] 5 | 6 | So you've made changes to your files, but you decide that you don't want to keep them! You can use `git checkout` for that! 7 | 8 | What happens if you have already update the index, like in file c? You have to reset the index first! 9 | 10 | [setup] 11 | 12 | echo a > a 13 | echo b > b 14 | echo c > c 15 | git add . 16 | git commit -m "Initial commit" 17 | echo x > a 18 | echo x > b 19 | echo x > c 20 | git add c 21 | 22 | [win] 23 | 24 | # Remove all changes in your local files! 25 | test "$(git diff --name-only | wc -l)" -eq 0 26 | -------------------------------------------------------------------------------- /levels/unused/commit-a: -------------------------------------------------------------------------------- 1 | title = Make a commit, but faster! 2 | cards = add reset checkout commit commit-a 3 | 4 | [description] 5 | 6 | There is a time-saving trick, where instead of a plain `git commit`, you can use 7 | 8 | git commit -a 9 | 10 | This will automatically add all changes you made to local files! Very convenient. 11 | 12 | [setup] 13 | 14 | echo a > a 15 | echo b > b 16 | echo c > c 17 | git add . 18 | git commit -m "Initial commit" 19 | echo x > a 20 | echo x > b 21 | echo x > c 22 | 23 | [win] 24 | 25 | # Make a commit where all files contain "x". 26 | test "$(git show main:a)" = x && test "$(git show main:b)" = x && test "$(git show main:c)" = x 27 | -------------------------------------------------------------------------------- /levels/unused/index-mv: -------------------------------------------------------------------------------- 1 | title = Rename a file in the next commit 2 | cards = add reset-file checkout-file mv commit 3 | 4 | [description] 5 | 6 | Other times, you might want to rename a file in the next commit. Use 7 | 8 | git mv [file] [new name] 9 | 10 | for that. The effect is very similar as if you had created a copy with a new name, and removed the old version. 11 | 12 | [setup] 13 | 14 | echo a > a 15 | echo SPECIAL > b 16 | echo x > c 17 | git add . 18 | git commit -m "Initial commit" 19 | echo x > a 20 | echo b >> b 21 | git add b 22 | 23 | [win] 24 | 25 | # Make a commit where you rename the file b to "x". 26 | test "$(git ls-tree --name-only main)" = "$(echo -e "a\nc\nx")" 27 | -------------------------------------------------------------------------------- /levels/low-level/basics: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | For this prototype, we assume you have some experience with the command line. Here are some commands that will be useful: 4 | 5 | - ls 6 | - echo content > file 7 | - cat file 8 | - mkdir dir 9 | 10 | Find the riddle in your current directory and put the answer into the file "answer"! 11 | 12 | [congrats] 13 | 14 | Omnomnom! 15 | 16 | For technical reasons, you can't use `cd` in this prototype yet. But there won't be a lot of interaction with the file system anyways. :) 17 | 18 | [setup] 19 | 20 | mkdir riddle 21 | echo "ppl p" > riddle/consonants 22 | echo "ae ie" > riddle/vowels 23 | 24 | [win] 25 | 26 | cat answer | grep -i "apple \\?pie" 27 | -------------------------------------------------------------------------------- /levels/shit-happens/restore-a-file-from-the-past: -------------------------------------------------------------------------------- 1 | title = Restore a file from the past 2 | cards = checkout checkout-from commit 3 | 4 | [description] 5 | 6 | Here's a similar problem: you really liked the essay from the very first commit, and want to have it back! Well, checkout can also restore things from older commits, Here's how: 7 | 8 | git checkout [commit] [file] 9 | 10 | [setup] 11 | 12 | echo "good version" > essay 13 | git add . 14 | git commit -m "Initial commit" 15 | echo "bad version" > essay 16 | git commit -am "\"Improve\" essay" 17 | 18 | [win] 19 | 20 | # Get the first version of your essay, and make a new commit with it. 21 | test "$(git show main:essay)" = "good version" 22 | -------------------------------------------------------------------------------- /levels/workflows/pr: -------------------------------------------------------------------------------- 1 | title = Cloning a repo 2 | cards = clone commit-auto reset-hard checkout file-new branch 3 | 4 | [description] 5 | 6 | Your friend has a problem! Clone the repo located in `../friend`, create a branch called "solution", and fix the problem in this branch. When you're ready, make a "Pull Request" by using `git tag pr`. 7 | 8 | 9 | [setup] 10 | 11 | rm -rf .git 12 | 13 | [setup friend] 14 | 15 | echo "2 + 3 = " > file 16 | git add . 17 | git commit -m "Initial commit" 18 | 19 | [actions friend] 20 | 21 | git ls-remote yours | grep pr && git fetch yours && git merge yours/solution 22 | git show main:file | grep 5 && hint "Thanks!" 23 | 24 | [win friend] 25 | 26 | git show main:file | grep 5 27 | -------------------------------------------------------------------------------- /scenes/input_dialog.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://scripts/input_dialog.gd" type="Script" id=1] 4 | 5 | [node name="InputDialog" type="WindowDialog"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | margin_left = 816.0 9 | margin_top = 446.0 10 | margin_right = -826.0 11 | margin_bottom = -591.0 12 | window_title = "Please enter a value:" 13 | script = ExtResource( 1 ) 14 | __meta__ = { 15 | "_edit_use_anchors_": false 16 | } 17 | 18 | [node name="LineEdit" type="LineEdit" parent="."] 19 | anchor_right = 1.0 20 | anchor_bottom = 1.0 21 | __meta__ = { 22 | "_edit_use_anchors_": false 23 | } 24 | [connection signal="text_entered" from="LineEdit" to="." method="_text_entered"] 25 | -------------------------------------------------------------------------------- /scenes/cli_badge.gd: -------------------------------------------------------------------------------- 1 | extends TextureRect 2 | 3 | export var active = true setget _set_active 4 | export var sparkling = true setget _set_sparkling 5 | export var impossible = false setget _set_impossible 6 | 7 | 8 | func _ready(): 9 | _set_sparkling(sparkling) 10 | 11 | func _set_active(new_active): 12 | active = new_active 13 | if active: 14 | self.self_modulate = Color(1, 1, 1) 15 | else: 16 | self.self_modulate = Color(0.2, 0.2, 0.2) 17 | sparkling = false 18 | 19 | func _set_sparkling(new_sparkling): 20 | sparkling = new_sparkling 21 | if $Particles2D: 22 | $Particles2D.emitting = sparkling 23 | 24 | func _set_impossible(new_impossible): 25 | impossible = new_impossible 26 | $Nope.visible = impossible 27 | -------------------------------------------------------------------------------- /levels/intro/cli: -------------------------------------------------------------------------------- 1 | title = The command line 2 | cards = 3 | 4 | [description] 5 | 6 | These playing cards are designed to be easy to use and to remember! We'd suggest sticking to them if you don't have a lot of experience with Git! 7 | 8 | [cli] 9 | 10 | But there's another way to interact with Git: 11 | 12 | Try typing `git init` into the black terminal below, and pressing the enter key! 13 | 14 | [setup] 15 | 16 | rm -rf .git 17 | 18 | [win] 19 | 20 | # Initialize the time machine! 21 | test -d .git 22 | 23 | [congrats] 24 | 25 | Cool! Instead of using the playing cards, you can also do everything via the command line! 26 | 27 | The command line is pretty powerful! Often, you can use it to solve tasks faster compared to using a graphical interface. 28 | -------------------------------------------------------------------------------- /levels/unused/steps: -------------------------------------------------------------------------------- 1 | title = One step after another 2 | cards = checkout commit reset-hard add 3 | 4 | [description] 5 | 6 | Sometimes, you might want to record the order in which things changed, instead of making a single commit. 7 | 8 | What happened here? Make two commits from the changes (using the "add" card), in an order that makes sense! 9 | 10 | [setup] 11 | 12 | echo something > file1 13 | echo something else > file2 14 | git add . 15 | git commit -m "Initial commit" 16 | 17 | echo this should happen first >> file1 18 | echo and this should happen after that >> file2 19 | 20 | [win] 21 | 22 | test "$(git diff-tree --no-commit-id --name-status -r main)" = "M file2" && 23 | test "$(git diff-tree --no-commit-id --name-status -r main^)" = "M file1" 24 | -------------------------------------------------------------------------------- /levels/low-level/tree-create: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | After carefully building the index we want, it would be nice to save a permanent snapshot of it, right? 4 | 5 | This is what the second type of objects is for: trees! You can convert the index into a tree using 6 | 7 | git write-tree 8 | 9 | Try it! :) 10 | 11 | [congrats] 12 | 13 | Nice! 14 | 15 | Can you make a different tree? Modify the index, then call `git write-tree` again! 16 | 17 | [setup] 18 | 19 | echo "file 1" > file1 20 | echo "file 2" > file2 21 | echo "file 3" > file3 22 | git add . 23 | 24 | [setup goal] 25 | 26 | echo "file 1" > file1 27 | echo "file 2" > file2 28 | echo "file 3" > file3 29 | git add . 30 | 31 | git write-tree 32 | 33 | [win] 34 | 35 | git cat-file -p 21a638f28022064c1f1df20844278b494d197979 36 | -------------------------------------------------------------------------------- /levels/sandbox/three-commits: -------------------------------------------------------------------------------- 1 | title = Sandbox with three commits 2 | cards = checkout add reset-file checkout-file commit merge rebase 3 | 4 | [setup] 5 | 6 | echo "You wake up." > you 7 | git add . 8 | git commit -m "The beginning" 9 | 10 | echo "You drink coffee." >> you 11 | git commit -am "First things first" 12 | 13 | echo "You hear a knock on the door." >> you 14 | git commit -am "Who's there?" 15 | 16 | git branch not_main 17 | 18 | [description] 19 | 20 | Here's a sandbox you can play around in. 21 | 22 | You can use both the playing cards, as well as the terminal. This is a real Git terminal! Fun things to try: 23 | 24 | - Make a commit that merges three timelines together at once! 25 | - Create and delete some tags! 26 | - Make a timeline that's completely independent of the rest! 27 | -------------------------------------------------------------------------------- /cards/rm.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/rm.svg-5a801265a830b015b9f9fc338459295b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/rm.svg" 13 | dest_files=[ "res://.import/rm.svg-5a801265a830b015b9f9fc338459295b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /levels/shit-happens/reflog: -------------------------------------------------------------------------------- 1 | title = Go back to where you were before 2 | cards = checkout reflog 3 | 4 | [description] 5 | 6 | Say you were looking at something in the past, and then switched back to the main branch. 7 | 8 | But then, you got reaaally distracted, and after your lunch break, you can't remember on which commit in the past you were before. How can you find out? 9 | 10 | There's a convenient command that shows you all the places your HEAD did point to in the past: 11 | 12 | git reflog 13 | 14 | [setup] 15 | 16 | for i in {1..10}; do 17 | git commit --allow-empty -m $i 18 | git branch $i 19 | done 20 | git checkout 3 21 | git checkout main 22 | 23 | [win] 24 | 25 | # Find out where you've been before, and go back there! 26 | test "$(git rev-parse HEAD)" = "$(git rev-parse 3)" 27 | -------------------------------------------------------------------------------- /cards/add.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/add.svg-6661eafc7f78160aaf9088910aa4975e.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/add.svg" 13 | dest_files=[ "res://.import/add.svg-6661eafc7f78160aaf9088910aa4975e.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/init.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/init.svg-eff485ab83d8f4f1066179f2c1d47d00.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/init.svg" 13 | dest_files=[ "res://.import/init.svg-eff485ab83d8f4f1066179f2c1d47d00.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/pull.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/pull.svg-95e22c3c54690b0bb9cd734d753f8b7d.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/pull.svg" 13 | dest_files=[ "res://.import/pull.svg-95e22c3c54690b0bb9cd734d753f8b7d.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/push.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/push.svg-840b26c5fe96f5b0ec0b58d17f5f6ebf.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/push.svg" 13 | dest_files=[ "res://.import/push.svg-840b26c5fe96f5b0ec0b58d17f5f6ebf.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/show.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/show.svg-61ddddfa47953effe89cdfe1d04ec7bb.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/show.svg" 13 | dest_files=[ "res://.import/show.svg-61ddddfa47953effe89cdfe1d04ec7bb.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/new.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/new.svg-c0a58ae9dc5d259b4f3ccd8d20fb9b77.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/new.svg" 13 | dest_files=[ "res://.import/new.svg-c0a58ae9dc5d259b4f3ccd8d20fb9b77.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/ref.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/ref.svg-aa67d6a878f7082d8758514d5b9eac2a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/ref.svg" 13 | dest_files=[ "res://.import/ref.svg-aa67d6a878f7082d8758514d5b9eac2a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/blob.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/blob.svg-32176d6d674206dbc69554456f0ea720.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/blob.svg" 13 | dest_files=[ "res://.import/blob.svg-32176d6d674206dbc69554456f0ea720.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/head.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/head.svg-062450ee486d6e5774c7ccb0fbe8c145.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/head.svg" 13 | dest_files=[ "res://.import/head.svg-062450ee486d6e5774c7ccb0fbe8c145.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/ref.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/ref.svg-ba1531cc32a42d39c1bb1fbb28f745fb.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/ref.svg" 13 | dest_files=[ "res://.import/ref.svg-ba1531cc32a42d39c1bb1fbb28f745fb.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/tree.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/tree.svg-3883bff21a1825dc9656cd06abe52ea6.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/tree.svg" 13 | dest_files=[ "res://.import/tree.svg-3883bff21a1825dc9656cd06abe52ea6.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/clone.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/clone.svg-1209893141d884916317a29bbda3c7ce.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/clone.svg" 13 | dest_files=[ "res://.import/clone.svg-1209893141d884916317a29bbda3c7ce.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/fetch.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/fetch.svg-271468d8652718244abe885572c5dd75.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/fetch.svg" 13 | dest_files=[ "res://.import/fetch.svg-271468d8652718244abe885572c5dd75.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/merge.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/merge.svg-5ff4b4b93d3f613b5ff54414397df67c.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/merge.svg" 13 | dest_files=[ "res://.import/merge.svg-5ff4b4b93d3f613b5ff54414397df67c.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/reset.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/reset.svg-cabb0177c79ff01692a21316e28c91f8.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/reset.svg" 13 | dest_files=[ "res://.import/reset.svg-cabb0177c79ff01692a21316e28c91f8.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/file.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/file.svg-7ab17abba6f9f631c0704e62c7804548.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/file.svg" 13 | dest_files=[ "res://.import/file.svg-7ab17abba6f9f631c0704e62c7804548.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/head.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/head.svg-ae0e309e573aee7fb692167423462810.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/head.svg" 13 | dest_files=[ "res://.import/head.svg-ae0e309e573aee7fb692167423462810.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /levels/low-level/blob-remove: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | There's a simple command to remove all objects that are not referenced by anything: 4 | 5 | git prune 6 | 7 | Remove all blobs in this repository. 8 | 9 | [congrats] 10 | 11 | Generally, `git prune` will be useful if you want to clean up some objects you made. 12 | 13 | Alternatively, you can also click the "Reload" button to restart a level. 14 | 15 | [setup] 16 | 17 | echo "My master password is a1b2c3d4e5" | git hash-object -w --stdin 18 | echo "This blob really should not exist" | git hash-object -w --stdin 19 | echo "This is a virus" | git hash-object -w --stdin 20 | 21 | [setup goal] 22 | 23 | [win] 24 | 25 | OBJECT_COUNT=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | wc -l) 26 | 27 | test "$OBJECT_COUNT" -eq 0 28 | -------------------------------------------------------------------------------- /nodes/head1.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/head1.svg-cb13de7d91f834684f5d15e9625a65b4.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/head1.svg" 13 | dest_files=[ "res://.import/head1.svg-cb13de7d91f834684f5d15e9625a65b4.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/head2.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/head2.svg-e772342989ef4f76b9420a365c0e5f01.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/head2.svg" 13 | dest_files=[ "res://.import/head2.svg-e772342989ef4f76b9420a365c0e5f01.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/head3.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/head3.svg-caa888292307a12254c45d043d73a543.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/head3.svg" 13 | dest_files=[ "res://.import/head3.svg-caa888292307a12254c45d043d73a543.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /resources/ref.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/ref.svg-929118b775267deea6efa13f0380523f.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resources/ref.svg" 13 | dest_files=[ "res://.import/ref.svg-929118b775267deea6efa13f0380523f.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/branch.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/branch.svg-eb8cc6ed28116b245fe8b2b88935589c.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/branch.svg" 13 | dest_files=[ "res://.import/branch.svg-eb8cc6ed28116b245fe8b2b88935589c.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/commit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/commit.svg-81b72e6d51b3c0f35ba9ffe75a779e27.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/commit.svg" 13 | dest_files=[ "res://.import/commit.svg-81b72e6d51b3c0f35ba9ffe75a779e27.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/rebase.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/rebase.svg-52003a9e4e854e9c851cf2a6352e6205.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/rebase.svg" 13 | dest_files=[ "res://.import/rebase.svg-52003a9e4e854e9c851cf2a6352e6205.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/reflog.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/reflog.svg-58811602af0895f986979b78b804f3d0.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/reflog.svg" 13 | dest_files=[ "res://.import/reflog.svg-58811602af0895f986979b78b804f3d0.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/revert.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/revert.svg-4babc742ff6ab2ab39bda112ad1f8738.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/revert.svg" 13 | dest_files=[ "res://.import/revert.svg-4babc742ff6ab2ab39bda112ad1f8738.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/commit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/commit.svg-6ec3c9ce6fe011df527371878fcfe9b8.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/commit.svg" 13 | dest_files=[ "res://.import/commit.svg-6ec3c9ce6fe011df527371878fcfe9b8.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/remote.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/remote.svg-7efe0b92e90a2ce57bc047939daa71f2.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/remote.svg" 13 | dest_files=[ "res://.import/remote.svg-7efe0b92e90a2ce57bc047939daa71f2.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/string.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/string.svg-c6f6a214f649f47a30ca1483c0b48d14.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/string.svg" 13 | dest_files=[ "res://.import/string.svg-c6f6a214f649f47a30ca1483c0b48d14.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/commit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/commit.svg-e84b6d5f0dcf8ee449788a8c35e799e5.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/commit.svg" 13 | dest_files=[ "res://.import/commit.svg-e84b6d5f0dcf8ee449788a8c35e799e5.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/removed.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/removed.svg-4418ffd24001968a3d41f1a4e493e553.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/removed.svg" 13 | dest_files=[ "res://.import/removed.svg-4418ffd24001968a3d41f1a4e493e553.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /levels/unused/split: -------------------------------------------------------------------------------- 1 | title = Split a commit! 2 | cards = checkout commit reset-hard reset add rebase-interactive rebase-continue show 3 | 4 | [description] 5 | 6 | Here, both changes happened in one commit! Split them to be in two commits instead. 7 | 8 | [setup] 9 | 10 | echo something > file1 11 | echo something else > file2 12 | git add . 13 | git commit -m "Initial commit" 14 | 15 | echo this should happen first >> file1 16 | echo and this should happen after that >> file2 17 | git commit -am "Both together" 18 | 19 | echo this is some other change >> file1 20 | echo this is some other change >> file2 21 | git commit -am "Something else" 22 | 23 | [win] 24 | 25 | test "$(git diff-tree --no-commit-id --name-status -r main^)" = "M file2" && 26 | test "$(git diff-tree --no-commit-id --name-status -r main~2)" = "M file1" 27 | -------------------------------------------------------------------------------- /cards/checkout.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/checkout.svg-3143aa02cfd5a2acddb95b1a75f60fe5.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/checkout.svg" 13 | dest_files=[ "res://.import/checkout.svg-3143aa02cfd5a2acddb95b1a75f60fe5.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/commit-a.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/commit-a.svg-74d4ed1a5b085effaa193222437dac11.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/commit-a.svg" 13 | dest_files=[ "res://.import/commit-a.svg-74d4ed1a5b085effaa193222437dac11.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/file-copy.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/file-copy.svg-b09df70c6492dae6c3a6ed1edf7b616e.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/file-copy.svg" 13 | dest_files=[ "res://.import/file-copy.svg-b09df70c6492dae6c3a6ed1edf7b616e.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/file-new.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/file-new.svg-241a480ced43a4be648eaa4a084bf605.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/file-new.svg" 13 | dest_files=[ "res://.import/file-new.svg-241a480ced43a4be648eaa4a084bf605.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/checkout.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/checkout.svg-9d9f3c5ff5668245d8bdf2826f9e46f6.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/checkout.svg" 13 | dest_files=[ "res://.import/checkout.svg-9d9f3c5ff5668245d8bdf2826f9e46f6.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/conflict.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/conflict.svg-058e8cc11300c63d34f3f66889543fa0.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/conflict.svg" 13 | dest_files=[ "res://.import/conflict.svg-058e8cc11300c63d34f3f66889543fa0.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/modified.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/modified.svg-bb49413a813e3dafb126da7aeec82740.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/modified.svg" 13 | dest_files=[ "res://.import/modified.svg-bb49413a813e3dafb126da7aeec82740.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /nodes/document.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/document.svg-b9fdc82a659cb4c40c5c95e09b615070.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://nodes/document.svg" 13 | dest_files=[ "res://.import/document.svg-b9fdc82a659cb4c40c5c95e09b615070.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/bisect-bad.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/bisect-bad.svg-62f0f685f6f91720307692ecdfa16854.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/bisect-bad.svg" 13 | dest_files=[ "res://.import/bisect-bad.svg-62f0f685f6f91720307692ecdfa16854.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/reset-file.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/reset-file.svg-15bbadedca7014f817d5e2a521d1d66b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/reset-file.svg" 13 | dest_files=[ "res://.import/reset-file.svg-15bbadedca7014f817d5e2a521d1d66b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/reset-hard.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/reset-hard.svg-babcffb163a4c5606782673185f034cd.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/reset-hard.svg" 13 | dest_files=[ "res://.import/reset-hard.svg-babcffb163a4c5606782673185f034cd.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/cli-badge.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/cli-badge.svg-d681319a61408aabc3551843085f21db.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/cli-badge.svg" 13 | dest_files=[ "res://.import/cli-badge.svg-d681319a61408aabc3551843085f21db.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/oh-my-git.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/oh-my-git.png-03c5c1a10d0db12a4f28d5f579674ccb.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/oh-my-git.png" 13 | dest_files=[ "res://.import/oh-my-git.png-03c5c1a10d0db12a4f28d5f579674ccb.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/untracked.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/untracked.svg-57d41d42d25d92c5e2dec38f8769d5ed.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://images/untracked.svg" 13 | dest_files=[ "res://.import/untracked.svg-57d41d42d25d92c5e2dec38f8769d5ed.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /levels/remotes/problems: -------------------------------------------------------------------------------- 1 | title = Problems 2 | cards = checkout add pull push commit-auto merge 3 | 4 | [description] 5 | 6 | Both you and your friend have been working on the file, and want to sync up! 7 | 8 | [setup yours] 9 | 10 | echo "The bike shed should be ???" > file 11 | git add . 12 | git commit -m "initial" 13 | 14 | git push -u friend main 15 | 16 | echo "The bike shed should be green" > file 17 | 18 | [setup friend] 19 | 20 | git checkout main 21 | 22 | echo "The bike shed should be blue" > file 23 | git commit -a -m "friends version" 24 | 25 | [win] 26 | 27 | # Commit your local changes. 28 | test "$(git status -s)" = "" 29 | 30 | [win friend] 31 | 32 | # Look at your friend's suggestion, make a compromise, and push it back. 33 | git rev-parse main^ && test "$(git rev-parse main^1^)" = "$(git rev-parse main^2^)" 34 | -------------------------------------------------------------------------------- /levels/unused/files-move: -------------------------------------------------------------------------------- 1 | title = No sleep required 2 | cards = file-new file-delete file-rename 3 | 4 | [description] 5 | 6 | Actually, you decide that you don't need any sleep. 7 | 8 | Because of that, you won't require a bed, and can build some other piece of furniture from the wood! 9 | 10 | 11 | [setup] 12 | 13 | echo A yellow cupboard with lots of drawers. > cupboard 14 | echo A really big yellow shelf. > shelf 15 | echo A comfortable, yellow bed with yellow cushions. > bed 16 | 17 | [win] 18 | 19 | # Rename the bed into something else, and give it a new description! 20 | NUM_FILES="$(ls | wc -l)" 21 | ! test -f bed && test "$NUM_FILES" -ge 3 && ! grep -r "yellow bed" . 22 | 23 | [congrats] 24 | 25 | Neat! It even still looks a bit comfortable! 26 | 27 | You head out, eager for your first lesson at time travel school! 28 | -------------------------------------------------------------------------------- /cards/bisect-good.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/bisect-good.svg-824dbd02cadbc866adc1cd549a996df6.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/bisect-good.svg" 13 | dest_files=[ "res://.import/bisect-good.svg-824dbd02cadbc866adc1cd549a996df6.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/cherry-pick.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/cherry-pick.svg-b916c94f654b5af97f4a3dff68693e53.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/cherry-pick.svg" 13 | dest_files=[ "res://.import/cherry-pick.svg-b916c94f654b5af97f4a3dff68693e53.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/commit-auto.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/commit-auto.svg-3602bf004267ccb8cb830fe4f194c90f.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/commit-auto.svg" 13 | dest_files=[ "res://.import/commit-auto.svg-3602bf004267ccb8cb830fe4f194c90f.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/file-delete.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/file-delete.svg-1f07b0a0ab33737c2bd1187d541f2bf2.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/file-delete.svg" 13 | dest_files=[ "res://.import/file-delete.svg-1f07b0a0ab33737c2bd1187d541f2bf2.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/file-rename.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/file-rename.svg-907de059f220a9b0b45bd9779f1d25f1.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/file-rename.svg" 13 | dest_files=[ "res://.import/file-rename.svg-907de059f220a9b0b45bd9779f1d25f1.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/merge-abort.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/merge-abort.svg-5ac15dd7f08f3cc4730cac5d99fe69b0.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/merge-abort.svg" 13 | dest_files=[ "res://.import/merge-abort.svg-5ac15dd7f08f3cc4730cac5d99fe69b0.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/bisect-start.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/bisect-start.svg-1e5a904f73da711887aa389242772e6d.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/bisect-start.svg" 13 | dest_files=[ "res://.import/bisect-start.svg-1e5a904f73da711887aa389242772e6d.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/branch-delete.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/branch-delete.svg-fd5a2026e417be4d8604f1634f4cb647.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/branch-delete.svg" 13 | dest_files=[ "res://.import/branch-delete.svg-fd5a2026e417be4d8604f1634f4cb647.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/checkout-file.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/checkout-file.svg-33b29020ffdca452c2ad5907a0f12ad0.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/checkout-file.svg" 13 | dest_files=[ "res://.import/checkout-file.svg-33b29020ffdca452c2ad5907a0f12ad0.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/checkout-from.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/checkout-from.svg-1440fff912552ca12f17cc68295ba9a9.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/checkout-from.svg" 13 | dest_files=[ "res://.import/checkout-from.svg-1440fff912552ca12f17cc68295ba9a9.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /cards/rebase-continue.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/rebase-continue.svg-ee156ef9d7f2f1525bf2f752ce95229e.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/rebase-continue.svg" 13 | dest_files=[ "res://.import/rebase-continue.svg-ee156ef9d7f2f1525bf2f752ce95229e.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /levels/unused/checkout: -------------------------------------------------------------------------------- 1 | title = Getting the last version 2 | cards = checkout-file 3 | 4 | [description] 5 | 6 | You've been working on your essay for a while. But - ughh! Now your cat walks over your keyboard and "helps you", so now it's all messed up! :/ 7 | 8 | But Git is here to help! To discard all changes your cat made, and go back to the version in the last commit, use `checkout`! 9 | 10 | [setup] 11 | 12 | echo "A" >> essay.txt 13 | git add . 14 | git commit -m "Initial commit" 15 | 16 | echo "B" >> essay.txt 17 | git commit -a -m "Improved version" 18 | 19 | echo "C" >> essay.txt 20 | git commit -a -m "Even better version" 21 | 22 | echo "D" >> essay.txt 23 | git commit -a -m "Marvelous version" 24 | 25 | echo "blarg 26 | blaaaargh" > essay.txt 27 | 28 | [win] 29 | 30 | # Restore the version from the last commit. 31 | cat essay.txt | grep D 32 | -------------------------------------------------------------------------------- /levels/unused/remotes-add: -------------------------------------------------------------------------------- 1 | title = Adding a remote 2 | cards = checkout 3 | 4 | [description] 5 | 6 | Let's work together with others! Your friend has their own repo at the URL `../friend` - you can add it using 7 | 8 | git remote add [name] [URL] 9 | 10 | where `[name]` is an arbitrary, short name you pick for the remote. 11 | 12 | When you've done that, you can get all commits from that remote using 13 | 14 | git pull friend 15 | 16 | There's a letter for you! 17 | 18 | [setup] 19 | 20 | git remote remove friend 21 | 22 | [setup friend] 23 | 24 | echo "I'm really committed to our friendship! <3" > love_letter 25 | git add . 26 | git commit -m "Write a letter" 27 | 28 | [win] 29 | 30 | # Add a remote that points to ../friend. 31 | git remote -v | grep '../friend' 32 | # Pull from the remote. 33 | git show HEAD:love_letter | grep committed 34 | -------------------------------------------------------------------------------- /cards/rebase-interactive.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/rebase-interactive.svg-9bd40b291a9d619bf58504a61e1cbcf7.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://cards/rebase-interactive.svg" 13 | dest_files=[ "res://.import/rebase-interactive.svg-9bd40b291a9d619bf58504a61e1cbcf7.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /levels/unused/restore: -------------------------------------------------------------------------------- 1 | title = Looking into the past 2 | cards = checkout-from 3 | 4 | [description] 5 | 6 | You've been working on your essay for a while. But you're not happy with the changes you've made recently. You want to go back to the version called "Best version"! 7 | 8 | No problem, you can use the `checkout` card to restore your essay from an older commit! 9 | 10 | [setup] 11 | 12 | echo "Initial version" > essay.txt 13 | git add . 14 | git commit -m "Initial commit" 15 | 16 | echo "Improved version" > essay.txt 17 | git commit -a -m "Improved version" 18 | 19 | echo "Best version" > essay.txt 20 | git commit -a -m "Best version" 21 | 22 | echo "Less-good version" > essay.txt 23 | git commit -a -m "Less-good version" 24 | 25 | [win] 26 | 27 | # For nostalgic reasons, restore the very first backup you made! 28 | diff essay.txt <(echo "Best version") 29 | -------------------------------------------------------------------------------- /levels/low-level/puzzle-precious-blob: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Create two trees pointing to the same blob! 4 | 5 | [setup] 6 | 7 | [setup goal] 8 | 9 | BLOB=$(echo "I am precious" | git hash-object -w --stdin) 10 | git update-index --add --cacheinfo 100644,$BLOB,a 11 | git write-tree 12 | git update-index --force-remove a 13 | git update-index --add --cacheinfo 100644,$BLOB,b 14 | git write-tree 15 | git update-index --force-remove b 16 | 17 | [win] 18 | 19 | TREES=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep tree | cut -f1 -d" ") 20 | 21 | ALL_TREE_CHILDREN=$(for TREE in $TREES; do 22 | git cat-file -p $TREE | cut -f1 | cut -f3 -d" " 23 | done) 24 | 25 | NUMBER_OF_CHILDREN=$(echo "$ALL_TREE_CHILDREN" | wc -l) 26 | UNIQUE_CHILDREN=$(echo "$ALL_TREE_CHILDREN" | sort -u | wc -l) 27 | 28 | test "$NUMBER_OF_CHILDREN" -gt "$UNIQUE_CHILDREN" 29 | -------------------------------------------------------------------------------- /levels/low-level/welcome: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | This is prototype #1 for the Git learning game by @bleeptrack and @blinry. Thanks for checking it out! <3 4 | 5 | You can interact with the repository labelled "yours" by typing Bash commands in the terminal below! The visualization will show you its internal status. 6 | 7 | Let's get started by initializing an empty Git repository in the current directory by typing: 8 | 9 | git init 10 | 11 | [congrats] 12 | 13 | Well done! 14 | 15 | An empty Git repository is... well, quite empty. The only thing that always exists is a reference called "HEAD" - we'll learn what that is later! 16 | 17 | But first, let's look at some basics! 18 | 19 | (Click "Next Level" as soon as you're ready!) 20 | 21 | [setup] 22 | 23 | rm -rf .git 24 | 25 | [setup goal] 26 | 27 | rm -rf .git 28 | 29 | git init 30 | 31 | [win] 32 | 33 | test -d .git 34 | -------------------------------------------------------------------------------- /levels/tags/remove-tag: -------------------------------------------------------------------------------- 1 | title = Removing tags 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | You added way too many tags? No prob! Delete them with 7 | 8 | git tag -d 9 | 10 | Remove all tags in this repo! 11 | 12 | --- 13 | tipp1 14 | --- 15 | tipp2 16 | --- 17 | tipp3 18 | 19 | [setup] 20 | 21 | echo "event 1" > feature-list 22 | 23 | git add . 24 | git commit -m "Adding feature 1" 25 | 26 | echo "event 2" >> feature-list 27 | 28 | git add . 29 | git commit -m "Adding feature 2" 30 | 31 | echo "event 3" >> feature-list 32 | 33 | git add . 34 | git commit -m "Adding feature 3" 35 | 36 | git tag v1 HEAD~2 37 | git tag v2 HEAD~1 38 | git tag v3 39 | 40 | git checkout --detach main 41 | 42 | [win] 43 | 44 | # Did you remove all tags? 45 | test "$(git tag -l | wc -l)" -eq 0 46 | 47 | [actions] 48 | 49 | 50 | 51 | [congrats] 52 | 53 | Well done :) 54 | -------------------------------------------------------------------------------- /levels/stash/stash: -------------------------------------------------------------------------------- 1 | title = Stashing 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | You will encounter situations in which you are working on your project but you need to 7 | put your current changes aside temporarily. To do so, you can use the stash function. Use 8 | git stash push 9 | to add your current changes to the stash stack. 10 | 11 | --- 12 | tipp1 13 | --- 14 | tipp2 15 | --- 16 | tipp3 17 | 18 | [setup] 19 | 20 | echo "Apple Pie:" > recipe 21 | 22 | git add . 23 | git commit -m "creating a recipe" 24 | 25 | echo "- 4 Apples" >> recipe 26 | 27 | git add . 28 | git commit -m "Adding ingredients" 29 | 30 | echo "- 500g Flour" >> recipe 31 | 32 | git checkout main 33 | 34 | [win] 35 | 36 | # Did you stash the current changes? 37 | test "$(git stash list | wc -l)" -ge 1 38 | 39 | [actions] 40 | 41 | 42 | 43 | [congrats] 44 | 45 | Nice stash you got there! :) 46 | -------------------------------------------------------------------------------- /scenes/player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [sub_resource type="GDScript" id=1] 4 | script/source = "extends KinematicBody2D 5 | 6 | export var speed = 800 7 | 8 | func _ready(): 9 | pass 10 | 11 | func _process(delta): 12 | var right = Input.get_action_strength(\"right\") - Input.get_action_strength(\"left\") 13 | var down = Input.get_action_strength(\"down\") - Input.get_action_strength(\"up\") 14 | move_and_slide(Vector2(right, down).normalized()*speed) 15 | " 16 | 17 | [sub_resource type="RectangleShape2D" id=2] 18 | extents = Vector2( 50, 50 ) 19 | 20 | [node name="Player" type="KinematicBody2D"] 21 | script = SubResource( 1 ) 22 | 23 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 24 | shape = SubResource( 2 ) 25 | 26 | [node name="Rect" type="ColorRect" parent="."] 27 | margin_left = -50.0 28 | margin_top = -50.0 29 | margin_right = 50.0 30 | margin_bottom = 50.0 31 | -------------------------------------------------------------------------------- /levels/low-level/index-update: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Instead of removing an entry from the index and adding one with the same name, you can also directly update that entry! 4 | 5 | Put the content you want in a file with a matching name, and then run 6 | 7 | git update-index 8 | 9 | This will create a new blob, and update the hash of the entry to that blob. 10 | 11 | Update an entry in the index! 12 | 13 | [setup] 14 | 15 | echo "file 1" > file1 16 | echo "file 2" > file2 17 | echo "file 3" > file3 18 | git add . 19 | 20 | [setup goal] 21 | 22 | echo "file 1" > file1 23 | echo "file 2" > file2 24 | echo "file 3" > file3 25 | git add . 26 | 27 | echo "new content" > file1 28 | git update-index file1 29 | 30 | [win] 31 | 32 | # This is not really a good test for the winning condition... 33 | test "$(git ls-files -s | git hash-object --stdin)" != "10c4b28623e7e44e09f5a596450a50ab7ac31fbe" -a "$(git ls-files | wc -l)" -eq 3 34 | -------------------------------------------------------------------------------- /levels/unused/fetch: -------------------------------------------------------------------------------- 1 | title = Fetching from remotes 2 | cards = checkout fetch commit-auto 3 | 4 | [description] 5 | 6 | Here, you already have two remotes configured! You can list them using `git remote`. 7 | 8 | Fetch from both, and look at the suggestions. 9 | 10 | Then, make a new commit on top of your original one that introduces a compromise. 11 | 12 | [setup] 13 | 14 | echo "The bikeshed should be ???" > proposal 15 | git add . 16 | git commit -m "What do you think?" 17 | 18 | [setup friend1] 19 | 20 | git pull yours main 21 | echo "The bikeshed should be green" > proposal 22 | git commit -am "Green" 23 | 24 | [setup friend2] 25 | 26 | git pull yours main 27 | echo "The bikeshed should be blue" > proposal 28 | git commit -am "Blue" 29 | 30 | [win] 31 | 32 | # Your proposal is acceptable for friend1. 33 | git show main:proposal | git grep green 34 | # Your proposal is acceptable for friend2. 35 | git show main:proposal | git grep blue 36 | -------------------------------------------------------------------------------- /levels/low-level/commit-parents: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | When using the commit-tree command, you can optionally specify a parent: 4 | 5 | git commit-tree -m "Description" -p 6 | 7 | Make a string of three commits! 8 | 9 | Hint: You'll need a tree object. What could be the easiest way to obtain one? 10 | 11 | [setup] 12 | 13 | [setup goal] 14 | 15 | git write-tree 16 | FIRST_COMMIT=$(git commit-tree 4b82 -m 'First commit :O') 17 | SECOND_COMMIT=$(git commit-tree 4b82 -p $FIRST_COMMIT -m 'Second commit :D') 18 | THIRD_COMMIT=$(git commit-tree 4b82 -p $SECOND_COMMIT -m 'Third commit \o/') 19 | 20 | [win] 21 | 22 | COMMITS=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep commit | cut -f1 -d" ") 23 | 24 | for COMMIT in $COMMITS; do 25 | echo a commit named $COMMIT 26 | if [ $(git rev-list $COMMIT | wc -l) -ge 3 ]; then 27 | return 0 28 | fi 29 | done 30 | 31 | return 1 32 | -------------------------------------------------------------------------------- /levels/stash/stash-pop: -------------------------------------------------------------------------------- 1 | title = Pop from Stash 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | When you stashed your changes and you want to apply them back to your current working directory, you can use 7 | git stash pop 8 | This will remove the changes from the stash stack. If you also want to keep the changes on the stash stack, use 9 | git stash apply 10 | 11 | --- 12 | tipp1 13 | --- 14 | tipp2 15 | --- 16 | tipp3 17 | 18 | [setup] 19 | 20 | echo "Apple Pie:" > recipe 21 | 22 | git add . 23 | git commit -m "creating a recipe" 24 | 25 | echo "- 4 Apples" >> recipe 26 | 27 | git add . 28 | git commit -m "Adding ingredients" 29 | 30 | echo "- 500g Flour" >> recipe 31 | 32 | git stash push 33 | git checkout main 34 | 35 | [win] 36 | 37 | # Did you pop the changes from the stash stack? 38 | test "$(git stash list | wc -l)" -eq 0 39 | 40 | [actions] 41 | 42 | 43 | 44 | [congrats] 45 | 46 | Yay, you got your changes back! :) 47 | -------------------------------------------------------------------------------- /levels/tags/add-tag-later: -------------------------------------------------------------------------------- 1 | title = Tagging later 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | But what happens if you forgot to tag your current commit? 7 | No Prob! You can also tag older commits via 8 | 9 | git tag 10 | 11 | Tag the commit "Adding feature 2" with the name "v1"! 12 | 13 | --- 14 | tipp1 15 | --- 16 | tipp2 17 | --- 18 | tipp3 19 | 20 | [setup] 21 | 22 | echo "event 1" > feature-list 23 | 24 | git add . 25 | git commit -m "Adding feature 1" 26 | 27 | echo "event 2" >> feature-list 28 | 29 | git add . 30 | git commit -m "Adding feature 2" 31 | 32 | echo "event 3" >> feature-list 33 | 34 | git add . 35 | git commit -m "Adding feature 3" 36 | 37 | git checkout --detach main 38 | 39 | [win] 40 | 41 | # Did you create a new tag? 42 | test "$(git show v1 -s --format=%h)" = "$(git show HEAD~1 -s --format=%h)" 43 | 44 | [actions] 45 | 46 | 47 | 48 | [congrats] 49 | 50 | Well done :) 51 | -------------------------------------------------------------------------------- /levels/low-level/puzzle-apocalypse: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Delete all objects in this repository using git commands only! 4 | 5 | Useful commands: 6 | 7 | git prune 8 | git reflog expire 9 | 10 | [setup] 11 | 12 | echo foo > foo 13 | BLOB=$(git hash-object -w foo) 14 | echo bar > bar 15 | git add . 16 | git commit -m "Initial commit" 17 | echo blabber >> bar 18 | git commit -a -m "Second commit" 19 | git update-ref refs/important HEAD 20 | git update-ref refs/interesting "$BLOB" 21 | 22 | [setup goal] 23 | 24 | echo foo > foo 25 | BLOB=$(git hash-object -w foo) 26 | echo bar > bar 27 | git add . 28 | git commit -m "Initial commit" 29 | echo blabber >> bar 30 | git commit -a -m "Second commit" 31 | git update-ref refs/important HEAD 32 | git update-ref refs/interesting "$BLOB" 33 | 34 | TREE=$(git mktree) 35 | git read-tree $TREE 36 | rm -rf .git/refs/* 37 | rm -rf .git/objects/* 38 | 39 | [win] 40 | 41 | test "$(git cat-file --batch-check --batch-all-objects | wc -l)" -eq 0 42 | -------------------------------------------------------------------------------- /levels/tags/add-tag: -------------------------------------------------------------------------------- 1 | title = Creating tags 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | Some of your commits may be special commits. Maybe you reached a milestone or a new version number. 7 | 8 | You can mark these commits with a special flag called 'tag'. 9 | 10 | Write 11 | 12 | git tag 13 | 14 | to tag your commit. 15 | 16 | --- 17 | tipp1 18 | --- 19 | tipp2 20 | --- 21 | tipp3 22 | 23 | [setup] 24 | 25 | echo "event 1" > feature-list 26 | 27 | git add . 28 | git commit -m "Adding feature 1" 29 | 30 | echo "event 2" >> feature-list 31 | 32 | git add . 33 | git commit -m "Adding feature 2" 34 | 35 | echo "event 3" >> feature-list 36 | 37 | git add . 38 | git commit -m "Adding feature 3" 39 | 40 | git checkout --detach main 41 | 42 | [win] 43 | 44 | # Did you create a new tag? 45 | test "$(git tag -l | wc -l)" -ge 1 46 | 47 | [actions] 48 | 49 | 50 | 51 | [congrats] 52 | 53 | Nice! You tagged your first commit :) 54 | -------------------------------------------------------------------------------- /levels/intro/risky: -------------------------------------------------------------------------------- 1 | title = Living dangerously 2 | cards = 3 | 4 | [description] 5 | 6 | So you have decided to apply for time travel school, to learn how to use this time machine called "Git"! 7 | 8 | How exciting! 9 | 10 | You're almost done with the paperwork! You just need to fill in one more reason why you want to learn Git. 11 | 12 | [congrats] 13 | 14 | Suddenly, your cat jumps on the table, snatches away the form, and runs away! Oh no. All your hard work, gone! 15 | 16 | You clearly need a better solution. 17 | 18 | (Click "Next Level" as soon as you're ready!) 19 | 20 | [setup] 21 | 22 | rm -rf .git 23 | 24 | echo "~ Why I want to learn Git ~ 25 | 26 | - So that I can undo mistakes 27 | - To track my projects over time" >> form.txt 28 | 29 | [actions] 30 | 31 | test "$(cat form.txt | wc -l )" -ge 5 && echo "(Has been stolen by your cat.) 32 | 33 | 34 | 35 | 36 | 37 | " > form.txt 38 | 39 | [win] 40 | 41 | # Add another line to form.txt! 42 | test "$(cat form.txt | wc -l )" -ge 5 43 | -------------------------------------------------------------------------------- /levels/low-level/commit-create: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | So a tree describes a directory structure at a specific point in time. 4 | 5 | It would be nice if we could remember when that state existed, and who authored it, right? 6 | 7 | Enter: commits. They are objects that point to a tree and contain some additional metadata. You can create a commit using 8 | 9 | git commit-tree -m "Description of your commit" 10 | 11 | Make a commit from the tree in this repository! 12 | 13 | [setup] 14 | 15 | touch empty_file 16 | git add . 17 | git write-tree 18 | 19 | rm empty_file 20 | git update-index --remove empty_file 21 | 22 | [setup goal] 23 | 24 | touch empty_file 25 | git add . 26 | git write-tree 27 | 28 | rm empty_file 29 | git update-index --remove empty_file 30 | 31 | git commit-tree 3185 -m 'Clever commit message' 32 | 33 | [win] 34 | 35 | COMMIT_COUNT=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep commit | wc -l) 36 | 37 | test "$COMMIT_COUNT" -gt 0 38 | -------------------------------------------------------------------------------- /levels/files/files-delete: -------------------------------------------------------------------------------- 1 | title = Unexpected Roommates 2 | cards = file-delete 3 | 4 | [description] 5 | 6 | The first day at Time Travel School comes to an end and you receive the key to your room. 7 | Full of excitement you open the door just to find... spider webs! Spider webs everywhere! 8 | 9 | Remove all the spider webs you can find with the remove card! 10 | 11 | [cli] 12 | 13 | On the command line, you can easily delete all files ending in -web using this command: 14 | 15 | rm *web 16 | 17 | [setup] 18 | 19 | echo A tiny spider web is next to your window. > tiny_web 20 | echo A big spider web sticks above your bed. > big_web 21 | echo A cozy bed. > bed 22 | echo An extra thick spider web is right beside your door. > thick_web 23 | 24 | [win] 25 | 26 | # Remove all spider webs. 27 | ! ls | grep thick_web && 28 | ! ls | grep big_web && 29 | ! ls | grep tiny_web 30 | 31 | # But make sure you keep your bed! 32 | ls | grep bed 33 | 34 | [congrats] 35 | 36 | Your room looks now very tidy and cozy! Time to unpack your stuff! 37 | -------------------------------------------------------------------------------- /levels/files/files-add: -------------------------------------------------------------------------------- 1 | title = Interior design 2 | cards = file-new file-delete 3 | 4 | [description] 5 | 6 | Now that your room looks tidy, you can start to unpack your stuff. You brought two new pieces of furniture with you and with a bright smile, 7 | you see that their colors match the color of your bed! 8 | 9 | Build up your two pieces of furniture by playing the touch card. 10 | Then name your furniture - you can choose whatever you like. 11 | 12 | Make sure the colors match! You can find the bed's color in its description. 13 | Don't forget to add a color and description to your new furnitures, too! 14 | 15 | [setup] 16 | 17 | echo A yellow cozy bed. > bed 18 | 19 | [win] 20 | 21 | # Add two more pieces of furniture 22 | NUM_FILES="$(ls | wc -l)" 23 | test "$NUM_FILES" -ge 3 24 | 25 | # Make sure the colors match your bed's color. 26 | NUM_FILES="$(ls | wc -l)" 27 | YELLOW_FILES="$(grep -li yellow * | wc -l)" 28 | test "$NUM_FILES" -ge 2 && test "$YELLOW_FILES" = "$NUM_FILES" 29 | 30 | [congrats] 31 | 32 | Don't you immediately feel more at home? 33 | -------------------------------------------------------------------------------- /levels/intro/commit: -------------------------------------------------------------------------------- 1 | title = Your first commit 2 | cards = commit-auto 3 | 4 | [description] 5 | 6 | You can use your time machine to make snapshots of objects around you! Here, let's practice this! 7 | 8 | (Your teacher pours some water into a glass.) 9 | 10 | [cli] 11 | 12 | Again, instead of using the card, you can also type the commands which are printed on it into the black terminal below! 13 | 14 | This is totally optional! But this will be a super useful skill in the real world - and it will give you a sparkling golden badge! :) 15 | 16 | [setup] 17 | 18 | echo "The glass is full of water." > glass 19 | 20 | [win] 21 | 22 | # Make a snapshot of the glass (a "commit") 23 | git rev-parse HEAD 24 | 25 | # Change the contents of the glass! 26 | ! test "$(cat glass)" = "The glass is full of water." 27 | 28 | # And make a second commit! 29 | git rev-parse HEAD^ && ! test "$(git show main:glass)" = "The glass is full of water." 30 | 31 | [congrats] 32 | 33 | Nice! You can try making some additional commits. When you feel comfortable, click on "Next Level". 34 | -------------------------------------------------------------------------------- /levels/stash/stash-branch: -------------------------------------------------------------------------------- 1 | title = Branch from stash 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | If you want to keep your changes but they don't belong to the main branch, you can easily 7 | create a new branch from your stashed changes. Just use 8 | git stash branch 9 | If you just want to use the latest stash entry, you can leave the option empty. 10 | 11 | Create a new branch from the stashed changes! 12 | 13 | --- 14 | tipp1 15 | --- 16 | tipp2 17 | --- 18 | tipp3 19 | 20 | [setup] 21 | 22 | echo "Apple Pie:" > recipe 23 | 24 | git add . 25 | git commit -m "creating a recipe" 26 | 27 | echo "- 4 Apples" >> recipe 28 | 29 | git add . 30 | git commit -m "Adding ingredients" 31 | 32 | echo "- 500g Flour" >> recipe 33 | git stash push 34 | 35 | git checkout main 36 | 37 | [win] 38 | 39 | # Did you create a new branch from the stashed changes? 40 | test "$(git branch --list| wc -l)" -ge 2 41 | 42 | [actions] 43 | 44 | 45 | 46 | [congrats] 47 | 48 | Stashed changes are in a new branch! :) 49 | -------------------------------------------------------------------------------- /levels/low-level/ref-remove: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | And finally, to delete a ref, use 4 | 5 | git update-ref -d refs/ 6 | 7 | Delete all refs! :P (Well, except for HEAD. HEAD is special.) 8 | 9 | [setup] 10 | 11 | echo hello > hello 12 | echo world > world 13 | BLOB1=$(git hash-object -w hello) 14 | BLOB2=$(git hash-object -w world) 15 | git add . 16 | TREE=$(git write-tree) 17 | COMMIT=$(git commit-tree $TREE -m "Initial commit") 18 | 19 | git update-ref refs/best_blob_ever "$BLOB1" 20 | git update-ref refs/beautiful_commit "$COMMIT" 21 | 22 | [setup goal] 23 | 24 | echo hello > hello 25 | echo world > world 26 | BLOB1=$(git hash-object -w hello) 27 | BLOB2=$(git hash-object -w world) 28 | git add . 29 | TREE=$(git write-tree) 30 | COMMIT=$(git commit-tree $TREE -m "Initial commit") 31 | 32 | git update-ref refs/best_blob_ever "$BLOB1" 33 | git update-ref refs/beautiful_commit "$COMMIT" 34 | 35 | for REF in $(git for-each-ref --format='%(refname)'); do 36 | git update-ref -d "$REF" 37 | done 38 | 39 | [win] 40 | 41 | test "$(git show-ref | wc -l)" -eq 0 42 | -------------------------------------------------------------------------------- /scenes/sandbox.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | func _ready(): 4 | var path = null 5 | 6 | var args = helpers.parse_args() 7 | if args.has("sandbox"): 8 | if args["sandbox"] is String: 9 | if args["sandbox"] == ".": 10 | args["sandbox"] = OS.get_environment("PWD") 11 | var dir = Directory.new() 12 | if dir.dir_exists(args["sandbox"]): 13 | path = args["sandbox"] 14 | else: 15 | helpers.crash("Directory %s does not exist" % args["sandbox"]) 16 | 17 | if path == null: 18 | path = game.tmp_prefix+"/repos/sandbox/" 19 | helpers.careful_delete(path) 20 | 21 | game.global_shell.run("mkdir '%s'" % path) 22 | game.global_shell.cd(path) 23 | game.global_shell.run("git init") 24 | game.global_shell.run("git symbolic-ref HEAD refs/heads/main") 25 | 26 | $Columns/Repository.path = path 27 | 28 | get_tree().set_screen_stretch(SceneTree.STRETCH_MODE_2D, SceneTree.STRETCH_ASPECT_KEEP, Vector2(1920, 1080), 1.5) 29 | 30 | $Columns/Terminal.repository = $Columns/Repository 31 | 32 | func update_repo(): 33 | $Columns/Repository.update_everything() 34 | -------------------------------------------------------------------------------- /levels/low-level/commit-rhombus: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | A commit can have multiple parents! You can specify the -p option multiple times, like this: 4 | 5 | git commit-tree -m "Description" -p -p 6 | 7 | Build a rhombus shape from commits, where two commits point to the same parent, and then a fourth commit points to both of them. 8 | 9 | [setup] 10 | 11 | [setup goal] 12 | 13 | TREE=$(git write-tree) 14 | SOUTH=$(git commit-tree $TREE -m "South") 15 | EAST=$(git commit-tree $TREE -m "East" -p $SOUTH) 16 | WEST=$(git commit-tree $TREE -m "West" -p $SOUTH) 17 | NORTH=$(git commit-tree $TREE -m "Nort" -p $EAST -p $WEST) 18 | 19 | [win] 20 | 21 | COMMITS=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep commit | cut -f1 -d" ") 22 | 23 | for COMMIT in $COMMITS; do 24 | # My first parent's parents has to be the same as my second parent's parent. 25 | if [ "$(git rev-parse --verify -q $COMMIT^1^)" = "$(git rev-parse --verify -q $COMMIT^2^)" ]; then 26 | return 0 27 | fi 28 | done 29 | 30 | return 1 31 | -------------------------------------------------------------------------------- /levels/index/new: -------------------------------------------------------------------------------- 1 | title = Add new files to the index 2 | cards = add commit 3 | 4 | [description] 5 | 6 | So far, when we made a commit, we've always recorded the current status of all objects, right? 7 | 8 | But Git allows you to pick which changes you want to put in a commit! 9 | 10 | To learn how that works, we need to learn about the "index"! In the index, we can prepare what will be in the next commit. In this game, the index is represented by a blue aura around icons in the file browser! 11 | 12 | Initially, the index is empty. To make a commit that contains a new file, we need to add it! 13 | 14 | [cli] 15 | 16 | You can use tab completion in the terminal! Start typing a filename, then press the tab key to complete its name. This will often save you some time! 17 | 18 | [setup] 19 | 20 | echo "The candle is burning with a blue flame." > candle 21 | 22 | [win] 23 | 24 | # Add the candle. 25 | test "$(git diff --cached --name-only)" = "candle" || file -f .git/candle-added && touch .git/candle-added 26 | 27 | # Make a commit. 28 | test "$(git ls-tree --name-only HEAD)" = "candle" 29 | -------------------------------------------------------------------------------- /levels/intro/copies: -------------------------------------------------------------------------------- 1 | title = Making backups 2 | cards = 3 | 4 | [description] 5 | 6 | This time, you're making a lot of backup copies - you can look at them by clicking on them! 7 | 8 | [congrats] 9 | 10 | Okay, this kind of works. 11 | 12 | But you're a bit worried that you'll end up with hundreds of copies of this form, and it will be hard to keep track of all of them. 13 | 14 | And especially when working with other people, sending copies back and forth doesn't seem ideal. 15 | 16 | You can't wait to try these time machines! 17 | 18 | [setup] 19 | 20 | rm -rf .git 21 | 22 | echo "~ Why I want to learn Git ~ 23 | 24 | (I still need to write this.)" >> form.txt 25 | 26 | 27 | echo "~ Why I want to learn Git ~ 28 | 29 | - So that I can undo mistakes" >> form2.txt 30 | 31 | 32 | echo "~ Why I want to learn Git ~ 33 | 34 | - So that I can undo mistakes 35 | - To track my projects over time" >> form2_final.txt 36 | 37 | cp form2_final.txt form2_really_final.txt 38 | 39 | [win] 40 | 41 | # Add another line to form2_really_final.txt! 42 | test "$(cat form2_really_final.txt | wc -l )" -ge 5 43 | -------------------------------------------------------------------------------- /levels/index/change: -------------------------------------------------------------------------------- 1 | title = Update files in the index 2 | cards = add commit 3 | 4 | [description] 5 | 6 | When we change files, the index won't change on its own. We have to use `git add` to update the index to the changed version of the file. 7 | 8 | Let's try that! 9 | 10 | The icons in the file browser show you when the actual file (white) and the version in the index (blue) are different, and when they are the same! 11 | 12 | [win] 13 | 14 | Good! The index is sometimes also called the "staging area" - it contains exactly what ends up in the next commit when you use `git commit`! 15 | 16 | [setup] 17 | 18 | echo "The candle is burning with a blue flame." > candle 19 | git add . 20 | git commit -m "The beginning" 21 | 22 | [win] 23 | 24 | # Make a change to the candle. 25 | test "$(git diff --name-only)" = "candle" || file -f .git/candle-changed && touch .git/candle-changed 26 | 27 | # Add the candle. 28 | test "$(git diff --cached --name-only)" = "candle" || file -f .git/candle-added && touch .git/candle-added 29 | 30 | # Make a commit. 31 | test "$(git diff --name-only HEAD HEAD^)" = "candle" 32 | -------------------------------------------------------------------------------- /levels/shit-happens/bad-commit: -------------------------------------------------------------------------------- 1 | title = Undo a bad commit 2 | cards = reset commit-a 3 | 4 | [description] 5 | 6 | Oh no, we made a bad commit! How can we undo making the commit, and go back to a point where we can try again? 7 | 8 | The answer is using `git reset [commit]`, which does two things: 9 | 10 | - It resets the current branch ref to the commit you specify. 11 | - And it resets the index to that commit. 12 | 13 | It does not change your working directory in any way, which means that after that, you can try making the commit you want again. 14 | 15 | [setup] 16 | 17 | echo "1 2 3 4" > numbers 18 | git add . 19 | git commit -m "Initial commit" 20 | echo "1 2 3 4 5 6 7 8 9 11" > numbers 21 | git commit -am "More numberrrrrs" 22 | 23 | [win] 24 | 25 | # In the last main commit, the numbers file contains the numbers from 1 to 10. 26 | test "$(git show main:numbers)" = "1 2 3 4 5 6 7 8 9 10" 27 | # The commit message of that commit is "More numbers". 28 | git log -1 --oneline | grep "More numbers" 29 | # The commit with the typo is not part of the main branch anymore. 30 | git log --oneline | grep -v "rrrrr" 31 | -------------------------------------------------------------------------------- /levels/low-level/ref-move: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | You can point refs to a new location using the same command you use to create them: 4 | 5 | git update-ref refs/ 6 | 7 | As an exercise, make all refs in this repository point to the tree object! 8 | 9 | [setup] 10 | 11 | echo hello > hello 12 | echo world > world 13 | BLOB1=$(git hash-object -w hello) 14 | BLOB2=$(git hash-object -w world) 15 | git add . 16 | TREE=$(git write-tree) 17 | COMMIT=$(git commit-tree $TREE -m "Initial commit") 18 | 19 | git update-ref refs/a "$BLOB1" 20 | git update-ref refs/b "$COMMIT" 21 | 22 | [setup goal] 23 | 24 | echo hello > hello 25 | echo world > world 26 | BLOB1=$(git hash-object -w hello) 27 | BLOB2=$(git hash-object -w world) 28 | git add . 29 | TREE=$(git write-tree) 30 | COMMIT=$(git commit-tree $TREE -m "Initial commit") 31 | 32 | git update-ref refs/a "$BLOB1" 33 | git update-ref refs/b "$COMMIT" 34 | 35 | for REF in $(git for-each-ref --format='%(refname)'); do 36 | git update-ref "$REF" "$TREE" 37 | done 38 | 39 | [win] 40 | 41 | test "$(git show-ref -s | sort -u)" = "c7863f72467ed8dd44f4b8ffdb8b57ca7d91dc9e" 42 | -------------------------------------------------------------------------------- /levels/stash/stash-clear: -------------------------------------------------------------------------------- 1 | title = Clear the Stash 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | If you want to inspect your stash stack, use the command 7 | git stash list 8 | 9 | Oh, you don't want to keep your stashed changes? There are way too many? Then go ahead and clear the stack with 10 | git stash clear 11 | If you only want to discard a certain stash entry, you can use 12 | git stash drop 13 | 14 | Clear your stash stack! 15 | 16 | --- 17 | tipp1 18 | --- 19 | tipp2 20 | --- 21 | tipp3 22 | 23 | [setup] 24 | 25 | echo "Apple Pie:" > recipe 26 | 27 | git add . 28 | git commit -m "creating a recipe" 29 | 30 | echo "- 4 Apples" >> recipe 31 | 32 | git add . 33 | git commit -m "Adding ingredients" 34 | 35 | echo "- 500g Flour" >> recipe 36 | git stash push 37 | 38 | echo "- 200g Sugar" >> recipe 39 | git stash push 40 | 41 | echo "- Pinch of Salt" >> recipe 42 | git stash push 43 | 44 | git checkout main 45 | 46 | [win] 47 | 48 | # Did you clear your stash stack? 49 | test "$(git stash list | wc -l)" -eq 0 50 | 51 | [actions] 52 | 53 | 54 | 55 | [congrats] 56 | 57 | All clear! :) 58 | -------------------------------------------------------------------------------- /scenes/levels.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var chapters 4 | 5 | func _ready(): 6 | reload() 7 | 8 | func reload(): 9 | chapters = [] 10 | 11 | var dir = Directory.new() 12 | dir.open("res://levels") 13 | dir.list_dir_begin() 14 | 15 | var chapter_names = [] 16 | 17 | while true: 18 | var file = dir.get_next() 19 | if file == "": 20 | break 21 | elif not file.begins_with(".") and file != "sequence": 22 | chapter_names.append(file) 23 | 24 | dir.list_dir_end() 25 | chapter_names.sort() 26 | 27 | var final_chapter_sequence = [] 28 | 29 | var chapter_sequence = Array(helpers.read_file("res://levels/sequence", "").split("\n")) 30 | 31 | for chapter in chapter_sequence: 32 | if chapter == "": 33 | continue 34 | if not chapter_names.has(chapter): 35 | helpers.crash("Chapter '%s' is specified in the sequence, but could not be found" % chapter) 36 | chapter_names.erase(chapter) 37 | final_chapter_sequence.push_back(chapter) 38 | 39 | #final_chapter_sequence += chapter_names 40 | 41 | for c in final_chapter_sequence: 42 | var chapter = Chapter.new() 43 | chapter.load("res://levels/%s" % c) 44 | chapters.push_back(chapter) 45 | -------------------------------------------------------------------------------- /levels/low-level/blob-create: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | At its core, Git is very simple. It stores "objects", which are basically files identified by an "identifier" (short: ID). 4 | 5 | There are four types of objects: blobs, trees, commits, and tags. The simplest type is a "blob", which is just a piece of text. 6 | 7 | Let's create some blobs! To do that, create a file with the desired content, and then use 8 | 9 | git hash-object -w 10 | 11 | The flag -w means "write", and tells Git to actually write the new blob to the disk. 12 | 13 | Create three new blobs! 14 | 15 | [congrats] 16 | 17 | Tip: You can also use a command like this to create a blob in a single line: 18 | 19 | echo "awesome content" | git hash-object -w --stdin 20 | 21 | Did you already notice that you can drag and drop all objects? :) 22 | 23 | [setup] 24 | 25 | [setup goal] 26 | 27 | echo "Hi" > file1 28 | echo "Ho" > file2 29 | echo "Hu" > file3 30 | git hash-object -w file1 31 | git hash-object -w file2 32 | git hash-object -w file3 33 | 34 | [win] 35 | 36 | BLOB_COUNT=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep blob | wc -l) 37 | 38 | test "$BLOB_COUNT" -gt 2 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | name = "oh-my-git" 2 | 3 | all: linux macos windows 4 | 5 | linux: 6 | mkdir -p build/$(name)-linux 7 | godot --export "Linux" "build/$(name)-linux/$(name)" 8 | cd build/$(name)-linux && zip -r ../$(name)-linux.zip * 9 | 10 | macos: 11 | mkdir -p build 12 | godot --export "Mac OS" "build/$(name)-macos.zip" 13 | 14 | windows: dependencies/windows/git/ 15 | mkdir -p build/$(name)-windows 16 | # We're using the debug template here so that the bash.exe doesn't spawn a cmd.exe each time... 17 | godot --export-debug "Windows" "build/$(name)-windows/$(name).exe" 18 | cp -r --parents dependencies/windows/git/ build/$(name)-windows/ 19 | cd build/$(name)-windows && zip -r ../$(name)-windows.zip * 20 | 21 | clean-unzipped: 22 | cd build && ls | grep -v '\.zip$$' | xargs rm -r 23 | 24 | clean: 25 | rm -rf build dependencies cache 26 | 27 | # Dependencies: 28 | 29 | cache/portablegit.7z.exe: 30 | mkdir -p cache 31 | wget https://github.com/git-for-windows/git/releases/download/v2.28.0.windows.1/PortableGit-2.28.0-64-bit.7z.exe -O cache/portablegit.7z.exe 32 | 33 | dependencies/windows/git/: cache/portablegit.7z.exe 34 | 7zr x cache/portablegit.7z.exe -odependencies/windows/git/ -y 35 | -------------------------------------------------------------------------------- /levels/low-level/tree-read: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | As soon as you have some tree objects, you can always read them and set the index exactly to their content! Unsurprisingly, the command is called 4 | 5 | git read-tree 6 | 7 | For , you can provide the hash of any tree object - you can right-click one to insert its hash into the terminal! 8 | 9 | Try reading some of the trees in this repository into the index! 10 | 11 | [setup] 12 | 13 | EMPTY_TREE=$(git write-tree) 14 | 15 | echo "file 1" > file1 16 | echo "file 2" > file2 17 | git add . 18 | git write-tree 19 | 20 | rm * 21 | echo "file A" > fileA 22 | echo "file B" > fileB 23 | echo "file C" > fileC 24 | git add . 25 | TRIPLE_TREE=$(git write-tree) 26 | 27 | git read-tree "$EMPTY_TREE" 28 | 29 | [setup goal] 30 | 31 | EMPTY_TREE=$(git write-tree) 32 | 33 | echo "file 1" > file1 34 | echo "file 2" > file2 35 | git add . 36 | git write-tree 37 | 38 | rm * 39 | echo "file A" > fileA 40 | echo "file B" > fileB 41 | echo "file C" > fileC 42 | git add . 43 | TRIPLE_TREE=$(git write-tree) 44 | 45 | git read-tree "$EMPTY_TREE" 46 | 47 | git read-tree "$TRIPLE_TREE" 48 | 49 | [win] 50 | 51 | test "$(git ls-files | wc -l)" -gt 0 52 | -------------------------------------------------------------------------------- /scenes/tcp_server.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal data_received(string) 4 | signal new_connection 5 | 6 | export var port: int 7 | 8 | var _s = TCP_Server.new() 9 | var _c 10 | var _connected = false 11 | 12 | func _ready(): 13 | start() 14 | 15 | func start(): 16 | _s.listen(port) 17 | 18 | func _process(_delta): 19 | if _s.is_connection_available(): 20 | if _connected: 21 | _c.disconnect_from_host() 22 | helpers.crash("Dropping active connection") 23 | _c = _s.take_connection() 24 | _connected = true 25 | emit_signal("new_connection") 26 | print("connected!") 27 | 28 | if _connected: 29 | if _c.get_status() != StreamPeerTCP.STATUS_CONNECTED: 30 | _connected = false 31 | print("disconnected") 32 | # var available = _c.get_available_bytes() 33 | # while available > 0: 34 | # var data = _c.get_utf8_string(available) 35 | # emit_signal("data_received", data) 36 | # available = _c.get_available_bytes() 37 | 38 | func send(text): 39 | if _connected: 40 | text += "\n" 41 | _c.put_utf8_string(text) 42 | var response = _c.get_utf8_string() 43 | emit_signal("data_received", response) 44 | else: 45 | helpers.crash("Trying to send data on closed connection") 46 | -------------------------------------------------------------------------------- /levels/merge/merge-abort: -------------------------------------------------------------------------------- 1 | title = Abort a merge 2 | cards = checkout commit-auto merge merge-abort 3 | 4 | [description] 5 | 6 | Sometimes you want to merge two commits, but a merge conflict occurs that you currently don't want to resolve. 7 | 8 | In these situations you can abort the merge to merge later. Use 9 | git merge --abort 10 | when you are in a merge process. 11 | 12 | Try to merge both commits and abort the merge afterwards. 13 | 14 | [setup] 15 | 16 | echo "A new day is starting" > you 17 | 18 | git add . 19 | git commit -m "Start" 20 | 21 | echo "Walking down the Main Lane." >> you 22 | 23 | git add . 24 | git commit -m "Main Lane" 25 | 26 | 27 | git checkout HEAD~1 28 | 29 | echo "Walking down the Side Lane." >> you 30 | 31 | git add . 32 | git commit -m "Side Lane" 33 | 34 | git checkout HEAD~1 35 | 36 | git branch -D main 37 | 38 | [actions] 39 | 40 | if test -f .git/MERGE_HEAD; then 41 | touch .git/secretfile 42 | fi 43 | 44 | [win] 45 | 46 | # You tried to merge? 47 | test -f .git/secretfile 48 | 49 | # You aborted to merge? 50 | test -f .git/secretfile && ! test -f .git/MERGE_HEAD && ! git rev-parse HEAD^^ 51 | 52 | [congrats] 53 | 54 | Aaah, let's merge later... 55 | -------------------------------------------------------------------------------- /levels/shit-happens/pushed-something-broken: -------------------------------------------------------------------------------- 1 | title = I pushed something broken 2 | cards = revert push 3 | 4 | [description] 5 | 6 | We were talking about how to undo a commit, and fix it. This only helps when you haven't already pushed it to a remote. When that has happened, and you want to undo the effects of the commit completely, your best option is `git revert` 7 | 8 | [setup] 9 | 10 | echo "this is fine 11 | 12 | ? 13 | 14 | ? 15 | 16 | ?" > text 17 | git add . 18 | git commit -m fine 19 | echo "this is fine 20 | 21 | this is also fine 22 | 23 | ? 24 | 25 | ?" > text 26 | git commit -am "also fine" 27 | echo "this is fine 28 | 29 | this is also fine 30 | 31 | this is very bad 32 | 33 | ?" > text 34 | git commit -am "very bad" 35 | echo "this is fine 36 | 37 | this is also fine 38 | 39 | this is very bad 40 | 41 | this is fine again" > text 42 | git commit -am "fine again" 43 | 44 | git push team main 45 | git branch -u team/main main 46 | 47 | [setup team] 48 | 49 | [win team] 50 | 51 | # The team's main branch no longer contains the bad thing. 52 | ! { git show main:text | grep -q "very bad"; } 53 | # And the history has not been modified. 54 | git show main^:text | grep -q "very bad" 55 | -------------------------------------------------------------------------------- /levels/stash/stash-merge: -------------------------------------------------------------------------------- 1 | title = Merging popped stash 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | When you want to reapply your changes but you already continued working on your file, you might get 7 | a merge conflict! Let's practise this situation. 8 | Pop the changes from the stash with 9 | git stash pop 10 | and resolve the merge conflict. Commit the resolved changes and clear the stash stack afterwards. 11 | 12 | --- 13 | tipp1 14 | --- 15 | tipp2 16 | --- 17 | tipp3 18 | 19 | [setup] 20 | 21 | echo "Apple Pie:" > recipe 22 | 23 | git add . 24 | git commit -m "creating a recipe" 25 | 26 | echo "- 4 Apples" >> recipe 27 | 28 | git add . 29 | git commit -m "Adding ingredients" 30 | 31 | echo "- 500g Flour" >> recipe 32 | 33 | git stash push 34 | 35 | echo "- Pinch of Salt" >> recipe 36 | 37 | git checkout main 38 | git add recipe 39 | 40 | [win] 41 | 42 | # Did you resolve the conflict and commit? 43 | { git show HEAD | grep "Flour"; } && { git show HEAD | grep "Salt"; } 44 | 45 | # Did you clear stash stack? 46 | test "$(git stash list | wc -l)" -eq 0 47 | 48 | [actions] 49 | 50 | 51 | 52 | [congrats] 53 | 54 | Yay, you got your changes back! :) 55 | -------------------------------------------------------------------------------- /levels/index/add: -------------------------------------------------------------------------------- 1 | title = Updating files in the index 2 | cards = add commit checkout 3 | 4 | [description] 5 | 6 | So you start working, and make changes to your files! Git lets you choose which of these changes you want to put in the next commit. This is like updating the index version of that file to the new version. 7 | 8 | This allows you to have smaller commits, that describe better what you changed! 9 | 10 | The command for this is the same - `git add`! 11 | 12 | [setup] 13 | 14 | echo a > a 15 | echo b > b 16 | echo c > c 17 | git add . 18 | git commit -m "Initial commit" 19 | 20 | [win] 21 | 22 | # Make changes to all files! 23 | test "$(cat a)" != "a" && 24 | test "$(cat b)" != "b" && 25 | test "$(cat c)" != "c" 26 | 27 | # Add only the changes of a and c, and make a commit! Finally, make a commit which captures the changes in b! 28 | 29 | test "$(git show main:a)" != "a" && 30 | test "$(git show main:b)" != "b" && 31 | test "$(git show main:c)" != "c" && 32 | test "$(git show main^:a)" != "a" && 33 | test "$(git show main^:b)" == "b" && 34 | test "$(git show main^:c)" != "c" 35 | 36 | [congrats] 37 | 38 | Well done! Try travelling between the commits using `git checkout`, so you can look at their contents again! 39 | -------------------------------------------------------------------------------- /levels/low-level/puzzle-trees-all-the-way-down: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Construct a chain of three trees, which don't point to anything else. 4 | 5 | This is hard! The `git mktree` command might be useful. 6 | 7 | [setup] 8 | 9 | [setup goal] 10 | 11 | git mktree 12 | TREE=$(echo -e "040000 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\tdir" | git mktree) 13 | echo -e "040000 tree $TREE\tdir" | git mktree 14 | 15 | [win] 16 | 17 | TREES=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep tree | cut -f1 -d" ") 18 | 19 | for TREE in $TREES; do 20 | if [ "$(git cat-file -p $TREE | wc -l)" -eq 1 ]; then 21 | if [ "$(git cat-file -p $TREE | cut -f1 | grep tree | wc -l)" -eq 1 ]; then 22 | # So the tree has exactly one child, and it is a tree! 23 | TREE2=$(git cat-file -p $TREE | cut -f1 | grep tree | cut -f3 -d" ") 24 | if [ "$(git cat-file -p $TREE2 | wc -l)" -eq 1 ]; then 25 | if [ "$(git cat-file -p $TREE2 | cut -f1 | grep tree | wc -l)" -eq 1 ]; then 26 | # Same for its child! \o/ 27 | return 0 28 | fi 29 | fi 30 | fi 31 | fi 32 | done 33 | 34 | return 1 35 | -------------------------------------------------------------------------------- /levels/index/reset: -------------------------------------------------------------------------------- 1 | title = Resetting files in the index 2 | cards = add reset-file commit 3 | 4 | [description] 5 | 6 | See the dark shadow behind the icons? That's the version of the file in the last commit! 7 | 8 | For example, these candles have been blown out, and that change has been added. 9 | 10 | But you decide that this was a mistake! You only want to blow out the red candle in the next commit! 11 | 12 | If you already have updated the index to a changed file, but want to reset it, you can use `git reset`! 13 | 14 | [setup] 15 | 16 | echo "It's burning!" > red_candle 17 | echo "It's burning!" > green_candle 18 | echo "It's burning!" > blue_candle 19 | git add . 20 | git commit -m "The beginning" 21 | 22 | echo "It's been blown out." > red_candle 23 | echo "It's been blown out." > green_candle 24 | echo "It's been blown out." > blue_candle 25 | git add . 26 | 27 | [win] 28 | 29 | # Reset the changes in the green and blue candles! 30 | git show :green_candle | grep burning && 31 | git show :blue_candle | grep burning && 32 | git show :red_candle | grep -v burning 33 | 34 | # And make a commit! 35 | git show main:green_candle | grep burning && 36 | git show main:blue_candle | grep burning && 37 | git show main:red_candle | grep -v burning 38 | -------------------------------------------------------------------------------- /levels/remotes/friend: -------------------------------------------------------------------------------- 1 | title = Friend 2 | cards = pull push commit-auto checkout 3 | 4 | [description] 5 | 6 | Your friend added another line to your essay! Get it, add a third one and send it to them! 7 | 8 | Take turns until you have five lines! 9 | 10 | [setup yours] 11 | 12 | echo "Line 1" > essay 13 | git add . 14 | git commit -m "One line" 15 | 16 | git push -u friend main 17 | 18 | [setup friend] 19 | 20 | git checkout main 21 | echo "Line 2, gnihihi" >> essay 22 | git commit -am "Another line" 23 | 24 | [actions friend] 25 | 26 | if test "$(git log --oneline | wc -l)" -eq 3; then 27 | git reset --hard main # Necessary because the working directory isn't updated when we push to the friend. 28 | echo "Line 4, blurbblubb" >> essay 29 | git commit -am "Final line" 30 | hint "Oh nice, I added a fourth line!" 31 | fi 32 | 33 | [win] 34 | 35 | # Got the second line from your friend 36 | git show HEAD:essay | grep gnihihi 37 | 38 | # Got the fourth line from your friend. 39 | git show HEAD:essay | grep blurbblubb 40 | 41 | [win friend] 42 | 43 | # The friend got a third line from you 44 | test "$(git show HEAD:essay | wc -l)" -ge 3 45 | 46 | # The friend got a fifth line from you 47 | test "$(git show HEAD:essay | wc -l)" -ge 5 48 | -------------------------------------------------------------------------------- /scenes/text_editor.gd: -------------------------------------------------------------------------------- 1 | extends TextEdit 2 | 3 | signal saved 4 | 5 | var path 6 | 7 | var _server 8 | var _client_connection 9 | 10 | func _ready(): 11 | # Initialize TCP server for fake editor. 12 | _server = TCP_Server.new() 13 | _server.listen(1234) 14 | 15 | func _process(_delta): 16 | if _server.is_connection_available(): 17 | _client_connection = _server.take_connection() 18 | var length = _client_connection.get_u32() 19 | var _filename = _client_connection.get_string(length) 20 | 21 | length = _client_connection.get_u32() 22 | var content = _client_connection.get_string(length) 23 | 24 | open(content) 25 | 26 | func _input(event): 27 | if event.is_action_pressed("save"): 28 | save() 29 | 30 | func open(content): 31 | text = content 32 | show() 33 | grab_focus() 34 | 35 | func save(): 36 | if visible: 37 | # Add a newline to the end of the file if there is none. 38 | if text.length() > 0 and text.substr(text.length()-1, 1) != "\n": 39 | text += "\n" 40 | 41 | _client_connection.put_string(text) 42 | 43 | emit_signal("saved") 44 | close() 45 | 46 | func close(): 47 | if _client_connection and _client_connection.is_connected_to_host(): 48 | _client_connection.disconnect_from_host() 49 | text = "" 50 | hide() 51 | -------------------------------------------------------------------------------- /levels/low-level/index-add: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Blobs usually represent the content of a file. But on their own, they don't have any metadata, not even a name! 4 | 5 | Git has a very powerful concept to store metadata related to blobs: the index! It's a list that relates blobs to filenames and access permissions. 6 | 7 | The most convenient option to add an entry to the index is via an existing file: 8 | 9 | echo "my content" > file 10 | git update-index --add file 11 | 12 | Add three entries to the index! For a bonus challenge: can you add a file that is inside of a directory, like "directory/file"? 13 | 14 | [congrats] 15 | 16 | There's another way to add an entry to the index directly: 17 | 18 | git update-index --add --cacheinfo ,, 19 | 20 | The first three numbers of the mode describe the type of the entry, "100" is a regular file. 21 | 22 | The second three number describe the permissions. Only "644" (non-executable) and "755" (executable) are supported. 23 | 24 | You can insert the hash of an object into the terminal by right-clicking on it! :) 25 | 26 | [setup] 27 | 28 | [setup goal] 29 | 30 | echo "file 1" > file1 31 | echo "file 2" > file2 32 | echo "file 3" > file3 33 | git add . 34 | 35 | [win] 36 | 37 | test "$(git ls-files | wc -l)" -ge 3 38 | -------------------------------------------------------------------------------- /scenes/chapter.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name Chapter 3 | 4 | var slug 5 | var levels 6 | 7 | # Path is an outer path. 8 | func load(path): 9 | levels = [] 10 | 11 | var parts = path.split("/") 12 | slug = parts[parts.size()-1] 13 | 14 | var level_names = [] 15 | var dir = Directory.new() 16 | dir.open("res://levels/%s" % slug) 17 | dir.list_dir_begin() 18 | 19 | while true: 20 | var file = dir.get_next() 21 | if file == "": 22 | break 23 | elif not file.begins_with(".") and file != "sequence": 24 | level_names.append(file) 25 | 26 | dir.list_dir_end() 27 | level_names.sort() 28 | 29 | var final_level_sequence = [] 30 | 31 | var level_sequence = Array(helpers.read_file("res://levels/%s/sequence" % slug, "").split("\n")) 32 | 33 | for level in level_sequence: 34 | if level == "": 35 | continue 36 | if not level_names.has(level): 37 | helpers.crash("Level '%s' is specified in the sequence, but could not be found" % level) 38 | level_names.erase(level) 39 | final_level_sequence.push_back(level) 40 | 41 | #final_level_sequence += level_names 42 | 43 | for l in final_level_sequence: 44 | var level = Level.new() 45 | level.load("res://levels/%s/%s" % [slug, l]) 46 | levels.push_back(level) 47 | 48 | func _to_string(): 49 | return str(levels) 50 | -------------------------------------------------------------------------------- /levels/intro/who-are-you: -------------------------------------------------------------------------------- 1 | title = Welcome to time travel school! 2 | cards = config-name commit-auto checkout 3 | 4 | [description] 5 | 6 | You're still pretty confused by everything that's going on. The next day, you decide to enroll in time travel school! 7 | 8 | Your time travel teacher welcomes you: "Hello there! Wanna tell us your name?" 9 | 10 | [setup] 11 | 12 | git config --global user.name "You" 13 | 14 | echo "~ Why do you want to learn how to use time machines? ~ 15 | 16 | [ ] To make sure that my cat doesn't eat my homework. 17 | [ ] So I don't have to keep copies of all my essays. 18 | [ ] To collaborate with other time travel students. 19 | [ ] Other, please specify:" > form 20 | 21 | [actions] 22 | 23 | test "$(git config user.name)" != "You" && cat form | grep -v Signature && echo " 24 | Signature: $(git config user.name)" >> form 25 | 26 | [win] 27 | 28 | # Introduce yourself. 29 | test "$(git config user.name)" != "You" 30 | 31 | # Fill out the enrollment form, and commit it! 32 | git show main:form | grep '\[[xX]\]' 33 | 34 | [congrats] 35 | 36 | "We're so glad to have you! 37 | 38 | Git can help you fix problems in the past! It allows you to collaborate with other students of time travel! It's really powerful, and it's really popular! I'll see you for your first lesson tomorrow!" 39 | -------------------------------------------------------------------------------- /levels/intro/remote: -------------------------------------------------------------------------------- 1 | title = Working together 2 | cards = pull commit-auto push 3 | 4 | [description] 5 | 6 | Let's add your name to our list of students! 7 | 8 | I already have a second commit of it in my time machine - let's work together! 9 | 10 | [cli] 11 | 12 | To go back to old commands, you can press arrow up and down. That way, you don't have to type in long commands twice. 13 | 14 | [congrats] 15 | 16 | Welcome to time travel school! :) I'll see you for your first class tomorrow! 17 | 18 | [setup] 19 | 20 | echo "~ List of current students ~" > students 21 | git add . 22 | git commit -m "Initial version" 23 | git push -u teacher main 24 | 25 | git update-ref -d refs/remotes/teacher/main 26 | 27 | [setup teacher] 28 | 29 | git reset --hard main 30 | 31 | echo " 32 | - Sam 33 | - Alex" >> students 34 | 35 | git add . 36 | git commit -m "Added two students" 37 | 38 | [win] 39 | 40 | # Get the second commit from your teacher using `git pull`. 41 | test "$(git log --oneline teacher/main | wc -l)" -ge 2 42 | 43 | # Add your name to the list of students 44 | test "$(cat students |wc -l)" -ge 5 45 | 46 | # Commit your result. 47 | test "$(git show main:students |wc -l)" -ge 5 48 | 49 | [win teacher] 50 | 51 | # And use `git push` to send it to your teacher! 52 | test "$(git show main:students |wc -l)" -ge 5 53 | -------------------------------------------------------------------------------- /scenes/drop_area.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var hovered = false 4 | var highlighted = false setget _set_highlighted 5 | 6 | func _ready(): 7 | _set_highlighted(false) 8 | 9 | func _mouse_entered(_area): 10 | hovered = true 11 | var tween = Tween.new() 12 | tween.interpolate_property($Highlight/Sprite.material, "shader_param/hovered", 0, 1, 0.1, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 13 | add_child(tween) 14 | tween.start() 15 | 16 | func _mouse_exited(_area): 17 | hovered = false 18 | var tween = Tween.new() 19 | tween.interpolate_property($Highlight/Sprite.material, "shader_param/hovered", 1, 0, 0.1, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT) 20 | add_child(tween) 21 | tween.start() 22 | 23 | func _input(event): 24 | if event is InputEventMouseButton: 25 | if event.button_index == BUTTON_LEFT and !event.pressed and hovered: 26 | if highlighted and game.dragged_object: 27 | game.dragged_object.dropped_on(get_parent_with_type()) 28 | 29 | func _set_highlighted(new_highlighted): 30 | highlighted = new_highlighted 31 | $Highlight.visible = highlighted 32 | 33 | func get_parent_with_type(): 34 | var parent = get_parent() 35 | while(!parent.get("type")): 36 | parent = parent.get_parent() 37 | return parent 38 | 39 | func highlight(type): 40 | if get_parent_with_type().type == type: 41 | _set_highlighted(true) 42 | -------------------------------------------------------------------------------- /levels/low-level/ref-create: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Let's take a look at "refs" (short for "references")! Refs are not objects, but rather very simple *pointers* to objects! They can help you keep track of what's where. 4 | 5 | You can create or update a ref with 6 | 7 | git update-ref refs/ 8 | 9 | Make sure to always start a ref's name with "refs/"! That's a convention that helps Git find all refs you create. If you forget the "refs/", you will not see the ref. 10 | 11 | Create refs that point to all objects in this repository! 12 | 13 | [setup] 14 | 15 | echo hello > hello 16 | echo world > world 17 | BLOB1=$(git hash-object -w hello) 18 | BLOB2=$(git hash-object -w world) 19 | git add . 20 | TREE=$(git write-tree) 21 | COMMIT=$(git commit-tree $TREE -m "Initial commit") 22 | 23 | [setup goal] 24 | 25 | echo hello > hello 26 | echo world > world 27 | BLOB1=$(git hash-object -w hello) 28 | BLOB2=$(git hash-object -w world) 29 | git add . 30 | TREE=$(git write-tree) 31 | COMMIT=$(git commit-tree $TREE -m "Initial commit") 32 | 33 | git update-ref refs/a $BLOB1 34 | git update-ref refs/b $BLOB2 35 | git update-ref refs/c $TREE 36 | git update-ref refs/d $COMMIT 37 | 38 | [win] 39 | 40 | OBJECTS=$(git cat-file --batch-check='%(objectname)' --batch-all-objects | sort) 41 | REF_TARGETS=$(git show-ref -s | sort | uniq) 42 | test "$OBJECTS" = "$REF_TARGETS" 43 | -------------------------------------------------------------------------------- /scripts/fake-editor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use IO::Socket; 4 | use File::Spec; 5 | 6 | $socket = IO::Socket::INET->new(PeerAddr => "127.0.0.1", 7 | PeerPort => 1234, 8 | Proto => "tcp", 9 | Type => SOCK_STREAM); 10 | 11 | my $absolute_path = File::Spec->rel2abs($ARGV[0]); 12 | 13 | # Send the length of the first argument as four bytes. 14 | $socket->send(pack("L", length($absolute_path))); 15 | # Send the first argument as a string. 16 | $socket->send($absolute_path); 17 | 18 | # Get and send the content of the file, prefixed by its length. 19 | my $content = do{local(@ARGV,$/)=$absolute_path;<>}; 20 | $socket->send(pack("L", length($content))); 21 | $socket->send($content); 22 | 23 | # Get size of written content. This blocks until the players hits save or close. 24 | my $length_str; 25 | $socket->recv($length_str, 4); 26 | my $length = unpack("L", $length_str); 27 | my $new_content = ""; 28 | $socket->recv($new_content, $length); 29 | 30 | # Write content back into the file. 31 | my $handle; 32 | open ($handle,'>',$absolute_path) or die("Error opening file"); 33 | print $handle $new_content; 34 | close ($handle) or die ("Error closing file"); 35 | 36 | # This call is intended to block, we're waiting for Godot to close the connection. 37 | my $reply; 38 | $socket->read($reply, 1000); 39 | -------------------------------------------------------------------------------- /scenes/tcp_server_shell.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal data_received(string) 4 | signal new_connection 5 | 6 | export var port: int 7 | 8 | var _s = TCP_Server.new() 9 | var _c 10 | var _connected = false 11 | 12 | func _ready(): 13 | start() 14 | 15 | func start(): 16 | _s.listen(port) 17 | 18 | func _process(_delta): 19 | if _s.is_connection_available(): 20 | if _connected: 21 | _c.disconnect_from_host() 22 | helpers.crash("Dropping active connection") 23 | _c = _s.take_connection() 24 | _connected = true 25 | emit_signal("new_connection") 26 | print("connected!") 27 | 28 | if _connected: 29 | if _c.get_status() != StreamPeerTCP.STATUS_CONNECTED: 30 | _connected = false 31 | print("disconnected") 32 | # var available = _c.get_available_bytes() 33 | # while available > 0: 34 | # var data = _c.get_utf8_string(available) 35 | # emit_signal("data_received", data) 36 | # available = _c.get_available_bytes() 37 | 38 | func send(text): 39 | if _connected: 40 | _c.put_utf8_string(text) 41 | var response = _c.get_utf8_string() 42 | var exit_code = _c.get_u32() 43 | 44 | var shell_command = ShellCommand.new() 45 | shell_command.command = text 46 | shell_command.output = response 47 | #print("response:") 48 | #print(response) 49 | shell_command.exit_code = exit_code 50 | return shell_command 51 | else: 52 | helpers.crash("Trying to send data on closed connection") 53 | -------------------------------------------------------------------------------- /levels/tags/remote-tag: -------------------------------------------------------------------------------- 1 | title = Remote Tags 2 | cards = pull push commit-auto checkout 3 | 4 | [description] 5 | 6 | When you work with remote repositories, tags are not pushed or pulled automatically. 7 | 8 | You can push a tag with 9 | git push 10 | Or all tags with: 11 | git push --tags 12 | 13 | Deleting tags on your remote works with: 14 | git push --delete 15 | 16 | You can also sync 17 | git fetch --prune --prune-tags 18 | 19 | 20 | Add a tag named "v2" to the last commit and push it to the remote. Also pull the v1 tag to your local repository. 21 | [setup yours] 22 | 23 | git checkout main 24 | 25 | git checkout main 26 | echo "toothbrush sharing" > project-ideas 27 | git add . 28 | git commit -m "First idea" 29 | 30 | echo "Is my phone upside down? App" >> project-ideas 31 | git commit -am "Another idea" 32 | 33 | 34 | 35 | git push friend main 36 | 37 | git branch -u friend/main main 38 | 39 | [setup friend] 40 | 41 | 42 | 43 | [actions friend] 44 | 45 | git tag v1 HEAD~1 46 | 47 | [win] 48 | # v1 tag in your repo 49 | test "$(git show v1 -s --format=%h)" = "$(git show HEAD~1 -s --format=%h)" 50 | 51 | # v2 tag in your repo 52 | test "$(git show v2 -s --format=%h)" = "$(git show HEAD -s --format=%h)" 53 | 54 | 55 | [win friend] 56 | 57 | # v2 tag in the remote 58 | test "$(git show v2 -s --format=%h)" = "$(git show HEAD -s --format=%h)" 59 | -------------------------------------------------------------------------------- /levels/branches/checkout-commit: -------------------------------------------------------------------------------- 1 | title = Moving through time 2 | cards = checkout commit-auto 3 | 4 | [description] 5 | 6 | The yellow boxes are frozen points in time, we call them "commits"! You can travel between them using the "checkout" card! (Try it!) 7 | 8 | Can you find out what happened here? Then, while on the last commit, edit the files to fix the problem, and make a new commit! 9 | 10 | [cli] 11 | 12 | To checkout a specific commit, type `git checkout`, then a space, and then right click on the commit you want! 13 | 14 | This will insert the commit's unique identifier! 15 | 16 | [setup] 17 | 18 | echo "This piggy bank belongs to the big sister. 19 | It contains 10 coins." > piggy_bank 20 | git add . 21 | git commit -m "The beginning" 22 | 23 | echo "A young girl with brown, curly hair." > little_sister 24 | git add . 25 | git commit -m "Little sister comes in" 26 | 27 | echo "Has 10 coins." >> little_sister 28 | echo "This piggy bank belongs to the big sister. 29 | It is empty." > piggy_bank 30 | git add . 31 | git commit -m "Little sister does something" 32 | 33 | git checkout HEAD^^ 34 | git branch -df main 35 | 36 | [win] 37 | 38 | # Restore sisterly peace. 39 | { git show HEAD:piggy_bank | grep "10 coins"; } && { git show HEAD:little_sister | grep -v "10 coins"; } && { git rev-parse HEAD^^^; } 40 | 41 | [congrats] 42 | 43 | Wonderful! Now that you're getting familiar with the time machine, let's look at some more complicated situations... 44 | -------------------------------------------------------------------------------- /scenes/text_editor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://fonts/default.tres" type="DynamicFont" id=1] 4 | [ext_resource path="res://scenes/text_editor.gd" type="Script" id=2] 5 | 6 | [node name="TextEditor" type="TextEdit" groups=[ 7 | "editors", 8 | ]] 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | custom_colors/background_color = Color( 0, 0, 0, 1 ) 12 | text = "Text here" 13 | syntax_highlighting = true 14 | wrap_enabled = true 15 | script = ExtResource( 2 ) 16 | __meta__ = { 17 | "_edit_use_anchors_": false 18 | } 19 | 20 | [node name="SaveButton" type="Button" parent="."] 21 | anchor_left = 1.0 22 | anchor_top = 1.0 23 | anchor_right = 1.0 24 | anchor_bottom = 1.0 25 | margin_left = -114.396 26 | margin_top = -59.399 27 | margin_right = -14.3955 28 | margin_bottom = -14.399 29 | focus_mode = 0 30 | custom_fonts/font = ExtResource( 1 ) 31 | enabled_focus_mode = 0 32 | text = "Save" 33 | __meta__ = { 34 | "_edit_use_anchors_": false 35 | } 36 | 37 | [node name="CloseButton" type="Button" parent="."] 38 | anchor_left = 1.0 39 | anchor_right = 1.0 40 | margin_left = -54.3247 41 | margin_top = 12.0 42 | margin_right = -14.3247 43 | margin_bottom = 52.0 44 | focus_mode = 0 45 | custom_fonts/font = ExtResource( 1 ) 46 | enabled_focus_mode = 0 47 | text = "x" 48 | __meta__ = { 49 | "_edit_use_anchors_": false 50 | } 51 | [connection signal="pressed" from="SaveButton" to="." method="save"] 52 | [connection signal="pressed" from="CloseButton" to="." method="close"] 53 | -------------------------------------------------------------------------------- /scenes/cards.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://fonts/default.tres" type="DynamicFont" id=1] 4 | [ext_resource path="res://fonts/big.tres" type="DynamicFont" id=2] 5 | [ext_resource path="res://scenes/cards.gd" type="Script" id=3] 6 | 7 | [node name="Cards" type="Control"] 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | mouse_filter = 2 11 | script = ExtResource( 3 ) 12 | __meta__ = { 13 | "_edit_use_anchors_": false 14 | } 15 | 16 | [node name="ColorRect" type="ColorRect" parent="."] 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | mouse_filter = 2 20 | color = Color( 0.105882, 0.137255, 0.211765, 1 ) 21 | __meta__ = { 22 | "_edit_use_anchors_": false 23 | } 24 | 25 | [node name="Button" type="Button" parent="."] 26 | visible = false 27 | anchor_left = 1.0 28 | anchor_right = 1.0 29 | margin_left = -172.0 30 | margin_top = 16.0 31 | margin_right = -16.0 32 | margin_bottom = 47.0 33 | custom_fonts/font = ExtResource( 1 ) 34 | text = "Draw new cards" 35 | __meta__ = { 36 | "_edit_use_anchors_": false 37 | } 38 | 39 | [node name="Energy" type="Label" parent="."] 40 | visible = false 41 | modulate = Color( 0.431373, 0.792157, 0.423529, 1 ) 42 | margin_left = 28.2219 43 | margin_top = 16.6766 44 | margin_right = 68.2219 45 | margin_bottom = 65.6766 46 | rect_scale = Vector2( 2, 2 ) 47 | custom_fonts/font = ExtResource( 2 ) 48 | text = "3" 49 | __meta__ = { 50 | "_edit_use_anchors_": false 51 | } 52 | [connection signal="pressed" from="Button" to="." method="redraw_all_cards"] 53 | -------------------------------------------------------------------------------- /levels/low-level/tree-nested: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | Trees can also point to other trees! This way, they can describe nested directory structures. 4 | 5 | When you add a file inside of a directory to the index, and then call `git write-tree`, it will create a nested tree for the directory, and attach the blob to it. 6 | 7 | To solve this level, build a little stick figure, as shown on the left - a tree that points to two blobs, as well to a tree that points to two blobs. 8 | 9 | [setup] 10 | 11 | [setup goal] 12 | 13 | echo "I'm the left arm" > arm1 14 | echo "I'm the right arm" > arm2 15 | mkdir hip 16 | echo "I'm the left leg" > hip/leg1 17 | echo "I'm the right leg" > hip/leg2 18 | git add . 19 | git write-tree 20 | 21 | [win] 22 | 23 | TREES=$(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep tree | cut -f1 -d" ") 24 | 25 | for OUTER_TREE in $TREES; do 26 | NUMBER_OF_BLOB_CHILDREN=$(git cat-file -p $OUTER_TREE | cut -f2 -d" " | grep blob | wc -l) 27 | NUMBER_OF_TREE_CHILDREN=$(git cat-file -p $OUTER_TREE | cut -f2 -d" " | grep tree | wc -l) 28 | 29 | if [ $NUMBER_OF_BLOB_CHILDREN -eq 2 -a $NUMBER_OF_TREE_CHILDREN -eq 1 ]; then 30 | TREE_CHILD=$(git cat-file -p $OUTER_TREE | cut -f1 | grep tree | cut -d" " -f3) 31 | NUMBER_OF_BLOB_CHILDREN_OF_TREE_CHILD=$(git cat-file -p $TREE_CHILD | cut -f2 -d" " | grep blob | wc -l) 32 | if [ $NUMBER_OF_BLOB_CHILDREN_OF_TREE_CHILD -eq 2 ]; then 33 | return 0 34 | fi 35 | fi 36 | done 37 | 38 | return 1 39 | -------------------------------------------------------------------------------- /levels/bisect/bisect: -------------------------------------------------------------------------------- 1 | title = Yellow brick road 2 | cards = checkout commit-auto reset-hard bisect-start bisect-good bisect-bad 3 | 4 | [description] 5 | 6 | (Please zoom out a bit using your mouse wheel! :D) 7 | 8 | Oh no! You have lost your key at some point during the day! 9 | 10 | Sure, you could look at every single commit in an attempt to find it - but there's a better way: your time machine has a built-in way to find the point in time where things went wrong quickly! 11 | 12 | First, play the "bisect start" card. Then, go to a commit where you don't have the key, and play the "bisect bad" card. Likewise, go to a commit early on where you have the key *in your pocket*, and play the "bisect good" card. 13 | 14 | After you've found the last good commit, reset the main branch to it. What happened to the key after you lost it? 15 | 16 | [setup] 17 | 18 | echo "You still have your key." > you 19 | 20 | for i in {1..30}; do 21 | if test $i -eq 12; then 22 | echo "Your pocket is empty." > you 23 | echo "Is on the ground." > key 24 | fi 25 | if test $i -eq 13; then 26 | echo "Is holding a key in its beak." > bird 27 | rm key 28 | fi 29 | if test $i -eq 14; then 30 | rm bird 31 | fi 32 | git add . 33 | git commit --allow-empty -m "$i" 34 | done 35 | 36 | [win] 37 | 38 | # Find the last good commit 39 | test "$(git log --pretty=%s main | head -1)" -eq 11 40 | 41 | [congrats] 42 | 43 | Well done! :) The only problem is that you now have to walk all the way back home, again... 44 | -------------------------------------------------------------------------------- /scripts/net-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use IPC::Open3; 4 | use IO::Socket; 5 | 6 | my $bash = "bash"; 7 | if ($#ARGV >= 0) { 8 | $bash = $ARGV[0]; 9 | } 10 | 11 | my $pid = open3(my $in, my $out, my $err, $bash); 12 | 13 | $socket = IO::Socket::INET->new(PeerAddr => "127.0.0.1", 14 | PeerPort => 6666, 15 | Proto => "tcp", 16 | Type => SOCK_STREAM); 17 | 18 | print $in 'export PATH="/usr/bin:/mingw64/bin:$PATH"' . "\n"; 19 | 20 | while(true) { 21 | #STDOUT->flush(); 22 | my $len; 23 | $socket->recv($len, 4); 24 | my $actual_len = unpack("L", $len); 25 | if ($actual_len == 0) { 26 | #print("not connected"); 27 | exit; 28 | } 29 | #print("still connected"); 30 | my $s2; 31 | $socket->recv($s2, $actual_len); 32 | 33 | #print($s2); 34 | STDOUT->flush(); 35 | 36 | $s = ""; 37 | $command = $s2 . "\necho -e \"\\v\"\n"; 38 | print $in $command; 39 | 40 | inner_while: while (true) { 41 | $line = <$out>; 42 | if ($line =~ s/\cK//) { 43 | STDOUT->flush(); 44 | last inner_while; 45 | } 46 | $s = $s . $line; 47 | } 48 | #print "got complete output"; 49 | #print $s; 50 | 51 | #print length($s); 52 | STDOUT->flush(); 53 | my $send_length = pack("L", length($s)); 54 | $socket->send($send_length); 55 | $socket->send($s); 56 | 57 | my $exit_code = pack("L", 0); 58 | $socket->send($exit_code); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /levels/merge/conflict: -------------------------------------------------------------------------------- 1 | title = Contradictions 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | Sometimes, timelines will contradict each other. 7 | 8 | For example, in this case, one of our clients wants these timelines merged, but they ate different things for breakfast in both timelines. 9 | 10 | Try to merge them together! You'll notice that there will be a conflict! The time machine will leave it up to you how to proceed: you can edit the problematic item, it will show you the conflicting sections. You can keep either of the two versions - or create a combination of them! Remove the >>>, <<<, and === markers, and make a new commit to finalize the merge! 11 | 12 | Let your finalized timeline be the "main" one. 13 | 14 | [setup] 15 | 16 | echo "Just woke up. Is hungry." > sam 17 | git add . 18 | git commit -m "The beginning" 19 | 20 | git checkout -b pancakes 21 | echo "Had blueberry pancakes with maple syrup for breakfast." > sam 22 | git add . 23 | git commit -m "Pancakes!" 24 | 25 | echo " 26 | Is at work." >> sam 27 | git commit -am "Go to work" 28 | 29 | git checkout -b muesli main 30 | echo "Had muesli with oats and strawberries for breakfast." > sam 31 | git add . 32 | git commit -m "Muesli!" 33 | 34 | echo " 35 | Is at work." >> sam 36 | git commit -am "Go to work" 37 | 38 | git checkout main 39 | 40 | [win] 41 | 42 | # Make a breakfast compromise in the 'main' branch. 43 | git rev-parse main^ && test "$(git rev-parse main^1^^)" = "$(git rev-parse main^2^^)" 44 | 45 | [congrats] 46 | 47 | Yum, that sounds like a good breakfast! 48 | -------------------------------------------------------------------------------- /scenes/sandbox.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://scenes/terminal.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://scenes/repository.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://styles/theme.tres" type="Theme" id=3] 6 | [ext_resource path="res://scenes/sandbox.gd" type="Script" id=4] 7 | 8 | [node name="Sandbox" type="Control"] 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | theme = ExtResource( 3 ) 12 | script = ExtResource( 4 ) 13 | __meta__ = { 14 | "_edit_use_anchors_": false 15 | } 16 | 17 | [node name="Background" type="ColorRect" parent="."] 18 | anchor_right = 1.0 19 | anchor_bottom = 1.0 20 | mouse_filter = 2 21 | color = Color( 0.0705882, 0.0705882, 0.0705882, 1 ) 22 | __meta__ = { 23 | "_edit_use_anchors_": false 24 | } 25 | 26 | [node name="Columns" type="HSplitContainer" parent="."] 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | margin_left = 5.0 30 | margin_top = 5.0 31 | margin_right = -5.0 32 | margin_bottom = -5.0 33 | __meta__ = { 34 | "_edit_use_anchors_": false 35 | } 36 | 37 | [node name="Repository" parent="Columns" instance=ExtResource( 2 )] 38 | anchor_right = 0.0 39 | anchor_bottom = 0.0 40 | margin_right = 949.0 41 | margin_bottom = 1070.0 42 | size_flags_horizontal = 3 43 | 44 | [node name="Terminal" parent="Columns" instance=ExtResource( 1 )] 45 | anchor_right = 0.0 46 | anchor_bottom = 0.0 47 | margin_left = 961.0 48 | margin_right = 1910.0 49 | margin_bottom = 1070.0 50 | size_flags_horizontal = 3 51 | [connection signal="command_done" from="Columns/Terminal" to="." method="update_repo"] 52 | -------------------------------------------------------------------------------- /scenes/arrow.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var source: String 4 | var target: String setget set_target 5 | var color = Color("333333") setget set_color 6 | 7 | var repository: Control 8 | 9 | func _ready(): 10 | set_color(color) 11 | 12 | func _process(_delta): 13 | position = Vector2(0,0) 14 | 15 | if not (repository and repository.objects.has(source)): 16 | return 17 | 18 | var start = repository.objects[source].position 19 | var end = start 20 | 21 | var parent_position = get_parent().get_parent().position 22 | 23 | var show_arrow = repository.objects.has(target) and repository.objects[target].visible and repository.objects[source].visible and repository.objects[source].type != "head" 24 | 25 | if show_arrow: 26 | end = repository.objects[target].position 27 | $Target.hide() 28 | 29 | $Line.visible = show_arrow 30 | $Tip.visible = show_arrow 31 | 32 | if $Target.text.substr(0, 5) != "refs/" or repository.objects.has(target): 33 | $Target.text = "" 34 | 35 | $Line.points[0] = start - parent_position 36 | $Line.points[1] = end - parent_position 37 | 38 | #$Line.points[0] += ($Line.points[1] - $Line.points[0]).normalized()*30 39 | $Line.points[1] -= ($Line.points[1] - $Line.points[0]).normalized()*30 40 | $Tip.position = $Line.points[1] 41 | $Tip.rotation = PI + atan2($Line.points[0].y - $Line.points[1].y, $Line.points[0].x - $Line.points[1].x) 42 | 43 | func set_color(new_color): 44 | color = new_color 45 | $Line.default_color = new_color 46 | $Tip/Polygon.color = new_color 47 | 48 | func set_target(new_target): 49 | target = new_target 50 | $Target.text = new_target 51 | -------------------------------------------------------------------------------- /scenes/drop_area.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://scenes/drop_area.gd" type="Script" id=1] 4 | [ext_resource path="res://nodes/blob.svg" type="Texture" id=2] 5 | 6 | [sub_resource type="CircleShape2D" id=1] 7 | radius = 63.1532 8 | 9 | [sub_resource type="Shader" id=2] 10 | code = "shader_type canvas_item; 11 | 12 | uniform float hovered; 13 | 14 | void fragment() { 15 | vec2 center = vec2(0.5, 0.5); 16 | float d = distance(UV, center); 17 | COLOR = vec4(0.4 + hovered*0.2, 0.65 + hovered*0.2, 1, smoothstep(0.5,0.2 + hovered*0.1,d)); 18 | } 19 | " 20 | 21 | [sub_resource type="ShaderMaterial" id=3] 22 | resource_local_to_scene = true 23 | shader = SubResource( 2 ) 24 | shader_param/hovered = 0.0 25 | 26 | [node name="DropArea" type="Node2D" groups=[ 27 | "drop_areas", 28 | ]] 29 | position = Vector2( -0.197731, 0.0673599 ) 30 | script = ExtResource( 1 ) 31 | 32 | [node name="Area2D" type="Area2D" parent="."] 33 | input_pickable = false 34 | collision_layer = 0 35 | collision_mask = 524288 36 | 37 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] 38 | shape = SubResource( 1 ) 39 | 40 | [node name="Highlight" type="Node2D" parent="."] 41 | 42 | [node name="Sprite" type="Sprite" parent="Highlight"] 43 | modulate = Color( 1, 1, 1, 0.643137 ) 44 | material = SubResource( 3 ) 45 | position = Vector2( -0.102825, -0.377726 ) 46 | scale = Vector2( 2.25501, 2.25501 ) 47 | texture = ExtResource( 2 ) 48 | [connection signal="area_entered" from="Area2D" to="." method="_mouse_entered"] 49 | [connection signal="area_exited" from="Area2D" to="." method="_mouse_exited"] 50 | -------------------------------------------------------------------------------- /levels/index/compare: -------------------------------------------------------------------------------- 1 | title = Step by step 2 | cards = checkout commit-auto 3 | 4 | [description] 5 | 6 | Welcome to today's lesson! We're going to learn how to make commits with more precision! 7 | 8 | Have a look at these two timelines. They have exactly the same outcome. But one of them makes it much easier to figure out what happened. 9 | 10 | [win] 11 | 12 | # Right! Having each change in its own commit makes it easier to understand what's going on! Let's learn how to do that! 13 | git branch --show-current | grep step-by-step 14 | 15 | [setup] 16 | 17 | echo "A small, but heavy glass ball." > ball 18 | echo "A thin book, that's standing upright." > book 19 | echo "A candle, burning with a blue flame." > candle 20 | echo "A smoke detector. It's absolutely silent." > smoke_detector 21 | 22 | git add . 23 | git commit -m "The beginning" 24 | 25 | git branch -M all-at-once 26 | 27 | echo "The ball is now touching the book." > ball 28 | echo "The book has fallen over." > book 29 | echo "The candle has been blown out." > candle 30 | 31 | git commit -am "The end" 32 | 33 | git checkout HEAD^ 34 | 35 | git checkout -b step-by-step 36 | 37 | echo "The ball is now touching the book." > ball 38 | git commit -am "The ball rolls towards the book" 39 | 40 | echo "The book has fallen over." > book 41 | git commit -am "The book falls over" 42 | 43 | echo "The candle has been blown out." > candle 44 | git commit -am "The book blows out the candle" 45 | 46 | git checkout HEAD~3 47 | 48 | [win] 49 | 50 | # Pick the timeline that's clearer, and make the alarm go off! 51 | git show step-by-step:smoke_detector | tail -n 1 | grep -v "absolutely silent" 52 | -------------------------------------------------------------------------------- /scenes/arrow.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://fonts/default.tres" type="DynamicFont" id=1] 4 | [ext_resource path="res://scenes/arrow.gd" type="Script" id=2] 5 | 6 | [node name="Arrow" type="Node2D"] 7 | show_behind_parent = true 8 | script = ExtResource( 2 ) 9 | 10 | [node name="Line" type="Line2D" parent="."] 11 | points = PoolVector2Array( -0.480499, -0.11055, 158.301, 0.581757 ) 12 | default_color = Color( 0.2, 0.2, 0.2, 1 ) 13 | 14 | [node name="Tip" type="Node2D" parent="."] 15 | position = Vector2( 158.06, 0.290878 ) 16 | z_index = 1 17 | 18 | [node name="Polygon" type="Polygon2D" parent="Tip"] 19 | position = Vector2( -24.7164, -6.37881 ) 20 | z_index = -1 21 | color = Color( 0.2, 0.2, 0.2, 1 ) 22 | polygon = PoolVector2Array( -4.87012, 27.2617, 39.7077, 6.0953, -4.87012, -15.0711 ) 23 | 24 | [node name="Label" type="Node2D" parent="."] 25 | visible = false 26 | position = Vector2( 102, 46 ) 27 | 28 | [node name="ID" type="Label" parent="Label"] 29 | margin_left = -19.374 30 | margin_top = -5.93085 31 | margin_right = 20.626 32 | margin_bottom = 8.06915 33 | custom_fonts/font = ExtResource( 1 ) 34 | custom_colors/font_color = Color( 1, 1, 1, 1 ) 35 | text = "label" 36 | align = 1 37 | __meta__ = { 38 | "_edit_use_anchors_": false 39 | } 40 | 41 | [node name="Target" type="Label" parent="."] 42 | margin_left = -230.84 43 | margin_top = 42.1225 44 | margin_right = 231.16 45 | margin_bottom = 68.1225 46 | custom_fonts/font = ExtResource( 1 ) 47 | custom_colors/font_color = Color( 0.356863, 0.356863, 0.356863, 1 ) 48 | text = "label" 49 | align = 1 50 | __meta__ = { 51 | "_edit_use_anchors_": false 52 | } 53 | -------------------------------------------------------------------------------- /levels/branches/branch-create: -------------------------------------------------------------------------------- 1 | title = Creating branches 2 | cards = checkout commit-auto branch branch-delete reset-hard 3 | 4 | [description] 5 | 6 | You were invited to two parties! At one of them, your favorite band is playing - and the other one is your best friend's birthday party. Where should you go? No worries - as a time travel agent in training, you can go to both parties! 7 | 8 | To make it easier to tell which timeline is which, you can create time portals! (We call these "branches".) 9 | 10 | [cli] 11 | 12 | Branches also make it really easy to travel between different places using the command line! As soon as you have a branch called "birthday", you can type `git checkout birthday` to travel to it! 13 | 14 | [setup] 15 | 16 | echo "You wrap the birthday present, and grab your concert ticket." > you 17 | git add . 18 | git commit -m "Evening preparations" 19 | echo "You go to the birthday party!" >> you 20 | git add . 21 | git commit -m "Go to the birthday" 22 | 23 | git checkout HEAD~1 24 | echo "You go to the concert!" > you 25 | git add . 26 | git commit -m "Go to the concert" 27 | 28 | git checkout HEAD~1 29 | 30 | git branch -D main 31 | 32 | [win] 33 | 34 | # Create a branch called 'birthday' that points to the birthday timeline. 35 | git show birthday | grep 'to the birthday' 36 | 37 | # Create a branch called 'concert' that points to the concert timeline. 38 | git show concert | grep 'to the concert' 39 | 40 | [congrats] 41 | 42 | Now you can travel between those branches easily (using `git checkout`) - try it! 43 | 44 | Your friend is happy that you made it to the birthday party and you also got your concert ticket signed. Yay! 45 | -------------------------------------------------------------------------------- /levels/branches/grow: -------------------------------------------------------------------------------- 1 | title = Branches grow with you! 2 | cards = checkout commit-auto branch branch-delete reset-hard 3 | 4 | [description] 5 | 6 | Note that there are two options to "travel to the end of a timeline": 7 | 8 | First, you can directly travel to the commit, like we've done it before. 9 | 10 | And second, you can travel to the branch label. In this case, when you make a new commit, the branch will grow with you, and still point at the end of the timeline! 11 | 12 | [cli] 13 | 14 | To travel to a branch, type `git checkout name_of_the_branch`. 15 | 16 | And to travel to the last commit, type `git checkout --detach name_of_the_branch`. 17 | 18 | [setup] 19 | 20 | echo "You wrap the birthday present, and grab your concert ticket." > you 21 | git add . 22 | git commit -m "Evening preparations" 23 | echo "You go to the birthday party!" >> you 24 | git add . 25 | git commit -m "Go to the birthday" 26 | git branch birthday 27 | 28 | git checkout HEAD~1 29 | echo "You go to the concert!" > you 30 | git add . 31 | git commit -m "Go to the concert" 32 | git branch concert 33 | 34 | git checkout HEAD~1 35 | 36 | git branch -D main 37 | 38 | [win] 39 | 40 | # Travel directly to the last yellow commit of the birthday timeline, make a change to 'you', and make a commit 41 | for commit in $(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep 'commit$' | cut -f1 -d' '); do 42 | if test $(git rev-parse $commit^) = $(git rev-parse birthday); then 43 | return 0 44 | fi 45 | done 46 | return 1 47 | 48 | # Travel to the blue 'concert' branch, make a change to 'you', and a commit. 49 | git show concert^ | grep "Go to the concert" 50 | -------------------------------------------------------------------------------- /scenes/cli_badge.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://images/cli-badge.svg" type="Texture" id=1] 4 | [ext_resource path="res://scenes/cli_badge.gd" type="Script" id=2] 5 | 6 | [sub_resource type="Curve" id=1] 7 | _data = [ Vector2( 0, 1 ), 0.0, 0.0, 0, 0, Vector2( 1, 0 ), 0.0, 0.0, 0, 0 ] 8 | 9 | [sub_resource type="CurveTexture" id=2] 10 | curve = SubResource( 1 ) 11 | 12 | [sub_resource type="ParticlesMaterial" id=3] 13 | emission_shape = 2 14 | emission_box_extents = Vector3( 20, 20, 1 ) 15 | flag_disable_z = true 16 | spread = 180.0 17 | gravity = Vector3( 0, 0, 0 ) 18 | initial_velocity = 75.0 19 | initial_velocity_random = 0.47 20 | orbit_velocity = 0.0 21 | orbit_velocity_random = 0.0 22 | scale = 7.25 23 | scale_curve = SubResource( 2 ) 24 | color = Color( 0.913725, 0.913725, 0.447059, 1 ) 25 | 26 | [node name="CLIBadge" type="TextureRect"] 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | rect_min_size = Vector2( 45, 0 ) 30 | size_flags_stretch_ratio = 0.0 31 | texture = ExtResource( 1 ) 32 | expand = true 33 | stretch_mode = 6 34 | script = ExtResource( 2 ) 35 | __meta__ = { 36 | "_edit_use_anchors_": false 37 | } 38 | 39 | [node name="Particles2D" type="Particles2D" parent="."] 40 | show_behind_parent = true 41 | position = Vector2( 21.7701, 14.5133 ) 42 | z_index = 1 43 | amount = 3 44 | randomness = 0.34 45 | process_material = SubResource( 3 ) 46 | 47 | [node name="Nope" type="Line2D" parent="."] 48 | visible = false 49 | position = Vector2( 22.4111, 18.7121 ) 50 | scale = Vector2( 1.32773, 1.32773 ) 51 | points = PoolVector2Array( -16.448, 11.5553, 16.0161, -12.8519 ) 52 | width = 5.0 53 | default_color = Color( 0.0705882, 0.0705882, 0.0705882, 1 ) 54 | -------------------------------------------------------------------------------- /levels/unused/pull-push: -------------------------------------------------------------------------------- 1 | title = Helping each other 2 | cards = checkout commit-auto reset-hard pull push 3 | 4 | [description] 5 | 6 | The events and timelines you see are always only what your own time machine knows about! 7 | 8 | Of course, time agents don't have to work alone! Here, your sidekick has already prepared a merge for you! You can use the "pull" card to transfer it to your own time machine. 9 | 10 | Then, add another event on top (what does Sam have for dinner?), and `push` the result, to transfer it back to your sidekick! 11 | 12 | You can only ever manipulate things in your own time machine (the one on the bottom). 13 | 14 | [setup yours] 15 | 16 | echo "Just woke up. Is hungry." > sam 17 | git add . 18 | git commit -m "The beginning" 19 | 20 | git checkout -b pancakes 21 | echo "Had blueberry pancakes with maple syrup for breakfast." > sam 22 | git add . 23 | git commit -m "Pancakes!" 24 | 25 | git checkout -b muesli main 26 | echo "Had muesli with oats and strawberries for breakfast." > sam 27 | git add . 28 | git commit -m "Muesli!" 29 | 30 | git checkout main 31 | 32 | git push -u sidekick main pancakes muesli 33 | 34 | [setup sidekick] 35 | 36 | git checkout main 37 | git merge pancakes 38 | git merge muesli 39 | 40 | echo "Had pancakes with strawberries for breakfast." > sam 41 | git add . 42 | git commit -m "Let's make this breakfast compromise" --author="Sidekick " 43 | 44 | [win sidekick] 45 | 46 | # Below main's parent, there is a rhombus: 47 | git rev-parse main^^ && test "$(git rev-parse main^^1^)" = "$(git rev-parse main^^2^)" 48 | 49 | [congrats] 50 | 51 | In reality, in many cases, a lot of time agents work together to build a really good future together! :) 52 | -------------------------------------------------------------------------------- /levels/branches/branch-remove: -------------------------------------------------------------------------------- 1 | title = Deleting branches 2 | cards = checkout commit-auto reset-hard branch-delete 3 | 4 | [description] 5 | 6 | Life is full of dangers, right? Even when walking to school, it seems like there's a lot of risks! 7 | 8 | This Monday is especially bad. You made it to school, but there's some timelines you definitely don't want to keep around. 9 | 10 | [setup] 11 | 12 | echo You leave your house and start walking to school. > you 13 | git add . 14 | git commit -m "Good morning!" 15 | 16 | echo You walk on the right side of the street. >> you 17 | git commit -am "Right side" 18 | 19 | echo You jump over an manhole in the walkway, and arrive at school on time. >> you 20 | git commit -am "Jump" 21 | 22 | git checkout HEAD^ -b friend 23 | echo Suddenly, you fall down, splash into stinking water, and are eaten by an alligator. >> you 24 | git commit -am "A new friend" 25 | 26 | git checkout HEAD~2 -b music 27 | echo You walk on the left side of the street. >> you 28 | git commit -am "Left side" 29 | 30 | echo Because you\'re kind of late, you start running. Someone throws a piano out of their windows, and it smashes you. >> you 31 | git commit -am "Sounds nice" 32 | 33 | git checkout HEAD^ -b ice-cream 34 | echo You\'re not in a hurry, and walk slowly. You even get some ice cream on your way. You arrive at school too late, your teacher is angry, and you are expelled. >> you 35 | git commit -am "Yum" 36 | 37 | git branch -M main leap 38 | git checkout leap^^ 39 | 40 | [win] 41 | 42 | # Find the bad branches and delete them. Keep only the best one. 43 | test "$(git show-ref --heads | cut -f2 -d' ')" = "$(echo refs/heads/leap)" 44 | 45 | [congrats] 46 | 47 | On second thought, maybe you even prefer the ice cream timeline to the main one? :) 48 | -------------------------------------------------------------------------------- /levels/branches/fork: -------------------------------------------------------------------------------- 1 | title = Make parallel commits 2 | cards = checkout commit-auto 3 | 4 | [description] 5 | 6 | Did you know that creating parallel timelines is perfectly legal and safe? It's true! 7 | 8 | Can you find out when things went wrong in this zoo? Then, go back to the last good commit and create a parallel universe where everyone is happy! 9 | 10 | [cli] 11 | 12 | The blue animal represents a concept known as the "HEAD pointer" in Git: It shows you which commit is the current one. 13 | 14 | Here's a cool trick to go to the previous commit: 15 | 16 | git checkout HEAD^ 17 | 18 | You can also go back two commits by typing, for example: 19 | 20 | git checkout HEAD~2 21 | 22 | [setup] 23 | 24 | mkdir cage 25 | echo "Looks very hungry." > cage/lion 26 | 27 | echo "A small child. 28 | It really loves cats!" > child 29 | git add . 30 | git commit -m "The beginning" 31 | 32 | echo "It's holding a lollipop." >> child 33 | git commit -am "The child buys something" 34 | 35 | mv child cage 36 | git add . 37 | git commit -m "The child climbs somewhere" 38 | 39 | git rm cage/child 40 | echo "Looks happy. :)" > cage/lion 41 | git add . 42 | git commit -m "Oh no" 43 | 44 | echo "It's sleeping." > cage/lion 45 | git add . 46 | git commit -m "Nap time!" 47 | 48 | git checkout --detach 49 | git branch -d main 50 | 51 | [win] 52 | 53 | # Make sure that the child is happy. 54 | git ls-tree --name-only -r HEAD | grep child 55 | 56 | # Make sure that the lion gets something to eat. 57 | git show HEAD:cage/lion | grep -v "very hungry" 58 | 59 | [congrats] 60 | 61 | Whew, good job! This seems like a *much* better outcome. 62 | 63 | Feel free to add more parallel timelines, or make them longer. 64 | 65 | If you're ready, our next mission is already waiting... 66 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Blue Oak Model License 2 | 3 | Version 1.0.0 4 | 5 | ## Purpose 6 | 7 | This license gives everyone as much permission to work with 8 | this software as possible, while protecting contributors 9 | from liability. 10 | 11 | ## Acceptance 12 | 13 | In order to receive this license, you must agree to its 14 | rules. The rules of this license are both obligations 15 | under that agreement and conditions to your license. 16 | You must not do anything with this software that triggers 17 | a rule that you cannot or will not follow. 18 | 19 | ## Copyright 20 | 21 | Each contributor licenses you to do everything with this 22 | software that would otherwise infringe that contributor's 23 | copyright in it. 24 | 25 | ## Notices 26 | 27 | You must ensure that everyone who gets a copy of 28 | any part of this software from you, with or without 29 | changes, also gets the text of this license or a link to 30 | . 31 | 32 | ## Excuse 33 | 34 | If anyone notifies you in writing that you have not 35 | complied with [Notices](#notices), you can keep your 36 | license by taking all practical steps to comply within 30 37 | days after the notice. If you do not do so, your license 38 | ends immediately. 39 | 40 | ## Patent 41 | 42 | Each contributor licenses you to do everything with this 43 | software that would otherwise infringe any patent claims 44 | they can license or become able to license. 45 | 46 | ## Reliability 47 | 48 | No contributor can revoke this license. 49 | 50 | ## No Liability 51 | 52 | ***As far as the law allows, this software comes as is, 53 | without any warranty or condition, and no contributor 54 | will be liable to anyone for any damages related to this 55 | software or this license, under any kind of legal claim.*** 56 | -------------------------------------------------------------------------------- /levels/index/steps: -------------------------------------------------------------------------------- 1 | title = Adding changes step by step 2 | cards = add reset-file commit 3 | 4 | [description] 5 | 6 | The index is really useful, because it allows us to be precise about which changes we want to include in each commit! 7 | 8 | [setup] 9 | 10 | echo "A hammer, balancing on its handle." > hammer 11 | echo "A bottle, containing a clear liquid." > bottle 12 | echo "A white sugar cube." > sugar_cube 13 | 14 | git add . 15 | git commit -m "The beginning" 16 | 17 | [win] 18 | 19 | # Make changes to all three objects, to form a logical sequence of events! 20 | test "$(git diff --name-only | wc -l)" -eq 3 || file -f .git/candle-changed && touch .git/candle-changed 21 | 22 | # Only add one of these changes! 23 | test "$(git diff --cached --name-only | wc -l)" -eq 1 || file -f .git/candle-added && touch .git/candle-added 24 | 25 | # And make a commit. 26 | COUNT=0 27 | for commit in $(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep 'commit$' | cut -f1 -d' '); do 28 | if test "$(git diff --name-only $commit $commit^ | wc -l)" -eq 1; then 29 | COUNT=$((COUNT+1)) 30 | fi 31 | done 32 | 33 | test "$COUNT" -ge 1 34 | 35 | # Make a second commit that only records a single change. 36 | COUNT=0 37 | for commit in $(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep 'commit$' | cut -f1 -d' '); do 38 | if test "$(git diff --name-only $commit $commit^ | wc -l)" -eq 1; then 39 | COUNT=$((COUNT+1)) 40 | fi 41 | done 42 | 43 | test "$COUNT" -ge 2 44 | 45 | # And a third one. 46 | COUNT=0 47 | for commit in $(git cat-file --batch-check='%(objectname) %(objecttype)' --batch-all-objects | grep 'commit$' | cut -f1 -d' '); do 48 | if test "$(git diff --name-only $commit $commit^ | wc -l)" -eq 1; then 49 | COUNT=$((COUNT+1)) 50 | fi 51 | done 52 | 53 | test "$COUNT" -ge 3 54 | -------------------------------------------------------------------------------- /scenes/card_particles.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [sub_resource type="Curve" id=1] 4 | _data = [ Vector2( 0, 1 ), 0.0, 0.0, 0, 0, Vector2( 1, 0 ), -2.75937, 0.0, 0, 0 ] 5 | 6 | [sub_resource type="CurveTexture" id=2] 7 | curve = SubResource( 1 ) 8 | 9 | [sub_resource type="ParticlesMaterial" id=3] 10 | emission_shape = 2 11 | emission_box_extents = Vector3( 100, 150, 1 ) 12 | flag_disable_z = true 13 | spread = 180.0 14 | gravity = Vector3( 0, 0, 0 ) 15 | initial_velocity = 232.55 16 | initial_velocity_random = 0.52 17 | orbit_velocity = 0.0 18 | orbit_velocity_random = 0.0 19 | scale = 14.95 20 | scale_curve = SubResource( 2 ) 21 | color = Color( 0.223529, 0.592157, 0.772549, 1 ) 22 | 23 | [sub_resource type="Animation" id=4] 24 | resource_name = "play" 25 | length = 2.0 26 | tracks/0/type = "method" 27 | tracks/0/path = NodePath(".") 28 | tracks/0/interp = 1 29 | tracks/0/loop_wrap = true 30 | tracks/0/imported = false 31 | tracks/0/enabled = true 32 | tracks/0/keys = { 33 | "times": PoolRealArray( 2 ), 34 | "transitions": PoolRealArray( 1 ), 35 | "values": [ { 36 | "args": [ ], 37 | "method": "queue_free" 38 | } ] 39 | } 40 | tracks/1/type = "value" 41 | tracks/1/path = NodePath("Particles:emitting") 42 | tracks/1/interp = 1 43 | tracks/1/loop_wrap = true 44 | tracks/1/imported = false 45 | tracks/1/enabled = true 46 | tracks/1/keys = { 47 | "times": PoolRealArray( 0 ), 48 | "transitions": PoolRealArray( 1 ), 49 | "update": 1, 50 | "values": [ true ] 51 | } 52 | 53 | [node name="CardParticles" type="Node2D"] 54 | 55 | [node name="Particles" type="Particles2D" parent="."] 56 | position = Vector2( -0.539337, -145.087 ) 57 | emitting = false 58 | amount = 32 59 | lifetime = 0.2 60 | one_shot = true 61 | explosiveness = 0.91 62 | local_coords = false 63 | process_material = SubResource( 3 ) 64 | 65 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 66 | autoplay = "play" 67 | anims/play = SubResource( 4 ) 68 | -------------------------------------------------------------------------------- /levels/low-level/symref-no-deref: -------------------------------------------------------------------------------- 1 | [description] 2 | 3 | When you have a symbolic ref (a ref pointing at another ref), and you decide you want it to be a regular ref again (pointing to an object), you're in for some trouble! :) 4 | 5 | What happens when you try pointing the symbolic ref directly to the blob using `git update-ref`? 6 | 7 | Oops! Turns out that when you reference a symbolic ref, it acts as if you had specified the ref it points to. To de-symbolic-ize it, use the `--no-deref` option directly after `update-ref`! 8 | 9 | Weird, huh? 10 | 11 | [congrats] 12 | 13 | Whew, we've covered a lot of things: Blobs! The index! Trees! Commits! Refs! 14 | 15 | You now know about almost everything about how Git repositories look like on the inside! We think that's pretty cool! :) 16 | 17 | Everything else is just convention and high-level commands that make interacting with the objects more convenient. 18 | 19 | We haven't covered: 20 | 21 | - tag objects (they are the fourth object type - a bit like refs with a description and an author) 22 | - configuration (allows you to specify remote repositories, for example) 23 | - working with local files (which is, uh, arguably pretty important :P) 24 | 25 | Thanks for playing! You're welcome to check out the "puzzle" levels in the dropdown, some of them are more advanced! 26 | 27 | [setup] 28 | 29 | BLOB1=$(echo delicious | git hash-object -w --stdin) 30 | BLOB2=$(echo very | git hash-object -w --stdin) 31 | git update-ref refs/curly "$BLOB1" 32 | git symbolic-ref refs/fries refs/curly 33 | 34 | [setup goal] 35 | 36 | BLOB1=$(echo delicious | git hash-object -w --stdin) 37 | BLOB2=$(echo very | git hash-object -w --stdin) 38 | git update-ref refs/curly "$BLOB1" 39 | git symbolic-ref refs/fries refs/curly 40 | 41 | git update-ref --no-deref refs/fries "$BLOB2" 42 | 43 | [win] 44 | 45 | git symbolic-ref refs/fries && return 1 46 | test "$(git show-ref -s refs/fries)" = "035e2968dafeea08e46e8fe6743cb8123e8b9aa6" 47 | -------------------------------------------------------------------------------- /scenes/notification.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://fonts/default.tres" type="DynamicFont" id=1] 4 | [ext_resource path="res://styles/theme.tres" type="Theme" id=2] 5 | [ext_resource path="res://scenes/notification.gd" type="Script" id=3] 6 | 7 | [sub_resource type="StyleBoxFlat" id=1] 8 | bg_color = Color( 0.0431373, 0.368627, 0.141176, 0.843137 ) 9 | corner_radius_top_left = 8 10 | corner_radius_top_right = 8 11 | corner_radius_bottom_right = 8 12 | corner_radius_bottom_left = 8 13 | 14 | [node name="Notification" type="Node2D"] 15 | z_index = 4096 16 | script = ExtResource( 3 ) 17 | 18 | [node name="Panel" type="Panel" parent="."] 19 | margin_left = 8.0 20 | margin_top = 8.0 21 | margin_right = 492.0 22 | margin_bottom = 170.0 23 | theme = ExtResource( 2 ) 24 | custom_styles/panel = SubResource( 1 ) 25 | __meta__ = { 26 | "_edit_use_anchors_": false 27 | } 28 | 29 | [node name="Label" type="Label" parent="Panel"] 30 | anchor_right = 1.0 31 | anchor_bottom = 1.0 32 | margin_left = 16.0 33 | margin_top = 13.0 34 | margin_right = -15.0 35 | margin_bottom = -15.0 36 | custom_fonts/font = ExtResource( 1 ) 37 | text = "This is a hint! This is a hint! This is a hint! This is a hint! This is a hint! This is a hint! This is a hint! This is a hint! This is a hint!" 38 | autowrap = true 39 | __meta__ = { 40 | "_edit_use_anchors_": false 41 | } 42 | 43 | [node name="CenterContainer" type="CenterContainer" parent="Panel"] 44 | anchor_top = 1.0 45 | anchor_right = 1.0 46 | anchor_bottom = 1.0 47 | margin_top = -58.0 48 | __meta__ = { 49 | "_edit_use_anchors_": false 50 | } 51 | 52 | [node name="OK" type="Button" parent="Panel/CenterContainer"] 53 | margin_left = 202.0 54 | margin_top = 9.0 55 | margin_right = 282.0 56 | margin_bottom = 48.0 57 | focus_mode = 0 58 | enabled_focus_mode = 0 59 | text = "Got it!" 60 | __meta__ = { 61 | "_edit_use_anchors_": false 62 | } 63 | [connection signal="pressed" from="Panel/CenterContainer/OK" to="." method="confirm"] 64 | -------------------------------------------------------------------------------- /scenes/level_select.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | onready var level_list = $ScrollContainer/MarginContainer/Levels 4 | 5 | func _ready(): 6 | reload() 7 | 8 | func load(chapter_id, level_id): 9 | game.current_chapter = chapter_id 10 | game.current_level = level_id 11 | get_tree().change_scene("res://scenes/main.tscn") 12 | 13 | func back(): 14 | get_tree().change_scene("res://scenes/title.tscn") 15 | 16 | 17 | func reload(): 18 | for child in level_list.get_children(): 19 | child.queue_free() 20 | 21 | var chapter_id = 0 22 | 23 | levels.reload() 24 | 25 | for chapter in levels.chapters: 26 | var level_id = 0 27 | 28 | var l = Label.new() 29 | l.text = chapter.slug 30 | l.set("custom_fonts/font", preload("res://fonts/big.tres")) 31 | l.align = HALIGN_CENTER 32 | level_list.add_child(l) 33 | 34 | for level in chapter.levels: 35 | var hb = HBoxContainer.new() 36 | 37 | var b = Button.new() 38 | b.text = level.title 39 | b.align = HALIGN_LEFT 40 | b.size_flags_horizontal = SIZE_EXPAND_FILL 41 | 42 | b.connect("pressed", self, "load", [chapter_id, level_id]) 43 | var slug = chapter.slug + "/" + level.slug 44 | if slug in game.state["solved_levels"]: 45 | b.set("custom_colors/font_color", Color(0.1, 0.8, 0.1, 1)) 46 | b.set("custom_colors/font_color_hover", Color(0.1, 0.8, 0.1, 1)) 47 | b.set("custom_colors/font_color_pressed", Color(0.1, 0.8, 0.1, 1)) 48 | 49 | hb.add_child(b) 50 | # 51 | var badge = preload("res://scenes/cli_badge.tscn").instance() 52 | hb.add_child(badge) 53 | badge.active = slug in game.state["cli_badge"] 54 | badge.sparkling = false 55 | 56 | level_list.add_child(hb) 57 | 58 | if badge.active: 59 | game.notify("You get a golden badge for each level you solve without using the playing cards! Can you solve them all using the command line?", badge, "cli-badge") 60 | level_id += 1 61 | 62 | chapter_id += 1 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /levels/merge/merge: -------------------------------------------------------------------------------- 1 | title = Merging timelines 2 | cards = checkout commit-auto merge 3 | 4 | [description] 5 | 6 | Here's a trick so that you can sleep a bit longer: just do all your morning activities in parallel universes, and then at the end, merge them together! 7 | 8 | [setup] 9 | 10 | echo "You do not have a baguette. 11 | 12 | You do not have coffee. 13 | 14 | You do not have a donut." > you 15 | 16 | git add . 17 | git commit -m "The Beginning" 18 | 19 | echo "You have a baguette. 20 | 21 | You do not have coffee. 22 | 23 | You do not have a donut." > you 24 | git add . 25 | git commit -m "You buy a baguette" 26 | 27 | echo "You ate a baguette. 28 | 29 | You do not have coffee. 30 | 31 | You do not have a donut." > you 32 | git add . 33 | git commit -m "You eat the baguette" 34 | 35 | git checkout HEAD~2 36 | echo "You do not have a baguette. 37 | 38 | You have coffee. 39 | 40 | You do not have a donut." > you 41 | git add . 42 | git commit -m "You buy some coffee" 43 | 44 | echo "You do not have a baguette. 45 | 46 | You drank coffee. 47 | 48 | You do not have a donut." > you 49 | git add . 50 | git commit -m "You drink the coffee" 51 | 52 | git checkout HEAD~2 53 | echo "You do not have a baguette. 54 | 55 | You do not have coffee. 56 | 57 | You have a donut." > you 58 | git add . 59 | git commit -m "You buy a donut" 60 | 61 | echo "You do not have a baguette. 62 | 63 | You do not have coffee. 64 | 65 | You ate a donut." > you 66 | git add . 67 | git commit -m "You eat the donut" 68 | 69 | git checkout --detach 70 | git branch -D main 71 | 72 | [win] 73 | 74 | # Build a situation where you consumed a baguette, a coffee, *and* a donut. 75 | { git show HEAD:you | grep "You ate.*baguette"; } && { git show HEAD:you | grep "You drank.*coffee"; } && { git show HEAD:you | grep "You ate.*donut"; } 76 | 77 | # Be on a merge commit. 78 | test "$(git log --pretty=%P -n 1 HEAD | wc -w)" -ge 2 79 | 80 | [congrats] 81 | 82 | I wonder if you're more relaxed when you *sleep* in parallel timelines... 83 | -------------------------------------------------------------------------------- /nodes/blob.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 55 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /levels/changing-the-past/reorder: -------------------------------------------------------------------------------- 1 | title = Reordering events 2 | cards = checkout commit-auto reset-hard rebase-interactive cherry-pick 3 | 4 | [description] 5 | 6 | Oops, looks like there's something messed up here. Can you put the events back into their correct order? 7 | 8 | There are two ways to do this: You can drag the "interactive rebase" card to the commit before the one you want to change, then reorder the lines in the file that opens, and save it. 9 | 10 | Or you can reset the main tag to the very first commit, and then cherry-pick single commits in the order you want. You have cards for both approaches! 11 | 12 | [setup] 13 | 14 | echo "You just woke up. 15 | 16 | You are NOT wearing underwear. 17 | 18 | You are NOT wearing pants. 19 | 20 | You are NOT wearing a shirt. 21 | 22 | You are NOT wearing shoes." > you 23 | git add . 24 | 25 | git commit -m "The Beginning" 26 | 27 | echo "You just woke up. 28 | 29 | You are NOT wearing underwear. 30 | 31 | You are NOT wearing pants. 32 | 33 | You are NOT wearing a shirt. 34 | 35 | You are wearing shoes." > you 36 | git commit -am "Put on shoes" 37 | 38 | echo "You just woke up. 39 | 40 | You are NOT wearing underwear. 41 | 42 | You are wearing pants. 43 | 44 | You are NOT wearing a shirt. 45 | 46 | You are wearing shoes." > you 47 | git commit -am "Put on pants" 48 | 49 | echo "You just woke up. 50 | 51 | You are wearing underwear. 52 | 53 | You are wearing pants. 54 | 55 | You are NOT wearing a shirt. 56 | 57 | You are wearing shoes." > you 58 | git commit -am "Put on underwear" 59 | 60 | echo "You just woke up. 61 | 62 | You are wearing underwear. 63 | 64 | You are wearing pants. 65 | 66 | You are wearing a shirt. 67 | 68 | You are wearing shoes." > you 69 | git commit -am "Put on shirt" 70 | 71 | [win] 72 | 73 | # Reorder the commits to dress yourself in the correct way 74 | { git log main --oneline | perl -0777 -ne'exit(1) if not /shoes[\s\S]*pants[\s\S]*underwear/'; } && { test "$(git log main --oneline | wc -l)" -eq 5; } 75 | 76 | [congrats] 77 | 78 | Feel free to reset the level and try the other strategy! Which one do you like better? 79 | -------------------------------------------------------------------------------- /nodes/commit.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /scenes/better_shell.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name BetterShell 3 | 4 | var exit_code 5 | 6 | var _cwd 7 | var _os = OS.get_name() 8 | 9 | func _init(): 10 | # Create required directories and move into the tmp directory. 11 | _cwd = "/tmp" 12 | run("mkdir -p '%s/repos'" % game.tmp_prefix) 13 | _cwd = game.tmp_prefix 14 | 15 | func cd(dir): 16 | _cwd = dir 17 | 18 | # Run a shell command given as a string. Run this if you're interested in the 19 | # output of the command. 20 | func run(command, crash_on_fail=true): 21 | var shell_command = ShellCommand.new() 22 | shell_command.command = command 23 | shell_command.crash_on_fail = crash_on_fail 24 | 25 | run_async_thread(shell_command) 26 | exit_code = shell_command.exit_code 27 | return shell_command.output 28 | 29 | func run_async(command, crash_on_fail=true): 30 | var shell_command = ShellCommand.new() 31 | shell_command.command = command 32 | shell_command.crash_on_fail = crash_on_fail 33 | 34 | var t = Thread.new() 35 | shell_command.thread = t 36 | t.start(self, "run_async_thread", shell_command) 37 | 38 | return shell_command 39 | 40 | func run_async_thread(shell_command): 41 | var debug = false 42 | 43 | var command = shell_command.command 44 | var crash_on_fail = shell_command.crash_on_fail 45 | 46 | if debug: 47 | print("$ %s" % command) 48 | 49 | var env = {} 50 | env["HOME"] = game.tmp_prefix 51 | 52 | var hacky_command = "" 53 | for variable in env: 54 | hacky_command += "export %s='%s';" % [variable, env[variable]] 55 | 56 | #hacky_command += "export PATH=\'"+game.tmp_prefix+":'\"$PATH\";" 57 | hacky_command += "cd '%s' || exit 1;" % _cwd 58 | hacky_command += command 59 | 60 | #print(hacky_command) 61 | 62 | var result 63 | var shell_command_internal = game.shell_test(hacky_command) 64 | 65 | shell_command.output = shell_command_internal.output 66 | shell_command.exit_code = shell_command_internal.exit_code 67 | shell_command.emit_signal("done") 68 | 69 | func _shell_binary(): 70 | if _os == "X11" or _os == "OSX": 71 | return "bash" 72 | elif _os == "Windows": 73 | return "dependencies\\windows\\git\\bin\\bash.exe" 74 | else: 75 | helpers.crash("Unsupported OS: %s" % _os) 76 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build" 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | PROJECT_NAME: oh-my-git 10 | GODOT_VERSION: 3.2.3 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-20.04 15 | container: 16 | image: barichello/godot-ci:3.2.3 17 | steps: 18 | - name: Get tag name 19 | id: tag_name 20 | run: echo ::set-output name=TAG_NAME::${GITHUB_REF#refs/tags/} 21 | - name: Install dependencies 22 | run: apt-get update && apt-get install -y rsync p7zip make 23 | - uses: actions/checkout@v2 24 | - name: Setup 25 | run: | 26 | mkdir -p ~/.local/share/godot/templates 27 | mv /root/.local/share/godot/templates/$GODOT_VERSION.stable ~/.local/share/godot/templates/$GODOT_VERSION.stable 28 | - name: Build Linux 29 | run: make linux 30 | - name: Build macOS 31 | run: make macos 32 | - name: Build Windows 33 | run: make windows 34 | - name: Clean up 35 | run: make clean-unzipped 36 | - name: Deploy Linux 37 | uses: josephbmanley/butler-publish-itchio-action@v1.0.2 38 | env: 39 | BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} 40 | CHANNEL: linux 41 | ITCH_GAME: ${{ env.PROJECT_NAME }} 42 | ITCH_USER: blinry 43 | PACKAGE: build/${{ env.PROJECT_NAME }}-linux.zip 44 | VERSION: ${{ steps.tag_name.outputs.TAG_NAME }} 45 | - name: Deploy macOS 46 | uses: josephbmanley/butler-publish-itchio-action@v1.0.2 47 | env: 48 | BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} 49 | CHANNEL: osx 50 | ITCH_GAME: ${{ env.PROJECT_NAME }} 51 | ITCH_USER: blinry 52 | PACKAGE: build/${{ env.PROJECT_NAME }}-macos.zip 53 | VERSION: ${{ steps.tag_name.outputs.TAG_NAME }} 54 | - name: Deploy Windows 55 | uses: josephbmanley/butler-publish-itchio-action@v1.0.2 56 | env: 57 | BUTLER_CREDENTIALS: ${{ secrets.BUTLER_CREDENTIALS }} 58 | CHANNEL: windows 59 | ITCH_GAME: ${{ env.PROJECT_NAME }} 60 | ITCH_USER: blinry 61 | PACKAGE: build/${{ env.PROJECT_NAME }}-windows.zip 62 | VERSION: ${{ steps.tag_name.outputs.TAG_NAME }} 63 | -------------------------------------------------------------------------------- /levels/branches/reorder: -------------------------------------------------------------------------------- 1 | title = Moving branches around 2 | cards = checkout commit-auto merge reset-hard 3 | 4 | [description] 5 | 6 | One of your colleagues messed up here, and put the branches in the wrong timelines! 7 | 8 | You could delete and re-create these branches - but you can also directly move them to different commits, by using 9 | 10 | git checkout 11 | 12 | on the branch names, and then using 13 | 14 | git reset --hard 15 | 16 | on the commit where you want the branch to be. 17 | 18 | The donut branch is in the right place, but the timeline is still incomplete - make you actually *eat* the donut in that branch! 19 | 20 | [setup] 21 | 22 | echo "You do not have a baguette. 23 | 24 | You do not have coffee. 25 | 26 | You do not have a donut." > you 27 | 28 | git add . 29 | git commit -m "The Beginning" 30 | 31 | git checkout -b coffee 32 | echo "You have a baguette. 33 | 34 | You do not have coffee. 35 | 36 | You do not have a donut." > you 37 | git add . 38 | git commit -m "You buy a baguette" 39 | 40 | echo "You ate a baguette. 41 | 42 | You do not have coffee. 43 | 44 | You do not have a donut." > you 45 | git add . 46 | git commit -m "You eat the baguette" 47 | 48 | git checkout -b baguette main 49 | echo "You do not have a baguette. 50 | 51 | You have coffee. 52 | 53 | You do not have a donut." > you 54 | git add . 55 | git commit -m "You buy some coffee" 56 | 57 | echo "You do not have a baguette. 58 | 59 | You drank coffee. 60 | 61 | You do not have a donut." > you 62 | git add . 63 | git commit -m "You drink the coffee" 64 | 65 | git checkout -b donut main 66 | echo "You do not have a baguette. 67 | 68 | You do not have coffee. 69 | 70 | You have a donut." > you 71 | git add . 72 | git commit -m "You buy a donut" 73 | 74 | git checkout --detach main 75 | 76 | [win] 77 | 78 | # Did you eat a baguette on the baguette branch? 79 | git show baguette:you | grep "You ate.*baguette" 80 | 81 | # Did you drink a coffee on the coffee branch? 82 | git show coffee:you | grep "You drank.*coffee" 83 | 84 | # Did you eat a donut on the donut branch? 85 | git show donut:you | grep "You ate.*donut" 86 | 87 | [actions] 88 | 89 | test "$(git rev-parse HEAD^)" = "$(git rev-parse donut)" && hint "Remember to checkout the blue branch tag when you want it to grow with the timeline." 90 | -------------------------------------------------------------------------------- /scenes/title.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://styles/theme.tres" type="Theme" id=1] 4 | [ext_resource path="res://images/oh-my-git.png" type="Texture" id=2] 5 | [ext_resource path="res://scenes/title.gd" type="Script" id=3] 6 | [ext_resource path="res://fonts/default.tres" type="DynamicFont" id=5] 7 | 8 | [node name="Title" type="Control"] 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | theme = ExtResource( 1 ) 12 | script = ExtResource( 3 ) 13 | __meta__ = { 14 | "_edit_use_anchors_": false 15 | } 16 | 17 | [node name="Label2" type="Label" parent="."] 18 | margin_left = 790.778 19 | margin_top = 594.135 20 | margin_right = 1131.78 21 | margin_bottom = 650.135 22 | custom_fonts/font = ExtResource( 5 ) 23 | custom_colors/font_color = Color( 0.533333, 0.392157, 0.392157, 1 ) 24 | text = "by bleeptrack & blinry" 25 | align = 1 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="Label3" type="Label" parent="."] 31 | margin_left = 709.713 32 | margin_top = 621.416 33 | margin_right = 1209.71 34 | margin_bottom = 677.416 35 | custom_fonts/font = ExtResource( 5 ) 36 | custom_colors/font_color = Color( 0.533333, 0.392157, 0.392157, 1 ) 37 | text = "Original score by winniehell" 38 | align = 1 39 | __meta__ = { 40 | "_edit_use_anchors_": false 41 | } 42 | 43 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 44 | margin_left = 784.553 45 | margin_top = 702.517 46 | margin_right = 1136.55 47 | margin_bottom = 885.517 48 | __meta__ = { 49 | "_edit_use_anchors_": false 50 | } 51 | 52 | [node name="Button" type="Button" parent="VBoxContainer"] 53 | margin_right = 351.0 54 | margin_bottom = 39.0 55 | text = "Levels" 56 | 57 | [node name="Button3" type="Button" parent="VBoxContainer"] 58 | margin_top = 44.0 59 | margin_right = 351.0 60 | margin_bottom = 83.0 61 | text = "Sandbox" 62 | 63 | [node name="Button2" type="Button" parent="VBoxContainer"] 64 | margin_top = 88.0 65 | margin_right = 351.0 66 | margin_bottom = 127.0 67 | text = "Quit" 68 | 69 | [node name="Sprite" type="Sprite" parent="."] 70 | 71 | [node name="oh-my-git" type="Sprite" parent="Sprite"] 72 | position = Vector2( 967.924, 306.066 ) 73 | scale = Vector2( 0.320895, 0.320895 ) 74 | texture = ExtResource( 2 ) 75 | [connection signal="pressed" from="VBoxContainer/Button" to="." method="levels"] 76 | [connection signal="pressed" from="VBoxContainer/Button3" to="." method="sandbox"] 77 | [connection signal="pressed" from="VBoxContainer/Button2" to="." method="quit"] 78 | -------------------------------------------------------------------------------- /levels/changing-the-past/rebase: -------------------------------------------------------------------------------- 1 | title = Rebasing 2 | cards = checkout commit-auto reset-hard rebase 3 | 4 | [description] 5 | 6 | Okay - turns out that saving time in the morning by utilizing parallel universes is against the regulations of the International Time Travel Association. You'll have to do your tasks in sequence after all. 7 | 8 | See the "rebase" card? When you drag it to a commit, it will copy the events in your current timeline after the specified one! This way, make a clean, linear timeline where you visit all three shops. 9 | 10 | Again, we want to make that our base reality - the "main" branch should point to that timeline! 11 | 12 | [setup] 13 | 14 | echo "You do not have a baguette. 15 | 16 | You do not have coffee. 17 | 18 | You do not have a donut." > you 19 | 20 | git add . 21 | git commit -m "The Beginning" 22 | 23 | git checkout -b baguette main 24 | echo "You have a baguette. 25 | 26 | You do not have coffee. 27 | 28 | You do not have a donut." > you 29 | git add . 30 | git commit -m "You buy a baguette" 31 | 32 | echo "You ate a baguette. 33 | 34 | You do not have coffee. 35 | 36 | You do not have a donut." > you 37 | git add . 38 | git commit -m "You eat the baguette" 39 | 40 | git checkout -b coffee main 41 | echo "You do not have a baguette. 42 | 43 | You have coffee. 44 | 45 | You do not have a donut." > you 46 | git add . 47 | git commit -m "You buy some coffee" 48 | 49 | echo "You do not have a baguette. 50 | 51 | You drank coffee. 52 | 53 | You do not have a donut." > you 54 | git add . 55 | git commit -m "You drink the coffee" 56 | 57 | git checkout -b donut main 58 | echo "You do not have a baguette. 59 | 60 | You do not have coffee. 61 | 62 | You have a donut." > you 63 | git add . 64 | git commit -m "You buy a donut" 65 | 66 | echo "You do not have a baguette. 67 | 68 | You do not have coffee. 69 | 70 | You ate a donut." > you 71 | git add . 72 | git commit -m "You eat the donut" 73 | 74 | git checkout --detach main 75 | 76 | 77 | [win] 78 | 79 | # Order all tree branches into one and move the main branch ref 80 | { git show main:you | grep "You ate.*baguette"; } && { git show main:you | grep "You drank.*coffee"; } && { git show main:you | grep "You ate.*donut"; } && { test "$(git log main --oneline | wc -l)" -eq 7; } 81 | 82 | [congrats] 83 | 84 | Notice how the other timelines and commits are still there - if anything goes wrong, you can also travel back to them. 85 | 86 | It's really hard to actually *destroy* stuff with your time machine. 87 | --------------------------------------------------------------------------------