@@ -239,6 +239,79 @@ cfg_unset() {
239239 git config $flag --unset-all " $1 " 2> /dev/null || true
240240}
241241
242+ # ── cfg_list helpers ──────────────────────────────────────────────────
243+ # Module-level state for cfg_list auto-mode deduplication.
244+ # Reset by cfg_list() at the start of each "auto" invocation.
245+ _cfg_list_seen=" "
246+ _cfg_list_result=" "
247+
248+ # Add a config entry, deduplicating by key+value combo.
249+ # Uses Unit Separator ($'\x1f') as delimiter to avoid collision with any value content.
250+ # Usage: _cfg_list_add_entry <origin> <key> <value>
251+ _cfg_list_add_entry () {
252+ local origin=" $1 " entry_key=" $2 " entry_value=" $3 "
253+ local id=$' \x1f ' " ${entry_key} =${entry_value} " $' \x1f '
254+
255+ # Use [[ ]] for literal string matching (no glob interpretation)
256+ if [[ " $_cfg_list_seen " == * " $id " * ]]; then
257+ return 0
258+ fi
259+
260+ _cfg_list_seen=" ${_cfg_list_seen}${id} "
261+ _cfg_list_result=" ${_cfg_list_result}${entry_key} " $' \x1f ' " ${entry_value} " $' \x1f ' " ${origin} " $' \n '
262+ }
263+
264+ # Parse git config --get-regexp output and add each entry with an origin label.
265+ # Usage: _cfg_list_parse_entries <origin> <get-regexp-output>
266+ _cfg_list_parse_entries () {
267+ local origin=" $1 " entries=" $2 "
268+ local line key value
269+ while IFS= read -r line; do
270+ [ -z " $line " ] && continue
271+ key=" ${line%% * } "
272+ if [[ " $line " == * " " * ]]; then
273+ value=" ${line#* } "
274+ else
275+ value=" "
276+ fi
277+ _cfg_list_add_entry " $origin " " $key " " $value "
278+ done <<< " $entries"
279+ }
280+
281+ # Format cfg_list output with alignment.
282+ # Detects auto-mode (Unit Separator delimited with origin) vs scoped (space delimited).
283+ # Usage: _cfg_list_format <output>
284+ _cfg_list_format () {
285+ local output=" $1 "
286+ if [ -z " $output " ]; then
287+ echo " No gtr configuration found"
288+ return 0
289+ fi
290+
291+ printf ' %s\n' " $output " | while IFS= read -r line; do
292+ [ -z " $line " ] && continue
293+
294+ local key value origin rest
295+ if [[ " $line " == * $' \x1f ' * ]]; then
296+ # Auto-mode format: key<US>value<US>origin
297+ key=" ${line%% $' \x1f ' * } "
298+ rest=" ${line#* $' \x1f ' } "
299+ value=" ${rest%% $' \x1f ' * } "
300+ origin=" ${rest#* $' \x1f ' } "
301+ printf " %-35s = %-25s [%s]\n" " $key " " $value " " $origin "
302+ else
303+ # Scoped format: key value (no origin)
304+ key=" ${line%% * } "
305+ if [[ " $line " == * " " * ]]; then
306+ value=" ${line#* } "
307+ else
308+ value=" "
309+ fi
310+ printf " %-35s = %s\n" " $key " " $value "
311+ fi
312+ done
313+ }
314+
242315# List all gtr.* config values
243316# Usage: cfg_list [scope]
244317# scope: auto (default), local, global, system
@@ -262,52 +335,11 @@ cfg_list() {
262335 output=$( git config --system --get-regexp ' ^gtr\.' 2> /dev/null || true)
263336 ;;
264337 auto)
265- # Merge all sources with origin labels
266- # Deduplicates by key+value combo, preserving all multi-values from highest priority source
267- local seen_keys=" "
268- local result=" "
338+ # Reset module-level state for this invocation
339+ _cfg_list_seen=" "
340+ _cfg_list_result=" "
269341 local key value line
270342
271- # Set up cleanup trap for helper functions (protects against early exit/return)
272- trap ' unset -f _cfg_list_add_entry _cfg_list_parse_entries 2>/dev/null' RETURN
273-
274- # Helper function to add entries with origin (inline to avoid Bash 3.2 nameref issues)
275- # Uses Unit Separator ($'\x1f') as delimiter to avoid conflicts with any values
276- _cfg_list_add_entry () {
277- local origin=" $1 "
278- local entry_key=" $2 "
279- local entry_value=" $3 "
280-
281- # For multi-valued keys: check if key+value combo already seen
282- # This allows multiple values for the same key from the same source
283- # Use Unit Separator as delimiter in seen_keys to avoid collision with any value content
284- local id=$' \x1f ' " ${entry_key} =${entry_value} " $' \x1f '
285- # Use [[ ]] for literal string matching (no glob interpretation)
286- if [[ " $seen_keys " == * " $id " * ]]; then
287- return 0
288- fi
289-
290- seen_keys=" ${seen_keys}${id} "
291- # Use Unit Separator ($'\x1f') as delimiter - won't appear in normal values
292- result=" ${result}${entry_key} " $' \x1f ' " ${entry_value} " $' \x1f ' " ${origin} " $' \n '
293- }
294-
295- # Parse get-regexp output and add each entry with an origin label
296- _cfg_list_parse_entries () {
297- local origin=" $1 "
298- local entries=" $2 "
299- while IFS= read -r line; do
300- [ -z " $line " ] && continue
301- key=" ${line%% * } "
302- if [[ " $line " == * " " * ]]; then
303- value=" ${line#* } "
304- else
305- value=" "
306- fi
307- _cfg_list_add_entry " $origin " " $key " " $value "
308- done <<< " $entries"
309- }
310-
311343 # Process in priority order: local > .gtrconfig > global > system
312344 _cfg_list_parse_entries " local" \
313345 " $( git config --local --get-regexp ' ^gtr\.' 2> /dev/null || true) "
@@ -334,52 +366,16 @@ cfg_list() {
334366 _cfg_list_parse_entries " system" \
335367 " $( git config --system --get-regexp ' ^gtr\.' 2> /dev/null || true) "
336368
337- # Clean up helper functions and clear trap (trap handles early exit cases)
338- unset -f _cfg_list_add_entry _cfg_list_parse_entries
339- trap - RETURN
340-
341- output=" $result "
369+ output=" $_cfg_list_result "
342370 ;;
343371 * )
344- # Unknown scope - warn and fall back to auto
345372 log_warn " Unknown scope '$scope ', using 'auto'"
346373 cfg_list " auto"
347374 return $?
348375 ;;
349376 esac
350377
351- # Format and display output
352- if [ -z " $output " ]; then
353- echo " No gtr configuration found"
354- return 0
355- fi
356-
357- # Format output with alignment
358- # Use printf '%s\n' instead of echo for safety with special characters
359- printf ' %s\n' " $output " | while IFS= read -r line; do
360- [ -z " $line " ] && continue
361-
362- local key value origin rest
363- # Check if line uses Unit Separator delimiter (auto mode with origin)
364- if [[ " $line " == * $' \x1f ' * ]]; then
365- # Format: key<US>value<US>origin
366- key=" ${line%% $' \x1f ' * } "
367- rest=" ${line#* $' \x1f ' } "
368- value=" ${rest%% $' \x1f ' * } "
369- origin=" ${rest#* $' \x1f ' } "
370- printf " %-35s = %-25s [%s]\n" " $key " " $value " " $origin "
371- else
372- # Format: key value (no origin, for scoped queries)
373- key=" ${line%% * } "
374- # Handle empty values (no space in line means value is empty)
375- if [[ " $line " == * " " * ]]; then
376- value=" ${line#* } "
377- else
378- value=" "
379- fi
380- printf " %-35s = %s\n" " $key " " $value "
381- fi
382- done
378+ _cfg_list_format " $output "
383379}
384380
385381# Get config value with environment variable fallback
0 commit comments