-
-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathsync_git_repo_from_pc1_to_pc2.sh
executable file
·786 lines (664 loc) · 34.7 KB
/
sync_git_repo_from_pc1_to_pc2.sh
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
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
#!/bin/bash
# Author: Gabriel Staples
# This file is part of eRCaGuy_dotfiles: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles
# sync_git_repo_from_pc1_to_pc2.sh
#
# Sometimes you need to develop software on one machine (ex: a decent laptop, running an IDE like
# Eclipse) while building on a remote server machine (ex: a powerful desktop, or a paid cloud-based
# server such as AWS or Google Cloud--like this guy: https://matttrent.com/remote-development/). The
# problem, however, is "how do I sync from the machine I work on to the machine I build on?". This
# script answers that problem. It uses git to sync from one to the other. Git is preferred over
# rsync or other sync tools since they try to sync *everything* and on large repos they take FOREVER
# (dozens of minutes, to hours)! This script is lightning-fast (seconds) and ***safe***, because it
# always backs up any uncommitted changes you have on either PC1 or PC2 before changing anything!
#
# A typical run might take <= ~30 seconds to 1 minute, and require ~25 MB of data (which you care
# about if running on a hotspot on your cell phone).
#
# Run it from the *client* machine where you develop code (PC1), NOT the server where you will build
# the code (PC2)!
#
# It MUST be run from a directory inside the repo you are syncing FROM.
# -------------
# INSTALLATION:
# -------------
#
# See also: "README_git-sync_repo_from_pc1_to_pc2.md" for more information.
#
# 1. Copy the ".bash_aliases_private" file to your home directory:
# cp -i path/to/eRCaGuy_dotfiles/home/.sync_git_repo_private ~
# 2. Edit the copy of that file in your home dir ("~/.sync_git_repo_private") as desired,
# updating the variables in it with your custom information.
# 3. Ensure all your ssh keys are set up on both PC1 and PC2. This includes A) the keys to push/pull
# to/from your remote git repos, and B) the keys required to ssh from PC1 into PC2.
# 4. Manually `git clone` your repo onto both PC1 and PC2.
# 5. On PC1, create symlinks in ~/bin to this sync script so you can run it from anywhere:
# cd /path/to/here
# mkdir -p ~/bin
# ln -s "${PWD}/sync_git_repo_from_pc1_to_pc2.sh" ~/bin/gs_sync_git_repo_from_pc1_to_pc2
# 6. See the help menu for more details on the command:
# gs_sync_git_repo_from_pc1_to_pc2 -h
# 7. Now cd into a repo on PC1 that you want to sync from PC1 (ex: some light development machine)
# to PC2 (ex: some powerful build machine), and run this script:
# gs_sync_git_repo_from_pc1_to_pc2
# You can run the sync script whenever you have changes you want to push from PC1 to PC2 for
# building or testing!
# 8. Done! About a minute later the sync will be complete. Pay attention to the output messages
# to see what `git` is doing behind the scenes, and to ensure it worked and no errors occurred.
# References:
# 1. For main notes & reference links see "sync_git_repo_from_pc1_to_pc2--notes.md"
# 1. Bash numerical comparisons:
# https://stackoverflow.com/questions/18668556/comparing-numbers-in-bash/18668580#18668580
# 1. How to create a branch in a remote git repository:
# https://tecadmin.net/how-to-create-a-branch-in-remote-git-repository/
# 1. [example of a previous bash program I wrote, to aid myself as I write in bash]:
# https://github.com/ElectricRCAircraftGuy/PDF2SearchablePDF/blob/master/pdf2searchablepdf.sh
# Background Research:
# 1. Google search for "workflow to develop locally but build remotely" -
# https://www.google.com/search?q=workflow+to+develop+locally+but+build+remotely&oq=workflow+to+develop+locally+but+build+remotely&aqs=chrome..69i57.7154j0j7&sourceid=chrome&ie=UTF-8
# 1. *****"Developing on a remote server _Without Jupyter and Vim_" - https://matttrent.com/remote-development/
# 1. Google search for "eclipse work local build remote" -
# https://www.google.com/search?q=eclipse+work+local+build+remote&oq=eclipse+work+local+build+remote&aqs=chrome..69i57.218j0j9&sourceid=chrome&ie=UTF-8
# 1. https://stackoverflow.com/questions/4216822/work-on-a-remote-project-with-eclipse-via-ssh
# NOTES TO SELF:
# Tests to run:
# 1) Test `echo "Detected there is no commit to reset."` below!
# touch file
# git add file
# rm file
# git status
# # now, 'file' is both staged for commit AND deleted, so `git add -A` followed by `git commit`
# # would have `git commit` error out and say: "nothing to commit, working tree clean".
# # So, let's get into this situation and ensure my script still works ok and handles this
# # edge case!
# gs_sync_git_repo_from_pc1_to_pc2
# ==================================================================================================
# PROGRAM PARAMETERS
# ==================================================================================================
VERSION="0.2.0"
AUTHOR="Gabriel Staples"
RETURN_CODE_SUCCESS=0
RETURN_CODE_ERROR=1
SCRIPT_NAME="$(basename "$0")"
README_URL="https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/useful_scripts/README_git-sync_repo_from_pc1_to_pc2.md"
SOURCE_CODE_URL="https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/useful_scripts/sync_git_repo_from_pc1_to_pc2.sh"
VERSION_STR_SHORT="sync_git_repo_from_pc1_to_pc2 (run as '$SCRIPT_NAME') version $VERSION"
VERSION_STR_LONG="
$VERSION_STR_SHORT
Author = $AUTHOR
Readme = $README_URL
Source Code = $SOURCE_CODE_URL
See '$SCRIPT_NAME -h' for more info.
"
HELP_STR="\
$VERSION_STR_SHORT
Purpose: synchronize a git repo from one computer (\"PC1\") to another (\"PC2\"). This is useful,
for example, to edit and write code on a local laptop (\"PC1\") while building on a more-powerful,
remote desktop (\"PC2\").
Usage:
$SCRIPT_NAME [pc2_target_name [cmd]]
Example:
# note that the 'time bazel build //...' command here runs remotely on pc2,
# while the 'gs_alert' part runs on your local machine
$SCRIPT_NAME desktop 'time bazel build //...'; gs_alert
Synchronize the git repo (whose directory you are currently in when running the command)
on the local computer (\"PC1\") to the remote 'pc2_target_name' computer
(\"PC2\"). Then, optionally run the command 'cmd' on PC2 in its target git repository
path (ie: directory) when done.
The input paramter 'pc2_target_name' is optional, UNLESS you want to pass in a 'cmd' to
run when done, in which case 'pc2_target_name' is mandatory and must come before 'cmd',
as shown above.
You must set and configure your 'pc2_target_name' options as 'TARGET' variables inside
your custom \"~/.sync_git_repo_private\" file. If 'pc2_target_name' is not passed as
an argument to this program call, it defaults to 'DEFAULT_TARGET' which you must also
set inside your custom \"~/.sync_git_repo_private\" file.
When the repo is done synchronizing from PC1 to PC2, it will run 'cmd' on PC2 in the
directory of the target git repository on PC2.
$SCRIPT_NAME
Run the script with the target set to the value of 'DEFAULT_TARGET', as defined by the
user in their custom \"~/.sync_git_repo_private\" file.
$SCRIPT_NAME -t
Print all user-defined 't'argets the user has defined inside
\"~/.sync_git_repo_private\".
$SCRIPT_NAME -h
print this help menu
$SCRIPT_NAME -?
print this help menu
$SCRIPT_NAME -v
print the author and version
Private Usage
(these are calls the script itself automatically makes):
$SCRIPT_NAME --update_pc2 <pc2_git_repo_target_dir> <sync_branch>
Finish synchronizing the changes from GitHub to PC2 in PC2's 'pc2_git_repo_target_dir',
from the remote 'sync_branch'. The script itself calls this automatically on PC2 via an
ssh command to PC2, when needed.
Examples:
$SCRIPT_NAME
Syncrhonize PC1 to PC2 where PC2 is the \"default\" target.
$SCRIPT_NAME default
Same as above, except explicitly specify \"default\" target.
$SCRIPT_NAME desktop
Synchronize to user-defined \"desktop\" target.
$SCRIPT_NAME desktop2
Synchronize to user-defined \"desktop2\" target.
With an optional 'cmd' parameter:
$SCRIPT_NAME default 'time bazel build //...'; gs_alert
Synchronize from PC1 to PC2, where PC2 is the explicitly-named 'default' target, AND
then run the command 'time bazel build //...' on PC2 in the target repo directory
on that PC when done synchronizing! Finally, when all of that finishes, run the
'gs_alert' alias to beep and open a popup to indicate the operation is complete.
Note: the 'gs_alert' alias is found here:
https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/home/.bash_aliases
Readme:
$README_URL
Source Code:
$SOURCE_CODE_URL
"
# ANSI Color Codes
# See this file, approximately here:
# https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/useful_scripts/git-diffn.sh#L126
COLOR_OFF="\033[m"
COLOR_RED="\033[31m"
COLOR_GRN="\033[32m"
COLOR_BLU="\033[34m"
COLOR_CYA="\033[36m"
# ==================================================================================================
# FUNCTION DEFINITIONS
# ==================================================================================================
# echo (print) the passed-in text in red
echo_red() {
echo -e "${COLOR_RED}$@${COLOR_OFF}"
}
# echo (print) the passed-in text in green
echo_grn() {
echo -e "${COLOR_GRN}$@${COLOR_OFF}"
}
# echo (print) the passed-in text in blue
echo_blu() {
echo -e "${COLOR_BLU}$@${COLOR_OFF}"
}
# echo (print) the passed-in text in cyan
echo_cya() {
echo -e "${COLOR_CYA}$@${COLOR_OFF}"
}
print_help() {
echo "$HELP_STR" | less -RFX
}
print_version() {
echo "$VERSION_STR_LONG"
}
# Run this always--this function shall get called every run
run_always() {
# See my answer here:
# https://stackoverflow.com/questions/59895/how-can-i-get-the-source-directory-of-a-bash-script-from-within-the-script-itsel/60157372#60157372
PATH_TO_THIS_SCRIPT="$(realpath "$0")"
echo "PATH_TO_THIS_SCRIPT = \"$PATH_TO_THIS_SCRIPT\""
echo "Running on PC user@hostname: $USER@$HOSTNAME"
}
parse_args() {
PC_TO_RUN_ON="pc1"
pc2_target_name="default"
pc2_target_cmd=""
# 1. Intended for private usage
# Call only `--update_pc2` function if desired (ie: when running this script from PC2 only!)
# Calling syntax: `./sync_git_repo_from_pc1_to_pc2.sh --update_pc2 <pc2_git_repo_target_dir> <sync_branch>'
if [ "$1" == "--update_pc2" ]; then # see `man test` or `man [` for meaning of `[ ]`.
if [ $# -eq 3 ]; then
PC_TO_RUN_ON="pc2"
PC2_GIT_REPO_TARGET_DIR="$2"
SYNC_BRANCH="$3"
return $RETURN_CODE_SUCCESS
else
echo_red "ERROR: '--update_pc2' command missing 2nd and/or 3rd arguments!"
exit $RETURN_CODE_ERROR
fi
fi
# 2. Public usage
# Help menu
if [ "$1" == "-h" ] || [ "$1" == "-?" ]; then
print_help
exit $RETURN_CODE_SUCCESS
# Version
elif [ "$1" == "-v" ]; then
print_version
exit $RETURN_CODE_SUCCESS
# Print targets
elif [ "$1" == "-t" ]; then
read_user_parameters
echo ""
print_targets
exit $RETURN_CODE_SUCCESS
fi
# Reminder to self: see bash associative array tutorial here!:
# https://www.artificialworlds.net/blog/2012/10/17/bash-associative-array-examples/
if [ "$#" -eq "1" ]; then
pc2_target_name="$1"
elif [ "$#" -eq "2" ]; then
pc2_target_name="$1"
pc2_target_cmd="$2"
elif [ "$#" -gt "2" ]; then
echo_red "ERROR: too many arguments!"
exit $RETURN_CODE_ERROR
fi
}
print_targets() {
echo "Valid targets you have already defined inside \"~/.sync_git_repo_private\" include:"
for target in "${!PC2_USERNAME[@]}"; do
echo " $target"
done
}
# Read the parameters from the user's "~/.sync_git_repo_private" file.
read_user_parameters() {
# See bash associative array tutorial here!:
# https://www.artificialworlds.net/blog/2012/10/17/bash-associative-array-examples/
# See also `help declare`. NB: `-g` makes these variables global; otherwise they would be
# local to this function only!
# - Note: use `unset` to delete a variable. Ex: `unset PC2_USERNAME`.
declare -gA PC2_USERNAME
declare -gA PC2_HOSTNAME
declare -gA PC2_TARGETDIR
declare -gA PC2_SYNCBRANCH
# Export these variables so that these will be the variables the user is setting in
# their custom "~/.sync_git_repo_private" file.
export PC2_USERNAME
export PC2_HOSTNAME
export PC2_TARGETDIR
export PC2_SYNCBRANCH
if [ -f ~/.sync_git_repo_private ]; then
# Source this file only if it exists
. ~/.sync_git_repo_private
else
echo_red "\
ERROR! You must have a copy of the \"eRCaGuy_dotfiles/home/.sync_git_repo_private\" file in
\"~/.sync_git_repo_private\", with your custom configuration settings in it. Please see the
installation instructions in the top of this script for installation details. Script location:
\"$PATH_TO_THIS_SCRIPT\"."
exit $RETURN_CODE_ERROR
fi
# Copy defaults into the associative array:
if [ -n "$DEFAULT_TARGET" ]; then
PC2_USERNAME["default"]="${PC2_USERNAME["$DEFAULT_TARGET"]}"
PC2_HOSTNAME["default"]="${PC2_HOSTNAME["$DEFAULT_TARGET"]}"
PC2_TARGETDIR["default"]="${PC2_TARGETDIR["$DEFAULT_TARGET"]}"
PC2_SYNCBRANCH["default"]="${PC2_SYNCBRANCH["$DEFAULT_TARGET"]}"
else
echo_red "\
ERROR: you must set the 'DEFAULT_TARGET' variable in \"~/.sync_git_repo_private\" to something."
exit $RETURN_CODE_ERROR
fi
PC2_SSH_USERNAME="${PC2_USERNAME["$pc2_target_name"]}"
PC2_SSH_HOST="${PC2_HOSTNAME["$pc2_target_name"]}"
PC2_GIT_REPO_TARGET_DIR="${PC2_TARGETDIR["$pc2_target_name"]}"
SYNC_BRANCH="${PC2_SYNCBRANCH["$pc2_target_name"]}"
echo "pc2_target_name = $pc2_target_name"
echo "DEFAULT_TARGET (called with \"default\") = $DEFAULT_TARGET"
echo "PC2_SSH_USERNAME = $PC2_SSH_USERNAME"
echo "PC2_SSH_HOST = $PC2_SSH_HOST"
echo "PC2_GIT_REPO_TARGET_DIR = $PC2_GIT_REPO_TARGET_DIR"
echo "SYNC_BRANCH = $SYNC_BRANCH"
# Ensure target name is valid and that none of these variables are empty strings.
if [ -z "$PC2_SSH_USERNAME" ] || [ -z "$PC2_SSH_HOST" ] || [ -z "$PC2_GIT_REPO_TARGET_DIR" ] ||
[ -z "$SYNC_BRANCH" ]; then
echo_red "ERROR: invalid 'pc2_target_name' (\"$pc2_target_name\") passed to this program."
echo_red " Please add this target to your custom \"~/.sync_git_repo_private\" file, or"
echo_red " choose a valid target already defined in that file, and try again."
print_targets
exit $RETURN_CODE_ERROR
fi
}
# A function to obtain the temporary directory we will use, given a directory to a git repo.
# Call syntax: `get_temp_dir "$REPO_ROOT_DIR`
# Example: if REPO_ROOT_DIR="~/dev/myrepo", then this function will return (echo out) "~/dev/myrepo_temp" as
# the temp directory
get_temp_dir () {
REPO_ROOT_DIR="$1" # Ex: /home/gabriel/dev/eRCaGuy_dotfiles
BASENAME="$(basename "$REPO_ROOT_DIR")" # Ex: eRCaGuy_dotfiles
DIRNAME="$(dirname "$REPO_ROOT_DIR")" # Ex: /home/gabriel/dev
TEMP_DIR="${DIRNAME}/${BASENAME}_temp" # this is where temp files will be stored
echo "$TEMP_DIR"
}
# (Runs on both PC1 and PC2):
# Create a temporary directory to store the results, & check the git repo for changes--very similar
# to what a human is doing when calling `git status`. This function determines if any local,
# uncommitted changes or untracked files exist.
create_temp_and_check_for_changes() {
# Get git root dir (so you can do `git commit -A` from this dir in case you are in a lower
# dir--ie: cd to the root FIRST, then `git commit -A`, then cd back to where you were). See:
# https://stackoverflow.com/questions/957928/is-there-a-way-to-get-the-git-root-directory-in-one-command/957978#957978
REPO_ROOT_DIR="$(git rev-parse --show-toplevel)" # Ex: /home/gabriel/dev/eRCaGuy_dotfiles
# echo "REPO_ROOT_DIR = $REPO_ROOT_DIR" # debugging
# Make a temp dir one level up from REPO_ROOT_DIR, naming it "repo-name_temp"
TEMP_DIR="$(get_temp_dir "$REPO_ROOT_DIR")"
# echo "TEMP_DIR = $TEMP_DIR" # debugging
mkdir -p "$TEMP_DIR"
echo "Storing temp files in \"$TEMP_DIR\"."
# See if any changes exist (as normally shown by `git status`).
# If any changes do exist, back up the file paths which are:
# 1) changed and staged
# 2) changed and not staged
# 3) untracked
FILES_STAGED="$TEMP_DIR/1_staged.txt"
FILES_NOT_STAGED="$TEMP_DIR/2_not_staged.txt"
FILES_UNTRACKED="$TEMP_DIR/3_untracked.txt"
# 1) Get list of changed and staged files:
# See: https://stackoverflow.com/questions/33610682/git-list-of-staged-files/33610683#33610683
git diff --name-only --cached > "$FILES_STAGED"
num_staged=$(cat "$FILES_STAGED" | wc -l)
echo " num_staged = $num_staged"
# 2) Get list of changed and not staged files:
# See (Implicitly learned from here): https://stackoverflow.com/questions/33610682/git-list-of-staged-files/33610683#33610683
git diff --name-only > "$FILES_NOT_STAGED"
num_not_staged=$(cat "$FILES_NOT_STAGED" | wc -l)
echo " num_not_staged = $num_not_staged"
# 3) Get list of untracked files:
# See: https://stackoverflow.com/questions/3801321/git-list-only-untracked-files-also-custom-commands/3801554#3801554
git ls-files --others --exclude-standard > "$FILES_UNTRACKED"
num_untracked=$(cat "$FILES_UNTRACKED" | wc -l)
echo " num_untracked = $num_untracked"
total=$[$num_staged + $num_not_staged + $num_untracked]
echo " total = $total"
}
# Load the 'pc2_actual_commit_hash' variable with the actual commit hash currently checked-out
# on pc2!
get_pc2_actual_commit_hash() {
pc2_actual_commit_hash="unknown"
# NB: do NOT use `-t` here with `ssh`, or else the `pc2_actual_commit_hash` will have some sort
# of hidden char(s) in it or something and fail to match the actual commit hash from pc1, even
# when they really *do* match!
pc2_actual_commit_hash="$(ssh $PC2_SSH_USERNAME@$PC2_SSH_HOST \
"cd \"$PC2_GIT_REPO_TARGET_DIR\"; git rev-parse HEAD")"
# Obtain return code from `ssh`; see: https://stackoverflow.com/a/38533260/4561887
ret_code="$?"
if [ "$ret_code" -ne "$RETURN_CODE_SUCCESS" ]; then
echo_red "ERROR: Failed to get pc2 actual commit hash! Please try again."
exit $ret_code
fi
}
# On local machine:
# Summary: Look for changes. Commit them to current local branch. Force Push them to remote SYNC
# branch. Uncommit them on local branch. Restore original state by re-staging any files that were
# previously staged. Done.
sync_pc1_to_remote_branch () {
echo ""
echo "============================"
echo "Syncing PC1 to remote branch"
echo "============================"
echo "Preparing to push current branch with all changes (including staged, unstaged, & untracked files)"
echo " to remote sync branch."
synced_commit_hash="NONE"
create_temp_and_check_for_changes
# Commit uncommitted changes (if any exist) into a temporary commit we will uncommit later
made_temp_commit=false
if [ "$total" -gt "0" ]; then
# Uncommitted changes *do* exist!
made_temp_commit=true
echo "Making a temporary commit of all uncommitted changes."
cd "$REPO_ROOT_DIR"
# Prepare a multi-line commit message in a variable first, then do the commit w/this msg.
# See *my own answer here!*:
# https://stackoverflow.com/questions/29933349/how-can-i-make-git-commit-messages-divide-into-multiple-lines/60826932#60826932
commit_msg="AUTOMATIC COMMIT TO SYNC TO PC2 (BUILD MACHINE)"
# Staged files
num="$num_staged"
file_names_path="$FILES_STAGED"
if [ "$num" -gt "0" ]; then
if [ "$num" -eq "1" ]; then
verbiage="1. This ${num} file was **staged** & is now committed:"
else
verbiage="1. These ${num} files were **staged** & are now committed:"
fi
commit_msg="$(printf "${commit_msg}\n\n${verbiage}")"
file_names="$(cat "$file_names_path")"
commit_msg="$(printf "${commit_msg}\n\n${file_names}")"
fi
# Not staged files
num="$num_not_staged"
file_names_path="$FILES_NOT_STAGED"
if [ "$num" -gt "0" ]; then
if [ "$num" -eq "1" ]; then
verbiage="2. This ${num} file was changed but **not staged** & is now committed:"
else
verbiage="2. These ${num} files were changed but **not staged** & are now committed:"
fi
commit_msg="$(printf "${commit_msg}\n\n${verbiage}")"
file_names="$(cat "$file_names_path")"
commit_msg="$(printf "${commit_msg}\n\n${file_names}")"
fi
# Untracked files
num="$num_untracked"
file_names_path="$FILES_UNTRACKED"
if [ "$num" -gt "0" ]; then
if [ "$num" -eq "1" ]; then
verbiage="3. This ${num} file was **untracked** & is now committed:"
else
verbiage="3. These ${num} files were **untracked** & are now committed:"
fi
commit_msg="$(printf "${commit_msg}\n\n${verbiage}")"
file_names="$(cat "$file_names_path")"
commit_msg="$(printf "${commit_msg}\n\n${file_names}")"
fi
git add -A
error_msg="$(git commit -m "$commit_msg")"
echo "$error_msg"
error_msg_last_line="$(echo "$error_msg" | tail -n1)"
if [ "$error_msg_last_line" == "nothing to commit, working tree clean" ]; then
echo "Detected there is no commit to reset."
made_temp_commit=false
fi
fi
# Print out and store current commit hash; how to get hash of current commit:
# see: https://stackoverflow.com/questions/949314/how-to-retrieve-the-hash-for-the-current-commit-in-git/949391#949391
synced_commit_hash="$(git rev-parse HEAD)"
# Only push the changes if pc2 doesn't already have these changes!
# - TODO: **ALSO** consider checking to see if the commit we are about to push is already on the
# remote server, and if it is, there is NOT a need to push to the remote server even though
# there IS a need to pull the new changes onto PC2. Note: think about this though: this may
# not actually be a scenario we will ever see in practice, so maybe this isn't necessary
# after-all.
get_pc2_actual_commit_hash
if [ "$synced_commit_hash" == "$pc2_actual_commit_hash" ]; then
need_to_sync_to_pc2="false"
echo "NOTHING TO DO. PC2 already has the changes from PC1!:"
echo " synced_commit_hash from pc1 = $synced_commit_hash"
echo " pc2_actual_commit_hash = $pc2_actual_commit_hash"
else
# The current commit hash on pc2 does NOT equal what's on pc1, so DO sync pc1's changes
# to the remote branch.
echo "Force pushing commit ${synced_commit_hash} to remote \"$SYNC_BRANCH\" branch."
echo "ENSURE YOU HAVE YOUR PROPER SSH KEYS FOR GITHUB LOADED INTO YOUR SSH AGENT"
echo " (w/'ssh-add <my_github_key>') OR ELSE THIS WILL FAIL!"
# TODO: figure out if origin is even available (ex: via a ping or something), and if not, error out right here!
git push --force origin "HEAD:$SYNC_BRANCH" # MAY NEED TO COMMENT OUT DURING TESTING
# Obtain return code from the last cmd (`git push`); see:
# https://stackoverflow.com/a/38533260/4561887
ret_code="$?"
if [ "$ret_code" -ne "$RETURN_CODE_SUCCESS" ]; then
echo_red "ERROR: FAILED to 'git push'! Please try again."
exit $ret_code
fi
echo "Done syncing PC1 to remote branch."
fi
# Uncommit the temporary commit we committed above
if [ "$made_temp_commit" = "true" ]; then
echo "Uncommitting the temporary commit we made above."
git reset HEAD~
# Now re-stage any files that were previously staged
# See: 1) https://stackoverflow.com/questions/4227994/how-do-i-use-the-lines-of-a-file-as-arguments-of-a-command/4229346#4229346
# and 2) *****https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
# and 3) *****+ [my own ans I just made now]:
# https://stackoverflow.com/questions/4227994/how-do-i-use-the-lines-of-a-file-as-arguments-of-a-command/60276836#60276836
if [ "$num_staged" -gt "0" ]; then
echo "Re-staging (via 'git add') any files that were previously staged."
# `git add` each file that was staged before in order to stage it again like it was
# - See link 3 just above for how this works
while IFS= read -r line
do
echo " git add \"$line\""
git add "$line"
done < "$FILES_STAGED"
fi
fi
}
# This command is intended to only run on PC2 (the destination).
# It ensures the ssh-agent is running and ssh keys are added so that PC2 can use its ssh keys to
# pull from GitHub over ssh withOUT forcing you to type in your ssh password every time you run
# this script!
# - This code block is copy-pasted from my ssh readme here:
# https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/tree/master/home/.ssh#auto-starting-the-the-ssh-agent-on-a-remote-ssh-based-development-machine
start_ssh_agent_on_pc2() {
if [ ! -S ~/.ssh/ssh_auth_sock ]; then
echo "'ssh-agent' has not been started since the last reboot. Starting 'ssh-agent' now."
echo "See: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/tree/master/home/.ssh#auto-starting-the-the-ssh-agent-on-a-remote-ssh-based-development-machine"
eval "$(ssh-agent -s)"
ln -sf "$SSH_AUTH_SOCK" ~/.ssh/ssh_auth_sock
fi
export SSH_AUTH_SOCK=~/.ssh/ssh_auth_sock
# see if any key files are already added to the ssh-agent, and if not, add them
ssh-add -l > /dev/null
if [ "$?" -ne "0" ]; then
echo "No ssh keys have been added to your 'ssh-agent' since the last reboot. Adding default keys now."
echo "See: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/tree/master/home/.ssh#auto-starting-the-the-ssh-agent-on-a-remote-ssh-based-development-machine"
ssh-add
fi
}
# This command is only intended to **run on PC2** (the destination), NOT PC1 (the source)!
# This is the main command to run on PC2 via ssh from PC1 in order to sync the remote branch to PC2!
# Call syntax: `update_pc2 "$PC2_GIT_REPO_TARGET_DIR"`
update_pc2 () {
echo "---\"update_pc2\" script start---"
start_ssh_agent_on_pc2
PC2_GIT_REPO_TARGET_DIR="$1"
cd "$PC2_GIT_REPO_TARGET_DIR"
create_temp_and_check_for_changes
# 1st, back up any uncommitted changes that may exist
if [ "$total" -gt "0" ]; then
# Uncommitted changes *do* exist!
echo "Uncommitted changes exist in PC2's repo, so committing them to new branch to save them in case"
echo " they are important."
# Produce a new branch name to back up these uncommitted changes.
# Get just the name of the currently-checked-out branch:
# See: https://stackoverflow.com/questions/6245570/how-to-get-the-current-branch-name-in-git/12142066#12142066
# - Will simply output "HEAD" if in a 'detached HEAD' state (ie: not on any branch)
current_branch_name=$(git rev-parse --abbrev-ref HEAD)
timestamp="$(date "+%Y%m%d-%H%Mhrs-%Ssec")"
new_branch_name="${current_branch_name}_SYNC_BAK_${timestamp}"
echo "Creating branch \"$new_branch_name\" to store all uncommitted changes."
git checkout -b "$new_branch_name"
echo "Committing all changes to branch \"$new_branch_name\"."
git add -A
git commit -m "DO BACKUP OF ALL UNCOMMITTED CHANGES ON PC2 (TARGET PC/BUILD MACHINE)"
fi
# 2nd, check out the sync branch and pull latest changes just pushed to it from PC1
# Hard-pull from the remote server to fully overwrite local copy of this branch.
# See: https://stackoverflow.com/questions/1125968/how-do-i-force-git-pull-to-overwrite-local-files/8888015#8888015
# TODO: figure out if origin is even available (ex: via a ping or something), and if not, error out right here!
echo "ENSURE YOU HAVE YOUR PROPER SSH KEYS FOR GITHUB LOADED INTO YOUR SSH AGENT"
echo " (w/'ssh-add <my_github_key>') OR ELSE THESE FOLLOWING STEPS WILL FAIL!"
echo " See here for help: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/tree/master/home/.ssh#auto-starting-the-the-ssh-agent-on-a-remote-ssh-based-development-machine"
echo "Force pulling from remote \"${SYNC_BRANCH}\" branch to overwrite local copy of this branch."
echo " 1/4: 'git fetch origin \"${SYNC_BRANCH}\"'"
git fetch origin "${SYNC_BRANCH}" # MAY NEED TO COMMENT OUT DURING TESTING
echo " 2/4: 'git checkout \"origin/${SYNC_BRANCH}\"'"
git checkout "origin/${SYNC_BRANCH}" # MAY NEED TO COMMENT OUT DURING TESTING
echo " 3/4: 'git branch -D \"${SYNC_BRANCH}\"'"
git branch -D "${SYNC_BRANCH}" # MAY NEED TO COMMENT OUT DURING TESTING
echo " 4/4: 'git checkout -b \"${SYNC_BRANCH}\"'"
git checkout -b "${SYNC_BRANCH}" # MAY NEED TO COMMENT OUT DURING TESTING
echo "---\"update_pc2\" script end---"
}
# On remote machine:
# Summary: Look for changes. Commit them to a new branch forked off of current branch. Call it
# current_branch_SYNC_BAK_20200217-2310hrs. Check out ${SYNC_BRANCH} branch. Pull and hard reset.
# Done! We are ready to build now!
sync_remote_branch_to_pc2 () {
echo ""
echo "============================"
echo "Syncing remote branch to PC2"
echo "============================"
# rsync a copy of this script over to a temp dir on PC2
TEMP_DIR="$(get_temp_dir "$PC2_GIT_REPO_TARGET_DIR")"
# echo "TEMP_DIR = \"$TEMP_DIR\"" # Debugging
echo "Making temp dir on PC2: \"$TEMP_DIR\"."
ssh $PC2_SSH_USERNAME@$PC2_SSH_HOST "mkdir -p \"$TEMP_DIR\""
echo "Copying this script to temp dir."
rsync "$PATH_TO_THIS_SCRIPT" "$PC2_SSH_USERNAME@$PC2_SSH_HOST:$TEMP_DIR/"
script_filename="$(basename "$PATH_TO_THIS_SCRIPT")"
script_path_on_pc2="$TEMP_DIR/$script_filename"
# echo "script_path_on_pc2 = $script_path_on_pc2" # Debugging
echo "Calling script on PC2 to sync from remote branch to PC2."
# NB: the `-t` flag to `ssh` tells it that it is an interactive shell. Some commands which use
# `screen` internally require the `-t` flag when being called over ssh. Using `-t` also has the
# effect of causing it to print out "Connection to $HOSTNAME closed" whenever the cmd is over.
# See `man ssh` to read more about the -t flag. Also see here:
# https://malcontentcomics.com/systemsboy/2006/07/send-remote-commands-via-ssh.html
# and here: https://www.cyberciti.biz/faq/unix-linux-execute-command-using-ssh/
ssh -t $PC2_SSH_USERNAME@$PC2_SSH_HOST "$script_path_on_pc2 --update_pc2 \
\"$PC2_GIT_REPO_TARGET_DIR\" \"$SYNC_BRANCH\""
# Obtain return code from `ssh`; see: https://stackoverflow.com/a/38533260/4561887
ret_code="$?"
if [ "$ret_code" -ne "$RETURN_CODE_SUCCESS" ]; then
echo_red "ERROR: FAILED TO SYNC! Please try again."
exit $ret_code
fi
# Ensure the commit hash on PC2 is now what we expect
get_pc2_actual_commit_hash
if [ "$synced_commit_hash" != "$pc2_actual_commit_hash" ]; then
# failure: print 1 as green and 1 as red
echo -e "synced_commit_hash from pc1 = ${COLOR_GRN}${synced_commit_hash}${COLOR_OFF}"
echo -e "pc2_actual_commit_hash = ${COLOR_RED}${pc2_actual_commit_hash}${COLOR_OFF}"
echo_red "ERROR: FAILED TO SYNC! Mismatch in pc1 synced commit hash vs pc2 actual commit hash."
echo_red "Please try again."
exit $RETURN_CODE_ERROR
fi
# success: print both as green
echo -e "synced_commit_hash from pc1 = ${COLOR_GRN}${synced_commit_hash}${COLOR_OFF}"
echo -e "pc2_actual_commit_hash = ${COLOR_GRN}${pc2_actual_commit_hash}${COLOR_OFF}"
echo_grn "Done syncing remote branch to PC2. It should be ready to be built on PC2 now!"
}
# Main code to run on PC1
main_pc1 () {
DIR_START="$(pwd)"
# echo "DIR_START = $DIR_START" # debugging
need_to_sync_to_pc2="true"
sync_pc1_to_remote_branch
if [ "$need_to_sync_to_pc2" == "true" ]; then
sync_remote_branch_to_pc2
fi
# Optional, but not a bad habit: cd back to where we started in case we ever add additional code
# after this and expect to be in the dir where we started
cd "$DIR_START"
echo ""
echo "=========================================================================================="
echo "SUMMARY:"
echo "=========================================================================================="
echo " Commit hash synced:"
echo_grn " From PC1: ${synced_commit_hash}"
echo_grn " Now on PC2: ${pc2_actual_commit_hash}"
# For printf help, see: https://stackoverflow.com/questions/994461/right-align-pad-numbers-in-bash/994471#994471
# and: http://www.cplusplus.com/reference/cstdio/printf/
printf " From PC1: %-35s Repo root: %s\n" "${USER}@${HOSTNAME}:" "${REPO_ROOT_DIR}"
printf " To PC2: %-35s Repo root: %s\n" "${PC2_SSH_USERNAME}@${PC2_SSH_HOST}:" "${PC2_GIT_REPO_TARGET_DIR}"
timestamp="$(date "+%Y.%m.%d %H:%Mhrs:%Ssec")"
echo -e " ${COLOR_GRN}Synchronization to PC2 completed successfully${COLOR_OFF} at timestamp: $timestamp"
# Only run the target command on PC2 if one has been specified
if [ -n "$pc2_target_cmd" ]; then
echo -e "Running cmd '${COLOR_BLU}${pc2_target_cmd}${COLOR_OFF}' on PC2 in directory \"$PC2_GIT_REPO_TARGET_DIR\":"
echo -e " ssh -t $PC2_SSH_USERNAME@$PC2_SSH_HOST \"cd '$PC2_GIT_REPO_TARGET_DIR' && ${COLOR_BLU}${pc2_target_cmd}${COLOR_OFF}\""
echo_blu "----"
ssh -t $PC2_SSH_USERNAME@$PC2_SSH_HOST "cd '$PC2_GIT_REPO_TARGET_DIR' && $pc2_target_cmd"
echo_blu "----"
fi
echo_blu "END!"
}
# ==================================================================================================
# MAIN PROGRAM ENTRY POINT
# ==================================================================================================
run_always "$@"
parse_args "$@"
if [ "$PC_TO_RUN_ON" == "pc1" ]; then
read_user_parameters
# Note: use `time` cmd in front to output the total time this process took when it ends!
time main_pc1
elif [ "$PC_TO_RUN_ON" == "pc2" ]; then
update_pc2 "$PC2_GIT_REPO_TARGET_DIR"
fi