├── LICENSE ├── README.md ├── contrib ├── git-sync-on-fswatch ├── git-sync-on-inotify └── modd.conf └── git-sync /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-sync 2 | 3 | Synchronize tracking repositories. 4 | 5 | This script intends to sync near-automatically via git 6 | in "tracking" repositories where a nice history is not 7 | as crucial as having one. 8 | 9 | 2012-2025 by Simon Thum and contributors, licensed under _CC0_ 10 | 11 | ## Use case 12 | 13 | Suppose you have a set of text files you care about, multiple machines 14 | to work on, and a central git repository (a.k.a. bare repository) at 15 | your disposal. You do not care about atomic commits, but coarse 16 | versioning and backup is grave. For example, server configuration or 17 | [org-mode](http://orgmode.org) files. 18 | 19 | In that case, `git-sync` will help you keep things in sync. 20 | 21 | Unlike the myriad of scripts to do just that already available, 22 | it follows the KISS principle: It is safe, small, requires nothing but 23 | git and bash, but does not even try to shield you from git. It is 24 | non-interactive, but will cautiously exit with a useful hint or error 25 | if there is any kind of problem. 26 | 27 | It is ultimately intended for git-savvy people. As a rule of thumb, if 28 | you know how to complete a failed rebase, you're fine. 29 | 30 | To synchronize automatically on filesystem changes, have a look at the 31 | `contrib` directory. Alternatively, here is an older blog about automatic 32 | `git-sync` operation: 33 | 34 | [Automated Syncing with Git](https://worthe-it.co.za/programming/2016/08/13/automated-syncing-with-git.html) 35 | 36 | Tested on msysgit and a real bash. In case you know bash scripting, it 37 | will probably make your eyes bleed, but for some reason it works. 38 | 39 | If you prefer, there is a [Typescript implementation](https://github.com/tiddly-gittly/git-sync-js) too. 40 | 41 | ### What does `git-sync` do? 42 | 43 | `git-sync` will likely get you from a dull normal git repo with some 44 | changes to an updated dull normal git repo equal to origin. It does 45 | this by commiting, pulling & pushing as appropriate. 46 | 47 | Care has been taken that any kind of problem, pre-existing or not, 48 | results in clear error messages and non-zero return code, but of 49 | course no guarantee can be given. 50 | 51 | The intent is to do everything that's needed to sync 52 | automatically, and resort to manual intervention as soon 53 | as something non-trivial occurs. It is designed to be safe 54 | in that `git-sync` will likely refuse to do anything not known to 55 | be safe. 56 | 57 | You can invoke git-sync in "check" mode, in which `git-sync` will not do 58 | anything except return zero if syncing may start, and non-zero if 59 | manual intervention is required. 60 | 61 | ### How am I supposed to use it? 62 | 63 | git-sync [mode] 64 | 65 | Mode can be empty, sync, or check. 66 | 67 | In "check" mode, `git-sync` will indicate if synchronization may start. This is useful 68 | to see if manual intervention is required (indicated by text and 69 | non-zero exit code). 70 | 71 | In sync mode (the default), just calling `git-sync` inside your 72 | repository will sync with the current branches remote, if that 73 | branch is enlisted. The repository must not be in the middle of a 74 | rebase, git-am, merge or whatever, not detached, and untracked files 75 | may also be treated as an obstacle (see Options). However, sync is 76 | likely to just work. Else, a clear error message should appear. 77 | 78 | If you don't sync in an intertwined manner (from multiple 79 | repositories/machines), `git-sync` is virtually guaranteed to succeed. 80 | When required git-sync will try to rebase, which may fail. This is 81 | when you'll need your git skills. 82 | 83 | If you want to use it programmatically or want fine-grain control 84 | over internal steps, you can have a try on [git-sync-js](https://github.com/tiddly-gittly/git-sync-js). Which is 85 | a typescript implementation of git-sync that follows the same logic. 86 | 87 | ## How does it work? 88 | 89 | The flow is roughly: 90 | 91 | 1. Sanity checks. You don't want to do this in the middle of a rebase. 92 | 2. Check for new files; exit if there are, unless allowed (see Options). In check mode, exit with 0. 93 | 3. Check for auto-commitable changes. 94 | 4. Perform auto-commit if there are any, see options. 95 | 5. Do one more check for leftover changes / general tidyness. 96 | 6. Fetch the upstream. 97 | 7. Relate upstream to ours. If ahead, push. If behind, fast-forward. If diverged, rebase, then push. 98 | 6. At exit, assert sync state once more just to be safe. 99 | 100 | On the first invocation, `git-sync` will ask you to enlist the 101 | current branch for sync using git config. This has to be done once for 102 | every repository (and branch, for completeness). 103 | 104 | Because git-sync rebases, the order of commits does not always reflect 105 | the order of changes. However auto-commit records the originating machine 106 | name and time by default. 107 | 108 | ## Options 109 | 110 | There are several `git config`-based options for tailoring your sync: 111 | 112 | branch.$branch_name.sync (bool) and git-sync.syncEnabled (bool) 113 | 114 | Configures the branch for sync. Without one of these two (or the equivalent `-s` 115 | command-line flag) git-sync will refuse to perform automated modifications of 116 | the repository. Both flags have the same meaning, and the branch-specific one 117 | can be used to override the settings of the generic flag on a per-branch basis. 118 | 119 | branch.$branch_name.syncNewFiles (bool) and git-sync.syncNewFiles (bool) 120 | 121 | Tells git-sync to invoke auto-commit even if new (untracked) files are 122 | present. Normally you have to commit those yourself to prevent accidental 123 | additions. git-sync will exit at stage 3 with an explanation in that case. Both 124 | flags have the same meaning, and the branch-specific one can be used to override 125 | the settings of the generic flag on a per-branch basis. 126 | 127 | branch.$branch_name.syncSkipHooks (bool) and git-sync.syncSkipHooks (bool) 128 | 129 | Tell git-sync to disable the pre-commit hook when creating the automated 130 | commit. Both flags have the same meaning, and the branch-specific one can be 131 | used to override the settings of the generic flag on a per-branch basis. 132 | 133 | branch.$branch_name.syncCommitMsg (string) 134 | 135 | A string which will be used in place of the default commit message (as shown 136 | below). 137 | 138 | branch.$branch_name.autocommitscript (string) 139 | 140 | A string which is being eval'ed by this script to perform an 141 | auto-commit. Here you can run a commit script which should not 142 | leave any uncommited state. The default will commit modified or 143 | all files with a more or less useful message. 144 | 145 | By default, commit is done using: 146 | 147 | git add -u ; git commit -m "changes from $(uname -n) on $(date)" 148 | 149 | Or if you enable `syncNewFiles`: 150 | 151 | git add -A ; git commit -m \"changes from $(uname -n) on $(date)\";" 152 | 153 | In both cases, setting the `syncSkipHooks` will add `--no-verify` to the `git 154 | commit` step. 155 | 156 | ### Command-line flags 157 | 158 | There are also some command-line flags you can set to control the sync: 159 | 160 | `-n` is the equivalent of `branch.$branch_name.syncNewFiles`, adding new files 161 | even if the matching `git config` option is not set. 162 | 163 | `-s` is the equivalent of `branch.$branch_name.sync`, allowing syncing a branch 164 | even if the matching `git config` option is not set. 165 | 166 | # `contrib` contents 167 | 168 | ## git-sync-on-inotify 169 | 170 | Automatically synchronize your git repository whenever a file is touched. 171 | 172 | `git-sync-on-inotify` uses the functionality of `git-sync` together with an 173 | `inotifywait` to automatically synchronize local changes you have made to your 174 | files as soon as they happen. `inotifywait` waits for events from the operating 175 | system which are triggered by any edits you make to files in the git repository. 176 | This means that updates are near instantaneous, without the need to resort to 177 | polling. `git-sync-on-inotify` still does a polling call to `git-sync` to 178 | ensure that changes from the upstream do make it in. This polling interval 179 | is configurable with the environment variable `GIT_SYNC_INTERVAL`. 180 | 181 | By design, this solution may miss changes until the next 182 | GIT_SYNC_INTERVAL time. 183 | 184 | ## git-sync-on-fswatch 185 | 186 | Works much like `git-sync-on-inotify`, from which it was derived, to 187 | automatically sync when triggered by file system watches. 188 | 189 | https://emcrisostomo.github.io/fswatch/ 190 | 191 | ## modd.conf 192 | 193 | Automatically sync upon local filesystem changes using modd. 194 | 195 | https://github.com/cortesi/modd 196 | 197 | This is a more robust solution to sync on local chances, but will not 198 | poll upstream except on startup. If you manage not to interleave 199 | sessions, that's fine. 200 | 201 | # License 202 | 203 | I declare this work to be useable under the provisions of the CC0 license. 204 | 205 | http://creativecommons.org/publicdomain/zero/1.0/ 206 | 207 | Attribution is appreciated, but not required. 208 | 209 | # Thanks 210 | 211 | Thanks go to all the people behind git. 212 | -------------------------------------------------------------------------------- /contrib/git-sync-on-fswatch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GIT_SYNC_DIRECTORY="${GIT_SYNC_DIRECTORY:-$(pwd)}" 3 | GIT_SYNC_COMMAND="${GIT_SYNC_COMMAND:-git-sync}" 4 | # In seconds 5 | GIT_SYNC_INTERVAL="${GIT_SYNC_INTERVAL:-60}" 6 | 7 | # Initialize the directory 8 | if [ ! -d "$GIT_SYNC_DIRECTORY" ]; then 9 | if [ -z "$GIT_SYNC_REPOSITORY" ]; then 10 | echo "Please specify a value for GIT_SYNC_REPOSITORY in order to initialize the git repository" 11 | exit 1 12 | else 13 | parent_dir=$(dirname "$GIT_SYNC_DIRECTORY") 14 | mkdir -p "$parent_dir" 15 | cd "$parent_dir" 16 | 17 | base_dir=$(basename "$GIT_SYNC_DIRECTORY") 18 | git clone "$GIT_SYNC_REPOSITORY" "$base_dir" 19 | cd "$GIT_SYNC_DIRECTORY" 20 | git config --add branch.$(basename $(git symbolic-ref -q HEAD)).pushRemote origin 21 | fi 22 | fi 23 | 24 | if [ ! -d "$GIT_SYNC_DIRECTORY" ]; then 25 | echo "Sync directory: $GIT_SYNC_DIRECTORY does not exist and could not be created." 26 | exit 1 27 | fi 28 | 29 | cd "$GIT_SYNC_DIRECTORY" 30 | 31 | remote_name=$(git config --get branch.$(basename $(git symbolic-ref -q HEAD)).remote) 32 | echo "Syncing $(git remote get-url $remote_name) at $(pwd) with a default sync interval of $GIT_SYNC_INTERVAL" 33 | 34 | $GIT_SYNC_COMMAND -n -s 35 | 36 | while true; do 37 | changedFile=$( 38 | timeout $GIT_SYNC_INTERVAL \ 39 | fswatch --exclude '\.git' --one-event --event MovedTo --event Updated --event Renamed --event Removed --event Created "$GIT_SYNC_DIRECTORY" \ 40 | --format-time "%w%f" 2>/dev/null 41 | ) 42 | if [ -z "$changedFile" ] 43 | then 44 | echo "Syncing due to timeout" 45 | $GIT_SYNC_COMMAND -n -s 46 | else 47 | echo "Syncing for: $changedFile" 48 | { git check-ignore "$changedFile" > /dev/null; } || $GIT_SYNC_COMMAND -n -s 49 | fi 50 | done 51 | -------------------------------------------------------------------------------- /contrib/git-sync-on-inotify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GIT_SYNC_DIRECTORY="${GIT_SYNC_DIRECTORY:-$(pwd)}" 3 | GIT_SYNC_COMMAND="${GIT_SYNC_COMMAND:-git-sync}" 4 | GIT_SYNC_INTERVAL="${GIT_SYNC_INTERVAL:-500}" 5 | 6 | # Initialize the directory 7 | if [ ! -d "$GIT_SYNC_DIRECTORY" ]; then 8 | if [ -z "$GIT_SYNC_REPOSITORY" ]; then 9 | echo "Please specify a value for GIT_SYNC_REPOSITORY in order to initialize the git repository" 10 | exit 1 11 | else 12 | parent_dir=$(dirname "$GIT_SYNC_DIRECTORY") 13 | mkdir -p "$parent_dir" 14 | cd "$parent_dir" 15 | 16 | base_dir=$(basename "$GIT_SYNC_DIRECTORY") 17 | git clone "$GIT_SYNC_REPOSITORY" "$base_dir" 18 | cd "$GIT_SYNC_DIRECTORY" 19 | git config --add branch.$(basename $(git symbolic-ref -q HEAD)).pushRemote origin 20 | fi 21 | fi 22 | 23 | if [ ! -d "$GIT_SYNC_DIRECTORY" ]; then 24 | echo "Sync directory: $GIT_SYNC_DIRECTORY does not exist and could not be created." 25 | exit 1 26 | fi 27 | 28 | cd "$GIT_SYNC_DIRECTORY" 29 | 30 | remote_name=$(git config --get branch.$(basename $(git symbolic-ref -q HEAD)).pushRemote) 31 | echo "Syncing $(git remote get-url $remote_name) at $(pwd) with a default sync interval of $GIT_SYNC_INTERVAL" 32 | 33 | $GIT_SYNC_COMMAND -n -s 34 | 35 | while true; do 36 | changedFile=$( 37 | inotifywait "$GIT_SYNC_DIRECTORY" -r -e modify,move,create,delete \ 38 | --format "%w%f" --exclude '\.git' -t "$GIT_SYNC_INTERVAL" 2>/dev/null 39 | ) 40 | if [ -z "$changedFile" ] 41 | then 42 | echo "Syncing due to timeout" 43 | $GIT_SYNC_COMMAND -n -s 44 | else 45 | echo "Syncing for: $changedFile" 46 | { git check-ignore "$changedFile" > /dev/null; } || $GIT_SYNC_COMMAND -n -s 47 | fi 48 | done 49 | -------------------------------------------------------------------------------- /contrib/modd.conf: -------------------------------------------------------------------------------- 1 | # Run git-sync on filesystem changes. 2 | # See https://github.com/cortesi/modd for futher information. 3 | ** { 4 | prep: git-sync 5 | } 6 | -------------------------------------------------------------------------------- /git-sync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # git-sync 4 | # 5 | # synchronize tracking repositories 6 | # 7 | # 2012-2025 by Simon Thum and contributors 8 | # Licensed as: CC0 9 | # 10 | # This script intends to sync via git near-automatically 11 | # in "tracking" repositories where a nice history is not 12 | # crucial, but having one at all is. 13 | # 14 | # Unlike the myriad of scripts to do just that already available, 15 | # it follows the KISS principle: It is small, requires nothing but 16 | # git and bash, but does not even try to shield you from git. 17 | # 18 | # Mode sync (default) 19 | # 20 | # Sync will likely get from you from a dull normal git repo with trivial 21 | # changes to an updated dull normal git repo equal to origin. No more, 22 | # no less. The intent is to do everything that's needed to sync 23 | # automatically, and resort to manual intervention as soon 24 | # as something non-trivial occurs. It is designed to be safe 25 | # in that it will likely refuse to do anything not known to 26 | # be safe. 27 | # 28 | # Mode check 29 | # 30 | # Check only performs the basic checks to make sure the repository 31 | # is in an orderly state to continue syncing, i.e. committing 32 | # changes, pull etc. without losing any data. When check returns 33 | # 0, sync can start immediately. This does not, however, indicate 34 | # that syncing is at all likely to succeed. 35 | 36 | # default commit message substituted into autocommit commands 37 | DEFAULT_AUTOCOMMIT_MSG="changes from $(uname -n) on $(date)" 38 | 39 | 40 | # AUTOCOMMIT_CMD="echo \"Please commit or stash pending changes\"; exit 1;" 41 | # TODO mode for stash push & pop 42 | 43 | print_usage() { 44 | cat << EOF 45 | usage: $0 [-h] [-n] [-s] [MODE] 46 | 47 | Synchronize the current branch to a remote backup 48 | MODE may be either "sync" (the default) or "check", to verify that the branch is ready to sync 49 | 50 | OPTIONS: 51 | -h Show this message 52 | -n Commit new files even if branch.\$branch_name.syncNewFiles isn't set 53 | -s Sync the branch even if branch.\$branch_name.sync isn't set 54 | EOF 55 | } 56 | sync_new_files_anyway="false" 57 | sync_anyway="false" 58 | 59 | while getopts "hns" opt ; do 60 | case $opt in 61 | h ) 62 | print_usage 63 | exit 0 64 | ;; 65 | n ) 66 | sync_new_files_anyway="true" 67 | ;; 68 | s ) 69 | sync_anyway="true" 70 | ;; 71 | esac 72 | done 73 | shift $((OPTIND-1)) 74 | 75 | # 76 | # utility functions, some adapted from git bash completion 77 | # 78 | 79 | __log_msg() 80 | { 81 | echo git-sync: $1 82 | } 83 | 84 | # echo the git dir 85 | __gitdir() 86 | { 87 | if [ "true" = "$(git rev-parse --is-inside-work-tree "$PWD" | head -1)" ]; then 88 | git rev-parse --git-dir "$PWD" 2>/dev/null 89 | fi 90 | } 91 | 92 | __syncnew_flag() 93 | { 94 | if [[ "true" == "$sync_new_files_anyway" ]]; then 95 | echo "true" 96 | return 97 | fi 98 | 99 | syncnew_flag="$(git config --get --bool branch.$branch_name.syncNewFiles)" 100 | if [ -z "$syncnew_flag" ] ; then 101 | syncnew_flag="$(git config --get --bool git-sync.syncNewFiles)" 102 | fi 103 | if [ -z "$syncnew_flag" ] ; then 104 | syncnew_flag="false" 105 | fi 106 | 107 | echo "$syncnew_flag" 108 | } 109 | 110 | __sync_flag() 111 | { 112 | if [[ "true" == "$sync_anyway" ]]; then 113 | echo "true" 114 | return 115 | fi 116 | 117 | sync_flag="$(git config --get --bool branch.$branch_name.sync)" 118 | if [ -z "$sync_flag" ] ; then 119 | sync_flag="$(git config --get --bool git-sync.syncEnabled)" 120 | fi 121 | if [ -z "$sync_flag" ] ; then 122 | sync_flag="false" 123 | fi 124 | 125 | echo "$sync_flag" 126 | } 127 | 128 | # compose the add step command 129 | __gitadd() 130 | { 131 | if [[ "true" == "$(__syncnew_flag)" ]]; then 132 | echo git add -A 133 | else 134 | echo git add -u 135 | fi 136 | } 137 | 138 | # compose the commit step command 139 | __gitcommit() 140 | { 141 | skip_hooks="$(git config --get --bool branch.$branch_name.syncSkipHooks)" 142 | if [ -z "$skip_hooks" ] ; then 143 | skip_hooks="$(git config --get --bool git-sync.syncSkipHooks)" 144 | fi 145 | if [ -z "$skip_hooks" ] ; then 146 | skip_hooks="false" 147 | fi 148 | 149 | if [[ "true" == "$skip_hooks" ]]; then 150 | echo git commit --no-verify -m \"%message\" 151 | else 152 | echo git commit -m \"%message\" 153 | fi 154 | } 155 | 156 | # echos repo state 157 | git_repo_state () 158 | { 159 | local g="$(__gitdir)" 160 | if [ -n "$g" ]; then 161 | if [ -f "$g/rebase-merge/interactive" ]; then 162 | echo "REBASE-i" 163 | elif [ -d "$g/rebase-merge" ]; then 164 | echo "REBASE-m" 165 | else 166 | if [ -d "$g/rebase-apply" ]; then 167 | echo "AM/REBASE" 168 | elif [ -f "$g/MERGE_HEAD" ]; then 169 | echo "MERGING" 170 | elif [ -f "$g/CHERRY_PICK_HEAD" ]; then 171 | echo "CHERRY-PICKING" 172 | elif [ -f "$g/BISECT_LOG" ]; then 173 | echo "BISECTING" 174 | fi 175 | fi 176 | if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then 177 | if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then 178 | echo "|BARE" 179 | else 180 | echo "|GIT_DIR" 181 | fi 182 | elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then 183 | git diff --no-ext-diff --quiet --exit-code || echo "|DIRTY" 184 | # if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then 185 | # git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" 186 | # fi 187 | # 188 | # if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then 189 | # if [ -n "$(git ls-files --others --exclude-standard)" ]; then 190 | # u="%" 191 | # fi 192 | # fi 193 | # 194 | # if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then 195 | # __git_ps1_show_upstream 196 | # fi 197 | fi 198 | else 199 | echo "NOGIT" 200 | fi 201 | } 202 | 203 | # check if we only have untouched, modified or (if configured) new files 204 | check_initial_file_state() 205 | { 206 | if [[ "true" == "$(__syncnew_flag)" ]]; then 207 | # allow for new files 208 | if [ ! -z "$(git status --porcelain | grep -E '^[^ \?][^M\?] *')" ]; then 209 | echo "NonNewOrModified" 210 | fi 211 | else 212 | # also bail on new files 213 | if [ ! -z "$(git status --porcelain | grep -E '^[^ ][^M] *')" ]; then 214 | echo "NotOnlyModified" 215 | fi 216 | fi 217 | } 218 | 219 | # look for local changes 220 | # used to decide if autocommit should be invoked 221 | local_changes() 222 | { 223 | if [ ! -z "$(git status --porcelain | grep -E '^(\?\?|[MARC] |[ MARC][MD])*')" ]; then 224 | echo "LocalChanges" 225 | fi 226 | } 227 | 228 | # determine sync state of repository, i.e. how the remote relates to our HEAD 229 | sync_state() 230 | { 231 | local count="$(git rev-list --count --left-right $remote_name/$branch_name...HEAD)" 232 | 233 | case "$count" in 234 | "") # no upstream 235 | echo "noUpstream" 236 | false 237 | ;; 238 | "0 0") 239 | echo "equal" 240 | true 241 | ;; 242 | "0 "*) 243 | echo "ahead" 244 | true 245 | ;; 246 | *" 0") 247 | echo "behind" 248 | true 249 | ;; 250 | *) 251 | echo "diverged" 252 | true 253 | ;; 254 | esac 255 | } 256 | 257 | # exit, issue warning if not in sync 258 | exit_assuming_sync() { 259 | if [ "equal" == "$(sync_state)" ] ; then 260 | __log_msg "In sync, all fine." 261 | exit 0; 262 | else 263 | __log_msg "Synchronization FAILED! You should definitely check your repository carefully!" 264 | __log_msg "(Possibly a transient network problem? Please try again in that case.)" 265 | exit 3 266 | fi 267 | } 268 | 269 | # 270 | # Here git-sync actually starts 271 | # 272 | 273 | # first some sanity checks 274 | rstate="$(git_repo_state)" 275 | if [[ -z "$rstate" || "|DIRTY" = "$rstate" ]]; then 276 | __log_msg "Preparing. Repo in $(__gitdir)" 277 | elif [[ "NOGIT" = "$rstate" ]] ; then 278 | __log_msg "No git repository detected. Exiting." 279 | exit 128 # matches git's error code 280 | else 281 | __log_msg "Git repo state considered unsafe for sync: $(git_repo_state)" 282 | exit 2 283 | fi 284 | 285 | # determine the current branch (thanks to stackoverflow) 286 | branch_name=$(git symbolic-ref -q HEAD) 287 | branch_name=${branch_name##refs/heads/} 288 | 289 | if [ -z "$branch_name" ] ; then 290 | __log_msg "Syncing is only possible on a branch." 291 | git status 292 | exit 2 293 | fi 294 | 295 | # while at it, determine the remote to operate on 296 | remote_name=$(git config --get branch.$branch_name.pushRemote) 297 | if [ -z "$remote_name" ] ; then 298 | remote_name=$(git config --get remote.pushDefault) 299 | fi 300 | if [ -z "$remote_name" ] ; then 301 | remote_name=$(git config --get branch.$branch_name.remote) 302 | fi 303 | 304 | if [ -z "$remote_name" ] ; then 305 | __log_msg "the current branch does not have a configured remote." 306 | echo 307 | __log_msg "Please use" 308 | echo 309 | __log_msg " git branch --set-upstream-to=[remote_name]/$branch_name" 310 | echo 311 | __log_msg "replacing [remote_name] with the name of your remote, i.e. - origin" 312 | __log_msg "to set the remote tracking branch for git-sync to work" 313 | exit 2 314 | fi 315 | 316 | # check if current branch is configured for sync 317 | if [[ "true" != "$(__sync_flag)" ]] ; then 318 | echo 319 | __log_msg "Please use" 320 | echo 321 | __log_msg " git config --bool branch.$branch_name.sync true" 322 | echo 323 | __log_msg "to enlist branch $branch_name for synchronization." 324 | __log_msg "Branch $branch_name has to have a same-named remote branch" 325 | __log_msg "for git-sync to work." 326 | echo 327 | __log_msg "(If you don't know what this means, you should change that" 328 | __log_msg "before relying on this script. You have been warned.)" 329 | echo 330 | exit 1 331 | fi 332 | 333 | # determine mode 334 | if [[ -z "$1" || "$1" == "sync" ]]; then 335 | mode="sync" 336 | elif [[ "check" == "$1" ]]; then 337 | mode="check" 338 | else 339 | __log_msg "Mode $1 not recognized" 340 | exit 100 341 | fi 342 | 343 | __log_msg "Mode $mode" 344 | 345 | __log_msg "Using $remote_name/$branch_name" 346 | 347 | # check for intentionally unhandled file states 348 | if [ ! -z "$(check_initial_file_state)" ] ; then 349 | __log_msg "There are changed files you should probably handle manually." 350 | git status 351 | exit 1 352 | fi 353 | 354 | # if in check mode, this is all we need to know 355 | if [ $mode == "check" ] ; then 356 | __log_msg "check OK; sync may start." 357 | exit 0 358 | fi 359 | 360 | # check if we have to commit local changes, if yes, do so 361 | if [ ! -z "$(local_changes)" ]; then 362 | autocommit_cmd="" 363 | config_autocommit_cmd="$(git config --get branch.$branch_name.autocommitscript)" 364 | 365 | # discern the three ways to auto-commit 366 | if [ ! -z "$config_autocommit_cmd" ]; then 367 | autocommit_cmd="$config_autocommit_cmd" 368 | else 369 | autocommit_cmd="$(__gitadd); $(__gitcommit);" 370 | fi 371 | 372 | commit_msg="$(git config --get branch.$branch_name.syncCommitMsg)" 373 | if [ "" == "$commit_msg" ]; then 374 | commit_msg=${DEFAULT_AUTOCOMMIT_MSG} 375 | fi 376 | autocommit_cmd=$(echo "$autocommit_cmd" | sed "s/%message/$commit_msg/") 377 | 378 | __log_msg "Committing local changes using ${autocommit_cmd}" 379 | eval $autocommit_cmd 380 | 381 | # after autocommit, we should be clean 382 | rstate="$(git_repo_state)" 383 | if [[ ! -z "$rstate" ]]; then 384 | __log_msg "Auto-commit left uncommitted changes. Please add or remove them as desired and retry." 385 | exit 1 386 | fi 387 | fi 388 | 389 | # fetch remote to get to the current sync state 390 | # TODO make fetching/pushing optional 391 | __log_msg "Fetching from $remote_name/$branch_name" 392 | git fetch $remote_name $branch_name 393 | if [ $? != 0 ] ; then 394 | __log_msg "git fetch $remote_name returned non-zero. Likely a network problem; exiting." 395 | exit 3 396 | fi 397 | 398 | case "$(sync_state)" in 399 | "noUpstream") 400 | __log_msg "Strange state, you're on your own. Good luck." 401 | exit 2 402 | ;; 403 | "equal") 404 | exit_assuming_sync 405 | ;; 406 | "ahead") 407 | __log_msg "Pushing changes..." 408 | git push $remote_name $branch_name:$branch_name 409 | if [ $? == 0 ]; then 410 | exit_assuming_sync 411 | else 412 | __log_msg "git push returned non-zero. Likely a connection failure." 413 | exit 3 414 | fi 415 | ;; 416 | "behind") 417 | __log_msg "We are behind, fast-forwarding..." 418 | git merge --ff --ff-only $remote_name/$branch_name 419 | if [ $? == 0 ]; then 420 | exit_assuming_sync 421 | else 422 | __log_msg "git merge --ff --ff-only returned non-zero ($?). Exiting." 423 | exit 2 424 | fi 425 | ;; 426 | "diverged") 427 | __log_msg "We have diverged. Trying to rebase..." 428 | git rebase $remote_name/$branch_name 429 | if [[ $? == 0 && -z "$(git_repo_state)" && "ahead" == "$(sync_state)" ]] ; then 430 | __log_msg "Rebasing went fine, pushing..." 431 | git push $remote_name $branch_name:$branch_name 432 | exit_assuming_sync 433 | else 434 | __log_msg "Rebasing failed, likely there are conflicting changes. Resolve them and finish the rebase before repeating git-sync." 435 | exit 1 436 | fi 437 | # TODO: save master, if rebasing fails, make a branch of old master 438 | ;; 439 | esac 440 | --------------------------------------------------------------------------------