subversion.class.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. <?php
  2. class subversionRepo
  3. {
  4. public $client;
  5. public $root;
  6. public $account;
  7. public $password;
  8. public $ssh;
  9. public $remote;
  10. public $encoding;
  11. public $svnVersion;
  12. public $repo;
  13. /**
  14. * Construct
  15. *
  16. * @param string $client
  17. * @param string $root
  18. * @param string $account
  19. * @param string $password
  20. * @param string $encoding
  21. * @param object $repo
  22. * @access public
  23. * @return void
  24. */
  25. public function __construct($client, $root, $account, $password, $encoding = 'UTF-8', $repo = null)
  26. {
  27. putenv('LC_ALL=C');
  28. $this->root = str_replace(array('%3A', '%2F', '+'), array(':', '/', ' '), urlencode(rtrim($root, '/')));
  29. $this->account = $account;
  30. $this->password = $password;
  31. $this->encoding = $encoding;
  32. $this->repo = $repo;
  33. $this->ssh = (stripos($this->root, 'svn') === 0 or stripos($this->root, 'https') === 0) ? true : false;
  34. $this->remote = !(stripos($this->root, 'file') === 0);
  35. $this->client = $this->remote ? $client . " --username @account@ --password @password@" : $client;
  36. if($this->encoding == 'utf-8') $this->encoding = 'gbk';
  37. $this->svnVersion = $this->getSVNVersion($client);
  38. }
  39. /**
  40. * List files.
  41. *
  42. * @param string $path
  43. * @param string $revision
  44. * @access public
  45. * @return array
  46. */
  47. public function ls($path, $revision = 'HEAD')
  48. {
  49. if(!scm::checkRevision($revision)) return array();
  50. $resourcePath = $path;
  51. $path = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($path)) . '"';
  52. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'ls', "-r $revision --xml")));
  53. $list = execCmd($cmd, 'string', $result);
  54. if($result)
  55. {
  56. $path = '"' . $this->root . '/' . $resourcePath . '"';
  57. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'ls', "-r $revision --xml")));
  58. $list = execCmd($cmd, 'string', $result);
  59. if($result) $list = '';
  60. }
  61. $listObject = simplexml_load_string($list);
  62. if(!empty($list) and empty($listObject))
  63. {
  64. $list = helper::convertEncoding($list, $this->encoding, 'utf-8');
  65. $listObject = simplexml_load_string($list);
  66. }
  67. if(!empty($listObject->list->entry)) $listObject = $listObject->list->entry;
  68. $infos = array();
  69. if(empty($listObject)) return $infos;
  70. foreach($listObject as $list)
  71. {
  72. $info = new stdclass();
  73. $info->size = 0;
  74. $info->name = (string)$list->name;
  75. $info->path = $resourcePath . '/' . $list->name;
  76. $info->kind = (string)$list['kind'];
  77. $info->revision = (int)$list->commit['revision'];
  78. $info->account = (string)$list->commit->author;
  79. $info->date = date('Y-m-d H:i:s', strtotime($list->commit->date));
  80. $info->comment = '';
  81. if($info->kind == 'file') $info->size = (int)$list->size > 1024 ? round((int)$list->size / 1024, 2) . "KB" : (int)$list->size . 'Bytes';
  82. $infos[] = $info;
  83. }
  84. /* Sort by kind */
  85. foreach($infos as $key => $info) $kind[$key] = $info->kind;
  86. if($infos) array_multisort($kind, SORT_ASC, $infos);
  87. return $infos;
  88. }
  89. /**
  90. * Get tags.
  91. *
  92. * @param string $path
  93. * @param string $revision
  94. * @param bool $onlyDir
  95. * @access public
  96. * @return array
  97. */
  98. public function tags($path, $revision = 'HEAD', $onlyDir = true)
  99. {
  100. if(!scm::checkRevision($revision)) return array();
  101. $infos = $this->ls($path, $revision);
  102. $dirs = array();
  103. foreach($infos as $info)
  104. {
  105. if($onlyDir and $info->kind != 'dir') continue;
  106. $dirs[$info->date][$info->name] = $info->name;
  107. }
  108. ksort($dirs);
  109. $tags = array();
  110. $trimed = trim($path, '/');
  111. $prefix = empty($trimed) ? '/' : '/' . $trimed . '/';
  112. foreach($dirs as $dirNames)
  113. {
  114. ksort($dirNames);
  115. foreach($dirNames as $dirName)
  116. {
  117. $dirPath = $prefix . $dirName;
  118. $tags[$dirPath] = $dirName;
  119. }
  120. }
  121. return $tags;
  122. }
  123. /**
  124. * Get branch.
  125. *
  126. * @access public
  127. * @return array
  128. */
  129. public function branch()
  130. {
  131. return array();
  132. }
  133. /**
  134. * Get last log.
  135. *
  136. * @param string $path
  137. * @param int $count
  138. * @access public
  139. * @return array
  140. */
  141. public function getLastLog($path, $count = 10)
  142. {
  143. $resourcePath = $path;
  144. $path = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($path)) . '"';
  145. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'log', "--limit $count --xml")));
  146. $comments = execCmd($cmd, 'string', $result);
  147. if($result)
  148. {
  149. $path = '"' . $this->root . '/' . $resourcePath . '"';
  150. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'log', "--limit $count --xml")));
  151. $comments = execCmd($cmd, 'string', $result);
  152. if($result) $comments = '';
  153. }
  154. $parsedComments = simplexml_load_string($comments);
  155. if(!empty($comments) and empty($parsedComments))
  156. {
  157. $comments = helper::convertEncoding($comments, $this->encoding, 'utf-8');
  158. $parsedComments = simplexml_load_string($comments);
  159. }
  160. $logs = array();
  161. foreach($parsedComments->logentry as $entry)
  162. {
  163. $log = new stdclass();
  164. $log->committer = (string)$entry->author;
  165. $log->revision = (int)$entry['revision'];
  166. $log->comment = trim((string)$entry->msg);
  167. $log->time = date('Y-m-d H:i:s', strtotime($entry->date));
  168. $log->change = array();
  169. $logs[] = $log;
  170. unset($log);
  171. }
  172. /* Sort by kind */
  173. foreach($logs as $key => $log) $revision[$key] = $log->revision;
  174. if($logs) array_multisort($revision, SORT_DESC, $logs);
  175. return $logs;
  176. }
  177. /**
  178. * Get log.
  179. *
  180. * @param string $path
  181. * @param int $fromRevision
  182. * @param string $toRevision
  183. * @param int $count
  184. * @param bool $quiet
  185. * @access public
  186. * @return array
  187. */
  188. public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0, $quiet = false)
  189. {
  190. if(!scm::checkRevision($fromRevision)) return array();
  191. if(!scm::checkRevision($toRevision)) return array();
  192. $resourcePath = $path;
  193. $count = $count == 0 ? '' : "--limit $count";
  194. $param = $quiet ? '-q' : '-v';
  195. $path = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($path)) . '"';
  196. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'log', "$count $param -r $fromRevision:$toRevision --xml")));
  197. $comments = execCmd($cmd, 'string', $result);
  198. if($result)
  199. {
  200. $path = '"' . $this->root . '/' . $resourcePath . '"';
  201. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'log', "$count $param -r $fromRevision:$toRevision --xml")));
  202. $comments = execCmd($cmd, 'string', $result);
  203. if($result) $comments = '';
  204. }
  205. $parsedComments = simplexml_load_string($comments);
  206. if(!empty($comments) and empty($parsedComments))
  207. {
  208. $comments = helper::convertEncoding($comments, $this->encoding, 'utf-8');
  209. $parsedComments = simplexml_load_string($comments);
  210. }
  211. $logs = array();
  212. $revision = array();
  213. if(empty($parsedComments->logentry)) return $logs;
  214. foreach($parsedComments->logentry as $entry)
  215. {
  216. $log = new stdclass();
  217. $log->committer = (string)$entry->author;
  218. $log->revision = (int)$entry['revision'];
  219. $log->comment = trim((string)$entry->msg);
  220. $log->time = date('Y-m-d H:i:s', strtotime($entry->date));
  221. $log->change = array();
  222. if(!empty($entry->paths))
  223. {
  224. foreach($entry->paths->path as $path)
  225. {
  226. $pathInfo = array();
  227. foreach($path->attributes() as $attr => $value) $pathInfo[$attr] = (string)$value;
  228. $log->change[(string)$path] = $pathInfo;
  229. }
  230. }
  231. if(in_array($log->revision, $revision)) continue;
  232. $logs[] = $log;
  233. $revision[] = $log->revision;
  234. unset($log);
  235. }
  236. /* Sort by kind */
  237. if($logs) array_multisort($revision, SORT_DESC, $logs);
  238. return $logs;
  239. }
  240. /**
  241. * Blame file.
  242. *
  243. * @param string $path
  244. * @param int $revision
  245. * @param bool $showComment
  246. * @access public
  247. * @return array
  248. */
  249. public function blame($path, $revision, $showComment = true)
  250. {
  251. if(!scm::checkRevision($revision)) return array();
  252. $resourcePath = $path;
  253. $path = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($path)) . '"';
  254. $file = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'cat', "-r $revision")));
  255. $blame = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'blame', "-r $revision --xml")));
  256. $output = execCmd($blame, 'string', $result);
  257. if($result)
  258. {
  259. $path = '"' . $this->root . '/' . $resourcePath . '"';
  260. $file = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'cat', "-r $revision")));
  261. $blame = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'blame', "-r $revision --xml")));
  262. $output = execCmd($blame, 'string', $result);
  263. if($result) return array();
  264. }
  265. $content = execCmd($file, 'array');
  266. $parsedResult = simplexml_load_string($output);
  267. if(!empty($output) and empty($parsedResult))
  268. {
  269. $output = helper::convertEncoding($output, $this->encoding, 'utf-8');
  270. $parsedResult = simplexml_load_string($output);
  271. }
  272. $blames = array();
  273. $revLine = 0;
  274. $revision = '';
  275. if($parsedResult->target->entry)
  276. {
  277. foreach($parsedResult->target->entry as $line)
  278. {
  279. if($line->commit['revision'] != $revision)
  280. {
  281. $blame = array();
  282. $blame['revision'] = (int)$line->commit['revision'];
  283. $blame['committer'] = (string)$line->commit->author;
  284. $blame['time'] = date('Y-m-d H:i:s', strtotime($line->commit->date));
  285. $blame['line'] = (int)$line['line-number'];
  286. $blame['lines'] = 1;
  287. $blame['content'] = isset($content[$blame['line'] - 1]) ? $content[$blame['line'] - 1] : '';
  288. $blame['message'] = '';
  289. if($showComment)
  290. {
  291. $log = $this->log('', $blame['revision'], 'HEAD', 1);
  292. $blame['message'] = $log[0]->comment;
  293. }
  294. $revision = $blame['revision'];
  295. $revLine = $blame['line'];
  296. $blames[$revLine] = $blame;
  297. }
  298. else
  299. {
  300. $blame = array();
  301. $blame['line'] = (int)$line['line-number'];
  302. $blame['content'] = zget($content, $blame['line'] - 1, '');
  303. $blames[$blame['line']] = $blame;
  304. $blames[$revLine]['lines'] ++;
  305. }
  306. }
  307. }
  308. return $blames;
  309. }
  310. /**
  311. * Diff file.
  312. *
  313. * @param string $path
  314. * @param int $fromRevision
  315. * @param int $toRevision
  316. * @access public
  317. * @return array
  318. */
  319. public function diff($path, $fromRevision, $toRevision)
  320. {
  321. if(!scm::checkRevision($fromRevision)) return array();
  322. if(!scm::checkRevision($toRevision)) return array();
  323. $resourcePath = $path;
  324. if(empty($toRevision)) $fromRevision = '';
  325. if($fromRevision == '^') $fromRevision = $toRevision - 1;
  326. $path = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($path)) . '"';
  327. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'diff', "-r $fromRevision:$toRevision")));
  328. $lines = execCmd($cmd, 'array', $result);
  329. if($result)
  330. {
  331. $path = '"' . $this->root . '/' . $resourcePath . '"';
  332. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'diff', "-r $fromRevision:$toRevision")));
  333. $lines = execCmd($cmd, 'array', $result);
  334. }
  335. return $lines;
  336. }
  337. /**
  338. * Cat file.
  339. *
  340. * @param string $entry
  341. * @param string $revision
  342. * @access public
  343. * @return string
  344. */
  345. public function cat($entry, $revision = 'HEAD')
  346. {
  347. if(!scm::checkRevision($revision)) return false;
  348. $resourcePath = $entry;
  349. $entry = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($entry)) . '"';
  350. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($entry, 'cat', "-r $revision")));
  351. $content = execCmd($cmd, 'string', $result);
  352. if($result)
  353. {
  354. $entry = '"' . $this->root . '/' . $resourcePath . '"';
  355. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($entry, 'cat', "-r $revision")));
  356. $content = execCmd($cmd, 'string', $result);
  357. }
  358. return $content;
  359. }
  360. /**
  361. * Get info.
  362. *
  363. * @param string $entry
  364. * @param string $revision
  365. * @access public
  366. * @return object
  367. */
  368. public function info($entry, $revision = 'HEAD')
  369. {
  370. if(!scm::checkRevision($revision)) return false;
  371. $resourcePath = $entry;
  372. $entry = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($entry)) . '"';
  373. $svnInfo = $this->replaceAuth(escapeCmd($this->buildCMD($entry, 'info', "-r $revision --xml")));
  374. $svninfo = execCmd($svnInfo, 'string', $result);
  375. if($result)
  376. {
  377. $entry = '"' . $this->root . '/' . $resourcePath . '"';
  378. $svnInfo = $this->replaceAuth(escapeCmd($this->buildCMD($entry, 'info', "-r $revision --xml")));
  379. $svninfo = execCmd($svnInfo, 'string', $result);
  380. if($result) $svninfo = '';
  381. }
  382. $parsedSvnInfo = simplexml_load_string($svninfo);
  383. if(!empty($svninfo) and empty($parsedSvnInfo))
  384. {
  385. $svninfo = helper::convertEncoding($svninfo, $this->encoding, 'utf-8');
  386. $parsedSvnInfo = simplexml_load_string($svninfo);
  387. }
  388. $info = new stdclass();
  389. $info->kind = empty($parsedSvnInfo->entry['kind']) ? '' : (string)$parsedSvnInfo->entry['kind'];
  390. $info->path = empty($parsedSvnInfo->entry['path']) ? '' : (string)$parsedSvnInfo->entry['path'];
  391. $info->revision = empty($parsedSvnInfo->entry['revision']) ? '' : (int)$parsedSvnInfo->entry['revision'];
  392. $info->cRevision = empty($parsedSvnInfo->entry->commit['revision']) ? '' : (int)$parsedSvnInfo->entry->commit['revision'];
  393. $info->root = empty($parsedSvnInfo->entry->repository->root) ? '' : (string)$parsedSvnInfo->entry->repository->root;
  394. return $info;
  395. }
  396. /**
  397. * Exec svn cmd.
  398. *
  399. * @param string $cmd
  400. * @access public
  401. * @return array
  402. */
  403. public function exec($cmd)
  404. {
  405. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD('', $cmd, '')));
  406. return execCmd($cmd, 'array');
  407. }
  408. /**
  409. * Parse diff.
  410. *
  411. * @param array $lines
  412. * @access public
  413. * @return array
  414. */
  415. public function parseDiff($lines)
  416. {
  417. if(empty($lines)) return array();
  418. $diffs = array();
  419. $num = count($lines);
  420. $endLine = end($lines);
  421. if(strpos($endLine, '\ No newline at end of file') === 0) $num -= 1;
  422. for($i = 0; $i < $num; $i ++)
  423. {
  424. $diffFile = new stdclass();
  425. if(strpos($lines[$i], "Index: ") === 0)
  426. {
  427. $fileName = str_replace('Index: ', '', $lines[$i]);
  428. $diffFile->fileName = $fileName;
  429. for($i++; $i < $num; $i ++)
  430. {
  431. $diff = new stdclass();
  432. if(strpos($lines[$i], '+++', 0) !== false) continue;
  433. if(strpos($lines[$i], '---', 0) !== false) continue;
  434. if(strpos($lines[$i], '======', 0) !== false) continue;
  435. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@\\s*($)/A', $lines[$i]))
  436. {
  437. $startLines = trim(str_replace(array('@', '+', '-'), '', $lines[$i]));
  438. list($oldStartLine, $newStartLine) = explode(' ', $startLines);
  439. list($diff->oldStartLine) = explode(',', $oldStartLine);
  440. list($diff->newStartLine) = explode(',', $newStartLine);
  441. $oldCurrentLine = $diff->oldStartLine;
  442. $newCurrentLine = $diff->newStartLine;
  443. $newLines = array();
  444. for($i++; $i < $num; $i ++)
  445. {
  446. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@\\s*($)/A', $lines[$i]))
  447. {
  448. $i --;
  449. break;
  450. }
  451. if(strpos($lines[$i], "Index: ") === 0) break;
  452. $line = $lines[$i];
  453. if(strpos($line, '\ No newline at end of file') === 0)continue;
  454. $sign = empty($line) ? '' : $line[0];
  455. $type = $sign != '-' ? $sign == '+' ? 'new' : 'all' : 'old';
  456. if($sign == '-' || $sign == '+') $line = substr_replace($line, ' ', 1, 0);
  457. $newLine = new stdclass();
  458. $newLine->type = $type;
  459. $newLine->oldlc = $type != 'new' ? $oldCurrentLine : '';
  460. $newLine->newlc = $type != 'old' ? $newCurrentLine : '';
  461. $newLine->line = $line;
  462. if($type != 'new') $oldCurrentLine++;
  463. if($type != 'old') $newCurrentLine++;
  464. $newLines[] = $newLine;
  465. }
  466. $diff->lines = $newLines;
  467. $diffFile->contents[] = $diff;
  468. }
  469. if(isset($lines[$i]) and strpos($lines[$i], "Index: ") === 0)
  470. {
  471. $i --;
  472. break;
  473. }
  474. }
  475. $diffs[] = $diffFile;
  476. }
  477. }
  478. return $diffs;
  479. }
  480. /**
  481. * Get commit count.
  482. *
  483. * @param int $commits
  484. * @param int $lastVersion
  485. * @access public
  486. * @return int
  487. */
  488. public function getCommitCount($commits = 0, $lastVersion = 0)
  489. {
  490. if(!scm::checkRevision($lastVersion)) return false;
  491. if(empty($commits)) $commits = 0;
  492. if(empty($lastVersion)) $lastVersion = 0;
  493. $lastRevision = $this->getLatestRevision();
  494. $count = 10000;
  495. $from = $lastVersion;
  496. while(true)
  497. {
  498. $logs = $this->log('', $from, $lastRevision, empty($from) ? $count : $count + 1, $quiet = true);
  499. if(empty($logs)) break;
  500. $num = empty($from) ? count($logs) : count($logs) - 1;
  501. $commits += $num;
  502. $from = reset($logs);
  503. $from = $from->revision;
  504. if($from == $lastRevision) break;
  505. }
  506. return $commits;
  507. }
  508. /**
  509. * Get first revision.
  510. *
  511. * @access public
  512. * @return int
  513. */
  514. public function getFirstRevision()
  515. {
  516. $logs = $this->log('', 0, 'HEAD', 1, $quiet = true);
  517. if(empty($logs)) return 0;
  518. $firstLog = end($logs);
  519. return $firstLog->revision;
  520. }
  521. /**
  522. * Get latest revision.
  523. *
  524. * @access public
  525. * @return int
  526. */
  527. public function getLatestRevision()
  528. {
  529. $info = $this->info('');
  530. return $info->cRevision;
  531. }
  532. /**
  533. * Get commits.
  534. *
  535. * @param string $version
  536. * @param int $count
  537. * @access public
  538. * @return array
  539. */
  540. public function getCommits($version = '', $count = 0)
  541. {
  542. if(!scm::checkRevision($version)) return array();
  543. $count = $count == 0 ? '' : "--limit $count";
  544. $path = '"' . $this->root . '"';
  545. if(stripos($this->root, 'https') === 0 or stripos($this->root, 'svn') === 0)
  546. {
  547. $comments = str_replace("\\", "/", "$this->client log $count -v -r $version:HEAD --non-interactive --trust-server-cert-failures=cn-mismatch --trust-server-cert --no-auth-cache --xml $path");
  548. if($this->svnVersion and version_compare($this->svnVersion, '1.9', '<')) $comments = str_replace("\\", "/", "$this->client log $count -v -r $version:HEAD --non-interactive --trust-server-cert --no-auth-cache --xml $path");
  549. }
  550. else
  551. {
  552. $comments = str_replace("\\", "/", "$this->client log $count -v -r $version:HEAD --no-auth-cache --xml $path");
  553. }
  554. $comments = $this->replaceAuth(escapeCmd($comments));
  555. $comments = execCmd($comments, 'string', $result);
  556. if($result) $comments = '';
  557. $parsedComments = simplexml_load_string($comments);
  558. if(!empty($comments) and empty($parsedComments))
  559. {
  560. $comments = helper::convertEncoding($comments, $this->encoding, 'utf-8');
  561. $parsedComments = simplexml_load_string($comments);
  562. }
  563. $logs = array();
  564. foreach($parsedComments->logentry as $entry)
  565. {
  566. $parsedLog = new stdClass();
  567. $parsedLog->committer = (string)$entry->author;
  568. $parsedLog->revision = (int)$entry['revision'];
  569. $parsedLog->comment = trim((string)$entry->msg);
  570. $parsedLog->time = date('Y-m-d H:i:s', strtotime($entry->date));
  571. $logs['commits'][$parsedLog->revision] = $parsedLog;
  572. $logs['files'][$parsedLog->revision] = array();
  573. if(!empty($entry->paths))
  574. {
  575. foreach($entry->paths->path as $file)
  576. {
  577. $parsedFile = new stdclass();
  578. $parsedFile->revision = $parsedLog->revision;
  579. $parsedFile->path = (string)$file;
  580. $parsedFile->type = (string)$file['kind'];
  581. $parsedFile->action = substr((string)$file['action'], 0, 1);
  582. if(isset($file['copyfrom-path'])) $parsedFile->copyfromPath = (string)$file['copyfrom-path'];
  583. if(isset($file['copyfrom-rev'])) $parsedFile->copyfromRev = (string)$file['copyfrom-rev'];
  584. $logs['files'][$parsedLog->revision][] = $parsedFile;
  585. }
  586. }
  587. }
  588. return $logs;
  589. }
  590. /**
  591. * Replace svn auth.
  592. *
  593. * @param string $cmd
  594. * @access public
  595. * @return string
  596. */
  597. public function replaceAuth($cmd)
  598. {
  599. return str_replace(array('@account@', '@password@'), array($this->account, $this->password), $cmd);
  600. }
  601. /**
  602. * Build command.
  603. *
  604. * @param string $path
  605. * @param string $action
  606. * @param string $param
  607. * @access public
  608. * @return string
  609. */
  610. public function buildCMD($path, $action, $param)
  611. {
  612. if($this->ssh)
  613. {
  614. $cmd = str_replace("\\", "/", "$this->client $action $param --non-interactive --trust-server-cert-failures=cn-mismatch --trust-server-cert --no-auth-cache $path");
  615. if($this->svnVersion and version_compare($this->svnVersion, '1.9', '<')) $cmd = str_replace("\\", "/", "$this->client $action $param --non-interactive --trust-server-cert --no-auth-cache $path");
  616. }
  617. else
  618. {
  619. $cmd = str_replace("\\", "/", "$this->client $action $param --no-auth-cache $path");
  620. }
  621. return $cmd;
  622. }
  623. /**
  624. * Get SVN version.
  625. *
  626. * @param string $client
  627. * @access public
  628. * @return string
  629. */
  630. public function getSVNVersion($client)
  631. {
  632. $versionCommand = "$client --version --quiet 2>&1";
  633. exec($versionCommand, $versionOutput, $versionResult);
  634. if($versionResult) return false;
  635. return end($versionOutput);
  636. }
  637. /**
  638. * Get download url.
  639. *
  640. * @param string $branch
  641. * @param string $savePath
  642. * @param string $ext
  643. * @access public
  644. * @return string
  645. */
  646. public function getDownloadUrl($branch = '', $savePath = '', $ext = 'zip')
  647. {
  648. global $app, $config;
  649. /* Get repo name. */
  650. $pathList = explode('/', trim($this->root, '/'));
  651. $repoDir = $savePath . DS . end($pathList);
  652. execCmd($this->replaceAuth(escapeCmd($this->buildCMD("$this->root $repoDir", 'export', ''))));
  653. $fileName = $savePath . DS . "{$this->repo->name}.zip";
  654. $app->loadClass('pclzip', true);
  655. $zip = new pclzip($fileName);
  656. $zip->create($repoDir, PCLZIP_OPT_REMOVE_PATH, $repoDir);
  657. $zfile = $app->loadClass('zfile');
  658. $zfile->removeDir($repoDir);
  659. return $config->webRoot . $app->getAppName() . 'data' . DS . 'repo' . DS . $this->repo->name . '.zip';
  660. }
  661. /**
  662. * List all files.
  663. *
  664. * @param string $path
  665. * @param string $revision
  666. * @param array $lists
  667. * @access public
  668. * @return array
  669. */
  670. public function getAllFiles($path = '', $revision = 'HEAD', &$lists = array())
  671. {
  672. if(!scm::checkRevision($revision)) return array();
  673. $resourcePath = $path;
  674. $path = '"' . $this->root . '/' . str_replace(array('%2F', '+'), array('/', ' '), urlencode($path)) . '"';
  675. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'ls', "-r $revision --xml")));
  676. $list = execCmd($cmd, 'string', $result);
  677. if($result)
  678. {
  679. $path = '"' . $this->root . '/' . $resourcePath . '"';
  680. $cmd = $this->replaceAuth(escapeCmd($this->buildCMD($path, 'ls', "-r $revision --xml")));
  681. $list = execCmd($cmd, 'string', $result);
  682. if($result) $list = '';
  683. }
  684. $listObject = simplexml_load_string($list);
  685. if(!empty($list) and empty($listObject))
  686. {
  687. $list = helper::convertEncoding($list, $this->encoding, 'utf-8');
  688. $listObject = simplexml_load_string($list);
  689. }
  690. if(!empty($listObject->list->entry)) $listObject = $listObject->list->entry;
  691. $infos = array();
  692. if(empty($listObject)) return $infos;
  693. foreach($listObject as $list)
  694. {
  695. $kind = (string)$list['kind'];
  696. $pathName = ltrim($path . DIRECTORY_SEPARATOR . (string)$list->name, DIRECTORY_SEPARATOR);
  697. if($kind == 'dir')
  698. {
  699. $this->getAllFiles($pathName, $revision, $lists);
  700. }
  701. else
  702. {
  703. $lists[] = rtrim($pathName, DIRECTORY_SEPARATOR);
  704. }
  705. }
  706. return $lists;
  707. }
  708. }