Newer
Older
Mantis_source-integration / SourceBitBucket / SourceBitBucket.php
@Damien Regad Damien Regad on 12 Sep 2015 12 KB Bump version
<?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 );
	}
}