do not care about the current branch in the mirror area
[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 # options
9 QUIET=
10 VERBOSE=
11 FORCE=
12
13 # local path to the reference (bare) repos
14 MASTER_GIT="/git"
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
22 FAILED_CMDS=()
23
24 function verbose () {
25     [ -n "$VERBOSE" ] && echo "--------------------" "$@"
26 }
27
28 function msg () {
29     [ -n "$QUIET" ] || echo "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "$@"
30 }
31
32 function failure () {
33     msg "Received signal - cleaning up $LOCKFILE and aborting"
34     rm -f $LOCKFILE
35     exit 1
36 }
37
38 function notify () {
39     local REPO_DIR=$1; shift
40     local SUBJECT=$1; shift
41     local BODY=$1; shift
42     verbose "notify" $SUBJECT
43
44     # already notified within a half day ?
45     GRACE=$((60*12))
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"
50         return
51     fi
52
53     for admin in $ADMINS; do
54         echo -e "$BODY" | mail -s "$SUBJECT" $admin
55     done
56     touch $REPO_DIR/$NOTIFIED_FILE
57 }
58
59 function clear_notify () {
60     local REPO_DIR=$1; shift
61     rm -f $REPO_DIR/$NOTIFIED_FILE
62 }
63
64 # run directory command
65 #   -or- to ignore errors
66 # run -i directory "command"
67 function run () {
68     local IGNORE=""
69     case "$1" in -i) IGNORE=true ; shift ;; esac
70     local REPO_DIR=$1; shift
71     local COMMAND="$@"
72
73     pushd ${REPO_DIR} > /dev/null
74     # quiet mode: record stdout and err for the mail
75     ok=true
76     if [ -n "$QUIET" ] ; then
77         OUTPUT=$($COMMAND 2>&1) || ok=
78         TORECORD="[$REPO_DIR] $COMMAND\n$OUTPUT"
79     else
80         if [ -z "$IGNORE" ] ; then
81             echo "[$REPO_DIR] Running $COMMAND"
82             $COMMAND || ok=
83             TORECORD="$COMMAND"
84         else
85             echo "[$REPO_DIR] Running $COMMAND [ignored]"
86             $COMMAND >& /dev/null
87         fi
88     fi
89     
90     # failed ?
91     if [ -z "$ok" ]; then
92         # let's record the failure unless ignore is set
93         if [ -z "$IGNORE" ] ; then
94             FAILED_CMDS=("${FAILED_CMDS[@]}" "$TORECORD")
95         fi
96     fi
97     verbose "after run with $COMMAND" "ok=$ok" "#failed=${#FAILED_CMDS[@]}"
98     popd > /dev/null
99 }
100
101 function merge_all_branches () {
102     local REPO_DIR=$1; shift
103     local NAME=$1; shift
104     local REMOTE=$1; shift
105
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)
109     popd > /dev/null
110
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
117     done
118 }
119
120 function push_all_branches () {
121     local REPO_DIR=$1; shift
122     local NAME=$1; shift
123     local PUSH_TO=$1; shift
124     local PUSH_FROM=$1; shift
125
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)
129     popd > /dev/null
130
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
134     done
135 }
136
137 function mirror_repo () {
138     local repo=$1; shift
139     FAILED_CMDS=()   # reset previous failure if any
140
141     NAME=$(basename ${repo} ".git")
142     GIT_NAME=${NAME}.git
143     REPO_DIR=${LOCAL_MIRROR_DIR}/${NAME}
144     REMOTE_REPO=${REMOTE_GIT}/${GIT_NAME}
145     MASTER_REPO=${MASTER_GIT}/${GIT_NAME}
146
147     # if there is no remote repository it may be that we only have
148     # the repository locally and don't need to mirror
149     git ls-remote $REMOTE_REPO &> /dev/null || return
150
151     if [ -d ${REPO_DIR} ] ; then
152         msg "pulling from ${NAME}"
153         run ${REPO_DIR} git fetch origin --tags
154         run ${REPO_DIR} git fetch origin
155         merge_all_branches $REPO_DIR $NAME origin
156         if [ -n "$FAILED_CMDS" ]; then
157             # format mail body
158             body="Can not fetch from ${MASTER_REPO}\n\n------------\n FAILED COMMANDS:\n"
159             for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
160             notify $REPO_DIR "git-mirror.sh failed to merge remote with module ${NAME}" "$body"
161             return
162         fi
163     else
164         msg "mirroring ${NAME} for the first time"
165         run ${LOCAL_MIRROR_DIR} git clone ${REMOTE_REPO}
166         run ${REPO_DIR} git remote add local_master ${MASTER_REPO}
167     fi
168
169     msg "pushing ${NAME} to local master"
170     run ${REPO_DIR} git fetch local_master --tags
171     run ${REPO_DIR} git fetch local_master
172     merge_all_branches $REPO_DIR $NAME local_master
173     if [ -n "$FAILED_CMDS" ]; then
174         pushd ${REPO_DIR} > /dev/null
175         STATUS_OUT=$(git status)
176         popd > /dev/null
177         # format mail body
178         body="STATUS in ${REPO_DIR}:\n${STATUS_OUT} \n\n------------\n FAILED COMMANDS:\n"
179         for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
180         notify $REPO_DIR "git-mirror.sh failed on with module ${NAME}" "$body"
181         return
182     fi
183     run ${REPO_DIR} git push --tags local_master
184     push_all_branches $REPO_DIR $NAME local_master origin 
185     if [ -n "$FAILED_CMDS" ]; then
186         # format mail body
187         body="FAILED COMMANDS:\n"
188         for line in "${FAILED_CMDS[@]}"; do body="$body$line\n"; done
189         notify $REPO_DIR "git-mirror.sh failed to push back with module ${NAME}" "$body"
190         return
191     fi
192     # success, remove previous check file if any
193     clear_notify $REPO_DIR
194     # touch a stamp so it's easier to figure out where/if things get stuck
195     touch ${REPO_DIR}/MIRRORED.stamp
196 }
197
198 function usage () {
199     echo "Usage $COMMAND [options] REPONAME*"
200     echo "  [-a admin-mails] : provide space-separated admins emails"
201     echo "  [-r remote-git-url] : e.g. -r git://git.onelab.eu/"
202     echo "  [-q] quiet mode for running under cron"
203     echo "  [-v] verbose mode"
204     echo "  [-f] force mode, runs even if the lock file is present"
205     echo "       see also manage-git-mirror.sh"
206     exit 1
207 }
208
209 while getopts "a:r:qvfh" opt; do
210   case $opt in
211       a) ADMINS=$OPTARG ;;
212       r) REMOTE_GIT=$OPTARG ;;
213       q) QUIET=true ;;
214       v) VERBOSE=true ;;
215       f) FORCE=true ;;
216       h) usage ;;
217       \?) echo "Invalid option: -$opt" >&2 ;;
218   esac
219 done
220 shift $((OPTIND-1))
221
222 # skip this if force is set
223 # the natural usage of force is, manage-git-mirror.sh touches the lock file
224 # which prevents the cron jobs from running during an hour
225 # then you can use with -f which leaves the lock file as it is
226 if [ -z "$FORCE" ] ; then
227     # is the stamp older than an hour ? 
228     GRACE=60
229     is_old=$(find $LOCKFILE -mmin +$GRACE 2> /dev/null)
230     if [ -n "$is_old" ] ; then
231         msg "$LOCKFILE is older than $GRACE minutes - removing"
232         rm -f $LOCKFILE
233     fi
234
235     if [ -f $LOCKFILE ] ; then
236         msg "Found $LOCKFILE - another git-mirror seems to be running. Aborting... " 
237         exit 1
238     fi
239 fi
240
241 trap failure INT
242
243 # if force is set we leave the lock file as is
244 [ -z "$FORCE" ] && date > $LOCKFILE
245 for gitrepo in "$@"; do mirror_repo $gitrepo ; done
246 [ -z "$FORCE" ] && rm -f $LOCKFILE