<?php
// this PHP script is licensed under the GPL
// It uses the svn command to display information about
// svn repositories.
// WARNING: This script is provided AS IS. There may be errors,
// security leaks and other problems. Use it at your own risk.
//
// Change history:
// initial author: Martin Maurer <martinmaurer at gmx.at>
//                 published at 23.08.2003

// config area. EDIT THE FOLLOWING LINES
	$SVN="svn"; // path to your svn command
    $DIFFCMD="--diff-cmd diff"; // which diff command to use.

// this is an alternative file diff method.
// you need to set the correct path to the callmydiff script
// and make the script executable (it calls diff with special parameters.)
//    $DIFFCMD="--diff-cmd /scratch/svntest/viewsvn/callmydiff"; // adapt this line
//    $SPECIALDIFF=1; // use the non standard diff cmd. needs the callmydiff script.

// just fooled around a bit - you can set a background picture
//    if ($extended==1)
//    	$content.="<body background='someimg.jpg'>";

// some colors, maybe adapt these to your wishes
    $DIRCOLOR="lightgreen";
    $FILECOLOR="lightblue";
    $TEXTCOLOR="black";

// define your repositories here - you only have change these to define any
// number of repositories
    $REPOSITORIES[]="file:///scratch/svntest/SVN";
    $REPOSITORIES[]="file:///scratch/svntest/db/SVN";
    $REPOSITORIES[]="file:///scratch/svntest/onemonth/SVN";
    $REPOSITORIES[]="file:///scratch/svntest/3months/SVN";

// ----------------------------------------------------------------
// you shouldnt have to edit the following lines unless you want to enhance
// this script.

// set some common variables if not set so far.
    if (!isset($repnr) || ($repnr<0) || ($repnr=="") || ($repnr>sizeof($REPOSITORIES)))
        $repnr=0;
    if (!isset($revnr) || ($revnr<0) || ($revnr==""))
        $revnr=0;
    if (!isset($SPECIALDIFF))
        $SPECIALDIFF=0;

