gitea.class.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. <?php
  2. class giteaRepo
  3. {
  4. public $client;
  5. public $projectID;
  6. public $root;
  7. public $token;
  8. public $branch;
  9. public $repo;
  10. /**
  11. * Construct
  12. *
  13. * @param string $client
  14. * @param string $root
  15. * @param string $username
  16. * @param string $password
  17. * @param string $encoding
  18. * @param object $repo
  19. * @access public
  20. * @return void
  21. */
  22. public function __construct($client, $root, $username, $password, $encoding = 'UTF-8', $repo = null)
  23. {
  24. putenv('LC_CTYPE=en_US.UTF-8');
  25. $this->repo = $repo;
  26. $this->client = $client;
  27. $this->root = rtrim($root, DIRECTORY_SEPARATOR);
  28. if(!realpath($this->root) and !empty($repo))
  29. {
  30. global $app;
  31. $project = $app->control->loadModel('gitea')->apiGetSingleProject($repo->serviceHost, $repo->serviceProject);
  32. if(isset($project->tokenCloneUrl))
  33. {
  34. $cmd = 'git clone --progress -v "' . $project->tokenCloneUrl . '" "' . $this->root . '" > "' . $app->getTmpRoot() . "log/clone.progress." . strtolower($repo->SCM) . ".{$repo->name}.log\" 2>&1 &";
  35. if(PHP_OS == 'WINNT') $cmd = "start /b $cmd";
  36. exec($cmd);
  37. return $app->control->locate($app->control->createLink('repo', 'showSyncCommit', "repoID={$repo->id}"));
  38. }
  39. }
  40. $branch = isset($_COOKIE['repoBranch']) ? $_COOKIE['repoBranch'] : '';
  41. if($branch)
  42. {
  43. $branches = $this->branch();
  44. if(isset($branches[$branch])) $branch = "origin/$branch";
  45. }
  46. $this->branch = $branch;
  47. chdir($this->root);
  48. exec("{$this->client} config core.quotepath false");
  49. }
  50. /**
  51. * List files.
  52. *
  53. * @param string $path
  54. * @param string $revision
  55. * @access public
  56. * @return array
  57. */
  58. public function ls($path, $revision = 'HEAD')
  59. {
  60. if(!scm::checkRevision($revision)) return array();
  61. $path = ltrim($path, DIRECTORY_SEPARATOR);
  62. $sub = '';
  63. chdir($this->root);
  64. if(!empty($path)) $sub = ":$path";
  65. if(!empty($this->branch))$revision = $this->branch;
  66. execCmd(escapeCmd("$this->client pull"));
  67. $cmd = escapeCmd("$this->client ls-tree -l $revision$sub");
  68. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  69. if($result) return array();
  70. $infos = array();
  71. foreach($list as $entry)
  72. {
  73. list($mod, $kind, $revision, $size, $name) = preg_split('/[\t ]+/', $entry);
  74. /* Get commit info. */
  75. $pathName = ltrim($path . DIRECTORY_SEPARATOR . $name, DIRECTORY_SEPARATOR);
  76. $cmd = escapeCmd("$this->client log -1 $this->branch -- $pathName");
  77. $commit = execCmd($cmd, 'array');
  78. $logs = $this->parseLog($commit);
  79. if($size > 1024 * 1024)
  80. {
  81. $size = round($size / (1024 * 1024), 2) . 'MB';
  82. }
  83. elseif($size > 1024)
  84. {
  85. $size = round($size / 1024, 2) . 'KB';
  86. }
  87. else
  88. {
  89. $size .= 'Bytes';
  90. }
  91. $info = new stdClass();
  92. $info->name = $name;
  93. $info->kind = $kind == 'tree' ? 'dir' : 'file';
  94. $info->revision = $logs ? $logs[0]->revision : $revision;
  95. $info->size = $size;
  96. $info->account = $logs ? $logs[0]->committer : '';
  97. $info->date = $logs ? $logs[0]->time : '';
  98. $info->comment = $logs ? $logs[0]->comment : '';
  99. $infos[] = $info;
  100. unset($info);
  101. }
  102. /* Sort by kind */
  103. foreach($infos as $key => $info) $kinds[$key] = $info->kind;
  104. if($infos) array_multisort($kinds, SORT_ASC, $infos);
  105. return $infos;
  106. }
  107. /**
  108. * Get tags
  109. *
  110. * @param string $path
  111. * @param string $revision
  112. * @access public
  113. * @return array
  114. */
  115. public function tags($path, $revision = 'HEAD')
  116. {
  117. if(!scm::checkRevision($revision)) return array();
  118. chdir($this->root);
  119. $cmd = escapeCmd("$this->client tag --sort=taggerdate");
  120. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  121. if($result) return array();
  122. foreach($list as $key => $tag)
  123. {
  124. if(!$tag) unset($list[$key]);
  125. }
  126. return $list;
  127. }
  128. /**
  129. * Get branch.
  130. *
  131. * @param string $showDetail
  132. * @param string $orderBy
  133. * @param int $limit
  134. * @param int $pageID
  135. * @access public
  136. * @return array
  137. */
  138. public function branch($showDetail = '', $orderBy = '', $limit = 0, $pageID = 1)
  139. {
  140. global $app;
  141. $apiRoot = $app->control->loadModel('gitea')->getApiRoot($this->repo->serviceHost);
  142. $url = sprintf($apiRoot, "/repos/{$this->repo->serviceProject}/branches");
  143. /* Max size of per_page in gitlab API is 100. */
  144. $params = array();
  145. $params['limit'] = $limit ? $limit : '100';
  146. $branches = array();
  147. for($page = $pageID; true; $page ++)
  148. {
  149. $params['page'] = $page;
  150. $branchList = json_decode(commonModel::http($url . '&' . http_build_query($params)));
  151. if(empty($branchList) || !is_array($branchList)) break;
  152. foreach($branchList as $branch)
  153. {
  154. if(!isset($branch->name)) continue;
  155. $branches[$branch->name] = $showDetail ? $branch : $branch->name;
  156. }
  157. /* Last page. */
  158. if($limit || count($branchList) < $params['limit']) break;
  159. }
  160. asort($branches);
  161. return $branches;
  162. }
  163. /**
  164. * Create a branch.
  165. *
  166. * @param string $branchName
  167. * @param string $ref
  168. * @access public
  169. * @return bool
  170. */
  171. public function createBranch($branchName = '', $ref = 'master')
  172. {
  173. global $app;
  174. $apiRoot = $app->control->loadModel('gitea')->getApiRoot($this->repo->serviceHost);
  175. $url = sprintf($apiRoot, "/repos/{$this->repo->serviceProject}/branches");
  176. $param = new stdclass();
  177. $param->old_branch_name = $ref;
  178. $param->new_branch_name = $branchName;
  179. $result = json_decode(commonModel::http($url, $param, array(), array(), 'json'));
  180. return array('result' => empty($result->name) ? 'fail' : 'success', 'message' => zget($result, 'message', 'Error'));
  181. }
  182. /**
  183. * 创建标签。
  184. * Create a tag in the repository.
  185. *
  186. * @param string $tagName The name of the tag to be created.
  187. * @param string $ref The revision of the tag, a commit SHA, another tag name, or branch name..
  188. * @param string $comment An optional comment for the tag.
  189. * @return array Returns an array with the result of the operation and a message. If the tag already exists, the result will be 'fail' and the message will be 'Tag is exists'. If the operation fails, the result will be 'fail' and the message will be 'Created fail.'. Otherwise, the result will be 'success' and the message will be an empty string.
  190. */
  191. public function createTag($tagName, $ref, $comment = '')
  192. {
  193. $tags = $this->tags('', 'HEAD');
  194. if(isset($tags[$tagName])) return array('result' => 'fail', 'message' => 'Tag is exists');
  195. chdir($this->root);
  196. execCmd(escapeCmd("{$this->client} stash"));
  197. $res = execCmd(escapeCmd("{$this->client} tag {$tagName} {$ref} -m '{$comment}'"), 'array');
  198. if(empty($res[0])) return array('result' => 'fail', 'message' => 'Created fail.');
  199. execCmd(escapeCmd("{$this->client} push origin {$tagName}"), 'array');
  200. return array('result' => 'success', 'message' => '');
  201. }
  202. /**
  203. * Get last log.
  204. *
  205. * @param string $path
  206. * @param int $count
  207. * @access public
  208. * @return array
  209. */
  210. public function getLastLog($path, $count = 10)
  211. {
  212. $path = ltrim($path, DIRECTORY_SEPARATOR);
  213. $revision = $this->branch ? $this->branch : 'HEAD';
  214. chdir($this->root);
  215. $list = execCmd(escapeCmd("$this->client log -10 $revision -- $path"), 'array');
  216. $logs = $this->parseLog($list);
  217. return $logs;
  218. }
  219. /**
  220. * Get logs
  221. *
  222. * @param string $path
  223. * @param string $fromRevision
  224. * @param string $toRevision
  225. * @param int $count
  226. * @access public
  227. * @return array
  228. */
  229. public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0)
  230. {
  231. if(!scm::checkRevision($fromRevision)) return array();
  232. if(!scm::checkRevision($toRevision)) return array();
  233. execCmd(escapeCmd("$this->client pull"));
  234. $path = ltrim($path, DIRECTORY_SEPARATOR);
  235. $count = $count == 0 ? '' : "-n $count";
  236. /* compatible with svn. */
  237. if($fromRevision === 'HEAD' and $this->branch) $fromRevision = $this->branch;
  238. if($toRevision === 'HEAD' and $this->branch) $toRevision = $this->branch;
  239. if($fromRevision === $toRevision)
  240. {
  241. $logs = array();
  242. chdir($this->root);
  243. $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status --stat-name-width=1000 -1 $fromRevision -- $path"), 'array');
  244. $logs = $this->parseLog($list);
  245. return $logs;
  246. }
  247. if(!$fromRevision)
  248. {
  249. $revisions = " $toRevision";
  250. }
  251. else
  252. {
  253. $revisions = "$fromRevision..$toRevision";
  254. }
  255. chdir($this->root);
  256. $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status --stat-name-width=1000 $count $revisions -- $path"), 'array');
  257. $logs = $this->parseLog($list);
  258. return $logs;
  259. }
  260. /**
  261. * Blame file
  262. *
  263. * @param string $path
  264. * @param string $revision
  265. * @param bool $showComment
  266. * @access public
  267. * @return array
  268. */
  269. public function blame($path, $revision, $showComment = true)
  270. {
  271. if(!scm::checkRevision($revision)) return array();
  272. $path = ltrim($path, DIRECTORY_SEPARATOR);
  273. chdir($this->root);
  274. $list = execCmd(escapeCmd("$this->client blame -c -l $revision -- $path"), 'array');
  275. $blames = array();
  276. $revLine = 0;
  277. $revision = '';
  278. foreach($list as $line)
  279. {
  280. if(empty($line)) continue;
  281. if($line[0] == '^') $line = substr($line, 1);
  282. preg_match('/^([0-9a-f]{39,40})\s.*\(\s*(\S+)\s+([\d-]+)\s(.*)\s(\d+)\)(.*)$/U', $line, $matches);
  283. if(isset($matches[1]) and $matches[1] != $revision)
  284. {
  285. $blame = array();
  286. $blame['revision'] = $matches[1];
  287. $blame['committer'] = $matches[2];
  288. $blame['time'] = $matches[3];
  289. $blame['line'] = $matches[5];
  290. $blame['lines'] = 1;
  291. $blame['content'] = strpos($matches[6], ' ') === false ? $matches[6] : substr($matches[6], 1);
  292. $log = $this->log('', '', '', 1);
  293. $blame['message'] = $log[0]->comment;
  294. $revision = $matches[1];
  295. $revLine = $matches[5];
  296. $blames[$revLine] = $blame;
  297. }
  298. elseif(isset($matches[5]))
  299. {
  300. $blame = array();
  301. $blame['line'] = $matches[5];
  302. $blame['content'] = strpos($matches[6], ' ') === false ? $matches[6] : substr($matches[6], 1);
  303. $blames[$matches[5]] = $blame;
  304. $blames[$revLine]['lines'] ++;
  305. }
  306. }
  307. return $blames;
  308. }
  309. /**
  310. * Diff file.
  311. *
  312. * @param string $path
  313. * @param string $fromRevision
  314. * @param string $toRevision
  315. * @param string $extra
  316. * @access public
  317. * @return array
  318. */
  319. public function diff($path, $fromRevision, $toRevision, $extra = '')
  320. {
  321. if(!scm::checkRevision($fromRevision) and $extra != 'isBranchOrTag') return array();
  322. if(!scm::checkRevision($toRevision) and $extra != 'isBranchOrTag') return array();
  323. $path = ltrim($path, DIRECTORY_SEPARATOR);
  324. chdir($this->root);
  325. if($toRevision == 'HEAD' and $this->branch) $toRevision = $this->branch;
  326. if($fromRevision == '^') $fromRevision = $toRevision . '^';
  327. if(strpos($fromRevision, '^') !== false)
  328. {
  329. $list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array');
  330. if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array');
  331. $fromRevision = $list[1];
  332. }
  333. $lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array');
  334. return $lines;
  335. }
  336. /**
  337. * Cat file.
  338. *
  339. * @param string $entry
  340. * @param string $revision
  341. * @access public
  342. * @return string
  343. */
  344. public function cat($entry, $revision = 'HEAD')
  345. {
  346. if(!scm::checkRevision($revision)) return false;
  347. chdir($this->root);
  348. if($revision == 'HEAD' and $this->branch) $revision = $this->branch;
  349. $cmd = escapeCmd("$this->client show $revision:$entry");
  350. $content = execCmd($cmd);
  351. if(is_array($content)) $content = implode("\n", $content);
  352. return $content;
  353. }
  354. /**
  355. * Get info.
  356. *
  357. * @param string $entry
  358. * @param string $revision
  359. * @access public
  360. * @return object
  361. */
  362. public function info($entry, $revision = 'HEAD')
  363. {
  364. if(!scm::checkRevision($revision)) return false;
  365. chdir($this->root);
  366. if($revision == 'HEAD' and $this->branch) $revision = $this->branch;
  367. $path = ltrim($entry, DIRECTORY_SEPARATOR);
  368. $cmd = escapeCmd("$this->client ls-tree $revision -- $path");
  369. $result = execCmd($cmd);
  370. $kind = '';
  371. if($result)
  372. {
  373. $results = explode("\n", trim($result));
  374. if(count($results) >= 2)
  375. {
  376. $kind = 'dir';
  377. }
  378. else
  379. {
  380. list($mode, $type) = explode(' ', $results[0]);
  381. $kind = $type == 'tree' ? 'dir' : 'file';
  382. }
  383. }
  384. $list = execCmd(escapeCmd("$this->client log -1 $revision --pretty=format:%H -- $path"), 'array');
  385. $revision = $list[0];
  386. $info = new stdclass();
  387. $info->kind = $kind;
  388. $info->path = $entry;
  389. $info->revision = $revision;
  390. $info->root = $this->root;
  391. return $info;
  392. }
  393. /**
  394. * Exec git cmd.
  395. *
  396. * @param string $cmd
  397. * @access public
  398. * @return array
  399. */
  400. public function exec($cmd)
  401. {
  402. chdir($this->root);
  403. return execCmd(escapeCmd("$this->client $cmd"), 'array');
  404. }
  405. /**
  406. * Parse diff.
  407. *
  408. * @param array $lines
  409. * @access public
  410. * @return array
  411. */
  412. public function parseDiff($lines)
  413. {
  414. if(empty($lines)) return array();
  415. $diffs = array();
  416. $num = count($lines);
  417. $endLine = end($lines);
  418. if(strpos($endLine, '\ No newline at end of file') === 0) $num -= 1;
  419. $newFile = false;
  420. $allFiles = array();
  421. for($i = 0; $i < $num; $i ++)
  422. {
  423. $diffFile = new stdclass();
  424. if(strpos($lines[$i], "diff --git ") === 0)
  425. {
  426. $fileInfo = explode(' ',$lines[$i]);
  427. $fileName = substr($fileInfo[2], strpos($fileInfo[2], '/') + 1);
  428. /* Prevent duplicate display of files. */
  429. if(in_array($fileName, $allFiles)) continue;
  430. $allFiles[] = $fileName;
  431. $diffFile->fileName = $fileName;
  432. for($i++; $i < $num; $i ++)
  433. {
  434. $diff = new stdclass();
  435. /* Fix bug #1757. */
  436. if($lines[$i] == '+++ /dev/null') $newFile = true;
  437. if(strpos($lines[$i], '+++', 0) !== false) continue;
  438. if(strpos($lines[$i], '---', 0) !== false) continue;
  439. if(strpos($lines[$i], '======', 0) !== false) continue;
  440. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i]))
  441. {
  442. $startLines = trim(str_replace(array('@', '+', '-'), '', $lines[$i]));
  443. list($oldStartLine, $newStartLine) = explode(' ', $startLines);
  444. list($diff->oldStartLine) = explode(',', $oldStartLine);
  445. list($diff->newStartLine) = explode(',', $newStartLine);
  446. $oldCurrentLine = $diff->oldStartLine;
  447. $newCurrentLine = $diff->newStartLine;
  448. if($newFile)
  449. {
  450. $oldCurrentLine = $diff->newStartLine;
  451. $newCurrentLine = $diff->oldStartLine;
  452. }
  453. $newLines = array();
  454. for($i++; $i < $num; $i ++)
  455. {
  456. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i]))
  457. {
  458. $i --;
  459. break;
  460. }
  461. if(strpos($lines[$i], "diff --git ") === 0) break;
  462. $line = $lines[$i];
  463. if(strpos($line, '\ No newline at end of file') === 0)continue;
  464. $sign = empty($line) ? '' : $line[0];
  465. if($sign == '-' and $newFile) $sign = '+';
  466. $type = $sign != '-' ? $sign == '+' ? 'new' : 'all' : 'old';
  467. if($sign == '-' || $sign == '+')
  468. {
  469. $line = substr_replace($line, ' ', 1, 0);
  470. if($newFile) $line = preg_replace('/^\-/', '+', $line);
  471. }
  472. $newLine = new stdclass();
  473. $newLine->type = $type;
  474. $newLine->oldlc = $type != 'new' ? $oldCurrentLine : '';
  475. $newLine->newlc = $type != 'old' ? $newCurrentLine : '';
  476. $newLine->line = htmlSpecialString($line);
  477. if($type != 'new') $oldCurrentLine++;
  478. if($type != 'old') $newCurrentLine++;
  479. $newLines[] = $newLine;
  480. }
  481. $diff->lines = $newLines;
  482. $diffFile->contents[] = $diff;
  483. }
  484. if(isset($lines[$i]) and strpos($lines[$i], "diff --git ") === 0)
  485. {
  486. $i --;
  487. $newFile = false;
  488. break;
  489. }
  490. }
  491. $diffs[] = $diffFile;
  492. }
  493. }
  494. return $diffs;
  495. }
  496. /**
  497. * Get commit count.
  498. *
  499. * @param int $commits
  500. * @param string $lastVersion
  501. * @access public
  502. * @return int
  503. */
  504. public function getCommitCount($commits = 0, $lastVersion = '')
  505. {
  506. if(!scm::checkRevision($lastVersion)) return false;
  507. chdir($this->root);
  508. $revision = $this->branch ? $this->branch : 'HEAD';
  509. return execCmd(escapeCmd("$this->client rev-list --count $revision -- ./"), 'string');
  510. }
  511. /**
  512. * Get first revision.
  513. *
  514. * @access public
  515. * @return string
  516. */
  517. public function getFirstRevision()
  518. {
  519. chdir($this->root);
  520. $list = execCmd(escapeCmd("$this->client rev-list --reverse HEAD -- ./"), 'array');
  521. return $list[0];
  522. }
  523. /**
  524. * Get latest revision
  525. *
  526. * @access public
  527. * @return string
  528. */
  529. public function getLatestRevision()
  530. {
  531. chdir($this->root);
  532. $revision = $this->branch ? $this->branch : 'HEAD';
  533. $list = execCmd(escapeCmd("$this->client rev-list -1 $revision -- ./"), 'array');
  534. return $list[0];
  535. }
  536. /**
  537. * Get commits.
  538. *
  539. * @param string $rversion
  540. * @param int $count
  541. * @param string $branch
  542. * @access public
  543. * @return array
  544. */
  545. public function getCommits($revision = '', $count = 0, $branch = '')
  546. {
  547. if(!scm::checkRevision($revision)) return array();
  548. if($revision == 'HEAD' and $branch)
  549. {
  550. $revision = $branch;
  551. }
  552. elseif(is_numeric($revision))
  553. {
  554. $revision = "--skip=$revision $branch";
  555. }
  556. $count = $count == 0 ? '' : "-n $count";
  557. chdir($this->root);
  558. if($branch)
  559. {
  560. execCmd(escapeCmd("$this->client checkout -b $branch"));
  561. execCmd(escapeCmd("$this->client checkout $branch"));
  562. execCmd(escapeCmd("$this->client pull"));
  563. }
  564. $list = execCmd(escapeCmd("$this->client log $count $revision -- ./"), 'array');
  565. $commits = $this->parseLog($list);
  566. $logs = array();
  567. foreach($commits as $commit)
  568. {
  569. $hash = $commit->revision;
  570. $log = new stdClass();
  571. $log->committer = $commit->committer;
  572. $log->revision = $commit->revision;
  573. $log->comment = $commit->comment;
  574. $log->time = $commit->time;
  575. $logs['commits'][$hash] = $log;
  576. $logs['files'][$hash] = array();
  577. }
  578. if(empty($logs)) return $logs;
  579. $hash = '';
  580. $files = execCmd(escapeCmd("$this->client whatchanged $count $revision --pretty=format:%an@_@%cd@_@%H@_@%s -- ./"), 'array');
  581. foreach($files as $commit)
  582. {
  583. $commit = trim($commit);
  584. if(empty($commit)) continue;
  585. $parsedCommit = explode('@_@', $commit);
  586. if(count($parsedCommit) == 4)
  587. {
  588. list($account, $date, $hash, $comment) = $parsedCommit;
  589. }
  590. else
  591. {
  592. $file = explode(' ', $commit);
  593. $file = explode("\t", end($file));
  594. if(!isset($file[1])) $file[1] = '';
  595. list($action, $path) = $file;
  596. $parsedFile = new stdclass();
  597. $parsedFile->revision = $hash;
  598. $parsedFile->path = '/' . trim($path);
  599. $parsedFile->oldPath = isset($file[2]) ? '/' . trim($file[2]) : '';
  600. $parsedFile->type = 'file';
  601. $parsedFile->action = substr($action, 0, 1);
  602. $logs['files'][$hash][] = $parsedFile;
  603. }
  604. }
  605. return $logs;
  606. }
  607. /**
  608. * Get clone url.
  609. *
  610. * @access public
  611. * @return string
  612. */
  613. public function getCloneUrl()
  614. {
  615. $url = new stdclass();
  616. $remote = execCmd(escapeCmd("$this->client remote -v"), 'array');
  617. $pregHttp = '/http(s)?:\/\/(www\.)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+)*\.git/';
  618. $pregSSH = '/ssh:\/\/git@[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:\d+)*(\/\w+)*\.git/';
  619. if(preg_match($pregHttp, $remote[0], $matches)) $url->http = $matches[0];
  620. if(preg_match($pregSSH, $remote[0], $matches)) $url->ssh = $matches[0];
  621. return $url;
  622. }
  623. /**
  624. * Parse log.
  625. *
  626. * @param array $logs
  627. * @access public
  628. * @return array
  629. */
  630. public function parseLog($logs)
  631. {
  632. $parsedLogs = array();
  633. $i = 0;
  634. foreach($logs as $line)
  635. {
  636. if(strpos($line, 'commit ') === 0)
  637. {
  638. if(isset($log))
  639. {
  640. $log->comment = trim($comment);
  641. $log->change = $changes;
  642. $parsedLogs[$i] = $log;
  643. $i++;
  644. }
  645. $log = new stdclass();
  646. $comment = '';
  647. $changes = array();
  648. $log->revision = trim(preg_replace('/^commit/', '', $line));
  649. }
  650. elseif(strpos($line, 'Author:') === 0)
  651. {
  652. $account = preg_replace('/^Author:/', '', $line);
  653. $log->committer = trim(preg_replace('/<[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+>/', '', $account));
  654. }
  655. elseif(strpos($line, 'Date:') === 0)
  656. {
  657. $date = trim(preg_replace('/^Date:/', '', $line));
  658. $log->time = date('Y-m-d H:i:s', strtotime($date));
  659. }
  660. elseif(preg_match('/^\s{2,}/', $line))
  661. {
  662. $comment .= $line;
  663. }
  664. elseif(strpos($line, "\t") !== false)
  665. {
  666. $lineList = explode("\t", $line);
  667. list($action, $entry) = $lineList;
  668. $entry = '/' . trim($entry);
  669. $pathInfo = array();
  670. $pathInfo['action'] = substr($action, 0, 1);
  671. $pathInfo['kind'] = 'file';
  672. $pathInfo['oldPath'] = isset($lineList[2]) ? '/' . trim($lineList[2]) : '';
  673. $changes[$entry] = $pathInfo;
  674. }
  675. }
  676. if(isset($log))
  677. {
  678. $log->comment = trim($comment);
  679. $log->change = $changes;
  680. $parsedLogs[$i] = $log;
  681. }
  682. return $parsedLogs;
  683. }
  684. /**
  685. * Get download url.
  686. *
  687. * @param string $branch
  688. * @param string $savePath
  689. * @param string $ext
  690. * @access public
  691. * @return string
  692. */
  693. public function getDownloadUrl($branch = 'master', $savePath = '', $ext = 'zip')
  694. {
  695. global $app;
  696. $api = $app->control->loadModel('gitea')->getApiRoot($this->repo->serviceHost);
  697. return sprintf($api, "/repos/{$this->repo->serviceProject}/archive/{$branch}.zip");
  698. }
  699. /**
  700. * List all files.
  701. *
  702. * @param string $path
  703. * @param string $revision
  704. * @param array $lists
  705. * @access public
  706. * @return array
  707. */
  708. public function getAllFiles($path, $revision = 'HEAD', &$lists = array())
  709. {
  710. if(!scm::checkRevision($revision)) return array();
  711. $path = ltrim($path, DIRECTORY_SEPARATOR);
  712. $sub = '';
  713. chdir($this->root);
  714. if(!empty($path)) $sub = ":$path";
  715. if(!empty($this->branch))$revision = $this->branch;
  716. $cmd = escapeCmd("$this->client ls-tree -l $revision$sub");
  717. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  718. if($result) return array();
  719. foreach($list as $entry)
  720. {
  721. list($mod, $kind, $revision, $size, $name) = preg_split('/[\t ]+/', $entry);
  722. /* Get commit info. */
  723. $pathName = ltrim($path . DIRECTORY_SEPARATOR . $name, DIRECTORY_SEPARATOR);
  724. if($kind == 'tree')
  725. {
  726. $this->getAllFiles($pathName, $revision, $lists);
  727. }
  728. else
  729. {
  730. $lists[] = rtrim($pathName, DIRECTORY_SEPARATOR);
  731. }
  732. }
  733. return $lists;
  734. }
  735. /**
  736. * 通过API创建合并请求。
  737. * Create mr by api.
  738. *
  739. * @param object $MR
  740. * @param string $openID
  741. * @param string $assignee
  742. * @access public
  743. * @return object|null
  744. */
  745. public function createMR($MR, $openID, $assignee)
  746. {
  747. $MRObject = new stdclass();
  748. $MRObject->title = $MR->title;
  749. $MRObject->head = $MR->sourceBranch;
  750. $MRObject->base = $MR->targetBranch;
  751. $MRObject->body = $MR->description;
  752. if(!empty($assignee)) $MRObject->assignee = $assignee;
  753. global $app;
  754. $url = sprintf($app->control->loadModel('gitea')->getApiRoot($MR->hostID), "/repos/{$MR->sourceProject}/pulls");
  755. $MR = json_decode(commonModel::http($url, $MRObject));
  756. if(isset($MR->number)) $MR->iid = $MR->number;
  757. if(isset($MR->mergeable))
  758. {
  759. if($MR->mergeable) $MR->merge_status = 'can_be_merged';
  760. if(!$MR->mergeable) $MR->merge_status = 'cannot_be_merged';
  761. }
  762. if(isset($MR->state) && $MR->state == 'open') $MR->state = 'opened';
  763. if(isset($MR->merged) && $MR->merged) $MR->state = 'merged';
  764. return $MR;
  765. }
  766. /**
  767. * Get a mr by api.
  768. *
  769. * @param int $MRID
  770. * @access public
  771. * @return array
  772. */
  773. public function getSingleMR($MRID)
  774. {
  775. $hostID = $this->repo->serviceHost;
  776. $projectID = $this->repo->serviceProject;
  777. global $app;
  778. $apiRoot = $app->control->loadModel('gitea')->getApiRoot($hostID);
  779. $url = sprintf($apiRoot, "/repos/{$projectID}/pulls/$MRID");
  780. $MR = json_decode(commonModel::http($url));
  781. if(!$MR || isset($MR->message) || isset($MR->errors)) return null;
  782. if(isset($MR->url) || isset($MR->html_url))
  783. {
  784. $diff = common::http(sprintf($apiRoot, "/repos/$projectID/pulls/$MRID.diff"));
  785. $MR->web_url = $MR->url;
  786. $MR->iid = $MR->number;
  787. $MR->state = $MR->state == 'open' ? 'opened' : $MR->state;
  788. if($MR->merged) $MR->state = 'merged';
  789. $MR->merge_status = $MR->mergeable ? 'can_be_merged' : 'cannot_be_merged';
  790. $MR->changes_count = empty($diff) ? 0 : 1;
  791. $MR->description = $MR->body;
  792. $MR->target_branch = $MR->base->ref;
  793. $MR->source_branch = $MR->head->ref;
  794. $MR->source_project_id = $projectID;
  795. $MR->target_project_id = $projectID;
  796. $MR->has_conflicts = empty($diff) ? true : false;
  797. $MR->is_draft = strpos($MR->title, 'Draft:') === 0;
  798. }
  799. return $MR;
  800. }
  801. }