gitgym 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. gitgym-0.1.0/.gitignore +29 -0
  2. gitgym-0.1.0/LICENSE +21 -0
  3. gitgym-0.1.0/PKG-INFO +140 -0
  4. gitgym-0.1.0/README.md +110 -0
  5. gitgym-0.1.0/exercises/.gitkeep +0 -0
  6. gitgym-0.1.0/exercises/01_basics/01_init/exercise.toml +20 -0
  7. gitgym-0.1.0/exercises/01_basics/01_init/setup.sh +15 -0
  8. gitgym-0.1.0/exercises/01_basics/01_init/verify.sh +15 -0
  9. gitgym-0.1.0/exercises/01_basics/02_staging/exercise.toml +21 -0
  10. gitgym-0.1.0/exercises/01_basics/02_staging/setup.sh +18 -0
  11. gitgym-0.1.0/exercises/01_basics/02_staging/verify.sh +15 -0
  12. gitgym-0.1.0/exercises/01_basics/03_status/exercise.toml +28 -0
  13. gitgym-0.1.0/exercises/01_basics/03_status/setup.sh +27 -0
  14. gitgym-0.1.0/exercises/01_basics/03_status/verify.sh +35 -0
  15. gitgym-0.1.0/exercises/01_basics/04_first_commit/exercise.toml +22 -0
  16. gitgym-0.1.0/exercises/01_basics/04_first_commit/setup.sh +19 -0
  17. gitgym-0.1.0/exercises/01_basics/04_first_commit/verify.sh +24 -0
  18. gitgym-0.1.0/exercises/01_basics/05_gitignore/exercise.toml +30 -0
  19. gitgym-0.1.0/exercises/01_basics/05_gitignore/setup.sh +25 -0
  20. gitgym-0.1.0/exercises/01_basics/05_gitignore/verify.sh +35 -0
  21. gitgym-0.1.0/exercises/02_committing/01_amend/exercise.toml +22 -0
  22. gitgym-0.1.0/exercises/02_committing/01_amend/setup.sh +19 -0
  23. gitgym-0.1.0/exercises/02_committing/01_amend/verify.sh +28 -0
  24. gitgym-0.1.0/exercises/02_committing/02_multi_commit/exercise.toml +25 -0
  25. gitgym-0.1.0/exercises/02_committing/02_multi_commit/setup.sh +19 -0
  26. gitgym-0.1.0/exercises/02_committing/02_multi_commit/verify.sh +24 -0
  27. gitgym-0.1.0/exercises/02_committing/03_diff/exercise.toml +24 -0
  28. gitgym-0.1.0/exercises/02_committing/03_diff/setup.sh +29 -0
  29. gitgym-0.1.0/exercises/02_committing/03_diff/verify.sh +38 -0
  30. gitgym-0.1.0/exercises/03_branching/01_create_branch/exercise.toml +21 -0
  31. gitgym-0.1.0/exercises/03_branching/01_create_branch/setup.sh +20 -0
  32. gitgym-0.1.0/exercises/03_branching/01_create_branch/verify.sh +23 -0
  33. gitgym-0.1.0/exercises/03_branching/02_switch_branches/exercise.toml +21 -0
  34. gitgym-0.1.0/exercises/03_branching/02_switch_branches/setup.sh +23 -0
  35. gitgym-0.1.0/exercises/03_branching/02_switch_branches/verify.sh +16 -0
  36. gitgym-0.1.0/exercises/03_branching/03_delete_branch/exercise.toml +21 -0
  37. gitgym-0.1.0/exercises/03_branching/03_delete_branch/setup.sh +30 -0
  38. gitgym-0.1.0/exercises/03_branching/03_delete_branch/verify.sh +22 -0
  39. gitgym-0.1.0/exercises/04_merging/01_fast_forward/exercise.toml +25 -0
  40. gitgym-0.1.0/exercises/04_merging/01_fast_forward/setup.sh +33 -0
  41. gitgym-0.1.0/exercises/04_merging/01_fast_forward/verify.sh +42 -0
  42. gitgym-0.1.0/exercises/04_merging/02_three_way_merge/exercise.toml +24 -0
  43. gitgym-0.1.0/exercises/04_merging/02_three_way_merge/setup.sh +34 -0
  44. gitgym-0.1.0/exercises/04_merging/02_three_way_merge/verify.sh +39 -0
  45. gitgym-0.1.0/exercises/04_merging/03_merge_conflict/exercise.toml +25 -0
  46. gitgym-0.1.0/exercises/04_merging/03_merge_conflict/setup.sh +34 -0
  47. gitgym-0.1.0/exercises/04_merging/03_merge_conflict/verify.sh +46 -0
  48. gitgym-0.1.0/exercises/05_history/01_log_basics/exercise.toml +27 -0
  49. gitgym-0.1.0/exercises/05_history/01_log_basics/setup.sh +59 -0
  50. gitgym-0.1.0/exercises/05_history/01_log_basics/verify.sh +47 -0
  51. gitgym-0.1.0/exercises/05_history/02_log_graph/exercise.toml +28 -0
  52. gitgym-0.1.0/exercises/05_history/02_log_graph/setup.sh +44 -0
  53. gitgym-0.1.0/exercises/05_history/02_log_graph/verify.sh +35 -0
  54. gitgym-0.1.0/exercises/05_history/03_blame/exercise.toml +25 -0
  55. gitgym-0.1.0/exercises/05_history/03_blame/setup.sh +46 -0
  56. gitgym-0.1.0/exercises/05_history/03_blame/verify.sh +48 -0
  57. gitgym-0.1.0/exercises/05_history/04_show/exercise.toml +28 -0
  58. gitgym-0.1.0/exercises/05_history/04_show/setup.sh +35 -0
  59. gitgym-0.1.0/exercises/05_history/04_show/verify.sh +43 -0
  60. gitgym-0.1.0/exercises/06_undoing/01_restore_file/exercise.toml +27 -0
  61. gitgym-0.1.0/exercises/06_undoing/01_restore_file/setup.sh +32 -0
  62. gitgym-0.1.0/exercises/06_undoing/01_restore_file/verify.sh +33 -0
  63. gitgym-0.1.0/exercises/06_undoing/02_unstage/exercise.toml +24 -0
  64. gitgym-0.1.0/exercises/06_undoing/02_unstage/setup.sh +27 -0
  65. gitgym-0.1.0/exercises/06_undoing/02_unstage/verify.sh +30 -0
  66. gitgym-0.1.0/exercises/06_undoing/03_revert/exercise.toml +27 -0
  67. gitgym-0.1.0/exercises/06_undoing/03_revert/setup.sh +41 -0
  68. gitgym-0.1.0/exercises/06_undoing/03_revert/verify.sh +41 -0
  69. gitgym-0.1.0/exercises/06_undoing/04_reset_soft/exercise.toml +26 -0
  70. gitgym-0.1.0/exercises/06_undoing/04_reset_soft/setup.sh +28 -0
  71. gitgym-0.1.0/exercises/06_undoing/04_reset_soft/verify.sh +33 -0
  72. gitgym-0.1.0/exercises/06_undoing/05_reset_mixed/exercise.toml +27 -0
  73. gitgym-0.1.0/exercises/06_undoing/05_reset_mixed/setup.sh +32 -0
  74. gitgym-0.1.0/exercises/06_undoing/05_reset_mixed/verify.sh +33 -0
  75. gitgym-0.1.0/exercises/07_rebase/01_basic_rebase/exercise.toml +25 -0
  76. gitgym-0.1.0/exercises/07_rebase/01_basic_rebase/setup.sh +41 -0
  77. gitgym-0.1.0/exercises/07_rebase/01_basic_rebase/verify.sh +43 -0
  78. gitgym-0.1.0/exercises/07_rebase/02_interactive_rebase/exercise.toml +32 -0
  79. gitgym-0.1.0/exercises/07_rebase/02_interactive_rebase/setup.sh +37 -0
  80. gitgym-0.1.0/exercises/07_rebase/02_interactive_rebase/verify.sh +40 -0
  81. gitgym-0.1.0/exercises/07_rebase/03_rebase_conflict/exercise.toml +28 -0
  82. gitgym-0.1.0/exercises/07_rebase/03_rebase_conflict/setup.sh +35 -0
  83. gitgym-0.1.0/exercises/07_rebase/03_rebase_conflict/verify.sh +41 -0
  84. gitgym-0.1.0/exercises/08_stashing/01_stash_basics/exercise.toml +26 -0
  85. gitgym-0.1.0/exercises/08_stashing/01_stash_basics/setup.sh +33 -0
  86. gitgym-0.1.0/exercises/08_stashing/01_stash_basics/verify.sh +31 -0
  87. gitgym-0.1.0/exercises/08_stashing/02_stash_pop_apply/exercise.toml +29 -0
  88. gitgym-0.1.0/exercises/08_stashing/02_stash_pop_apply/setup.sh +28 -0
  89. gitgym-0.1.0/exercises/08_stashing/02_stash_pop_apply/verify.sh +33 -0
  90. gitgym-0.1.0/exercises/09_advanced/01_cherry_pick/exercise.toml +31 -0
  91. gitgym-0.1.0/exercises/09_advanced/01_cherry_pick/setup.sh +43 -0
  92. gitgym-0.1.0/exercises/09_advanced/01_cherry_pick/verify.sh +39 -0
  93. gitgym-0.1.0/exercises/09_advanced/02_bisect/exercise.toml +38 -0
  94. gitgym-0.1.0/exercises/09_advanced/02_bisect/setup.sh +73 -0
  95. gitgym-0.1.0/exercises/09_advanced/02_bisect/verify.sh +48 -0
  96. gitgym-0.1.0/exercises/09_advanced/03_tags/exercise.toml +31 -0
  97. gitgym-0.1.0/exercises/09_advanced/03_tags/setup.sh +30 -0
  98. gitgym-0.1.0/exercises/09_advanced/03_tags/verify.sh +65 -0
  99. gitgym-0.1.0/exercises/09_advanced/04_aliases/exercise.toml +33 -0
  100. gitgym-0.1.0/exercises/09_advanced/04_aliases/setup.sh +19 -0
  101. gitgym-0.1.0/exercises/09_advanced/04_aliases/verify.sh +27 -0
  102. gitgym-0.1.0/pyproject.toml +63 -0
  103. gitgym-0.1.0/src/gitgym/__init__.py +6 -0
  104. gitgym-0.1.0/src/gitgym/__main__.py +3 -0
  105. gitgym-0.1.0/src/gitgym/cli.py +441 -0
  106. gitgym-0.1.0/src/gitgym/config.py +9 -0
  107. gitgym-0.1.0/src/gitgym/display.py +95 -0
  108. gitgym-0.1.0/src/gitgym/exercise.py +45 -0
  109. gitgym-0.1.0/src/gitgym/progress.py +86 -0
  110. gitgym-0.1.0/src/gitgym/runner.py +105 -0
  111. gitgym-0.1.0/src/gitgym/watcher.py +188 -0
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .pytest_cache/
6
+ .venv/
7
+ *.egg-info/
8
+
9
+ # Node.js
10
+ node_modules/
11
+ dist/
12
+ *.tsbuildinfo
13
+
14
+ # Test coverage
15
+ coverage/
16
+
17
+ # Environment
18
+ .env
19
+ .env.local
20
+
21
+ # OS
22
+ .DS_Store
23
+ Thumbs.db
24
+
25
+ # Claude Code workflow
26
+ prompt.md
27
+ specs/
28
+ run_loop.sh
29
+ logs/
gitgym-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Marquez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gitgym-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitgym
3
+ Version: 0.1.0
4
+ Summary: An interactive CLI platform for learning git, inspired by Rustlings.
5
+ Project-URL: Homepage, https://github.com/enerrio/git-gym
6
+ Project-URL: Repository, https://github.com/enerrio/git-gym
7
+ Project-URL: Issues, https://github.com/enerrio/git-gym/issues
8
+ Project-URL: Changelog, https://github.com/enerrio/git-gym/blob/main/CHANGELOG.md
9
+ Author: enerrio
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: cli,education,exercises,git,learning,tutorial
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: MacOS
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Education
24
+ Classifier: Topic :: Software Development :: Version Control :: Git
25
+ Requires-Python: >=3.12
26
+ Requires-Dist: click
27
+ Provides-Extra: watch
28
+ Requires-Dist: watchdog; extra == 'watch'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # gitgym
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/gitgym)](https://pypi.org/project/gitgym/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/gitgym)](https://pypi.org/project/gitgym/)
35
+ [![CI](https://github.com/enerrio/gitgym/actions/workflows/ci.yml/badge.svg)](https://github.com/enerrio/gitgym/actions/workflows/ci.yml)
36
+
37
+ An interactive CLI for learning git through hands-on exercises, inspired by [Rustlings](https://github.com/rust-lang/rustlings). No quizzes — you practice real git commands in real repositories inside a safe, sandboxed workspace.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install gitgym
43
+ ```
44
+
45
+ Or with [uv](https://docs.astral.sh/uv/):
46
+
47
+ ```bash
48
+ uv tool install gitgym
49
+ ```
50
+
51
+ **Requirements:** Python 3.12+ and git. macOS and Linux only (Windows is not currently supported).
52
+
53
+ ## Quick Start
54
+
55
+ ```bash
56
+ gitgym list # see all 32 exercises
57
+ gitgym next # set up the next exercise
58
+ cd ~/.gitgym/exercises/01_basics/01_init # cd into the printed path
59
+ # ... run git commands to solve the exercise ...
60
+ gitgym verify # check your work
61
+ ```
62
+
63
+ ## Recommended Setup
64
+
65
+ Open **two terminal windows** (or tabs) side by side:
66
+
67
+ | Terminal 1 (work) | Terminal 2 (control) |
68
+ | --------------------------------------- | --------------------------------- |
69
+ | `cd` into the exercise directory | `gitgym describe` — read the goal |
70
+ | Run git commands to solve the exercise | `gitgym hint` — get a hint |
71
+ | Edit files, stage, commit, branch, etc. | `gitgym verify` — check your work |
72
+ | | `gitgym watch` — live feedback |
73
+
74
+ This keeps your git work separate from the gitgym CLI, just like a real workflow.
75
+
76
+ ## Commands
77
+
78
+ | Command | Description |
79
+ | ------------------------- | ---------------------------------------------------------- |
80
+ | `gitgym list` | List all exercises grouped by topic with completion status |
81
+ | `gitgym start [exercise]` | Set up an exercise (defaults to next incomplete) |
82
+ | `gitgym next` | Alias for `gitgym start` with no argument |
83
+ | `gitgym describe` | Print the current exercise's description and goal |
84
+ | `gitgym verify` | Check if the current exercise's goal state is met |
85
+ | `gitgym watch` | Auto re-verify on changes (Ctrl+C to stop) |
86
+ | `gitgym hint` | Show the next progressive hint |
87
+ | `gitgym reset [exercise]` | Reset an exercise to its initial state |
88
+ | `gitgym reset --all` | Reset all exercises and clear progress |
89
+ | `gitgym progress` | Show overall progress summary |
90
+ | `gitgym clean` | Remove all gitgym data from your system |
91
+
92
+ Exercise names are shown in `gitgym list` (e.g. `init`, `staging`, `amend`). Use these names with `gitgym start` and `gitgym reset`.
93
+
94
+ ## Exercises
95
+
96
+ 32 exercises across 9 topics, from beginner to advanced:
97
+
98
+ | Topic | Exercises |
99
+ | ------------------- | ------------------------------------------------------ |
100
+ | **Basics** | init, staging, status, first_commit, gitignore |
101
+ | **Committing** | amend, multi_commit, diff |
102
+ | **Branching** | create_branch, switch_branches, delete_branch |
103
+ | **Merging** | fast_forward, three_way_merge, merge_conflict |
104
+ | **History** | log_basics, log_graph, blame, show |
105
+ | **Undoing Changes** | restore_file, unstage, revert, reset_soft, reset_mixed |
106
+ | **Rebase** | basic_rebase, interactive_rebase, rebase_conflict |
107
+ | **Stashing** | stash_basics, stash_pop_apply |
108
+ | **Advanced** | cherry_pick, bisect, tags, aliases |
109
+
110
+ ## Features
111
+
112
+ **Progressive hints** — Each exercise has multiple hints revealed one at a time:
113
+
114
+ ```
115
+ $ gitgym hint
116
+ Hint 1/3: Look at the `git init` command.
117
+
118
+ $ gitgym hint
119
+ Hint 2/3: Run `git init` inside the exercise directory.
120
+ ```
121
+
122
+ **Watch mode** — `gitgym watch` polls the exercise directory and re-verifies automatically whenever you make changes. No need to switch terminals to run verify.
123
+
124
+ **Progress tracking** — Your progress is saved locally in `~/.gitgym/progress.json`. Close your terminal and pick up where you left off. Run `gitgym progress` to see a per-topic breakdown.
125
+
126
+ **Cleanup** — When you're done, run `gitgym clean` to remove all exercise data from your system.
127
+
128
+ ## Platform Support
129
+
130
+ gitgym works on **macOS** and **Linux**. Windows is not currently supported because exercises use bash shell scripts. Windows users can use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) as a workaround.
131
+
132
+ ## Contributing
133
+
134
+ Exercises follow a simple structure — each one is a directory with three files:
135
+
136
+ - `exercise.toml` — metadata, description, goal, and hints
137
+ - `setup.sh` — creates the initial repo state
138
+ - `verify.sh` — checks if the goal is met (exit 0 = pass)
139
+
140
+ See the `exercises/` directory for examples.
gitgym-0.1.0/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # gitgym
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/gitgym)](https://pypi.org/project/gitgym/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/gitgym)](https://pypi.org/project/gitgym/)
5
+ [![CI](https://github.com/enerrio/gitgym/actions/workflows/ci.yml/badge.svg)](https://github.com/enerrio/gitgym/actions/workflows/ci.yml)
6
+
7
+ An interactive CLI for learning git through hands-on exercises, inspired by [Rustlings](https://github.com/rust-lang/rustlings). No quizzes — you practice real git commands in real repositories inside a safe, sandboxed workspace.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install gitgym
13
+ ```
14
+
15
+ Or with [uv](https://docs.astral.sh/uv/):
16
+
17
+ ```bash
18
+ uv tool install gitgym
19
+ ```
20
+
21
+ **Requirements:** Python 3.12+ and git. macOS and Linux only (Windows is not currently supported).
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ gitgym list # see all 32 exercises
27
+ gitgym next # set up the next exercise
28
+ cd ~/.gitgym/exercises/01_basics/01_init # cd into the printed path
29
+ # ... run git commands to solve the exercise ...
30
+ gitgym verify # check your work
31
+ ```
32
+
33
+ ## Recommended Setup
34
+
35
+ Open **two terminal windows** (or tabs) side by side:
36
+
37
+ | Terminal 1 (work) | Terminal 2 (control) |
38
+ | --------------------------------------- | --------------------------------- |
39
+ | `cd` into the exercise directory | `gitgym describe` — read the goal |
40
+ | Run git commands to solve the exercise | `gitgym hint` — get a hint |
41
+ | Edit files, stage, commit, branch, etc. | `gitgym verify` — check your work |
42
+ | | `gitgym watch` — live feedback |
43
+
44
+ This keeps your git work separate from the gitgym CLI, just like a real workflow.
45
+
46
+ ## Commands
47
+
48
+ | Command | Description |
49
+ | ------------------------- | ---------------------------------------------------------- |
50
+ | `gitgym list` | List all exercises grouped by topic with completion status |
51
+ | `gitgym start [exercise]` | Set up an exercise (defaults to next incomplete) |
52
+ | `gitgym next` | Alias for `gitgym start` with no argument |
53
+ | `gitgym describe` | Print the current exercise's description and goal |
54
+ | `gitgym verify` | Check if the current exercise's goal state is met |
55
+ | `gitgym watch` | Auto re-verify on changes (Ctrl+C to stop) |
56
+ | `gitgym hint` | Show the next progressive hint |
57
+ | `gitgym reset [exercise]` | Reset an exercise to its initial state |
58
+ | `gitgym reset --all` | Reset all exercises and clear progress |
59
+ | `gitgym progress` | Show overall progress summary |
60
+ | `gitgym clean` | Remove all gitgym data from your system |
61
+
62
+ Exercise names are shown in `gitgym list` (e.g. `init`, `staging`, `amend`). Use these names with `gitgym start` and `gitgym reset`.
63
+
64
+ ## Exercises
65
+
66
+ 32 exercises across 9 topics, from beginner to advanced:
67
+
68
+ | Topic | Exercises |
69
+ | ------------------- | ------------------------------------------------------ |
70
+ | **Basics** | init, staging, status, first_commit, gitignore |
71
+ | **Committing** | amend, multi_commit, diff |
72
+ | **Branching** | create_branch, switch_branches, delete_branch |
73
+ | **Merging** | fast_forward, three_way_merge, merge_conflict |
74
+ | **History** | log_basics, log_graph, blame, show |
75
+ | **Undoing Changes** | restore_file, unstage, revert, reset_soft, reset_mixed |
76
+ | **Rebase** | basic_rebase, interactive_rebase, rebase_conflict |
77
+ | **Stashing** | stash_basics, stash_pop_apply |
78
+ | **Advanced** | cherry_pick, bisect, tags, aliases |
79
+
80
+ ## Features
81
+
82
+ **Progressive hints** — Each exercise has multiple hints revealed one at a time:
83
+
84
+ ```
85
+ $ gitgym hint
86
+ Hint 1/3: Look at the `git init` command.
87
+
88
+ $ gitgym hint
89
+ Hint 2/3: Run `git init` inside the exercise directory.
90
+ ```
91
+
92
+ **Watch mode** — `gitgym watch` polls the exercise directory and re-verifies automatically whenever you make changes. No need to switch terminals to run verify.
93
+
94
+ **Progress tracking** — Your progress is saved locally in `~/.gitgym/progress.json`. Close your terminal and pick up where you left off. Run `gitgym progress` to see a per-topic breakdown.
95
+
96
+ **Cleanup** — When you're done, run `gitgym clean` to remove all exercise data from your system.
97
+
98
+ ## Platform Support
99
+
100
+ gitgym works on **macOS** and **Linux**. Windows is not currently supported because exercises use bash shell scripts. Windows users can use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) as a workaround.
101
+
102
+ ## Contributing
103
+
104
+ Exercises follow a simple structure — each one is a directory with three files:
105
+
106
+ - `exercise.toml` — metadata, description, goal, and hints
107
+ - `setup.sh` — creates the initial repo state
108
+ - `verify.sh` — checks if the goal is met (exit 0 = pass)
109
+
110
+ See the `exercises/` directory for examples.
File without changes
@@ -0,0 +1,20 @@
1
+ [exercise]
2
+ name = "init"
3
+ topic = "Basics"
4
+ title = "Initialize a Repository"
5
+ description = """
6
+ Every git journey starts with `git init`. In this exercise, you'll \
7
+ initialize a new git repository in the provided directory.
8
+ """
9
+
10
+ [goal]
11
+ summary = "The directory should be a valid git repository."
12
+
13
+ [[hints]]
14
+ text = "Look at the `git init` command."
15
+
16
+ [[hints]]
17
+ text = "Run `git init` inside the exercise directory."
18
+
19
+ [[hints]]
20
+ text = "Just run: git init"
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ mkdir -p "$EXERCISE_DIR"
6
+ cd "$EXERCISE_DIR"
7
+
8
+ # Remove any existing git repo so the exercise is in the expected initial state
9
+ # (idempotent: running setup.sh a second time resets back to pre-init state)
10
+ if [ -d ".git" ]; then
11
+ rm -rf .git
12
+ fi
13
+
14
+ # Create a file for the user to work with
15
+ echo "Hello, git!" >hello.txt
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ cd "$EXERCISE_DIR"
6
+
7
+ # Check that it's a git repo
8
+ if [ ! -d ".git" ]; then
9
+ echo "This directory is not a git repository yet."
10
+ echo "Use 'git init' to initialize it."
11
+ exit 1
12
+ fi
13
+
14
+ echo "Great job! You initialized a git repository."
15
+ exit 0
@@ -0,0 +1,21 @@
1
+ [exercise]
2
+ name = "staging"
3
+ topic = "Basics"
4
+ title = "Stage a File"
5
+ description = """
6
+ Before git can track a file, you need to stage it with `git add`. \
7
+ In this exercise, a file called `hello.txt` exists in the repo but \
8
+ has not been staged yet. Stage it so git will include it in the next commit.
9
+ """
10
+
11
+ [goal]
12
+ summary = "hello.txt should be staged (in the index)."
13
+
14
+ [[hints]]
15
+ text = "Use `git status` to see the current state of the repository."
16
+
17
+ [[hints]]
18
+ text = "Look at the `git add` command."
19
+
20
+ [[hints]]
21
+ text = "Run `git add hello.txt` to stage the file."
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ mkdir -p "$EXERCISE_DIR"
6
+ cd "$EXERCISE_DIR"
7
+
8
+ # Initialize a fresh git repo (idempotent: remove any existing one first)
9
+ if [ -d ".git" ]; then
10
+ rm -rf .git
11
+ fi
12
+
13
+ git init --initial-branch=main
14
+ git config user.email "gitgym@example.com"
15
+ git config user.name "Git Gym"
16
+
17
+ # Create an untracked file for the user to stage
18
+ echo "Hello, git!" >hello.txt
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ cd "$EXERCISE_DIR"
6
+
7
+ # Check that hello.txt is staged (present in the index)
8
+ if ! git diff --cached --name-only | grep -qx "hello.txt"; then
9
+ echo "hello.txt is not staged yet."
10
+ echo "Use 'git add hello.txt' to stage it."
11
+ exit 1
12
+ fi
13
+
14
+ echo "Great job! hello.txt is staged and ready to commit."
15
+ exit 0
@@ -0,0 +1,28 @@
1
+ [exercise]
2
+ name = "status"
3
+ topic = "Basics"
4
+ title = "Interpret git status"
5
+ description = """
6
+ `git status` shows you exactly what is staged, what is modified, and what \
7
+ is untracked. In this exercise the repo already has some files committed. \
8
+ Three new files have been added:
9
+
10
+ - `feature.txt` — a new file that should be staged
11
+ - `notes.txt` — a new file that should NOT be staged
12
+ - `readme.txt` — a previously committed file that has been modified
13
+
14
+ Your task: stage `feature.txt` and the modified `readme.txt`, but leave \
15
+ `notes.txt` untracked.
16
+ """
17
+
18
+ [goal]
19
+ summary = "feature.txt and the modified readme.txt are staged; notes.txt is not."
20
+
21
+ [[hints]]
22
+ text = "Run `git status` to see the current state of all files."
23
+
24
+ [[hints]]
25
+ text = "Use `git add <file>` to stage a specific file."
26
+
27
+ [[hints]]
28
+ text = "Stage both files: `git add feature.txt readme.txt`"
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ mkdir -p "$EXERCISE_DIR"
6
+ cd "$EXERCISE_DIR"
7
+
8
+ # Start fresh (idempotent)
9
+ if [ -d ".git" ]; then
10
+ rm -rf .git
11
+ fi
12
+
13
+ git init --initial-branch=main
14
+ git config user.email "gitgym@example.com"
15
+ git config user.name "Git Gym"
16
+
17
+ # Create and commit an initial file so readme.txt has a committed version
18
+ echo "# My Project" >readme.txt
19
+ git add readme.txt
20
+ git commit -m "Initial commit"
21
+
22
+ # Modify the committed file (now it appears as "modified" in git status)
23
+ echo "# My Project (updated)" >readme.txt
24
+
25
+ # Add two new untracked files — user must stage only feature.txt
26
+ echo "New feature content" >feature.txt
27
+ echo "Private notes" >notes.txt
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ cd "$EXERCISE_DIR"
6
+
7
+ STAGED=$(git diff --cached --name-only)
8
+ PASS=1
9
+
10
+ # feature.txt must be staged
11
+ if ! echo "$STAGED" | grep -qx "feature.txt"; then
12
+ echo "feature.txt is not staged yet."
13
+ echo "Use 'git add feature.txt' to stage it."
14
+ PASS=0
15
+ fi
16
+
17
+ # readme.txt (modified) must be staged
18
+ if ! echo "$STAGED" | grep -qx "readme.txt"; then
19
+ echo "readme.txt is not staged yet."
20
+ echo "Use 'git add readme.txt' to stage the modified version."
21
+ PASS=0
22
+ fi
23
+
24
+ # notes.txt must NOT be staged
25
+ if echo "$STAGED" | grep -qx "notes.txt"; then
26
+ echo "notes.txt should not be staged — leave it untracked."
27
+ PASS=0
28
+ fi
29
+
30
+ if [ "$PASS" -eq 0 ]; then
31
+ exit 1
32
+ fi
33
+
34
+ echo "Great job! You staged the right files and left notes.txt untracked."
35
+ exit 0
@@ -0,0 +1,22 @@
1
+ [exercise]
2
+ name = "first_commit"
3
+ topic = "Basics"
4
+ title = "Create Your First Commit"
5
+ description = """
6
+ Staging a file is only half the story — you still need to save those \
7
+ changes permanently with `git commit`. In this exercise, `hello.txt` is \
8
+ already staged and ready to go. Create a commit with a meaningful message \
9
+ to record the snapshot.
10
+ """
11
+
12
+ [goal]
13
+ summary = "The repo has at least one commit with a non-empty message."
14
+
15
+ [[hints]]
16
+ text = "Use `git log` to see the commit history (it will be empty until you commit)."
17
+
18
+ [[hints]]
19
+ text = "Look at the `git commit` command."
20
+
21
+ [[hints]]
22
+ text = "Run `git commit -m \"Your message here\"` to create the commit."
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ mkdir -p "$EXERCISE_DIR"
6
+ cd "$EXERCISE_DIR"
7
+
8
+ # Start fresh (idempotent)
9
+ if [ -d ".git" ]; then
10
+ rm -rf .git
11
+ fi
12
+
13
+ git init --initial-branch=main
14
+ git config user.email "gitgym@example.com"
15
+ git config user.name "Git Gym"
16
+
17
+ # Stage a file so the user only needs to commit
18
+ echo "Hello, git!" >hello.txt
19
+ git add hello.txt
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ cd "$EXERCISE_DIR"
6
+
7
+ # Check there is at least one commit
8
+ COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo 0)
9
+ if [ "$COMMIT_COUNT" -eq 0 ]; then
10
+ echo "No commits found yet."
11
+ echo "Use 'git commit -m \"your message\"' to create your first commit."
12
+ exit 1
13
+ fi
14
+
15
+ # Check the commit message is non-empty
16
+ COMMIT_MSG=$(git log -1 --format="%s")
17
+ if [ -z "$COMMIT_MSG" ]; then
18
+ echo "Your commit has an empty message."
19
+ echo "A good commit message describes what changed and why."
20
+ exit 1
21
+ fi
22
+
23
+ echo "Great job! You made your first commit: \"$COMMIT_MSG\""
24
+ exit 0
@@ -0,0 +1,30 @@
1
+ [exercise]
2
+ name = "gitignore"
3
+ topic = "Basics"
4
+ title = "Ignore Files with .gitignore"
5
+ description = """
6
+ Not every file belongs in version control. Build artifacts, log files, \
7
+ and editor configs should usually be excluded. Git reads a `.gitignore` \
8
+ file to know which files to skip.
9
+
10
+ In this exercise, the repo contains two files that should never be \
11
+ committed:
12
+
13
+ - `build.log` — a log file generated during builds
14
+ - `secret.key` — a sensitive file that must not be tracked
15
+
16
+ Create a `.gitignore` that excludes both of these files. The rest of \
17
+ the repo should remain unaffected.
18
+ """
19
+
20
+ [goal]
21
+ summary = ".gitignore exists and causes build.log and secret.key to be ignored by git."
22
+
23
+ [[hints]]
24
+ text = "Create a file named `.gitignore` in the exercise directory."
25
+
26
+ [[hints]]
27
+ text = "Each line in `.gitignore` is a pattern. Add `build.log` and `secret.key` on separate lines."
28
+
29
+ [[hints]]
30
+ text = "Run `git status` after creating .gitignore — the two files should no longer appear as untracked."
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ mkdir -p "$EXERCISE_DIR"
6
+ cd "$EXERCISE_DIR"
7
+
8
+ # Start fresh (idempotent)
9
+ if [ -d ".git" ]; then
10
+ rm -rf .git
11
+ fi
12
+ rm -f .gitignore
13
+
14
+ git init --initial-branch=main
15
+ git config user.email "gitgym@example.com"
16
+ git config user.name "Git Gym"
17
+
18
+ # Commit a normal source file to give the repo some history
19
+ echo "print('hello')" >main.py
20
+ git add main.py
21
+ git commit -m "Initial commit"
22
+
23
+ # Create files that the user should ignore (do NOT stage or commit them)
24
+ echo "build output log" >build.log
25
+ echo "super-secret-value" >secret.key
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXERCISE_DIR="$1"
5
+ cd "$EXERCISE_DIR"
6
+
7
+ PASS=1
8
+
9
+ # .gitignore must exist
10
+ if [ ! -f ".gitignore" ]; then
11
+ echo ".gitignore does not exist yet."
12
+ echo "Create a .gitignore file in the exercise directory."
13
+ exit 1
14
+ fi
15
+
16
+ # build.log must be ignored
17
+ if ! git check-ignore -q build.log 2>/dev/null; then
18
+ echo "build.log is not being ignored by git."
19
+ echo "Add 'build.log' to your .gitignore."
20
+ PASS=0
21
+ fi
22
+
23
+ # secret.key must be ignored
24
+ if ! git check-ignore -q secret.key 2>/dev/null; then
25
+ echo "secret.key is not being ignored by git."
26
+ echo "Add 'secret.key' to your .gitignore."
27
+ PASS=0
28
+ fi
29
+
30
+ if [ "$PASS" -eq 0 ]; then
31
+ exit 1
32
+ fi
33
+
34
+ echo "Great job! Your .gitignore correctly excludes build.log and secret.key."
35
+ exit 0