<?php
/**
 * CVS Loginfo class - Parses the output when executed by loginfo.
 *
 * @author    Magnus Määttä <magnus@php.net>
 * @copyright    (C) Copyright 2004 Magnus Määttä.
 * @package    cvs
 * @version    $Id: class_cvs_loginfo.php,v 1.43 2005/02/01 20:32:30 magnus Exp $
 * @license    PHP Version 3
 */

/**
 * CVS_Loginfo class
 */
class CVS_Loginfo
{

    /**#@+
     * @access    protected
     */

    /**
     * List of applications checked for.
     *
     * @var        array
     */
    protected $apps = array();

    /**
     * List of applications available.
     *
     * @var        array
     */
    protected $have = array();

    /**
     * Commit data
     *
     * @var        array
     */
    protected $data = array();

    /**
     * Configuration
     *
     * @var        array
     */
    protected $config = array();

    /**#@-*/

    /**#@+
     * @access    public
     */

    /**
     * Class constructor.
     *
     * @param    string    $argv            argv passed to the main script.
     * @param    string    $stdin        Data from php://stdin
     * @param    int        $ppid            Parent process ID.
     * @param    bool        $new_format    If you want to use new format (1.12.x)
     * @return    void
     */
    public function __construct($argv, $stdin, $ppid, $new_format = false)
    {
        /* This early check is needed for newer versions. */
        if ($new_format) {
            if (strtolower(trim($argv[3])) == '- new directory') $this->bail("Adding a dir.");
            if (strtolower(trim($argv[3])) == '- imported') $this->bail("Importing.");
        }

        /* Debug ? */
        $this->config['debug'] = false;

        /* New format ? */
        $this->config['new_format'] = $new_format;

        /* Who to mail. */
        $this->config['mailto'] = $argv[1];

        /* Largest allowed diff, in bytes (150KB). */
        $this->config['diffsize'] = 153600;

        /* Size this large won't get sent at all. */
        $this->config['maxsize'] = 512000;

        /* Files to skip diff for. */
        $this->config['skipdiff'] = 'efs|bz2|tar|gz|tgz|gif|jpe|jpg|jpeg|pdf|png|exe|zip|class|jar|rar|dll|so';

        /* Ignore commit messages from users. */
        $this->config['ignored_users'] = array();

        /* Use sendmail to send the commit mail? */
        $this->config['use_sendmail'] = true;
        
        /* SMTP server */
        $this->config['smtp_server'] = "10.0.1.2";

        /* Mail domain */
        $this->config['domain'] = "novell.stoldgods.nu";
        
        /* Diff include type. 0 = plain, 1 = inline, 2 = attachment. */
        $this->config['diff_include'] = 1;

        /* Commitinfo log file. */
        $this->config['loginfo_lastdir'] = "/tmp/loginfo.lastdir.$ppid";

        /* cvsusers file. */
        $this->config['cvsroot'] = getenv('CVSROOT');
        if (is_file("{$this->config['cvsroot']}/CVSROOT/cvsusers")) {
            $this->config['cvsusers'] = $this->config['cvsroot'] .'/CVSROOT/cvsusers';
        } else {
            $this->config['cvsusers'] = false;
        }

        /* Initialize data['files']. */
        $this->data['files'] = array();

        /* All modules/dirs where files have been modified. */
        $this->data['modules'] = array();
        
        /* File status */
        $this->data['file_status'] = array('added' => '', 'modified' => '', 'removed' => 'log_message');

        /* Load saved data. */
        if (is_file($this->config['loginfo_lastdir'] .'.data')) {
            $data = unserialize(file_get_contents($this->config['loginfo_lastdir'] .'.data'));
            $this->data = $data;
            unlink($this->config['loginfo_lastdir'] .'.data');
        }

        /* The user who made the commit. */
        $this->data['user'] = $argv[2];

        /* Data from stdin. */
        $this->data['stdin'] = $stdin;

        /* Find module. */
        if ($new_format) {
            $tmp = explode("\n", $stdin);
            $tmp2 = trim(str_replace($this->config['cvsroot'], '', substr($tmp[0], strpos($tmp[0], '/'))), "\n/ ");

            $this->data['module'] = $tmp2;
            $this->data['modules'][] = $tmp2;
        } else {
            $tmp = explode(" ", $argv[3], 2);
            $this->data['module'] = $tmp[0];
            $this->data['modules'][] = $tmp[0];
        }

        /* Don't report new directories and imports. */
        if (!$new_format) {
            $tmp2 = $tmp[1];
            if (strtolower(trim($tmp2)) == '- new directory') $this->bail("Adding a dir.");
            if (strtolower(trim($tmp2)) == '- imported') $this->bail("Importing.");
        }

        /* Find files. */
        if ($new_format) {
            array_shift($argv);
            array_shift($argv);
            array_shift($argv);
            $this->findFiles($argv, true);
        } else {
            $this->findFiles($tmp[1]);
        }

        /* Get added, removed, modified files and tag and log message. */
        $this->findFileStatus();

        /* Get lastdir */
        $lastdir = file_get_contents($this->config['loginfo_lastdir']);

        /* Check if this is the last dir. */
        if (trim($this->data['module'], '/') != trim($lastdir, '/')) {
            $data = serialize($this->data);
            $fp = fopen($this->config['loginfo_lastdir'] .'.data', 'w');
            fwrite($fp, $data, strlen($data));
            fclose($fp);
            $this->bail("Not last dir.");
        }

        /* Host that made the commit. */
        $this->data['remote_host'] = getenv('REMOTE_HOST');

        /* Check for applications. */
        $this->haveApp('diffstat');
        $this->haveApp('cvs');
    }

    /**
     * Kill script with a message.
     *
     * @param    string    $msg
     * @return    void
     */
    public function bail($msg)
    {
        if ($this->config['debug']) {
            echo "\n=== Bail: $msg\n\n";
        }
        exit(0);
    }

    /**
     * Change a configuration option
     *
     * @param    string    $config        Configuration name
     * @param    mixed    $value        Configuration value
     * @return    void
     */
    public function changeConfig($config, $value)
    {
        $this->config[$config] = $value;
    }

    /**
     * Get a configuration value.
     *
     * @param    string    $config        Configuration name
     * @return    mixed    false is not found.
     */
    public function getConfig($config)
    {
        if (isset($this->config[$config])) {
            return $this->config[$config];
        } else {
            return false;
        }
    }

    /**
     * Get the info collected by this script.
     *
     * @return    array
     */
    public function getCommitInfo()
    {
        /* We're at the last dir - Remove temp file. */
        unlink($this->config['loginfo_lastdir']);

        /* Look up user information. */
        if ($this->config['cvsusers']) {
            sleep(1);
            $userinfo = new CVS_Cvsusers($this->config['cvsusers'], 0);
            $cvsuser = $userinfo->getInfo($this->data['user']);
            if (is_array($cvsuser)) {
                $fullname = $cvsuser['fullname'];
                $email = $cvsuser['email'];
            } else {
                $fullname = false;
                $email = false;
            }
        } else {
            $fullname = false;
            $email = false;
        }

        $data = $this->processData();
        $array = array(
            'tag'            => $data['tag'],
            'fullname'    => $fullname,
            'email'        => $email,
            'mailto'        => $this->config['mailto'],
            'user'        => $this->data['user'],
            'module'        => $this->data['module'],
            'diffstat'    => $data['diffstat'],
            'remote_host'    => $this->data['remote_host'],
            'log_message'    => $data['log_message'],
            'added_files'    => $data['added_files'],
            'added_diffs'    => $data['added_diffs'],
            'modified_files'    => $data['modified_files'],
            'modified_diffs'    => $data['modified_diffs'],
            'removed_files'    => $data['removed_files'],
        );
        
        return $array;
    }

    /**
     * Send commit mail to defined user.
     *
     * @return    bool
     */
    public function sendCommitMail()
    {
        /* Check if the commit is from a user that should be ignored. */
        if (is_array($this->config['ignored_users']) && in_array($this->data['user'], $this->config['ignored_users'])) {
            $this->bail("Username is in ignore list - not sending commit email.");
        }

        $info = $this->getCommitInfo();
        $com_body  = "{$info['user']}\t{$info['remote_host']}\t". date("r", time()) ."\n\n";
        $this->addLog(&$com_body, $info['added_files'], "Added Files:", $info['tag']);
        $this->addLog(&$com_body, $info['modified_files'], "Modified Files:", $info['tag']);
        $this->addLog(&$com_body, $info['removed_files'], "Removed Files:", $info['tag']);
        $com_body .= "  Log:\n";
        foreach (explode("\n", $info['log_message']) as $line) {
            $com_body .= "    $line\n";
        }

        if (strlen($info['diffstat']) > 10) {
            /* Only include diffstat if it's more then 10 chars. */
            $com_body .= "  Diffstat:\n";
            foreach (explode("\n", $info['diffstat']) as $line) {
                $com_body .= "   $line\n";
            }
        }

        $com_body .= "\n";
        /* Mime stuff. */
        $time = time();
        $boundary = "{$info['user']}-". md5(time());
        $mime_header = "MIME-Version: 1.0\r\n".
            "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n";
        $mime_body = "This is a MIME encoded message\n\n".
            "Content-Transfer-Encoding: 7bit\n".
            "--$boundary\n".
            "Content-Type: text/plain; charset=\"iso-8859-1\"\n\n".
            $com_body;

        $diffs_added = implode("\n\n", $info['added_diffs']);
        $diffs_modif = implode("\n\n", $info['modified_diffs']);

        if ((strlen($diffs_modif) + strlen($diffs_added)) > $this->config['maxsize']) {
            $body = $mime_body ."--$boundary\n";
            /* Only set this to mode two if we're using mode two. */
            if (!$this->config['diff_include'] || $this->config['diff_include'] == 1) {
                $diff_include = 1;
            } else {
                $diff_include = 2;
            }

            if (strlen($diffs_modif) < $this->config['maxsize']) {
                echo "Only sending diff for modified files.\n";
                $body .= $this->attachment($info['modified_diffs'], 'modified', $boundary, $info['user'], $diff_include, true);
            } elseif (strlen($diffs_added) < $this->config['maxsize']) {
                echo "Only sending diff for modified files.\n";
                $body .= $this->attachment($info['added_diffs'], 'added', $boundary, $info['user'], $diff_include, true);
            } else {
                echo "Diffs too large. Won't include diffs in mail.\n";
                $body .= $mime_body . "\n\nDiffs are too large. None included.\n--$boundary--\n";
            }
        } else {
            $body = $mime_body;
            if ($this->config['diff_include'] != 0) $body .= "--$boundary\n";
            $body .= $this->attachment($info['modified_diffs'],
                'modified', 
                $boundary,
                $info['user'],
                $this->config['diff_include']);
            $body .= $this->attachment($info['added_diffs'],
                'added',
                $boundary,
                $info['user'],
                $this->config['diff_include'],
                true);
        }

        /* Some headers. */
        $subject = "cvs: {$this->data['module']} /". $this->makeSubject($info['added_files']) .
            $this->makeSubject($info['modified_files']) . $this->makeSubject($info['removed_files']);
        $msgid = "Message-ID: <cvs.{$info['user']}.". time() ."@{$this->config['domain']}>\r\n";

        if (isset($info['fullname']) && isset($info['email'])) {
            $strings = preg_quote("äöåüûÿúùçøßÄÖÅÛÜÚÙÇØÆ");
            $string2 = "äöåüûÿúùçøßÄÖÅÛÜÚÙÇØÆ";
            $tmp2 = '';
            $tmp = explode(' ', $info['fullname']);
            for ($x = 0; $x < count($tmp); $x++) {
                if (preg_match("/[$strings]/", $tmp[$x])) {
                    $replace = array();
                    $with = array();
                    for ($i = 0; $i < strlen($string2); $i++) {
                        $replace[] = $string2{$i};
                        $with[] = '='. strtoupper(dechex(ord($string2{$i})));
                    }
                    $tmp2 .= ' =?iso-8859-15?q?' . str_replace($replace, $with, $tmp[$x]) . '?=';
                } else {
                    /* No special chars. */
                    $tmp2 .= " {$tmp[$x]}";
                }
            }
            $fullname = trim($tmp2, ' ');
            $from = "From: $fullname <{$info['email']}>\r\n";
            $mail_from = $info['email'];
        } else {
            $from = "From: cvs-commit@{$this->config['domain']}\r\n";
            $mail_from = 'cvs-commit@'. $this->config['domain'];
        }

        $to = "To: {$this->config['mailto']}\r\n";
        $cvs_module = "X-CVS-Module: {$this->data['module']}\r\n";
        $commit_host = "X-CVS-Commit-Host: {$this->data['remote_host']}\r\n";

        $header = $from . $to . $msgid . $cvs_module . $commit_host . $mime_header;

        /* Print a message to the user so he or she knows where the commit have been mailed. */
        echo "Mailing {$this->config['mailto']}\n";

        if ($this->config['use_sendmail']) {
            $status = mail($this->config['mailto'], $subject, $body, $header);
            if (!$status) {
                echo "Failed to send mail.\n";
                $this->bail("Failed to send mail!");
            }
        } else {
            $fp = fsockopen("tcp://{$this->config['smtp_server']}", 25, $errno, $errstr, 30);
            if (is_resource($fp)) {
                fwrite($fp, "HELO {$this->config['domain']}\r\n");
                fwrite($fp, "MAIL FROM: <$mail_from>\r\n");
                fwrite($fp, "RCPT TO: <{$this->config['mailto']}>\r\n");
                fwrite($fp, "DATA\r\n");
                fwrite($fp, "$header");
                fwrite($fp, "Subject: $subject\r\n");
                fwrite($fp, "$body\r\n.\r\nQUIT\r\n");
                fclose($fp);
            } else {
                echo "Failed to send mail!\n.";
                $this->bail("Error [$errno]: $errstr");
            }
        }

    }
    /**#@-*/

    /**#@+
     * @access    protected
     */

    /**
     * Make attachments of the diffs in array.
     *
     * @param    array    $array
     * @param    string    $diff_name    The type, modified, added.
     * @param    string    $boundary    The boundary string
     * @param    string    $user        Username
     * @param    int    $type        Type of attachment. 0 = Plain, 1 = inline, 2 = attachment
     * @param    bool    $last        If this is the last attachment.
     * @return    string
     */
    protected function attachment($array, $diff_name, $boundary, $user, $type = 1, $last = false)
    {
        $data = '';
        $i = 0;
        do {
            $key = key($array);
            if (!$key) break;
            $i++;
            if ($type == 1 || $type == 2) {
                $tmp = explode('/', $key);
                $tmp2 = $tmp[count($tmp) - 1];
                $name = "$tmp2-$user-$diff_name-". date("Ymd-Hms", time()) .".txt";
                $data .= "Content-Type: text/plain; name=\"$name\"\n";

                /* inline or attachment? */
                if ($type == 1) {
                    $data .= "Content-Disposition: inline; filename=\"$name\"\n\n";
                } else {
                    $data .= "Content-Disposition: attachment; filename=\"$name\"\n\n";
                }
            }

            $data .= "{$array[$key]}\n";
            if ((!($i == count($array) - 1) || !$last) && $type != 0) {
                $data .= "--$boundary\n";
            }
        } while (next($array));

        if (($i >= count($array) - 1) && $last) {
            $data .= "--$boundary--\n";
        }

        return $data;
    }

    /**
     * Add formated message to the body.
     *
     * @param    string    $body        The mail body by reference.
     * @param    array    $array        The array with files.
     * @param    string    $title        The title.
     * @param    string    $tag        The CVS tag (branch).
     * @return    void
     */
    protected function addLog(&$body, $array, $title, $tag = false)
    {
        sort($array);
        $last = '';
        if (count($array) > 0) {
            is_string($tag) ? $body .= "  $title\t\t\t$tag\n" : $body .= "  $title\n";
        } else {
            return;
        }

        foreach ($array as $file) {
            $tmp = explode('/', $file['file']);
            $tmp2 = '';

            /* Get module and path from name. */
            for ($i = 0; $i < count($tmp) - 1; $i++) {
                $tmp2 .= "/{$tmp[$i]}";
            }

            if ($last == $tmp2) {
                $body .= '    ';
                for ($i = 0; $i < strlen($tmp2); $i++) {
                    $body .= ' ';
                }
                $body .= "\t\t". $tmp[count($tmp) - 1] ."\n";
            } else {
                $last = $tmp2;
                $body .= "    $tmp2\t\t". $tmp[count($tmp) - 1] ."\n";
            }
        }
    }

