gitrepo.class.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. <?php
  2. class GitRepo
  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->client = $client;
  26. $this->root = rtrim($root, DIRECTORY_SEPARATOR);
  27. $branch = isset($_COOKIE['repoBranch']) ? $_COOKIE['repoBranch'] : '';
  28. if($branch)
  29. {
  30. $branches = $this->branch();
  31. $cmd = escapeCmd("$this->client branch -r");
  32. execCmd($cmd . ' 2>&1', 'string', $result);
  33. if(isset($branches[$branch]) && !empty($result)) $branch = "origin/$branch";
  34. }
  35. $this->branch = $branch;
  36. $this->repo = $repo;
  37. chdir($this->root);
  38. exec("{$this->client} config core.quotepath false");
  39. }
  40. /**
  41. * List files.
  42. *
  43. * @param string $path
  44. * @param string $revision
  45. * @access public
  46. * @return array
  47. */
  48. public function ls($path, $revision = 'HEAD')
  49. {
  50. if(!scm::checkRevision($revision)) return array();
  51. $path = ltrim($path, DIRECTORY_SEPARATOR);
  52. $sub = '';
  53. chdir($this->root);
  54. if(!empty($path)) $sub = ":$path";
  55. if(!empty($this->branch))$revision = $this->branch;
  56. $cmd = escapeCmd("$this->client config core.quotepath false");
  57. execCmd($cmd . ' 2>&1');
  58. $cmd = escapeCmd("$this->client ls-tree -l $revision$sub");
  59. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  60. if($result) return array();
  61. $infos = array();
  62. foreach($list as $entry)
  63. {
  64. list($mod, $kind, $revision, $size, $name) = preg_split('/[\t ]+/', $entry);
  65. /* Get commit info. */
  66. $pathName = ltrim($path . DIRECTORY_SEPARATOR . $name, DIRECTORY_SEPARATOR);
  67. $cmd = escapeCmd("$this->client log -1 $this->branch -- $pathName");
  68. $commit = execCmd($cmd, 'array');
  69. $logs = $this->parseLog($commit);
  70. if($size > 1024 * 1024)
  71. {
  72. $size = round($size / (1024 * 1024), 2) . 'MB';
  73. }
  74. else if($size > 1024)
  75. {
  76. $size = round($size / 1024, 2) . 'KB';
  77. }
  78. else
  79. {
  80. $size .= 'Bytes';
  81. }
  82. $info = new stdClass();
  83. $info->name = $name;
  84. $info->kind = $kind == 'tree' ? 'dir' : 'file';
  85. $info->revision = $logs ? $logs[0]->revision : $revision;
  86. $info->size = $size;
  87. $info->account = $logs ? $logs[0]->committer : '';
  88. $info->date = $logs ? $logs[0]->time : '';
  89. $info->comment = $logs ? $logs[0]->comment : '';
  90. $infos[] = $info;
  91. unset($info);
  92. }
  93. /* Sort by kind */
  94. foreach($infos as $key => $info) $kinds[$key] = $info->kind;
  95. if($infos) array_multisort($kinds, SORT_ASC, $infos);
  96. return $infos;
  97. }
  98. /**
  99. * Get tags
  100. *
  101. * @param string $path
  102. * @param string $revision
  103. * @access public
  104. * @return array
  105. */
  106. public function tags($path, $revision = 'HEAD')
  107. {
  108. if(!scm::checkRevision($revision)) return array();
  109. chdir($this->root);
  110. $cmd = escapeCmd("$this->client tag --sort=taggerdate");
  111. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  112. if($result) return array();
  113. foreach($list as $key => $tag)
  114. {
  115. if(!$tag) unset($list[$key]);
  116. }
  117. return $list;
  118. }
  119. /**
  120. * Get branch.
  121. *
  122. * @access public
  123. * @return array
  124. */
  125. public function branch()
  126. {
  127. chdir($this->root);
  128. /* Get local branch. */
  129. $cmd = escapeCmd("$this->client branch -a");
  130. $list = execCmd($cmd . ' 2>&1', 'array', $result);
  131. if($result) return array();
  132. /* Get default branch. */
  133. $defaultBranch = execCmd("$this->client symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'");
  134. $defaultBranch = trim($defaultBranch);
  135. $branches = array();
  136. foreach($list as $localBranch)
  137. {
  138. $localBranch = trim($localBranch);
  139. if(substr($localBranch, 0, 19) == 'remotes/origin/HEAD') continue;
  140. if(substr($localBranch, 0, 1) == '*') $localBranch = substr($localBranch, 1);
  141. if(substr($localBranch, 0, 15) == 'remotes/origin/') $localBranch = substr($localBranch, 15);
  142. $localBranch = trim($localBranch);
  143. if(empty($localBranch))continue;
  144. if($localBranch != $defaultBranch) $branches[$localBranch] = $localBranch;
  145. }
  146. asort($branches);
  147. if($defaultBranch) $branches = array($defaultBranch => $defaultBranch) + $branches;
  148. return $branches;
  149. }
  150. /**
  151. * Create a branch.
  152. *
  153. * @param string $branchName
  154. * @param string $ref
  155. * @access public
  156. * @return bool
  157. */
  158. public function createBranch($branchName = '', $ref = 'master')
  159. {
  160. chdir($this->root);
  161. $res = execCmd(escapeCmd("{$this->client} checkout -b {$branchName} origin/{$ref}"), 'array');
  162. if(empty($res[0])) return array('result' => 'fail', 'message' => 'Branch is exists.');
  163. execCmd(escapeCmd("{$this->client} push origin {$branchName}"), 'array');
  164. return array('result' => 'success', 'message' => '');
  165. }
  166. /**
  167. * 创建标签。
  168. * Create a tag in the repository.
  169. *
  170. * @param string $tagName The name of the tag to be created.
  171. * @param string $ref The revision of the tag, a commit SHA, another tag name, or branch name..
  172. * @param string $comment An optional comment for the tag.
  173. * @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.
  174. */
  175. public function createTag($tagName, $ref, $comment = '')
  176. {
  177. $tags = $this->tags('', 'HEAD');
  178. if(isset($tags[$tagName])) return array('result' => 'fail', 'message' => 'Tag is exists');
  179. chdir($this->root);
  180. execCmd(escapeCmd("{$this->client} stash"));
  181. $res = execCmd(escapeCmd("{$this->client} tag {$tagName} {$ref} -m '{$comment}'"), 'array');
  182. if(empty($res[0])) return array('result' => 'fail', 'message' => 'Created fail.');
  183. execCmd(escapeCmd("{$this->client} push origin {$tagName}"), 'array');
  184. return array('result' => 'success', 'message' => '');
  185. }
  186. /**
  187. * Get last log.
  188. *
  189. * @param string $path
  190. * @param int $count
  191. * @access public
  192. * @return array
  193. */
  194. public function getLastLog($path, $count = 10)
  195. {
  196. $path = ltrim($path, DIRECTORY_SEPARATOR);
  197. $revision = $this->branch ? $this->branch : 'HEAD';
  198. chdir($this->root);
  199. $list = execCmd(escapeCmd("$this->client log -10 $revision -- $path"), 'array');
  200. $logs = $this->parseLog($list);
  201. return $logs;
  202. }
  203. /**
  204. * Get logs
  205. *
  206. * @param string $path
  207. * @param string $fromRevision
  208. * @param string $toRevision
  209. * @param int $count
  210. * @access public
  211. * @return array
  212. */
  213. public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0)
  214. {
  215. if(!scm::checkRevision($fromRevision)) return array();
  216. if(!scm::checkRevision($toRevision)) return array();
  217. execCmd(escapeCmd("$this->client pull"));
  218. $path = ltrim($path, DIRECTORY_SEPARATOR);
  219. $count = $count == 0 ? '' : "-n $count";
  220. /* compatible with svn. */
  221. if($fromRevision == 'HEAD' and $this->branch) $fromRevision = $this->branch;
  222. if($toRevision == 'HEAD' and $this->branch) $toRevision = $this->branch;
  223. if($fromRevision === $toRevision)
  224. {
  225. $logs = array();
  226. chdir($this->root);
  227. $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status --stat-name-width=1000 -1 $fromRevision -- $path"), 'array');
  228. $logs = $this->parseLog($list);
  229. return $logs;
  230. }
  231. if(!$fromRevision)
  232. {
  233. $revisions = " $toRevision";
  234. }
  235. else
  236. {
  237. $revisions = "$fromRevision..$toRevision";
  238. }
  239. chdir($this->root);
  240. $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status --stat-name-width=1000 $count $revisions -- $path"), 'array');
  241. $logs = $this->parseLog($list);
  242. return $logs;
  243. }
  244. /**
  245. * Blame file
  246. *
  247. * @param string $path
  248. * @param string $revision
  249. * @param bool $showComment
  250. * @access public
  251. * @return array
  252. */
  253. public function blame($path, $revision, $showComment = true)
  254. {
  255. if(!scm::checkRevision($revision)) return array();
  256. $path = ltrim($path, DIRECTORY_SEPARATOR);
  257. chdir($this->root);
  258. $list = execCmd(escapeCmd("$this->client blame -c -l $revision -- $path"), 'array');
  259. $blames = array();
  260. $revLine = 0;
  261. $revision = '';
  262. foreach($list as $line)
  263. {
  264. if(empty($line)) continue;
  265. if($line[0] == '^') $line = substr($line, 1);
  266. preg_match('/^([0-9a-f]{39,40})\s.*\(\s*(\S+)\s+([\d-]+)\s(.*)\s(\d+)\)(.*)$/U', $line, $matches);
  267. if(isset($matches[1]) and $matches[1] != $revision)
  268. {
  269. $blame = array();
  270. $blame['revision'] = $matches[1];
  271. $blame['committer'] = $matches[2];
  272. $blame['time'] = $matches[3];
  273. $blame['line'] = $matches[5];
  274. $blame['lines'] = 1;
  275. $blame['content'] = strpos($matches[6], ' ') === false ? $matches[6] : substr($matches[6], 1);
  276. $blame['message'] = '';
  277. if($showComment)
  278. {
  279. $log = $this->log('', '', '', 1);
  280. $blame['message'] = $log[0]->comment;
  281. }
  282. $revision = $matches[1];
  283. $revLine = $matches[5];
  284. $blames[$revLine] = $blame;
  285. }
  286. elseif(isset($matches[5]))
  287. {
  288. $blame = array();
  289. $blame['line'] = $matches[5];
  290. $blame['content'] = strpos($matches[6], ' ') === false ? $matches[6] : substr($matches[6], 1);
  291. $blames[$matches[5]] = $blame;
  292. $blames[$revLine]['lines'] ++;
  293. }
  294. }
  295. return $blames;
  296. }
  297. /**
  298. * Diff file.
  299. *
  300. * @param string $path
  301. * @param string $fromRevision
  302. * @param string $toRevision
  303. * @param string $extra
  304. * @access public
  305. * @return array
  306. */
  307. public function diff($path, $fromRevision, $toRevision, $extra = '')
  308. {
  309. if(!scm::checkRevision($fromRevision) and $extra != 'isBranchOrTag') return array();
  310. if(!scm::checkRevision($toRevision) and $extra != 'isBranchOrTag') return array();
  311. $path = ltrim($path, DIRECTORY_SEPARATOR);
  312. chdir($this->root);
  313. if($toRevision == 'HEAD' and $this->branch) $toRevision = $this->branch;
  314. if($fromRevision == '^') $fromRevision = $toRevision . '^';
  315. if(strpos($fromRevision, '^') !== false)
  316. {
  317. $list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array');
  318. if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array');
  319. $fromRevision = $list[1];
  320. }
  321. $lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array');
  322. return $lines;
  323. }
  324. /**
  325. * Cat file.
  326. *
  327. * @param string $entry
  328. * @param string $revision
  329. * @access public
  330. * @return string
  331. */
  332. public function cat($entry, $revision = 'HEAD')
  333. {
  334. if(!scm::checkRevision($revision)) return false;
  335. chdir($this->root);
  336. if($revision == 'HEAD' and $this->branch) $revision = $this->branch;
  337. $cmd = escapeCmd("$this->client show $revision:$entry");
  338. $content = execCmd($cmd);
  339. if(is_array($content)) $content = implode("\n", $content);
  340. return $content;
  341. }
  342. /**
  343. * Get info.
  344. *
  345. * @param string $entry
  346. * @param string $revision
  347. * @access public
  348. * @return object
  349. */
  350. public function info($entry, $revision = 'HEAD')
  351. {
  352. if(!scm::checkRevision($revision)) return false;
  353. chdir($this->root);
  354. if($revision == 'HEAD' and $this->branch) $revision = $this->branch;
  355. $path = ltrim($entry, DIRECTORY_SEPARATOR);
  356. $cmd = escapeCmd("$this->client ls-tree $revision -- $path");
  357. $result = execCmd($cmd);
  358. $kind = '';
  359. if($result)
  360. {
  361. $results = explode("\n", trim($result));
  362. if(count($results) >= 2)
  363. {
  364. $kind = 'dir';
  365. }
  366. else
  367. {
  368. list($mode, $type) = explode(' ', $results[0]);
  369. $kind = $type == 'tree' ? 'dir' : 'file';
  370. }
  371. }
  372. $list = execCmd(escapeCmd("$this->client log -1 $revision --pretty=format:%H -- $path"), 'array');
  373. $revision = $list[0];
  374. $info = new stdclass();
  375. $info->kind = $kind;
  376. $info->path = $entry;
  377. $info->revision = $revision;
  378. $info->root = $this->root;
  379. return $info;
  380. }
  381. /**
  382. * Exec git cmd.
  383. *
  384. * @param string $cmd
  385. * @access public
  386. * @return array
  387. */
  388. public function exec($cmd)
  389. {
  390. chdir($this->root);
  391. return execCmd(escapeCmd("$this->client $cmd"), 'array');
  392. }
  393. /**
  394. * Parse diff.
  395. *
  396. * @param array $lines
  397. * @access public
  398. * @return array
  399. */
  400. public function parseDiff($lines)
  401. {
  402. if(empty($lines)) return array();
  403. $diffs = array();
  404. $num = count($lines);
  405. $endLine = end($lines);
  406. if(strpos($endLine, '\ No newline at end of file') === 0) $num -= 1;
  407. $newFile = false;
  408. $allFiles = array();
  409. for($i = 0; $i < $num; $i ++)
  410. {
  411. $diffFile = new stdclass();
  412. if(strpos($lines[$i], "diff --git ") === 0)
  413. {
  414. $fileInfo = explode(' ',$lines[$i]);
  415. $fileName = substr($fileInfo[2], strpos($fileInfo[2], '/') + 1);
  416. /* Prevent duplicate display of files. */
  417. if(in_array($fileName, $allFiles)) continue;
  418. $allFiles[] = $fileName;
  419. $diffFile->fileName = $fileName;
  420. for($i++; $i < $num; $i ++)
  421. {
  422. $diff = new stdclass();
  423. /* Fix bug #1757. */
  424. if($lines[$i] == '+++ /dev/null') $newFile = true;
  425. if(strpos($lines[$i], '+++', 0) !== false) continue;
  426. if(strpos($lines[$i], '---', 0) !== false) continue;
  427. if(strpos($lines[$i], '======', 0) !== false) continue;
  428. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i]))
  429. {
  430. $startLines = trim(str_replace(array('@', '+', '-'), '', $lines[$i]));
  431. list($oldStartLine, $newStartLine) = explode(' ', $startLines);
  432. list($diff->oldStartLine) = explode(',', $oldStartLine);
  433. list($diff->newStartLine) = explode(',', $newStartLine);
  434. $oldCurrentLine = $diff->oldStartLine;
  435. $newCurrentLine = $diff->newStartLine;
  436. if($newFile)
  437. {
  438. $oldCurrentLine = $diff->newStartLine;
  439. $newCurrentLine = $diff->oldStartLine;
  440. }
  441. $newLines = array();
  442. for($i++; $i < $num; $i ++)
  443. {
  444. if(preg_match('/^@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@/A', $lines[$i]))
  445. {
  446. $i --;
  447. break;
  448. }
  449. if(strpos($lines[$i], "diff --git ") === 0) break;
  450. $line = $lines[$i];
  451. if(strpos($line, '\ No newline at end of file') === 0)continue;
  452. $sign = empty($line) ? '' : $line[0];
  453. if($sign == '-' and $newFile) $sign = '+';
  454. $type = $sign != '-' ? $sign == '+' ? 'new' : 'all' : 'old';
  455. if($sign == '-' || $sign == '+')
  456. {
  457. $line = substr_replace($line, ' ', 1, 0);
  458. if($newFile) $line = preg_replace('/^\-/', '+', $line);
  459. }
  460. $newLine = new stdclass();
  461. $newLine->type = $type;
  462. $newLine->oldlc = $type != 'new' ? $oldCurrentLine : '';
  463. $newLine->newlc = $type != 'old' ? $newCurrentLine : '';
  464. $newLine->line = htmlSpecialString($line);
  465. if($type != 'new') $oldCurrentLine++;
  466. if($type != 'old') $newCurrentLine++;
  467. $newLines[] = $newLine;
  468. }
  469. $diff->lines = $newLines;
  470. $diffFile->contents[] = $diff;
  471. }
  472. if(isset($lines[$i]) and strpos($lines[$i], "diff --git ") === 0)
  473. {
  474. $i --;
  475. $newFile = false;
  476. break;
  477. }
  478. }
  479. $diffs[] = $diffFile;
  480. }
  481. }
  482. return $diffs;
  483. }
  484. /**
  485. * Get commit count.
  486. *
  487. * @param int $commits
  488. * @param string $lastVersion
  489. * @access public
  490. * @return int
  491. */
  492. public function getCommitCount($commits = 0, $lastVersion = '')
  493. {
  494. if(!scm::checkRevision($lastVersion)) return false;
  495. chdir($this->root);
  496. $revision = $this->branch ? $this->branch : 'HEAD';
  497. return execCmd(escapeCmd("$this->client rev-list --count $revision -- ./"), 'string');
  498. }
  499. /**
  500. * Get first revision.
  501. *
  502. * @access public
  503. * @return string
  504. */
  505. public function getFirstRevision()
  506. {
  507. chdir($this->root);
  508. $list = execCmd(escapeCmd("$this->client rev-list --reverse HEAD -- ./"), 'array');
  509. return $list[0];
  510. }
  511. /**
  512. * Get latest revision
  513. *
  514. * @access public
  515. * @return string
  516. */
  517. public function getLatestRevision()
  518. {
  519. chdir($this->root);
  520. $revision = $this->branch ? $this->branch : 'HEAD';
  521. $list = execCmd(escapeCmd("$this->client rev-list -1 $revision -- ./"), 'array');
  522. return $list[0];
  523. }
  524. /**
  525. * Get commits.
  526. *
  527. * @param string $rversion
  528. * @param int $count
  529. * @param string $branch
  530. * @access public
  531. * @return array
  532. */
  533. public function getCommits($revision = '', $count = 0, $branch = '')
  534. {
  535. if(!scm::checkRevision($revision)) return array();
  536. if($revision == 'HEAD' and $branch)
  537. {
  538. $revision = $branch;
  539. }
  540. elseif(is_numeric($revision))
  541. {
  542. $revision = "--skip=$revision $branch";
  543. }
  544. $count = $count == 0 ? '' : "-n $count";
  545. chdir($this->root);
  546. if($branch)
  547. {
  548. execCmd(escapeCmd("$this->client checkout -b $branch"), 'array');
  549. execCmd(escapeCmd("$this->client checkout $branch"), 'array');
  550. execCmd(escapeCmd("$this->client pull"));
  551. }
  552. $list = execCmd(escapeCmd("$this->client log $count $revision -- ./"), 'array');
  553. $commits = $this->parseLog($list);
  554. $logs = array();
  555. foreach($commits as $commit)
  556. {
  557. $hash = $commit->revision;
  558. $log = new stdClass();
  559. $log->committer = $commit->committer;
  560. $log->revision = $commit->revision;
  561. $log->comment = $commit->comment;
  562. $log->time = $commit->time;
  563. $logs['commits'][$hash] = $log;
  564. $logs['files'][$hash] = array();
  565. }
  566. if(empty($logs)) return $logs;
  567. $hash = '';
  568. $files = execCmd(escapeCmd("$this->client whatchanged $count $revision --pretty=format:%an@_@%cd@_@%H@_@%s -- ./"), 'array');
  569. foreach($files as $commit)
  570. {
  571. $commit = trim($commit);
  572. if(empty($commit)) continue;
  573. $parsedCommit = explode('@_@', $commit);
  574. if(count($parsedCommit) == 4)
  575. {
  576. list($account, $date, $hash, $comment) = $parsedCommit;
  577. }
  578. else
  579. {
  580. $file = explode(' ', $commit);
  581. $file = explode("\t", end($file));
  582. if(!isset($file[1])) $file[1] = '';
  583. list($action, $path) = $file;
  584. $parsedFile = new stdclass();
  585. $parsedFile->revision = $hash;
  586. $parsedFile->path = '/' . trim($path);
  587. $parsedFile->oldPath = isset($file[2]) ? '/' . trim($file[2]) : '';
  588. $parsedFile->type = 'file';
  589. $parsedFile->action = substr($action, 0, 1);
  590. $logs['files'][$hash][] = $parsedFile;
  591. }
  592. }
  593. return $logs;
  594. }
  595. /**
  596. * Get clone url.
  597. *
  598. * @access public
  599. * @return string
  600. */
  601. public function getCloneUrl()
  602. {
  603. $url = new stdclass();
  604. $remote = execCmd(escapeCmd("$this->client remote -v"), 'array');
  605. if(strpos($remote[0], 'oauth2:')) $remote[0] = preg_replace('/oauth2.*@/', '', $remote[0]);
  606. $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)?/';
  607. $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/';
  608. if(preg_match($pregHttp, $remote[0], $matches)) $url->http = $matches[0];
  609. if(preg_match($pregSSH, $remote[0], $matches)) $url->ssh = $matches[0];
  610. return $url;
  611. }
  612. /**
  613. * Parse log.
  614. *
  615. * @param array $logs
  616. * @access public
  617. * @return array
  618. */
  619. public function parseLog($logs)
  620. {
  621. $parsedLogs = array();
  622. $i = 0;
  623. foreach($logs as $line)
  624. {
  625. if(strpos($line, 'commit ') === 0)
  626. {
  627. if(isset($log))
  628. {
  629. $log->comment = trim($comment);
  630. $log->change = $changes;
  631. $parsedLogs[$i] = $log;
  632. $i++;
  633. }
  634. $log = new stdclass();
  635. $comment = '';
  636. $changes = array();
  637. $log->revision = trim(preg_replace('/^commit/', '', $line));
  638. }
  639. elseif(strpos($line, 'Author:') === 0)
  640. {
  641. $account = preg_replace('/^Author:/', '', $line);
  642. $log->committer = trim(preg_replace('/<[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+>/', '', $account));
  643. }
  644. elseif(strpos($line, 'Date:') === 0)
  645. {
  646. $date = trim(preg_replace('/^Date:/', '', $line));
  647. $log->time = date('Y-m-d H:i:s', strtotime($date));
  648. }
  649. elseif(preg_match('/^\s{2,}/', $line))
  650. {
  651. $comment .= $line;
  652. }
  653. elseif(strpos($line, "\t") !== false)
  654. {
  655. $lineList = explode("\t", $line);
  656. list($action, $entry) = $lineList;
  657. $entry = '/' . trim($entry);
  658. $pathInfo = array();
  659. $pathInfo['action'] = $action;
  660. $pathInfo['kind'] = 'file';
  661. $pathInfo['oldPath'] = $entry;
  662. $path = '/' . trim(isset($lineList[2]) ? $lineList[2] : $entry);
  663. $path = str_replace('//', '/', $path);
  664. $changes[$path] = $pathInfo;
  665. }
  666. }
  667. if(isset($log))
  668. {
  669. $log->comment = trim($comment);
  670. $log->change = $changes;
  671. $parsedLogs[$i] = $log;
  672. }
  673. return $parsedLogs;
  674. }
  675. /**
  676. * Get download url.
  677. *
  678. * @param string $branch
  679. * @param string $savePath
  680. * @param string $ext
  681. * @access public
  682. * @return string
  683. */
  684. public function getDownloadUrl($branch = 'master', $savePath = '', $ext = 'zip')
  685. {
  686. global $app, $config;
  687. $gitDir = scandir($this->root);
  688. $files = '';
  689. foreach($gitDir as $path)
  690. {
  691. if(!in_array($path, array('.', '..', '.git'))) $files .= $this->root . DS . "$path,";
  692. }
  693. $app->loadClass('pclzip', true);
  694. $fileName = $savePath . DS . "{$this->repo->name}_$branch.zip";
  695. $zip = new pclzip($fileName);
  696. $zip->create($files, PCLZIP_OPT_REMOVE_PATH, $this->root);
  697. return $config->webRoot . $app->getAppName() . 'data' . DS . 'repo' . DS . "{$this->repo->name}_$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. }