blob: 0bb1df651c7f8b0266b105442f54db1f5ac5cf3b (
plain) (
tree)
|
|
#!/usr/bin/env bash
set -Eeuo pipefail
REPOSITORY_PATH_FORMAT="/var/lib/gitolite/repositories/%s.git"
MERGE_JOB_FORMAT="%s-merge-patch"
JENKINS_SSH_PORT=38844
JENKINS_SSH_HOST="localhost"
msg() {
echo "==> $1"
}
read_branches() {
local prefix="refs/$1/"
local -n read_branches_target="$2"
local refname
local objname
local -i name_length
local -i max_length=0
while IFS=$'\t' read -r refname objname; do
local branch_name="${refname#"${prefix}"}"
read_branches_target["${branch_name}"]="${objname}"
name_length=${#branch_name}
if (( name_length > max_length )); then
max_length=$name_length
fi
done < <(git for-each-ref "${prefix}**" \
--format=$'%(refname)\t%(objectname)' \
--color=never)
local branch_name
for branch_name in "${!read_branches_target[@]}"; do
printf " %*s = %s\n" \
$((-max_length)) \
"${branch_name}" \
"${read_branches_target["${branch_name}"]}"
done
}
get_or_empty() {
local -n get_or_empty_array="$1"
local key="$2"
local -n get_or_empty_target="$3"
if [[ -v get_or_empty_array["${key}"] ]]; then
get_or_empty_target="${get_or_empty_array["${key}"]}"
else
# shellcheck disable=SC2034
get_or_empty_target=''
fi
}
archive_if_needed() {
local archive_prefix="$1"
local branch_name="$2"
local old_tip="$3"
local new_tip="$4"
if ! git merge-base --is-ancestor "${old_tip}" "${new_tip}"; then
local archive_name="${archive_prefix}${branch_name}"
msg "Refusing to clobber ${branch_name}, archiving as ${archive_name}"
git tag -a -m "Archive before synchronization with ${new_tip}" "${archive_name}" "${old_tip}"
fi
}
sync_mirror() {
if (( $# != 1 )); then
echo "Usage: $0 REPOSITORY" >&2
exit 1
fi
local project="$1"
local repository
# shellcheck disable=2059
repository="$(printf "${REPOSITORY_PATH_FORMAT}" "${project}")"
local archive_prefix
archive_prefix="$(TZ='UTC' printf 'archive/%(%Y%m%dT%H%M%S)TZ/')"
cd "${repository}"
local remote
remote="$(git config --get gitmirror.remoteName)"
local remote_url
remote_url="$(git config --get gitmirror.remoteUrl)"
if [[ -z "${remote}" || -z "${remote_url}" ]]; then
msg "FATAL: ${repository} not configured for mirroring" >&2
exit 2
fi
msg "Synchronizing ${repository} with ${remote}"
git config "remote.${remote}.url" "${remote_url}"
msg "Remote tracking branches before fetch"
# shellcheck disable=SC2034
local -A old_tracking
read_branches "remotes/${remote}" old_tracking
msg "Local branches"
# shellcheck disable=SC2034
local -A local_branches
read_branches "heads" local_branches
msg "Fetch from ${remote}"
git fetch --tags --force --progress -- "${remote_url}" \
"+refs/heads/*:refs/remotes/${remote}/*"
msg "Remote tracking branches after fetch"
local -A new_tracking
read_branches "remotes/${remote}" new_tracking
local -a to_patch=()
local branch_name
for branch_name in "${!new_tracking[@]}"; do
if [[ "${branch_name}" =~ ^patch-for/.* ]]; then
msg "FATAL: Refusing to pull patch ${branch_name} from remote" >&2
exit 2
fi
local new_tip="${new_tracking["${branch_name}"]}"
local old_tip
get_or_empty old_tracking "${branch_name}" old_tip
local local_tip
get_or_empty local_branches "${branch_name}" local_tip
local patch_tip
get_or_empty local_branches "patch-for/${branch_name}" patch_tip
if [[ -n "${old_tip}" ]]; then
archive_if_needed "${archive_prefix}" "${remote}/${branch_name}" \
"${old_tip}" "${new_tip}"
fi
if [[ -z "${local_tip}" ]]; then
msg "Create local branch ${branch_name} at ${new_tip}"
git branch --no-track "${branch_name}" "${patch_tip:-"${new_tip}"}"
if [[ -n "${patch_tip}" ]]; then
msg "Will patch newly created local branch ${branch_name}"
to_patch+=("${branch_name}")
fi
else
if [[ -z "${patch_tip}" ]]; then
if [[ "${local_tip}" != "${new_tip}" ]]; then
archive_if_needed "${archive_prefix}" "${branch_name}" \
"${local_tip}" "${old_tip:-"${new_tip}"}"
msg "Update local branch ${branch_name} to ${new_tip}"
git update-ref "refs/heads/${branch_name}" "${new_tip}" "${local_tip}"
fi
else
if ! git merge-base --is-ancestor "${new_tip}" "${local_tip}"; then
msg "Will update local branch ${branch_name} to ${new_tip} by patching"
to_patch+=("${branch_name}")
fi
fi
fi
done
local job_name
# shellcheck disable=2059
job_name="$(printf "${MERGE_JOB_FORMAT}" "${project}")"
for branch_name in "${to_patch[@]}"; do
msg "Triggering Jenkings job ${job_name} for branch ${branch_name}"
ssh -p "${JENKINS_SSH_PORT}" "${JENKINS_SSH_HOST}" \
build "${job_name}" -p "BRANCH=${branch_name}"
done
}
if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
sync_mirror "$@"
fi
|