mirror of https://github.com/garrytan/gstack.git
fix(artifacts-init): honor gh/glab git_protocol when picking push URL
`gstack-artifacts-init` always converted the canonical HTTPS URL to its
SSH form for `git push origin`. Users on `gh config set git_protocol
https` (the `gh auth login` default) lack an SSH key on the remote, so
the post-create `git ls-remote $PUSH_URL` verification fails with a
cryptic SSH prompt and the script exits without finishing setup. Same
hazard on the gitlab branch.
Pre-fix the only recovery was rerunning with `--remote <https url>`,
which bypassed the canonicalization step entirely and required the user
to know the workaround.
Resolution order for the push URL:
1. `--push-protocol https|ssh` flag wins.
2. `auto` (default) reads `gh config get git_protocol` (github branch)
or `glab config get git_protocol` (gitlab branch).
3. Empty / unreadable config falls back to SSH — the historical
default, kept so users who never set `git_protocol` see no change.
Error message after a failed `ls-remote` also points HTTPS users at the
override flag, so the next attempt is one rerun away.
Fixes #1348
This commit is contained in:
parent
a3259400a3
commit
78aaa2017f
|
|
@ -9,6 +9,14 @@
|
|||
# Usage:
|
||||
# gstack-artifacts-init [--remote <url>] [--host github|gitlab|manual]
|
||||
# [--url-form-supported true|false]
|
||||
# [--push-protocol auto|https|ssh]
|
||||
#
|
||||
# --push-protocol controls the URL form used for `git push origin`:
|
||||
# auto (default) — honors `gh config get git_protocol` (github) or
|
||||
# `glab config get git_protocol` (gitlab). Falls back to
|
||||
# SSH when neither is set / readable.
|
||||
# https — force HTTPS (requires gh/glab credential helper or PAT).
|
||||
# ssh — force SSH (legacy default; requires SSH key on remote).
|
||||
#
|
||||
# Interactive by default. Pass --remote to skip the host prompt.
|
||||
#
|
||||
|
|
@ -43,11 +51,17 @@ REMOTE_FILE="$HOME/.gstack-artifacts-remote.txt"
|
|||
REMOTE_URL=""
|
||||
HOST_PREF=""
|
||||
URL_FORM_SUPPORTED="false"
|
||||
PUSH_PROTOCOL="auto"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--remote) REMOTE_URL="$2"; shift 2 ;;
|
||||
--host) HOST_PREF="$2"; shift 2 ;;
|
||||
--url-form-supported) URL_FORM_SUPPORTED="$2"; shift 2 ;;
|
||||
--push-protocol)
|
||||
case "$2" in
|
||||
auto|https|ssh) PUSH_PROTOCOL="$2"; shift 2 ;;
|
||||
*) echo "Invalid --push-protocol: $2 (expected auto|https|ssh)" >&2; exit 1 ;;
|
||||
esac ;;
|
||||
--help|-h) sed -n '2,32p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "Unknown flag: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
|
|
@ -179,21 +193,60 @@ if [ -z "$CANONICAL_HTTPS" ]; then
|
|||
CANONICAL_HTTPS="$REMOTE_URL"
|
||||
fi
|
||||
|
||||
# Use SSH for git push (more reliable for repeated pushes than HTTPS+token).
|
||||
# Fall back to the canonical input if derivation fails.
|
||||
PUSH_URL=$("$URL_BIN" --to ssh "$CANONICAL_HTTPS" 2>/dev/null || echo "$CANONICAL_HTTPS")
|
||||
# Resolve push protocol. `auto` honors the user's CLI git_protocol setting
|
||||
# (gh / glab); explicit `https` / `ssh` skips detection. SSH is the historical
|
||||
# default for repeated pushes (no token refresh dance), but HTTPS-configured
|
||||
# users hit a hard wall when we force SSH and they lack an SSH key on the
|
||||
# remote — see issue #1348.
|
||||
RESOLVED_PROTOCOL="$PUSH_PROTOCOL"
|
||||
if [ "$RESOLVED_PROTOCOL" = "auto" ]; then
|
||||
case "$HOST_PREF" in
|
||||
github)
|
||||
DETECTED=$(gh config get git_protocol 2>/dev/null || echo "")
|
||||
;;
|
||||
gitlab)
|
||||
DETECTED=$(glab config get git_protocol 2>/dev/null || echo "")
|
||||
;;
|
||||
*)
|
||||
DETECTED=""
|
||||
;;
|
||||
esac
|
||||
case "$DETECTED" in
|
||||
https) RESOLVED_PROTOCOL="https" ;;
|
||||
ssh) RESOLVED_PROTOCOL="ssh" ;;
|
||||
*) RESOLVED_PROTOCOL="ssh" ;; # historical default when unset/unknown
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "$RESOLVED_PROTOCOL" = "https" ]; then
|
||||
PUSH_URL="$CANONICAL_HTTPS"
|
||||
else
|
||||
PUSH_URL=$("$URL_BIN" --to ssh "$CANONICAL_HTTPS" 2>/dev/null || echo "$CANONICAL_HTTPS")
|
||||
fi
|
||||
|
||||
# ---- verify push URL is reachable ----
|
||||
echo "Verifying remote connectivity: $PUSH_URL"
|
||||
if ! git ls-remote "$PUSH_URL" >/dev/null 2>&1; then
|
||||
cat >&2 <<EOF
|
||||
if [ "$RESOLVED_PROTOCOL" = "ssh" ]; then
|
||||
cat >&2 <<EOF
|
||||
Remote not reachable via SSH: $PUSH_URL
|
||||
This could mean:
|
||||
- Wrong URL
|
||||
- SSH key not added to your git host (GitHub: gh ssh-key list; GitLab: glab ssh-key list)
|
||||
- Network issue
|
||||
- You use HTTPS for git (gh config get git_protocol = https) — re-run with --push-protocol https
|
||||
Fix and re-run gstack-artifacts-init.
|
||||
EOF
|
||||
else
|
||||
cat >&2 <<EOF
|
||||
Remote not reachable via HTTPS: $PUSH_URL
|
||||
This could mean:
|
||||
- Wrong URL
|
||||
- HTTPS credentials not configured (GitHub: gh auth setup-git; GitLab: glab auth git-credential)
|
||||
- Network issue
|
||||
Fix and re-run gstack-artifacts-init.
|
||||
EOF
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -32,14 +32,22 @@ let fakeBinDir: string;
|
|||
let ghCallLog: string;
|
||||
let glabCallLog: string;
|
||||
|
||||
function makeFakeGh(opts: { authStatus?: 'ok' | 'fail'; repoCreate?: 'success' | 'already-exists' | 'fail'; webUrl?: string } = {}) {
|
||||
function makeFakeGh(opts: { authStatus?: 'ok' | 'fail'; repoCreate?: 'success' | 'already-exists' | 'fail'; webUrl?: string; gitProtocol?: 'https' | 'ssh' | '' } = {}) {
|
||||
const authStatus = opts.authStatus ?? 'ok';
|
||||
const repoCreate = opts.repoCreate ?? 'success';
|
||||
const webUrl = opts.webUrl ?? `https://github.com/testuser/gstack-artifacts-testuser`;
|
||||
const gitProtocol = opts.gitProtocol ?? '';
|
||||
const script = `#!/bin/bash
|
||||
echo "gh $@" >> "${ghCallLog}"
|
||||
case "$1" in
|
||||
auth) ${authStatus === 'ok' ? 'exit 0' : 'exit 1'} ;;
|
||||
config)
|
||||
# gh config get git_protocol
|
||||
if [ "$2" = "get" ] && [ "$3" = "git_protocol" ]; then
|
||||
${gitProtocol ? `echo "${gitProtocol}"; exit 0` : 'exit 1'}
|
||||
fi
|
||||
exit 1
|
||||
;;
|
||||
repo)
|
||||
shift
|
||||
case "$1" in
|
||||
|
|
@ -65,14 +73,21 @@ exit 0
|
|||
fs.writeFileSync(path.join(fakeBinDir, 'gh'), script, { mode: 0o755 });
|
||||
}
|
||||
|
||||
function makeFakeGlab(opts: { authStatus?: 'ok' | 'fail'; repoCreate?: 'success' | 'fail'; webUrl?: string } = {}) {
|
||||
function makeFakeGlab(opts: { authStatus?: 'ok' | 'fail'; repoCreate?: 'success' | 'fail'; webUrl?: string; gitProtocol?: 'https' | 'ssh' | '' } = {}) {
|
||||
const authStatus = opts.authStatus ?? 'ok';
|
||||
const repoCreate = opts.repoCreate ?? 'success';
|
||||
const webUrl = opts.webUrl ?? 'https://gitlab.com/testuser/gstack-artifacts-testuser';
|
||||
const gitProtocol = opts.gitProtocol ?? '';
|
||||
const script = `#!/bin/bash
|
||||
echo "glab $@" >> "${glabCallLog}"
|
||||
case "$1" in
|
||||
auth) ${authStatus === 'ok' ? 'exit 0' : 'exit 1'} ;;
|
||||
config)
|
||||
if [ "$2" = "get" ] && [ "$3" = "git_protocol" ]; then
|
||||
${gitProtocol ? `echo "${gitProtocol}"; exit 0` : 'exit 1'}
|
||||
fi
|
||||
exit 1
|
||||
;;
|
||||
repo)
|
||||
shift
|
||||
case "$1" in
|
||||
|
|
@ -299,6 +314,69 @@ describe('gstack-artifacts-init brain-admin hookup printout (codex Finding #3)',
|
|||
});
|
||||
});
|
||||
|
||||
describe('gstack-artifacts-init push protocol (issue #1348)', () => {
|
||||
test('--push-protocol=auto + gh git_protocol=https → origin uses HTTPS', () => {
|
||||
// Issue #1348: users with `gh config set git_protocol https` (the
|
||||
// `gh auth login` default) lack an SSH key on the remote, so forcing
|
||||
// an SSH push URL fails at the ls-remote verification step.
|
||||
makeFakeGh({ gitProtocol: 'https', webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
||||
const r = run(['--host', 'github']);
|
||||
if (r.status !== 0) console.error('STDERR:', r.stderr);
|
||||
expect(r.status).toBe(0);
|
||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
||||
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
||||
});
|
||||
|
||||
test('--push-protocol=auto + gh git_protocol=ssh → origin uses SSH (existing default)', () => {
|
||||
makeFakeGh({ gitProtocol: 'ssh', webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
||||
const r = run(['--host', 'github']);
|
||||
expect(r.status).toBe(0);
|
||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
||||
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
||||
});
|
||||
|
||||
test('--push-protocol=auto + gh git_protocol unset → falls back to SSH (legacy default)', () => {
|
||||
// gh config get returns non-zero / empty when never set. We default to
|
||||
// SSH to keep the historical behavior for users who haven't opted in.
|
||||
makeFakeGh({ gitProtocol: '', webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
||||
const r = run(['--host', 'github']);
|
||||
expect(r.status).toBe(0);
|
||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
||||
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
||||
});
|
||||
|
||||
test('--push-protocol=https overrides gh git_protocol=ssh', () => {
|
||||
makeFakeGh({ gitProtocol: 'ssh', webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
||||
const r = run(['--host', 'github', '--push-protocol', 'https']);
|
||||
expect(r.status).toBe(0);
|
||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
||||
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
|
||||
});
|
||||
|
||||
test('--push-protocol=ssh overrides gh git_protocol=https', () => {
|
||||
makeFakeGh({ gitProtocol: 'https', webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
|
||||
const r = run(['--host', 'github', '--push-protocol', 'ssh']);
|
||||
expect(r.status).toBe(0);
|
||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
||||
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
|
||||
});
|
||||
|
||||
test('--push-protocol with invalid value exits 1', () => {
|
||||
makeFakeGh({});
|
||||
const r = run(['--host', 'github', '--push-protocol', 'bogus']);
|
||||
expect(r.status).not.toBe(0);
|
||||
expect(r.stderr).toContain('Invalid --push-protocol');
|
||||
});
|
||||
|
||||
test('gitlab branch: glab git_protocol=https → origin uses HTTPS', () => {
|
||||
makeFakeGlab({ gitProtocol: 'https' });
|
||||
const r = run(['--host', 'gitlab']);
|
||||
expect(r.status).toBe(0);
|
||||
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
|
||||
expect(remote.stdout.trim()).toBe('https://gitlab.com/testuser/gstack-artifacts-testuser');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gstack-artifacts-init idempotency', () => {
|
||||
test('--remote <url> bypasses provider selection entirely', () => {
|
||||
makeFakeGh({});
|
||||
|
|
|
|||
Loading…
Reference in New Issue