|
| 1 | +<?php |
| 2 | + |
| 3 | +# Copyright (c) 2012 John Reese |
| 4 | +# Licensed under the MIT license |
| 5 | + |
| 6 | +if ( false === include_once( config_get( 'plugin_path' ) . 'Source/MantisSourcePlugin.class.php' ) ) { |
| 7 | + return; |
| 8 | +} |
| 9 | + |
| 10 | +require_once( config_get( 'core_path' ) . 'url_api.php' ); |
| 11 | + |
| 12 | +class SourceGitphpPlugin extends MantisSourcePlugin { |
| 13 | + public function register() { |
| 14 | + $this->name = plugin_lang_get( 'title' ); |
| 15 | + $this->description = plugin_lang_get( 'description' ); |
| 16 | + |
| 17 | + $this->version = '0.10'; |
| 18 | + $this->requires = array( |
| 19 | + 'MantisCore' => '1.2.0', |
| 20 | + 'Source' => '0.16', |
| 21 | + ); |
| 22 | + |
| 23 | + $this->author = 'John Reese'; |
| 24 | + $this-> contact = '[email protected]'; |
| 25 | + $this->url = 'http://noswap.com'; |
| 26 | + } |
| 27 | + |
| 28 | + public $type = 'gitphp'; |
| 29 | + |
| 30 | + public function show_type() { |
| 31 | + return plugin_lang_get( 'gitphp' ); |
| 32 | + } |
| 33 | + |
| 34 | + public function show_changeset( $p_repo, $p_changeset ) { |
| 35 | + $t_ref = substr( $p_changeset->revision, 0, 8 ); |
| 36 | + $t_branch = $p_changeset->branch; |
| 37 | + |
| 38 | + return "$t_branch $t_ref"; |
| 39 | + } |
| 40 | + |
| 41 | + public function show_file( $p_repo, $p_changeset, $p_file ) { |
| 42 | + return "$p_file->action - $p_file->filename"; |
| 43 | + } |
| 44 | + |
| 45 | + private function uri_base( $p_repo ) { |
| 46 | + $t_uri_base = $p_repo->info['gitphp_root'] . '?p=' . $p_repo->info['gitphp_project'] . '&'; |
| 47 | + return $t_uri_base; |
| 48 | + } |
| 49 | + |
| 50 | + public function url_repo( $p_repo, $t_changeset=null ) { |
| 51 | + return $this->uri_base( $p_repo ) . ( $t_changeset ? 'h=' . $t_changeset->revision : '' ); |
| 52 | + } |
| 53 | + |
| 54 | + public function url_changeset( $p_repo, $p_changeset ) { |
| 55 | + return $this->uri_base( $p_repo ) . 'a=commitdiff&h=' . $p_changeset->revision; |
| 56 | + } |
| 57 | + |
| 58 | + public function url_file( $p_repo, $p_changeset, $p_file ) { |
| 59 | + return $this->uri_base( $p_repo ) . 'a=blob&f=' . $p_file->filename . |
| 60 | + '&h=' . $p_file->revision . '&hb=' . $p_changeset->revision; |
| 61 | + } |
| 62 | + |
| 63 | + public function url_diff( $p_repo, $p_changeset, $p_file ) { |
| 64 | + return $this->uri_base( $p_repo ) . 'a=blobdiff&f=' . $p_file->filename . |
| 65 | + '&h=' . $p_file->revision . '&hb=' . $p_changeset->revision . '&hp=' . $p_changeset->parent; |
| 66 | + } |
| 67 | + |
| 68 | + public function update_repo_form( $p_repo ) { |
| 69 | + $t_gitphp_root = null; |
| 70 | + $t_gitphp_project = null; |
| 71 | + |
| 72 | + if ( isset( $p_repo->info['gitphp_root'] ) ) { |
| 73 | + $t_gitphp_root = $p_repo->info['gitphp_root']; |
| 74 | + } |
| 75 | + |
| 76 | + if ( isset( $p_repo->info['gitphp_project'] ) ) { |
| 77 | + $t_gitphp_project = $p_repo->info['gitphp_project']; |
| 78 | + } |
| 79 | + |
| 80 | + if ( isset( $p_repo->info['master_branch'] ) ) { |
| 81 | + $t_master_branch = $p_repo->info['master_branch']; |
| 82 | + } else { |
| 83 | + $t_master_branch = 'master'; |
| 84 | + } |
| 85 | +?> |
| 86 | +<tr <?php echo helper_alternate_class() ?>> |
| 87 | +<td class="category"><?php echo plugin_lang_get( 'gitphp_root' ) ?></td> |
| 88 | +<td><input name="gitphp_root" maxlength="250" size="40" value="<?php echo string_attribute( $t_gitphp_root ) ?>"/></td> |
| 89 | +</tr> |
| 90 | +<tr <?php echo helper_alternate_class() ?>> |
| 91 | +<td class="category"><?php echo plugin_lang_get( 'gitphp_project' ) ?></td> |
| 92 | +<td><input name="gitphp_project" maxlength="250" size="40" value="<?php echo string_attribute( $t_gitphp_project ) ?>"/></td> |
| 93 | +</tr> |
| 94 | +<tr <?php echo helper_alternate_class() ?>> |
| 95 | +<td class="category"><?php echo plugin_lang_get( 'master_branch' ) ?></td> |
| 96 | +<td><input name="master_branch" maxlength="250" size="40" value="<?php echo string_attribute( $t_master_branch ) ?>"/></td> |
| 97 | +</tr> |
| 98 | +<?php |
| 99 | + } |
| 100 | + |
| 101 | + public function update_repo( $p_repo ) { |
| 102 | + $f_gitphp_root = gpc_get_string( 'gitphp_root' ); |
| 103 | + $f_gitphp_project = gpc_get_string( 'gitphp_project' ); |
| 104 | + $f_master_branch = gpc_get_string( 'master_branch' ); |
| 105 | + |
| 106 | + $p_repo->info['gitphp_root'] = $f_gitphp_root; |
| 107 | + $p_repo->info['gitphp_project'] = $f_gitphp_project; |
| 108 | + $p_repo->info['master_branch'] = $f_master_branch; |
| 109 | + |
| 110 | + return $p_repo; |
| 111 | + } |
| 112 | + |
| 113 | + public function precommit( ) { |
| 114 | + # TODO: Implement real commit sequence. |
| 115 | + return; |
| 116 | + } |
| 117 | + |
| 118 | + public function commit( $p_repo, $p_data ) { |
| 119 | + # Handle branch names with '+' character |
| 120 | + $p_data = str_replace('_plus_', '+', $p_data); |
| 121 | + |
| 122 | + # The -d option from curl requires you to encode your own data. |
| 123 | + # Once it reaches here it is decoded. Hence we split by a space |
| 124 | + # were as the curl command uses a '+' character instead. |
| 125 | + # i.e. DATA=`echo $INPUT | sed -e 's/ /+/g'` |
| 126 | + list ( , $t_commit_id, $t_branch) = explode(' ', $p_data); |
| 127 | + list ( , , $t_branch) = explode('/', $t_branch, 3); |
| 128 | + |
| 129 | + # master_branch contains comma-separated list of branches |
| 130 | + $t_branches = explode(',', $p_repo->info['master_branch']); |
| 131 | + if (!in_array('*', $t_branches) and !in_array($t_branch, $t_branches)) |
| 132 | + { |
| 133 | + return; |
| 134 | + } |
| 135 | + |
| 136 | + return $this->import_commits($p_repo, null, $t_commit_id, $t_branch); |
| 137 | + } |
| 138 | + |
| 139 | + public function import_full( $p_repo ) { |
| 140 | + echo '<pre>'; |
| 141 | + $t_branch = $p_repo->info['master_branch']; |
| 142 | + if ( is_blank( $t_branch ) ) { |
| 143 | + $t_branch = 'master'; |
| 144 | + } |
| 145 | + |
| 146 | + if ($t_branch != '*') |
| 147 | + { |
| 148 | + $t_branches = array_map( 'trim', explode( ',', $t_branch ) ); |
| 149 | + } |
| 150 | + else |
| 151 | + { |
| 152 | + $t_heads_url = $this->uri_base( $p_repo ) . 'a=heads'; |
| 153 | + $t_branches_input = url_get( $t_heads_url ); |
| 154 | + |
| 155 | + $t_branches_input = str_replace( array("\r", "\n", '<', '>', ' '), array('', '', '<', '>', ' '), $t_branches_input ); |
| 156 | + |
| 157 | + $t_branches_input_p1 = strpos( $t_branches_input, '<table class="heads">' ); |
| 158 | + $t_branches_input_p2 = strpos( $t_branches_input, '<div class="page_footer">' ); |
| 159 | + $t_gitphp_heads = substr( $t_branches_input, $t_branches_input_p1, $t_branches_input_p2 - $t_branches_input_p1 ); |
| 160 | + preg_match_all( '/<a class="list name".*>(.*)<\/a>/iU', $t_gitphp_heads, $t_matches, PREG_SET_ORDER ); |
| 161 | + |
| 162 | + $t_branches = array(); |
| 163 | + foreach ($t_matches as $match) |
| 164 | + { |
| 165 | + $t_branch = trim($match[1]); |
| 166 | + if ($match[1] != 'origin' and !in_array($t_branch,$t_branches)) |
| 167 | + { |
| 168 | + $t_branches[] = $t_branch; |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + $t_changesets = array(); |
| 174 | + |
| 175 | + $t_changeset_table = plugin_table( 'changeset', 'Source' ); |
| 176 | + |
| 177 | + foreach( $t_branches as $t_branch ) { |
| 178 | + $t_query = "SELECT parent FROM $t_changeset_table |
| 179 | + WHERE repo_id=" . db_param() . ' AND branch=' . db_param() . |
| 180 | + 'ORDER BY timestamp ASC'; |
| 181 | + $t_result = db_query_bound( $t_query, array( $p_repo->id, $t_branch ), 1 ); |
| 182 | + |
| 183 | + $t_commits = array( $t_branch ); |
| 184 | + |
| 185 | + if ( db_num_rows( $t_result ) > 0 ) { |
| 186 | + $t_parent = db_result( $t_result ); |
| 187 | + echo "Oldest '$t_branch' branch parent: '$t_parent'\n"; |
| 188 | + |
| 189 | + if ( !empty( $t_parent ) ) { |
| 190 | + $t_commits[] = $t_parent; |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + $t_changesets = array_merge( $t_changesets, $this->import_commits( $p_repo, $this->uri_base( $p_repo ), $t_commits, $t_branch ) ); |
| 195 | + } |
| 196 | + |
| 197 | + echo '</pre>'; |
| 198 | + |
| 199 | + return $t_changesets; |
| 200 | + } |
| 201 | + |
| 202 | + public function import_latest( $p_repo ) { |
| 203 | + return $this->import_full( $p_repo ); |
| 204 | + } |
| 205 | + |
| 206 | + private function import_commits( $p_repo, $p_uri_base, $p_commit_ids, $p_branch='' ) { |
| 207 | + static $s_parents = array(); |
| 208 | + static $s_counter = 0; |
| 209 | + |
| 210 | + if ( is_array( $p_commit_ids ) ) { |
| 211 | + $s_parents = array_merge( $s_parents, $p_commit_ids ); |
| 212 | + } else { |
| 213 | + $s_parents[] = $p_commit_ids; |
| 214 | + } |
| 215 | + |
| 216 | + $t_changesets = array(); |
| 217 | + |
| 218 | + while( count( $s_parents ) > 0 && $s_counter < 200 ) { |
| 219 | + $t_commit_id = array_shift( $s_parents ); |
| 220 | + |
| 221 | + echo "Retrieving $t_commit_id ...\n "; |
| 222 | + |
| 223 | + # Handle branch names with '+' character |
| 224 | + $t_fixed_id = str_replace('+', '%2B', $t_commit_id); |
| 225 | + $t_commit_url = $this->uri_base( $p_repo ) . 'a=commit&h=' . $t_fixed_id; |
| 226 | + $t_input = url_get( $t_commit_url ); |
| 227 | + |
| 228 | + if ( !$t_input ) { |
| 229 | + echo "failed.\n"; |
| 230 | + echo "'$t_commit_url' did not return any data.\n"; |
| 231 | + die(); |
| 232 | + } |
| 233 | + |
| 234 | + list( $t_changeset, $t_commit_parents ) = $this->commit_changeset( $p_repo, $t_input, $p_branch ); |
| 235 | + if ( !is_null( $t_changeset ) ) { |
| 236 | + $t_changesets[] = $t_changeset; |
| 237 | + } |
| 238 | + |
| 239 | + $s_parents = array_merge( $s_parents, $t_commit_parents ); |
| 240 | + $s_counter += 1; |
| 241 | + } |
| 242 | + |
| 243 | + $s_counter = 0; |
| 244 | + return $t_changesets; |
| 245 | + } |
| 246 | + |
| 247 | + private function commit_changeset( $p_repo, $p_input, $p_branch='' ) { |
| 248 | + |
| 249 | + $t_input = str_replace( array("\r", "\n", '<', '>', ' '), array('', '', '<', '>', ' '), $p_input ); |
| 250 | + |
| 251 | + # Extract sections of commit data and changed files |
| 252 | + $t_input_p1 = strpos( $t_input, '<div class="title">' ); |
| 253 | + $t_input_p2 = strpos( $t_input, '<div class="list_head">' ); |
| 254 | + if ( false === $t_input_p1 || false === $t_input_p2 ) { |
| 255 | + echo "commit data failure.\n"; |
| 256 | + var_dump( strlen( $t_input ), $t_input_p1, $t_input_p2 ); |
| 257 | + die(); |
| 258 | + } |
| 259 | + $t_gitphp_data = substr( $t_input, $t_input_p1, $t_input_p2 - $t_input_p1 ); |
| 260 | + |
| 261 | + $t_input_p1 = strpos( $t_input, '<table class="diff_tree">' ); |
| 262 | + |
| 263 | + if ( false === $t_input_p1 ) { |
| 264 | + $t_input_p1 = strpos( $t_input, '<tr class="light">' ); |
| 265 | + if ( false === $t_input_p1 ) { |
| 266 | + $t_input_p1 = strpos( $t_input, '<tr class="dark">' ); |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | + $t_input_p2 = strpos( $t_input, '<div class="page_footer">' ); |
| 271 | + |
| 272 | + if ( false === $t_input_p1 || false === $t_input_p2 ) { |
| 273 | + echo 'file data failure.'; |
| 274 | + var_dump( strlen( $t_input ), $t_input_p1, $t_input_p2 ); |
| 275 | + print_r($t_input); |
| 276 | + die(); |
| 277 | + } |
| 278 | + |
| 279 | + $t_gitphp_files = substr( $t_input, $t_input_p1, $t_input_p2 - $t_input_p1 ); |
| 280 | + |
| 281 | + |
| 282 | + # Get commit revsion and make sure it's not a dupe |
| 283 | + preg_match( '#<td class="monospace">([a-f0-9]*)#', $t_gitphp_data, $t_matches ); |
| 284 | + |
| 285 | + $t_commit['revision'] = $t_matches[1]; |
| 286 | + |
| 287 | + echo "processing $t_commit[revision] ... \n"; |
| 288 | + |
| 289 | + if ( !SourceChangeset::exists( $p_repo->id, $t_commit['revision'] ) ) { |
| 290 | + |
| 291 | + # Parse for commit data |
| 292 | + # m modifier means "make . include newlines" |
| 293 | + # x modifier means "ignore whitespace" |
| 294 | + preg_match( '#<td>author</td>.*?<td>(.*?)<.*?<time.*?>(.*?)<.*?<td>committer</td>.*?<td>(.*?)<.*<div.class="page_body">(.*?)</div>.*#mx', |
| 295 | + $t_gitphp_data, $t_matches ); |
| 296 | + |
| 297 | + $t_commit['author'] = $t_matches[1]; |
| 298 | + # gitphp doesn't parse email address for some reason? |
| 299 | + $t_commit['author_email'] = "n/a"; |
| 300 | + $t_commit['date'] = date( 'Y-m-d H:i:s', strtotime( $t_matches[2] ) ); |
| 301 | + $t_commit['committer'] = $t_matches[3]; |
| 302 | + $t_commit['committer_email'] = "n/a"; |
| 303 | + $t_commit['message'] = trim( preg_replace( '#<br.*?>#', PHP_EOL, $t_matches[4] ) ); |
| 304 | + |
| 305 | + $t_parents = array(); |
| 306 | + |
| 307 | + if ( preg_match_all( '#parent</td>.*?<td.class="monospace">.*?<a.*?>(.*?)<.*#mx', $t_gitphp_data, $t_matches ) ) { |
| 308 | + foreach( $t_matches[1] as $t_match ) { |
| 309 | + $t_parents[] = $t_commit['parent'] = $t_match; |
| 310 | + } |
| 311 | + } |
| 312 | + |
| 313 | + # Strip ref links and signoff spans from commit message |
| 314 | + $t_commit['message'] = preg_replace( array( '#<a[^>]*>([^<]*)</a>#', '#<span[^>]*>(.*?)</span>#' ), |
| 315 | + '$1', $t_commit['message'] ); |
| 316 | + |
| 317 | + # Prepend a # sign to mantis number |
| 318 | + $t_commit['message'] = preg_replace( '#(mantis)\s+(\d+)#i', '$1 #$2',$t_commit['message'] ); |
| 319 | + |
| 320 | + # Parse for changed file data |
| 321 | + $t_commit['files'] = array(); |
| 322 | + |
| 323 | + preg_match_all( '#class="list".*?h=(\w*).*?f=(.*?)".*?<.a>#', |
| 324 | + $t_gitphp_files, $t_matches, PREG_SET_ORDER ); |
| 325 | + |
| 326 | + foreach( $t_matches as $t_file_matches ) { |
| 327 | + $t_file = array(); |
| 328 | + $t_file['filename'] = str_replace('%2F', '/', $t_file_matches[2]); |
| 329 | + $t_file['revision'] = $t_file_matches[1]; |
| 330 | + |
| 331 | + if ( isset( $t_file_matches[3] ) ) { |
| 332 | + if ( $t_file_matches[3] == 'new' or $t_file_matches[3] == 'moved' ) { |
| 333 | + $t_file['action'] = 'add'; |
| 334 | + } else if ( $t_file_matches[3] == 'deleted' or $t_file_matches[3] == 'similarity' ) { |
| 335 | + $t_file['action'] = 'rm'; |
| 336 | + } else { |
| 337 | + $t_file['action'] = 'mod'; |
| 338 | + } |
| 339 | + } else { |
| 340 | + $t_file['action'] = 'mod'; |
| 341 | + } |
| 342 | + |
| 343 | + $t_commit['files'][] = $t_file; |
| 344 | + } |
| 345 | + |
| 346 | + $t_changeset = new SourceChangeset( $p_repo->id, $t_commit['revision'], $p_branch, |
| 347 | + $t_commit['date'], $t_commit['author'], $t_commit['message'], 0, |
| 348 | + ( isset( $t_commit['parent'] ) ? $t_commit['parent'] : '' ) ); |
| 349 | + |
| 350 | + $t_changeset->author_email = $t_commit['author_email']; |
| 351 | + $t_changeset->committer = $t_commit['committer']; |
| 352 | + $t_changeset->committer_email = $t_commit['committer_email']; |
| 353 | + |
| 354 | + foreach( $t_commit['files'] as $t_file ) { |
| 355 | + $t_changeset->files[] = new SourceFile( 0, $t_file['revision'], $t_file['filename'], $t_file['action'] ); |
| 356 | + } |
| 357 | + |
| 358 | + $t_changeset->save(); |
| 359 | + |
| 360 | + echo "saved.\n"; |
| 361 | + return array( $t_changeset, $t_parents ); |
| 362 | + } else { |
| 363 | + echo "already exists.\n"; |
| 364 | + return array( null, array() ); |
| 365 | + } |
| 366 | + } |
| 367 | +} |
0 commit comments