gitlab.class.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. <?php
  2. class gitlabRepo
  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 gitlab api url.
  14. * @param string $root id of gitlab project.
  15. * @param string $username null
  16. * @param string $password token of gitlab api.
  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. $this->client = $client;
  25. $this->root = rtrim($root, '/') . '/';
  26. $this->token = $password;
  27. $this->branch = isset($_COOKIE['repoBranch']) ? $_COOKIE['repoBranch'] : 'HEAD';
  28. $this->repo = $repo;
  29. }
  30. /**
  31. * List files.
  32. *
  33. * @param string $path
  34. * @param string $revision
  35. * @access public
  36. * @return array
  37. */
  38. public function ls($path, $revision = 'HEAD')
  39. {
  40. if(!scm::checkRevision($revision)) return array();
  41. $api = "tree";
  42. $param = new stdclass();
  43. $param->path = ltrim($path, '/');
  44. $param->ref = $revision;
  45. $param->recursive = 0;
  46. if(!empty($this->branch)) $param->ref = $this->branch;
  47. $list = $this->fetch($api, $param, true);
  48. if(empty($list)) return array();
  49. $infos = array();
  50. foreach($list as $file)
  51. {
  52. if(!isset($file->type)) continue;
  53. $info = new stdClass();
  54. $info->name = $file->name;
  55. $info->kind = $file->type == 'blob' ? 'file' : 'dir';
  56. if($file->type == 'blob')
  57. {
  58. $file = $this->files($file->path, $this->branch);
  59. $info->revision = zget($file, 'revision', '');
  60. $info->comment = zget($file, 'comment', '');
  61. $info->account = zget($file, 'committer', '');
  62. $info->date = zget($file, 'date', '');
  63. $info->size = zget($file, 'size', '');
  64. }
  65. else
  66. {
  67. $commits = $this->getCommitsByPath($file->path, '', '', 1);
  68. if(empty($commits)) continue;
  69. $commit = $commits[0];
  70. $info->revision = $commit->id;
  71. $info->comment = $commit->message;
  72. $info->account = $commit->committer_name;
  73. $info->date = date('Y-m-d H:i:s', strtotime($commit->committed_date));
  74. $info->size = 0;
  75. }
  76. $infos[] = $info;
  77. unset($info);
  78. }
  79. /* Sort by kind */
  80. foreach($infos as $key => $info) $kinds[$key] = $info->kind;
  81. if($infos) array_multisort($kinds, SORT_ASC, $infos);
  82. return $infos;
  83. }
  84. /**
  85. * Get files info.
  86. *
  87. * The API path requested is: "GET /projects/:id/repository/files/:file_path".
  88. * Known issue of GitLab API: if a '%' in 'file_path', GitLab API will show a error 'file_path should be a valid file path'.
  89. *
  90. * @param string $path
  91. * @param string $ref
  92. * @access public
  93. * @return object
  94. * @doc https://docs.gitlab.com/ee/api/repository_files.html
  95. */
  96. public function files($path, $ref = 'master')
  97. {
  98. $path = urlencode($path);
  99. $api = "files/$path";
  100. $param = new stdclass();
  101. $param->ref = $ref;
  102. $file = $this->fetch($api, $param);
  103. if(!isset($file->file_name)) return false;
  104. $commits = $this->getCommitsByPath($path, '', '', 1);
  105. $file->revision = $file->commit_id;
  106. $file->size = $this->formatBytes($file->size);
  107. if(!empty($commits))
  108. {
  109. $commit = $commits[0];
  110. $file->revision = $commit->id;
  111. $file->committer = $commit->committer_name;
  112. $file->comment = $commit->message;
  113. $file->date = date('Y-m-d H:i:s', strtotime($commit->committed_date));
  114. }
  115. return $file;
  116. }
  117. /**
  118. * Get tags
  119. *
  120. * @param string $showDetail
  121. * @param string $revision
  122. * @param bool $onlyDir
  123. * @param string $orderBy
  124. * @param int $limit
  125. * @param int $pageID
  126. * @access public
  127. * @return array
  128. */
  129. public function tags($showDetail = '', $revision = 'HEAD', $onlyDir = true, $orderBy = '', $limit = 0, $pageID = 1)
  130. {
  131. if(!$orderBy) $orderBy = 'updated_desc';
  132. $api = "tags";
  133. $tags = array();
  134. $params = array();
  135. $params['per_page'] = $limit ? $limit : '100';
  136. $sort = explode('_', $orderBy);
  137. $params['order'] = $sort[0];
  138. $params['sort'] = isset($sort[1]) ? $sort[1] : 'asc';
  139. if($showDetail && $showDetail != 'all') $params['search'] = $showDetail;
  140. for($page = $pageID; true; $page ++)
  141. {
  142. $params['page'] = $page;
  143. $list = $this->fetch($api, $params);
  144. if(empty($list) || !is_array($list)) break;
  145. foreach($list as $tag) $tags[] = $showDetail ? $tag : $tag->name;
  146. if($limit || count($list) < $params['per_page']) break;
  147. }
  148. return $tags;
  149. }
  150. /**
  151. * Get branches.
  152. *
  153. * @access public
  154. * @param string $showDetail
  155. * @param string $orderBy
  156. * @param int $limit
  157. * @param int $pageID
  158. * @return array
  159. */
  160. public function branch($showDetail = '', $orderBy = '', $limit = 0, $pageID = 1)
  161. {
  162. /* Max size of per_page in gitlab API is 100. */
  163. $params = array();
  164. $params['per_page'] = $limit ? $limit : '100';
  165. if($showDetail && $showDetail != 'all') $params['search'] = $showDetail;
  166. $branches = array();
  167. $default = array();
  168. for($page = $pageID; true; $page ++)
  169. {
  170. $params['page'] = $page;
  171. $branchList = $this->fetch("branches", $params);
  172. if(empty($branchList) || !is_array($branchList)) break;
  173. foreach($branchList as $branch)
  174. {
  175. if(!isset($branch->name)) continue;
  176. if($branch->default)
  177. {
  178. $default[$branch->name] = $showDetail ? $branch : $branch->name;
  179. }
  180. else
  181. {
  182. $branches[$branch->name] = $showDetail ? $branch : $branch->name;
  183. }
  184. }
  185. /* Last page. */
  186. if($limit || count($branchList) < $params['per_page']) break;
  187. }
  188. asort($branches);
  189. $branches = $default + $branches;
  190. return $branches;
  191. }
  192. /**
  193. * Create a branch.
  194. *
  195. * @param string $branchName
  196. * @param string $ref
  197. * @access public
  198. * @return bool
  199. */
  200. public function createBranch($branchName = '', $ref = 'master')
  201. {
  202. global $app;
  203. $param = new stdclass();
  204. $param->ref = $ref;
  205. $param->branch = $branchName;
  206. $result = $app->control->loadModel('gitlab')->apiCreateBranch($this->repo->serviceHost, $this->repo->serviceProject, $param);
  207. return array('result' => empty($result->name) ? 'fail' : 'success', 'message' => empty($result->name) ? $result->message : '');
  208. }
  209. /**
  210. * Creates a new tag in the GitLab repository.
  211. *
  212. * @param string $tagName The name of the tag to be created.
  213. * @param string $ref The revision of the tag, a commit SHA, another tag name, or branch name.
  214. * @param string $comment An optional comment for the tag.
  215. * @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.
  216. */
  217. public function createTag($tagName = '', $ref = 'master', $comment = '')
  218. {
  219. global $app;
  220. $tag = new stdclass();
  221. $tag->tag_name = $tagName;
  222. $tag->ref = $ref; /* Create a tag from a commit SHA, another tag name, or branch name. */
  223. $tag->message = $comment;
  224. $result = $app->control->loadModel('gitlab')->apiCreateTag($this->repo->serviceHost, $this->repo->serviceProject, $tag);
  225. return array('result' => empty($result->name) ? 'fail' : 'success', 'message' => empty($result->name) ? $result->message : '');
  226. }
  227. /**
  228. * Get last log.
  229. *
  230. * @param string $path
  231. * @param int $count
  232. * @access public
  233. * @return array
  234. */
  235. public function getLastLog($path, $count = 10)
  236. {
  237. return $this->log($path);
  238. }
  239. /**
  240. * Get logs.
  241. *
  242. * @param string $path
  243. * @param string $fromRevision
  244. * @param string $toRevision
  245. * @param int $count
  246. * @access public
  247. * @return array
  248. */
  249. public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0)
  250. {
  251. if(!scm::checkRevision($fromRevision)) return array();
  252. if(!scm::checkRevision($toRevision)) return array();
  253. $path = ltrim($path, DIRECTORY_SEPARATOR);
  254. $count = $count == 0 ? '' : "-n $count";
  255. $list = $this->getCommitsByPath($path, $fromRevision, $toRevision);
  256. foreach($list as $commit)
  257. {
  258. if(isset($commit->id)) $commit->diffs = $this->getFilesByCommit($commit->id);
  259. }
  260. return $this->parseLog($list);
  261. }
  262. /**
  263. * Blame file
  264. *
  265. * @param string $path
  266. * @param string $revision
  267. * @param bool $showComment
  268. * @access public
  269. * @return array
  270. */
  271. public function blame($path, $revision, $showComment = true)
  272. {
  273. if(!scm::checkRevision($revision)) return array();
  274. $path = ltrim($path, DIRECTORY_SEPARATOR);
  275. $path = urlencode($path);
  276. $api = "files/$path/blame";
  277. $param = new stdclass;
  278. $param->ref = ($revision and $revision != 'HEAD') ? $revision : $this->branch;
  279. $results = $this->fetch($api, $param);
  280. if(empty($results) or isset($results->message)) return array();
  281. $blames = array();
  282. $revision = '';
  283. $lineNumber = 1;
  284. foreach($results as $blame)
  285. {
  286. $line = array();
  287. $line['revision'] = $blame->commit->id;
  288. $line['committer'] = $blame->commit->committer_name;
  289. $line['message'] = $blame->commit->message;
  290. $line['time'] = date('Y-m-d H:i:s', strtotime($blame->commit->committed_date));
  291. $line['line'] = $lineNumber;
  292. $line['lines'] = count($blame->lines);
  293. $line['content'] = array_shift($blame->lines);
  294. $blames[$lineNumber] = $line;
  295. $lineNumber ++;
  296. foreach($blame->lines as $line)
  297. {
  298. $blames[$lineNumber] = array('line' => $lineNumber, 'content' => $line);
  299. $lineNumber ++;
  300. }
  301. }
  302. return $blames;
  303. }
  304. /**
  305. * Diff file.
  306. *
  307. * @param string $path
  308. * @param string $fromRevision
  309. * @param string $toRevision
  310. * @param string $fromProject
  311. * @param string $extra
  312. * @access public
  313. * @return array
  314. */
  315. public function diff($path, $fromRevision, $toRevision, $fromProject = '', $extra = '')
  316. {
  317. if(!scm::checkRevision($fromRevision) and $extra != 'isBranchOrTag') return array();
  318. if(!scm::checkRevision($toRevision) and $extra != 'isBranchOrTag') return array();
  319. $sameVersion = $fromRevision == '^' || strpos($fromRevision, $toRevision) === 0;
  320. $api = $sameVersion ? "commits/$toRevision/diff" : "compare";
  321. $params = array('from' => $fromRevision, 'to' => $toRevision, 'straight' => true);
  322. if($fromProject) $params['from_project_id'] = $fromProject;
  323. if($toRevision == 'HEAD' and $this->branch) $params['to'] = $this->branch;
  324. $results = $this->fetch($api, $sameVersion ? array() : $params);
  325. $diffs = isset($results->diffs) ? $results->diffs : array();
  326. if($sameVersion && is_array($results)) $diffs = $results;
  327. if(!$diffs) return array();
  328. $lines = array();
  329. foreach($diffs as $diff)
  330. {
  331. if($path != '' && strpos($diff->new_path, $path) === false) continue;
  332. $lines[] = sprintf("diff --git a/%s b/%s", $diff->old_path, $diff->new_path);
  333. $lines[] = sprintf("index %s ... %s %s ", $fromRevision, $toRevision, $diff->b_mode);
  334. $lines[] = sprintf("--a/%s", $diff->old_path);
  335. $lines[] = sprintf("--b/%s", $diff->new_path);
  336. $diffLines = explode("\n", $diff->diff);
  337. foreach($diffLines as $diffLine) $lines[] = $diffLine;
  338. }
  339. return $lines;
  340. }
  341. /**
  342. * Cat file.
  343. *
  344. * @param string $entry
  345. * @param string $revision
  346. * @access public
  347. * @return string
  348. */
  349. public function cat($entry, $revision = 'HEAD')
  350. {
  351. if(!scm::checkRevision($revision)) return false;
  352. if($revision == 'HEAD' and $this->branch) $revision = $this->branch;
  353. $file = $this->files($entry, $revision);
  354. return isset($file->content) ? base64_decode($file->content) : '';
  355. }
  356. /**
  357. * Get info.
  358. *
  359. * @param string $entry
  360. * @param string $revision
  361. * @access public
  362. * @return object
  363. */
  364. public function info($entry, $revision = 'HEAD')
  365. {
  366. if(!scm::checkRevision($revision)) return false;
  367. $info = new stdclass();
  368. $info->kind = 'dir';
  369. $info->path = $entry;
  370. $info->revision = $revision;
  371. $info->root = '';
  372. if($revision == 'HEAD' and $this->branch) $info->revision = $this->branch;
  373. if($entry)
  374. {
  375. $parent = dirname($entry);
  376. if($parent == '.') $parent = '/';
  377. if($parent == '') $parent = '/';
  378. $list = $this->tree($parent, 0);
  379. $file = new stdclass();
  380. if(!empty($list)) foreach($list as $node) if($node->path == $entry) $file = $node;
  381. $commits = $this->getCommitsByPath($entry);
  382. if(!empty($commits)) $file->revision = zget($commits[0], 'id', '');
  383. $info->kind = (isset($file->type) and $file->type == 'tree') ? 'dir' : 'file';
  384. }
  385. return $info;
  386. }
  387. /**
  388. * Exec git cmd.
  389. *
  390. * @param string $cmd
  391. * @access public
  392. * @todo Exec commands by gitlab api.
  393. * @return array
  394. */
  395. public function exec($cmd)
  396. {
  397. return execCmd(escapeCmd("$this->client $cmd"), 'array');
  398. }
  399. /**
  400. * Parse diff.
  401. *
  402. * @param array $lines
  403. * @access public
  404. * @return array
  405. */
  406. public function parseDiff($lines)
  407. {
  408. if(empty($lines)) return array();
  409. $diffs = array();
  410. $num = count($lines);
  411. $endLine = end($lines);
  412. if(strpos($endLine, '\ No newline at end of file') === 0) $num -= 1;
  413. $newFile = false;
  414. $allFiles = array();
  415. for($i = 0; $i < $num; $i ++)
  416. {
  417. $diffFile = new stdclass();
  418. if(strpos($lines[$i], "diff --git ") === 0)
  419. {
  420. $fileInfo = explode(' ',$lines[$i]);
  421. $fileName = substr($fileInfo[2], strpos($fileInfo[2], '/') + 1);
  422. /* Prevent duplicate display of files. */
  423. if(in_array($fileName, $allFiles)) continue;
  424. $allFiles[] = $fileName;
  425. $diffFile->fileName = $fileName;
  426. for($i++; $i < $num; $i ++)
  427. {
  428. $diff = new stdclass();
  429. /* Fix bug #1757. */
  430. if($lines[$i] == '+++ /dev/null') $newFile = true;
  431. if(strpos($lines[$i], '+++', 0) !== false) continue;
  432. if(strpos($lines[$i], '---', 0) !== false) continue;
  433. if(strpos($lines[$i], '======', 0) !== false) continue;
  434. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i]))
  435. {
  436. $startLines = trim(str_replace(array('@', '+', '-'), '', $lines[$i]));
  437. list($oldStartLine, $newStartLine) = explode(' ', $startLines);
  438. list($diff->oldStartLine) = explode(',', $oldStartLine);
  439. list($diff->newStartLine) = explode(',', $newStartLine);
  440. $oldCurrentLine = $diff->oldStartLine;
  441. $newCurrentLine = $diff->newStartLine;
  442. if($newFile)
  443. {
  444. $oldCurrentLine = $diff->newStartLine;
  445. $newCurrentLine = $diff->oldStartLine;
  446. }
  447. $newLines = array();
  448. for($i++; $i < $num; $i ++)
  449. {
  450. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i]))
  451. {
  452. $i --;
  453. break;
  454. }
  455. if(strpos($lines[$i], "diff --git ") === 0) break;
  456. $line = $lines[$i];
  457. if(strpos($line, '\ No newline at end of file') === 0)continue;
  458. $sign = empty($line) ? '' : $line[0];
  459. if($sign == '-' and $newFile) $sign = '+';
  460. $type = $sign != '-' ? $sign == '+' ? 'new' : 'all' : 'old';
  461. if($sign == '-' || $sign == '+')
  462. {
  463. $line = substr_replace($line, ' ', 1, 0);
  464. if($newFile) $line = preg_replace('/^\-/', '+', $line);
  465. }
  466. $newLine = new stdclass();
  467. $newLine->type = $type;
  468. $newLine->oldlc = $type != 'new' ? $oldCurrentLine : '';
  469. $newLine->newlc = $type != 'old' ? $newCurrentLine : '';
  470. $newLine->line = htmlSpecialString($line);
  471. if($type != 'new') $oldCurrentLine++;
  472. if($type != 'old') $newCurrentLine++;
  473. $newLines[] = $newLine;
  474. }
  475. $diff->lines = $newLines;
  476. $diffFile->contents[] = $diff;
  477. }
  478. if(isset($lines[$i]) and strpos($lines[$i], "diff --git ") === 0)
  479. {
  480. $i --;
  481. $newFile = false;
  482. break;
  483. }
  484. }
  485. $diffs[] = $diffFile;
  486. }
  487. }
  488. return $diffs;
  489. }
  490. /**
  491. * Get commit count.
  492. *
  493. * @param int $commits
  494. * @param string $lastVersion
  495. * @access public
  496. * @return int
  497. */
  498. public function getCommitCount($commits = 0, $lastVersion = '')
  499. {
  500. if(!scm::checkRevision($lastVersion)) return false;
  501. chdir($this->root);
  502. $revision = $this->branch ? $this->branch : 'HEAD';
  503. return execCmd(escapeCmd("$this->client rev-list --count $revision -- ./"), 'string');
  504. }
  505. /**
  506. * Get first revision.
  507. *
  508. * @access public
  509. * @return string
  510. */
  511. public function getFirstRevision()
  512. {
  513. chdir($this->root);
  514. $list = execCmd(escapeCmd("$this->client rev-list --reverse HEAD -- ./"), 'array');
  515. return $list[0];
  516. }
  517. /**
  518. * Get latest revision
  519. *
  520. * @access public
  521. * @return string
  522. */
  523. public function getLatestRevision()
  524. {
  525. chdir($this->root);
  526. $revision = $this->branch ? $this->branch : 'HEAD';
  527. $list = execCmd(escapeCmd("$this->client rev-list -1 $revision -- ./"), 'array');
  528. return $list[0];
  529. }
  530. /**
  531. * Get commits.
  532. *
  533. * @param string $version
  534. * @param int $count
  535. * @param string $branch
  536. * @param bool $getFile
  537. * @access public
  538. * @return array
  539. */
  540. public function getCommits($version = '', $count = 0, $branch = '', $getFile = false)
  541. {
  542. if(!scm::checkRevision($version)) return array();
  543. $api = "commits";
  544. $commits = array();
  545. $files = array();
  546. if(empty($count)) $count = 10;
  547. if(!empty($version) and $count == 1)
  548. {
  549. $api .= '/' . $version;
  550. $commit = $this->fetch($api);
  551. if(isset($commit->id))
  552. {
  553. $log = new stdclass;
  554. $log->committer = $commit->committer_name;
  555. $log->revision = $commit->id;
  556. $log->comment = $commit->message;
  557. $log->time = date('Y-m-d H:i:s', strtotime($commit->created_at));
  558. $commits[$commit->id] = $log;
  559. if($getFile) $files[$commit->id] = $this->getFilesByCommit($log->revision);
  560. return array('commits' => $commits, 'files' => $files);
  561. }
  562. }
  563. $params = array();
  564. $params['ref_name'] = $branch;
  565. $params['per_page'] = $count;
  566. $params['all'] = 0;
  567. if($version and $version != 'HEAD')
  568. {
  569. /* Get since param. */
  570. if(substr($version, 0, 5) == 'since')
  571. {
  572. $since = true;
  573. $version = substr($version, 5);
  574. }
  575. $committedDate = $this->getCommittedDate($version);
  576. if(!$committedDate) return array('commits' => array(), 'files' => array());
  577. if(!empty($since))
  578. {
  579. $params['since'] = $committedDate;
  580. }
  581. else
  582. {
  583. $params['until'] = $committedDate;
  584. }
  585. }
  586. $list = $this->fetch($api, $params);
  587. foreach($list as $commit)
  588. {
  589. if(!is_object($commit)) continue;
  590. $log = new stdclass;
  591. $log->committer = $commit->committer_name;
  592. $log->revision = $commit->id;
  593. $log->comment = $commit->message;
  594. $log->time = date('Y-m-d H:i:s', strtotime($commit->created_at));
  595. $commits[$commit->id] = $log;
  596. if($getFile) $files[$commit->id] = $this->getFilesByCommit($log->revision);
  597. }
  598. return array('commits' => $commits, 'files' => $files);
  599. }
  600. /**
  601. * getCommit
  602. *
  603. * @param int $sha
  604. * @access public
  605. * @return void
  606. */
  607. public function getCommittedDate($sha)
  608. {
  609. if(!scm::checkRevision($sha)) return null;
  610. if(!$sha or $sha == 'HEAD') return date('c');
  611. global $dao;
  612. $time = $dao->select('time')->from(TABLE_REPOHISTORY)->where('revision')->eq($sha)->fetch('time');
  613. if($time) return date('c', strtotime($time));
  614. $result = $this->fetch("commits/$sha");
  615. return (isset($result->committed_date)) ? $result->committed_date : false;
  616. }
  617. /**
  618. * Get commits by path.
  619. *
  620. * @param string $path
  621. * @param string $fromRevision
  622. * @param string $toRevision
  623. * @param int $perPage
  624. * @param int $page
  625. * @param string $beginDate
  626. * @param string $endDate
  627. * @param string $committer
  628. * @access public
  629. * @return array
  630. */
  631. public function getCommitsByPath($path, $fromRevision = '', $toRevision = '', $perPage = 0, $page = 1, $beginDate = '', $endDate = '', $committer = '')
  632. {
  633. $path = ltrim($path, DIRECTORY_SEPARATOR);
  634. $api = "commits";
  635. $param = new stdclass();
  636. $param->path = urldecode($path);
  637. $param->ref_name = $toRevision ? $toRevision : $this->branch;
  638. $fromDate = $beginDate ? $beginDate : $this->getCommittedDate($fromRevision);
  639. $toDate = $endDate ? $endDate : $this->getCommittedDate($toRevision);
  640. $since = '';
  641. $until = '';
  642. if(($fromRevision && $toRevision) || ($beginDate && $endDate))
  643. {
  644. $since = min($fromDate, $toDate);
  645. $until = max($fromDate, $toDate);
  646. }
  647. elseif($fromRevision || $beginDate)
  648. {
  649. $since = $fromDate;
  650. }
  651. elseif($toRevision || $endDate)
  652. {
  653. $until = $toDate;
  654. }
  655. if($since) $param->since = $since;
  656. if($until) $param->until = $until;
  657. if($perPage) $param->per_page = $perPage;
  658. if($page) $param->page = $page;
  659. if($committer) $param->author = $committer;
  660. return $this->fetch($api, $param);
  661. }
  662. /**
  663. * Get files by commit.
  664. *
  665. * @param string $commit
  666. * @access public
  667. * @return void
  668. */
  669. public function getFilesByCommit($revision)
  670. {
  671. if(!scm::checkRevision($revision)) return array();
  672. $api = "commits/{$revision}/diff";
  673. $params = new stdclass;
  674. $params->page = 1;
  675. $params->per_page = 100;
  676. $allResults = array();
  677. while(true)
  678. {
  679. $results = $this->fetch($api, $params);
  680. $params->page ++;
  681. if(!is_array($results)) $results = array();
  682. $allResults = $allResults + $results;
  683. if(count($results) < 100) break;
  684. }
  685. $files = array();
  686. foreach($allResults as $row)
  687. {
  688. $file = new stdclass();
  689. $file->revision = $revision;
  690. $file->path = '/' . $row->new_path;
  691. $file->type = 'file';
  692. $file->oldPath = '/' . $row->old_path;
  693. $file->action = 'M';
  694. if($row->new_file) $file->action = 'A';
  695. if($row->renamed_file) $file->action = 'R';
  696. if($row->deleted_file) $file->action = 'D';
  697. $files[] = $file;
  698. }
  699. return $files;
  700. }
  701. /**
  702. * Repository/tree api.
  703. *
  704. * @param string $path
  705. * @param bool $recursive
  706. * @param bool $loop
  707. * @access public
  708. * @return mixed
  709. */
  710. public function tree($path, $recursive = 1, $loop = false)
  711. {
  712. $api = "tree";
  713. $params = array();
  714. $params['path'] = ltrim($path, '/');
  715. $params['ref'] = $this->branch;
  716. $params['recursive'] = (int) $recursive;
  717. return $this->fetch($api, $params, $loop, $loop ? true : false);
  718. }
  719. /**
  720. * Fetch data from gitlab api.
  721. *
  722. * @param string $api
  723. * @param array $params
  724. * @param bool $needToLoop
  725. * @param bool $multi
  726. * @access public
  727. * @return mixed
  728. */
  729. public function fetch($api, $params = array(), $needToLoop = false, $multi = false)
  730. {
  731. $params = (array) $params;
  732. $params['private_token'] = $this->token;
  733. $params['per_page'] = isset($params['per_page']) ? $params['per_page'] : 100;
  734. $api = ltrim($api, '/');
  735. $api = $this->root . $api . '?' . http_build_query($params);
  736. if($needToLoop)
  737. {
  738. $allResults = array();
  739. if($multi)
  740. {
  741. $results = commonModel::http($api . "&page=1", null, array(), array(), 'data', 'GET', 30, true, false);
  742. if(empty($results['header']['X-Total-Pages']) && empty($results['header']['x-total-pages'])) return array();
  743. $totalPages = isset($results['header']['X-Total-Pages']) ? isset($results['header']['X-Total-Pages']) : $results['header']['x-total-pages'];
  744. if($totalPages == 1)
  745. {
  746. $allResults = json_decode($results['body']);
  747. }
  748. else
  749. {
  750. $requests = array();
  751. for($page = 1; $page <= $totalPages; $page++)
  752. {
  753. $requests[$page]['url'] = $api . "&page={$page}";
  754. }
  755. $results = requests::request_multiple($requests, array('timeout' => 60));
  756. foreach($results as $result)
  757. {
  758. if(empty($result->body)) continue;
  759. $data = json_decode($result->body);
  760. $allResults = array_merge($allResults, $data);
  761. }
  762. }
  763. }
  764. else
  765. {
  766. for($page = 1; true; $page++)
  767. {
  768. $results = json_decode(commonModel::http($api . "&page={$page}", null, array(), array(), 'json'));
  769. if(!is_array($results)) break;
  770. if(!empty($results)) $allResults = array_merge($allResults, $results);
  771. if(count($results) < 100) break;
  772. }
  773. }
  774. return $allResults;
  775. }
  776. else
  777. {
  778. $response = commonModel::http($api, null, array(), array(), 'data', 'POST', 30, true, false);
  779. if(!empty(commonModel::$requestErrors))
  780. {
  781. commonModel::$requestErrors = array();
  782. return array();
  783. }
  784. if(in_array($response[1], array(500, 404, 401))) return array();
  785. return json_decode($response['body']);
  786. }
  787. }
  788. /**
  789. * Format bytes shown.
  790. *
  791. * @param int $size
  792. * @static
  793. * @access public
  794. * @return string
  795. */
  796. public static function formatBytes($size)
  797. {
  798. if($size < 1024) return $size . 'Bytes';
  799. if(round($size / (1024 * 1024), 2) > 1) return round($size / (1024 * 1024), 2) . 'G';
  800. if(round($size / 1024, 2) > 1) return round($size / 1024, 2) . 'M';
  801. return round($size, 2) . 'KB';
  802. }
  803. /**
  804. * Parse log.
  805. *
  806. * @param array $logs
  807. * @access public
  808. * @return array
  809. */
  810. public function parseLog($logs)
  811. {
  812. $parsedLogs = array();
  813. foreach($logs as $commit)
  814. {
  815. if(!isset($commit->id)) continue;
  816. $parsedLog = new stdclass();
  817. $parsedLog->revision = $commit->id;
  818. $parsedLog->committer = $commit->committer_name;
  819. $parsedLog->time = date('Y-m-d H:i:s', strtotime($commit->committed_date));
  820. $parsedLog->comment = $commit->message;
  821. $parsedLog->change = array();
  822. if(!empty($commit->diffs))
  823. {
  824. foreach($commit->diffs as $diff)
  825. {
  826. $parsedLog->change[$diff->path] = array();
  827. $parsedLog->change[$diff->path]['action'] = $diff->action;
  828. $parsedLog->change[$diff->path]['kind'] = $diff->type;
  829. $parsedLog->change[$diff->path]['oldPath'] = $diff->oldPath;
  830. }
  831. }
  832. $parsedLogs[] = $parsedLog;
  833. }
  834. return $parsedLogs;
  835. }
  836. /**
  837. * Get download url.
  838. *
  839. * @param string $branch
  840. * @param string $savePath
  841. * @param string $ext
  842. * @access public
  843. * @return string
  844. */
  845. public function getDownloadUrl($branch = 'master', $savePath = '', $ext = 'zip')
  846. {
  847. $params = array();
  848. $params['private_token'] = $this->token;
  849. $params['sha'] = $branch;
  850. return "{$this->root}archive.{$ext}" . '?' . http_build_query($params);
  851. }
  852. /**
  853. * List all files.
  854. *
  855. * @param string $path
  856. * @param string $revision
  857. * @param array $lists
  858. * @access public
  859. * @return array
  860. */
  861. public function getAllFiles($path = '', $revision = 'HEAD', &$lists = array())
  862. {
  863. if(!scm::checkRevision($revision)) return array();
  864. $api = "tree";
  865. $param = new stdclass();
  866. $param->path = ltrim($path, '/');
  867. $param->ref = $revision;
  868. $param->recursive = 0;
  869. if(!empty($this->branch)) $param->ref = $this->branch;
  870. $list = $this->fetch($api, $param, true);
  871. if(empty($list)) return array();
  872. $infos = array();
  873. foreach($list as $file)
  874. {
  875. if(!isset($file->type)) continue;
  876. if($file->type == 'blob')
  877. {
  878. $lists[] = $file->path;
  879. }
  880. else
  881. {
  882. $this->getAllFiles($file->path, $revision, $lists);
  883. }
  884. }
  885. return $lists;
  886. }
  887. /**
  888. * 获取特定对象的api。
  889. * Get api url for target.
  890. *
  891. * @param string $target
  892. * @access public
  893. * @return string
  894. */
  895. public function getApiUrl($target)
  896. {
  897. if($target == 'project')
  898. {
  899. return str_replace('repository/', '', $this->root). "?private_token={$this->token}";
  900. }
  901. $params = array();
  902. $params['private_token'] = $this->token;
  903. $params['page'] = 1;
  904. $params['per_page'] = isset($params['per_page']) ? $params['per_page'] : 100;
  905. $api = $this->root . $target . '?' . http_build_query($params);
  906. return $api;
  907. }
  908. /**
  909. * 通过API创建合并请求。
  910. * Create mr by api.
  911. *
  912. * @param object $MR
  913. * @param string $openID
  914. * @param string $assignee
  915. * @access public
  916. * @return object|null
  917. */
  918. public function createMR($MR, $openID, $assignee)
  919. {
  920. $MRObject = new stdclass();
  921. $MRObject->title = $MR->title;
  922. $MRObject->target_project_id = $MR->targetProject;
  923. $MRObject->source_branch = $MR->sourceBranch;
  924. $MRObject->target_branch = $MR->targetBranch;
  925. $MRObject->description = $MR->description;
  926. $MRObject->remove_source_branch = $MR->removeSourceBranch == '1' ? true : false;
  927. $MRObject->squash = $MR->squash == '1' ? 1 : 0;
  928. if(!empty($assignee)) $MRObject->assignee_ids = $assignee;
  929. global $app;
  930. $url = str_replace('repository/', '', $this->root);
  931. $url .= "merge_requests?private_token={$this->token}";
  932. if(!$app->user->admin) $url .= "&sudo={$openID}";
  933. return json_decode(commonModel::http($url, $MRObject));
  934. }
  935. /**
  936. * Get a mr by api.
  937. *
  938. * @param int $MRID
  939. * @access public
  940. * @return array
  941. */
  942. public function getSingleMR($MRID)
  943. {
  944. $this->root = str_replace('repository/', '', $this->root);
  945. $MR = $this->fetch("merge_requests/$MRID");
  946. if(empty($MR)) return null;
  947. if(empty($MR->changes_count)) $MR->has_conflicts = 0;
  948. $MR->is_draft = strpos($MR->title, 'Draft:') === 0 || strpos($MR->title, 'WIP:') === 0;
  949. return $MR;
  950. }
  951. /**
  952. * 根据开始时间和结束时间获取提交时间和提交人。
  953. * Get commit count by date.
  954. *
  955. * @param int $startDate
  956. * @param int $endDate
  957. * @access public
  958. * @return array
  959. */
  960. public function getCommitByDate($startDate, $endDate)
  961. {
  962. $statistics = array();
  963. $commits = $this->fetch('commits', array('since' => $startDate, 'until' => $endDate), true);
  964. foreach($commits as $commit)
  965. {
  966. $item = new stdclass();
  967. $item->author = $commit->author_name;
  968. $item->time = date('Y-m-d H:i:s', strtotime($commit->committed_date));
  969. $statistics[] = $item;
  970. }
  971. return $statistics;
  972. }
  973. }