Make command invoker use the Git::$git variable for the executable.
[phpgit.git] / library / Git.php
blob25f85ad92ab7a8670d52fe9509ed9fd0eeaceb0e
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 //var_dump($command);
72 $options = array_merge(array(
73 'withKeepCwd' => false,
74 'withExtendedOutput' => false,
75 'withExceptions' => true,
76 'withRawOutput' => false,
77 ), $options);
78 if ($options['withKeepCwd'] || is_null($this->dir)) {
79 $cwd = getcwd();
80 } else {
81 $cwd = $this->dir;
83 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
84 // Windows dependent code, stolen from PHPT
85 $com = new COM('WScript.Shell');
86 $com->CurrentDirectory = $cwd;
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 } else {
93 // untested! Also stolen from PHPT
94 // this seems needlessly complicated
95 $pipes_template = array(
96 0 => array('pipe', 'r'),
97 1 => array('pipe', 'w'),
98 2 => array('pipe', 'w'),
99 3 => array('pipe', 'w'), // pipe to write exit code to
101 $pipes = array();
102 $proc = proc_open($command, $pipes_template, $pipes);
103 fwrite($pipes[0], $istream);
104 fclose($pipes[0]);
105 $stderr = $stdout = '';
106 while (true) {
107 $read = array($pipes[1]);
108 $except = $write = null;
109 $n = stream_select($read, $write, $except, 30);
110 if ($n === 0) {
111 throw new Git_Exception('Process timed out');
112 } elseif ($n > 0) {
113 $errLine = fread($pipes[2], 8192);
114 $outLine = fread($pipes[1], 8192);
115 $stderr .= $errLine;
116 $stdout .= $outLine;
117 if ($errLine === '' && $outLine === '') {
118 fclose($pipes[1]);
119 fclose($pipes[2]);
120 break;
124 $status = trim(fread($pipes[3], 5));
125 fclose($pipes[3]);
126 $close = proc_close($proc);
127 if (empty($code)) $status = $close;
129 // We want $stderr, $stdout and $status
130 if (!$options['withRawOutput']) {
131 // This feels buggy to me, especially with git cat-file
132 $stdout = rtrim($stdout);
133 $stderr = rtrim($stderr);
135 if ($options['withExceptions'] && $status !== 0) {
136 throw new Git_Exception_Command($command, $status, $stderr);
138 // Trace code omitted
139 if ($options['withExtendedOutput']) {
140 return array($status, $stdout, $stderr);
141 } else {
142 return $stdout;
147 * Transforms an associative array of arguments to command line options.
148 * Arguments can be like 'r' or 'no-commit'.
149 * @note Original Python version kwargs used 'no_commit'
150 * form due to Python conventions. We decided to use a more direct
151 * approach because our associative array allow them.
153 public function transformArgs($kwargs) {
154 $args = array();
155 foreach ($kwargs as $k => $v) {
156 if (strlen($k) == 1) {
157 if ($v === true) $args[] = "-$k";
158 else $args[] = "-$k$v";
159 } else {
160 // $k = dashify($k);
161 if ($v === true) $args[] = "--$k";
162 else $args[] = "--$k=$v";
165 return $args;
169 * Runs a given Git command with the specified arguments and return
170 * the result as a string.
171 * @param $method Name of command, but camelCased instead of dash-ified.
172 * @param $args Array of arguments. There is actually only one argument,
173 * which is an array of associative and numerically indexed
174 * parameters.
175 * @return String output.
177 public function __call($method, $raw_args) {
178 // split out "kwargs" (to be converted to options) from regular
179 // "args" (which get inserted normally)
180 $args = array();
181 $kwargs = array();
182 foreach ($raw_args as $raw) {
183 if (is_array($raw)) $kwargs = $raw; // only one assoc array allowed!
184 else $args[] = $raw;
186 for ($i = 0; isset($kwargs[$i]); $i++) {
187 $args[] = $kwargs[$i];
188 unset($kwargs[$i]);
190 // Grab "special" arguments prior to transformation
191 $executeKwargs = array();
192 foreach ($kwargs as $k => $v) {
193 if (isset(self::$executeKwargs[$k])) {
194 $executeKwargs[$k] = $v;
195 unset($kwargs[$k]);
198 // $args and $kwargs are interesting
199 $opt_args = $this->transformArgs($kwargs);
200 // parse through $args again to determine which ones are actually kwargs
201 $ext_args = $args;
202 // Full arguments
203 $args = array_merge($opt_args, $ext_args);
204 // Convert methodName to method-name (our equivalent of dashify).
205 // This is kind of inefficient.
206 $command = '';
207 for ($i = 0, $max = strlen($method); $i < $max; $i++) {
208 $c = $method[$i];
209 $command .= ctype_upper($c) ? '-' . strtolower($c) : $c;
211 $call = array_merge(array(self::$git, $command), $args);
212 $result = $this->execute($call);
213 return $result;