update from MirBSD CVS: never close stdout or stderr (or stdin)
[shellsnippets/shellsnippets.git] / mksh / svn2cvs
1 #!/usr/bin/env mksh
2 id='$MirOS: contrib/hosted/tg/svn2cvs.sh,v 1.4 2014/02/10 00:36:12 tg Exp $'
3 #-
4 # Copyright (c) 2008, 2014
5 #       Thorsten Glaser <tg@mirbsd.org>
6 #
7 # Provided that these terms and disclaimer and all copyright notices
8 # are retained or reproduced in an accompanying document, permission
9 # is granted to deal in this work without restriction, including un-
10 # limited rights to use, publicly perform, distribute, sell, modify,
11 # merge, give away, or sublicence.
12 #
13 # This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
14 # the utmost extent permitted by applicable law, neither express nor
15 # implied; without malicious intent or gross negligence. In no event
16 # may a licensor, author or contributor be held liable for indirect,
17 # direct, other damage, loss, or other issues arising in any way out
18 # of dealing in the work, even if advised of the possibility of such
19 # damage or existence of a defect, except proven that it results out
20 # of said person's immediate fault when using the work as intended.
21
22 export LC_ALL=C TZ=UTC RCSINIT='-x,v -z'
23 unset LANGUAGE
24 nl='
25 '
26 saveIFS=$IFS
27 id=${id#\$}
28 id=${id%\$}
29 be='\e[1;31m'
30 bm='\e[1;32m'
31 bi='\e[1m'
32 bo='\e[0m'
33
34 print -n $bo\\r
35 i=0
36 if ! whence -p cmp >/dev/null; then
37         print -u2 ${be}Error${bo}: you must install cmp to continue.
38         i=1
39 fi
40 if ! whence -p cvs >/dev/null; then
41         print -u2 ${be}Error${bo}: you must install GNU CVS to continue.
42         i=1
43 fi
44 if ! whence -p rcs >/dev/null || ! whence -p ci >/dev/null || \
45     ! whence -p co >/dev/null; then
46         print -u2 ${be}Error${bo}: you must install GNU RCS to continue.
47         i=1
48 fi
49 if ! whence -p svn >/dev/null; then
50         print -u2 ${be}Error${bo}: you must install Subversion to continue.
51         i=1
52 fi
53 (( i )) && exit 1
54
55
56 function die {
57         print -u2 -- "${be}$*${bo}"
58         exit 1
59 }
60
61 function usage {
62         print -u2 "${bi}Syntax${bo}:\tmksh svn2cvs.sh [-ht] [-B baserev] [-H headrev]"
63         print -u2 "\t[-m module] -r repo -s svnurl"
64         print -u2 "Normal conversion goes from baserev (1) to headrev (HEAD)"
65         print -u2 "WARNING: This script is not yet whitespace-in-pathname safe!"
66         print -u2 "\nExample:"
67         print -u2 \$ mksh svn2cvs -t -r /cvs -s \
68             svn+ssh://www.FreeWRT.org/svn/trunk
69         print -u2 \$ mksh svn2cvs -B 10000 -r /cvs -m OpenWrt/packages/utils/mksh \\
70         print -u2 \\t-s https://svn.openwrt.org/openwrt/packages/utils/mksh
71         print -u2 \$ mksh svn2cvs -r /cvs -m BSDanywhere -s \
72             svn://svn.startek.ch/BSDanywhere/trunk
73         exit ${1:-1}
74 }
75
76
77 flag_t=0
78 baserev=1
79 headrev=HEAD
80 module=
81 repo=
82 url=
83
84 while getopts "B:H:hm:r:s:t" ch; do
85         case $ch {
86         (B)     [[ $OPTARG = +([0-9]) ]] || die base revision "'$OPTARG'" \
87                     not numeric
88                 baserev=$OPTARG
89                 ;;
90         (H)     [[ $OPTARG = +([0-9]) ]] || die head revision "'$OPTARG'" \
91                     not numeric
92                 headrev=$OPTARG
93                 ;;
94         (h)     usage 0
95                 ;;
96         (m)     module=${OPTARG##*(/)}
97                 ;;
98         (r)     repo=${OPTARG%%*(/)}
99                 ;;
100         (s)     url=${OPTARG%%*(/)}
101                 ;;
102         (t)     flag_t=1
103                 ;;
104         (*)     usage
105                 ;;
106         }
107 done
108 shift $((OPTIND - 1))
109
110 [[ -z $repo || -z $url ]] && usage
111
112 [[ $repo = :* ]] && die remote repositories are not allowed
113 [[ $repo = /* ]] || repo=$(pwd)/$repo
114
115 if [[ ! -d $repo/CVSROOT/. ]]; then
116         print -u2 ${bi}=== Initialising CVS repository "'$repo'"${bo}
117         mkdir -p "$repo" || die cannot create CVS repository at "'$repo'"
118         cvs -d "$repo" init || die cannot initialise CVS repository \
119             at "'$repo'"
120 fi
121
122 [[ -n $module ]] || module=${url##*/}
123 R=$repo/$module
124 [[ -d $R/. ]] || mkdir -p "$R" || die cannot create module "'$R'"
125 rp=${R%/*}
126 bp=${R##*/}
127
128 [[ $url = -* ]] && die SVN repository URL cannot begin with a dash
129 [[ $module = -* ]] && die CVS module cannot begin with a dash
130 [[ $bp = -* ]] && die last component of CVS module cannot begin with a dash
131
132 T=$(mktemp -d ${TMPDIR:-/tmp}/svn2cvs.XXXXXXXXXXXX) || die cannot create \
133     temporary directory
134 trap "cd /; rm -rf $T; exit 0" 0
135 trap "cd /; rm -rf $T; exit 1" 1 2 3 5 13 15
136
137 cd "$T"
138 mkdir c s
139
140 print -u2 ${bi}=== Preparing CVS module "'$R'" for operation${bo}
141 find "$R" -name '*,v' >f.rcs
142 if [[ -s f.rcs ]]; then
143         xargs chmod ug=r,o-x <f.rcs
144         xargs rcs -kb <f.rcs
145 fi
146
147 print -u2 ${bi}=== Initial checkout of CVS "$R"${bo}
148 (cd c; cvs -qd "$repo" co -PA -d "$bp" "$module") || die cannot checkout sources
149
150 svn log -r $baserev:$headrev "$url" >log
151 if [[ ! -s f.rcs && $(wc -l <log) -lt 2 ]]; then
152         i=$(svn info "$url" | sed -n 's/Last Changed Rev: //p')
153         if [[ $headrev = HEAD ]] || (( (i < headrev) && (i > baserev) )); then
154                 print -u2 "${be}*** Fabricating commit for r$i as" \
155                     r${baserev}:${headrev} are empty and no ,v files$bo
156                 baserev=$i
157         fi
158         svn log -r $baserev:$headrev "$url" >log
159 fi
160
161 cat log |&
162 i=0
163 while IFS= read -pr line; do
164         if [[ $i != 0 && $line = ------------------------------------------------------------------------ ]]; then
165                 logmsg=${logmsg%%*($nl)}
166                 print -u2 ${bi}=== Got log message for revision $i by $author \
167                     on $ymd $hms:${bo}
168                 print -r -- "$logmsg" | sed "s/^/${bm}>>>${bo} /" >&2
169
170                 if [[ -d s/$bp/. ]]; then
171                         print -u2 ${bi}=== Updating SVN checkout to \
172                             revision $i${bo}
173                         (cd "s/$bp"; svn up -r$i) || die cannot update sources
174                 else
175                         print -u2 ${bi}=== Initial checkout of SVN \
176                             "${url}@$baserev"${bo}
177                         (cd s; svn co -r $i "$url" "$bp") || die cannot \
178                             checkout sources
179                 fi
180
181                 print -u2 ${bi}=== Generating list of files${bo}
182                 rm -f f.*
183                 for dir in c s; do
184                         (cd $dir; find "$bp" -type f) | \
185                             fgrep -v -e /CVS/ -e /.svn/ | \
186                             sort >f.$dir
187                 done
188
189                 IFS=$nl
190                 set -A cfiles -- $(<f.c)
191                 set -A sfiles -- $(<f.s)
192                 IFS=$saveIFS
193
194                 ic=0; nc=${#cfiles[*]}
195                 is=0; ns=${#sfiles[*]}
196                 while (( (ic < nc) || (is < ns) )); do
197                         if (( ic == nc )); then
198                                 print -r -- "${sfiles[is++]}" >>f.add
199                         elif (( is == ns )); then
200                                 print -r -- "${cfiles[ic++]}" >>f.del
201                         elif [[ ${cfiles[ic]} = ${sfiles[is]} ]]; then
202                                 cmp -s "c/${cfiles[ic]}" "s/${sfiles[is]}" || \
203                                     print -r -- "${cfiles[ic]}" >>f.chg
204                                 let ++ic ++is
205                         elif [[ ${cfiles[ic]} < ${sfiles[is]} ]]; then
206                                 print -r -- "${cfiles[ic++]}" >>f.del
207                         else
208                                 print -r -- "${sfiles[is++]}" >>f.add
209                         fi
210                 done
211                 # clear the possibly huge arrays
212                 set -A cfiles
213                 set -A sfiles
214
215                 [[ -e f.del ]] && print -u2 ${bi}=== Handling file deletions${bo}
216                 [[ -e f.del ]] && (cd "$rp"; while IFS= read -r fn; do
217                         co -l "$fn"
218                         ci -f -d"${ymd} ${hms}+00" -m"$logmsg" -T -w"$author" \
219                             -sdead "$fn"
220                         [[ -d ${fn%/*}/Attic/. ]] || mkdir -p "${fn%/*}/Attic"
221                         mv "${fn},v" "${fn%/*}/Attic/"
222                 done) <f.del
223
224                 [[ -e f.add ]] && print -u2 ${bi}=== Preparing file additions${bo}
225                 [[ -e f.add ]] && (cd "$rp"; while IFS= read -r fn; do
226                         if [[ -e ${fn%/*}/Attic/${fn##*/},v ]]; then
227                                 mv "${fn%/*}/Attic/${fn##*/},v" "${fn%/*}/"
228                         else
229                                 [[ -d ${fn%/*}/. ]] || mkdir -p "${fn%/*}"
230                                 rcs -i -kb -L \
231                                     -t-"autoconverted by $id from ${url}/${fn#$bp/}" \
232                                     -T "$fn"
233                         fi
234                 done) <f.add
235
236                 [[ -e f.add ]] && cat f.add >>f.chg
237                 if [[ -e f.chg ]]; then
238                         print -u2 ${bi}=== Handling content changes${bo}
239                         (cd "$rp"; xargs co -l) <f.chg
240                         while IFS= read -r fn; do
241                                 cat "s/$fn" >"$rp/$fn"
242                         done <f.chg
243                         (cd "$rp"; xargs ci -f -d"${ymd} ${hms}+00" \
244                             -m"$logmsg" -T -w"$author" -sr$i) <f.chg
245                 fi
246
247                 print -u2 ${bi}=== Updating CVS checkout to HEAD${bo}
248                 (cd "c/$bp"; CVSREADONLYFS=1 cvs -q up -PAd) || die cannot \
249                     update sources
250
251                 if (( flag_t )); then
252                         print -u2 ${bi}=== Tagging deposited revision $i${bo}
253                         (cd "c/$bp"; cvs -q tag -F From_SVN_r$i)
254                 fi
255         fi
256
257         if [[ $line = ------------------------------------------------------------------------ ]]; then
258                 read -pr i x author y ymd hms line || break
259                 i=${i#r}
260                 logmsg=
261                 print -u2 ${bi}=== Begin parsing revision $i${bo}
262                 read -pr line || break
263         else
264                 logmsg=$logmsg${logmsg:+$nl}$line
265         fi
266 done
267
268 print -u2 ${bi}=== Fixing up properties${bo}
269 if [[ -d s/$bp/. ]]; then
270         (cd "s/$bp"; svn proplist -v -R .; print Properties end) |&
271         rm -f binlist
272         while read -p x y z line; do
273                 if [[ $x = Properties ]]; then
274                         fn=${z#\'}
275                         fn=${fn%\':}
276                         [[ -f $R/${fn},v ]] || fn=
277                 elif [[ -z $fn ]]; then
278                         continue
279                 elif [[ $x = svn:executable ]]; then
280                         chmod +x "$R/${fn},v"
281                 elif [[ $x = svn:mime-type && $z = application/* ]]; then
282                         print -r -- "$bp/${fn},v" >>binlist
283                 fi
284         done
285 fi
286 (cd "$rp"; find "$bp" -name '*,v') >f.rcs
287 [[ -s f.rcs ]] && (cd "$rp"; xargs rcs -kkv) <f.rcs
288 [[ -e binlist ]] && (cd "$rp"; xargs rcs -kb) <binlist
289
290 print -u2 ${bm}=== All done.${bo}
291 cd /
292 rm -rf "$T"
293 exit 0