keep on notify errors twice a day
[infrastructure.git] / scripts / git-mirror.sh
1 #!/bin/bash
2 COMMAND=$(basename $0)
3
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"
8 # -q option
9 QUIET=
10
11 # local path to the reference (bare) repos
12 MASTER_GIT="/git"
13 # local path to the working repos; can be trashed if needed
14 LOCAL_MIRROR_DIR="/git-mirror"
15 # a file in each repo to avoid too many notifications
16 NOTIFIED_FILE="NOTIFIED_ADMINS"
17 # global stamp to avoid multiple instances of this script
18 RUNNING_FILE=$LOCAL_MIRROR_DIR/RUNNING_MIRROR
19 # global list - errors to report
20 FAILED_CMDS=()
21
22 function msg () {
23     [ -n "$QUIET" ] || echo "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "$@"
24 }
25
26 function failure () {
27     msg "Received signal - cleaning up $RUNNING_FILE and aborting"
28     rm -f $RUNNING_FILE
29     exit 1
30 }
31
32 function error () {
33     local REPO=$1; shift
34     local SUBJECT=$1; shift
35     local BODY=$1; shift
36
37     # already notified within a half day ?
38     GRACE=$((60*12))
39     is_old=$(find $REPO/$NOTIFIED_FILE -mmin +$GRACE 2> /dev/null)
40     # not there or less than 12 our old: don't notify
41     [ -z "$is_old" ] && return
42
43     for admin in $ADMINS; do
44         echo -e "$BODY" | mail -s "$SUBJECT" $admin
45     done
46     touch $REPO/$NOTIFIED_FILE
47 }
48
49 # run directory command
50 #   -or- to ignore errors
51 # run -i directory "command"
52 function run () {
53     local IGNORE=""
54     case "$1" in -i) IGNORE=true ; shift ;; esac
55     local REPO=$1; shift
56     local COMMAND="$@"
57
58     pushd ${REPO} > /dev/null
59     # quiet mode: record stdout and err for the mail
60     ok=true
61     if [ -n "$QUIET" ] ; then
62         OUTPUT=$($COMMAND 2>&1) || ok=
63         TORECORD="[$REPO] $COMMAND\n$OUTPUT"
64     else
65         echo "[$REPO] Running $COMMAND"
66         $COMMAND || ok=
67         TORECORD="$COMMAND"
68     fi
69     # failed ?
70     if [ -z "$ok" ]; then
71         # let's record the failure unless ignore is set
72         if [ -z "$IGNORE" ] ; then
73             FAILED_CMDS=("${FAILED_CMDS[@]}" "$TORECORD")
74         fi
75     fi
76     popd > /dev/null
77 }
78
79 function merge_all_branches () {
80     local REPO_DIR=$1; shift
81     local NAME=$1; shift
82     local REMOTE=$1; shift
83
84     pushd $REPO_DIR > /dev/null
85     BRANCHES=$(git branch -r | grep $REMOTE | grep -v HEAD | sed "s/.*\///g" | grep -v master)
86     popd > /dev/null
87
88     run ${REPO_DIR} git checkout master
89     run ${REPO_DIR} git merge --ff $REMOTE/master
90     for BRANCH in $BRANCHES ; do
91         run -i  ${REPO_DIR} git branch $BRANCH $REMOTE/$BRANCH
92         run ${REPO_DIR} git checkout $BRANCH
93         run ${REPO_DIR} git merge --ff $REMOTE/$BRANCH
94     done
95 }
96
97 function push_all_branches () {
98     local REPO_DIR=$1; shift
99     local NAME=$1; shift
100     local PUSH_TO=$1; shift
101     local PUSH_FROM=$1; shift
102
103     pushd $REPO_DIR > /dev/null
104     BRANCHES=$(git branch -r | grep $PUSH_FROM | grep -v HEAD | sed "s/.*\///g" | grep -v master)
105     popd > /dev/null
106
107     run ${REPO_DIR} git push $PUSH_TO master:master
108     for BRANCH in $BRANCHES ; do
109         run ${REPO_DIR} git push $PUSH_TO $BRANCH:$BRANCH
110     done
111 }
112
113 function mirror_repo () {
114     local repo=$1; shift
115     FAILED_CMDS=()   # reset previous failure if any
116
117     NAME=$(basename ${repo} ".git")
118     GIT_NAME=${NAME}.git
119     REPO_DIR=${LOCAL_MIRROR_DIR}/${NAME}
120     REMOTE_REPO=${REMOTE_GIT}/${GIT_NAME}
121     MASTER_REPO=${MASTER_GIT}/${GIT_NAME}
122
123     # if there is no remote repository it may be that we only have
124     # the repository locally and don't need to mirror
125     git ls-remote $REMOTE_REPO &> /dev/null || return
126
127     if [ -d ${REPO_DIR} ] ; then
128         msg "pulling from ${NAME}"
129         run ${REPO_DIR} git fetch origin --tags
130         run ${REPO_DIR} git fetch origin
131         merge_all_branches $REPO_DIR $NAME origin
132         if [ $? -ne 0 ]; then
133             # format mail body
134             body="Can not fetch from ${MASTER_REPO}\n\n------------\n FAILED COMMANDS:\n"
135             for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
136             error $REPO_DIR "git-mirror.sh failed to merge remote with module ${NAME}" "$body"
137         fi
138     else
139         msg "mirroring ${NAME} for the first time"
140         run ${LOCAL_MIRROR_DIR} git clone ${REMOTE_REPO}
141         run ${REPO_DIR} git remote add local_master ${MASTER_REPO}
142     fi
143
144     msg "pushing ${NAME} to local master"
145     run ${REPO_DIR} git fetch local_master --tags
146     run ${REPO_DIR} git fetch local_master
147     merge_all_branches $REPO_DIR $NAME local_master
148     if [ -n "$FAILED_CMDS" ]; then
149         pushd ${REPO_DIR} > /dev/null
150         STATUS_OUT=$(git status)
151         popd > /dev/null
152         # format mail body
153         body="STATUS in ${REPO_DIR}:\n${STATUS_OUT} \n\n------------\n FAILED COMMANDS:\n"
154         for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
155         error $REPO_DIR "git-mirror.sh failed on with module ${NAME}" "$body"
156         return
157     fi
158     run ${REPO_DIR} git push --tags local_master
159     push_all_branches $REPO_DIR $NAME local_master origin 
160     if [ $? -ne 0 ]; then
161         # format mail body
162         body="FAILED COMMANDS:\n"
163         for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
164         error $REPO_DIR "git-mirror.sh failed to push back with module ${NAME}" "$body"
165         return
166     fi
167     # success, remove previous check file if any
168     rm -f $REPO_DIR/$NOTIFIED_FILE
169 }
170
171 function usage () {
172     echo "Usage $COMMAND [-a admin-mails] [-r remote-git-url] [-q] REPONAME*"
173     exit 1
174 }
175
176 while getopts "a:r:qh" opt; do
177   case $opt in
178       a) ADMINS=$OPTARG ;;
179       r) REMOTE_GIT=$OPTARG ;;
180       q) QUIET=true ;;
181       h) usage ;;
182       \?) echo "Invalid option: -$opt" >&2 ;;
183   esac
184 done
185
186 # is the stamp older than an hour ? 
187 # in minutes
188 GRACE=60
189 is_old=$(find $RUNNING_FILE -mmin +$GRACE 2> /dev/null)
190 if [ -n "$is_old" ] ; then
191     msg "$RUNNING_FILE is older than $GRACE minutes - removing"
192     rm -f $RUNNING_FILE
193 fi
194
195 if [ -f $RUNNING_FILE ] ; then
196     msg "Found $RUNNING_FILE - another git-mirror seems to be running. Aborting... " 
197     exit 1
198 fi
199
200 trap failure INT
201
202 shift $((OPTIND-1))
203 date > $RUNNING_FILE
204 for gitrepo in "$@"; do mirror_repo $gitrepo ; done
205 rm -f $RUNNING_FILE