    /**
     * Make a subject line of modified, etc files.
     *
     * @param    array    $array        An array with the files.
     * @return    string
     */
    protected function makeSubject($array)
    {
        $line = '';
        sort($array);
        $last = '';

        foreach ($array as $file) {
            $tmp = explode('/', $file['file']);
            $tmp2 = '';

            for ($i = 1; $i < (count($tmp) - 1); $i++) {
                $tmp2 .= "{$tmp[$i]}/";
            } 

            if ($last == $tmp2 && $tmp[count($tmp) - 1] != $tmp2) {
                $line .= $tmp[count($tmp) - 1] .' ';
            } else {
                $line .= "$tmp2 ". $tmp[count($tmp) - 1] .' ';
            }
        }

        return $line;
    }

    /**
     * Test if an app is available and save it to class config
     *
     * @param    string    $app        Application to search for.
     * @return    void
     */
    protected function haveApp($app)
    {
        $retval = 0;
        $retarr = NULL;
        exec("which $app", &$retarr, &$retval);
        if ($retval == 0) {
            $this->have[$app] = 1;
            $this->apps[$app] = $retarr[0];
        }
    }

    /**
     * Check if the input string is a CVS file and revision string.
     *
     * @param    string    $string
     * @return    bool
     */
    protected function isFileString($string)
    {
        $tmp = explode(",", $string);
        if (count($tmp) == 3) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Split a string into filename, old revision and new revision.
     *
     * @param    string    $string
     * @return    array
     */
    protected function infoExtract($string)
    {
        $tmp = explode(",", $string);
        $array = array('file' => $tmp[0], 'old_rev' => $tmp[1], 'new_rev' => $tmp[2]);
        return $array;
    }


    /**
     * Find files and revisions in a string.
     *
     * @param    string    $string
     * @param    bool        $newform        If we should use the new format.
     * @return    void
     */
    protected function findFiles($string, $newform = false)
    {
        if ($newform) {
            while (count($string)) {
                $file = array(
                    'file'        => $this->data['module'] .'/'. array_shift($string),
                    'old_rev'    => array_shift($string),
                    'new_rev'    => array_shift($string),
                );
                $tmp = $file['file'];
                $this->data['files'][$tmp] = $file;
            }
        } else {
            $array = explode(" ", $string);
            $count = count($array);
            $files = array();

            /* Find files and add them to $files array. */
            for ($i = 0; $i < $count; $i++) {
                if ($this->isFileString($array[$i])) {
                    /* File without space in the name */
                    $file = $this->infoExtract("{$this->data['module']}/{$array[$i]}");
                } else {
                    /*
                     * The file must have a space in it.
                     * Find where it ends.
                     */
                    for ($x = $i; $x < $count; $x++) {
                        /*
                         * Find next string that is a cvs commit string
                         */
                        if ($this->isFileString($array[$x])) {
                            /*
                             * Current entry in the array is the one with the revisions.
                             */
                            /* Reset tmp string. */
                            $tmp = '';
                            for ($y = $i; $y <= $x; $y++) {
                                /* Add everything from where the last file ended. */
                                $tmp .= " {$array[$y]}";
                            }
                            /* Trim added whitespaces in the beginning. */
                            $tmp = trim($tmp);
                            /* Skip forward since we're done here. */
                            $i = $y;
                            /* Add entry to files array. */
                            $file = $this->infoExtract("{$this->data['module']}/$tmp");
                        }
                    } /* End for-loop */
                } /* End if (is_file_string()) */

                /* Add found data*/
                $tmp = $file['file'];
                $this->data['files'][$tmp] = $file;

            }
        } /* End else($newform) */
    }

    /**
     * Find status of files.
     *
     * @return    void
     */
    protected function findFileStatus()
    {
        $modified = false;
        $added = false;
        $removed = false;
        $log_message = false;
        $status = false;
        $tag = false;

        /* Walk through data from stdin. */
        foreach (explode("\n", $this->data['stdin']) as $line) {
            $line = trim($line);
            if ($status == 'modified')        { $modified = $tmp; }
            elseif ($status == 'added')    { $added = $tmp; }
            elseif ($status == 'removed')    { $removed = $tmp; }
            elseif ($status == 'log')        { $log_message = $tmp; }

            if ($line == "Added Files:") {
                $status = 'added';
                $tmp = '';
            } elseif ($line == "Modified Files:") {
                $status = 'modified';
                $tmp = '';
            } elseif ($line == "Removed Files:") {
                $status = 'removed';
                $tmp = '';
            } elseif ($line == 'Log Message:') {
                $status = 'log';
                $tmp = '';
            } elseif (preg_match("/^Tag: [A-Za-z0-9_\-]+$/", $line)) {
                $tmp2 = explode(':', $line);
                $tag = trim($tmp2[1]);
                $tmp = '';
            } elseif ($status) {
                $tmp .= $line ."\n";
            }
        }

        $this->data['file_status']['added']         .= $added;
        $this->data['file_status']['modified']        .= $modified;
        $this->data['file_status']['removed']        .= $removed;
        $this->data['file_status']['log_message']     = $log_message;
        $this->data['file_status']['tag']             = $tag;
    }
    /**
     * Process the data collected.
     *
     * @return    array
     */
    protected function processData()
    {
        $modified_diff = array();
        $added_diff = array();
        $diffstat = false;
        $status = false;
        $tag = $this->data['file_status']['tag'];
        $tmp = false;

        $replace = array('  ', "\t", "\n");
        $with = array(' ', ' ', ' ');

        /* Remove spaces etc and explode. */
        $added_files = str_replace($replace, $with, trim($this->data['file_status']['added']));
        $modified_files = str_replace($replace, $with, trim($this->data['file_status']['modified']));
        $removed_files = str_replace($replace, $with, trim($this->data['file_status']['removed']));
        $log_message = str_replace("\r\n", "\n", $this->data['file_status']['log_message']);
        $added_array = array();
        $removed_array = array();
        $modified_array = array();

        foreach ($this->data['files'] as $file) {
            $filename = $file['file'];

            if ($file['old_rev'] == 'NULL' || $file['old_rev'] == 'NONE') {
                /* Added file. */
                $added_array[$filename] = $file;
                $exec_str = "{$this->apps['cvs']} -d:local:{$this->config['cvsroot']} -Qn checkout -p -r1.1 $filename";
                exec($exec_str, &$diff);
                $diff = implode("\n", $diff);

                /* Check that the diff isn't larger then allowed size. */
                if (strlen($diff) < $this->config['diffsize']) {
                    $added_diff[$filename] = "Index: {$file['file']}\n+++ {$file['file']}\n$diff";
                }
            } elseif ($file['new_rev'] == 'NULL' || $file['new_rev'] == 'NONE') {
                /* Removed file. */
                $removed_array[$filename] = $file;
            } elseif ($file['new_rev'] != 'NULL' && $file['old_rev'] != 'NULL' &&
                    $file['new_rev'] != 'NONE' && $file['old_rev'] != 'NONE') {
                /* Modified file. */
                $modified_array[$filename] = $file;
                $exec_str  = "{$this->apps['cvs']} -d:local:{$this->config['cvsroot']} -Qn rdiff -u -r ";
                $exec_str .= "{$file['old_rev']} -r {$file['new_rev']} $filename";
                
                exec($exec_str, &$diff);
                $diff = implode("\n", $diff);

                /* Check that the diff isn't larger then allowed size. */
                if (strlen($diff) < $this->config['diffsize']) {
                    $modified_diff[$filename] = $diff;
                }
            } else {
                echo "=======================================\n";
                echo "Unknown file!\n";
                echo "{$file['file']}\n";
                echo "=======================================\n";
            }
        }
        
        if ($this->have['diffstat'] && (!isset($this->config['diffstat']) || $this->config['diffstat'] == true)) {
            $fp = fopen($this->config['loginfo_lastdir'] .'.diff', 'w');
            if (is_resource($fp)) {
                foreach ($modified_diff as $diff) {
                    fwrite($fp, $diff, strlen($diff));
                }
                fclose($fp);

                /* Generate diffstat data. */
                exec("{$this->apps['diffstat']} {$this->config['loginfo_lastdir']}.diff", &$diffstat);
                $diffstat  = implode("\n", $diffstat);
                $diffstat .= ', '. count($added_array) ." files added\n";
            }

            /* Remove temp file. */
            if (is_file($this->config['loginfo_lastdir'] .'.diff')) {
                unlink($this->config['loginfo_lastdir'] .'.diff');
            }
        }

        return array(
            'added_diffs'        => $added_diff,
            'added_files'        => $added_array,
            'modified_diffs'    => $modified_diff,
            'modified_files'    => $modified_array,
            'removed_files'    => $removed_array,
            'diffstat'            => $diffstat,
            'log_message'        => $log_message,
            'tag'                    => $tag,
        );
    }
    /**#@-*/
}