blob: 6e0becd0bc8164c042fd3fb2f3998c4154feb0e1 [file] [log] [blame]
<?php
class CommitLogFetcher {
function __construct($db) {
$this->db = $db;
}
static function find_commit_id_by_revision($db, $repository_id, $revision)
{
$commit_rows = $db->query_and_fetch_all('SELECT commit_id FROM commits WHERE commit_repository = $1 AND commit_revision = $2', array($repository_id, $revision));
if ($commit_rows)
return $commit_rows[0]['commit_id'];
if (!ctype_alnum($revision))
return NULL;
$commit_rows = $db->query_and_fetch_all('SELECT commit_id FROM commits WHERE commit_repository = $1 AND commit_revision LIKE $2 LIMIT 2', array($repository_id, Database::escape_for_like($revision) . '%'));
if (!$commit_rows)
return NULL;
if (count($commit_rows) > 1)
return -1; // There are more than one matches.
return $commit_rows[0]['commit_id'];
}
function fetch_for_tasks($task_id_list, $task_by_id)
{
$commit_rows = $this->db->query_and_fetch_all('SELECT task_commits.*, commits.*, committers.*
FROM task_commits, commits LEFT OUTER JOIN committers ON commit_committer = committer_id
WHERE taskcommit_commit = commit_id AND taskcommit_task = ANY ($1)', array('{' . implode(', ', $task_id_list) . '}'));
if (!is_array($commit_rows))
return NULL;
$commits = array();
foreach ($commit_rows as &$commit_row) {
$associated_task = &$task_by_id[$commit_row['taskcommit_task']];
# FIXME: The last parameter should be determined based on commit_ownerships.
$commit = $this->format_commit($commit_row, $commit_row, /* owns_commits */ FALSE);
$commit['repository'] = $commit_row['commit_repository'];
array_push($commits, $commit);
array_push($associated_task[Database::is_true($commit_row['taskcommit_is_fix']) ? 'fixes' : 'causes'], $commit_row['commit_id']);
}
return $commits;
}
function repository_id_from_name($name)
{
$repository_row = $this->db->select_first_row('repositories', 'repository', array('name' => $name, 'owner' => NULL));
if (!$repository_row)
return NULL;
return $repository_row['repository_id'];
}
function fetch_between($repository_id, $before_first_revision, $last_revision, $keyword = NULL)
{
$statements = 'SELECT commit_id as "id",
commit_revision as "revision",
commit_previous_commit as "previousCommit",
commit_time as "time",
committer_name as "authorName",
committer_account as "authorEmail",
commit_repository as "repository",
commit_message as "message",
commit_order as "order",
commit_testability as "testability",
EXISTS(SELECT * FROM commit_ownerships WHERE commit_owner = commit_id) as "ownsCommits"
FROM commits LEFT OUTER JOIN committers ON commit_committer = committer_id
WHERE commit_repository = $1 AND commit_reported = true';
$values = array($repository_id);
if ($before_first_revision && $last_revision) {
$preceding_commit = $this->commit_for_revision($repository_id, $before_first_revision);
$last_commit = $this->commit_for_revision($repository_id, $last_revision);
$preceding = $preceding_commit['commit_time'];
$last = $last_commit['commit_time'];
if (!$preceding != !$last)
exit_with_error('InconsistentCommits');
if ($preceding)
$column_name = 'commit_time';
else {
$preceding = $preceding_commit['commit_order'];
$last = $last_commit['commit_order'];
$column_name = 'commit_order';
if (!$preceding || !$last)
return array();
}
if ($preceding >= $last)
exit_with_error('InvalidCommitRange');
array_push($values, $preceding);
$statements .= ' AND ' . $column_name . ' > $' . count($values);
array_push($values, $last);
$statements .= ' AND ' . $column_name . ' <= $' . count($values);
}
if ($keyword) {
array_push($values, '%' . Database::escape_for_like($keyword) . '%');
$keyword_index = '$' . count($values);
array_push($values, ltrim($keyword, 'r'));
$revision_index = '$' . count($values);
$statements .= "
AND ((committer_name LIKE $keyword_index OR committer_account LIKE $keyword_index) OR commit_revision = $revision_index)";
}
$commits = $this->db->query_and_fetch_all($statements . ' ORDER BY commit_time, commit_order', $values);
if (!is_array($commits))
return NULL;
foreach ($commits as &$commit) {
$commit['time'] = Database::to_js_time($commit['time']);
$commit['ownsCommits'] = Database::is_true($commit['ownsCommits']);
}
return $commits;
}
function fetch_owned_commits_for_revision($repository_id, $commit_revision) {
return $this->db->query_and_fetch_all('SELECT owned.commit_repository as "repository",
owned.commit_revision as "revision",
owned.commit_time as "time",
owned.commit_id as "id"
FROM commits AS owned, commit_ownerships, commits AS owner
WHERE owned.commit_id = commit_owned AND owner.commit_id = commit_owner AND owner.commit_revision = $1 AND owner.commit_repository = $2',
array($commit_revision, $repository_id));
}
# FIXME: this is not DRY. Ideally, $db should provide the ability to search with criteria that specifies a range.
function fetch_last_reported_between_orders($repository_id, $from, $to)
{
$statements = 'SELECT * FROM commits LEFT OUTER JOIN committers ON commit_committer = committer_id
WHERE commit_repository = $1 AND commit_reported = true';
$from = intval($from);
$to = intval($to);
$statements .= ' AND commit_order >= $2 AND commit_order <= $3 ORDER BY commit_order DESC LIMIT 1';
$commits = $this->db->query_and_fetch_all($statements, array($repository_id, $from, $to));
if (!is_array($commits))
return NULL;
foreach ($commits as &$commit)
$commit = $this->format_single_commit($commit)[0];
return $commits;
}
function fetch_oldest($repository_id) {
return $this->format_single_commit($this->db->select_first_row('commits', 'commit', array('repository' => $repository_id), array('time', 'order')));
}
function fetch_latest($repository_id) {
return $this->format_single_commit($this->db->select_last_row('commits', 'commit', array('repository' => $repository_id), array('time', 'order')));
}
function fetch_latest_for_platform($repository_id, $platform_id)
{
$query_result = $this->db->query_and_fetch_all("SELECT commits.* FROM test_runs, builds, build_commits, commits
WHERE run_build = build_id AND NOT EXISTS (SELECT * FROM build_requests WHERE request_build = build_id LIMIT 1)
AND run_config IN (SELECT config_id FROM test_configurations
WHERE config_type = 'current' AND config_platform = $2 AND config_metric
IN (SELECT metric_id FROM test_metrics, tests WHERE metric_test = test_id and test_parent IS NULL))
AND run_build = build_id AND commit_build = build_id AND build_commit = commit_id AND commit_repository = $1
ORDER BY build_time DESC LIMIT 1;", array($repository_id, $platform_id));
/* This query is approximation. It finds the commit of the last build instead of the last commit.
We would ideally run the following query but it's much slower ~70s compared to 300ms.
SELECT commits.*, commit_build, run_id
FROM commits, build_commits, test_runs
WHERE commit_repository = 9 AND commit_id = build_commit AND commit_build = run_build
AND run_config IN (SELECT config_id FROM test_configurations WHERE config_platform = 38 AND config_type = 'current')
AND NOT EXISTS (SELECT * FROM build_requests WHERE request_build = commit_build)
ORDER BY commit_time DESC, commit_order DESC LIMIT 1; */
if (!$query_result)
return array();
return $this->format_single_commit($query_result[0]);
}
function fetch_last_reported($repository_id) {
return $this->format_single_commit($this->db->select_last_row('commits', 'commit', array('repository' => $repository_id, 'reported' => true), array('time', 'order')));
}
function fetch_revision($repository_id, $revision, $prefix_match) {
$all_but_first = substr($revision, 1);
$is_svn_revision = false;
if ($revision[0] == 'r' && ctype_digit($all_but_first)) {
$revision = $all_but_first;
$is_svn_revision = true;
}
if ($prefix_match && !$is_svn_revision)
$commit = $this->commit_for_revision_prefix($repository_id, $revision);
else
$commit = $this->commit_for_revision($repository_id, $revision);
return $this->format_single_commit($commit);
}
private function commit_for_revision($repository_id, $revision) {
$commit_info = array('repository' => $repository_id, 'revision' => $revision);
$row = $this->db->select_last_row('commits', 'commit', $commit_info);
if (!$row)
exit_with_error('UnknownCommit', $commit_info);
return $row;
}
private function commit_for_revision_prefix($repository_id, $revision_prefix) {
$rows = $this->db->query_and_fetch_all('SELECT * FROM commits WHERE commit_repository = $1 AND commit_revision LIKE $2 ORDER BY commit_revision LIMIT 2', array($repository_id, Database::escape_for_like($revision_prefix) . '%'));
if (count($rows) == 0)
exit_with_error('UnknownCommit', array('repository' => $repository_id, 'revision' => $revision_prefix));
if (count($rows) == 2) {
if ($rows[0]['commit_revision'] == $revision_prefix)
return $rows[0];
exit_with_error('AmbiguousRevisionPrefix', array('repository' => $repository_id, 'revision' => $revision_prefix));
}
return $rows[0];
}
private function format_single_commit($commit_row) {
if (!$commit_row)
return array();
$committer = $this->db->select_first_row('committers', 'committer', array('id' => $commit_row['commit_committer']));
$owns_commits = !!$this->db->select_first_row('commit_ownerships', 'commit', array('owner' => $commit_row['commit_id']));
return array($this->format_commit($commit_row, $committer, $owns_commits));
}
private function format_commit($commit_row, $committer_row, $owns_commits) {
return array(
'id' => $commit_row['commit_id'],
'revision' => $commit_row['commit_revision'],
'repository' => $commit_row['commit_repository'],
'previousCommit' => $commit_row['commit_previous_commit'],
'time' => Database::to_js_time($commit_row['commit_time']),
'order' => $commit_row['commit_order'],
'authorName' => $committer_row ? $committer_row['committer_name'] : null,
'authorEmail' => $committer_row ? $committer_row['committer_account'] : null,
'message' => $commit_row['commit_message'],
'testability' => $commit_row['commit_testability'],
'ownsCommits' => $owns_commits
);
}
}
?>