-
Notifications
You must be signed in to change notification settings - Fork 1
/
git-id
executable file
·384 lines (331 loc) · 11.2 KB
/
git-id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#!/usr/bin/env bash
# vim: set tabstop=4 expandtab shiftwidth=4 bg=dark:
# vim: set textwidth=80:
#==============================================================================
#
# File : git-id
# Description : manage Git user identities for the current session
#
# Copyright (c) 2015, Kilian Cavalotti <kilian@stanford.edu>
#
# Licensed under the GNU General Public License, v2
#==============================================================================
# -- environment setup --------------------------------------------------------
# check if the current script has been sourced or executed
[[ "$0" != "${BASH_SOURCE[0]}" ]] && gitid_sourced=1 || gitid_sourced=0
# get script name and path
gitid_path=$(readlink -m "${BASH_SOURCE[0]}")
gitid_name=${gitid_path##*/}
# -- functions ----------------------------------------------------------------
# this function is exported in the user environment and embeds all available
# actions
git-id() {
usage() {
cat << EOU
$gitid_name: manage Git user identities
Actions: add add a new identity, or update an existing one
delete remove an existing identity
list list existing identities
show show identity info
current display current identity
use use the selected identity
reset unset the environment, reset to the default id
help this message
Usage: $gitid_name add <username> <full name> <email> <s:sshkey>|<t:token>
$gitid_name delete <username>
$gitid_name list
$gitid_name show <username>
$gitid_name current
$gitid_name use <username>
$gitid_name reset
$gitid_name help
Notes:
1. a path to a SSH private key *or* an access token can be provided, but not
both, they're mutually exclusive.
2. The type of credential should be indicated with the "s:" (for SSH key) or
"t:" (for token) prefix.
3. when invoked without any parameters, git-id acts as a command to use in
GIT_SSH and/or GIT_ASKPASS
EOU
return 0
}
## git functions ----------------------------------------------------------
# sanity check: verify that Git is installed, and that we can access a
# repo's local git config
git_sanity_check() {
type git &>/dev/null || {
echo "Really? Git is not even installed?"
return 1
}
git rev-parse --show-toplevel &>/dev/null || {
echo "The current directory doesn't seem to be part of a Git repo."
return 1
}
}
# lookup key in gitconfig identity namespace
# @input $1: identity
# $2: key (name, email...)
# @output value
lookup() {
local id="$1"
local key="$2"
local v
v=$(git config "identity.$id.$key") || return 1
echo "$v"
}
## actions ----------------------------------------------------------------
# add a new (or update an exiting) identity to the list of identities for
# the current repository
# @input $1: identity
# $2: full name (quoted if needed)
# $3: email address
# $4: path to the SSH key to be used with that id, *OR*
# access token
# @output: status message
add_id() {
local id="$1"
local name="$2"
local email="$3"
local cred="$4"
# check if id already exists
lookup "$id" name &>/dev/null \
&& actioned="updated" \
|| actioned="created"
# check if cred is a file (sshkey) or not (token)
case ${cred%:*} in
s) sshkey=${cred#*:}
;;
t) token=${cred#*:}
;;
*) echo "error: invalid credential type (should be s: or t:)"
return 1
;;
esac
# check if $sshkey is a valid private key
[[ -n ${sshkey:-} ]] && {
head -n1 "$sshkey" |& grep -q "PRIVATE KEY" || {
mesg="error: $sshkey doesn't appear to be a valid "
mesg+="SSH private key."
echo "$mesg"
return 1
}
}
# store the identity in the local git config
{ git config identity."$id".name "$name"
git config identity."$id".email "$email"
[[ -n ${sshkey:-} ]] && git config identity."$id".sshkey "$sshkey"
[[ -n ${token:-} ]] && git config identity."$id".token "$token"
} && echo "success: identity $id $actioned."
}
# remove an identity from the list of identities for the current repository
# @input $1: identity
# @output status message
remove_id() {
local id="$1"
# check if id already exists
lookup "$id" name &>/dev/null || {
echo "error: identity $id does not exist."
return 1
}
git config --remove-section identity."$id" \
&& echo "success: identity $id removed."
}
# list existing identities for the current repository
# @input n/a
# @output sorted list of identities
list_ids() {
git config --get-regexp ^identity\. | cut -d. -f2 | sort -u
}
# display identity details
# @input $1: identity
# @output id name, email, sshkey, token
show_id() {
local id="$1"
local name email sshkey token
# check if id exists
lookup "$id" name &>/dev/null || {
echo "$id is not defined"
return 1
}
name="$(lookup "$id" name)"
email="$(lookup "$id" email)"
sshkey="$(lookup "$id" sshkey)"
token="$(lookup "$id" token)"
[[ "$name" == "" ]] && {
echo "error: id doesn't exist"
return 1
}
echo "[$id]"
echo " name: $name"
echo " email: $email"
if [[ -n ${sshkey:-} ]]; then echo "ssh key: ${sshkey:-}"; fi
if [[ -n ${token:-} ]]; then echo " token: ${token:-}"; fi
}
# set environment to use specific identity
# @input $1: identity
# @output: status message
use_id() {
local id=$1
local name email sshkey token
# check if id exists
lookup "$id" name &>/dev/null || {
echo "$id is not defined"
return 1
}
# get id details
name="$(lookup "$id" name)"
email="$(lookup "$id" email)"
sshkey="$(lookup "$id" sshkey)"
token="$(lookup "$id" token)"
# check if identity already set
[[ -n ${GIT_ID:-} ]] && \
echo "switching Git id from $GIT_ID to $id" || \
echo "using identity: $id"
# set the environment
export GIT_ID="$id"
export GIT_AUTHOR_NAME="$name"
export GIT_AUTHOR_EMAIL="$email"
export GIT_COMMITTER_NAME="$name"
export GIT_COMMITTER_EMAIL="$email"
# set GIT_SSH to this very script if we have a ssh key
[[ -n ${sshkey:-} ]] && {
export GIT_SSH=$gitid_path
}
# set GIT_ASKPASS to this very script if we have a token
[[ -n ${token:-} ]] && {
export GIT_ASKPASS=$gitid_path
}
}
# list existing identities for the current repository
# @input n/a
# @output sorted list of identities
list_ids() {
git config --get-regexp ^identity\. | cut -d. -f2 | sort -u
}
# read current identitiy from the environment
# @input n/a
# @output current identity
current_id() {
local id=${GIT_ID:-}
[[ $id == "" ]] && {
echo "error: identity not set"
return 1
} || echo "$id"
}
# reset environment
# @input n/a
# @output status message
env_reset() {
# unset variables
unset GIT_ID
unset GIT_AUTHOR_NAME
unset GIT_AUTHOR_EMAIL
unset GIT_COMMITTER_NAME
unset GIT_COMMITTER_EMAIL
unset GIT_SSH
unset GIT_ASKPASS
echo "environment unset"
}
# function called when the script is executed (not sourced) and used from
# the GIT_SSH env variable. Used to specify different SSH key when
# pulling/pushing to Git remotes.
# Directly executed by Git, so no input/output interaction
# Needs to return 0, otherwise git may receive a SIGPIPE and display errors
# like "failed to push some refs"
git_ssh() {
local id=$GIT_ID
ssh -i "$(lookup "$id" sshkey)" $@
exit 0
}
# function called when the script is executed (not sourced) and used from
# the GIT_ASKPASS env variable. Used to provide username and password
# pulling/pushing to Git remotes.
# Directly executed by Git, so no input/output interaction
# Needs to return 0, otherwise git may receive a SIGPIPE and display errors
# like "failed to push some refs"
# @input $1: "Username" or "Password"
# @output requested information
git_askpass() {
local action=$1
local id=$GIT_ID
case $action in
Username) echo "$id" ;;
Password) lookup "$id" token ;;
esac
exit 0
}
# -- main -----------------------------------------------------------------
# get action from the command line arguments
action="${1:-}"
git_sanity_check || return 1
case $action in
add)
shift
[[ $# == 4 ]] || {
usage
return 1
} && add_id "$1" "$2" "$3" "$4"
;;
delete|remove)
shift
if [[ $# == 1 ]]; then
remove_id "$1"
else
usage
return 1
fi
;;
list)
list_ids
;;
show)
shift
if [[ $# == 1 ]]; then
show_id "$1"
else
usage
return 1
fi
;;
current)
current_id
;;
use)
shift
if [[ $# == 1 ]]; then
[[ $gitid_sourced == 1 ]] || {
echo -n "error: identity cannot be set by executing the "
echo -n "script directly, you need to source the script "
echo "and call the function instead ('git-id use $1')"
return 1
}
use_id "$1" || return 1
else
usage
return 1
fi
;;
reset)
env_reset
return 1
;;
Username*|Password*)
# getting a Username/Password prompt means we're being used as
# GIT_ASKPASS, return username and token
git_askpass "$action"
;;
*@*|*.*)
# anything like user@host.tld means we're being used as GIT_SSH
git_ssh $@
;;
help|*)
usage
return 1
;;
esac
}
# -- excution entry point -----------------------------------------------------
# if executed directly, pass the arguments to the correct function
if [[ $gitid_sourced == 0 ]]; then git-id $@; fi
## returns if script is sourced, or exits if it's executed
return 2>/dev/null || exit