| <?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 getLicenses(&$projectids) { |
| global $App; |
| |
| $sql = "SELECT distinct l.LicenseID as Id, l.Description as License FROM ProjectLicenses as p join (SYS_Licenses as l) on (p.LicenseId = l.LicenseId) where p.ProjectId IN ( '" . implode( "','", $projectids ) . "')"; |
| log_query($sql); |
| $result = $App->foundation_sql( $sql ); |
| //mysql_error_check(); |
| $licenses = array(); |
| while( $row = mysql_fetch_assoc($result) ) { |
| $id = $row['Id']; |
| $license = $row['License']; |
| $licenses[$id] = $license; |
| } |
| return $licenses; |
| } |
| |
| 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; |
| // } |
| // } |
| |
| $ids = array(); |
| foreach($projects as $project) { |
| $ids[] = "'" . $project->getId() . "'"; |
| } |
| $ids = join(',', $ids); |
| |
| /* |
| * 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')"; |
| |
| $sql = "select distinct |
| r.project as project, r.path as repo, c.ref as ref, a.name as authorName, a.email as author, |
| sum(f.added)+sum(f.removed) as size, c.date as date, c.comment |
| from GitRepo as r |
| join GitCommit as c on r.path=c.path |
| join GitCommitAuthor as a on c.path=a.path and c.ref=a.ref |
| join GitCommitFile as f on c.path=f.path and c.ref=f.ref |
| where project in ($ids) |
| and a.email not in ('cvs', 'cvs2git') |
| and a.name not in ('cvs2svn', 'cvs2git migration') |
| group by c.ref"; |
|
|
| $result = $App->dashboard_sql($sql);
|
| while ($row = mysql_fetch_assoc($result)) { |
| $project = $row['project']; |
| $repo = $row['repo'];
|
| $ref = $row['ref'];
|
| // $committer = strtolower($row['committer']); |
|
|
| $authorName = $row['authorName']; |
| $author = $row['author']; |
| |
| // HACK: When importing from CVS, some older projects |
| // ended up with many commits done by then-committers |
| // having just the committer id in both the author and |
| // author name fields. Some of the commits pre-date our |
| // record keeping and so do not have committer records |
| // in the database. |
| // FIXME: Fix the committer records in the database. |
| if ($authorName == $author) |
| if (preg_match('/^[\w]*$/', $author)) continue; |
| if (preg_match('/^[[:alnum:]]+$/', $author)) continue; |
|
|
| $author = strtolower($author);
|
| $size = $row['size'] . ' lines';
|
| $comment = $this->getAbbreviatedGitComment($row['comment']);
|
| $date = $row['date'];
|
| |
| if ($this->isProjectCommitter($projects, $author, $date)) continue; |
| |
| $link = $this->getRefLink($project, $repo, $ref); |
| |
| // $repository = $repositories[$repo]; |
| // $path = $repository->getLink(); |
| // $link = "$path/commit?id=$ref"; |
| |
| // TODO Determine the link. |
| |
| $this->addContribution($author, $authorName, $ref, substr($ref,0,6), $link, $size, $comment); |
| } |
| } |
| |
| function getRefLink($project, $repo, $ref) { |
| // FIXME Handle LocationTech and PolarSys |
| if (preg_match('/\/gitroot(\/.*)$/', $repo, $matches)) { |
| $path = $matches[1]; |
| return "http://git.eclipse.org/c$path/commit?id=$ref"; |
| } elseif (preg_match('/^\/home\/data\/gitmirrors\/github\/([^\/]*)\/(.+)\.git$/', $repo, $matches)) { |
| $path = $matches[2]; |
| return "https://github.com/eclipse/$path/commit/$ref"; |
| } |
| return null; |
| } |
| |
| 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); |
| } |
| } |
| ?> |