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