PageRenderTime 1317ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/SourceSVN/SourceSVN.php

https://github.com/cocox/source-integration
PHP | 461 lines | 341 code | 89 blank | 31 comment | 83 complexity | 31113d6bf5ceaac44945b91ccf703b5d MD5 | raw file
  1. <?php
  2. # Copyright (c) 2010 John Reese
  3. # Licensed under the MIT license
  4. if ( false === include_once( config_get( 'plugin_path' ) . 'Source/MantisSourcePlugin.class.php' ) ) {
  5. return;
  6. }
  7. class SourceSVNPlugin extends MantisSourcePlugin {
  8. public function register() {
  9. $this->name = lang_get( 'plugin_SourceSVN_title' );
  10. $this->description = lang_get( 'plugin_SourceSVN_description' );
  11. $this->version = '0.16';
  12. $this->requires = array(
  13. 'MantisCore' => '1.2.0',
  14. 'Source' => '0.16',
  15. );
  16. $this->author = 'John Reese';
  17. $this->contact = 'jreese@leetcode.net';
  18. $this->url = 'http://leetcode.net';
  19. }
  20. public function config() {
  21. return array(
  22. 'svnpath' => '',
  23. 'svnargs' => '',
  24. 'svnssl' => false,
  25. 'winstart' => false,
  26. );
  27. }
  28. public function errors() {
  29. return array(
  30. 'SVNPathInvalid' => 'Path to Subversion binary invalid or inaccessible',
  31. );
  32. }
  33. public $type = 'svn';
  34. public $configuration = true;
  35. public function show_type() {
  36. return lang_get( 'plugin_SourceSVN_svn' );
  37. }
  38. public function show_changeset( $p_repo, $p_changeset ) {
  39. return "$p_changeset->branch r$p_changeset->revision";
  40. }
  41. public function show_file( $p_repo, $p_changeset, $p_file ) {
  42. return $p_file->action . ' - ' . $p_file->filename;
  43. }
  44. public function url_repo( $p_repo, $p_changeset=null ) {}
  45. public function url_changeset( $p_repo, $p_changeset ) {}
  46. public function url_file( $p_repo, $p_changeset, $p_file ) {}
  47. public function url_diff( $p_repo, $p_changeset, $p_file ) {}
  48. public function update_repo_form( $p_repo ) {
  49. $t_svn_username = isset( $p_repo->info['svn_username'] ) ? $p_repo->info['svn_username'] : '';
  50. $t_svn_password = isset( $p_repo->info['svn_password'] ) ? $p_repo->info['svn_password'] : '';
  51. $t_standard_repo = isset( $p_repo->info['standard_repo'] ) ? $p_repo->info['standard_repo'] : '';
  52. $t_trunk_path = isset( $p_repo->info['trunk_path'] ) ? $p_repo->info['trunk_path'] : '';
  53. $t_branch_path = isset( $p_repo->info['branch_path'] ) ? $p_repo->info['branch_path'] : '';
  54. $t_tag_path = isset( $p_repo->info['tag_path'] ) ? $p_repo->info['tag_path'] : '';
  55. $t_ignore_paths = isset( $p_repo->info['ignore_paths'] ) ? $p_repo->info['ignore_paths'] : '';
  56. ?>
  57. <tr <?php echo helper_alternate_class() ?>>
  58. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_svn_username' ) ?></td>
  59. <td><input name="svn_username" maxlength="250" size="40" value="<?php echo string_attribute( $t_svn_username ) ?>"/></td>
  60. </tr>
  61. <tr <?php echo helper_alternate_class() ?>>
  62. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_svn_password' ) ?></td>
  63. <td><input name="svn_password" maxlength="250" size="40" value="<?php echo string_attribute( $t_svn_password ) ?>"/></td>
  64. </tr>
  65. <tr <?php echo helper_alternate_class() ?>>
  66. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_standard_repo' ) ?></td>
  67. <td><input name="standard_repo" type="checkbox" <?php echo ($t_standard_repo ? 'checked="checked"' : '') ?>/></td>
  68. </tr>
  69. <tr <?php echo helper_alternate_class() ?>>
  70. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_trunk_path' ) ?></td>
  71. <td><input name="trunk_path" maxlength="250" size="40" value="<?php echo string_attribute( $t_trunk_path ) ?>"/></td>
  72. </tr>
  73. <tr <?php echo helper_alternate_class() ?>>
  74. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_branch_path' ) ?></td>
  75. <td><input name="branch_path" maxlength="250" size="40" value="<?php echo string_attribute( $t_branch_path ) ?>"/></td>
  76. </tr>
  77. <tr <?php echo helper_alternate_class() ?>>
  78. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_tag_path' ) ?></td>
  79. <td><input name="tag_path" maxlength="250" size="40" value="<?php echo string_attribute( $t_tag_path ) ?>"/></td>
  80. </tr>
  81. <tr <?php echo helper_alternate_class() ?>>
  82. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_ignore_paths' ) ?></td>
  83. <td><input name="ignore_paths" type="checkbox" <?php echo ($t_ignore_paths ? 'checked="checked"' : '') ?>/></td>
  84. </tr>
  85. <?php
  86. }
  87. public function update_repo( $p_repo ) {
  88. $p_repo->info['svn_username'] = gpc_get_string( 'svn_username' );
  89. $p_repo->info['svn_password'] = gpc_get_string( 'svn_password' );
  90. $p_repo->info['standard_repo'] = gpc_get_bool( 'standard_repo', false );
  91. $p_repo->info['trunk_path'] = gpc_get_string( 'trunk_path' );
  92. $p_repo->info['branch_path'] = gpc_get_string( 'branch_path' );
  93. $p_repo->info['tag_path'] = gpc_get_string( 'tag_path' );
  94. $p_repo->info['ignore_paths'] = gpc_get_bool( 'ignore_paths', false );
  95. return $p_repo;
  96. }
  97. private static $config_form_handled = false;
  98. public function update_config_form() {
  99. # Prevent more than one SVN class from outputting form elements.
  100. if ( !SourceSVNPlugin::$config_form_handled ) {
  101. SourceSVNPlugin::$config_form_handled = true;
  102. $t_svnpath = config_get( 'plugin_SourceSVN_svnpath', '' );
  103. $t_svnargs = config_get( 'plugin_SourceSVN_svnargs', '' );
  104. $t_svnssl = config_get( 'plugin_SourceSVN_svnssl', '' );
  105. $t_winstart = config_get( 'plugin_SourceSVN_winstart', '' );
  106. ?>
  107. <tr <?php echo helper_alternate_class() ?>>
  108. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_svnpath' ) ?></td>
  109. <td><input name="plugin_SourceSVN_svnpath" value="<?php echo string_attribute( $t_svnpath ) ?>" size="40"/></td>
  110. </tr>
  111. <tr <?php echo helper_alternate_class() ?>>
  112. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_svnargs' ) ?></td>
  113. <td><input name="plugin_SourceSVN_svnargs" value="<?php echo string_attribute( $t_svnargs ) ?>" size="40"/></td>
  114. </tr>
  115. <tr <?php echo helper_alternate_class() ?>>
  116. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_svnssl' ) ?></td>
  117. <td><input name="plugin_SourceSVN_svnssl" type="checkbox" <?php if ( $t_svnssl ) echo 'checked="checked"' ?>/></td>
  118. </tr>
  119. <tr <?php echo helper_alternate_class() ?>>
  120. <td class="category"><?php echo lang_get( 'plugin_SourceSVN_winstart' ) ?></td>
  121. <td><input name="plugin_SourceSVN_winstart" type="checkbox" <?php if ( $t_winstart ) echo 'checked="checked"' ?>/></td>
  122. </tr>
  123. <?php
  124. }
  125. }
  126. public function update_config() {
  127. # Prevent more than one SVN class from handling form elements.
  128. if ( !SourceSVNPlugin::$config_form_handled ) {
  129. SourceSVNPlugin::$config_form_handled = true;
  130. $f_svnpath = gpc_get_string( 'plugin_SourceSVN_svnpath', '' );
  131. $t_svnpath = config_get( 'plugin_SourceSVN_svnpath', '' );
  132. $f_svnpath = rtrim( $f_svnpath, DIRECTORY_SEPARATOR );
  133. if ( $f_svnpath != $t_svnpath ) {
  134. if ( is_blank( $f_svnpath ) ) {
  135. config_delete( 'plugin_SourceSVN_svnpath' );
  136. } else {
  137. # be sure that the path is valid
  138. if ( ( $t_binary = SourceSVNPlugin::svn_binary( $f_svnpath, true ) ) != 'svn' ) {
  139. config_set( 'plugin_SourceSVN_svnpath', $f_svnpath );
  140. } else {
  141. plugin_error( 'SVNPathInvalid', ERROR );
  142. }
  143. }
  144. }
  145. $f_svnargs = gpc_get_string( 'plugin_SourceSVN_svnargs', '' );
  146. if ( $f_svnargs != config_get( 'plugin_SourceSVN_svnargs', '' ) ) {
  147. config_set( 'plugin_SourceSVN_svnargs', $f_svnargs );
  148. }
  149. $f_svnssl = gpc_get_bool( 'plugin_SourceSVN_svnssl', false );
  150. if ( $f_svnssl != config_get( 'plugin_SourceSVN_svnssl', false ) ) {
  151. config_set( 'plugin_SourceSVN_svnssl', $f_svnssl );
  152. }
  153. $f_winstart = gpc_get_bool( 'plugin_SourceSVN_winstart', false );
  154. if ( $f_winstart != config_get( 'plugin_SourceSVN_winstart', false ) ) {
  155. config_set( 'plugin_SourceSVN_winstart', $f_winstart );
  156. }
  157. }
  158. }
  159. public function commit( $p_repo, $p_data ) {
  160. if ( preg_match( '/(\d+)/', $p_data, $p_matches ) ) {
  161. $svn = $this->svn_call( $p_repo );
  162. $t_url = $p_repo->url;
  163. $t_revision = $p_matches[1];
  164. $t_svnlog = explode( "\n", shell_exec( "$svn log -v $t_url -r$t_revision" ) );
  165. if ( SourceChangeset::exists( $p_repo->id, $t_revision ) ) {
  166. echo "Revision $t_revision already committed!\n";
  167. return null;
  168. }
  169. return $this->process_svn_log( $p_repo, $t_svnlog );
  170. }
  171. }
  172. public function import_full( $p_repo ) {
  173. $this->check_svn();
  174. $svn = $this->svn_call( $p_repo );
  175. $t_changeset_table = plugin_table( 'changeset', 'Source' );
  176. $t_max_query = "SELECT revision FROM $t_changeset_table
  177. WHERE repo_id=" . db_param() . '
  178. ORDER BY CAST( revision AS DECIMAL ) DESC';
  179. $t_db_revision = db_result( db_query_bound( $t_max_query, array( $p_repo->id ), 1 ) );
  180. $t_url = $p_repo->url;
  181. $t_rev = ( false === $t_db_revision ? 0 : $t_db_revision + 1 );
  182. echo "<pre>";
  183. while( true ) {
  184. echo "Requesting svn log for {$p_repo->name} starting with revision {$t_rev}...\n";
  185. $t_svnlog = explode( "\n", shell_exec( "$svn log -v -r $t_rev:HEAD --limit 200 $t_url" ) );
  186. $t_changesets = $this->process_svn_log( $p_repo, $t_svnlog );
  187. # if an array is returned, processing is done
  188. if ( is_array( $t_changesets ) ) {
  189. echo "</pre>";
  190. return $t_changesets;
  191. # if a number is returned, repeat from given revision
  192. } else if ( is_numeric( $t_changesets ) ) {
  193. $t_rev = $t_changesets + 1;
  194. }
  195. }
  196. echo "</pre>";
  197. }
  198. public function import_latest( $p_repo ) {
  199. return $this->import_full( $p_repo );
  200. }
  201. private function check_svn() {
  202. $svn = self::svn_binary();
  203. if ( is_blank( shell_exec( "$svn help" ) ) ) {
  204. trigger_error( ERROR_GENERIC, ERROR );
  205. }
  206. }
  207. private function svn_call( $p_repo=null ) {
  208. static $s_call;
  209. # Get a full binary call, including configured parameters
  210. if ( is_null( $s_call ) ) {
  211. $s_call = self::svn_binary() . ' --non-interactive';
  212. if ( config_get( 'plugin_SourceSVN_svnssl', false ) ) {
  213. $s_call .= ' --trust-server-cert';
  214. }
  215. $t_svnargs = config_get( 'plugin_SourceSVN_svnargs', '' );
  216. if ( !is_blank( $t_svnargs ) ) {
  217. $s_call .= " $t_svnargs";
  218. }
  219. }
  220. # If not given a repo, just return the base SVN binary
  221. if ( is_null( $p_repo ) ) {
  222. return $s_call;
  223. }
  224. $t_call = $s_call;
  225. # With a repo, add arguments for repo info
  226. $t_username = escapeshellarg($p_repo->info['svn_username']);
  227. $t_password = escapeshellarg($p_repo->info['svn_password']);
  228. if ( !is_blank( $t_username ) ) {
  229. $t_call .= ' --username ' . $t_username;
  230. }
  231. if ( !is_blank( $t_password ) ) {
  232. $t_call .= ' --password ' . $t_password;
  233. }
  234. # Done
  235. return $t_call;
  236. }
  237. /**
  238. * Generate, validate, and cache the SVN binary path.
  239. * @param string Path to SVN
  240. * @param boolean Reset cached value
  241. * @return string SVN binary
  242. */
  243. public static function svn_binary( $p_path=null, $p_reset=false ) {
  244. static $s_binary;
  245. if ( is_null( $s_binary ) || $p_reset ) {
  246. if ( is_null( $p_path ) ) {
  247. $t_path = config_get( 'plugin_SourceSVN_svnpath', '' );
  248. } else {
  249. $t_path = $p_path;
  250. }
  251. if ( !is_blank( $t_path ) && is_dir( $t_path ) ) {
  252. # Linux / UNIX paths
  253. $t_binary = $t_path . DIRECTORY_SEPARATOR . 'svn';
  254. if ( is_file( $t_binary ) && is_executable( $t_binary ) ) {
  255. return $s_binary = $t_binary;
  256. }
  257. # Windows paths
  258. $t_binary = $t_path . DIRECTORY_SEPARATOR . 'svn.exe';
  259. if ( is_file( $t_binary ) && is_executable( $t_binary ) ) {
  260. if ( config_get( 'plugin_SourceSVN_winstart', '' ) ) {
  261. return "start /B /D \"{$t_path}\" svn.exe";
  262. } else {
  263. return $s_binary = $t_binary;
  264. }
  265. }
  266. }
  267. # Generic pathless call
  268. $s_binary = 'svn';
  269. }
  270. return $s_binary;
  271. }
  272. private function process_svn_log( $p_repo, $p_svnlog ) {
  273. $t_state = 0;
  274. $t_svnline = str_pad( '', 72, '-' );
  275. $t_changesets = array();
  276. $t_changeset = null;
  277. $t_comments = '';
  278. $t_count = 0;
  279. $t_trunk_path = $p_repo->info['trunk_path'];
  280. $t_branch_path = $p_repo->info['branch_path'];
  281. $t_tag_path = $p_repo->info['tag_path'];
  282. $t_ignore_paths = $p_repo->info['ignore_paths'];
  283. $t_discarded = false;
  284. echo "Processing svn log...\n";
  285. foreach( $p_svnlog as $t_line ) {
  286. # starting state, do nothing
  287. if ( 0 == $t_state ) {
  288. if ( $t_line == $t_svnline ) {
  289. $t_state = 1;
  290. }
  291. # Changeset info
  292. } elseif ( 1 == $t_state && preg_match( '/^r([0-9]+) \| ([^|]+) \| ([0-9\-]+) ([0-9:]+)/', $t_line, $t_matches ) ) {
  293. if ( !is_null( $t_changeset ) ) {
  294. if ( !is_blank( $t_changeset->branch ) ) {
  295. $t_changeset->save();
  296. $t_changesets[] = $t_changeset;
  297. } else {
  298. $t_discarded = $t_changeset->revision;
  299. }
  300. }
  301. $t_changeset = new SourceChangeset( $p_repo->id, $t_matches[1], '', $t_matches[3] . ' ' . $t_matches[4], $t_matches[2], '' );
  302. $t_state = 2;
  303. # Changed paths
  304. } elseif ( 2 == $t_state ) {
  305. if ( strlen( $t_line ) == 0 ) {
  306. $t_state = 3;
  307. } else {
  308. if ( preg_match( '/^\s+([a-zA-Z])\s+([^\(]+)(?: \(from [^\)]+\))?/', $t_line, $t_matches ) ) {
  309. switch( $t_matches[1] ) {
  310. case 'A': $t_action = 'add'; break;
  311. case 'D': $t_action = 'rm'; break;
  312. case 'M': $t_action = 'mod'; break;
  313. case 'R': $t_action = 'mv'; break;
  314. default: $t_action = $t_matches[1];
  315. }
  316. $t_file = new SourceFile( $t_changeset->id, '', trim( $t_matches[2] ), $t_action );
  317. $t_changeset->files[] = $t_file;
  318. # Branch-checking
  319. if ( is_blank( $t_changeset->branch) ) {
  320. # Look for standard trunk/branches/tags information
  321. if ( $p_repo->info['standard_repo'] ) {
  322. if ( preg_match( '@/(?:(trunk)|(?:branches|tags)/([^/]+))@i', $t_file->filename, $t_matches ) ) {
  323. if ( !is_blank( $t_matches[1] ) ) {
  324. $t_changeset->branch = $t_matches[1];
  325. } else {
  326. $t_changeset->branch = $t_matches[2];
  327. }
  328. }
  329. } else {
  330. # Look for non-standard trunk path
  331. if ( !is_blank( $t_trunk_path ) && preg_match( '@^/*(' . $t_trunk_path . ')@i', $t_file->filename, $t_matches ) ) {
  332. $t_changeset->branch = $t_matches[1];
  333. # Look for non-standard branch path
  334. } else if ( !is_blank( $t_branch_path ) && preg_match( '@^/*(?:' . $t_branch_path . ')/([^/]+)@i', $t_file->filename, $t_matches ) ) {
  335. $t_changeset->branch = $t_matches[1];
  336. # Look for non-standard tag path
  337. } else if ( !is_blank( $t_tag_path ) && preg_match( '@^/*(?:' . $t_tag_path . ')/([^/]+)@i', $t_file->filename, $t_matches ) ) {
  338. $t_changeset->branch = $t_matches[1];
  339. # Fall back to just using the root folder as the branch name
  340. } else if ( !$t_ignore_paths && preg_match( '@/([^/]+)@', $t_file->filename, $t_matches ) ) {
  341. $t_changeset->branch = $t_matches[1];
  342. }
  343. }
  344. }
  345. }
  346. }
  347. # Changeset comments
  348. } elseif ( 3 == $t_state ) {
  349. if ( $t_line == $t_svnline ) {
  350. $t_state = 1;
  351. } else {
  352. if ( !is_blank($t_changeset->message) ) {
  353. $t_changeset->message .= "\n$t_line";
  354. } else {
  355. $t_changeset->message .= $t_line;
  356. }
  357. }
  358. # Should only happen at the end...
  359. } else {
  360. break;
  361. }
  362. }
  363. if ( !is_null( $t_changeset ) ) {
  364. echo "Parsed to revision {$t_changeset->revision}.\n";
  365. $t_changeset->save();
  366. $t_changesets[] = $t_changeset;
  367. } else {
  368. echo "No revisions parsed.\n";
  369. }
  370. return $t_changesets;
  371. }
  372. }