Skip to content

Instantly share code, notes, and snippets.

@memchr
Last active August 18, 2023 15:36
Show Gist options
  • Save memchr/cd5298f39f1cb9de9702b37f2ff6aa54 to your computer and use it in GitHub Desktop.
Save memchr/cd5298f39f1cb9de9702b37f2ff6aa54 to your computer and use it in GitHub Desktop.
#!/usr/bin/bash
set -e
source $HOME/.local/lib/bash/blib.sh
load color message
colorize
msg_style=1
DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/git-id"
IDENTITY_DATABASE="${DATA_DIR}/identities.json"
[[ ! -f $IDENTITY_DATABASE ]] && echo '[]' > "${IDENTITY_DATABASE}"
mkdir -p "${DATA_DIR}"
USAGE="\
usage: git id [<options>] <identity>
or: git id -w -m <email> [<options>] <identity>
<identity> identity
-m <email> email address
-s, --sign, --signing
sign commits
-n, --no-sign, --no-signing
do not sign commits
-f, --sign-format signing key format
-k, --ssh-key path to ssh key used for signing commits
-t, --ssh-trusted-keys
path the file containing trusted ssh public keys
-g, --gpg-key OpenPGP key used for signing commits
-w, --write, --save
save identity
-h, --help show this help
"
unset TEM
TEMP=$(getopt -o 'm:snf:k:t:g:whv' --long 'email:,sign,signing,no-sign,no-signing,sign-format:,ssh-key:,ssh-trusted-keys:,gpg-key:,write,save,help,view,show' -n "$0" -- "$@")
if [[ $? -ne 0 ]]; then
echo "Terminating..." >&2
exit 1
fi
eval set -- "$TEMP"
declare -A flags=(
[email]=
[sign]=
[sign_format]=
[ssh_key]=
[ssh_trusted_keys]=
[gpg_key]=
[save]=
)
declare -A defaults=(
[sign_format]=ssh
[ssh_key]="$HOME/.ssh/gitsigning"
[ssh_trusted_keys]="$HOME/.ssh/allowed_signers"
[gpg_key]=
)
declare -A identity
while true; do
case "$1" in
-s | --sign | --signing )
flags[sign]=true
shift 1
continue
;;
-n | --no-sign | --no-signing )
flags[nosign]=true
shift 1
continue
;;
-w | --save | --write)
flags[save]=1
shift 1
continue
;;
-m | --email)
flags[email]=$2
shift 2
continue
;;
-f | --sign-format)
case "$2" in
ssh|gpg) ;;
*)
error "unknown sign format: $2\n${color[none]}available formats: ssh, gpg"
exit 2
;;
esac
flags[sign_format]=$2
shift 2
continue
;;
-k | --ssh-key)
flags[ssh_key]=$2
shift 2
continue
;;
-t | --ssh-trusted-keys)
flags[ssh_trusted_keys]=$2
shift 2
continue
;;
-g | --gpg-key)
flags[gpg_key]=$2
shift 2
continue
;;
-v | --show | --view)
flags[view]=1
shift 1
continue
;;
-h | --help)
echo -n "$USAGE"
exit
;;
--)
shift
break
;;
esac
done
main() {
local name=$1
case "$name" in
# local user
local)
(( flags[sign] )) &&
msg "user is local, signing will not be used."
git config user.name local && git config user.email '<>' && git config commit.gpgsign false
;;
'')
error "no identity provided"
;;
*)
if (( flags[save] )); then
save_identity "$name"
elif (( flags[view] )); then
view_identity "$name"
else
set_identity "$name"
fi
;;
esac
}
set_identity() {
local name=$1
identity=()
read_identity "$name"
msg "Using identity '$name'"
git config user.name "$name"
git config user.email "${identity[email]}"
if [[ ${identity[sign]} == true ]] && [[ ${flags[nosign]} != true ]]; then
msg2 "${color[none]}enable commit signing using '${identity[sign_format]}'"
git config commit.gpgsign true
case "${identity[sign_format]}" in
ssh)
msg2 "${color[none]}signing key is ${color[yellow]}'${identity[ssh_key]}'${color[none]}"
msg2 "${color[none]}allowed signers file is ${color[yellow]}'${identity[ssh_trusted_keys]}'${color[none]}"
git config gpg.format ssh
git config gpg.ssh.allowedsignersfile "${identity[ssh_trusted_keys]}"
git config user.signingkey "${identity[ssh_key]}"
;;
gpg)
msg2 "${color[none]}signing key is ${color[yellow]}'${identity[gpg_key]}'${color[none]}"
git config gpg.format openpgp
git config user.signingkey "${identity[gpg_key]}"
;;
esac
fi
}
read_identity() {
local _jq_filter='
.[$name] |
.email,
.sign // "",
.sign_format // "",
.ssh_key // "",
.ssh_trusted_keys // "",
.gpg_key // ""
'
local _tmp
identity[name]=$1
mapfile -t _tmp < <(
jq -r --arg name "$1" "$_jq_filter" < "${IDENTITY_DATABASE}"
)
if [[ -z ${_tmp[0]} ]]; then
error "user '$name' does not exist"
exit 2
fi
identity[email]="$(get_property email "${_tmp[0]}")"
identity[sign]="$(get_property sign "${_tmp[1]}")"
identity[sign_format]="$(get_property sign_format "${_tmp[2]}")"
identity[ssh_key]="$(get_property ssh_key "${_tmp[3]}")"
identity[ssh_trusted_keys]="$(get_property ssh_trusted_keys "${_tmp[4]}")"
identity[gpg_key]="$(get_property gpg_key "${_tmp[5]}")"
}
INFO_FMT="\
name : %s
email : %s
commit signing : %s
signing key format : %s
gpg key : %s
ssh key : %s
allowed signer file : %s
"
view_identity() {
local name=$1
identity=()
read_identity "$name"
printf "$INFO_FMT" \
"${identity[name]}" \
"${identity[email]}" \
"${identity[sign]}" \
"${identity[sign_format]}" \
"${identity[gpg_key]}" \
"${identity[ssh_key]}" \
"${identity[ssh_trusted_keys]}"
}
save_identity() {
local name=$1
local email="$(get_property_with_null email)"
local sign="$(get_property_with_null sign)"
local sign_format="$(get_property_with_null sign_format)"
local ssh_key="$(get_property_with_null ssh_key)"
local ssh_trusted_keys="$(get_property_with_null ssh_trusted_keys)"
local gpg_key="$(get_property_with_null gpg_key)"
local _jq_filter=("{")
for v in email sign sign_format ssh_key ssh_trusted_keys gpg_key; do
if [[ ${!v} =~ ^(true|false)$ ]]; then
_jq_filter+=("$v: ${!v},")
elif [[ ${!v} != null ]]; then
_jq_filter+=("$v: \"${!v}\",")
fi
done
_jq_filter+=("}")
exec 3< <(jq ". * {$name: ${_jq_filter[*]}}" < "${IDENTITY_DATABASE}")
wait
cat <&3 > "${IDENTITY_DATABASE}"
}
# get a property with fallbacks and defaults
get_property() {
local key=$1
local fallback=$2
fallback=$(eval "echo \"${fallback}\"")
echo "${flags[$key]:-"${fallback:-"${defaults[$key]}"}"}"
}
# output null if there are no fallbacks or defaults, suitable for json
get_property_with_null() {
local key=$1
local fallback=$2
local ret=$(get_property "$key" "$fallback")
echo "${ret:-null}"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment