11#! /usr/bin/env bash
22
3- VERSION=" 2025.7.1 "
3+ VERSION=" 2025.9.5 "
44
55GIT_DEFAULT_BRANCH=main
66GIT_PUBLIC_BRANCH=public
@@ -17,61 +17,35 @@ SCRIPT_NAME="$(basename "$0")"
1717CONFIRM=0
1818
1919# # PURPOSE RELATED VARS
20- # PROJECT_DIR =$( git rev-parse --show-toplevel )
21- PROJECT_DIR =" $( cd " ${SCRIPT_DIR} " && git rev-parse --show-toplevel) "
20+ # REPO_DIR =$( git rev-parse --show-toplevel )
21+ REPO_DIR =" $( cd " ${SCRIPT_DIR} " && git rev-parse --show-toplevel) "
2222
2323# PUBLIC_GITIGNORE=files/git/pub.gitignore
2424
25- # # ref: https://stackoverflow.com/questions/53839253/how-can-i-convert-an-array-into-a-comma-separated-string
26- declare -a PRIVATE_CONTENT_ARRAY
27- PRIVATE_CONTENT_ARRAY+=(' **/private/***' )
28- PRIVATE_CONTENT_ARRAY+=(' **/vault/***' )
29- PRIVATE_CONTENT_ARRAY+=(' **/archive/***' )
30- PRIVATE_CONTENT_ARRAY+=(' **/save/***' )
31- PRIVATE_CONTENT_ARRAY+=(' **/vault.yml' )
32- PRIVATE_CONTENT_ARRAY+=(' **/*vault.yml' )
33- PRIVATE_CONTENT_ARRAY+=(' **/secrets.yml' )
34- PRIVATE_CONTENT_ARRAY+=(' **/*secrets.yml' )
35- PRIVATE_CONTENT_ARRAY+=(' .vault_pass' )
36- # PRIVATE_CONTENT_ARRAY+=('***/*vault*')
37- PRIVATE_CONTENT_ARRAY+=(' ***/vault.yml' )
38- PRIVATE_CONTENT_ARRAY+=(' **/integration_config.yml' )
39- PRIVATE_CONTENT_ARRAY+=(' **/integration_config.vault.yml' )
40- PRIVATE_CONTENT_ARRAY+=(' *.log' )
41-
42- printf -v PRIVATE_CONTENT_LIST ' %s,' " ${PRIVATE_CONTENT_ARRAY[@]} "
43- PRIVATE_CONTENT_LIST=" ${PRIVATE_CONTENT_LIST% ,} "
44-
4525# # ref: https://stackoverflow.com/questions/53839253/how-can-i-convert-an-array-into-a-comma-separated-string
4626declare -a EXCLUDES_ARRAY
4727EXCLUDES_ARRAY+=(' .git' )
4828EXCLUDES_ARRAY+=(' .gitmodule' )
49- EXCLUDES_ARRAY+=(' .idea' )
50- EXCLUDES_ARRAY+=(' .vscode' )
51- EXCLUDES_ARRAY+=(' venv' )
52- EXCLUDES_ARRAY+=(' **/.DS_Store' )
53- EXCLUDES_ARRAY+=(' *.log' )
5429
55- printf -v EXCLUDES_LIST ' %s,' " ${EXCLUDES_ARRAY[@]} "
56- EXCLUDES_LIST=" ${EXCLUDES_LIST% ,} "
30+ # Read .gitignore and populate excludes array
31+ while read -r line; do
32+ line=$( echo " $line " | sed ' s/^[[:space:]]*//;s/[[:space:]]*$//' )
33+ [[ -z " $line " || " $line " =~ ^# .* ]] && continue
34+ EXCLUDES_ARRAY+= (" $line " )
35+ done < " ${REPO_DIR} /.gitignore"
5736
58- # # https://serverfault.com/questions/219013/showing-total-progress-in-rsync-is-it-possible
59- # # https://www.studytonight.com/linux-guide/how-to-exclude-files-and-directory-using-rsync
60- RSYNC_OPTS_GIT_MIRROR=()
61- RSYNC_OPTS_GIT_MIRROR+=(" -dar" )
62- RSYNC_OPTS_GIT_MIRROR+=(" --links" )
63- RSYNC_OPTS_GIT_MIRROR+=(" --delete-excluded" )
64- RSYNC_OPTS_GIT_MIRROR+=(" --exclude={${EXCLUDES_LIST} ,${PRIVATE_CONTENT_LIST} }" )
37+ # Read .rsync-ignore and populate excludes array
38+ while read -r line; do
39+ line=$( echo " $line " | sed ' s/^[[:space:]]*//;s/[[:space:]]*$//' )
40+ [[ -z " $line " || " $line " =~ ^# .* ]] && continue
41+ EXCLUDES_ARRAY+= (" $line " )
42+ done < " ${REPO_DIR} /.rsync-ignore"
6543
66- RSYNC_OPTS_GIT_UPDATE=()
67- RSYNC_OPTS_GIT_UPDATE+=(" -ari" )
68- RSYNC_OPTS_GIT_UPDATE+=(" --links" )
44+ printf -v EXCLUDES_LIST ' %s,' " ${EXCLUDES_ARRAY[@]} "
45+ EXCLUDES_LIST= " ${EXCLUDES_LIST% ,} "
6946
70- # # https://www.pixelstech.net/article/1577768087-Create-temp-file-in-Bash-using-mktemp-and-trap
7147TEMP_DIR= $( mktemp -d -p ~ )
7248
73- trap ' rm -fr "$TEMP_DIR"' EXIT
74-
7549# ### LOGGING RELATED
7650LOG_ERROR= 0
7751LOG_WARN= 1
@@ -119,6 +93,8 @@ reverse_array LOGLEVEL_TO_STR LOGLEVELSTR_TO_LEVEL
11993# LOG_LEVEL=${LOG_DEBUG}
12094LOG_LEVEL= ${LOG_INFO}
12195
96+ # --- Logging Functions ---
97+
12298function log_error() {
12399 if [ " $LOG_LEVEL " -ge " $LOG_ERROR " ]; then
124100 log_message " ${LOG_ERROR} " " ${1} "
@@ -264,6 +240,8 @@ function set_log_level() {
264240
265241}
266242
243+ # --- Helper Functions ---
244+
267245function execute() {
268246 log_info " ${* } "
269247 if ! " $@ "
@@ -314,13 +292,13 @@ function git_commit_push() {
314292 local REMOTE_AND_BRANCH
315293 LOCAL_BRANCH=" $( git symbolic-ref --short HEAD) " && \
316294 REMOTE_AND_BRANCH=$( git rev-parse --abbrev-ref " ${LOCAL_BRANCH} @{upstream}" ) && \
317- IFS=/ read -r REMOTE REMOTE_BRANCH <<< " ${REMOTE_AND_BRANCH}" && \
295+ IFS=/ read -r REMOTE_NAME REMOTE_BRANCH <<< " ${REMOTE_AND_BRANCH}" && \
318296 echo " Staging changes:" && \
319297 (git add -A || true) && \
320298 echo " Committing changes:" && \
321- (git commit -am " group updates to public branch" || true) && \
322- echo " Pushing branch '${LOCAL_BRANCH} ' to remote '${REMOTE } ' branch '${REMOTE_BRANCH} ':" && \
323- (git push -f -u " ${REMOTE } " " ${LOCAL_BRANCH} :${REMOTE_BRANCH} " || true)
299+ (git commit -am " Sync: Automated sync from main to public branch. " || true) && \
300+ echo " Pushing branch '${LOCAL_BRANCH} ' to remote '${REMOTE_NAME } ' branch '${REMOTE_BRANCH} ':" && \
301+ (git push -f -u " ${REMOTE_NAME } " " ${LOCAL_BRANCH} :${REMOTE_BRANCH} " || true)
324302}
325303
326304function search_repo_keywords () {
@@ -365,7 +343,7 @@ function search_repo_keywords () {
365343 # # ref: https://stackoverflow.com/questions/6565471/how-can-i-exclude-directories-from-grep-r#8692318
366344 # # ref: https://unix.stackexchange.com/questions/342008/find-and-echo-file-names-only-with-pattern-found
367345 # # ref: https://www.baeldung.com/linux/find-exclude-paths
368- local FIND_CMD=" find ${PROJECT_DIR } / \( ${FIND_EXCLUDE_DIRS} \) -o -exec ${GREP_COMMAND} {} 2>/dev/null +"
346+ local FIND_CMD=" find ${REPO_DIR } / \( ${FIND_EXCLUDE_DIRS} \) -o -exec ${GREP_COMMAND} {} 2>/dev/null +"
369347 log_info " ${FIND_CMD} "
370348
371349 local EXCEPTION_COUNT
@@ -380,68 +358,139 @@ function search_repo_keywords () {
380358 return " ${EXCEPTION_COUNT} "
381359}
382360
383- function sync_public_branch() {
384- local RSYNC_MIRROR_OPTS=" ${RSYNC_OPTS_GIT_MIRROR[*]} "
385- local RSYNC_UPDATE_OPTS=" ${RSYNC_OPTS_GIT_UPDATE[*]} "
361+ # --- Core Functions ---
386362
387- log_debug " RSYNC_MIRROR_OPTS=${RSYNC_MIRROR_OPTS} "
388- log_debug " RSYNC_UPDATE_OPTS=${RSYNC_UPDATE_OPTS} "
363+ # Function to clean up the temporary directory
364+ cleanup () {
365+ if [[ -d " ${TEMP_DIR} " ]]; then
366+ log_info " Cleaning up temporary directory: ${TEMP_DIR} "
367+ rm -rf " ${TEMP_DIR} "
368+ fi
369+ }
389370
390- git fetch --all
391- git checkout ${GIT_DEFAULT_BRANCH}
371+ # Function to handle errors
372+ on_error () {
373+ local exit_code=" $? "
374+ if [[ " $exit_code " -ne 0 ]]; then
375+ log_error " Script failed with error code $exit_code ."
376+ cleanup
377+ fi
378+ }
392379
393- # RSYNC_OPTS=${RSYNC_OPTS_GIT_MIRROR[@]}
394- log_debug " copy project to temporary dir $TEMP_DIR "
395- local RSYNC_CMD
396- RSYNC_CMD=" rsync ${RSYNC_MIRROR_OPTS} ${PROJECT_DIR} / ${TEMP_DIR} /"
397- execute_eval_command " ${RSYNC_CMD} "
380+ # Function to copy the project to a temporary directory
381+ copy_project_to_temp_dir () {
382+ local REPO_DIR=" $1 "
383+ TEMP_DIR=$( mktemp -d /tmp/sync-repo.XXXXXXXXXX)
384+ log_info " Copying project to temporary directory: ${TEMP_DIR} "
385+
386+ # local RSYNC_CMD="rsync -av --exclude={'${EXCLUDES_LIST}'} --exclude='.git/' '${REPO_DIR}/' '${TEMP_DIR}/'"
387+ local RSYNC_CMD=" rsync -dar --links --exclude={${EXCLUDES_LIST} } '${REPO_DIR} /' '${TEMP_DIR} /'"
388+
389+ if [[ " ${DRY_RUN} " == " true" ]]; then
390+ log_info " Dry run: Would have executed: ${RSYNC_CMD} "
391+ # Since it's a dry run, we don't actually execute the rsync
392+ else
393+ log_debug " Executing: ${RSYNC_CMD} "
394+ execute_eval_command " ${RSYNC_CMD} "
395+ fi
396+ }
398397
399- log_info " Checkout public branch"
400- git checkout ${GIT_PUBLIC_BRANCH}
398+ # Function to update the public branch
399+ sync_public_branch () {
400+ local REPO_DIR=" $1 "
401+ local PUBLIC_BRANCH=" $2 "
401402
402- if [ $GIT_REMOVE_CACHED_FILES -eq 1 ] ; then
403- log_info " Removing files cached in git "
404- git rm -r --cached .
405- fi
403+ log_info " Stashing any local changes on the current branch. "
404+ if ! git -C " ${REPO_DIR} " stash push -u -m " Stash before sync to ${PUBLIC_BRANCH} " ; then
405+ log_error " Failed to stash local changes. "
406+ fi
406407
407- log_info " Copy ${TEMP_DIR} to project dir $PROJECT_DIR "
408- RSYNC_CMD=" rsync ${RSYNC_UPDATE_OPTS} ${TEMP_DIR} / ${PROJECT_DIR} /"
409- execute_eval_command " ${RSYNC_CMD} "
408+ git fetch --all
410409
411- printf -v TO_REMOVE ' %s ' " ${PRIVATE_CONTENT_ARRAY[@]} "
412- TO_REMOVE=" ${TO_REMOVE% } "
413- log_info " TO_REMOVE=${TO_REMOVE} "
414- CLEANUP_CMD=" rm -fr ${TO_REMOVE} "
415- execute_eval_command " ${CLEANUP_CMD} "
410+ log_info " Checking out public branch: ${PUBLIC_BRANCH} "
411+ if ! git -C " ${REPO_DIR} " checkout " ${PUBLIC_BRANCH} " ; then
412+ log_error " Failed to checkout branch: ${PUBLIC_BRANCH} "
413+ fi
416414
417- if [ -n " ${PUBLIC_GITIGNORE} " ]; then
418- if [ -e $PUBLIC_GITIGNORE ]; then
419- log_info " Update public files:"
420- cp -p $PUBLIC_GITIGNORE .gitignore
415+ log_info " Pulling latest changes from the public branch."
416+ # local REMOTE_BRANCH=$(git -C "${REPO_DIR}" rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
417+ local REMOTE_AND_BRANCH=$( git rev-parse --abbrev-ref " ${PUBLIC_BRANCH} @{upstream}" ) && \
418+ IFS=/ read -r REMOTE_NAME REMOTE_BRANCH <<< " ${REMOTE_AND_BRANCH}" && \
419+
420+ if [[ -z " ${REMOTE_BRANCH} " ]]; then
421+ log_warn " No upstream branch found for ${PUBLIC_BRANCH} . Skipping pull."
422+ else
423+ log_info " Pulling from REMOTE_BRANCH remote: ${REMOTE_NAME} "
424+ if ! git -C " ${REPO_DIR} " pull " ${REMOTE_NAME} " " ${REMOTE_BRANCH} :${PUBLIC_BRANCH} " ; then
425+ log_warn " Failed to pull from ${REMOTE_NAME} /${REMOTE_BRANCH} :${PUBLIC_BRANCH} . Continuing anyway."
426+ fi
427+ fi
428+
429+ log_info " Syncing temporary directory to public branch."
430+
431+ if [ " ${GIT_REMOVE_CACHED_FILES} " -eq 1 ]; then
432+ log_info " Removing files cached in git"
433+ git rm -r --cached .
421434 fi
422- fi
423435
424- log_info " Show changes before push:"
425- git status
436+ log_info " Copy ${TEMP_DIR} to project dir ${REPO_DIR} "
437+ # Added --delete and --exclude '.git/'
438+ local RSYNC_CMD=" rsync -dar --links --delete --exclude '.git/' '${TEMP_DIR} /' '${REPO_DIR} /'"
439+ # local RSYNC_CMD="rsync -av --delete --exclude '.git/' '${TEMP_DIR}/' '${REPO_DIR}/'"
440+ # local RSYNC_CMD="rsync ${RSYNC_UPDATE_OPTS} ${TEMP_DIR}/ ${REPO_DIR}/"
441+
442+ if [[ " ${DRY_RUN} " == " true" ]]; then
443+ log_info " Dry run: Would have executed: ${RSYNC_CMD} "
444+ else
445+ log_debug " Executing: ${RSYNC_CMD} "
446+ if ! eval " ${RSYNC_CMD} " ; then
447+ log_error " rsync failed during sync to public branch."
448+ fi
449+ fi
426450
427- # # https://stackoverflow.com/questions/5989592/git-cannot-checkout-branch-error-pathspec-did-not-match-any-files-kn
428- # # git diff --name-only ${GIT_PUBLIC_BRANCH} ${GIT_DEFAULT_BRANCH} --
451+ if [ -n " ${PUBLIC_GITIGNORE} " ]; then
452+ if [ -e " ${PUBLIC_GITIGNORE} " ]; then
453+ log_info " Update public files:"
454+ cp -p " ${PUBLIC_GITIGNORE} " .gitignore
455+ fi
456+ fi
429457
430- if [ $CONFIRM -eq 0 ]; then
431- # # https://www.shellhacks.com/yes-no-bash-script-prompt-confirmation/
432- read -p " Are you sure you want to merge the changes above to public branch ${TARGET_BRANCH} ? " -n 1 -r
433- echo # (optional) move to a new line
434- if [[ ! $REPLY =~ ^[Yy]$ ]]
435- then
436- exit 1
458+ log_info " Show changes before push:"
459+ git status
460+
461+ # # https://stackoverflow.com/questions/5989592/git-cannot-checkout-branch-error-pathspec-did-not-match-any-files-kn
462+ # # git diff --name-only ${GIT_PUBLIC_BRANCH} ${GIT_DEFAULT_BRANCH} --
463+
464+ if [ $CONFIRM -eq 0 ]; then
465+ # # https://www.shellhacks.com/yes-no-bash-script-prompt-confirmation/
466+ read -p " Are you sure you want to merge the changes above to public branch ${TARGET_BRANCH} ? " -n 1 -r
467+ echo # (optional) move to a new line
468+ if [[ ! $REPLY =~ ^[Yy]$ ]]
469+ then
470+ exit 1
471+ fi
437472 fi
438- fi
473+
474+ # # https://stackoverflow.com/questions/5738797/how-can-i-push-a-local-git-branch-to-a-remote-with-a-different-name-easily
475+ log_info " Add all the files:"
476+ git_commit_push
439477
440- # # https://stackoverflow.com/questions/5738797/how-can-i-push-a-local-git-branch-to-a-remote-with-a-different-name-easily
441- log_info " Add all the files:"
442- git_commit_push
443- log_info " Checkout ${GIT_DEFAULT_BRANCH} branch:" && \
444- git checkout ${GIT_DEFAULT_BRANCH}
478+ # log_info "Checkout ${GIT_DEFAULT_BRANCH} branch:" && \
479+ # git checkout ${GIT_DEFAULT_BRANCH}
480+
481+ log_info " Returning to the original branch and applying stashed changes."
482+ if ! git -C " ${REPO_DIR} " checkout -; then
483+ log_error " Failed to checkout original branch."
484+ fi
485+
486+ log_info " Returning to the original branch and applying stashed changes."
487+ if git -C " ${REPO_DIR} " stash list | grep -q ' stash' ; then
488+ if ! git -C " ${REPO_DIR} " stash pop; then
489+ log_warn " Failed to apply stashed changes. You may have uncommitted changes. Please handle manually."
490+ fi
491+ else
492+ log_info " No stashed changes to apply."
493+ fi
445494}
446495
447496
@@ -477,7 +526,7 @@ function main() {
477526 done
478527 shift $(( OPTIND- 1 ))
479528
480- log_debug " PROJECT_DIR =${PROJECT_DIR } "
529+ log_debug " REPO_DIR =${REPO_DIR } "
481530 log_debug " TEMP_DIR=${TEMP_DIR} "
482531
483532 search_repo_keywords
@@ -487,7 +536,15 @@ function main() {
487536 exit ${RETURN_STATUS}
488537 fi
489538
490- sync_public_branch
539+ trap on_error ERR
540+
541+ copy_project_to_temp_dir " ${REPO_DIR} "
542+ sync_public_branch " ${REPO_DIR} " " ${GIT_PUBLIC_BRANCH} "
543+
544+ log_info " Sync completed successfully."
545+ cleanup
546+
547+ trap - ERR
491548
492549}
493550
0 commit comments