cookutils view cooker @ rev 1057
cook: follow changed version in the "Summary"; lighttpd/index.cgi: follow changed version in the "[download]".
author | Aleksej Bobylev <al.bobylev@gmail.com> |
---|---|
date | Fri May 18 11:42:03 2018 +0300 (2018-05-18) |
parents | 54c97f545127 |
children | 77291d41f21a |
line source
1 #!/bin/sh
2 #
3 # SliTaz Build Bot. The Cooker is a tool to automate and test SliTaz package
4 # building. Please read the Cookbook documentation for more information
5 # and discuss with the AUTHORS before adding anything here. PS: no translations
6 # here since it's not an end user tool and it's not useful. All devs should
7 # at least understand basic English.
8 #
10 . /usr/lib/slitaz/libcook.sh
12 # Set pkg name and use same wok as cook.
13 pkg="$2"
14 wok="$WOK"
16 # PID file.
17 pidfile='/var/run/cooker.pid'
19 #
20 # Functions
21 #
23 usage() {
24 cat <<EOT
26 Usage: cooker [<command>] [<options>]
28 Commands with <options>:
29 -u | usage Display this short usage.
30 -s | setup Setup the Cooker environment.
31 setup-cron [<hours>] Setup a cron job for the Cooker.
32 check-cron Check Cooker cron job.
33 arch-db Create host arch packages DB.
34 -n | note <note_text> Add a note to the cooknotes.
35 -ns | notes Display all the cooknotes.
36 -b | block <package> Block a package so cook will skip it.
37 -ub | unblock <package> Unblock a blocked package.
38 -R | reverse <package> Cook all reverse dependencies for a package.
39 -p | pkg <package> Same as 'cook pkg' but with cooker log.
40 -f | flavor <flavor_name> Cook all packages of a flavor.
41 -l | list <list_file> Cook all packages in the given list.
42 -c | cat <category> Cook all packages of a category.
43 -r | rev <rev_number> Cook packages of a specific revision.
44 -a | all Find and cook all unbuilt packages.
45 -T | tasks List existing cooker tasks.
46 -t | task <task> Executing specified task.
47 -o | outgoing Find changes in wok that we can move to wok-hg.
48 autodeps Find dependencies for all packages in wok.
50 EOT
51 exit 0
52 }
55 # Some messages occur in activity but log verbose output when checking for commits
56 # into a log file.
58 log_commits() {
59 sed '/^.\//d' | sed '/^.hg/d' | tee -a $LOGS/commits.log
60 }
63 # Clean up before exit when check and cook commits finish.
65 clean_exit() {
66 rm -f $command; touch $command
67 [ "$previous_command" ] && ps | grep -q "${previous_command/:/ }" &&
68 echo -n "$previous_command" > $command
69 rm -f $pidfile
70 }
73 # Summary for commits.
75 commits_summary() {
76 msg="from revision $cur to $new"
77 [ "$new" == "$cur" ] && msg="revision $new"
78 echo "Will cook $msg"
79 separator
80 title "Summary for commits"
81 echo "Hg wok revision : $cur"
82 echo "Pulled revision : $new"
83 echo "Check date : $(date '+%F %T')"
84 }
87 # Return all the names of packages bundled in this receipt
89 all_names() {
90 local split=" $SPLIT "
91 unset SPLIT
92 . $wok/$pkg/receipt
94 if ! head -n1 $WOK/$pkg/receipt | fgrep -q 'v2'; then
95 # For receipts v1: $SPLIT may present in the $WANTED package,
96 # but split packages have their own receipts
97 echo $PACKAGE
98 elif [ "${split/ $PACKAGE /}" != "$split" ]; then
99 echo $SPLIT
100 else
101 echo $PACKAGE $SPLIT
102 fi
103 }
106 # Scan packages build deps and fill up cookorder list.
108 cook_order_scan() {
109 rm -f $cookorder $cookorder.split
110 touch $cookorder $cookorder.split
112 # Make combined split table: beginning from actual information with fresh
113 # commits. Example:
114 # freetype freetype freetype-dev
115 # harfbuzz harfbuzz harfbuzz-apps harfbuzz-dev
116 while read pkg; do
117 echo "$pkg $(all_names)" >> $cookorder.split
118 done < $cooklist
119 cat $cache/split.db >> $cookorder.split
121 maxlen=$(wc -L < $cooklist)
123 while read pkg; do
124 unset WANTED BUILD_DEPENDS
125 . $wok/$pkg/receipt
126 bdeps=$(
127 # Substitite each package of BUILD_DEPENDS list by the "main"
128 # receipt which builds this package. Example:
129 # BUILD_DEPENDS="freetype-dev harfbuzz-dev" -> bdeps="freetype harfbuzz"
130 for i in $BUILD_DEPENDS; do
131 main="$(awk -F$'\t' -vi="$i" '{
132 if (index(" " $2 " ", i)) {print $1; exit}
133 }' $cookorder.split)"
134 echo ${main:-$i}
135 done
136 )
137 # The :: is for web interface color.
138 bdeps=$(echo $WANTED $bdeps | tr '\n' ' ')
139 printf "%-${maxlen}s :: %s\n" "$pkg" "$bdeps"
140 for dep in $bdeps; do
141 if grep -q "^$dep$" $cooklist; then
142 if ! grep -q "^$dep$" $cookorder; then
143 echo "$dep" >> $cookorder
144 fi
145 fi
146 done
147 done < $cooklist
149 # Append unordered packages to cookorder.
150 while read pkg; do
151 if ! grep -q "^$pkg$" $cookorder; then
152 echo "$pkg" >> $cookorder
153 fi
154 done < $cooklist
155 }
158 # Scan and rescan until the cooklist is ordered then handle WANTED.
160 cook_order() {
161 time=$(date +%s)
162 scan=0
163 rm -rf $cache/cookorder.d
164 mkdir -p $cache/cookorder.d
166 # Keep an original cooklist so we do a diff when ordering is finished.
167 cp -f $cooklist $cooklist.0
168 echo 'cookorder' > $command
169 title 'Initial Cooker order scan'
170 cook_order_scan
172 # Diff between the cooklist and new ordered list ? So copy the last
173 # cookorder to cooklist and rescan it.
174 while /bin/true; do
175 if ! cmp -s $cooklist $cookorder; then
176 scan=$(($scan + 1))
177 title "Diff scan: $scan"
179 md5stamp=$(md5sum $cookorder | cut -d' ' -f1)
180 if [ -e "$cache/cookorder.d/$md5stamp" ]; then
181 newline
182 echo 'A dependency loop was detected. Interrupting the cookorder.'
183 break
184 fi
185 touch $cache/cookorder.d/$md5stamp
187 mv -f $cookorder $cooklist
188 cook_order_scan
190 else
191 break
192 fi
193 done
194 # Clean
195 rm -rf $cache/cookorder.d; rm $cookorder.split
197 # Keep a diff between submitted cooklist and the ordered.
198 diff $cooklist.0 $cooklist > $cooklist.diff
199 rm -f $cookorder $cooklist.0
201 # Scan finished: append pkg to WANTED or leave it in the ordered cooklist.
202 # TODO: grep the line number to get pkg position and keep it higher.
203 title 'Handle WANTED package'
204 while read pkg; do
205 unset WANTED
206 . $wok/$pkg/receipt
207 for wanted in $WANTED; do
208 echo "$pkg :: $wanted"
209 if grep -q ^${wanted}$ $cooklist; then
210 sed -i -e "/^$pkg$/d" \
211 -e "/^$wanted$/ a $pkg" $cooklist
212 fi
213 done
214 done < $cooklist
216 # Show ordered cooklist
217 title 'Cooklist order'
218 cat $cooklist
219 separator
221 time=$(($(date +%s) - $time))
222 pkgs=$(wc -l < $cooklist)
223 title 'Summary for cookorder'
224 cat <<EOT
225 Ordered packages : $pkgs
226 Scans executed : $scan
227 Scan duration : ${time}s
228 EOT
229 separator
231 rm -f $command
232 }
235 # Remove blocked (faster this way than grepping before).
237 strip_blocked() {
238 while read pkg; do
239 sed -i "/^${pkg}$/d" $cooklist
240 done < $blocked
241 sed -i '/^$/d' $cooklist
242 }
245 # Use in default mode and with all cmd.
247 cook_commits() {
248 if [ -s "$commits" ]; then
249 while read pkg; do
250 ps | grep -q "cook $pkg$" && continue
251 echo "cook:$pkg" > $command
252 cook $pkg || broken
253 sed -i "/^${pkg}$/d" $commits
254 done < $commits
255 fi
256 }
259 # Cook all packages in a cooklist.
261 cook_list() {
262 while read pkg; do
263 ps | grep -q "cook $pkg$" && continue
264 cook $pkg || broken
265 sed -i "/^${pkg}$/d" $cooklist
266 done < $cooklist
267 }
270 # Create a arch.$ARCH file for each package cooked for the target host
271 # architecture
272 #
273 # The deal: we don't want all packages handled by cooker commands and stats,
274 # since we don't cross compile all packages for each arch but only a set of
275 # packages to provide one full featured desktop, servers and goodies useful
276 # for the host system.
277 #
279 arch_db() {
280 count=0
281 echo "Cleaning packages DB : arch.$ARCH"
282 rm -f $wok/*/arch.$ARCH && cd $wok
283 echo "Creating $ARCH packages DB..."
284 for pkg in *; do
285 [ -s $wok/$pkg/receipt ] || continue
286 HOST_ARCH=
287 . $wok/$pkg/receipt
288 if [ -n "$HOST_ARCH" ]; then
289 if $(echo "$HOST_ARCH" | egrep -q "$ARCH|any"); then
290 count=$(($count + 1))
291 echo "Adding: $pkg"
292 touch $pkg/arch.$ARCH
293 fi
294 unset HOST_ARCH
295 else
296 # HOST_ARCH not set --> package is suitable for current ARCH (equivalent to "any")
297 count=$(($count + 1))
298 echo "Adding: $pkg"
299 touch $pkg/arch.$ARCH
300 fi
301 done
302 echo "Packages for $ARCH : $count"
303 }
306 # Compare wok and wok-hg file $1, display signs:
307 # '+' file added, '-' file removed, '~' file changed, '=' file not changed
309 compare_wok_file() {
310 local f1='n' f2='n' # n: not exists, e: exists
311 [ -e "$wok/$1" ] && f1='e'
312 [ -e "$wok-hg/$1" ] && f2='e'
313 case "$f1$f2" in
314 en) echo "+ $1";;
315 ne) [ -n "$del" ] && echo "- $1";;
316 ee)
317 if cmp -s "$wok/$1" "$wok-hg/$1"; then
318 [ -n "$eq" ] && echo "= $1"
319 else
320 echo "~ $1"
321 fi
322 ;;
323 esac
324 }
327 # Compare wok and wok-hg folder $1; process only:
328 # receipt, description.*txt, all files in the stuff folder
330 compare_wok_folder() {
331 IFS=$'\n'
332 {
333 for i in $wok $wok-hg; do
334 ls $i/$1/receipt 2>/dev/null
335 ls $i/$1/description.*txt 2>/dev/null
336 [ -d $i/$1/stuff ] && find $i/$1/stuff -type f
337 done
338 } | sed "s|$wok/$1/||; s|$wok-hg/$1/||" | sort -u | \
339 while read file; do
340 compare_wok_file "$1/$file"
341 done
342 }
345 # Compare entire wok
347 compare_wok() {
348 {
349 cd $wok; ls
350 cd $wok-hg; ls
351 } | sort -u | \
352 while read folder; do
353 result="$(compare_wok_folder $folder)"
354 [ -n "$result" ] && echo -e "$result\n"
355 done
356 }
359 #
360 # Commands
361 #
363 previous_command="$(cat $command 2>/dev/null)"
364 case "$1" in
365 usage|help|-u|-h)
366 usage ;;
368 setup|-s)
369 # Setup the Cooker environment.
370 title 'Setting up the Cooker'
371 mkdir -p $CACHE
372 echo "Cooker setup using: $SLITAZ" | log
373 for pkg in $SETUP_PKGS mercurial rsync tazlito; do
374 [ ! -d "$INSTALLED/$pkg" ] && tazpkg get-install $pkg
375 done
376 mkdir -p $SLITAZ && cd $SLITAZ
377 if [ -d "${wok}-hg" ]; then
378 echo -e 'Hg wok already exists.\n'
379 exit 1
380 fi
381 if [ -d "$wok" ]; then
382 echo -e 'Build wok already exists.\n'
383 exit 1
384 fi
386 # Directories and files
387 echo "mkdir's and touch files in: $SLITAZ"
388 mkdir -p $PKGS $LOGS $FEEDS $CACHE $SRC
389 for f in $activity $blocked $broken $commits $cooklist $command; do
390 touch $f
391 done
392 hg clone $WOK_URL ${wok}-hg || exit 1
393 [ -d "$flavors" ] || hg clone $FLAVORS_URL flavors
394 cp -a ${wok}-hg $wok
395 footer ;;
397 arch-db)
398 # Manually create arch packages DB.
399 arch_db ;;
401 setup-cron)
402 # Create cron job for the cooker.
403 [ "$2" ] || hours=2
404 if [ ! -f "$crontabs" ]; then
405 mkdir -p /var/spool/cron/crontabs
406 fi
407 if ! fgrep -q /usr/bin/cooker $crontabs; then
408 cat > $crontabs <<EOT
409 # Run SliTaz Cooker every $hours hours
410 59 */$hours * * * touch $CACHE/cooker-request
411 */5 * * * * [ $CACHE/cooker-request -nt $CACHE/activity ] && /usr/bin/cooker --output=html
412 */5 * * * * [ -z "$(pidof cooker)" ] && [ -s $CACHE/recook-packages ] && /usr/bin/cooker list $CACHE/recook-packages
413 EOT
414 touch $CACHE/cooker-request $CACHE/recook-packages
415 chmod 666 $CACHE/cooker-request $CACHE/recook-packages
416 killall crond 2>/dev/null && /etc/init.d/crond start
417 fi ;;
419 check-cron)
420 if [ ! -f "$crontabs" ]; then
421 echo "There is no $crontabs here. Use setup-cron option."
422 exit 1
423 fi
424 fgrep /usr/bin/cooker $crontabs ;;
426 note|-n)
427 # Blocked a pkg and want others to know why? Post a note!
428 [ -n "$2" ] && echo "$(date '+%F %R') : $2" >> $cooknotes ;;
430 notes|-ns)
431 # View cooknotes.
432 title 'Cooknotes'
433 cat $cooknotes
434 footer ;;
436 block|-b)
437 # Block a package.
438 [ "$pkg" ] && cook $pkg --block ;;
440 unblock|-ub)
441 # Unblock a package.
442 [ "$pkg" ] && cook $pkg --unblock ;;
444 reverse|-r)
445 # Cook all reverse dependencies for a package. This command lets us
446 # control the Cooker manually for commits that will cook a lot of packages.
447 #
448 # Use hg commit? Ex: hg commit -m "Message bla bla | cooker:reverse"
449 #
450 if [ ! -d "$wok/$pkg" ]; then
451 echo -e "\nNo package $2 found.\n"
452 exit 0
453 fi
454 rm -f $cooklist; touch $cooklist
455 title "Reverse cooklist for: $pkg"
457 cd $wok
458 for rev in *; do
459 [ -s $wok/$rev/receipt ] || continue
460 unset WANTED DEPENDS BUILD_DEPENDS; . $wok/$rev/receipt
461 if echo "$WANTED $DEPENDS $BUILD_DEPENDS" | fgrep -q $pkg; then
462 echo "$rev" | tee -a $cooklist
463 fi
464 done
465 footer "Reverse dependencies found: $(wc -l < $cooklist)"
466 strip_blocked
467 cook_order | tee $LOGS/cookorder.log
468 cook_list ;;
470 pkg|-p)
471 # Same as 'cook pkg' but with log for web interface.
472 ps | grep -q "cook $pkg$" && echo 'Already running' && continue
473 cook $pkg || broken
474 clean_exit ;;
476 cat|-c)
477 # Cook all packages of a category.
478 cat="$2"
479 rm -f $cooklist; touch $cooklist
481 cd $wok
482 for pkg in *; do
483 [ -s $pkg/receipt ] || continue
484 unset CATEGORY; . $pkg/receipt
485 [ "$CATEGORY" == "$cat" ] && echo $pkg >> $cooklist
486 done
487 strip_blocked
488 cook_order | tee $LOGS/cookorder.log
489 cook_list ;;
491 flavor|-f)
492 # Cook all packages of a flavor.
493 name="$2"
494 if [ ! -d "$flavors/$name" ]; then
495 echo -e "\nSpecified flavor does not exist: $name\n"
496 exit 1
497 fi
498 if [ -d "$flavors/.hg" ]; then
499 cd $flavors; hg pull -u
500 fi
501 list="$flavors/$name/packages.list"
502 cp -a $list $cooklist
503 strip_blocked
504 cook_order | tee $LOGS/cookorder.log
505 cook_list ;;
507 list|-l)
508 # Cook a list of packages given in argument.
509 list="$2"
510 if [ ! -f "$list" ]; then
511 echo -e "\nSpecified list does not exist: $list\n"
512 exit 1
513 fi
514 cat $list >> $cooklist
515 echo -n > $list
516 strip_blocked
517 cook_order | tee $LOGS/cookorder.log
518 cook_list ;;
520 rev|-r)
521 # Cook or recook a specific Hg revision.
522 rev="$2"
523 [ "$rev" ] || exit 0
524 rm -f $cooklist; touch $cooklist
526 cd $wok
527 for pkg in $(hg log --rev=$rev --template "{files}"); do
528 echo "$pkg" | cut -d/ -f1 >> $cooklist
529 done
530 strip_blocked
531 cook_order | tee $LOGS/cookorder.log
532 cook_list ;;
534 all|-a)
535 # Try to build all unbuilt packages except blocked's.
536 echo 'cooker:all' > $command
537 rm -f $cooklist; touch $cooklist
538 title 'Cooker cooklist'
540 # Find all unbuilt packages. Get EXTRAVERSION from packed receipt
541 # if it exists since extra version is added when packing the package.
542 echo 'Searching for all unbuilt packages' | log
544 cd $wok
545 for pkg in *; do
546 [ -s $pkg/receipt ] || continue
547 unset EXTRAVERSION
548 . $pkg/receipt
549 [ -f "$pkg/taz/$PACKAGE-$VERSION/receipt" ] && \
550 . $pkg/taz/$PACKAGE-$VERSION/receipt
551 if [ ! -f "$PKGS/$PACKAGE-$VERSION$EXTRAVERSION.tazpkg" ]; then
552 echo $pkg; echo $pkg >> $cooklist
553 fi
554 done
555 strip_blocked
556 cook_order | tee $LOGS/cookorder.log
557 echo "Packages to cook: $(wc -l < $cooklist)" | log
558 cook_list ;;
560 tasks|-T)
561 # List existing cooker tasks
562 [ ! -d "$tasks" ] && echo 'There are no tasks.' && exit 0
563 title 'Cooker tasks list'
564 last=$(ls $tasks | tail -n1)
565 for task in $(ls $tasks); do
566 . $tasks/$task
567 echo "Task name : $task"
568 echo "Description : $DESC"
569 separator $([ $task != $last ] && echo '-')
570 done
571 newline ;;
573 task|-t)
574 # Executing specified task
575 task="$2"
576 title "Executing cooker task: $task"
577 . $tasks/$task; task
578 footer "Task $task finished" ;;
580 outgoing|-o)
581 # Find changes in wok that we can move to wok-hg
582 compare_wok
583 ;;
585 autodeps)
586 # Find dependencies for all packages in wok
587 cd $WOK
588 for pkg in *; do
589 cook $pkg --deps --quiet
590 done | tee $cache/autodeps
591 ;;
593 *)
594 # Default is to cook all commits if not yet running.
595 [ -n "$1" ] && usage
596 cooklist=$commits
597 if [ -f "$pidfile" ]; then
598 pid=$(cat $pidfile)
599 if [ -s /proc/$pid/status ]; then
600 echo -e "\nStill cooking latest commits with pid:"
601 echo -e " $pid\n"
602 exit 0
603 fi
604 rm -f "$pidfile"
605 fi
607 # Start and get a PID file.
608 rm -f $LOGS/commits.log
609 newline
610 echo 'Checking for commits' | log_commits
611 separator | tee -a $LOGS/commits.log
613 echo $$ > $pidfile
614 trap 'echo -e "\nCooker stopped: PID $$\n" && \
615 rm -f $pidfile $command && exit 1' INT TERM
617 echo "Cooker PID : $$" | log_commits
618 echo "Cooker date : $(date '+%F %T')" | log_commits
620 # Get revisions. Here we have 2 echoes since we want a msg on screen,
621 # in commits log and activity DB without a space before.
622 cd $wok || exit 1
623 cur=$(hg head --template '{rev}\n')
624 echo "Updating wok : ${wok}-hg (rev $cur)" | log_commits
625 echo "Updating wok: ${wok}-hg" | log
626 echo 'hg:pull' > $command
627 cd $wok-hg; hg pull -u | log_commits
628 new=$(hg head --template '{rev}\n')
629 # Store last rev to be used by CGI so it doesn't need to call hg head
630 # on each load.
631 echo "$new" > $wokrev
633 # Sync build wok with rsync so we don't take care about removing old
634 # files as before.
635 if [ "$new" -gt "$cur" ]; then
636 echo "Changes found from: $cur to $new" | log
637 echo 'Syncing build wok with Hg wok...' | log_commits
638 rsync -r -t -c -l -u -v -D -E $wok-hg/ $wok/ | \
639 sed '/^$/d' | log_commits
640 else
641 echo "No revision changes: $cur vs $new" | log
642 separator | log_commits
643 clean_exit; newline
644 exit 0
645 fi
647 # Get and display modifications.
648 cd $wok-hg
649 commits_summary | log_commits
650 cur=$(($cur + 1))
651 rm -f $commits.tmp; touch $commits.tmp
652 for rev in $(seq $cur $new); do
653 for file in $(hg log --rev=$rev --template "{files}"); do
654 pkg=$(echo $file | cut -d/ -f1)
655 desc=$(hg log --rev=$rev --template "{desc}" $file)
656 echo "Committed package : $pkg - $desc" | log_commits
657 echo $pkg >> $commits.tmp
658 done
659 done
661 # We may have deleted packages and files in stuff/. Remove it and
662 # clean DB as well as log file.
663 cd $wok
664 for pkg in *; do
665 if [ ! -d "$wok-hg/$pkg" ]; then
666 echo "Removing package: $pkg" | log_commits
667 if [ -s $wok/$pkg/receipt ]; then
668 . $wok/$pkg/receipt
669 rm -f $PKGS/$PACKAGE-$VERSION*
670 fi
671 rm -rf $wok/$pkg $LOGS/$pkg.log
672 sed -i "/^${pkg}$/d" $blocked $broken $commits.tmp
673 sed -i "/^$pkg\t/d" $PKGS/packages.info
674 sed -i "/^$pkg:/d" $cache/files.list
675 fi
676 if [ -d "$wok/$pkg/stuff" ]; then
677 if [ ! -d "$wok-hg/$pkg/stuff" ]; then
678 echo "Removing stuff: $pkg/stuff" | log_commits
679 rm -rf $wok/$pkg/stuff
680 else
681 for stuff_file in $(cd $wok/$pkg/stuff; find \( -type f -o -type l \) | sed 's|^\./||'); do
682 if [ ! -f "$wok-hg/$pkg/stuff/$stuff_file" -a \
683 ! -h "$wok-hg/$pkg/stuff/$stuff_file" ]; then
684 echo "Removing file from stuff: $wok/$pkg/stuff/$stuff_file" | log_commits
685 rm -f $wok/$pkg/stuff/$stuff_file
686 rmdir --parents --ignore-fail-on-non-empty $(dirname "$wok/$pkg/stuff/$stuff_file")
687 fi
688 done
689 fi
690 fi
691 done
693 # Keep previous commit and discard duplicate lines
694 cat $commits $commits.tmp | sed '/^$/d' > $commits.new
695 uniq $commits.new > $commits; rm $commits.*
697 # Handle cross compilation. Create arch packages DB and remove pkgs
698 # not cooked for this arch from the commits list.
699 arch_db
700 while read pkg; do
701 if [ ! -f "$wok/$pkg/arch.$ARCH" ]; then
702 echo "Cooker arch : skip $pkg (not included in: $ARCH)" | \
703 log_commits
704 sed -i "/^${pkg}$/d" $commits
705 else
706 echo "Cooker arch : $ARCH" | log_commits
707 fi
708 done < $commits
710 # Re-create split database
711 cook splitdb
713 # Stats
714 pkgs=$(wc -l < $commits)
715 echo "Packages to cook: $pkgs" | log
716 echo "Packages to cook : $pkgs" | log_commits
717 separator | log_commits
718 newline
719 strip_blocked
720 cook_order | tee $LOGS/cookorder.log
721 cook_commits
722 clean_exit ;;
723 esac
725 exit 0