PageRenderTime 1082ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/Vcs/lib/Horde/Vcs/Git.php

https://github.com/sgtcarneiro/horde
PHP | 323 lines | 160 code | 39 blank | 124 comment | 26 complexity | 74fe2cd24b3a5abc0de047e90d52df75 MD5 | raw file
  1. <?php
  2. /**
  3. * Horde_Vcs_Git implementation.
  4. *
  5. * Constructor args:
  6. * <pre>
  7. * 'sourceroot': The source root for this repository
  8. * 'paths': Hash with the locations of all necessary binaries: 'git'
  9. * </pre>
  10. *
  11. * @TODO find bad output earlier - use proc_open, check stderr or result codes?
  12. *
  13. * Copyright 2008-2011 The Horde Project (http://www.horde.org/)
  14. *
  15. * See the enclosed file COPYING for license information (LGPL). If you
  16. * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
  17. *
  18. * @author Chuck Hagenbuch <chuck@horde.org>
  19. * @author Michael Slusarz <slusarz@horde.org>
  20. * @package Vcs
  21. */
  22. class Horde_Vcs_Git extends Horde_Vcs
  23. {
  24. /**
  25. * Does driver support patchsets?
  26. *
  27. * @var boolean
  28. */
  29. protected $_patchsets = true;
  30. /**
  31. * Does driver support branches?
  32. *
  33. * @var boolean
  34. */
  35. protected $_branches = true;
  36. /**
  37. * Does driver support snapshots?
  38. *
  39. * @var boolean
  40. */
  41. protected $_snapshots = true;
  42. /**
  43. * The available diff types.
  44. *
  45. * @var array
  46. */
  47. protected $_diffTypes = array('unified');
  48. /**
  49. * The list of branches for the repo.
  50. *
  51. * @var array
  52. */
  53. protected $_branchlist;
  54. /**
  55. * The git version
  56. *
  57. * @var string
  58. */
  59. public $version;
  60. /**
  61. * @throws Horde_Vcs_Exception
  62. */
  63. public function __construct($params = array())
  64. {
  65. parent::__construct($params);
  66. if (!is_executable($this->getPath('git'))) {
  67. throw new Horde_Vcs_Exception('Missing git binary (' . $this->getPath('git') . ' is missing or not executable)');
  68. }
  69. $v = trim(shell_exec($this->getPath('git') . ' --version'));
  70. $this->version = preg_replace('/[^\d\.]/', '', $v);
  71. // Try to find the repository if we don't have the exact path. @TODO put
  72. // this into a builder method/object and cache the results.
  73. if (!file_exists($this->sourceroot() . '/HEAD')) {
  74. if (file_exists($this->sourceroot() . '.git/HEAD')) {
  75. $this->_sourceroot .= '.git';
  76. } elseif (file_exists($this->sourceroot() . '/.git/HEAD')) {
  77. $this->_sourceroot .= '/.git';
  78. } else {
  79. throw new Horde_Vcs_Exception('Can not find git repository.');
  80. }
  81. }
  82. }
  83. /**
  84. * TODO
  85. */
  86. public function isValidRevision($rev)
  87. {
  88. return $rev && preg_match('/^[a-f0-9]+$/i', $rev);
  89. }
  90. /**
  91. * TODO
  92. */
  93. public function isFile($where, $branch = null)
  94. {
  95. if (!$branch) {
  96. $branch = $this->getDefaultBranch();
  97. }
  98. $where = str_replace($this->sourceroot() . '/', '', $where);
  99. $command = $this->getCommand() . ' ls-tree ' . escapeshellarg($branch) . ' ' . escapeshellarg($where) . ' 2>&1';
  100. exec($command, $entry, $retval);
  101. if (!count($entry)) { return false; }
  102. $data = explode(' ', $entry[0]);
  103. return ($data[1] == 'blob');
  104. }
  105. /**
  106. * TODO
  107. */
  108. public function getCommand()
  109. {
  110. return escapeshellcmd($this->getPath('git')) . ' --git-dir=' . escapeshellarg($this->sourceroot());
  111. }
  112. /**
  113. * TODO
  114. *
  115. * @throws Horde_Vcs_Exception
  116. */
  117. public function annotate($fileob, $rev)
  118. {
  119. $this->assertValidRevision($rev);
  120. $command = $this->getCommand() . ' blame -p ' . escapeshellarg($rev) . ' -- ' . escapeshellarg($fileob->queryModulePath()) . ' 2>&1';
  121. $pipe = popen($command, 'r');
  122. if (!$pipe) {
  123. throw new Horde_Vcs_Exception('Failed to execute git annotate: ' . $command);
  124. }
  125. $curr_rev = null;
  126. $db = $lines = array();
  127. $lines_group = $line_num = 0;
  128. while (!feof($pipe)) {
  129. $line = rtrim(fgets($pipe, 4096));
  130. if (!$line || ($line[0] == "\t")) {
  131. if ($lines_group) {
  132. $lines[] = array(
  133. 'author' => $db[$curr_rev]['author'] . ' ' . $db[$curr_rev]['author-mail'],
  134. 'date' => $db[$curr_rev]['author-time'],
  135. 'line' => $line ? substr($line, 1) : '',
  136. 'lineno' => $line_num++,
  137. 'rev' => $curr_rev
  138. );
  139. --$lines_group;
  140. }
  141. } elseif ($line != 'boundary') {
  142. if ($lines_group) {
  143. list($prefix, $linedata) = explode(' ', $line, 2);
  144. switch ($prefix) {
  145. case 'author':
  146. case 'author-mail':
  147. case 'author-time':
  148. //case 'author-tz':
  149. $db[$curr_rev][$prefix] = trim($linedata);
  150. break;
  151. }
  152. } else {
  153. $curr_line = explode(' ', $line);
  154. $curr_rev = $curr_line[0];
  155. $line_num = $curr_line[2];
  156. $lines_group = isset($curr_line[3]) ? $curr_line[3] : 1;
  157. }
  158. }
  159. }
  160. pclose($pipe);
  161. return $lines;
  162. }
  163. /**
  164. * Function which returns a file pointing to the head of the requested
  165. * revision of a file.
  166. *
  167. * @param string $fullname Fully qualified pathname of the desired file
  168. * to checkout
  169. * @param string $rev Revision number to check out
  170. *
  171. * @return resource A stream pointer to the head of the checkout.
  172. * @throws Horde_Vcs_Exception
  173. */
  174. public function checkout($file, $rev)
  175. {
  176. $this->assertValidRevision($rev);
  177. $file_ob = $this->getFileObject($file);
  178. $hash = $file_ob->getHashForRevision($rev);
  179. if ($hash == '0000000000000000000000000000000000000000') {
  180. throw new Horde_Vcs_Exception($file . ' is deleted in commit ' . $rev);
  181. }
  182. if ($pipe = popen($this->getCommand() . ' cat-file blob ' . $hash . ' 2>&1', VC_WINDOWS ? 'rb' : 'r')) {
  183. return $pipe;
  184. }
  185. throw new Horde_Vcs_Exception('Couldn\'t perform checkout of the requested file');
  186. }
  187. /**
  188. * Create a range of revisions between two revision numbers.
  189. *
  190. * @param Horde_Vcs_File $file The desired file.
  191. * @param string $r1 The initial revision.
  192. * @param string $r2 The ending revision.
  193. *
  194. * @return array The revision range, or empty if there is no straight
  195. * line path between the revisions.
  196. */
  197. public function getRevisionRange($file, $r1, $r2)
  198. {
  199. $revs = $this->_getRevisionRange($file, $r1, $r2);
  200. return empty($revs)
  201. ? array_reverse($this->_getRevisionRange($file, $r2, $r1))
  202. : $revs;
  203. }
  204. /**
  205. * TODO
  206. */
  207. protected function _getRevisionRange($file, $r1, $r2)
  208. {
  209. $cmd = $this->getCommand() . ' rev-list ' . escapeshellarg($r1 . '..' . $r2) . ' -- ' . escapeshellarg($file->queryModulePath());
  210. $revs = array();
  211. exec($cmd, $revs);
  212. return array_map('trim', $revs);
  213. }
  214. /**
  215. * Obtain the differences between two revisions of a file.
  216. *
  217. * @param Horde_Vcs_File $file The desired file.
  218. * @param string $rev1 Original revision number to compare from.
  219. * @param string $rev2 New revision number to compare against.
  220. * @param array $opts The following optional options:
  221. * <pre>
  222. * 'num' - (integer) DEFAULT: 3
  223. * 'type' - (string) DEFAULT: 'unified'
  224. * 'ws' - (boolean) DEFAULT: true
  225. * </pre>
  226. *
  227. * @return string The diff text.
  228. */
  229. protected function _diff($file, $rev1, $rev2, $opts)
  230. {
  231. $diff = array();
  232. $flags = '';
  233. if (!$opts['ws']) {
  234. $flags .= ' -b -w ';
  235. }
  236. if (!$rev1) {
  237. $command = $this->getCommand() . ' show --oneline ' . escapeshellarg($rev2) . ' -- ' . escapeshellarg($file->queryModulePath()) . ' 2>&1';
  238. } else {
  239. switch ($opts['type']) {
  240. case 'unified':
  241. $flags .= '--unified=' . escapeshellarg((int)$opts['num']);
  242. break;
  243. }
  244. // @TODO: add options for $hr options - however these may not
  245. // be compatible with some diffs.
  246. $command = $this->getCommand() . ' diff -M -C ' . $flags . ' --no-color ' . escapeshellarg($rev1 . '..' . $rev2) . ' -- ' . escapeshellarg($file->queryModulePath()) . ' 2>&1';
  247. }
  248. exec($command, $diff, $retval);
  249. return $diff;
  250. }
  251. /**
  252. * Returns an abbreviated form of the revision, for display.
  253. *
  254. * @param string $rev The revision string.
  255. *
  256. * @return string The abbreviated string.
  257. */
  258. public function abbrev($rev)
  259. {
  260. return substr($rev, 0, 7) . '[...]';
  261. }
  262. /**
  263. * TODO
  264. */
  265. public function getBranchList()
  266. {
  267. if (!isset($this->_branchlist)) {
  268. $this->_branchlist = array();
  269. exec($this->getCommand() . ' show-ref --heads', $branch_list);
  270. foreach ($branch_list as $val) {
  271. $line = explode(' ', trim($val), 2);
  272. $this->_branchlist[substr($line[1], strrpos($line[1], '/') + 1)] = $line[0];
  273. }
  274. }
  275. return $this->_branchlist;
  276. }
  277. /**
  278. * @TODO ?
  279. */
  280. public function getDefaultBranch()
  281. {
  282. return 'master';
  283. }
  284. }