#!/usr/bin/ksh -p # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # This script takes a file list and a workspace and builds a set of html files # suitable for doing a code review of source changes via a web page. # Documentation is available via the manual page, webrev.1, or just # type 'webrev -h'. # # Acknowledgements to contributors to webrev are listed in the webrev(1) # man page. # REMOVED_COLOR=brown CHANGED_COLOR=blue NEW_COLOR=blue HTML=' \n' FRAMEHTML=' \n' STDHEAD=' ' # # UDiffs need a slightly different CSS rule for 'new' items (we don't # want them to be bolded as we do in cdiffs or sdiffs). # UDIFFCSS=' ' # Upload the webrev via rsync. Return 0 on success, 1 on error. function rsync_upload { if (( $# != 1 )); then return 1 fi typeset dst=$1 print " Syncing: \c" # end source directory with a slash in order to copy just # directory contents, not the whole directory $RSYNC -r -q $WDIR/ $dst if (( $? != 0 )); then print "failed to sync webrev directory " \ "'$WDIR' to '$dst'" return 1 fi print "Done." return 0 } # Upload the webrev via SSH. Return 0 on success, 1 on error. function ssh_upload { if (( $# != 1 )); then print "ssh_upload: wrong usage" return 1 fi typeset dst=$1 typeset -r host_spec=${dst%%:*} typeset -r dir_spec=${dst#*:} # if the deletion was explicitly requested there is no need # to perform it again if [[ -z $Dflag ]]; then # we do not care about return value because this might be # the first time this directory is uploaded delete_webrev 0 fi # if the supplied path is absolute we assume all directories are # created, otherwise try to create all directories in the path # except the last one which will be created by scp if [[ "${dir_spec}" == */* && "${dir_spec}" != /* ]]; then print " Creating dirs: \c" typeset -r dirs_mk=${dir_spec%/*} typeset -r batch_file_mkdir=$( $MKTEMP /tmp/$webrev_mkdir.XXX ) OLDIFS=$IFS IFS=/ mk= for dir in $dirs_mk; do if [[ -z $mk ]]; then mk=$dir else mk=$mk/$dir fi echo "mkdir $mk" >> $batch_file_mkdir done IFS=$OLDIFS $SFTP -b $batch_file_mkdir $host_spec 2>/dev/null 1>&2 if (( $? != 0 )); then echo "Failed to create remote directories" rm -f $batch_file_mkdir return 1 fi rm -f $batch_file_mkdir print "Done." fi print " Uploading: \c" $SCP -q -C -B -o PreferredAuthentications=publickey -r \ $WDIR $dst if (( $? != 0 )); then print "failed to upload webrev directory" \ "'$WDIR' to '$dst'" return 1 fi print "Done." return 0 } # # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp # on failure. # function delete_webrev { if (( $# != 1 )); then print "delete_webrev: wrong usage" return 1 fi # Strip the transport specification part of remote target first. typeset -r stripped_target=${remote_target##*://} typeset -r host_spec=${stripped_target%%:*} typeset -r dir_spec=${stripped_target#*:} integer -r check=$1 typeset dir_rm # Do not accept an absolute path. if [[ ${dir_spec} == /* ]]; then return 1 fi # Strip the ending slash. if [[ ${dir_spec} == */ ]]; then dir_rm=${dir_spec%%/} else dir_rm=${dir_spec} fi print "Removing remote: \c" if [[ -z "$dir_rm" ]]; then print "empty directory for removal" return 1 fi # Prepare batch file. typeset -r batch_file_rm=$( $MKTEMP /tmp/webrev_remove.XXX ) if [[ -z $batch_file_rm ]]; then print "Cannot create temporary file" return 1 fi print "rename $dir_rm $TRASH_DIR/removed.$$" > $batch_file_rm # Perform remote deletion and remove the batch file. $SFTP -b $batch_file_rm $host_spec 2>/dev/null 1>&2 integer -r ret=$? rm -f $batch_file_rm if (( $ret != 0 && $check > 0 )); then print "Failed" return $ret fi print "Done." return 0 } # # Upload webrev to remote site # function upload_webrev { typeset -r rsync_prefix="rsync://" typeset -r ssh_prefix="ssh://" if [[ ! -d "$WDIR" ]]; then echo "webrev directory '$WDIR' does not exist" return 1 fi # Perform a late check to make sure we do not upload closed source # to remote target when -n is used. If the user used custom remote # target he probably knows what he is doing. if [[ -n $nflag && -z $tflag ]]; then $FIND $WDIR -type d -name closed \ | $GREP closed >/dev/null if (( $? == 0 )); then echo "directory '$WDIR' contains \"closed\" directory" return 1 fi fi # we have the URI for remote destination now so let's start the upload if [[ -n $tflag ]]; then if [[ "${remote_target}" == ${rsync_prefix}?* ]]; then rsync_upload ${remote_target##$rsync_prefix} return $? elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then ssh_upload ${remote_target##$ssh_prefix} return $? else echo "invalid upload URI ($remote_target)" return 1 fi else # try rsync first and fallback to SSH in case it fails rsync_upload ${remote_target} if (( $? != 0 )); then echo "rsync upload failed, falling back to SSH" ssh_upload ${remote_target} fi return $? fi return 0 } # # input_cmd | url_encode | output_cmd # # URL-encode (percent-encode) reserved characters as defined in RFC 3986. # # Reserved characters are: :/?#[]@!$&'()*+,;= # # While not a reserved character itself, percent '%' is reserved by definition # so encode it first to avoid recursive transformation, and skip '/' which is # a path delimiter. # function url_encode { sed -e "s|%|%25|g" -e "s|:|%3A|g" -e "s|\&|%26|g" \ -e "s|?|%3F|g" -e "s|#|%23|g" -e "s|\[|%5B|g" \ -e "s|*|%2A|g" -e "s|@|%40|g" -e "s|\!|%21|g" \ -e "s|=|%3D|g" -e "s|;|%3B|g" -e "s|\]|%5D|g" \ -e "s|(|%28|g" -e "s|)|%29|g" -e "s|\'|%27|g" \ -e "s|+|%2B|g" -e "s|\,|%2C|g" -e "s|\\\$|%24|g" } # # input_cmd | html_quote | output_cmd # or # html_quote filename | output_cmd # # Make a piece of source code safe for display in an HTML
block. # html_quote() { sed -e "s/&/\&/g" -e "s/\</g" -e "s/>/\>/g" "$@" | expand } # # input_cmd | bug2url | output_cmd # # Scan for bugids and insert links to the relevent bug database. # bug2url() { sed -e 's|[0-9]\{5,\}|&|g' } # # input_cmd | sac2url | output_cmd # # Scan for ARC cases and insert links to the relevent SAC database. # This is slightly complicated because inside the SWAN, SAC cases are # grouped by ARC: PSARC/2006/123. But on OpenSolaris.org, they are # referenced as 2006/123 (without labelling the ARC). # sac2url() { if [[ -z "$Oflag" ]]; then sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|\1 \2/\3|g' else sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|\1 \2/\3|g' fi } # # strip_unchanged| output_cmd # # Removes chunks of sdiff documents that have not changed. This makes it # easier for a code reviewer to find the bits that have changed. # # Deleted lines of text are replaced by a horizontal rule. Some # identical lines are retained before and after the changed lines to # provide some context. The number of these lines is controlled by the # variable C in the $AWK script below. # # The script detects changed lines as any line that has a "
" inx = c % C c = C } for (i = 0; i < c; i++) print ln[(inx + i) % C] } c = 0; print next } { if (c >= C) { ln[c % C] = $0 c++; next; } c++; print } END { if (c > (C * 2)) print "\n
\n" } # function sp(n) {for (i=0;i%4d %s \n", n, NR, $0} # NR==8 {wl("#7A7ADD");next} # NR==54 {wl("#7A7ADD");sp(3);next} # NR==56 {wl("#7A7ADD");next} # NR==57 {wl("black");printf "\n"; next} # : : # # This script is then run on the original source file to generate the # HTML that corresponds to the source file. # # The two HTML files are then combined into a single piece of HTML that # uses an HTML table construct to present the files side by side. You'll # notice that the changes are color-coded: # # black - unchanged lines # blue - changed lines # bold blue - new lines # brown - deleted lines # # Blank lines are inserted in each file to keep unchanged lines in sync # (side-by-side). This format is familiar to users of sdiff(1) or # Teamware's filemerge tool. # sdiff_to_html() { diff -b $1 $2 > /tmp/$$.diffs TNAME=$3 TPATH=$4 COMMENT=$5 # # Now we have the diffs, generate the HTML for the old file. # $AWK ' BEGIN { printf "function sp(n) {for (i=0;i %%4d %%s\\n\", NR, $0}\n" printf "function changed() " printf "{printf \"%%4d %%s\\n\", NR, $0}\n" printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n" } /^ {next} /^>/ {next} /^---/ {next} { split($1, a, /[cad]/) ; if (index($1, "a")) { if (a[1] == 0) { n = split(a[2], r, /,/); if (n == 1) printf "BEGIN\t\t{sp(1)}\n" else printf "BEGIN\t\t{sp(%d)}\n",\ (r[2] - r[1]) + 1 next } printf "NR==%s\t\t{", a[1] n = split(a[2], r, /,/); s = r[1]; if (n == 1) printf "bl();printf \"\\n\"; next}\n" else { n = r[2] - r[1] printf "bl();sp(%d);next}\n",\ (r[2] - r[1]) + 1 } next } if (index($1, "d")) { n = split(a[1], r, /,/); n1 = r[1] n2 = r[2] if (n == 1) printf "NR==%s\t\t{removed(); next}\n" , n1 else printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2 next } if (index($1, "c")) { n = split(a[1], r, /,/); n1 = r[1] n2 = r[2] final = n2 d1 = 0 if (n == 1) printf "NR==%s\t\t{changed();" , n1 else { d1 = n2 - n1 printf "NR==%s,NR==%s\t{changed();" , n1, n2 } m = split(a[2], r, /,/); n1 = r[1] n2 = r[2] if (m > 1) { d2 = n2 - n1 if (d2 > d1) { if (n > 1) printf "if (NR==%d)", final printf "sp(%d);", d2 - d1 } } printf "next}\n" ; next } } END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" } ' /tmp/$$.diffs > /tmp/$$.file1 # # Now generate the HTML for the new file # $AWK ' BEGIN { printf "function sp(n) {for (i=0;i %%4d %%s\\n\", NR, $0}\n" printf "function changed() " printf "{printf \"%%4d %%s\\n\", NR, $0}\n" printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n" } /^ {next} /^>/ {next} /^---/ {next} { split($1, a, /[cad]/) ; if (index($1, "d")) { if (a[2] == 0) { n = split(a[1], r, /,/); if (n == 1) printf "BEGIN\t\t{sp(1)}\n" else printf "BEGIN\t\t{sp(%d)}\n",\ (r[2] - r[1]) + 1 next } printf "NR==%s\t\t{", a[2] n = split(a[1], r, /,/); s = r[1]; if (n == 1) printf "bl();printf \"\\n\"; next}\n" else { n = r[2] - r[1] printf "bl();sp(%d);next}\n",\ (r[2] - r[1]) + 1 } next } if (index($1, "a")) { n = split(a[2], r, /,/); n1 = r[1] n2 = r[2] if (n == 1) printf "NR==%s\t\t{new() ; next}\n" , n1 else printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2 next } if (index($1, "c")) { n = split(a[2], r, /,/); n1 = r[1] n2 = r[2] final = n2 d2 = 0; if (n == 1) { final = n1 printf "NR==%s\t\t{changed();" , n1 } else { d2 = n2 - n1 printf "NR==%s,NR==%s\t{changed();" , n1, n2 } m = split(a[1], r, /,/); n1 = r[1] n2 = r[2] if (m > 1) { d1 = n2 - n1 if (d1 > d2) { if (n > 1) printf "if (NR==%d)", final printf "sp(%d);", d1 - d2 } } printf "next}\n" ; next } } END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" } ' /tmp/$$.diffs > /tmp/$$.file2 # # Post-process the HTML files by running them back through $AWK # html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html # # Now combine into a valid HTML file and side-by-side into a table # print "$HTML$STDHEAD" print " $WNAME Sdiff $TPATH/$TNAME " print "" print "Print this page" print "$COMMENT\n" print "" print "" framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \ "$COMMENT" } # # framed_sdiff
" print " " strip_unchanged /tmp/$$.file1.html print "" print " " strip_unchanged /tmp/$$.file2.html print "# # Expects lefthand and righthand side html files created by sdiff_to_html. # We use insert_anchors() to augment those with HTML navigation anchors, # and then emit the main frame. Content is placed into: # # $WDIR/DIR/$TNAME.lhs.html # $WDIR/DIR/$TNAME.rhs.html # $WDIR/DIR/$TNAME.frames.html # # NOTE: We rely on standard usage of $WDIR and $DIR. # function framed_sdiff { typeset TNAME=$1 typeset TPATH=$2 typeset lhsfile=$3 typeset rhsfile=$4 typeset comments=$5 typeset RTOP # Enable html files to access WDIR via a relative path. RTOP=$(relative_dir $TPATH $WDIR) # Make the rhs/lhs files and output the frameset file. print "$HTML$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF $comments
EOF cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html close='' print $close >> $WDIR/$DIR/$TNAME.lhs.html print $close >> $WDIR/$DIR/$TNAME.rhs.html print "$FRAMEHTML$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html print "$WNAME Framed-Sdiff " \ "$TPATH/$TNAME " >> $WDIR/$DIR/$TNAME.frames.html cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF