├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── git-sync.sh ├── repo_settings └── default_sync_project.sh ├── request-git-sync.sh └── util ├── change_detector.sh ├── gawk ├── base_processing.gawk ├── change_detector.gawk ├── global.gawk ├── global_const.gawk ├── global_util.gawk ├── input_processing.gawk ├── post_fetch_processing.gawk └── pre_fetch_processing.gawk ├── repo_create.sh ├── restore-after-crash.sh ├── set_base_logic.sh ├── set_env.sh └── sync_pass.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /sync-projects 2 | /repo_settings 3 | /.vscode 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "util/bash-git-credential-helper"] 2 | path = util/bash-git-credential-helper 3 | url = https://github.com/it3xl/bash-git-credential-helper.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Ilya Tretyakov, it3xl.ru, git-repo-sync, https://github.com/it3xl/git-repo-sync 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-repo-sync 2 | 3 | ## Synchronization of Branches between Remote Git-repositories 4 | 5 | * The **git-repo-sync** is a bash script that synchronizes branches between two remote Git-repositories. 6 | * Git-tags are not synchronized. 7 | * You configure once what branches to synchronize and how. 8 | * You have to investigate some time to understand **git-repo-sync** conflict solving strategies and configuring. 9 | * Your run **git-repo-sync** periodically, preferebly every few minutes. 10 | 11 | *Warning!* Before reading the following keep in mind the difference between **local** and **remote** Git repositories. 12 | 13 | If your people push (commit) often to a single synchronized Git-branch and do it to different remote Git-repositories, then: 14 | 15 | * Run **git-repo-sync** before pushing to such the branch. 16 | 17 | ### Manual Action of Synchronization 18 | 19 | If someone pushed to the same branch (to another remote repo) in between the runing of **git-repo-sync** and your pusing. In this case: 20 | 21 | * Run **git-repo-sync** 22 | * Udpate your local repository (git fetch). 23 | * Check if your commits (push) wasn't deleted from your remote Git-repository. (FYI. You local commits in your local repository will not be changed!) 24 | * If it was deleted in the remote repo: 25 | * merge, rebase, etc., your local branch over the latest remote commits; 26 | * repeat Git-push for your branch. 27 | * Repeat everything until your pushed branch will be in expected commits in your remote Git-repository. 28 | 29 | This situation is covered by notifications but you have to configure this by yourlself in your enterprise environment. 30 | 31 | ## Use Cases 32 | 33 | * Adhesion of Git-remote-repositories of clients and software/support suppliers. Temporary or permanent. 34 | * Independence from an external remote Git repository if it is slow and could be out of service time after time. 35 | * You software teams have independent Git remote repositories. 36 | 37 | ## Requirements 38 | 39 | * Install Git 40 | * Use bash to run **git-repo-sync**. (It is not tested for zsh) 41 | * Tune any automation to run **git-repo-sync** periodically - crones, schedulers, Jenkins, GitLab-CI, etc. Or run it periodically yourself. 42 | 43 | This is enough for Windows, Arch based Linux (Manjaro), GNU based Linux 44 | 45 | ### macOS Additional Requirements 46 | 47 | * Update bash by running (restart your shell after this) 48 | * `brew install bash` 49 | * Install gAWK (GNU AWK) 50 | * `brew install gawk` 51 | 52 | ### Ubuntu Additional Requirements 53 | 54 | * Install gAWK (GNU AWK). 55 | * consider [this case](https://askubuntu.com/questions/561621/choosing-awk-version-on-ubuntu-14-04/561626#561626). 56 | 57 | ### Other Linux Additional Requirements 58 | 59 | * Check gawk presence. Run `gawk '{ exit; }'` or see https://unix.stackexchange.com/a/236666/207074 60 | * Check that bash version is 4.2 or above. 61 | 62 | ## How to use 63 | 64 | Copy **git-repo-sync** somewhere 65 | 66 | git clone https://github.com/it3xl/git-repo-sync.git 67 | 68 | Let **git-repo-sync** know location of your remote Git repositories.
69 | Modify `url_a` and `url_b` variables in [default_sync_project.sh](https://github.com/it3xl/git-repo-sync/blob/master/repo_settings/default_sync_project.sh).
70 | You can use URL-s and file paths. 71 | 72 | url_a=https://example.com/git/my_repo.git 73 | 74 | url_b='/c/my-folder/my-local-git-repo-folder' 75 | 76 | Run periodically the `git-sync.sh` file, which is located in the root of **git-repo-sync**. 77 | 78 | bash git-sync.sh 79 | 80 | The `git-sync.sh` will tell you if there are any troubles. For example you need to update awk to gAWK in Ubuntu. 81 | 82 | ## The Trade-off 83 | 84 | _The Trade-off_ is an automated Git-conflict solving logic of git-repo-sync. 85 | 86 | Even if you run **git-repo-sync** periodically and often, you still have a chance to get a Git-conflict. But a small chance.
87 | So, you must know what to do in case of Git-conflicts solved by git-repo-sync. 88 | 89 | ### Minimize chances of The Trade-off 90 | 91 | Run **git-repo-sync** before Git-pushing. I.e. synchronize your both Git-remote-repos before pushing into any of them.
92 | In this case, Git will be responsible for conflict resolution, not **git-repo-sync**. 93 | 94 | ### When git-repo-sync will be solving the conflicts. 95 | 96 | You should have the both 97 | 98 | - You run **git-repo-sync** rarely. I.e. someone aready pushed commites exactly to your branch after last running of **git-repo-sync**. 99 | - And you and your teammate have pushed changes to the same Git-branch but through different remote repositories and your remote repositories are no synchronized between your Git-pushes. 100 | 101 | Basically, you don't know about **git-repo-sync** until you are in this situation. 102 | 103 | ### Behavior of git-repo-sync in case of Git-conflicst 104 | 105 | **git-repo-sync** sees a Git-conflict and uses one of Conflict Solving strategies described below.
106 | As a result, you should provide the below steps to fix The Trade-off. 107 | 108 | ### Your steps to fix The Trade-off 109 | 110 | The main idea is "Re-push your local Git-commit in case of a conflict". 111 | 112 | - Run **git-repo-sync** to synchronize both Git-remote-repositories (if you have no periodical auto-runs). 113 | 114 | - Upload changes from your remote Git-repository to your local repository. 115 | - Check if you local commit have lost its remote counterpart. I.e. the commit exist only in your local repository. 116 | - Performe Git-merge/rebase of your local commit. 117 | - Performe Git-push of your changes. 118 | 119 | - Run **git-repo-sync** to synchronize your changes with changes from another side Git-remote-repository (if you have no periodical auto-runs). 120 | 121 | ### How do I know if there were Git-conflicts 122 | 123 | - Check it manually. This is described in the above steps. 124 | - **git-repo-sync** has notifications over plain text files. Ask your DevOps to distribute it. 125 | 126 | ## Using On Linux 127 | 128 | Run `git-sync.sh` and it will tell you what **git-repo-sync** needs.
129 | In most cases you have to install gAWK. This applies to Ubuntu.
130 | Docker Alpine Linux images require *bash* and *gAWK* to be installed.
131 | You have to update the *bash* if you use an extra old Linux distro. 132 | 133 | ## Using on Windows 134 | 135 | Ha! You're lucky. You have to do nothing and have five options to run **git-repo-sync**. 136 | 137 | Open PowerShell or CMD in the **git-repo-sync** folder and run one of three. 138 | 139 | "C:\Program Files\Git\bin\bash.exe" git-sync.sh 140 | "C:\Program Files\Git\usr\bin\bash.exe" git-sync.sh 141 | "C:\Program Files\Git\git-bash.exe" git-sync.sh 142 | 143 | Or you can reinstall Git and integrate the bash into your Windows during installation. Then run 144 | 145 | bash git-sync.sh 146 | 147 | Or you can try to update the PATH environment variable. Try to add the following (that wasn't tested by me) 148 | 149 | ;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin 150 | 151 | ## Do not synchronize all branches 152 | 153 | Despite that there are [fair cases](https://github.com/it3xl/git-repo-sync/issues/3#issuecomment-771494886) when it is useful to sync all branches, this is not always a good idea.
154 | Some well know Git-servers block some branches in different ways. Some of them create "trash"-branches which you do not want to see synchronized.
155 | 156 | So, you can synchronize branches that have special prefixes only.
157 | You could configure these prefixes in [default_sync_project.sh](https://github.com/it3xl/git-repo-sync/blob/master/repo_settings/default_sync_project.sh) configuration file.
158 | What's important, these prefixes are related to correspondent *conflict solving strategies*. 159 | 160 | ## Conflict Solving Strategies 161 | 162 | ### The Victim Strategy 163 | 164 | By default all branches are synced under this strategy.
165 | You can do whatever you want with such branches from both sides (repositories).
166 | In case of commit conflicts, any newest commit will win.
167 | You can relocate branches to any position, delete and move them back in history if you run **git-repo-sync** regularly.
168 | 169 | Use the following variable to limit branches synchronized by this strategy. 170 | 171 | victim_branches_prefix=@ 172 | 173 | The most common value for victim_branches_prefix is "@".
174 | In this case only branches that start with `@` will be synchronized. 175 | E.g. `@dev`, `@dev-staging`, `@test`, `@test-staging`, `@my-feature`, etc. 176 | 177 | ### The Conventional Strategy 178 | 179 | By using this strategy you limit what your teammates may do from another side repository with branches on your side remote repository. 180 | 181 | Branches with the following prefix will be owned by the repo from [url_a](https://github.com/it3xl/git-repo-sync/blob/master/repo_settings/default_sync_project.sh) variable. Let's call it *A side*. 182 | 183 | side_a_conventional_branches_prefix=client- 184 | 185 | Branches with the following prefix will be owned by the repo from [url_b](https://github.com/it3xl/git-repo-sync/blob/master/repo_settings/default_sync_project.sh) variable. Let's call it *B side*. 186 | 187 | side_b_conventional_branches_prefix=vendor- 188 | 189 | Other examples of prefix pairs: `a-`, `b-`; `microsoft/`, `google/`; `foo-`, `bar-`; 190 | 191 | On the owning side repo: You can do whatever you want with such branches. 192 | 193 | On a repo of another side:
194 | You can do fast-forward updates and merges.
195 | You can move such branches back in Git-history if you run **git-repo-sync** periodically. 196 | 197 | All commit conflicts will be solved in favor of the owning side.
198 | 199 | ### Other Unimplemented Strategies 200 | 201 | Just propouse something interesting.
202 | BTW, the Victim and Conventional approaches cover 80% of cases you need (I beleive). 203 | 204 | ## Disaster Protection 205 | 206 | People have to make mistakes to become better. This is normal. But let's protect our clients from such the mistakes.
207 | Define *sync_enabling_branch* variable 208 | 209 | sync_enabling_branch=it3xl_git_repo_sync_enabled 210 | 211 | Its value may represent any branch name.
212 | Examples: `@test`, `client-prod`, `vendor-master`, `it3xl_git_repo_sync_enabled`.
213 | 214 | The **git-repo-sync** will check if such a branch exist in both remote repositories and that it has the same or related commits, i.e. its commits are located in the same Git-tree.
215 | This will protect you from occasional adhesion of unrelated git-repositories and deletion of branches that have the same names.
216 | Git may store many independent projects (trees) in the same repository and this is uncommon behavior for many users. 217 | 218 | I advise to use `it3xl_git_repo_sync_enabled` branch name to make this explicit for others that their remote Git-repo is synchronized with another remote repo.
219 | They could search for the word *it3xl_git_repo_sync_enabled* in the Internet and understand the applied sync solution. 220 | 221 | Be aware that a branch mentioned in the `sync_enabling_branch` variable will be alwasy synchronized by **git-repo-sync**.
222 | Probably this is not a good idea to specify here the `master` branch name because a branch mentioned in `sync_enabling_branch` will be synchronized under the Victim strategy. But you can specify there a branch with one of your conventional prefixes for the Conventional syncing of it. For example `client-master`. 223 | 224 | ## Notes, Drawbacks & Limitations 225 | * Usage with SSH isn't tested but possible. 226 | * **git-repo-sync** is resilient for HTTP fails and interruptions. 227 | * It has protections from an occasional deletion of your entire remote repository. 228 | * Arbitrary Git-history rewriting is supported. 229 | * Within a single installation, **git-repo-sync** can synchronize as many pairs of Git-repositories as you want. Every sync pair is a sync project for **git-repo-sync**. 230 | * Git-tags are not synchronized. 231 | * Remarks why: Some Git-servers block manipulations with Git-tags. Time was saved for research and covering all possible cases. Another repo's tags created issues for Git-tag based CI/CD-s. 232 | * **git-repo-sync** doesn't attempt to do Git-merge or rebase. Just FYI. 233 | 234 | ## Support Operations 235 | 236 | ### Remote Repo Replacing Support 237 | 238 | This is a real case of my customer. You may want to synchronize your existing Git-repo with a Git-repo of your new software parnter. 239 | 240 | Option 1.
241 | Create a new git-repo-sync project and use it (project description file or environment variables). 242 | 243 | Option 2.
244 | Modify your existing project. Update its description file or environment variables.
245 | Delete `git-repo-sync/sync-projects/` directory.
246 | Start synchronization as usual. 247 | 248 | Option 3.
249 | Your Git-repository is extra huge and you can't recreate it. This is a TL;DR. Ask a Git-professional for a help. 250 | 251 | ## Known Issues 252 | 253 | ### It is still untracked 254 | `Something went wrong for . It is still untracked. 255 | Possibly the program or the network were interrupted.` 256 | 257 | * Disable your antivirus or Check Point. 258 | * Check if this branch is blocked by your Git-server. 259 | 260 | ## Automation support 261 | * **git-repo-sync** works with remote Git repositories asynchronously, by default. 262 | * It works much faster under \*nix OS-es because Git-bash on Windows is slower. But compare to network latency, this is nothing. 263 | * You can separate change detection and synchronization phases of **git-repo-sync** for readability of CI/CD logs. 264 | * Multiple configuration capabilities are supported. Environment, configuration files, combination of them. 265 | * Integration with **bash Git Credential Helper - [git-cred](https://github.com/it3xl/bash-git-credential-helper)** to obtain credentials from a parent shell environment. 266 | * You shouldn't do anything in case of connectivity fails. Continue to run **git-repo-sync** periodically and everything will be restored automatically. 267 | * After every synchronization, analyze notification files to send notifications about branch deletions or commit conflict solving. 268 | See `git-repo-sync/sync-projects//file-signals/` 269 | * `notify_solving` - for conflict solving 270 | * `notify_del` - for deletions 271 | * See [instructions](https://github.com/it3xl/git-repo-sync/blob/master/repo_settings/default_sync_project.sh) on how to configure more synchronization pairs of remote Git repositories. 272 | * Number of pairs is unlimited. Every pair is a separate sync project. 273 | 274 | -------------------------------------------------------------------------------- /git-sync.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | set -euf +x -o pipefail 4 | 5 | #echo 6 | #echo Start $(basename $BASH_SOURCE) 7 | 8 | invoke_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | source "$invoke_path/util/set_env.sh" "$@" 10 | 11 | 12 | 13 | echo 14 | source "$path_git_sync_util/repo_create.sh" 15 | cd "$path_sync_repo" 16 | 17 | 18 | rm -f "$env_notify_del_file" 19 | rm -f "$env_notify_solving_file" 20 | 21 | source "$path_git_sync_util/restore-after-crash.sh" 22 | 23 | 24 | source "$path_git_sync_util/sync_pass.sh" 25 | source "$path_git_sync_util/sync_pass.sh" 26 | 27 | 28 | echo 29 | echo @ RESULT: Successfully completed with $git_sync_pass_num_required sync-pass'/'es. 30 | #echo 31 | #echo End $(basename $BASH_SOURCE) 32 | -------------------------------------------------------------------------------- /repo_settings/default_sync_project.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | # 3 | ## Post your questions on https://github.com/it3xl/git-repo-sync/issues 4 | ## I will be glad to explain the ambiguities and to improve this instruction for others. 5 | 6 | 7 | # Configure location of your remote Git repositories. 8 | # 9 | url_a=https://example.com/git/my_repo.git 10 | # 11 | url_b='/c/my-folder/my-local-git-repo-folder' 12 | 13 | # Don't sync all branches. Synchronize branches with the following prefix. 14 | # 15 | # victim_branches_prefix=@ 16 | 17 | # Prevent catastrophe! 18 | # Don't sync repositories without the specified branch in the same Git-tree (same or related commits). 19 | # 20 | # sync_enabling_branch=it3xl_git_repo_sync_enabled 21 | 22 | # Limit branch manipulations for another repository side. 23 | # See "Conventional Syncing strategy" below. 24 | # 25 | # side_a_conventional_branches_prefix=client- 26 | # side_b_conventional_branches_prefix=vendor- 27 | 28 | 29 | # Automation integration. See details below 30 | # 31 | # git_sync_project_folder=my-sync-project 32 | # 33 | # use_bash_git_credential_helper=1 34 | 35 | 36 | # 37 | ## 38 | ### Descriptions & Explanations 39 | ## 40 | # 41 | 42 | 43 | ## This Configuration File 44 | # 45 | # Variables mentioned in this file configure synchronization of two remote Git-repositories. 46 | # Use the following options to configure your synchronization. 47 | # 48 | ## Uncomment and modify required variables in this file. 49 | # 50 | ## Create a copy of this file in one of the following locations 51 | # * git-repo-sync/repo_settings 52 | # * git-repo-sync/../git-repo-sync.repo_settings 53 | # Pass the name of your copied file to git-sync.sh as the first parameter for every run. 54 | # Invocation example: $ ./git-sync.sh my-sync-project.sh 55 | # 56 | ## Create a copy of this file in an arbitrary location. 57 | # Pass its full path to git-sync.sh as the first parameter for every run. 58 | # Or use a relative to "git-repo-sync" path. 59 | # 60 | ## Declare the variables in your script or environment. 61 | # Warning! Your configuration file will be ignored if your parent environment 62 | # or calling script have the git_sync_project_folder variable. 63 | 64 | ## url_a 65 | ## url_b 66 | # Let's call your two synchronized remote Git repositories as sides A and B. 67 | # Then url_a and url_b variables point to git remote repositories of the A and B sides accordingly. 68 | # It could be an URL or a file path (SSH addresses wasn't tested yet). 69 | # For paths on Windows use the following notation 70 | # url_a="/c/my-folder/my-git-local-repo-folder" 71 | 72 | ## victim_branches_prefix 73 | # Limit branches which will be synchronized under a Victim Sync strategy. 74 | # If undefined or empty then all branches will be synced. 75 | # (except conventionally prefixed branches described below) 76 | # 77 | ## The Victim Sync strategy. 78 | # You can do whatever you want with such branches from both remote sides (repositories). 79 | # In case of commit conflicts, any newest commit will win. 80 | # You can relocate branches to any position or delete them, etc. 81 | # You can move a branch back in history if you sync your repos regularly. 82 | # 83 | # The most common value is "@". 84 | # Examples: @dev, @dev-staging, @test, @test-staging 85 | 86 | ## sync_enabling_branch 87 | # Represents any branch name. 88 | # The git-repo-sync will check that such a branch exist in both remote repositories 89 | # and that it has the same or related commits, i.e. located in the same Git-tree. 90 | # This will protect you from occasional adhesion of unrelated git-repositories. 91 | # Git may store many independent projects in the same repository and this is uncommon behavior for many users. 92 | # 93 | # We advise to use it3xl_git_repo_sync_enabled name to make it explicit to others that their Git-repo is syncing with another remote repo. 94 | # Examples: @test, client-prod, vendor-master, it3xl_git_repo_sync_enabled 95 | 96 | ## side_a_conventional_branches_prefix 97 | # Branches with a prefix from this variable will be owned by the repo from "url_a". Let's call it A side. 98 | # 99 | ## side_b_conventional_branches_prefix 100 | # Branches with a prefix from this variable will be owned by the repo from "url_b". Let's call it B side. 101 | # 102 | # Branches with such prefixes will be updated under the Conventional Sync strategy. 103 | # You can define both or one variable. 104 | # 105 | ## The Conventional Sync strategy 106 | # On a repo of the owning side: You can do whatever you want with such branches. 107 | # On a repo of another side: You can do fast-forward updates and merges. 108 | # You can move such a branch back in Git-history from an non-owning side if you run git-repo-sync regularly. 109 | # All commit conflicts will be solved in favor of the owning side. 110 | # 111 | # Example of prefix pairs: client-, vendor-; a-, b-; microsoft/, google/ 112 | 113 | 114 | ## git_sync_project_folder 115 | # It defines a folder in which your sync-project artifacts will be stored inside of "git-repo-sync/sync-projects/". 116 | # In the absence of git_sync_project_folder, its value will be taken from the name of a provided configuration file. 117 | # 118 | # Warning! Your configuration file will be ignored if your parent environment 119 | # or calling script have this variable. In this case, all required variables must be configured in the parent environment. 120 | 121 | 122 | ## use_bash_git_credential_helper=1 123 | # This variables enables using of the "git-cred", the "bash Git Credential Helper" from https://github.com/it3xl/bash-git-credential-helper 124 | # 125 | # Git-cred allows you to use Git-credential values from environment variables 126 | # which are defined automatically by any Continues Integration (CI/CD) tool. 127 | # 128 | # You can use "git-cred" as an external tool and tune everything manually. 129 | # But configuring it here allows you to initialize git-cred only once. 130 | # BTW, "git-cred" allows a free relocation of your installation "git-repo-sync" folder. 131 | # 132 | # * Before using git-cred you must complete the following steps. 133 | # 134 | # ** Load Git sub-modules inside of your "git-repo-sync" folder (https://github.com/it3xl/git-repo-sync) 135 | # 136 | # ** Before any call to "git-sync.sh" or to "request-git-sync.sh", define the following environment variables in your CI/CD automation server or tool 137 | # For the repo in $url_a 138 | # git_cred_username_repo_a=some-login 139 | # git_cred_password_repo_a=some-password 140 | # For the repo in $url_b 141 | # git_cred_username_repo_b=another-login 142 | # git_cred_password_repo_b=another-password 143 | # 144 | # ** Assign use_bash_git_credential_helper variable to 1. 145 | 146 | 147 | -------------------------------------------------------------------------------- /request-git-sync.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | set -euf +x -o pipefail 4 | 5 | echo 6 | echo Start $(basename $BASH_SOURCE) 7 | 8 | invoke_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | source "$invoke_path/util/set_env.sh" "$@" 10 | 11 | rm -f "$env_modifications_signal_file" 12 | rm -f "$env_modifications_signal_file_a" 13 | rm -f "$env_modifications_signal_file_b" 14 | 15 | echo 16 | source "$path_git_sync_util/repo_create.sh" 17 | cd "$path_sync_repo" 18 | 19 | source "$path_git_sync_util/restore-after-crash.sh" 20 | 21 | source "$path_git_sync_util/change_detector.sh" 22 | 23 | echo 24 | if (( $changes_detected == 1 )); then 25 | install -D /dev/null "$env_modifications_signal_file" 26 | install -D /dev/null "$env_modifications_signal_file_a" 27 | install -D /dev/null "$env_modifications_signal_file_b" 28 | 29 | # Passing of remote refs to prevent excessive network requesting. 30 | echo "$remote_refs_a" >> "$env_modifications_signal_file_a" 31 | 32 | echo "$remote_refs_b" >> "$env_modifications_signal_file_b" 33 | 34 | echo '@' RESULT: Synchronization requested. 35 | else 36 | echo '@' RESULT: Refs are the same. Exit. 37 | echo 38 | 39 | # exit 40 | fi 41 | 42 | 43 | echo 44 | echo End $(basename $BASH_SOURCE) 45 | -------------------------------------------------------------------------------- /util/change_detector.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | if [[ $env_allow_async == 1 ]]; then 4 | echo '! Async (async remote refs checks are used)' 5 | 6 | mkdir -p "$path_async_output" 7 | 8 | git ls-remote --heads "$url_a" $sync_ref_specs > "$path_async_output/remote_refs_a.txt" & 9 | pid_remote_refs_a=$! 10 | git ls-remote --heads "$url_b" $sync_ref_specs > "$path_async_output/remote_refs_b.txt" & 11 | pid_remote_refs_b=$! 12 | 13 | err_remote_refs_a=0; 14 | wait $pid_remote_refs_a || err_remote_refs_a=$? 15 | err_remote_refs_b=0; 16 | wait $pid_remote_refs_b || err_remote_refs_b=$? 17 | 18 | remote_refs_a=$(<"$path_async_output/remote_refs_a.txt") 19 | remote_refs_b=$(<"$path_async_output/remote_refs_b.txt") 20 | 21 | if (( $err_remote_refs_a != 0 )); then 22 | echo 23 | echo "> Async fail | Change detection | $origin_a | Error $err_remote_refs_a" 24 | echo "$remote_refs_a" 25 | echo ">" 26 | fi; 27 | if (( $err_remote_refs_b != 0 )); then 28 | echo 29 | echo "> Async fail | Change detection | $origin_b | Error $err_remote_refs_b" 30 | echo "$remote_refs_b" 31 | echo ">" 32 | fi; 33 | if (( $err_remote_refs_a != 0 )); then 34 | echo 35 | echo "> Exit." 36 | exit $err_remote_refs_a; 37 | fi; 38 | if (( $err_remote_refs_b != 0 )); then 39 | echo 40 | echo "> Exit." 41 | exit $err_remote_refs_b; 42 | fi; 43 | else 44 | echo '! Sync (sync remote refs checks are used)' 45 | 46 | remote_refs_b=$(git ls-remote --heads "$url_b" $sync_ref_specs) 47 | remote_refs_a=$(git ls-remote --heads "$url_a" $sync_ref_specs) 48 | fi; 49 | 50 | 51 | track_refs_a=$(git for-each-ref --format="%(objectname) %(refname)" $track_refspecs_a) 52 | track_refs_b=$(git for-each-ref --format="%(objectname) %(refname)" $track_refspecs_b) 53 | 54 | ## remote_count=$(echo "$remote_refs_a" | awk 'END { print NR }';) 55 | ## track_count=$(echo "$track_refs_a" | awk 'END { print NR }';) 56 | 57 | export remote_refs_a; 58 | export remote_refs_b; 59 | export track_refs_a; 60 | export track_refs_b; 61 | 62 | changes_detected=$($env_awk_edition --file="$path_git_sync_util/gawk/change_detector.gawk") 63 | echo 64 | 65 | -------------------------------------------------------------------------------- /util/gawk/base_processing.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | @include "global.gawk" 4 | 5 | 6 | function unlock_deletion(){ 7 | if(remote_empty[side_both]){ 8 | deletion_blocked_by = "deletion-blocked:remotes-are-empty" 9 | return; 10 | } 11 | if(remote_empty[side_a]){ 12 | deletion_blocked_by = "deletion-blocked:a-remote-is-empty" 13 | return; 14 | } 15 | if(remote_empty[side_b]){ 16 | deletion_blocked_by = "deletion-blocked:b-remote-is-empty" 17 | return; 18 | } 19 | 20 | deletion_allowed = 1; 21 | } 22 | function generate_missing_refs( ref){ 23 | for(ref in refs){ 24 | if(!refs[ref][side_a][remote][ref_key]){ 25 | refs[ref][side_a][remote][ref_key] = remote_refs_path ref; 26 | } 27 | if(!refs[ref][side_b][remote][ref_key]){ 28 | refs[ref][side_b][remote][ref_key] = remote_refs_path ref; 29 | } 30 | if(!refs[ref][side_a][track][ref_key]){ 31 | refs[ref][side_a][track][ref_key] = track_refs_path origin[side_a] "/" ref; 32 | } 33 | if(!refs[ref][side_b][track][ref_key]){ 34 | refs[ref][side_b][track][ref_key] = track_refs_path origin[side_b] "/" ref; 35 | } 36 | 37 | # d_trace("ref is " ref); 38 | # d_trace("track ref_key side_a " refs[ref][side_a][track][ref_key]); 39 | # d_trace("track ref_key side_b " refs[ref][side_b][track][ref_key]); 40 | # d_trace("remote ref_key side_a " refs[ref][side_a][remote][ref_key]); 41 | # d_trace("remote ref_key side_b " refs[ref][side_b][remote][ref_key]); 42 | } 43 | } 44 | 45 | function append_by_side(side, host, addition){ 46 | host[side] = host[side] (host[side] ? newline_substitution : "") addition; 47 | } 48 | function append_by_val(host, addition){ 49 | host[val_key] = host[val_key] (host[val_key] ? newline_substitution : "") addition; 50 | } 51 | 52 | function use_victim_sync(ref){ 53 | return !use_conv_sync(ref); 54 | } 55 | 56 | function use_conv_sync(ref) { 57 | return side_a_conv_ref(ref) || side_b_conv_ref(ref); 58 | } 59 | 60 | function side_a_conv_ref(ref){ 61 | return side_conv_ref(ref, side_a); 62 | } 63 | 64 | function side_b_conv_ref(ref){ 65 | return side_conv_ref(ref, side_b); 66 | } 67 | 68 | function side_conv_ref(ref, side, conv_pref){ 69 | conv_pref = prefix[side]; 70 | if(!conv_pref){ 71 | return 0; 72 | } 73 | 74 | return index(ref, conv_pref) == 1; 75 | } 76 | 77 | function explicit_victim_ref(ref){ 78 | if(!pref_victim){ 79 | return 0; 80 | } 81 | 82 | return index(ref, pref_victim) == 1; 83 | } 84 | 85 | function sync_all_refs(){ 86 | return !pref_victim; 87 | } 88 | -------------------------------------------------------------------------------- /util/gawk/change_detector.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | @include "global.gawk" 4 | 5 | 6 | BEGIN { 7 | write_after_line("> change detecting"); 8 | 9 | sync_enabling_branch = ENVIRON["sync_enabling_branch"]; 10 | 11 | parse_refs("remote_refs_a", remote, side_a); 12 | parse_refs("remote_refs_b", remote, side_b); 13 | parse_refs("track_refs_a", track, side_a); 14 | parse_refs("track_refs_b", track, side_b); 15 | 16 | exit; 17 | } 18 | 19 | function parse_refs(env_var, dest_key, side, split_arr, ind, val, split_val, sha, ref){ 20 | split(ENVIRON[env_var], split_arr, "\n"); 21 | 22 | for(ind in split_arr){ 23 | val = split_arr[ind]; 24 | if(!val){ 25 | continue; 26 | } 27 | 28 | split(val, split_val, " "); 29 | 30 | sha = split_val[1]; 31 | ref = split_val[2]; 32 | sub(/^refs\/heads\/|^refs\/remotes\/[^\/]+\//, "", ref); 33 | if(!ref || !sha){ 34 | continue; 35 | } 36 | 37 | refs[ref][side][dest_key][sha_key] = sha; 38 | } 39 | } 40 | 41 | 42 | END { 43 | process_emptiness(); 44 | 45 | if(side_empty[side_both]){ 46 | write("both-repos-are-empty") 47 | } 48 | block_sync(); 49 | 50 | changed = 0; 51 | 52 | for (ref in refs) { 53 | a_remote = refs[ref][side_a][remote][sha_key] 54 | a_track = refs[ref][side_a][track][sha_key] 55 | 56 | b_remote = refs[ref][side_b][remote][sha_key] 57 | b_track = refs[ref][side_b][track][sha_key] 58 | 59 | if(!a_remote || !a_track || !b_remote || !b_track){ 60 | changed = 1; 61 | trace(ref " has empty sha; On" (a_track?"":" a_track") (b_track?"":" b_track") (a_remote?"":" a_remote") (b_remote?"":" b_remote")) 62 | continue; 63 | } 64 | 65 | if(a_remote != b_remote \ 66 | || a_track != b_track \ 67 | || a_remote != a_track){ 68 | changed = 1; 69 | trace(ref " has unequal sha") 70 | continue; 71 | } 72 | } 73 | 74 | print changed; 75 | 76 | write("> change detecting end"); 77 | } 78 | 79 | function block_sync( ref){ 80 | if(side_empty[side_both]){ 81 | return; 82 | } 83 | 84 | ref = sync_enabling_branch; 85 | 86 | if(!ref){ 87 | return; 88 | } 89 | 90 | _block_sync_by_side(refs[ref][side_a][remote][sha_key], side_a); 91 | _block_sync_by_side(refs[ref][side_b][remote][sha_key], side_b); 92 | } 93 | 94 | function _block_sync_by_side(remote_sha, side){ 95 | if(remote_sha){ 96 | return; 97 | } 98 | if(remote_empty[side]){ 99 | return; 100 | } 101 | 102 | write("Syncing is blocked as \"" sync_enabling_branch "\" branch doesn't exist in the \""side"\" remote repo"); 103 | 104 | exit 92; 105 | } 106 | 107 | -------------------------------------------------------------------------------- /util/gawk/global.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | @include "global_const.gawk" 4 | @include "global_util.gawk" 5 | 6 | 7 | function process_emptiness( not_empty_remote, remote_sha, not_empty_track, track_sha){ 8 | for (side in sides) { 9 | for (ref in refs) { 10 | remote_sha = refs[ref][side][remote][sha_key]; 11 | if(!remote_sha) 12 | continue; 13 | 14 | not_empty_remote[side] = 1; 15 | break; 16 | } 17 | } 18 | 19 | remote_empty[side_a] = !not_empty_remote[side_a] 20 | remote_empty[side_b] = !not_empty_remote[side_b] 21 | remote_empty[side_any] = remote_empty[side_a] || remote_empty[side_b]; 22 | remote_empty[side_both] = remote_empty[side_a] && remote_empty[side_b]; 23 | 24 | 25 | for (side in sides) { 26 | for (ref in refs) { 27 | track_sha = refs[ref][side][track][sha_key]; 28 | if(!track_sha) 29 | continue; 30 | 31 | not_empty_track[side] = 1; 32 | break; 33 | } 34 | } 35 | 36 | track_empty[side_a] = !not_empty_track[side_a] 37 | track_empty[side_b] = !not_empty_track[side_b] 38 | track_empty[side_any] = track_empty[side_a] || track_empty[side_b]; 39 | track_empty[side_both] = track_empty[side_a] && track_empty[side_b]; 40 | 41 | side_empty[side_a] = remote_empty[side_a] && track_empty[side_a]; 42 | side_empty[side_b] = remote_empty[side_b] && track_empty[side_b]; 43 | side_empty[side_any] = side_empty[side_a] || side_empty[side_b]; 44 | side_empty[side_both] = side_empty[side_a] && side_empty[side_b]; 45 | 46 | attach_mode = remote_empty[side_any] && ! track_empty[side_any]; 47 | } 48 | -------------------------------------------------------------------------------- /util/gawk/global_const.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | BEGIN { # Constants. 4 | track_refs_path = "refs/remotes/"; 5 | remote_refs_path = "refs/heads/"; 6 | 7 | remote = "remote-key" 8 | track = "track-key" 9 | all_track = "all-track-key" 10 | 11 | sha_key = "sha-key"; 12 | ref_key = "ref-key"; 13 | 14 | side_a = "A"; 15 | side_b = "B"; 16 | 17 | side_any = "side-any-key"; 18 | side_both = "side-both-key"; 19 | 20 | sides[side_a] = side_a; 21 | sides[side_b] = side_b; 22 | 23 | asides[side_a] = sides[side_b] 24 | asides[side_b] = sides[side_a] 25 | 26 | val_key = "a-value-key"; 27 | common = "common-key"; 28 | equal = "equal-key"; 29 | empty_both = "empty-both-key"; 30 | empty_any = "empty-any-key"; 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /util/gawk/global_util.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | BEGIN { # Constants. 4 | out_stream_attached = "/dev/stderr"; 5 | } 6 | BEGIN { 7 | awk_trace_on = !! ENVIRON["env_awk_trace_on"]; 8 | } 9 | 10 | 11 | function write(msg){ 12 | print msg >> out_stream_attached; 13 | } 14 | function write_after_line(msg){ 15 | write("\n" msg); 16 | } 17 | function trace(msg){ 18 | if(!awk_trace_on) 19 | return; 20 | 21 | if(!msg){ 22 | print "|" >> out_stream_attached; 23 | return; 24 | } 25 | 26 | print "|" msg >> out_stream_attached; 27 | } 28 | function trace_header(msg){ 29 | trace(); 30 | trace(msg); 31 | trace(); 32 | } 33 | function trace_after_line(msg){ 34 | trace(); 35 | trace(msg); 36 | } 37 | function trace_line(msg){ 38 | trace(msg); 39 | trace(); 40 | } 41 | function d_trace(msg){ # Development trace. 42 | if(0) 43 | return; 44 | 45 | trace("|~" msg) 46 | } 47 | 48 | END{ # Disposing. 49 | # Possibly the close here is excessive. 50 | #https://www.gnu.org/software/gawk/manual/html_node/Close-Files-And-Pipes.html#Close-Files-And-Pipes 51 | close(out_stream_attached); 52 | } 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /util/gawk/input_processing.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | @include "global.gawk" 4 | 5 | 6 | BEGIN { # Constants. 7 | } 8 | BEGIN { # Parameters. 9 | initial_states_processing(); 10 | } 11 | function initial_states_processing( side, split_arr, split_val, ind, ref, val, sha){ 12 | sync_enabling_branch = ENVIRON["sync_enabling_branch"]; 13 | 14 | origin_a = ENVIRON["origin_a"]; 15 | if(!origin_a){ 16 | write("Error. Parameter origin_a is empty"); 17 | exit 82; 18 | } 19 | origin[side_a] = origin_a; 20 | origin_a = "" 21 | 22 | origin_b = ENVIRON["origin_b"]; 23 | if(!origin_b){ 24 | write("Error. Parameter origin_b is empty"); 25 | exit 83; 26 | } 27 | origin[side_b] = origin_b; 28 | origin_b = "" 29 | 30 | pref_a_conv = ENVIRON["pref_a_conv"]; 31 | prefix[side_a] = pref_a_conv; 32 | pref_a_conv = "" 33 | 34 | pref_b_conv = ENVIRON["pref_b_conv"]; 35 | prefix[side_b] = pref_b_conv; 36 | pref_b_conv = "" 37 | 38 | pref_victim = ENVIRON["pref_victim"]; 39 | 40 | newline_substitution = ENVIRON["env_awk_newline_substitution"]; 41 | if(!newline_substitution){ 42 | write("Error. Parameter newline_substitution is empty"); 43 | exit 86; 44 | } 45 | 46 | split(ENVIRON["conv_move"], split_arr, "\n"); 47 | for(ind in split_arr){ 48 | val = split_arr[ind]; 49 | 50 | split(val, split_val, " "); 51 | 52 | ref = split_val[1]; 53 | sha = split_val[2]; 54 | if(!ref || !sha){ 55 | continue; 56 | } 57 | 58 | conv_move[ref][sha]; 59 | } 60 | 61 | split(ENVIRON["victim_move"], split_arr, "\n"); 62 | for(ind in split_arr){ 63 | val = split_arr[ind]; 64 | 65 | split(val, split_val, " "); 66 | 67 | ref = split_val[1]; 68 | sha = split_val[2]; 69 | if(!ref || !sha){ 70 | continue; 71 | } 72 | 73 | victim_move[ref][sha]; 74 | } 75 | } 76 | 77 | 78 | BEGINFILE { # Preparing processing for every portion of refs. 79 | file_states_processing(); 80 | } 81 | function file_states_processing() { 82 | current_dest = ""; 83 | current_side = ""; 84 | ref_prefix = ""; 85 | switch (++file_num) { 86 | case 1: 87 | current_dest = remote; 88 | current_side = side_a; 89 | ref_prefix = remote_refs_path; 90 | break; 91 | case 2: 92 | current_dest = remote; 93 | current_side = side_b; 94 | ref_prefix = remote_refs_path; 95 | break; 96 | case 3: 97 | current_dest = track; 98 | current_side = side_a; 99 | ref_prefix = track_refs_path origin[side_a] "/"; 100 | break; 101 | case 4: 102 | current_dest = track; 103 | current_side = side_b; 104 | ref_prefix = track_refs_path origin[side_b] "/"; 105 | break; 106 | } 107 | } 108 | { # Ref states preparation. 109 | if(!$2){ 110 | # Empty input stream of an empty refs' variable or an empty line. 111 | next; 112 | } 113 | 114 | prepare_ref_states(); 115 | } 116 | function prepare_ref_states( ref){ 117 | prefix_name_key(); 118 | 119 | ref = $3; 120 | if( !sync_all_refs() \ 121 | && ref != sync_enabling_branch \ 122 | && !explicit_victim_ref(ref) \ 123 | && !side_a_conv_ref(ref) \ 124 | && !side_b_conv_ref(ref) \ 125 | ){ 126 | trace("!unexpected " $2 " in " current_dest " " $1 "; branch name (" ref ") has no allowed prefixes or not allowed"); 127 | 128 | next; 129 | } 130 | 131 | sync_refs[ref] = ref; 132 | refs[ref][current_side][current_dest][sha_key] = $1; 133 | refs[ref][current_side][current_dest][ref_key] = $2; 134 | } 135 | function prefix_name_key( refspec_split) { # Generates a common key for all 4 locations of every ref. 136 | $3 = $2 137 | split($3, refspec_split, ref_prefix); 138 | $3 = refspec_split[2]; 139 | } 140 | 141 | 142 | END { 143 | process_emptiness(); 144 | } 145 | -------------------------------------------------------------------------------- /util/gawk/post_fetch_processing.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | @include "base_processing.gawk" 4 | 5 | 6 | BEGIN { 7 | write_after_line("> main processing"); 8 | } 9 | 10 | @include "input_processing.gawk" 11 | 12 | 13 | END { 14 | main_processing(); 15 | write("> main processing end"); 16 | } 17 | 18 | function main_processing( ref){ 19 | generate_missing_refs(); 20 | 21 | unlock_deletion(); 22 | if(!deletion_allowed){ 23 | write(deletion_blocked_by); 24 | } 25 | 26 | for(ref in refs){ 27 | state_to_action(ref); 28 | } 29 | actions_to_operations(); 30 | operations_to_refspecs(); 31 | process_excluded_tracks(); 32 | refspecs_to_stream(); 33 | } 34 | function state_to_action(ref, remote_sha, track_sha, side, is_victim, ref_type){ 35 | if(attach_mode) 36 | return; 37 | # if(remote_empty[side_any]) 38 | # return; 39 | 40 | for(side in sides){ 41 | remote_sha[side] = refs[ref][side][remote][sha_key]; 42 | track_sha[side] = refs[ref][side][track][sha_key]; 43 | } 44 | 45 | remote_sha[equal] = remote_sha[side_a] == remote_sha[side_b]; 46 | track_sha[equal] = track_sha[side_a] == track_sha[side_b]; 47 | 48 | if(remote_sha[equal] && track_sha[equal] && track_sha[side_a] == remote_sha[side_b]) 49 | return; 50 | 51 | remote_sha[common] = remote_sha[equal] ? remote_sha[side_a] : ""; 52 | remote_sha[empty_both] = !(remote_sha[side_a] || remote_sha[side_b]); 53 | remote_sha[empty_any] = !remote_sha[side_a] || !remote_sha[side_b]; 54 | 55 | track_sha[common] = track_sha[equal] ? track_sha[side_a] : ""; 56 | track_sha[empty_both] = !(track_sha[side_a] || track_sha[side_b]); 57 | track_sha[empty_any] = !track_sha[side_a] || !track_sha[side_b]; 58 | 59 | is_victim = use_victim_sync(ref); 60 | 61 | if(remote_sha[empty_both]){ 62 | # A branch in the ref was deleted manually in both repos. 63 | 64 | trace(ref ": action-remove-tracking-as-unknown-on-both-remotes"); 65 | a_remove_tracking_both[ref]; 66 | 67 | return; 68 | } 69 | 70 | # ! All further actions assume that remote refs are unequal. 71 | 72 | if(track_sha[empty_both]){ 73 | trace("!Warning!"); 74 | trace("!! Something went wrong for " ref ". It is still untracked."); 75 | trace("!! Possibly the program or the network were interrupted."); 76 | trace("!! We will try to sync it later (during the second sync pass)."); 77 | 78 | return; 79 | } 80 | 81 | if(del_to_action(ref, is_victim, remote_sha, track_sha)){ 82 | return; 83 | } 84 | if(move_to_refspec_by_state(ref, conv_move, is_victim)){ 85 | return; 86 | } 87 | if(move_to_refspec_by_state(ref, victim_move, is_victim)){ 88 | return; 89 | } 90 | if(victim_move_to_refspec(ref, remote_sha, track_sha, is_victim)){ 91 | return; 92 | } 93 | if(ff_move_by_remote(ref, remote_sha, track_sha)){ 94 | return; 95 | } 96 | 97 | ref_type = is_victim ? "victim" : "conv"; 98 | trace(ref ": action-solve-all-others." ref_type "; it has different track or/and remote branch commits"); 99 | if(is_victim){ 100 | a_victim_solve[ref]; 101 | }else{ 102 | a_conv_solve[ref]; 103 | } 104 | } 105 | function del_to_action(ref, is_victim, remote_sha, track_sha, side, aside, deletion_state, unowned_ref, action_key){ 106 | if(!track_sha[equal]){ 107 | return; 108 | } 109 | 110 | for(side in sides){ 111 | aside = asides[side]; 112 | 113 | deletion_state = !remote_sha[side] && remote_sha[aside] == track_sha[common]; 114 | if(!deletion_state){ 115 | continue; 116 | } 117 | 118 | if(is_victim){ 119 | if(deletion_allowed){ 120 | trace(ref ": action-del-victim on " aside "; it is disappeared from " side); 121 | a_del[aside][ref]; 122 | }else{ 123 | trace(ref ": action-del-blocked-victim-restore on " aside "; disappeared from " side); 124 | a_restore_del[ref]; 125 | } 126 | 127 | return 1; 128 | } 129 | 130 | unowned_ref = side_conv_ref(ref, aside); 131 | 132 | if(!deletion_allowed || unowned_ref){ 133 | action_key = "action-del-blocked-conv"; 134 | if(unowned_ref){ 135 | action_key = action_key "&unowned-ref"; 136 | } 137 | 138 | trace(ref ": " action_key " on " aside "; disappeared from " side); 139 | a_conv_solve[ref]; 140 | append_by_val(out_notify_solving, "conventional-restore-as-del-blocked | " side " | " ref " | " action_key " | restoring-to:" side ":" refs[ref][side][track][sha_key]); 141 | 142 | return 1; 143 | } 144 | 145 | trace(ref ": action-del-conv on " aside "; it is disappeared from " side); 146 | a_del[aside][ref]; 147 | 148 | return 1; 149 | } 150 | } 151 | function move_to_refspec_by_state(ref, source_refs, is_victim, ref_item, action_sha, cmd, parent_sha, parent_side, child_side, action_key, moving_back, owner_action, force_key){ 152 | for(ref_item in source_refs){ 153 | if(ref_item != ref){ 154 | continue; 155 | } 156 | for(action_sha in source_refs[ref_item]){} 157 | 158 | break; 159 | } 160 | if(ref_item != ref){ 161 | return; 162 | } 163 | 164 | 165 | cmd = "git merge-base " refs[ref][side_a][track][ref_key] " " refs[ref][side_b][track][ref_key]; 166 | 167 | 168 | cmd | getline parent_sha; 169 | close(cmd); 170 | 171 | if(parent_sha == refs[ref][side_a][track][sha_key]){ 172 | parent_side = side_a; 173 | } else if(parent_sha == refs[ref][side_b][track][sha_key]){ 174 | parent_side = side_b; 175 | } else if(!parent_sha && ref == sync_enabling_branch){ 176 | write("\nSyncing is blocked. You are trying to sync unrelated Git-remote repositories"); 177 | write("\"" ref "\" has different SHA and has no a parent commit"); 178 | write("\"" ref "\" located in " side_a ":" refs[ref][side_a][remote][sha_key] " vs " side_b ":" refs[ref][side_b][remote][sha_key]); 179 | 180 | exit 99; 181 | } else { 182 | # We didn't covered Git multi root cases by this logic yet. 183 | trace(ref ":~ rejected-move-by-state; " side_a " & " side_b " lost ff-inheritance at " parent_sha); 184 | 185 | return; 186 | } 187 | 188 | child_side = asides[parent_side]; 189 | action_key = "action-fast-forward-by-state"; 190 | 191 | moving_back = parent_sha == action_sha; 192 | if(moving_back){ 193 | if(!is_victim){ 194 | owner_action = side_conv_ref(ref, parent_side); 195 | if(!owner_action){ 196 | # Moving back from a non-owner side is forbidden. 197 | trace(ref ":~ forbidden-moving-back-by-state; non-owner side cannot move conventional ref back"); 198 | 199 | return; 200 | } 201 | } 202 | parent_side = child_side; 203 | child_side = asides[parent_side]; 204 | force_key = "+"; 205 | action_key = "action-moving-back-by-state"; 206 | 207 | append_by_val(out_notify_solving, "moving-back-by-state | " parent_side " | " ref " | out-of " parent_side ":" refs[ref][parent_side][track][sha_key] " to " child_side ":" refs[ref][child_side][track][sha_key]); 208 | } 209 | 210 | trace(ref ": " action_key "; out-of " parent_side ":" refs[ref][parent_side][track][sha_key] " to " child_side ":" refs[ref][child_side][track][sha_key]); 211 | out_push[parent_side] = out_push[parent_side] " " force_key refs[ref][child_side][track][ref_key] ":" refs[ref][parent_side][remote][ref_key]; 212 | 213 | # Let's inform a calling logic that we've processed the current ref. 214 | return 1; 215 | } 216 | function victim_move_to_refspec(ref, remote_sha, track_sha, is_victim, ref_item, action_sha, source_side, target_side){ 217 | if(!is_victim) 218 | return; 219 | 220 | for(ref_item in victim_move){ 221 | if(ref_item != ref){ 222 | continue; 223 | } 224 | for(action_sha in victim_move[ref_item]){} 225 | 226 | break; 227 | } 228 | if(ref_item != ref){ 229 | return; 230 | } 231 | 232 | # The idea below is to ignore a candidate ref if sha was changed since last check. 233 | # E.g. process with ra==ta & rb==tb & ra!=rb 234 | 235 | if(remote_sha[equal]) 236 | return; 237 | if(track_sha[equal]) 238 | return; 239 | 240 | if(remote_sha[side_a] != track_sha[side_a]) 241 | return; 242 | if(remote_sha[side_b] != track_sha[side_b]) 243 | return; 244 | 245 | if(remote_sha[side_a] != action_sha && remote_sha[side_b] != action_sha) 246 | return; 247 | 248 | if(remote_sha[side_a] == action_sha){ 249 | source_side = side_a; 250 | } else if(remote_sha[side_b] == action_sha){ 251 | source_side = side_b; 252 | } else { 253 | return; 254 | } 255 | 256 | target_side = asides[source_side]; 257 | 258 | if(action_sha != refs[ref][source_side][remote][sha_key]){ 259 | write("\nError! The victim_move_to_refspec sync logic brocken."); 260 | write("\"" action_sha "\" must be " refs[ref][source_side][remote][sha_key]); 261 | 262 | cmd = "need_interrupt_app" 263 | while (0 < (cmd | getline fail_text)){ 264 | write(fail_text) 265 | } 266 | close(cmd); 267 | 268 | if(fail_text == "need-interrupt-app"){ 269 | exit 97; 270 | } 271 | } 272 | trace(ref ": action-victim-nff-move; out-of " target_side ":" refs[ref][target_side][remote][sha_key] " to " source_side ":" refs[ref][source_side][remote][sha_key]); 273 | out_push[target_side] = out_push[target_side] " +" refs[ref][source_side][track][ref_key] ":" refs[ref][target_side][remote][ref_key]; 274 | 275 | append_by_val(out_notify_solving, "victim-non-fast-forward-move | " target_side " | " ref " | out-of " target_side ":" refs[ref][target_side][remote][sha_key] " to " source_side ":" refs[ref][source_side][remote][sha_key]); 276 | 277 | # Let's inform a calling logic that we've processed the current ref. 278 | return 1; 279 | } 280 | function ff_move_by_remote(ref, remote_sha, track_sha, cmd, parent_sha, parent_side, child_side){ 281 | # Process when ra==ta & rb==tb & ra!=rb & all not empty. 282 | if(remote_sha[empty_any]) 283 | return; 284 | if(track_sha[empty_any]) 285 | return; 286 | if(remote_sha[equal]) 287 | return; 288 | if(track_sha[equal]) 289 | return; 290 | if(remote_sha[side_a] != track_sha[side_a]) 291 | return; 292 | if(remote_sha[side_b] != track_sha[side_b]) 293 | return; 294 | 295 | cmd = "git merge-base " refs[ref][side_a][track][ref_key] " " refs[ref][side_b][track][ref_key]; 296 | 297 | 298 | cmd | getline parent_sha; 299 | close(cmd); 300 | 301 | if(parent_sha == refs[ref][side_a][track][sha_key]){ 302 | parent_side = side_a; 303 | } else if(parent_sha == refs[ref][side_b][track][sha_key]){ 304 | parent_side = side_b; 305 | } else if(!parent_sha && ref == sync_enabling_branch){ 306 | write("\nYou are trying to sync unrelated Git-remote repositories. Syncing is blocked"); 307 | write("\"" ref "\" has different SHA and has no a parent commit"); 308 | write("\"" ref "\" located in " side_a ":" refs[ref][side_a][remote][sha_key] " vs " side_b ":" refs[ref][side_b][remote][sha_key]); 309 | 310 | exit 98; 311 | } else { 312 | # We didn't covered Git multy root cases by this logic yet. 313 | trace(ref ":~ rejected-fast-forward-by-remote; " side_a " & " side_b " lost ff-inheritance at " parent_sha); 314 | 315 | return; 316 | } 317 | 318 | child_side = asides[parent_side]; 319 | 320 | trace(ref ": action-fast-forward-by-remote; out-of " parent_side ":" refs[ref][parent_side][remote][sha_key] " to " child_side ":" refs[ref][child_side][track][sha_key]); 321 | out_push[parent_side] = out_push[parent_side] " " refs[ref][child_side][track][ref_key] ":" refs[ref][parent_side][remote][ref_key]; 322 | 323 | # Let's inform a calling logic that we've processed the current ref. 324 | return 1; 325 | } 326 | function actions_to_operations( side, aside, ref, attach_both, track_sha, another_track_sha, remote_sha, ref_owner){ 327 | if(attach_mode){ 328 | attach_both = remote_empty[side_both]; 329 | for(side in sides){ 330 | if(!remote_empty[side]){ 331 | continue 332 | } 333 | for(ref in refs){ 334 | track_sha = refs[ref][side][track][sha_key]; 335 | if(!track_sha){ 336 | continue; 337 | } 338 | if(attach_both){ 339 | op_push_attach_from_track[side][ref]; 340 | append_by_val(out_notify_solving, "attach-both-empty-sides | " side " | " ref " | restoring-to " side ":" track_sha); 341 | }else{ 342 | aside = asides[side]; 343 | another_track_sha = refs[ref][aside][track][sha_key]; 344 | 345 | op_push_attach_from_another[side][ref]; 346 | append_by_val(out_notify_solving, "attach-empty-side | " side " | " ref " | restoring-to " aside ":" another_track_sha); 347 | } 348 | } 349 | } 350 | } 351 | 352 | for(ref in a_remove_tracking_both){ 353 | for(side in sides){ 354 | track_sha = refs[ref][side][track][sha_key]; 355 | if(!track_sha){ 356 | continue; 357 | } 358 | op_remove_both_tracking[side][ref]; 359 | append_by_val(out_notify_solving, "tracking-removed | " side " | " ref " | " side ":" track_sha); 360 | } 361 | } 362 | for(ref in a_restore_del){ 363 | for(side in sides){ 364 | remote_sha = refs[ref][side][remote][sha_key] 365 | if(remote_sha){ 366 | continue; 367 | } 368 | op_push_restore_from_track[side][ref]; 369 | 370 | track_sha = refs[ref][side][track][sha_key]; 371 | append_by_val(out_notify_solving, "victim-restore-as-del-blocked | " side " | " ref " | restoring-to:" side ":" track_sha); 372 | } 373 | } 374 | 375 | for(side in a_del){ 376 | for(ref in a_del[side]){ 377 | op_del_track[ref]; 378 | op_push_del[side][ref]; 379 | } 380 | } 381 | 382 | for(side in sides){ 383 | aside = asides[side]; 384 | for(ref in a_conv_solve){ 385 | ref_owner = side_conv_ref(ref, side); 386 | 387 | if(!ref_owner){ 388 | continue; 389 | } 390 | 391 | if(refs[ref][side][remote][sha_key]){ 392 | op_conv_push_nff[aside][ref]; 393 | } else if(refs[ref][aside][remote][sha_key]){ 394 | op_conv_push_nff[side][ref]; 395 | } 396 | } 397 | } 398 | } 399 | function operations_to_refspecs( side, aside, ref){ 400 | for(side in op_remove_both_tracking){ 401 | for(ref in op_remove_both_tracking[side]){ 402 | out_remove_tracking = out_remove_tracking " " origin[side] "/" ref; 403 | } 404 | } 405 | 406 | for(side in sides){ 407 | for(ref in op_del_track){ 408 | if(refs[ref][side][track][sha_key]){ 409 | out_remove_tracking = out_remove_tracking " " origin[side] "/" ref; 410 | } 411 | } 412 | } 413 | 414 | for(side in op_push_attach_from_track){ 415 | for(ref in op_push_attach_from_track[side]){ 416 | out_push[side] = out_push[side] " +" refs[ref][side][track][ref_key] ":" refs[ref][side][remote][ref_key]; 417 | } 418 | } 419 | 420 | for(side in op_push_attach_from_another){ 421 | aside = asides[side]; 422 | for(ref in op_push_attach_from_another[side]){ 423 | out_push[side] = out_push[side] " +" refs[ref][aside][track][ref_key] ":" refs[ref][side][remote][ref_key]; 424 | } 425 | } 426 | 427 | for(side in op_push_restore_from_track){ 428 | for(ref in op_push_restore_from_track[side]){ 429 | out_push[side] = out_push[side] " +" refs[ref][side][track][ref_key] ":" refs[ref][side][remote][ref_key]; 430 | } 431 | } 432 | 433 | for(side in op_push_restore_from_another){ 434 | aside = asides[side]; 435 | for(ref in op_push_restore_from_another[side]){ 436 | out_push[side] = out_push[side] " +" refs[ref][aside][track][ref_key] ":" refs[ref][side][remote][ref_key]; 437 | } 438 | } 439 | 440 | for(side in op_push_del){ 441 | for(ref in op_push_del[side]){ 442 | out_push[side] = out_push[side] " +:" refs[ref][side][remote][ref_key]; 443 | 444 | append_by_val(out_notify_del, "deletion | " side " | " ref " | " refs[ref][side][remote][sha_key]); 445 | } 446 | } 447 | 448 | for(side in op_conv_push_nff){ 449 | aside = asides[side]; 450 | for(ref in op_conv_push_nff[side]){ 451 | out_push[side] = out_push[side] " +" refs[ref][aside][track][ref_key] ":" refs[ref][side][remote][ref_key]; 452 | 453 | if(refs[ref][side][remote][sha_key]){ 454 | append_by_val(out_notify_solving, "conventional-conflict-solving | " side " | " ref " | out-of " side ":" refs[ref][side][remote][sha_key] " to " aside ":" refs[ref][aside][remote][sha_key]); 455 | } 456 | } 457 | } 458 | set_victim_refspec(); 459 | 460 | # We may use post fetching as workaround for network fails and program interruptions. 461 | # Also FF-updating may fail in case of rare conflicting with a remote repo. 462 | # Without the post fetching these cases will not be resolved ever. 463 | # But we don't use it for now as we migrated to preprocessing git fetching. 464 | for(side in op_post_fetch){ 465 | for(ref in op_post_fetch[side]){ 466 | out_post_fetch[side] = out_post_fetch[side] " +" refs[ref][side][remote][ref_key] ":" refs[ref][side][track][ref_key]; 467 | } 468 | } 469 | } 470 | 471 | function set_victim_refspec( ref, remote_sha_a, track_sha_a, trace_action, track_sha_a_txt, remote_sha_b, track_sha_b, track_sha_b_txt, cmd, newest_sha, side_winner, side_victim, victim_sha){ 472 | for(ref in a_victim_solve){ 473 | # We expects that "no sha" cases will be processed by common NFF-solving actions. 474 | # But this approach with variables help to solve severe errors. Also it makes code more resilient. 475 | 476 | remote_sha_a = refs[ref][side_a][remote][sha_key]; 477 | track_sha_a = refs[ref][side_a][track][sha_key]; 478 | 479 | remote_sha_b = refs[ref][side_b][remote][sha_key]; 480 | track_sha_b = refs[ref][side_b][track][sha_key]; 481 | 482 | # d_trace("a " ref "; track_sha_a:" track_sha_a "; remote_sha_a:" remote_sha_a); 483 | # d_trace("b " ref "; track_sha_b:" track_sha_b "; remote_sha_b:" remote_sha_b); 484 | 485 | if(track_sha_a && track_sha_b){ 486 | # Have both tracks. 487 | cmd = "git rev-list " refs[ref][side_a][track][ref_key] " " refs[ref][side_b][track][ref_key] " --max-count=1" 488 | cmd | getline newest_sha; 489 | close(cmd); 490 | } else if(track_sha_a){ 491 | newest_sha = track_sha_a; 492 | } else if(track_sha_b){ 493 | newest_sha = track_sha_b; 494 | } 495 | 496 | if(newest_sha == track_sha_a){ 497 | side_winner = side_a 498 | side_victim = side_b 499 | } else if(newest_sha == track_sha_b){ 500 | side_winner = side_b 501 | side_victim = side_a 502 | } else { 503 | trace("unexpected behavior during victim solving | " ref); 504 | 505 | continue; 506 | } 507 | 508 | out_push[side_victim] = out_push[side_victim] " +" refs[ref][side_winner][track][ref_key] ":" refs[ref][side_victim][remote][ref_key]; 509 | 510 | victim_sha = refs[ref][side_victim][remote][sha_key]; 511 | # Do not show solving for new branch creation. 512 | if(victim_sha){ 513 | append_by_val(out_notify_solving, "victim-conflict-solving | " side_victim " | " ref " | out-of " victim_sha " to " side_winner ":" refs[ref][side_winner][remote][sha_key]); 514 | } 515 | 516 | trace_action = victim_sha ? "victim-conflict-solving" : "victim-empty-solving"; 517 | 518 | track_sha_a_txt = track_sha_a ? track_sha_a : ""; 519 | track_sha_b_txt = track_sha_b ? track_sha_b : ""; 520 | trace(trace_action ": " ref " on " side_winner " beat " side_victim " with " track_sha_a_txt " vs " track_sha_b_txt); 521 | } 522 | } 523 | 524 | function process_excluded_tracks( side, ref){ 525 | parse_excluded_tracks("all_track_refs_a", side_a); 526 | parse_excluded_tracks("all_track_refs_b", side_b); 527 | 528 | for(ref in excluded_track_state){ 529 | for(side in sides){ 530 | trace(ref ": action-remove-excluded-track;" side ":" excluded_track_state[ref][side][sha_key] "; not under sync any more") 531 | } 532 | } 533 | 534 | } 535 | function parse_excluded_tracks(track_env_var, side, split_arr, ind, val, split_val, sha, refspec, refspec_split, track_ref_path, ref){ 536 | split(ENVIRON[track_env_var], split_arr, "\n"); 537 | for(ind in split_arr){ 538 | val = split_arr[ind]; 539 | 540 | split(val, split_val, " "); 541 | 542 | sha = split_val[1]; 543 | refspec = split_val[2]; 544 | if(!sha || !refspec){ 545 | continue; 546 | } 547 | 548 | # refs/remotes/origin_x/ 549 | track_ref_path = track_refs_path origin[side] "/" 550 | 551 | split(refspec, refspec_split, track_ref_path); 552 | ref = refspec_split[2]; 553 | 554 | if(sync_refs[ref]){ 555 | continue; 556 | } 557 | 558 | excluded_track_state[ref][side][sha_key] = sha; 559 | 560 | out_remove_tracking = out_remove_tracking " " origin[side] "/" ref; 561 | } 562 | } 563 | 564 | function refspecs_to_stream( out_processing_requested){ 565 | 566 | out_processing_requested = 0 < length(out_remove_tracking out_push[side_a] out_push[side_b] out_post_fetch[side_a] out_post_fetch[side_b]); 567 | 568 | 569 | print out_processing_requested; 570 | 571 | print out_remove_tracking; 572 | print out_notify_del[val_key]; 573 | 574 | print out_push[side_a]; 575 | print out_push[side_b]; 576 | print out_notify_solving[val_key]; 577 | 578 | print out_post_fetch[side_a]; 579 | print out_post_fetch[side_b]; 580 | 581 | # Must print finishing line otherwise previous empty lines will be ignored by mapfile command in bash. 582 | print "{[end-of-results]}" 583 | } 584 | 585 | 586 | -------------------------------------------------------------------------------- /util/gawk/pre_fetch_processing.gawk: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | @include "base_processing.gawk" 4 | 5 | 6 | BEGIN { 7 | write_after_line("> pre processing"); 8 | } 9 | 10 | @include "input_processing.gawk" 11 | 12 | 13 | END { 14 | main_processing(); 15 | write("> pre processing end"); 16 | } 17 | function main_processing( ref){ 18 | generate_missing_refs(); 19 | 20 | for(ref in refs){ 21 | state_to_action(ref); 22 | } 23 | actions_to_refspecs(); 24 | refspecs_to_stream(); 25 | } 26 | function state_to_action(ref, remote_sha, track_sha, side, is_victim){ 27 | for(side in sides){ 28 | remote_sha[side] = refs[ref][side][remote][sha_key]; 29 | track_sha[side] = refs[ref][side][track][sha_key]; 30 | } 31 | 32 | remote_sha[equal] = remote_sha[side_a] == remote_sha[side_b]; 33 | track_sha[equal] = track_sha[side_a] == track_sha[side_b]; 34 | 35 | if(remote_sha[equal] && track_sha[equal] && track_sha[side_a] == remote_sha[side_b]) 36 | return; 37 | 38 | remote_sha[common] = remote_sha[equal] ? remote_sha[side_a] : ""; 39 | remote_sha[empty_both] = !(remote_sha[side_a] || remote_sha[side_b]); 40 | remote_sha[empty_any] = !remote_sha[side_a] || !remote_sha[side_b]; 41 | 42 | track_sha[common] = track_sha[equal] ? track_sha[side_a] : ""; 43 | track_sha[empty_both] = !(track_sha[side_a] || track_sha[side_b]); 44 | track_sha[empty_any] = !track_sha[side_a] || !track_sha[side_b]; 45 | 46 | is_victim = use_victim_sync(ref); 47 | 48 | if(remote_sha[empty_both]) 49 | return; 50 | 51 | request_victim_move(ref, remote_sha, track_sha, is_victim); 52 | 53 | if(remote_sha[equal]){ 54 | request_update_tracking(ref, remote_sha, track_sha); 55 | 56 | return; 57 | } 58 | 59 | if(track_sha[empty_both]){ 60 | request_update_tracking(ref, remote_sha, track_sha); 61 | 62 | return; 63 | } 64 | 65 | request_update_tracking(ref, remote_sha, track_sha); 66 | 67 | request_conv_move(ref, remote_sha, track_sha, is_victim); 68 | } 69 | function request_update_tracking(ref, remote_sha, track_sha){ 70 | for(side in sides){ 71 | if(!remote_sha[side]){ 72 | # No an update source. 73 | continue; 74 | } 75 | if(remote_sha[side] == track_sha[side]){ 76 | # No need to update. Tracking and remote refs are the same. 77 | continue; 78 | } 79 | 80 | # Possibly gitSync or the network was interrupted. 81 | # Or this ref was moved back. 82 | # But the default scenario is that the remote ref was modified. 83 | # Let's update the tracking ref. 84 | trace(ref ": action-fetch from " side "; " ((track_sha[side]) ? "track ref is outdated" : "track ref is unknown")); 85 | a_fetch[side][ref]; 86 | } 87 | } 88 | function request_conv_move(ref, remote_sha, track_sha, is_victim, side, aside, ref_owner){ 89 | if(is_victim) 90 | return; 91 | 92 | if(!track_sha[equal]) 93 | return; 94 | 95 | if(track_sha[empty_both]) 96 | return; 97 | 98 | for(side in sides){ 99 | 100 | # d_trace(ref_owner " " side " " ref " " prefix[side]); 101 | 102 | if(!remote_sha[side]){ 103 | continue; 104 | } 105 | 106 | if(remote_sha[side] == track_sha[side]){ 107 | continue; 108 | } 109 | 110 | aside = asides[side]; 111 | if(remote_sha[aside] != track_sha[aside]){ 112 | continue; 113 | } 114 | 115 | # Let's allow updating of the another side conventional refs. Remember fast-forward updating candidates. 116 | trace(ref ": action-check-conventional-move; outdated on " side); 117 | a_conv_move[ref][remote_sha[side]]; 118 | } 119 | } 120 | function request_victim_move(ref, remote_sha, track_sha, is_victim, side, aside){ 121 | # This allows to decrease Git local request for regulary synced repos. 122 | 123 | if(!is_victim) 124 | return; 125 | 126 | if(remote_sha[empty_any]) 127 | return; 128 | if(track_sha[empty_any]) 129 | return; 130 | 131 | if(remote_sha[equal]) 132 | return; 133 | 134 | if(!track_sha[equal]) 135 | return; 136 | 137 | # We expect that request_update_tracking will request required here fetching. 138 | 139 | for(side in sides){ 140 | 141 | if(remote_sha[side] == track_sha[side]){ 142 | continue; 143 | } 144 | 145 | aside = asides[side]; 146 | if(remote_sha[aside] != track_sha[aside]){ 147 | continue; 148 | } 149 | 150 | # Let's allow updating of the another side conventional refs. Remember fast-forward updating candidates. 151 | trace(ref ": action-request-non-fast-forward; ref changed only on " side); 152 | a_victim_move[ref][remote_sha[side]]; 153 | } 154 | } 155 | 156 | function actions_to_refspecs( side, aside, ref){ 157 | for(side in a_fetch){ 158 | for(ref in a_fetch[side]){ 159 | out_fetch[side] = out_fetch[side] " +" refs[ref][side][remote][ref_key] ":" refs[ref][side][track][ref_key]; 160 | } 161 | } 162 | 163 | for(ref in a_conv_move){ 164 | for(sha in a_conv_move[ref]){ 165 | append_by_val(out_conv_move, ref " " sha); 166 | } 167 | } 168 | 169 | for(ref in a_victim_move){ 170 | for(sha in a_victim_move[ref]){ 171 | append_by_val(out_victim_move, ref " " sha); 172 | } 173 | } 174 | } 175 | 176 | function refspecs_to_stream(){ 177 | print out_fetch[side_a]; 178 | print out_fetch[side_b]; 179 | 180 | print out_conv_move[val_key]; 181 | print out_victim_move[val_key]; 182 | 183 | # Must print finishing line otherwise previous empty lines will be ignored by mapfile command in bash. 184 | print "{[end-of-results]}" 185 | } 186 | 187 | 188 | -------------------------------------------------------------------------------- /util/repo_create.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | set -euf +x -o pipefail 4 | 5 | 6 | function delete_project_repo_and_exit() { 7 | echo Deletion of "$path_sync_repo" to allow restart git-cred initializing. 8 | rm -rf "$path_sync_repo" 9 | 10 | echo Interruption on error. 11 | 12 | exit 1; 13 | } 14 | 15 | function create_sync_repo(){ 16 | if [[ -f "$path_sync_repo/.git/config" ]]; then 17 | return 18 | fi 19 | 20 | echo @ `basename "$BASH_SOURCE"` started 21 | 22 | mkdir -p "$path_sync_repo" 23 | 24 | # Using --quiet to prevent "hint: Using 'master' as the name for the initial branch..." 25 | git -C "$path_sync_repo" init --quiet 26 | 27 | git -C "$path_sync_repo" config --local advice.pushUpdateRejected false 28 | #git -C "$path_sync_repo" config --local core.logAllRefUpdates 29 | 30 | git -C "$path_sync_repo" remote add $origin_a "$url_a" 31 | git -C "$path_sync_repo" remote add $origin_b "$url_b" 32 | 33 | 34 | [[ "$use_bash_git_credential_helper" == "1" ]] && { 35 | echo "Initializing git-cred as use_bash_git_credential_helper is set to $use_bash_git_credential_helper" 36 | 37 | if [[ ! -f "$git_cred" ]]; then 38 | echo 39 | echo Error! Exit! You have to update/download Git-SubModules of '"'git-repo-sync'"' project to use 40 | echo the '"'bash-git-credential-helper'"' from $git_cred 41 | echo Run '"'git submodule update --init --recursive'"' in the root folder of '"'git-repo-sync'"'. 42 | echo Or comment out '"'use_bash_git_credential_helper=1'"' line in your sync project settings file. 43 | echo 44 | 45 | delete_project_repo_and_exit 46 | fi 47 | 48 | GIT_CRED_DO_NOT_EXIT=1 49 | 50 | # The it3xl/bash-git-credential-helper requires the working directory to be set to our Git-repository. 51 | cd "$path_sync_repo" 52 | 53 | source "$git_cred" init repo_a $url_a 54 | [[ $GIT_CRED_FAILED != 0 ]] && delete_project_repo_and_exit 55 | 56 | source "$git_cred" init repo_b $url_b 57 | [[ $GIT_CRED_FAILED != 0 ]] && delete_project_repo_and_exit 58 | } 59 | 60 | 61 | echo Repo created at $path_sync_repo 62 | 63 | echo @ `basename "$BASH_SOURCE"` ended 64 | } 65 | create_sync_repo 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /util/restore-after-crash.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | # Puts only error output to a variable. Prevents useless execution by "--count=1". 4 | # Looks inside "refs/remotes" only. 5 | errors_for_each=$(git for-each-ref --count=1 refs/remotes 2>&1 1>/dev/null) 6 | 7 | 8 | if [[ $errors_for_each == 'warning: ignoring broken ref refs/remotes/'* ]]; then 9 | echo 10 | echo 11 | echo '@@' Crash Recovery '@@' 12 | echo '@' 13 | echo '@' Possibly the internal checking repository is slightly broken after an unexpected PC switching off. 14 | echo '@' All remote references will be reloaded. So, you will see an increased log related to internal references updating. 15 | echo '@' It is because we got the following problem':' 16 | echo "@ $errors_for_each" 17 | echo '@' 18 | echo '@@@@@@@@@@@@@@@@@@@@' 19 | echo 20 | 21 | rm -rf "$(git rev-parse --absolute-git-dir)/refs/remotes" 22 | mkdir "$(git rev-parse --absolute-git-dir)/refs/remotes" 23 | fi; 24 | -------------------------------------------------------------------------------- /util/set_base_logic.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | -------------------------------------------------------------------------------- /util/set_env.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | # echo;echo Start `basename "$BASH_SOURCE"` 4 | 5 | [[ ${git_sync_env_initialized:+var_is_not_empty} ]] || { 6 | 7 | function need_interrupt_app(){ 8 | func=process_need_interrupt_app 9 | [[ "$(type -t $func)" == 'function' ]] && { 10 | $func 11 | } || { 12 | echo "@@ Error! Application interruption wasn't processed." 13 | echo "@@ Be aware of the above error." 14 | } 15 | }; export -f need_interrupt_app 16 | 17 | function git_fail(){ 18 | operation=$1 19 | origin=$2 20 | exit_code=$3 21 | info=${4:-} 22 | 23 | func=process_git_fail 24 | [[ "$(type -t $func)" == 'function' ]] && { 25 | $func $operation $origin $exit_code $info 26 | } || { 27 | echo "@@ git-operation-failed: git $operation $origin; with $exit_code exit code; $info" 28 | } 29 | }; export -f git_fail 30 | 31 | function git_sync_env_run_settings_script(){ 32 | 33 | local file_name_repo_settings="${1-}" 34 | 35 | [[ ! "$file_name_repo_settings" ]] && { 36 | file_name_repo_settings="default_sync_project.sh" 37 | 38 | echo "Info. No configuration file in the first parameter. $file_name_repo_settings file will be used." 39 | } 40 | 41 | relative_settings_file="$path_git_sync/$file_name_repo_settings" 42 | relative_sibling_settings_file="$path_git_sync/../git-repo-sync.repo_settings/$file_name_repo_settings" 43 | 44 | absolute_settings_file="$file_name_repo_settings" 45 | subfolder_settings_file="$path_git_sync/repo_settings/$file_name_repo_settings" 46 | 47 | 48 | if [[ -f "$relative_settings_file" ]]; then 49 | echo Settings. Using relative config file. $relative_settings_file 50 | source "$relative_settings_file" 51 | elif [[ -f "$relative_sibling_settings_file" ]]; then 52 | echo Settings. Injecting sibling relative config file. $relative_sibling_settings_file 53 | source "$relative_sibling_settings_file" 54 | elif [[ -f "$absolute_settings_file" ]]; then 55 | echo Settings. Using absolute config file. $absolute_settings_file 56 | source "$absolute_settings_file" 57 | elif [[ -f "$subfolder_settings_file" ]]; then 58 | echo Settings. Using repo_settings subfolder config file. $subfolder_settings_file 59 | source "$subfolder_settings_file" 60 | else 61 | echo "Error! Exit! The first parameter must be an absolute path, relative path or a name of a file with your sync-project repo settings." 62 | echo The '"'$file_name_repo_settings'"' is not recognized as a file. 63 | 64 | exit 101; 65 | fi 66 | 67 | env_project_folder=$(basename ${file_name_repo_settings%.*}) 68 | } 69 | 70 | function git_sync_env_init(){ 71 | 72 | git_sync_env_initialized=$(date +%T) 73 | export git_sync_env_initialized 74 | 75 | export path_git_sync="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )" 76 | export path_git_sync_util="$path_git_sync/util" 77 | 78 | export env_awk_edition=${env_awk_edition:-gawk} 79 | 80 | type $env_awk_edition 2> /dev/null || { 81 | echo 82 | echo "Error! Exit!" 83 | echo 84 | echo " @ Our tool is optimized to work with gawk. I.e. GNU Awk (env_awk_edition = $env_awk_edition)" 85 | echo " @ You need to install gawk as we didn't adopted other AWK editions yet." 86 | edition_of_awk=$(awk -W version 2> /dev/null | head -n 1) 87 | echo " @ Your current awk is - '$edition_of_awk'" 88 | 89 | echo 90 | echo " @ Run the gawk command yourself as some shells show a hint on how to install it." 91 | echo 92 | 93 | exit 102; 94 | } 95 | 96 | # AWKPATH is env variable of GAWK that is used by the @include directive. 97 | # We need to set AWKPATH because our current directory commonly points points out to the sync Git repo, not our GAWK scripts. 98 | export AWKPATH="$path_git_sync_util/gawk" 99 | 100 | if [[ ${git_sync_project_folder:+1} ]]; then 101 | echo 'Info. Taking configuration from a parent environment as git_sync_project_folder is defined' 102 | 103 | env_project_folder=$git_sync_project_folder 104 | else 105 | echo 'Info. Seeking a configuration file provided in the first parameter as git_sync_project_folder isn''t defined' 106 | 107 | git_sync_env_run_settings_script "$@" 108 | fi 109 | 110 | if [[ ! ${url_a:+1} ]]; then missed_repo_settings+="url_a "; fi 111 | if [[ ! ${url_b:+1} ]]; then missed_repo_settings+="url_b "; fi 112 | 113 | if [[ ${missed_repo_settings:+1} ]]; then 114 | echo "Error! Exit! The following repo properties must be set: $missed_repo_settings"; 115 | 116 | exit 103; 117 | fi 118 | 119 | 120 | sync_enabling_branch=${sync_enabling_branch:-} 121 | 122 | pref_a_conv=${side_a_conventional_branches_prefix:-} 123 | pref_b_conv=${side_b_conventional_branches_prefix:-} 124 | 125 | # If this var is empty, then we ignore the Victim branches functionality and its "The latest action wins" conflict solving strategy. 126 | pref_victim=${victim_branches_prefix:-} 127 | 128 | conventional_prefixes_trace_values=" 129 | pref_a_conv is '$pref_a_conv' 130 | pref_b_conv is '$pref_b_conv'" 131 | 132 | if [[ "$pref_a_conv" && "$pref_a_conv" == "$pref_b_conv" ]]; then 133 | echo "Error! Exit! We expected you to assign different values for conventional ref prefixes. $conventional_prefixes_trace_values" 134 | 135 | exit 104; 136 | fi; 137 | 138 | prefixes_trace_values=" 139 | pref_victim is '$pref_victim' $conventional_prefixes_trace_values" 140 | 141 | if [[ "$pref_victim" \ 142 | && ( "$pref_a_conv" == "$pref_victim" \ 143 | || "$pref_b_conv" == "$pref_victim" ) ]]; 144 | then 145 | echo "Error! Exit! We expect that the victim ref prefix have letters different from conventional ref prefixes. $prefixes_trace_values" 146 | 147 | exit 105; 148 | fi; 149 | 150 | export origin_a=origin_a 151 | export origin_b=origin_b 152 | 153 | all_tracks_refspec_a="refs/remotes/$origin_a" 154 | all_tracks_refspec_b="refs/remotes/$origin_b" 155 | 156 | if [[ "$pref_victim" ]]; then 157 | sync_ref_specs="${pref_a_conv:+${pref_a_conv}* }${pref_b_conv:+${pref_b_conv}* }${pref_victim:+${pref_victim}* }$sync_enabling_branch" 158 | 159 | track_refspecs_a="${pref_a_conv:+refs/remotes/$origin_a/${pref_a_conv}* }` 160 | `${pref_b_conv:+refs/remotes/$origin_a/${pref_b_conv}* }` 161 | `${pref_victim:+refs/remotes/$origin_a/${pref_victim}* }` 162 | `${sync_enabling_branch:+refs/remotes/$origin_a/$sync_enabling_branch}" 163 | 164 | track_refspecs_b="${pref_a_conv:+refs/remotes/$origin_b/${pref_a_conv}* }` 165 | `${pref_b_conv:+refs/remotes/$origin_b/${pref_b_conv}* }` 166 | `${pref_victim:+refs/remotes/$origin_b/${pref_victim}* }` 167 | `${sync_enabling_branch:+refs/remotes/$origin_b/$sync_enabling_branch}" 168 | else 169 | echo 'Info. *All-branches-sync mode! Use "..._prefix" configuration parameters to limit synced branches.' 170 | 171 | sync_ref_specs=; 172 | track_refspecs_a=$all_tracks_refspec_a 173 | track_refspecs_b=$all_tracks_refspec_b 174 | fi 175 | 176 | export sync_ref_specs 177 | 178 | export track_refspecs_a 179 | export track_refspecs_b 180 | 181 | export pref_a_conv 182 | export url_a 183 | export pref_b_conv 184 | export url_b 185 | export pref_victim 186 | export sync_enabling_branch 187 | 188 | export use_bash_git_credential_helper=${use_bash_git_credential_helper-} 189 | 190 | export git_sync_pass_num=0 191 | export git_sync_pass_num_required=0 192 | export post_fetch_processing_num=0 193 | 194 | # The way we receive data from gawk we can't use new line char in the output. So we are using a substitution. 195 | export env_awk_newline_substitution='|||||' 196 | 197 | env_allow_async=${env_allow_async:-1} 198 | # env_allow_async=0 199 | export env_allow_async 200 | 201 | env_trace_refs=${env_trace_refs:-0} 202 | # env_trace_refs=1 203 | export env_trace_refs 204 | 205 | env_allow_multiple_sync_passes=${env_allow_multiple_sync_passes:-0} 206 | 207 | # These vars can be used for debugging and testing purposes. 208 | export env_awk_trace_on=1 209 | export env_process_if_refs_are_the_same=0 210 | 211 | path_project_root="$path_git_sync/sync-projects/$env_project_folder" 212 | export path_sync_repo="$path_project_root/sync_repo" 213 | # Catches outputs of the fork-join async implementation. 214 | export path_async_output="$path_project_root/async_output" 215 | signal_files_folder=file-signals 216 | export env_modifications_signal_file="$path_project_root/$signal_files_folder/there-are-modifications" 217 | export env_modifications_signal_file_a="$path_project_root/$signal_files_folder/there-are-modifications_a" 218 | export env_modifications_signal_file_b="$path_project_root/$signal_files_folder/there-are-modifications_b" 219 | export env_notify_del_file="$path_project_root/$signal_files_folder/notify_del" 220 | export env_notify_solving_file="$path_project_root/$signal_files_folder/notify_solving" 221 | 222 | export git_cred="$path_git_sync_util/bash-git-credential-helper/git-cred.sh" 223 | 224 | } 225 | git_sync_env_init "$@" 226 | 227 | source "$path_git_sync_util/set_base_logic.sh" 228 | } 229 | 230 | 231 | 232 | # echo End `basename "$BASH_SOURCE"` 233 | -------------------------------------------------------------------------------- /util/sync_pass.sh: -------------------------------------------------------------------------------- 1 | # it3xl.ru git-repo-sync https://github.com/it3xl/git-repo-sync 2 | 3 | function sync_pass(){ 4 | ((++git_sync_pass_num)) 5 | 6 | (( 1 < git_sync_pass_num )) && [[ "$env_allow_multiple_sync_passes" = '0' ]] && return; 7 | 8 | if (( ${changes_detected:-1} != 1 )); then 9 | # echo '@' Previous sync-pass din not find any changes. 10 | 11 | if [[ $env_process_if_refs_are_the_same != 1 ]]; then 12 | # echo '@' Sync-pass $git_sync_pass_num was interrupted as refs are equal 13 | return; 14 | fi; 15 | fi 16 | 17 | if [[ ! -f "$env_modifications_signal_file" ]]; then 18 | source "$path_git_sync_util/change_detector.sh" 19 | 20 | if (( $changes_detected != 1 )); then 21 | echo '@' RESULT: Refs are the same. 22 | 23 | if [[ $env_process_if_refs_are_the_same != 1 ]]; then 24 | echo '@@' Sync-pass $git_sync_pass_num was interrupted as refs are equal 25 | return; 26 | fi; 27 | fi 28 | else 29 | changes_detected=1 30 | 31 | echo '@' RESULT: Synchronization requested. 32 | 33 | remote_refs_a=$(<"$env_modifications_signal_file_a") 34 | remote_refs_b=$(<"$env_modifications_signal_file_b") 35 | 36 | rm -f "$env_modifications_signal_file" 37 | rm -f "$env_modifications_signal_file_a" 38 | rm -f "$env_modifications_signal_file_b" 39 | fi 40 | 41 | 42 | ((++git_sync_pass_num_required)) 43 | echo "! Running $git_sync_pass_num_required sync pass" 44 | 45 | track_refs_a=$(git for-each-ref --format="%(objectname) %(refname)" $track_refspecs_a) 46 | track_refs_b=$(git for-each-ref --format="%(objectname) %(refname)" $track_refspecs_b) 47 | 48 | if [[ $env_trace_refs == 1 ]]; then 49 | echo 50 | echo remote_refs_a= 51 | echo "$remote_refs_a" 52 | echo remote_refs_b= 53 | echo "$remote_refs_b" 54 | echo track_refs_a= 55 | echo "$track_refs_a" 56 | echo track_refs_b= 57 | echo "$track_refs_b" 58 | fi; 59 | 60 | # $env_awk_edition --file="$path_git_sync/../proto/proto.gawk" <(echo) 61 | # exit 62 | 63 | 64 | pre_fetch_processing='pre_fetch_processing.gawk' 65 | pre_proc_data=$($env_awk_edition \ 66 | --file="$path_git_sync_util/gawk/$pre_fetch_processing" \ 67 | <(echo "$remote_refs_a") \ 68 | <(echo "$remote_refs_b") \ 69 | <(echo "$track_refs_a") \ 70 | <(echo "$track_refs_b") \ 71 | ) 72 | 73 | if [[ $env_trace_refs == 1 ]]; then 74 | echo 75 | echo pre_proc_data is 76 | echo "$pre_proc_data" 77 | fi; 78 | # exit 79 | 80 | mapfile -t pre_proc_list < <(echo "$pre_proc_data") 81 | 82 | fetch_spec_a="${pre_proc_list[0]}"; 83 | fetch_spec_b="${pre_proc_list[1]}"; 84 | conv_move="${pre_proc_list[2]//$env_awk_newline_substitution/$'\n'}"; 85 | victim_move="${pre_proc_list[3]//$env_awk_newline_substitution/$'\n'}"; 86 | end_of_results="${pre_proc_list[4]}"; 87 | 88 | # Let's export for an usage in post_fetch_processing.gawk. 89 | export conv_move 90 | export victim_move 91 | 92 | end_of_results_expected='{[end-of-results]}'; 93 | # This comparison must have double quotes on the second operand. Otherwise it doesn't work. 94 | if [[ $end_of_results != "$end_of_results_expected" ]]; then 95 | echo '@' ERROR: An unexpected internal processing results end. Exit. 96 | echo 97 | 98 | # !!! EXIT !!! 99 | exit 22; 100 | fi; 101 | 102 | if [[ $env_trace_refs == 1 ]]; then 103 | echo fetch_spec_a 104 | echo "$fetch_spec_a" 105 | echo fetch_spec_b 106 | echo "$fetch_spec_b" 107 | echo conv_move 108 | echo "$conv_move" 109 | echo victim_move 110 | echo "$victim_move" 111 | fi; 112 | # exit 113 | 114 | 115 | if [[ $env_allow_async == 1 && -n "$fetch_spec_a" && -n "$fetch_spec_b" ]]; then 116 | echo;echo "> Fetch (async)" 117 | 118 | git fetch --no-tags $origin_a $fetch_spec_a > "$path_async_output/fetch_a.txt" & 119 | pid_fetch_a=$! 120 | git fetch --no-tags $origin_b $fetch_spec_b > "$path_async_output/fetch_b.txt" & 121 | pid_fetch_b=$! 122 | 123 | fetch_report_a="> Fetch $origin_a " 124 | wait $pid_fetch_a && fetch_report_a+="(async success)" || fetch_report_a+="(async failure)" 125 | fetch_report_b+="> Fetch $origin_b " 126 | wait $pid_fetch_b && fetch_report_b+="(async success)" || fetch_report_b+="(async failure)" 127 | 128 | echo $fetch_report_a 129 | echo $fetch_spec_a 130 | cat < "$path_async_output/fetch_a.txt" 131 | 132 | echo $fetch_report_b 133 | echo $fetch_spec_b 134 | cat < "$path_async_output/fetch_b.txt" 135 | else 136 | if [[ -n "$fetch_spec_a" ]]; then 137 | echo;echo "> Fetch $origin_a (sync)" 138 | echo $fetch_spec_a 139 | git fetch --no-tags $origin_a $fetch_spec_a 140 | fi; 141 | if [[ -n "$fetch_spec_b" ]]; then 142 | echo;echo "> Fetch $origin_b (sync)" 143 | echo $fetch_spec_b 144 | git fetch --no-tags $origin_b $fetch_spec_b 145 | fi; 146 | fi; 147 | 148 | 149 | track_refs_a=$(git for-each-ref --format="%(objectname) %(refname)" $track_refspecs_a) 150 | track_refs_b=$(git for-each-ref --format="%(objectname) %(refname)" $track_refspecs_b) 151 | 152 | export all_track_refs_a=$(git for-each-ref --format="%(objectname) %(refname)" $all_tracks_refspec_a) 153 | export all_track_refs_b=$(git for-each-ref --format="%(objectname) %(refname)" $all_tracks_refspec_b) 154 | 155 | # Prevents excessive processing for cleaning of excluded track refs. 156 | [[ "$all_track_refs_a" == "$track_refs_a" ]] && all_track_refs_a=; 157 | [[ "$all_track_refs_b" == "$track_refs_b" ]] && all_track_refs_b=; 158 | 159 | 160 | if [[ $env_trace_refs == 1 ]]; then 161 | echo 162 | echo track_refs_a= 163 | echo "$track_refs_a" 164 | echo track_refs_b= 165 | echo "$track_refs_b" 166 | fi; 167 | # exit 168 | 169 | 170 | proc_data=$($env_awk_edition \ 171 | --file="$path_git_sync_util/gawk/post_fetch_processing.gawk" \ 172 | `# --lint` \ 173 | <(echo "$remote_refs_a") \ 174 | <(echo "$remote_refs_b") \ 175 | <(echo "$track_refs_a") \ 176 | <(echo "$track_refs_b") \ 177 | ) 178 | 179 | mapfile -t proc_list < <(echo "$proc_data") 180 | 181 | if [[ $env_trace_refs == 1 ]]; then 182 | echo 183 | echo proc_data is 184 | echo "$proc_data" 185 | fi; 186 | # exit 187 | 188 | processing_requested="${proc_list[0]}"; 189 | remove_tracking_spec="${proc_list[1]}"; 190 | notify_del="${proc_list[2]//$env_awk_newline_substitution/$'\n'}"; 191 | 192 | push_spec_a="${proc_list[3]}"; 193 | push_spec_b="${proc_list[4]}"; 194 | notify_solving="${proc_list[5]//$env_awk_newline_substitution/$'\n'}"; 195 | 196 | post_fetch_spec_a="${proc_list[6]}"; 197 | post_fetch_spec_b="${proc_list[7]}"; 198 | 199 | end_of_results="${proc_list[8]}"; 200 | 201 | if [[ $env_trace_refs == 1 ]]; then 202 | echo 203 | echo processing_requested is "$processing_requested" 204 | echo remove_tracking_spec is 205 | echo "$remove_tracking_spec" 206 | echo notify_del is 207 | echo "$notify_del" 208 | echo push_spec_a is 209 | echo "$push_spec_a" 210 | echo push_spec_b is 211 | echo "$push_spec_b" 212 | echo notify_solving is 213 | echo "$notify_solving" 214 | echo post_fetch_spec_a is 215 | echo "$post_fetch_spec_a" 216 | echo post_fetch_spec_b is 217 | echo "$post_fetch_spec_b" 218 | fi; 219 | # exit 220 | 221 | end_of_results_expected='{[end-of-results]}'; 222 | # This comparison must have double quotes on the second operand. Otherwise it doesn't work. 223 | if [[ $end_of_results != "$end_of_results_expected" ]]; then 224 | echo '@' ERROR: An unexpected internal processing results end. Exit. 225 | echo 226 | 227 | # !!! EXIT !!! 228 | exit 23 229 | fi; 230 | 231 | if [[ "$processing_requested" == '1' ]]; then 232 | ((++post_fetch_processing_num)) 233 | fi; 234 | 235 | mkdir -p "$path_async_output" 236 | 237 | if [[ -n "$remove_tracking_spec" ]]; then 238 | echo;echo "> Delete track branches" 239 | git branch --delete --force --remotes $remove_tracking_spec 240 | fi; 241 | 242 | if [[ -n "$notify_del" ]]; then 243 | echo;echo "> Notify Deletion" 244 | 245 | install -D /dev/null "$env_notify_del_file" 246 | 247 | echo > "$env_notify_del_file" 248 | echo "$notify_del" >> "$env_notify_del_file" 249 | fi; 250 | 251 | if [[ -n "$notify_solving" ]]; then 252 | echo;echo "> Notify Solving" 253 | 254 | install -D /dev/null "$env_notify_solving_file" 255 | 256 | echo > "$env_notify_solving_file" 257 | echo "$notify_solving" >> "$env_notify_solving_file" 258 | fi; 259 | 260 | if [[ $env_allow_async == 1 && -n "$push_spec_a" && -n "$push_spec_b" ]]; then 261 | echo;echo "> Push (async)" 262 | 263 | { git push $origin_a $push_spec_a || git_fail push $origin_a $?; } > "$path_async_output/push_a.txt" & 264 | pid_push_a=$! 265 | { git push $origin_b $push_spec_b || git_fail push $origin_b $?; } > "$path_async_output/push_b.txt" & 266 | pid_push_b=$! 267 | 268 | push_report_a="> Push $origin_a (async) " 269 | wait $pid_push_a && push_report_a+="(async success)" || push_report_a+="(async failure)" 270 | push_report_b+="> Push $origin_b (async) " 271 | wait $pid_push_b && push_report_b+="(async success)" || push_report_b+="(async failure)" 272 | 273 | echo $push_report_a 274 | echo $push_spec_a 275 | cat < "$path_async_output/push_a.txt" 276 | 277 | echo $push_report_b 278 | echo $push_spec_b 279 | cat < "$path_async_output/push_b.txt" 280 | else 281 | if [[ -n "$push_spec_a" ]]; then 282 | echo;echo "> Push $origin_a (sync)" 283 | echo $push_spec_a 284 | git push $origin_a $push_spec_a || git_fail push $origin_a $? 285 | fi; 286 | if [[ -n "$push_spec_b" ]]; then 287 | echo;echo "> Push $origin_b (sync)" 288 | echo $push_spec_b 289 | git push $origin_b $push_spec_b || git_fail push $origin_b $? 290 | fi; 291 | fi; 292 | 293 | 294 | if [[ $env_allow_async == 1 && -n "$post_fetch_spec_a" && -n "$post_fetch_spec_b" ]]; then 295 | echo;echo "> Post-fetch (async)" 296 | 297 | git fetch --no-tags $origin_a $post_fetch_spec_a > "$path_async_output/post_fetch_a.txt" & 298 | pid_post_fetch_a=$! 299 | git fetch --no-tags $origin_b $post_fetch_spec_b > "$path_async_output/post_fetch_b.txt" & 300 | pid_post_fetch_b=$! 301 | 302 | post_fetch_report_a="> Post-fetch $origin_a " 303 | wait $pid_post_fetch_a && post_fetch_report_a+="(async success)" || post_fetch_report_a+="(async failure)" 304 | post_fetch_report_b+="> Post-fetch $origin_b " 305 | wait $pid_post_fetch_b && post_fetch_report_b+="(async success)" || post_fetch_report_b+="(async failure)" 306 | 307 | echo $post_fetch_report_a 308 | echo $post_fetch_spec_a 309 | cat < "$path_async_output/post_fetch_a.txt" 310 | 311 | echo $post_fetch_report_b 312 | echo $post_fetch_spec_b 313 | cat < "$path_async_output/post_fetch_b.txt" 314 | else 315 | if [[ -n "$post_fetch_spec_a" ]]; then 316 | echo;echo "> Post-fetch $origin_a (sync)" 317 | echo $post_fetch_spec_a 318 | git fetch --no-tags $origin_a $post_fetch_spec_a 319 | fi; 320 | if [[ -n "$post_fetch_spec_b" ]]; then 321 | echo;echo "> Post-fetch $origin_b (sync)" 322 | echo $post_fetch_spec_b 323 | git fetch --no-tags $origin_b $post_fetch_spec_b 324 | fi; 325 | fi; 326 | } 327 | sync_pass 328 | 329 | 330 | --------------------------------------------------------------------------------