Try to guess the right binary on a Windows x64 system
[gitphp.git] / include / git / GitExe.class.php
blob:a/include/git/GitExe.class.php -> blob:b/include/git/GitExe.class.php
--- a/include/git/GitExe.class.php
+++ b/include/git/GitExe.class.php
@@ -1,7 +1,61 @@
 <?php
-/**
- * GitPHP GitExe
- *
+
+/**
+ * git cat-file constant
+ */
+define('GIT_CAT_FILE','cat-file');
+
+/**
+ * git diff-tree constant
+ */
+define('GIT_DIFF_TREE','diff-tree');
+
+/**
+ * git ls-tree constant
+ */
+define('GIT_LS_TREE','ls-tree');
+
+/**
+ * git rev-list constant
+ */
+define('GIT_REV_LIST','rev-list');
+
+/**
+ * git rev-parse constant
+ */
+define('GIT_REV_PARSE','rev-parse');
+
+/**
+ * git show-ref constant
+ */
+define('GIT_SHOW_REF','show-ref');
+
+/**
+ * git archive constant
+ */
+define('GIT_ARCHIVE','archive');
+
+/**
+ * git grep constant
+ */
+define('GIT_GREP','grep');
+
+/**
+ * git blame constant
+ */
+define('GIT_BLAME','blame');
+
+/**
+ * git name-rev constant
+ */
+define('GIT_NAME_REV','name-rev');
+
+/**
+ * git for-each-ref constant
+ */
+define('GIT_FOR_EACH_REF','for-each-ref');
+
+/**
  * Class to wrap git executable
  *
  * @author Christopher Han <xiphux@gmail.com>
@@ -9,116 +63,145 @@
  * @package GitPHP
  * @subpackage Git
  */
