4 # for sending emails (-a option)
5 ADMINS="Thierry.Parmentelat@inria.fr"
6 # the other end of the mirror (-r option)
7 REMOTE_GIT="git://git.planet-lab.org"
13 # local path to the reference (bare) repos
15 # local path to the working repos; can be trashed if needed
16 LOCAL_MIRROR_DIR="/git-mirror"
17 # a file in each repo to avoid too many notifications
18 NOTIFIED_FILE="NOTIFIED"
19 # global stamp to avoid multiple instances of this script
20 LOCKFILE=$LOCAL_MIRROR_DIR/LOCK
21 # global list - errors to report
25 [ -n "$VERBOSE" ] && echo "--------------------" "$@"
29 [ -n "$QUIET" ] || echo "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "$@"
33 msg "Received signal - cleaning up $LOCKFILE and aborting"
39 local REPO_DIR=$1; shift
40 local SUBJECT=$1; shift
42 verbose "notify" $SUBJECT
44 # already notified within a half day ?
46 is_recent=$(find $REPO_DIR/$NOTIFIED_FILE -mmin -$GRACE 2> /dev/null)
47 # not there or less than 12 our old: don't notify
48 if [ -n "$is_recent" ] ; then
49 verbose "skipping recent notification -- $SUBJECT"
53 for admin in $ADMINS; do
54 echo -e "$BODY" | mail -s "$SUBJECT" $admin
56 touch $REPO_DIR/$NOTIFIED_FILE
59 function clear_notify () {
60 local REPO_DIR=$1; shift
61 rm -f $REPO_DIR/$NOTIFIED_FILE
64 # run directory command
65 # -or- to ignore errors
66 # run -i directory "command"
69 case "$1" in -i) IGNORE=true ; shift ;; esac
70 local REPO_DIR=$1; shift
73 pushd ${REPO_DIR} > /dev/null
74 # quiet mode: record stdout and err for the mail
76 if [ -n "$QUIET" ] ; then
77 OUTPUT=$($COMMAND 2>&1) || ok=
78 TORECORD="[$REPO_DIR] $COMMAND\n$OUTPUT"
80 if [ -z "$IGNORE" ] ; then
81 echo "[$REPO_DIR] Running $COMMAND"
85 echo "[$REPO_DIR] Running $COMMAND [ignored]"
92 # let's record the failure unless ignore is set
93 if [ -z "$IGNORE" ] ; then
94 FAILED_CMDS=("${FAILED_CMDS[@]}" "$TORECORD")
97 verbose "after run with $COMMAND" "ok=$ok" "#failed=${#FAILED_CMDS[@]}"
101 function merge_all_branches () {
102 local REPO_DIR=$1; shift
104 local REMOTE=$1; shift
106 pushd $REPO_DIR > /dev/null
107 BRANCHES=$(git branch -r | grep $REMOTE | grep -v HEAD | sed "s/.*\///g" | grep -v master)
108 HAS_MASTER=$(git branch -l | grep master)
111 [ -n "$HAS_MASTER" ] && run ${REPO_DIR} git checkout master
112 [ -n "$HAS_MASTER" ] && run ${REPO_DIR} git merge --ff $REMOTE/master
113 for BRANCH in $BRANCHES ; do
114 run -i ${REPO_DIR} git branch $BRANCH $REMOTE/$BRANCH
115 run ${REPO_DIR} git checkout $BRANCH
116 run ${REPO_DIR} git merge --ff $REMOTE/$BRANCH
120 function push_all_branches () {
121 local REPO_DIR=$1; shift
123 local PUSH_TO=$1; shift
124 local PUSH_FROM=$1; shift
126 pushd $REPO_DIR > /dev/null
127 BRANCHES=$(git branch -r | grep $PUSH_FROM | grep -v HEAD | sed "s/.*\///g" | grep -v master)
128 HAS_MASTER=$(git branch -l | grep master)
131 [ -n "$HAS_MASTER" ] && run ${REPO_DIR} git push $PUSH_TO master:master
132 for BRANCH in $BRANCHES ; do
133 run ${REPO_DIR} git push $PUSH_TO $BRANCH:$BRANCH
137 function mirror_repo () {
139 FAILED_CMDS=() # reset previous failure if any
141 NAME=$(basename ${repo} ".git")
143 REPO_DIR=${LOCAL_MIRROR_DIR}/${NAME}
144 REMOTE_REPO=${REMOTE_GIT}/${GIT_NAME}
145 MASTER_REPO=${MASTER_GIT}/${GIT_NAME}
147 # if the local master is a symlink (like /git/vserver-reference.git -> sliceref.git)
149 # we use this for either aliases (like vserver-reference and sliceimage) or
150 # for repos managed in other locations (like /git-slave) but where
151 # the symlink is needed so they get served by git-daemon
152 [ -h ${MASTER_REPO} ] && return
154 # if there is no remote repository it may be that we only have
155 # the repository locally and don't need to mirror
156 git ls-remote $REMOTE_REPO &> /dev/null || return
158 if [ -d ${REPO_DIR} ] ; then
159 msg "pulling from ${NAME}"
160 run ${REPO_DIR} git fetch origin --tags
161 run ${REPO_DIR} git fetch origin
162 merge_all_branches $REPO_DIR $NAME origin
163 if [ -n "$FAILED_CMDS" ]; then
165 body="Can not fetch from ${MASTER_REPO}\n\n------------\n FAILED COMMANDS:\n"
166 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
167 notify $REPO_DIR "git-mirror.sh failed to merge remote with module ${NAME}" "$body"
171 msg "mirroring ${NAME} for the first time"
172 run ${LOCAL_MIRROR_DIR} git clone ${REMOTE_REPO}
173 run ${REPO_DIR} git remote add local_master ${MASTER_REPO}
176 msg "pushing ${NAME} to local master"
177 run ${REPO_DIR} git fetch local_master --tags
178 run ${REPO_DIR} git fetch local_master
179 merge_all_branches $REPO_DIR $NAME local_master
180 if [ -n "$FAILED_CMDS" ]; then
181 pushd ${REPO_DIR} > /dev/null
182 STATUS_OUT=$(git status)
185 body="STATUS in ${REPO_DIR}:\n${STATUS_OUT} \n\n------------\n FAILED COMMANDS:\n"
186 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
187 notify $REPO_DIR "git-mirror.sh failed on with module ${NAME}" "$body"
190 run ${REPO_DIR} git push --tags local_master
191 push_all_branches $REPO_DIR $NAME local_master origin
192 if [ -n "$FAILED_CMDS" ]; then
194 body="FAILED COMMANDS:\n"
195 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
196 notify $REPO_DIR "git-mirror.sh failed to push back with module ${NAME}" "$body"
199 # success, remove previous check file if any
200 clear_notify $REPO_DIR
201 # touch a stamp so it's easier to figure out where/if things get stuck
202 touch ${REPO_DIR}/MIRRORED.stamp
206 # hook to simpler, pull-only repos
207 # e.g. /git-slave/tophat.git on onelab.eu
208 # the idea here is, we need to keep a local read-only mirror
209 # of some repositories
210 # instead of hacking this script that is already scary, I preferred to write
211 # a separate script git-update.sh that runs a bit like auto-update.sh
212 # and that additionally takes care of remote branches
213 # the layout is as follows
214 # * manually created the local mirror by running
216 # git clone ssh://tophat@git.top-hat.info/tophat.git
217 # which creates a non-bare repo right under /git-slave
218 # * create a symlink for git-daemon
220 # ln -s /git-slave/tophat/.git tophat.git
221 # * enable updates by just dropping a symlink there
222 # cd /git-slave/tophat
223 # ln -s /root/infrastructure/scripts/git-udate.sh
225 function slave_repo () {
229 [ -x $subdir/git-update.sh ] && $subdir/git-update.sh
234 echo "Usage $COMMAND [options] REPONAME*"
235 echo " [-a admin-mails] : provide space-separated admins emails"
236 echo " [-r remote-git-url] : e.g. -r git://git.onelab.eu/"
237 echo " [-s slave-area] : git-update.sh in all subdirs of slave-area that have one"
238 echo " typically /git-slave on the onelab side for e.g. tophat"
239 echo " [-q] quiet mode for running under cron"
240 echo " [-v] verbose mode"
241 echo " [-f] force mode, runs even if the lock file is present"
242 echo " see also manage-git-mirror.sh"
246 # set this as a default - won't harm anyone
247 SLAVE_GITDIR=/git-slave
249 while getopts "a:r:s:qvfh" opt; do
252 r) REMOTE_GIT=$OPTARG ;;
253 s) SLAVE_GITDIR=$OPTARG ;;
258 \?) echo "Invalid option: -$opt" >&2 ;;
263 # skip this if force is set
264 # the natural usage of force is, manage-git-mirror.sh touches the lock file
265 # which prevents the cron jobs from running during an hour
266 # then you can use with -f which leaves the lock file as it is
267 if [ -z "$FORCE" ] ; then
268 # is the stamp older than an hour ?
270 is_old=$(find $LOCKFILE -mmin +$GRACE 2> /dev/null)
271 if [ -n "$is_old" ] ; then
272 msg "$LOCKFILE is older than $GRACE minutes - removing"
276 if [ -f $LOCKFILE ] ; then
277 msg "Found $LOCKFILE - another git-mirror seems to be running. Aborting... "
284 # if force is set we leave the lock file as is
285 [ -z "$FORCE" ] && date > $LOCKFILE
286 for gitrepo in "$@"; do mirror_repo $gitrepo ; done
287 if [ -d "$SLAVE_GITDIR" ] ; then slave_repo $SLAVE_GITDIR ; fi
288 [ -z "$FORCE" ] && rm -f $LOCKFILE