Improve command line code to pull everything at once.
[phpgit.git] / library / Git.php
blobac91fa53c97ee0b2ecc2533c9b412031672a7f43
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 // Input
104 fwrite($pipes[0], $istream);
105 fclose($pipes[0]);
106 // Output
107 $stdout = stream_get_contents($pipes[1]);
108 fclose($pipes[1]);
109 // Error
110 $stderr = stream_get_contents($pipes[2]);
111 fclose($pipes[2]);
112 // Status
113 $status = trim(fread($pipes[3], 5));
114 fclose($pipes[3]);
115 // Cleanup
116 $close = proc_close($proc);
117 if (empty($code)) $status = $close;
119 // We want $stderr, $stdout and $status
120 if (!$options['withRawOutput']) {
121 // This feels buggy to me, especially with git cat-file
122 $stdout = rtrim($stdout);
123 $stderr = rtrim($stderr);
125 if ($options['withExceptions'] && $status !== 0) {
126 throw new Git_Exception_Command($command, $status, $stderr);
128 // Trace code omitted
129 if ($options['withExtendedOutput']) {
130 return array($status, $stdout, $stderr);
131 } else {
132 return $stdout;
137 * Transforms an associative array of arguments to command line options.
138 * Arguments can be like 'r' or 'no-commit'.
139 * @note Original Python version kwargs used 'no_commit'
140 * form due to Python conventions. We decided to use a more direct
141 * approach because our associative array allow them.
143 public function transformArgs($kwargs) {
144 $args = array();
145 foreach ($kwargs as $k => $v) {
146 if (strlen($k) == 1) {
147 if ($v === true) $args[] = "-$k";
148 else $args[] = "-$k$v";
149 } else {
150 // $k = dashify($k);
151 if ($v === true) $args[] = "--$k";
152 else $args[] = "--$k=$v";
155 return $args;
159 * Runs a given Git command with the specified arguments and return
160 * the result as a string.
161 * @param $method Name of command, but camelCased instead of dash-ified.
162 * @param $args Array of arguments. There is actually only one argument,
163 * which is an array of associative and numerically indexed
164 * parameters.
165 * @return String output.
167 public function __call($method, $raw_args) {
168 // split out "kwargs" (to be converted to options) from regular
169 // "args" (which get inserted normally)
170 $args = array();
171 $kwargs = array();
172 foreach ($raw_args as $raw) {
173 if (is_array($raw)) $kwargs = $raw; // only one assoc array allowed!
174 else $args[] = $raw;
176 for ($i = 0; isset($kwargs[$i]); $i++) {
177 $args[] = $kwargs[$i];
178 unset($kwargs[$i]);
180 // Grab "special" arguments prior to transformation
181 $executeKwargs = array();
182 foreach ($kwargs as $k => $v) {
183 if (isset(self::$executeKwargs[$k])) {
184 $executeKwargs[$k] = $v;
185 unset($kwargs[$k]);
188 // $args and $kwargs are interesting
189 $opt_args = $this->transformArgs($kwargs);
190 // parse through $args again to determine which ones are actually kwargs
191 $ext_args = $args;
192 // Full arguments
193 $args = array_merge($opt_args, $ext_args);
194 // Convert methodName to method-name (our equivalent of dashify).
195 // This is kind of inefficient.
196 $command = '';
197 for ($i = 0, $max = strlen($method); $i < $max; $i++) {
198 $c = $method[$i];
199 $command .= ctype_upper($c) ? '-' . strtolower($c) : $c;
201 $call = array_merge(array(self::$git, $command), $args);
202 $result = $this->execute($call);
203 return $result;