I'd like a bash utility to help with managing the scenario where many git repositories are cloned into subdirectories under some parent directory. I have a project where each server's config is managed in a separate git repository like that. What I've found is that as changes are made locally to this collection of cloned repos, it can be hard to keep track of which ones now have local changes and hence need to be pushed to the remote. Could you write such a utility please? I've called it "git-recursive" and created an empty directory for it. I'd structure it as a command that takes a subcommand, but for now the only subcommand required to be implemented is "status". That subcommand should recursively look in directories from either a specified parent directory or the current working directory. Any directory that is a cloned git repository should be checked for current status. Output "local changes" or "pending commit" as appropriate. It is not necessary to look in subdirectories of any discovered git repository as repos are never cloned into other cloned repos.
ls -la /home/david/projects/code-associates/utilities/git-recursive && echo "---PARENT---" && ls -la /home/david/projects/code-associates/utilities
total 8 drwxr-xr-x 2 david david 4096 Jun 24 11:13 . drwxr-xr-x 5 david david 4096 Jun 24 11:13 .. ---PARENT--- total 24 drwxr-xr-x 5 david david 4096 Jun 24 11:13 . drwxr-xr-x 7 david david 4096 Feb 18 10:37 .. drwxr-xr-x 8 david david 4096 Jun 24 11:12 .git -rw-r--r-- 1 david david 11 Dec 5 2025 README.md drwxr-xr-x 2 david david 4096 Dec 7 2025 git-identity drwxr-xr-x 2 david david 4096 Jun 24 11:13 git-recursive
ls -la /home/david/projects/code-associates/utilities/git-identity
total 32 drwxr-xr-x 2 david david 4096 Dec 7 2025 . drwxr-xr-x 5 david david 4096 Jun 24 11:13 .. -rw-r--r-- 1 david david 6232 Dec 7 2025 README.md -rwx--x--x 1 david david 12932 Dec 7 2025 git-identity
{
"file_path": "/home/david/projects/code-associates/utilities/git-identity/git-identity"
}1 #!/bin/bash
2
3 # git-identity - A CLI utility for managing git identities in local repositories
4 # Sets user.name and user.email for the current repository based on an email address
5 # Also manages SSH keys for GitHub with per-identity key selection
6
7 set -e
8
9 SCRIPT_NAME="git-identity"
10 VERSION="2.0.0"
11
12 # Prefix for SSH key files to differentiate from other keys
13 SSH_KEY_PREFIX="gitid_"
14 SSH_DIR="$HOME/.ssh"
15 SSH_CONFIG="$SSH_DIR/config"
16
17 # Extract username from email address
18 # e.g., "john.doe@example.com" -> "John Doe"
19 extract_name_from_email() {
20 local email="$1"
21 local local_part="${email%%@*}"
22
23 # Replace common separators with spaces
24 local name="${local_part//[._-]/ }"
25
26 # Capitalize each word
27 local result=""
28 for word in $name; do
29 # Capitalize first letter, lowercase rest
30 local capitalized="$(echo "${word:0:1}" | tr '[:lower:]' '[:upper:]')$(echo "${word:1}" | tr '[:upper:]' '[:lower:]')"
31 if [[ -z "$result" ]]; then
32 result="$capitalized"
33 else
34 result="$result $capitalized"
35 fi
36 done
37
38 echo "$result"
39 }
40
41 # Extract local part from email (before @)
42 extract_local_part() {
43 local email="$1"
44 echo "${email%%@*}"
45 }
46
47 # Generate hostname alias from email
48 # e.g., "john.doe@example.com" -> "john.doe.github.com"
49 generate_host_alias() {
50 local email="$1"
51 local local_part=$(extract_local_part "$email")
52 # Replace underscores with hyphens for valid hostname
53 local sanitized="${local_part//_/-}"
54 echo "${sanitized}.github.com"
55 }
56
57 # Generate SSH key filename from email
58 generate_key_filename() {
59 local email="$1"
60 local local_part=$(extract_local_part "$email")
61 # Sanitize for filename: replace special chars with underscores
62 local sanitized="${local_part//[^a-zA-Z0-9]/_}"
63 echo "${SSH_KEY_PREFIX}${sanitized}"
64 }
65
66 # Validate email format
67 validate_email() {
68 local email="$1"
69 if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
70 return 0
71 else
72 return 1
73 fi
74 }
75
76 # Check if we're in a git repository
77 check_git_repo() {
78 if ! git rev-parse --git-dir > /dev/null 2>&1; then
79 echo "Error: Not in a git repository" >&2
80 exit 1
81 fi
82 }
83
84 # Ensure SSH directory exists
85 ensure_ssh_dir() {
86 if [[ ! -d "$SSH_DIR" ]]; then
87 mkdir -p "$SSH_DIR"
88 chmod 700 "$SSH_DIR"
89 fi
90 }
91
92 # Show current git identity
93 show_current() {
94 check_git_repo
95
96 local name=$(git config --local user.name 2>/dev/null || echo "(not set)")
97 local email=$(git config --local user.email 2>/dev/null || echo "(not set)")
98
99 echo "Current local git identity:"
100 echo " Name: $name"
101 echo " Email: $email"
102
103 if [[ "$email" != "(not set)" ]]; then
104 local host_alias=$(generate_host_alias "$email")
105 local key_name=$(generate_key_filename "$email")
106 local key_path="$SSH_DIR/$key_name"
107
108 echo ""
109 echo "SSH configuration:"
110 echo " Host alias: $host_alias"
111 if [[ -f "$key_path" ]]; then
112 echo " SSH key: $key_path (exists)"
113 else
114 echo " SSH key: $key_path (not created)"
115 fi
116 fi
117 }
118
119 # Set git identity
120 set_identity() {
121 local email="$1"
122
123 check_git_repo
124
125 if ! validate_email "$email"; then
126 echo "Error: Invalid email format: $email" >&2
127 exit 1
128 fi
129
130 local name=$(extract_name_from_email "$email")
131
132 git config --local user.name "$name"
133 git config --local user.email "$email"
134
135 local host_alias=$(generate_host_alias "$email")
136 local key_name=$(generate_key_filename "$email")
137 local key_path="$SSH_DIR/$key_name"
138
139 echo "Git identity set for this repository:"
140 echo " Name: $name"
141 echo " Email: $email"
142 echo ""
143 echo "GitHub SSH host alias: $host_alias"
144
145 if [[ -f "$key_path" ]]; then
146 echo "SSH key exists: $key_path"
147 else
148 echo ""
149 echo "Note: SSH key not found for this identity."
150 echo "Run '$SCRIPT_NAME create $email' to create one."
151 fi
152 }
153
154 # Clear local git identity
155 clear_identity() {
156 check_git_repo
157
158 git config --local --unset user.name 2>/dev/null || true
159 git config --local --unset user.email 2>/dev/null || true
160
161 echo "Local git identity cleared."
162 }
163
164 # Add or update SSH config entry for a host alias
165 update_ssh_config() {
166 local host_alias="$1"
167 local key_path="$2"
168
169 ensure_ssh_dir
170
171 # Create config file if it doesn't exist
172 if [[ ! -f "$SSH_CONFIG" ]]; then
173 touch "$SSH_CONFIG"
174 chmod 600 "$SSH_CONFIG"
175 fi
176
177 # Check if entry already exists
178 if grep -q "^Host ${host_alias}$" "$SSH_CONFIG" 2>/dev/null; then
179 echo "Updating existing SSH config entry for $host_alias"
180 # Remove existing entry (from Host line to next Host line or end of file)
181 local temp_file=$(mktemp)
182 awk -v host="$host_alias" '
183 BEGIN { skip = 0 }
184 /^Host / {
185 if ($2 == host) {
186 skip = 1
187 next
188 } else {
189 skip = 0
190 }
191 }
192 !skip { print }
193 ' "$SSH_CONFIG" > "$temp_file"
194 mv "$temp_file" "$SSH_CONFIG"
195 fi
196
197 # Append new entry
198 cat >> "$SSH_CONFIG" << EOF
199
200 # Git identity: $host_alias (managed by git-identity)
201 Host $host_alias
202 HostName github.com
203 User git
204 IdentityFile $key_path
205 IdentitiesOnly yes
206 EOF
207
208 echo "SSH config updated for host alias: $host_alias"
209 }
210
211 # Remove SSH config entry for a host alias
212 remove_ssh_config() {
213 local host_alias="$1"
214
215 if [[ ! -f "$SSH_CONFIG" ]]; then
216 return
217 fi
218
219 if grep -q "^Host ${host_alias}$" "$SSH_CONFIG" 2>/dev/null; then
220 local temp_file=$(mktemp)
221 awk -v host="$host_alias" '
222 BEGIN { skip = 0 }
223 /^# Git identity:.*\(managed by git-identity\)$/ {
224 getline next_line
225 if (next_line ~ "^Host " host "$") {
226 skip = 1
227 next
228 } else {
229 print
230 print next_line
231 next
232 }
233 }
234 /^Host / {
235 if ($2 == host) {
236 skip = 1
237 next
238 } else {
239 skip = 0
240 }
241 }
242 !skip { print }
243 ' "$SSH_CONFIG" > "$temp_file"
244 mv "$temp_file" "$SSH_CONFIG"
245 echo "Removed SSH config entry for $host_alias"
246 fi
247 }
248
249 # Create SSH key for an identity
250 create_key() {
251 local email="$1"
252
253 if ! validate_email "$email"; then
254 echo "Error: Invalid email format: $email" >&2
255 exit 1
256 fi
257
258 ensure_ssh_dir
259
260 local key_name=$(generate_key_filename "$email")
261 local key_path="$SSH_DIR/$key_name"
262 local host_alias=$(generate_host_alias "$email")
263
264 if [[ -f "$key_path" ]]; then
265 echo "Error: SSH key already exists: $key_path" >&2
266 echo "Use '$SCRIPT_NAME delete $email' to remove it first." >&2
267 exit 1
268 fi
269
270 echo "Creating SSH key for identity: $email"
271 echo "Key file: $key_path"
272 echo ""
273
274 # Generate ED25519 key (recommended for GitHub)
275 ssh-keygen -t ed25519 -C "$email" -f "$key_path"
276
277 # Update SSH config
278 update_ssh_config "$host_alias" "$key_path"
279
280 echo ""
281 echo "SSH key created successfully!"
282 echo ""
283 echo "Host alias for git remotes: $host_alias"
284 echo ""
285 echo "Public key (add this to GitHub):"
286 echo "----------------------------------------"
287 cat "${key_path}.pub"
288 echo "----------------------------------------"
289 echo ""
290 echo "To use this identity with a repository, update the remote URL:"
291 echo " git remote set-url origin git@${host_alias}:<user>/<repo>.git"
292 echo ""
293 echo "Or clone using the host alias:"
294 echo " git clone git@${host_alias}:<user>/<repo>.git"
295 }
296
297 # List all managed SSH keys
298 list_keys() {
299 ensure_ssh_dir
300
301 echo "Managed SSH keys (prefix: ${SSH_KEY_PREFIX}):"
302 echo ""
303
304 local found=0
305 for key_file in "$SSH_DIR"/${SSH_KEY_PREFIX}*; do
306 # Skip if no matches (glob returns pattern if no matches)
307 [[ -e "$key_file" ]] || continue
308 # Skip public keys, only show private keys
309 [[ "$key_file" == *.pub ]] && continue
310
311 found=1
312 local key_name=$(basename "$key_file")
313 local pub_key="${key_file}.pub"
314
315 echo "Key: $key_name"
316 echo " Path: $key_file"
317
318 if [[ -f "$pub_key" ]]; then
319 # Extract email from public key comment
320 local comment=$(awk '{print $3}' "$pub_key" 2>/dev/null)
321 if [[ -n "$comment" ]]; then
322 echo " Email: $comment"
323 local host_alias=$(generate_host_alias "$comment")
324 echo " Host alias: $host_alias"
325 fi
326 echo " Public key: $pub_key"
327 fi
328 echo ""
329 done
330
331 if [[ $found -eq 0 ]]; then
332 echo "No managed SSH keys found."
333 echo "Use '$SCRIPT_NAME create <email>' to create one."
334 fi
335 }
336
337 # Delete SSH key for an identity
338 delete_key() {
339 local email="$1"
340
341 if ! validate_email "$email"; then
342 echo "Error: Invalid email format: $email" >&2
343 exit 1
344 fi
345
346 local key_name=$(generate_key_filename "$email")
347 local key_path="$SSH_DIR/$key_name"
348 local host_alias=$(generate_host_alias "$email")
349
350 if [[ ! -f "$key_path" ]]; then
351 echo "Error: SSH key not found: $key_path" >&2
352 exit 1
353 fi
354
355 echo "This will delete the SSH key for: $email"
356 echo " Private key: $key_path"
357 echo " Public key: ${key_path}.pub"
358 echo ""
359 read -p "Are you sure? (y/N) " -n 1 -r
360 echo ""
361
362 if [[ $REPLY =~ ^[Yy]$ ]]; then
363 rm -f "$key_path" "${key_path}.pub"
364 remove_ssh_config "$host_alias"
365 echo "SSH key deleted."
366 else
367 echo "Cancelled."
368 fi
369 }
370
371 # Show public key for an identity
372 show_pubkey() {
373 local email="$1"
374
375 if ! validate_email "$email"; then
376 echo "Error: Invalid email format: $email" >&2
377 exit 1
378 fi
379
380 local key_name=$(generate_key_filename "$email")
381 local pub_key="$SSH_DIR/${key_name}.pub"
382
383 if [[ ! -f "$pub_key" ]]; then
384 echo "Error: Public key not found: $pub_key" >&2
385 echo "Use '$SCRIPT_NAME create $email' to create a key." >&2
386 exit 1
387 fi
388
389 cat "$pub_key"
390 }
391
392 # Show usage
393 usage() {
394 cat << EOF
395 $SCRIPT_NAME v$VERSION - Manage git identities and SSH keys for GitHub
396
397 Usage:
398 $SCRIPT_NAME <email> Set git identity for current repository
399 $SCRIPT_NAME show Show current local git identity
400 $SCRIPT_NAME clear Clear local git identity
401
402 SSH Key Management:
403 $SCRIPT_NAME create <email> Create SSH key for an identity
404 $SCRIPT_NAME list List all managed SSH keys
405 $SCRIPT_NAME delete <email> Delete SSH key for an identity
406 $SCRIPT_NAME pubkey <email> Show public key for an identity
407
408 $SCRIPT_NAME help Show this help message
409
410 Examples:
411 # Set identity for current repository
412 $SCRIPT_NAME john.doe@example.com
413
414 # Create SSH key for a new identity
415 $SCRIPT_NAME create john.doe@example.com
416
417 # List all managed SSH keys
418 $SCRIPT_NAME list
419
420 The username is automatically derived from the email address:
421 john.doe@example.com -> John Doe
422 jane_smith@company.org -> Jane Smith
423
424 SSH Key Scheme:
425 Each identity gets a unique hostname alias for GitHub:
426 john.doe@example.com -> john.doe.github.com
427
428 Use this alias in your git remote URLs to automatically select
429 the correct SSH key for each identity:
430 git@john.doe.github.com:user/repo.git
431
432 The SSH config (~/.ssh/config) is automatically updated to map
433 these aliases to github.com with the appropriate identity key.
434 EOF
435 }
436
437 # Main
438 case "${1:-}" in
439 "")
440 usage
441 exit 1
442 ;;
443 help|--help|-h)
444 usage
445 exit 0
446 ;;
447 show|--show|-s)
448 show_current
449 ;;
450 clear|--clear|-c)
451 clear_identity
452 ;;
453 list|--list|-l)
454 list_keys
455 ;;
456 create)
457 if [[ -z "${2:-}" ]]; then
458 echo "Error: Email address required" >&2
459 echo "Usage: $SCRIPT_NAME create <email>" >&2
460 exit 1
461 fi
462 create_key "$2"
463 ;;
464 delete)
465 if [[ -z "${2:-}" ]]; then
466 echo "Error: Email address required" >&2
467 echo "Usage: $SCRIPT_NAME delete <email>" >&2
468 exit 1
469 fi
470 delete_key "$2"
471 ;;
472 pubkey)
473 if [[ -z "${2:-}" ]]; then
474 echo "Error: Email address required" >&2
475 echo "Usage: $SCRIPT_NAME pubkey <email>" >&2
476 exit 1
477 fi
478 show_pubkey "$2"
479 ;;
480 *)
481 set_identity "$1"
482 ;;
483 esac
484 The sibling git-identity shows me the house style. Let me write git-recursive to match it.
#!/bin/bash
# git-recursive - Operate on many git repositories cloned under a parent directory
# Discovers cloned repositories in subdirectories and reports their status, so it
# is easy to see which ones have uncommitted work or commits that need pushing.
set -e
SCRIPT_NAME="git-recursive"
VERSION="1.0.0"
# Run a git command against a specific repository directory
git_in() {
local dir="$1"
shift
git -C "$dir" "$@" 2>/dev/null
}
# Determine and print the status of a single discovered repository.
# Reports "local changes" if the working tree is dirty, "pending commit"
# if there are commits not yet pushed to the upstream, "no upstream" if the
# branch has nothing to push to, or "clean" if there is nothing outstanding.
report_repo() {
local dir="$1"
local rel="${dir#"$ROOT"/}"
[[ "$rel" == "$dir" ]] && rel="."
local states=()
# Uncommitted work in the working tree (staged, modified or untracked)
if [[ -n "$(git_in "$dir" status --porcelain)" ]]; then
states+=("local changes")
fi
# Commits that exist locally but have not been pushed
if git_in "$dir" rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null; then
local ahead
ahead=$(git_in "$dir" rev-list --count '@{upstream}..HEAD')
if [[ "${ahead:-0}" -gt 0 ]]; then
states+=("pending commit")
fi
elif git_in "$dir" rev-parse HEAD >/dev/null; then
# Branch has commits but no upstream to push them to
states+=("no upstream")
fi
local status
if [[ ${#states[@]} -eq 0 ]]; then
status="clean"
else
local IFS=", "
status="${states[*]}"
fi
printf '%-50s %s\n' "$rel" "$status"
}
# Recursively walk a directory tree. When a cloned repository is found it is
# reported and not descended into, since repos are never cloned inside repos.
scan_dir() {
local dir="$1"
# A cloned repository has a .git entry (directory, or a file for worktrees)
if [[ -e "$dir/.git" ]]; then
report_repo "$dir"
return
fi
local entry
for entry in "$dir"/*/; do
[[ -d "$entry" ]] || continue
scan_dir "${entry%/}"
done
}
# "status" subcommand entry point
cmd_status() {
local parent="${1:-$PWD}"
if [[ ! -d "$parent" ]]; then
echo "Error: Not a directory: $parent" >&2
exit 1
fi
# Normalise to an absolute path so relative output is stable
ROOT="$(cd "$parent" && pwd)"
scan_dir "$ROOT"
}
# Show usage
usage() {
cat << EOF
$SCRIPT_NAME v$VERSION - Status across many cloned git repositories
Usage:
$SCRIPT_NAME status [parent-dir] Report the status of every cloned
repository found under parent-dir
(defaults to the current directory)
$SCRIPT_NAME help Show this help message
Each repository found under the parent directory is reported with one or
more of the following states:
local changes The working tree has uncommitted changes
pending commit There are commits that have not been pushed upstream
no upstream The branch has commits but no upstream to push to
clean Nothing outstanding
Subdirectories of a discovered repository are not searched, as repositories
are never cloned inside other repositories.
Example:
# Check every server config repo cloned under ~/servers
$SCRIPT_NAME status ~/servers
EOF
}
# Main
case "${1:-}" in
"")
usage
exit 1
;;
help|--help|-h)
usage
exit 0
;;
status)
shift
cmd_status "$@"
;;
*)
echo "Error: Unknown subcommand: $1" >&2
echo "Run '$SCRIPT_NAME help' for usage." >&2
exit 1
;;
esac
File created successfully at: /home/david/projects/code-associates/utilities/git-recursive/git-recursive (file state is current in your context — no need to Read it back)
chmod +x /home/david/projects/code-associates/utilities/git-recursive/git-recursive # Build a small test tree of cloned repos to exercise the script TMP=$(mktemp -d) cd "$TMP" mkdir -p servers git init -q servers/clean-repo && (cd servers/clean-repo && git commit -q --allow-empty -m init) git init -q servers/dirty-repo && (cd servers/dirty-repo && git commit -q --allow-empty -m init && echo hi > newfile) git init -q servers/nested/deep-repo && (cd servers/nested/deep-repo && git commit -q --allow-empty -m init) mkdir -p servers/not-a-repo/plain echo "=== run from parent ===" /home/david/projects/code-associates/utilities/git-recursive/git-recursive status "$TMP/servers" echo "=== help ===" /home/david/projects/code-associates/utilities/git-recursive/git-recursive help echo "=== bad subcommand exit code ===" /home/david/projects/code-associates/utilities/git-recursive/git-recursive bogus; echo "exit=$?" rm -rf "$TMP"
Exit code 1
=== run from parent ===
clean-repo no upstream
dirty-repo local changes,no upstream
nested/deep-repo no upstream
=== help ===
git-recursive v1.0.0 - Status across many cloned git repositories
Usage:
git-recursive status [parent-dir] Report the status of every cloned
repository found under parent-dir
(defaults to the current directory)
git-recursive help Show this help message
Each repository found under the parent directory is reported with one or
more of the following states:
local changes The working tree has uncommitted changes
pending commit There are commits that have not been pushed upstream
no upstream The branch has commits but no upstream to push to
clean Nothing outstanding
Subdirectories of a discovered repository are not searched, as repositories
are never cloned inside other repositories.
Example:
# Check every server config repo cloned under ~/servers
git-recursive status ~/servers
=== bad subcommand exit code ===
Error: Unknown subcommand: bogus
Run 'git-recursive help' for usage.
exit=1
pwd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directoryWorks. One cosmetic bug: the state separator rendered as local changes,no upstream with no space. The ${array[*]} join only uses the first char of IFS. Let me fix that.
local status
if [[ ${#states[@]} -eq 0 ]]; then
status="clean"
else
local IFS=", "
status="${states[*]}"
fi local status
if [[ ${#states[@]} -eq 0 ]]; then
status="clean"
else
printf -v status '%s, ' "${states[@]}"
status="${status%, }"
fiThe file /home/david/projects/code-associates/utilities/git-recursive/git-recursive has been updated successfully. (file state is current in your context — no need to Read it back)
TMP=$(mktemp -d); cd "$TMP"; mkdir -p servers git init -q servers/dirty && (cd servers/dirty && git commit -q --allow-empty -m init && echo hi > f) # simulate a repo with an upstream that is ahead (pending commit) git init -q --bare remote.git git clone -q remote.git servers/ahead && (cd servers/ahead && git commit -q --allow-empty -m init && git push -q -u origin HEAD && git commit -q --allow-empty -m local) git clone -q remote.git servers/upToDate && (cd servers/upToDate && git fetch -q) echo "=== status ===" /home/david/projects/code-associates/utilities/git-recursive/git-recursive status "$TMP/servers" cd /; rm -rf "$TMP"
warning: You appear to have cloned an empty repository. === status === ahead pending commit dirty local changes, no upstream upToDate clean Shell cwd was reset to /home/david/projects/code-associates/utilities/git-recursive
All four states detect correctly:
ahead → pending commit (commit not yet pushed)dirty → local changes, no upstreamupToDate → cleangit-recursive at /home/david/projects/code-associates/utilities/git-recursive/git-recursive (executable), styled to match the sibling git-identity utility.
Usage: git-recursive status [parent-dir] — defaults to the current directory if no parent is given.
It recursively descends from the parent directory, and for each cloned repo it finds (a directory with a .git entry) it reports one or more states, then stops descending into that repo:
| State | Meaning |
|---|---|
local changes |
working tree is dirty (staged/modified/untracked) |
pending commit |
commits exist locally but aren't pushed upstream |
no upstream |
branch has commits but no upstream to push to |
clean |
nothing outstanding |
Notes on a couple of judgment calls:
local changes) vs. committed-but-unpushed (pending commit) — since a repo can be in either or both, and they call for different actions (commit vs. push). A repo with both shows local changes, pending commit.no upstream because a freshly-cloned-but-never-pushed branch otherwise looks clean even though it has unpushed work. In your scenario (config repos cloned from remotes) most repos will have an upstream, so this mainly flags anomalies.The subcommand dispatch is in place, so adding pull, push, etc. later is a matter of dropping in another cmd_* function. Want me to add a README.md to match git-identity, or wire up any further subcommands?
Thanks! Can you also test on the production repos cloned here? /home/david/projects/nuevasync/Deployment/config_repos
/home/david/projects/code-associates/utilities/git-recursive/git-recursive status /home/david/projects/nuevasync/Deployment/config_repos
cluster_cluster_cfg_c1 clean cluster_cluster_cfg_c2 local changes cluster_cluster_cfg_cs clean cluster_general_cfg_c1 clean cluster_general_cfg_c2 clean cluster_general_cfg_cs clean server_cfg_c1_1 clean server_cfg_c1_2 clean server_cfg_c1_20 clean server_cfg_c1_21 clean server_cfg_c1_80 clean server_cfg_c2_22 clean server_cfg_c2_5 clean server_cfg_c2_50 clean server_cfg_c2_6 clean server_cfg_c2_7 clean server_cfg_c2_8 clean server_cfg_cs_99 clean
Worked against the production set. It discovered 18 cloned repos under config_repos, all reporting cleanly. One stands out:
cluster_cluster_cfg_c2 → local changes — has uncommitted work in its working tree.Everything else is clean (committed and pushed, nothing pending). So that one repo is the only thing currently needing attention — you'd want to commit and push it.
Want me to peek at what the uncommitted changes are in cluster_cluster_cfg_c2 (a git status/git diff there), or leave it for you to handle?
Sounds good — it handled the real set cleanly: 18 repos discovered, correctly flagged the one with uncommitted work, and the rest clean. The utility's ready to use.