-
-/**
- * Constants for git commands
- */
-define('GIT_CAT_FILE','cat-file');
-define('GIT_DIFF_TREE','diff-tree');
-define('GIT_LS_TREE','ls-tree');
-define('GIT_REV_LIST','rev-list');
-define('GIT_REV_PARSE','rev-parse');
-define('GIT_SHOW_REF','show-ref');
-define('GIT_ARCHIVE','archive');
-define('GIT_GREP','grep');
-define('GIT_BLAME','blame');
-define('GIT_NAME_REV','name-rev');
-define('GIT_FOR_EACH_REF','for-each-ref');
-define('GIT_CONFIG','config');
-
-/**
- * Git Executable class
- *
- * @package GitPHP
- * @subpackage Git
- */
-class GitPHP_GitExe
+class GitPHP_GitExe implements GitPHP_Observable_Interface
 {
-	/**
-	 * project
-	 *
-	 * Stores the project internally
-	 *
-	 * @access protected
-	 */
-	protected $project;
-
-	/**
-	 * bin
-	 *
-	 * Stores the binary path internally
-	 *
-	 * @access protected
+
+	/**
+	 * The binary path
+	 *
+	 * @var string
 	 */
 	protected $binary;
-
-	/**
-	 * __construct
-	 *
+	
+	/**
+	 * The binary version
+	 *
+	 * @var string
+	 */
+	protected $version;
+
+	/**
+	 * Whether the version has been read
+	 *
+	 * @var boolean
+	 */
+	protected $versionRead = false;
+
+	/**
+	 * Observers
+	 *
+	 * @var GitPHP_Observer_Interface[]
+	 */
+	protected $observers = array();
+
+	/**
+	 * Whether the exec function is allowed by the install
+	 *
+	 * @var null|boolean
+	 */
+	protected $execAllowed = null;
+
+	/**
+	 * Whether the shell_exec function is allowed by the install
+	 *
+	 * @var null|boolean
+	 */
+	protected $shellExecAllowed = null;
+
+	/**
+	 * Whether the popen function is allowed by the install
+	 *
+	 * @var null|boolean
+	 */
+	protected $popenAllowed = null;
+
+	/**
 	 * Constructor
 	 *
 	 * @param string $binary path to git binary
-	 * @param mixed $project project to operate on
-	 * @return mixed git executable class
-	 */
-	public function __construct($project = null)
-	{
-		$binary = GitPHP_Config::GetInstance()->GetValue('gitbin');
+	 */
+	public function __construct($binary = '')
+	{
 		if (empty($binary)) {
-			$this->binary = GitPHP_GitExe::DefaultBinary();
-		} else {
-			$this->binary = $binary;
-		}
-
-		$this->SetProject($project);
-	}
-
-	/**
-	 * SetProject
-	 *
-	 * Sets the project for this executable
-	 *
-	 * @param mixed $project project to set
-	 */
-	public function SetProject($project = null)
-	{
-		$this->project = $project;
-	}
-
-	/**
-	 * Execute
-	 *
+			$binary = GitPHP_GitExe::DefaultBinary();
+		}
+		$this->binary = $binary;
+	}
+
+	/**
 	 * Executes a command
 	 *
+	 * @param string $projectPath path to project
 	 * @param string $command the command to execute
-	 * @param array $args arguments
+	 * @param string[] $args arguments
 	 * @return string result of command
 	 */
-	public function Execute($command, $args)
+	public function Execute($projectPath, $command, $args)
+	{
+		if ($this->shellExecAllowed === null) {
+			$this->shellExecAllowed = GitPHP_Util::FunctionAllowed('shell_exec');
+			if (!$this->shellExecAllowed) {
+				throw new GitPHP_DisabledFunctionException('shell_exec');
+			}
+		}
+
+		$fullCommand = $this->CreateCommand($projectPath, $command, $args);
+
+		$this->Log('Begin executing "' . $fullCommand . '"');
+
+		$ret = shell_exec($fullCommand);
+
+		$this->Log('Finish executing "' . $fullCommand . '"' .
+			"\nwith result: " . $ret);
+
+		return $ret;
+	}
+
+	/**
+	 * Opens a resource to a command
+	 *
+	 * @param string $projectPath path to project
+	 * @param string $command the command to execute
+	 * @param string[] $args arguments
+	 * @param string $mode process open mode
+	 * @return resource process handle
+	 */
+	public function Open($projectPath, $command, $args, $mode = 'r')
+	{
+		if ($this->popenAllowed === null) {
+			$this->popenAllowed = GitPHP_Util::FunctionAllowed('popen');
+			if (!$this->popenAllowed) {
+				throw new GitPHP_DisabledFunctionException('popen');
+			}
+		}
+
+		$fullCommand = $this->CreateCommand($projectPath, $command, $args);
+
+		return popen($fullCommand, $mode);
+	}
+
+	/**
+	 * Creates a command
+	 *
+	 * @param string $projectPath path to project
+	 * @param string $command the command to execute
+	 * @param string[] $args arguments
+	 * @return string full executable string
+	 */
+	protected function CreateCommand($projectPath, $command, $args)
 	{
 		$gitDir = '';
-		if ($this->project) {
-			$gitDir = '--git-dir=' . $this->project->GetPath();
+		if (!empty($projectPath)) {
+			$gitDir = '--git-dir=' . escapeshellarg($projectPath);
 		}
 		
-		$fullCommand = $this->binary . ' ' . $gitDir . ' ' . $command . ' ' . implode(' ', $args);
-
-		GitPHP_Log::GetInstance()->Log('Begin executing "' . $fullCommand . '"');
-
-		$ret = shell_exec($fullCommand);
-
-		GitPHP_Log::GetInstance()->Log('Finish executing "' . $fullCommand . '"');
-
-		return $ret;
-	}
-
-	/**
-	 * GetBinary
-	 *
+		return $this->binary . ' ' . $gitDir . ' ' . $command . ' ' . implode(' ', $args);
+	}
+
+	/**
 	 * Gets the binary for this executable
 	 *
 	 * @return string binary
-	 * @access public
 	 */
 	public function GetBinary()
 	{
@@ -126,29 +209,44 @@
 	}
 
 	/**
-	 * GetVersion
-	 *
 	 * Gets the version of the git binary
 	 *
 	 * @return string version
-	 * @access public
 	 */
 	public function GetVersion()
 	{
+		if (!$this->versionRead)
+			$this->ReadVersion();
+
+		return $this->version;
+	}
+
+	/**
+	 * Reads the git version
+	 */
+	protected function ReadVersion()
+	{
+		if ($this->shellExecAllowed === null) {
+			$this->shellExecAllowed = GitPHP_Util::FunctionAllowed('shell_exec');
+			if (!$this->shellExecAllowed) {
+				throw new GitPHP_DisabledFunctionException('shell_exec');
+			}
+		}
+
+		$this->versionRead = true;
+
+		$this->version = '';
+
 		$versionCommand = $this->binary . ' --version';
 		$ret = trim(shell_exec($versionCommand));
 		if (preg_match('/^git version ([0-9\.]+)$/i', $ret, $regs)) {
-			return $regs[1];
-		}
-		return '';
-	}
-
-	/**
-	 * CanSkip
-	 *
+			$this->version = $regs[1];
+		}
+	}
+
+	/**
 	 * Tests if this version of git can skip through the revision list
 	 *
-	 * @access public
 	 * @return boolean true if we can skip
 	 */
 	public function CanSkip()
@@ -167,11 +265,8 @@
 	}
 
 	/**
-	 * CanShowSizeInTree
-	 *
 	 * Tests if this version of git can show the size of a blob when listing a tree
 	 *
-	 * @access public
 	 * @return true if we can show sizes
 	 */
 	public function CanShowSizeInTree()
@@ -194,11 +289,8 @@
 	}
 
 	/**
-	 * CanIgnoreRegexpCase
-	 *
 	 * Tests if this version of git has the regexp tuning option to ignore regexp case
 	 *
-	 * @access public
 	 * @return true if we can ignore regexp case
 	 */
 	public function CanIgnoreRegexpCase()
@@ -219,21 +311,88 @@
 	}
 
 	/**
-	 * DefaultBinary
-	 *
+	 * Tests if this executable is valid
+	 *
+	 * @return boolean true if valid
+	 */
+	public function Valid()
+	{
+		if ($this->execAllowed === null) {
+			$this->execAllowed = GitPHP_Util::FunctionAllowed('exec');
+			if (!$this->execAllowed) {
+				throw new GitPHP_DisabledFunctionException('exec');
+			}
+		}
+
+		if (empty($this->binary))
+			return false;
+
+		$code = 0;
+		$out = exec($this->binary . ' --version', $tmp, $code);
+
+		return $code == 0;
+	}
+
+	/**
+	 * Add a new observer
+	 *
+	 * @param GitPHP_Observer_Interface $observer observer
+	 */
+	public function AddObserver($observer)
+	{
+		if (!$observer)
+			return;
+
+		if (array_search($observer, $this->observers) !== false)
+			return;
+
+		$this->observers[] = $observer;
+	}
+
+	/**
+	 * Remove an observer
+	 *
+	 * @param GitPHP_Observer_Interface $observer observer
+	 */
+	public function RemoveObserver($observer)
+	{
+		if (!$observer)
+			return;
+
+		$key = array_search($observer, $this->observers);
+
+		if ($key === false)
+			return;
+
+		unset($this->observers[$key]);
+	}
+
+	/**
+	 * Log an execution
+	 *
+	 * @param string $message message
+	 */
+	private function Log($message)
+	{
+		if (empty($message))
+			return;
+
+		foreach ($this->observers as $observer) {
+			$observer->ObjectChanged($this, GitPHP_Observer_Interface::LoggableChange, array($message));
+		}
+	}
+
+	/**
 	 * Gets the default binary for the platform
 	 *
-	 * @access public
-	 * @static
 	 * @return string binary
 	 */
 	public static function DefaultBinary()
 	{
-		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+		if (GitPHP_Util::IsWindows()) {
 			// windows
 
-			$arch = php_uname('m');
-			if (strpos($arch, '64') !== false) {
+			if (GitPHP_Util::Is64Bit()) {
 				// match x86_64 and x64 (64 bit)
 				// C:\Program Files (x86)\Git\bin\git.exe
 				return 'C:\\Progra~2\\Git\\bin\\git.exe';

comments