squash-only 1.0.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 +21 -0
- package/README.md +210 -0
- package/bin/squash-only.js +62 -0
- package/package.json +42 -0
- package/scripts/squash-only.sh +260 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Ryszkiewicz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# squash-only
|
|
2
|
+
|
|
3
|
+
A bash script to automatically configure all your GitHub repositories to use squash-only merge strategy.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
This script updates all repositories you own on GitHub to:
|
|
8
|
+
- ✅ Enable squash merges
|
|
9
|
+
- ❌ Disable merge commits
|
|
10
|
+
- ❌ Disable rebase merges
|
|
11
|
+
|
|
12
|
+
It automatically skips repositories you don't own and provides a summary of the results.
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- `bash` (version 4+)
|
|
17
|
+
- `curl`
|
|
18
|
+
- `jq`
|
|
19
|
+
- GitHub authentication (see below)
|
|
20
|
+
|
|
21
|
+
**Note:** If using the Node.js binary wrapper, you'll also need Node.js installed.
|
|
22
|
+
|
|
23
|
+
## Authentication
|
|
24
|
+
|
|
25
|
+
The script supports three methods for GitHub authentication (in order of preference):
|
|
26
|
+
|
|
27
|
+
### 1. Environment Variable
|
|
28
|
+
Set `GITHUB_TOKEN` in your environment:
|
|
29
|
+
```bash
|
|
30
|
+
export GITHUB_TOKEN=your_token_here
|
|
31
|
+
./scripts/squash-only.sh
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. GitHub CLI (`gh`)
|
|
35
|
+
If you have the GitHub CLI installed and authenticated:
|
|
36
|
+
```bash
|
|
37
|
+
gh auth login
|
|
38
|
+
./scripts/squash-only.sh
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. Personal Access Token (PAT)
|
|
42
|
+
If neither of the above are available, the script will display instructions for creating a PAT with the required permissions.
|
|
43
|
+
|
|
44
|
+
**Required PAT permissions:**
|
|
45
|
+
- `repo` (Full control of private repositories)
|
|
46
|
+
|
|
47
|
+
## Installation & Usage
|
|
48
|
+
|
|
49
|
+
You can run this tool in several ways:
|
|
50
|
+
|
|
51
|
+
### Option 1: Using npx (Recommended - No Installation Required)
|
|
52
|
+
|
|
53
|
+
Run directly from the GitHub repository without installing:
|
|
54
|
+
```bash
|
|
55
|
+
npx github:pRizz/squash-only
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
With custom sleep interval:
|
|
59
|
+
```bash
|
|
60
|
+
npx github:pRizz/squash-only --sleep 0.5
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Note:** If the package is published to npm, you can also use:
|
|
64
|
+
```bash
|
|
65
|
+
npx squash-only
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Option 2: Using the Bash Script Directly
|
|
69
|
+
|
|
70
|
+
Run the bash script directly:
|
|
71
|
+
```bash
|
|
72
|
+
./scripts/squash-only.sh
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
With custom sleep interval:
|
|
76
|
+
```bash
|
|
77
|
+
./scripts/squash-only.sh --sleep 0.5
|
|
78
|
+
# or
|
|
79
|
+
./scripts/squash-only.sh -s 1.0
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Option 3: Run Locally with npm (Development)
|
|
83
|
+
|
|
84
|
+
If you've cloned the repository, you can run it locally using npm scripts:
|
|
85
|
+
```bash
|
|
86
|
+
npm start
|
|
87
|
+
# or
|
|
88
|
+
npm run squash-only
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
With custom sleep interval:
|
|
92
|
+
```bash
|
|
93
|
+
npm start -- --sleep 0.5
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You can also use `npx` to run the local version:
|
|
97
|
+
```bash
|
|
98
|
+
npx .
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Option 4: Install Globally via npm/pnpm
|
|
102
|
+
|
|
103
|
+
Install the package globally:
|
|
104
|
+
```bash
|
|
105
|
+
npm install -g squash-only
|
|
106
|
+
# or
|
|
107
|
+
pnpm install -g squash-only
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Then run it from anywhere:
|
|
111
|
+
```bash
|
|
112
|
+
squash-only
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Usage Examples
|
|
116
|
+
|
|
117
|
+
### Basic usage
|
|
118
|
+
```bash
|
|
119
|
+
# Using npx (recommended)
|
|
120
|
+
npx github:pRizz/squash-only
|
|
121
|
+
|
|
122
|
+
# Or run locally with npm
|
|
123
|
+
npm start
|
|
124
|
+
|
|
125
|
+
# Or using the bash script directly
|
|
126
|
+
./scripts/squash-only.sh
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Custom sleep interval
|
|
130
|
+
Control the delay between API requests (default: 0.2 seconds):
|
|
131
|
+
```bash
|
|
132
|
+
# Using npx
|
|
133
|
+
npx github:pRizz/squash-only --sleep 0.5
|
|
134
|
+
|
|
135
|
+
# Or run locally with npm
|
|
136
|
+
npm start -- --sleep 0.5
|
|
137
|
+
|
|
138
|
+
# Or using the bash script
|
|
139
|
+
./scripts/squash-only.sh --sleep 0.5
|
|
140
|
+
# or
|
|
141
|
+
./scripts/squash-only.sh -s 1.0
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Options
|
|
145
|
+
|
|
146
|
+
- `-s, --sleep SECONDS` - Set the sleep interval between API requests (must be a number)
|
|
147
|
+
|
|
148
|
+
## Features
|
|
149
|
+
|
|
150
|
+
- 🔐 **Automatic authentication** - Tries multiple authentication methods
|
|
151
|
+
- 📄 **Pagination support** - Handles users with 100+ repositories
|
|
152
|
+
- 🔍 **Ownership filtering** - Only updates repositories you own
|
|
153
|
+
- 📊 **Progress tracking** - Shows success, skipped, and failed counts
|
|
154
|
+
- ⏱️ **Performance metrics** - Displays elapsed time
|
|
155
|
+
- 🛡️ **Error handling** - Validates inputs and provides clear error messages
|
|
156
|
+
|
|
157
|
+
## Output
|
|
158
|
+
|
|
159
|
+
The script provides:
|
|
160
|
+
- Real-time progress updates for each repository
|
|
161
|
+
- Success/failure status for each update
|
|
162
|
+
- A summary at the end showing:
|
|
163
|
+
- Number of successfully updated repositories
|
|
164
|
+
- Number of skipped repositories (not owned by you)
|
|
165
|
+
- Number of failed updates (if any)
|
|
166
|
+
- Total elapsed time
|
|
167
|
+
|
|
168
|
+
Example output:
|
|
169
|
+
```
|
|
170
|
+
────────────────────────────────────
|
|
171
|
+
Summary:
|
|
172
|
+
✅ Successfully updated: 15
|
|
173
|
+
⏭️ Skipped: 3
|
|
174
|
+
⏱️ Elapsed time: 2m 30s
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Examples
|
|
178
|
+
|
|
179
|
+
Update all your repos with default settings:
|
|
180
|
+
```bash
|
|
181
|
+
# Using npx (recommended)
|
|
182
|
+
npx github:pRizz/squash-only
|
|
183
|
+
|
|
184
|
+
# Or run locally with npm
|
|
185
|
+
npm start
|
|
186
|
+
|
|
187
|
+
# Or using the bash script
|
|
188
|
+
./scripts/squash-only.sh
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Update with a longer delay between requests:
|
|
192
|
+
```bash
|
|
193
|
+
# Using npx
|
|
194
|
+
npx github:pRizz/squash-only --sleep 1.0
|
|
195
|
+
|
|
196
|
+
# Or using the bash script
|
|
197
|
+
./scripts/squash-only.sh --sleep 1.0
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Use with environment variable:
|
|
201
|
+
```bash
|
|
202
|
+
# Using npx
|
|
203
|
+
GITHUB_TOKEN=ghp_xxxxx npx github:pRizz/squash-only
|
|
204
|
+
|
|
205
|
+
# Or run locally with npm
|
|
206
|
+
GITHUB_TOKEN=ghp_xxxxx npm start
|
|
207
|
+
|
|
208
|
+
# Or using the bash script
|
|
209
|
+
GITHUB_TOKEN=ghp_xxxxx ./scripts/squash-only.sh
|
|
210
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const scriptPath = path.join(__dirname, '..', 'scripts', 'squash-only.sh');
|
|
8
|
+
|
|
9
|
+
// Check if script exists
|
|
10
|
+
if (!fs.existsSync(scriptPath)) {
|
|
11
|
+
console.error(`❌ Error: Script not found at ${scriptPath}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Get all arguments except node and script name
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
// Spawn the bash script with all arguments
|
|
19
|
+
const child = spawn('bash', [scriptPath, ...args], {
|
|
20
|
+
stdio: 'inherit',
|
|
21
|
+
cwd: path.join(__dirname, '..'),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
let isExiting = false;
|
|
25
|
+
|
|
26
|
+
// Handle termination signals (Ctrl-C, etc.)
|
|
27
|
+
const handleSignal = (signal) => {
|
|
28
|
+
if (isExiting) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
isExiting = true;
|
|
32
|
+
|
|
33
|
+
// Forward the signal to the child process
|
|
34
|
+
if (child && !child.killed) {
|
|
35
|
+
child.kill(signal);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
process.on('SIGINT', handleSignal);
|
|
40
|
+
process.on('SIGTERM', handleSignal);
|
|
41
|
+
|
|
42
|
+
// Cleanup on process exit
|
|
43
|
+
process.on('exit', () => {
|
|
44
|
+
if (child && !child.killed) {
|
|
45
|
+
child.kill('SIGTERM');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
child.on('error', (error) => {
|
|
50
|
+
console.error(`❌ Error executing script: ${error.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
child.on('exit', (code, signal) => {
|
|
55
|
+
// If child was killed by a signal, exit with appropriate code
|
|
56
|
+
if (signal) {
|
|
57
|
+
process.exit(signal === 'SIGINT' ? 130 : 143);
|
|
58
|
+
} else {
|
|
59
|
+
process.exit(code || 0);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "squash-only",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scripts for making all your repos on GitHub Squash Only!",
|
|
5
|
+
"bin": {
|
|
6
|
+
"squash-only": "./bin/squash-only.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"scripts",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=12.0.0"
|
|
16
|
+
},
|
|
17
|
+
"preferGlobal": true,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"start": "node bin/squash-only.js",
|
|
20
|
+
"squash-only": "node bin/squash-only.js",
|
|
21
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/pRizz/squash-only.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"squash",
|
|
29
|
+
"github",
|
|
30
|
+
"merge",
|
|
31
|
+
"squash-merge",
|
|
32
|
+
"cli",
|
|
33
|
+
"automation",
|
|
34
|
+
"git"
|
|
35
|
+
],
|
|
36
|
+
"author": "Peter Ryszkiewicz",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/pRizz/squash-only/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/pRizz/squash-only#readme"
|
|
42
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
SLEEP_SECS=0.2
|
|
5
|
+
SHOULD_EXIT=0
|
|
6
|
+
|
|
7
|
+
# Temporary files for tracking counts across subshells
|
|
8
|
+
SUCCESS_COUNT_FILE=$(mktemp)
|
|
9
|
+
SKIP_COUNT_FILE=$(mktemp)
|
|
10
|
+
FAILED_COUNT_FILE=$(mktemp)
|
|
11
|
+
|
|
12
|
+
cleanup() {
|
|
13
|
+
rm -f "$SUCCESS_COUNT_FILE" "$SKIP_COUNT_FILE" "$FAILED_COUNT_FILE"
|
|
14
|
+
}
|
|
15
|
+
trap cleanup EXIT
|
|
16
|
+
|
|
17
|
+
# Handle termination signals
|
|
18
|
+
handle_exit() {
|
|
19
|
+
SHOULD_EXIT=1
|
|
20
|
+
echo ""
|
|
21
|
+
echo "⚠️ Interrupted. Cleaning up..."
|
|
22
|
+
cleanup
|
|
23
|
+
exit 130
|
|
24
|
+
}
|
|
25
|
+
trap handle_exit INT TERM
|
|
26
|
+
|
|
27
|
+
get_github_token() {
|
|
28
|
+
# Check if GITHUB_TOKEN is already in environment
|
|
29
|
+
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
|
30
|
+
echo "$GITHUB_TOKEN"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Check if gh CLI is installed
|
|
35
|
+
if ! command -v gh &> /dev/null; then
|
|
36
|
+
print_pat_instructions
|
|
37
|
+
return 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Try to get token from gh CLI
|
|
41
|
+
local maybeToken
|
|
42
|
+
maybeToken=$(gh auth token 2>/dev/null)
|
|
43
|
+
if [ -n "$maybeToken" ]; then
|
|
44
|
+
echo "$maybeToken"
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# gh is installed but no token available
|
|
49
|
+
print_pat_instructions
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
print_pat_instructions() {
|
|
54
|
+
cat << 'EOF'
|
|
55
|
+
|
|
56
|
+
To use this script, you need a GitHub Personal Access Token (PAT).
|
|
57
|
+
|
|
58
|
+
1. Go to https://github.com/settings/tokens/new
|
|
59
|
+
2. Give your token a descriptive name (e.g., "Squash Only Script")
|
|
60
|
+
3. Set an expiration (recommended: 90 days or custom)
|
|
61
|
+
4. Select the following permissions:
|
|
62
|
+
- repo (Full control of private repositories)
|
|
63
|
+
- This includes: repo:status, repo_deployment, public_repo, repo:invite, security_events
|
|
64
|
+
5. Click "Generate token"
|
|
65
|
+
6. Copy the token and run this script with:
|
|
66
|
+
GITHUB_TOKEN=your_token_here ./scripts/squash-only.sh
|
|
67
|
+
|
|
68
|
+
Alternatively, you can install the GitHub CLI (gh) and authenticate:
|
|
69
|
+
brew install gh
|
|
70
|
+
gh auth login
|
|
71
|
+
|
|
72
|
+
EOF
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
increment_counter() {
|
|
76
|
+
local counter_file=$1
|
|
77
|
+
local current
|
|
78
|
+
current=$(cat "$counter_file")
|
|
79
|
+
echo $((current + 1)) > "$counter_file"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
process_repo() {
|
|
83
|
+
local repo=$1
|
|
84
|
+
local owner=$2
|
|
85
|
+
|
|
86
|
+
# Check if we should exit
|
|
87
|
+
[ "$SHOULD_EXIT" -eq 1 ] && return 1
|
|
88
|
+
|
|
89
|
+
if [ -z "$repo" ] || [ "$repo" = "null" ]; then
|
|
90
|
+
return 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if [ "$owner" != "$GITHUB_USER" ]; then
|
|
94
|
+
echo "────────────────────────────────────"
|
|
95
|
+
echo "⏭️ Skipping repo: $repo (owned by $owner)"
|
|
96
|
+
increment_counter "$SKIP_COUNT_FILE"
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "────────────────────────────────────"
|
|
101
|
+
echo "Updating repo: $repo"
|
|
102
|
+
|
|
103
|
+
local http_code
|
|
104
|
+
http_code=$(curl -s --max-time 30 -o /dev/null -w "%{http_code}" \
|
|
105
|
+
-X PATCH \
|
|
106
|
+
-H "Authorization: token $GITHUB_TOKEN" \
|
|
107
|
+
-H "Accept: application/vnd.github+json" \
|
|
108
|
+
https://api.github.com/repos/$repo \
|
|
109
|
+
-d '{
|
|
110
|
+
"allow_merge_commit": false,
|
|
111
|
+
"allow_rebase_merge": false,
|
|
112
|
+
"allow_squash_merge": true
|
|
113
|
+
}')
|
|
114
|
+
|
|
115
|
+
# Check if we should exit after curl
|
|
116
|
+
[ "$SHOULD_EXIT" -eq 1 ] && return 1
|
|
117
|
+
|
|
118
|
+
if [ "$http_code" -eq 200 ]; then
|
|
119
|
+
echo "✅ Success ($http_code)"
|
|
120
|
+
echo " https://github.com/$repo"
|
|
121
|
+
increment_counter "$SUCCESS_COUNT_FILE"
|
|
122
|
+
else
|
|
123
|
+
echo "❌ Failed ($http_code)"
|
|
124
|
+
increment_counter "$FAILED_COUNT_FILE"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Check again before sleeping
|
|
128
|
+
[ "$SHOULD_EXIT" -eq 1 ] && return 1
|
|
129
|
+
|
|
130
|
+
echo "Sleeping ${SLEEP_SECS}s…"
|
|
131
|
+
sleep "$SLEEP_SECS"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fetch_all_repos() {
|
|
135
|
+
local page=1
|
|
136
|
+
local per_page=100
|
|
137
|
+
|
|
138
|
+
while true; do
|
|
139
|
+
# Check if we should exit
|
|
140
|
+
[ "$SHOULD_EXIT" -eq 1 ] && break
|
|
141
|
+
|
|
142
|
+
local body
|
|
143
|
+
body=$(curl -s --max-time 30 -H "Authorization: token $GITHUB_TOKEN" \
|
|
144
|
+
"https://api.github.com/user/repos?per_page=$per_page&page=$page")
|
|
145
|
+
|
|
146
|
+
# Check if we should exit after curl
|
|
147
|
+
[ "$SHOULD_EXIT" -eq 1 ] && break
|
|
148
|
+
|
|
149
|
+
local repo_count
|
|
150
|
+
repo_count=$(echo "$body" | jq '. | length')
|
|
151
|
+
if [ -z "$repo_count" ] || [ "$repo_count" = "0" ] || [ "$repo_count" = "null" ]; then
|
|
152
|
+
break
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo "$body" | jq -r '.[] | "\(.full_name)|\(.owner.login)"' | while IFS='|' read -r repo owner; do
|
|
156
|
+
[ "$SHOULD_EXIT" -eq 1 ] && break
|
|
157
|
+
process_repo "$repo" "$owner" || break
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
[ "$SHOULD_EXIT" -eq 1 ] && break
|
|
161
|
+
|
|
162
|
+
if [ "$repo_count" -lt "$per_page" ]; then
|
|
163
|
+
break
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
page=$((page + 1))
|
|
167
|
+
done
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
is_number() {
|
|
171
|
+
local value=$1
|
|
172
|
+
if [[ "$value" =~ ^[0-9]+\.?[0-9]*$ ]]; then
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
return 1
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
parse_args() {
|
|
179
|
+
while [[ $# -gt 0 ]]; do
|
|
180
|
+
case $1 in
|
|
181
|
+
-s|--sleep)
|
|
182
|
+
if [ -z "${2:-}" ]; then
|
|
183
|
+
echo "❌ Error: --sleep requires a value"
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
if ! is_number "$2"; then
|
|
187
|
+
echo "❌ Error: --sleep value must be a number (got: $2)"
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
SLEEP_SECS="$2"
|
|
191
|
+
shift 2
|
|
192
|
+
;;
|
|
193
|
+
*)
|
|
194
|
+
echo "❌ Error: Unknown option: $1"
|
|
195
|
+
echo "Usage: $0 [--sleep SECONDS]"
|
|
196
|
+
exit 1
|
|
197
|
+
;;
|
|
198
|
+
esac
|
|
199
|
+
done
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
main() {
|
|
203
|
+
parse_args "$@"
|
|
204
|
+
|
|
205
|
+
# Initialize counters
|
|
206
|
+
echo "0" > "$SUCCESS_COUNT_FILE"
|
|
207
|
+
echo "0" > "$SKIP_COUNT_FILE"
|
|
208
|
+
echo "0" > "$FAILED_COUNT_FILE"
|
|
209
|
+
|
|
210
|
+
echo "Updating all your repos to Squash Only!"
|
|
211
|
+
echo "→ Disables merge commits"
|
|
212
|
+
echo "→ Disables rebase merges"
|
|
213
|
+
echo "→ Enables squash merges"
|
|
214
|
+
|
|
215
|
+
echo "→ Logging in to GitHub…"
|
|
216
|
+
GITHUB_TOKEN=$(get_github_token)
|
|
217
|
+
if [ $? -ne 0 ]; then
|
|
218
|
+
exit 1
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
echo "Fetching your username…"
|
|
222
|
+
GITHUB_USER=$(curl -s --max-time 30 -H "Authorization: token $GITHUB_TOKEN" \
|
|
223
|
+
https://api.github.com/user | jq -r '.login')
|
|
224
|
+
|
|
225
|
+
# Check if we should exit
|
|
226
|
+
[ "$SHOULD_EXIT" -eq 1 ] && exit 130
|
|
227
|
+
|
|
228
|
+
if [ -z "$GITHUB_USER" ] || [ "$GITHUB_USER" = "null" ]; then
|
|
229
|
+
echo "❌ Failed to get GitHub username"
|
|
230
|
+
exit 1
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
echo "Fetching your repos…"
|
|
234
|
+
START_TIME=$(date +%s)
|
|
235
|
+
fetch_all_repos
|
|
236
|
+
END_TIME=$(date +%s)
|
|
237
|
+
|
|
238
|
+
ELAPSED=$((END_TIME - START_TIME))
|
|
239
|
+
ELAPSED_MIN=$((ELAPSED / 60))
|
|
240
|
+
ELAPSED_SEC=$((ELAPSED % 60))
|
|
241
|
+
|
|
242
|
+
echo ""
|
|
243
|
+
echo "────────────────────────────────────"
|
|
244
|
+
echo "Summary:"
|
|
245
|
+
SUCCESS_COUNT=$(cat "$SUCCESS_COUNT_FILE")
|
|
246
|
+
SKIP_COUNT=$(cat "$SKIP_COUNT_FILE")
|
|
247
|
+
FAILED_COUNT=$(cat "$FAILED_COUNT_FILE")
|
|
248
|
+
echo " ✅ Successfully updated: $SUCCESS_COUNT"
|
|
249
|
+
echo " ⏭️ Skipped: $SKIP_COUNT"
|
|
250
|
+
if [ "$FAILED_COUNT" -gt 0 ]; then
|
|
251
|
+
echo " ❌ Failed: $FAILED_COUNT"
|
|
252
|
+
fi
|
|
253
|
+
if [ "$ELAPSED_MIN" -gt 0 ]; then
|
|
254
|
+
echo " ⏱️ Elapsed time: ${ELAPSED_MIN}m ${ELAPSED_SEC}s"
|
|
255
|
+
else
|
|
256
|
+
echo " ⏱️ Elapsed time: ${ELAPSED}s"
|
|
257
|
+
fi
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main "$@"
|