| <?php | |
| /******************************************************************************* | |
| * Copyright (c) 2011, 2012 Eclipse Foundation and others. | |
| * All rights reserved. This program and the accompanying materials | |
| * are made available under the terms of the Eclipse Public License v1.0 | |
| * which accompanies this distribution, and is available at | |
| * http://www.eclipse.org/legal/epl-v10.html | |
| * | |
| * Contributors: | |
| * Wayne Beaton (Eclipse Foundation)- initial API and implementation | |
| *******************************************************************************/ | |
| /* | |
| * This script assumes that it is being included by another script. We | |
| * assume that the $App variable has already been defined. | |
| */ | |
| require_once(dirname(__FILE__) ."/Project.class.php"); | |
| require_once(dirname(__FILE__) ."/Committer.class.php"); | |
| require_once(dirname(__FILE__) . "/debug.php"); | |
| require_once(dirname(__FILE__) . "/common.php"); | |
| trace_file_info(__FILE__); | |
| // require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php"); | |
| // $App = new App(); | |
| // $log = new IPLog(array(getProject('eclipse.orion'))); | |
| // print_r($log->getContributions()); | |
| // | |
| //echo get_trace_html(); | |
| class IPLog { | |
| var $projects; | |
| var $committers = array(); | |
| var $contributors = null; | |
| var $trace; | |
| function __construct($projects) { | |
| $this->projects = $projects; | |
| $this->trace = trace("IPLog"); | |
| } | |
| function getContributions() { | |
| if ($this->contributors === null) { | |
| $this->gatherGitContributions($this->projects); | |
| $this->gatherBugzillaContributions($this->projects); | |
| } | |
| uasort($this->contributors, 'contributors_sort_by_name'); | |
| return $this->contributors; | |
| } | |
| /** | |
| * Gather any contributions provided through Git for $project. Contributions | |
| * are added to the provided array. This function makes a call to the dash | |
| * server for each Git repository listed by the project. The script running | |
| * on the server provides the data in CSV form. | |
| * | |
| * @internal | |
| * @param Project $project | |
| * @param Contribution[] $contributions | |
| */ | |
| function gatherGitContributions(&$projects) { | |
| global $App; | |
| $repositories = array(); | |
| foreach($projects as $project) { | |
| foreach($project->getSourceRepositories() as $repository) { | |
| if ($repository->getType() != 'git') continue; | |
| $path = preg_replace('/^\/gitroot\//', '/home/data/git/', $repository->getPath()); | |
| $repositories[$path] = $repository; | |
| } | |
| } | |
| /* | |
| * Select records from the repository. | |
| * | |
| * Note that we only want those records that could possibly be contributed | |
| * by a non-committer. Candidate records either have an author that is | |
| * different from the committer, or--if they are from projects that use | |
| * Gerrit--have a changeId. Note that these records are not necessarily | |
| * from non-committers, but they could be. | |
| */ | |
| $sql = " | |
| select | |
| repo, ref, committer, author, authorName, size, comment, commitDate as date | |
| from gitlogcache | |
| where | |
| repo in ('" . implode("','", array_keys($repositories)) . "') | |
| and author not in ('cvs', 'cvs2git') | |
| and authorName not in ('cvs2svn', 'cvs2git migration') | |
| and (changeId is not null or (author != committer))"; | |
| $result = $App->dashboard_sql($sql); | |
| while ($row = mysql_fetch_assoc($result)) { | |
| $repo = $row['repo']; | |
| $ref = $row['ref']; | |
| $committer = strtolower($row['committer']); | |
| $author = strtolower($row['author']); | |
| $authorName = $row['authorName']; | |
| $size = $row['size'] . ' lines'; | |
| $comment = $this->getAbbreviatedGitComment($row['comment']); | |
| $date = $row['date']; | |
| if ($this->isProjectCommitter($projects, $author, $date)) continue; | |
| $repository = $repositories[$repo]; | |
| $path = $repository->getLink(); | |
| $link = "$path/commit?id=$ref"; | |
| $this->addContribution($author, $authorName, $ref, substr($ref,0,6), $link, $size, $comment); | |
| } | |
| } | |
| function getAbbreviatedGitComment($string) { | |
| $lines = split("\n", $string); | |
| return $lines[0]; | |
| } | |
| /** | |
| * @internal | |
| * @param unknown_type $author | |
| * @param unknown_type $name | |
| * @param unknown_type $ref | |
| * @param unknown_type $id | |
| * @param unknown_type $link | |
| * @param unknown_type $size | |
| * @param unknown_type $comment | |
| */ | |
| function addContribution($author, $name, $ref, $id, $link, $size, $comment) { | |
| $contributor = $this->addContributor($author, $name); | |
| if (isset($contributor->contributions[$ref])) | |
| $contributor->contributions[$ref]->size += $size; | |
| else | |
| $contributor->contributions[$ref] = new Contribution($id, $link, $author, $size, $comment); | |
| } | |
| function addContributor($email, $name) { | |
| if (!isset($this->contributors[$email])) { | |
| $this->contributors[$email] = new Contributor($email, $name); | |
| } | |
| return $this->contributors[$email]; | |
| } | |
| /** | |
| * @internal | |
| * @param unknown_type $projects | |
| */ | |
| function gatherBugzillaContributions(&$projects) { | |
| foreach($projects as $project) { | |
| IPLogBugzillaBuilder::assemble($this, $project); | |
| } | |
| } | |
| function isProjectCommitter(&$projects, $author, $date) { | |
| foreach($projects as $project) { | |
| $committer = $this->findProjectCommitter($project, $author); | |
| if (!$committer) continue; | |
| if ($committer->isCommitter($project->getId(), $date)) return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Attempt to find a committer on a particular project. The value of | |
| * $author may be a committer id, or it could be an email address. | |
| * | |
| * @param Project $project | |
| * @param string $author | |
| * @return Committer|NULL | |
| */ | |
| function findProjectCommitter(&$project, $author) { | |
| if (!isset($this->committers[$project->getId()])) | |
| $this->committers[$project->getId()] = getCommittersForProject($project->getId(), false); | |
| foreach($this->committers[$project->getId()] as $committer) { | |
| if ($committer->id == $author) return $committer; | |
| if ($committer->hasEmailAddress($author)) return $committer; | |
| } | |
| return null; | |
| } | |
| function getSourceRepositories() { | |
| $repositories = array(); | |
| foreach($this->projects as $project) | |
| foreach($project->getSourceRepositories() as $repository) | |
| $repositories[] = $repository; | |
| return $repositories; | |
| } | |
| } | |
| class Contributor { | |
| var $name; | |
| var $email; | |
| var $contributions = array(); | |
| function __construct($email, $name) { | |
| $this->email = $email; | |
| $this->name = $name; | |
| } | |
| } | |
| class Contribution { | |
| var $ref; | |
| var $link; | |
| var $author; | |
| var $size; | |
| var $comments; | |
| function __construct($ref, $link, $author, $size, $comments) { | |
| $this->ref = $ref; | |
| $this->link = $link; | |
| $this->author = $author; | |
| $this->size = $size; | |
| $this->comments = $comments; | |
| } | |
| } | |
| function contributors_sort_by_name($a, $b) { | |
| return strcasecmp($a->name, $b->name); | |
| } | |
| /** | |
| * The IPLogBugzillaBuilder queries Bugzilla to populate the log. | |
| * | |
| * @internal | |
| */ | |
| class IPLogBugzillaBuilder { | |
| /* @var IPLog */ | |
| var $iplog; | |
| var $project; | |
| var $corrections = array(); | |
| static function assemble(&$iplog, &$project) { | |
| $builder = new self($iplog, $project); | |
| $trace = trace("Assemble Bugzilla records for " . $project->getName()); | |
| $builder->loadCorrections(); | |
| $builder->assembleAttachmentContributions($trace); | |
| $builder->assembleCommentContributions($trace); | |
| } | |
| function __construct(&$iplog, &$project) { | |
| $this->iplog = $iplog; | |
| $this->project = $project; | |
| $this->committers = array(); | |
| foreach(getCommittersForProject($project->getId()) as $committer) | |
| $this->committers[$committer->email] = $committer; | |
| } | |
| /** | |
| * We no longer support corrections, but there are a mess of them in the database already, | |
| * so we should honour what's there. At least for comments. | |
| * | |
| * @internal | |
| */ | |
| function loadCorrections() { | |
| global $App; | |
| $id = $this->project->getId(); | |
| $sql = "SELECT ItemType as type, Action as action, ItemID as id FROM IPLogCorrections where ProjectId = '$id'"; | |
| $result = $App->foundation_sql( $sql ); | |
| while($row = mysql_fetch_assoc($result)) { | |
| $this->corrections[$row['type']][$row['action']][$row['id']] = 'remove'; | |
| } | |
| } | |
| /** | |
| * Query the Bugzilla database for iplog+ flagged attachments in the project. | |
| * Assemble contributions from the results. | |
| * | |
| * Skip attachments made by committers. | |
| * | |
| * @internal | |
| * @param Tracer $trace | |
| */ | |
| function assembleAttachmentContributions($trace) { | |
| global $App; | |
| $product = $this->project->getBugzillaProduct(); | |
| $components = $this->project->getBugzillaComponents(); | |
| if ($components) | |
| $components = "'" . implode("','", $components) . "'"; | |
| $sql = " | |
| SELECT | |
| bugs.bug_id as bug_id, | |
| bugs.short_desc as title, | |
| products.name as product, | |
| components.name as component, | |
| attachments.attach_id as attach_id, | |
| attachments.description as description, | |
| attachments.isobsolete as isobsolete, | |
| attachments.ispatch as ispatch, | |
| attachments.creation_ts as creation, | |
| LENGTH(attach_data.thedata) AS size, | |
| profiles.login_name as contributor, | |
| profiles.realname as realname | |
| FROM | |
| bugs | |
| join products on (bugs.product_id = products.id) | |
| join components on (bugs.component_id = components.id) | |
| join attachments on (bugs.bug_id = attachments.bug_id and attachments.isobsolete = 0) | |
| join attach_data on (attachments.attach_id = attach_data.id) | |
| join profiles on (attachments.submitter_id = profiles.userid) | |
| join flags on (attachments.attach_id = flags.attach_id and flags.status = '+') | |
| join flagtypes on (flags.type_id = flagtypes.id and flagtypes.name = 'iplog') | |
| WHERE | |
| products.name = '$product' | |
| and bugs.bug_id not in (select bg.bug_id from bug_group_map as bg join groups as g on (bg.group_id=g.id and g.name = 'Security_Advisories')) | |
| and bugs.bug_status IN ( 'RESOLVED', 'VERIFIED', 'CLOSED' ) | |
| and bugs.resolution IN ( 'FIXED' )"; | |
| if ($components) | |
| $sql .= " and bugs.component_id in (SELECT id FROM components WHERE product_id=products.id and name in ($components))"; | |
| $trace->trace($sql); | |
| $result = $App->bugzilla_sql( $sql ); | |
| while( $row = mysql_fetch_assoc($result) ) { | |
| $bugId = $row['bug_id']; | |
| $author = $row['contributor']; | |
| $creation = $row['creation']; | |
| $attachId = $row['attach_id']; | |
| // if (isset($this->corrections['CONTRIB']['RMA'][$attachId])) continue; | |
| $trace->trace("Reviewing attachment by $author bug $bugId attachment $attachId"); | |
| if ($this->isCommitter($author, $creation)) continue; | |
| $name = $row['realname']; | |
| $ref = "$bugId#a$attachId"; | |
| $size = ($attachId == 104976 ? 1543319 : $row['size']) . ' bytes'; | |
| $link = "https://bugs.eclipse.org/$bugId"; | |
| $comment = $row['title'] . "\nAttachment $attachId: " . $row['description']; | |
| $trace->trace("Attachment contribution from $author bug $bugId attachment $attachId"); | |
| $this->iplog->addContribution($author, $name, $ref, $bugId, $link, $size, $comment); | |
| } | |
| } | |
| /** | |
| * Query the Bugzilla database for iplog+ flagged bugs in the project. | |
| * Assemble contributions from the comments in those bugs. | |
| * | |
| * Skip comments made by committers. | |
| * Skip comments indicated in the corrections table (Foundation database). | |
| * | |
| * @internal | |
| * @param Tracer $trace | |
| */ | |
| function assembleCommentContributions($trace) { | |
| global $App; | |
| $product = $this->project->getBugzillaProduct(); | |
| $components = $this->project->getBugzillaComponents(); | |
| if ($components) | |
| $components = "'" . implode("','", $components) . "'"; | |
| $sql = " | |
| SELECT | |
| bugs.bug_id as bug_id, | |
| bugs.short_desc as title, | |
| products.name as product, | |
| components.name as component, | |
| flagtypes.name as bugflag_name, | |
| flags.status as bugflag_value, | |
| longdescs.comment_id as comment_id, | |
| longdescs.bug_when as creation, | |
| LENGTH(longdescs.thetext) as size, | |
| profiles.login_name as contributor, | |
| profiles.realname as realname | |
| FROM | |
| bugs | |
| join products on (bugs.product_id = products.id) | |
| join components on (bugs.component_id = components.id) | |
| join flags on (bugs.bug_id = flags.bug_id and flags.attach_id is null) | |
| join flagtypes on (flags.type_id = flagtypes.id) | |
| join longdescs on (bugs.bug_id = longdescs.bug_id) | |
| join profiles on (profiles.userid = longdescs.who) | |
| WHERE | |
| products.name = '$product' | |
| and flagtypes.name = 'iplog' | |
| and flags.status = '+' | |
| and bugs.bug_id not in (select bg.bug_id from bug_group_map as bg join groups as g on (bg.group_id=g.id and g.name = 'Security_Advisories')) | |
| and bugs.bug_status IN ( 'RESOLVED', 'VERIFIED', 'CLOSED' ) | |
| and bugs.resolution IN ( 'FIXED' )"; | |
| if ($components) | |
| $sql .= " and bugs.component_id in (SELECT id FROM components WHERE product_id=products.id and name in ($components))"; | |
| $trace->trace($sql); | |
| $result = $App->bugzilla_sql( $sql ); | |
| $bugs = array(); | |
| while( $row = mysql_fetch_assoc($result) ) { | |
| $bugId = $row['bug_id']; | |
| $author = $row['contributor']; | |
| $creation = $row['creation']; | |
| $commentId = $row['comment_id']; | |
| $trace->trace("Reviewing comment by $author bug $bugId comment $commentId"); | |
| if (isset($this->corrections['CONTRIB']['RMC'][$commentId])) continue; | |
| if ($this->isCommitter($author, $creation)) continue; | |
| $name = $row['realname']; | |
| $ref = "$bugId#c"; | |
| $size = $row['size'] . ' bytes'; | |
| $link = "https://bugs.eclipse.org/$bugId"; | |
| $comment = $row['title'] . "\n" . 'Comment'; | |
| $trace->trace("Comment contribution from $author bug $bugId comment $commentId"); | |
| $this->iplog->addContribution($author, $name, $ref, $bugId, $link, $size, $comment); | |
| } | |
| } | |
| /** | |
| * Answers true if the author was/is a committer on the given date. | |
| * | |
| * @internal | |
| * @param string $author email address | |
| * @param string $when date in a strtotime-parseable format | |
| * @return boolean | |
| */ | |
| private function isCommitter($author, $when) { | |
| if (!isset($this->committers[$author])) return false; | |
| return $this->committers[$author]->isCommitter($this->project->getId(), $when); | |
| } | |
| } | |
| ?> |