geet-geet 0.2.0 → 0.4.0

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.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
package/README.md CHANGED
@@ -9,11 +9,15 @@ Only Git's *view* of the filesystem changes.
9
9
 
10
10
  ## Why?
11
11
  > “I built something useful, and I think that **SOME but not all of my code is re-usable**.
12
- I want to publish some of my code for other's to use (or to re-use myself)...
12
+ I want to publish some of my code for others to use (or to re-use myself)...
13
13
  but **I don't want to spend weeks refactoring** to split apart the reusable code from the implementation-specific code.
14
14
  In fact, it may not even be possible to move around all my files without breaking things.
15
15
  Plus, supporting this template is my **secondary task** which I want to do in tandem with my **primary development**, using my main repository's working directory and publishing some pieces to the template repo.”
16
16
 
17
+ ## Why Not?
18
+ > If you can super cleanly separate your template from your app or make your sourcecode fully modular, you don't need geet, use a normal repo or maybe submodules.
19
+
20
+ ## Why?
17
21
  - **Making code re-usable is a struggle**, especially if you want to ship an incomplete template that is not easily separated into a standalone module.
18
22
  - Modern React Native / Expo apps are **extremely path-sensitive**:
19
23
  * file-based routing
@@ -25,8 +29,6 @@ Only Git's *view* of the filesystem changes.
25
29
  - submodules don't support interleaving files and folders of template code with custom app code
26
30
  - I want to simultaneously develop many apps with a similar architecture
27
31
 
28
- ## Why Not?
29
- > If you can super cleanly separate your template from your app or make your sourcecode fully modular, you don't need geet, use a normal repo or maybe submodules.
30
32
 
31
33
  ---
32
34
  ## PreReqs
@@ -43,9 +45,6 @@ npm install -g geet-geet
43
45
  geet
44
46
  ```
45
47
 
46
- Try our [Demo](/docs/DEMO.md)
47
-
48
- ---
49
48
 
50
49
  ## Table of Contents
51
50
 
@@ -58,4 +57,7 @@ Try our [Demo](/docs/DEMO.md)
58
57
  7. [Advanced: Preventing app-specific code](/docs/PREVENT_COMMIT_PATTERNS.md)
59
58
  8. [Contributing to geet](/docs/CONTRIBUTING.md)
60
59
  9. [FAQ](/docs/FAQ.md)
61
- 10. [Demo](/docs/DEMO.md)
60
+ 10. [Demo](/docs/DEMO.md)
61
+
62
+ ---
63
+
package/bin/geet.sh CHANGED
@@ -157,6 +157,11 @@ case "$cmd" in
157
157
  echo "$(is_ignored "${GEET_ARGS[@]:1}")"
158
158
  ;;
159
159
 
160
+ inspect)
161
+ source "$GEET_LIB/inspect.sh"
162
+ inspect "${GEET_ARGS[@]:1}"
163
+ ;;
164
+
160
165
  # Default: assume git subcommand
161
166
  *)
162
167
  source "$GEET_LIB/git.sh"
@@ -1,4 +1,4 @@
1
- # 5. Contributing to geet
1
+ # Contributing to geet
2
2
 
3
3
  ## Development setup
4
4
 
@@ -6,13 +6,7 @@
6
6
  # Clone the repo
7
7
  git clone https://github.com/modularizer/geet.git
8
8
  cd geet
9
-
10
- # The scripts are in lib/
11
- ls lib/
12
-
13
- # Test any script
14
- bash lib/doctor.sh help
15
- bash lib/ghcli.sh help
9
+ npm install -g .
16
10
  ```
17
11
 
18
12
  ## Project structure
@@ -20,21 +14,33 @@ bash lib/ghcli.sh help
20
14
  ```text
21
15
  geet/
22
16
  lib/
23
- cli.sh # Main router
24
- git.sh # Git operations wrapper
25
- init.sh # Initialize layer
26
- tree.sh # Inspect layer contents
27
- split.sh # Export layer files
28
- session.sh # Isolated build helper
29
- doctor.sh # Health checks
30
- gh.sh # GitHub integration
31
- template.sh # Create new layer
17
+ detach.sh # File detachment for conflict resolution
18
+ digest-and-locate.sh # Environment setup and routing
19
+ doctor.sh # Health checks
20
+ flags.sh # Flag parsing utilities
21
+ ghcli.sh # GitHub CLI integration
22
+ git.sh # Git operations wrapper
23
+ help.sh # Help text and command listing
24
+ ignored.sh # Check if files are ignored/included/excluded
25
+ include.sh # Manage included files
26
+ init.sh # Initialize layer
27
+ install.sh # Clone and initialize templates
28
+ logger.sh # Logging utilities
29
+ pre-commit # Pre-commit hook
30
+ prework.sh # Show environment info
31
+ session.sh # Isolated build helper
32
+ split.sh # Export layer files
33
+ sync.sh # Compile .geetinclude to .geetexclude
34
+ template.sh # Create new layer
35
+ tree.sh # Inspect layer contents
36
+ version.sh # Version display
37
+ whoops.sh # Open GitHub issues
38
+ why.sh # Show reasons to use/not use geet
32
39
 
33
40
  bin/
34
- geet.sh # npm executable wrapper
41
+ geet.sh # Main router and npm executable wrapper
35
42
 
36
- geetinclude.sample # Whitelist example
37
- package.json # npm package config
43
+ package.json # npm package config
38
44
  ```
