4 # for sending emails (-a option)
5 ADMINS="Thierry.Parmentelat@inria.fr baris@metin.org"
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)
110 run ${REPO_DIR} git checkout master
111 run ${REPO_DIR} git merge --ff $REMOTE/master
112 for BRANCH in $BRANCHES ; do
113 run -i ${REPO_DIR} git branch $BRANCH $REMOTE/$BRANCH
114 run ${REPO_DIR} git checkout $BRANCH
115 run ${REPO_DIR} git merge --ff $REMOTE/$BRANCH
119 function push_all_branches () {
120 local REPO_DIR=$1; shift
122 local PUSH_TO=$1; shift
123 local PUSH_FROM=$1; shift
125 pushd $REPO_DIR > /dev/null
126 BRANCHES=$(git branch -r | grep $PUSH_FROM | grep -v HEAD | sed "s/.*\///g" | grep -v master)
129 run ${REPO_DIR} git push $PUSH_TO master:master
130 for BRANCH in $BRANCHES ; do
131 run ${REPO_DIR} git push $PUSH_TO $BRANCH:$BRANCH
135 function mirror_repo () {
137 FAILED_CMDS=() # reset previous failure if any
139 NAME=$(basename ${repo} ".git")
141 REPO_DIR=${LOCAL_MIRROR_DIR}/${NAME}
142 REMOTE_REPO=${REMOTE_GIT}/${GIT_NAME}
143 MASTER_REPO=${MASTER_GIT}/${GIT_NAME}
145 # if there is no remote repository it may be that we only have
146 # the repository locally and don't need to mirror
147 git ls-remote $REMOTE_REPO &> /dev/null || return
149 if [ -d ${REPO_DIR} ] ; then
150 msg "pulling from ${NAME}"
151 run ${REPO_DIR} git fetch origin --tags
152 run ${REPO_DIR} git fetch origin
153 merge_all_branches $REPO_DIR $NAME origin
154 if [ -n "$FAILED_CMDS" ]; then
156 body="Can not fetch from ${MASTER_REPO}\n\n------------\n FAILED COMMANDS:\n"
157 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
158 notify $REPO_DIR "git-mirror.sh failed to merge remote with module ${NAME}" "$body"
162 msg "mirroring ${NAME} for the first time"
163 run ${LOCAL_MIRROR_DIR} git clone ${REMOTE_REPO}
164 run ${REPO_DIR} git remote add local_master ${MASTER_REPO}
167 msg "pushing ${NAME} to local master"
168 run ${REPO_DIR} git fetch local_master --tags
169 run ${REPO_DIR} git fetch local_master
170 merge_all_branches $REPO_DIR $NAME local_master
171 if [ -n "$FAILED_CMDS" ]; then
172 pushd ${REPO_DIR} > /dev/null
173 STATUS_OUT=$(git status)
176 body="STATUS in ${REPO_DIR}:\n${STATUS_OUT} \n\n------------\n FAILED COMMANDS:\n"
177 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
178 notify $REPO_DIR "git-mirror.sh failed on with module ${NAME}" "$body"
181 run ${REPO_DIR} git push --tags local_master
182 push_all_branches $REPO_DIR $NAME local_master origin
183 if [ -n "$FAILED_CMDS" ]; then
185 body="FAILED COMMANDS:\n"
186 for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
187 notify $REPO_DIR "git-mirror.sh failed to push back with module ${NAME}" "$body"
190 # success, remove previous check file if any
191 clear_notify $REPO_DIR
195 echo "Usage $COMMAND [options] REPONAME*"
196 echo " [-a admin-mails] : provide space-separated admins emails"
197 echo " [-r remote-git-url] : e.g. -r git://git.onelab.eu/"
198 echo " [-q] quiet mode for running under cron"
199 echo " [-v] verbose mode"
200 echo " [-f] force mode, runs even if the lock file is present"
201 echo " see also manage-git-mirror.sh"
205 while getopts "a:r:qvfh" opt; do
208 r) REMOTE_GIT=$OPTARG ;;
213 \?) echo "Invalid option: -$opt" >&2 ;;
218 # skip this if force is set
219 # the natural usage of force is, manage-git-mirror.sh touches the lock file
220 # which prevents the cron jobs from running during an hour
221 # then you can use with -f which leaves the lock file as it is
222 if [ -z "$FORCE" ] ; then
223 # is the stamp older than an hour ?
225 is_old=$(find $LOCKFILE -mmin +$GRACE 2> /dev/null)
226 if [ -n "$is_old" ] ; then
227 msg "$LOCKFILE is older than $GRACE minutes - removing"
231 if [ -f $LOCKFILE ] ; then
232 msg "Found $LOCKFILE - another git-mirror seems to be running. Aborting... "
239 # if force is set we leave the lock file as is
240 [ -z "$FORCE" ] && date > $LOCKFILE
241 for gitrepo in "$@"; do mirror_repo $gitrepo ; done
242 [ -z "$FORCE" ] && rm -f $LOCKFILE