eb440bde1560f3d8609afa49bae19bb93d4ee741
[phpgit.git] / library / Git.php
blobeb440bde1560f3d8609afa49bae19bb93d4ee741
1 <?php
3 require_once dirname(__FILE__) . '/Git/Lazy.php';
4 require_once dirname(__FILE__) . '/Git/Actor.php';
5 require_once dirname(__FILE__) . '/Git/Commit.php';
6 require_once dirname(__FILE__) . '/Git/Exception.php';
7 require_once dirname(__FILE__) . '/Git/Repo.php';
8 require_once dirname(__FILE__) . '/Git/Tree.php';
9 require_once dirname(__FILE__) . '/Git/Blob.php';
11 /**
12 * Wrapper for Git executable. Lowest level interface.
14 class Git
17 const S = DIRECTORY_SEPARATOR;
19 static protected $executeKwargs = array(
20 'istream', 'withKeepCwd', 'withExtendedOutput',
21 'withExceptions', 'withRawOutput'
24 /**
25 * Git executable to invoke. You can replace this with a full path
26 * to your Git executable/wrapper script, but be sure to escape
27 * everything properly!
29 static public $git = 'git';
31 /**
32 * Current working directory.
34 protected $dir;
36 /**
37 * @param $dir Current working directory.
39 public function __construct($dir) {
40 $this->dir = $dir;
43 /**
44 * Returns current working directory.
46 public function getDir() {return $this->dir;}
48 // GitPython appears to have an implementation of getting
49 // attributes which also calls process. We're only going to support
50 // pure method calls... for now.
51 // public function __get($name)
53 /**
54 * Executes a command on shell and returns output.
55 * @warning $istream is a STRING not a HANDLE, as it is in the Python
56 * implementation. We might want to change this some time.
57 * @param $command Command argument list to handle.
58 * @param $istream Stdin string passed to subprocess.
59 * @param $options Lookup array of options. These options are:
60 * 'withKeepCwd' => Whether to use current working directory from
61 * getcwd() or the Git directory in $this->dir
62 * 'withExtendedOutput' => Whether to return array(status, stdout, stderr)
63 * 'withExceptions' => Whether to raise an exception if Git returns
64 * a non-zero exit status
65 * 'withRawOutput' => Whether to avoid stripping off trailing whitespace
66 * @return String stdout output when withExtendedOutput is false, see above
67 * if true.
69 public function execute($command, $istream = null, $options = array()) {
70 if (is_array($command)) $command = implode(' ', $command);
71 $options = array_merge(array(
72 'withKeepCwd' => false,
73 'withExtendedOutput' => false,
74 'withExceptions' => true,
75 'withRawOutput' => false,
76 ), $options);
77 if ($options['withKeepCwd'] || is_null($this->dir)) {
78 $cwd = getcwd();
79 } else {
80 $cwd = $this->dir;
82 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
83 // Windows dependent code, stolen from PHPT
84 $com = new COM('WScript.Shell');
85 $old = getcwd();
86 chdir($cwd); // :HACK: Determine the proper way to pass cwd to COM
87 $proc = $com->Exec($command);
88 if (!is_null($istream)) $proc->StdIn->Write($istream);
89 $stdout = $proc->StdOut->ReadAll();
90 $stderr = $proc->StdErr->ReadAll();
91 $status = $proc->ExitCode;
92 chdir($old);
93 } else {
94 // untested! Also stolen from PHPT
95 // this seems needlessly complicated
96 $pipes_template = array(
97 0 => array('pipe', 'r'),
98 1 => array('pipe', 'w'),
99 2 => array('pipe', 'w'),
100 3 => array('pipe', 'w'), // pipe to write exit code to
102 $pipes = array();
103 $proc = proc_open($command, $pipes_template, $pipes);
104 fwrite($pipes[0], $this->istream);
105 fclose($pipes[0]);
106 while (true) {
107 $read = array($this->_pipes[1]);
108 $except = $write = null;
109 $n = stream_select($read, $write, $except, 30);
110 $stderr = $stdout = '';
111 if ($n === 0) {
112 throw new Git_Exception('Process timed out');
113 } elseif ($n > 0) {
114 $errLine = fread($this->_pipes[2], 8192);
115 $outLine = fread($this->_pipes[1], 8192);
116 $stderr .= $errLine;
117 $stdout .= $outLine;
118 if ($errLine === '' && $outLine === '') {
119 fclose($pipes[1]);
120 fclose($pipes[2]);
121 break;
125 $status = trim(fread($pipes[3], 5));
126 fclose($pipes[3]);
127 $close = proc_close($proc);
128 if (empty($code)) $status = $close;
130 // We want $stderr, $stdout and $status
131 if (!$options['withRawOutput']) {
132 // This feels buggy to me, especially with git cat-file
133 $stdout = rtrim($stdout);
134 $stderr = rtrim($stderr);
136 if ($options['withExceptions'] && $status !== 0) {
137 throw new Git_Exception_Command($command, $status, $stderr);
139 // Trace code omitted
140 if ($options['withExtendedOutput']) {
141 return array($status, $stdout, $stderr);
142 } else {
143 return $stdout;
148 * Transforms an associative array of arguments to command line options.
149 * Arguments can be like 'r' or 'no-commit'.
150 * @note Original Python version kwargs used 'no_commit'
151 * form due to Python conventions. We decided to use a more direct
152 * approach because our associative array allow them.
154 public function transformArgs($kwargs) {
155 $args = array();
156 foreach ($kwargs as $k => $v) {
157 if (strlen($k) == 1) {
158 if ($v === true) $args[] = "-$k";
159 else $args[] = "-$k$v";
160 } else {
161 // $k = dashify($k);
162 if ($v === true) $args[] = "--$k";
163 else $args[] = "--$k=$v";
166 return $args;
170 * Runs a given Git command with the specified arguments and return
171 * the result as a string.
172 * @param $method Name of command, but camelCased instead of dash-ified.
173 * @param $args Array of arguments. There is actually only one argument,
174 * which is an array of associative and numerically indexed
175 * parameters.
176 * @return String output.
178 public function __call($method, $raw_args) {
179 // split out "kwargs" (to be converted to options) from regular
180 // "args" (which get inserted normally)
181 $args = array();
182 $kwargs = array();
183 foreach ($raw_args as $raw) {
184 if (is_array($raw)) $kwargs = $raw; // only one assoc array allowed!
185 else $args[] = $raw;
187 for ($i = 0; isset($kwargs[$i]); $i++) {
188 $args[] = $kwargs[$i];
189 unset($kwargs[$i]);
191 // Grab "special" arguments prior to transformation
192 $executeKwargs = array();
193 foreach ($kwargs as $k => $v) {
194 if (isset(self::$executeKwargs[$k])) {
195 $executeKwargs[$k] = $v;
196 unset($kwargs[$k]);
199 // $args and $kwargs are interesting
200 $opt_args = $this->transformArgs($kwargs);
201 // parse through $args again to determine which ones are actually kwargs
202 $ext_args = $args;
203 // Full arguments
204 $args = array_merge($opt_args, $ext_args);
205 // Convert methodName to method-name (our equivalent of dashify).
206 // This is kind of inefficient.
207 $command = '';
208 for ($i = 0, $max = strlen($method); $i < $max; $i++) {
209 $c = $method[$i];
210 $command .= ctype_upper($c) ? '-' . strtolower($c) : $c;
212 $call = array_merge(array('git', $command), $args);
213 $result = $this->execute($call);
214 return $result;