39
45
 
40
46
  ## Architecture principles
@@ -57,16 +63,26 @@ geet/
57
63
  - Document non-obvious behavior
58
64
  - Validate inputs early
59
65
  - Fail fast with clear errors
66
+ - Use `source` a lot
67
+ - Use `digest-and-locate.sh` for variable definitions and things that all scripts need access to
68
+ - Use `has_flag`, `extract_flag`, `log`, and `debug` heavily
60
69
 
61
70
  ## Testing changes
62
71
 
63
72
  ```bash
64
73
  # Run doctor to check for issues
65
- bash lib/doctor.sh
74
+ geet doctor
75
+
76
+ # Test the main executable
77
+ geet help
78
+ geet doctor
66
79
 
67
80
  # Test in a real project
68
81
  cd /path/to/test-project
69
- /path/to/geet/lib/cli.sh doctor
82
+ /path/to/geet/bin/geet.sh doctor
83
+
84
+ # Or if installed globally
85
+ geet doctor
70
86
  ```
71
87
 
72
88
  ## Common tasks
@@ -74,8 +90,8 @@ cd /path/to/test-project
74
90
  ### Adding a new command
75
91
 
76
92
  1. Create `lib/mycommand.sh`
77
- 2. Add to `lib/cli.sh` router
78
- 3. Update help text
93
+ 2. Add to `bin/geet.sh` router
94
+ 3. Update help text in `lib/help.sh`
79
95
  4. Test thoroughly
80
96
 
81
97
  ### Updating documentation
@@ -127,16 +143,18 @@ This system is intentionally small, explicit, and evolvable.
127
143
  It will grow **only** when real workflows demand it.
128
144
 
129
145
  Core features:
130
- - ✅ Cloning templates
131
- - ✅ Init workflow
146
+ - ✅ Template creation and initialization
147
+ - ✅ Install workflow (clone + init)
132
148
  - ✅ Layered templates
133
- - ✅ Include/exclude modes
134
- - ✅ Introspection tools
135
- - ✅ Export functionality
149
+ - ✅ Include/exclude modes with sync
150
+ - ✅ File detachment (hard and soft)
151
+ - ✅ Introspection tools (tree, prework)
152
+ - ✅ Export functionality (split)
136
153
  - ✅ Build sessions
137
154
  - ✅ Safety rails
138
- - ✅ Post-init hooks
139
- - ✅ GitHub integration
155
+ - ✅ Pre-commit hooks
156
+ - ✅ GitHub CLI integration (publish, pr, issues)
140
157
  - ✅ Multi-layer support
158
+ - ✅ Doctor health checks
141
159
 
142
160
  That's the core. Everything else is optional.
package/lib/help.sh CHANGED
@@ -19,6 +19,7 @@ TEMPLATE MANAGEMENT:
19
19
  FILE MANAGEMENT:
20
20
  tree [list|tracked|all] Show what files the template includes
21
21
  split <dest> [mode] Export template files to external folder
22
+ inspect <path> Show which layer tracks a file and its git status
22
23
  sync Compile .geetinclude whitelist into .geetexclude
23
24
  include <path> Manage included files
24
25
  ignored|included|excluded <path> Check if a path is ignored/included/excluded
@@ -61,6 +62,7 @@ USAGE:
61
62
  install <repo> <dir> [--public|--private|--internal] Do a git clone of a repo and convert it into a repo of your own
62
63
  tree [list|tracked|all] Show what files the template includes
63
64
  split <dest> [mode] Export template files to external folder
65
+ inspect <path> Show which layer tracks a file and its git status
64
66
  prework See what we know
65
67
  why / whynot Reasons to (or not to) use geet
66
68
  version / --version Show geet version
