-
Notifications
You must be signed in to change notification settings - Fork 1
/
wormhole
executable file
·563 lines (459 loc) · 19.9 KB
/
wormhole
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
#!/bin/bash
# wormhole: Git bundling helper (using git bundles as a wormhole for syncing
# when direct repo access is unavailable).
# Copyright 2016--2018 Chris White. CC-BY-SA 3.0.
# Initially based on http://stackoverflow.com/a/3639182/2877364 by
# http://stackoverflow.com/users/119963/jefromi ; heavy modifications made.
### Documentation #########################################################
# Print the full help text, or, if $1 is provided, only section $1
function print_help()
{
# Basic intro
cat <<EOT
wormhole <cmd> [options]
Copyright (c) 2016--2018 Chris White, CC-BY-SA 3.0 (see source)
The two hosts are called bilbo and frodo (why not?). Bilbo is the one
that starts the process.
EOT
s='' # Which sections to print, or empty for all
(( $# )) && s="$1"
ok='' # Whether $s (if given) is valid
msg='' # What to print
brief_help='
Run "wormhole help <cmd>" for help about <cmd> (or "wormhole help" for all).
Valid <cmd>s: bilboinit, frodoinit, frodopush, bilbofetch, bilbopush,
frodofetch, touch, bundle, unbundle
'
if [[ $1 = 'brief' ]]; then
echo -n "$brief_help"
return
fi
[[ -z $s ]] && ok='yes' && msg+=\
'
Commands and <options>:
-h, --help, help
Print this help message
'
# Command-specific help --------------------
[[ -z $s || $s = bilboinit || $s = bi ]] && ok='yes' && msg+=\
'
bilboinit, bi [bundle path]
Run in your git directory on bilbo to create a bundle to start
the process. Output is [bundle path] or ./bilbo.bundle .
'
[[ -z $s || $s = frodoinit || $s = 'fi' ]] && ok='yes' && msg+=\
'
frodoinit, fi <destrepo> [bundle path]
Run on frodo to unpack bilbo.bundle from bilbo into <destrepo>.
If [bundle path] is specified, use it. Otherwise, use
./bilbo.bundle .
'
[[ -z $s || $s = frodopush || $s = fp ]] && ok='yes' && msg+=\
'
frodopush, fp [bundle path]
Run in your repo on frodo to make [bundle path]
(default ./frodo.bundle) with changes bilbo lacks.
The second and subsequent times, it will re-use the last
[bundle path] unless you specify one.
'
[[ -z $s || $s = bilbofetch || $s = bf ]] && ok='yes' && msg+=\
'
bilbofetch, bf [bundle path]
Run in your repo on bilbo to grab from frodo.
The first time, provide the path to frodo.bundle.
Afterwords, run after overwriting
frodo.bundle with the latest.
If you specify a bundle path after the first time, it will be
used as the default for future bilbofetches.
'
[[ -z $s || $s = bilbopush || $s = bp ]] && ok='yes' && msg+=\
'
bilbopush, bp [bundle path]
Run in your repo on bilbo to make [bundle path] with
changes frodo lacks. The default bundle path is the
path provided to bilboinit. Any time you specify a
[bundle path], that path will be remembered and used
by default on subsequent bilbopushes.
'
[[ -z $s || $s = frodofetch || $s = ff ]] && ok='yes' && msg+=\
'
frodofetch, ff
Run on frodo to grab bilbo.bundle, analogously to bilbofetch.
Uses the bundle path that was used for frodoinit.
Run after overwriting bilbo.bundle with the latest.
If you specify a bundle path after the first time, it will be
used as the default for future frodofetches.
'
[[ -z $s || $s = touch ]] && ok='yes' && msg+=\
'
touch
Creates an empty commit in the current repo. Useful if you
want to send a bundle to resync, but you have not made any
real commits yet.
'
[[ -z $s || $s = bundle ]] && ok='yes' && msg+=\
'
bundle <filename>
Creates a bundle of the entire repo in <filename>.
You can unpack that bundle using "wormhole unbundle".
'
[[ -z $s || $s = unbundle ]] && ok='yes' && msg+=\
'
unbundle <destdir> [bundle path]
Unpack a bundle into <destdir>.
If [bundle path] is specified, use it. Otherwise, use
"./bilbo.bundle".
'
# --------------------
[[ -z "$ok" ]] && msg+="Unknown command '$s'."$'\n'"${brief_help}"
[[ $msg ]] && echo -n "$msg" # -n because the text already has \n's
} #print_help()
### Definitions ###########################################################
KEY_BILBOBUNDLE="vendor.chrisw.bilbobundle"
# bilbopush: Filename of the bundle to push to
KEY_FRODOBUNDLE="vendor.chrisw.frodobundle"
# frodopush: Filename of the bundle to push to
KEY_BILBOMTIME="vendor.chrisw.bilbomtime"
# frodofetch: Last-modified time of the bilbo bundle, in sec. since Epoch
KEY_FRODOMTIME="vendor.chrisw.frodomtime"
# bilbofetch: Last-modified time of the frodo bundle, in sec. since Epoch
function echoexit()
{ #usage: echoexit <exit code> <message to print to stderr>
local code="$1"
shift
echo "$@" 1>&2
exit "$code"
} #echoexit()
function abspath() {
# generate absolute path from relative path
# $1 : relative filename, which must exist.
# return : absolute path
# From http://stackoverflow.com/a/23002317/2877364 by
# http://stackoverflow.com/users/2709/alexander-klimetschek ,
# with docs updated by chrisw.
# Also updated with echoexit() calls per
# https://github.com/koalaman/shellcheck/wiki/SC2164
if [[ -d "$1" ]]; then # a directory
( cd "$1" || echoexit 3 "can't cd to $1"; pwd)
elif [[ -f "$1" ]]; then # a file
if [[ $1 = /* ]]; then # already absolute
echo "$1"
elif [[ $1 = */* ]]; then # relative path
echo "$(cd "${1%/*}" ||
echoexit 3 "can't cd to ${1%/*}"; pwd)/${1##*/}"
else # in current directory
echo "$(pwd)/$1"
fi
fi
} #abspath()
function confirmrepo() {
# Die if we are not in a repo.
# $1 is the command we are running.
git rev-parse --show-toplevel &>/dev/null ||
echoexit 2 "Please cd to your repo, than try $1 again"
} #confirmrepo()
### Command-line processing ###############################################
argv=("$@")
want_help=''
# thanks to http://wiki.bash-hackers.org/howto/getopts_tutorial
while getopts ':h' opt ; do
case "$opt" in
(h) want_help='yes'
break
;;
(*) if [[ "${argv[$OPTIND-1]}" = "--help" ]]; then
want_help='yes'
shift # OK since we're about to break
break
else
echoexit 1 "Unknown option ${argv[$OPTIND-2]}"
fi
;;
esac
done
shift $((OPTIND-1)) # skip ahead to non-options
if [[ $# -lt 1 ]]; then
if [[ $want_help ]]; then
print_help
else
print_help brief # to stdout so it can be paged
fi
exit 0
fi
# Which command has the caller requested?
cmd="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
# thanks to http://stackoverflow.com/questions/2264428#comment11480091_2264537
# by http://stackoverflow.com/users/712605/richard-hansen
shift # get rid of the command
if [[ $cmd = 'help' ]]; then
want_help='yes'
cmd="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
shift
fi
if [[ $want_help ]]; then
print_help "$cmd"
exit 0
fi
# Sanity checks that we're on the right machine. Not guaranteed to catch
# everything.
if echo "$cmd" | grep -iq '^\(bilbo\|b.$\)' ; then
# bilbo commands fail if we're on frodo
(git config --local "$KEY_FRODOBUNDLE" ||
git config remote.bilbo.url
) &>/dev/null &&
echoexit 1 "Can't run $cmd on frodo"
elif echo "$cmd" | grep -iq '^\(frodo\|f.$\)' ; then
# frodo commands fail if we're on bilbo
(git config --local "$KEY_BILBOBUNDLE" ||
git config remote.frodo.url
) &>/dev/null &&
echoexit 1 "Can't run $cmd on bilbo"
fi
### Main processing #######################################################
case "$cmd" in
(bilboinit|bi|bundle)
# on bilbo, the initial home of the repo.
# bilboinit is bundle+metadata.
confirmrepo "$cmd"
destbundle="${1:-bilbo.bundle}" # after :- is the default
touch "$destbundle" || echoexit 2 "Can't write to $destbundle"
destbundle="$(abspath "$destbundle")" # must be after `touch`
[[ -f $destbundle && -w $destbundle ]] ||
echoexit 2 "$destbundle isn't a writeable file"
# Stash the bundle name for bilbopush
[[ $cmd != bundle ]] &&
git config --local "$KEY_BILBOBUNDLE" "$destbundle"
# Make the bundle
git bundle create "$destbundle" --branches --tags ||
echoexit $? "Couldn't create $destbundle"
# Pass the git exit code to the outside world in case it's useful.
# Note: --branches --tags, NOT --all, because the recipient
# doesn't care where any other remotes we have may be.
# See git-rev-list(1) for more.
git bundle verify "$destbundle" ||
echoexit $? "Couldn't verify $destbundle"
[[ $cmd != bundle ]] && echo ">>> Now send $destbundle to frodo."
;;
(frodoinit|'fi'|unbundle)
# Continuing on frodo after transferring the bundle.
# frodoinit = unbundle+metadata.
# Check options
[[ $# -lt 1 ]] && echoexit 1 "Need a destination repo name"
destrepo="$1"
srcbundle="${2:-bilbo.bundle}"
[[ -f "$srcbundle" && -r "$srcbundle" ]] ||
echoexit 2 "Can't access $srcbundle"
srcbundle="$(abspath "$srcbundle")"
[[ $cmd = unbundle ]] && origin="bundle"
[[ $cmd != unbundle ]] && origin="bilbo"
# Clone the bundle into frodo's repo
echo ">>> Cloning from $srcbundle"
git clone -o "$origin" "$srcbundle" "$destrepo" || exit $?
echo ">>> If there's a 'nonexistent ref' warning above, don't worry about it."
# Note: no need to save the path to bilbo.bundle in the config
# file, since git clone will save it as the remote pull URL.
cd "$destrepo" || echoexit 2 "Can't cd to $destrepo"
# so the following will work
# Set up remote tracking branches for all the branches in the bundle.
# Note: this relies on the fact that branch names cannot include spaces.
for branchname in $(git branch -a | grep "remotes/$origin" |
sed 's/^.*remotes\/'"$origin"'\///') ; do
git branch "$branchname" "remotes/$origin/$branchname" ||
echoexit $? "Can't make remote-tracking branch for $branchname"
done
# Check out the master branch. Assumes master exists.
git checkout master || echoexit $? "Can't check out master"
# Thanks to https://git.wiki.kernel.org/index.php/Git_FAQ#How_do_I_clone_a_repository_with_all_remotely_tracked_branches.3F ,
# with thanks to http://stackoverflow.com/a/7216269/2877364 by
# http://stackoverflow.com/users/915724/dave for the link.
# you now have a clone, complete with remote branches and tags
if [[ $cmd != unbundle ]]; then
# On success, stash the bundle's mtime for later sanity checks
if mtime="$(stat -c %Y "$srcbundle" 2>/dev/null )" ; then
git config --local --int "$KEY_BILBOMTIME" "$mtime"
fi
fi
# Report
echo ">>> Cloned $srcbundle into $destrepo"
echo ">>> Remotes:"
git remote -v
echo ">>> Branches:"
git branch -a
echo ">>> Status:"
git status
;;
(frodopush|fp)
# make some commits on frodo; time to transfer back to bilbo
# use the known master branch of bilbo as a basis
confirmrepo "$cmd"
if [[ $# -ge 1 ]]; then
destbundle="$1"
touch "$destbundle" # or -w will fail on the first frodopush
[[ -w "$destbundle" ]] || echoexit 1 "Can't write to $destbundle"
destbundle="$(abspath "$destbundle")"
git config --local "$KEY_FRODOBUNDLE" "$destbundle"
elif ! destbundle="$(git config --local --path "$KEY_FRODOBUNDLE")" ; then
touch frodo.bundle
destbundle="$(abspath "frodo.bundle")"
fi # bundle name specified else
echo ">>> Don't worry about any 'excluded by the rev-list options'
messages. Those are things bilbo already has."
if git bundle create "$destbundle" ^bilbo/master --branches --tags ; then
echo ">>> Now send $destbundle to bilbo."
else
echoexit $? \
">>> If it says 'empty bundle', that means bilbo has everything you do.
If you need to send a bundle anyway, run 'wormhole touch' and then run
'wormhole frodopush' again."
fi
;;
(bilbofetch|bf)
# After copying frodo.bundle back over to bilbo, **replacing**
# any existing frodo.bundle, continue on bilbo.
confirmrepo "$cmd"
# See if the remote already exists. Thanks to
# http://stackoverflow.com/a/26843249/2877364 by
# http://stackoverflow.com/users/96823/tig
# Whether this is the first time we've run bilbofetch
firsttime=
# The source bundle to use
srcbundle="$(git config --path remote.frodo.url)" || firsttime=y
# If remote doesn't exist, this is the first time we're
# fetching from frodo.
[[ $firsttime && $# -lt 1 ]] &&
echoexit 1 "Need a path to the bundle from frodo"
# Command line can override the remote.frodo.url path.
[[ $# -ge 1 ]] && srcbundle="$1"
[[ -r "$srcbundle" ]] || echoexit 2 "Can't read $srcbundle"
if [[ $firsttime ]]; then
# Remote doesn't exist, so this is the first time we're
# fetching from frodo.
# Add the remote
git bundle verify "$srcbundle" || exit $?
git remote add frodo "$srcbundle" || exit $?
echo -n ">>> Fetching from "
git config remote.frodo.url || # a sanity check
echoexit $? "...no, can't find remote frodo (?!!?!)"
# fetch all the refs from the remote
# (creating remote branches like frodo/master if necessary)
git fetch frodo || exit $?
# Make bilbo's master track frodo's master
git branch --set-upstream-to frodo/master master || exit $?
# Thanks to http://stackoverflow.com/a/6300386/2877364
# by http://stackoverflow.com/users/6309/vonc
# Since we succeeded, stash the bundle's mtime for later sanity checks
if mtime="$(stat -c %Y "$srcbundle" 2>/dev/null)" ; then
git config --local --int "$KEY_FRODOMTIME" "$mtime"
fi
shouldwarn="" # no warning about mtime
else #subsequent times
# Sanity check: has the mtime changed? Warn if not.
if mtime="$(stat -c %Y "$srcbundle" 2>/dev/null)" ; then
shouldwarn=$( # should we print the warning below?
lastmtime="$(git config --local --int "$KEY_FRODOMTIME")" &&
[[ ! "$mtime" -gt "$lastmtime" ]] &&
echo 1)
fi
# If the user specified a new bundle path, use it going forward
[[ $# -ge 1 ]] && git config --path remote.frodo.url "$srcbundle"
# Fetch
echo -n ">>> Fetching from "
git config remote.frodo.url ||
echoexit $? "...no, can't find remote frodo"
# exit is a sanity check
git bundle verify "$(git config remote.frodo.url)" || exit $?
git fetch frodo || exit $?
# Since we succeeded, stash the bundle's mtime for later sanity checks
if mtime="$(stat -c %Y "$srcbundle" 2>/dev/null)"; then
git config --local --int "$KEY_FRODOMTIME" "$mtime"
fi
fi #first time else
# Report
git status
if [[ "$shouldwarn" ]]; then
echo \
">>> WARNING: $srcbundle does not appear to be newer than at
the last bilbofetch. Did you update it with the latest?" 1>&2
fi
echo \
'>>> If all of that succeeded, you can "git merge" or "git pull" the changes
from frodo (e.g., if frodo/master is ahead of your master).
HOWEVER, if bilbo and frodo have diverged, rebase your commits on frodo
rather than merging! E.g., git rebase frodo/master master .'
;;
(bilbopush|bp)
# make some commits on bilbo; time to transfer to frodo
# again, use the known master branch as a basis
confirmrepo "$cmd"
if [[ $# -ge 1 ]]; then
destbundle="$1"
touch "$destbundle" # or -w will fail on the first bilbopush
[[ -w "$destbundle" ]] || echoexit 1 "Can't write to $destbundle"
git config --local "$KEY_BILBOBUNDLE" "$(abspath "$destbundle")"
elif ! destbundle="$(git config --local --path "$KEY_BILBOBUNDLE")" ; then
touch bilbo.bundle
destbundle="$(abspath "bilbo.bundle")"
fi # bundle name specified else
echo ">>> Don't worry about any 'excluded by the rev-list options'
messages. Those are things frodo already has."
if git bundle create "$destbundle" ^frodo/master --branches --tags ; then
echo ">>> Now send $destbundle to frodo."
else
echoexit $? \
">>> If it says 'empty bundle', that means frodo has everything you do.
If you need to send a bundle anyway, run 'wormhole touch' and then run
'wormhole bilbopush' again."
fi
;;
(frodofetch|ff)
# After copying the bundle to frodo, **replacing** the original bundle,
# run this to update all the refs
confirmrepo "$cmd"
srcbundle="$(git config --path remote.bilbo.url)" ||
echoexit 1 "The bilbo remote doesn't seem to exist!"
# Command-line overrides the stored bundle path
if [[ $# -ge 1 ]]; then
srcbundle="$1"
[[ -r "$srcbundle" ]] || echoexit 2 "Can't read $srcbundle"
git config --path remote.bilbo.url "$srcbundle"
fi
# Sanity check: has the mtime changed? Warn if not.
if mtime="$(stat -c %Y "$srcbundle" 2>/dev/null)" ; then
shouldwarn=$( # should we print the warning below?
lastmtime=$(git config --local --int "$KEY_BILBOMTIME") &&
[[ ! "$mtime" -gt "$lastmtime" ]] &&
echo 1)
fi
# Fetch
echo ">>> Fetching from $srcbundle"
git bundle verify "$srcbundle" || exit $?
git fetch bilbo || exit $?
# Since we succeeded, stash the bundle's mtime for later sanity checks
if mtime="$(stat -c %Y "$srcbundle" 2>/dev/null)" ; then
git config --local --int "$KEY_BILBOMTIME" "$mtime"
fi
# Report
git status
if [[ $shouldwarn ]]; then
echo \
">>> WARNING: $srcbundle does not appear to be newer than at
the last frodofetch. Did you update it with the latest?" 1>&2
fi
echo \
'>>> If all of that succeeded, you can "git merge" or "git pull" the changes
from bilbo (e.g., if bilbo/master is ahead of your master).
HOWEVER, if bilbo and frodo have diverged, rebase your commits on bilbo
rather than merging! E.g., git rebase bilbo/master master .'
;;
(touch)
confirmrepo "$cmd"
git commit --allow-empty -m 'Empty commit from "wormhole touch"
See https://github.com/cxw42/git-tools/ for more about wormhole.'
;;
(*)
echoexit 1 \
"$(printf "Unknown command \"%s\".\\nRun \"wormhole -h\" for help." \
"$cmd")"
;;
esac #command
# vi: set ts=4 sts=4 sw=4 expandtab ai ff=unix ft=sh: #