gogs.class.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. <?php
  2. class gogsRepo
  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('gogs')->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. else if($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('gogs')->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. $branches = $this->branch();
  174. if(isset($branches[$branchName])) return array('result' => 'fail', 'message' => 'Branch is exists');
  175. chdir($this->root);
  176. execCmd(escapeCmd("{$this->client} stash"));
  177. $res = execCmd(escapeCmd("{$this->client} checkout -b {$branchName} origin/{$ref}"), 'array');
  178. if(empty($res[0])) return array('result' => 'fail', 'message' => 'Created fail.');
  179. execCmd(escapeCmd("{$this->client} push origin {$branchName}"), 'array');
  180. return array('result' => 'success', 'message' => '');
  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 -l -c $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 commits by MR branches.
  609. *
  610. * @param string $sourceBranch
  611. * @param string $targetBranch
  612. * @access public
  613. * @return array
  614. */
  615. public function getMRCommits($sourceBranch, $targetBranch)
  616. {
  617. execCmd(escapeCmd("$this->client checkout $sourceBranch"));
  618. execCmd(escapeCmd("$this->client pull"));
  619. execCmd(escapeCmd("$this->client checkout $targetBranch"));
  620. execCmd(escapeCmd("$this->client pull"));
  621. $commits = execCmd(escapeCmd("$this->client log origin/$sourceBranch ^origin/$targetBranch"), 'array');
  622. $commits = $this->parseLog($commits);
  623. foreach($commits as $commit) $commit->id = $commit->revision;
  624. return $commits;
  625. }
  626. /**
  627. * Get clone url.
  628. *
  629. * @access public
  630. * @return string
  631. */
  632. public function getCloneUrl()
  633. {
  634. $url = new stdclass();
  635. $remote = execCmd(escapeCmd("$this->client remote -v"), 'array');
  636. $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/';
  637. $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/';
  638. if(preg_match($pregHttp, $remote[0], $matches)) $url->http = $matches[0];
  639. if(preg_match($pregSSH, $remote[0], $matches)) $url->ssh = $matches[0];
  640. return $url;
  641. }
  642. /**
  643. * Parse log.
  644. *
  645. * @param array $logs
  646. * @access public
  647. * @return array
  648. */
  649. public function parseLog($logs)
  650. {
  651. $parsedLogs = array();
  652. $i = 0;
  653. foreach($logs as $line)
  654. {
  655. if(strpos($line, 'commit ') === 0)
  656. {
  657. if(isset($log))
  658. {
  659. $log->comment = trim($comment);
  660. $log->change = $changes;
  661. $parsedLogs[$i] = $log;
  662. $i++;
  663. }
  664. $log = new stdclass();
  665. $comment = '';
  666. $changes = array();
  667. $log->revision = trim(preg_replace('/^commit/', '', $line));
  668. }
  669. elseif(strpos($line, 'Author:') === 0)
  670. {
  671. $account = preg_replace('/^Author:/', '', $line);
  672. $log->committer = trim(preg_replace('/<[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+>/', '', $account));
  673. }
  674. elseif(strpos($line, 'Date:') === 0)
  675. {
  676. $date = trim(preg_replace('/^Date:/', '', $line));
  677. $log->time = date('Y-m-d H:i:s', strtotime($date));
  678. }
  679. elseif(preg_match('/^\s{2,}/', $line))
  680. {
  681. $comment .= $line;
  682. }
  683. elseif(strpos($line, "\t") !== false)
  684. {
  685. $lineList = explode("\t", $line);
  686. list($action, $entry) = $lineList;
  687. $entry = '/' . trim($entry);
  688. $pathInfo = array();
  689. $pathInfo['action'] = substr($action, 0, 1);
  690. $pathInfo['kind'] = 'file';
  691. $pathInfo['oldPath'] = isset($lineList[2]) ? '/' . trim($lineList[2]) : '';
  692. $changes[$entry] = $pathInfo;
  693. }
  694. }
  695. if(isset($log))
  696. {
  697. $log->comment = trim($comment);
  698. $log->change = $changes;
  699. $parsedLogs[$i] = $log;
  700. }
  701. return $parsedLogs;
  702. }
  703. /**
  704. * Get download url.
  705. *
  706. * @param string $branch
  707. * @param string $savePath
  708. * @param string $ext
  709. * @access public
  710. * @return string
  711. */
  712. public function getDownloadUrl($branch = 'master', $savePath = '', $ext = 'zip')
  713. {
  714. execCmd(escapeCmd("$this->client checkout $branch"));
  715. execCmd(escapeCmd("$this->client pull"));
  716. global $app, $config;
  717. $gitDir = scandir($this->root);
  718. $files = '';
  719. foreach($gitDir as $path)
  720. {
  721. if(!in_array($path, array('.', '..', '.git'))) $files .= $this->root . DS . "$path,";
  722. }
  723. $app->loadClass('pclzip', true);
  724. $fileName = $savePath . DS . "{$this->repo->name}_$branch.zip";
  725. $zip = new pclzip($fileName);
  726. $zip->create($files, PCLZIP_OPT_REMOVE_PATH, $this->root);
  727. return $config->webRoot . $app->getAppName() . 'data' . DS . 'repo' . DS . "{$this->repo->name}_$branch.zip";
  728. }
  729. /**
  730. * List all files.
  731. *
  732. * @param string $path
  733. * @param string $revision
  734. * @param array $lists
  735. * @access public
  736. * @return array
  737. */
  738. public function getAllFiles($path, $revision = 'HEAD', &$lists = array())
  739. {
  740. if(!scm::checkRevision($revision)) return array();
  741. $path = ltrim($path, DIRECTORY_SEPARATOR);
  742. $sub = '';
  743. chdir($this->root);
  744. if(!empty($path)) $sub = ":$path";
  745. if(!empty($this->branch))$revision = $this->branch;
  746. $cmd = escapeCmd("$this->client ls-tree -l $revision$sub");
  747. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  748. if($result) return array();
  749. foreach($list as $entry)
  750. {
  751. list($mod, $kind, $revision, $size, $name) = preg_split('/[\t ]+/', $entry);
  752. /* Get commit info. */
  753. $pathName = ltrim($path . DIRECTORY_SEPARATOR . $name, DIRECTORY_SEPARATOR);
  754. if($kind == 'tree')
  755. {
  756. $this->getAllFiles($pathName, $revision, $lists);
  757. }
  758. else
  759. {
  760. $lists[] = rtrim($pathName, DIRECTORY_SEPARATOR);
  761. }
  762. }
  763. return $lists;
  764. }
  765. /**
  766. * 通过API创建合并请求。
  767. * Create mr by api.
  768. *
  769. * @param object $MR
  770. * @param string $openID
  771. * @param string $assignee
  772. * @access public
  773. * @return object|null
  774. */
  775. public function createMR($MR, $openID, $assignee)
  776. {
  777. $MRObject = new stdclass();
  778. $MRObject->title = $MR->title;
  779. $MRObject->head = $MR->sourceBranch;
  780. $MRObject->base = $MR->targetBranch;
  781. $MRObject->body = $MR->description;
  782. if(!empty($assignee)) $MRObject->assignee = $assignee;
  783. global $app;
  784. $url = sprintf($app->control->loadModel('gogs')->getApiRoot($MR->hostID), "/repos/{$MR->sourceProject}/pulls");
  785. $MR = json_decode(commonModel::http($url, $MRObject));
  786. if(isset($MR->number)) $MR->iid = $MR->number;
  787. if(isset($MR->mergeable))
  788. {
  789. if($MR->mergeable) $MR->merge_status = 'can_be_merged';
  790. if(!$MR->mergeable) $MR->merge_status = 'cannot_be_merged';
  791. }
  792. if(isset($MR->state) && $MR->state == 'open') $MR->state = 'opened';
  793. if(isset($MR->merged) && $MR->merged) $MR->state = 'merged';
  794. return $MR;
  795. }
  796. /**
  797. * Get a mr by api.
  798. *
  799. * @param int $MRID
  800. * @access public
  801. * @return array
  802. */
  803. public function getSingleMR($MRID)
  804. {
  805. $hostID = $this->repo->serviceHost;
  806. $projectID = $this->repo->serviceProject;
  807. global $app;
  808. $apiRoot = $app->control->loadModel('gogs')->getApiRoot($hostID);
  809. $url = sprintf($apiRoot, "/repos/{$projectID}/pulls/$MRID");
  810. $MR = json_decode(commonModel::http($url));
  811. if(!$MR || isset($MR->message) || isset($MR->errors)) return null;
  812. if(isset($MR->url) || isset($MR->html_url))
  813. {
  814. $diff = common::http(sprintf($apiRoot, "/repos/$projectID/pulls/$MRID.diff"));
  815. $MR->web_url = $MR->html_url;
  816. $MR->iid = $MR->id;
  817. $MR->state = $MR->state == 'open' ? 'opened' : $MR->state;
  818. if($MR->merged) $MR->state = 'merged';
  819. $MR->merge_status = $MR->mergeable ? 'can_be_merged' : 'cannot_be_merged';
  820. $MR->changes_count = empty($diff) ? 0 : 1;
  821. $MR->description = $MR->body;
  822. $MR->target_branch = $MR->base_branch;
  823. $MR->source_branch = $MR->head_branch;
  824. $MR->source_project_id = $projectID;
  825. $MR->target_project_id = $projectID;
  826. $MR->has_conflicts = empty($diff) ? true : false;
  827. $MR->is_draft = strpos($MR->title, 'Draft:') === 0;
  828. }
  829. return $MR;
  830. }
  831. }