model.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. <?php
  2. /**
  3. * The model file of svn module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Yanyi Cao <caoyanyi@easycorp.com>
  8. * @package svn
  9. * @link https://www.zentao.net
  10. */
  11. class svnModel extends model
  12. {
  13. /**
  14. * @param string $moduleName
  15. * @param string $methodName
  16. */
  17. public function __construct($moduleName = '', $methodName = '')
  18. {
  19. parent::__construct($moduleName, $methodName);
  20. putenv('LC_ALL=C');
  21. }
  22. /**
  23. * The svn binary client.
  24. *
  25. * @var int
  26. * @access public
  27. */
  28. public $client;
  29. /**
  30. * Repos.
  31. *
  32. * @var array
  33. * @access public
  34. */
  35. public $repos = array();
  36. /**
  37. * The root path of a repo
  38. *
  39. * @var string
  40. * @access public
  41. */
  42. public $repoRoot = '';
  43. /**
  44. * Users
  45. *
  46. * @var array
  47. * @access public
  48. */
  49. public $users = array();
  50. /**
  51. * 执行定时任务同步提交信息。
  52. * Sync commit info by cron.
  53. *
  54. * @access public
  55. * @return bool
  56. */
  57. public function run()
  58. {
  59. $this->setRepos();
  60. if(empty($this->repos)) return false;
  61. /* Get commit triggerType jobs by repoIdList. */
  62. $commentGroup = $this->loadModel('job')->getTriggerGroup('commit', array_keys($this->repos));
  63. /* Get tag triggerType jobs by repoIdList. */
  64. $tagGroup = $this->job->getTriggerGroup('tag', array_keys($this->repos));
  65. $_COOKIE['repoBranch'] = '';
  66. $this->loadModel('compile');
  67. foreach($this->repos as $repoID => $repo)
  68. {
  69. $this->updateCommit($repo, $commentGroup, true);
  70. /* Create compile by tag. */
  71. $jobs = zget($tagGroup, $repoID, array());
  72. foreach($jobs as $job)
  73. {
  74. $job->lastTag = trim(str_replace(rtrim($repo->path, '/') . $job->svnDir, '', $job->lastTag), '/');
  75. $dirs = $this->getRepoTags($repo, $job->svnDir);
  76. $isNew = empty($job->lastTag) ? true : false;
  77. $lastTag = '';
  78. foreach($dirs as $dir)
  79. {
  80. if(!$isNew && $dir == $job->lastTag)
  81. {
  82. $isNew = true;
  83. continue;
  84. }
  85. if(!$isNew) continue;
  86. $lastTag = $dir;
  87. $tag = rtrim($repo->path , '/') . '/' . trim($job->svnDir, '/') . '/' . $lastTag;
  88. $this->compile->createByJob($job->id, $tag, 'tag');
  89. }
  90. if($lastTag) $this->dao->update(TABLE_JOB)->set('lastTag')->eq($lastTag)->where('id')->eq($job->id)->exec();
  91. }
  92. }
  93. return !dao::isError();
  94. }
  95. /**
  96. * 保存提交信息。
  97. * Save commits.
  98. *
  99. * @param object $repo
  100. * @param array $logs
  101. * @param object $lastInDB
  102. * @param array $commentGroup
  103. * @param bool $printLog
  104. * @access public
  105. * @return bool
  106. */
  107. public function saveCommits($repo, $logs, $lastInDB, $commentGroup, $printLog)
  108. {
  109. $this->loadModel('repo');
  110. $this->loadModel('git');
  111. $version = (int)$lastInDB->commit + 1;
  112. foreach($logs as $log)
  113. {
  114. if($printLog) $this->printLog("parsing log {$log->revision}");
  115. if($printLog) $this->printLog("comment is\n----------\n" . trim($log->msg) . "\n----------");
  116. $objects = $this->repo->parseComment($log->msg);
  117. if($objects)
  118. {
  119. if($printLog) $this->printLog('extract' .
  120. ' story:' . join(' ', $objects['stories']) .
  121. ' task:' . join(' ', $objects['tasks']) .
  122. ' bug:' . join(',', $objects['bugs']) .
  123. ' design:' . join(',', $objects['designs'])
  124. );
  125. $this->repo->saveAction2PMS($objects, $log, $this->repoRoot, $repo->encoding, 'svn');
  126. $this->git->linkCommit($objects['designs'], $repo->id, $log);
  127. }
  128. else
  129. {
  130. if($printLog) $this->printLog('no objects found' . "\n");
  131. }
  132. /* Create compile by comment. */
  133. $jobs = zget($commentGroup, $repo->id, array());
  134. foreach($jobs as $job)
  135. {
  136. foreach(explode(',', $job->comment) as $comment)
  137. {
  138. if(strpos($log->msg, $comment) !== false)
  139. {
  140. $this->loadModel('job')->exec($job->id, array(), 'commit');
  141. continue 2;
  142. }
  143. }
  144. }
  145. $version = $this->repo->saveOneCommit($repo->id, $log, $version);
  146. }
  147. $this->repo->updateCommitCount($repo->id, $lastInDB->commit + count($logs));
  148. $this->dao->update(TABLE_REPO)->set('lastSync')->eq(helper::now())->where('id')->eq($repo->id)->exec();
  149. return !dao::isError();
  150. }
  151. /**
  152. * 更新提交信息。
  153. * Update commit.
  154. *
  155. * @param object $repo
  156. * @param array $commentGroup
  157. * @param bool $printLog
  158. * @access public
  159. * @return void
  160. */
  161. public function updateCommit($repo, $commentGroup, $printLog = true)
  162. {
  163. /* Load mudule and print log. */
  164. if($printLog) $this->printLog("begin repo {$repo->name}");
  165. $this->setRepo($repo);
  166. /* Print log and get lastInDB. */
  167. if($printLog) $this->printLog("get this repo logs.");
  168. $lastInDB = $this->loadModel('repo')->getLatestCommit($repo->id);
  169. /* Ignore unsynced repo. */
  170. if(empty($lastInDB))
  171. {
  172. if($printLog) $this->printLog("Please init repo {$repo->name}");
  173. return false;
  174. }
  175. $logs = $this->repo->getUnsyncedCommits($repo);
  176. if(empty($logs)) return true;
  177. /* Update code commit history. */
  178. if($printLog) $this->printLog("get " . count($logs) . " logs");
  179. if($printLog) $this->printLog('begin parsing logs');
  180. $this->saveCommits($repo, $logs, $lastInDB, $commentGroup, $printLog);
  181. if($printLog) $this->printLog("\n\nrepo #" . $repo->id . ': ' . $repo->path . " finished");
  182. }
  183. /**
  184. * 设置代码库列表。
  185. * Set the repos.
  186. *
  187. * @access public
  188. * @return void
  189. */
  190. public function setRepos()
  191. {
  192. $repos = $this->loadModel('repo')->getListBySCM('Subversion');
  193. $svnRepos = array();
  194. $paths = array();
  195. foreach($repos as $repo)
  196. {
  197. if(isset($paths[$repo->path])) continue;
  198. unset($repo->acl);
  199. unset($repo->desc);
  200. $svnRepos[$repo->id] = $repo;
  201. $paths[$repo->path] = $repo->path;
  202. }
  203. if(empty($svnRepos)) echo "You must set one svn repo.\n";
  204. $this->repos = $svnRepos;
  205. }
  206. /**
  207. * 获取代码库列表。
  208. * Get repos.
  209. *
  210. * @access public
  211. * @return array
  212. */
  213. public function getRepos()
  214. {
  215. $this->setRepos();
  216. return helper::arrayColumn($this->repos, 'path');
  217. }
  218. /**
  219. * 设置仓库属性。
  220. * Set repo.
  221. *
  222. * @param object $repo
  223. * @access public
  224. * @return bool
  225. */
  226. public function setRepo($repo)
  227. {
  228. $this->setClient($repo);
  229. $this->setRepoRoot($repo);
  230. return true;
  231. }
  232. /**
  233. * 设置svn客户端。
  234. * Set the svn binary client of a repo.
  235. *
  236. * @param object $repo
  237. * @access public
  238. * @return bool
  239. */
  240. public function setClient($repo)
  241. {
  242. $this->client = $repo->client . " --non-interactive";
  243. if(stripos($repo->path, 'https') === 0 || stripos($repo->path, 'svn') === 0)
  244. {
  245. $cmd = $repo->client . ' --version --quiet';
  246. $version = `$cmd`;
  247. if(!$version) return false;
  248. if(version_compare($version, '1.6.0', '>'))
  249. {
  250. $this->client .= ' --trust-server-cert';
  251. }
  252. }
  253. if(isset($repo->account)) $this->client .= " --username $repo->account --password $repo->password --no-auth-cache";
  254. return true;
  255. }
  256. /**
  257. * 设置仓库根目录。
  258. * set the root path of a repo.
  259. *
  260. * @param object $repo
  261. * @access public
  262. * @return void
  263. */
  264. public function setRepoRoot($repo)
  265. {
  266. $scm = $this->app->loadClass('scm');
  267. $scm->setEngine($repo);
  268. $info = $scm->info('');
  269. $this->repoRoot = $info->root;
  270. }
  271. /**
  272. * 获取仓库目录信息。
  273. * Get tags histories for repo.
  274. *
  275. * @param object $repo
  276. * @param string $path
  277. * @access public
  278. * @return array
  279. */
  280. public function getRepoTags($repo, $path)
  281. {
  282. $scm = $this->app->loadClass('scm');
  283. $scm->setEngine($repo);
  284. return $scm->tags($path);
  285. }
  286. /**
  287. * 获取代码提交记录。
  288. * Get repo logs.
  289. *
  290. * @param object $repo
  291. * @param int $fromRevision
  292. * @access public
  293. * @return array
  294. */
  295. public function getRepoLogs($repo, $fromRevision)
  296. {
  297. /* The svn log command. */
  298. $scm = $this->app->loadClass('scm');
  299. $scm->setEngine($repo);
  300. $logs = $scm->log('', $fromRevision);
  301. if(empty($logs)) return array();
  302. /* Process logs. */
  303. foreach($logs as $log)
  304. {
  305. $log->author = $log->committer;
  306. $log->msg = $log->comment;
  307. $log->date = $log->time;
  308. /* Process files. */
  309. $log->files = array();
  310. foreach($log->change as $file => $info) $log->files[$info['action']][] = $file;
  311. }
  312. return $logs;
  313. }
  314. /**
  315. * 根据URL获取对比信息。
  316. * Get diff by url.
  317. *
  318. * @param string $url
  319. * @param int $revision
  320. * @access public
  321. * @return string|false
  322. */
  323. public function diff($url, $revision)
  324. {
  325. $repo = $this->getRepoByURL($url);
  326. if(!$repo) return false;
  327. $this->setClient($repo);
  328. putenv('LC_CTYPE=en_US.UTF-8');
  329. $oldRevision = $revision - 1;
  330. $url = str_replace('%2F', '/', urlencode($url));
  331. $url = str_replace('%3A', ':', $url);
  332. $cmd = $this->client . " diff -r $oldRevision:$revision $url 2>&1";
  333. $diff = `$cmd`;
  334. $encoding = isset($repo->encoding) ? $repo->encoding : 'utf-8';
  335. if($encoding and $encoding != 'utf-8') $diff = helper::convertEncoding($diff, $encoding);
  336. return $diff;
  337. }
  338. /**
  339. * 根据URL获取文件内容。
  340. * Cat a url.
  341. *
  342. * @param string $url
  343. * @param int $revision
  344. * @access public
  345. * @return string|false
  346. */
  347. public function cat($url, $revision)
  348. {
  349. $repo = $this->getRepoByURL($url);
  350. if(!$repo) return false;
  351. $this->setClient($repo);
  352. putenv('LC_CTYPE=en_US.UTF-8');
  353. $url = str_replace('%2F', '/', urlencode($url));
  354. $url = str_replace('%3A', ':', $url);
  355. $cmd = $this->client . " cat $url@$revision 2>&1";
  356. $code = `$cmd`;
  357. $encoding = isset($repo->encoding) ? $repo->encoding : 'utf-8';
  358. if($encoding and $encoding != 'utf-8') $code = helper::convertEncoding($code, $encoding);
  359. return $code;
  360. }
  361. /**
  362. * 根据URL获取代码库信息。
  363. * Get repo by url.
  364. *
  365. * @param string $url
  366. * @access public
  367. * @return object|false
  368. */
  369. public function getRepoByURL($url)
  370. {
  371. if(empty($this->repos)) $this->setRepos();
  372. foreach($this->repos as $repo)
  373. {
  374. if(strpos(strtolower($url), strtolower($repo->path)) !== false) return $repo;
  375. }
  376. return false;
  377. }
  378. /**
  379. * 输出日志。
  380. * Print log.
  381. *
  382. * @param string $log
  383. * @access public
  384. * @return void
  385. */
  386. public function printLog($log)
  387. {
  388. echo helper::now() . " $log\n";
  389. }
  390. /**
  391. * 将日志从xml格式转换为对象。
  392. * Convert log from xml format to object.
  393. *
  394. * @param array $log
  395. * @access public
  396. * @return object|null
  397. */
  398. public function convertLog($log)
  399. {
  400. if(empty($log)) return null;
  401. list($hash, $account, $date) = $log;
  402. $account = preg_replace('/^Author:/', '', $account);
  403. $account = trim(preg_replace('/<[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+>/', '', $account));
  404. $date = trim(preg_replace('/^Date:/', '', $date));
  405. $count = count($log);
  406. $comment = '';
  407. $files = array();
  408. for($i = 3; $i < $count; $i++)
  409. {
  410. $line = $log[$i];
  411. if(preg_match('/^\s{2,}/', $line))
  412. {
  413. $comment .= $line;
  414. }
  415. elseif(strpos($line, "\t") !== false)
  416. {
  417. list($action, $entry) = explode("\t", $line);
  418. $entry = '/' . trim($entry);
  419. $files[$action][] = $entry;
  420. }
  421. }
  422. $parsedLog = new stdClass();
  423. $parsedLog->author = $account;
  424. $parsedLog->revision = trim(preg_replace('/^commit/', '', $hash));
  425. $parsedLog->msg = trim($comment);
  426. $parsedLog->date = date('Y-m-d H:i:s', strtotime($date));
  427. $parsedLog->files = $files;
  428. return $parsedLog;
  429. }
  430. }