-
Notifications
You must be signed in to change notification settings - Fork 18
/
gister
executable file
·385 lines (339 loc) · 10.9 KB
/
gister
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
385
#!/bin/sh
SEMVER='v2.4.1'
# Mac OS X compatibility
if [ "$(uname)" = "Darwin" ]; then
SED=gsed
DATE=gdate
else
SED=sed
DATE=date
fi
# Ubuntu compatibility
if type gist > /dev/null; then
GIST=gist
else
GIST=gist-paste
fi
help() {
cat<<'END'
gister -- shell script to access https://gist.github.com
gister [ACTION]
gister description file.txt [...]
Actions:
sync sync all your gists
search regexp code search (command line)
check report dirty gist repositories
export id dir branch export a gist to other git repo
migrate migrate from <1.0.0
version version
help this help page
readme display README
Usage:
`gister init` will associate your gists with your GitHub account and ask you where to store local copies of your gist.
Run `gister sync` to sync all your gists (created and starred).
`gister description file.txt ...` will create the gist with the provided description,
clone the gist repo and put the gistid to clipborad.
`gister` will pass all arguments to gist as `gist -c -o -d description ...`, so you can use other options that gist understands,
e.g. `gister description -P` will work.
`gister check` reports all dirty (containing uncommited changes) gist repositories.
Within the root directory of a git repository,
`gister export id dir branch_name` exports a gist (id) into a subdirectory (dir).
END
}
main() {
gisthome=${GIST_HOME:=$(git config --global --path --get gist.home)}
if test -f $HOME/.gist; then
github_oauth_token=$(cat $HOME/.gist)
else
echo 'You need a github oauth2 token.'
fi
set -e
if test -n "$GISTER_DEBUG"; then
set -x
fi
case $1 in
check) check;;
fetchall) fetchall;;
help|-h|--help) help;;
export) export_to $2 $3 $4;;
init) init;;
migrate) migrate;;
search) code_search $2;;
sync) sync;;
version) echo gister $SEMVER;;
*) publish "$@";;
esac
}
fetchgist() {
if test -f $gisthome/$1.list; then
mv $gisthome/$1.list $gisthome/$1.list.backup
fi
curl -s -H "Authorization: token $github_oauth_token" "https://api.github.com/$2?per_page=100" | jq . > $gisthome/$1.list.dirty
[ "$(cat "$gisthome/$1.list.dirty" | jq -r .message?)" = 'Bad credentials' ] && { echo "Bad Credentials; clear your credentials and re-init"; exit 1; }
for i in $(seq 2 100000); do
if ! (curl -s -H "Authorization: token $github_oauth_token" "https://api.github.com/$2?page=$i&per_page=100" | jq '.' | grep --silent '^\[]$'); then
curl -s -H "Authorization: token $github_oauth_token" "https://api.github.com/$2?page=$i&per_page=100" | jq '.' >> $gisthome/$1.list.dirty
else
if [ $i -eq 100000 ]; then
echo 'I can only fetch up to 10 million gists for you.'
fi
break
fi
done
# If you have more than 100 gists, gists.list will contain more than one array.
# Thus we need to combine them into one.
cat $gisthome/$1.list.dirty |
# delete all `[`s
$SED -r '/^\[$/d' |
# recover the first `[`
$SED -r '1 i [' |
# replace all `]`s to `,`
$SED -r 's/^]$/,/g' |
# delete `,` on the last line (JSON does not permit this!)
$SED -r '$d' |
# recover the last `]`
$SED -r '$ a ]' > $gisthome/$1.list
}
fetchlist() {
fetchgist gists gists
fetchgist starred "gists/starred"
}
fetchall() {
echo '`fetchall` is deprecated. Use `gister sync` instead.'
sync
}
check_command() {
to_check_command=$1
command -v $to_check_command >/dev/null 2>&1
}
get_paste() {
# Linux, BSD, etc
if check_command xclip; then
xclip -o
elif check_command xsel; then
xsel -o
# Mac OS X
elif check_command pbpaste; then
pbpaste
# Windows (Cygwin)
elif check_command getclip; then
getclip
else
echo 'Error: No clipboard command found!'
fi
}
update_csearch_index() {
if (which cindex > /dev/null); then
export CSEARCHINDEX=$gisthome/.csearchindex
cindex $gisthome/tree
else # do nothing
echo 'codesearch is not installed. Skip building index.'
fi
}
publish() {
local gist_description gist_argv
gist_description="$1"
shift 1
if [ $# -eq 0 ]; then
help
else
gist_argv=$@
# post gist and open it in browser
$GIST -c -d "$gist_description" $gist_argv
# record the id
local gist_id=$(get_paste | grep -o -E '/[0-9a-f]+$' | $SED -e 's/\///')
# add a record
cd $gisthome
curl -s -H "Authorization: token $github_oauth_token" 'https://api.github.com/gists?per_page=1' >> gists.list
# clone
cd $gisthome/tree
if [ -z $GISTER_USE_HTTPS ]; then
git clone [email protected]:$gist_id.git --separate-git-dir $gisthome/repo/$gist_id
else
git clone https://gist.github.com/$gist_id.git --separate-git-dir $gisthome/repo/$gist_id
fi
# code search index
update_csearch_index
fi
}
code_search() {
if (which csearch > /dev/null); then
export CSEARCHINDEX=$gisthome/.csearchindex
csearch -i -l -n "$1"
else
grep -r -E "$1" $gisthome/tree
fi
}
export_to() {
local gistid subdirectory branch_name
gistid="$1"
subdirectory="$2"
branch_name="$3"
git remote add "$gistid" "$gisthome/tree/$gistid"
git fetch "$gistid"
git checkout -b "$branch_name" "$gistid/master"
mkdir "$subdirectory"
git mv -k * "$subdirectory"
git commit -m "Import from gist $gistid"
git checkout master
git merge "$branch_name" --allow-unrelated-histories
git remote rm "$gistid"
git branch -d "$branch_name"
}
init() {
# login
if ! test -f $HOME/.gist; then
echo 'We need your username and password to get an OAuth2 token (with the "gist" permission).'
echo 'We will not store your password.'
$GIST --login
echo 'Your GitHub OAuth2 token is stored at ~/.gist'
fi
# store
echo 'Where do you want to store local copies of your gists?'
read -p 'Enter full path to the directory: ' gist_store_directory
git config --global gist.home $gist_store_directory
gist_store_directory=$(git config --global --path --get gist.home)
mkdir -p $gist_store_directory/tree $gist_store_directory/repo
echo "Your gists will be stored at $gist_store_directory"
echo 'You can overwrite this using environment variable $GIST_HOME'
}
migrate() {
# migrate to new storage
cd $gisthome
mkdir -p tree
ls --file-type --hide gonzui.db --hide tree --hide repo | grep '/$' | xargs -I '{}' mv '{}' tree
mkdir -p repo
cd tree
ls | xargs -I '{}' git init --separate-git-dir ../repo/'{}' '{}'
# index via new engine
update_csearch_index
}
sync_gists() {
cd $gisthome
local gists_list_length=$(cat $gisthome/gists.list | jq '. | length')
for i in $(seq 0 $(($gists_list_length - 1))); do
local gist_id=$(cat $gisthome/gists.list | jq --raw-output ".[$i].id")
local gist_updated_at=$(cat $gisthome/gists.list | jq --raw-output ".[$i].updated_at")
sync_gist $gist_id $gist_updated_at "own"
done
}
sync_starred() {
cd $gisthome
local gists_list_length=$(cat $gisthome/starred.list | jq '. | length')
local github_user=$(git config --global github.user)
local myself=${github_user:-$(whoami)}
for i in $(seq 0 $(($gists_list_length - 1))); do
local gist_owner=$(cat $gisthome/starred.list | jq --raw-output ".[$i].owner.login")
if [ "$gist_owner" != "$myself" ]; then
local gist_id=$(cat $gisthome/starred.list | jq --raw-output ".[$i].id")
local gist_updated_at=$(cat $gisthome/starred.list | jq --raw-output ".[$i].updated_at")
sync_gist $gist_id $gist_updated_at "starred"
fi
done
}
sync() {
fetchlist
sync_gists
sync_starred
mark_deleted_gists
update_csearch_index
}
is_dirty() {
if [ $(git status --porcelain | wc -l) -eq 0 ]; then
return 1 # clean
else
return 0 # dirty
fi
}
retry_three_times() {
local retry_command="$1"
# fibonacci backoff
local first_retry="$retry_command || (sleep 2; $retry_command)"
local second_retry="($first_retry) || (sleep 8; $retry_command)"
local third_retry="($second_retry) || (sleep 34; $retry_command)"
eval "$third_retry"
}
retry_git() {
retry_three_times "git $1 >/dev/null"
}
sync_gist() {
local gist_id=$1
local gist_updated_at=$2
local gist_type=$3
echo "syncing $gist_id"
if [ \( -d $gisthome/tree/$gist_id \) -a \( -d $gisthome/repo/$gist_id \) ]; then
cd $gisthome/tree/$gist_id
# Compare update time to skip already up to date repos.
# This will speed sync on slow network connection.
# This may contain a bug if the gist is updated by someone else, e.g. forking.
local last_commit_unixtime=$(git log -1 --pretty=format:%ct)
local last_updated_unixtime=$($DATE --date="$gist_updated_at" +"%s")
local time_difference=$(($last_commit_unixtime - $last_updated_unixtime))
local time_difference_abs=$(echo $time_difference | tr -d -)
# Local machine's and GitHub's time may differ slightly.
if test $time_difference_abs -gt 1; then
if [ $gist_type = "own" ]; then
if (which legit > /dev/null); then
legit sync > /dev/null
else
if (is_dirty); then
if [ -z $GISTER_AUTO_COMMIT ]; then
echo "DIRTY $gist_id"
else
echo "DIRTY $gist_id, auto committing"
git add . && git commit -m "automated update" &&
retry_git pull && retry_git push > /dev/null
fi
else
retry_git pull && retry_git push > /dev/null
fi
fi
elif [ $gist_type = "starred" ]; then
if (is_dirty); then
echo "DIRTY $gist_id"
else
retry_git pull
fi
else
echo "You have encountered a bug."
echo "Please report it at https://github.com/weakish/gister/issues"
fi
else
if [ -n $GISTER_DEBUG ]; then
echo "SKIPPED $gist_id (up to date)"
fi
fi
else
cd $gisthome/tree
# Incase someone deleted the working directory but left the repo directory
# or vice versa.
rm -rf $gisthome/tree/$gist_id
rm -rf $gisthome/repo/$gist_id
if [ -z $GISTER_USE_HTTPS ]; then
git clone [email protected]:$gist_id.git --separate-git-dir $gisthome/repo/$gist_id
else
git clone https://gist.github.com/$gist_id.git --separate-git-dir $gisthome/repo/$gist_id
fi
fi
}
mark_deleted_gists() {
cd $gisthome/tree
for gist_id in [0-9a-f]*; do
if ! (grep -F -q '"git_pull_url": "https://gist.github.com/'$gist_id'.git"' $gisthome/gists.list ||
grep -F -q '"git_pull_url": "https://gist.github.com/'$gist_id'.git"' $gisthome/starred.list); then
mv $gist_id _$gist_id
sed -i -r "s#tree/$argv[1]#tree/_$gist_id#" $gisthome/repo/$gist_id/config
fi
done
}
check() {
cd $gisthome/tree
for d in [0-9a-f]*; do # excluding deleted gists
cd "$d" > /dev/null
if (is_dirty); then
echo "DIRTY $d"
fi
cd $gisthome/tree
done
}
main "$@"