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