Add argument escaping.
[phpgit.git] / library / Git.php
blob2d8e5224111e18c804941869984e6480a16d692b
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)) {
71 foreach ($command as &$c) $c = escapeshellarg($c);
72 $command = implode(' ', $command);
74 //var_dump($command);
75 $options = array_merge(array(
76 'withKeepCwd' => false,
77 'withExtendedOutput' => false,
78 'withExceptions' => true,
79 'withRawOutput' => false,
80 ), $options);
81 if ($options['withKeepCwd'] || is_null($this->dir)) {
82 $cwd = getcwd();
83 } else {
84 $cwd = $this->dir;
86 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
87 // Windows dependent code, stolen from PHPT
88 $com = new COM('WScript.Shell');
89 $com->CurrentDirectory = $cwd;
90 $proc = $com->Exec($command);
91 if (!is_null($istream)) $proc->StdIn->Write($istream);
92 $stdout = $proc->StdOut->ReadAll();
93 $stderr = $proc->StdErr->ReadAll();
94 $status = $proc->ExitCode;
95 } else {
96 // untested! Also stolen from PHPT
97 // this seems needlessly complicated
98 $pipes_template = array(
99 0 => array('pipe', 'r'),
100 1 => array('pipe', 'w'),
101 2 => array('pipe', 'w'),
102 3 => array('pipe', 'w'), // pipe to write exit code to
104 $pipes = array();
105 $proc = proc_open($command, $pipes_template, $pipes);
106 // Input
107 fwrite($pipes[0], $istream);
108 fclose($pipes[0]);
109 // Output
110 $stdout = stream_get_contents($pipes[1]);
111 fclose($pipes[1]);
112 // Error
113 $stderr = stream_get_contents($pipes[2]);
114 fclose($pipes[2]);
115 // Status
116 $status = trim(fread($pipes[3], 5));
117 fclose($pipes[3]);
118 // Cleanup
119 $close = proc_close($proc);
120 if (empty($code)) $status = $close;
122 // We want $stderr, $stdout and $status
123 if (!$options['withRawOutput']) {
124 // This feels buggy to me, especially with git cat-file
125 $stdout = rtrim($stdout);
126 $stderr = rtrim($stderr);
128 if ($options['withExceptions'] && $status !== 0) {
129 throw new Git_Exception_Command($command, $status, $stderr);
131 // Trace code omitted
132 if ($options['withExtendedOutput']) {
133 return array($status, $stdout, $stderr);
134 } else {
135 return $stdout;
140 * Transforms an associative array of arguments to command line options.
141 * Arguments can be like 'r' or 'no-commit'.
142 * @note Original Python version kwargs used 'no_commit'
143 * form due to Python conventions. We decided to use a more direct
144 * approach because our associative array allow them.
146 public function transformArgs($kwargs) {
147 $args = array();
148 foreach ($kwargs as $k => $v) {
149 if (strlen($k) == 1) {
150 if ($v === true) $args[] = "-$k";
151 else $args[] = "-$k$v";
152 } else {
153 // $k = dashify($k);
154 if ($v === true) $args[] = "--$k";
155 else $args[] = "--$k=$v";
158 return $args;
162 * Runs a given Git command with the specified arguments and return
163 * the result as a string.
164 * @param $method Name of command, but camelCased instead of dash-ified.
165 * @param $args Array of arguments. There is actually only one argument,
166 * which is an array of associative and numerically indexed
167 * parameters.
168 * @return String output.
170 public function __call($method, $raw_args) {
171 // split out "kwargs" (to be converted to options) from regular
172 // "args" (which get inserted normally)
173 $args = array();
174 $kwargs = array();
175 foreach ($raw_args as $raw) {
176 if (is_array($raw)) $kwargs = $raw; // only one assoc array allowed!
177 else $args[] = $raw;
179 for ($i = 0; isset($kwargs[$i]); $i++) {
180 $args[] = $kwargs[$i];
181 unset($kwargs[$i]);
183 // Grab "special" arguments prior to transformation
184 $executeKwargs = array();
185 foreach ($kwargs as $k => $v) {
186 if (isset(self::$executeKwargs[$k])) {
187 $executeKwargs[$k] = $v;
188 unset($kwargs[$k]);
191 // $args and $kwargs are interesting
192 $opt_args = $this->transformArgs($kwargs);
193 // parse through $args again to determine which ones are actually kwargs
194 $ext_args = $args;
195 // Full arguments
196 $args = array_merge($opt_args, $ext_args);
197 // Convert methodName to method-name (our equivalent of dashify).
198 // This is kind of inefficient.
199 $command = '';
200 for ($i = 0, $max = strlen($method); $i < $max; $i++) {
201 $c = $method[$i];
202 $command .= ctype_upper($c) ? '-' . strtolower($c) : $c;
204 $call = array_merge(array(self::$git, $command), $args);
205 $result = $this->execute($call);
206 return $result;