fix(mkrep): make origin linking idempotent #155

Merged
rootiest merged 1 commits from fix/mkrep-origin-idempotent into main 2026-09-16 23:39:30 +00:00
3 changed files with 87 additions and 8 deletions
+53
View File
@@ -0,0 +1,53 @@
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# DEPENDENCIES
# _mkrep_say, __fish_palette, git
#
# SYNOPSIS
# _mkrep_add_origin <silent> <url>
#
# DESCRIPTION
# Links origin to <url>, idempotently, for mkrep's --remote and --server
# link-existing flows. Both can run against a directory that is already a
# repo -- `mkrep .` on an existing checkout, or a second mkrep against the
# same target -- where a bare `git remote add origin` fails with "remote
# origin already exists" and takes the whole call down with it.
#
# No origin yet: adds it. An origin already pointing at <url>: reports that
# and succeeds, since the requested end state already holds. An origin
# pointing somewhere else: refuses, naming both URLs. Repointing is not
# silently assumed -- an origin the caller did not ask about is more likely
# a checkout mkrep was aimed at by mistake than one it should rewrite, so
# the fix belongs in the caller's hands (git remote set-url origin <url>).
#
# ARGUMENTS
# silent 1 to suppress the success/already-linked notes (mkrep's -s)
# url Remote URL origin should point at
#
# EXIT STATUS
# 0 origin now points at <url> (added, or already did)
# 1 git remote add failed, or origin points somewhere else
function _mkrep_add_origin --argument-names silent url
__fish_palette
set -l existing (git remote get-url origin 2>/dev/null)
if test -z "$existing"
git remote add origin $url
or begin
echo "$c_err""✘$c_reset Failed to add remote $c_arg$url$c_reset" >&2
return 1
end
_mkrep_say $silent "$c_ok""✔$c_reset Linked remote $c_arg$url$c_reset"
return 0
end
if test "$existing" = "$url"
_mkrep_say $silent "$c_warn""→$c_reset origin already points at $c_arg$url$c_reset"
return 0
end
echo "$c_err""✘$c_reset origin already points at $c_arg$existing$c_reset, not $c_arg$url$c_reset — run $c_cmd""git remote set-url origin $url$c_reset to repoint it" >&2
return 1
end
+10 -8
View File
@@ -6,7 +6,8 @@
# #
# DEPENDENCIES # DEPENDENCIES
# _fish_mkdir_p, __fish_palette, _mkrep_say, _mkrep_verbose, # _fish_mkdir_p, __fish_palette, _mkrep_say, _mkrep_verbose,
# _mkrep_default_remote_cmd, _mkrep_remote_url, _mkrep_repo_exists, git # _mkrep_add_origin, _mkrep_default_remote_cmd, _mkrep_remote_url,
# _mkrep_repo_exists, git
# #
# SYNOPSIS # SYNOPSIS
# mkrep [--cd | --no-cd] [--mkdir | --no-mkdir] [--git | --no-git] # mkrep [--cd | --no-cd] [--mkdir | --no-mkdir] [--git | --no-git]
@@ -28,7 +29,12 @@
# empty slot because clean just emptied it. # empty slot because clean just emptied it.
# #
# --remote links an already-existing remote (git remote add origin # --remote links an already-existing remote (git remote add origin
# <url>) -- it does not create anything. --new-remote creates one first # <url>) -- it does not create anything. Linking is idempotent: an origin
# already pointing at that URL is reported and accepted, so rerunning
# mkrep against the same target, or pointing it at a checkout that is
# already linked, succeeds instead of failing on "remote origin already
# exists". An origin pointing somewhere else is an error, not a silent
# repoint. --new-remote creates one first
# by running a shell command template in the new repo directory, then # by running a shell command template in the new repo directory, then
# nothing further is needed since the template itself does the linking # nothing further is needed since the template itself does the linking
# (e.g. gh repo create {name} --source=. --remote=origin --push). # (e.g. gh repo create {name} --source=. --remote=origin --push).
@@ -333,13 +339,11 @@ function mkrep --description 'Create a directory, cd into it, and git init it'
if set -q _flag_remote if set -q _flag_remote
_mkrep_verbose $silent $verbose "$c_dim""Running: git remote add origin $_flag_remote$c_reset" _mkrep_verbose $silent $verbose "$c_dim""Running: git remote add origin $_flag_remote$c_reset"
git remote add origin $_flag_remote _mkrep_add_origin $silent $_flag_remote
or begin or begin
echo "$c_err""✘$c_reset Failed to add remote $c_arg$_flag_remote$c_reset" >&2
cd $orig_pwd cd $orig_pwd
return 1 return 1
end end
_mkrep_say $silent "$c_ok""✔$c_reset Linked remote $c_arg$_flag_remote$c_reset"
end end
if set -q _flag_new_remote if set -q _flag_new_remote
@@ -384,13 +388,11 @@ function mkrep --description 'Create a directory, cd into it, and git init it'
if _mkrep_repo_exists $srv_type $USER $name if _mkrep_repo_exists $srv_type $USER $name
set -l url (_mkrep_remote_url $srv_type $USER $name $srv_url) set -l url (_mkrep_remote_url $srv_type $USER $name $srv_url)
_mkrep_say $silent "$c_warn""→$c_reset $c_arg$USER/$name$c_reset already exists on $srv_type; linking instead of creating" _mkrep_say $silent "$c_warn""→$c_reset $c_arg$USER/$name$c_reset already exists on $srv_type; linking instead of creating"
git remote add origin $url _mkrep_add_origin $silent $url
or begin or begin
echo "$c_err""✘$c_reset Failed to add remote $c_arg$url$c_reset" >&2
cd $orig_pwd cd $orig_pwd
return 1 return 1
end end
_mkrep_say $silent "$c_ok""✔$c_reset Linked remote $c_arg$url$c_reset"
else else
# Creating a repository on a live forge is the only outward-facing # Creating a repository on a live forge is the only outward-facing
# thing mkrep does, and an exported $GIT_SERVER alone is enough to # thing mkrep does, and an exported $GIT_SERVER alone is enough to
+24
View File
@@ -163,6 +163,30 @@ check "--remote links origin to the given url" https://example.invalid/me/repo.g
cd $start cd $start
rm -rf $base rm -rf $base
# `mkrep .` against an existing checkout, and a plain rerun against the same
# target, both reach `git remote add origin` on a repo that already has one.
# A bare add fails there with "remote origin already exists" and takes the
# whole call down, so linking has to accept the end state it already wanted.
section "mkrep: --remote is idempotent"
set -l base (_mkrep_sandbox)
set -l target $base/repo
mkrep --remote https://example.invalid/me/repo.git $target >/dev/null
mkrep --remote https://example.invalid/me/repo.git $target >/dev/null
check "relinking the same url exits 0" 0 $status
check "relinking leaves one origin" 1 (count (git -C $target remote))
set -l url (git -C $target remote get-url origin)
check "relinking leaves origin untouched" https://example.invalid/me/repo.git $url
# A different url is a different repo. Repointing a checkout the caller did
# not ask about is more likely a mistargeted mkrep than an intended rewrite.
mkrep --remote https://example.invalid/me/other.git $target >/dev/null 2>/tmp/mkrep-test-err
check "relinking a different url exits 1" 1 $status
set -l url (git -C $target remote get-url origin)
check "a refused relink leaves origin alone" https://example.invalid/me/repo.git $url
cd $start
rm -rf $base
section "mkrep: --remote and --new-remote are exclusive" section "mkrep: --remote and --new-remote are exclusive"
set -l base (_mkrep_sandbox) set -l base (_mkrep_sandbox)