package/lib/inspect.sh ADDED
@@ -0,0 +1,660 @@
1
+ # inspect.sh — inspect which layer tracks a file and its git status
2
+ # Usage:
3
+ # source inspect.sh
4
+ # inspect <path>
5
+
6
+ inspect() {
7
+
8
+ # digest-and-locate.sh provides: APP_DIR, TEMPLATE_DIR, DOTGIT, TEMPLATE_NAME,
9
+ # GEET_ALIAS, die, log, debug
10
+
11
+ # Color helpers
12
+ _color_enabled() {
13
+ [[ -t 1 ]] || return 1
14
+ case "${COLOR_MODE:-}" in
15
+ light|dark) return 0 ;;
16
+ *) return 1 ;;
17
+ esac
18
+ }
19
+
20
+ # ANSI color codes
21
+ if _color_enabled; then
22
+ c_reset=$'\033[0m'
23
+ c_bold=$'\033[1m'
24
+ c_green=$'\033[32m'
25
+ c_red=$'\033[31m'
26
+ c_yellow=$'\033[33m'
27
+ c_blue=$'\033[34m'
28
+ c_cyan=$'\033[36m'
29
+ c_gray=$'\033[90m'
30
+ else
31
+ c_reset=""
32
+ c_bold=""
33
+ c_green=""
34
+ c_red=""
35
+ c_yellow=""
36
+ c_blue=""
37
+ c_cyan=""
38
+ c_gray=""
39
+ fi
40
+
41
+ # Get concise status for a single file
42
+ get_file_status() {
43
+ local file="$1"
44
+ local repo="$2" # "template" or "app"
45
+
46
+ # Check if tracked
47
+ local is_tracked=false
48
+ if [[ "$repo" == "template" ]]; then
49
+ git --git-dir="$DOTGIT" --work-tree="$APP_DIR" ls-files --error-unmatch -- "$file" >/dev/null 2>&1 && is_tracked=true
50
+ else
51
+ git -C "$APP_DIR" ls-files --error-unmatch -- "$file" >/dev/null 2>&1 && is_tracked=true
52
+ fi
53
+
54
+ if [[ "$is_tracked" == "false" ]]; then
55
+ # Check if ignored/excluded
56
+ if [[ "$repo" == "template" ]]; then
57
+ if geet_git check-ignore -q -- "$file" 2>/dev/null; then
58
+ echo "excluded"
59
+ return
60
+ fi
61
+ else
62
+ if git -C "$APP_DIR" check-ignore -q -- "$file" 2>/dev/null; then
63
+ echo "ignored"
64
+ return
65
+ fi
66
+ fi
67
+ echo "untracked"
68
+ return
69
+ fi
70
+
71
+ # File is tracked - check detachment state (template only)
72
+ if [[ "$repo" == "template" ]]; then
73
+ # Check if hard-detached
74
+ local ls_v_output=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" ls-files -v -- "$file" 2>/dev/null || echo "")
75
+ if [[ "$ls_v_output" =~ ^S ]]; then
76
+ echo "detached"
77
+ return
78
+ fi
79
+
80
+ # Check if soft-detached
81
+ local merge_attr=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" check-attr merge -- "$file" 2>/dev/null | awk -F': ' '{print $3}')
82
+ if [[ "$merge_attr" == "keep-ours" ]]; then
83
+ echo "slid"
84
+ return
85
+ fi
86
+ fi
87
+
88
+ # Check if modified
89
+ local status_output=""
90
+ if [[ "$repo" == "template" ]]; then
91
+ status_output=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" status --porcelain -- "$file" 2>/dev/null || echo "")
92
+ else
93
+ status_output=$(git -C "$APP_DIR" status --porcelain -- "$file" 2>/dev/null || echo "")
94
+ fi
95
+
96
+ if [[ -z "$status_output" ]]; then
97
+ echo "clean"
98
+ else
99
+ local status_code="${status_output:0:2}"
100
+ case "$status_code" in
101
+ " M"|"M "|"MM") echo "modified" ;;
102
+ " D"|"D "|"DD") echo "deleted" ;;
103
+ "A "|"AM") echo "added" ;;
104
+ *) echo "modified" ;;
105
+ esac
106
+ fi
107
+ }
108
+
109
+ # Inspect a directory and show summary for all files (recursive)
110
+ inspect_directory() {
111
+ local dir="$1"
112
+
113
+ echo
114
+ echo "${c_bold}Inspecting directory (recursive):${c_reset} $dir"
115
+ echo
116
+ printf "%-60s %-15s %-15s\n" "File" "Template" "App"
117
+ printf "%-60s %-15s %-15s\n" "----" "--------" "---"
118
+
119
+ # Collect files from all three sources: template HEAD, app HEAD, and working tree
120
+ local all_files=$(
121
+ {
122
+ # Files from template repo HEAD
123
+ if [[ -d "$DOTGIT" ]]; then
124
+ geet_git ls-files 2>/dev/null | grep "^${dir}" || true
125
+ fi
126
+ # Files from app repo HEAD
127
+ if [[ -d "$APP_DIR/.git" ]]; then
128
+ git -C "$APP_DIR" ls-files 2>/dev/null | grep "^${dir}" || true
129
+ fi
130
+ # Files from working tree (excluding .git and dot-git directories)
131
+ find "$APP_DIR/$dir" -type d \( -name .git -o -name dot-git \) -prune -o -type f -print 2>/dev/null | sed "s|^$APP_DIR/||; s|^\./||" || true
132
+ } | sort -u
133
+ )
134
+
135
+ # Process each unique file
136
+ while IFS= read -r rel_path; do
137
+ [[ -z "$rel_path" ]] && continue
138
+
139
+ # Get status for both repos
140
+ local template_status=$(get_file_status "$rel_path" "template")
141
+ local app_status=$(get_file_status "$rel_path" "app")
142
+
143
+ # Color code the status
144
+ local template_colored=""
145
+ local app_colored=""
146
+
147
+ case "$template_status" in
148
+ clean) template_colored="${c_green}clean${c_reset}" ;;
149
+ modified) template_colored="${c_yellow}modified${c_reset}" ;;
150
+ detached) template_colored="${c_yellow}detached${c_reset}" ;;
151
+ slid) template_colored="${c_cyan}slid${c_reset}" ;;
152
+ excluded) template_colored="${c_gray}excluded${c_reset}" ;;
153
+ untracked) template_colored="${c_red}untracked${c_reset}" ;;
154
+ deleted) template_colored="${c_red}deleted${c_reset}" ;;
155
+ *) template_colored="$template_status" ;;
156
+ esac
157
+
158
+ case "$app_status" in
159
+ clean) app_colored="${c_green}clean${c_reset}" ;;
160
+ modified) app_colored="${c_yellow}modified${c_reset}" ;;
161
+ ignored) app_colored="${c_gray}ignored${c_reset}" ;;
162
+ untracked) app_colored="${c_red}untracked${c_reset}" ;;
163
+ deleted) app_colored="${c_red}deleted${c_reset}" ;;
164
+ *) app_colored="$app_status" ;;
165
+ esac
166
+
167
+ # Skip files that are excluded by template AND ignored by app
168
+ [[ "$template_status" == "excluded" && "$app_status" == "ignored" ]] && continue
169
+
170
+ printf "%-60s %-24s %-24s\n" "$rel_path" "$template_colored" "$app_colored"
171
+ done <<< "$all_files"
172
+
173
+ echo
174
+ }
175
+
176
+ # Inspect files matching a glob pattern
177
+ inspect_glob() {
178
+ local pattern="$1"
179
+
180
+ echo
181
+ echo "${c_bold}Inspecting pattern:${c_reset} $pattern"
182
+ echo
183
+ printf "%-60s %-15s %-15s\n" "File" "Template" "App"
184
+ printf "%-60s %-15s %-15s\n" "----" "--------" "---"
185
+
186
+ # Collect files from all three sources: template HEAD, app HEAD, and working tree
187
+ local all_files=$(
188
+ {
189
+ # Files from template repo HEAD matching pattern
190
+ if [[ -d "$DOTGIT" ]]; then
191
+ geet_git ls-files 2>/dev/null | while IFS= read -r file; do
192
+ # Use bash pattern matching
193
+ shopt -s globstar nullglob
194
+ case "$file" in
195
+ $pattern) echo "$file" ;;
196
+ esac
197
+ shopt -u globstar nullglob
198
+ done
199
+ fi
200
+ # Files from app repo HEAD matching pattern
201
+ if [[ -d "$APP_DIR/.git" ]]; then
202
+ git -C "$APP_DIR" ls-files 2>/dev/null | while IFS= read -r file; do
203
+ shopt -s globstar nullglob
204
+ case "$file" in
205
+ $pattern) echo "$file" ;;
206
+ esac
207
+ shopt -u globstar nullglob
208
+ done
209
+ fi
210
+ # Files from working tree matching pattern
211
+ shopt -s nullglob globstar
212
+ for f in $APP_DIR/$pattern; do
213
+ if [[ -f "$f" ]]; then
214
+ local path="${f#$APP_DIR/}"
215
+ path="${path#./}"
216
+ echo "$path"
217
+ fi
218
+ done
219
+ shopt -u nullglob globstar
220
+ } | sort -u
221
+ )
222
+
223
+ # Process each unique file
224
+ while IFS= read -r rel_path; do
225
+ [[ -z "$rel_path" ]] && continue
226
+
227
+ # Get status for both repos
228
+ local template_status=$(get_file_status "$rel_path" "template")
229
+ local app_status=$(get_file_status "$rel_path" "app")
230
+
231
+ # Color code the status
232
+ local template_colored=""
233
+ local app_colored=""
234
+
235
+ case "$template_status" in
236
+ clean) template_colored="${c_green}clean${c_reset}" ;;
237
+ modified) template_colored="${c_yellow}modified${c_reset}" ;;
238
+ detached) template_colored="${c_yellow}detached${c_reset}" ;;
239
+ slid) template_colored="${c_cyan}slid${c_reset}" ;;
240
+ excluded) template_colored="${c_gray}excluded${c_reset}" ;;
241
+ untracked) template_colored="${c_red}untracked${c_reset}" ;;
242
+ deleted) template_colored="${c_red}deleted${c_reset}" ;;
243
+ *) template_colored="$template_status" ;;
244
+ esac
245
+
246
+ case "$app_status" in
247
+ clean) app_colored="${c_green}clean${c_reset}" ;;
248
+ modified) app_colored="${c_yellow}modified${c_reset}" ;;
249
+ ignored) app_colored="${c_gray}ignored${c_reset}" ;;
250
+ untracked) app_colored="${c_red}untracked${c_reset}" ;;
251
+ deleted) app_colored="${c_red}deleted${c_reset}" ;;
252
+ *) app_colored="$app_status" ;;
253
+ esac
254
+
255
+ # Skip files that are excluded by template AND ignored by app
256
+ [[ "$template_status" == "excluded" && "$app_status" == "ignored" ]] && continue
257
+
258
+ printf "%-60s %-24s %-24s\n" "$rel_path" "$template_colored" "$app_colored"
259
+ done <<< "$all_files"
260
+
261
+ echo
262
+ }
263
+
264
+ usage() {
265
+ cat <<EOF
266
+ $GEET_ALIAS inspect — inspect which layer tracks a file and its git status
267
+
268
+ Given a file path, shows detailed inspection with tracking status, commits, and diffs.
269
+ Given a directory path, shows summary table of all files (recursive).
270
+ Given a glob pattern, shows summary table of all matching files.
271
+
272
+ Usage:
273
+ $GEET_ALIAS inspect <path|pattern>
274
+
275
+ Examples:
276
+ $GEET_ALIAS inspect README.md # detailed single file view
277
+ $GEET_ALIAS inspect lib/ # recursive directory summary
278
+ $GEET_ALIAS inspect "**/*.md" # all .md files recursively
279
+ $GEET_ALIAS inspect "lib/*.sh" # glob pattern in lib/
280
+ $GEET_ALIAS inspect . # entire project (recursive)
281
+ $GEET_ALIAS inspect --help
282
+
283
+ Single file output includes:
284
+ - File modification time (mtime)
285
+ - Tracking status (tracked|excluded|ignored|untracked) for both repos
286
+ - Detachment state for template repo (attached|slid|detached)
287
+ - Last commit hash and commit time for each repo
288
+ - Git status (clean|modified|deleted|added)
289
+ - Content comparison across all three states (working tree, template HEAD, app HEAD)
290
+ - Pairwise diffs showing ahead/behind/diverged status
291
+
292
+ Directory/glob output shows one line per file from all three sources:
293
+ - Template HEAD (via geet git ls-files)
294
+ - App HEAD (via git ls-files)
295
+ - Working tree (filesystem)
296
+
297
+ Status indicators:
298
+ - Template: clean|modified|detached|slid|excluded|untracked|deleted
299
+ - App: clean|modified|ignored|untracked|deleted
300
+
301
+ Color coding:
302
+ - Green: clean
303
+ - Yellow: modified, detached
304
+ - Cyan: slid
305
+ - Red: untracked, deleted
306
+ - Gray: excluded, ignored
307
+
308
+ Notes:
309
+ - Directory/glob inspection excludes .git and dot-git directories
310
+ - Files that are excluded by template AND ignored by app are hidden
311
+ - Use quotes around glob patterns to prevent shell expansion
312
+ EOF
313
+ }
314
+
315
+ # Handle help
316
+ if [[ "${1:-}" == "help" || "${1:-}" == "-h" || "${1:-}" == "--help" || -z "${1:-}" ]]; then
317
+ usage
318
+ return 0
319
+ fi
320
+
321
+ path="${1}"
322
+
323
+ # Convert absolute path to relative if needed
324
+ if [[ "$path" == "$APP_DIR/"* ]]; then
325
+ path="${path#"$APP_DIR/"}"
326
+ fi
327
+
328
+ # Strip leading ./ if present
329
+ path="${path#./}"
330
+
331
+ # Check if path is a directory - if so, show summary for all files
332
+ if [[ -d "$APP_DIR/$path" ]]; then
333
+ inspect_directory "$path"
334
+ return 0
335
+ fi
336
+
337
+ # Check if path contains glob characters - if so, treat as pattern
338
+ if [[ "$path" == *"*"* || "$path" == *"?"* || "$path" == *"["* ]]; then
339
+ inspect_glob "$path"
340
+ return 0
341
+ fi
342
+
343
+ echo
344
+ echo "${c_bold}Path:${c_reset} $path"
345
+
346
+ # Show file mtime if file exists
347
+ if [[ -e "$APP_DIR/$path" ]]; then
348
+ if stat -c '%y' "$APP_DIR/$path" >/dev/null 2>&1; then
349
+ # GNU stat
350
+ file_mtime=$(stat -c '%y' "$APP_DIR/$path" 2>/dev/null | cut -d'.' -f1)
351
+ else
352
+ # BSD stat (macOS)
353
+ file_mtime=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M:%S' "$APP_DIR/$path" 2>/dev/null)
354
+ fi
355
+ [[ -n "$file_mtime" ]] && echo "${c_gray}File modified: $file_mtime${c_reset}"
356
+ fi
357
+
358
+ echo
359
+
360
+ # Template repo inspection
361
+ if [[ -d "$DOTGIT" ]]; then
362
+ template_name="${TEMPLATE_NAME:-template}"
363
+ echo "${c_bold}Template repo${c_reset} ${c_blue}($template_name)${c_reset}:"
364
+
365
+ # Check if tracked in template repo
366
+ is_tracked_template=false
367
+ is_ignored_template=false
368
+
369
+ if git --git-dir="$DOTGIT" --work-tree="$APP_DIR" ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
370
+ is_tracked_template=true
371
+ fi
372
+
373
+ # Check if ignored by template repo (whitelist system via .geetexclude)
374
+ if geet_git check-ignore -q -- "$path" 2>/dev/null; then
375
+ is_ignored_template=true
376
+ fi
377
+
378
+ # Display tracking status
379
+ if [[ "$is_tracked_template" == "true" ]]; then
380
+ echo " ${c_green}✔ tracked${c_reset}"
381
+
382
+ # Determine detachment state
383
+ # Check if hard-detached (skip-worktree)
384
+ ls_v_output=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" ls-files -v -- "$path" 2>/dev/null || echo "")
385
+ if [[ "$ls_v_output" =~ ^S ]]; then
386
+ echo " ${c_yellow}state: detached${c_reset}"
387
+ else
388
+ # Check if soft-detached (merge=keep-ours)
389
+ merge_attr=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" check-attr merge -- "$path" 2>/dev/null | awk -F': ' '{print $3}')
390
+ if [[ "$merge_attr" == "keep-ours" ]]; then
391
+ echo " ${c_cyan}state: slid${c_reset}"
392
+ else
393
+ echo " ${c_green}state: attached${c_reset}"
394
+ fi
395
+ fi
396
+
397
+ # Get the commit hash and time for this file (last commit that touched it)
398
+ commit_hash=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" log -1 --format="%h" -- "$path" 2>/dev/null || echo "")
399
+ if [[ -n "$commit_hash" ]]; then
400
+ echo " ${c_gray}commit: $commit_hash${c_reset}"
401
+
402
+ # Get commit time
403
+ commit_time=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" log -1 --format="%ci" -- "$path" 2>/dev/null | cut -d'.' -f1 || echo "")
404
+ [[ -n "$commit_time" ]] && echo " ${c_gray}commit time: $commit_time${c_reset}"
405
+ fi
406
+
407
+ # Check status (modified, deleted, etc.)
408
+ status_output=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" status --porcelain -- "$path" 2>/dev/null || echo "")
409
+
410
+ if [[ -z "$status_output" ]]; then
411
+ echo " ${c_green}status: clean${c_reset}"
412
+ else
413
+ # Parse status code
414
+ status_code="${status_output:0:2}"
415
+ case "$status_code" in
416
+ " M"|"M "|"MM") echo " ${c_yellow}status: modified${c_reset}" ;;
417
+ " D"|"D "|"DD") echo " ${c_red}status: deleted${c_reset}" ;;
418
+ "A "|"AM") echo " ${c_cyan}status: added${c_reset}" ;;
419
+ "??") echo " ${c_gray}status: untracked${c_reset}" ;;
420
+ *) echo " status: ${status_code}" ;;
421
+ esac
422
+ fi
423
+
424
+ template_tracked=true
425
+ else
426
+ # Not tracked - check if excluded or just untracked
427
+ if [[ "$is_ignored_template" == "true" ]]; then
428
+ echo " ${c_yellow}✖ excluded${c_reset}"
429
+ else
430
+ echo " ${c_red}✖ untracked${c_reset}"
431
+ fi
432
+ template_tracked=false
433
+ fi
434
+ else
435
+ echo "${c_bold}Template repo:${c_reset}"
436
+ echo " ${c_red}✖ not initialized${c_reset}"
437
+ template_tracked=false
438
+ fi
439
+
440
+ echo
441
+
442
+ # App repo inspection
443
+ # Find app repo .git directory
444
+ if [[ -d "$APP_DIR/.git" ]]; then
445
+ app_name="${APP_NAME:-app}"
446
+ echo "${c_bold}App repo${c_reset} ${c_blue}($app_name)${c_reset}:"
447
+
448
+ # Check if tracked in app repo
449
+ is_tracked_app=false
450
+ is_ignored_app=false
451
+
452
+ if git -C "$APP_DIR" ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
453
+ is_tracked_app=true
454
+ fi
455
+
456
+ # Check if ignored by app repo
457
+ if git -C "$APP_DIR" check-ignore -q -- "$path" 2>/dev/null; then
458
+ is_ignored_app=true
459
+ fi
460
+
461
+ # Display tracking status
462
+ if [[ "$is_tracked_app" == "true" ]]; then
463
+ echo " ${c_green}✔ tracked${c_reset}"
464
+
465
+ # Get the commit hash and time for this file (last commit that touched it)
466
+ commit_hash=$(git -C "$APP_DIR" log -1 --format="%h" -- "$path" 2>/dev/null || echo "")
467
+ if [[ -n "$commit_hash" ]]; then
468
+ echo " ${c_gray}commit: $commit_hash${c_reset}"
469
+
470
+ # Get commit time
471
+ commit_time=$(git -C "$APP_DIR" log -1 --format="%ci" -- "$path" 2>/dev/null | cut -d'.' -f1 || echo "")
472
+ [[ -n "$commit_time" ]] && echo " ${c_gray}commit time: $commit_time${c_reset}"
473
+ fi
474
+
475
+ # Check status
476
+ status_output=$(git -C "$APP_DIR" status --porcelain -- "$path" 2>/dev/null || echo "")
477
+
478
+ if [[ -z "$status_output" ]]; then
479
+ echo " ${c_green}status: clean${c_reset}"
480
+ else
481
+ # Parse status code
482
+ status_code="${status_output:0:2}"
483
+ case "$status_code" in
484
+ " M"|"M "|"MM") echo " ${c_yellow}status: modified${c_reset}" ;;
485
+ " D"|"D "|"DD") echo " ${c_red}status: deleted${c_reset}" ;;
486
+ "A "|"AM") echo " ${c_cyan}status: added${c_reset}" ;;
487
+ "??") echo " ${c_gray}status: untracked${c_reset}" ;;
488
+ *) echo " status: ${status_code}" ;;
489
+ esac
490
+ fi
491
+
492
+ app_tracked=true
493
+ else
494
+ # Not tracked - check if ignored or just untracked
495
+ if [[ "$is_ignored_app" == "true" ]]; then
496
+ echo " ${c_yellow}✖ ignored${c_reset}"
497
+ else
498
+ echo " ${c_red}✖ untracked${c_reset}"
499
+ fi
500
+ app_tracked=false
501
+ fi
502
+ else
503
+ echo "${c_bold}App repo:${c_reset}"
504
+ echo " ${c_red}✖ not found${c_reset}"
505
+ app_tracked=false
506
+ fi
507
+
508
+ echo
509
+
510
+ # Content comparison across all three states
511
+ if [[ -e "$APP_DIR/$path" ]] || [[ "$template_tracked" == "true" ]] || [[ "$app_tracked" == "true" ]]; then
512
+ echo "${c_bold}Content comparison:${c_reset}"
513
+
514
+ # Get checksums for comparison
515
+ working_hash=""
516
+ template_head_hash=""
517
+ app_head_hash=""
518
+
519
+ # Working tree hash
520
+ if [[ -e "$APP_DIR/$path" ]]; then
521
+ working_hash=$(md5sum "$APP_DIR/$path" 2>/dev/null | cut -d' ' -f1 || echo "")
522
+ fi
523
+
524
+ # Template HEAD hash
525
+ if [[ "$template_tracked" == "true" ]]; then
526
+ template_head_hash=$(git --git-dir="$DOTGIT" --work-tree="$APP_DIR" show HEAD:"$path" 2>/dev/null | md5sum | cut -d' ' -f1 || echo "")
527
+ fi
528
+
529
+ # App HEAD hash
530
+ if [[ "$app_tracked" == "true" ]]; then
531
+ app_head_hash=$(git -C "$APP_DIR" show HEAD:"$path" 2>/dev/null | md5sum | cut -d' ' -f1 || echo "")
532
+ fi
533
+
534
+ # Compare and show relationships
535
+ working_exists=$([[ -n "$working_hash" ]] && echo "true" || echo "false")
536
+ template_exists=$([[ -n "$template_head_hash" ]] && echo "true" || echo "false")
537
+ app_exists=$([[ -n "$app_head_hash" ]] && echo "true" || echo "false")
538
+
539
+ # Build comparison display
540
+ if [[ "$working_exists" == "true" && "$template_exists" == "true" && "$app_exists" == "true" ]]; then
541
+ # All three exist - compare them
542
+ if [[ "$working_hash" == "$template_head_hash" && "$working_hash" == "$app_head_hash" ]]; then
543
+ echo " ${c_green}✓ All three states identical${c_reset}"
544
+ elif [[ "$working_hash" == "$template_head_hash" && "$working_hash" != "$app_head_hash" ]]; then
545
+ echo " ${c_yellow}Working tree = Template HEAD ≠ App HEAD${c_reset}"
546
+ elif [[ "$working_hash" == "$app_head_hash" && "$working_hash" != "$template_head_hash" ]]; then
547
+ echo " ${c_yellow}Working tree = App HEAD ≠ Template HEAD${c_reset}"
548
+ elif [[ "$template_head_hash" == "$app_head_hash" && "$working_hash" != "$template_head_hash" ]]; then
549
+ echo " ${c_yellow}Template HEAD = App HEAD ≠ Working tree${c_reset}"
550
+ else
551
+ echo " ${c_red}✗ All three states differ${c_reset}"
552
+ fi
553
+ elif [[ "$working_exists" == "true" && "$template_exists" == "true" && "$app_exists" == "false" ]]; then
554
+ if [[ "$working_hash" == "$template_head_hash" ]]; then
555
+ echo " ${c_green}Working tree = Template HEAD${c_reset} ${c_gray}(not in app HEAD)${c_reset}"
556
+ else
557
+ echo " ${c_yellow}Working tree ≠ Template HEAD${c_reset} ${c_gray}(not in app HEAD)${c_reset}"
558
+ fi
559
+ elif [[ "$working_exists" == "true" && "$template_exists" == "false" && "$app_exists" == "true" ]]; then
560
+ if [[ "$working_hash" == "$app_head_hash" ]]; then
561
+ echo " ${c_green}Working tree = App HEAD${c_reset} ${c_gray}(not in template HEAD)${c_reset}"
562
+ else
563
+ echo " ${c_yellow}Working tree ≠ App HEAD${c_reset} ${c_gray}(not in template HEAD)${c_reset}"
564
+ fi
565
+ elif [[ "$working_exists" == "false" && "$template_exists" == "true" && "$app_exists" == "true" ]]; then
566
+ if [[ "$template_head_hash" == "$app_head_hash" ]]; then
567
+ echo " ${c_green}Template HEAD = App HEAD${c_reset} ${c_gray}(no working tree)${c_reset}"
568
+ else
569
+ echo " ${c_yellow}Template HEAD ≠ App HEAD${c_reset} ${c_gray}(no working tree)${c_reset}"
570
+ fi
571
+ fi
572
+
573
+ echo
574
+ fi
575
+
576
+ # Show all three pairwise diffs
577
+ echo "${c_bold}Diffs:${c_reset}"
578
+
579
+ # Create temp files for all three states
580
+ tmp_working=$(mktemp)
581
+ tmp_template_head=$(mktemp)
582
+ tmp_app_head=$(mktemp)
583
+
584
+ # Get contents
585
+ working_file_exists=false
586
+ template_head_exists=false
587
+ app_head_exists=false
588
+
589
+ if [[ -e "$APP_DIR/$path" ]]; then
590
+ cp "$APP_DIR/$path" "$tmp_working" 2>/dev/null && working_file_exists=true
591
+ fi
592
+
593
+ if [[ "$template_tracked" == "true" ]]; then
594
+ git --git-dir="$DOTGIT" --work-tree="$APP_DIR" show HEAD:"$path" > "$tmp_template_head" 2>/dev/null && template_head_exists=true
595
+ fi
596
+
597
+ if [[ "$app_tracked" == "true" ]]; then
598
+ git -C "$APP_DIR" show HEAD:"$path" > "$tmp_app_head" 2>/dev/null && app_head_exists=true
599
+ fi
600
+
601
+ # Helper function to compute diff stats on one line
602
+ compute_diff() {
603
+ local file1="$1"
604
+ local file2="$2"
605
+ local name1="$3"
606
+ local name2="$4"
607
+ local show_status="${5:-false}" # Show ahead/behind for HEAD comparisons
608
+
609
+ if cmp -s "$file1" "$file2"; then
610
+ echo " ${c_green}${name1} → ${name2}: identical${c_reset}"
611
+ else
612
+ local tmp_d=$(mktemp)
613
+ diff -u "$file1" "$file2" > "$tmp_d" 2>/dev/null || true
614
+ set +o pipefail
615
+ local add=$(grep "^\+" "$tmp_d" | grep -v "^\+\+\+" | wc -l | tr -d ' \n\r')
616
+ local rem=$(grep "^\-" "$tmp_d" | grep -v "^\-\-\-" | wc -l | tr -d ' \n\r')
617
+ set -o pipefail
618
+ add=${add:-0}
619
+ rem=${rem:-0}
620
+ rm -f "$tmp_d"
621
+
622
+ # Build descriptive diff summary
623
+ local summary=""
624
+ local status=""
625
+
626
+ if [[ "$add" -eq 0 && "$rem" -gt 0 ]]; then
627
+ summary="${name2} has ${rem} fewer line(s)"
628
+ [[ "$show_status" == "true" ]] && status=" ${c_cyan}(${name2} is behind ${name1})${c_reset}"
629
+ elif [[ "$add" -gt 0 && "$rem" -eq 0 ]]; then
630
+ summary="${name2} has ${add} more line(s)"
631
+ [[ "$show_status" == "true" ]] && status=" ${c_cyan}(${name2} is ahead ${name1})${c_reset}"
632
+ elif [[ "$add" -gt 0 && "$rem" -gt 0 ]]; then
633
+ summary="${name2} has ${add} more, ${rem} fewer lines"
634
+ status=" ${c_red}(diverged)${c_reset}"
635
+ fi
636
+
637
+ echo " ${c_yellow}${name1} → ${name2}:${c_reset} ${summary}${status}"
638
+ fi
639
+ }
640
+
641
+ # Diff 1: Working tree vs Template HEAD
642
+ if [[ "$working_file_exists" == "true" && "$template_head_exists" == "true" ]]; then
643
+ compute_diff "$tmp_working" "$tmp_template_head" "Working tree" "Template HEAD" "true"
644
+ fi
645
+
646
+ # Diff 2: Working tree vs App HEAD
647
+ if [[ "$working_file_exists" == "true" && "$app_head_exists" == "true" ]]; then
648
+ compute_diff "$tmp_working" "$tmp_app_head" "Working tree" "App HEAD" "true"
649
+ fi
650
+
651
+ # Diff 3: Template HEAD vs App HEAD (show ahead/behind status)
652
+ if [[ "$template_head_exists" == "true" && "$app_head_exists" == "true" ]]; then
653
+ compute_diff "$tmp_template_head" "$tmp_app_head" "Template HEAD" "App HEAD" "true"
654
+ fi
655
+
656
+ # Cleanup
657
+ rm -f "$tmp_working" "$tmp_template_head" "$tmp_app_head"
658
+ echo
659
+
660
+ } # end of inspect()
package/lib/version.sh CHANGED
File without changes
package/lib/why.sh CHANGED
@@ -5,23 +5,33 @@
5
5
  # whynot
