Clean up git object docblocks
[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
@@ -54,11 +54,6 @@
  * git for-each-ref constant
  */
 define('GIT_FOR_EACH_REF','for-each-ref');
-
-/**
- * git config constant
- */
-define('GIT_CONFIG','config');
 
 /**
  * Class to wrap git executable
@@ -68,23 +63,16 @@
  * @package GitPHP
  * @subpackage Git
  */
-class GitPHP_GitExe
+class GitPHP_GitExe implements GitPHP_Observable_Interface
 {
 
 	/**
-	 * The singleton instance
-	 *
-	 * @var GitPHP_GitExe
-	 */
-	protected static $instance;
-
-	/**
 	 * The binary path
 	 *
 	 * @var string
 	 */
 	protected $binary;
-	
+
 	/**
 	 * The binary version
 	 *
@@ -100,32 +88,60 @@
 	protected $versionRead = false;
 
 	/**
-	 * Returns the singleton instance
-	 *
-	 * @return GitPHP_GitExe instance of git exe classe
-	 */
-	public static function GetInstance()
-	{
-		if (!self::$instance) {
-			self::$instance = new GitPHP_GitExe(GitPHP_Config::GetInstance()->GetValue('gitbin'));
-		}
-		return self::$instance;
-	}
-
-	/**
-	 * Releases the singleton instance
-	 */
-	public static function DestroyInstance()
-	{
-		self::$instance = null;
-	}
+	 * 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;
+
+	/**
+	 * Whether the proc_open function is allowed by the install
+	 *
+	 * @var null|boolean
+	 */
+	protected $procOpenAllowed = null;
+
+	/**
+	 * Whether or not caching function GetProcess is initialized
+	 *
+	 * @var null|boolean
+	 */
+	protected $getProcessInitialized = false;
+
+	/**
+	 * Processes spawned for batch object fetching
+	 *
+	 * @var array
+	 */
+	protected static $processes = array();
 
 	/**
 	 * Constructor
 	 *
 	 * @param string $binary path to git binary
 	 */
-	protected function __construct($binary)
+	public function __construct($binary = '')
 	{
 		if (empty($binary)) {
 			$binary = GitPHP_GitExe::DefaultBinary();
@@ -143,16 +159,129 @@
 	 */
 	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);
 
-		GitPHP_DebugLog::GetInstance()->Log('Begin executing "' . $fullCommand . '"');
+		$this->Log('Execute', '', 'start');
 
 		$ret = shell_exec($fullCommand);
 
-		GitPHP_DebugLog::GetInstance()->Log('Finish executing "' . $fullCommand . '"' .
-			"\nwith result: " . $ret);
+		$this->Log('Execute', $fullCommand . "\n\n" . $ret, 'stop');
 
 		return $ret;
+	}
+
+	protected function GetProcess($projectPath)
+	{
+		if (!$this->getProcessInitialized) {
+			register_shutdown_function(array($this, 'DestroyAllProcesses'));
+			$this->getProcessInitialized = true;
+		}
+
+		if (!isset(self::$processes[$projectPath])) {
+			GitPHP_DebugLog::GetInstance()->TimerStart();
+
+			$process = proc_open(
+				$cmd = $this->CreateCommand($projectPath, GIT_CAT_FILE, array('--batch')),
+				array(
+					0 => array('pipe', 'r'),
+					1 => array('pipe', 'w'),
+					2 => array('file', GitPHP_Util::NullFile(), 'w'),
+				),
+				$pipes
+			);
+
+			self::$processes[$projectPath] = array(
+				'process' => $process,
+				'pipes'   => $pipes,
+			);
+
+			GitPHP_DebugLog::GetInstance()->TimerStop('proc_open', $cmd);
+		}
+
+		return self::$processes[$projectPath];
+	}
+
+	public function DestroyAllProcesses()
+	{
+		foreach (self::$processes as $projectPath => $process) {
+			$this->DestroyProcess($projectPath);
+		}
+	}
+
+	protected function DestroyProcess($projectPath)
+	{
+		$pipes = self::$processes[$projectPath]['pipes'];
+		foreach ($pipes as $pipe) {
+			fclose($pipe);
+		}
+		$process = self::$processes[$projectPath]['process'];
+		proc_terminate($process);
+		proc_close($process);
+		unset(self::$processes[$projectPath]);
+	}
+
+	public function GetObjectData($projectPath, $hash)
+	{
+		if ($this->procOpenAllowed === null) {
+			$this->procOpenAllowed = GitPHP_Util::FunctionAllowed('proc_open');
+			if (!$this->procOpenAllowed) {
+				throw new GitPHP_DisabledFunctionException('proc_open');
+			}
+		}
+
+		$process = $this->GetProcess($projectPath);
+		$pipes = $process['pipes'];
+
+		$data = $hash . "\n";
+		if (fwrite($pipes[0], $data) !== mb_orig_strlen($data)) {
+			$this->DestroyProcess($projectPath);
+			return false;
+		}
+		fflush($pipes[0]);
+
+		$ln = rtrim(fgets($pipes[1]));
+		if (!$ln) {
+			$this->DestroyProcess($projectPath);
+			return false;
+		}
+
+		$parts = explode(" ", rtrim($ln));
+		if (count($parts) == 2 && $parts[1] == 'missing') {
+			return false;
+		} else if (count($parts) != 3) {
+			$this->DestroyProcess($projectPath);
+			return false;
+		}
+
+		list($hash, $type, $n) = $parts;
+
+		$contents = '';
+		while (mb_orig_strlen($contents) < $n) {
+			$buf = fread($pipes[1], min(4096, $n - mb_orig_strlen($contents)));
+			if ($buf === false) {
+				$this->DestroyProcess($projectPath);
+				return false;
+			}
+			$contents .= $buf;
+		}
+
+		if (fgetc($pipes[1]) != "\n") {
+			$this->DestroyProcess($projectPath);
+			return false;
+		}
+
+		return array(
+			'hash' => $hash,
+			'contents' => $contents,
+			'type' => $type,
+		);
 	}
 
 	/**
@@ -166,6 +295,13 @@
 	 */
 	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);
@@ -183,9 +319,9 @@
 	{
 		$gitDir = '';
 		if (!empty($projectPath)) {
-			$gitDir = '--git-dir=' . $projectPath;
-		}
-		
+			$gitDir = '--git-dir=' . escapeshellarg($projectPath);
+		}
+
 		return $this->binary . ' ' . $gitDir . ' ' . $command . ' ' . implode(' ', $args);
 	}
 
@@ -217,6 +353,13 @@
 	 */
 	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 = '';
@@ -301,13 +444,71 @@
 	 */
 	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);
+		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
+	 * @param string $msg_data message
+	 * @param string $type message
+	 */
+	private function Log($message, $msg_data, $type)
+	{
+		if (empty($message))
+			return;
+
+		foreach ($this->observers as $observer) {
+			$observer->ObjectChanged($this, GitPHP_Observer_Interface::LoggableChange, array($message, $msg_data, $type));
+		}
 	}
 
 	/**

comments