doc/user/project/repository/mirror/bidirectional.md
{{< details >}}
{{< /details >}}
{{< history >}}
{{< /history >}}
[!warning] Bidirectional mirroring may cause conflicts.
Bidirectional mirroring configures two repositories to both pull from, and push to, each other. There is no guarantee that either repository can update without errors.
If you configure bidirectional mirroring, prepare your repositories for conflicts. Configure them to reduce conflicts, and how to settle them when they occur:
A push event webhook in the downstream instance can help reduce race conditions by syncing changes more frequently.
Prerequisites:
To create the webhook in the downstream instance:
Create a personal access token with API scope.
In the top bar, select Search or go to and find your project.
Select Settings > Webhooks.
Add the webhook URL, which (in this case) uses the pull mirror API request to trigger an immediate pull after a repository update:
https://gitlab.example.com/api/v4/projects/:id/mirror/pull?private_token=<your_access_token>
Select Push Events.
Select Add Webhook.
To test the integration, select Test and confirm GitLab doesn't return an error message.
[!warning] This solution negatively affects the performance of Git push operations, because they are proxied to the upstream Git repository.
In this configuration, one Git repository acts as the authoritative upstream, and
the other as downstream. This server-side pre-receive hook accepts a push only
after first pushing the commit to the upstream repository. Install this hook on
your downstream repository.
For example:
#!/usr/bin/env bash
# --- Assume only one push mirror target
# Push mirroring remotes are named `remote_mirror_<id>`.
# This line finds the first remote and uses that.
TARGET_REPO=$(git remote | grep -m 1 remote_mirror)
proxy_push()
{
# --- Arguments
OLDREV=$(git rev-parse $1)
NEWREV=$(git rev-parse $2)
REFNAME="$3"
# --- Pattern of branches to proxy pushes
allowlist=$(expr "$branch" : "\(master\)")
case "$refname" in
refs/heads/*)
branch=$(expr "$refname" : "refs/heads/\(.*\)")
if [ "$allowlist" = "$branch" ]; then
# handle https://git-scm.com/docs/git-receive-pack#_quarantine_environment
unset GIT_QUARANTINE_PATH
error="$(git push --quiet $TARGET_REPO $NEWREV:$REFNAME 2>&1)"
fail=$?
if [ "$fail" != "0" ]; then
echo >&2 ""
echo >&2 " Error: updates were rejected by upstream server"
echo >&2 " This is usually caused by another repository pushing changes"
echo >&2 " to the same ref. You may want to first integrate remote changes"
echo >&2 ""
return
fi
fi
;;
esac
}
# Allow dual mode: run from the command line just like the update hook, or
# if no arguments are given, then run as a hook script:
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
# Output to the terminal in command line mode. If someone wanted to
# resend an email, they could redirect the output to sendmail themselves
PAGER= proxy_push $2 $3 $1
else
# Push is proxied upstream one ref at a time. It is possible for some refs
# to succeed, and others to fail. This results in a failed push.
while read oldrev newrev refname
do
proxy_push $oldrev $newrev $refname
done
fi
This sample has a few limitations:
allowlist patterns are proxy pushed.$TARGET_REPO
is seen as a ref update, and Git displays warnings about it.