<?php
# Copyright (c) 2014 Sergey Marchenko
# Licensed under the MIT license
if( false === include_once(config_get( 'plugin_path' ) . 'Source/MantisSourcePlugin.class.php') ) {
return;
}
require_once(config_get( 'core_path' ) . 'json_api.php');
class SourceBitBucketPlugin extends MantisSourcePlugin {
protected $main_url = "https://bitbucket.org/";
protected $api_url_10 = 'https://bitbucket.org/api/1.0/';
protected $api_url_20 = 'https://bitbucket.org/api/2.0/';
public function register() {
$this->name = plugin_lang_get( 'title' );
$this->description = plugin_lang_get( 'description' );
$this->version = '0.19';
$this->requires = array(
'MantisCore' => '1.2.16',
'Source' => '0.18',
);
$this->author = 'Sergey Marchenko';
$this->contact = 'sergey@mzsl.ru';
$this->url = 'http://zetabyte.ru';
}
public $type = 'bb';
public function show_type() {
return plugin_lang_get( 'bitbucket' );
}
public function show_changeset( $p_repo, $p_changeset ) {
$t_ref = substr( $p_changeset->revision, 0, 8 );
$t_branch = $p_changeset->branch;
return "$t_branch $t_ref";
}
public function show_file( $p_repo, $p_changeset, $p_file ) {
return "$p_file->action - $p_file->filename";
}
public function url_repo( $p_repo, $p_changeset = null ) {
if( empty($p_repo->info) ) return '';
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_ref = '';
if( !is_null( $p_changeset ) )
$t_ref = "/src/?at=$p_changeset->revision";
return $this->main_url . "$t_username/$t_reponame$t_ref";
}
public function url_changeset( $p_repo, $p_changeset ) {
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_ref = $p_changeset->revision;
return $this->main_url . "$t_username/$t_reponame/commits/$t_ref";
}
public function url_file( $p_repo, $p_changeset, $p_file ) {
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_ref = $p_changeset->revision;
$t_filename = $p_file->filename;
return $this->main_url . "$t_username/$t_reponame/src/$t_ref/$t_filename";
}
public function url_diff( $p_repo, $p_changeset, $p_file ) {
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_ref = $p_changeset->revision;
$t_filename = $p_file->filename;
return $this->main_url . "$t_username/$t_reponame/diff/$t_filename?diff2=$t_ref";
}
public function update_repo_form( $p_repo ) {
$t_bit_basic_login = null;
$t_bit_basic_pwd = null;
$t_bit_username = null;
$t_bit_reponame = null;
if( isset($p_repo->info['bit_basic_login']) ) {
$t_bit_basic_login = $p_repo->info['bit_basic_login'];
}
if( isset($p_repo->info['bit_basic_pwd']) ) {
$t_bit_basic_pwd = $p_repo->info['bit_basic_pwd'];
}
if( isset($p_repo->info['bit_username']) ) {
$t_bit_username = $p_repo->info['bit_username'];
}
if( isset($p_repo->info['bit_reponame']) ) {
$t_bit_reponame = $p_repo->info['bit_reponame'];
}
if( isset($p_repo->info['master_branch']) ) {
$t_master_branch = $p_repo->info['master_branch'];
} else {
$t_master_branch = 'master';
}
?>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'bit_basic_login' ) ?></td>
<td><input name="bit_basic_login" maxlength="250" size="40"
value="<?php echo string_attribute( $t_bit_basic_login ) ?>"/></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'bit_basic_pwd' ) ?></td>
<td><input type="password" name="bit_basic_pwd" maxlength="250" size="40"
value="<?php echo string_attribute( $t_bit_basic_pwd ) ?>"/></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'bit_username' ) ?></td>
<td><input name="bit_username" maxlength="250" size="40"
value="<?php echo string_attribute( $t_bit_username ) ?>"/></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'bit_reponame' ) ?></td>
<td><input name="bit_reponame" maxlength="250" size="40"
value="<?php echo string_attribute( $t_bit_reponame ) ?>"/></td>
</tr>
<tr>
<td class="spacer"></td>
</tr>
<tr <?php echo helper_alternate_class() ?>>
<td class="category"><?php echo plugin_lang_get( 'master_branch' ) ?></td>
<td><input name="master_branch" maxlength="250" size="40"
value="<?php echo string_attribute( $t_master_branch ) ?>"/></td>
</tr>
<?php
}
public function update_repo( $p_repo ) {
$f_basic_login = gpc_get_string( 'bit_basic_login' );
$f_basic_pwd = gpc_get_string( 'bit_basic_pwd' );
$f_bit_username = gpc_get_string( 'bit_username' );
$f_bit_reponame = gpc_get_string( 'bit_reponame' );
$f_master_branch = gpc_get_string( 'master_branch' );
if( !preg_match( '/\*|^[a-zA-Z0-9_\., -]*$/', $f_master_branch ) ) {
echo 'Invalid parameter: \'Primary Branch\'';
trigger_error( ERROR_GENERIC, ERROR );
}
$p_repo->info['bit_basic_login'] = $f_basic_login;
$p_repo->info['bit_basic_pwd'] = $f_basic_pwd;
$p_repo->info['bit_username'] = $f_bit_username;
$p_repo->info['bit_reponame'] = $f_bit_reponame;
$p_repo->info['master_branch'] = $f_master_branch;
return $p_repo;
}
private function api_url10( $p_path ) {
return $this->api_url_10 . $p_path;
}
private function api_url20( $p_path ) {
return $this->api_url_20 . $p_path;
}
private function api_json_url( $p_repo, $p_url ) {
$t_data = $this->url_get( $p_repo, $p_url );
$t_json = json_decode( utf8_encode( $t_data ) );
return $t_json;
}
public function precommit() {
return;
}
public function commit( $p_repo, $p_data ) {
$t_commits = array();
foreach ( $p_data['commits'] as $t_commit ) {
$t_commits[] = $t_commit['id'];
}
$t_refData = explode( '/', $p_data['ref'] );
$t_branch = $t_refData[2];
return $this->import_commits( $p_repo, $t_commits, $t_branch );
}
public function import_full( $p_repo, $p_use_cache = true ) {
echo '<pre>';
$t_branch = $p_repo->info['master_branch'];
if( is_blank( $t_branch ) ) {
$t_branch = 'master';
}
if( $t_branch != '*' ) {
$t_branches = array_map( 'trim', explode( ',', $t_branch ) );
} else {
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_uri = $this->api_url10( "repositories/$t_username/$t_reponame/branches" );
$t_json = $this->api_json_url( $p_repo, $t_uri );
$t_branches = array();
foreach ( $t_json as $t_branchname => $t_branch ) {
if(isset($t_branchname)) {
if (strpos($t_branchname, '/') !== FALSE) {
$t_branches[] = $t_branch->raw_node;
} else {
$t_branches[] = $t_branchname;
}
}
}
$t_branches = array_unique($t_branches);
}
$t_changesets = array();
$t_changeset_table = plugin_table( 'changeset', 'Source' );
foreach ( $t_branches as $t_branch ) {
$t_query = "SELECT parent FROM $t_changeset_table
WHERE repo_id=" . db_param() . ' AND branch=' . db_param() .
' ORDER BY timestamp ASC';
$t_result = db_query_bound( $t_query, array($p_repo->id, $t_branch), 1 );
$t_commits = array($t_branch);
if( db_num_rows( $t_result ) > 0 ) {
$t_parent = db_result( $t_result );
echo "Oldest '$t_branch' branch parent: '$t_parent'\n";
if( !empty($t_parent) ) {
$t_commits[] = $t_parent;
}
}
if( $p_use_cache ) foreach ( $t_commits as $t_commit_id ) $this->load_all_commits( $p_repo, $t_commit_id );
$t_changesets = array_merge( $t_changesets, $this->import_commits( $p_repo, $t_commits, $t_branch ) );
}
echo '</pre>';
return $t_changesets;
}
public function import_latest( $p_repo ) {
return $this->import_full( $p_repo, false );
}
private $commits_cache = array();
private function load_all_commits( $p_repo, $p_commit_id, $p_next = '' ) {
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_url = empty($p_next) ? $this->api_url20( "repositories/$t_username/$t_reponame/commits/$p_commit_id" ) : $p_next;
$t_json = $this->api_json_url( $p_repo, $t_url );
if( property_exists( $t_json, 'values' ) ) {
foreach ( $t_json->values as $t_item ) {
$this->commits_cache[$t_item->hash] = $t_item;
}
}
if( property_exists( $t_json, 'next' ) ) $this->load_all_commits( $p_repo, $p_commit_id, $t_json->next );
}
public function import_commits( $p_repo, $p_commit_ids, $p_branch = '' ) {
static $s_parents = array();
static $s_counter = 0;
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
if( is_array( $p_commit_ids ) ) {
$s_parents = array_merge( $s_parents, $p_commit_ids );
} else {
$s_parents[] = $p_commit_ids;
}
$t_changesets = array();
while( count( $s_parents ) > 0 && $s_counter < 200 ) {
$t_commit_id = array_shift( $s_parents );
echo "Retrieving $t_commit_id ... ";
$t_json = null;
if( empty($this->commits_cache[$t_commit_id]) ) {
$t_url = $this->api_url20( "repositories/$t_username/$t_reponame/commit/$t_commit_id/" );
$t_json = $this->api_json_url( $p_repo, $t_url );
} else {
$t_json = $this->commits_cache[$t_commit_id];
}
if( false === $t_json || is_null( $t_json ) ) {
# Some error occured retrieving the commit
echo "failed.\n";
continue;
} else if( !property_exists( $t_json, 'hash' ) ) {
echo 'failed (', $t_json->error->message, ").\n";
continue;
}
list($t_changeset, $t_commit_parents) = $this->json_commit_changeset( $p_repo, $t_json, $p_branch );
if( $t_changeset ) {
$t_changesets[] = $t_changeset;
}
$s_parents = array_merge( $s_parents, $t_commit_parents );
}
$s_counter = 0;
return $t_changesets;
}
private function get_author_name( $p_row ) {
return trim( substr( $p_row, 0, strpos( $p_row, '<' ) ) );
}
private function get_author_email( $p_row ) {
$start = strpos( $p_row, '<' ) + 1;
$length = strpos( $p_row, '>' ) - $start;
return trim( substr( $p_row, $start, $length ) );
}
private function json_commit_changeset( $p_repo, $p_json, $p_branch = '' ) {
echo "processing $p_json->hash ... ";
if( !SourceChangeset::exists( $p_repo->id, $p_json->hash ) ) {
$t_parents = array();
foreach ( $p_json->parents as $t_parent ) {
$t_parents[] = $t_parent->hash;
}
$t_changeset = new SourceChangeset(
$p_repo->id,
$p_json->hash,
$p_branch,
date( 'Y-m-d H:i:s', strtotime( $p_json->date ) ),
$this->get_author_name( $p_json->author->raw ),
$p_json->message
);
if( count( $p_json->parents ) > 0 ) {
$t_parent = $p_json->parents[0];
$t_changeset->parent = $t_parent->hash;
}
$t_changeset->author_email = $this->get_author_email( $p_json->author->raw );
$t_changeset->committer = $t_changeset->author;
$t_changeset->committer_email = $t_changeset->author_email;
$t_username = $p_repo->info['bit_username'];
$t_reponame = $p_repo->info['bit_reponame'];
$t_commit_id = $p_json->hash;
$t_url = $this->api_url10( "repositories/$t_username/$t_reponame/changesets/$t_commit_id/diffstat/" );
$t_files = $this->api_json_url( $p_repo, $t_url );
if( !empty($t_files) ) {
foreach ( $t_files as $t_file ) {
switch( $t_file->type ) {
case 'added':
$t_changeset->files[] = new SourceFile(0, '', $t_file->file, 'add');
break;
case 'modified':
$t_changeset->files[] = new SourceFile(0, '', $t_file->file, 'mod');
break;
case 'removed':
$t_changeset->files[] = new SourceFile(0, '', $t_file->file, 'rm');
break;
}
}
}
$t_changeset->save();
echo "saved.\n";
return array($t_changeset, $t_parents);
} else {
echo "already exists.\n";
return array(null, array());
}
}
public function url_get( $p_repo, $p_url ) {
$t_user_pass = $p_repo->info['bit_basic_login'] . ':' . $p_repo->info['bit_basic_pwd'];
# Use the PHP cURL extension
if( function_exists( 'curl_init' ) ) {
$t_curl = curl_init( $p_url );
# cURL options
$t_curl_opt[CURLOPT_USERPWD] = $t_user_pass;
$t_curl_opt[CURLOPT_RETURNTRANSFER] = true;
$t_vers = curl_version();
$t_curl_opt[CURLOPT_USERAGENT] =
'mantisbt/' . MANTIS_VERSION . ' php-curl/' . $t_vers['version'];
# Set the options
curl_setopt_array( $t_curl, $t_curl_opt );
# Retrieve data
$t_data = curl_exec( $t_curl );
curl_close( $t_curl );
if( $t_data !== false ) {
return $t_data;
}
}
# Last resort system call
$t_url = escapeshellarg( $p_url ); //use -u user:pass
return shell_exec( 'curl -u ' . $t_user_pass . ' ' . $t_url );
}
}