6
6
 
7
7
  why() {
8
- cat <<'EOF'
9
- Why use geet?
8
+ printf '%b' "$(cat <<'EOF'
9
+ Use geet if this resonates with you...
10
+ \033[3m
11
+ "I built something useful, and I think that SOME but not all of my code is re-usable.
12
+ I want to publish some of my code for others to use (or to re-use myself)...
13
+ but I don't want to spend weeks refactoring to split apart the reusable code from the implementation-specific code.
14
+ In fact, it may not even be possible to move around all my files without breaking things.
15
+ Plus, supporting this template is my secondary task which I want to do in tandem with my primary development,
16
+ using my main repository's working directory and publishing some pieces to the template repo."
17
+
18
+ \033[0m
10
19
 
11
- I built something useful, and I think that SOME but not all of my code is re-usable.
12
- I want to publish some of my code for other's to use (or to re-use myself)...
13
- but I don't want to spend weeks refactoring to split apart the reusable code from the implementation-specific code.
14
- In fact, it may not even be possible to move around all my files without breaking things.
15
- Plus, supporting this template is my secondary task which I want to do in tandem with my primary development,
16
- using my main repository's working directory and publishing some pieces to the template repo.
17
20
  EOF
21
+ )"
22
+
18
23
  }
19
24
 
20
25
  whynot() {
21
- cat <<'EOF'
22
- Why NOT use geet?
26
+ printf '%b' "$(cat <<'EOF'
27
+ As much as I love this package, it is not for every use case...
28
+ \033[3m
29
+ You probably do not need geet if you can super cleanly separate your template from your app or make your source code fully modular.
30
+ If you can use a package distribution like pypi or npm that is probably best. If git submodules work for you, that is good too.
31
+ If those solutions are not working for you, then check back here.
32
+
33
+ \033[0m
23
34
 
24
- If you can super cleanly separate your template from your app or make your sourcecode fully modular,
25
- you don't need geet, use a normal repo or maybe submodules.
26
35
  EOF
36
+ )"
27
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geet-geet",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "geet": "bin/geet.sh",