webrev.sh revision 2f54b716e4d3cb0dc99066638fed631e3cbec97c
2N/A# The contents of this file are subject to the terms of the 2N/A# Common Development and Distribution License (the "License"). 2N/A# You may not use this file except in compliance with the License. 2N/A# See the License for the specific language governing permissions 2N/A# and limitations under the License. 2N/A# When distributing Covered Code, include this CDDL HEADER in each 2N/A# If applicable, add the following below this CDDL HEADER, with the 2N/A# fields enclosed by brackets "[]" replaced with your own identifying 2N/A# information: Portions Copyright [yyyy] [name of copyright owner] 2N/A# Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. 2N/A# Copyright 2008, 2010, Richard Lowe 2N/A# This script takes a file list and a workspace and builds a set of html files 2N/A# suitable for doing a code review of source changes via a web page. 2N/A# Documentation is available via the manual page, webrev.1, or just # Acknowledgements to contributors to webrev are listed in the webrev(1) HTML=
'<?xml version="1.0"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" STDHEAD=
'<meta http-equiv="cache-control" content="no-cache"></meta> <meta http-equiv="Pragma" content="no-cache"></meta> <meta http-equiv="Expires" content="-1"></meta> Note to customizers: the body of the webrev is IDed as SUNWwebrev mechanism available in some browsers. For example, to have all "removed" information be red instead of <style type="text/css" media="screen"> background-color: #eeeeee; border-top: 1px solid #aaa; border-bottom: 1px solid #aaa; a:hover { background-color: #ffcc99; } <style type="text/css" media="print"> pre { font-size: 0.8em; font-family: courier, monospace; } hr { border: none 0; border-top: 1px solid #aaa; height: 1px; } # 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). <style type="text/css" media="screen"> # Display remote target with prefix and trailing slash. print " Upload to: ${display_target}\n" \ # Upload the webrev via rsync. Return 0 on success, 1 on error. print "\nERROR: rsync_upload: wrong usage ($#)" print "\nERROR: rsync_upload: cannot create temporary file" # The source directory must end with a slash in order to copy just # directory contents, not the whole directory. print "Failed.\nERROR: rsync failed" print "src dir: '${src_dir}'\ndst dir: '$dst'" # Create directories on remote host using SFTP. Return 0 on success, # 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 # Remove the last directory from directory specification. print "\nERROR: remote_mkdirs:" \ "cannot create temporary file for batch file" # Use the '-' prefix to ignore mkdir errors in order # to avoid an error in case the directory already # exists. We check the directory with chdir to be sure print "\nERROR: remote_mkdirs:" \ "cannot create temporary file for error messages" print "\nERROR: failed to create remote directories" # Upload the webrev via SSH. Return 0 on success, 1 on error. print "\nERROR: ssh_upload: wrong number of arguments" # Display the upload information before calling delete_webrev # because it will also print its progress. # If the deletion was explicitly requested there is no need # We do not care about return value because this might be # the first time this directory is uploaded. # Create remote directories. Any error reporting will be done # in remote_mkdirs function. print "\nERROR: ssh_upload:" \ "cannot create temporary file for error messages" print "Failed.\nERROR: scp failed" # Delete webrev at remote site. Return 0 on success, 1 or exit code from sftp # on failure. If first argument is 1 then perform the check of sftp return # value otherwise ignore it. If second argument is present it means this run # only performs deletion. print "delete_webrev: wrong number of arguments" # Strip the transport specification part of remote target first. # Do not accept an absolute path. # Strip the ending slash. print "\nERROR: empty directory for removal" print "\nERROR: delete_webrev: cannot create temporary file" # Perform remote deletion and remove the batch file. print "\nERROR: delete_webrev:" \ "cannot create temporary file for error messages" print "Failed.\nERROR: failed to remove remote directories" # Upload webrev to remote site if [[ ! -d "$WDIR" ]]; then print "\nERROR: webrev directory '$WDIR' does not exist" # 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. print "\nERROR: directory '$WDIR' contains" \ # We have the URI for remote destination now so let's start the upload. elif [[ "${remote_target}" == ${ssh_prefix}?* ]]; then # Try rsync first and fallback to SSH in case it fails. print "Failed. (falling back to SSH)" # 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 # The quotation character is deliberately not escaped in order to make # the substitution work with GNU sed. $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 # html_quote filename | output_cmd # Make a piece of source code safe for display in an HTML <pre> block. $SED -e "s/&/\&/g" -e "s/</\</g" -e "s/>/\>/g" "$@" | expand # input_cmd | its2url | output_cmd # Scan for information tracking system references and insert <a> links to the # strip_unchanged <infile> | 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 "<span class=" # string embedded (unchanged lines have no particular class and are not # part of a <span>). Blank lines (without a sequence number) are also # detected since they flag lines that have been inserted or deleted. NF == 0 || /<span class="/ { print "\n</pre><hr></hr><pre>" END { if (c > (C * 2)) print "\n</pre><hr></hr>" } # This function takes two files as arguments, obtains their diff, and # processes the diff output to present the files as an HTML document with # the files displayed side-by-side, differences shown in color. It also # takes a delta comment, rendered as an HTML snippet, as the third # argument. The function takes two files as arguments, then the name of # file, the path, and the comment. The HTML will be delivered on stdout, # 1234567</a> my bugid' > <file>.html # FYI: This function is rather unusual in its use of awk. The initial # diff run produces conventional diff output showing changed lines mixed # with editing codes. The changed lines are ignored - we're interested in # the editing codes, e.g. # These editing codes are parsed by the awk script and used to generate # another awk script that generates HTML, e.g the above lines would turn # into something like this: # BEGIN { printf "<pre>\n" } # function sp(n) {for (i=0;i<n;i++)printf "\n"} # function wl(n) {printf "<font color=%s>%4d %s </font>\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 # 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. # Now we have the diffs, generate the HTML for the old file. printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n" printf "function removed() " printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n" printf "function changed() " printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n" printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n" printf "BEGIN\t\t{sp(1)}\n" printf "BEGIN\t\t{sp(%d)}\n",\ printf "NR==%s\t\t{", a[1] printf "bl();printf \"\\n\"; next}\n" printf "bl();sp(%d);next}\n",\ printf "NR==%s\t\t{removed(); next}\n" , n1 printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2 printf "NR==%s\t\t{changed();" , n1 printf "NR==%s,NR==%s\t{changed();" , n1, n2 if (n > 1) printf "if (NR==%d)", final printf "sp(%d);", d2 - d1 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" } # Now generate the HTML for the new file printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n" printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n" printf "function changed() " printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n" printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n" printf "BEGIN\t\t{sp(1)}\n" printf "BEGIN\t\t{sp(%d)}\n",\ printf "NR==%s\t\t{", a[2] printf "bl();printf \"\\n\"; next}\n" printf "bl();sp(%d);next}\n",\ printf "NR==%s\t\t{new() ; next}\n" , n1 printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2 printf "NR==%s\t\t{changed();" , n1 printf "NR==%s,NR==%s\t{changed();" , n1, n2 if (n > 1) printf "if (NR==%d)", final printf "sp(%d);", d1 - d2 END { printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" } # Post-process the HTML files by running them back through $AWK # Now combine into a valid HTML file and side-by-side into a table print "</head><body id=\"SUNWwebrev\">" print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>" print "<table><tr valign=\"top\">" print "</pre></td><td><pre>" # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment> # 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: # NOTE: We rely on standard usage of $WDIR and $DIR. # Enable html files to access WDIR via a relative path. # Make the rhs/lhs files and output the frameset file. <script type="text/javascript" src="${RTOP}ancnav.js"></script> <body id="SUNWwebrev" onkeypress="keypress(event);"> <pre>$comments</pre><hr></hr> 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 print $close >> $WDIR/$DIR/$TNAME.lhs.html print $close >> $WDIR/$DIR/$TNAME.rhs.html print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html print "<title>$WNAME Framed-Sdiff " \ "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF <frameset cols="50%,50%"> <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame> <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame> <frame src="${RTOP}ancnav.html" scrolling="no" marginwidth="0" marginheight="0" name="nav"></frame> Alas 'frames' webrev requires that your browser supports frames and has the feature enabled. # Merge codereview output files to a single conforming postscript file, by: # - removing all extraneous headers/trailers # - making the page numbers right # - removing pages devoid of contents which confuse some cat > /tmp/$$.crmerge.pl << \EOF print scalar(<>); # %!PS-Adobe--- print "%%Orientation: Landscape\n"; next if (/^%%Pages:\s*\d+/); if ($pno == 0 || $page =~ /\)S/) { # Header or single page containing text print "%%Page: ? $pno\n" if ($pno > 0); # Skip from %%Trailer of one document to Endprolog $doprint = 0 if (/^%%Trailer/); $page .= $_ if ($doprint); print "%%Page: ? $pno\n"; print "%%Trailer\n%%Pages: $pno\n"; # input_cmd | insert_anchors | output_cmd # Flag blocks of difference with sequentially numbered invisible # anchors. These are used to drive the frames version of the # NOTE: Anchor zero flags the top of the file irrespective of changes, # an additional anchor is also appended to flag the bottom. # The script detects changed lines as any line that has a "<span # class=" string embedded (unchanged lines have no class set and are # not part of a <span>. Blank lines (without a sequence number) # are also detected since they flag lines that have been inserted or printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++; NF == 0 || /^<span class=/ { printf "<b style=\"font-size: large; color: red\">"; for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n"; printf "<form name=\"eof\">"; printf "<input name=\"value\" value=\"%d\" " \ "type=\"hidden\"></input>", anc - 1; # Print a relative return path from $1 to $2. For example if # this function would print "../../../../". # In the event that $1 is not in $2 a warning is printed to stderr, # and $2 is returned-- the result of this is that the resulting webrev typeset cur="${1##$2?(/)}" # If the first path was specified absolutely, and it does # not start with the second path, it's an error. if [[ "$cur" = "/${1#/}" ]]; then print -u2 "\nWARNING: relative_dir: \"$1\" not relative " print -u2 "to \"$2\". Check input paths. Framed webrev " print -u2 "will not be relocatable!" # This is kind of ugly. The sed script will do the following: # 1. Strip off a leading "." or "./": this is important to get # the correct arcnav links for files in $WDIR. # 2. Strip off a trailing "/": this is not strictly necessary, # but is kind of nice, since it doesn't end up in "//" at # the end of a relative path. # 3. Replace all remaining sequences of non-"/" with "..": the # assumption here is that each dirname represents another # level of relative separation. # 4. Append a trailing "/" only for non-empty paths: this way # the caller doesn't need to duplicate this logic, and does # not end up using $RTOP/file for files in $WDIR. # Emit javascript for frame navigation // on each side, so we pluck from the left. // this must be: val >= maxval myInt=setInterval("scrollByPix()",10); function handlePress(b) { scrollToAnc(getAncValue() - 1); scrollToAnc(getAncValue() + 1); function handleRelease(b) { } else if (keychar == "j" || keychar == " ") { function ValidateDiffNum(){ # Output anchor navigation file for framed sdiffs. function frame_navigation print "$HTML<head>$STDHEAD" <title>Anchor Navigation</title> <meta http-equiv="Content-Type" content="text/html"> div.button td { padding-left: 5px; padding-right: 5px; background-color: #eee; text-align: center; border: 1px #444 outset; cursor: pointer; } onkeypress="keypress(event);"> <noscript lang="javascript"> <p><big>Framed Navigation controls require Javascript</big><br></br> Either this browser is incompatable or javascript is not enabled</p> <table width="100%" border="0" align="center"> <td valign="middle" width="25%">Diff navigation: Use 'j' and 'k' for next and previous diffs; or use buttons <td align="center" valign="top" width="50%"> <table border="0" align="center"> <a onMouseDown="handlePress(1);return true;" onMouseUp="handleRelease(1);return true;" onMouseOut="handleRelease(1);return true;" title="Go to Beginning Of file">BOF</a></td> <a onMouseDown="handlePress(3);return true;" onMouseUp="handleRelease(3);return true;" onMouseOut="handleRelease(3);return true;" title="Scroll Up: Press and Hold to accelerate" onClick="return false;">Scroll Up</a></td> <a onMouseDown="handlePress(2);return true;" onMouseUp="handleRelease(2);return true;" onMouseOut="handleRelease(2);return true;" title="Go to previous Diff" onClick="return false;">Prev Diff</a> <a onMouseDown="handlePress(6);return true;" onMouseUp="handleRelease(6);return true;" onMouseOut="handleRelease(6);return true;" title="Go to End Of File">EOF</a></td> <a onMouseDown="handlePress(4);return true;" onMouseUp="handleRelease(4);return true;" onMouseOut="handleRelease(4);return true;" title="Scroll Down: Press and Hold to accelerate" onClick="return false;">Scroll Down</a></td> <a onMouseDown="handlePress(5);return true;" onMouseUp="handleRelease(5);return true;" onMouseOut="handleRelease(5);return true;" onClick="return false;">Next Diff</a></td> <th valign="middle" width="25%"> <form action="" name="diff" onsubmit="return ValidateDiffNum();"> <input name="display" value="BOF" size="8" type="text"></input> <input name="real" value="0" size="8" type="hidden"></input> # diff_to_html <filename> <filepath> { U | C } <comment> # Processes the output of diff to produce an HTML file representing either # context or unified diffs. print "$HTML<head>$STDHEAD" print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>" if [[ $DIFFTYPE == "U" ]]; then <a class="print" href="javascript:print()">Print this page</a> /^-------/ { printf "<center><h1>%s</h1></center>\n", $0; next } /^\@\@.*\@\@$/ { printf "</pre><hr></hr><pre>\n"; printf "<span class=\"newmarker\">%s</span>\n", $0; /^\*\*\*/ { printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0; /^---/ { printf "<span class=\"newmarker\">%s</span>\n", $0; /^\+/ {printf "<span class=\"new\">%s</span>\n", $0; next} /^!/ {printf "<span class=\"changed\">%s</span>\n", $0; next} /^-/ {printf "<span class=\"removed\">%s</span>\n", $0; next} print "</pre></body></html>\n" # source_to_html { new | old } <filename> # Process a plain vanilla source file to transform it into an HTML file. print "$HTML<head>$STDHEAD" print "<title>$WNAME $WHICH $TNAME</title>" print "<body id=\"SUNWwebrev\">" print "</pre></body></html>" # comments_from_teamware {text|html} parent-file child-file # newest delta from the parent, get all deltas from the child starting # with that delta, and then get all info starting with the second oldest # delta in that list (the first delta unique to the child). # This code adapted from Bill Shannon's "spc" script if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then /^COMMENTS:/ {p=1; continue} /^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; } {if (p==0) continue; print $0 }' sid1=${sids[$((N-2))]} # Gets 2nd to last sid if [[ $fmt == "text" ]]; then # comments_from_wx {text|html} filepath # Given the pathname of a file, find its location in a "wx" active # file list and print the following comment. Output is either text or # HTML; if the latter, embedded bugids (sequence of 5 or more digits) # This is also used with Mercurial and the file list provided by hg-active. do getline ; while (NF > 0) while (NF > 0) { print ; getline } comm="*** NO COMMENTS ***" if [[ $fmt == "text" ]]; then # getcomments {text|html} filepath parentpath # Fetch the comments depending on what SCM mode we're in. # Mercurial support uses a file list in wx format, so this # will be used there, too # printCI <total-changed> <inserted> <deleted> <modified> <unchanged> # Print out Code Inspection figures similar to sccs-prt(1) format. printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \ # difflines <oldfile> <newfile> # Calculate and emit number of added, removed, modified and unchanged lines, # and total lines changed, the sum of added + removed + modified. # Change range of lines: N,Nc n=split(substr($1,1,length($1)-1), counts, ","); # 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines. # following would be 5 - 3 = 2! Hence +1 for correction. r=(counts[2]-counts[1])+1; # Now count replacement lines: each represents a change instead # of a delete, so increment c and decrement r. while (getline != /^\.$/) { # If there were more replacement lines than original lines, # then r will be negative; in this case there are no deletions, # but there are r changes that should be counted as adds, and # since r is negative, subtract it from a and add it to c. # If there were more original lines than replacement lines, then # r will be positive; in this case, increment d by that much. # The first line is a replacement; any more are additions. while (getline != /^\.$/) a++; # Add lines: both Na and N,Na while (getline != /^\.$/) a++; # Delete range of lines: N,Nd n=split(substr($1,1,length($1)-1), counts, ","); # 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines. # following would be 5 - 3 = 2! Hence +1 for correction. r=(counts[2]-counts[1])+1; # Delete line: Nd. For example 10d says line 10 is deleted. # Finish off - print results printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n", (c+d+a), c, d, a, error); # End of $AWK, Check to see if any trouble occurred. if (( $? > 0 || err > 0 )); then print "Unexpected Error occurred reading" \ "\`diff -e $1 $2\`: \$?=$?, err=" $err # Calculate unchanged lines print "<span class=\"lineschanged\">" # Sets up webrev to source its information from a wx-formatted file. # Sets the global 'wxfile' variable. # If the wx file pathname is relative then make it absolute # because the webrev does a "cd" later on. if (NF == 0) { c = -c; continue } # flist_from_teamware [ <args-to-putback-n> ] # Generate the file list by extracting file names from a putback -n. Some # names may come from the "update/create" messages and others from the # "currently checked out" warning. Renames are detected here too. Extract # values for CODEMGR_WS and CODEMGR_PARENT from the output of the putback # -n as well, but remove them if they are already defined. "valid teamware workspace" print " File list from: 'putback -n $parent_args $*' ... \c" /^update:|^create:/ {print $2} /^Parent workspace:/ {printf("CODEMGR_PARENT=%s\n",$3)} /^Child workspace:/ {printf("CODEMGR_WS=%s\n",$3)} /^The following files are currently checked out/ {p = 1; continue} $1 == "to:" {print $2, old} # Call hg-active to get the active list output in the wx active list format # Call hg-active to get a wx-style active list, and hand it off to print " File list from: hg-active -p $parent ...\c" print # Blank line for the \c above print -u2 "Error: hg-active tool not found. Exiting" # flist_from_wx prints the Done, so we don't have to. # Generate the file list by extracting file names from svn status. print -u2 " File list from: svn status ... \c" # Use "eval" to set env variables that are listed in the file # list. Then copy those into our local versions of those # variables if they have not been set already. # Check to see if CODEMGR_PARENT is set in the flist file. if (@stat = stat($ARGV[0])) { # If the child's version doesn't exist then # The following two sections propagate file permissions the # same way SCCS does. If the file is already under version # control, always use permissions from the SCCS/s.file. If # the file is not under SCCS control, use permissions from the # working copy. In all cases, the file copied to the webrev # is set to read only, and group/other permissions are set to # match those of the file owner. This way, even if the file # is currently checked out, the webrev will display the final # permissions that would result after check in. # Snag new version of file. # Get the parent's version of the file. First see whether the # child's version is checked out and get the parent's version # with keywords expanded or unexpanded as appropriate. # Parent is not a real workspace, but just a raw # directory tree - use the file that's there as # Get old file mode, from the parent revision manifest entry. # Mercurial only stores a "file is executable" flag, but the # manifest will display an octal mode "644" or "755". if [[ "$PDIR" == "." ]]; then # match the exact filename, and return only the permission digits # Get new file mode, directly from the filesystem. # Normalize the mode to match Mercurial's behavior. # new version of the file. # parent's version of the file # Note that we get this from the last version common to both # ourselves and the parent. References are via $CWS since we have no # guarantee that the parent workspace is reachable via the filesystem. # Snag new version of file. # Get the parent's version of the file. if [[ $stat != "A" ]]; then # Snag new version of file. # Snag the parent's version of the file. print "*** Error: file not in parent or child" print 'Usage:\twebrev [common-options] webrev [common-options] ( <file> | - ) webrev [common-options] -w <wx file> -C <filename>: Use <filename> for the information tracking configuration. -i <filename>: Include <filename> in the index.html file. -I <filename>: Use <filename> for the information tracking registry. -n: do not generate the webrev (useful with -U) -O: Print bugids/arc cases suitable for OpenSolaris. -o <outdir>: Output webrev to specified directory. -p <compare-against>: Use specified parent wkspc or basis for comparison -t <remote_target>: Specify remote destination for webrev upload -U: upload the webrev to remote destination -w <wxfile>: Use specified wx active file. WDIR: Control the output directory. WEBREV_TRASH_DIR: Set directory for webrev delete. TeamWare: webrev [common-options] -l [arguments to 'putback'] CODEMGR_WS: Workspace location. CODEMGR_PARENT: Parent workspace location. # Main program starts here trap "rm -f /tmp/$$.* ; exit" 0 1 2 3 15 # set name of trash directory for remote webrev deletion if [[ ! -x $PERL ]]; then print -u2 "Error: No perl interpreter found. Exiting." print -u2 "Error: Could not find which_scm. Exiting." # These aren't fatal, but we want to note them to the user. # We don't warn on the absence of 'wx' until later when we've # determined that we actually need to try to invoke it. [[ ! -x $CODEREVIEW ]] && print -u2 "WARNING: codereview(1) not found." [[ ! -x $PS2PDF ]] && print -u2 "WARNING: ps2pdf(1) not found." [[ ! -x $WDIFF ]] && print -u2 "WARNING: wdiff not found." # Declare global total counters. # prefixes for upload targets while getopts "C:Di:I:lnNo:Op:t:Uw" opt # If -l has been specified, we need to abort further options # processing, because subsequent arguments are going to be # arguments to 'putback -n'. # Strip the trailing slash to correctly form remote target. print "it does not make sense to skip webrev generation" \ echo "remote target has to be used only for upload or delete" # For the invocation "webrev -n -U" with no other options, webrev will assume # that the webrev exists in ${CWS}/webrev, but will upload it using the name # $(basename ${CWS}). So we need to get CWS set before we skip any remaining # 1. CODEMGR_WS from the environment # 1. hg root from CODEMGR_WS environment variable # 2. hg root from directory of invocation # If we're in OpenSolaris mode, we enforce a minor policy: # help to make sure the reviewer doesn't accidentally publish print -u2 "OpenSolaris output not permitted with" \ # 1. CODEMGR_WS from environment # 2. Relative path from current directory to SVN repository root if [[ $line == "URL: "* ]]; then elif [[ $line == "Repository Root: "* ]]; then # If no SCM has been determined, take either the environment setting # setting for CODEMGR_WS, or the current directory if that wasn't set. # If the command line options indicate no webrev generation, either # explicitly (-n) or implicitly (-D but not -U), then there's a whole # ton of logic we can skip. # Instead of increasing indentation, we intentionally leave this loop # body open here, and exit via break from multiple points within. # Search for DO_EVERYTHING below to find the break points and closure. # DO_EVERYTHING: break point # If this manually set as the parent, and it appears to be an earlier webrev, # then note that fact and set the parent to the raw_files/new subdirectory. print -u2 "$1: no such file or not readable" # Before we go on to further consider -l and -w, work out which SCM we think print -u2 "Unable to determine SCM in use and file list not specified" print -u2 "See which_scm(1) for SCM detection information." print -u2 "Unsupported SCM in use ($SCM_MODE) and file list not specified" # If the -l flag is given instead of the name of a file list, # then generate the file list by extracting file names from a print -u2 -- "Error: -l option only applies to TeamWare" # If the -w is given then assume the file list is in Bonwick's "wx" # command format, i.e. pathname lines alternating with SCCS comment # lines with blank lines as separators. Use the SCCS comments later [[ -z $wxfile ]] && print -u2 "wx file not specified, and could not " \ "be auto-detected (check \$CODEMGR_WS)" && exit 1 print -u2 "$wxfile: no such file or not readable" print -u2 " File list from: wx 'active' file '$wxfile' ... \c" print -u2 " File list from: standard input" print -u2 "WARNING: unused arguments: $*" # Before we entered the DO_EVERYTHING loop, we should have already set CWS # and CODEMGR_WS as needed. Here, we set the parent workspace. # 1) via -p command line option # 2) in the user environment # 4) automatically based on the workspace # For 1, codemgr_parent will already be set. Here's 2: # If we're in auto-detect mode and we haven't already gotten the file # list, then see if we can get it by probing for wx. print -u2 "WARNING: wx not found!" # We need to use wx list -w so that we get renamed files, etc. # but only if a wx active file exists-- otherwise wx will # hang asking us to initialize our wx information. print -u2 " File list from: 'wx list -w' ... \c" # If by hook or by crook we've gotten a file list by now (perhaps # from the command line), eval it to extract environment variables from # it: This is method 3 for finding the parent. # (4) If we still don't have a value for codemgr_parent, get it # Parent can either be specified with -p # Specified with CODEMGR_PARENT in the environment # or taken from hg's default path. # If the parent is a webrev, we want to do some things against # the natural workspace parent (file list, comments, etc) # If hg-active exists, then we run it. In the case of no explicit # flist given, we'll use it for our comments. In the case of an # explicit flist given we'll try to use it for comments for any # files mentioned in the flist. # If we have a file list now, pull out any variables set # therein. We do this now (rather than when we possibly use # hg-active to find comments) to avoid stomping specifications # in the user-specified flist. # Only call hg-active if we don't have a wx formatted file already # At this point we must have a wx flist either from hg-active, # or in general. Use it to try and find our parent revision, # If we still don't have a parent, we must have been given a # wx-style active list with no HG_PARENT specification, run # hg-active and pull an HG_PARENT out of it, ignore the rest. print -u2 "Error: Cannot discover parent revision" # We only will have a real parent workspace in the case one # was specified (be it an older webrev, or another checkout). print -u2 " Unknown type of SCM in use" # If the user didn't specify a -i option, check to see if there is a # webrev-info file in the workspace directory. # $INCLUDE_FILE may be a relative path, and the script alters # PWD, so we just stash a copy in /tmp. # DO_EVERYTHING: break point print "ERROR: Unable to read database registry file $REGFILE" print "ERROR: Unable to read database configuration file $cf" $SED -e '/^#/d' -e '/^[ ]*$/d' $cf | while read LINE; do # If an information tracking system is explicitly identified by prefix, # we want to disregard the specified priorities and resolve it accordingly. # To that end, we'll build a sed script to do each valid prefix in turn. # When an informational URL was provided, translate it to a # hyperlink. When omitted, simply use the prefix text. if [[ -z ${itsinfo["${p}_INFO"]} ]]; then itsinfo["${p}_INFO"]="<a href=\\\"${itsinfo["${p}_INFO"]}\\\">${p}</a>" # Assume that, for this invocation of webrev, all references # to this information tracking system should resolve through # If the caller specified -O, then always use EXTERNAL_URL. # Otherwise, look in the list of domains for a matching if [[ -n ${itsinfo["${p}_INTERNAL_URL_${d}"]} ]]; then itsinfo["${p}_URL"]="${itsinfo[${p}_INTERNAL_URL_${d}]}" if [[ -z ${itsinfo["${p}_URL"]} ]]; then itsinfo["${p}_URL"]="${itsinfo[${p}_EXTERNAL_URL]}" # Turn the destination URL into a hyperlink itsinfo["${p}_URL"]="<a href=\\\"${itsinfo[${p}_URL]}\\\">&</a>" # The character class below contains a literal tab s;${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g s;^${p};${itsinfo[${p}_INFO]}; # The previous loop took care of explicit specification. Now use # the configured priorities to attempt implicit translations. print "/^${itsinfo[${p}_REGEX]}[ ]/ { s;^${itsinfo[${p}_REGEX]};${itsinfo[${p}_URL]};g # Search for DO_EVERYTHING above for matching "for" statement # and explanation of this terminator. # Name of the webrev, derived from the workspace name or output directory; # in the future this could potentially be an option. # Make sure remote target is well formed for remote upload/delete. # If remote target is not specified, build it from scratch using # Check upload target prefix first. print "ERROR: invalid prefix of upload URI" \ # If destination specification is not in the form of # host_spec:remote_dir then assume it is just remote hostname # and append a colon and destination directory formed from # local webrev directory name. if [[ "${remote_target}" == *: ]]; then print "ERROR: badly formed upload URI" \ # Strip trailing slash. Each upload method will deal with directory # specification separately. # Option -D by itself (option -U not present) implies no webrev generation. # Do not generate the webrev, just upload it or delete it. if [ "${WDIR%%/*}" ]; then if [[ ! -d $WDIR ]]; then # Summarize what we're going to do. | $SED -e 's/\([0-9a-f]\{12\}\).*/\1/'` print "Compare against: $PWS" print " Output to: $WDIR" # Save the file list in the webrev dir # Clean up the file list: Remove comments, blank lines and env variables. # For Mercurial, create a cache of manifest entries. # Transform the FLIST into a temporary sed script that matches # relevant entries in the Mercurial manifest as follows: # 1) The script will be used against the parent revision manifest, # so for FLIST lines that have two filenames (a renamed file) # keep only the old name. # 2) Escape all forward slashes the filename. # 3) Change the filename into another sed command that matches # that file in "hg manifest -v" output: start of line, three # octal digits for file permissions, space, a file type flag # character, space, the filename, end of line. # 4) Eliminate any duplicate entries. (This can occur if a # file has been used as the source of an hg cp and it's # also been modified in the same changeset.) # Apply the generated script to the output of "hg manifest -v" # to get the relevant subset for this webrev. # First pass through the files: generate the per-file webrev HTML-files. # Normally, each line in the file list is just a pathname of a # file that has been modified or created in the child. A file # that is renamed in the child workspace has two names on the # line: new name followed by the old name. PDIR="." # File at root of workspace if [[ $DIR == $P ]]; then DIR="." # File at root of workspace if [[ "$DIR" == "$P" ]]; then DIR="." # File at root of workspace # Make the webrev mirror directory if necessary # We stash old and new files into parallel directories in $WDIR # and do our diffs there. This makes it possible to generate # clean looking diffs which don't have absolute paths present. # Keep the old PWD around, so we can safely switch back after # diff generation, such that build_old_new runs in a # consistent environment. if [[ $? == 0 && $rename == 1 ]]; then # If we have old and new versions of the file then run the appropriate # diffs. This is complicated by a couple of factors: # - renames must be handled specially: we emit a 'remove' # - new files and deleted files must be handled specially # - Solaris patch(1m) can't cope with file creation # (and hence renames) as of this writing. # - To make matters worse, gnu patch doesn't interpret the # output of Solaris diff properly when it comes to # adds and deletes. We need to do some "cleansing" # [to add a file] @@ -1,0 +X,Y @@ --> @@ -0,0 +X,Y @@ # [to del a file] @@ -X,Y +1,0 @@ --> @@ -X,Y +0,0 @@ # Tack the patch we just made onto the accumulated patch for the # renamed file: may also have differences # new file: count added lines # old file: count deleted lines # Now we generate the postscript for this file. We generate diffs # only in the event that there is delta, or the file is new (it seems # tree-killing to print out the contents of deleted files). print " Generating PDF: Skipped: no output available" print " Generating PDF: \c" print " Generating PDF: Skipped: missing 'ps2pdf' or 'codereview'" # If we're in OpenSolaris mode and there's a closed dir under $WDIR, # delete it - prevent accidental publishing of closed source # links to the source files and their diffs. # Save total changed lines for Code Inspection. exec 3<&1 # duplicate stdout to FD3. exec 1<&- # Close stdout. print "<body id=\"SUNWwebrev\">" print "<div class=\"summary\">" print "<h2>Code Review for $WNAME</h2>" # Get the preparer's name: # If the SCM detected is Mercurial, and the configuration property # ui.username is available, use that, but be careful to properly escape # angle brackets (HTML syntax characters) in the email address. # Otherwise, use the current userid in the form "John Doe (jdoe)", but # to maintain compatibility with passwd(4), we must support '&' substitutions. ($login, $pw, $uid, $gid, $quota, $cmt, $gcos) = getpwuid($<); $gcos =~ s/\&/ucfirst($login)/e; printf "%s (%s)\n", $gcos, $login; print "<tr><th>Workspace:</th><td>$CWS" print "<tr><th>Compare against:</th><td>" print "<tr><th>Summary of changes:</th><td>" print "<tr><th>Patch of changes:</th><td>" print "<tr><th>Printable review:</th><td>" print "<tr><th>Author comments:</th><td><div>" # Second pass through the files: generate the rest of the index file if [[ $DIR == $P ]]; then DIR="." # File at root of workspace # Avoid processing the same file twice. # It's possible for renamed files to # appear twice in the file list # If there's a diffs file, make diffs links print " ------ ------ ------" # If there's an old file, make the link # If there's an new file, make the link # For renamed files, clearly state whether or not they are modified print "<i>(copied and modified from $oldname)</i>" print "<i>(renamed and modified from $oldname)</i>" # If there's an old file, but no new file, the file was deleted print " <i>(deleted)</i>" print " <i>Closed source: omitted from" \ print "<blockquote><pre>" # Add additional comments comment print "<!-- Add comments to explain changes in $P here -->" # Include warnings for important file mode situations: # 1) New executable files # 2) Permission changes of any kind # 3) Existing executable files print "<span class=\"chmod\">" print "<p>new executable file: mode $new_mode</p>" print "<span class=\"chmod\">" print "<span class=\"chmod\">" print "<p>executable file: mode $new_mode</p>" print "<p style=\"font-size: small\">" print "This code review page was prepared using <b>$0</b>." print "OpenSolaris</a> project. The latest version may be obtained" exec 1<&3 # dup FD 3 to restore stdout. # If remote deletion was specified and fails do not continue.