// select the correct repository
    $REPOSITORY=$REPOSITORIES[$repnr];

    // this function executes a command and returns stderr and stdout
    // as one string
	function mysystem($command) {
		if (!($p=popen("($command)2>&1","r")))
        {
     		return "";
       	}

		while (!feof($p)) {
			$line=fgets($p,1000);
			$out .= $line;
		}
		pclose($p);
		return $out;
	}

    // retieves information about a directory in the repository
    // calls 'svn list -v [-r rev ] ...'
    function getDir($repstr, $revision)
    {
        global $SVN;
        $revstr="";
        if ($revision!=0)
            $revstr="-r \"$revision\"";
        $command=$SVN." list -v $revstr \"".$repstr."\"";
        return mysystem($command);
    }

    // determines if a line (returned by getDir) represents a directory
    // or a file (by checking if it ends with '/')
    function isDir($str)
    {
        if (strrpos($str, "/")==strlen($str)-1)
            return 1;
        return 0;
    }

    // for sorting the directory view
    // first directories then files
    // each of the two sorted alphabetically
	function cmp ($a, $b) {
    	if (isDir($a) && !isDir($b)) return -1;
    	if (isDir($b) && !isDir($a)) return 1;
        return strcmp(getFileName($a), getFileName($b));
	}

    // gets the log information of a file
    // calls 'svn log [-r rev] ...
    function getLog($repstr, $dirrev)
    {
        global $SVN;
        if (($dirrev!="") && ($dirrev!=0))
	        $command=$SVN." log -r \"$dirrev:1\" ".$repstr;
        else
	        $command=$SVN." log ".$repstr;
        return mysystem($command);
    }

    // extracts revision out of a log string
    // needed so that i know which revisions of the file are available
    // and to create the download links
    function getRevision($revstr)
    {
        $i=4;
        while (($i<strlen($revstr)) && (ctype_digit($revstr[$i])))
        {
            $revision.=$revstr[$i];
            $i++;
        }
        return $revision;
    }

    // gets a file (at a specific revision) out of the repository
    // dont cache the data here - huge files would fill the apache process's
    // memory.
    function getFile($file, $revision)
    {
        global $SVN;
        $command=$SVN." cat "."-r \"".$revision."\" \"".$file."\"";
		if (!($p=popen("($command)2>&1","r")))
        {
			return "";
		}

		while (!feof($p)) {
			$line=fgets($p,1000);
			echo $line;
		}
		pclose($p);
    }

    // gets a file (at a specific revision) out of the repository
    // outputs the file readable on the browser.
    // dont cache the data here - huge files would fill the apache process's
    // memory.
    function getFileHtml($file, $revision)
    {
        global $SVN;
        $command=$SVN." cat "."-r \"".$revision."\" \"".$file."\"";
		if (!($p=popen("($command)2>&1","r")))
        {
			return "";
		}

        echo "<pre>";
		while (!feof($p)) {
			$line=fgets($p,1000);
			echo htmlentities($line);
		}
        echo "</pre>";
		pclose($p);
    }

    // what is the previous revision of a specific file starting from
    // revision $revision
    function getPrev($file, $revision)
    {
        global $SVN;
        $command=$SVN." log -r \"".($revision-1).":1\" \"".$file."\"";
        $log=mysystem($command);
    	$lines=explode("\n", $log);
        return getRevision($lines[1]);
    }

    // special diff output. (two columns for the two revisions,
    // with colours marking the changed lines)
    function formatmydiff($str, $rev1, $rev2)
    {
    	$lines=explode("::DIFF::", $str);
        $widthlimit="50%";

        $result="<table border=1 cellpadding=0 cellspacing=0 width=100%>\n";
        $result.="<tr><td width=$widthlimit>revision $rev1</td><td width=$widthlimit>revision $rev2</td></tr>\n";
	    for ($i=0;$i<count($lines);$i++) {
            $result.="<tr>";
            if (strpos($lines[$i], "OLD")!==false)
            {
                $result.="<td width=$widthlimit bgcolor='green'>".nl2br(htmlentities(substr($lines[$i],5)))."</td><td width=$widthlimit></td>";
            }
            else if (strpos($lines[$i], "NEW")!==false)
            {
                $result.="<td width=$widthlimit></td><td width=$widthlimit bgcolor='green'>".nl2br(htmlentities(substr($lines[$i],5)))."</td>";
            }
            else
            {
                $pos=strpos($lines[$i], "<-->");
                if ($pos!==false)
                {
                    $left=nl2br(htmlentities(substr($lines[$i], 9, $pos-9)));
                    $right=nl2br(htmlentities(substr($lines[$i], $pos+5)));
                	$result.="<td width=$widthlimit bgcolor='yellow'>$left</td><td width=$widthlimit bgcolor='yellow'>$right</td>";
                }
//                else $result.="Error in parse".$lines[$i];
            }

            $result.="</tr>\n";
        }
        $result.="</table>\n";
        return $result;
    }

    // get the difference between to files
    // if the difference is bigger than the amount of memory for the
    // php script permits this will fail
    function getDiff($file, $revision)
    {
        global $SVN;
        global $DIFFCMD;
        global $SPECIALDIFF;

        $previous=getPrev($file, $revision);
        if ($previous!="")
        {
            // i had to use --diff-cmd diff here, as comparing two 30MB files seems to
            // be too hard for the internal diff algo. (as of subversion 0.26)
	        $command="cd /tmp && ".$SVN." diff $DIFFCMD -r \"$previous:$revision\" \"".$file."\"";
            if ($SPECIALDIFF==0)
	    	    return "<h1>Difference of $file between $previous and $revision: </h1>\n<pre>".(htmlentities(mysystem($command)))."</pre>\n";
            else
	    	    return "<h1>Difference of $file between $previous and $revision: </h1>\n".(formatmydiff(mysystem($command), $previous, $revision));
        }
        else
            return "No previous revision found<br>\n";
    }

    // extracts filename of "svn list -v"
    // FIXME: improve this function
    function getFileName($liststr)
    {
        return trim(substr($liststr, 40));
    }

    // extracts revision of "svn list -v"
    // FIXME: improve this function
    function getFileRevision($liststr)
    {
        return trim(substr($liststr, 2, 7));
    }

    // extracts size of "svn list -v"
    // FIXME: improve this function
    function getFileSize($liststr)
    {
        return trim(substr($liststr, 19, 8));
    }

    // extracts date of "svn list -v"
    // FIXME: improve this function
    function getFileDate($liststr)
    {
        return trim(substr($liststr, 27, 13));
    }

    if (isset($diff)) // we requested a diff of a file+revision (to the previous revision)
    {
        $content.=getDiff($REPOSITORY."/".$path, $diff);
        $content.="<br><a href='javascript:history.back()'>Back</a>";
    }
    else if (isset($get)) // retrieve file
    {
        getFile($REPOSITORY."/".$path, $get);
        $content="";
    }
    else if (isset($gethtml)) // retrieve file readable (converts htmlentities)
    {
        getFileHtml($REPOSITORY."/".$path, $gethtml);
        $content="";
    }
    else if (isset($detail)) // get log of a file
    {
        // beautify the listing via style sheet
        $content.="<style type='text/css'>
					<!--
					 a:link { text-decoration:none; font-weight:bold; color:#0000E0; }
					 a:visited { text-decoration:none; font-weight:bold; color:#0000E0; }
					 a:hover { text-decoration:none; font-weight:bold; background-color:#FFFF00; }
					 a:active { text-decoration:none; font-weight:bold; background-color:#CCFFFF; }
					-->
					</style>
        ";
        $content.="<h1>File ".$path."<br></h1>\n";
        $log=getLog($REPOSITORY."/".$path, $revnr);
        $content.="<table border cellspacing=0 cellpadding=0 width=100%>";
    	$lines=explode("\n", $log);
        $content.="<tr><td>";
	    for ($i=0;$i<count($lines);$i++) {
            if ($lines[$i]!="------------------------------------------------------------------------")
            {
                if (strpos($lines[$i], "rev ")!==false) // this is the line with rev xxxx:
                {
                    $rev=getRevision($lines[$i]);
    
                    $content.="<tr><td bgcolor='lightblue'>&nbsp;<a href='$PHP_SELF?path=$path&get=$rev".$revstr."&repnr=$repnr'>".$lines[$i]."</a>&nbsp;</td><td><a href='$PHP_SELF?path=$path&diff=$rev".$revstr."&repnr=$repnr'>&nbsp;Diff to previous version&nbsp;</td><td><a href='$PHP_SELF?path=$path&gethtml=$rev&repnr=$repnr".$revstr."'>Show File</a></td></tr><tr><td colspan=3 bgcolor='lightgrey'>\n";
                }
                else
                    $content.=$lines[$i]."<br>\n";
            }
            else
            {
            	$content.="</td></tr>\n";
        	}
        }
        $content.="</td></tr></table>";
        // the back button is easy and efficient to go back
        $content.="<br><a href='javascript:history.back()'>Back</a>";
    }
    else
    {
        // beautify the listing via style sheet
        $content.="<style type='text/css'>
					<!--
					 a:link { text-decoration:none; font-weight:bold; color:#0000E0; }
					 a:visited { text-decoration:none; font-weight:bold; color:#0000E0; }
					 a:hover { text-decoration:none; font-weight:bold; background-color:#FFFF00; }
					 a:active { text-decoration:none; font-weight:bold; background-color:#CCFFFF; }
                                         * {
                                           color: $TEXTCOLOR;
                                        }
					-->
					</style>
        ";

        // change repository and displayed revision here
        $content.="<form>Repository: <select name=repnr>";
        for ($i=0;$i<sizeof($REPOSITORIES);$i++)
            $content.="<option value='$i'".(($i==$repnr)?" selected":"").">".$REPOSITORIES[$i];
        $content.="</select><br>";
        $content.="Revision number: <input type=text name=revnr value='".(($revnr!=0)?$revnr:"")."'><br>";
        $content.="<input type=submit></form>";

        $content.="<h1>Files in directory ".$path."<br></h1>\n";
	    $files=getDir($REPOSITORY."/".$path, $revnr);

    	$lines=explode("\n", $files);

		usort ($lines, "cmp");

        $content.="<table border cellpadding=0 cellspacing=0 width=100%>";
        $content.="<tr><td>Filename</td><td width=10>Revision</td><td width=100>Size in Bytes</td><td width=10>Date</td></tr>";
	    for ($i=0;$i<count($lines);$i++) {
            $content.="<tr>";
    	    if (isDir($lines[$i])) //directories
	    	    $content.="<td bgcolor='$DIRCOLOR'><a href='$PHP_SELF?path=$path".getFileName($lines[$i])."&revnr=$revnr&repnr=$repnr'>".getFileName($lines[$i])."</a></td><td  width=10 align=right>".getFileRevision($lines[$i])."</td><td width=10 align=right>".getFileSize($lines[$i])."</td><td width=100 align=right>".getFileDate($lines[$i])."</td>";
	        else // files
		        $content.="<td bgcolor='$FILECOLOR'><a href='$PHP_SELF?path=$path".getFileName($lines[$i])."&detail=1&revnr=$revnr&repnr=$repnr'>".getFileName($lines[$i])."</a></td><td width=10 align=right>".getFileRevision($lines[$i])."</td><td width=10 align=right>".getFileSize($lines[$i])."</td><td width=100 align=right>".getFileDate($lines[$i])."</td>";
            $content.="</tr>";
    	}
        $content.="</table>";
        $content.="<br><a href='javascript:history.back()'>Back</a>";
    }
    echo $content;
?>
