PageRenderTime 67ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/vcs_stats/includes/vcs_git.php

https://github.com/yak/virtualmeet-vcs-tools
PHP | 303 lines | 131 code | 80 blank | 92 comment | 23 complexity | af503cfa26260a32cbc244a80892a072 MD5 | raw file
Possible License(s): JSON
  1. <?php
  2. require_once('shell.php');
  3. require_once('commit.php');
  4. /**
  5. * Git interface
  6. *
  7. * Simplistic Git interface geared towards getting commit data and branching
  8. * for the purpose of running code stats against the source. As such, it only
  9. * wraps a subset of functionality.
  10. *
  11. * @package vcs-stats
  12. * @author Kristoffer Lindqvist <kris@tsampa.org>
  13. * @copyright 2009 Kristoffer Lindqvist
  14. * @license http://www.opensource.org/licenses/mit-license.php MIT license
  15. */
  16. class VCS_Git {
  17. const CUT_BINARY = '/usr/bin/env cut';
  18. const WC_BINARY = '/usr/bin/env wc';
  19. protected $repository_path = null;
  20. protected $binary_path = null;
  21. protected $changelog = null;
  22. /**
  23. * Convenience wrapper for all gymnastics required to call Git
  24. *
  25. * @var string
  26. */
  27. protected $git_cmd_prefix = null;
  28. public function __construct($repository_path, $binary_path = '/usr/bin/git') {
  29. // ---- got Git?
  30. if (!is_file($binary_path)) {
  31. throw new GitException("Could not find Git at $binary_path");
  32. }
  33. // ---- got a repository?
  34. if (!is_string($repository_path) || strlen($repository_path) == 0) {
  35. throw new InvalidArgumentException('Invalid or no repository path');
  36. }
  37. if (!is_dir($repository_path)) {
  38. throw new GitException("There is no Git repository at $repository_path");
  39. }
  40. if (!is_dir("$repository_path/.git")) {
  41. $this->error("$repository_path is not a Git repository");
  42. }
  43. $this->binary_path = $binary_path;
  44. $this->repository_path = $repository_path;
  45. $this->git_cmd_prefix = 'cd ' . $this->repository_path . ' && ' . $this->binary_path;
  46. }
  47. // ----- repository ----- //
  48. /**
  49. * Get the path to the root of the repository
  50. *
  51. * @return string
  52. */
  53. public function get_repository_path() {
  54. return $this->repository_path;
  55. }
  56. /**
  57. * Get the name of the Version Control System
  58. *
  59. * @return string
  60. */
  61. public function get_vcs_name() {
  62. return 'git';
  63. }
  64. /**
  65. * Get the version of the Version Control System
  66. *
  67. * @return string
  68. */
  69. public function get_vcs_version() {
  70. $stdout = Shell::exec($this->binary_path . ' --version | cut -d " " -f3', $exit_code, $stderr);
  71. if ($exit_code !== 0) {
  72. throw new GitException("Could not get git version: $stderr");
  73. }
  74. return $stdout;
  75. }
  76. // ----- branching ----- //
  77. /**
  78. * Get the name of the currently active branch
  79. *
  80. * @return string
  81. */
  82. public function get_current_branch() {
  83. $stdout = trim(Shell::exec($this->git_cmd_prefix . ' branch | ' . self::CUT_BINARY . ' -d " " -f 2', $exit_code, $stderr));
  84. if ($exit_code !== 0 || strlen($stdout) === 0) {
  85. throw new GitException("Could not get current branch: $stderr");
  86. }
  87. return $stdout;
  88. }
  89. /**
  90. * Create a new branch from a commit id
  91. *
  92. * @param string $branch: name of the new branch to create
  93. * @param string $commit_id: commit id to branch from
  94. * @return boolean true if the create succeeded
  95. */
  96. public function create_branch($branch, $commit_id) {
  97. $stdout = Shell::exec($this->git_cmd_prefix . " branch $branch $commit_id", $exit_code, $stderr);
  98. if ($exit_code !== 0) {
  99. throw new GitException("Could not create branch $branch: $stderr");
  100. }
  101. return true;
  102. }
  103. /**
  104. * Delete a branch
  105. *
  106. * @param string $branch: name of the branch to delete
  107. * @return boolean true if the delete succeeded
  108. */
  109. public function delete_branch($branch) {
  110. Shell::exec($this->git_cmd_prefix . ' branch -D ' . $branch, $exit_code, $stderr); // . " > /dev/null"
  111. if ($exit_code !== 0) {
  112. throw new GitException("Could not delete branch $branch: $stderr");
  113. }
  114. return true;
  115. }
  116. /**
  117. * Switch to an existing branch
  118. *
  119. * @param string $branch: name of the branch to switch to
  120. * @return boolean true if the switch succeeded
  121. */
  122. public function switch_to_branch($branch) {
  123. $stdout = Shell::exec($this->git_cmd_prefix . ' checkout ' . $branch, $exit_code, $stderr);
  124. if ($exit_code !== 0) {
  125. throw new GitException("Could not switch branch to $branch: $stderr");
  126. }
  127. return true;
  128. }
  129. // ----- log ----- //
  130. /**
  131. * Get the HEAD commit, eg. the most recent change commit in the active branch
  132. *
  133. * @return Commit
  134. */
  135. public function get_head_commit() {
  136. $stdout = Shell::exec($this->git_cmd_prefix . ' log -n1 --no-merges ', $exit_code, $stderr);
  137. if ($exit_code !== 0) {
  138. throw new GitException("Could not get HEAD commit: $stderr");
  139. }
  140. return $this->create_commit($stdout);
  141. }
  142. /**
  143. * Get the parent (first older) change commit of a commit
  144. *
  145. * @param string $commit_id: commit id to get the parent for
  146. * @return Commit
  147. */
  148. public function get_parent_of_commit($commit_id) {
  149. $stdout = Shell::exec($this->git_cmd_prefix . ' log -n1 --no-merges ' . $commit_id . '^', $exit_code, $stderr);
  150. if ($exit_code !== 0) {
  151. throw new GitException("Could not get parent of commit $commit_id: $stderr");
  152. }
  153. return $this->create_commit($stdout);
  154. }
  155. /**
  156. * Get the total number of change commits in the currently active branch
  157. *
  158. * @return integer
  159. */
  160. public function get_commit_count() {
  161. $stdout = Shell::exec($this->git_cmd_prefix . ' log --oneline --no-merges | ' . self::WC_BINARY . ' -l', $exit_code, $stderr);
  162. if ($exit_code !== 0 || !is_numeric($stdout)) {
  163. throw new GitException("Could not get commit count: $stderr");
  164. }
  165. return (int)$stdout;
  166. }
  167. /**
  168. * Get a particulat commit
  169. *
  170. * @param string $commit_id: commit to get
  171. * @return Commit
  172. */
  173. public function get_commit($commit_id) {
  174. if (strlen($commit_id) !== 40) {
  175. throw new GitException('Commit is not a SHA1 hash: ' . $commit_id);
  176. }
  177. $stdout = Shell::exec($this->git_cmd_prefix . ' log -c ' . $commit_id . ' -n 1', $exit_code, $stderr);
  178. if ($exit_code !== 0) {
  179. throw new GitException("Could not get commit $commit_id: $stderr");
  180. }
  181. return $this->create_commit($stdout);
  182. }
  183. // ===== PRIVATE INTERFACE ===== //
  184. /**
  185. * Create a Commit based on Git log data
  186. *
  187. * @param string $commit_data: commit data to parse
  188. * @return Commit
  189. */
  190. protected function create_commit($commit_data) {
  191. $commit_data = explode("\n", $commit_data);
  192. if ($commit_data) {
  193. $line_count = count($commit_data);
  194. $commit_author = null;
  195. $commit_date = null;
  196. for ($i = 0; $i < $line_count; $i++) {
  197. $line = explode(' ', $commit_data[$i], 2);
  198. if (!isset($line[1])) {
  199. continue;
  200. }
  201. $line[1] = trim($line[1]);
  202. switch ($line[0]) {
  203. case 'commit':
  204. $commit_id = $line[1];
  205. break;
  206. case 'Author:':
  207. $commit_author = $line[1];
  208. break;
  209. case 'Date:':
  210. // postgres can munch this standard date string as-is, no
  211. // need to go via a Unix timestamp, eg. strtotime()
  212. $commit_date = $line[1];
  213. break;
  214. default:
  215. continue;
  216. }
  217. }
  218. $commit_message = trim( $commit_data[$line_count - 1] );
  219. return new Commit($commit_id, $commit_author, $commit_date, $commit_message);
  220. }
  221. }
  222. }
  223. class GitException extends Exception { }