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)
148 # then skip it, the target itself will be handled if in scope
149 [ -h ${MASTER_REPO} ] && return
151 # if there is no remote repository it may be that we only have
152 # the repository locally and don't need to mirror
153 git ls-remote $REMOTE_REPO &> /dev/null || return
155 if [ -d ${REPO_DIR} ] ; then
156 msg "pulling from ${NAME}"
157 run ${REPO_DIR} git fetch origin --tags
158 run ${REPO_DIR} git fetch origin
159 merge_all_branches $REPO_DIR $NAME origin
160 if [ -n "$FAILED_CMDS" ]; then
162 body="Can not fetch from ${MASTER_REPO}\n\n------------\n FAILED COMMANDS:\n"
163 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
164 notify $REPO_DIR "git-mirror.sh failed to merge remote with module ${NAME}" "$body"
168 msg "mirroring ${NAME} for the first time"
169 run ${LOCAL_MIRROR_DIR} git clone ${REMOTE_REPO}
170 run ${REPO_DIR} git remote add local_master ${MASTER_REPO}
173 msg "pushing ${NAME} to local master"
174 run ${REPO_DIR} git fetch local_master --tags
175 run ${REPO_DIR} git fetch local_master
176 merge_all_branches $REPO_DIR $NAME local_master
177 if [ -n "$FAILED_CMDS" ]; then
178 pushd ${REPO_DIR} > /dev/null
179 STATUS_OUT=$(git status)
182 body="STATUS in ${REPO_DIR}:\n${STATUS_OUT} \n\n------------\n FAILED COMMANDS:\n"
183 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
184 notify $REPO_DIR "git-mirror.sh failed on with module ${NAME}" "$body"
187 run ${REPO_DIR} git push --tags local_master
188 push_all_branches $REPO_DIR $NAME local_master origin
189 if [ -n "$FAILED_CMDS" ]; then
191 body="FAILED COMMANDS:\n"
192 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
193 notify $REPO_DIR "git-mirror.sh failed to push back with module ${NAME}" "$body"
196 # success, remove previous check file if any
197 clear_notify $REPO_DIR
198 # touch a stamp so it's easier to figure out where/if things get stuck
199 touch ${REPO_DIR}/MIRRORED.stamp
203 echo "Usage $COMMAND [options] REPONAME*"
204 echo " [-a admin-mails] : provide space-separated admins emails"
205 echo " [-r remote-git-url] : e.g. -r git://git.onelab.eu/"
206 echo " [-q] quiet mode for running under cron"
207 echo " [-v] verbose mode"
208 echo " [-f] force mode, runs even if the lock file is present"
209 echo " see also manage-git-mirror.sh"
213 while getopts "a:r:qvfh" opt; do
216 r) REMOTE_GIT=$OPTARG ;;
221 \?) echo "Invalid option: -$opt" >&2 ;;
226 # skip this if force is set
227 # the natural usage of force is, manage-git-mirror.sh touches the lock file
228 # which prevents the cron jobs from running during an hour
229 # then you can use with -f which leaves the lock file as it is
230 if [ -z "$FORCE" ] ; then
231 # is the stamp older than an hour ?
233 is_old=$(find $LOCKFILE -mmin +$GRACE 2> /dev/null)
234 if [ -n "$is_old" ] ; then
235 msg "$LOCKFILE is older than $GRACE minutes - removing"
239 if [ -f $LOCKFILE ] ; then
240 msg "Found $LOCKFILE - another git-mirror seems to be running. Aborting... "
247 # if force is set we leave the lock file as is
248 [ -z "$FORCE" ] && date > $LOCKFILE
249 for gitrepo in "$@"; do mirror_repo $gitrepo ; done
250 [ -z "$FORCE" ] && rm -f $LOCKFILE