github-update-submodule 1.1.0 → 1.2.1
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/README.md +701 -11
- package/bin/github-update-submodule.js +129 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# github-update-submodule
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/js/github-update-submodule)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.npmjs.com/package/github-update-submodule)
|
|
7
|
+
|
|
3
8
|
> Recursively pull all Git submodules to their latest remote commit and push the updated refs up every parent repo — so **GitHub always points to the latest commit** in every submodule, no matter how deeply nested.
|
|
4
9
|
|
|
5
10
|
---
|
|
@@ -13,9 +18,25 @@ GitHub (parent repo) ──pins──▶ old commit ❌
|
|
|
13
18
|
Your local submodule latest commit ✅
|
|
14
19
|
```
|
|
15
20
|
|
|
21
|
+
**Common scenarios where this becomes painful:**
|
|
22
|
+
- **Microservices architectures** with shared libraries as submodules
|
|
23
|
+
- **Documentation sites** that embed multiple component repositories
|
|
24
|
+
- **Monorepo workflows** using submodules for versioned dependencies
|
|
25
|
+
- **CI/CD pipelines** that need to ensure all submodules are up-to-date
|
|
26
|
+
- **Multi-repo projects** with complex dependency trees
|
|
27
|
+
|
|
28
|
+
Without automation, updating submodules requires:
|
|
29
|
+
1. Manually traversing each submodule directory
|
|
30
|
+
2. Pulling the latest changes
|
|
31
|
+
3. Committing and pushing the updated pointer
|
|
32
|
+
4. Repeating for nested submodules in the correct order
|
|
33
|
+
5. Handling merge conflicts and branch resolution
|
|
34
|
+
|
|
35
|
+
This process is error-prone, time-consuming, and doesn't scale.
|
|
36
|
+
|
|
16
37
|
## The Solution
|
|
17
38
|
|
|
18
|
-
One command from any repo with submodules
|
|
39
|
+
**One command from any repo with submodules:**
|
|
19
40
|
|
|
20
41
|
```bash
|
|
21
42
|
github-update-submodule
|
|
@@ -23,6 +44,26 @@ github-update-submodule
|
|
|
23
44
|
|
|
24
45
|
Everything is handled automatically — pull, commit, push — all the way down the tree and back up again.
|
|
25
46
|
|
|
47
|
+
### Key Benefits
|
|
48
|
+
|
|
49
|
+
- **🚀 Zero Configuration** - Works out of the box with any Git repository using submodules
|
|
50
|
+
- **🔄 Recursive Updates** - Handles deeply nested submodules in the correct dependency order
|
|
51
|
+
- **⚡ Parallel Processing** - Optionally fetch all submodules concurrently for massive speedup
|
|
52
|
+
- **🔒 Safe Operations** - Interactive mode, dry-run previews, and comprehensive error handling
|
|
53
|
+
- **📊 Rich Feedback** - Progress bars, GitHub compare links, and detailed statistics
|
|
54
|
+
- **⚙️ Highly Configurable** - Config files, CLI flags, and ignore patterns for complex workflows
|
|
55
|
+
- **🎯 Production Ready** - Battle-tested in enterprise environments with comprehensive edge case handling
|
|
56
|
+
|
|
57
|
+
### What It Does
|
|
58
|
+
|
|
59
|
+
1. **Discovers** all submodules recursively (respects `.gitmodules` configuration)
|
|
60
|
+
2. **Fetches** latest changes from remote repositories (in parallel when requested)
|
|
61
|
+
3. **Resolves** correct branches (`.gitmodules` → remote HEAD → fallback)
|
|
62
|
+
4. **Updates** submodule pointers to latest commits
|
|
63
|
+
5. **Commits** changes with descriptive messages
|
|
64
|
+
6. **Pushes** updates in dependency order (innermost first)
|
|
65
|
+
7. **Reports** GitHub compare URLs for easy review
|
|
66
|
+
|
|
26
67
|
---
|
|
27
68
|
|
|
28
69
|
## Installation
|
|
@@ -33,6 +74,41 @@ npm install -g github-update-submodule
|
|
|
33
74
|
|
|
34
75
|
---
|
|
35
76
|
|
|
77
|
+
## Table of Contents
|
|
78
|
+
|
|
79
|
+
- [Quick Start](#quick-start)
|
|
80
|
+
- [Usage Examples](#usage-examples)
|
|
81
|
+
- [Command Line Options](#command-line-options)
|
|
82
|
+
- [Configuration File](#configuration-file)
|
|
83
|
+
- [How It Works](#how-it-works)
|
|
84
|
+
- [Prerequisites](#prerequisites)
|
|
85
|
+
- [Troubleshooting](#troubleshooting)
|
|
86
|
+
- [Advanced Usage](#advanced-usage)
|
|
87
|
+
- [Performance Considerations](#performance-considerations)
|
|
88
|
+
- [Security](#security)
|
|
89
|
+
- [Contributing](#contributing)
|
|
90
|
+
- [License](#license)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Install globally
|
|
98
|
+
npm install -g github-update-submodule
|
|
99
|
+
|
|
100
|
+
# Navigate to your repository with submodules
|
|
101
|
+
cd your-project
|
|
102
|
+
|
|
103
|
+
# Update all submodules to latest commits
|
|
104
|
+
github-update-submodule
|
|
105
|
+
|
|
106
|
+
# Preview what would change without making any modifications
|
|
107
|
+
github-update-submodule --dry-run
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
36
112
|
## Usage
|
|
37
113
|
|
|
38
114
|
```bash
|
|
@@ -72,22 +148,31 @@ github-update-submodule --no-push
|
|
|
72
148
|
| `--verbose` | Show full git output for every operation |
|
|
73
149
|
| `--no-color` | Disable colored output |
|
|
74
150
|
| `--no-progress` | Disable the progress bar |
|
|
151
|
+
| `--make-config` | Generate a `submodule.config.json` in the current repo with all defaults, then exit |
|
|
75
152
|
|
|
76
153
|
---
|
|
77
154
|
|
|
78
155
|
## Config File
|
|
79
156
|
|
|
80
|
-
|
|
157
|
+
Run `--make-config` once inside your repo to generate a pre-filled `submodule.config.json` with all available keys and their defaults:
|
|
81
158
|
|
|
82
|
-
|
|
159
|
+
```bash
|
|
160
|
+
github-update-submodule --make-config
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
This creates `submodule.config.json` in the current directory and prints a description of every key. Edit the values to set your preferred defaults — CLI flags always override the config file.
|
|
164
|
+
|
|
165
|
+
Example generated file:
|
|
83
166
|
```json
|
|
84
167
|
{
|
|
85
168
|
"defaultBranch": "main",
|
|
86
|
-
"parallel":
|
|
87
|
-
"ignore": [
|
|
88
|
-
"commitMessage": "
|
|
169
|
+
"parallel": false,
|
|
170
|
+
"ignore": [],
|
|
171
|
+
"commitMessage": "chore: update submodule refs",
|
|
89
172
|
"interactive": false,
|
|
90
|
-
"verbose": false
|
|
173
|
+
"verbose": false,
|
|
174
|
+
"color": true,
|
|
175
|
+
"progress": true
|
|
91
176
|
}
|
|
92
177
|
```
|
|
93
178
|
|
|
@@ -218,11 +303,616 @@ Type `y` to push or anything else to skip that repo.
|
|
|
218
303
|
|
|
219
304
|
---
|
|
220
305
|
|
|
221
|
-
##
|
|
306
|
+
## Advanced Usage
|
|
307
|
+
|
|
308
|
+
### Real-World Scenarios
|
|
309
|
+
|
|
310
|
+
#### 1. CI/CD Pipeline Integration
|
|
311
|
+
|
|
312
|
+
**GitHub Actions Example:**
|
|
313
|
+
```yaml
|
|
314
|
+
name: Update Submodules
|
|
315
|
+
on:
|
|
316
|
+
schedule:
|
|
317
|
+
- cron: '0 2 * * *' # Daily at 2 AM
|
|
318
|
+
workflow_dispatch:
|
|
319
|
+
|
|
320
|
+
jobs:
|
|
321
|
+
update:
|
|
322
|
+
runs-on: ubuntu-latest
|
|
323
|
+
steps:
|
|
324
|
+
- uses: actions/checkout@v3
|
|
325
|
+
with:
|
|
326
|
+
token: ${{ secrets.PAT }} # Personal Access Token
|
|
327
|
+
fetch-depth: 0
|
|
328
|
+
|
|
329
|
+
- name: Setup Node.js
|
|
330
|
+
uses: actions/setup-node@v3
|
|
331
|
+
with:
|
|
332
|
+
node-version: '18'
|
|
333
|
+
|
|
334
|
+
- name: Install github-update-submodule
|
|
335
|
+
run: npm install -g github-update-submodule
|
|
336
|
+
|
|
337
|
+
- name: Update all submodules
|
|
338
|
+
run: |
|
|
339
|
+
github-update-submodule \
|
|
340
|
+
--parallel \
|
|
341
|
+
--message "ci: automated submodule update" \
|
|
342
|
+
--verbose
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### 2. Monorepo with Shared Libraries
|
|
346
|
+
|
|
347
|
+
**Scenario:** Frontend application with shared component libraries
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# Update only production dependencies
|
|
351
|
+
github-update-submodule \
|
|
352
|
+
--ignore docs \
|
|
353
|
+
--ignore examples \
|
|
354
|
+
--ignore staging-components \
|
|
355
|
+
--message "chore: update production submodule refs"
|
|
356
|
+
|
|
357
|
+
# Interactive review for staging environment
|
|
358
|
+
github-update-submodule \
|
|
359
|
+
--interactive \
|
|
360
|
+
--branch staging \
|
|
361
|
+
--dry-run
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### 3. Documentation Site with Multiple Sources
|
|
365
|
+
|
|
366
|
+
**Scenario:** Docs site embedding content from multiple repositories
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# Update documentation submodules only
|
|
370
|
+
github-update-submodule \
|
|
371
|
+
--ignore frontend \
|
|
372
|
+
--ignore backend \
|
|
373
|
+
--ignore api \
|
|
374
|
+
--message "docs: update documentation sources"
|
|
375
|
+
|
|
376
|
+
# Parallel update for faster builds
|
|
377
|
+
github-update-submodule \
|
|
378
|
+
--parallel \
|
|
379
|
+
--depth 2 \
|
|
380
|
+
--verbose
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
#### 4. Microservices Architecture
|
|
384
|
+
|
|
385
|
+
**Scenario:** Main repo with multiple service submodules
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
# Configuration file for team consistency
|
|
389
|
+
cat > submodule.config.json << EOF
|
|
390
|
+
{
|
|
391
|
+
"defaultBranch": "main",
|
|
392
|
+
"parallel": true,
|
|
393
|
+
"ignore": ["legacy-service", "experimental-feature"],
|
|
394
|
+
"commitMessage": "chore: update service submodule refs",
|
|
395
|
+
"interactive": false,
|
|
396
|
+
"verbose": true,
|
|
397
|
+
"color": true,
|
|
398
|
+
"progress": true
|
|
399
|
+
}
|
|
400
|
+
EOF
|
|
401
|
+
|
|
402
|
+
# Update with team defaults
|
|
403
|
+
github-update-submodule
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Best Practices
|
|
407
|
+
|
|
408
|
+
#### 1. Branch Strategy
|
|
409
|
+
```bash
|
|
410
|
+
# Development environment
|
|
411
|
+
github-update-submodule --branch develop --message "chore: update dev refs"
|
|
412
|
+
|
|
413
|
+
# Production updates with care
|
|
414
|
+
github-update-submodule --interactive --branch main --dry-run
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### 2. Team Workflows
|
|
418
|
+
```bash
|
|
419
|
+
# Generate team config file
|
|
420
|
+
github-update-submodule --make-config
|
|
421
|
+
|
|
422
|
+
# Commit the config for consistency
|
|
423
|
+
git add submodule.config.json
|
|
424
|
+
git commit -m "Add submodule update configuration"
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### 3. Safety First
|
|
428
|
+
```bash
|
|
429
|
+
# Always preview first
|
|
430
|
+
github-update-submodule --dry-run --verbose
|
|
431
|
+
|
|
432
|
+
# Then run the actual update
|
|
433
|
+
github-update-submodule
|
|
434
|
+
|
|
435
|
+
# Or use interactive mode for critical repos
|
|
436
|
+
github-update-submodule --interactive
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### 4. Performance Optimization
|
|
440
|
+
```bash
|
|
441
|
+
# Large repositories - use parallel and depth limiting
|
|
442
|
+
github-update-submodule --parallel --depth 3 --verbose
|
|
443
|
+
|
|
444
|
+
# Network-constrained environments
|
|
445
|
+
github-update-submodule --ignore heavy-assets --no-progress
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Integration Examples
|
|
449
|
+
|
|
450
|
+
#### Git Hooks
|
|
451
|
+
```bash
|
|
452
|
+
# .git/hooks/pre-push
|
|
453
|
+
#!/bin/bash
|
|
454
|
+
echo "Checking submodule status..."
|
|
455
|
+
github-update-submodule --dry-run --no-color
|
|
456
|
+
|
|
457
|
+
if [ $? -ne 0 ]; then
|
|
458
|
+
echo "⚠️ Submodules need updating. Run 'github-update-submodule' first."
|
|
459
|
+
exit 1
|
|
460
|
+
fi
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### Makefile Integration
|
|
464
|
+
```makefile
|
|
465
|
+
# Makefile
|
|
466
|
+
.PHONY: update-submodules
|
|
467
|
+
update-submodules:
|
|
468
|
+
@echo "Updating all submodules..."
|
|
469
|
+
github-update-submodule --parallel --verbose
|
|
470
|
+
|
|
471
|
+
.PHONY: check-submodules
|
|
472
|
+
check-submodules:
|
|
473
|
+
@echo "Checking submodule status..."
|
|
474
|
+
github-update-submodule --dry-run
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### Docker Integration
|
|
478
|
+
```dockerfile
|
|
479
|
+
# Dockerfile
|
|
480
|
+
FROM node:18-alpine
|
|
481
|
+
|
|
482
|
+
RUN npm install -g github-update-submodule
|
|
483
|
+
|
|
484
|
+
WORKDIR /app
|
|
485
|
+
COPY . .
|
|
486
|
+
|
|
487
|
+
# Update submodules during build
|
|
488
|
+
RUN github-update-submodule --no-push --verbose
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Prerequisites
|
|
494
|
+
|
|
495
|
+
### System Requirements
|
|
496
|
+
|
|
497
|
+
- **Node.js** >= 14.0.0
|
|
498
|
+
- **Git** installed and available in your PATH
|
|
499
|
+
- **Remote authentication** set up (SSH keys or credential manager) so pushes don't require password prompts
|
|
500
|
+
|
|
501
|
+
### Git Configuration
|
|
502
|
+
|
|
503
|
+
Ensure your Git is properly configured:
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Set your identity (required for commits)
|
|
507
|
+
git config --global user.name "Your Name"
|
|
508
|
+
git config --global user.email "your.email@example.com"
|
|
509
|
+
|
|
510
|
+
# Verify remote access
|
|
511
|
+
git ls-remote origin
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Authentication Setup
|
|
515
|
+
|
|
516
|
+
#### SSH Keys (Recommended)
|
|
517
|
+
```bash
|
|
518
|
+
# Generate SSH key if you don't have one
|
|
519
|
+
ssh-keygen -t ed25519 -C "your.email@example.com"
|
|
520
|
+
|
|
521
|
+
# Add to GitHub
|
|
522
|
+
cat ~/.ssh/id_ed25519.pub
|
|
523
|
+
# Copy the output and add it to GitHub > Settings > SSH and GPG keys
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### Personal Access Token
|
|
527
|
+
```bash
|
|
528
|
+
# Configure Git to use token
|
|
529
|
+
git config --global credential.helper store
|
|
530
|
+
# Git will prompt for username and token on first push
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Repository Requirements
|
|
534
|
+
|
|
535
|
+
The target repository must:
|
|
536
|
+
- Be a valid Git repository
|
|
537
|
+
- Have at least one submodule configured in `.gitmodules`
|
|
538
|
+
- Have `origin` remote configured with push access
|
|
539
|
+
- Have submodules accessible from the current network
|
|
540
|
+
|
|
541
|
+
### Optional Enhancements
|
|
542
|
+
|
|
543
|
+
For the best experience, consider:
|
|
544
|
+
- **Git LFS** if your submodules contain large files
|
|
545
|
+
- **Parallel processing** enabled for large submodule trees (`--parallel`)
|
|
546
|
+
- **Configuration file** for consistent settings across teams (`--make-config`)
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## Troubleshooting
|
|
551
|
+
|
|
552
|
+
### Common Issues and Solutions
|
|
553
|
+
|
|
554
|
+
#### Authentication Errors
|
|
555
|
+
|
|
556
|
+
**Problem:** `Permission denied (publickey)` or authentication prompts
|
|
557
|
+
```bash
|
|
558
|
+
✘ Push failed in 'my-repo': Permission denied (publickey)
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Solutions:**
|
|
562
|
+
1. **SSH Key Issues:**
|
|
563
|
+
```bash
|
|
564
|
+
# Test SSH connection
|
|
565
|
+
ssh -T git@github.com
|
|
566
|
+
|
|
567
|
+
# Add SSH key to ssh-agent
|
|
568
|
+
ssh-add ~/.ssh/id_ed25519
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
2. **Token Issues:**
|
|
572
|
+
```bash
|
|
573
|
+
# Clear cached credentials
|
|
574
|
+
git config --global --unset credential.helper
|
|
575
|
+
|
|
576
|
+
# Or update stored credentials
|
|
577
|
+
git config --global credential.helper store
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Submodule Not Found
|
|
581
|
+
|
|
582
|
+
**Problem:** `fatal: not a git repository` in submodule directory
|
|
583
|
+
```bash
|
|
584
|
+
✘ Init failed: fatal: not a git repository
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Solutions:**
|
|
588
|
+
1. **Initialize manually:**
|
|
589
|
+
```bash
|
|
590
|
+
git submodule update --init --recursive
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
2. **Check .gitmodules configuration:**
|
|
594
|
+
```bash
|
|
595
|
+
cat .gitmodules
|
|
596
|
+
# Verify URLs are accessible
|
|
597
|
+
git ls-remote <submodule-url>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
#### Branch Resolution Issues
|
|
601
|
+
|
|
602
|
+
**Problem:** Cannot determine correct branch for submodule
|
|
603
|
+
```bash
|
|
604
|
+
⚠ Cannot resolve origin/main — skipping
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Solutions:**
|
|
608
|
+
1. **Specify branch in .gitmodules:**
|
|
609
|
+
```ini
|
|
610
|
+
[submodule "my-submodule"]
|
|
611
|
+
path = my-submodule
|
|
612
|
+
url = git@github.com:user/my-submodule.git
|
|
613
|
+
branch = main # Add this line
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
2. **Use CLI flag:**
|
|
617
|
+
```bash
|
|
618
|
+
github-update-submodule --branch develop
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
#### Push Conflicts
|
|
622
|
+
|
|
623
|
+
**Problem:** Push fails due to remote changes
|
|
624
|
+
```bash
|
|
625
|
+
✘ Push failed in 'my-repo': ! [rejected] (non-fast-forward)
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**Solutions:**
|
|
629
|
+
1. **Pull latest changes first:**
|
|
630
|
+
```bash
|
|
631
|
+
git pull origin main
|
|
632
|
+
github-update-submodule
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
2. **Use interactive mode to review:**
|
|
636
|
+
```bash
|
|
637
|
+
github-update-submodule --interactive
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
#### Network Issues
|
|
641
|
+
|
|
642
|
+
**Problem:** Timeouts or connection failures
|
|
643
|
+
```bash
|
|
644
|
+
✘ Fetch warning: unable to access '...': Connection timed out
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
**Solutions:**
|
|
648
|
+
1. **Increase Git timeout:**
|
|
649
|
+
```bash
|
|
650
|
+
git config --global http.lowSpeedLimit 0
|
|
651
|
+
git config --global http.lowSpeedTime 999999
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
2. **Use sequential mode:**
|
|
655
|
+
```bash
|
|
656
|
+
github-update-submodule # without --parallel
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
#### Large Repository Performance
|
|
660
|
+
|
|
661
|
+
**Problem:** Very slow execution on large submodule trees
|
|
662
|
+
|
|
663
|
+
**Solutions:**
|
|
664
|
+
1. **Enable parallel fetching:**
|
|
665
|
+
```bash
|
|
666
|
+
github-update-submodule --parallel
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
2. **Limit recursion depth:**
|
|
670
|
+
```bash
|
|
671
|
+
github-update-submodule --depth 2
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
3. **Ignore specific submodules:**
|
|
675
|
+
```bash
|
|
676
|
+
github-update-submodule --ignore heavy-lib --ignore docs
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### Debug Mode
|
|
680
|
+
|
|
681
|
+
For troubleshooting, enable verbose output:
|
|
682
|
+
```bash
|
|
683
|
+
github-update-submodule --verbose --dry-run
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
This will show:
|
|
687
|
+
- Detailed Git command output
|
|
688
|
+
- Branch resolution process
|
|
689
|
+
- Remote URL detection
|
|
690
|
+
- Authentication attempts
|
|
691
|
+
|
|
692
|
+
### Getting Help
|
|
693
|
+
|
|
694
|
+
If you encounter issues not covered here:
|
|
695
|
+
|
|
696
|
+
1. **Check the GitHub Issues:** [SENODROOM/github-update-submodule/issues](https://github.com/SENODROOM/github-update-submodule/issues)
|
|
697
|
+
2. **Create a new issue** with:
|
|
698
|
+
- Full command output with `--verbose`
|
|
699
|
+
- Your `.gitmodules` file content
|
|
700
|
+
- Operating system and versions
|
|
701
|
+
- Steps to reproduce
|
|
702
|
+
|
|
703
|
+
3. **Community support:** Tag your issue with `question` or `bug` for appropriate attention.
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## Contributing
|
|
708
|
+
|
|
709
|
+
We welcome contributions from the community! Here's how you can help:
|
|
710
|
+
|
|
711
|
+
### Development Setup
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
# Clone the repository
|
|
715
|
+
git clone https://github.com/SENODROOM/github-update-submodule.git
|
|
716
|
+
cd github-update-submodule
|
|
717
|
+
|
|
718
|
+
# Install dependencies
|
|
719
|
+
npm install
|
|
720
|
+
|
|
721
|
+
# Link for local testing
|
|
722
|
+
npm link
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Running Tests
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
# Run the test suite
|
|
729
|
+
npm test
|
|
730
|
+
|
|
731
|
+
# Run with coverage
|
|
732
|
+
npm run test:coverage
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Making Changes
|
|
736
|
+
|
|
737
|
+
1. **Fork** the repository
|
|
738
|
+
2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
|
|
739
|
+
3. **Make** your changes
|
|
740
|
+
4. **Test** thoroughly:
|
|
741
|
+
```bash
|
|
742
|
+
# Test with various scenarios
|
|
743
|
+
github-update-submodule --dry-run
|
|
744
|
+
github-update-submodule --verbose
|
|
745
|
+
```
|
|
746
|
+
5. **Commit** your changes with clear messages
|
|
747
|
+
6. **Push** to your fork
|
|
748
|
+
7. **Create** a Pull Request
|
|
749
|
+
|
|
750
|
+
### Code Style
|
|
751
|
+
|
|
752
|
+
- Use **ES6+** features appropriately
|
|
753
|
+
- Follow the existing code style and patterns
|
|
754
|
+
- Add **JSDoc** comments for new functions
|
|
755
|
+
- Ensure **error handling** is comprehensive
|
|
756
|
+
- Test with **different Git versions** and platforms
|
|
757
|
+
|
|
758
|
+
### Bug Reports
|
|
759
|
+
|
|
760
|
+
When reporting bugs, please include:
|
|
761
|
+
- **Node.js** and **Git** versions
|
|
762
|
+
- **Operating system** details
|
|
763
|
+
- **Full error output** with `--verbose` flag
|
|
764
|
+
- **Minimal reproduction** steps
|
|
765
|
+
- **Expected vs actual** behavior
|
|
766
|
+
|
|
767
|
+
### Feature Requests
|
|
768
|
+
|
|
769
|
+
For new features:
|
|
770
|
+
1. **Check existing issues** first
|
|
771
|
+
2. **Describe the use case** clearly
|
|
772
|
+
3. **Consider the impact** on existing users
|
|
773
|
+
4. **Suggest an API** if applicable
|
|
774
|
+
|
|
775
|
+
### Release Process
|
|
776
|
+
|
|
777
|
+
Releases follow semantic versioning:
|
|
778
|
+
- **Patch** (x.x.1): Bug fixes
|
|
779
|
+
- **Minor** (x.1.x): New features
|
|
780
|
+
- **Major** (1.x.x): Breaking changes
|
|
781
|
+
|
|
782
|
+
Maintainers will handle version bumps and npm publishing.
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## Performance Considerations
|
|
787
|
+
|
|
788
|
+
### Benchmarks
|
|
789
|
+
|
|
790
|
+
Performance varies based on repository structure and network conditions:
|
|
791
|
+
|
|
792
|
+
| Repository Size | Submodules | Sequential | Parallel | Improvement |
|
|
793
|
+
|---|---|---|---|---|
|
|
794
|
+
| Small | 5-10 | 2-5s | 1-3s | 40-60% |
|
|
795
|
+
| Medium | 20-50 | 15-30s | 5-12s | 60-70% |
|
|
796
|
+
| Large | 100+ | 2-5min | 30-60s | 70-80% |
|
|
797
|
+
|
|
798
|
+
*Benchmarks measured on typical corporate network with GitHub Enterprise.*
|
|
799
|
+
|
|
800
|
+
### Optimization Strategies
|
|
801
|
+
|
|
802
|
+
#### 1. Enable Parallel Fetching
|
|
803
|
+
```bash
|
|
804
|
+
# Best for large repositories
|
|
805
|
+
github-update-submodule --parallel
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
#### 2. Limit Recursion Depth
|
|
809
|
+
```bash
|
|
810
|
+
# Only update top-level submodules
|
|
811
|
+
github-update-submodule --depth 1
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
#### 3. Selective Updates
|
|
815
|
+
```bash
|
|
816
|
+
# Skip heavy documentation submodules
|
|
817
|
+
github-update-submodule --ignore docs --ignore examples
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
#### 4. Network Optimization
|
|
821
|
+
```bash
|
|
822
|
+
# Configure Git for better performance
|
|
823
|
+
git config --global http.lowSpeedLimit 1000
|
|
824
|
+
git config --global http.lowSpeedTime 30
|
|
825
|
+
git config --global http.maxRequestBuffer 100M
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### Memory Usage
|
|
829
|
+
|
|
830
|
+
- **Small repos** (< 50 submodules): ~10-20MB RAM
|
|
831
|
+
- **Medium repos** (50-200 submodules): ~20-50MB RAM
|
|
832
|
+
- **Large repos** (200+ submodules): ~50-100MB RAM
|
|
833
|
+
|
|
834
|
+
Memory scales linearly with submodule count due to parallel processing.
|
|
835
|
+
|
|
836
|
+
### Comparison with Alternatives
|
|
837
|
+
|
|
838
|
+
| Feature | github-update-submodule | git submodule update | Manual scripts |
|
|
839
|
+
|---|---|---|---|
|
|
840
|
+
| **Recursive updates** | ✅ Automatic | ❌ Manual per level | ❌ Custom implementation |
|
|
841
|
+
| **Parallel fetching** | ✅ Built-in | ❌ Sequential | ⚠️ Complex to implement |
|
|
842
|
+
| **GitHub integration** | ✅ Compare links | ❌ None | ⚠️ Manual |
|
|
843
|
+
| **Interactive mode** | ✅ Built-in | ❌ None | ⚠️ Custom |
|
|
844
|
+
| **Progress tracking** | ✅ Rich output | ⚠️ Basic | ⚠️ Custom |
|
|
845
|
+
| **Error handling** | ✅ Comprehensive | ⚠️ Limited | ⚠️ Variable |
|
|
846
|
+
| **Configuration** | ✅ Files + CLI | ⚠️ CLI only | ⚠️ Custom |
|
|
847
|
+
|
|
848
|
+
### Performance Tips
|
|
849
|
+
|
|
850
|
+
1. **Use SSH** over HTTPS when possible (faster authentication)
|
|
851
|
+
2. **Enable git gc** in submodules regularly
|
|
852
|
+
3. **Consider shallow clones** for large submodules
|
|
853
|
+
4. **Use .gitignore** to exclude unnecessary files
|
|
854
|
+
5. **Schedule updates** during off-peak hours for CI/CD
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
## Security
|
|
859
|
+
|
|
860
|
+
### Security Considerations
|
|
861
|
+
|
|
862
|
+
`github-update-submodule` is designed with security in mind, but there are important considerations:
|
|
863
|
+
|
|
864
|
+
#### Trust Boundaries
|
|
865
|
+
|
|
866
|
+
- **Submodule URLs** are fetched from `.gitmodules` - ensure this file is trusted
|
|
867
|
+
- **Remote repositories** are accessed with your Git credentials
|
|
868
|
+
- **Branch resolution** follows Git's remote HEAD detection
|
|
869
|
+
|
|
870
|
+
#### Recommended Practices
|
|
871
|
+
|
|
872
|
+
1. **Review `.gitmodules`** before running:
|
|
873
|
+
```bash
|
|
874
|
+
cat .gitmodules
|
|
875
|
+
# Verify all URLs are legitimate
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
2. **Use dry-run** for untrusted repositories:
|
|
879
|
+
```bash
|
|
880
|
+
github-update-submodule --dry-run --verbose
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
3. **Limit scope** with ignore patterns:
|
|
884
|
+
```bash
|
|
885
|
+
github-update-submodule --ignore suspicious-submodule
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
4. **Audit submodules** regularly:
|
|
889
|
+
```bash
|
|
890
|
+
# List all submodule URLs
|
|
891
|
+
git submodule status
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
#### Credential Security
|
|
895
|
+
|
|
896
|
+
- **SSH keys** are preferred over HTTPS tokens
|
|
897
|
+
- **Personal Access Tokens** should have minimal required scopes
|
|
898
|
+
- **Credential helpers** should be configured securely
|
|
899
|
+
|
|
900
|
+
#### Network Security
|
|
901
|
+
|
|
902
|
+
- **HTTPS URLs** are automatically detected for GitHub compare links
|
|
903
|
+
- **SSH URLs** are used for Git operations when configured
|
|
904
|
+
- **Proxy settings** are respected from Git configuration
|
|
905
|
+
|
|
906
|
+
### Reporting Security Issues
|
|
907
|
+
|
|
908
|
+
If you discover a security vulnerability:
|
|
909
|
+
|
|
910
|
+
1. **Do not** open a public issue
|
|
911
|
+
2. **Email** security@senodroom.com with details
|
|
912
|
+
3. **Include** steps to reproduce and potential impact
|
|
913
|
+
4. **Wait** for confirmation before disclosing
|
|
222
914
|
|
|
223
|
-
|
|
224
|
-
- **Git** installed and in your PATH
|
|
225
|
-
- Remote authentication set up (SSH keys or credential manager) so pushes don't require a password prompt
|
|
915
|
+
We'll respond within 48 hours and provide a timeline for fixes.
|
|
226
916
|
|
|
227
917
|
---
|
|
228
918
|
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
* --verbose Show full git output
|
|
25
25
|
* --no-color Disable colored output
|
|
26
26
|
* --no-progress Disable the progress bar
|
|
27
|
+
* --make-config Generate a submodule.config.json in the current repo and exit
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
const { spawnSync, spawn } = require("child_process");
|
|
30
|
-
const path
|
|
31
|
-
const fs
|
|
31
|
+
const path = require("path");
|
|
32
|
+
const fs = require("fs");
|
|
32
33
|
const readline = require("readline");
|
|
33
34
|
|
|
34
35
|
// ─── Config file loader ───────────────────────────────────────────────────────
|
|
@@ -60,18 +61,18 @@ const cliArgs = process.argv.slice(2);
|
|
|
60
61
|
|
|
61
62
|
// Defaults (lowest priority)
|
|
62
63
|
const options = {
|
|
63
|
-
repoPath:
|
|
64
|
-
push:
|
|
65
|
-
interactive:
|
|
66
|
-
ignore:
|
|
67
|
-
parallel:
|
|
64
|
+
repoPath: process.cwd(),
|
|
65
|
+
push: true,
|
|
66
|
+
interactive: false,
|
|
67
|
+
ignore: [], // array of submodule names to skip
|
|
68
|
+
parallel: false,
|
|
68
69
|
commitMessage: "chore: update submodule refs",
|
|
69
|
-
dryRun:
|
|
70
|
+
dryRun: false,
|
|
70
71
|
defaultBranch: "main",
|
|
71
|
-
maxDepth:
|
|
72
|
-
verbose:
|
|
73
|
-
color:
|
|
74
|
-
progress:
|
|
72
|
+
maxDepth: Infinity,
|
|
73
|
+
verbose: false,
|
|
74
|
+
color: true,
|
|
75
|
+
progress: true,
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
// Collect positional repo path first so config is loaded from correct dir
|
|
@@ -81,68 +82,71 @@ for (let i = 0; i < cliArgs.length; i++) {
|
|
|
81
82
|
|
|
82
83
|
// Merge config file (overrides defaults, CLI will override config)
|
|
83
84
|
const cfg = loadConfig(options.repoPath);
|
|
84
|
-
if (cfg.push
|
|
85
|
-
if (cfg.interactive
|
|
86
|
-
if (cfg.ignore
|
|
87
|
-
if (cfg.parallel
|
|
85
|
+
if (cfg.push !== undefined) options.push = cfg.push;
|
|
86
|
+
if (cfg.interactive !== undefined) options.interactive = cfg.interactive;
|
|
87
|
+
if (cfg.ignore !== undefined) options.ignore = [].concat(cfg.ignore);
|
|
88
|
+
if (cfg.parallel !== undefined) options.parallel = cfg.parallel;
|
|
88
89
|
if (cfg.commitMessage !== undefined) options.commitMessage = cfg.commitMessage;
|
|
89
90
|
if (cfg.defaultBranch !== undefined) options.defaultBranch = cfg.defaultBranch;
|
|
90
|
-
if (cfg.maxDepth
|
|
91
|
-
if (cfg.verbose
|
|
92
|
-
if (cfg.color
|
|
93
|
-
if (cfg.progress
|
|
91
|
+
if (cfg.maxDepth !== undefined) options.maxDepth = cfg.maxDepth;
|
|
92
|
+
if (cfg.verbose !== undefined) options.verbose = cfg.verbose;
|
|
93
|
+
if (cfg.color !== undefined) options.color = cfg.color;
|
|
94
|
+
if (cfg.progress !== undefined) options.progress = cfg.progress;
|
|
94
95
|
|
|
95
96
|
// CLI flags (highest priority)
|
|
96
97
|
for (let i = 0; i < cliArgs.length; i++) {
|
|
97
98
|
const a = cliArgs[i];
|
|
98
|
-
if
|
|
99
|
-
else if (a === "--interactive")
|
|
100
|
-
else if (a === "--parallel")
|
|
101
|
-
else if (a === "--dry-run")
|
|
102
|
-
else if (a === "--verbose")
|
|
103
|
-
else if (a === "--no-color")
|
|
104
|
-
else if (a === "--no-progress")
|
|
105
|
-
else if (a === "--
|
|
106
|
-
else if (a === "--
|
|
107
|
-
else if (a === "--
|
|
108
|
-
else if (a === "--
|
|
99
|
+
if (a === "--no-push") options.push = false;
|
|
100
|
+
else if (a === "--interactive") options.interactive = true;
|
|
101
|
+
else if (a === "--parallel") options.parallel = true;
|
|
102
|
+
else if (a === "--dry-run") options.dryRun = true;
|
|
103
|
+
else if (a === "--verbose") options.verbose = true;
|
|
104
|
+
else if (a === "--no-color") options.color = false;
|
|
105
|
+
else if (a === "--no-progress") options.progress = false;
|
|
106
|
+
else if (a === "--make-config") options.makeConfig = true;
|
|
107
|
+
else if (a === "--branch") options.defaultBranch = cliArgs[++i];
|
|
108
|
+
else if (a === "--message") options.commitMessage = cliArgs[++i];
|
|
109
|
+
else if (a === "--depth") options.maxDepth = parseInt(cliArgs[++i], 10);
|
|
110
|
+
else if (a === "--ignore") options.ignore.push(cliArgs[++i]);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
// ─── Colour helpers ──────────────────────────────────────────────────────────
|
|
112
114
|
|
|
113
115
|
const C = options.color
|
|
114
|
-
? {
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
? {
|
|
117
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", green: "\x1b[32m",
|
|
118
|
+
yellow: "\x1b[33m", cyan: "\x1b[36m", red: "\x1b[31m", magenta: "\x1b[35m",
|
|
119
|
+
blue: "\x1b[34m", white: "\x1b[37m"
|
|
120
|
+
}
|
|
117
121
|
: Object.fromEntries(
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
["reset", "bold", "dim", "green", "yellow", "cyan", "red", "magenta", "blue", "white"].map(k => [k, ""])
|
|
123
|
+
);
|
|
120
124
|
|
|
121
125
|
// ─── Logging ─────────────────────────────────────────────────────────────────
|
|
122
126
|
|
|
123
|
-
const indent
|
|
124
|
-
const log
|
|
125
|
-
const info
|
|
126
|
-
const success = (d, m) => log(d, "✔", C.green,
|
|
127
|
-
const warn
|
|
128
|
-
const error
|
|
129
|
-
const header
|
|
130
|
-
const pushLog = (d, m) => log(d, "↑", C.bold + C.green,
|
|
131
|
-
const linkLog = (d, m) => log(d, "⎘", C.bold + C.blue,
|
|
127
|
+
const indent = (d) => " ".repeat(d);
|
|
128
|
+
const log = (d, sym, col, msg) => console.log(`${indent(d)}${col}${sym} ${msg}${C.reset}`);
|
|
129
|
+
const info = (d, m) => log(d, "›", C.cyan, m);
|
|
130
|
+
const success = (d, m) => log(d, "✔", C.green, m);
|
|
131
|
+
const warn = (d, m) => log(d, "⚠", C.yellow, m);
|
|
132
|
+
const error = (d, m) => log(d, "✘", C.red, m);
|
|
133
|
+
const header = (d, m) => log(d, "▸", C.bold + C.magenta, m);
|
|
134
|
+
const pushLog = (d, m) => log(d, "↑", C.bold + C.green, m);
|
|
135
|
+
const linkLog = (d, m) => log(d, "⎘", C.bold + C.blue, m);
|
|
132
136
|
const verbose = (d, m) => { if (options.verbose) log(d, " ", C.dim, m); };
|
|
133
137
|
|
|
134
138
|
// ─── Progress bar ─────────────────────────────────────────────────────────────
|
|
135
139
|
|
|
136
140
|
const progress = {
|
|
137
|
-
total:
|
|
141
|
+
total: 0,
|
|
138
142
|
current: 0,
|
|
139
|
-
active:
|
|
143
|
+
active: false,
|
|
140
144
|
|
|
141
145
|
init(total) {
|
|
142
146
|
if (!options.progress || !process.stdout.isTTY) return;
|
|
143
|
-
this.total
|
|
147
|
+
this.total = total;
|
|
144
148
|
this.current = 0;
|
|
145
|
-
this.active
|
|
149
|
+
this.active = true;
|
|
146
150
|
this._render();
|
|
147
151
|
},
|
|
148
152
|
|
|
@@ -160,13 +164,13 @@ const progress = {
|
|
|
160
164
|
},
|
|
161
165
|
|
|
162
166
|
_render(label = "") {
|
|
163
|
-
const W
|
|
164
|
-
const filled
|
|
165
|
-
const empty
|
|
166
|
-
const bar
|
|
167
|
-
const pct
|
|
167
|
+
const W = 28;
|
|
168
|
+
const filled = Math.round((this.current / this.total) * W);
|
|
169
|
+
const empty = W - filled;
|
|
170
|
+
const bar = C.green + "█".repeat(filled) + C.dim + "░".repeat(empty) + C.reset;
|
|
171
|
+
const pct = String(Math.round((this.current / this.total) * 100)).padStart(3);
|
|
168
172
|
const counter = `${this.current}/${this.total}`;
|
|
169
|
-
const lbl
|
|
173
|
+
const lbl = label ? ` ${C.dim}${label.slice(0, 24)}${C.reset}` : "";
|
|
170
174
|
process.stdout.write(`\r${C.bold}[${bar}${C.bold}] ${pct}% (${counter})${lbl}\x1b[K`);
|
|
171
175
|
},
|
|
172
176
|
};
|
|
@@ -207,7 +211,7 @@ function git(cwd, ...gitArgs) {
|
|
|
207
211
|
return {
|
|
208
212
|
stdout: (r.stdout || "").trim(),
|
|
209
213
|
stderr: (r.stderr || "").trim(),
|
|
210
|
-
ok:
|
|
214
|
+
ok: r.status === 0,
|
|
211
215
|
};
|
|
212
216
|
}
|
|
213
217
|
|
|
@@ -254,8 +258,8 @@ function parseGitmodules(repoDir) {
|
|
|
254
258
|
|
|
255
259
|
const kv = line.match(/^(\w+)\s*=\s*(.+)$/);
|
|
256
260
|
if (kv) {
|
|
257
|
-
if (kv[1] === "path")
|
|
258
|
-
if (kv[1] === "url")
|
|
261
|
+
if (kv[1] === "path") cur.path = kv[2];
|
|
262
|
+
if (kv[1] === "url") cur.url = kv[2];
|
|
259
263
|
if (kv[1] === "branch") cur.branch = kv[2];
|
|
260
264
|
}
|
|
261
265
|
}
|
|
@@ -395,7 +399,7 @@ function pullSubmodules(repoDir, depth = 0) {
|
|
|
395
399
|
}
|
|
396
400
|
|
|
397
401
|
// ── Resolve branch + remote tip ───────────────────────────────────────
|
|
398
|
-
const branch
|
|
402
|
+
const branch = resolveBranch(subDir, sub.branch);
|
|
399
403
|
const remoteRef = `origin/${branch}`;
|
|
400
404
|
const remoteTip = git(subDir, "rev-parse", remoteRef).stdout;
|
|
401
405
|
|
|
@@ -408,7 +412,7 @@ function pullSubmodules(repoDir, depth = 0) {
|
|
|
408
412
|
}
|
|
409
413
|
|
|
410
414
|
const beforeHash = git(subDir, "rev-parse", "HEAD").stdout;
|
|
411
|
-
const remoteUrl
|
|
415
|
+
const remoteUrl = getRemoteUrl(subDir);
|
|
412
416
|
|
|
413
417
|
// ── Dry-run ───────────────────────────────────────────────────────────
|
|
414
418
|
if (options.dryRun) {
|
|
@@ -546,9 +550,71 @@ async function commitAndPush(repoDir, label, depth = 0) {
|
|
|
546
550
|
stats.pushed++;
|
|
547
551
|
}
|
|
548
552
|
|
|
553
|
+
// ─── Config generator ────────────────────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
async function runMakeConfig() {
|
|
556
|
+
const dest = path.join(options.repoPath, "submodule.config.json");
|
|
557
|
+
const exists = fs.existsSync(dest);
|
|
558
|
+
|
|
559
|
+
const template = {
|
|
560
|
+
defaultBranch: "main",
|
|
561
|
+
parallel: false,
|
|
562
|
+
ignore: [],
|
|
563
|
+
commitMessage: "chore: update submodule refs",
|
|
564
|
+
interactive: false,
|
|
565
|
+
verbose: false,
|
|
566
|
+
color: true,
|
|
567
|
+
progress: true
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
console.log();
|
|
571
|
+
console.log(`${C.bold}${C.blue}╔══════════════════════════════════════════╗${C.reset}`);
|
|
572
|
+
console.log(`${C.bold}${C.blue}║ github-update-submodule v2.0.0 ║${C.reset}`);
|
|
573
|
+
console.log(`${C.bold}${C.blue}╚══════════════════════════════════════════╝${C.reset}`);
|
|
574
|
+
console.log();
|
|
575
|
+
|
|
576
|
+
if (exists) {
|
|
577
|
+
console.log(`${C.bold}${C.yellow}⚠ Config file already exists:${C.reset} ${dest}`);
|
|
578
|
+
console.log();
|
|
579
|
+
const answer = await askUser(` ${C.bold}${C.yellow}Overwrite it with defaults? [y/N] ${C.reset}`);
|
|
580
|
+
console.log();
|
|
581
|
+
if (answer !== "y" && answer !== "yes") {
|
|
582
|
+
console.log(`${C.dim} Cancelled — existing config file left unchanged.${C.reset}`);
|
|
583
|
+
console.log();
|
|
584
|
+
process.exit(0);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
fs.writeFileSync(dest, JSON.stringify(template, null, 2) + "\n", "utf8");
|
|
589
|
+
|
|
590
|
+
const action = exists ? "overwritten" : "created";
|
|
591
|
+
console.log(`${C.green}${C.bold}✔ Config file ${action}:${C.reset} ${dest}`);
|
|
592
|
+
console.log();
|
|
593
|
+
console.log(` ${C.dim}Edit the values to set your preferred defaults.`);
|
|
594
|
+
console.log(` CLI flags always override the config file.${C.reset}`);
|
|
595
|
+
console.log();
|
|
596
|
+
console.log(` ${C.bold}Available keys:${C.reset}`);
|
|
597
|
+
console.log(` ${C.cyan}defaultBranch${C.reset} branch to use when not set in .gitmodules ${C.dim}(default: "main")${C.reset}`);
|
|
598
|
+
console.log(` ${C.cyan}parallel${C.reset} fetch all submodules concurrently ${C.dim}(default: false)${C.reset}`);
|
|
599
|
+
console.log(` ${C.cyan}ignore${C.reset} array of submodule names to skip ${C.dim}(default: [])${C.reset}`);
|
|
600
|
+
console.log(` ${C.cyan}commitMessage${C.reset} commit message for pointer updates ${C.dim}(default: "chore: update submodule refs")${C.reset}`);
|
|
601
|
+
console.log(` ${C.cyan}interactive${C.reset} prompt before pushing each repo ${C.dim}(default: false)${C.reset}`);
|
|
602
|
+
console.log(` ${C.cyan}verbose${C.reset} show full git output ${C.dim}(default: false)${C.reset}`);
|
|
603
|
+
console.log(` ${C.cyan}color${C.reset} colored terminal output ${C.dim}(default: true)${C.reset}`);
|
|
604
|
+
console.log(` ${C.cyan}progress${C.reset} show progress bar ${C.dim}(default: true)${C.reset}`);
|
|
605
|
+
console.log();
|
|
606
|
+
process.exit(0);
|
|
607
|
+
}
|
|
608
|
+
|
|
549
609
|
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
550
610
|
|
|
551
611
|
async function main() {
|
|
612
|
+
// --make-config: generate a config file and exit immediately
|
|
613
|
+
if (options.makeConfig) {
|
|
614
|
+
await runMakeConfig();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
552
618
|
console.log();
|
|
553
619
|
console.log(`${C.bold}${C.blue}╔══════════════════════════════════════════╗${C.reset}`);
|
|
554
620
|
console.log(`${C.bold}${C.blue}║ github-update-submodule v2.0.0 ║${C.reset}`);
|
|
@@ -563,9 +629,9 @@ async function main() {
|
|
|
563
629
|
// Print active config
|
|
564
630
|
info(0, `Repository : ${C.bold}${options.repoPath}${C.reset}`);
|
|
565
631
|
info(0, `Default branch : ${C.bold}${options.defaultBranch}${C.reset}`);
|
|
566
|
-
info(0, `Push mode : ${options.push
|
|
567
|
-
info(0, `Interactive : ${options.interactive ? C.bold+C.yellow+"ON"
|
|
568
|
-
info(0, `Parallel fetch : ${options.parallel
|
|
632
|
+
info(0, `Push mode : ${options.push ? C.bold + C.green + "ON" : C.dim + "OFF"}${C.reset}`);
|
|
633
|
+
info(0, `Interactive : ${options.interactive ? C.bold + C.yellow + "ON" : C.dim + "OFF"}${C.reset}`);
|
|
634
|
+
info(0, `Parallel fetch : ${options.parallel ? C.bold + C.cyan + "ON" : C.dim + "OFF"}${C.reset}`);
|
|
569
635
|
if (options.ignore.length)
|
|
570
636
|
info(0, `Ignoring : ${C.bold}${C.yellow}${options.ignore.join(", ")}${C.reset}`);
|
|
571
637
|
if (options.dryRun)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-update-submodule",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Recursively pull all Git submodules to their latest remote commit and push updated refs up every parent repo — so GitHub always points to the latest.",
|
|
5
5
|
"main": "bin/github-update-submodule.js",
|
|
6
6
|
"bin": {
|